diff --git a/examples/GUI/MDIDemo.h b/examples/GUI/MDIDemo.h index e5564d3bc4..0c877583c0 100644 --- a/examples/GUI/MDIDemo.h +++ b/examples/GUI/MDIDemo.h @@ -151,6 +151,12 @@ public: } } + void activeDocumentChanged() override + { + if (auto* activeDoc = getActiveDocument()) + Logger::outputDebugString ("activeDocumentChanged() to " + activeDoc->getName()); + } + private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoMultiDocumentPanel) }; @@ -171,9 +177,26 @@ public: showInTabsButton.onClick = [this] { updateLayoutMode(); }; addAndMakeVisible (showInTabsButton); - addNoteButton.onClick = [this] { addNote ("Note " + String (multiDocumentPanel.getNumDocuments() + 1), "Hello World!"); }; + oneDocShouldBeFullscreenButton.onClick = [this] + { + multiDocumentPanel.useFullscreenWhenOneDocument (oneDocShouldBeFullscreenButton.getToggleState()); + }; + addAndMakeVisible (oneDocShouldBeFullscreenButton); + oneDocShouldBeFullscreenButton.setToggleState (false, juce::sendNotification); + + addNoteButton.onClick = [this] + { + addNote ("Note " + String (noteCounter), "Hello World! " + String (noteCounter)); + ++noteCounter; + }; addAndMakeVisible (addNoteButton); + closeActiveDocumentButton.onClick = [this] + { + multiDocumentPanel.closeDocumentAsync (multiDocumentPanel.getActiveDocument(), false, [] (auto) {}); + }; + addAndMakeVisible (closeActiveDocumentButton); + closeApplicationButton.onClick = [this] { multiDocumentPanel.closeAllDocumentsAsync (true, [] (bool allSaved) @@ -191,7 +214,7 @@ public: addNote ("Notes Demo", "You can drag-and-drop text files onto this page to open them as notes.."); addExistingNotes(); - setSize (500, 500); + setSize (650, 500); } void paint (Graphics& g) override @@ -203,10 +226,15 @@ public: { auto area = getLocalBounds(); - auto buttonArea = area.removeFromTop (28).reduced (2); - closeApplicationButton.setBounds (buttonArea.removeFromRight (150)); - addNoteButton .setBounds (buttonArea.removeFromRight (150)); - showInTabsButton .setBounds (buttonArea); + auto topButtonRow = area.removeFromTop (28).reduced (2); + + showInTabsButton .setBounds (topButtonRow.removeFromLeft (150)); + + closeApplicationButton .setBounds (topButtonRow.removeFromRight (150)); + addNoteButton .setBounds (topButtonRow.removeFromRight (150)); + closeActiveDocumentButton .setBounds (topButtonRow.removeFromRight (150)); + + oneDocShouldBeFullscreenButton.setBounds (area.removeFromTop (28).reduced (2).removeFromLeft (240)); multiDocumentPanel.setBounds (area); } @@ -261,11 +289,14 @@ private: createNotesForFiles (files); } - ToggleButton showInTabsButton { "Show with tabs" }; - TextButton addNoteButton { "Create a new note" }, - closeApplicationButton { "Close app" }; + ToggleButton showInTabsButton { "Show with tabs" }; + ToggleButton oneDocShouldBeFullscreenButton { "Fill screen when only one note is open" }; + TextButton addNoteButton { "Create a new note" }, + closeApplicationButton { "Close app" }, + closeActiveDocumentButton { "Close active document" }; DemoMultiDocumentPanel multiDocumentPanel; + int noteCounter = 1; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MDIDemo) }; diff --git a/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp b/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp index 255d3306cd..b711858cd4 100644 --- a/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp +++ b/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp @@ -56,19 +56,19 @@ void MultiDocumentPanelWindow::closeButtonPressed() void MultiDocumentPanelWindow::activeWindowStatusChanged() { DocumentWindow::activeWindowStatusChanged(); - updateOrder(); + updateActiveDocument(); } void MultiDocumentPanelWindow::broughtToFront() { DocumentWindow::broughtToFront(); - updateOrder(); + updateActiveDocument(); } -void MultiDocumentPanelWindow::updateOrder() +void MultiDocumentPanelWindow::updateActiveDocument() { if (auto* owner = getOwner()) - owner->updateOrder(); + owner->updateActiveDocumentFromUIState(); } MultiDocumentPanel* MultiDocumentPanelWindow::getOwner() const noexcept @@ -84,7 +84,7 @@ struct MultiDocumentPanel::TabbedComponentInternal : public TabbedComponent void currentTabChanged (int, const String&) override { if (auto* owner = findParentComponentOfClass()) - owner->updateOrder(); + owner->updateActiveDocumentFromUIState(); } }; @@ -262,22 +262,85 @@ bool MultiDocumentPanel::addDocument (Component* const component, } resized(); - activeDocumentChanged(); + updateActiveDocument (component); return true; } -void MultiDocumentPanel::closeDocumentInternal (Component* component) +void MultiDocumentPanel::recreateLayout() +{ + tabComponent.reset(); + + for (int i = getNumChildComponents(); --i >= 0;) + { + std::unique_ptr dw (dynamic_cast (getChildComponent (i))); + + if (dw != nullptr) + { + dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString()); + dw->clearContentComponent(); + } + } + + resized(); + + auto tempComps = components; + components.clear(); + + { + // We want to preserve the activeComponent, so we are blocking the changes originating + // from addDocument() + const ScopedValueSetter scope { isLayoutBeingChanged, true }; + + for (auto* c : tempComps) + addDocument (c, + Colour ((uint32) static_cast (c->getProperties().getWithDefault ("mdiDocumentBkg_", + (int) Colours::white.getARGB()))), + MultiDocHelpers::shouldDeleteComp (c)); + } + + if (activeComponent != nullptr) + setActiveDocument (activeComponent); + + updateActiveDocumentFromUIState(); +} + +void MultiDocumentPanel::closeDocumentInternal (Component* componentToClose) { // Intellisense warns about component being uninitialised. // I'm not sure how a function argument could be uninitialised. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001) + const OptionalScopedPointer component { componentToClose, + MultiDocHelpers::shouldDeleteComp (componentToClose) }; + component->removeComponentListener (this); - const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component); component->getProperties().remove ("mdiDocumentDelete_"); component->getProperties().remove ("mdiDocumentBkg_"); + const auto removedIndex = components.indexOf (component); + + if (removedIndex < 0) + { + jassertfalse; + return; + } + + components.remove (removedIndex); + + // See if the active document needs to change because of closing a document. It should only + // change if we closed the active document. If so, the next active document should be the + // subsequent one. + if (component == activeComponent) + { + auto* newActiveComponent = components[jmin (removedIndex, components.size() - 1)]; + updateActiveDocument (newActiveComponent); + } + + // We update the UI to reflect the new state, but we want to prevent the UI state callback + // to change the active document. + const ScopedValueSetter scope { isLayoutBeingChanged, true }; + if (mode == FloatingWindows) { for (auto* child : getChildren()) @@ -292,11 +355,6 @@ void MultiDocumentPanel::closeDocumentInternal (Component* component) } } - if (shouldDelete) - delete component; - - components.removeFirstMatchingValue (component); - if (isFullscreenWhenOneDocument() && components.size() == 1) { for (int i = getNumChildComponents(); --i >= 0;) @@ -307,13 +365,11 @@ void MultiDocumentPanel::closeDocumentInternal (Component* component) dw->clearContentComponent(); } - addAndMakeVisible (components.getFirst()); + addAndMakeVisible (getActiveDocument()); } } else { - jassert (components.indexOf (component) >= 0); - if (tabComponent != nullptr) { for (int i = tabComponent->getNumTabs(); --i >= 0;) @@ -325,25 +381,18 @@ void MultiDocumentPanel::closeDocumentInternal (Component* component) removeChildComponent (component); } - if (shouldDelete) - delete component; - - if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed) + if (components.size() <= numDocsBeforeTabsUsed && getActiveDocument() != nullptr) + { tabComponent.reset(); - - components.removeFirstMatchingValue (component); - - if (components.size() > 0 && tabComponent == nullptr) - addAndMakeVisible (components.getFirst()); + addAndMakeVisible (getActiveDocument()); + } } resized(); // This ensures that the active tab is painted properly when a tab is closed! - if (auto* activeComponent = getActiveDocument()) - setActiveDocument (activeComponent); - - activeDocumentChanged(); + if (auto* activeDocument = getActiveDocument()) + setActiveDocument (activeDocument); JUCE_END_IGNORE_WARNINGS_MSVC } @@ -438,15 +487,7 @@ Component* MultiDocumentPanel::getDocument (const int index) const noexcept Component* MultiDocumentPanel::getActiveDocument() const noexcept { - if (mode == FloatingWindows) - { - for (auto* child : getChildren()) - if (auto* dw = dynamic_cast (child)) - if (dw->isActiveWindow()) - return dw->getContentComponent(); - } - - return components.getLast(); + return activeComponent; } void MultiDocumentPanel::setActiveDocument (Component* component) @@ -490,7 +531,10 @@ void MultiDocumentPanel::setMaximumNumDocuments (const int newNumber) void MultiDocumentPanel::useFullscreenWhenOneDocument (const bool shouldUseTabs) { - numDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0; + const auto newNumDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0; + + if (std::exchange (numDocsBeforeTabsUsed, newNumDocsBeforeTabsUsed) != newNumDocsBeforeTabsUsed) + recreateLayout(); } bool MultiDocumentPanel::isFullscreenWhenOneDocument() const noexcept @@ -501,38 +545,8 @@ bool MultiDocumentPanel::isFullscreenWhenOneDocument() const noexcept //============================================================================== void MultiDocumentPanel::setLayoutMode (const LayoutMode newLayoutMode) { - if (mode != newLayoutMode) - { - mode = newLayoutMode; - - if (mode == FloatingWindows) - { - tabComponent.reset(); - } - else - { - for (int i = getNumChildComponents(); --i >= 0;) - { - std::unique_ptr dw (dynamic_cast (getChildComponent (i))); - - if (dw != nullptr) - { - dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString()); - dw->clearContentComponent(); - } - } - } - - resized(); - - auto tempComps = components; - components.clear(); - - for (auto* c : tempComps) - addDocument (c, - Colour ((uint32) static_cast (c->getProperties().getWithDefault ("mdiDocumentBkg_", (int) Colours::white.getARGB()))), - MultiDocHelpers::shouldDeleteComp (c)); - } + if (std::exchange (mode, newLayoutMode) != newLayoutMode) + recreateLayout(); } void MultiDocumentPanel::setBackgroundColour (Colour newBackgroundColour) @@ -590,31 +604,36 @@ void MultiDocumentPanel::componentNameChanged (Component&) } } -void MultiDocumentPanel::updateOrder() +void MultiDocumentPanel::updateActiveDocumentFromUIState() { - auto oldList = components; - - if (mode == FloatingWindows) + auto* newActiveComponent = [&]() -> Component* { - components.clear(); - - for (auto* child : getChildren()) - if (auto* dw = dynamic_cast (child)) - components.add (dw->getContentComponent()); - } - else - { - if (tabComponent != nullptr) + if (mode == FloatingWindows) { - if (auto* current = tabComponent->getCurrentContentComponent()) + for (auto* c : components) { - components.removeFirstMatchingValue (current); - components.add (current); + if (auto* window = static_cast (c->getParentComponent())) + if (window->isActiveWindow()) + return c; } } - } - if (components != oldList) + if (tabComponent != nullptr) + if (auto* current = tabComponent->getCurrentContentComponent()) + return current; + + return activeComponent; + }(); + + updateActiveDocument (newActiveComponent); +} + +void MultiDocumentPanel::updateActiveDocument (Component* component) +{ + if (isLayoutBeingChanged) + return; + + if (std::exchange (activeComponent, component) != component) activeDocumentChanged(); } diff --git a/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.h b/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.h index 52ed931186..2a41489785 100644 --- a/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.h +++ b/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.h @@ -64,7 +64,7 @@ public: private: //============================================================================== - void updateOrder(); + void updateActiveDocument(); MultiDocumentPanel* getOwner() const noexcept; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiDocumentPanelWindow) @@ -362,11 +362,15 @@ private: friend class MultiDocumentPanelWindow; Component* getContainerComp (Component*) const; - void updateOrder(); + void updateActiveDocumentFromUIState(); + void updateActiveDocument (Component*); void addWindow (Component*); + void recreateLayout(); LayoutMode mode = MaximisedWindowsWithTabs; Array components; + Component* activeComponent = nullptr; + bool isLayoutBeingChanged = false; std::unique_ptr tabComponent; Colour backgroundColour { Colours::lightblue }; int maximumNumDocuments = 0, numDocsBeforeTabsUsed = 0;