JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_MultiDocumentPanel.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29MultiDocumentPanelWindow::MultiDocumentPanelWindow (Colour backgroundColour)
30 : DocumentWindow (String(), backgroundColour,
31 DocumentWindow::maximiseButton | DocumentWindow::closeButton, false)
32{
33}
34
35MultiDocumentPanelWindow::~MultiDocumentPanelWindow()
36{
37}
38
39//==============================================================================
40void MultiDocumentPanelWindow::maximiseButtonPressed()
41{
42 if (auto* owner = getOwner())
43 owner->setLayoutMode (MultiDocumentPanel::MaximisedWindowsWithTabs);
44 else
45 jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
46}
47
48void MultiDocumentPanelWindow::closeButtonPressed()
49{
50 if (auto* owner = getOwner())
51 owner->closeDocumentAsync (getContentComponent(), true, nullptr);
52 else
53 jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
54}
55
56void MultiDocumentPanelWindow::activeWindowStatusChanged()
57{
58 DocumentWindow::activeWindowStatusChanged();
59 updateActiveDocument();
60}
61
62void MultiDocumentPanelWindow::broughtToFront()
63{
64 DocumentWindow::broughtToFront();
65 updateActiveDocument();
66}
67
68void MultiDocumentPanelWindow::updateActiveDocument()
69{
70 if (auto* owner = getOwner())
71 owner->updateActiveDocumentFromUIState();
72}
73
74MultiDocumentPanel* MultiDocumentPanelWindow::getOwner() const noexcept
75{
76 return findParentComponentOfClass<MultiDocumentPanel>();
77}
78
79//==============================================================================
81{
82 TabbedComponentInternal() : TabbedComponent (TabbedButtonBar::TabsAtTop) {}
83
84 void currentTabChanged (int, const String&) override
85 {
86 if (auto* owner = findParentComponentOfClass<MultiDocumentPanel>())
87 owner->updateActiveDocumentFromUIState();
88 }
89};
90
91
92//==============================================================================
93MultiDocumentPanel::MultiDocumentPanel()
94{
95 setOpaque (true);
96}
97
98MultiDocumentPanel::~MultiDocumentPanel()
99{
100 for (int i = components.size(); --i >= 0;)
101 if (auto* component = components[i])
102 closeDocumentInternal (component);
103}
104
105//==============================================================================
106namespace MultiDocHelpers
107{
108 static bool shouldDeleteComp (Component* const c)
109 {
110 return c->getProperties() ["mdiDocumentDelete_"];
111 }
112}
113
114#if JUCE_MODAL_LOOPS_PERMITTED
115bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
116{
117 while (! components.isEmpty())
118 if (! closeDocument (components.getLast(), checkItsOkToCloseFirst))
119 return false;
120
121 return true;
122}
123#endif
124
125void MultiDocumentPanel::closeLastDocumentRecursive (SafePointer<MultiDocumentPanel> parent,
126 bool checkItsOkToCloseFirst,
127 std::function<void (bool)> callback)
128{
129 if (parent->components.isEmpty())
130 {
131 NullCheckedInvocation::invoke (callback, true);
132 return;
133 }
134
135 parent->closeDocumentAsync (parent->components.getLast(),
136 checkItsOkToCloseFirst,
137 [parent, checkItsOkToCloseFirst, callback] (bool closeResult)
138 {
139 if (parent == nullptr)
140 return;
141
142 if (! closeResult)
143 {
144 NullCheckedInvocation::invoke (callback, false);
145 return;
146 }
147
148 parent->closeLastDocumentRecursive (parent, checkItsOkToCloseFirst, std::move (callback));
149 });
150}
151
152void MultiDocumentPanel::closeAllDocumentsAsync (bool checkItsOkToCloseFirst, std::function<void (bool)> callback)
153{
154 closeLastDocumentRecursive (this, checkItsOkToCloseFirst, std::move (callback));
155}
156
157#if JUCE_MODAL_LOOPS_PERMITTED
158bool MultiDocumentPanel::tryToCloseDocument (Component*)
159{
160 // If you hit this assertion then you need to implement this method in a subclass.
162 return false;
163}
164#endif
165
166MultiDocumentPanelWindow* MultiDocumentPanel::createNewDocumentWindow()
167{
168 return new MultiDocumentPanelWindow (backgroundColour);
169}
170
171void MultiDocumentPanel::addWindow (Component* component)
172{
173 auto* dw = createNewDocumentWindow();
174
175 dw->setResizable (true, false);
176 dw->setContentNonOwned (component, true);
177 dw->setName (component->getName());
178
179 auto bkg = component->getProperties() ["mdiDocumentBkg_"];
180 dw->setBackgroundColour (bkg.isVoid() ? backgroundColour : Colour ((uint32) static_cast<int> (bkg)));
181
182 int x = 4;
183
184 if (auto* topComp = getChildren().getLast())
185 if (topComp->getX() == x && topComp->getY() == x)
186 x += 16;
187
188 dw->setTopLeftPosition (x, x);
189
190 auto pos = component->getProperties() ["mdiDocumentPos_"];
191 if (pos.toString().isNotEmpty())
192 dw->restoreWindowStateFromString (pos.toString());
193
194 addAndMakeVisible (dw);
195 dw->toFront (true);
196}
197
198bool MultiDocumentPanel::addDocument (Component* const component,
199 Colour docColour,
200 const bool deleteWhenRemoved)
201{
202 // If you try passing a full DocumentWindow or ResizableWindow in here, you'll end up
203 // with a frame-within-a-frame! Just pass in the bare content component.
204 jassert (dynamic_cast<ResizableWindow*> (component) == nullptr);
205
206 if (component == nullptr || (maximumNumDocuments > 0 && components.size() >= maximumNumDocuments))
207 return false;
208
209 components.add (component);
210 component->getProperties().set ("mdiDocumentDelete_", deleteWhenRemoved);
211 component->getProperties().set ("mdiDocumentBkg_", (int) docColour.getARGB());
212 component->addComponentListener (this);
213
214 if (mode == FloatingWindows)
215 {
216 if (isFullscreenWhenOneDocument())
217 {
218 if (components.size() == 1)
219 {
220 addAndMakeVisible (component);
221 }
222 else
223 {
224 if (components.size() == 2)
225 addWindow (components.getFirst());
226
227 addWindow (component);
228 }
229 }
230 else
231 {
232 addWindow (component);
233 }
234 }
235 else
236 {
237 if (tabComponent == nullptr && components.size() > numDocsBeforeTabsUsed)
238 {
239 tabComponent.reset (new TabbedComponentInternal());
240 addAndMakeVisible (tabComponent.get());
241
242 auto temp = components;
243
244 for (auto& c : temp)
245 tabComponent->addTab (c->getName(), docColour, c, false);
246
247 resized();
248 }
249 else
250 {
251 if (tabComponent != nullptr)
252 tabComponent->addTab (component->getName(), docColour, component, false);
253 else
254 addAndMakeVisible (component);
255 }
256
257 setActiveDocument (component);
258 }
259
260 resized();
261 updateActiveDocument (component);
262 return true;
263}
264
265void MultiDocumentPanel::recreateLayout()
266{
267 tabComponent.reset();
268
269 for (int i = getNumChildComponents(); --i >= 0;)
270 {
271 std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
272
273 if (dw != nullptr)
274 {
275 dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString());
276 dw->clearContentComponent();
277 }
278 }
279
280 resized();
281
282 auto tempComps = components;
283 components.clear();
284
285 {
286 // We want to preserve the activeComponent, so we are blocking the changes originating
287 // from addDocument()
288 const ScopedValueSetter<bool> scope { isLayoutBeingChanged, true };
289
290 for (auto* c : tempComps)
291 addDocument (c,
292 Colour ((uint32) static_cast<int> (c->getProperties().getWithDefault ("mdiDocumentBkg_",
293 (int) Colours::white.getARGB()))),
294 MultiDocHelpers::shouldDeleteComp (c));
295 }
296
297 if (activeComponent != nullptr)
298 setActiveDocument (activeComponent);
299
300 updateActiveDocumentFromUIState();
301}
302
303void MultiDocumentPanel::closeDocumentInternal (Component* componentToClose)
304{
305 // Intellisense warns about component being uninitialised.
306 // I'm not sure how a function argument could be uninitialised.
307 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
308
309 const OptionalScopedPointer<Component> component { componentToClose,
310 MultiDocHelpers::shouldDeleteComp (componentToClose) };
311
312 component->removeComponentListener (this);
313
314 component->getProperties().remove ("mdiDocumentDelete_");
315 component->getProperties().remove ("mdiDocumentBkg_");
316
317 const auto removedIndex = components.indexOf (component);
318
319 if (removedIndex < 0)
320 {
322 return;
323 }
324
325 components.remove (removedIndex);
326
327 // See if the active document needs to change because of closing a document. It should only
328 // change if we closed the active document. If so, the next active document should be the
329 // subsequent one.
330 if (component == activeComponent)
331 {
332 auto* newActiveComponent = components[jmin (removedIndex, components.size() - 1)];
333 updateActiveDocument (newActiveComponent);
334 }
335
336 // We update the UI to reflect the new state, but we want to prevent the UI state callback
337 // to change the active document.
338 const ScopedValueSetter<bool> scope { isLayoutBeingChanged, true };
339
340 if (mode == FloatingWindows)
341 {
342 for (auto* child : getChildren())
343 {
344 if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
345 {
346 if (dw->getContentComponent() == component)
347 {
348 std::unique_ptr<MultiDocumentPanelWindow> (dw)->clearContentComponent();
349 break;
350 }
351 }
352 }
353
354 if (isFullscreenWhenOneDocument() && components.size() == 1)
355 {
356 for (int i = getNumChildComponents(); --i >= 0;)
357 {
358 std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
359
360 if (dw != nullptr)
361 dw->clearContentComponent();
362 }
363
364 addAndMakeVisible (getActiveDocument());
365 }
366 }
367 else
368 {
369 if (tabComponent != nullptr)
370 {
371 for (int i = tabComponent->getNumTabs(); --i >= 0;)
372 if (tabComponent->getTabContentComponent (i) == component)
373 tabComponent->removeTab (i);
374 }
375 else
376 {
377 removeChildComponent (component);
378 }
379
380 if (components.size() <= numDocsBeforeTabsUsed && getActiveDocument() != nullptr)
381 {
382 tabComponent.reset();
383 addAndMakeVisible (getActiveDocument());
384 }
385 }
386
387 resized();
388
389 // This ensures that the active tab is painted properly when a tab is closed!
390 if (auto* activeDocument = getActiveDocument())
391 setActiveDocument (activeDocument);
392
393 JUCE_END_IGNORE_WARNINGS_MSVC
394}
395
396#if JUCE_MODAL_LOOPS_PERMITTED
397bool MultiDocumentPanel::closeDocument (Component* component,
398 const bool checkItsOkToCloseFirst)
399{
400 // Intellisense warns about component being uninitialised.
401 // I'm not sure how a function argument could be uninitialised.
402 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
403
404 if (component == nullptr)
405 return true;
406
407 if (components.contains (component))
408 {
409 if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
410 return false;
411
412 closeDocumentInternal (component);
413 }
414 else
415 {
417 }
418
419 return true;
420
421 JUCE_END_IGNORE_WARNINGS_MSVC
422}
423#endif
424
425void MultiDocumentPanel::closeDocumentAsync (Component* component,
426 const bool checkItsOkToCloseFirst,
427 std::function<void (bool)> callback)
428{
429 // Intellisense warns about component being uninitialised.
430 // I'm not sure how a function argument could be uninitialised.
431 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
432
433 if (component == nullptr)
434 {
435 NullCheckedInvocation::invoke (callback, true);
436 return;
437 }
438
439 if (components.contains (component))
440 {
441 if (checkItsOkToCloseFirst)
442 {
443 tryToCloseDocumentAsync (component,
444 [parent = SafePointer<MultiDocumentPanel> { this }, component, callback] (bool closedSuccessfully)
445 {
446 if (parent == nullptr)
447 return;
448
449 if (closedSuccessfully)
450 parent->closeDocumentInternal (component);
451
452 NullCheckedInvocation::invoke (callback, closedSuccessfully);
453 });
454
455 return;
456 }
457
458 closeDocumentInternal (component);
459 }
460 else
461 {
463 }
464
465 NullCheckedInvocation::invoke (callback, true);
466
467 JUCE_END_IGNORE_WARNINGS_MSVC
468}
469
470int MultiDocumentPanel::getNumDocuments() const noexcept
471{
472 return components.size();
473}
474
475Component* MultiDocumentPanel::getDocument (const int index) const noexcept
476{
477 return components [index];
478}
479
480Component* MultiDocumentPanel::getActiveDocument() const noexcept
481{
482 return activeComponent;
483}
484
485void MultiDocumentPanel::setActiveDocument (Component* component)
486{
487 jassert (component != nullptr);
488
489 if (mode == FloatingWindows)
490 {
491 component = getContainerComp (component);
492
493 if (component != nullptr)
494 component->toFront (true);
495 }
496 else if (tabComponent != nullptr)
497 {
498 jassert (components.indexOf (component) >= 0);
499
500 for (int i = tabComponent->getNumTabs(); --i >= 0;)
501 {
502 if (tabComponent->getTabContentComponent (i) == component)
503 {
504 tabComponent->setCurrentTabIndex (i);
505 break;
506 }
507 }
508 }
509 else
510 {
511 component->grabKeyboardFocus();
512 }
513}
514
515void MultiDocumentPanel::activeDocumentChanged()
516{
517}
518
519void MultiDocumentPanel::setMaximumNumDocuments (const int newNumber)
520{
521 maximumNumDocuments = newNumber;
522}
523
524void MultiDocumentPanel::useFullscreenWhenOneDocument (const bool shouldUseTabs)
525{
526 const auto newNumDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0;
527
528 if (std::exchange (numDocsBeforeTabsUsed, newNumDocsBeforeTabsUsed) != newNumDocsBeforeTabsUsed)
529 recreateLayout();
530}
531
532bool MultiDocumentPanel::isFullscreenWhenOneDocument() const noexcept
533{
534 return numDocsBeforeTabsUsed != 0;
535}
536
537//==============================================================================
538void MultiDocumentPanel::setLayoutMode (const LayoutMode newLayoutMode)
539{
540 if (std::exchange (mode, newLayoutMode) != newLayoutMode)
541 recreateLayout();
542}
543
544void MultiDocumentPanel::setBackgroundColour (Colour newBackgroundColour)
545{
546 if (backgroundColour != newBackgroundColour)
547 {
548 backgroundColour = newBackgroundColour;
549 setOpaque (newBackgroundColour.isOpaque());
550 repaint();
551 }
552}
553
554//==============================================================================
555void MultiDocumentPanel::paint (Graphics& g)
556{
557 g.fillAll (backgroundColour);
558}
559
560void MultiDocumentPanel::resized()
561{
562 if (mode == MaximisedWindowsWithTabs || components.size() == numDocsBeforeTabsUsed)
563 {
564 for (auto* child : getChildren())
565 child->setBounds (getLocalBounds());
566 }
567
568 setWantsKeyboardFocus (components.size() == 0);
569}
570
571Component* MultiDocumentPanel::getContainerComp (Component* c) const
572{
573 if (mode == FloatingWindows)
574 {
575 for (auto* child : getChildren())
576 if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
577 if (dw->getContentComponent() == c)
578 return dw;
579 }
580
581 return c;
582}
583
584void MultiDocumentPanel::componentNameChanged (Component&)
585{
586 if (mode == FloatingWindows)
587 {
588 for (auto* child : getChildren())
589 if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
590 dw->setName (dw->getContentComponent()->getName());
591 }
592 else if (tabComponent != nullptr)
593 {
594 for (int i = tabComponent->getNumTabs(); --i >= 0;)
595 tabComponent->setTabName (i, tabComponent->getTabContentComponent (i)->getName());
596 }
597}
598
599void MultiDocumentPanel::updateActiveDocumentFromUIState()
600{
601 auto* newActiveComponent = [&]() -> Component*
602 {
603 if (mode == FloatingWindows)
604 {
605 for (auto* c : components)
606 {
607 if (auto* window = static_cast<MultiDocumentPanelWindow*> (c->getParentComponent()))
608 if (window->isActiveWindow())
609 return c;
610 }
611 }
612
613 if (tabComponent != nullptr)
614 if (auto* current = tabComponent->getCurrentContentComponent())
615 return current;
616
617 return activeComponent;
618 }();
619
620 updateActiveDocument (newActiveComponent);
621}
622
623void MultiDocumentPanel::updateActiveDocument (Component* component)
624{
625 if (isLayoutBeingChanged)
626 return;
627
628 if (std::exchange (activeComponent, component) != component)
629 activeDocumentChanged();
630}
631
632} // namespace juce
Represents a colour, also including a transparency value.
Definition juce_Colour.h:38
bool isOpaque() const noexcept
Returns true if this colour is completely opaque.
uint32 getARGB() const noexcept
Returns a 32-bit integer that represents this colour.
Holds a pointer to some type of Component, which automatically becomes null if the component is delet...
The base class for all JUCE user-interface objects.
void removeComponentListener(ComponentListener *listenerToRemove)
Removes a component listener.
Component * getParentComponent() const noexcept
Returns the component which this component is inside.
void grabKeyboardFocus()
Tries to give keyboard focus to this component.
void toFront(bool shouldAlsoGainKeyboardFocus)
Brings the component to the front of its siblings.
void addComponentListener(ComponentListener *newListener)
Adds a listener to be told about changes to the component hierarchy or position.
NamedValueSet & getProperties() noexcept
Returns the set of properties that belong to this component.
virtual void setName(const String &newName)
Sets the name of this component.
String getName() const noexcept
Returns the name of this component.
A graphics context, used for drawing a component or image.
void fillAll() const
Fills the context's entire clip region with the current colour or brush.
This is a derivative of DocumentWindow that is used inside a MultiDocumentPanel component.
LayoutMode
The different layout modes available.
bool set(const Identifier &name, const var &newValue)
Changes or adds a named value.
bool remove(const Identifier &name)
Removes a value from the set.
A base class for top-level windows that can be dragged around and resized.
The JUCE String class!
Definition juce_String.h:53
A component with a TabbedButtonBar along one of its sides.
T exchange(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define jassertfalse
This will always cause an assertion failure.
typedef int
JUCE Namespace.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
void currentTabChanged(int, const String &) override
Callback method to indicate the selected tab has been changed.