Skip to content
Merged
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
22 changes: 8 additions & 14 deletions Src/Common/Controls/DetailControls/DataTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// (http://www.gnu.org/licenses/lgpl-2.1.html)

using SIL.FieldWorks.Common.Controls;
using SIL.FieldWorks.Common.DetailRules;
using SIL.FieldWorks.Common.Framework.DetailControls.Resources;
using SIL.FieldWorks.Common.FwUtils;
using SIL.FieldWorks.Common.RootSites;
Expand All @@ -22,7 +23,6 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Forms;
Expand Down Expand Up @@ -4534,8 +4534,8 @@ private bool DisplayJumpToToolAndFilterAnthroItem(UIItemDisplayProperties displa
return false;
}

var fieldName = XmlUtils.GetOptionalAttributeValue(CurrentSlice.ConfigurationNode, "field");
if (String.IsNullOrEmpty(fieldName) || !fieldName.Equals("AnthroCodes"))
var fieldName = XmlUtils.GetOptionalAttributeValue(CurrentSlice?.ConfigurationNode, "field");
if (!AnthroItemFilterLink.Applies(fieldName))
{
display.Enabled = display.Visible = false;
return true;
Expand Down Expand Up @@ -4605,7 +4605,7 @@ public virtual bool OnJumpToTool(object commandObject)
/// </summary>
public virtual bool OnJumpToLexiconEditFilterAnthroItems(object commandObject)
{
OnJumpToToolAndFilterAnthroItem("FilterAnthroItems", "lexiconEdit");
OnJumpToToolAndFilterAnthroItem("lexiconEdit");
return true;
}

Expand All @@ -4615,24 +4615,18 @@ public virtual bool OnJumpToLexiconEditFilterAnthroItems(object commandObject)
/// </summary>
public virtual bool OnJumpToNotebookEditFilterAnthroItems(object commandObject)
{
OnJumpToToolAndFilterAnthroItem("FilterAnthroItems", "notebookEdit");
OnJumpToToolAndFilterAnthroItem("notebookEdit");
return true;
}

private void OnJumpToToolAndFilterAnthroItem(string linkSetupInfo, string toolToJumpTo)
private void OnJumpToToolAndFilterAnthroItem(string toolToJumpTo)
{
var obj = ((CurrentSlice.Control as VectorReferenceLauncher).MainControl as VectorReferenceView).SelectedObject;
if (obj == null)
return;
var hvo = obj.Hvo;

FwLinkArgs link = new FwAppArgs(Cache.ProjectId.Handle, toolToJumpTo, Guid.Empty);
List<Property> additionalProps = link.PropertyTableEntries;
additionalProps.Add(new Property("SuspendLoadListUntilOnChangeFilter", link.ToolName));
additionalProps.Add(new Property("LinkSetupInfo", linkSetupInfo));
additionalProps.Add(new Property("HvoOfAnthroItem", hvo.ToString(CultureInfo.InvariantCulture)));
#pragma warning disable 618 // suppress obsolete warning
m_mediator.PostMessage("FollowLink", link);
m_mediator.PostMessage("FollowLink",
AnthroItemFilterLink.Create(Cache.ProjectId.Handle, toolToJumpTo, obj.Hvo));
#pragma warning restore 618
}

Expand Down
43 changes: 43 additions & 0 deletions Src/FdoUi/DetailRules/AnthroItemFilterLink.cs
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 System;
using System.Globalization;
using SIL.FieldWorks.Common.FwUtils;

// Rules both detail views share. Nothing here may depend on WinForms, Avalonia, or a slice
// or control type, so the rules outlive WinForms. DetailRulesBoundaryTests enforces this.
namespace SIL.FieldWorks.Common.DetailRules
{
/// <summary>
/// The jump that opens a tool filtered to the records carrying one anthropology category.
/// It is offered only on the Anthropology Categories field. The link names the tool, holds
/// the tool's list load until the filter is in place, and carries the category.
/// </summary>
public static class AnthroItemFilterLink
{
/// <summary>The field whose items offer the jump.</summary>
public const string FieldName = "AnthroCodes";

/// <summary>Whether the items of a field offer the jump.</summary>
public static bool Applies(string fieldName)
=> string.Equals(fieldName, FieldName, StringComparison.Ordinal);

/// <summary>The link for one category.</summary>
/// <param name="projectHandle">The project the link opens in.</param>
/// <param name="tool">The tool to filter, e.g. "lexiconEdit".</param>
/// <param name="anthroItemHvo">The category to filter on.</param>
public static FwLinkArgs Create(string projectHandle, string tool, int anthroItemHvo)
{
if (string.IsNullOrEmpty(tool))
throw new ArgumentException("The tool is required.", nameof(tool));
var link = new FwAppArgs(projectHandle, tool, Guid.Empty);
var properties = link.PropertyTableEntries;
properties.Add(new Property("SuspendLoadListUntilOnChangeFilter", tool));
properties.Add(new Property("LinkSetupInfo", "FilterAnthroItems"));
properties.Add(new Property("HvoOfAnthroItem", anthroItemHvo.ToString(CultureInfo.InvariantCulture)));
return link;
}
}
}
92 changes: 92 additions & 0 deletions Src/FdoUi/DetailRules/ComplexFormVisibility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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.Linq;
using SIL.LCModel;
using SIL.LCModel.Infrastructure;

// Rules both detail views share. Nothing here may depend on WinForms, Avalonia, or a slice
// or control type, so the rules outlive WinForms. DetailRulesBoundaryTests enforces this.
namespace SIL.FieldWorks.Common.DetailRules
{
/// <summary>
/// The two dictionary-visibility marks a complex form's components can carry: a component
/// is a primary lexeme when the complex form is shown as a subentry under it, and a
/// component is in the show-in list when it shows the complex form as a referenced complex
/// form. Each mark is a subsequence of the complex-form ref's components, kept in
/// component order.
/// </summary>
public static class ComplexFormVisibility
{
/// <summary>
/// Whether <paramref name="component"/> can be marked as showing the subentry of
/// <paramref name="complexFormRef"/>: the ref is a complex form and the component is an
/// entry or a sense.
/// </summary>
public static bool CanShowSubentryUnderComponent(ILexEntryRef complexFormRef, ICmObject component)
=> complexFormRef != null && complexFormRef.RefType == LexEntryRefTags.krtComplexForm
&& (component is ILexEntry || component is ILexSense);

/// <summary>
/// Whether the complex form is shown as a subentry under the component.
/// </summary>
public static bool ShowsSubentryUnderComponent(ILexEntryRef complexFormRef, ICmObject component)
=> complexFormRef.PrimaryLexemesRS.Contains(component);

/// <summary>
/// Marks the component as showing the subentry, or unmarks it when it already does, as
/// one undoable task.
/// </summary>
public static void ToggleSubentryUnderComponent(ILexEntryRef complexFormRef, ICmObject component,
string undoText, string redoText)
=> Toggle(complexFormRef.PrimaryLexemesRS, complexFormRef, component, undoText, redoText);

/// <summary>
/// The ref through which <paramref name="complexForm"/> is a complex form, or null when
/// it is not one.
/// </summary>
public static ILexEntryRef ComplexFormRefOf(ILexEntry complexForm)
=> complexForm?.EntryRefsOS.FirstOrDefault(r => r.RefType == LexEntryRefTags.krtComplexForm);

/// <summary>
/// Whether the component shows the complex form as a referenced complex form.
/// </summary>
public static bool ShowsComplexFormIn(ILexEntryRef complexFormRef, ICmObject component)
=> complexFormRef.ShowComplexFormsInRS.Contains(component);

/// <summary>
/// Marks the component as showing the complex form, or unmarks it when it already does,
/// as one undoable task.
/// </summary>
public static void ToggleShowComplexFormIn(ILexEntryRef complexFormRef, ICmObject component,
string undoText, string redoText)
=> Toggle(complexFormRef.ShowComplexFormsInRS, complexFormRef, component, undoText, redoText);

// Removes a marked component; otherwise inserts it where the ref's component order puts
// it. A component the ref does not list is left unmarked.
private static void Toggle(ILcmReferenceSequence<ICmObject> marked, ILexEntryRef complexFormRef,
ICmObject component, string undoText, string redoText)
{
UndoableUnitOfWorkHelper.Do(undoText, redoText, complexFormRef.Cache.ActionHandlerAccessor, () =>
{
if (marked.Contains(component))
{
marked.Remove(component);
return;
}
var index = 0;
foreach (var listed in complexFormRef.ComponentLexemesRS)
{
if (listed == component)
{
marked.Insert(index, component);
return;
}
if (marked.Contains(listed))
index++;
}
});
}
}
}
51 changes: 51 additions & 0 deletions Src/FdoUi/FdoUiTests/DetailRules/AnthroItemFilterLinkTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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;
using System.Linq;
using NUnit.Framework;
using SIL.FieldWorks.Common.DetailRules;
using SIL.FieldWorks.Common.FwUtils;

namespace SIL.FieldWorks.FdoUi
{
/// <summary>
/// The anthropology-category filter jump: where it applies and what its link carries.
/// </summary>
[TestFixture]
public class AnthroItemFilterLinkTests
{
[Test]
public void Applies_OnlyToTheAnthropologyCategoriesField()
{
Assert.That(AnthroItemFilterLink.Applies("AnthroCodes"), Is.True);
Assert.That(AnthroItemFilterLink.Applies("anthrocodes"), Is.False, "field names are exact");
Assert.That(AnthroItemFilterLink.Applies("SemanticDomains"), Is.False);
Assert.That(AnthroItemFilterLink.Applies(null), Is.False);
}

[Test]
public void Create_NamesTheTool_HoldsItsListLoad_AndCarriesTheCategory()
{
var link = AnthroItemFilterLink.Create("my-project", "notebookEdit", 4321);

Assert.That(link, Is.InstanceOf<FwAppArgs>());
Assert.That(((FwAppArgs)link).Database, Is.EqualTo("my-project"));
Assert.That(link.ToolName, Is.EqualTo("notebookEdit"));
Assert.That(link.TargetGuid, Is.EqualTo(Guid.Empty), "the link filters a list; it targets no record");
Assert.That(link.PropertyTableEntries.Select(p => p.name + "=" + p.value), Is.EqualTo(new[]
{
"SuspendLoadListUntilOnChangeFilter=notebookEdit", "LinkSetupInfo=FilterAnthroItems",
"HvoOfAnthroItem=4321"
}));
}

[Test]
public void Create_RequiresATool()
{
Assert.That(() => AnthroItemFilterLink.Create("my-project", null, 1), Throws.ArgumentException);
Assert.That(() => AnthroItemFilterLink.Create("my-project", "", 1), Throws.ArgumentException);
}
}
}
Loading
Loading