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
242 changes: 241 additions & 1 deletion Src/Common/Controls/DetailControls/DataTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
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.RootSites;
using SIL.LCModel;
using SIL.LCModel.Core.Cellar;
using SIL.LCModel.Core.KernelInterfaces;
using SIL.LCModel.Core.Text;
using SIL.LCModel.Core.WritingSystems;
using SIL.LCModel.DomainServices;
using SIL.LCModel.Infrastructure;
Expand All @@ -28,6 +28,7 @@
using System.Windows.Forms;
using System.Xml;
using XCore;
using static SIL.FieldWorks.Common.FwUtils.FwUtils;

namespace SIL.FieldWorks.Common.Framework.DetailControls
{
Expand Down Expand Up @@ -1047,6 +1048,243 @@ public virtual void ShowObject(ICmObject root, string layoutName, string layoutC
}
}

/// <summary>
/// Jump to the slice that contains the given field object and value.
/// </summary>
private void JumpToField(object arguments)
{
var array = (object[])arguments;
int fieldHvo = (int)array[0];
string fieldName = PlainFieldName((string)array[1]);
string fieldValue = (string)array[2];
ICmObject fieldObj = Cache.ServiceLocator.GetInstance<ICmObjectRepository>().GetObject(fieldHvo);
// fieldObj.fieldName == fieldValue.
bool found = false;
if (!String.IsNullOrEmpty(fieldName))
{
// Try matching object and field first.
foreach (Slice slice in Slices)
{
if (slice.IsHeaderNode)
{
continue;
}
int sliceFlid = GetFlid(slice);
string sliceFieldName = sliceFlid == 0 ? "" : m_cache.MetaDataCacheAccessor.GetFieldName(sliceFlid);
if (sliceFieldName == fieldName && slice.Object == fieldObj)
{
found = true;
}
// Look for special cases.
else if (fieldName == "ComplexFormEntryRefs" && sliceFieldName == "PrimaryLexemes" &&
slice.Object is ILexEntryRef ler && ler.OwningEntry == fieldObj)
{
found = true;
}
else if (fieldName == "ConfigReferencedEntries" && sliceFieldName == "ComponentLexemes" &&
slice.Object == fieldObj)
{
found = true;
}
else if (fieldName == "DefinitionOrGloss" && (sliceFieldName == "Definition" || sliceFieldName == "Gloss") &&
slice.Object == fieldObj &&
SliceMatchesValue(slice, fieldValue))
{
found = true;
}
else if (fieldName == "LookupComplexEntryType" && sliceFieldName == "ComplexEntryTypes" &&
slice.Object is ILexEntryRef ler2 && ler2.OwningEntry == fieldObj)
{
found = true;
}
else if ((fieldName == "MLHeadWord" || fieldName == "HeadWordRef") &&
(sliceFieldName == "Form" || sliceFieldName == "CitationForm") &&
(slice.Object == fieldObj || fieldObj is ILexEntry lexEntry && lexEntry.LexemeFormOA == slice.Object) &&
SliceMatchesValue(slice, fieldValue))
{
found = true;
}
else if (fieldName == "MLPartOfSpeech" && sliceFieldName == "MorphoSyntaxAnalysis" &&
slice is MSAReferenceComboBoxSlice && slice.Object is ILexSense sense && sense.MorphoSyntaxAnalysisRA == fieldObj)
{
found = true;
}
else if (fieldName == "MorphTypes" && sliceFieldName == "MorphType" &&
slice.Object is IMoAffixForm affix && fieldObj is IMoMorphSynAnalysis msa && msa.MorphTypes.Contains(affix.MorphTypeRA))
{
found = true;
}
else if (fieldName == "MorphTypes" && sliceFieldName == "MorphTypes" &&
slice.Object is IMoAffixForm affix2 && affix2.MorphTypeRA == fieldObj)
{
found = true;
}
else if (fieldName == "ReverseAbbr" && sliceFieldName == "ComplexEntryTypes" &&
slice.Object is ILexEntryRef ler3 && ler3.ComplexEntryTypesRS.Contains(fieldObj))
{
found = true;
}
else if (fieldName == "ReverseAbbr" && sliceFieldName == "VariantEntryTypes" &&
slice.Object is ILexEntryRef ler4 && ler4.EntryTypes.Contains(fieldObj))
{
found = true;
}
if (found)
{
m_currentSliceNew = slice;
break;
}
}
}
if (!found)
{
Slice objectSlice = null;
Slice valueSlice = null;
// Look for the closest matching slice.
foreach (Slice slice in Slices)
{
if (slice.IsHeaderNode)
{
continue;
}
int sliceFlid = GetFlid(slice);
string sliceFieldName = sliceFlid == 0 ? "" : m_cache.MetaDataCacheAccessor.GetFieldName(sliceFlid);
if ((fieldName == "MLHeadWord" || fieldName == "HeadWordRef") && sliceFieldName == "Form" &&
(slice.Object == fieldObj || fieldObj is ILexEntry lexEntry && lexEntry.LexemeFormOA == slice.Object))
{
// MLHeadWord, and HeadWordRef default to Form if the value doesn't match CitationForm or Form.
m_currentSliceNew = slice;
found = true;
break;
}
// Try matching object and/or value.
if (slice.Object == fieldObj && SliceMatchesValue(slice, fieldValue))
{
m_currentSliceNew = slice;
found = true;
break;
}
if (objectSlice == null && fieldObj != Root && slice.Object == fieldObj)
{
objectSlice = slice;
}
if (valueSlice == null && SliceMatchesValue(slice, fieldValue))
{
valueSlice = slice;
}
}
// Prefer matching value over matching object.
if (!found && valueSlice != null)
{
m_currentSliceNew = valueSlice;
found = true;
}
else if (!found && objectSlice != null)
{
m_currentSliceNew = objectSlice;
found = true;
}
}
if (found)
{
// Set the current slice.
m_fCurrentContentControlObjectTriggered = true;
OnReadyToSetCurrentSlice(false);
}
}

