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
160 changes: 22 additions & 138 deletions Src/Common/Controls/DetailControls/Slice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using SIL.FieldWorks.Common.Controls;
using SIL.FieldWorks.Common.Framework.DetailControls.Resources;
using SIL.FieldWorks.Common.FwUtils;
using SIL.FieldWorks.Common.DetailRules;
using SIL.FieldWorks.Common.RootSites;
using SIL.FieldWorks.FdoUi;
using SIL.FieldWorks.LexText.Controls;
Expand Down Expand Up @@ -1398,145 +1398,31 @@ public String GetChooserHelpTopicID(string ChooserDlgHelpTopicID)
return GetHelpTopicID(ChooserDlgHelpTopicID, "khtpChoose");
}

private String GetHelpTopicID(String xmlHelpTopicID, String generatedIDPrefix)
{
String helpTopicID;

if (xmlHelpTopicID == "khtpField-PhRegularRule-RuleFormula")
xmlHelpTopicID = "khtpChoose-Environment";

if (!String.IsNullOrEmpty(xmlHelpTopicID))
helpTopicID = xmlHelpTopicID;
else
{
helpTopicID = GenerateHelpTopicId(generatedIDPrefix);
}
return helpTopicID;
}
// An authored id returns at once; the subject is described only when generating.
private string GetHelpTopicID(string xmlHelpTopicID, string generatedIDPrefix)
=> FieldHelpTopics.Resolve(xmlHelpTopicID, generatedIDPrefix, DescribeHelpTopicSubject, helpTopicIsValid);

private string GenerateHelpTopicId(string helpTopicPrefix)
private HelpTopicSubject DescribeHelpTopicSubject()
{
String generatedHelpTopicID;

String tempfieldName = XmlUtils.GetOptionalAttributeValue(ConfigurationNode, "field");
String templabelName = XmlUtils.GetOptionalAttributeValue(ConfigurationNode, "label");
String areaName = m_propertyTable.GetStringProperty("areaChoice", null);
string toolName = m_propertyTable.GetStringProperty("currentContentControl", null);
int parentHvo = Convert.ToInt32(XmlUtils.GetOptionalAttributeValue(ConfigurationNode, "hvoDisplayParent"));

if (tempfieldName == "Targets" && parentHvo != 0)
// Ceoss Reference (entry level) or lexical relation (sense level) subitems
var fieldName = XmlUtils.GetOptionalAttributeValue(ConfigurationNode, "field");
var subject = new HelpTopicSubject
{
var repo = m_cache.ServiceLocator.GetInstance<ILexEntryRepository>();
ILexEntry lex;
repo.TryGetObject(parentHvo, out lex);

if (lex != null) // It must be the entry level
{
generatedHelpTopicID = helpTopicPrefix + "-" + toolName + "-CrossReferenceSubitem";
}
else // It must be the sense level
{
generatedHelpTopicID = helpTopicPrefix + "-" + toolName + "-LexicalRelationSubitem";

}
}
else
FieldName = fieldName,
Label = XmlUtils.GetOptionalAttributeValue(ConfigurationNode, "label"),
ClassName = Cache.DomainDataByFlid.MetaDataCache.GetClassName(Object.ClassID),
OwnerClassName = Object.Owner?.ClassName,
SortKey = Object is ICmPossibility ? Object.SortKey : null,
AreaName = m_propertyTable.GetStringProperty("areaChoice", null),
ToolName = m_propertyTable.GetStringProperty("currentContentControl", null)
};
// A lexical-relation Targets slice carries the entry or sense it is shown under.
var parentHvo = Convert.ToInt32(XmlUtils.GetOptionalAttributeValue(ConfigurationNode, "hvoDisplayParent"));
if (fieldName == "Targets" && parentHvo != 0)
{
templabelName = getAlphaNumeric(templabelName);
if (String.IsNullOrEmpty(tempfieldName))
{
// try to use the slice label, without spaces.
tempfieldName = templabelName;
}
generatedHelpTopicID = GetGeneratedHelpTopicId(helpTopicPrefix, tempfieldName);
if (!helpTopicIsValid(generatedHelpTopicID))
{
// try to use the slice label, without spaces if the helpTopicID does not work for the field xml attribute.
generatedHelpTopicID = GetGeneratedHelpTopicId(helpTopicPrefix, templabelName);
if (!helpTopicIsValid(generatedHelpTopicID))
{
if (helpTopicPrefix.Equals("khtpChoose"))
generatedHelpTopicID = "khtpChoose-CmPossibility";
else if (areaName == "lists")
{
generatedHelpTopicID = "khtp-CustomListField"; // If the list isn't defined, use the generic list help topic

}
else
{
generatedHelpTopicID = "khtpNoHelpTopic"; // else use the generic no help topic
}
}
}
}

