Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Src/Common/Controls/DetailControls/ChooserCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ public MakeInflAffixEntryChooserCommand(LcmCache cache, bool fCloseBeforeExecuti
m_slot = slot;
}

/// <summary>
/// This command opens the modal New Entry dialog, so have the chooser keep the main window
/// active as it hides, to avoid another application flashing in front first.
/// </summary>
public override bool KeepOwnerActiveWhenHiding
{
get { return true; }
}

//methods

public override ObjectLabel Execute()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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 NUnit.Framework;

namespace SIL.FieldWorks.Common.Framework.DetailControls
{
/// <summary>
/// Guards the KeepOwnerActiveWhenHiding opt-in contract for the affix chooser commands
/// (LT-22578). A chooser command whose Execute() opens another modal dialog must return true
/// so ReallySimpleListChooser.HideForCommand re-activates the FLEx main window as the chooser
/// hides; without it an unrelated application can flash in front while the new dialog loads. A
/// command that opens no dialog must stay false (the base default). The flag is the only part
/// of the fix that is deterministic and UI-free; the actual owner activation and window
/// Z-order require a live message pump and are verified manually.
/// </summary>
[TestFixture]
public class ChooserCommandKeepOwnerActiveWhenHidingTests
{
/// <summary>
/// Opens the modal New Entry dialog (InsertEntryDlg), so it must opt in.
/// </summary>
[Test]
public void MakeInflAffixEntryChooserCommand_OptsIn()
{
var command = new MakeInflAffixEntryChooserCommand(
null, true, "label", true, null, null, null);
Assert.That(command.KeepOwnerActiveWhenHiding, Is.True);
}

/// <summary>
/// Creates a slot without opening a dialog, so it keeps the base default of false. This
/// also exercises the base ChooserCommand default (this command does not override it).
/// </summary>
[Test]
public void MakeInflAffixSlotChooserCommand_DoesNotOptIn()
{
var command = new MakeInflAffixSlotChooserCommand(
null, true, "label", 0, false, null, null);
Assert.That(command.KeepOwnerActiveWhenHiding, Is.False);
}
}
}
14 changes: 14 additions & 0 deletions Src/Common/Controls/XMLViews/ChooserCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ public bool ShouldCloseBeforeExecuting
m_fShouldCloseBeforeExecuting = value;
}
}

/// <summary>
/// When true, the chooser re-enables its owner window just before hiding itself to run this
/// command. Execute() for such a command opens another modal dialog, which re-disables the
/// owner; re-enabling it first makes the OS return activation to the FLEx main window when
/// the chooser hides, instead of momentarily revealing an unrelated application's window.
/// Defaults to false so behavior is unchanged for every command that does not opt in. See
/// the "Create new inflectional affix" flow (LT-22578).
/// </summary>
public virtual bool KeepOwnerActiveWhenHiding
{
get { return false; }
}

/// <summary>
/// The entire text of the label that will appear in the chooser
/// </summary>
Expand Down
25 changes: 23 additions & 2 deletions Src/Common/Controls/XMLViews/ReallySimpleListChooser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2281,6 +2281,27 @@ private void InitializeComponent()
}
#endregion

/// <summary>
/// Hide the chooser before running <paramref name="cmd"/>. When the command opts in via
/// KeepOwnerActiveWhenHiding (its Execute() opens another modal dialog), re-enable and then
/// re-activate our owner around the hide. While the chooser is modal the OS has disabled the
/// owner, so hiding alone would hand the foreground to another application (the next window
/// by z-order) instead of back to the FLEx main window, causing a brief flash. Re-enabling is
/// not enough on its own; the Activate() is what pulls FLEx forward, and it is not blocked by
/// the foreground lock because we are still the foreground process here (LT-22578).
/// </summary>
private void HideForCommand(ChooserCommand cmd)
{
if (cmd != null && cmd.KeepOwnerActiveWhenHiding && Owner != null)
{
Owner.Enabled = true;
Visible = false;
Owner.Activate();
return;
}
Visible = false;
}