private string PlainFieldName(string fieldname)
{
if (fieldname.EndsWith("OA") || fieldname.EndsWith("OS") || fieldname.EndsWith("OC")
|| fieldname.EndsWith("RA") || fieldname.EndsWith("RS") || fieldname.EndsWith("RC"))
{
return fieldname.Substring(0, fieldname.Length - 2);
}
return fieldname;
}

private int GetFlid(Slice slice)
{
if (slice.Flid != 0)
{
return slice.Flid;
}
if (slice is ViewPropertySlice vpSlice)
{
return vpSlice.FieldId;
}
return 0;
}


/// <summary>
/// Does the slice's display text match the given text?
/// </summary>
private bool SliceMatchesValue(Slice slice, string text)
{
try
{
if (slice is MultiStringSlice)
{
ITsMultiString multiString = m_cache.DomainDataByFlid.get_MultiStringProp(slice.Object.Hvo, GetFlid(slice));
if (MultiStringMatchesValue(multiString, text))
{
return true;
}
for (int i = 0; i < multiString.StringCount; i++)
{
ITsString tsString = multiString.GetStringFromIndex(i, out int ws);
if (tsString.Text == text)
{
return true;
}
}
}
else if (slice is MorphTypeAtomicReferenceSlice && slice.Object is IMoAffixForm affix)
{
IMoMorphType morphType = affix.MorphTypeRA;
if (MultiStringMatchesValue(morphType.Name, text) || MultiStringMatchesValue(morphType.Abbreviation, text))
{
return true;
}

}
else if (slice is PossibilityReferenceVectorSlice)
{
int[] hvos = SetupContents(GetFlid(slice), slice.Object);
for (int i = 0; i < hvos.Length; i++)
{
ICmObject obj = m_cache.ServiceLocator.GetInstance<ICmObjectRepository>().GetObject(hvos[i]);
if (obj is ICmPossibility possibility)
{
if (MultiStringMatchesValue(possibility.Name, text) || MultiStringMatchesValue(possibility.Abbreviation, text))
{
return true;
}
}
}
}
}
catch { }
return false;
}

private bool MultiStringMatchesValue(ITsMultiString multiString, string text)
{
if (multiString != null)
{
for (int i = 0; i < multiString.StringCount; i++)
{
ITsString tsString = multiString.GetStringFromIndex(i, out int ws);
if (TsStringUtils.NormalizeNfd(tsString.Text) == TsStringUtils.NormalizeNfd(text))
{
return true;
}
}
}
return false;
}

