diff --git a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index 79e1379f0f..22a08c8041 100644 --- a/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -112,7 +112,7 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete void copyValueToValueTree() { if (state.isValid()) - state.setProperty (owner.valuePropertyID, value, owner.undoManager); + state.setPropertyExcludingListener (this, owner.valuePropertyID, value, owner.undoManager); } void valueTreePropertyChanged (ValueTree&, const Identifier& property) override @@ -397,7 +397,7 @@ struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private Attached private Slider::Listener { Pimpl (AudioProcessorValueTreeState& s, const String& p, Slider& sl) - : AttachedControlBase (s, p), slider (sl) + : AttachedControlBase (s, p), slider (sl), ignoreCallbacks (false) { NormalisableRange range (s.getParameterRange (paramID)); slider.setRange (range.start, range.end, range.interval); @@ -418,19 +418,28 @@ struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private Attached void setValue (float newValue) override { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + ignoreCallbacks = true; slider.setValue (newValue, sendNotificationSync); } void sliderValueChanged (Slider* s) override { - if (! ModifierKeys::getCurrentModifiers().isRightButtonDown()) + const ScopedLock selfCallbackLock (selfCallbackMutex); + + if ((! ignoreCallbacks) && (! ModifierKeys::getCurrentModifiers().isRightButtonDown())) setNewUnnormalisedValue ((float) s->getValue()); + + ignoreCallbacks = false; } void sliderDragStarted (Slider*) override { beginParameterChange(); } void sliderDragEnded (Slider*) override { endParameterChange(); } Slider& slider; + bool ignoreCallbacks; + CriticalSection selfCallbackMutex; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; @@ -447,7 +456,7 @@ struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl : private Attach private ComboBox::Listener { Pimpl (AudioProcessorValueTreeState& s, const String& p, ComboBox& c) - : AttachedControlBase (s, p), combo (c) + : AttachedControlBase (s, p), combo (c), ignoreCallbacks (false) { sendInitialUpdate(); combo.addListener (this); @@ -461,17 +470,28 @@ struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl : private Attach void setValue (float newValue) override { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + ignoreCallbacks = true; combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync); } void comboBoxChanged (ComboBox* comboBox) override { - beginParameterChange(); - setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f); - endParameterChange(); + const ScopedLock selfCallbackLock (selfCallbackMutex); + + if (! ignoreCallbacks) + { + beginParameterChange(); + setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f); + endParameterChange(); + } + ignoreCallbacks = false; } ComboBox& combo; + bool ignoreCallbacks; + CriticalSection selfCallbackMutex; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; @@ -488,7 +508,7 @@ struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl : private Attached private Button::Listener { Pimpl (AudioProcessorValueTreeState& s, const String& p, Button& b) - : AttachedControlBase (s, p), button (b) + : AttachedControlBase (s, p), button (b), ignoreCallbacks (false) { sendInitialUpdate(); button.addListener (this); @@ -502,17 +522,28 @@ struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl : private Attached void setValue (float newValue) override { + const ScopedLock selfCallbackLock (selfCallbackMutex); + + ignoreCallbacks = true; button.setToggleState (newValue >= 0.5f, sendNotificationSync); } void buttonClicked (Button* b) override { - beginParameterChange(); - setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f); - endParameterChange(); + const ScopedLock selfCallbackLock (selfCallbackMutex); + + if (! ignoreCallbacks) + { + beginParameterChange(); + setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f); + endParameterChange(); + } + ignoreCallbacks = false; } Button& button; + bool ignoreCallbacks; + CriticalSection selfCallbackMutex; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; diff --git a/modules/juce_core/containers/juce_ListenerList.cpp b/modules/juce_core/containers/juce_ListenerList.cpp new file mode 100644 index 0000000000..c9b2b3f7ee --- /dev/null +++ b/modules/juce_core/containers/juce_ListenerList.cpp @@ -0,0 +1,183 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#if JUCE_UNIT_TESTS + +struct ListenerBase +{ + ListenerBase (int& counter) : c (counter) {} + virtual ~ListenerBase () {} + + virtual void f () = 0; + virtual void f (void*) = 0; + virtual void f (void*, void*) = 0; + virtual void f (void*, void*, void*) = 0; + virtual void f (void*, void*, void*, void*) = 0; + virtual void f (void*, void*, void*, void*, void*) = 0; + virtual void f (void*, void*, void*, void*, void*, void*) = 0; + + int& c; +}; + +struct Listener1 : public ListenerBase +{ + Listener1 (int& counter) : ListenerBase (counter) {} + + void f () override { c += 1; } + void f (void*) override { c += 2; } + void f (void*, void*) override { c += 3; } + void f (void*, void*, void*) override { c += 4; } + void f (void*, void*, void*, void*) override { c += 5; } + void f (void*, void*, void*, void*, void*) override { c += 6; } + void f (void*, void*, void*, void*, void*, void*) override { c += 7; } +}; + +struct Listener2 : public ListenerBase +{ + Listener2 (int& counter) : ListenerBase (counter) {} + + void f () override { c -= 2; } + void f (void*) override { c -= 4; } + void f (void*, void*) override { c -= 6; } + void f (void*, void*, void*) override { c -= 8; } + void f (void*, void*, void*, void*) override { c -= 10; } + void f (void*, void*, void*, void*, void*) override { c -= 12; } + void f (void*, void*, void*, void*, void*, void*) override { c -= 14; } +}; + +class ListenerListTests : public UnitTest +{ +public: + ListenerListTests() : UnitTest ("ListenerList") {} + + template + void callHelper (std::vector& expectedCounterValues, T v) + { + counter = 0; + listeners.call (&ListenerBase::f, v); + expect (counter == expectedCounterValues[1]); + + counter = 0; + listeners.call (&ListenerBase::f); + expect (counter == expectedCounterValues[0]); + + ListenerList::DummyBailOutChecker boc; + + counter = 0; + listeners.callChecked (boc, &ListenerBase::f, v); + expect (counter == expectedCounterValues[1]); + + counter = 0; + listeners.callChecked (boc, &ListenerBase::f); + expect (counter == expectedCounterValues[0]); + } + + template + void callHelper (std::vector& expectedCounterValues, T first, Args... args) + { + const int expected = expectedCounterValues[sizeof... (args) + 1]; + + counter = 0; + listeners.call (&ListenerBase::f, first, args...); + expect (counter == expected); + + ListenerList::DummyBailOutChecker boc; + counter = 0; + listeners.callChecked (boc, &ListenerBase::f, first, args...); + expect (counter == expected); + + callHelper (expectedCounterValues, args...); + } + + template + void callExcludingHelper (ListenerBase& listenerToExclude, + std::vector& expectedCounterValues, T v) + { + counter = 0; + listeners.callExcluding (listenerToExclude, &ListenerBase::f, v); + expect (counter == expectedCounterValues[1]); + + counter = 0; + listeners.callExcluding (listenerToExclude, &ListenerBase::f); + expect (counter == expectedCounterValues[0]); + + ListenerList::DummyBailOutChecker boc; + + counter = 0; + listeners.callCheckedExcluding (listenerToExclude, boc, &ListenerBase::f, v); + expect (counter == expectedCounterValues[1]); + + counter = 0; + listeners.callCheckedExcluding (listenerToExclude, boc, &ListenerBase::f); + expect (counter == expectedCounterValues[0]); + } + + template + void callExcludingHelper (ListenerBase& listenerToExclude, + std::vector& expectedCounterValues, T first, Args... args) + { + const int expected = expectedCounterValues[sizeof... (args) + 1]; + + counter = 0; + listeners.callExcluding (listenerToExclude, &ListenerBase::f, first, args...); + expect (counter == expected); + + ListenerList::DummyBailOutChecker boc; + counter = 0; + listeners.callCheckedExcluding (listenerToExclude, boc, &ListenerBase::f, first, args...); + expect (counter == expected); + + callExcludingHelper (listenerToExclude, expectedCounterValues, args...); + } + + void runTest() override + { + beginTest ("Call single listener"); + listeners.add (&listener1); + std::vector expectedCounterValues = { 1, 2, 3, 4, 5, 6, 7 }; + callHelper (expectedCounterValues, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + + beginTest ("Call multiple listeners"); + listeners.add (&listener2); + expectedCounterValues = { -1, -2, -3, -4, -5, -6, -7 }; + callHelper (expectedCounterValues, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + + beginTest ("Call listeners excluding"); + expectedCounterValues = { 1, 2, 3, 4, 5, 6, 7 }; + callExcludingHelper (listener2, expectedCounterValues, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + } + + int counter = 0; + ListenerList listeners; + Listener1 listener1 {counter}; + Listener2 listener2 {counter}; +}; + +static ListenerListTests listenerListTests; + +#endif diff --git a/modules/juce_core/containers/juce_ListenerList.h b/modules/juce_core/containers/juce_ListenerList.h index 405ddc8770..5618738dc1 100644 --- a/modules/juce_core/containers/juce_ListenerList.h +++ b/modules/juce_core/containers/juce_ListenerList.h @@ -152,8 +152,18 @@ public: callChecked (static_cast (DummyBailOutChecker()), callbackFunction); } + /** Calls a member function, with no parameters, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + void callExcluding (ListenerClass& listenerToExclude, void (ListenerClass::*callbackFunction) ()) + { + callCheckedExcluding (listenerToExclude, + static_cast (DummyBailOutChecker()), callbackFunction); + } + /** Calls a member function on each listener in the list, with no parameters and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) ()) @@ -162,6 +172,20 @@ public: (iter.getListener()->*callbackFunction) (); } + /** Calls a member function on all but the specified listener in the list with a bail-out-checker. + This can be useful if the caller is also a listener and needs to exclude itself. See the class + description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) ()) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (); + } + //============================================================================== /** Calls a member function on each listener in the list, with 1 parameter. */ template @@ -171,8 +195,21 @@ public: (iter.getListener()->*callbackFunction) (param1); } - /** Calls a member function on each listener in the list, with one parameter and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + /** Calls a member function, with 1 parameter, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + template + void callExcluding (ListenerClass& listenerToExclude, + void (ListenerClass::*callbackFunction) (P1), LL_PARAM(1)) + { + for (Iterator iter (*this); iter.next();) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1); + } + + /** Calls a member function on each listener in the list, with 1 parameter and a bail-out-checker. + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) (P1), @@ -182,6 +219,21 @@ public: (iter.getListener()->*callbackFunction) (param1); } + /** Calls a member function, with 1 parameter, on all but the specified listener in the list + with a bail-out-checker. This can be useful if the caller is also a listener and needs to + exclude itself. See the class description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1), + LL_PARAM(1)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1); + } + //============================================================================== /** Calls a member function on each listener in the list, with 2 parameters. */ template @@ -192,8 +244,22 @@ public: (iter.getListener()->*callbackFunction) (param1, param2); } + /** Calls a member function, with 2 parameters, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + template + void callExcluding (ListenerClass& listenerToExclude, + void (ListenerClass::*callbackFunction) (P1, P2), + LL_PARAM(1), LL_PARAM(2)) + { + for (Iterator iter (*this); iter.next();) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2); + } + /** Calls a member function on each listener in the list, with 2 parameters and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) (P1, P2), @@ -203,6 +269,21 @@ public: (iter.getListener()->*callbackFunction) (param1, param2); } + /** Calls a member function, with 2 parameters, on all but the specified listener in the list + with a bail-out-checker. This can be useful if the caller is also a listener and needs to + exclude itself. See the class description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2), + LL_PARAM(1), LL_PARAM(2)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2); + } + //============================================================================== /** Calls a member function on each listener in the list, with 3 parameters. */ template @@ -213,8 +294,22 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3); } + /** Calls a member function, with 3 parameters, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + template + void callExcluding (ListenerClass& listenerToExclude, + void (ListenerClass::*callbackFunction) (P1, P2, P3), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) + { + for (Iterator iter (*this); iter.next();) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3); + } + /** Calls a member function on each listener in the list, with 3 parameters and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) (P1, P2, P3), @@ -224,6 +319,21 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3); } + /** Calls a member function, with 3 parameters, on all but the specified listener in the list + with a bail-out-checker. This can be useful if the caller is also a listener and needs to + exclude itself. See the class description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3); + } + //============================================================================== /** Calls a member function on each listener in the list, with 4 parameters. */ template @@ -234,8 +344,22 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3, param4); } + /** Calls a member function, with 4 parameters, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + template + void callExcluding (ListenerClass& listenerToExclude, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) + { + for (Iterator iter (*this); iter.next();) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4); + } + /** Calls a member function on each listener in the list, with 4 parameters and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), @@ -245,6 +369,21 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3, param4); } + /** Calls a member function, with 4 parameters, on all but the specified listener in the list + with a bail-out-checker. This can be useful if the caller is also a listener and needs to + exclude itself. See the class description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4); + } + //============================================================================== /** Calls a member function on each listener in the list, with 5 parameters. */ template @@ -255,8 +394,22 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); } + /** Calls a member function, with 5 parameters, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + template + void callExcluding (ListenerClass& listenerToExclude, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) + { + for (Iterator iter (*this); iter.next();) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); + } + /** Calls a member function on each listener in the list, with 5 parameters and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), @@ -266,8 +419,23 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); } + /** Calls a member function, with 5 parameters, on all but the specified listener in the list + with a bail-out-checker. This can be useful if the caller is also a listener and needs to + exclude itself. See the class description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); + } + //============================================================================== - /** Calls a member function on each listener in the list, with 5 parameters. */ + /** Calls a member function on each listener in the list, with 6 parameters. */ template void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) @@ -276,8 +444,22 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); } - /** Calls a member function on each listener in the list, with 5 parameters and a bail-out-checker. - See the class description for info about writing a bail-out checker. */ + /** Calls a member function, with 6 parameters, on all but the specified listener in the list. + This can be useful if the caller is also a listener and needs to exclude itself. + */ + template + void callExcluding (ListenerClass& listenerToExclude, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) + { + for (Iterator iter (*this); iter.next();) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); + } + + /** Calls a member function on each listener in the list, with 6 parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. + */ template void callChecked (const BailOutCheckerType& bailOutChecker, void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), @@ -287,6 +469,20 @@ public: (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); } + /** Calls a member function, with 5 parameters, on all but the specified listener in the list + with a bail-out-checker. This can be useful if the caller is also a listener and needs to + exclude itself. See the class description for info about writing a bail-out checker. + */ + template + void callCheckedExcluding (ListenerClass& listenerToExclude, + const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + if (iter.getListener() != &listenerToExclude) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); + } //============================================================================== /** A dummy bail-out checker that always returns false. diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index 3d80dbb4d5..1804a3a3b1 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -129,6 +129,7 @@ namespace juce #include "containers/juce_AbstractFifo.cpp" #include "containers/juce_NamedValueSet.cpp" +#include "containers/juce_ListenerList.cpp" #include "containers/juce_PropertySet.cpp" #include "containers/juce_Variant.cpp" #include "files/juce_DirectoryIterator.cpp" diff --git a/modules/juce_data_structures/values/juce_ValueTree.cpp b/modules/juce_data_structures/values/juce_ValueTree.cpp index 51b041ca2a..96e6938c36 100644 --- a/modules/juce_data_structures/values/juce_ValueTree.cpp +++ b/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -103,6 +103,30 @@ public: } } + template + void callListenersExcluding (ValueTree::Listener* listenerToExclude, + Method method, ValueTree& tree, ParamType& param2) const + { + const int numListeners = valueTreesWithListeners.size(); + + if (numListeners == 1) + { + valueTreesWithListeners.getUnchecked(0)->listeners.callExcluding (*listenerToExclude, method, tree, param2); + } + else if (numListeners > 0) + { + const SortedSet listenersCopy (valueTreesWithListeners); + + for (int i = 0; i < numListeners; ++i) + { + ValueTree* const v = listenersCopy.getUnchecked(i); + + if (i == 0 || valueTreesWithListeners.contains (v)) + v->listeners.callExcluding (*listenerToExclude, method, tree, param2); + } + } + } + template void callListeners (Method method, ValueTree& tree, ParamType1& param2, ParamType2& param3) const { @@ -126,12 +150,15 @@ public: } } - void sendPropertyChangeMessage (const Identifier& property) + void sendPropertyChangeMessage (const Identifier& property, ValueTree::Listener* listenerToExclude = nullptr) { ValueTree tree (this); for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) - t->callListeners (&ValueTree::Listener::valueTreePropertyChanged, tree, property); + if (listenerToExclude == nullptr) + t->callListeners (&ValueTree::Listener::valueTreePropertyChanged, tree, property); + else + t->callListenersExcluding (listenerToExclude, &ValueTree::Listener::valueTreePropertyChanged, tree, property); } void sendChildAddedMessage (ValueTree child) @@ -169,23 +196,24 @@ public: callListeners (&ValueTree::Listener::valueTreeParentChanged, tree); } - void setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager) + void setProperty (const Identifier& name, const var& newValue, UndoManager* const undoManager, + ValueTree::Listener* listenerToExclude = nullptr) { if (undoManager == nullptr) { if (properties.set (name, newValue)) - sendPropertyChangeMessage (name); + sendPropertyChangeMessage (name, listenerToExclude); } else { if (const var* const existingValue = properties.getVarPointer (name)) { if (*existingValue != newValue) - undoManager->perform (new SetPropertyAction (this, name, newValue, *existingValue, false, false)); + undoManager->perform (new SetPropertyAction (this, name, newValue, *existingValue, false, false, listenerToExclude)); } else { - undoManager->perform (new SetPropertyAction (this, name, newValue, var(), true, false)); + undoManager->perform (new SetPropertyAction (this, name, newValue, var(), true, false, listenerToExclude)); } } } @@ -459,9 +487,11 @@ public: { public: SetPropertyAction (SharedObject* const so, const Identifier& propertyName, - const var& newVal, const var& oldVal, bool isAdding, bool isDeleting) + const var& newVal, const var& oldVal, bool isAdding, bool isDeleting, + ValueTree::Listener* listenerToExclude = nullptr) : target (so), name (propertyName), newValue (newVal), oldValue (oldVal), - isAddingNewProperty (isAdding), isDeletingProperty (isDeleting) + isAddingNewProperty (isAdding), isDeletingProperty (isDeleting), + excludeListener (listenerToExclude) { } @@ -472,7 +502,7 @@ public: if (isDeletingProperty) target->removeProperty (name, nullptr); else - target->setProperty (name, newValue, nullptr); + target->setProperty (name, newValue, nullptr, excludeListener); return true; } @@ -511,6 +541,7 @@ public: const var newValue; var oldValue; const bool isAddingNewProperty : 1, isDeletingProperty : 1; + ValueTree::Listener* excludeListener; JUCE_DECLARE_NON_COPYABLE (SetPropertyAction) }; @@ -762,12 +793,17 @@ const var* ValueTree::getPropertyPointer (const Identifier& name) const noexcept } ValueTree& ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager) +{ + return setPropertyExcludingListener (nullptr, name, newValue, undoManager); +} + +ValueTree& ValueTree::setPropertyExcludingListener (Listener* listenerToExclude, const Identifier& name, const var& newValue, UndoManager* undoManager) { jassert (name.toString().isNotEmpty()); // Must have a valid property name! jassert (object != nullptr); // Trying to add a property to a null ValueTree will fail! if (object != nullptr) - object->setProperty (name, newValue, undoManager); + object->setProperty (name, newValue, undoManager, listenerToExclude); return *this; } diff --git a/modules/juce_data_structures/values/juce_ValueTree.h b/modules/juce_data_structures/values/juce_ValueTree.h index 52bb8d3c63..95f7ac948c 100644 --- a/modules/juce_data_structures/values/juce_ValueTree.h +++ b/modules/juce_data_structures/values/juce_ValueTree.h @@ -483,6 +483,11 @@ public: /** Removes a listener that was previously added with addListener(). */ void removeListener (Listener* listener); + /** Changes a named property of the node, but will not notify a specified listener of the change. + @see setProperty + */ + ValueTree& setPropertyExcludingListener (Listener* listenerToExclude, const Identifier& name, const var& newValue, UndoManager* undoManager); + /** Causes a property-change callback to be triggered for the specified property, calling any listeners that are registered. */