diff --git a/Builds/Linux/Makefile b/Builds/Linux/Makefile index 6f8708174d..b78d5fabec 100644 --- a/Builds/Linux/Makefile +++ b/Builds/Linux/Makefile @@ -127,6 +127,7 @@ OBJECTS := \ $(OBJDIR)/juce_Component_49f01dfa.o \ $(OBJDIR)/juce_ComponentListener_e0eda7ce.o \ $(OBJDIR)/juce_Desktop_e3b47b99.o \ + $(OBJDIR)/juce_ModalComponentManager_b87bddba.o \ $(OBJDIR)/juce_ArrowButton_ebac7066.o \ $(OBJDIR)/juce_Button_886d3491.o \ $(OBJDIR)/juce_DrawableButton_e03899cf.o \ @@ -793,6 +794,11 @@ $(OBJDIR)/juce_Desktop_e3b47b99.o: ../../src/gui/components/juce_Desktop.cpp @echo "Compiling juce_Desktop.cpp" @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" +$(OBJDIR)/juce_ModalComponentManager_b87bddba.o: ../../src/gui/components/juce_ModalComponentManager.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling juce_ModalComponentManager.cpp" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + $(OBJDIR)/juce_ArrowButton_ebac7066.o: ../../src/gui/components/buttons/juce_ArrowButton.cpp -@mkdir -p $(OBJDIR) @echo "Compiling juce_ArrowButton.cpp" diff --git a/Builds/MacOSX/Juce.xcodeproj/project.pbxproj b/Builds/MacOSX/Juce.xcodeproj/project.pbxproj index 2fd68d1228..ebe1f3851a 100644 --- a/Builds/MacOSX/Juce.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/Juce.xcodeproj/project.pbxproj @@ -96,6 +96,7 @@ 621B7B0B518E63E69122C13E = { isa = PBXBuildFile; fileRef = D0D9267E200BD462361810F7; }; 97982824572C18827047D92A = { isa = PBXBuildFile; fileRef = E13F33E386E1A0D5FC546521; }; 927E7250FCE62E838599DF83 = { isa = PBXBuildFile; fileRef = 621B3A4B154182F69DDE2989; }; + 9AE6891C35CE161CB1707B4B = { isa = PBXBuildFile; fileRef = 2FFF9AFE4BD9437CE096E52B; }; 06994A713C38F415C4E8A009 = { isa = PBXBuildFile; fileRef = 18EE6576A9ED098632CE5155; }; 98F737B7459895BFCDC7965E = { isa = PBXBuildFile; fileRef = 8B1C747E63EEF036AD9AF3D8; }; 7033A0968B1C81A821CCC296 = { isa = PBXBuildFile; fileRef = 1C3D15546065C1A9AA5AA0C6; }; @@ -550,6 +551,8 @@ A0D6308567AAA50D1163D9D3 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ComponentListener.h; path = ../../src/gui/components/juce_ComponentListener.h; sourceTree = SOURCE_ROOT; }; 621B3A4B154182F69DDE2989 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_Desktop.cpp; path = ../../src/gui/components/juce_Desktop.cpp; sourceTree = SOURCE_ROOT; }; A1F58C1A972425C2B43DD1B3 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_Desktop.h; path = ../../src/gui/components/juce_Desktop.h; sourceTree = SOURCE_ROOT; }; + 2FFF9AFE4BD9437CE096E52B = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_ModalComponentManager.cpp; path = ../../src/gui/components/juce_ModalComponentManager.cpp; sourceTree = SOURCE_ROOT; }; + 41C8C324F13ADA3423FC3B0F = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ModalComponentManager.h; path = ../../src/gui/components/juce_ModalComponentManager.h; sourceTree = SOURCE_ROOT; }; 18EE6576A9ED098632CE5155 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_ArrowButton.cpp; path = ../../src/gui/components/buttons/juce_ArrowButton.cpp; sourceTree = SOURCE_ROOT; }; EB182DC4124FEFFFC87D12C4 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ArrowButton.h; path = ../../src/gui/components/buttons/juce_ArrowButton.h; sourceTree = SOURCE_ROOT; }; 8B1C747E63EEF036AD9AF3D8 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_Button.cpp; path = ../../src/gui/components/buttons/juce_Button.cpp; sourceTree = SOURCE_ROOT; }; @@ -1455,6 +1458,8 @@ A0D6308567AAA50D1163D9D3, 621B3A4B154182F69DDE2989, A1F58C1A972425C2B43DD1B3, + 2FFF9AFE4BD9437CE096E52B, + 41C8C324F13ADA3423FC3B0F, 2DB55F83F4310F0C4E4E03AA, 17B11D96CDB313ED60D8CFE0, BCB2FFE7C2A4084A267F57F2, @@ -1930,6 +1935,7 @@ 621B7B0B518E63E69122C13E, 97982824572C18827047D92A, 927E7250FCE62E838599DF83, + 9AE6891C35CE161CB1707B4B, 06994A713C38F415C4E8A009, 98F737B7459895BFCDC7965E, 7033A0968B1C81A821CCC296, diff --git a/Builds/VisualStudio2005/Juce.vcproj b/Builds/VisualStudio2005/Juce.vcproj index c4f4f4913a..91d3cc3947 100644 --- a/Builds/VisualStudio2005/Juce.vcproj +++ b/Builds/VisualStudio2005/Juce.vcproj @@ -349,6 +349,8 @@ + + diff --git a/Builds/VisualStudio2008/Juce.vcproj b/Builds/VisualStudio2008/Juce.vcproj index c7d8aa47f1..0246a9ae70 100644 --- a/Builds/VisualStudio2008/Juce.vcproj +++ b/Builds/VisualStudio2008/Juce.vcproj @@ -349,6 +349,8 @@ + + diff --git a/Builds/VisualStudio2008_DLL/Juce.vcproj b/Builds/VisualStudio2008_DLL/Juce.vcproj index 889644155b..c98fc80379 100644 --- a/Builds/VisualStudio2008_DLL/Juce.vcproj +++ b/Builds/VisualStudio2008_DLL/Juce.vcproj @@ -351,6 +351,8 @@ + + diff --git a/Builds/VisualStudio2010/Juce.vcxproj b/Builds/VisualStudio2010/Juce.vcxproj index 993589cef0..598a1b5746 100644 --- a/Builds/VisualStudio2010/Juce.vcxproj +++ b/Builds/VisualStudio2010/Juce.vcxproj @@ -210,6 +210,7 @@ + @@ -549,6 +550,7 @@ + diff --git a/Builds/VisualStudio2010/Juce.vcxproj.filters b/Builds/VisualStudio2010/Juce.vcxproj.filters index 85fb0ccddf..55a432946b 100644 --- a/Builds/VisualStudio2010/Juce.vcxproj.filters +++ b/Builds/VisualStudio2010/Juce.vcxproj.filters @@ -430,6 +430,9 @@ Juce\Source\gui\components + + Juce\Source\gui\components + Juce\Source\gui\components\buttons @@ -1521,6 +1524,9 @@ Juce\Source\gui\components + + Juce\Source\gui\components + Juce\Source\gui\components\buttons diff --git a/Builds/iPhone/Juce.xcodeproj/project.pbxproj b/Builds/iPhone/Juce.xcodeproj/project.pbxproj index f5f13d37b6..ad213a3423 100644 --- a/Builds/iPhone/Juce.xcodeproj/project.pbxproj +++ b/Builds/iPhone/Juce.xcodeproj/project.pbxproj @@ -96,6 +96,7 @@ 621B7B0B518E63E69122C13E = { isa = PBXBuildFile; fileRef = D0D9267E200BD462361810F7; }; 97982824572C18827047D92A = { isa = PBXBuildFile; fileRef = E13F33E386E1A0D5FC546521; }; 927E7250FCE62E838599DF83 = { isa = PBXBuildFile; fileRef = 621B3A4B154182F69DDE2989; }; + 9AE6891C35CE161CB1707B4B = { isa = PBXBuildFile; fileRef = 2FFF9AFE4BD9437CE096E52B; }; 06994A713C38F415C4E8A009 = { isa = PBXBuildFile; fileRef = 18EE6576A9ED098632CE5155; }; 98F737B7459895BFCDC7965E = { isa = PBXBuildFile; fileRef = 8B1C747E63EEF036AD9AF3D8; }; 7033A0968B1C81A821CCC296 = { isa = PBXBuildFile; fileRef = 1C3D15546065C1A9AA5AA0C6; }; @@ -550,6 +551,8 @@ A0D6308567AAA50D1163D9D3 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ComponentListener.h; path = ../../src/gui/components/juce_ComponentListener.h; sourceTree = SOURCE_ROOT; }; 621B3A4B154182F69DDE2989 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_Desktop.cpp; path = ../../src/gui/components/juce_Desktop.cpp; sourceTree = SOURCE_ROOT; }; A1F58C1A972425C2B43DD1B3 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_Desktop.h; path = ../../src/gui/components/juce_Desktop.h; sourceTree = SOURCE_ROOT; }; + 2FFF9AFE4BD9437CE096E52B = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_ModalComponentManager.cpp; path = ../../src/gui/components/juce_ModalComponentManager.cpp; sourceTree = SOURCE_ROOT; }; + 41C8C324F13ADA3423FC3B0F = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ModalComponentManager.h; path = ../../src/gui/components/juce_ModalComponentManager.h; sourceTree = SOURCE_ROOT; }; 18EE6576A9ED098632CE5155 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_ArrowButton.cpp; path = ../../src/gui/components/buttons/juce_ArrowButton.cpp; sourceTree = SOURCE_ROOT; }; EB182DC4124FEFFFC87D12C4 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = juce_ArrowButton.h; path = ../../src/gui/components/buttons/juce_ArrowButton.h; sourceTree = SOURCE_ROOT; }; 8B1C747E63EEF036AD9AF3D8 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_Button.cpp; path = ../../src/gui/components/buttons/juce_Button.cpp; sourceTree = SOURCE_ROOT; }; @@ -1455,6 +1458,8 @@ A0D6308567AAA50D1163D9D3, 621B3A4B154182F69DDE2989, A1F58C1A972425C2B43DD1B3, + 2FFF9AFE4BD9437CE096E52B, + 41C8C324F13ADA3423FC3B0F, 2DB55F83F4310F0C4E4E03AA, 17B11D96CDB313ED60D8CFE0, BCB2FFE7C2A4084A267F57F2, @@ -1930,6 +1935,7 @@ 621B7B0B518E63E69122C13E, 97982824572C18827047D92A, 927E7250FCE62E838599DF83, + 9AE6891C35CE161CB1707B4B, 06994A713C38F415C4E8A009, 98F737B7459895BFCDC7965E, 7033A0968B1C81A821CCC296, diff --git a/Juce.jucer b/Juce.jucer index 268f848a72..f2ea35a8c9 100644 --- a/Juce.jucer +++ b/Juce.jucer @@ -495,6 +495,10 @@ file="src/gui/components/juce_Desktop.cpp"/> + + diff --git a/amalgamation/juce_amalgamated_template.cpp b/amalgamation/juce_amalgamated_template.cpp index 53f3f0e569..2628e59bff 100644 --- a/amalgamation/juce_amalgamated_template.cpp +++ b/amalgamation/juce_amalgamated_template.cpp @@ -223,6 +223,7 @@ #include "../src/gui/components/juce_Component.cpp" #include "../src/gui/components/juce_ComponentListener.cpp" #include "../src/gui/components/juce_Desktop.cpp" + #include "../src/gui/components/juce_ModalComponentManager.cpp" #include "../src/gui/components/buttons/juce_ArrowButton.cpp" #include "../src/gui/components/buttons/juce_Button.cpp" #include "../src/gui/components/buttons/juce_DrawableButton.cpp" diff --git a/extras/Jucer (experimental)/Source/model/Project/jucer_ProjectExport_MSVC.h b/extras/Jucer (experimental)/Source/model/Project/jucer_ProjectExport_MSVC.h index a01f65417c..f049d6b3cb 100644 --- a/extras/Jucer (experimental)/Source/model/Project/jucer_ProjectExport_MSVC.h +++ b/extras/Jucer (experimental)/Source/model/Project/jucer_ProjectExport_MSVC.h @@ -1184,7 +1184,7 @@ protected: const String getProjectType() const { - if (project.isGUIApplication()) + if (project.isGUIApplication() || project.isCommandLineApp()) return "Application"; else if (project.isAudioPlugin() || project.isBrowserPlugin()) return "DynamicLibrary"; diff --git a/extras/audio plugin host/Plugin Host.jucer b/extras/audio plugin host/Plugin Host.jucer index a22e9acf38..4dd93359c1 100644 --- a/extras/audio plugin host/Plugin Host.jucer +++ b/extras/audio plugin host/Plugin Host.jucer @@ -13,12 +13,12 @@ jucerVersion="3.0.0"> + juceFolder="../.."/> + rtasFolder="c:\SDKs\PT_80_SDK" juceFolder="../.." libraryType="1"/> - + rtasFolder="c:\SDKs\PT_80_SDK" juceFolder="../.." libraryType="1"/> + + juceFolder="../../.." objCExtraSuffix="JuceDemo"/> + rtasFolder="c:\SDKs\PT_80_SDK" juceFolder="../../.." libraryType="1"/> + rtasFolder="c:\SDKs\PT_80_SDK" juceFolder="../../.." libraryType="1"/> + juceFolder="../.."/> + juceFolder="../.."/> + rtasFolder="c:\SDKs\PT_80_SDK" juceFolder="../.." libraryType="1"/> - + rtasFolder="c:\SDKs\PT_80_SDK" juceFolder="../.." libraryType="1"/> + modalComponentStack, modalComponentReturnValueKeys; -static Array modalReturnValues; +#define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); enum ComponentMessageNumbers { @@ -38520,9 +38528,8 @@ enum ComponentMessageNumbers exitModalStateMessage = 0x7fff0002 }; -#define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); - static uint32 nextComponentUID = 0; +Component* Component::currentlyFocusedComponent = 0; Component::Component() : parentComponent_ (0), @@ -38568,8 +38575,6 @@ Component::~Component() if (flags.hasHeavyweightPeerFlag) removeFromDesktop(); - modalComponentStack.removeValue (this); - for (int i = childComponentList_.size(); --i >= 0;) childComponentList_.getUnchecked(i)->parentComponent_ = 0; @@ -39776,59 +39781,17 @@ int Component::runModalLoop() if (! MessageManager::getInstance()->isThisTheMessageThread()) { // use a callback so this can be called from non-gui threads - return (int) (pointer_sized_int) - MessageManager::getInstance() - ->callFunctionOnMessageThread (&runModalLoopCallback, this); + return (int) (pointer_sized_int) MessageManager::getInstance() + ->callFunctionOnMessageThread (&runModalLoopCallback, this); } - SafePointer prevFocused (getCurrentlyFocusedComponent()); - if (! isCurrentlyModal()) - enterModalState(); + enterModalState (true); - JUCE_TRY - { - while (flags.currentlyModalFlag && flags.visibleFlag) - { - if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) - break; - - // check whether this component was deleted during the last message - if (! isValidMessageListener()) - break; - } - } -#if JUCE_CATCH_UNHANDLED_EXCEPTIONS - catch (const std::exception& e) - { - JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); - return 0; - } - catch (...) - { - JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__); - return 0; - } -#endif - - const int modalIndex = modalComponentReturnValueKeys.indexOf (this); - int returnValue = 0; - - if (modalIndex >= 0) - { - modalComponentReturnValueKeys.remove (modalIndex); - returnValue = modalReturnValues.remove (modalIndex); - } - - modalComponentStack.removeValue (this); - - if (prevFocused != 0) - prevFocused->grabKeyboardFocus(); - - return returnValue; + return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); } -void Component::enterModalState (const bool takeKeyboardFocus_) +void Component::enterModalState (const bool takeKeyboardFocus_, ModalComponentManager::Callback* const callback) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. @@ -39840,10 +39803,7 @@ void Component::enterModalState (const bool takeKeyboardFocus_) if (! isCurrentlyModal()) { - modalComponentStack.add (this); - modalComponentReturnValueKeys.add (this); - modalReturnValues.add (0); - + ModalComponentManager::getInstance()->startModal (this, callback); flags.currentlyModalFlag = true; setVisible (true); @@ -39858,20 +39818,7 @@ void Component::exitModalState (const int returnValue) { if (MessageManager::getInstance()->isThisTheMessageThread()) { - const int modalIndex = modalComponentReturnValueKeys.indexOf (this); - - if (modalIndex >= 0) - { - modalReturnValues.set (modalIndex, returnValue); - } - else - { - modalComponentReturnValueKeys.add (this); - modalReturnValues.add (returnValue); - } - - modalComponentStack.removeValue (this); - + ModalComponentManager::getInstance()->endModal (this, returnValue); flags.currentlyModalFlag = false; bringModalComponentToFront(); @@ -39901,14 +39848,12 @@ bool Component::isCurrentlyBlockedByAnotherModalComponent() const int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() throw() { - return modalComponentStack.size(); + return ModalComponentManager::getInstance()->getNumModalComponents(); } Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) throw() { - Component* const c = static_cast (modalComponentStack [modalComponentStack.size() - index - 1]); - - return c->isValidComponent() ? c : 0; + return ModalComponentManager::getInstance()->getModalComponent (index); } void Component::bringModalComponentToFront() @@ -41974,6 +41919,239 @@ END_JUCE_NAMESPACE /*** End of inlined file: juce_Desktop.cpp ***/ +/*** Start of inlined file: juce_ModalComponentManager.cpp ***/ +BEGIN_JUCE_NAMESPACE + +class ModalComponentManager::ModalItem : public ComponentListener +{ +public: + ModalItem (Component* const comp, Callback* const callback) + : component (comp), returnValue (0), isActive (true), isDeleted (false) + { + if (callback != 0) + callbacks.add (callback); + + jassert (comp != 0); + component->addComponentListener (this); + } + + ~ModalItem() + { + if (! isDeleted) + component->removeComponentListener (this); + } + + void componentBeingDeleted (Component&) + { + isDeleted = true; + cancel(); + } + + void componentVisibilityChanged (Component&) + { + if (! component->isShowing()) + cancel(); + } + + void componentParentHierarchyChanged (Component&) + { + if (! component->isShowing()) + cancel(); + } + + void cancel() + { + if (isActive) + { + isActive = false; + ModalComponentManager::getInstance()->triggerAsyncUpdate(); + } + } + + Component* component; + OwnedArray callbacks; + int returnValue; + bool isActive, isDeleted; + +private: + ModalItem (const ModalItem&); + ModalItem& operator= (const ModalItem&); +}; + +ModalComponentManager::ModalComponentManager() +{ +} + +ModalComponentManager::~ModalComponentManager() +{ + clearSingletonInstance(); +} + +juce_ImplementSingleton_SingleThreaded (ModalComponentManager); + +void ModalComponentManager::startModal (Component* component, Callback* callback) +{ + if (component != 0) + stack.add (new ModalItem (component, callback)); +} + +void ModalComponentManager::attachCallback (Component* component, Callback* callback) +{ + if (callback != 0) + { + ScopedPointer callbackDeleter (callback); + + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + { + item->callbacks.add (callback); + callbackDeleter.release(); + break; + } + } + } +} + +void ModalComponentManager::endModal (Component* component) +{ + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + item->cancel(); + } +} + +void ModalComponentManager::endModal (Component* component, int returnValue) +{ + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + { + item->returnValue = returnValue; + item->cancel(); + } + } +} + +int ModalComponentManager::getNumModalComponents() const +{ + int n = 0; + for (int i = 0; i < stack.size(); ++i) + if (stack.getUnchecked(i)->isActive) + ++n; + + return n; +} + +Component* ModalComponentManager::getModalComponent (const int index) const +{ + int n = 0; + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (item->isActive) + if (n++ == index) + return item->component; + } + + return 0; +} + +bool ModalComponentManager::isModal (Component* const comp) const +{ + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (item->isActive && item->component == comp) + return true; + } + + return false; +} + +bool ModalComponentManager::isFrontModalComponent (Component* const comp) const +{ + return comp == getModalComponent (0); +} + +void ModalComponentManager::handleAsyncUpdate() +{ + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (! item->isActive) + { + for (int j = item->callbacks.size(); --j >= 0;) + item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue); + + stack.remove (i); + } + } +} + +class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback +{ +public: + ReturnValueRetriever (int& value_, bool& finished_) : value (value_), finished (finished_) {} + ~ReturnValueRetriever() {} + + void modalStateFinished (int returnValue) + { + finished = true; + value = returnValue; + } + +private: + int& value; + bool& finished; + + ReturnValueRetriever (const ReturnValueRetriever&); + ReturnValueRetriever& operator= (const ReturnValueRetriever&); +}; + +int ModalComponentManager::runEventLoopForCurrentComponent() +{ + // This can only be run from the message thread! + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + Component* currentlyModal = getModalComponent (0); + + if (currentlyModal == 0) + return 0; + + Component::SafePointer prevFocused (Component::getCurrentlyFocusedComponent()); + + int returnValue = 0; + bool finished = false; + attachCallback (currentlyModal, new ReturnValueRetriever (returnValue, finished)); + + JUCE_TRY + { + while (! finished) + { + if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) + break; + } + } + JUCE_CATCH_EXCEPTION + + if (prevFocused != 0) + prevFocused->grabKeyboardFocus(); + + return returnValue; +} + +END_JUCE_NAMESPACE +/*** End of inlined file: juce_ModalComponentManager.cpp ***/ + + /*** Start of inlined file: juce_ArrowButton.cpp ***/ BEGIN_JUCE_NAMESPACE @@ -46659,15 +46837,39 @@ void ComboBox::labelTextChanged (Label*) triggerAsyncUpdate(); } +class ComboBox::Callback : public ModalComponentManager::Callback +{ +public: + Callback (ComboBox* const box_) + : box (box_) + { + } + + void modalStateFinished (int returnValue) + { + if (box != 0) + { + box->menuActive = false; + + if (returnValue != 0) + box->setSelectedId (returnValue); + } + } + +private: + Component::SafePointer box; + + Callback (const Callback&); + Callback& operator= (const Callback&); +}; + void ComboBox::showPopup() { if (! menuActive) { const int selectedId = getSelectedId(); - Component::SafePointer deletionWatcher (this); PopupMenu menu; - menu.setLookAndFeel (&getLookAndFeel()); for (int i = 0; i < items.size(); ++i) @@ -46686,19 +46888,9 @@ void ComboBox::showPopup() if (items.size() == 0) menu.addItem (1, noChoicesMessage, false); - const int itemHeight = jlimit (12, 24, getHeight()); - menuActive = true; - const int resultId = menu.showAt (this, selectedId, - getWidth(), 1, itemHeight); - - if (deletionWatcher == 0) - return; - - menuActive = false; - - if (resultId != 0) - setSelectedId (resultId); + menu.showAt (this, selectedId, getWidth(), 1, jlimit (12, 24, getHeight()), + new Callback (this)); } } @@ -52776,6 +52968,27 @@ void TextEditor::paintOverChildren (Graphics& g) getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); } +class TextEditorMenuPerformer : public ModalComponentManager::Callback +{ +public: + TextEditorMenuPerformer (TextEditor* const editor_) + : editor (editor_) + { + } + + void modalStateFinished (int returnValue) + { + if (editor != 0 && returnValue != 0) + editor->performPopupMenuAction (returnValue); + } + +private: + Component::SafePointer editor; + + TextEditorMenuPerformer (const TextEditorMenuPerformer&); + TextEditorMenuPerformer& operator= (const TextEditorMenuPerformer&); +}; + void TextEditor::mouseDown (const MouseEvent& e) { beginDragAutoRepeat (100); @@ -52794,12 +53007,7 @@ void TextEditor::mouseDown (const MouseEvent& e) m.setLookAndFeel (&getLookAndFeel()); addPopupMenuItems (m, &e); - menuActive = true; - const int result = m.show(); - menuActive = false; - - if (result != 0) - performPopupMenuAction (result); + m.show (0, 0, 0, 0, new TextEditorMenuPerformer (this)); } } } @@ -53219,7 +53427,7 @@ void TextEditor::enablementChanged() UndoManager* TextEditor::getUndoManager() throw() { - return isReadOnly() ? &undoManager : 0; + return isReadOnly() ? 0 : &undoManager; } void TextEditor::clearInternal (UndoManager* const um) @@ -57558,6 +57766,11 @@ FileChooserDialogBox::~FileChooserDialogBox() } bool FileChooserDialogBox::show (int w, int h) +{ + return showAt (-1, -1, w, h); +} + +bool FileChooserDialogBox::showAt (int x, int y, int w, int h) { if (w <= 0) { @@ -57571,7 +57784,10 @@ bool FileChooserDialogBox::show (int w, int h) if (h <= 0) h = 500; - centreWithSize (w, h); + if (x < 0 || y < 0) + centreWithSize (w, h); + else + setBounds (x, y, w, h); const bool ok = (runModalLoop() != 0); setVisible (false); @@ -67191,29 +67407,13 @@ END_JUCE_NAMESPACE /*** Start of inlined file: juce_MenuBarComponent.cpp ***/ BEGIN_JUCE_NAMESPACE -class DummyMenuComponent : public Component -{ - DummyMenuComponent (const DummyMenuComponent&); - DummyMenuComponent& operator= (const DummyMenuComponent&); - -public: - DummyMenuComponent() {} - ~DummyMenuComponent() {} - - void inputAttemptWhenModal() - { - exitModalState (0); - } -}; - MenuBarComponent::MenuBarComponent (MenuBarModel* model_) : model (0), itemUnderMouse (-1), currentPopupIndex (-1), - indexToShowAgain (-1), + topLevelIndexClicked (0), lastMouseX (0), - lastMouseY (0), - inModalState (false) + lastMouseY (0) { setRepaintsOnMouseActivity (true); setWantsKeyboardFocus (false); @@ -67225,9 +67425,12 @@ MenuBarComponent::MenuBarComponent (MenuBarModel* model_) MenuBarComponent::~MenuBarComponent() { setModel (0); - Desktop::getInstance().removeGlobalMouseListener (this); - currentPopup = 0; +} + +MenuBarModel* MenuBarComponent::getModel() const throw() +{ + return model; } void MenuBarComponent::setModel (MenuBarModel* const newModel) @@ -67314,144 +67517,102 @@ void MenuBarComponent::repaintMenuItem (int index) } } -void MenuBarComponent::updateItemUnderMouse (int x, int y) +void MenuBarComponent::setItemUnderMouse (const int index) { - const int newItem = getItemAt (x, y); - - if (itemUnderMouse != newItem) + if (itemUnderMouse != index) { repaintMenuItem (itemUnderMouse); - itemUnderMouse = newItem; + itemUnderMouse = index; repaintMenuItem (itemUnderMouse); } } -void MenuBarComponent::hideCurrentMenu() +void MenuBarComponent::setOpenItem (int index) { - currentPopup = 0; - repaint(); + if (currentPopupIndex != index) + { + repaintMenuItem (currentPopupIndex); + currentPopupIndex = index; + repaintMenuItem (currentPopupIndex); + + if (index >= 0) + Desktop::getInstance().addGlobalMouseListener (this); + else + Desktop::getInstance().removeGlobalMouseListener (this); + } } +void MenuBarComponent::updateItemUnderMouse (int x, int y) +{ + setItemUnderMouse (getItemAt (x, y)); +} + +class MenuBarComponent::AsyncCallback : public ModalComponentManager::Callback +{ +public: + AsyncCallback (MenuBarComponent* const bar_, const int topLevelIndex_) + : bar (bar_), topLevelIndex (topLevelIndex_) + { + } + + ~AsyncCallback() {} + + void modalStateFinished (int returnValue) + { + if (bar != 0) + bar->menuDismissed (topLevelIndex, returnValue); + } + +private: + Component::SafePointer bar; + const int topLevelIndex; + + AsyncCallback (const AsyncCallback&); + AsyncCallback& operator= (const AsyncCallback&); +}; + void MenuBarComponent::showMenu (int index) { if (index != currentPopupIndex) { - if (inModalState) - { - hideCurrentMenu(); - indexToShowAgain = index; - return; - } - - indexToShowAgain = -1; - currentPopupIndex = -1; - itemUnderMouse = index; - currentPopup = 0; + PopupMenu::dismissAllActiveMenus(); menuBarItemsChanged (0); - Component::SafePointer prevFocused (getCurrentlyFocusedComponent()); - Component::SafePointer deletionChecker (this); + setOpenItem (index); + setItemUnderMouse (index); - enterModalState (false); - inModalState = true; - int result = 0; - ApplicationCommandManager* managerOfChosenCommand = 0; - - Desktop::getInstance().addGlobalMouseListener (this); - - for (;;) + if (index >= 0) { - const int x = getScreenX() + xPositions [itemUnderMouse]; - const int w = xPositions [itemUnderMouse + 1] - xPositions [itemUnderMouse]; + PopupMenu m (model->getMenuForIndex (itemUnderMouse, + menuNames [itemUnderMouse])); - currentPopupIndex = itemUnderMouse; - indexToShowAgain = -1; - repaint(); + if (m.lookAndFeel == 0) + m.setLookAndFeel (&getLookAndFeel()); - if (((unsigned int) itemUnderMouse) < (unsigned int) menuNames.size()) - { - PopupMenu m (model->getMenuForIndex (itemUnderMouse, - menuNames [itemUnderMouse])); + const Rectangle itemPos (xPositions [index], 0, xPositions [index + 1] - xPositions [index], getHeight()); - if (m.lookAndFeel == 0) - m.setLookAndFeel (&getLookAndFeel()); - - currentPopup = m.createMenuComponent (x, getScreenY(), - w, getHeight(), - 0, w, 0, 0, - true, this, - &managerOfChosenCommand, - this); - } - - if (currentPopup == 0) - { - currentPopup = new DummyMenuComponent(); - addAndMakeVisible (currentPopup); - } - - currentPopup->enterModalState (false); - currentPopup->toFront (false); // need to do this after making it modal, or it could - // be stuck behind other comps that are already modal.. - result = currentPopup->runModalLoop(); - - if (deletionChecker == 0) - return; - - const int lastPopupIndex = currentPopupIndex; - currentPopup = 0; - currentPopupIndex = -1; - - if (result != 0) - { - topLevelIndexClicked = lastPopupIndex; - break; - } - else if (indexToShowAgain >= 0) - { - menuBarItemsChanged (0); - repaint(); - itemUnderMouse = indexToShowAgain; - - if (((unsigned int) itemUnderMouse) >= (unsigned int) menuNames.size()) - break; - } - else - { - break; - } - } - - Desktop::getInstance().removeGlobalMouseListener (this); - - inModalState = false; - exitModalState (0); - - if (prevFocused != 0) - prevFocused->grabKeyboardFocus(); - - const Point mousePos (getMouseXYRelative()); - updateItemUnderMouse (mousePos.getX(), mousePos.getY()); - repaint(); - - if (result != 0) - { - if (managerOfChosenCommand != 0) - { - ApplicationCommandTarget::InvocationInfo info (result); - info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; - - managerOfChosenCommand->invoke (info, true); - } - - postCommandMessage (result); + m.showMenu (itemPos + getScreenPosition(), + 0, itemPos.getWidth(), 0, 0, true, this, + new AsyncCallback (this, index)); } } } +void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId) +{ + topLevelIndexClicked = topLevelIndex; + postCommandMessage (itemId); +} + void MenuBarComponent::handleCommandMessage (int commandId) { - if (model != 0) + const Point mousePos (getMouseXYRelative()); + updateItemUnderMouse (mousePos.getX(), mousePos.getY()); + + if (! isCurrentlyBlockedByAnotherModalComponent()) + setOpenItem (-1); + + if (commandId != 0 && model != 0) model->menuItemSelected (commandId, topLevelIndexClicked); } @@ -67482,7 +67643,6 @@ void MenuBarComponent::mouseDown (const MouseEvent& e) void MenuBarComponent::mouseDrag (const MouseEvent& e) { const MouseEvent e2 (e.getEventRelativeTo (this)); - const int item = getItemAt (e2.x, e2.y); if (item >= 0) @@ -67495,8 +67655,11 @@ void MenuBarComponent::mouseUp (const MouseEvent& e) updateItemUnderMouse (e2.x, e2.y); - if (itemUnderMouse < 0 && dynamic_cast (static_cast (currentPopup)) != 0) - hideCurrentMenu(); + if (itemUnderMouse < 0 && getLocalBounds().contains (e2.x, e2.y)) + { + setOpenItem (-1); + PopupMenu::dismissAllActiveMenus(); + } } void MenuBarComponent::mouseMove (const MouseEvent& e) @@ -67542,11 +67705,6 @@ bool MenuBarComponent::keyPressed (const KeyPress& key) return used; } -void MenuBarComponent::inputAttemptWhenModal() -{ - hideCurrentMenu(); -} - void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/) { StringArray newNames; @@ -67565,8 +67723,7 @@ void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/) void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/, const ApplicationCommandTarget::InvocationInfo& info) { - if (model == 0 - || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0) + if (model == 0 || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0) return; for (int i = 0; i < menuNames.size(); ++i) @@ -67575,10 +67732,8 @@ void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/, if (menu.containsCommandItem (info.commandID)) { - itemUnderMouse = i; - repaintMenuItem (i); + setItemUnderMouse (i); startTimer (200); - break; } } @@ -67927,8 +68082,7 @@ public: static Window* create (const PopupMenu& menu, const bool dismissOnMouseUp, Window* const owner_, - const int minX, const int maxX, - const int minY, const int maxY, + const Rectangle& target, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, @@ -67966,14 +68120,14 @@ public: mw->componentAttachedTo = componentAttachedTo; mw->componentAttachedToOriginal = componentAttachedTo; - mw->calculateWindowPos (minX, maxX, minY, maxY, alignToRectangle); + mw->calculateWindowPos (target, alignToRectangle); mw->setTopLeftPosition (mw->windowPos.getX(), mw->windowPos.getY()); mw->updateYPositions(); if (itemIdThatMustBeVisible != 0) { - const int y = minY - mw->windowPos.getY(); + const int y = target.getY() - mw->windowPos.getY(); mw->ensureItemIsVisible (itemIdThatMustBeVisible, (((unsigned int) y) < (unsigned int) mw->windowPos.getHeight()) ? y : -1); } @@ -68414,13 +68568,10 @@ private: return false; } - void calculateWindowPos (const int minX, const int maxX, - const int minY, const int maxY, - const bool alignToRectangle) + void calculateWindowPos (const Rectangle& target, const bool alignToRectangle) { const Rectangle mon (Desktop::getInstance() - .getMonitorAreaContaining (Point ((minX + maxX) / 2, - (minY + maxY) / 2), + .getMonitorAreaContaining (target.getCentre(), #if JUCE_MAC true)); #else @@ -68432,19 +68583,19 @@ private: if (alignToRectangle) { - x = minX; + x = target.getX(); - const int spaceUnder = mon.getHeight() - (maxY - mon.getY()); - const int spaceOver = minY - mon.getY(); + const int spaceUnder = mon.getHeight() - (target.getBottom() - mon.getY()); + const int spaceOver = target.getY() - mon.getY(); if (heightToUse < spaceUnder - 30 || spaceUnder >= spaceOver) - y = maxY; + y = target.getBottom(); else - y = minY - heightToUse; + y = target.getY() - heightToUse; } else { - bool tendTowardsRight = (minX + maxX) / 2 < mon.getCentreX(); + bool tendTowardsRight = target.getCentreX() < mon.getCentreX(); if (owner != 0) { @@ -68453,38 +68604,38 @@ private: const bool ownerGoingRight = (owner->getX() + owner->getWidth() / 2 > owner->owner->getX() + owner->owner->getWidth() / 2); - if (ownerGoingRight && maxX + widthToUse < mon.getRight() - 4) + if (ownerGoingRight && target.getRight() + widthToUse < mon.getRight() - 4) tendTowardsRight = true; - else if ((! ownerGoingRight) && minX > widthToUse + 4) + else if ((! ownerGoingRight) && target.getX() > widthToUse + 4) tendTowardsRight = false; } - else if (maxX + widthToUse < mon.getRight() - 32) + else if (target.getRight() + widthToUse < mon.getRight() - 32) { tendTowardsRight = true; } } - const int biggestSpace = jmax (mon.getRight() - maxX, - minX - mon.getX()) - 32; + const int biggestSpace = jmax (mon.getRight() - target.getRight(), + target.getX() - mon.getX()) - 32; if (biggestSpace < widthToUse) { - layoutMenuItems (biggestSpace + (maxX - minX) / 3, widthToUse, heightToUse); + layoutMenuItems (biggestSpace + target.getWidth() / 3, widthToUse, heightToUse); if (numColumns > 1) layoutMenuItems (biggestSpace - 4, widthToUse, heightToUse); - tendTowardsRight = (mon.getRight() - maxX) >= (minX - mon.getX()); + tendTowardsRight = (mon.getRight() - target.getRight()) >= (target.getX() - mon.getX()); } if (tendTowardsRight) - x = jmin (mon.getRight() - widthToUse - 4, maxX); + x = jmin (mon.getRight() - widthToUse - 4, target.getRight()); else - x = jmax (mon.getX() + 4, minX - widthToUse); + x = jmax (mon.getX() + 4, target.getX() - widthToUse); - y = minY; - if ((minY + maxY) / 2 > mon.getCentreY()) - y = jmax (mon.getY(), maxY - heightToUse); + y = target.getY(); + if (target.getCentreY() > mon.getCentreY()) + y = jmax (mon.getY(), target.getBottom() - heightToUse); } x = jmax (mon.getX() + 1, jmin (mon.getRight() - (widthToUse + 6), x)); @@ -68717,13 +68868,10 @@ private: if (childComp->isValidComponent() && childComp->itemInfo.hasActiveSubMenu()) { - const Point topLeft (childComp->relativePositionToGlobal (Point())); - const Point bottomRight (childComp->relativePositionToGlobal (Point (childComp->getWidth(), childComp->getHeight()))); - activeSubMenu = Window::create (*(childComp->itemInfo.subMenu), dismissOnMouseUp, this, - topLeft.getX(), bottomRight.getX(), topLeft.getY(), bottomRight.getY(), + childComp->getScreenBounds(), 0, maximumNumColumns, standardItemHeight, false, 0, menuBarComponent, @@ -69097,7 +69245,7 @@ void PopupMenu::addSectionHeader (const String& title) addCustomItem (0X4734a34f, new HeaderItemComponent (title)); } -Component* PopupMenu::createMenuComponent (const int x, const int y, const int w, const int h, +Component* PopupMenu::createMenuComponent (const Rectangle& target, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, @@ -69107,20 +69255,10 @@ Component* PopupMenu::createMenuComponent (const int x, const int y, const int w ApplicationCommandManager** managerOfChosenCommand, Component* const componentAttachedTo) { - Window* const pw - = Window::create (*this, - ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(), - 0, - x, x + w, - y, y + h, - minimumWidth, - maximumNumColumns, - standardItemHeight, - alignToRectangle, - itemIdThatMustBeVisible, - menuBarComponent, - managerOfChosenCommand, - componentAttachedTo); + Window* const pw = Window::create (*this, ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(), + 0, target, minimumWidth, maximumNumColumns, standardItemHeight, + alignToRectangle, itemIdThatMustBeVisible, menuBarComponent, + managerOfChosenCommand, componentAttachedTo); if (pw != 0) pw->setVisible (true); @@ -69128,56 +69266,86 @@ Component* PopupMenu::createMenuComponent (const int x, const int y, const int w return pw; } -int PopupMenu::showMenu (const int x, const int y, const int w, const int h, +// This invokes any command manager commands and deletes the menu window when it is dismissed +class PopupMenuCompletionCallback : public ModalComponentManager::Callback +{ +public: + PopupMenuCompletionCallback() + : managerOfChosenCommand (0) + { + } + + ~PopupMenuCompletionCallback() {} + + void modalStateFinished (int result) + { + if (managerOfChosenCommand != 0 && result != 0) + { + ApplicationCommandTarget::InvocationInfo info (result); + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; + + managerOfChosenCommand->invoke (info, true); + } + } + + ApplicationCommandManager* managerOfChosenCommand; + ScopedPointer component; + +private: + PopupMenuCompletionCallback (const PopupMenuCompletionCallback&); + PopupMenuCompletionCallback& operator= (const PopupMenuCompletionCallback&); +}; + +int PopupMenu::showMenu (const Rectangle& target, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, const bool alignToRectangle, - Component* const componentAttachedTo) + Component* const componentAttachedTo, + ModalComponentManager::Callback* userCallback) { + ScopedPointer userCallbackDeleter (userCallback); + Component::SafePointer prevFocused (Component::getCurrentlyFocusedComponent()); Component::SafePointer prevTopLevel ((prevFocused != 0) ? prevFocused->getTopLevelComponent() : 0); - Window::wasHiddenBecauseOfAppChange() = false; - int result = 0; - ApplicationCommandManager* managerOfChosenCommand = 0; + PopupMenuCompletionCallback* callback = new PopupMenuCompletionCallback(); + ScopedPointer callbackDeleter (callback); - ScopedPointer popupComp (createMenuComponent (x, y, w, h, - itemIdThatMustBeVisible, - minimumWidth, - maximumNumColumns > 0 ? maximumNumColumns : 7, - standardItemHeight, - alignToRectangle, 0, - &managerOfChosenCommand, - componentAttachedTo)); + callback->component = createMenuComponent (target, + itemIdThatMustBeVisible, + minimumWidth, + maximumNumColumns > 0 ? maximumNumColumns : 7, + standardItemHeight, + alignToRectangle, 0, + &callback->managerOfChosenCommand, + componentAttachedTo); - if (popupComp != 0) + if (callback->component == 0) + return 0; + + callbackDeleter.release(); + + callback->component->enterModalState (false, userCallbackDeleter.release()); + callback->component->toFront (false); // need to do this after making it modal, or it could + // be stuck behind other comps that are already modal.. + + ModalComponentManager::getInstance()->attachCallback (callback->component, callback); + + if (userCallback != 0) + return 0; + + const int result = callback->component->runModalLoop(); + + if (! Window::wasHiddenBecauseOfAppChange()) { - popupComp->enterModalState (false); - popupComp->toFront (false); // need to do this after making it modal, or it could - // be stuck behind other comps that are already modal.. + if (prevTopLevel != 0) + prevTopLevel->toFront (true); - result = popupComp->runModalLoop(); - popupComp = 0; - - if (! Window::wasHiddenBecauseOfAppChange()) - { - if (prevTopLevel != 0) - prevTopLevel->toFront (true); - - if (prevFocused != 0) - prevFocused->grabKeyboardFocus(); - } - } - - if (managerOfChosenCommand != 0 && result != 0) - { - ApplicationCommandTarget::InvocationInfo info (result); - info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; - - managerOfChosenCommand->invoke (info, true); + if (prevFocused != 0) + prevFocused->grabKeyboardFocus(); } return result; @@ -69186,7 +69354,8 @@ int PopupMenu::showMenu (const int x, const int y, const int w, const int h, int PopupMenu::show (const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, - const int standardItemHeight) + const int standardItemHeight, + ModalComponentManager::Callback* callback) { const Point mousePos (Desktop::getMousePosition()); @@ -69194,7 +69363,8 @@ int PopupMenu::show (const int itemIdThatMustBeVisible, itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, - standardItemHeight); + standardItemHeight, + callback); } int PopupMenu::showAt (const int screenX, @@ -69202,39 +69372,39 @@ int PopupMenu::showAt (const int screenX, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, - const int standardItemHeight) + const int standardItemHeight, + ModalComponentManager::Callback* callback) { - return showMenu (screenX, screenY, 1, 1, + return showMenu (Rectangle (screenX, screenY, 1, 1), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, - false, 0); + false, 0, callback); } int PopupMenu::showAt (Component* componentToAttachTo, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, - const int standardItemHeight) + const int standardItemHeight, + ModalComponentManager::Callback* callback) { if (componentToAttachTo != 0) { - return showMenu (componentToAttachTo->getScreenX(), - componentToAttachTo->getScreenY(), - componentToAttachTo->getWidth(), - componentToAttachTo->getHeight(), + return showMenu (componentToAttachTo->getScreenBounds(), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, - true, componentToAttachTo); + true, componentToAttachTo, callback); } else { return show (itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, - standardItemHeight); + standardItemHeight, + callback); } } @@ -89958,7 +90128,7 @@ void Path::addArrow (const Line& line, float lineThickness, startNewSubPath (line.getPointAlongLine (0, lineThickness)); lineTo (line.getPointAlongLine (0, -lineThickness)); - lineTo (reversed.getPointAlongLine (0, lineThickness)); + lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness)); lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); lineTo (line.getEnd()); lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); diff --git a/juce_amalgamated.h b/juce_amalgamated.h index c6130e404c..c3c6925611 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -64,7 +64,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 19 +#define JUCE_BUILDNUMBER 20 /** Current Juce version number. @@ -14254,7 +14254,7 @@ private: static classname* _singletonInstance; \ static JUCE_NAMESPACE::CriticalSection _singletonLock; \ \ - static classname* getInstance() \ + static classname* JUCE_CALLTYPE getInstance() \ { \ if (_singletonInstance == 0) \ {\ @@ -14282,12 +14282,12 @@ private: return _singletonInstance; \ } \ \ - static inline classname* getInstanceWithoutCreating() throw() \ + static inline classname* JUCE_CALLTYPE getInstanceWithoutCreating() throw() \ { \ return _singletonInstance; \ } \ \ - static void deleteInstance() \ + static void JUCE_CALLTYPE deleteInstance() \ { \ const JUCE_NAMESPACE::ScopedLock sl (_singletonLock); \ if (_singletonInstance != 0) \ @@ -24927,6 +24927,167 @@ private: #endif // __JUCE_BORDERSIZE_JUCEHEADER__ /*** End of inlined file: juce_BorderSize.h ***/ + +/*** Start of inlined file: juce_ModalComponentManager.h ***/ +#ifndef __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ +#define __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ + + +/*** Start of inlined file: juce_DeletedAtShutdown.h ***/ +#ifndef __JUCE_DELETEDATSHUTDOWN_JUCEHEADER__ +#define __JUCE_DELETEDATSHUTDOWN_JUCEHEADER__ + +/** + Classes derived from this will be automatically deleted when the application exits. + + After JUCEApplication::shutdown() has been called, any objects derived from + DeletedAtShutdown which are still in existence will be deleted in the reverse + order to that in which they were created. + + So if you've got a singleton and don't want to have to explicitly delete it, just + inherit from this and it'll be taken care of. +*/ +class JUCE_API DeletedAtShutdown +{ +protected: + /** Creates a DeletedAtShutdown object. */ + DeletedAtShutdown(); + + /** Destructor. + + It's ok to delete these objects explicitly - it's only the ones left + dangling at the end that will be deleted automatically. + */ + virtual ~DeletedAtShutdown(); + +public: + /** Deletes all extant objects. + + This shouldn't be used by applications, as it's called automatically + in the shutdown code of the JUCEApplication class. + */ + static void deleteAll(); + +private: + DeletedAtShutdown (const DeletedAtShutdown&); + DeletedAtShutdown& operator= (const DeletedAtShutdown&); + + static CriticalSection& getLock(); + static Array & getObjects(); +}; + +#endif // __JUCE_DELETEDATSHUTDOWN_JUCEHEADER__ +/*** End of inlined file: juce_DeletedAtShutdown.h ***/ + +/** + Manages the system's stack of modal components. + + Normally you'll just use the Component methods to invoke modal states in components, + and won't have to deal with this class directly, but this is the singleton object that's + used internally to manage the stack. + + @see Component::enterModalState, Component::exitModalState, Component::isCurrentlyModal, + Component::getCurrentlyModalComponent, Component::isCurrentlyBlockedByAnotherModalComponent +*/ +class JUCE_API ModalComponentManager : public AsyncUpdater, + public DeletedAtShutdown +{ +public: + + /** Receives callbacks when a modal component is dismissed. + + You can register a callback using Component::enterModalState() or + ModalComponentManager::attachCallback(). + */ + class Callback + { + public: + /** */ + Callback() {} + + /** Destructor. */ + virtual ~Callback() {} + + /** Called to indicate that a modal component has been dismissed. + + You can register a callback using Component::enterModalState() or + ModalComponentManager::attachCallback(). + + The returnValue parameter is the value that was passed to Component::exitModalState() + when the component was dismissed. + + The callback object will be deleted shortly after this method is called. + */ + virtual void modalStateFinished (int returnValue) = 0; + }; + + /** Returns the number of components currently being shown modally. + @see getModalComponent + */ + int getNumModalComponents() const; + + /** Returns one of the components being shown modally. + An index of 0 is the most recently-shown, topmost component. + */ + Component* getModalComponent (int index) const; + + /** Returns true if the specified component is in a modal state. */ + bool isModal (Component* component) const; + + /** Returns true if the specified component is currently the topmost modal component. */ + bool isFrontModalComponent (Component* component) const; + + /** Adds a new callback that will be called when the specified modal component is dismissed. + + If the component is modal, then when it is dismissed, either by being hidden, or by calling + Component::exitModalState(), then the Callback::modalStateFinished() method will be + called. + + Each component can have any number of callbacks associated with it, and this one is added + to that list. + + The object that is passed in will be deleted by the manager when it's no longer needed. If + the given component is not currently modal, the callback object is deleted immediately and + no action is taken. + */ + void attachCallback (Component* component, Callback* callback); + + /** Runs the event loop until the currently topmost modal component is dismissed, and + returns the exit code for that component. + */ + int runEventLoopForCurrentComponent(); + + juce_DeclareSingleton_SingleThreaded_Minimal (ModalComponentManager); + +protected: + /** Creates a ModalComponentManager. + You shouldn't ever call the constructor - it's a singleton, so use ModalComponentManager::getInstance() + */ + ModalComponentManager(); + + /** Destructor. */ + ~ModalComponentManager(); + + /** @internal */ + void handleAsyncUpdate(); + +private: + class ModalItem; + class ReturnValueRetriever; + + friend class Component; + friend class OwnedArray ; + OwnedArray stack; + + void startModal (Component* component, Callback* callback); + void endModal (Component* component, int returnValue); + void endModal (Component* component); + +}; + +#endif // __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ +/*** End of inlined file: juce_ModalComponentManager.h ***/ + class LookAndFeel; class MouseInputSource; class MouseInputSourceInternal; @@ -26576,7 +26737,7 @@ public: passed into exitModalState(). @see enterModalState, exitModalState, isCurrentlyModal, getCurrentlyModalComponent, - isCurrentlyBlockedByAnotherModalComponent, MessageManager::dispatchNextMessage + isCurrentlyBlockedByAnotherModalComponent, ModalComponentManager */ int runModalLoop(); @@ -26590,9 +26751,15 @@ public: get the focus, which is usually what you'll want it to do. If not, it will leave the focus unchanged. - @see exitModalState, runModalLoop + The callback is an optional object which will receive a callback when the modal + component loses its modal status, either by being hidden or when exitModalState() + is called. If you pass an object in here, the system will take care of deleting it + later, after making the callback + + @see exitModalState, runModalLoop, ModalComponentManager::attachCallback */ - void enterModalState (bool takeKeyboardFocus = true); + void enterModalState (bool takeKeyboardFocus = true, + ModalComponentManager::Callback* callback = 0); /** Ends a component's modal state. @@ -27717,53 +27884,6 @@ public: #define __JUCE_DESKTOP_JUCEHEADER__ -/*** Start of inlined file: juce_DeletedAtShutdown.h ***/ -#ifndef __JUCE_DELETEDATSHUTDOWN_JUCEHEADER__ -#define __JUCE_DELETEDATSHUTDOWN_JUCEHEADER__ - -/** - Classes derived from this will be automatically deleted when the application exits. - - After JUCEApplication::shutdown() has been called, any objects derived from - DeletedAtShutdown which are still in existence will be deleted in the reverse - order to that in which they were created. - - So if you've got a singleton and don't want to have to explicitly delete it, just - inherit from this and it'll be taken care of. -*/ -class JUCE_API DeletedAtShutdown -{ -protected: - /** Creates a DeletedAtShutdown object. */ - DeletedAtShutdown(); - - /** Destructor. - - It's ok to delete these objects explicitly - it's only the ones left - dangling at the end that will be deleted automatically. - */ - virtual ~DeletedAtShutdown(); - -public: - /** Deletes all extant objects. - - This shouldn't be used by applications, as it's called automatically - in the shutdown code of the JUCEApplication class. - */ - static void deleteAll(); - -private: - DeletedAtShutdown (const DeletedAtShutdown&); - DeletedAtShutdown& operator= (const DeletedAtShutdown&); - - static CriticalSection& getLock(); - static Array & getObjects(); -}; - -#endif // __JUCE_DELETEDATSHUTDOWN_JUCEHEADER__ -/*** End of inlined file: juce_DeletedAtShutdown.h ***/ - - /*** Start of inlined file: juce_Timer.h ***/ #ifndef __JUCE_TIMER_JUCEHEADER__ #define __JUCE_TIMER_JUCEHEADER__ @@ -34964,12 +35084,19 @@ public: in zero. @param standardItemHeight if this is non-zero, it will be used as the standard height for menu items (apart from custom items) + @param callback if this is non-zero, the menu will be launched asynchronously, + returning immediately, and the callback will receive a + call when the menu is either dismissed or has an item + selected. This object will be owned and deleted by the + system, so make sure that it works safely and that any + pointers that it uses are safely within scope. @see showAt */ int show (int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, - int standardItemHeight = 0); + int standardItemHeight = 0, + ModalComponentManager::Callback* callback = 0); /** Displays the menu at a specific location. @@ -34987,7 +35114,8 @@ public: int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, - int standardItemHeight = 0); + int standardItemHeight = 0, + ModalComponentManager::Callback* callback = 0); /** Displays the menu as if it's attached to a component such as a button. @@ -34999,7 +35127,8 @@ public: int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, - int standardItemHeight = 0); + int standardItemHeight = 0, + ModalComponentManager::Callback* callback = 0); /** Closes any menus that are currently open. @@ -35098,6 +35227,7 @@ private: friend class ItemComponent; friend class Window; friend class PopupMenuCustomComponent; + friend class MenuBarComponent; friend class OwnedArray ; friend class ScopedPointer ; @@ -35107,16 +35237,16 @@ private: void addSeparatorIfPending(); - int showMenu (int x, int y, int w, int h, + int showMenu (const Rectangle& target, int itemIdThatMustBeVisible, int minimumWidth, int maximumNumColumns, int standardItemHeight, bool alignToRectangle, - Component* componentAttachedTo); + Component* componentAttachedTo, + ModalComponentManager::Callback* callback); - friend class MenuBarComponent; - Component* createMenuComponent (int x, int y, int w, int h, + Component* createMenuComponent (const Rectangle& target, int itemIdThatMustBeVisible, int minimumWidth, int maximumNumColumns, @@ -35660,10 +35790,6 @@ public: /** @internal */ bool isTextInputActive() const; - juce_UseDebuggingNewOperator - -protected: - /** This adds the items to the popup menu. By default it adds the cut/copy/paste items, but you can override this if @@ -35701,6 +35827,10 @@ protected: */ virtual void performPopupMenuAction (int menuItemID); + juce_UseDebuggingNewOperator + +protected: + /** Scrolls the minimum distance needed to get the caret into view. */ void scrollToMakeSureCursorIsVisible(); @@ -36462,6 +36592,9 @@ private: bool isRealItem() const throw(); }; + class Callback; + friend class Callback; + OwnedArray items; Value currentId; int lastCurrentId; @@ -50012,7 +50145,16 @@ public: Leave the width or height as 0 to use the default size */ - bool show (int width = 0,int height = 0); + bool show (int width = 0, int height = 0); + + /** Displays and runs the dialog box modally. + + This will show the box with the specified size at the specified location, + returning true if the user pressed 'ok', or false if they cancelled. + + Leave the width or height as 0 to use the default size. + */ + bool showAt (int x, int y, int width, int height); /** A set of colour IDs to use to change the colour of various aspects of the box. @@ -50628,6 +50770,9 @@ private: #endif #ifndef __JUCE_DESKTOP_JUCEHEADER__ +#endif +#ifndef __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ + #endif #ifndef __JUCE_KEYBOARDFOCUSTRAVERSER_JUCEHEADER__ @@ -51809,6 +51954,10 @@ public: */ void setModel (MenuBarModel* newModel); + /** Returns the current menu bar model being used. + */ + MenuBarModel* getModel() const throw(); + /** Pops up one of the menu items. This lets you manually open one of the menus - it could be triggered by a @@ -51833,8 +51982,6 @@ public: /** @internal */ void mouseMove (const MouseEvent& e); /** @internal */ - void inputAttemptWhenModal(); - /** @internal */ void handleCommandMessage (int commandId); /** @internal */ bool keyPressed (const KeyPress& key); @@ -51847,20 +51994,22 @@ public: juce_UseDebuggingNewOperator private: + class AsyncCallback; + friend class AsyncCallback; MenuBarModel* model; StringArray menuNames; Array xPositions; - int itemUnderMouse, currentPopupIndex, topLevelIndexClicked, indexToShowAgain; + int itemUnderMouse, currentPopupIndex, topLevelIndexClicked; int lastMouseX, lastMouseY; - bool inModalState; - ScopedPointer currentPopup; int getItemAt (int x, int y); + void setItemUnderMouse (int index); + void setOpenItem (int index); void updateItemUnderMouse (int x, int y); - void hideCurrentMenu(); void timerCallback(); void repaintMenuItem (int index); + void menuDismissed (int topLevelIndex, int itemId); MenuBarComponent (const MenuBarComponent&); MenuBarComponent& operator= (const MenuBarComponent&); diff --git a/src/core/juce_Singleton.h b/src/core/juce_Singleton.h index d2b52cbb7e..9c91b05197 100644 --- a/src/core/juce_Singleton.h +++ b/src/core/juce_Singleton.h @@ -93,7 +93,7 @@ static classname* _singletonInstance; \ static JUCE_NAMESPACE::CriticalSection _singletonLock; \ \ - static classname* getInstance() \ + static classname* JUCE_CALLTYPE getInstance() \ { \ if (_singletonInstance == 0) \ {\ @@ -121,12 +121,12 @@ return _singletonInstance; \ } \ \ - static inline classname* getInstanceWithoutCreating() throw() \ + static inline classname* JUCE_CALLTYPE getInstanceWithoutCreating() throw() \ { \ return _singletonInstance; \ } \ \ - static void deleteInstance() \ + static void JUCE_CALLTYPE deleteInstance() \ { \ const JUCE_NAMESPACE::ScopedLock sl (_singletonLock); \ if (_singletonInstance != 0) \ diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index 179a924aba..3ef52e264a 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 19 +#define JUCE_BUILDNUMBER 20 /** Current Juce version number. diff --git a/src/gui/components/controls/juce_ComboBox.cpp b/src/gui/components/controls/juce_ComboBox.cpp index 98dad7bbb9..395b335896 100644 --- a/src/gui/components/controls/juce_ComboBox.cpp +++ b/src/gui/components/controls/juce_ComboBox.cpp @@ -525,15 +525,40 @@ void ComboBox::labelTextChanged (Label*) //============================================================================== +class ComboBox::Callback : public ModalComponentManager::Callback +{ +public: + Callback (ComboBox* const box_) + : box (box_) + { + } + + void modalStateFinished (int returnValue) + { + if (box != 0) + { + box->menuActive = false; + + if (returnValue != 0) + box->setSelectedId (returnValue); + } + } + +private: + Component::SafePointer box; + + Callback (const Callback&); + Callback& operator= (const Callback&); +}; + + void ComboBox::showPopup() { if (! menuActive) { const int selectedId = getSelectedId(); - Component::SafePointer deletionWatcher (this); PopupMenu menu; - menu.setLookAndFeel (&getLookAndFeel()); for (int i = 0; i < items.size(); ++i) @@ -552,19 +577,9 @@ void ComboBox::showPopup() if (items.size() == 0) menu.addItem (1, noChoicesMessage, false); - const int itemHeight = jlimit (12, 24, getHeight()); - menuActive = true; - const int resultId = menu.showAt (this, selectedId, - getWidth(), 1, itemHeight); - - if (deletionWatcher == 0) - return; - - menuActive = false; - - if (resultId != 0) - setSelectedId (resultId); + menu.showAt (this, selectedId, getWidth(), 1, jlimit (12, 24, getHeight()), + new Callback (this)); } } diff --git a/src/gui/components/controls/juce_ComboBox.h b/src/gui/components/controls/juce_ComboBox.h index 4841f252b4..dc2bc25a4a 100644 --- a/src/gui/components/controls/juce_ComboBox.h +++ b/src/gui/components/controls/juce_ComboBox.h @@ -388,6 +388,9 @@ private: bool isRealItem() const throw(); }; + class Callback; + friend class Callback; + OwnedArray items; Value currentId; int lastCurrentId; diff --git a/src/gui/components/controls/juce_TextEditor.cpp b/src/gui/components/controls/juce_TextEditor.cpp index 8a12261cfe..ec4b9cd58a 100644 --- a/src/gui/components/controls/juce_TextEditor.cpp +++ b/src/gui/components/controls/juce_TextEditor.cpp @@ -1773,6 +1773,28 @@ void TextEditor::paintOverChildren (Graphics& g) } //============================================================================== +class TextEditorMenuPerformer : public ModalComponentManager::Callback +{ +public: + TextEditorMenuPerformer (TextEditor* const editor_) + : editor (editor_) + { + } + + void modalStateFinished (int returnValue) + { + if (editor != 0 && returnValue != 0) + editor->performPopupMenuAction (returnValue); + } + +private: + Component::SafePointer editor; + + TextEditorMenuPerformer (const TextEditorMenuPerformer&); + TextEditorMenuPerformer& operator= (const TextEditorMenuPerformer&); +}; + + void TextEditor::mouseDown (const MouseEvent& e) { beginDragAutoRepeat (100); @@ -1791,12 +1813,7 @@ void TextEditor::mouseDown (const MouseEvent& e) m.setLookAndFeel (&getLookAndFeel()); addPopupMenuItems (m, &e); - menuActive = true; - const int result = m.show(); - menuActive = false; - - if (result != 0) - performPopupMenuAction (result); + m.show (0, 0, 0, 0, new TextEditorMenuPerformer (this)); } } } @@ -2221,7 +2238,7 @@ void TextEditor::enablementChanged() //============================================================================== UndoManager* TextEditor::getUndoManager() throw() { - return isReadOnly() ? &undoManager : 0; + return isReadOnly() ? 0 : &undoManager; } void TextEditor::clearInternal (UndoManager* const um) diff --git a/src/gui/components/controls/juce_TextEditor.h b/src/gui/components/controls/juce_TextEditor.h index 6ea365b90f..031e582c85 100644 --- a/src/gui/components/controls/juce_TextEditor.h +++ b/src/gui/components/controls/juce_TextEditor.h @@ -538,9 +538,6 @@ public: /** @internal */ bool isTextInputActive() const; - juce_UseDebuggingNewOperator - -protected: //============================================================================== /** This adds the items to the popup menu. @@ -579,6 +576,10 @@ protected: */ virtual void performPopupMenuAction (int menuItemID); + //============================================================================== + juce_UseDebuggingNewOperator + +protected: //============================================================================== /** Scrolls the minimum distance needed to get the caret into view. */ void scrollToMakeSureCursorIsVisible(); diff --git a/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp b/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp index 6f28a16f73..b83ea71659 100644 --- a/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp +++ b/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp @@ -75,6 +75,11 @@ FileChooserDialogBox::~FileChooserDialogBox() //============================================================================== bool FileChooserDialogBox::show (int w, int h) +{ + return showAt (-1, -1, w, h); +} + +bool FileChooserDialogBox::showAt (int x, int y, int w, int h) { if (w <= 0) { @@ -88,7 +93,10 @@ bool FileChooserDialogBox::show (int w, int h) if (h <= 0) h = 500; - centreWithSize (w, h); + if (x < 0 || y < 0) + centreWithSize (w, h); + else + setBounds (x, y, w, h); const bool ok = (runModalLoop() != 0); setVisible (false); diff --git a/src/gui/components/filebrowser/juce_FileChooserDialogBox.h b/src/gui/components/filebrowser/juce_FileChooserDialogBox.h index b24ba49853..3baf0de6f8 100644 --- a/src/gui/components/filebrowser/juce_FileChooserDialogBox.h +++ b/src/gui/components/filebrowser/juce_FileChooserDialogBox.h @@ -103,8 +103,16 @@ public: Leave the width or height as 0 to use the default size */ - bool show (int width = 0,int height = 0); + bool show (int width = 0, int height = 0); + /** Displays and runs the dialog box modally. + + This will show the box with the specified size at the specified location, + returning true if the user pressed 'ok', or false if they cancelled. + + Leave the width or height as 0 to use the default size. + */ + bool showAt (int x, int y, int width, int height); //============================================================================== /** A set of colour IDs to use to change the colour of various aspects of the box. diff --git a/src/gui/components/juce_Component.cpp b/src/gui/components/juce_Component.cpp index 7ae6448d49..cc12be2fc7 100644 --- a/src/gui/components/juce_Component.cpp +++ b/src/gui/components/juce_Component.cpp @@ -39,15 +39,12 @@ BEGIN_JUCE_NAMESPACE #include "../../events/juce_MessageManager.h" #include "../../events/juce_Timer.h" #include "../../core/juce_Time.h" -#include "../../core/juce_Singleton.h" #include "../../core/juce_PlatformUtilities.h" #include "mouse/juce_MouseInputSource.h" -//============================================================================== -Component* Component::currentlyFocusedComponent = 0; -static Array modalComponentStack, modalComponentReturnValueKeys; -static Array modalReturnValues; +//============================================================================== +#define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); enum ComponentMessageNumbers { @@ -55,10 +52,8 @@ enum ComponentMessageNumbers exitModalStateMessage = 0x7fff0002 }; -//============================================================================== -#define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); - static uint32 nextComponentUID = 0; +Component* Component::currentlyFocusedComponent = 0; //============================================================================== @@ -106,8 +101,6 @@ Component::~Component() if (flags.hasHeavyweightPeerFlag) removeFromDesktop(); - modalComponentStack.removeValue (this); - for (int i = childComponentList_.size(); --i >= 0;) childComponentList_.getUnchecked(i)->parentComponent_ = 0; @@ -1330,59 +1323,17 @@ int Component::runModalLoop() if (! MessageManager::getInstance()->isThisTheMessageThread()) { // use a callback so this can be called from non-gui threads - return (int) (pointer_sized_int) - MessageManager::getInstance() - ->callFunctionOnMessageThread (&runModalLoopCallback, this); + return (int) (pointer_sized_int) MessageManager::getInstance() + ->callFunctionOnMessageThread (&runModalLoopCallback, this); } - SafePointer prevFocused (getCurrentlyFocusedComponent()); - if (! isCurrentlyModal()) - enterModalState(); + enterModalState (true); - JUCE_TRY - { - while (flags.currentlyModalFlag && flags.visibleFlag) - { - if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) - break; - - // check whether this component was deleted during the last message - if (! isValidMessageListener()) - break; - } - } -#if JUCE_CATCH_UNHANDLED_EXCEPTIONS - catch (const std::exception& e) - { - JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); - return 0; - } - catch (...) - { - JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__); - return 0; - } -#endif - - const int modalIndex = modalComponentReturnValueKeys.indexOf (this); - int returnValue = 0; - - if (modalIndex >= 0) - { - modalComponentReturnValueKeys.remove (modalIndex); - returnValue = modalReturnValues.remove (modalIndex); - } - - modalComponentStack.removeValue (this); - - if (prevFocused != 0) - prevFocused->grabKeyboardFocus(); - - return returnValue; + return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); } -void Component::enterModalState (const bool takeKeyboardFocus_) +void Component::enterModalState (const bool takeKeyboardFocus_, ModalComponentManager::Callback* const callback) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. @@ -1394,10 +1345,7 @@ void Component::enterModalState (const bool takeKeyboardFocus_) if (! isCurrentlyModal()) { - modalComponentStack.add (this); - modalComponentReturnValueKeys.add (this); - modalReturnValues.add (0); - + ModalComponentManager::getInstance()->startModal (this, callback); flags.currentlyModalFlag = true; setVisible (true); @@ -1412,20 +1360,7 @@ void Component::exitModalState (const int returnValue) { if (MessageManager::getInstance()->isThisTheMessageThread()) { - const int modalIndex = modalComponentReturnValueKeys.indexOf (this); - - if (modalIndex >= 0) - { - modalReturnValues.set (modalIndex, returnValue); - } - else - { - modalComponentReturnValueKeys.add (this); - modalReturnValues.add (returnValue); - } - - modalComponentStack.removeValue (this); - + ModalComponentManager::getInstance()->endModal (this, returnValue); flags.currentlyModalFlag = false; bringModalComponentToFront(); @@ -1455,14 +1390,12 @@ bool Component::isCurrentlyBlockedByAnotherModalComponent() const int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() throw() { - return modalComponentStack.size(); + return ModalComponentManager::getInstance()->getNumModalComponents(); } Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) throw() { - Component* const c = static_cast (modalComponentStack [modalComponentStack.size() - index - 1]); - - return c->isValidComponent() ? c : 0; + return ModalComponentManager::getInstance()->getModalComponent (index); } void Component::bringModalComponentToFront() diff --git a/src/gui/components/juce_Component.h b/src/gui/components/juce_Component.h index 3485d85a20..7ef57b1d82 100644 --- a/src/gui/components/juce_Component.h +++ b/src/gui/components/juce_Component.h @@ -41,6 +41,8 @@ #include "../../text/juce_StringArray.h" #include "../../containers/juce_Array.h" #include "../../containers/juce_NamedValueSet.h" +#include "juce_ModalComponentManager.h" + class LookAndFeel; class MouseInputSource; class MouseInputSourceInternal; @@ -1728,7 +1730,7 @@ public: passed into exitModalState(). @see enterModalState, exitModalState, isCurrentlyModal, getCurrentlyModalComponent, - isCurrentlyBlockedByAnotherModalComponent, MessageManager::dispatchNextMessage + isCurrentlyBlockedByAnotherModalComponent, ModalComponentManager */ int runModalLoop(); @@ -1742,9 +1744,15 @@ public: get the focus, which is usually what you'll want it to do. If not, it will leave the focus unchanged. - @see exitModalState, runModalLoop + The callback is an optional object which will receive a callback when the modal + component loses its modal status, either by being hidden or when exitModalState() + is called. If you pass an object in here, the system will take care of deleting it + later, after making the callback + + @see exitModalState, runModalLoop, ModalComponentManager::attachCallback */ - void enterModalState (bool takeKeyboardFocus = true); + void enterModalState (bool takeKeyboardFocus = true, + ModalComponentManager::Callback* callback = 0); /** Ends a component's modal state. diff --git a/src/gui/components/juce_ModalComponentManager.cpp b/src/gui/components/juce_ModalComponentManager.cpp new file mode 100644 index 0000000000..ce525f9373 --- /dev/null +++ b/src/gui/components/juce_ModalComponentManager.cpp @@ -0,0 +1,267 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-10 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#include "../../core/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_Component.h" +#include "juce_ModalComponentManager.h" +#include "../../events/juce_MessageManager.h" +#include "../../application/juce_Application.h" + + +//============================================================================== +class ModalComponentManager::ModalItem : public ComponentListener +{ +public: + ModalItem (Component* const comp, Callback* const callback) + : component (comp), returnValue (0), isActive (true), isDeleted (false) + { + if (callback != 0) + callbacks.add (callback); + + jassert (comp != 0); + component->addComponentListener (this); + } + + ~ModalItem() + { + if (! isDeleted) + component->removeComponentListener (this); + } + + void componentBeingDeleted (Component&) + { + isDeleted = true; + cancel(); + } + + void componentVisibilityChanged (Component&) + { + if (! component->isShowing()) + cancel(); + } + + void componentParentHierarchyChanged (Component&) + { + if (! component->isShowing()) + cancel(); + } + + void cancel() + { + if (isActive) + { + isActive = false; + ModalComponentManager::getInstance()->triggerAsyncUpdate(); + } + } + + Component* component; + OwnedArray callbacks; + int returnValue; + bool isActive, isDeleted; + +private: + ModalItem (const ModalItem&); + ModalItem& operator= (const ModalItem&); +}; + +//============================================================================== +ModalComponentManager::ModalComponentManager() +{ +} + +ModalComponentManager::~ModalComponentManager() +{ + clearSingletonInstance(); +} + +juce_ImplementSingleton_SingleThreaded (ModalComponentManager); + + +//============================================================================== +void ModalComponentManager::startModal (Component* component, Callback* callback) +{ + if (component != 0) + stack.add (new ModalItem (component, callback)); +} + +void ModalComponentManager::attachCallback (Component* component, Callback* callback) +{ + if (callback != 0) + { + ScopedPointer callbackDeleter (callback); + + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + { + item->callbacks.add (callback); + callbackDeleter.release(); + break; + } + } + } +} + +void ModalComponentManager::endModal (Component* component) +{ + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + item->cancel(); + } +} + +void ModalComponentManager::endModal (Component* component, int returnValue) +{ + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + { + item->returnValue = returnValue; + item->cancel(); + } + } +} + +int ModalComponentManager::getNumModalComponents() const +{ + int n = 0; + for (int i = 0; i < stack.size(); ++i) + if (stack.getUnchecked(i)->isActive) + ++n; + + return n; +} + +Component* ModalComponentManager::getModalComponent (const int index) const +{ + int n = 0; + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (item->isActive) + if (n++ == index) + return item->component; + } + + return 0; +} + +bool ModalComponentManager::isModal (Component* const comp) const +{ + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (item->isActive && item->component == comp) + return true; + } + + return false; +} + +bool ModalComponentManager::isFrontModalComponent (Component* const comp) const +{ + return comp == getModalComponent (0); +} + +void ModalComponentManager::handleAsyncUpdate() +{ + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (! item->isActive) + { + for (int j = item->callbacks.size(); --j >= 0;) + item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue); + + stack.remove (i); + } + } +} + +class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback +{ +public: + ReturnValueRetriever (int& value_, bool& finished_) : value (value_), finished (finished_) {} + ~ReturnValueRetriever() {} + + void modalStateFinished (int returnValue) + { + finished = true; + value = returnValue; + } + +private: + int& value; + bool& finished; + + ReturnValueRetriever (const ReturnValueRetriever&); + ReturnValueRetriever& operator= (const ReturnValueRetriever&); +}; + +int ModalComponentManager::runEventLoopForCurrentComponent() +{ + // This can only be run from the message thread! + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + Component* currentlyModal = getModalComponent (0); + + if (currentlyModal == 0) + return 0; + + Component::SafePointer prevFocused (Component::getCurrentlyFocusedComponent()); + + int returnValue = 0; + bool finished = false; + attachCallback (currentlyModal, new ReturnValueRetriever (returnValue, finished)); + + JUCE_TRY + { + while (! finished) + { + if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) + break; + } + } + JUCE_CATCH_EXCEPTION + + if (prevFocused != 0) + prevFocused->grabKeyboardFocus(); + + return returnValue; +} + + +END_JUCE_NAMESPACE diff --git a/src/gui/components/juce_ModalComponentManager.h b/src/gui/components/juce_ModalComponentManager.h new file mode 100644 index 0000000000..8baf256d9a --- /dev/null +++ b/src/gui/components/juce_ModalComponentManager.h @@ -0,0 +1,144 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-10 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ +#define __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ + +#include "../../core/juce_Singleton.h" +#include "../../events/juce_AsyncUpdater.h" +#include "../../utilities/juce_DeletedAtShutdown.h" + + +//============================================================================== +/** + Manages the system's stack of modal components. + + Normally you'll just use the Component methods to invoke modal states in components, + and won't have to deal with this class directly, but this is the singleton object that's + used internally to manage the stack. + + @see Component::enterModalState, Component::exitModalState, Component::isCurrentlyModal, + Component::getCurrentlyModalComponent, Component::isCurrentlyBlockedByAnotherModalComponent +*/ +class JUCE_API ModalComponentManager : public AsyncUpdater, + public DeletedAtShutdown +{ +public: + //============================================================================== + /** Receives callbacks when a modal component is dismissed. + + You can register a callback using Component::enterModalState() or + ModalComponentManager::attachCallback(). + */ + class Callback + { + public: + /** */ + Callback() {} + + /** Destructor. */ + virtual ~Callback() {} + + /** Called to indicate that a modal component has been dismissed. + + You can register a callback using Component::enterModalState() or + ModalComponentManager::attachCallback(). + + The returnValue parameter is the value that was passed to Component::exitModalState() + when the component was dismissed. + + The callback object will be deleted shortly after this method is called. + */ + virtual void modalStateFinished (int returnValue) = 0; + }; + + //============================================================================== + /** Returns the number of components currently being shown modally. + @see getModalComponent + */ + int getNumModalComponents() const; + + /** Returns one of the components being shown modally. + An index of 0 is the most recently-shown, topmost component. + */ + Component* getModalComponent (int index) const; + + /** Returns true if the specified component is in a modal state. */ + bool isModal (Component* component) const; + + /** Returns true if the specified component is currently the topmost modal component. */ + bool isFrontModalComponent (Component* component) const; + + /** Adds a new callback that will be called when the specified modal component is dismissed. + + If the component is modal, then when it is dismissed, either by being hidden, or by calling + Component::exitModalState(), then the Callback::modalStateFinished() method will be + called. + + Each component can have any number of callbacks associated with it, and this one is added + to that list. + + The object that is passed in will be deleted by the manager when it's no longer needed. If + the given component is not currently modal, the callback object is deleted immediately and + no action is taken. + */ + void attachCallback (Component* component, Callback* callback); + + /** Runs the event loop until the currently topmost modal component is dismissed, and + returns the exit code for that component. + */ + int runEventLoopForCurrentComponent(); + + //============================================================================== + juce_DeclareSingleton_SingleThreaded_Minimal (ModalComponentManager); + +protected: + /** Creates a ModalComponentManager. + You shouldn't ever call the constructor - it's a singleton, so use ModalComponentManager::getInstance() + */ + ModalComponentManager(); + + /** Destructor. */ + ~ModalComponentManager(); + + /** @internal */ + void handleAsyncUpdate(); + +private: + class ModalItem; + class ReturnValueRetriever; + + friend class Component; + friend class OwnedArray ; + OwnedArray stack; + + void startModal (Component* component, Callback* callback); + void endModal (Component* component, int returnValue); + void endModal (Component* component); + +}; + + +#endif // __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ diff --git a/src/gui/components/menus/juce_MenuBarComponent.cpp b/src/gui/components/menus/juce_MenuBarComponent.cpp index 2604157c72..f04e9754d1 100644 --- a/src/gui/components/menus/juce_MenuBarComponent.cpp +++ b/src/gui/components/menus/juce_MenuBarComponent.cpp @@ -32,31 +32,14 @@ BEGIN_JUCE_NAMESPACE #include "../lookandfeel/juce_LookAndFeel.h" -//============================================================================== -class DummyMenuComponent : public Component -{ - DummyMenuComponent (const DummyMenuComponent&); - DummyMenuComponent& operator= (const DummyMenuComponent&); - -public: - DummyMenuComponent() {} - ~DummyMenuComponent() {} - - void inputAttemptWhenModal() - { - exitModalState (0); - } -}; - //============================================================================== MenuBarComponent::MenuBarComponent (MenuBarModel* model_) : model (0), itemUnderMouse (-1), currentPopupIndex (-1), - indexToShowAgain (-1), + topLevelIndexClicked (0), lastMouseX (0), - lastMouseY (0), - inModalState (false) + lastMouseY (0) { setRepaintsOnMouseActivity (true); setWantsKeyboardFocus (false); @@ -68,9 +51,12 @@ MenuBarComponent::MenuBarComponent (MenuBarModel* model_) MenuBarComponent::~MenuBarComponent() { setModel (0); - Desktop::getInstance().removeGlobalMouseListener (this); - currentPopup = 0; +} + +MenuBarModel* MenuBarComponent::getModel() const throw() +{ + return model; } void MenuBarComponent::setModel (MenuBarModel* const newModel) @@ -158,144 +144,102 @@ void MenuBarComponent::repaintMenuItem (int index) } } -void MenuBarComponent::updateItemUnderMouse (int x, int y) +void MenuBarComponent::setItemUnderMouse (const int index) { - const int newItem = getItemAt (x, y); - - if (itemUnderMouse != newItem) + if (itemUnderMouse != index) { repaintMenuItem (itemUnderMouse); - itemUnderMouse = newItem; + itemUnderMouse = index; repaintMenuItem (itemUnderMouse); } } -void MenuBarComponent::hideCurrentMenu() +void MenuBarComponent::setOpenItem (int index) { - currentPopup = 0; - repaint(); + if (currentPopupIndex != index) + { + repaintMenuItem (currentPopupIndex); + currentPopupIndex = index; + repaintMenuItem (currentPopupIndex); + + if (index >= 0) + Desktop::getInstance().addGlobalMouseListener (this); + else + Desktop::getInstance().removeGlobalMouseListener (this); + } } +void MenuBarComponent::updateItemUnderMouse (int x, int y) +{ + setItemUnderMouse (getItemAt (x, y)); +} + +class MenuBarComponent::AsyncCallback : public ModalComponentManager::Callback +{ +public: + AsyncCallback (MenuBarComponent* const bar_, const int topLevelIndex_) + : bar (bar_), topLevelIndex (topLevelIndex_) + { + } + + ~AsyncCallback() {} + + void modalStateFinished (int returnValue) + { + if (bar != 0) + bar->menuDismissed (topLevelIndex, returnValue); + } + +private: + Component::SafePointer bar; + const int topLevelIndex; + + AsyncCallback (const AsyncCallback&); + AsyncCallback& operator= (const AsyncCallback&); +}; + void MenuBarComponent::showMenu (int index) { if (index != currentPopupIndex) { - if (inModalState) - { - hideCurrentMenu(); - indexToShowAgain = index; - return; - } - - indexToShowAgain = -1; - currentPopupIndex = -1; - itemUnderMouse = index; - currentPopup = 0; + PopupMenu::dismissAllActiveMenus(); menuBarItemsChanged (0); - Component::SafePointer prevFocused (getCurrentlyFocusedComponent()); - Component::SafePointer deletionChecker (this); + setOpenItem (index); + setItemUnderMouse (index); - enterModalState (false); - inModalState = true; - int result = 0; - ApplicationCommandManager* managerOfChosenCommand = 0; - - Desktop::getInstance().addGlobalMouseListener (this); - - for (;;) + if (index >= 0) { - const int x = getScreenX() + xPositions [itemUnderMouse]; - const int w = xPositions [itemUnderMouse + 1] - xPositions [itemUnderMouse]; + PopupMenu m (model->getMenuForIndex (itemUnderMouse, + menuNames [itemUnderMouse])); - currentPopupIndex = itemUnderMouse; - indexToShowAgain = -1; - repaint(); + if (m.lookAndFeel == 0) + m.setLookAndFeel (&getLookAndFeel()); - if (((unsigned int) itemUnderMouse) < (unsigned int) menuNames.size()) - { - PopupMenu m (model->getMenuForIndex (itemUnderMouse, - menuNames [itemUnderMouse])); + const Rectangle itemPos (xPositions [index], 0, xPositions [index + 1] - xPositions [index], getHeight()); - if (m.lookAndFeel == 0) - m.setLookAndFeel (&getLookAndFeel()); - - currentPopup = m.createMenuComponent (x, getScreenY(), - w, getHeight(), - 0, w, 0, 0, - true, this, - &managerOfChosenCommand, - this); - } - - if (currentPopup == 0) - { - currentPopup = new DummyMenuComponent(); - addAndMakeVisible (currentPopup); - } - - currentPopup->enterModalState (false); - currentPopup->toFront (false); // need to do this after making it modal, or it could - // be stuck behind other comps that are already modal.. - result = currentPopup->runModalLoop(); - - if (deletionChecker == 0) - return; - - const int lastPopupIndex = currentPopupIndex; - currentPopup = 0; - currentPopupIndex = -1; - - if (result != 0) - { - topLevelIndexClicked = lastPopupIndex; - break; - } - else if (indexToShowAgain >= 0) - { - menuBarItemsChanged (0); - repaint(); - itemUnderMouse = indexToShowAgain; - - if (((unsigned int) itemUnderMouse) >= (unsigned int) menuNames.size()) - break; - } - else - { - break; - } - } - - Desktop::getInstance().removeGlobalMouseListener (this); - - inModalState = false; - exitModalState (0); - - if (prevFocused != 0) - prevFocused->grabKeyboardFocus(); - - const Point mousePos (getMouseXYRelative()); - updateItemUnderMouse (mousePos.getX(), mousePos.getY()); - repaint(); - - if (result != 0) - { - if (managerOfChosenCommand != 0) - { - ApplicationCommandTarget::InvocationInfo info (result); - info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; - - managerOfChosenCommand->invoke (info, true); - } - - postCommandMessage (result); + m.showMenu (itemPos + getScreenPosition(), + 0, itemPos.getWidth(), 0, 0, true, this, + new AsyncCallback (this, index)); } } } +void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId) +{ + topLevelIndexClicked = topLevelIndex; + postCommandMessage (itemId); +} + void MenuBarComponent::handleCommandMessage (int commandId) { - if (model != 0) + const Point mousePos (getMouseXYRelative()); + updateItemUnderMouse (mousePos.getX(), mousePos.getY()); + + if (! isCurrentlyBlockedByAnotherModalComponent()) + setOpenItem (-1); + + if (commandId != 0 && model != 0) model->menuItemSelected (commandId, topLevelIndexClicked); } @@ -327,7 +271,6 @@ void MenuBarComponent::mouseDown (const MouseEvent& e) void MenuBarComponent::mouseDrag (const MouseEvent& e) { const MouseEvent e2 (e.getEventRelativeTo (this)); - const int item = getItemAt (e2.x, e2.y); if (item >= 0) @@ -340,8 +283,11 @@ void MenuBarComponent::mouseUp (const MouseEvent& e) updateItemUnderMouse (e2.x, e2.y); - if (itemUnderMouse < 0 && dynamic_cast (static_cast (currentPopup)) != 0) - hideCurrentMenu(); + if (itemUnderMouse < 0 && getLocalBounds().contains (e2.x, e2.y)) + { + setOpenItem (-1); + PopupMenu::dismissAllActiveMenus(); + } } void MenuBarComponent::mouseMove (const MouseEvent& e) @@ -387,11 +333,6 @@ bool MenuBarComponent::keyPressed (const KeyPress& key) return used; } -void MenuBarComponent::inputAttemptWhenModal() -{ - hideCurrentMenu(); -} - void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/) { StringArray newNames; @@ -410,8 +351,7 @@ void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/) void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/, const ApplicationCommandTarget::InvocationInfo& info) { - if (model == 0 - || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0) + if (model == 0 || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0) return; for (int i = 0; i < menuNames.size(); ++i) @@ -420,10 +360,8 @@ void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/, if (menu.containsCommandItem (info.commandID)) { - itemUnderMouse = i; - repaintMenuItem (i); + setItemUnderMouse (i); startTimer (200); - break; } } diff --git a/src/gui/components/menus/juce_MenuBarComponent.h b/src/gui/components/menus/juce_MenuBarComponent.h index 95d5ca61d6..0bc8857efe 100644 --- a/src/gui/components/menus/juce_MenuBarComponent.h +++ b/src/gui/components/menus/juce_MenuBarComponent.h @@ -60,6 +60,10 @@ public: */ void setModel (MenuBarModel* newModel); + /** Returns the current menu bar model being used. + */ + MenuBarModel* getModel() const throw(); + //============================================================================== /** Pops up one of the menu items. @@ -86,8 +90,6 @@ public: /** @internal */ void mouseMove (const MouseEvent& e); /** @internal */ - void inputAttemptWhenModal(); - /** @internal */ void handleCommandMessage (int commandId); /** @internal */ bool keyPressed (const KeyPress& key); @@ -102,20 +104,22 @@ public: juce_UseDebuggingNewOperator private: + class AsyncCallback; + friend class AsyncCallback; MenuBarModel* model; StringArray menuNames; Array xPositions; - int itemUnderMouse, currentPopupIndex, topLevelIndexClicked, indexToShowAgain; + int itemUnderMouse, currentPopupIndex, topLevelIndexClicked; int lastMouseX, lastMouseY; - bool inModalState; - ScopedPointer currentPopup; int getItemAt (int x, int y); + void setItemUnderMouse (int index); + void setOpenItem (int index); void updateItemUnderMouse (int x, int y); - void hideCurrentMenu(); void timerCallback(); void repaintMenuItem (int index); + void menuDismissed (int topLevelIndex, int itemId); MenuBarComponent (const MenuBarComponent&); MenuBarComponent& operator= (const MenuBarComponent&); diff --git a/src/gui/components/menus/juce_PopupMenu.cpp b/src/gui/components/menus/juce_PopupMenu.cpp index adc10b98b6..962824e116 100644 --- a/src/gui/components/menus/juce_PopupMenu.cpp +++ b/src/gui/components/menus/juce_PopupMenu.cpp @@ -309,8 +309,7 @@ public: static Window* create (const PopupMenu& menu, const bool dismissOnMouseUp, Window* const owner_, - const int minX, const int maxX, - const int minY, const int maxY, + const Rectangle& target, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, @@ -348,14 +347,14 @@ public: mw->componentAttachedTo = componentAttachedTo; mw->componentAttachedToOriginal = componentAttachedTo; - mw->calculateWindowPos (minX, maxX, minY, maxY, alignToRectangle); + mw->calculateWindowPos (target, alignToRectangle); mw->setTopLeftPosition (mw->windowPos.getX(), mw->windowPos.getY()); mw->updateYPositions(); if (itemIdThatMustBeVisible != 0) { - const int y = minY - mw->windowPos.getY(); + const int y = target.getY() - mw->windowPos.getY(); mw->ensureItemIsVisible (itemIdThatMustBeVisible, (((unsigned int) y) < (unsigned int) mw->windowPos.getHeight()) ? y : -1); } @@ -804,13 +803,10 @@ private: } //============================================================================== - void calculateWindowPos (const int minX, const int maxX, - const int minY, const int maxY, - const bool alignToRectangle) + void calculateWindowPos (const Rectangle& target, const bool alignToRectangle) { const Rectangle mon (Desktop::getInstance() - .getMonitorAreaContaining (Point ((minX + maxX) / 2, - (minY + maxY) / 2), + .getMonitorAreaContaining (target.getCentre(), #if JUCE_MAC true)); #else @@ -822,19 +818,19 @@ private: if (alignToRectangle) { - x = minX; + x = target.getX(); - const int spaceUnder = mon.getHeight() - (maxY - mon.getY()); - const int spaceOver = minY - mon.getY(); + const int spaceUnder = mon.getHeight() - (target.getBottom() - mon.getY()); + const int spaceOver = target.getY() - mon.getY(); if (heightToUse < spaceUnder - 30 || spaceUnder >= spaceOver) - y = maxY; + y = target.getBottom(); else - y = minY - heightToUse; + y = target.getY() - heightToUse; } else { - bool tendTowardsRight = (minX + maxX) / 2 < mon.getCentreX(); + bool tendTowardsRight = target.getCentreX() < mon.getCentreX(); if (owner != 0) { @@ -843,38 +839,38 @@ private: const bool ownerGoingRight = (owner->getX() + owner->getWidth() / 2 > owner->owner->getX() + owner->owner->getWidth() / 2); - if (ownerGoingRight && maxX + widthToUse < mon.getRight() - 4) + if (ownerGoingRight && target.getRight() + widthToUse < mon.getRight() - 4) tendTowardsRight = true; - else if ((! ownerGoingRight) && minX > widthToUse + 4) + else if ((! ownerGoingRight) && target.getX() > widthToUse + 4) tendTowardsRight = false; } - else if (maxX + widthToUse < mon.getRight() - 32) + else if (target.getRight() + widthToUse < mon.getRight() - 32) { tendTowardsRight = true; } } - const int biggestSpace = jmax (mon.getRight() - maxX, - minX - mon.getX()) - 32; + const int biggestSpace = jmax (mon.getRight() - target.getRight(), + target.getX() - mon.getX()) - 32; if (biggestSpace < widthToUse) { - layoutMenuItems (biggestSpace + (maxX - minX) / 3, widthToUse, heightToUse); + layoutMenuItems (biggestSpace + target.getWidth() / 3, widthToUse, heightToUse); if (numColumns > 1) layoutMenuItems (biggestSpace - 4, widthToUse, heightToUse); - tendTowardsRight = (mon.getRight() - maxX) >= (minX - mon.getX()); + tendTowardsRight = (mon.getRight() - target.getRight()) >= (target.getX() - mon.getX()); } if (tendTowardsRight) - x = jmin (mon.getRight() - widthToUse - 4, maxX); + x = jmin (mon.getRight() - widthToUse - 4, target.getRight()); else - x = jmax (mon.getX() + 4, minX - widthToUse); + x = jmax (mon.getX() + 4, target.getX() - widthToUse); - y = minY; - if ((minY + maxY) / 2 > mon.getCentreY()) - y = jmax (mon.getY(), maxY - heightToUse); + y = target.getY(); + if (target.getCentreY() > mon.getCentreY()) + y = jmax (mon.getY(), target.getBottom() - heightToUse); } x = jmax (mon.getX() + 1, jmin (mon.getRight() - (widthToUse + 6), x)); @@ -1107,13 +1103,10 @@ private: if (childComp->isValidComponent() && childComp->itemInfo.hasActiveSubMenu()) { - const Point topLeft (childComp->relativePositionToGlobal (Point())); - const Point bottomRight (childComp->relativePositionToGlobal (Point (childComp->getWidth(), childComp->getHeight()))); - activeSubMenu = Window::create (*(childComp->itemInfo.subMenu), dismissOnMouseUp, this, - topLeft.getX(), bottomRight.getX(), topLeft.getY(), bottomRight.getY(), + childComp->getScreenBounds(), 0, maximumNumColumns, standardItemHeight, false, 0, menuBarComponent, @@ -1494,7 +1487,7 @@ void PopupMenu::addSectionHeader (const String& title) } //============================================================================== -Component* PopupMenu::createMenuComponent (const int x, const int y, const int w, const int h, +Component* PopupMenu::createMenuComponent (const Rectangle& target, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, @@ -1504,20 +1497,10 @@ Component* PopupMenu::createMenuComponent (const int x, const int y, const int w ApplicationCommandManager** managerOfChosenCommand, Component* const componentAttachedTo) { - Window* const pw - = Window::create (*this, - ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(), - 0, - x, x + w, - y, y + h, - minimumWidth, - maximumNumColumns, - standardItemHeight, - alignToRectangle, - itemIdThatMustBeVisible, - menuBarComponent, - managerOfChosenCommand, - componentAttachedTo); + Window* const pw = Window::create (*this, ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(), + 0, target, minimumWidth, maximumNumColumns, standardItemHeight, + alignToRectangle, itemIdThatMustBeVisible, menuBarComponent, + managerOfChosenCommand, componentAttachedTo); if (pw != 0) pw->setVisible (true); @@ -1525,56 +1508,87 @@ Component* PopupMenu::createMenuComponent (const int x, const int y, const int w return pw; } -int PopupMenu::showMenu (const int x, const int y, const int w, const int h, +// This invokes any command manager commands and deletes the menu window when it is dismissed +class PopupMenuCompletionCallback : public ModalComponentManager::Callback +{ +public: + PopupMenuCompletionCallback() + : managerOfChosenCommand (0) + { + } + + ~PopupMenuCompletionCallback() {} + + void modalStateFinished (int result) + { + if (managerOfChosenCommand != 0 && result != 0) + { + ApplicationCommandTarget::InvocationInfo info (result); + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; + + managerOfChosenCommand->invoke (info, true); + } + } + + ApplicationCommandManager* managerOfChosenCommand; + ScopedPointer component; + +private: + PopupMenuCompletionCallback (const PopupMenuCompletionCallback&); + PopupMenuCompletionCallback& operator= (const PopupMenuCompletionCallback&); +}; + + +int PopupMenu::showMenu (const Rectangle& target, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, const int standardItemHeight, const bool alignToRectangle, - Component* const componentAttachedTo) + Component* const componentAttachedTo, + ModalComponentManager::Callback* userCallback) { + ScopedPointer userCallbackDeleter (userCallback); + Component::SafePointer prevFocused (Component::getCurrentlyFocusedComponent()); Component::SafePointer prevTopLevel ((prevFocused != 0) ? prevFocused->getTopLevelComponent() : 0); - Window::wasHiddenBecauseOfAppChange() = false; - int result = 0; - ApplicationCommandManager* managerOfChosenCommand = 0; + PopupMenuCompletionCallback* callback = new PopupMenuCompletionCallback(); + ScopedPointer callbackDeleter (callback); - ScopedPointer popupComp (createMenuComponent (x, y, w, h, - itemIdThatMustBeVisible, - minimumWidth, - maximumNumColumns > 0 ? maximumNumColumns : 7, - standardItemHeight, - alignToRectangle, 0, - &managerOfChosenCommand, - componentAttachedTo)); + callback->component = createMenuComponent (target, + itemIdThatMustBeVisible, + minimumWidth, + maximumNumColumns > 0 ? maximumNumColumns : 7, + standardItemHeight, + alignToRectangle, 0, + &callback->managerOfChosenCommand, + componentAttachedTo); - if (popupComp != 0) + if (callback->component == 0) + return 0; + + callbackDeleter.release(); + + callback->component->enterModalState (false, userCallbackDeleter.release()); + callback->component->toFront (false); // need to do this after making it modal, or it could + // be stuck behind other comps that are already modal.. + + ModalComponentManager::getInstance()->attachCallback (callback->component, callback); + + if (userCallback != 0) + return 0; + + const int result = callback->component->runModalLoop(); + + if (! Window::wasHiddenBecauseOfAppChange()) { - popupComp->enterModalState (false); - popupComp->toFront (false); // need to do this after making it modal, or it could - // be stuck behind other comps that are already modal.. + if (prevTopLevel != 0) + prevTopLevel->toFront (true); - result = popupComp->runModalLoop(); - popupComp = 0; - - if (! Window::wasHiddenBecauseOfAppChange()) - { - if (prevTopLevel != 0) - prevTopLevel->toFront (true); - - if (prevFocused != 0) - prevFocused->grabKeyboardFocus(); - } - } - - if (managerOfChosenCommand != 0 && result != 0) - { - ApplicationCommandTarget::InvocationInfo info (result); - info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; - - managerOfChosenCommand->invoke (info, true); + if (prevFocused != 0) + prevFocused->grabKeyboardFocus(); } return result; @@ -1583,7 +1597,8 @@ int PopupMenu::showMenu (const int x, const int y, const int w, const int h, int PopupMenu::show (const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, - const int standardItemHeight) + const int standardItemHeight, + ModalComponentManager::Callback* callback) { const Point mousePos (Desktop::getMousePosition()); @@ -1591,7 +1606,8 @@ int PopupMenu::show (const int itemIdThatMustBeVisible, itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, - standardItemHeight); + standardItemHeight, + callback); } int PopupMenu::showAt (const int screenX, @@ -1599,39 +1615,39 @@ int PopupMenu::showAt (const int screenX, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, - const int standardItemHeight) + const int standardItemHeight, + ModalComponentManager::Callback* callback) { - return showMenu (screenX, screenY, 1, 1, + return showMenu (Rectangle (screenX, screenY, 1, 1), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, - false, 0); + false, 0, callback); } int PopupMenu::showAt (Component* componentToAttachTo, const int itemIdThatMustBeVisible, const int minimumWidth, const int maximumNumColumns, - const int standardItemHeight) + const int standardItemHeight, + ModalComponentManager::Callback* callback) { if (componentToAttachTo != 0) { - return showMenu (componentToAttachTo->getScreenX(), - componentToAttachTo->getScreenY(), - componentToAttachTo->getWidth(), - componentToAttachTo->getHeight(), + return showMenu (componentToAttachTo->getScreenBounds(), itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, standardItemHeight, - true, componentToAttachTo); + true, componentToAttachTo, callback); } else { return show (itemIdThatMustBeVisible, minimumWidth, maximumNumColumns, - standardItemHeight); + standardItemHeight, + callback); } } diff --git a/src/gui/components/menus/juce_PopupMenu.h b/src/gui/components/menus/juce_PopupMenu.h index 69e0539b76..bde88d6136 100644 --- a/src/gui/components/menus/juce_PopupMenu.h +++ b/src/gui/components/menus/juce_PopupMenu.h @@ -240,12 +240,19 @@ public: in zero. @param standardItemHeight if this is non-zero, it will be used as the standard height for menu items (apart from custom items) + @param callback if this is non-zero, the menu will be launched asynchronously, + returning immediately, and the callback will receive a + call when the menu is either dismissed or has an item + selected. This object will be owned and deleted by the + system, so make sure that it works safely and that any + pointers that it uses are safely within scope. @see showAt */ int show (int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, - int standardItemHeight = 0); + int standardItemHeight = 0, + ModalComponentManager::Callback* callback = 0); /** Displays the menu at a specific location. @@ -264,7 +271,8 @@ public: int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, - int standardItemHeight = 0); + int standardItemHeight = 0, + ModalComponentManager::Callback* callback = 0); /** Displays the menu as if it's attached to a component such as a button. @@ -276,7 +284,8 @@ public: int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, - int standardItemHeight = 0); + int standardItemHeight = 0, + ModalComponentManager::Callback* callback = 0); //============================================================================== /** Closes any menus that are currently open. @@ -383,6 +392,7 @@ private: friend class ItemComponent; friend class Window; friend class PopupMenuCustomComponent; + friend class MenuBarComponent; friend class OwnedArray ; friend class ScopedPointer ; @@ -392,16 +402,16 @@ private: void addSeparatorIfPending(); - int showMenu (int x, int y, int w, int h, + int showMenu (const Rectangle& target, int itemIdThatMustBeVisible, int minimumWidth, int maximumNumColumns, int standardItemHeight, bool alignToRectangle, - Component* componentAttachedTo); + Component* componentAttachedTo, + ModalComponentManager::Callback* callback); - friend class MenuBarComponent; - Component* createMenuComponent (int x, int y, int w, int h, + Component* createMenuComponent (const Rectangle& target, int itemIdThatMustBeVisible, int minimumWidth, int maximumNumColumns, diff --git a/src/gui/graphics/geometry/juce_Path.cpp b/src/gui/graphics/geometry/juce_Path.cpp index 972edbc031..85b45112c6 100644 --- a/src/gui/graphics/geometry/juce_Path.cpp +++ b/src/gui/graphics/geometry/juce_Path.cpp @@ -601,7 +601,7 @@ void Path::addArrow (const Line& line, float lineThickness, startNewSubPath (line.getPointAlongLine (0, lineThickness)); lineTo (line.getPointAlongLine (0, -lineThickness)); - lineTo (reversed.getPointAlongLine (0, lineThickness)); + lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness)); lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); lineTo (line.getEnd()); lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); diff --git a/src/juce_app_includes.h b/src/juce_app_includes.h index efd137f590..97e6ec7a45 100644 --- a/src/juce_app_includes.h +++ b/src/juce_app_includes.h @@ -404,6 +404,9 @@ #ifndef __JUCE_DESKTOP_JUCEHEADER__ #include "gui/components/juce_Desktop.h" #endif +#ifndef __JUCE_MODALCOMPONENTMANAGER_JUCEHEADER__ + #include "gui/components/juce_ModalComponentManager.h" +#endif #ifndef __JUCE_KEYBOARDFOCUSTRAVERSER_JUCEHEADER__ #include "gui/components/keyboard/juce_KeyboardFocusTraverser.h" #endif