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.

622 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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->closeDocumentAsync (getContentComponent(), true, nullptr);
  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. for (int i = components.size(); --i >= 0;)
  80. if (auto* component = components[i])
  81. closeDocumentInternal (component);
  82. }
  83. //==============================================================================
  84. namespace MultiDocHelpers
  85. {
  86. static bool shouldDeleteComp (Component* const c)
  87. {
  88. return c->getProperties() ["mdiDocumentDelete_"];
  89. }
  90. }
  91. #if JUCE_MODAL_LOOPS_PERMITTED
  92. bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
  93. {
  94. while (! components.isEmpty())
  95. if (! closeDocument (components.getLast(), checkItsOkToCloseFirst))
  96. return false;
  97. return true;
  98. }
  99. #endif
  100. void MultiDocumentPanel::closeLastDocumentRecursive (SafePointer<MultiDocumentPanel> parent,
  101. bool checkItsOkToCloseFirst,
  102. std::function<void (bool)> callback)
  103. {
  104. if (parent->components.isEmpty())
  105. {
  106. if (callback != nullptr)
  107. callback (true);
  108. return;
  109. }
  110. parent->closeDocumentAsync (parent->components.getLast(),
  111. checkItsOkToCloseFirst,
  112. [parent, checkItsOkToCloseFirst, callback] (bool closeResult)
  113. {
  114. if (parent == nullptr)
  115. return;
  116. if (! closeResult)
  117. {
  118. if (callback != nullptr)
  119. callback (false);
  120. return;
  121. }
  122. parent->closeLastDocumentRecursive (parent, checkItsOkToCloseFirst, std::move (callback));
  123. });
  124. }
  125. void MultiDocumentPanel::closeAllDocumentsAsync (bool checkItsOkToCloseFirst, std::function<void (bool)> callback)
  126. {
  127. closeLastDocumentRecursive (this, checkItsOkToCloseFirst, std::move (callback));
  128. }
  129. #if JUCE_MODAL_LOOPS_PERMITTED
  130. bool MultiDocumentPanel::tryToCloseDocument (Component*)
  131. {
  132. // If you hit this assertion then you need to implement this method in a subclass.
  133. jassertfalse;
  134. return false;
  135. }
  136. #endif
  137. MultiDocumentPanelWindow* MultiDocumentPanel::createNewDocumentWindow()
  138. {
  139. return new MultiDocumentPanelWindow (backgroundColour);
  140. }
  141. void MultiDocumentPanel::addWindow (Component* component)
  142. {
  143. auto* dw = createNewDocumentWindow();
  144. dw->setResizable (true, false);
  145. dw->setContentNonOwned (component, true);
  146. dw->setName (component->getName());
  147. auto bkg = component->getProperties() ["mdiDocumentBkg_"];
  148. dw->setBackgroundColour (bkg.isVoid() ? backgroundColour : Colour ((uint32) static_cast<int> (bkg)));
  149. int x = 4;
  150. if (auto* topComp = getChildren().getLast())
  151. if (topComp->getX() == x && topComp->getY() == x)
  152. x += 16;
  153. dw->setTopLeftPosition (x, x);
  154. auto pos = component->getProperties() ["mdiDocumentPos_"];
  155. if (pos.toString().isNotEmpty())
  156. dw->restoreWindowStateFromString (pos.toString());
  157. addAndMakeVisible (dw);
  158. dw->toFront (true);
  159. }
  160. bool MultiDocumentPanel::addDocument (Component* const component,
  161. Colour docColour,
  162. const bool deleteWhenRemoved)
  163. {
  164. // If you try passing a full DocumentWindow or ResizableWindow in here, you'll end up
  165. // with a frame-within-a-frame! Just pass in the bare content component.
  166. jassert (dynamic_cast<ResizableWindow*> (component) == nullptr);
  167. if (component == nullptr || (maximumNumDocuments > 0 && components.size() >= maximumNumDocuments))
  168. return false;
  169. components.add (component);
  170. component->getProperties().set ("mdiDocumentDelete_", deleteWhenRemoved);
  171. component->getProperties().set ("mdiDocumentBkg_", (int) docColour.getARGB());
  172. component->addComponentListener (this);
  173. if (mode == FloatingWindows)
  174. {
  175. if (isFullscreenWhenOneDocument())
  176. {
  177. if (components.size() == 1)
  178. {
  179. addAndMakeVisible (component);
  180. }
  181. else
  182. {
  183. if (components.size() == 2)
  184. addWindow (components.getFirst());
  185. addWindow (component);
  186. }
  187. }
  188. else
  189. {
  190. addWindow (component);
  191. }
  192. }
  193. else
  194. {
  195. if (tabComponent == nullptr && components.size() > numDocsBeforeTabsUsed)
  196. {
  197. tabComponent.reset (new TabbedComponentInternal());
  198. addAndMakeVisible (tabComponent.get());
  199. auto temp = components;
  200. for (auto& c : temp)
  201. tabComponent->addTab (c->getName(), docColour, c, false);
  202. resized();
  203. }
  204. else
  205. {
  206. if (tabComponent != nullptr)
  207. tabComponent->addTab (component->getName(), docColour, component, false);
  208. else
  209. addAndMakeVisible (component);
  210. }
  211. setActiveDocument (component);
  212. }
  213. resized();
  214. activeDocumentChanged();
  215. return true;
  216. }
  217. void MultiDocumentPanel::closeDocumentInternal (Component* component)
  218. {
  219. // Intellisense warns about component being uninitialised.
  220. // I'm not sure how a function argument could be uninitialised.
  221. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
  222. component->removeComponentListener (this);
  223. const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component);
  224. component->getProperties().remove ("mdiDocumentDelete_");
  225. component->getProperties().remove ("mdiDocumentBkg_");
  226. if (mode == FloatingWindows)
  227. {
  228. for (auto* child : getChildren())
  229. {
  230. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  231. {
  232. if (dw->getContentComponent() == component)
  233. {
  234. std::unique_ptr<MultiDocumentPanelWindow> (dw)->clearContentComponent();
  235. break;
  236. }
  237. }
  238. }
  239. if (shouldDelete)
  240. delete component;
  241. components.removeFirstMatchingValue (component);
  242. if (isFullscreenWhenOneDocument() && components.size() == 1)
  243. {
  244. for (int i = getNumChildComponents(); --i >= 0;)
  245. {
  246. std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
  247. if (dw != nullptr)
  248. dw->clearContentComponent();
  249. }
  250. addAndMakeVisible (components.getFirst());
  251. }
  252. }
  253. else
  254. {
  255. jassert (components.indexOf (component) >= 0);
  256. if (tabComponent != nullptr)
  257. {
  258. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  259. if (tabComponent->getTabContentComponent (i) == component)
  260. tabComponent->removeTab (i);
  261. }
  262. else
  263. {
  264. removeChildComponent (component);
  265. }
  266. if (shouldDelete)
  267. delete component;
  268. if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed)
  269. tabComponent.reset();
  270. components.removeFirstMatchingValue (component);
  271. if (components.size() > 0 && tabComponent == nullptr)
  272. addAndMakeVisible (components.getFirst());
  273. }
  274. resized();
  275. // This ensures that the active tab is painted properly when a tab is closed!
  276. if (auto* activeComponent = getActiveDocument())
  277. setActiveDocument (activeComponent);
  278. activeDocumentChanged();
  279. JUCE_END_IGNORE_WARNINGS_MSVC
  280. }
  281. #if JUCE_MODAL_LOOPS_PERMITTED
  282. bool MultiDocumentPanel::closeDocument (Component* component,
  283. const bool checkItsOkToCloseFirst)
  284. {
  285. // Intellisense warns about component being uninitialised.
  286. // I'm not sure how a function argument could be uninitialised.
  287. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
  288. if (component == nullptr)
  289. return true;
  290. if (components.contains (component))
  291. {
  292. if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
  293. return false;
  294. closeDocumentInternal (component);
  295. }
  296. else
  297. {
  298. jassertfalse;
  299. }
  300. return true;
  301. JUCE_END_IGNORE_WARNINGS_MSVC
  302. }
  303. #endif
  304. void MultiDocumentPanel::closeDocumentAsync (Component* component,
  305. const bool checkItsOkToCloseFirst,
  306. std::function<void (bool)> callback)
  307. {
  308. // Intellisense warns about component being uninitialised.
  309. // I'm not sure how a function argument could be uninitialised.
  310. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
  311. if (component == nullptr)
  312. {
  313. if (callback != nullptr)
  314. callback (true);
  315. return;
  316. }
  317. if (components.contains (component))
  318. {
  319. if (checkItsOkToCloseFirst)
  320. {
  321. tryToCloseDocumentAsync (component,
  322. [parent = SafePointer<MultiDocumentPanel> { this }, component, callback] (bool closedSuccessfully)
  323. {
  324. if (parent == nullptr)
  325. return;
  326. if (closedSuccessfully)
  327. parent->closeDocumentInternal (component);
  328. if (callback != nullptr)
  329. callback (closedSuccessfully);
  330. });
  331. return;
  332. }
  333. closeDocumentInternal (component);
  334. }
  335. else
  336. {
  337. jassertfalse;
  338. }
  339. if (callback != nullptr)
  340. callback (true);
  341. JUCE_END_IGNORE_WARNINGS_MSVC
  342. }
  343. int MultiDocumentPanel::getNumDocuments() const noexcept
  344. {
  345. return components.size();
  346. }
  347. Component* MultiDocumentPanel::getDocument (const int index) const noexcept
  348. {
  349. return components [index];
  350. }
  351. Component* MultiDocumentPanel::getActiveDocument() const noexcept
  352. {
  353. if (mode == FloatingWindows)
  354. {
  355. for (auto* child : getChildren())
  356. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  357. if (dw->isActiveWindow())
  358. return dw->getContentComponent();
  359. }
  360. return components.getLast();
  361. }
  362. void MultiDocumentPanel::setActiveDocument (Component* component)
  363. {
  364. jassert (component != nullptr);
  365. if (mode == FloatingWindows)
  366. {
  367. component = getContainerComp (component);
  368. if (component != nullptr)
  369. component->toFront (true);
  370. }
  371. else if (tabComponent != nullptr)
  372. {
  373. jassert (components.indexOf (component) >= 0);
  374. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  375. {
  376. if (tabComponent->getTabContentComponent (i) == component)
  377. {
  378. tabComponent->setCurrentTabIndex (i);
  379. break;
  380. }
  381. }
  382. }
  383. else
  384. {
  385. component->grabKeyboardFocus();
  386. }
  387. }
  388. void MultiDocumentPanel::activeDocumentChanged()
  389. {
  390. }
  391. void MultiDocumentPanel::setMaximumNumDocuments (const int newNumber)
  392. {
  393. maximumNumDocuments = newNumber;
  394. }
  395. void MultiDocumentPanel::useFullscreenWhenOneDocument (const bool shouldUseTabs)
  396. {
  397. numDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0;
  398. }
  399. bool MultiDocumentPanel::isFullscreenWhenOneDocument() const noexcept
  400. {
  401. return numDocsBeforeTabsUsed != 0;
  402. }
  403. //==============================================================================
  404. void MultiDocumentPanel::setLayoutMode (const LayoutMode newLayoutMode)
  405. {
  406. if (mode != newLayoutMode)
  407. {
  408. mode = newLayoutMode;
  409. if (mode == FloatingWindows)
  410. {
  411. tabComponent.reset();
  412. }
  413. else
  414. {
  415. for (int i = getNumChildComponents(); --i >= 0;)
  416. {
  417. std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
  418. if (dw != nullptr)
  419. {
  420. dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString());
  421. dw->clearContentComponent();
  422. }
  423. }
  424. }
  425. resized();
  426. auto tempComps = components;
  427. components.clear();
  428. for (auto* c : tempComps)
  429. addDocument (c,
  430. Colour ((uint32) static_cast<int> (c->getProperties().getWithDefault ("mdiDocumentBkg_", (int) Colours::white.getARGB()))),
  431. MultiDocHelpers::shouldDeleteComp (c));
  432. }
  433. }
  434. void MultiDocumentPanel::setBackgroundColour (Colour newBackgroundColour)
  435. {
  436. if (backgroundColour != newBackgroundColour)
  437. {
  438. backgroundColour = newBackgroundColour;
  439. setOpaque (newBackgroundColour.isOpaque());
  440. repaint();
  441. }
  442. }
  443. //==============================================================================
  444. void MultiDocumentPanel::paint (Graphics& g)
  445. {
  446. g.fillAll (backgroundColour);
  447. }
  448. void MultiDocumentPanel::resized()
  449. {
  450. if (mode == MaximisedWindowsWithTabs || components.size() == numDocsBeforeTabsUsed)
  451. {
  452. for (auto* child : getChildren())
  453. child->setBounds (getLocalBounds());
  454. }
  455. setWantsKeyboardFocus (components.size() == 0);
  456. }
  457. Component* MultiDocumentPanel::getContainerComp (Component* c) const
  458. {
  459. if (mode == FloatingWindows)
  460. {
  461. for (auto* child : getChildren())
  462. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  463. if (dw->getContentComponent() == c)
  464. return dw;
  465. }
  466. return c;
  467. }
  468. void MultiDocumentPanel::componentNameChanged (Component&)
  469. {
  470. if (mode == FloatingWindows)
  471. {
  472. for (auto* child : getChildren())
  473. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  474. dw->setName (dw->getContentComponent()->getName());
  475. }
  476. else if (tabComponent != nullptr)
  477. {
  478. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  479. tabComponent->setTabName (i, tabComponent->getTabContentComponent (i)->getName());
  480. }
  481. }
  482. void MultiDocumentPanel::updateOrder()
  483. {
  484. auto oldList = components;
  485. if (mode == FloatingWindows)
  486. {
  487. components.clear();
  488. for (auto* child : getChildren())
  489. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  490. components.add (dw->getContentComponent());
  491. }
  492. else
  493. {
  494. if (tabComponent != nullptr)
  495. {
  496. if (auto* current = tabComponent->getCurrentContentComponent())
  497. {
  498. components.removeFirstMatchingValue (current);
  499. components.add (current);
  500. }
  501. }
  502. }
  503. if (components != oldList)
  504. activeDocumentChanged();
  505. }
  506. } // namespace juce