return generatedHelpTopicID;
}

private string GetGeneratedHelpTopicId(string helpTopicPrefix, String fieldName)
{
var ownerClassName = Object.Owner == null ? null : Object.Owner.ClassName;
var className = Cache.DomainDataByFlid.MetaDataCache.GetClassName(Object.ClassID);
// Distinguish the Example (sense) field and the expanded example (LexExtendedNote) field
className = (fieldName == "Example" && ownerClassName == "LexExtendedNote") ? "LexExtendedNote" : className;
// Distinguish the Translation (sense) field and the expanded example (LexExtendedNote) field
className = fieldName.StartsWith("Translation")&& (ownerClassName == "LexExtendedNote" || (Object.Owner != null && Object.Owner.ClassName == "LexExtendedNote")) ? "LexExtendedNote" : className;
var toolName = m_propertyTable.GetStringProperty("currentContentControl", null);

String generatedHelpTopicID;

generatedHelpTopicID = helpTopicPrefix + "-" + toolName + "-" + className + "-" + fieldName;

if (!helpTopicIsValid(generatedHelpTopicID))
{
if (String.Equals(className, "CmPossibility"))
generatedHelpTopicID = helpTopicPrefix + "-" + toolName + "-" + Object.SortKey + "-" + fieldName;

if (!helpTopicIsValid(generatedHelpTopicID))
{
generatedHelpTopicID = helpTopicPrefix + "-" + toolName + "-" + fieldName;
if (!helpTopicIsValid(generatedHelpTopicID))
{
generatedHelpTopicID = helpTopicPrefix + "-" + className + "-" + fieldName;
if (!helpTopicIsValid(generatedHelpTopicID))
{
generatedHelpTopicID = helpTopicPrefix + "-" + fieldName;
}
}
}
}
return generatedHelpTopicID;
}

/// <summary>
/// Generates a possible help topic id from the field name, but does NOT check it for validity!
/// </summary>
private static string getAlphaNumeric(string fromStr)
{
var candidateID = new StringBuilder("");

if (String.IsNullOrEmpty(fromStr))
return candidateID.ToString();

// Should we capitalize the next letter?
bool nextCapital = true;

// Lets turn our field into a candidate help page!
foreach (char ch in fromStr)
{
if (Char.IsLetterOrDigit(ch)) // might we include numbers someday?
{
if (nextCapital)
candidateID.Append(Char.ToUpper(ch));
else
candidateID.Append(ch);
nextCapital = false;
}
else // unrecognized character... exclude it
nextCapital = true; // next letter should be a capital
subject.TargetsParentIsEntry = m_cache.ServiceLocator.GetInstance<ILexEntryRepository>()
.TryGetObject(parentHvo, out _);
}
return candidateID.ToString();
return subject;
}

/// <summary>
Expand All @@ -1546,9 +1432,7 @@ private bool helpTopicIsValid(String helpStr)
{
if (m_mediator == null)
return false;
var helpTopicProvider = m_propertyTable.GetValue<IHelpTopicProvider>("HelpTopicProvider");
return (helpTopicProvider != null && !String.IsNullOrEmpty(helpStr))
&& (helpTopicProvider.GetHelpString(helpStr) != null);
return FieldHelpTopics.KnownBy(m_propertyTable.GetValue<IHelpTopicProvider>("HelpTopicProvider"))(helpStr);
}

/// <summary></summary>
Expand Down
46 changes: 46 additions & 0 deletions Src/Common/FwAvalonia/Detail/DetailModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,40 @@ public DetailLinkRequest(DetailField field, DetailChooserLink link)
public DetailChooserLink Link { get; }
}

