diff --git a/Src/Common/Controls/DetailControls/DataTree.cs b/Src/Common/Controls/DetailControls/DataTree.cs index 27ea2ea211..f83c79bea9 100644 --- a/Src/Common/Controls/DetailControls/DataTree.cs +++ b/Src/Common/Controls/DetailControls/DataTree.cs @@ -4404,8 +4404,8 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu if (PersistenceProvder != null) RestorePreferences(); - Subscriber.Subscribe(EventConstants.PostponePropChanged, PostponePropChanged); - Subscriber.Subscribe(EventConstants.JumpToField, JumpToField); + Subscriber.Subscribe(EventConstants.PostponePropChanged, PostponePropChanged, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.JumpToField, JumpToField, m_propertyTable.GetWindow()); } public IxCoreColleague[] GetMessageTargets() diff --git a/Src/Common/Controls/DetailControls/GhostStringSlice.cs b/Src/Common/Controls/DetailControls/GhostStringSlice.cs index a507cd4f07..73de88e2ed 100644 --- a/Src/Common/Controls/DetailControls/GhostStringSlice.cs +++ b/Src/Common/Controls/DetailControls/GhostStringSlice.cs @@ -459,12 +459,12 @@ private void SwitchToReal() int hvoNewObj; try { - Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, false)); + Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, false, datatree.PropTable?.GetWindow())); hvoNewObj = MakeRealObject(tssTyped); } finally { - Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, true)); + Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, true, datatree.PropTable?.GetWindow())); } // Now try to make a suitable selection in the slice that replaces this. diff --git a/Src/Common/Controls/XMLViews/BrowseViewer.cs b/Src/Common/Controls/XMLViews/BrowseViewer.cs index cae0c518cc..ee0171e387 100644 --- a/Src/Common/Controls/XMLViews/BrowseViewer.cs +++ b/Src/Common/Controls/XMLViews/BrowseViewer.cs @@ -3298,7 +3298,7 @@ public virtual void Init(Mediator mediator, PropertyTable propertyTable, XmlNode m_xbv.Init(mediator, propertyTable, configurationParameters); m_xbv.AccessibleName = "BrowseViewer"; m_mediator = mediator; - Subscriber.Subscribe(EventConstants.RemoveFilters, RemoveFilters); + Subscriber.Subscribe(EventConstants.RemoveFilters, RemoveFilters, propertyTable.GetWindow()); } /// /// Should not be called if disposed. diff --git a/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs b/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs index bff84e09d8..1a1a91ab77 100644 --- a/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs +++ b/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs @@ -104,8 +104,8 @@ public override void Init(XmlNode nodeSpec, int hvoRoot, int fakeFlid, // Use the ones in fakeFlid, and any we create. base.Init(nodeSpec, hvoRoot, fakeFlid, cache, mediator, bv); - Subscriber.Subscribe(EventConstants.DeleteRecord, DeleteRecord); - Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing); + Subscriber.Subscribe(EventConstants.DeleteRecord, DeleteRecord, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing, m_propertyTable.GetWindow()); } #endregion Construction, initialization, and disposal. diff --git a/Src/Common/Controls/XMLViews/XmlBrowseViewBase.cs b/Src/Common/Controls/XMLViews/XmlBrowseViewBase.cs index 921c316730..ec4dc567ce 100644 --- a/Src/Common/Controls/XMLViews/XmlBrowseViewBase.cs +++ b/Src/Common/Controls/XMLViews/XmlBrowseViewBase.cs @@ -2085,9 +2085,9 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod SetSelectedRowHighlighting();//read the property table - Subscriber.Subscribe(EventConstants.SaveScrollPosition, SaveScrollPosition); - Subscriber.Subscribe(EventConstants.RestoreScrollPosition, RestoreScrollPosition); - Subscriber.Subscribe(EventConstants.PrepareToRefresh, PrepareToRefresh); + Subscriber.Subscribe(EventConstants.SaveScrollPosition, SaveScrollPosition, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.RestoreScrollPosition, RestoreScrollPosition, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.PrepareToRefresh, PrepareToRefresh, m_propertyTable.GetWindow()); } #endregion XCore Colleague overrides diff --git a/Src/Common/Controls/XMLViews/XmlBrowseViewBaseVc.cs b/Src/Common/Controls/XMLViews/XmlBrowseViewBaseVc.cs index fdbb7a5cb8..613317e2a5 100644 --- a/Src/Common/Controls/XMLViews/XmlBrowseViewBaseVc.cs +++ b/Src/Common/Controls/XMLViews/XmlBrowseViewBaseVc.cs @@ -1767,7 +1767,7 @@ public override void DoHotLinkAction(string strData, ISilDataAccess sda) FwLinkArgs linkArgs = new FwLinkArgs(url); linkArgs.DisplayErrorMsg = false; var retObj = new ReturnObject(linkArgs); - Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, retObj)); + Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, retObj, m_xbv?.m_bv?.PropTable?.GetWindow())); if (retObj.ReturnValue) return; } diff --git a/Src/Common/FieldWorks/FieldWorks.cs b/Src/Common/FieldWorks/FieldWorks.cs index 9e01e6df2c..5d69adec14 100644 --- a/Src/Common/FieldWorks/FieldWorks.cs +++ b/Src/Common/FieldWorks/FieldWorks.cs @@ -1849,7 +1849,7 @@ private static ProjectId ShowWelcomeDialog(FwAppArgs args, FwApp startingApp, Pr s_projectId = projectToTry; // Window is open on this project, we must not try to initialize it again. if (Form.ActiveForm is IxWindow mainWindow) { - Publisher.Publish(new PublisherParameterObject(EventConstants.SFMImport)); + Publisher.Publish(new PublisherParameterObject(EventConstants.SFMImport, null, mainWindow)); } else { diff --git a/Src/Common/FwUtils/EndOfActionManager.cs b/Src/Common/FwUtils/EndOfActionManager.cs index d243a62458..7ede4857fb 100644 --- a/Src/Common/FwUtils/EndOfActionManager.cs +++ b/Src/Common/FwUtils/EndOfActionManager.cs @@ -19,7 +19,11 @@ namespace SIL.FieldWorks.Common.FwUtils public sealed class EndOfActionManager { private bool m_initialized = false; - private readonly Dictionary m_events = new Dictionary(); + // The events are grouped by message, and then by scope within each message. At most a message + // is queued once per scope (last one wins). Note: Null is a valid scope, it follows the same + // rules; queued once and last one wins, but null scope is unique in that the message is delivered + // to all subscribers regardless of their scope (null or not null). + private readonly Dictionary> m_events = new Dictionary>(); /// /// Returns the EndOfAction event that is currently being published. @@ -51,8 +55,23 @@ internal void AddEvent(PublisherParameterObject publisherParameterObject) } } - // Add the dictionary entry if the key is not present. Overwrite the value if the key is present. - m_events[publisherParameterObject.Message] = publisherParameterObject.Data; + // The whole parameter object is kept so the delivery scope survives until the idle publish. + // Group by both message and scope: overwrite an already-queued publish only if it has the + // same scope; publishes of the same message from different scopes are all delivered. + if (!m_events.TryGetValue(publisherParameterObject.Message, out var queued)) + { + queued = new List(); + m_events.Add(publisherParameterObject.Message, queued); + } + var sameScopeIndex = queued.FindIndex(e => ReferenceEquals(e.Scope, publisherParameterObject.Scope)); + if (sameScopeIndex >= 0) + { + queued[sameScopeIndex] = publisherParameterObject; + } + else + { + queued.Add(publisherParameterObject); + } } /// Should be private, but is public to support testing. @@ -63,11 +82,14 @@ public bool IdleEndOfAction(object _) { foreach (var orderedEvent in EndOfActionOrder.Order) { - if (m_events.TryGetValue(orderedEvent, out object data)) + if (m_events.TryGetValue(orderedEvent, out var queuedEvents)) { m_events.Remove(orderedEvent); CurrentEndOfActionEvent = orderedEvent; - FwUtils.Publisher.Publish(new PublisherParameterObject(orderedEvent, data)); + foreach (var publisherParameterObject in queuedEvents) + { + FwUtils.Publisher.Publish(publisherParameterObject); + } CurrentEndOfActionEvent = null; } diff --git a/Src/Common/FwUtils/FwUtilsTests/PubSubSystemTests.cs b/Src/Common/FwUtils/FwUtilsTests/PubSubSystemTests.cs index 456032a54d..95ce8fc649 100644 --- a/Src/Common/FwUtils/FwUtilsTests/PubSubSystemTests.cs +++ b/Src/Common/FwUtils/FwUtilsTests/PubSubSystemTests.cs @@ -205,6 +205,147 @@ public void Test_Two_Subscribers_For_MessageOneHandling() Assert.That(subscriber2.One, Is.True); // Did not change. } + /// + /// A scoped publish goes to same-scope and unscoped subscribers, but not to + /// subscribers with a different scope. + /// + [Test] + public void Scoped_Publish_Delivers_Only_To_Matching_Scope_And_Unscoped_Subscribers() + { + // Set up. + var scopeA = new TestPubSubScope(); + var subscriberA = new ScopeAwareSubscriber(scopeA) { One = true }; + var subscriberB = new ScopeAwareSubscriber(new TestPubSubScope()) { One = true }; + var unscopedSubscriber = new ScopeAwareSubscriber(null) { One = true }; + subscriberA.DoSubscriptions(); + subscriberB.DoSubscriptions(); + unscopedSubscriber.DoSubscriptions(); + + // Run test. + FwUtils.Publisher.Publish(new PublisherParameterObject("MessageOne", false, scopeA)); + Assert.That(subscriberA.One, Is.False); // Same scope: delivered. + Assert.That(subscriberB.One, Is.True); // Different scope: not delivered. + Assert.That(unscopedSubscriber.One, Is.False); // Unscoped subscriber: delivered. + + subscriberA.DoUnsubscriptions(); + subscriberB.DoUnsubscriptions(); + unscopedSubscriber.DoUnsubscriptions(); + } + + /// + /// An unscoped publish is process-wide: every subscriber receives it, scoped or not. + /// + [Test] + public void Unscoped_Publish_Delivers_To_Scoped_And_Unscoped_Subscribers() + { + // Set up. + var scopedSubscriber = new ScopeAwareSubscriber(new TestPubSubScope()) { One = true }; + var unscopedSubscriber = new ScopeAwareSubscriber(null) { One = true }; + scopedSubscriber.DoSubscriptions(); + unscopedSubscriber.DoSubscriptions(); + + // Run test. + FwUtils.Publisher.Publish(new PublisherParameterObject("MessageOne", false)); + Assert.That(scopedSubscriber.One, Is.False); // Delivered. + Assert.That(unscopedSubscriber.One, Is.False); // Delivered. + + scopedSubscriber.DoUnsubscriptions(); + unscopedSubscriber.DoUnsubscriptions(); + } + + /// + /// The scope must survive the EndOfActionManager round trip through the idle queue. + /// + [Test] + public void Scope_Is_Preserved_Through_PublishAtEndOfAction() + { + // Set up. + var scopeA = new TestPubSubScope(); + var subscriberA = new ScopeAwareSubscriber(scopeA) { Two = int.MinValue }; + var subscriberB = new ScopeAwareSubscriber(new TestPubSubScope()) { Two = int.MinValue }; + subscriberA.DoSubscriptions(); + subscriberB.DoSubscriptions(); + + FwUtils.Publisher.PublishAtEndOfAction(new PublisherParameterObject(EventConstants.SelectionChanged, int.MaxValue, scopeA)); + + // Nothing is delivered until the idle queue drains. + Assert.That(subscriberA.Two, Is.EqualTo(int.MinValue)); + Assert.That(subscriberB.Two, Is.EqualTo(int.MinValue)); + + // SUT - Process the EndOfActionManager IdleQueue. + FwUtils.Publisher.EndOfActionManager.IdleEndOfAction(null); + + Assert.That(subscriberA.Two, Is.EqualTo(int.MaxValue)); // Same scope: delivered. + Assert.That(subscriberB.Two, Is.EqualTo(int.MinValue)); // Different scope: not delivered. + + subscriberA.DoUnsubscriptions(); + subscriberB.DoUnsubscriptions(); + } + + /// + /// EndOfAction coalescing is per (message, scope): the same message queued from two + /// scopes is delivered to both, while a re-publish from the same scope overwrites. + /// + [Test] + public void PublishAtEndOfAction_Coalesces_Per_Scope() + { + // Set up. + var scopeA = new TestPubSubScope(); + var scopeB = new TestPubSubScope(); + var subscriberA = new ScopeAwareSubscriber(scopeA) { Two = int.MinValue }; + var subscriberB = new ScopeAwareSubscriber(scopeB) { Two = int.MinValue }; + subscriberA.DoSubscriptions(); + subscriberB.DoSubscriptions(); + + FwUtils.Publisher.PublishAtEndOfAction(new PublisherParameterObject(EventConstants.SelectionChanged, 1, scopeA)); + FwUtils.Publisher.PublishAtEndOfAction(new PublisherParameterObject(EventConstants.SelectionChanged, 2, scopeB)); + // Same scope again: coalesces with (overwrites) the first scopeA publish. + FwUtils.Publisher.PublishAtEndOfAction(new PublisherParameterObject(EventConstants.SelectionChanged, 3, scopeA)); + + // SUT - Process the EndOfActionManager IdleQueue. + FwUtils.Publisher.EndOfActionManager.IdleEndOfAction(null); + + Assert.That(subscriberA.Two, Is.EqualTo(3)); // Latest scopeA publish won. + Assert.That(subscriberB.Two, Is.EqualTo(2)); // scopeB publish survived independently. + + subscriberA.DoUnsubscriptions(); + subscriberB.DoUnsubscriptions(); + } + + /// + /// Prefix subscriptions honor the same scope rule as specific subscriptions: + /// scoped publishes skip other-scope prefix subscribers and reach same-scope and + /// unscoped ones; non-matching prefixes are never delivered. + /// + [Test] + public void Prefix_Subscriptions_Honor_Scope() + { + // Set up. + var scopeA = new TestPubSubScope(); + var subscriberA = new PrefixScopeAwareSubscriber(scopeA); + var subscriberB = new PrefixScopeAwareSubscriber(new TestPubSubScope()); + var unscopedSubscriber = new PrefixScopeAwareSubscriber(null); + subscriberA.DoSubscriptions(); + subscriberB.DoSubscriptions(); + unscopedSubscriber.DoSubscriptions(); + + // Run test. + FwUtils.Publisher.Publish(new PublisherParameterObject("PrefixedMessageOne", 7, scopeA)); + Assert.That(subscriberA.LastMessage, Is.EqualTo("PrefixedMessageOne")); // Same scope: delivered. + Assert.That(subscriberB.LastMessage, Is.Null); // Different scope: not delivered. + Assert.That(unscopedSubscriber.LastMessage, Is.EqualTo("PrefixedMessageOne")); // Unscoped subscriber: delivered. + + subscriberA.LastMessage = null; + unscopedSubscriber.LastMessage = null; + FwUtils.Publisher.Publish(new PublisherParameterObject("UnrelatedMessage", 7, scopeA)); + Assert.That(subscriberA.LastMessage, Is.Null); // Prefix does not match: not delivered. + Assert.That(unscopedSubscriber.LastMessage, Is.Null); + + subscriberA.DoUnsubscriptions(); + subscriberB.DoUnsubscriptions(); + unscopedSubscriber.DoUnsubscriptions(); + } + [Test] [TestCase(null)] [TestCase("")] @@ -553,6 +694,81 @@ private void MessageTwoHandler(object newValue) } } + private sealed class TestPubSubScope : IPubSubScope + { + } + + private sealed class PrefixScopeAwareSubscriber + { + private readonly IPubSubScope _scope; + + internal PrefixScopeAwareSubscriber(IPubSubScope scope) + { + _scope = scope; + } + + internal string LastMessage { get; set; } + + /// + /// This is the subscribed prefix handler for messages starting with "PrefixedMessage". + /// + private void PrefixedMessageHandler(string message, object newValue) + { + LastMessage = message; + } + + internal void DoSubscriptions() + { + FwUtils.Subscriber.PrefixSubscribe("PrefixedMessage", PrefixedMessageHandler, _scope); + } + + internal void DoUnsubscriptions() + { + FwUtils.Subscriber.PrefixUnsubscribe("PrefixedMessage", PrefixedMessageHandler); + } + } + + private sealed class ScopeAwareSubscriber + { + private readonly IPubSubScope _scope; + + internal ScopeAwareSubscriber(IPubSubScope scope) + { + _scope = scope; + } + + internal bool One { get; set; } + internal int Two { get; set; } + + /// + /// This is the subscribed message handler for "MessageOne" message. + /// + private void MessageOneHandler(object newValue) + { + One = (bool)newValue; + } + + /// + /// This is the subscribed message handler for the SelectionChanged message. + /// + private void SelectionChangedHandler(object newValue) + { + Two = (int)newValue; + } + + internal void DoSubscriptions() + { + FwUtils.Subscriber.Subscribe("MessageOne", MessageOneHandler, _scope); + FwUtils.Subscriber.Subscribe(EventConstants.SelectionChanged, SelectionChangedHandler, _scope); + } + + internal void DoUnsubscriptions() + { + FwUtils.Subscriber.Unsubscribe("MessageOne", MessageOneHandler); + FwUtils.Subscriber.Unsubscribe(EventConstants.SelectionChanged, SelectionChangedHandler); + } + } + private sealed class EndOfAction_MultipleSubscriber { internal string First { get; set; } diff --git a/Src/Common/FwUtils/IPubSubScope.cs b/Src/Common/FwUtils/IPubSubScope.cs new file mode 100644 index 0000000000..9a2911a510 --- /dev/null +++ b/Src/Common/FwUtils/IPubSubScope.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +namespace SIL.FieldWorks.Common.FwUtils +{ + /// + /// Marker interface for objects that scope Pub/Sub delivery. + /// + /// The Publisher and Subscriber are process-wide singletons, but most messages are + /// window-scoped: the legacy Mediator they replace delivered only within one main window. + /// To reproduce that, a subscriber passes its main window when subscribing, and a publisher + /// passes the same window in its PublisherParameterObject; the Publisher then delivers a + /// scoped publish only to subscribers with the identical (reference-equal) scope. + /// A Publisher with a null scope will go to all the message subscribers and a Subscriber + /// with a null scope will receive all the messages, regardless of them being published + /// with or without a scope. + /// Null is for publishers with no window context (e.g. app bootstrap code) and for + /// messages that must reach a window the publisher cannot name (e.g. Send/Receive messages + /// aimed at the project's reopened instance). (Process-wide delivery is a Pub/Sub-era + /// capability, not preserved Mediator behavior. The Mediator had no all-windows broadcast; + /// instead its app-wide coordination was accomplished by iterating FwApp.MainWindows directly.) + /// + /// The scope IS the main window: the XCore IxWindow interface extends this one, so main + /// windows pass this and everything else obtains the window via + /// PropertyTable.GetWindow(). + /// Do not implement this interface on anything that is not a main window. + /// + public interface IPubSubScope + { + } +} diff --git a/Src/Common/FwUtils/ISubscriber.cs b/Src/Common/FwUtils/ISubscriber.cs index e50aaa45f6..9a8586979d 100644 --- a/Src/Common/FwUtils/ISubscriber.cs +++ b/Src/Common/FwUtils/ISubscriber.cs @@ -20,7 +20,11 @@ public interface ISubscriber /// The message being subscribed to receive. /// The method on subscriber to call, when /// has been published - void Subscribe(string message, Action messageHandler); + /// Optional delivery scope (normally the subscriber's main window). + /// When non-null, scoped publishes of are delivered only if they + /// carry the same scope. Null subscribes will receive messages from all publishers. + /// See . + void Subscribe(string message, Action messageHandler, IPubSubScope scope = null); /// /// An object subscribes to messages that begin with using @@ -33,7 +37,11 @@ public interface ISubscriber /// The message prefix being subscribed to receive. /// The method on subscriber to call, when a message that /// begins with has been published. - void PrefixSubscribe(string messagePrefix, Action messageHandler); + /// Optional delivery scope (normally the subscriber's main window). + /// When non-null, scoped publishes of matching messages are delivered only if they + /// carry the same scope. Null subscribes will receive messages from all publishers. + /// See . + void PrefixSubscribe(string messagePrefix, Action messageHandler, IPubSubScope scope = null); /// /// Register end of interest (unsubscribe) of an object in receiving @@ -52,14 +60,16 @@ public interface ISubscriber void PrefixUnsubscribe(string messagePrefix, Action messageHandler); /// - /// Get all current subscriptions. + /// Get all current subscriptions: per message, each handler with the scope it subscribed + /// under (null scope = process-wide). /// - IReadOnlyDictionary>> Subscriptions { get; } + IReadOnlyDictionary, IPubSubScope>> Subscriptions { get; } /// - /// Get all the current prefix subscriptions. If a message starts with one of these + /// Get all the current prefix subscriptions: per prefix, each handler with the scope it + /// subscribed under (null scope = process-wide). If a message starts with one of these /// prefixes then the prefix subscribers will get notified. /// - IReadOnlyDictionary>> PrefixSubscriptions { get; } + IReadOnlyDictionary, IPubSubScope>> PrefixSubscriptions { get; } } } \ No newline at end of file diff --git a/Src/Common/FwUtils/Publisher.cs b/Src/Common/FwUtils/Publisher.cs index 672c763059..873d89e152 100644 --- a/Src/Common/FwUtils/Publisher.cs +++ b/Src/Common/FwUtils/Publisher.cs @@ -26,36 +26,48 @@ public Publisher(ISubscriber subscriber) } /// - /// Publish the message using the new value. + /// Publish the message using the given data. /// /// The message to publish. - /// The new value to send to subscribers. This may be null. - private void PublishMessage(string message, object newValue) + /// The data to send to subscribers. This may be null. + /// The delivery scope. When non-null, only subscribers with the same + /// scope (or none) are invoked; when null, every subscriber is. See . + private void PublishMessage(string message, object data, IPubSubScope scope) { Guard.AgainstNullOrEmptyString(message, nameof(message)); // Check if 'message' was subscribed to. if (_subscriber.Subscriptions.TryGetValue(message, out var subscribers)) { - foreach (var subscriberAction in subscribers.ToList()) + foreach (var subscription in subscribers.ToList()) { - // NB: It is possible that the action's object is disposed, - // but we'll not fret about making sure it isn't disposed, - // but we will expect the subscribers to be well-behaved and unsubscribe, - // when they get disposed. - subscriberAction(newValue); + // A scoped publish is delivered only to subscribers in the same scope (e.g. the + // same main window). A null scope on either end means process-wide delivery. + if (scope == null || subscription.Value == null || ReferenceEquals(scope, subscription.Value)) + { + // NB: It is possible that the action's object is disposed, + // but we'll not fret about making sure it isn't disposed, + // but we will expect the subscribers to be well-behaved and unsubscribe, + // when they get disposed. + subscription.Key(data); + } } } // Check if 'message' contains a prefix that was subscribed to. // Note: ToArray() is important to avoid new entries being added while iterating over the collection. - foreach (KeyValuePair>> entry in _subscriber.PrefixSubscriptions.ToArray()) + foreach (KeyValuePair, IPubSubScope>> entry in _subscriber.PrefixSubscriptions.ToArray()) { if (message.StartsWith(entry.Key)) { - foreach (var subscriberAction in entry.Value.ToList()) + foreach (var subscription in entry.Value.ToList()) { - subscriberAction(message, newValue); + // Same delivery rule as specific subscriptions: a null scope on either end + // means process-wide; otherwise the scopes must be the same window. + if (scope == null || subscription.Value == null || ReferenceEquals(scope, subscription.Value)) + { + subscription.Key(message, data); + } } } } @@ -70,7 +82,7 @@ public void Publish(PublisherParameterObject publisherParameterObject) { Guard.AgainstNull(publisherParameterObject, nameof(publisherParameterObject)); - PublishMessage(publisherParameterObject.Message, publisherParameterObject.Data); + PublishMessage(publisherParameterObject.Message, publisherParameterObject.Data, publisherParameterObject.Scope); } /// @@ -89,7 +101,7 @@ public void Publish(IList publisherParameterObjects) foreach (var publisherParameterObject in publisherParameterObjects) { - PublishMessage(publisherParameterObject.Message, publisherParameterObject.Data); + PublishMessage(publisherParameterObject.Message, publisherParameterObject.Data, publisherParameterObject.Scope); } } #endregion diff --git a/Src/Common/FwUtils/PublisherParameterObject.cs b/Src/Common/FwUtils/PublisherParameterObject.cs index 93a8d8a4a7..a678f4e054 100644 --- a/Src/Common/FwUtils/PublisherParameterObject.cs +++ b/Src/Common/FwUtils/PublisherParameterObject.cs @@ -8,7 +8,7 @@ namespace SIL.FieldWorks.Common.FwUtils { public sealed class PublisherParameterObject { - public PublisherParameterObject(string message, object data = null) + public PublisherParameterObject(string message, object data = null, IPubSubScope scope = null) { if (string.IsNullOrWhiteSpace(message)) { @@ -17,10 +17,18 @@ public PublisherParameterObject(string message, object data = null) Message = message; Data = data; + Scope = scope; } public string Message { get; } public object Data { get; } + + /// + /// Delivery scope (normally the publishing main window). When non-null, only + /// subscribers that subscribed with the same scope (or with none) receive this publish. + /// A null scope publishes to all subscribers, regardless of their scope. See . + /// + public IPubSubScope Scope { get; } } /// diff --git a/Src/Common/FwUtils/Subscriber.cs b/Src/Common/FwUtils/Subscriber.cs index 469129557c..c838a354e4 100644 --- a/Src/Common/FwUtils/Subscriber.cs +++ b/Src/Common/FwUtils/Subscriber.cs @@ -13,10 +13,10 @@ namespace SIL.FieldWorks.Common.FwUtils /// internal sealed class Subscriber : ISubscriber { - private readonly Dictionary>> _subscriptions = - new Dictionary>>(); - private readonly Dictionary>> _prefixSubscriptions = - new Dictionary>>(); + private readonly Dictionary, IPubSubScope>> _subscriptions = + new Dictionary, IPubSubScope>>(); + private readonly Dictionary, IPubSubScope>> _prefixSubscriptions = + new Dictionary, IPubSubScope>>(); #region Implementation of ISubscriber @@ -27,16 +27,20 @@ internal sealed class Subscriber : ISubscriber /// The message being subscribed to receive. /// The method on subscriber to call, when /// has been published - public void Subscribe(string message, Action messageHandler) + /// Optional delivery scope (normally the subscriber's main window). + /// When non-null, scoped publishes of are delivered only if they + /// carry the same scope. Null subscribes will receive messages from all publishers. + /// See . + public void Subscribe(string message, Action messageHandler, IPubSubScope scope = null) { if (!_subscriptions.TryGetValue(message, out var subscribers)) { - subscribers = new HashSet>(); + subscribers = new Dictionary, IPubSubScope>(); _subscriptions.Add(message, subscribers); } // NB: If an ill-behaved subscribing object registers the same delegate more than once - // for the same message, then only one registration will 'take'. - subscribers.Add(messageHandler); + // for the same message, then only one registration will 'take' (the latest scope wins). + subscribers[messageHandler] = scope; } /// @@ -50,16 +54,20 @@ public void Subscribe(string message, Action messageHandler) /// The message prefix being subscribed to receive. /// The method on subscriber to call, when a message that /// begins with has been published. - public void PrefixSubscribe(string messagePrefix, Action messageHandler) + /// Optional delivery scope (normally the subscriber's main window). + /// When non-null, scoped publishes of matching messages are delivered only if they + /// carry the same scope. Null subscribes will receive messages from all publishers. + /// See . + public void PrefixSubscribe(string messagePrefix, Action messageHandler, IPubSubScope scope = null) { if (!_prefixSubscriptions.TryGetValue(messagePrefix, out var prefixSubscribers)) { - prefixSubscribers = new HashSet>(); + prefixSubscribers = new Dictionary, IPubSubScope>(); _prefixSubscriptions.Add(messagePrefix, prefixSubscribers); } // NB: If an ill-behaved subscribing object registers the same delegate more than once - // for the same message, then only one registration will 'take'. - prefixSubscribers.Add(messageHandler); + // for the same message, then only one registration will 'take' (the latest scope wins). + prefixSubscribers[messageHandler] = scope; } /// @@ -103,17 +111,19 @@ public void PrefixUnsubscribe(string messagePrefix, Action messa } /// - /// Get all current subscriptions. + /// Get all current subscriptions: per message, each handler with the scope it subscribed + /// under (null scope = process-wide). /// - public IReadOnlyDictionary>> Subscriptions => - new ReadOnlyDictionary>>(_subscriptions); + public IReadOnlyDictionary, IPubSubScope>> Subscriptions => + new ReadOnlyDictionary, IPubSubScope>>(_subscriptions); /// - /// Get all the current prefix subscriptions. If a message starts with one of these + /// Get all the current prefix subscriptions: per prefix, each handler with the scope it + /// subscribed under (null scope = process-wide). If a message starts with one of these /// prefixes then the prefix subscribers will get notified. /// - public IReadOnlyDictionary>> PrefixSubscriptions => - new ReadOnlyDictionary>>(_prefixSubscriptions); + public IReadOnlyDictionary, IPubSubScope>> PrefixSubscriptions => + new ReadOnlyDictionary, IPubSubScope>>(_prefixSubscriptions); #endregion } diff --git a/Src/FdoUi/FdoUiCore.cs b/Src/FdoUi/FdoUiCore.cs index 44487532a7..6af24c591e 100644 --- a/Src/FdoUi/FdoUiCore.cs +++ b/Src/FdoUi/FdoUiCore.cs @@ -400,7 +400,7 @@ public static CmObjectUi CreateNewUiObject(Mediator mediator, PropertyTable prop try { // Don't postpone PropChanged (cf. LT-22095). - Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, false)); + Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, false, propertyTable.GetWindow())); var cache = propertyTable.GetValue("cache"); switch (classId) { @@ -420,7 +420,7 @@ public static CmObjectUi CreateNewUiObject(Mediator mediator, PropertyTable prop } finally { - Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, true)); + Publisher.Publish(new PublisherParameterObject(EventConstants.PostponePropChanged, true, propertyTable.GetWindow())); } } @@ -1026,7 +1026,7 @@ public bool DeleteUnderlyingObject() object command = this; if (m_command != null) command = m_command; - Publisher.Publish(new PublisherParameterObject(EventConstants.DeleteRecord, command)); + Publisher.Publish(new PublisherParameterObject(EventConstants.DeleteRecord, command, m_propertyTable.GetWindow())); } else { diff --git a/Src/LexText/Interlinear/ConcordanceControl.cs b/Src/LexText/Interlinear/ConcordanceControl.cs index d31b7e573e..1f3536c701 100644 --- a/Src/LexText/Interlinear/ConcordanceControl.cs +++ b/Src/LexText/Interlinear/ConcordanceControl.cs @@ -99,7 +99,7 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod // Load any saved settings. LoadSettings(); - Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing); + Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing, m_propertyTable.GetWindow()); } /// diff --git a/Src/LexText/Interlinear/InterlinMaster.cs b/Src/LexText/Interlinear/InterlinMaster.cs index 6c44d978c3..eb48e6eff0 100644 --- a/Src/LexText/Interlinear/InterlinMaster.cs +++ b/Src/LexText/Interlinear/InterlinMaster.cs @@ -775,8 +775,8 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod m_fullyInitialized = true; RefreshPaneBar(); - Subscriber.Subscribe(EventConstants.PrepareToRefresh, PrepareToRefresh); - Subscriber.Subscribe(EventConstants.RefreshInterlin, RefreshInterlin); + Subscriber.Subscribe(EventConstants.PrepareToRefresh, PrepareToRefresh, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.RefreshInterlin, RefreshInterlin, m_propertyTable.GetWindow()); } /// @@ -1356,7 +1356,7 @@ protected override void UpdateContextHistory() link.PropertyTableEntries.Add(new Property("InterlinearTab", InterlinearTab.ToString())); Clerk.SelectedRecordChanged(true, true); // make sure we update the record count in the Status bar. - Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, link)); + Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, link, m_propertyTable.GetWindow())); } } diff --git a/Src/LexText/Interlinear/InterlinearTextsRecordClerk.cs b/Src/LexText/Interlinear/InterlinearTextsRecordClerk.cs index 6c8f286f40..7e4077e45e 100644 --- a/Src/LexText/Interlinear/InterlinearTextsRecordClerk.cs +++ b/Src/LexText/Interlinear/InterlinearTextsRecordClerk.cs @@ -41,7 +41,7 @@ public List GetScriptureIds() public override void ActivateUI(bool useRecordTreeBar, bool updateStatusBar = true) { // Only needs to handle changes if this RecordClerk is being used in the GUI. - Subscriber.Subscribe(EventConstants.AddTexts, AddTexts); + Subscriber.Subscribe(EventConstants.AddTexts, AddTexts, m_propertyTable.GetWindow()); base.ActivateUI(useRecordTreeBar, updateStatusBar); } @@ -259,7 +259,7 @@ private bool AddNewText(Command command) { // Tell the user we're turning off the filter, and then do it. MessageBox.Show(ITextStrings.ksTurningOffFilter, ITextStrings.ksNote, MessageBoxButtons.OK); - Publisher.Publish(new PublisherParameterObject(EventConstants.RemoveFilters, this)); + Publisher.Publish(new PublisherParameterObject(EventConstants.RemoveFilters, this, m_propertyTable.GetWindow())); m_activeMenuBarFilter = null; } SaveOnChangeRecord(); // commit any changes before we create a new text. @@ -288,7 +288,7 @@ private bool AddNewText(Command command) return false; if (!InDesiredTool("interlinearEdit")) { - Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, new FwLinkArgs("interlinearEdit", CurrentObject.Guid))); + Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, new FwLinkArgs("interlinearEdit", CurrentObject.Guid), m_propertyTable.GetWindow())); } return true; } diff --git a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs index d075ee9984..c321b4f89d 100644 --- a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs +++ b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs @@ -2630,7 +2630,7 @@ private void OnSelectCreateNewEntry(object sender, EventArgs args) private void OnSelectEditLexicalEntry(object sender, EventArgs args) { ILexEntry lexEntry = GetLexEntry(); - Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToPopupLexEntry, lexEntry.Hvo)); + Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToPopupLexEntry, lexEntry.Hvo, m_sandbox.m_propertyTable?.GetWindow())); } private ILexEntry GetLexEntry() diff --git a/Src/LexText/Interlinear/StatisticsView.cs b/Src/LexText/Interlinear/StatisticsView.cs index 386977bec8..8e892bf357 100644 --- a/Src/LexText/Interlinear/StatisticsView.cs +++ b/Src/LexText/Interlinear/StatisticsView.cs @@ -88,7 +88,7 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu mediator.AddColleague(this); //add our current state to the history system string toolName = _propertyTable.GetStringProperty("currentContentControl", ""); - Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, Guid.Empty))); + Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, Guid.Empty), _propertyTable.GetWindow())); } private void RebuildStatisticsTable() diff --git a/Src/LexText/LexTextControls/DataNotebook/AnthroFieldMappingDlg.cs b/Src/LexText/LexTextControls/DataNotebook/AnthroFieldMappingDlg.cs index 2ca2e08adf..c37f0d63ca 100644 --- a/Src/LexText/LexTextControls/DataNotebook/AnthroFieldMappingDlg.cs +++ b/Src/LexText/LexTextControls/DataNotebook/AnthroFieldMappingDlg.cs @@ -279,7 +279,7 @@ private void m_btnAddCustom_Click(object sender, EventArgs e) { // Show the ConfigureCustomFields dialog (by publisher to avoid circular dependencies) Publisher.Publish(new PublisherParameterObject(EventConstants.ConfigureCustomFields, - new Tuple(m_mediator, m_propertyTable, AreaConstants.notebook))); + new Tuple(m_mediator, m_propertyTable, AreaConstants.notebook), m_propertyTable.GetWindow())); // Now, clean up our map of possible field targets and reload the field combo list. List delFields = new List(); foreach (int key in m_mapFlidName.Keys) diff --git a/Src/LexText/LexTextControls/EntryDlgListener.cs b/Src/LexText/LexTextControls/EntryDlgListener.cs index 2a91776b0f..6928c5b16e 100644 --- a/Src/LexText/LexTextControls/EntryDlgListener.cs +++ b/Src/LexText/LexTextControls/EntryDlgListener.cs @@ -34,7 +34,12 @@ protected override string PersistentLabel public InsertEntryDlgListener() { - Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector); + } + + public override void Init(Mediator mediator, PropertyTable propertyTable, System.Xml.XmlNode configurationParameters) + { + base.Init(mediator, propertyTable, configurationParameters); + Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector, propertyTable.GetWindow()); } #endregion Construction and Initialization diff --git a/Src/LexText/LexTextControls/InsertEntryDlg.cs b/Src/LexText/LexTextControls/InsertEntryDlg.cs index 0d748e843a..7b18916e13 100644 --- a/Src/LexText/LexTextControls/InsertEntryDlg.cs +++ b/Src/LexText/LexTextControls/InsertEntryDlg.cs @@ -1428,7 +1428,7 @@ private void InsertEntryDlg_Closing(object sender, CancelEventArgs e) CreateNewEntry(); if (DialogResult == DialogResult.Retry) { - Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToPopupLexEntry, m_entry.Hvo)); + Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToPopupLexEntry, m_entry.Hvo, m_propertyTable.GetWindow())); DialogResult = DialogResult.OK; } break; diff --git a/Src/LexText/LexTextControls/LexImportWizardMarker.cs b/Src/LexText/LexTextControls/LexImportWizardMarker.cs index bed7551572..8ab5facfd7 100644 --- a/Src/LexText/LexTextControls/LexImportWizardMarker.cs +++ b/Src/LexText/LexTextControls/LexImportWizardMarker.cs @@ -1061,7 +1061,7 @@ private void btnAddCustomField_Click(object sender, EventArgs e) return; } Publisher.Publish(new PublisherParameterObject(EventConstants.ConfigureCustomFields, - new Tuple(med, m_propertyTable, AreaConstants.lexicon))); + new Tuple(med, m_propertyTable, AreaConstants.lexicon), m_propertyTable.GetWindow())); // The above call can cause the Mediator to 'go away', so check it and // restore the member variable for everyone else who may be surprised diff --git a/Src/LexText/LexTextControls/RecordDlgListener.cs b/Src/LexText/LexTextControls/RecordDlgListener.cs index 82458b4de4..b352675884 100644 --- a/Src/LexText/LexTextControls/RecordDlgListener.cs +++ b/Src/LexText/LexTextControls/RecordDlgListener.cs @@ -30,7 +30,12 @@ protected override string PersistentLabel public InsertRecordDlgListener() { - Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector); + } + + public override void Init(Mediator mediator, PropertyTable propertyTable, System.Xml.XmlNode configurationParameters) + { + base.Init(mediator, propertyTable, configurationParameters); + Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector, propertyTable.GetWindow()); } #endregion Construction and Initialization diff --git a/Src/LexText/LexTextDll/AreaListener.cs b/Src/LexText/LexTextDll/AreaListener.cs index bd454aa5a4..1fd563f85f 100644 --- a/Src/LexText/LexTextDll/AreaListener.cs +++ b/Src/LexText/LexTextDll/AreaListener.cs @@ -154,11 +154,11 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu mediator.AddColleague(this); m_ctotalLists = 0; m_ccustomLists = 0; - Subscriber.Subscribe(EventConstants.SetInitialContentObject, SetInitialContentObject); - Subscriber.Subscribe(EventConstants.SetToolFromName, SetToolFromName); - Subscriber.Subscribe(EventConstants.ReloadAreaTools, ReloadAreaTools); - Subscriber.Subscribe(EventConstants.GetContentControlParameters, GetContentControlParameters); - Subscriber.Subscribe(EventConstants.GetToolForList, GetToolForList); + Subscriber.Subscribe(EventConstants.SetInitialContentObject, SetInitialContentObject, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.SetToolFromName, SetToolFromName, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.ReloadAreaTools, ReloadAreaTools, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.GetContentControlParameters, GetContentControlParameters, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.GetToolForList, GetToolForList, m_propertyTable.GetWindow()); } private DateTime m_lastToolChange = DateTime.MinValue; diff --git a/Src/LexText/Lexicon/FLExBridgeListener.cs b/Src/LexText/Lexicon/FLExBridgeListener.cs index d36413bec7..b3ca8c942f 100644 --- a/Src/LexText/Lexicon/FLExBridgeListener.cs +++ b/Src/LexText/Lexicon/FLExBridgeListener.cs @@ -348,7 +348,7 @@ public bool OnFLExBridge(object commandObject) if (conflictOccurred) { // Send a message for the reopened instance to display the message viewer (used to be conflict report), - // we have been disposed by now + // we have been disposed by now. Publisher.Publish(new PublisherParameterObject(EventConstants.ViewMessages, null)); } } @@ -498,7 +498,7 @@ public bool OnLiftBridge(object argument) if (conflictOccurred) { // Send a message for the reopened instance to display the message viewer (used to be conflict report), - // we have been disposed by now + // we have been disposed by now. Publisher.Publish(new PublisherParameterObject(EventConstants.ViewLiftMessages, null)); } } @@ -1512,7 +1512,7 @@ private void JumpToFlexObject(object sender, FLExJumpEventArgs e) if (!string.IsNullOrEmpty(e.JumpUrl)) { var args = new LocalLinkArgs { Link = e.JumpUrl }; - Publisher.Publish(new PublisherParameterObject(EventConstants.HandleLocalHotlink, args)); + Publisher.Publish(new PublisherParameterObject(EventConstants.HandleLocalHotlink, args, _propertyTable.GetWindow())); } } diff --git a/Src/LexText/Lexicon/LexReferenceMultiSlice.cs b/Src/LexText/Lexicon/LexReferenceMultiSlice.cs index f113ad3345..c75bb2ccb8 100644 --- a/Src/LexText/Lexicon/LexReferenceMultiSlice.cs +++ b/Src/LexText/Lexicon/LexReferenceMultiSlice.cs @@ -757,7 +757,7 @@ public void HandleMoreMenuItem(object sender, EventArgs ea) ILexRefType newKid = list.Services.GetInstance().Create(); list.PossibilitiesOS.Add(newKid); m_cache.DomainDataByFlid.EndUndoTask(); - Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, new FwLinkArgs("lexRefEdit", newKid.Guid))); + Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, new FwLinkArgs("lexRefEdit", newKid.Guid), m_propertyTable.GetWindow())); } protected void ExpandNewNode() diff --git a/Src/LexText/Lexicon/LexReferenceSequenceView.cs b/Src/LexText/Lexicon/LexReferenceSequenceView.cs index 214f3b458e..0d57fe497f 100644 --- a/Src/LexText/Lexicon/LexReferenceSequenceView.cs +++ b/Src/LexText/Lexicon/LexReferenceSequenceView.cs @@ -85,7 +85,7 @@ protected override void Delete() { if (m_displayParent != null && hvoObj == m_displayParent.Hvo) // We need to handle this the same way as the delete command in the slice menu. - Publisher.Publish(new PublisherParameterObject(EventConstants.DataTreeDelete, null)); + Publisher.Publish(new PublisherParameterObject(EventConstants.DataTreeDelete, null, m_propertyTable.GetWindow())); else DeleteObjectFromVector(sel, cvsli, hvoObj, LexEdStrings.ksUndoDeleteRef, LexEdStrings.ksRedoDeleteRef); } diff --git a/Src/LexText/Lexicon/ReversalListener.cs b/Src/LexText/Lexicon/ReversalListener.cs index 6d046ddc96..9588fea8b2 100644 --- a/Src/LexText/Lexicon/ReversalListener.cs +++ b/Src/LexText/Lexicon/ReversalListener.cs @@ -449,7 +449,7 @@ public override void ChangeOwningObject(Guid newGuid, bool updateAndNotify, Dict SetOwningObject(newOwningObj, updateAndNotify); // Reloads and sorts the list. m_propertyTable.SetProperty("ActiveClerkOwningObject", newOwningObj, true); m_propertyTable.SetPropertyPersistence("ActiveClerkOwningObject", false); - Publisher.Publish(new PublisherParameterObject(EventConstants.ClerkOwningObjChanged, this)); + Publisher.Publish(new PublisherParameterObject(EventConstants.ClerkOwningObjChanged, this, m_propertyTable.GetWindow())); } else { diff --git a/Src/LexText/Morphology/MasterCatDlgListener.cs b/Src/LexText/Morphology/MasterCatDlgListener.cs index 540454fd7d..020593cca5 100644 --- a/Src/LexText/Morphology/MasterCatDlgListener.cs +++ b/Src/LexText/Morphology/MasterCatDlgListener.cs @@ -35,7 +35,12 @@ protected override string PersistentLabel /// public MasterCatDlgListener() { - Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector); + } + + public override void Init(Mediator mediator, PropertyTable propertyTable, System.Xml.XmlNode configurationParameters) + { + base.Init(mediator, propertyTable, configurationParameters); + Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector, propertyTable.GetWindow()); } #endregion Construction and Initialization diff --git a/Src/LexText/Morphology/MasterDlgListener.cs b/Src/LexText/Morphology/MasterDlgListener.cs index 841bb7867e..bec52ab696 100644 --- a/Src/LexText/Morphology/MasterDlgListener.cs +++ b/Src/LexText/Morphology/MasterDlgListener.cs @@ -155,7 +155,7 @@ protected virtual void Dispose(bool disposing) /// /// Initialize the IxCoreColleague object. /// - public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configurationParameters) + public virtual void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configurationParameters) { CheckDisposed(); diff --git a/Src/LexText/Morphology/MasterInflFeatDlgListener.cs b/Src/LexText/Morphology/MasterInflFeatDlgListener.cs index 9f7f22e245..0b0238b035 100644 --- a/Src/LexText/Morphology/MasterInflFeatDlgListener.cs +++ b/Src/LexText/Morphology/MasterInflFeatDlgListener.cs @@ -36,7 +36,12 @@ protected override string PersistentLabel /// public MasterInflFeatDlgListener() { - Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector); + } + + public override void Init(Mediator mediator, PropertyTable propertyTable, System.Xml.XmlNode configurationParameters) + { + base.Init(mediator, propertyTable, configurationParameters); + Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector, propertyTable.GetWindow()); } #endregion Construction and Initialization diff --git a/Src/LexText/Morphology/MasterPhonFeatDlgListener.cs b/Src/LexText/Morphology/MasterPhonFeatDlgListener.cs index 1e36f1471c..35a740af96 100644 --- a/Src/LexText/Morphology/MasterPhonFeatDlgListener.cs +++ b/Src/LexText/Morphology/MasterPhonFeatDlgListener.cs @@ -39,7 +39,12 @@ protected override string PersistentLabel /// public MasterPhonFeatDlgListener() { - Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector); + } + + public override void Init(Mediator mediator, PropertyTable propertyTable, System.Xml.XmlNode configurationParameters) + { + base.Init(mediator, propertyTable, configurationParameters); + Subscriber.Subscribe(EventConstants.DialogInsertItemInVector, DialogInsertItemInVector, propertyTable.GetWindow()); } #endregion Construction and Initialization diff --git a/Src/LexText/Morphology/ParserAnalysisRemover.cs b/Src/LexText/Morphology/ParserAnalysisRemover.cs index 95937434a8..e1a7ac110c 100644 --- a/Src/LexText/Morphology/ParserAnalysisRemover.cs +++ b/Src/LexText/Morphology/ParserAnalysisRemover.cs @@ -103,7 +103,7 @@ public void Process() m_dlg.ProgressBar.Step = 1; // stop parser if it's running. - Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser)); + Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser, null, m_dlg.PropTable?.GetWindow())); NonUndoableUnitOfWorkHelper.Do(cache.ActionHandlerAccessor, () => { @@ -125,7 +125,7 @@ public void Process() }); // Interlin display may be affected. - Publisher.Publish(new PublisherParameterObject(EventConstants.RefreshInterlin, null)); + Publisher.Publish(new PublisherParameterObject(EventConstants.RefreshInterlin, null, m_dlg.PropTable?.GetWindow())); } #endregion IUtility implementation diff --git a/Src/LexText/Morphology/RespellerDlg.cs b/Src/LexText/Morphology/RespellerDlg.cs index 1ed033efd6..461eb1c3bc 100644 --- a/Src/LexText/Morphology/RespellerDlg.cs +++ b/Src/LexText/Morphology/RespellerDlg.cs @@ -572,7 +572,7 @@ private void m_btnApply_Click(object sender, EventArgs e) // On the other hand, we don't want to update the new wordform until after DoIt...it might not exist before, // and we won't be messing up any existing occurrences. - Publisher.Publish(new PublisherParameterObject(EventConstants.ItemDataModified, m_cache.ServiceLocator.GetObject(m_respellUndoaction.NewWordform))); + Publisher.Publish(new PublisherParameterObject(EventConstants.ItemDataModified, m_cache.ServiceLocator.GetObject(m_respellUndoaction.NewWordform), m_propertyTable.GetWindow())); ChangesWereMade = true; @@ -1537,10 +1537,10 @@ private void CoreDoIt(ProgressDialogWorkingOn progress, Mediator mediator) } else { - Publisher.Publish(new PublisherParameterObject(EventConstants.ItemDataModified, wfOld)); + Publisher.Publish(new PublisherParameterObject(EventConstants.ItemDataModified, wfOld, RespellSda?.PropTable?.GetWindow())); } } - Publisher.Publish(new PublisherParameterObject(EventConstants.ItemDataModified, wfNew)); + Publisher.Publish(new PublisherParameterObject(EventConstants.ItemDataModified, wfNew, RespellSda?.PropTable?.GetWindow())); uuow.RollBack = false; } } diff --git a/Src/LexText/Morphology/UserAnalysisRemover.cs b/Src/LexText/Morphology/UserAnalysisRemover.cs index 12a255178f..5e9e611690 100644 --- a/Src/LexText/Morphology/UserAnalysisRemover.cs +++ b/Src/LexText/Morphology/UserAnalysisRemover.cs @@ -125,7 +125,7 @@ public void Process() } }); - Publisher.Publish(new PublisherParameterObject(EventConstants.RefreshInterlin, null)); + Publisher.Publish(new PublisherParameterObject(EventConstants.RefreshInterlin, null, m_dlg.PropTable?.GetWindow())); } diff --git a/Src/LexText/ParserUI/ImportWordSetDlg.cs b/Src/LexText/ParserUI/ImportWordSetDlg.cs index 76f27a1710..a59b9878fc 100644 --- a/Src/LexText/ParserUI/ImportWordSetDlg.cs +++ b/Src/LexText/ParserUI/ImportWordSetDlg.cs @@ -32,6 +32,7 @@ public class ImportWordSetDlg : Form /// xCore Mediator. /// protected Mediator m_mediator; + protected PropertyTable m_propertyTable; /// /// /// @@ -72,6 +73,7 @@ public ImportWordSetDlg(Mediator mediator, PropertyTable propertyTable) { //InitializeComponent(); m_mediator = mediator; + m_propertyTable = propertyTable; m_cache = propertyTable.GetValue("cache"); m_helpTopicProvider = propertyTable.GetValue("HelpTopicProvider"); @@ -208,10 +210,10 @@ private void btnImport_Click(object sender, System.EventArgs e) return; } - Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser)); + Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser, null, m_propertyTable.GetWindow())); CreateWordsetFromFiles(m_paths); - Publisher.Publish(new PublisherParameterObject(EventConstants.FilterListChanged, null)); // let record clerk know the list of filters has changed. + Publisher.Publish(new PublisherParameterObject(EventConstants.FilterListChanged, null, m_propertyTable.GetWindow())); // let record clerk know the list of filters has changed. DialogResult = DialogResult.OK; } diff --git a/Src/LexText/ParserUI/ParserListener.cs b/Src/LexText/ParserUI/ParserListener.cs index 9b4adfc524..7658879ab0 100644 --- a/Src/LexText/ParserUI/ParserListener.cs +++ b/Src/LexText/ParserUI/ParserListener.cs @@ -86,9 +86,9 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu m_sda = m_cache.MainCacheAccessor; m_sda.AddNotification(this); - Subscriber.Subscribe(EventConstants.StopParser, StopParser); - Subscriber.Subscribe(EventConstants.RefreshPopupWindowFonts, RefreshPopupWindowFonts); - Subscriber.Subscribe(EventConstants.Idle, Idle); + Subscriber.Subscribe(EventConstants.StopParser, StopParser, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.RefreshPopupWindowFonts, RefreshPopupWindowFonts, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.Idle, Idle, m_propertyTable.GetWindow()); } /// diff --git a/Src/XCore/xCoreInterfaces/IxCoreColleague.cs b/Src/XCore/xCoreInterfaces/IxCoreColleague.cs index 727399793c..40ed6d03ae 100644 --- a/Src/XCore/xCoreInterfaces/IxCoreColleague.cs +++ b/Src/XCore/xCoreInterfaces/IxCoreColleague.cs @@ -11,6 +11,7 @@ using System.Xml; using System.Windows.Forms; using System.Collections.Generic; +using SIL.FieldWorks.Common.FwUtils; namespace XCore { @@ -137,8 +138,10 @@ PropertyTable PropTable /// /// This is an interface implemented by xWindow (and perhaps other main window classes?) /// that allows a few of their key functions to be called by things that don't reference xCore. + /// A main window is also the Pub/Sub delivery scope (IPubSubScope): messages published with a + /// window as their scope are delivered only to subscribers that subscribed with the same window. /// - public interface IxWindow : IMediatorProvider, IPropertyTableProvider + public interface IxWindow : IMediatorProvider, IPropertyTableProvider, IPubSubScope { /// /// Call this for the duration of a block of code where we don't want idle events. diff --git a/Src/XCore/xCoreInterfaces/PropertyTable.cs b/Src/XCore/xCoreInterfaces/PropertyTable.cs index 875675fefe..eb312f676b 100644 --- a/Src/XCore/xCoreInterfaces/PropertyTable.cs +++ b/Src/XCore/xCoreInterfaces/PropertyTable.cs @@ -323,6 +323,22 @@ public T GetValue(string name) return GetValue(name, SettingsGroup.BestSettings); } + /// + /// The main window owning this property table (each main window registers itself under + /// "window" in its own table), or null when unavailable — a private or test property + /// table, or early window construction. Being an IxWindow, the result also serves as the + /// Pub/Sub delivery scope; a null scope means process-wide delivery, so "unavailable" + /// can only widen delivery, never lose a message. + /// + /// Null-safe by design: unlike GetValue<T>, this never throws when the + /// "window" property holds a non-window (as some test fixtures do). + public IxWindow GetWindow() + { + CheckDisposed(); + + return GetValue("window") as IxWindow; + } + /// /// Get the value of the property of the specified settingsGroup. /// diff --git a/Src/XCore/xWindow.cs b/Src/XCore/xWindow.cs index eb6b73f9e5..8c4c4792a2 100644 --- a/Src/XCore/xWindow.cs +++ b/Src/XCore/xWindow.cs @@ -441,8 +441,8 @@ private void BootstrapPart1() InitializeComponent(); - Subscriber.Subscribe(EventConstants.PrepareToRefresh, PrepareToRefresh); - Subscriber.Subscribe(EventConstants.SetInitialContentObject, SetInitialContentObject); + Subscriber.Subscribe(EventConstants.PrepareToRefresh, PrepareToRefresh, this); + Subscriber.Subscribe(EventConstants.SetInitialContentObject, SetInitialContentObject, this); } /// @@ -802,7 +802,7 @@ protected virtual void LoadUIFromXmlDocument(XmlDocument configuration, string c // Add the content control // Note: We should be able to do it directly, since everything needed is in the default properties. - Publisher.Publish(new PublisherParameterObject(EventConstants.SetInitialContentObject, m_windowConfigurationNode)); + Publisher.Publish(new PublisherParameterObject(EventConstants.SetInitialContentObject, m_windowConfigurationNode, this)); m_sidebarAdapter.FinishInit(); m_menuBarAdapter.FinishInit(); @@ -1230,8 +1230,10 @@ protected ChoiceGroupCollection MakeGroupSet(XmlNode m_windowConfigurationNode, /// private void SetInitialContentObject(object windowConfigurationNode) { - // We only want to handle this if there are no other listeners - if (Subscriber.Subscriptions[EventConstants.SetInitialContentObject].Count > 1) + // We only want to handle this if there are no other listeners for this window + // (count only subscriptions in this window's scope or with no scope). + if (Subscriber.Subscriptions[EventConstants.SetInitialContentObject] + .Count(subscription => subscription.Value == null || ReferenceEquals(subscription.Value, this)) > 1) return; CheckDisposed(); @@ -1789,8 +1791,8 @@ public void SynchronizedOnIdleTime() UpdateControls(); - // Notify any subscribers that the application is idle. - Publisher.Publish(new PublisherParameterObject(EventConstants.Idle, null)); + // Notify this window's subscribers that the application is idle. + Publisher.Publish(new PublisherParameterObject(EventConstants.Idle, null, this)); } /// @@ -1912,7 +1914,7 @@ public bool OnCloseWindow(object sender) { CheckDisposed(); - Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser)); + Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser, null, this)); this.Close(); return true; @@ -2243,7 +2245,7 @@ protected virtual void XWindow_Closing(object sender, CancelEventArgs e) m_widgetUpdateTimer.Enabled = false; e.Cancel = false; - Publisher.Publish(new PublisherParameterObject(EventConstants.ConsideringClosing, e)); + Publisher.Publish(new PublisherParameterObject(EventConstants.ConsideringClosing, e, this)); if (e.Cancel) { m_widgetUpdateTimer.Enabled = true; diff --git a/Src/xWorks/CustomListDlg.cs b/Src/xWorks/CustomListDlg.cs index 803c2861cc..3ba3bf6896 100644 --- a/Src/xWorks/CustomListDlg.cs +++ b/Src/xWorks/CustomListDlg.cs @@ -315,7 +315,7 @@ protected virtual void DoOKAction() private void ReloadListsArea() { - Publisher.Publish(new PublisherParameterObject(EventConstants.ReloadAreaTools, "lists")); + Publisher.Publish(new PublisherParameterObject(EventConstants.ReloadAreaTools, "lists", m_propertyTable.GetWindow())); } /// diff --git a/Src/xWorks/DTMenuHandler.cs b/Src/xWorks/DTMenuHandler.cs index 5bd344373b..ea3618f3e8 100644 --- a/Src/xWorks/DTMenuHandler.cs +++ b/Src/xWorks/DTMenuHandler.cs @@ -173,7 +173,7 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu m_mediator = mediator; m_propertyTable = propertyTable; m_configuration = configurationParameters; - Subscriber.Subscribe(EventConstants.DataTreeDelete, DataTreeDelete); + Subscriber.Subscribe(EventConstants.DataTreeDelete, DataTreeDelete, m_propertyTable.GetWindow()); } /// diff --git a/Src/xWorks/DictionaryConfigurationController.cs b/Src/xWorks/DictionaryConfigurationController.cs index 5c05770e49..0ef02cf553 100644 --- a/Src/xWorks/DictionaryConfigurationController.cs +++ b/Src/xWorks/DictionaryConfigurationController.cs @@ -374,7 +374,7 @@ public DictionaryConfigurationController(IDictionaryConfigurationView view, Prop SaveModel(); MasterRefreshRequired = false; // We're reloading the whole app, that's refresh enough View.Close(); - Publisher.Publish(new PublisherParameterObject(EventConstants.ReloadAreaTools, "lists")); + Publisher.Publish(new PublisherParameterObject(EventConstants.ReloadAreaTools, "lists", _propertyTable.GetWindow())); }; SetManagerTypeInfo(dialog); dialog.ShowDialog(View as Form); diff --git a/Src/xWorks/DictionaryConfigurationMigrators/PreHistoricMigrator.cs b/Src/xWorks/DictionaryConfigurationMigrators/PreHistoricMigrator.cs index 33e86850c1..d57a72467a 100644 --- a/Src/xWorks/DictionaryConfigurationMigrators/PreHistoricMigrator.cs +++ b/Src/xWorks/DictionaryConfigurationMigrators/PreHistoricMigrator.cs @@ -128,7 +128,7 @@ private XmlNode GetConfigureLayoutsNodeForTool(string tool) { var collector = new XmlNode[1]; var parameter = new Tuple("lexicon", tool, collector); - Publisher.Publish(new PublisherParameterObject(EventConstants.GetContentControlParameters, parameter)); + Publisher.Publish(new PublisherParameterObject(EventConstants.GetContentControlParameters, parameter, m_propertyTable.GetWindow())); var controlNode = collector[0]; var parameters = controlNode.SelectSingleNode("parameters"); var configureLayouts = XmlUtils.FindNode(parameters, "configureLayouts"); diff --git a/Src/xWorks/ExportDialog.cs b/Src/xWorks/ExportDialog.cs index 122b5220d4..5f73d91a64 100644 --- a/Src/xWorks/ExportDialog.cs +++ b/Src/xWorks/ExportDialog.cs @@ -518,7 +518,7 @@ private Control EnsureViewInfo() } var collector = new XmlNode[1]; var parameter = new Tuple(area, tool, collector); - Publisher.Publish(new PublisherParameterObject(EventConstants.GetContentControlParameters, parameter)); + Publisher.Publish(new PublisherParameterObject(EventConstants.GetContentControlParameters, parameter, m_propertyTable.GetWindow())); var controlNode = collector[0]; Debug.Assert(controlNode != null); XmlNode dynLoaderNode = controlNode.SelectSingleNode("dynamicloaderinfo"); @@ -1078,7 +1078,7 @@ private object ExportGrammarSketch(IThreadedProgress progress, object[] args) var sXslts = (string)args[2]; m_progressDlg = progress; var parameter = new Tuple(sDataType, outPath, sXslts); - Publisher.Publish(new PublisherParameterObject(EventConstants.SaveAsWebpage, parameter)); + Publisher.Publish(new PublisherParameterObject(EventConstants.SaveAsWebpage, parameter, m_propertyTable.GetWindow())); m_progressDlg.Step(1000); return null; } diff --git a/Src/xWorks/FwXApp.cs b/Src/xWorks/FwXApp.cs index 3031c04195..6278bbc0e7 100644 --- a/Src/xWorks/FwXApp.cs +++ b/Src/xWorks/FwXApp.cs @@ -200,7 +200,7 @@ public override void HandleIncomingLink(FwLinkArgs link) FwXWindow fwxwnd = m_rgMainWindows.Count > 0 ? (FwXWindow)m_rgMainWindows[0] : null; if (fwxwnd != null) { - Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, link)); + Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, link, fwxwnd)); bool topmost = fwxwnd.TopMost; fwxwnd.TopMost = true; fwxwnd.TopMost = topmost; diff --git a/Src/xWorks/FwXWindow.cs b/Src/xWorks/FwXWindow.cs index 67f595cddd..abac6d170a 100644 --- a/Src/xWorks/FwXWindow.cs +++ b/Src/xWorks/FwXWindow.cs @@ -314,7 +314,7 @@ private void BasicInit(FwApp app) m_propertyTable.SetProperty("App", app, true); m_propertyTable.SetPropertyPersistence("App", false); } - Subscriber.Subscribe(EventConstants.JumpToPopupLexEntry, JumpToPopupLexEntry); + Subscriber.Subscribe(EventConstants.JumpToPopupLexEntry, JumpToPopupLexEntry, this); } /// ------------------------------------------------------------------------------------ @@ -1673,7 +1673,7 @@ public void PrepareToRefresh() // REVIEW (Hasso) 2023.07: we had used MediatorSendMessageToAllNow as this needs to go to everyone right now. This // method on the mediator will not stop when the message is handled, nor is it deferred. // REVIEW: do we need to replicate this urgency in PubSub? - Publisher.Publish(new PublisherParameterObject(EventConstants.PrepareToRefresh)); + Publisher.Publish(new PublisherParameterObject(EventConstants.PrepareToRefresh, null, this)); } /// @@ -1870,7 +1870,7 @@ public bool ShowStylesDialog(string paraStyleName, string charStyleName, (m_app as FwXApp).OnMasterRefresh(null); // Refresh the fonts on popup windows. - Publisher.Publish(new PublisherParameterObject(EventConstants.RefreshPopupWindowFonts, null)); + Publisher.Publish(new PublisherParameterObject(EventConstants.RefreshPopupWindowFonts, null, this)); } return false; // refresh already called if needed } @@ -2136,7 +2136,7 @@ protected override void XWindow_Closing(object sender, System.ComponentModel.Can // into a situation where the main window was disposed of while the parser // thread was trying to execute com calls on the UI thread and using the // main form as the Invoke point. - Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser)); + Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser, null, this)); base.XWindow_Closing(sender, e); } @@ -2260,7 +2260,7 @@ public bool OnFinishedInit() if (m_startupLink != null) { - Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, m_startupLink)); + Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, m_startupLink, this)); } UpdateControls(); return true; diff --git a/Src/xWorks/GeneratedHtmlViewer.cs b/Src/xWorks/GeneratedHtmlViewer.cs index c3c2f0e301..1ece8d24ca 100644 --- a/Src/xWorks/GeneratedHtmlViewer.cs +++ b/Src/xWorks/GeneratedHtmlViewer.cs @@ -789,9 +789,9 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu //add our current state to the history system string toolName = m_propertyTable.GetStringProperty("currentContentControl", ""); - Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, Guid.Empty))); + Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, Guid.Empty), m_propertyTable.GetWindow())); - Subscriber.Subscribe(EventConstants.SaveAsWebpage, SaveAsWebpage); + Subscriber.Subscribe(EventConstants.SaveAsWebpage, SaveAsWebpage, m_propertyTable.GetWindow()); } #if notnow void Browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) diff --git a/Src/xWorks/LinkListener.cs b/Src/xWorks/LinkListener.cs index c69a9065df..4e4a416c06 100644 --- a/Src/xWorks/LinkListener.cs +++ b/Src/xWorks/LinkListener.cs @@ -195,9 +195,9 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu m_propertyTable.SetProperty("LinkListener", this, true); m_propertyTable.SetPropertyPersistence("LinkListener", false); - Subscriber.Subscribe(EventConstants.AddContextToHistory, AddContextToHistory); - Subscriber.Subscribe(EventConstants.HandleLocalHotlink, HandleLocalHotlink); - Subscriber.Subscribe(EventConstants.FollowLink, FollowLink); + Subscriber.Subscribe(EventConstants.AddContextToHistory, AddContextToHistory, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.HandleLocalHotlink, HandleLocalHotlink, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.FollowLink, FollowLink, m_propertyTable.GetWindow()); } /// @@ -418,7 +418,7 @@ public bool OnTestFollowLink(object unused) CheckDisposed(); LcmCache cache = m_propertyTable.GetValue("cache"); Guid[] guids = (from entry in cache.LanguageProject.LexDbOA.Entries select entry.Guid).ToArray(); - Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, new FwLinkArgs("lexiconEdit", guids[guids.Length - 1]))); + Publisher.Publish(new PublisherParameterObject(EventConstants.FollowLink, new FwLinkArgs("lexiconEdit", guids[guids.Length - 1]), m_propertyTable.GetWindow())); return true; } @@ -512,7 +512,7 @@ private bool FollowActiveLink(bool suspendLoadingRecord) // Thus we've created this method (on AreaListener) which we call through the FwUtils Publisher/Subscriber. var parameters = new object[2]; parameters[0] = majorObject; - Publisher.Publish(new PublisherParameterObject(EventConstants.GetToolForList, parameters)); + Publisher.Publish(new PublisherParameterObject(EventConstants.GetToolForList, parameters, m_propertyTable.GetWindow())); realTool = (string)parameters[1]; break; case RnResearchNbkTags.kClassId: @@ -555,7 +555,7 @@ private bool FollowActiveLink(bool suspendLoadingRecord) true); m_propertyTable.SetPropertyPersistence("SuspendLoadingRecordUntilOnJumpToRecord", false); } - Publisher.Publish(new PublisherParameterObject(EventConstants.SetToolFromName, m_lnkActive.ToolName)); + Publisher.Publish(new PublisherParameterObject(EventConstants.SetToolFromName, m_lnkActive.ToolName, m_propertyTable.GetWindow())); // Note: It can be Guid.Empty in cases where it was never set, // or more likely, when the HVO was set to -1. if (m_lnkActive.TargetGuid != Guid.Empty) diff --git a/Src/xWorks/RecordBrowseView.cs b/Src/xWorks/RecordBrowseView.cs index 6f958c283b..c9cf56c52e 100644 --- a/Src/xWorks/RecordBrowseView.cs +++ b/Src/xWorks/RecordBrowseView.cs @@ -678,8 +678,8 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod // so call it again, since we are ready now. ShowRecord(); - Subscriber.Subscribe(EventConstants.ClerkOwningObjChanged, ClerkOwningObjChanged); - Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing); + Subscriber.Subscribe(EventConstants.ClerkOwningObjChanged, ClerkOwningObjChanged, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing, m_propertyTable.GetWindow()); } private void CheckExpectedListItemsClassInSync() diff --git a/Src/xWorks/RecordClerk.cs b/Src/xWorks/RecordClerk.cs index 5d027e8b75..e3744ae20b 100644 --- a/Src/xWorks/RecordClerk.cs +++ b/Src/xWorks/RecordClerk.cs @@ -1375,7 +1375,7 @@ private void UpdateOwningObject(bool fUpdateOwningObjectOnlyIfChanged) } } if (old != newObj) - Publisher.Publish(new PublisherParameterObject(EventConstants.ClerkOwningObjChanged, this)); + Publisher.Publish(new PublisherParameterObject(EventConstants.ClerkOwningObjChanged, this, m_propertyTable.GetWindow())); } } @@ -2102,8 +2102,8 @@ public virtual void ReloadIfNeeded() virtual public void ActivateUI(bool useRecordTreeBar, bool updateStatusBar = true) { // RecordClerk only needs to handle changes if RecordClerk is being used in GUI - Subscriber.Subscribe(EventConstants.FilterListChanged, FilterListChanged); - Subscriber.Subscribe(EventConstants.DeleteRecord, DeleteRecord); + Subscriber.Subscribe(EventConstants.FilterListChanged, FilterListChanged, m_propertyTable.GetWindow()); + Subscriber.Subscribe(EventConstants.DeleteRecord, DeleteRecord, m_propertyTable.GetWindow()); m_fIsActiveInGui = true; CheckDisposed(); @@ -2312,7 +2312,7 @@ private void BroadcastChange(bool suppressFocusChange) { m_list.CurrentIndex = FindClosestValidIndex(idx, cobj); } - Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser)); + Publisher.Publish(new PublisherParameterObject(EventConstants.StopParser, null, m_propertyTable.GetWindow())); } finally { @@ -2539,7 +2539,7 @@ public bool OnInsertItemInVector(object argument) try { var retObj = new ReturnObject(argument); - Publisher.Publish(new PublisherParameterObject(EventConstants.DialogInsertItemInVector, retObj)); + Publisher.Publish(new PublisherParameterObject(EventConstants.DialogInsertItemInVector, retObj, m_propertyTable.GetWindow())); if (retObj.ReturnValue) return true; } @@ -3160,13 +3160,13 @@ private void m_list_AboutToReload(object sender, EventArgs e) // For now, we'll not try to be concerned about restoring scroll position // in a context where we're reloading after suppressing a reload. if (!m_fReloadingDueToMissingObject) - Publisher.Publish(new PublisherParameterObject(EventConstants.SaveScrollPosition, this)); + Publisher.Publish(new PublisherParameterObject(EventConstants.SaveScrollPosition, this, m_propertyTable.GetWindow())); } private void m_list_DoneReload(object sender, EventArgs e) { if (!m_fReloadingDueToMissingObject) - Publisher.Publish(new PublisherParameterObject(EventConstants.RestoreScrollPosition, this)); + Publisher.Publish(new PublisherParameterObject(EventConstants.RestoreScrollPosition, this, m_propertyTable.GetWindow())); } internal ListUpdateHelper UpdateHelper { get; set; } diff --git a/Src/xWorks/RecordDocView.cs b/Src/xWorks/RecordDocView.cs index 0c026e09a0..70b82acf99 100644 --- a/Src/xWorks/RecordDocView.cs +++ b/Src/xWorks/RecordDocView.cs @@ -68,7 +68,7 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod InitBase(mediator, propertyTable, configurationParameters); m_fullyInitialized = true; - Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing); + Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing, m_propertyTable.GetWindow()); } protected override void GetMessageAdditionalTargets(System.Collections.Generic.List collector) diff --git a/Src/xWorks/RecordEditView.cs b/Src/xWorks/RecordEditView.cs index 413ac65c64..c728d60213 100644 --- a/Src/xWorks/RecordEditView.cs +++ b/Src/xWorks/RecordEditView.cs @@ -126,7 +126,7 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod m_dataEntryForm.StyleSheet = FontHeightAdjuster.StyleSheetFromPropertyTable(m_propertyTable); m_fullyInitialized = true; - Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing); + Subscriber.Subscribe(EventConstants.ConsideringClosing, ConsideringClosing, m_propertyTable.GetWindow()); } /// ----------------------------------------------------------------------------------- diff --git a/Src/xWorks/RecordView.cs b/Src/xWorks/RecordView.cs index e9d55c9800..fff7afb81e 100644 --- a/Src/xWorks/RecordView.cs +++ b/Src/xWorks/RecordView.cs @@ -163,7 +163,7 @@ protected virtual void UpdateContextHistory() if (Clerk.CurrentObject != null) guid = Clerk.CurrentObject.Guid; Clerk.SelectedRecordChanged(true, true); // make sure we update the record count in the Status bar. - Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, guid))); + Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, guid), m_propertyTable.GetWindow())); } } diff --git a/Src/xWorks/XWorksViewBase.cs b/Src/xWorks/XWorksViewBase.cs index e56cc91eee..b0ed0e75f6 100644 --- a/Src/xWorks/XWorksViewBase.cs +++ b/Src/xWorks/XWorksViewBase.cs @@ -565,7 +565,7 @@ private void mp_ShowFirstPaneChanged(object sender, EventArgs e) private void ReloadListsArea() { - Publisher.Publish(new PublisherParameterObject(EventConstants.ReloadAreaTools, "lists")); + Publisher.Publish(new PublisherParameterObject(EventConstants.ReloadAreaTools, "lists", m_propertyTable.GetWindow())); } private void DoDeleteCustomListCmd(ICmPossibilityList curList) diff --git a/Src/xWorks/XhtmlDocView.cs b/Src/xWorks/XhtmlDocView.cs index 63672c5f8d..cb58b39154 100644 --- a/Src/xWorks/XhtmlDocView.cs +++ b/Src/xWorks/XhtmlDocView.cs @@ -713,7 +713,7 @@ private static void JumpToFieldAt(object sender, EventArgs e) } // Jump to field on idle to allow JumpToRecord to finish. object[] arguments = new object[] { fieldObj.Hvo, fieldName, fieldElement.TextContent }; - Publisher.PublishAtEndOfAction(new PublisherParameterObject(EventConstants.JumpToField, arguments)); + Publisher.PublishAtEndOfAction(new PublisherParameterObject(EventConstants.JumpToField, arguments, propertyTable.GetWindow())); } } @@ -1520,7 +1520,7 @@ private string SaveConfiguredXhtmlWithProgress(string configurationFile, bool al { if (progressDlg.IsCanceling) { - Publisher.Publish(new PublisherParameterObject(EventConstants.SetToolFromName, "lexiconEdit")); + Publisher.Publish(new PublisherParameterObject(EventConstants.SetToolFromName, "lexiconEdit", m_propertyTable.GetWindow())); } else { diff --git a/Src/xWorks/XmlDocView.cs b/Src/xWorks/XmlDocView.cs index fdbca0c90a..a543bdfff2 100644 --- a/Src/xWorks/XmlDocView.cs +++ b/Src/xWorks/XmlDocView.cs @@ -798,7 +798,7 @@ protected override void ShowRecord() Guid guid = Guid.Empty; if (clerk.CurrentObject != null) guid = clerk.CurrentObject.Guid; - Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, guid))); + Publisher.Publish(new PublisherParameterObject(EventConstants.AddContextToHistory, new FwLinkArgs(toolName, guid), m_propertyTable.GetWindow())); SelectAndScrollToCurrentRecord(); base.ShowRecord(); @@ -1269,7 +1269,7 @@ public override void Init(Mediator mediator, PropertyTable propertyTable, XmlNod InitBase(mediator, propertyTable, configurationParameters); - Subscriber.Subscribe(EventConstants.ClerkOwningObjChanged, ClerkOwningObjChanged); + Subscriber.Subscribe(EventConstants.ClerkOwningObjChanged, ClerkOwningObjChanged, m_propertyTable.GetWindow()); } ///