From 824dccd672c713a3c95790ab4854770d4b64cd0d Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 14 Jul 2025 09:28:15 -0700 Subject: [PATCH 1/4] Fix LT-20848: Word gloss/cat doesn't get inserted automatically --- .../Interlinear/SandboxBase.ComboHandlers.cs | 118 +-------------- Src/LexText/Interlinear/SandboxBase.cs | 139 ++++++++++++++++++ 2 files changed, 144 insertions(+), 113 deletions(-) diff --git a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs index b458f1d098..05a7f65cbd 100644 --- a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs +++ b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs @@ -812,129 +812,21 @@ internal ITsString NewAnalysisString(string str) return TsStringUtils.MakeString(str, m_caches.MainCache.DefaultAnalWs); } - /// - /// Synchronize the word gloss and POS with the morpheme gloss and MSA info, to the extent possible. - /// Currently works FROM the morpheme TO the Word, but going the other way may be useful, too. - /// - /// for the word gloss: - /// - if only one morpheme, copy sense gloss to word gloss - /// - if multiple morphemes, copy first stem gloss to word gloss, but only if word gloss is empty. - /// for the POS: - /// - if there is more than one stem and they have different parts of speech, do nothing. - /// - if there is more than one derivational affix (DA), do nothing. - /// - otherwise, if there is no DA, use the POS of the stem. - /// - if there is no stem, do nothing. - /// - if there is a DA, use its 'to' POS. - /// (currently we don't insist that the 'from' POS matches the stem) - /// + internal void SyncMonomorphemicGlossAndPos(bool fCopyToWordGloss, bool fCopyToWordPos) { CheckDisposed(); - if (!fCopyToWordGloss && !fCopyToWordPos) - return; - - ISilDataAccess sda = m_caches.DataAccess; - int cmorphs = sda.get_VecSize(m_hvoSbWord, ktagSbWordMorphs); - int hvoSbRootSense = 0; - int hvoStemPos = 0; // ID in real database of part-of-speech of stem. - bool fGiveUpOnPOS = false; - int hvoDerivedPos = 0; // real ID of POS output of derivational MSA. - for (int imorph = 0; imorph < cmorphs; imorph++) - { - int hvoMorph = sda.get_VecItem(m_hvoSbWord, ktagSbWordMorphs, imorph); - int hvoSbSense = sda.get_ObjectProp(hvoMorph, ktagSbMorphGloss); - if (hvoSbSense == 0) - continue; // Can't sync from morph sense to word if we don't have morph sense. - var sense = m_caches.RealObject(hvoSbSense) as ILexSense; - IMoMorphSynAnalysis msa = sense.MorphoSyntaxAnalysisRA; - - // ITsString prefix = sda.get_StringProp(hvoMorph, ktagSbMorphPrefix); - // ITsString suffix = sda.get_StringProp(hvoMorph, ktagSbMorphPostfix); - // bool fStem = prefix.Length == 0 && suffix.Length == 0; - - bool fStem = msa is IMoStemMsa; - - // If we have only one morpheme, treat it as the stem from which we will copy the gloss. - // otherwise, use the first stem we find, if any. - if ((fStem && hvoSbRootSense == 0) || cmorphs == 1) - hvoSbRootSense = hvoSbSense; - - if (fStem) - { - int hvoPOS = (msa as IMoStemMsa).PartOfSpeechRA != null ? (msa as IMoStemMsa).PartOfSpeechRA.Hvo : 0; - if (hvoPOS != hvoStemPos && hvoStemPos != 0) - { - // found conflicting stems - fGiveUpOnPOS = true; - } - else - hvoStemPos = hvoPOS; - } - else if (msa is IMoDerivAffMsa) - { - if (hvoDerivedPos != 0) - fGiveUpOnPOS = true; // more than one DA - else - hvoDerivedPos = (msa as IMoDerivAffMsa).ToPartOfSpeechRA != null ? (msa as IMoDerivAffMsa).ToPartOfSpeechRA.Hvo : 0; - } - } - - // If we found a sense to copy from, do it. Replace the word gloss even there already is - // one, since users get confused/frustrated if we don't. (See LT-6141.) It's marked as a - // guess after all! - CopySenseToWordGloss(fCopyToWordGloss, hvoSbRootSense); - - // If we didn't find a stem, we don't have enough information to find a POS. - if (hvoStemPos == 0) - fGiveUpOnPOS = true; - - int hvoLexPos = 0; - if (!fGiveUpOnPOS) - { - if (hvoDerivedPos != 0) - hvoLexPos = hvoDerivedPos; - else - hvoLexPos = hvoStemPos; - } - CopyLexPosToWordPos(fCopyToWordPos, hvoLexPos); + m_sandbox.SyncMonomorphemicGlossAndPos(fCopyToWordGloss, fCopyToWordPos); } protected virtual void CopySenseToWordGloss(bool fCopyWordGloss, int hvoSbRootSense) { - if (hvoSbRootSense != 0 && fCopyWordGloss) - { - ISilDataAccess sda = m_caches.DataAccess; - m_caches.DataAccess.SetInt(m_hvoSbWord, ktagSbWordGlossGuess, 1); - int hvoRealSense = m_caches.RealHvo(hvoSbRootSense); - foreach (int wsId in m_sandbox.m_choices.EnabledWritingSystemsForFlid(InterlinLineChoices.kflidWordGloss)) - { - // Update the guess, by copying the glosses of the SbNamedObj representing the sense - // to the word gloss property. - //ITsString tssGloss = sda.get_MultiStringAlt(hvoSbRootSense, ktagSbNamedObjName, wsId); - // No, it is safer to copy from the real sense. We may be displaying more WSS for the word than the sense. - ITsString tssGloss = m_caches.MainCache.MainCacheAccessor.get_MultiStringAlt(hvoRealSense, LexSenseTags.kflidGloss, wsId); - sda.SetMultiStringAlt(m_hvoSbWord, ktagSbWordGloss, wsId, tssGloss); - sda.PropChanged(null, (int)PropChangeType.kpctNotifyAll, m_hvoSbWord, ktagSbWordGloss, - wsId, 0, 0); - } - } + m_sandbox.CopySenseToWordGloss(fCopyWordGloss, hvoSbRootSense); } + protected virtual int CopyLexPosToWordPos(bool fCopyToWordCat, int hvoMsaPos) { - int hvoPos = 0; - if (fCopyToWordCat && hvoMsaPos != 0) - { - // got the one we want, in the real database. Make a corresponding sandbox one - // and install it as a guess - hvoPos = m_sandbox.CreateSecondaryAndCopyStrings(InterlinLineChoices.kflidWordPos, hvoMsaPos, - CmPossibilityTags.kflidAbbreviation); - int hvoSbWordPos = m_caches.DataAccess.get_ObjectProp(m_hvoSbWord, ktagSbWordPos); - m_caches.DataAccess.SetObjProp(m_hvoSbWord, ktagSbWordPos, hvoPos); - m_caches.DataAccess.SetInt(hvoPos, ktagSbNamedObjGuess, 1); - m_caches.DataAccess.PropChanged(m_rootb, (int)PropChangeType.kpctNotifyAll, m_hvoSbWord, - ktagSbWordPos, 0, 1, (hvoSbWordPos == 0 ? 0 : 1)); - } - return hvoPos; + return m_sandbox.CopyLexPosToWordPos(fCopyToWordCat, hvoMsaPos); } } diff --git a/Src/LexText/Interlinear/SandboxBase.cs b/Src/LexText/Interlinear/SandboxBase.cs index fdb99a3a9e..649dc8a3a0 100644 --- a/Src/LexText/Interlinear/SandboxBase.cs +++ b/Src/LexText/Interlinear/SandboxBase.cs @@ -1425,6 +1425,11 @@ private bool LoadRealDataIntoSec1(int hvoSbWord, bool fLookForDefaults, bool fAd // improve performance. All the relevant data should already have // been loaded while creating the main interlinear view. LoadSecDataForEntry(entryReal, senseReal, hvoSbWord, cda, wsVern, hvoMbSec, fGuessing, sdaMain); + bool fDirty = Caches.DataAccess.IsDirty(); + bool fApproved = !UsingGuess; + bool fHasApprovedWordGloss = HasWordGloss() && (fDirty || fApproved); + bool fHasApprovedWordCat = HasWordCat() && (fDirty || fApproved); + CopyLexEntryInfoToMonomorphemicWordGlossAndPos(!fHasApprovedWordGloss, !fHasApprovedWordCat); } } if (bldrError.Length > 0) @@ -1472,6 +1477,140 @@ private bool LoadRealDataIntoSec1(int hvoSbWord, bool fLookForDefaults, bool fAd return fGuessing != 0; } + public virtual void CopyLexEntryInfoToMonomorphemicWordGlossAndPos(bool fCopyToWordGloss, bool fCopyToWordPos) + { + // conditionally set up the word gloss and POS to correspond to monomorphemic lex morph entry info. + SyncMonomorphemicGlossAndPos(fCopyToWordGloss, fCopyToWordPos); + // Forget we had an existing wordform; otherwise, the program considers + // all changes to be editing the wordform, and since it belongs to the + // old analysis, the old analysis gets resurrected. + m_hvoWordGloss = 0; + } + + /// + /// Synchronize the word gloss and POS with the morpheme gloss and MSA info, to the extent possible. + /// Currently works FROM the morpheme TO the Word, but going the other way may be useful, too. + /// + /// for the word gloss: + /// - if only one morpheme, copy sense gloss to word gloss + /// - if multiple morphemes, copy first stem gloss to word gloss, but only if word gloss is empty. + /// for the POS: + /// - if there is more than one stem and they have different parts of speech, do nothing. + /// - if there is more than one derivational affix (DA), do nothing. + /// - otherwise, if there is no DA, use the POS of the stem. + /// - if there is no stem, do nothing. + /// - if there is a DA, use its 'to' POS. + /// (currently we don't insist that the 'from' POS matches the stem) + /// + internal void SyncMonomorphemicGlossAndPos(bool fCopyToWordGloss, bool fCopyToWordPos) + { + if (!fCopyToWordGloss && !fCopyToWordPos) + return; + + ISilDataAccess sda = m_caches.DataAccess; + int cmorphs = sda.get_VecSize(RootWordHvo, ktagSbWordMorphs); + int hvoSbRootSense = 0; + int hvoStemPos = 0; // ID in real database of part-of-speech of stem. + bool fGiveUpOnPOS = false; + int hvoDerivedPos = 0; // real ID of POS output of derivational MSA. + for (int imorph = 0; imorph < cmorphs; imorph++) + { + int hvoMorph = sda.get_VecItem(RootWordHvo, ktagSbWordMorphs, imorph); + int hvoSbSense = sda.get_ObjectProp(hvoMorph, ktagSbMorphGloss); + if (hvoSbSense == 0) + continue; // Can't sync from morph sense to word if we don't have morph sense. + var sense = m_caches.RealObject(hvoSbSense) as ILexSense; + IMoMorphSynAnalysis msa = sense.MorphoSyntaxAnalysisRA; + + // ITsString prefix = sda.get_StringProp(hvoMorph, ktagSbMorphPrefix); + // ITsString suffix = sda.get_StringProp(hvoMorph, ktagSbMorphPostfix); + // bool fStem = prefix.Length == 0 && suffix.Length == 0; + + bool fStem = msa is IMoStemMsa; + + // If we have only one morpheme, treat it as the stem from which we will copy the gloss. + // otherwise, use the first stem we find, if any. + if ((fStem && hvoSbRootSense == 0) || cmorphs == 1) + hvoSbRootSense = hvoSbSense; + + if (fStem) + { + int hvoPOS = (msa as IMoStemMsa).PartOfSpeechRA != null ? (msa as IMoStemMsa).PartOfSpeechRA.Hvo : 0; + if (hvoPOS != hvoStemPos && hvoStemPos != 0) + { + // found conflicting stems + fGiveUpOnPOS = true; + } + else + hvoStemPos = hvoPOS; + } + else if (msa is IMoDerivAffMsa) + { + if (hvoDerivedPos != 0) + fGiveUpOnPOS = true; // more than one DA + else + hvoDerivedPos = (msa as IMoDerivAffMsa).ToPartOfSpeechRA != null ? (msa as IMoDerivAffMsa).ToPartOfSpeechRA.Hvo : 0; + } + } + + // If we found a sense to copy from, do it. Replace the word gloss even there already is + // one, since users get confused/frustrated if we don't. (See LT-6141.) It's marked as a + // guess after all! + CopySenseToWordGloss(fCopyToWordGloss, hvoSbRootSense); + + // If we didn't find a stem, we don't have enough information to find a POS. + if (hvoStemPos == 0) + fGiveUpOnPOS = true; + + int hvoLexPos = 0; + if (!fGiveUpOnPOS) + { + if (hvoDerivedPos != 0) + hvoLexPos = hvoDerivedPos; + else + hvoLexPos = hvoStemPos; + } + CopyLexPosToWordPos(fCopyToWordPos, hvoLexPos); + } + + protected virtual void CopySenseToWordGloss(bool fCopyWordGloss, int hvoSbRootSense) + { + if (hvoSbRootSense != 0 && fCopyWordGloss) + { + ISilDataAccess sda = m_caches.DataAccess; + m_caches.DataAccess.SetInt(RootWordHvo, ktagSbWordGlossGuess, 1); + int hvoRealSense = m_caches.RealHvo(hvoSbRootSense); + foreach (int wsId in m_choices.EnabledWritingSystemsForFlid(InterlinLineChoices.kflidWordGloss)) + { + // Update the guess, by copying the glosses of the SbNamedObj representing the sense + // to the word gloss property. + //ITsString tssGloss = sda.get_MultiStringAlt(hvoSbRootSense, ktagSbNamedObjName, wsId); + // No, it is safer to copy from the real sense. We may be displaying more WSS for the word than the sense. + ITsString tssGloss = m_caches.MainCache.MainCacheAccessor.get_MultiStringAlt(hvoRealSense, LexSenseTags.kflidGloss, wsId); + sda.SetMultiStringAlt(RootWordHvo, ktagSbWordGloss, wsId, tssGloss); + sda.PropChanged(null, (int)PropChangeType.kpctNotifyAll, RootWordHvo, ktagSbWordGloss, + wsId, 0, 0); + } + } + } + protected virtual int CopyLexPosToWordPos(bool fCopyToWordCat, int hvoMsaPos) + { + int hvoPos = 0; + if (fCopyToWordCat && hvoMsaPos != 0) + { + // got the one we want, in the real database. Make a corresponding sandbox one + // and install it as a guess + hvoPos = CreateSecondaryAndCopyStrings(InterlinLineChoices.kflidWordPos, hvoMsaPos, + CmPossibilityTags.kflidAbbreviation); + int hvoSbWordPos = m_caches.DataAccess.get_ObjectProp(RootWordHvo, ktagSbWordPos); + m_caches.DataAccess.SetObjProp(RootWordHvo, ktagSbWordPos, hvoPos); + m_caches.DataAccess.SetInt(hvoPos, ktagSbNamedObjGuess, 1); + m_caches.DataAccess.PropChanged(RootBox, (int)PropChangeType.kpctNotifyAll, RootWordHvo, + ktagSbWordPos, 0, 1, (hvoSbWordPos == 0 ? 0 : 1)); + } + return hvoPos; + } + /// /// Does multiString contain a lexical pattern (e.g. [Seg]*)? /// From 6932651d18c8c74b8f8f55ee49c023af77dec87a Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 14 Jul 2025 10:07:45 -0700 Subject: [PATCH 2/4] Copy word gloss and pos after editing morph breaks --- .../Interlinear/SandboxBase.ComboHandlers.cs | 1 + Src/LexText/Interlinear/SandboxBase.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs index 05a7f65cbd..2a59e97087 100644 --- a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs +++ b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs @@ -1216,6 +1216,7 @@ internal void UpdateMorphBreaks(string sMorphs) MorphemeBreaker mb = new MorphemeBreaker(m_caches, sMorphs, m_hvoSbWord, m_wsVern, m_sandbox); mb.Run(); + m_sandbox.CopyLexEntryInfoToMonomorphemicWordGlossAndPos(); m_rootb.Reconstruct(); // Everything changed, more or less. // We've changed properties that the morph manager cares about, but we don't want it // to fire when we fix the selection. diff --git a/Src/LexText/Interlinear/SandboxBase.cs b/Src/LexText/Interlinear/SandboxBase.cs index 649dc8a3a0..055a9554df 100644 --- a/Src/LexText/Interlinear/SandboxBase.cs +++ b/Src/LexText/Interlinear/SandboxBase.cs @@ -1425,11 +1425,7 @@ private bool LoadRealDataIntoSec1(int hvoSbWord, bool fLookForDefaults, bool fAd // improve performance. All the relevant data should already have // been loaded while creating the main interlinear view. LoadSecDataForEntry(entryReal, senseReal, hvoSbWord, cda, wsVern, hvoMbSec, fGuessing, sdaMain); - bool fDirty = Caches.DataAccess.IsDirty(); - bool fApproved = !UsingGuess; - bool fHasApprovedWordGloss = HasWordGloss() && (fDirty || fApproved); - bool fHasApprovedWordCat = HasWordCat() && (fDirty || fApproved); - CopyLexEntryInfoToMonomorphemicWordGlossAndPos(!fHasApprovedWordGloss, !fHasApprovedWordCat); + CopyLexEntryInfoToMonomorphemicWordGlossAndPos(); } } if (bldrError.Length > 0) @@ -1477,10 +1473,14 @@ private bool LoadRealDataIntoSec1(int hvoSbWord, bool fLookForDefaults, bool fAd return fGuessing != 0; } - public virtual void CopyLexEntryInfoToMonomorphemicWordGlossAndPos(bool fCopyToWordGloss, bool fCopyToWordPos) + internal void CopyLexEntryInfoToMonomorphemicWordGlossAndPos() { + bool fDirty = Caches.DataAccess.IsDirty(); + bool fApproved = !UsingGuess; + bool fHasApprovedWordGloss = HasWordGloss() && (fDirty || fApproved); + bool fHasApprovedWordCat = HasWordCat() && (fDirty || fApproved); // conditionally set up the word gloss and POS to correspond to monomorphemic lex morph entry info. - SyncMonomorphemicGlossAndPos(fCopyToWordGloss, fCopyToWordPos); + SyncMonomorphemicGlossAndPos(!fHasApprovedWordGloss, !fHasApprovedWordCat); // Forget we had an existing wordform; otherwise, the program considers // all changes to be editing the wordform, and since it belongs to the // old analysis, the old analysis gets resurrected. From 75869ebd023abc31c13d0ac60fe4f3acdd84ac51 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 14 Jul 2025 11:18:18 -0700 Subject: [PATCH 3/4] Fix unit test --- Src/LexText/Interlinear/ITextDllTests/SandboxBaseTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/LexText/Interlinear/ITextDllTests/SandboxBaseTests.cs b/Src/LexText/Interlinear/ITextDllTests/SandboxBaseTests.cs index 8283403b97..71f68bcc0e 100644 --- a/Src/LexText/Interlinear/ITextDllTests/SandboxBaseTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/SandboxBaseTests.cs @@ -100,7 +100,7 @@ public void HandleTab() VerifySelection(sandbox, false, SandboxBase.ktagSbWordGloss, 0, -1); // Next the icon on the word cat line. sandbox.HandleTab(false); - VerifySelection(sandbox, true, SandboxBase.ktagWordPosIcon, 0, -1); + VerifySelection(sandbox, true, SandboxBase.ktagSbNamedObjName, SandboxBase.ktagSbWordPos, -1); // Then we wrap around to the start icon on the word line. sandbox.HandleTab(false); VerifySelection(sandbox, true, SandboxBase.ktagAnalysisIcon, 0, -1); @@ -117,7 +117,7 @@ public void HandleTab() sandbox.HandleTab(true); VerifySelection(sandbox, true, SandboxBase.ktagAnalysisIcon, 0, -1); sandbox.HandleTab(true); - VerifySelection(sandbox, true, SandboxBase.ktagWordPosIcon, 0, -1); + VerifySelection(sandbox, true, SandboxBase.ktagSbNamedObjName, SandboxBase.ktagSbWordPos, -1); sandbox.HandleTab(true); VerifySelection(sandbox, false, SandboxBase.ktagSbWordGloss, 0, -1); sandbox.HandleTab(true); From e18ebf67f450ac69ab723ea38df14d545bcdac9b Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Wed, 16 Jul 2025 10:26:50 -0700 Subject: [PATCH 4/4] Insert gloss/word automatically when showing interlinear guess --- Src/LexText/Interlinear/InterlinVc.cs | 101 ++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/Src/LexText/Interlinear/InterlinVc.cs b/Src/LexText/Interlinear/InterlinVc.cs index 7537a2f2e5..922a4f5874 100644 --- a/Src/LexText/Interlinear/InterlinVc.cs +++ b/Src/LexText/Interlinear/InterlinVc.cs @@ -1896,6 +1896,14 @@ private void DisplayWordGloss(InterlinLineSpec spec, int choiceIndex) var wa = (IWfiAnalysis) m_defaultObj; if (wa.MeaningsOC.Count == 0) { + ITsString rootGlossName = GetRootGlossName(wa); + if (m_hvoDefault != m_hvoWordBundleAnalysis && rootGlossName != null) + { + // Display our best guess for the gloss. + m_this.SetColor(m_vwenv, m_this.LabelRGBFor(choiceIndex)); + m_vwenv.AddString(rootGlossName); + break; + } // There's no gloss, display something indicating it is missing. m_this.SetColor(m_vwenv, m_this.LabelRGBFor(choiceIndex)); m_vwenv.AddString(m_this.m_tssMissingAnalysis); @@ -1929,6 +1937,35 @@ private void DisplayWordGloss(InterlinLineSpec spec, int choiceIndex) } } + private ITsString GetRootGlossName(IWfiAnalysis wa) + { + // This is modeled after SandboxBase.SyncMonomorphemicGlossAndPos. + ITsString rootGloss = null; + foreach (IWfiMorphBundle morphBundle in wa.MorphBundlesOS) + { + // Get the sense for morphBundle. + ILexSense sense = morphBundle.SenseRA; + if (sense == null && morphBundle.MorphRA != null) + { + ILexEntry lexEntry = morphBundle.MorphRA.Owner as ILexEntry; + if (lexEntry != null && lexEntry.SensesOS.Count > 0) + { + sense = lexEntry.SensesOS[0]; + } + } + if (sense?.Gloss?.BestAnalysisAlternative == null) + continue; + // Consider the sense's gloss. + IMoMorphSynAnalysis msa = sense.MorphoSyntaxAnalysisRA; + bool fStem = msa is IMoStemMsa; + // If we have only one morpheme, treat it as the stem from which we will copy the gloss. + // otherwise, use the first stem we find, if any. + if ((fStem && rootGloss == null) || wa.MorphBundlesOS.Count == 1) + rootGloss = sense.Gloss.BestAnalysisAlternative; + } + return rootGloss; + } + private void DisplayWordPOS(int choiceIndex) { switch(m_defaultObj.ClassID) @@ -1941,6 +1978,19 @@ private void DisplayWordPOS(int choiceIndex) if (m_hvoDefault != m_hvoWordBundleAnalysis) { m_this.SetGuessing(m_vwenv, m_this.GetGuessColor(m_defaultObj)); + var wa = (IWfiAnalysis) m_defaultObj; + int hvoPos = wa.CategoryRA != null ? wa.CategoryRA.Hvo : 0; + if (hvoPos == 0) + { + ITsString rootPOSName = GetRootPOSName(wa); + if (rootPOSName != null) + { + // Display our best guess for the gloss. + m_this.SetColor(m_vwenv, m_this.LabelRGBFor(choiceIndex)); + m_vwenv.AddString(rootPOSName); + break; + } + } } m_this.AddAnalysisPos(m_vwenv, m_hvoDefault, m_hvoWordBundleAnalysis, choiceIndex); break; @@ -1954,6 +2004,57 @@ private void DisplayWordPOS(int choiceIndex) throw new Exception("Invalid type found in Segment analysis"); } } + + private ITsString GetRootPOSName(IWfiAnalysis wa) + { + // This is modeled after SandboxBase.SyncMonomorphemicGlossAndPos. + IPartOfSpeech stemPOS = null; + IPartOfSpeech derivedPOS = null; + foreach (IWfiMorphBundle morphBundle in wa.MorphBundlesOS) + { + // Get the sense for morphBundle. + ILexSense sense = morphBundle.SenseRA; + if (sense == null && morphBundle.MorphRA != null) + { + if (morphBundle.MorphRA.Owner is ILexEntry lexEntry && lexEntry.SensesOS.Count > 0) + { + sense = lexEntry.SensesOS[0]; + } + } + if (sense == null) + continue; + // Consider the sense's part of speech. + IMoMorphSynAnalysis msa = sense.MorphoSyntaxAnalysisRA; + bool fStem = msa is IMoStemMsa; + if (fStem) + { + IPartOfSpeech POS = (msa as IMoStemMsa).PartOfSpeechRA; + if (POS != stemPOS && stemPOS != null) + { + // found conflicting stems + return null; + } + else + stemPOS = POS; + } + else if (msa is IMoDerivAffMsa) + { + if (derivedPOS != null) + return null; // more than one DA + else + derivedPOS = (msa as IMoDerivAffMsa).ToPartOfSpeechRA; + } + } + if (stemPOS == null) + return null; + + IPartOfSpeech lexPOS = derivedPOS ?? stemPOS; + if (lexPOS == null) + return null; + if (lexPOS.Abbreviation.BestAnalysisAlternative.Length > 0) + return lexPOS.Abbreviation.BestAnalysisAlternative; + return lexPOS.Name.BestAnalysisAlternative; + } } public override void DisplayVec(IVwEnv vwenv, int hvo, int tag, int frag)