/// <summary>
/// The inputs for generating a row's help topic when the row has no
/// <see cref="DetailField.HelpTopicId"/>. Plain strings only, so this layer stays free of
/// the domain model.
/// </summary>
public sealed class DetailHelpTopicSource
{
/// <summary>Captures the generator's inputs for one row.</summary>
/// <param name="fieldName">The layout field name; may be null.</param>
/// <param name="label">The raw, unlocalized layout label; may be null.</param>
/// <param name="className">The class of the row's object.</param>
/// <param name="ownerClassName">The owner's class; null when unowned.</param>
/// <param name="sortKey">The object's sort key.</param>
/// <param name="targetsParentIsEntry">For a lexical-relation row: true under an entry,
/// false under a sense; null for other rows.</param>
public DetailHelpTopicSource(string fieldName, string label, string className,
string ownerClassName, string sortKey, bool? targetsParentIsEntry = null)
{
FieldName = fieldName;
Label = label;
ClassName = className;
OwnerClassName = ownerClassName;
SortKey = sortKey;
TargetsParentIsEntry = targetsParentIsEntry;
}

public string FieldName { get; }
public string Label { get; }
public string ClassName { get; }
public string OwnerClassName { get; }
public string SortKey { get; }
public bool? TargetsParentIsEntry { get; }
}

/// <summary>
/// A field on a lexical-edit detail view, projected from a typed <see cref="ViewNode"/> and bound to live
/// values by an <see cref="IDetailValueProvider"/>. This is the product contract that replaces the
Expand Down Expand Up @@ -1610,6 +1644,18 @@ public DetailField(
/// </summary>
public bool CanResetItemOrder { get; }

/// <summary>
/// The layout's authored help topic id, or null when the host generates one from
/// <see cref="HelpTopicSource"/>. Settable so the composer can stamp it from the node.
/// </summary>
public string HelpTopicId { get; set; }

/// <summary>
/// What the host needs to generate this row's help topic when the layout authors
/// none; null on rows that have no object. Stamped by the composer.
/// </summary>
public DetailHelpTopicSource HelpTopicSource { get; set; }

/// <summary>False for display-only fields (e.g. reference fields without chooser write-back yet).</summary>
public bool IsEditable { get; }

Expand Down
2 changes: 1 addition & 1 deletion Src/Common/FwAvalonia/Detail/LexiconFirstSlice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private static ViewNode StampProductLeaf(ViewNode source, string automationId, s
=> new ViewNode(source.StableId, ViewNodeKind.Field, labelOverride ?? source.Label, source.Abbreviation,
source.Field, source.RawEditor, source.EditorClassification, source.WritingSystem, source.Visibility,
source.Expansion, source.Indented, source.TargetLayout, null,
source.LocalizationKey, automationId, HostRouting.Product);
source.LocalizationKey, automationId, HostRouting.Product, helpTopicId: source.HelpTopicId);

private static ViewNode Leaf(string stableId, string label, string field, string editor, string ws, string automationId)
=> new ViewNode(stableId, ViewNodeKind.Field, label, null, field, editor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ private static JObject WriteNode(ViewNode node)
AddIfPresent(o, "menu", node.MenuId);
AddIfPresent(o, "contextMenu", node.ContextMenuId);
AddIfPresent(o, "hotlinks", node.HotlinksId);
AddIfPresent(o, "helpTopicID", node.HelpTopicId);
AddIfPresent(o, "ghost", node.GhostField);
AddIfPresent(o, "ghostWs", node.GhostWs);
AddIfPresent(o, "ghostClass", node.GhostClass);
Expand Down Expand Up @@ -212,7 +213,8 @@ private static ViewNode ReadNode(JToken token)
(bool?)o["forVariant"] ?? false,
ghostInitMethod: (string)o["ghostInitMethod"],
condition: ReadCondition((JObject)o["condition"]),
chooserLinks: ((JArray)o["chooserLinks"])?.Select(ReadChooserLink).ToList());
chooserLinks: ((JArray)o["chooserLinks"])?.Select(ReadChooserLink).ToList(),
helpTopicId: (string)o["helpTopicID"]);
}

