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);
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)
diff --git a/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs b/Src/LexText/Interlinear/SandboxBase.ComboHandlers.cs
index b458f1d098..2a59e97087 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);
}
}
@@ -1324,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 fdb99a3a9e..055a9554df 100644
--- a/Src/LexText/Interlinear/SandboxBase.cs
+++ b/Src/LexText/Interlinear/SandboxBase.cs
@@ -1425,6 +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);
+ CopyLexEntryInfoToMonomorphemicWordGlossAndPos();
}
}
if (bldrError.Length > 0)
@@ -1472,6 +1473,144 @@ private bool LoadRealDataIntoSec1(int hvoSbWord, bool fLookForDefaults, bool fAd
return fGuessing != 0;
}
+ 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(!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.
+ 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]*)?
///