Skip to content

Commit b18c601

Browse files
aror92claude
andauthored
LT-22725: Show abbreviation for user-defined natural classes (#1158)
* LT-22725: Show abbreviation for user-defined natural classes In the phonological rule formula, a feature-based natural class was always drawn as its list of features, even when the user had built and named the class. Only classes abbreviated "C" or "V" showed their abbreviation, and only when they had a single feature. The rule view now draws a user-defined feature-based natural class as its abbreviation in brackets, whatever its number of features. An empty abbreviation displays as "***". Classes created automatically when features are inserted directly into a rule still display their features. A class counts as user-defined when it has a name that is not auto-generated. That check now takes the natural class as an argument, so the view can apply it to each class it draws rather than to the current selection. Line count and width use the same check, so the cell is sized to the abbreviation. Register the display dependencies on NC's name and abbreviation for displaying phonological & affix process rules. This ensures if names or abbreviations are updated, the display widths are recalculated so new names/abbreviations fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 30564cc commit b18c601

5 files changed

Lines changed: 172 additions & 66 deletions

File tree

‎Src/LexText/Morphology/AffixRuleFormulaVc.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ public override void Display(IVwEnv vwenv, int hvo, int frag)
160160

161161
var ctxtLens = new VwLength[m_rule.InputOS.Count];
162162
vwenv.NoteDependency(new[] {m_rule.Hvo}, new[] {MoAffixProcessTags.kflidInput}, 1 );
163+
// Column widths below are fixed from each input natural class.
164+
NoteNaturalClassDependencies(vwenv, m_rule.InputOS);
163165
for (int i = 0; i < m_rule.InputOS.Count; i++)
164166
{
165167
int idxWidth = GetStrWidth(TsStringUtils.MakeString(Convert.ToString(i + 1), userWs), m_indexProps, vwenv);

‎Src/LexText/Morphology/MetaRuleFormulaVc.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public override void Display(IVwEnv vwenv, int hvo, int frag)
9393
{
9494
case kfragRule:
9595
m_rule = m_cache.ServiceLocator.GetInstance<IPhMetathesisRuleRepository>().GetObject(hvo);
96+
NoteNaturalClassDependencies(vwenv, m_rule.StrucDescOS);
9697
if (m_rule.Disabled)
9798
{
9899
vwenv.set_StringProperty((int)FwTextPropType.ktptNamedStyle, "Disabled Text");

‎Src/LexText/Morphology/RegRuleFormulaVc.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public override void Display(IVwEnv vwenv, int hvo, int frag)
8585
case kfragRHS:
8686
m_rhs = m_cache.ServiceLocator.GetInstance<IPhSegRuleRHSRepository>().GetObject(hvo);
8787
var rule = m_rhs.OwningRule;
88+
NoteNaturalClassDependencies(vwenv, rule.StrucDescOS, m_rhs.StrucChangeOS,
89+
new IPhContextOrVar[] { m_rhs.LeftContextOA, m_rhs.RightContextOA });
8890
if (rule.Disabled)
8991
{
9092
vwenv.set_StringProperty((int)FwTextPropType.ktptNamedStyle, "Disabled Text");

‎Src/LexText/Morphology/RuleFormulaControl.cs‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,18 @@ public bool IsFeatureBasedNCNameUserDefined()
997997
if (!IsFeatsNCContextCurrent){ return false; }
998998

999999
var ctxt = (IPhSimpleContextNC) CurrentContext;
1000-
var natClass = (IPhNCFeatures)ctxt.FeatureStructureRA;
1000+
return IsFeatureBasedNCNameUserDefined((IPhNCFeatures)ctxt.FeatureStructureRA);
1001+
}
1002+
1003+
/// <summary>
1004+
/// Reports whether a feature-based natural class carries a name the user gave it,
1005+
/// as opposed to no name or the name generated when phonological features are inserted
1006+
/// directly into a rule. Returns false when <paramref name="natClass"/> is null.
1007+
/// </summary>
1008+
public static bool IsFeatureBasedNCNameUserDefined(IPhNCFeatures natClass)
1009+
{
1010+
if (natClass == null)
1011+
return false;
10011012

10021013
// Get the auto-generated text that precedes the rulename. (e.g. extracting "Created automatically for rule "" from "Created automatically for rule "{0}"")
10031014
// Note that the generated rulename following the stubtext is different for "regular" vs affix rules, but in all cases the auto-generated stub is the same.

‎Src/LexText/Morphology/RuleFormulaVcBase.cs‎

Lines changed: 155 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2015 SIL International
1+
// Copyright (c) 2015 SIL International
22
// This software is licensed under the LGPL, version 2.1 or later
33
// (http://www.gnu.org/licenses/lgpl-2.1.html)
44

@@ -39,6 +39,9 @@ public abstract class RuleFormulaVcBase : PatternVcBase
3939
// variant frags
4040
public const int kfragXVariable = 113;
4141

42+
public const int kfragNCAbbreviation = 114;
43+
public const int kfragStars = 115;
44+
4245
// fake flids
4346
public const int ktagFeature = -200;
4447
public const int ktagVariable = -201;
@@ -49,6 +52,7 @@ public abstract class RuleFormulaVcBase : PatternVcBase
4952

5053
protected ITsString m_infinity;
5154
protected ITsString m_x;
55+
protected ITsString m_stars;
5256

5357
protected RuleFormulaVcBase(LcmCache cache, PropertyTable propertyTable)
5458
: base(cache, propertyTable)
@@ -57,6 +61,7 @@ protected RuleFormulaVcBase(LcmCache cache, PropertyTable propertyTable)
5761
m_propertyTable = propertyTable;
5862
m_infinity = TsStringUtils.MakeString("\u221e", userWs);
5963
m_x = TsStringUtils.MakeString("X", userWs);
64+
m_stars = TsStringUtils.MakeString("***", userWs);
6065
}
6166

6267
/// <summary>
@@ -77,6 +82,20 @@ public override void Display(IVwEnv vwenv, int hvo, int frag)
7782
{
7883
switch (frag)
7984
{
85+
// If there is no writing system for the abbreviation,
86+
// or the abbreviation is empty, display kfragStars as fallback.
87+
case kfragNCAbbreviation:
88+
int abbrWs = WritingSystemServices.ActualWs(m_cache, WritingSystemServices.kwsFirstAnal, hvo,
89+
PhNaturalClassTags.kflidAbbreviation);
90+
ITsString abbr = abbrWs != 0
91+
? m_cache.MainCacheAccessor.get_MultiStringAlt(hvo, PhNaturalClassTags.kflidAbbreviation, abbrWs)
92+
: null;
93+
if (abbr != null && abbr.Length > 0)
94+
vwenv.AddStringAltMember(PhNaturalClassTags.kflidAbbreviation, abbrWs, this);
95+
else
96+
vwenv.AddProp(PhNaturalClassTags.kflidAbbreviation, this, kfragStars);
97+
break;
98+
8099
case kfragContext:
81100
var ctxtOrVar = m_cache.ServiceLocator.GetInstance<IPhContextOrVarRepository>().GetObject(hvo);
82101
bool isOuterIterCtxt = false;
@@ -114,83 +133,95 @@ public override void Display(IVwEnv vwenv, int hvo, int frag)
114133
// Natural class simple context with a feature-based natural class
115134
var natClass = (IPhNCFeatures) ncCtxt.FeatureStructureRA;
116135

117-
int numLines = GetNumLines(ncCtxt);
118-
if (numLines == 0)
136+
// User-defined feature-based natural classes should display
137+
// only the abbreviation.
138+
if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass))
119139
{
120140
if (!isOuterIterCtxt)
121141
OpenSingleLinePile(vwenv, GetMaxNumLines());
122142

143+
// use normal brackets around abbreviation
123144
vwenv.AddProp(ktagInnerNonBoundary, this, kfragLeftBracket);
124-
vwenv.AddProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragQuestions);
145+
146+
vwenv.AddObjProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragNCAbbreviation);
147+
125148
vwenv.AddProp(ktagInnerNonBoundary, this, kfragRightBracket);
126149

127150
if (!isOuterIterCtxt)
128151
CloseSingleLinePile(vwenv);
129152
}
130-
else if (numLines == 1)
153+
else
131154
{
132-
if (!isOuterIterCtxt)
133-
OpenSingleLinePile(vwenv, GetMaxNumLines());
155+
int numLines = GetNumLines(ncCtxt);
156+
if (numLines == 0)
157+
{
158+
if (!isOuterIterCtxt)
159+
OpenSingleLinePile(vwenv, GetMaxNumLines());
134160

135-
// use normal brackets for a single line context
136-
vwenv.AddProp(ktagInnerNonBoundary, this, kfragLeftBracket);
161+
vwenv.AddProp(ktagInnerNonBoundary, this, kfragLeftBracket);
162+
vwenv.AddProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragQuestions);
163+
vwenv.AddProp(ktagInnerNonBoundary, this, kfragRightBracket);
137164

138-
// special consonant and vowel natural classes only display the abbreviation
139-
if (natClass.Abbreviation.AnalysisDefaultWritingSystem.Text == "C"
140-
|| natClass.Abbreviation.AnalysisDefaultWritingSystem.Text == "V")
141-
{
142-
vwenv.AddObjProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragNC);
165+
if (!isOuterIterCtxt)
166+
CloseSingleLinePile(vwenv);
143167
}
144-
else
168+
else if (numLines == 1)
145169
{
170+
if (!isOuterIterCtxt)
171+
OpenSingleLinePile(vwenv, GetMaxNumLines());
172+
173+
// use normal brackets for a single line context
174+
vwenv.AddProp(ktagInnerNonBoundary, this, kfragLeftBracket);
175+
146176
if (natClass.FeaturesOA != null && natClass.FeaturesOA.FeatureSpecsOC.Count > 0)
147177
vwenv.AddObjProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragFeatNC);
148178
else if (ncCtxt.PlusConstrRS.Count > 0)
149179
vwenv.AddObjVecItems(PhSimpleContextNCTags.kflidPlusConstr, this, kfragPlusVariable);
150180
else
151181
vwenv.AddObjVecItems(PhSimpleContextNCTags.kflidMinusConstr, this, kfragMinusVariable);
152-
}
153-
vwenv.AddProp(ktagInnerNonBoundary, this, kfragRightBracket);
154182

155-
if (!isOuterIterCtxt)
156-
CloseSingleLinePile(vwenv);
157-
}
158-
else
159-
{
160-
// multiline context
161-
162-
// left bracket pile
163-
int maxNumLines = GetMaxNumLines();
164-
vwenv.Props = m_bracketProps;
165-
vwenv.set_IntProperty((int)FwTextPropType.ktptMarginLeading, (int)FwTextPropVar.ktpvMilliPoint, PileMargin);
166-
vwenv.OpenInnerPile();
167-
AddExtraLines(maxNumLines - numLines, ktagLeftNonBoundary, vwenv);
168-
vwenv.AddProp(ktagLeftNonBoundary, this, kfragLeftBracketUpHook);
169-
for (int i = 1; i < numLines - 1; i++)
170-
vwenv.AddProp(ktagLeftNonBoundary, this, kfragLeftBracketExt);
171-
vwenv.AddProp(ktagLeftBoundary, this, kfragLeftBracketLowHook);
172-
vwenv.CloseInnerPile();
173-
174-
// feature and variable pile
175-
vwenv.set_IntProperty((int)FwTextPropType.ktptAlign, (int)FwTextPropVar.ktpvEnum, (int)FwTextAlign.ktalLeft);
176-
vwenv.OpenInnerPile();
177-
AddExtraLines(maxNumLines - numLines, vwenv);
178-
vwenv.AddObjProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragFeatNC);
179-
vwenv.AddObjVecItems(PhSimpleContextNCTags.kflidPlusConstr, this, kfragPlusVariable);
180-
vwenv.AddObjVecItems(PhSimpleContextNCTags.kflidMinusConstr, this, kfragMinusVariable);
181-
vwenv.CloseInnerPile();
182-
183-
// right bracket pile
184-
vwenv.Props = m_bracketProps;
185-
if (!isOuterIterCtxt)
186-
vwenv.set_IntProperty((int)FwTextPropType.ktptMarginTrailing, (int)FwTextPropVar.ktpvMilliPoint, PileMargin);
187-
vwenv.OpenInnerPile();
188-
AddExtraLines(maxNumLines - numLines, ktagRightNonBoundary, vwenv);
189-
vwenv.AddProp(ktagRightNonBoundary, this, kfragRightBracketUpHook);
190-
for (int i = 1; i < numLines - 1; i++)
191-
vwenv.AddProp(ktagRightNonBoundary, this, kfragRightBracketExt);
192-
vwenv.AddProp(ktagRightBoundary, this, kfragRightBracketLowHook);
193-
vwenv.CloseInnerPile();
183+
vwenv.AddProp(ktagInnerNonBoundary, this, kfragRightBracket);
184+
185+
if (!isOuterIterCtxt)
186+
CloseSingleLinePile(vwenv);
187+
}
188+
else
189+
{
190+
// multiline context
191+
192+
// left bracket pile
193+
int maxNumLines = GetMaxNumLines();
194+
vwenv.Props = m_bracketProps;
195+
vwenv.set_IntProperty((int)FwTextPropType.ktptMarginLeading, (int)FwTextPropVar.ktpvMilliPoint, PileMargin);
196+
vwenv.OpenInnerPile();
197+
AddExtraLines(maxNumLines - numLines, ktagLeftNonBoundary, vwenv);
198+
vwenv.AddProp(ktagLeftNonBoundary, this, kfragLeftBracketUpHook);
199+
for (int i = 1; i < numLines - 1; i++)
200+
vwenv.AddProp(ktagLeftNonBoundary, this, kfragLeftBracketExt);
201+
vwenv.AddProp(ktagLeftBoundary, this, kfragLeftBracketLowHook);
202+
vwenv.CloseInnerPile();
203+
204+
// feature and variable pile
205+
vwenv.set_IntProperty((int)FwTextPropType.ktptAlign, (int)FwTextPropVar.ktpvEnum, (int)FwTextAlign.ktalLeft);
206+
vwenv.OpenInnerPile();
207+
AddExtraLines(maxNumLines - numLines, vwenv);
208+
vwenv.AddObjProp(PhSimpleContextNCTags.kflidFeatureStructure, this, kfragFeatNC);
209+
vwenv.AddObjVecItems(PhSimpleContextNCTags.kflidPlusConstr, this, kfragPlusVariable);
210+
vwenv.AddObjVecItems(PhSimpleContextNCTags.kflidMinusConstr, this, kfragMinusVariable);
211+
vwenv.CloseInnerPile();
212+
213+
// right bracket pile
214+
vwenv.Props = m_bracketProps;
215+
if (!isOuterIterCtxt)
216+
vwenv.set_IntProperty((int)FwTextPropType.ktptMarginTrailing, (int)FwTextPropVar.ktpvMilliPoint, PileMargin);
217+
vwenv.OpenInnerPile();
218+
AddExtraLines(maxNumLines - numLines, ktagRightNonBoundary, vwenv);
219+
vwenv.AddProp(ktagRightNonBoundary, this, kfragRightBracketUpHook);
220+
for (int i = 1; i < numLines - 1; i++)
221+
vwenv.AddProp(ktagRightNonBoundary, this, kfragRightBracketExt);
222+
vwenv.AddProp(ktagRightBoundary, this, kfragRightBracketLowHook);
223+
vwenv.CloseInnerPile();
224+
}
194225
}
195226
}
196227
else
@@ -360,6 +391,10 @@ public override ITsString DisplayVariant(IVwEnv vwenv, int tag, int frag)
360391
tss = m_x;
361392
break;
362393