private void HandleCommmandChoice(ChooserCommandNode node)
{
if (node != null)
Expand All @@ -2289,7 +2310,7 @@ private void HandleCommmandChoice(ChooserCommandNode node)
if (cmd != null)
{
if (cmd.ShouldCloseBeforeExecuting)
Visible = false;
HideForCommand(cmd);
m_chosenLabel = cmd.Execute();
}
}
Expand All @@ -2300,7 +2321,7 @@ private void OnOKClick(object sender, EventArgs e)
Persist();
if (m_linkCmd != null)
{
Visible = false;
HideForCommand(m_linkCmd);
m_chosenLabel = m_linkCmd.Execute();
m_fLinkExecuted = true;
}
Expand Down
22 changes: 20 additions & 2 deletions Src/LexText/Lexicon/EntrySequenceReferenceLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ protected override void HandleChooser()
chooser.InitializeExtras(null, Mediator, m_propertyTable);
chooser.AddLink(LexEdStrings.ksAddAComponent, ReallySimpleListChooser.LinkType.kDialogLink,
new AddPrimaryLexemeChooserCommand(m_cache, false, null, m_mediator, m_propertyTable, m_obj, FindForm()));
DialogResult res = chooser.ShowDialog();
DialogResult res = chooser.ShowDialog(FindForm());
if (DialogResult.Cancel == res)
return;
if (chooser.ChosenObjects != null)
Expand Down Expand Up @@ -204,7 +204,7 @@ private void HandleChooserForBackRefs(string fieldName, bool fPropContainsEntryR
// Step 3 of LT-11155:
chooser.AddLink(LexEdStrings.ksAddAComplexForm, ReallySimpleListChooser.LinkType.kDialogLink,
new AddComplexFormChooserCommand(m_cache, false, null, m_mediator, m_propertyTable, m_obj, FindForm()));
DialogResult res = chooser.ShowDialog();
DialogResult res = chooser.ShowDialog(FindForm());
if (DialogResult.Cancel == res)
return;
var chosenObjects = chooser.ChosenObjects;
Expand Down Expand Up @@ -353,6 +353,15 @@ public AddPrimaryLexemeChooserCommand(LcmCache cache, bool fCloseBeforeExecuting
m_parentWindow = parentWindow;
}

/// <summary>
/// This command opens the modal LinkEntryOrSenseDlg, so have the chooser keep the main
/// window active as it hides, to avoid another application flashing in front first.
/// </summary>
public override bool KeepOwnerActiveWhenHiding
{
get { return true; }
}

public override ObjectLabel Execute()
{
ObjectLabel result = null;
Expand Down Expand Up @@ -426,6 +435,15 @@ public AddComplexFormChooserCommand(LcmCache cache, bool fCloseBeforeExecuting,
m_parentWindow = parentWindow;
}

/// <summary>
/// This command opens the modal EntryGoDlg, so have the chooser keep the main window
/// active as it hides, to avoid another application flashing in front first.
/// </summary>
public override bool KeepOwnerActiveWhenHiding
{
get { return true; }
}

public override ObjectLabel Execute()
{
ObjectLabel result = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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 NUnit.Framework;
using SIL.FieldWorks.XWorks.LexEd;

namespace LexEdDllTests
{
/// <summary>
/// Guards the KeepOwnerActiveWhenHiding opt-in contract for the Lexicon "Add a Component" and
/// "Add a Complex Form" chooser commands (LT-22578). Both are launched from a chooser that
/// hides itself and then open a modal dialog (LinkEntryOrSenseDlg / EntryGoDlg), so both must
/// opt in to keep an unrelated application from flashing in front while the dialog loads. See
/// ChooserCommandKeepOwnerActiveWhenHidingTests (DetailControls) for the affix equivalents and
/// the rationale for why only the opt-in flag is unit-tested.
/// </summary>
[TestFixture]
public class EntrySequenceChooserCommandKeepOwnerActiveWhenHidingTests
{
/// <summary>
/// "Add a Component" opens the modal LinkEntryOrSenseDlg, so it must opt in.
/// </summary>
[Test]
public void AddPrimaryLexemeChooserCommand_OptsIn()
{
var command = new AddPrimaryLexemeChooserCommand(
null, false, "label", null, null, null, null);
Assert.That(command.KeepOwnerActiveWhenHiding, Is.True);
}

/// <summary>
/// "Add a Complex Form" opens the modal EntryGoDlg, so it must opt in.
/// </summary>
[Test]
public void AddComplexFormChooserCommand_OptsIn()
{
var command = new AddComplexFormChooserCommand(
null, false, "label", null, null, null, null);
Assert.That(command.KeepOwnerActiveWhenHiding, Is.True);
}
}
}
3 changes: 2 additions & 1 deletion Src/LexText/Morphology/InflAffixTemplateControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,8 @@ public bool OnInflTemplateAddInflAffixMsa(object cmd)

using (var chooser = MakeChooserWithExtantMsas(m_slot, cmd as XCore.Command))
{
chooser.ShowDialog(this);
// Own the chooser to the top-level main window, not `this` child control.
chooser.ShowDialog(m_propertyTable.GetValue<Form>("window"));
if (chooser.DialogResult == DialogResult.OK)
{
if (chooser.ChosenObjects != null && chooser.ChosenObjects.Count() > 0)
Expand Down
Loading