From 6d2abb74cf96df2ee2cc74adbea4bc55775838d2 Mon Sep 17 00:00:00 2001 From: mark-sil <83427558+mark-sil@users.noreply.github.com> Date: Wed, 11 Nov 2026 09:26:45 -0500 Subject: [PATCH] Publish JumpToRecord at once from three detail-pane handlers Three WinForms detail-pane handlers now publish JumpToRecord to their own window, so the window's active clerk selects the record before the handler returns. Until now each queued it with the Mediator's BroadcastMessageUntilHandled, which ran after the handler had returned and offered the jump to every colleague of the window. The three senders are ReversalPOSMenuHandler's OnMoveReversalPOS and OnMergeReversalPOS, and Slice.InsertObject, which jumps to a new sub-item that got no slice under the inserting one. The jump can run at once because nothing in these handlers needs it to be late. The reversal handlers do nothing after it, and in Slice.InsertObject the clerk's part of the jump touches no slice, so the focus step that follows is unaffected. What the unit of work left queued, the detail pane's postponed refresh, does not depend on the jump, and the clerk's display of the record it jumped to stays queued behind it, as before. When a filter hides the target, the clerk's prompt to turn the filter off now opens inside the handler, so the two reversal handlers publish as their last step. Slice.InsertObject tested the broadcast's result, which was always false; that test is gone and the focus step after it runs as before. Co-Authored-By: Claude Opus 5.5 --- .../SliceInsertJumpTests.cs | 126 ++++++++++++++++++ Src/Common/Controls/DetailControls/Slice.cs | 7 +- Src/LexText/Lexicon/ReversalEntryPOS.cs | 11 +- 3 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 Src/Common/Controls/DetailControls/DetailControlsTests/SliceInsertJumpTests.cs diff --git a/Src/Common/Controls/DetailControls/DetailControlsTests/SliceInsertJumpTests.cs b/Src/Common/Controls/DetailControls/DetailControlsTests/SliceInsertJumpTests.cs new file mode 100644 index 0000000000..053703e6e0 --- /dev/null +++ b/Src/Common/Controls/DetailControls/DetailControlsTests/SliceInsertJumpTests.cs @@ -0,0 +1,126 @@ +// 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) + +using System.Collections.Generic; +using System.Windows.Forms; +using NUnit.Framework; +using SIL.FieldWorks.Common.FwUtils; +using static SIL.FieldWorks.Common.FwUtils.FwUtils; +using SIL.LCModel; +using SIL.LCModel.Core.Text; +using XCore; + +namespace SIL.FieldWorks.Common.Framework.DetailControls +{ + /// + /// An insert that leaves no slice for the new object publishes JumpToRecord for it, to the + /// slice's own window only. + /// + [TestFixture] + public class SliceInsertJumpTests : MemoryOnlyBackendProviderRestoredForEachTestTestBase + { + private Inventory m_parts; + private Inventory m_layouts; + private ILexEntry m_entry; + private Mediator m_mediator; + private PropertyTable m_propertyTable; + private DataTree m_dtree; + private Form m_parent; + private StubWindow m_otherWindow; + private List m_jumps; + private List m_otherWindowJumps; + + public override void FixtureSetup() + { + base.FixtureSetup(); + m_layouts = DataTreeTests.GenerateLayouts(); + m_parts = DataTreeTests.GenerateParts(); + } + + public override void TestSetup() + { + base.TestSetup(); + + m_entry = Cache.ServiceLocator.GetInstance().Create(); + m_entry.CitationForm.VernacularDefaultWritingSystem = + TsStringUtils.MakeString("form", Cache.DefaultVernWs); + + m_mediator = new Mediator(); + m_propertyTable = new PropertyTable(m_mediator); + m_propertyTable.SetProperty("cache", Cache, false); + m_propertyTable.SetPropertyDispose("cache", false); + var window = new StubWindow(); + m_propertyTable.SetProperty("window", window, false); + m_otherWindow = new StubWindow(); + m_jumps = new List(); + m_otherWindowJumps = new List(); + Subscriber.Subscribe(EventConstants.JumpToRecord, RecordJump, window); + Subscriber.Subscribe(EventConstants.JumpToRecord, RecordOtherWindowJump, m_otherWindow); + + m_dtree = new DataTree(); + m_dtree.Init(m_mediator, m_propertyTable, null); + m_parent = new Form(); + m_parent.Controls.Add(m_dtree); + m_dtree.Initialize(Cache, false, m_layouts, m_parts); + // This layout shows the citation form only, so an etymology gets no slice. + m_dtree.ShowObject(m_entry, "CfOnly", null, m_entry, false); + + // The insert runs its own unit of work. + m_actionHandler.EndUndoTask(); + } + + public override void TestTearDown() + { + Subscriber.Unsubscribe(EventConstants.JumpToRecord, RecordJump); + Subscriber.Unsubscribe(EventConstants.JumpToRecord, RecordOtherWindowJump); + if (m_parent != null) + { + m_parent.Close(); + m_parent.Dispose(); + m_parent = null; + } + if (m_propertyTable != null) + { + m_propertyTable.Dispose(); + m_propertyTable = null; + } + if (m_mediator != null) + { + m_mediator.Dispose(); + m_mediator = null; + } + + base.TestTearDown(); + } + + private void RecordJump(object target) => m_jumps.Add(target); + + private void RecordOtherWindowJump(object target) => m_otherWindowJumps.Add(target); + + [Test] + public void InsertWithNoSliceForTheNewObject_JumpsToIt() + { + var slice = (Slice)m_dtree.Controls[0]; + + slice.HandleInsertCommand("Etymology", "LexEtymology", "LexEntry", null, + out var newObject); + + Assert.That(newObject, Is.InstanceOf(), "the insert happened"); + Assert.That(m_jumps, Is.EqualTo(new object[] { newObject.Hvo }), + "one jump, to the new object, by the time the insert returns"); + Assert.That(m_otherWindowJumps, Is.Empty, "another window hears nothing"); + Assert.That(m_mediator.JobItems, Is.Zero, "nothing is queued on the Mediator"); + } + + private sealed class StubWindow : IxWindow + { + public Mediator Mediator => null; + public PropertyTable PropTable => null; + public void SuspendIdleProcessing() { } + public void ResumeIdleProcessing() { } + public void SuspendWindowSizePersistence() { } + public void ResumeWindowSizePersistence() { } + } + } +} diff --git a/Src/Common/Controls/DetailControls/Slice.cs b/Src/Common/Controls/DetailControls/Slice.cs index bae37ac5fc..2f4d9e469a 100644 --- a/Src/Common/Controls/DetailControls/Slice.cs +++ b/Src/Common/Controls/DetailControls/Slice.cs @@ -13,6 +13,7 @@ using SIL.FieldWorks.Common.Controls; using SIL.FieldWorks.Common.Framework.DetailControls.Resources; using SIL.FieldWorks.Common.FwUtils; +using static SIL.FieldWorks.Common.FwUtils.FwUtils; using SIL.FieldWorks.Common.DetailRules; using SIL.FieldWorks.Common.RootSites; using SIL.FieldWorks.FdoUi; @@ -2091,10 +2092,8 @@ int InsertObject(int flid, int newObjectClassId, out ICmObject newObject) else { // If possible, jump to the newly inserted sub item. -#pragma warning disable 618 // suppress obsolete warning - if (m_mediator.BroadcastMessageUntilHandled("JumpToRecord", uiObj.Object.Hvo)) -#pragma warning restore 618 - return insertionPosition; + Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToRecord, + uiObj.Object.Hvo, m_propertyTable.GetWindow())); // If we haven't found a slice...common now, because there's rarely a need to expand anything... // and some slice was added, focus it. foreach (Slice slice in Parent.Controls) diff --git a/Src/LexText/Lexicon/ReversalEntryPOS.cs b/Src/LexText/Lexicon/ReversalEntryPOS.cs index 7ded706ed7..5060288d9a 100644 --- a/Src/LexText/Lexicon/ReversalEntryPOS.cs +++ b/Src/LexText/Lexicon/ReversalEntryPOS.cs @@ -10,6 +10,7 @@ using SIL.FieldWorks.Common.Controls; using SIL.FieldWorks.Common.Framework.DetailControls; using SIL.FieldWorks.Common.FwUtils; +using static SIL.FieldWorks.Common.FwUtils.FwUtils; using SIL.LCModel; using SIL.LCModel.Infrastructure; using XCore; @@ -95,9 +96,8 @@ public bool OnMoveReversalPOS(object cmd) }); // Note: PropChanged should happen on the old owner and the new in the 'Add" method call. // Have to jump to a main PartOfSpeech, as RecordClerk doesn't know anything about subcategories. -#pragma warning disable 618 // suppress obsolete warning - m_mediator.BroadcastMessageUntilHandled("JumpToRecord", newOwner.MainPossibility.Hvo); -#pragma warning restore 618 + Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToRecord, + newOwner.MainPossibility.Hvo, m_propertyTable.GetWindow())); } } @@ -165,9 +165,8 @@ public bool OnMergeReversalPOS(object cmd) ()=> survivor.MergeObject(currentPOS, false)); // Note: PropChanged should happen on the old owner and the new in the 'Add" method call. // Have to jump to a main PartOfSpeech, as RecordClerk doesn't know anything about subcategories. -#pragma warning disable 618 // suppress obsolete warning - m_mediator.BroadcastMessageUntilHandled("JumpToRecord", survivor.MainPossibility.Hvo); -#pragma warning restore 618 + Publisher.Publish(new PublisherParameterObject(EventConstants.JumpToRecord, + survivor.MainPossibility.Hvo, m_propertyTable.GetWindow())); } }