private void SetCurrentSliceNewFromObject(ICmObject obj)
{
foreach (Slice slice in Slices)
Expand Down Expand Up @@ -1248,6 +1486,7 @@ protected override void Dispose(bool disposing)
if (disposing)
{
Subscriber.Unsubscribe(EventConstants.PostponePropChanged, PostponePropChanged);
Subscriber.Unsubscribe(EventConstants.JumpToField, JumpToField);

// Do this first, before setting m_fDisposing to true.
if (m_sda != null)
Expand Down Expand Up @@ -3711,6 +3950,7 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu
RestorePreferences();

Subscriber.Subscribe(EventConstants.PostponePropChanged, PostponePropChanged);
Subscriber.Subscribe(EventConstants.JumpToField, JumpToField);
}

public IxCoreColleague[] GetMessageTargets()
Expand Down
1 change: 1 addition & 0 deletions Src/Common/FwUtils/EventConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static class EventConstants
public const string GetToolForList = "GetToolForList";
public const string HandleLocalHotlink = "HandleLocalHotlink";
public const string ItemDataModified = "ItemDataModified";
public const string JumpToField = "JumpToField";
public const string JumpToPopupLexEntry = "JumpToPopupLexEntry";
public const string JumpToRecord = "JumpToRecord";
public const string LinkFollowed = "LinkFollowed";
Expand Down
28 changes: 28 additions & 0 deletions Src/xWorks/ConfiguredLcmGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
using SIL.Reporting;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.IO;
Expand Down Expand Up @@ -404,6 +406,11 @@ internal static IFragment GenerateContentForEntry(ICmObject entry, ConfigurableD
return settings.ContentGenerator.CreateFragment();
}

if (entry is ILexEntry)
{
// Record the guid of the source entry for JumpToField.
settings.ConfigSource[configuration] = entry.Guid;
}
var nodeList = BuildNodeList(new List<ConfigurableDictionaryNode>(), configuration);
var pieces = configuration.ReferencedOrDirectChildren
.Select(childNode => new ConfigFragment(childNode, GenerateContentForFieldByReflection(entry, BuildNodeList(nodeList, childNode), publicationDecorator,
Expand Down Expand Up @@ -493,6 +500,11 @@ internal static IFragment GenerateContentForFieldByReflection(object field, List
bool fUseReverseSubField = false)
{
var config = nodeList.Last();
if (field is ICmObject fieldObj)
{
// Record the guid of the source field for JumpToField.
settings.ConfigSource[config] = fieldObj.Guid;
}

if (!config.IsEnabled)
{
Expand Down Expand Up @@ -2161,6 +2173,11 @@ private static IFragment GenerateCollectionItemContent(List<ConfigurableDictiona
|| config.ReferencedOrDirectChildren == null)
return settings.ContentGenerator.CreateFragment();

if (item is ILexEntry obj)
{
// Record the guid of the source entry for JumpToField.
settings.ConfigSource[config] = obj.Guid;
}
var bldr = settings.ContentGenerator.CreateFragment();
var listOptions = config.DictionaryNodeOptions as DictionaryNodeListOptions;
if (listOptions is DictionaryNodeListAndParaOptions)
Expand Down Expand Up @@ -2332,6 +2349,11 @@ private static IFragment GenerateCrossReferenceChildren(List<ConfigurableDiction
switch (child.FieldDescription)
{
case "ConfigTargets":
if (reference is ICmObject fieldObj)
{
// Record the guid of the source field for JumpToField.
settings.ConfigSource[child] = fieldObj.Guid;
}
var content = settings.ContentGenerator.CreateFragment();
foreach (var referenceListItem in referenceList)
{
Expand Down Expand Up @@ -2384,6 +2406,9 @@ private static IFragment GenerateSubentryTypeChild(List<ConfigurableDictionaryNo
if (!nodeList.Last().IsEnabled)
return settings.ContentGenerator.CreateFragment();

var config = nodeList.Last();
// Record the guid of the source field for JumpToField.
settings.ConfigSource[config] = subEntry.Guid;
var complexEntryRef = EntryRefForSubentry(subEntry, mainEntryOrSense);
return complexEntryRef == null
? settings.ContentGenerator.CreateFragment()
Expand Down Expand Up @@ -3492,6 +3517,9 @@ public class GeneratorSettings
{
public ILcmContentGenerator ContentGenerator = new LcmXhtmlGenerator();
public ILcmStylesGenerator StylesGenerator = new CssGenerator();
public ConcurrentDictionary<ConfigurableDictionaryNode, Guid> ConfigSource = new ConcurrentDictionary<ConfigurableDictionaryNode, Guid>();
public bool WriteConfigSource = true;

public LcmCache Cache { get; }
public ReadOnlyPropertyTable PropertyTable { get; }
public bool UseRelativePaths { get; }
Expand Down
1 change: 1 addition & 0 deletions Src/xWorks/LcmJsonGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ public static List<JArray> SavePublishedJsonWithStyles(int[] entriesToSave, Dict
// could contain different data for unique names. The unique names can be generated
// in different orders.
displayXhtmlSettings.StylesGenerator = settings.StylesGenerator;
displayXhtmlSettings.WriteConfigSource = false;

var entryContents = new Tuple<ICmObject, StringBuilder, StringBuilder>[entryCount];
var entryActions = new List<Action>();
Expand Down
7 changes: 6 additions & 1 deletion Src/xWorks/LcmXhtmlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public class LcmXhtmlGenerator : ILcmContentGenerator
public const int EntriesPerPage = 1000;
#endif


/// <summary>
/// Saves the generated content in the Temp directory, to a unique but discoverable and somewhat stable location.
/// </summary>
Expand Down Expand Up @@ -570,6 +569,12 @@ private void WriteNodeId(XmlWriter xw, ConfigurableDictionaryNode config, Config
if (settings != null && (settings.IsWebExport || settings.IsXhtmlExport))
return;
xw.WriteAttributeString("nodeId", $"{config.GetNodeId()}");
if (settings.WriteConfigSource && settings.ConfigSource.TryGetValue(config, out Guid guid))
{
// Write out the source guid for JumpToField to use.
xw.WriteAttributeString("sourceGuid", $"{guid}");
xw.WriteAttributeString("sourceField", $"{config.FieldDescription}");
}
}

public IFragment GenerateAudioLinkContent(ConfigurableDictionaryNode config, ConfiguredLcmGenerator.GeneratorSettings settings, string classname,
Expand Down
Loading
Loading