diff --git a/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml b/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml
index 36a9dc33aa..1c9dfebc13 100644
--- a/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml
+++ b/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml
@@ -439,7 +439,9 @@
-
+
+
+
diff --git a/Src/LexText/Morphology/AffixRuleFormulaControl.cs b/Src/LexText/Morphology/AffixRuleFormulaControl.cs
index 96d776c50b..e2dba908c2 100644
--- a/Src/LexText/Morphology/AffixRuleFormulaControl.cs
+++ b/Src/LexText/Morphology/AffixRuleFormulaControl.cs
@@ -841,7 +841,9 @@ public override void SetMappingNaturalClass(SelectionHelper sel = null)
var natClasses = new HashSet();
foreach (var nc in m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS)
{
- if (nc.ClassID == PhNCFeaturesTags.kClassId)
+ // Rule mappings only allow feature-based, user-defined natural classes
+ // to be selected.
+ if (nc is IPhNCFeatures featNC && IsFeatureBasedNCUserDefined(featNC))
natClasses.Add(nc);
}
var selectedNc = DisplayChooser(MEStrings.ksRuleNCOpt, MEStrings.ksRuleNCChooserLink,
diff --git a/Src/LexText/Morphology/AffixRuleFormulaSlice.cs b/Src/LexText/Morphology/AffixRuleFormulaSlice.cs
index 8a54d8fa56..8c7f5a872a 100644
--- a/Src/LexText/Morphology/AffixRuleFormulaSlice.cs
+++ b/Src/LexText/Morphology/AffixRuleFormulaSlice.cs
@@ -62,7 +62,11 @@ public bool OnMappingSetNaturalClass(object args)
public bool OnDisplayMappingJumpToNaturalClass(object commandObject, ref XCore.UIItemDisplayProperties display)
{
CheckDisposed();
- bool enable = AffixRuleFormulaControl.IsNCIndexCurrent;
+ // Note that a mapping's Natural Class is always feature-based.
+ // For feature-based NCs, we only want to allow jumping to the NC
+ // if it is user-defined.
+ var mapping = AffixRuleFormulaControl.CurrentObject as IMoModifyFromInput;
+ bool enable = mapping != null && RuleFormulaControl.IsFeatureBasedNCUserDefined(mapping.ModificationRA);
display.Enabled = enable;
display.Visible = enable;
return true;
diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs
new file mode 100644
index 0000000000..9ad1952579
--- /dev/null
+++ b/Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs
@@ -0,0 +1,64 @@
+// 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.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using SIL.LCModel;
+
+namespace SIL.FieldWorks.XWorks.MorphologyEditor
+{
+ ///
+ /// Tests to confirm that includes every
+ /// natural class except a feature-based one generated automatically for a rule.
+ ///
+ [TestFixture]
+ public class NaturalClassRecordListTests : RuleFormulaVcTestBase
+ {
+ private IEnumerable NCRecordList(params ICmObject[] natClasses)
+ {
+ return NaturalClassRecordList.ExcludeAutoGenerated(
+ natClasses.Select(nc => nc.Hvo), Cache);
+ }
+ [Test]
+ public void AutoGeneratedClass_IsExcluded()
+ {
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
+ new Dictionary { { "vd", "+" } });
+
+ // Since the only NC is autogenerated, the NC record list should be empty
+ Assert.That(NCRecordList(natClass), Is.Empty);
+ }
+
+ [Test]
+ public void NamedFeatureClass_IsIncluded()
+ {
+ IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd",
+ new Dictionary { { "vd", "+" } });
+
+ Assert.That(NCRecordList(natClass), Is.EqualTo(new[] { natClass.Hvo }));
+ }
+
+ ///
+ /// A feature-based class the user creates starts with no name, so an unnamed class must
+ /// be included for the user to be able to name it.
+ ///
+ [Test]
+ public void UnnamedFeatureClass_IsIncluded()
+ {
+ IPhNCFeatures natClass = AddFeatureNaturalClass(null, null,
+ new Dictionary { { "vd", "+" } });
+
+ Assert.That(NCRecordList(natClass), Is.EqualTo(new[] { natClass.Hvo }));
+ }
+
+ [Test]
+ public void SegmentClass_IsIncluded()
+ {
+ IPhNCSegments natClass = AddSegmentNaturalClass("Vowels", "V");
+
+ Assert.That(NCRecordList(natClass), Is.EqualTo(new[] { natClass.Hvo }));
+ }
+ }
+}
diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs
index 17e2139aa2..c85d4c7d70 100644
--- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs
+++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs
@@ -21,41 +21,45 @@ public class RuleFormulaControlNaturalClassNameTests : RuleFormulaVcTestBase
[Test]
public void NoClass_IsNotUserDefined()
{
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(null), Is.False);
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(null), Is.False);
}
///
- /// The check answers whether a class carries a name the user gave it, and an unnamed
- /// class carries none. Set Phonological Features relies on that answer to keep
- /// offering feature edits on unnamed classes (LT-22576).
+ /// A user defined class can have no name, while an auto-generated one will always have
+ /// a generated name. Check that if the class has no name, it is flagged as user defined.
///
[Test]
- public void ClassWithNoName_IsNotUserDefined()
+ public void ClassWithNoName_IsUserDefined()
{
IPhNCFeatures natClass = AddFeatureNaturalClass(null, "Vd",
new FeatVals { { "vd", "+" } });
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False,
- "an unnamed class carries no name the user gave it");
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True,
+ "an unnamed class must be user defined");
}
+ ///
+ /// A user defined class can have an empty name, while an auto-generated one will always
+ /// have a nonempty generated name. Check that if the class's name is empty,
+ /// it is flagged as user defined.
+ ///
[Test]
- public void ClassWithAnEmptyName_IsNotUserDefined()
+ public void ClassWithAnEmptyName_IsUserDefined()
{
IPhNCFeatures natClass = AddFeatureNaturalClass(string.Empty, "Vd",
new FeatVals { { "vd", "+" } });
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False,
- "an empty name is not a name the user gave the class");
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True,
+ "a class with an empty name must be user defined");
}
[Test]
public void ClassNamedForARule_IsNotUserDefined()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" } });
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False);
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.False);
}
///
@@ -70,7 +74,7 @@ public void ClassNamedForARuleBehindAPrefix_IsNotUserDefined()
natClass.Name.SetUserWritingSystem("Phonemes s and t - "
+ string.Format(MEStrings.ksRuleNCFeatsName, "s to n"));
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False);
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.False);
}
[Test]
@@ -79,7 +83,7 @@ public void NamedClass_IsUserDefined()
IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd",
new FeatVals { { "vd", "+" } });
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.True);
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True);
}
///
@@ -101,7 +105,7 @@ public void ClassNamedInAnAnalysisWritingSystemOtherThanTheUserOne_IsUserDefined
"the name must reach the check through the analysis writing system, "
+ "not through a placeholder");
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.True);
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True);
}
[Test]
@@ -109,8 +113,44 @@ public void NamedClassWithNoFeatures_IsUserDefined()
{
IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd", new FeatVals());
- Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.True,
+ Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True,
"a name qualifies a class whatever it carries for features");
}
+
+ ///
+ /// No class is neither user defined nor auto-generated.
+ ///
+ [Test]
+ public void NullClass_IsNotAutoGenerated()
+ {
+ Assert.That(RuleFormulaControl.IsAutoGeneratedNaturalClass(null), Is.False);
+ }
+
+ ///
+ /// A translation may put the rule name first, leaving no generated text before the
+ /// placeholder. Its generated names are still recognized, but ordinary names are not.
+ ///
+ [Test]
+ public void FormatStartingWithRuleName_RecognisesGeneratedName()
+ {
+ const string format = "{0} rule: created automatically";
+
+ Assert.That(RuleFormulaControl.IsAutoGeneratedName(string.Format(format, "s to n"), format),
+ Is.True, "a name generated from the format is recognized");
+ Assert.That(RuleFormulaControl.IsAutoGeneratedName("Voiced", format), Is.False,
+ "an empty stub before the rule name must not match every name");
+ }
+
+ ///
+ /// A translation lacking the rule name placeholder must not break recognition; no name
+ /// counts as generated from it.
+ ///
+ [Test]
+ public void FormatWithoutPlaceholder_RecognisesNothing()
+ {
+ const string format = "Created automatically for rule";
+
+ Assert.That(RuleFormulaControl.IsAutoGeneratedName(format, format), Is.False);
+ }
}
}
diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs
index c8f2f59105..39ae080cfc 100644
--- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs
+++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs
@@ -40,7 +40,7 @@ public void RegularRule_RegistersTheNameAndAbbreviationOfItsClass()
[Test]
public void RegularRule_RegistersTheNameOfARuleNamedClass()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" } });
IPhSegRuleRHS rhs = AddRegularRule("s to n");
AddStrucDescNCContext(rhs, natClass);
diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs
index d4f6469427..e831a3f8e2 100644
--- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs
+++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs
@@ -67,7 +67,7 @@ public void NamedClass_WithEmptyAbbreviation_DrawsStars()
[Test]
public void RuleNamedClass_WithOneFeature_DrawsFeature()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" } });
Assert.That(DrawContext(AddStandaloneNCContext(natClass)), Is.EqualTo("[+ vd]"));
@@ -76,7 +76,7 @@ public void RuleNamedClass_WithOneFeature_DrawsFeature()
[Test]
public void RuleNamedClass_WithThreeFeatures_DrawsEveryFeatureInAPile()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" }, { "cons", "+" }, { "cont", "-" } });
string drawn = DrawContext(AddStandaloneNCContext(natClass));
@@ -92,7 +92,7 @@ public void RuleNamedClass_WithThreeFeatures_DrawsEveryFeatureInAPile()
[Test]
public void RuleNamedClass_WithNoFeatures_DrawsQuestions()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", new FeatVals());
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals());
Assert.That(DrawContext(AddStandaloneNCContext(natClass)), Is.EqualTo("[???]"));
}
@@ -105,7 +105,7 @@ public void RuleNamedClass_WithNoFeatures_DrawsQuestions()
[Test]
public void RuleNamedClass_AbbreviatedC_DrawsFeature()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "cons", "+" } });
natClass.Abbreviation.SetAnalysisDefaultWritingSystem("C");
@@ -119,7 +119,7 @@ public void RuleNamedClass_AbbreviatedC_DrawsFeature()
[Test]
public void RuleNamedClass_OnceNamed_DrawsAbbreviation()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" } });
IPhSimpleContextNC ctxt = AddStandaloneNCContext(natClass);
Assert.That(DrawContext(ctxt), Is.EqualTo("[+ vd]"), "before the class is named");
diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs
index 991e79e2e4..8d5c23287e 100644
--- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs
+++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs
@@ -52,7 +52,7 @@ public void NamedClass_WithNoAbbreviation_IsSizedToTheStars()
[Test]
public void RuleNamedClass_WithOneFeature_IsSizedToItsFeature()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" } });
AssertCellMatchesDrawing(AddStandaloneNCContext(natClass), "[+ vd]");
@@ -70,7 +70,7 @@ public void NamedClass_WithThreeFeatures_OccupiesOneLine()
[Test]
public void RuleNamedClass_WithThreeFeatures_OccupiesOneLinePerFeature()
{
- IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n",
+ IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n",
new FeatVals { { "vd", "+" }, { "cons", "+" }, { "cont", "-" } });
Assert.That(NumLinesFor(AddStandaloneNCContext(natClass)), Is.EqualTo(3));
diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs
index 7ba2075b2c..54c8d2348c 100644
--- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs
+++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs
@@ -93,10 +93,10 @@ protected IPhNCFeatures AddFeatureNaturalClass(string name, string abbreviation,
}
///
- /// Adds a feature-based natural class named the way one built for a rule is named, in the
- /// user writing system and with no abbreviation.
+ /// Adds a feature-based natural class named the way one auto-generated for a rule
+ /// is named, in the user writing system and with no abbreviation.
///
- protected IPhNCFeatures AddRuleNamedFeatureNaturalClass(string ruleName, FeatVals featVals)
+ protected IPhNCFeatures AddAutoGeneratedFeatureNaturalClass(string ruleName, FeatVals featVals)
{
IPhNCFeatures natClass = AddFeatureNaturalClass(null, null, featVals);
natClass.Name.SetUserWritingSystem(string.Format(MEStrings.ksRuleNCFeatsName, ruleName));
diff --git a/Src/LexText/Morphology/NaturalClassRecordList.cs b/Src/LexText/Morphology/NaturalClassRecordList.cs
new file mode 100644
index 0000000000..5e9f392552
--- /dev/null
+++ b/Src/LexText/Morphology/NaturalClassRecordList.cs
@@ -0,0 +1,32 @@
+// 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.Collections.Generic;
+using System.Linq;
+using SIL.LCModel;
+
+namespace SIL.FieldWorks.XWorks.MorphologyEditor
+{
+ ///
+ /// Exclude all auto-generated feature-based natural classes from the NC record list.
+ ///
+ public class NaturalClassRecordList : RecordList
+ {
+ protected override IEnumerable GetObjectSet()
+ {
+ return ExcludeAutoGenerated(base.GetObjectSet(), m_cache);
+ }
+
+ ///
+ /// The given natural class HVOs, less the feature-based classes generated automatically
+ /// for a rule.
+ ///
+ internal static IEnumerable ExcludeAutoGenerated(IEnumerable hvos, LcmCache cache)
+ {
+ var repo = cache.ServiceLocator.GetInstance();
+ return hvos.Where(hvo =>
+ !RuleFormulaControl.IsAutoGeneratedNaturalClass(repo.GetObject(hvo) as IPhNaturalClass));
+ }
+ }
+}
diff --git a/Src/LexText/Morphology/RuleFormulaControl.cs b/Src/LexText/Morphology/RuleFormulaControl.cs
index ab19d20a68..1be1e17319 100644
--- a/Src/LexText/Morphology/RuleFormulaControl.cs
+++ b/Src/LexText/Morphology/RuleFormulaControl.cs
@@ -613,7 +613,9 @@ private void m_insertionControl_Insert(object sender, InsertEventArgs e)
break;
case RuleInsertType.NaturalClass:
- IEnumerable natClasses = m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.OrderBy(natc => natc.ShortName);
+ // show all natural classes that are not auto generated
+ IEnumerable natClasses = m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.Where(natc => !IsAutoGeneratedNaturalClass(natc))
+ .OrderBy(natc => natc.ShortName);
ICmObject ncObj = DisplayChooser(MEStrings.ksRuleNCOpt, MEStrings.ksRuleNCChooserLink,
"naturalClassedit", "RuleNaturalClassFlatList", natClasses);
var nc = ncObj as IPhNaturalClass;
@@ -981,48 +983,82 @@ protected int GetIndexToRemove(ICmObject[] objs, SelectionHelper sel, bool forwa
return -1;
}
///
- /// Returns a bool representing whether the currently selected feature-based natural class simple context is user defined.
+ /// Returns a bool representing whether the currently selected feature-based
+ /// natural class simple context is user defined.
///
///
- /// A user-defined natural class has a name assigned by the user.
- /// Natural classes are auto-generated when a user assigns phonological features, and they receive auto-generated names of a specific form.
+ /// A user-defined natural class has either no name or a name assigned by the user.
+ /// Natural classes are auto-generated when a user assigns phonological features,
+ /// and they receive auto-generated names of a specific form.
///
///
- /// Returns false if the natural class is not feature based, or if it has no name or an auto-generated name.
- /// Returns true otherwise.
+ /// Returns false if the natural class is not feature based,
+ /// or if it has an auto-generated name.
+ /// Returns true otherwise (including if the class has no name).
///
- public bool IsFeatureBasedNCNameUserDefined()
+ public bool IsFeatureBasedNCUserDefined()
{
// If the class is not feature based, return false.
if (!IsFeatsNCContextCurrent){ return false; }
var ctxt = (IPhSimpleContextNC) CurrentContext;
- return IsFeatureBasedNCNameUserDefined((IPhNCFeatures)ctxt.FeatureStructureRA);
+ return IsFeatureBasedNCUserDefined((IPhNCFeatures)ctxt.FeatureStructureRA);
}
///
- /// Reports whether a feature-based natural class carries a name the user gave it,
- /// as opposed to no name or the name generated when phonological features are inserted
- /// directly into a rule. Returns false when is null.
+ /// Reports whether a feature-based natural class is user defined.
+ /// Returns false when the class's name is auto-generated or
+ /// is null. Returns true otherwise.
///
- public static bool IsFeatureBasedNCNameUserDefined(IPhNCFeatures natClass)
+ public static bool IsFeatureBasedNCUserDefined(IPhNCFeatures natClass)
{
if (natClass == null)
return false;
- // Get the auto-generated text that precedes the rulename. (e.g. extracting "Created automatically for rule "" from "Created automatically for rule "{0}"")
- // 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.
- string autoGeneratedNameStub = (MEStrings.ksRuleNCFeatsName).Split('{')[0];
-
- // If the natural class name is not null or auto generated, then it is a user defined class name.
- // If a class has no name in any WS, it reads back from LCM as the placeholder "***" rather than as empty text,
- // so we need to manually check whether the name exists in any available WS to determine if the NC is named.
+ // If the NC name is not auto generated, then the class is user defined.
+ // A user-defined NC may have no name, an auto generated NC always has a
+ // generated name.
var name = natClass.Name;
bool isNamed = name != null && name.AvailableWritingSystemIds.Any(ws => !string.IsNullOrEmpty(name.get_String(ws).Text));
string ncName = isNamed ? name.UserDefaultWritingSystem?.Text : null;
- bool isAutoGeneratedOrNull = string.IsNullOrEmpty(ncName) || ncName.Contains(autoGeneratedNameStub);
- return !isAutoGeneratedOrNull;
+
+ // Get the name for an autogenerated natural class, localized in the
+ // user interface language.
+ // e.g. In English, the name would be "Created automatically for rule "{0}""
+ return !IsAutoGeneratedName(ncName, MEStrings.ksRuleNCFeatsName);
}
+
+ ///
+ /// Reports whether is a name generated from
+ /// , the format string auto-generated natural classes are
+ /// named with (its rule name placeholder is "{0}"). The generated text may sit anywhere
+ /// in the name. Returns false for a null or empty name, and for a format with no
+ /// placeholder, since generated names can't be recognized from it.
+ ///
+ internal static bool IsAutoGeneratedName(string ncName, string format)
+ {
+ if (string.IsNullOrEmpty(ncName))
+ return false;
+
+ // Different languages will put the rulename before or after the generated
+ // stubtext, so we need to check against both pre and post rulename stubs.
+ int ruleNameIndex = format.IndexOf("{0}", StringComparison.Ordinal);
+ if (ruleNameIndex < 0)
+ return false; // can't recognize generated names in this translation
+ string autoGenPreStub = format.Substring(0, ruleNameIndex);
+ string autoGenPostStub = format.Substring(ruleNameIndex + 3);
+ return ncName.Contains(autoGenPreStub) && ncName.Contains(autoGenPostStub);
+ }
+
+ ///
+ /// True for feature-based natural classes generated automatically for a rule.
+ /// Phoneme-based classes are never auto-generated.
+ ///
+ public static bool IsAutoGeneratedNaturalClass(IPhNaturalClass nc)
+ {
+ return nc is IPhNCFeatures featNC && !IsFeatureBasedNCUserDefined(featNC);
+ }
+
///
/// Sets the phonological features for the currently selected natural class simple context with
/// a feature-based natural class.
diff --git a/Src/LexText/Morphology/RuleFormulaSlice.cs b/Src/LexText/Morphology/RuleFormulaSlice.cs
index 0b1be0bb1b..3ad0e079ae 100644
--- a/Src/LexText/Morphology/RuleFormulaSlice.cs
+++ b/Src/LexText/Morphology/RuleFormulaSlice.cs
@@ -153,7 +153,7 @@ public bool OnDisplayContextSetFeatures(object commandObject, ref UIItemDisplayP
bool enable = false;
if (RuleFormulaControl.IsFeatsNCContextCurrent)
{
- bool isUserDefinedNC = RuleFormulaControl.IsFeatureBasedNCNameUserDefined();
+ bool isUserDefinedNC = RuleFormulaControl.IsFeatureBasedNCUserDefined();
enable = !isUserDefinedNC; //enable is true iff IsFeatsNCContextCurrent is true and IsNCNameUserDefined is false
}
@@ -173,6 +173,12 @@ public bool OnDisplayContextJumpToNaturalClass(object commandObject, ref UIItemD
{
CheckDisposed();
bool enable = RuleFormulaControl.IsNCContextCurrent;
+
+ // If the current context is a feature-based natural class,
+ // we only want to allow jumping to the natural class if it is user-defined.
+ if (RuleFormulaControl.IsFeatsNCContextCurrent)
+ enable = RuleFormulaControl.IsFeatureBasedNCUserDefined();
+
display.Enabled = enable;
display.Visible = enable;
return true;
diff --git a/Src/LexText/Morphology/RuleFormulaVcBase.cs b/Src/LexText/Morphology/RuleFormulaVcBase.cs
index 007c5be9f4..8b2030d269 100644
--- a/Src/LexText/Morphology/RuleFormulaVcBase.cs
+++ b/Src/LexText/Morphology/RuleFormulaVcBase.cs
@@ -135,7 +135,7 @@ public override void Display(IVwEnv vwenv, int hvo, int frag)
// User-defined feature-based natural classes should display
// only the abbreviation.
- if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass))
+ if (RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass))
{
if (!isOuterIterCtxt)
OpenSingleLinePile(vwenv, GetMaxNumLines());
@@ -561,7 +561,7 @@ protected int GetNumLines(IPhContextOrVar ctxtOrVar)
// User-defined, feature-based natural classes should display
// only the abbreviation, which is a single line.
- if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass))
+ if (RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass))
return 1;
// Otherwise, the list of features is displayed.
@@ -679,7 +679,7 @@ int GetNCCtxtWidth(IPhSimpleContextNC ctxt, IVwEnv vwenv)
// User-defined feature-based natural classes display only the abbreviation,
// so get width of the abbreviation.
var natClass = (IPhNCFeatures)ctxt.FeatureStructureRA;
- if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass))
+ if (RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass))
{
int len = GetStrWidth(ctxt.FeatureStructureRA.Abbreviation.BestAnalysisAlternative, null, vwenv);
len += GetStrWidth(m_leftBracket, null, vwenv);