private static T ParseEnum<T>(JObject o, string name, T fallback) where T : struct
Expand Down
10 changes: 9 additions & 1 deletion Src/Common/FwAvalonia/ViewDefinition/ViewDefinitionModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,10 @@ public ViewNode(
ViewStringList enumStringList = null,
IReadOnlyList<string> visibleWritingSystems = null,
bool toggleValue = false,
bool reorder = false)
bool reorder = false,
string helpTopicId = null)
{
HelpTopicId = helpTopicId;
Reorder = reorder;
ToggleValue = toggleValue;
VisibleWritingSystems = visibleWritingSystems;
Expand Down Expand Up @@ -565,6 +567,12 @@ public ViewNode(
/// even when the property is virtual; the order persists as a virtual ordering.
/// </summary>
public bool Reorder { get; }

/// <summary>
/// The slice's authored <c>helpTopicID</c>; null when the layout has none and the
/// topic is generated from the field, its object and the tool.
/// </summary>
public string HelpTopicId { get; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ private static ViewNode CloneWith(ViewNode n, ViewVisibility visibility, string
n.LocalizationKey, n.AutomationId, n.Routing, n.BoldEmphasis, n.FontScalePercent, n.MenuId,
n.ContextMenuId, n.HotlinksId, n.GhostField, n.GhostWs, n.GhostClass, n.GhostLabel,
n.ForVariant, n.CustomEditorClass, n.CustomEditorAssembly, n.GhostInitMethod, n.Condition,
n.ChooserLinks, n.EnumStringList, visibleWritingSystems, n.ToggleValue, n.Reorder);
n.ChooserLinks, n.EnumStringList, visibleWritingSystems, n.ToggleValue, n.Reorder,
n.HelpTopicId);

// Copy a (leaf) node under a new StableId; AutomationId is dropped so the duplicate gets a fresh,
// non-colliding identity (the renderer derives one from the new StableId by convention).
Expand All @@ -320,6 +321,7 @@ private static ViewNode CloneWithId(ViewNode n, string newId)
n.LocalizationKey, null, n.Routing, n.BoldEmphasis, n.FontScalePercent, n.MenuId,
n.ContextMenuId, n.HotlinksId, n.GhostField, n.GhostWs, n.GhostClass, n.GhostLabel,
n.ForVariant, n.CustomEditorClass, n.CustomEditorAssembly, n.GhostInitMethod, n.Condition,
n.ChooserLinks, n.EnumStringList, n.VisibleWritingSystems, n.ToggleValue, n.Reorder);
n.ChooserLinks, n.EnumStringList, n.VisibleWritingSystems, n.ToggleValue, n.Reorder,
n.HelpTopicId);
}
}
8 changes: 5 additions & 3 deletions Src/Common/FwAvalonia/ViewDefinition/XmlLayoutImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public sealed class XmlLayoutImporter : IViewDefinitionImporter
{
"label", "abbr", "field", "ws", "editor", "visibility", "expansion",
"localizationKey", "labelId", "automationId", "routing", "menu", "contextMenu", "hotlinks",
"forVariant", "visibleWritingSystems", "reorder"
"forVariant", "visibleWritingSystems", "reorder", "helpTopicID"
};

public static readonly HashSet<string> HandledObjSeqAttributes =
Expand Down Expand Up @@ -394,7 +394,8 @@ private ViewNode CreateNode(
localizationKey, automationId, routing, boldEmphasis, fontScalePercent,
menuId, contextMenuId, hotlinksId,
chooserLinks: chooserLinks.Count > 0 ? chooserLinks : null,
visibleWritingSystems: visibleWss);
visibleWritingSystems: visibleWss,
helpTopicId: Attr(contentEl, "helpTopicID"));
}

// Dynamic custom slices keep their legacy class/assembly identity so the host can
Expand All @@ -416,7 +417,8 @@ private ViewNode CreateNode(
toggleValue: ParseOptionalBool(Attr(contentEl, "toggleValue")) ?? false,
// The slice's reorder= attribute: its items may be reordered
// even when the property is virtual.
reorder: ParseOptionalBool(Attr(contentEl, "reorder")) ?? false);
reorder: ParseOptionalBool(Attr(contentEl, "reorder")) ?? false,
helpTopicId: Attr(contentEl, "helpTopicID"));
}
case "obj":
case "seq":
Expand Down
Loading
Loading