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.

615 lines
17KB

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