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);