394+
case kfragStars:
395+
tss = m_stars;
396+
break;
397+
363398
default:
364399
tss = base.DisplayVariant(vwenv, tag, frag);
365400
break;
@@ -428,6 +463,51 @@ void DisplayIterCtxt(int numLines, IVwEnv vwenv)
428463
vwenv.CloseInnerPile();
429464
}
430465

466+
/// <summary>
467+
/// Makes the fragment being displayed depend on the name and abbreviation of every
468+
/// feature-based natural class in the given contexts, including those nested in sequence
469+
/// and iteration contexts. Changing either property then rebuilds the whole fragment, so
470+
/// the choice between abbreviation and feature list, line counts and cell widths are all
471+
/// recomputed. Null contexts are ignored.
472+
/// </summary>
473+
protected static void NoteNaturalClassDependencies(IVwEnv vwenv,
474+
params IEnumerable<IPhContextOrVar>[] contextGroups)
475+
{
476+
var hvos = new List<int>();
477+
var tags = new List<int>();
478+
foreach (IEnumerable<IPhContextOrVar> group in contextGroups)
479+
{
480+
foreach (IPhContextOrVar ctxtOrVar in group)
481+
CollectFeatureNaturalClasses(ctxtOrVar, hvos, tags);
482+
}
483+
if (hvos.Count > 0)
484+
vwenv.NoteDependency(hvos.ToArray(), tags.ToArray(), hvos.Count);
485+
}
486+
487+
private static void CollectFeatureNaturalClasses(IPhContextOrVar ctxtOrVar,
488+
List<int> hvos, List<int> tags)
489+
{
490+
switch (ctxtOrVar)
491+
{
492+
case IPhSequenceContext seqCtxt:
493+
foreach (IPhPhonContext member in seqCtxt.MembersRS)
494+
CollectFeatureNaturalClasses(member, hvos, tags);
495+
break;
496+
497+
case IPhIterationContext iterCtxt:
498+
CollectFeatureNaturalClasses(iterCtxt.MemberRA, hvos, tags);
499+
break;
500+
501+
case IPhSimpleContextNC ncCtxt
502+
when ncCtxt.FeatureStructureRA is IPhNCFeatures natClass:
503+
hvos.Add(natClass.Hvo);
504+
tags.Add(PhNaturalClassTags.kflidName);
505+
hvos.Add(natClass.Hvo);
506+
tags.Add(PhNaturalClassTags.kflidAbbreviation);
507+
break;
508+
}
509+
}
510+
431511
/// <summary>
432512
/// Gets the maximum number of lines to display the specified sequence of simple contexts.
433513
/// </summary>
@@ -478,6 +558,13 @@ protected int GetNumLines(IPhContextOrVar ctxtOrVar)
478558
if (ncCtxt.FeatureStructureRA != null && ncCtxt.FeatureStructureRA.ClassID == PhNCFeaturesTags.kClassId)
479559
{
480560
var natClass = (IPhNCFeatures) ncCtxt.FeatureStructureRA;
561+
562+
// User-defined, feature-based natural classes should display
563+
// only the abbreviation, which is a single line.
564+
if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass))
565+
return 1;
566+
567+
// Otherwise, the list of features is displayed.
481568
if (natClass.FeaturesOA != null)
482569
numFeats = natClass.FeaturesOA.FeatureSpecsOC.Count;
483570
}
@@ -589,19 +676,22 @@ int GetNCCtxtWidth(IPhSimpleContextNC ctxt, IVwEnv vwenv)
589676
{
590677
if (ctxt.FeatureStructureRA != null && ctxt.FeatureStructureRA.ClassID == PhNCFeaturesTags.kClassId)
591678
{
679+
// User-defined feature-based natural classes display only the abbreviation,
680+
// so get width of the abbreviation.
681+
var natClass = (IPhNCFeatures)ctxt.FeatureStructureRA;
682+
if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass))
683+
{
684+
int len = GetStrWidth(ctxt.FeatureStructureRA.Abbreviation.BestAnalysisAlternative, null, vwenv);
685+
len += GetStrWidth(m_leftBracket, null, vwenv);
686+
len += GetStrWidth(m_rightBracket, null, vwenv);
687+
return len;
688+
}
689+
592690
int numLines = GetNumLines(ctxt);
593691
if (numLines == 1)
594692
{
595693
int len;
596-
if (ctxt.FeatureStructureRA.Abbreviation.UserDefaultWritingSystem.Text == "C"
597-
|| ctxt.FeatureStructureRA.Abbreviation.UserDefaultWritingSystem.Text == "V")
598-
{
599-
len = GetStrWidth(ctxt.FeatureStructureRA.Abbreviation.BestAnalysisAlternative, null, vwenv);
600-
}
601-
else
602-
{
603-
len = GetNCFeatsWidth(ctxt, vwenv);
604-
}
694+
len = GetNCFeatsWidth(ctxt, vwenv);
605695
len += GetStrWidth(m_leftBracket, null, vwenv);
606696
len += GetStrWidth(m_rightBracket, null, vwenv);
607697
return len;

0 commit comments

Comments
 (0)