private IAnalysis FinishItOff()
			{
				FdoCache fdoCache = m_caches.MainCache;
				var wfRepository = fdoCache.ServiceLocator.GetInstance<IWfiWordformRepository>();
				if (m_wf == null)
				{
					IWfiWordform wf;
					// first see if we can find a matching form
					if (wfRepository.TryGetObject(m_tssForm, false, out wf))
						m_wf = wf;
					else
					{
						// The user selected a case form that did not previously have a WfiWordform.
						// Since he is confirming this analysis, we now need to create one.
						// Note: if in context of the wordforms DummyRecordList, the RecordList is
						// smart enough to handle inserting one object without having to Reload the whole list.
						m_wf = fdoCache.ServiceLocator.GetInstance<IWfiWordformFactory>().Create(m_tssForm);
					}
				}
				// If sandbox contains only an empty morpheme string, don't consider this to be a true analysis.
				// Assume that the user was not finished with his analysis (cf. LT-1621).
				if (m_sandbox.IsAnalysisEmpty)
				{
					return m_wf;
				}

				// Update the wordform with any additional wss.
				List<int> wordformWss = m_choices.OtherWritingSystemsForFlid(InterlinLineChoices.kflidWord, 0);
				// we need another way to detect the static ws for kflidWord.
				foreach (int wsId in wordformWss)
				{
					UpdateMlaIfDifferent(m_hvoSbWord, ktagSbWordForm, wsId, m_wf.Hvo, WfiWordformTags.kflidForm);
				}

				// (LT-7807 later refined by FWR-3536)
				// if we're in a special mode for adding monomorphemic words to lexicon and the user's proposed analysis is monomorphemic,
				// if there is an existing possible analysis that matches on form, gloss, and POS, use it.
				// else if there is an existing possible analysis that matches on form and gloss and has no POS,
				//	update the POS and use it.
				// else if the occurrence is currently analyzed as a particular sense and there are no other occurrences
				//  of that sense, update the gloss and/or POS of the sense to match what we want (and use it)
				// else if there is a matching entry with the right form, add a suitable sense to use
				// else make a new entry and sense to use.
				if (m_sandbox.ShouldAddWordGlossToLexicon)
				{
					IhMorphEntry.MorphItem matchingMorphItem = new IhMissingEntry.MorphItem(0, null);
					ITsString tssWf = m_wf.Form.get_String(m_sandbox.RawWordformWs);
					// go through the combo options for lex entries / senses to see if we can find any existing matches.
					using (IhMorphEntry handler = InterlinComboHandler.MakeCombo(m_helpTopicProvider, ktagWordGlossIcon, m_sandbox, 0) as SandboxBase.IhMorphEntry)
					{
						List<IhMorphEntry.MorphItem> morphItems = handler.MorphItems;
						// see if we can use an existing Sense, if it matches the word gloss and word MSA
						foreach (IhMorphEntry.MorphItem morphItem in morphItems)
						{
							// skip lex senses that do not match word gloss and pos in the Sandbox
							if (!SbWordGlossMatchesSenseGloss(morphItem))
								continue;
							if (!SbWordPosMatchesSenseMsaPos(morphItem))
								continue;
							// found a LexSense matching our Word Gloss and MSA POS
							matchingMorphItem = morphItem;
							break;
						}
						if (matchingMorphItem.m_hvoSense == 0)
						{
							// Next see if we can find an existing analysis where the gloss matches and POS is null.
							foreach (IhMorphEntry.MorphItem morphItem in morphItems)
							{
								// skip lex senses that do not match word gloss and pos in the Sandbox
								if (!SbWordGlossMatchesSenseGloss(morphItem))
									continue;
								// found a LexSense matching our Word Gloss and that has no POS.
								var pos = m_caches.RealObject(m_sda.get_ObjectProp(m_hvoSbWord, ktagSbWordPos)) as IPartOfSpeech;
								var sense = m_caches.MainCache.ServiceLocator.GetObject(morphItem.m_hvoSense) as ILexSense;
								if (sense == null)
									continue; // don't think this can happen but play safe.
								var msa = sense.MorphoSyntaxAnalysisRA as IMoStemMsa;
								if (msa == null || msa.PartOfSpeechRA != null)
									continue; // for this case we can only use one that doesn't already have a POS.
								msa.PartOfSpeechRA = pos; // adjust it
								if (m_oldAnalysis.WfiAnalysis != null) // always?
								{
									if (m_oldAnalysis.WfiAnalysis.CategoryRA != pos)
										m_oldAnalysis.WfiAnalysis.CategoryRA = pos;
								}
								matchingMorphItem = morphItem; // and use it.
								break;
							}
						}
						if (matchingMorphItem.m_hvoSense == 0 && m_oldAnalysis != null && m_oldAnalysis.WfiAnalysis != null)
						{
							// still don't have one we can use; see whether it is legitimate to modify the current
							// analysis.
							var oldAnalysis = m_oldAnalysis.WfiAnalysis;
							if (oldAnalysis.MorphBundlesOS.Count == 1
								&& oldAnalysis.MorphBundlesOS[0].SenseRA != null
								&& oldAnalysis.MorphBundlesOS[0].SenseRA.MorphoSyntaxAnalysisRA is IMoStemMsa
								&& OnlyUsedThisOnce(oldAnalysis)
								&& OnlyUsedThisOnce(oldAnalysis.MorphBundlesOS[0].SenseRA))
							{
								// We're allowed to change the existing sense and analysis! A side effect of updating the sense
								// is updating the MSA of the morph bundle of the oldAnalysis.
								var pos = m_caches.RealObject(m_sda.get_ObjectProp(m_hvoSbWord, ktagSbWordPos)) as IPartOfSpeech;
								UpdateSense(oldAnalysis.MorphBundlesOS[0].SenseRA, pos);
								if (oldAnalysis.CategoryRA != pos)
									oldAnalysis.CategoryRA = pos;
								if (m_oldAnalysis.Gloss != null)
								{
									// if the old analysis is a gloss, update it also.
									CopyGlossesToWfiGloss(m_oldAnalysis.Gloss);
									return m_oldAnalysis.Gloss;
								}
								// Don't have an old gloss, create one.
								var newGloss = oldAnalysis.Services.GetInstance<IWfiGlossFactory>().Create();
								oldAnalysis.MeaningsOC.Add(newGloss);
								CopyGlossesToWfiGloss(newGloss);
								return newGloss;
							}
						}
						// If we get here we could not use an existing analysis with any safe modification.
						// if we couldn't use an existing sense but we match a LexEntry form,
						// add a new sense to an existing entry.
						ILexEntry bestEntry = null;
						if (morphItems.Count > 0 && matchingMorphItem.m_hvoSense == 0)
						{
							// Tried using FindBestLexEntryAmongstHomographs() but it matches
							// only CitationForm which MorphItems doesn't know anything about,
							// and doesn't match against Allomorphs which MorphItems do track
							// so this could lead to a crash (LT-9430).
							//
							// Solution: if the user specified a category, see if we can find an entry
							// with a sense using that same category
							// otherwise just add the new sense to the first entry in MorphItems.
							IhMorphEntry.MorphItem bestMorphItem = morphItems[0];
							foreach (IhMorphEntry.MorphItem morphItem in morphItems)
							{
								// skip items that do not match word main pos in the Sandbox
								if (!SbWordMainPosMatchesSenseMsaMainPos(morphItem))
									continue;
								bestMorphItem = morphItem;
								break;
							}

							bestEntry = bestMorphItem.GetPrimaryOrOwningEntry(m_caches.MainCache);
							// lookup this entry;
							matchingMorphItem = FindLexEntryMorphItem(morphItems, bestEntry);
						}

						if (matchingMorphItem.m_hvoMorph == 0)
						{
							// we didn't find a matching lex entry, so create a new entry
							ILexEntry newEntry;
							ILexSense newSense;
							IMoForm allomorph;
							handler.CreateNewEntry(true, out newEntry, out allomorph, out newSense);
						}
						else if (bestEntry != null)
						{
							// we found matching lex entry, so create a new sense for it
							var senseFactory = fdoCache.ServiceLocator.GetInstance<ILexSenseFactory>();
							ILexSense newSense = senseFactory.Create(bestEntry, new SandboxGenericMSA(), "");
							// copy over any word glosses we're showing.
							CopyGlossesToSense(newSense);
							// copy over the Word POS
							var pos = m_caches.RealObject(m_sda.get_ObjectProp(m_hvoSbWord, ktagSbWordPos)) as IPartOfSpeech;
							(newSense.MorphoSyntaxAnalysisRA as IMoStemMsa).PartOfSpeechRA = pos;
							var morph = fdoCache.ServiceLocator.GetInstance<IMoFormRepository>().GetObject(matchingMorphItem.m_hvoMorph);
							handler.UpdateMorphEntry(morph, bestEntry, newSense);
						}
						else
						{
							// we found a matching lex entry and sense, so select it.
							int iMorphItem = morphItems.IndexOf(matchingMorphItem);
							handler.HandleSelect(iMorphItem);
						}
					}
				}

				BuildMorphLists(); // Used later on in the code.
				m_hvoCategoryReal = m_caches.RealHvo(m_sda.get_ObjectProp(m_hvoSbWord, ktagSbWordPos));

				// We may need to create a new WfiAnalysis based on whether we have any sandbox gloss content.
				bool fNeedGloss = false;
				bool fWordGlossLineIsShowing = false; // Set to 'true' if the wrod gloss line is included in the m_choices fields.
				foreach (InterlinLineSpec ilc in m_choices)
				{
					if (ilc.Flid == InterlinLineChoices.kflidWordGloss)
					{
						fWordGlossLineIsShowing = true;
						break;
					}
				}
				if (fWordGlossLineIsShowing)
				{
					// flag that we need to create wfi gloss information if any configured word gloss lines have content.
					foreach (int wsId in m_choices.WritingSystemsForFlid(InterlinLineChoices.kflidWordGloss))
					{
						if (m_sda.get_MultiStringAlt(m_hvoSbWord, ktagSbWordGloss, wsId).Length > 0)
						{
							fNeedGloss = true;
							break;
						}
					}
				}

				// We decided to take this logic out (see LT-1653) because it is annoying when
				// deliberately removing a wrong morpheme breakdown guess.
				//				// We need to create (or find an existing) WfiAnalysis if we have any morphemes,
				//				// a word gloss, or a word category.
				//				bool fNeedAnalysis = fNeedGloss || m_cmorphs > 1 || m_hvoCategoryReal != 0;
				//
				//				// If we have exactly one morpheme, see if it has some non-trivial information
				//				// associated. If not, don't make an analysis.
				//				if (!fNeedGloss && m_cmorphs == 1 && m_hvoCategoryReal == 0 && m_analysisMsas[0] == 0
				//					&& m_analysisSenses[0] == 0 && m_analysisMorphs[0] == 0)
				//				{
				//					fNeedAnalysis = false;
				//				}
				//				// If there's no information at all, the 'analysis' is just the original wordform.
				//				if (!fNeedAnalysis)
				//					return m_hvoWordform;
				// OK, we have some information that corresponds to an analysis. Find or create
				// an analysis that matches.
				int wsVern = m_sandbox.RawWordformWs;
				m_wa = FindMatchingAnalysis(true);
				bool fFoundAnalysis = m_wa != null;
				if (!fFoundAnalysis)
				{
					// Clear the checksum on the wordform. This allows the parser filer to re-evaluate it and
					// delete the old analysis if it is just a simpler, parser-generated form of the one we're now making.
					m_wf.Checksum = 0;
					// Check whether there's a parser-generated analysis that the current settings
					// subsume.  If so, reuse that analysis by filling in the missing data (word gloss,
					// word category, and senses).
					// Another option is that there is an existing 'analysis' that is a trivial one,
					// created by word-only glossing. We can re-use that, filling in the other details
					// now supplied.
					var partialWa = FindMatchingAnalysis(false);
					bool fNewAnal = partialWa == null;
					if (fNewAnal)
					{
						foreach (var ana in m_wf.AnalysesOC)
						{
							if (m_oldAnalysis != null &&
								ana == m_oldAnalysis.WfiAnalysis &&
								OnlyUsedThisOnce(ana) &&
								IsAnalysisHumanApproved(m_caches.MainCache, ana))
							{
								ObsoleteAnalysis = ana;
								break;
							}
						}
						// Create one.
						var waFactory = fdoCache.ServiceLocator.GetInstance<IWfiAnalysisFactory>();
						var waNew = waFactory.Create();
						m_wf.AnalysesOC.Add(waNew);
						m_wa = waNew;
					}
					else
					{
						m_wa = partialWa;
						// For setting word glosses, we should treat this as a 'found' not new analysis
						// if it has any glosses, so we will search for and find any existing ones that match.
						fFoundAnalysis = m_wa.MeaningsOC.Count > 0;
					}
					IPartOfSpeech pos = null;
					if (m_hvoCategoryReal != 0)
						pos = fdoCache.ServiceLocator.GetInstance<IPartOfSpeechRepository>().GetObject(m_hvoCategoryReal);
					m_wa.CategoryRA = pos;
					var mbFactory = fdoCache.ServiceLocator.GetInstance<IWfiMorphBundleFactory>();
					var msaRepository = fdoCache.ServiceLocator.GetInstance<IMoMorphSynAnalysisRepository>();
					var mfRepository = fdoCache.ServiceLocator.GetInstance<IMoFormRepository>();
					var senseRepository = fdoCache.ServiceLocator.GetInstance<ILexSenseRepository>();
					var inflTypeRepository = fdoCache.ServiceLocator.GetInstance<ILexEntryInflTypeRepository>();
					for (int imorph = 0; imorph < m_cmorphs; imorph++)
					{
						IWfiMorphBundle mb;
						if (imorph >= m_wa.MorphBundlesOS.Count)
						{
							mb = mbFactory.Create();
							m_wa.MorphBundlesOS.Insert(imorph, mb);
						}
						else
						{
							mb = m_wa.MorphBundlesOS[imorph];
						}
						// An undo operation can leave stale information in the sandbox.  If
						// that happens, the stored database id values are invalid.  Set them
						// all to zero if the morph is invalid.  (See LT-3824 for a crash
						// scenario.)  This fix prevents a crash, but doesn't do anything for
						// restoring the original values before the operation that is undone.
						if (m_analysisMorphs[imorph] != 0 &&
							!m_sdaMain.get_IsValidObject(m_analysisMorphs[imorph]))
						{
							m_analysisMorphs[imorph] = 0;
							m_analysisMsas[imorph] = 0;
							m_analysisSenses[imorph] = 0;
						}
						// Set the Morph of the bundle if we know a real one; otherwise, just set its Form
						if (m_analysisMorphs[imorph] == 0)
						{
							int hvoSbMorph = m_sda.get_VecItem(m_hvoSbWord, ktagSbWordMorphs, imorph);
							mb.Form.set_String(wsVern, m_sandbox.GetFullMorphForm(hvoSbMorph));
							// Copy any other wss over, without any funny business about morpheme breaks
							// Per LT-14891, we don't allow editing of the other morph lines, so no need to set them.
							//foreach (int ws in m_choices.OtherWritingSystemsForFlid(InterlinLineChoices.kflidMorphemes, 0))
							//{
							//    mb.Form.set_String(ws,
							//        m_caches.DataAccess.get_MultiStringAlt(hvoSbMorph, ktagSbNamedObjName, ws));
							//}
						}
						else
						{
							mb.MorphRA = mfRepository.GetObject(m_analysisMorphs[imorph]);
						}
						// Set the MSA if we have one. Note that it is (pathologically) possible that the user has done
						// something in another window to destroy the MSA we remember, so don't try to set it if so.
						if (m_analysisMsas[imorph] != 0 && m_sdaMain.get_IsValidObject(m_analysisMsas[imorph]))
						{
							mb.MsaRA = msaRepository.GetObject(m_analysisMsas[imorph]);
						}
						// Likewise the Sense
						if (m_analysisSenses[imorph] != 0)
						{
							mb.SenseRA = senseRepository.GetObject(m_analysisSenses[imorph]);
							// set the InflType if we have one.
							var hvoSbInflType = GetRealHvoFromSbWmbInflType(imorph);
							mb.InflTypeRA = hvoSbInflType != 0 ? inflTypeRepository.GetObject(hvoSbInflType) : null;
						}

					}
				}
				else if (fWordGlossLineIsShowing) // If the line is not showing at all, don't bother.
				{
					// (LT-1428) Since we're using an existing WfiAnalysis,
					// We will find or create a new WfiGloss (even for blank lines)
					// if WfiAnalysis already has WfiGlosses
					//	or m_hvoWordGloss is nonzero
					//	or Sandbox has gloss content.
					bool fSbGlossContent = fNeedGloss;
					int cGloss = m_wa.MeaningsOC.Count;
					fNeedGloss = cGloss > 0 || m_wg != null || fSbGlossContent;
				}

				if (m_wa != null)
					EnsureCorrectMorphForms();

				if (!fNeedGloss || m_fWantOnlyWfiAnalysis)
				{
					return m_wa;
				}
				if (m_wg != null)
				{
					// We may consider editing it instead of making a new one.
					// But ONLY if it belongs to the right analysis!!
					if (m_wg.Owner != m_wa)
						m_wg = null;
				}
				/* These are the types of things we are trying to accomplish here.
				Problem 1 -- Correcting a spelling mistake.
					Gloss1: mn <-
				User corrects spelling to men
				Desired:
					Gloss1: men <-
				Bad result:
					Gloss1: mn
					Gloss2: men <-

				Problem 2 -- Switching to a different gloss via typing.
					Gloss1: he <-
					Gloss2: she
				User types in she rather than using dropdown box to select it
				Desired:
					Gloss1: he
					Gloss2: she <-
				Bad result:
					Gloss1: she <-
					Gloss2: she

				Problem 2A
							Gloss1: he <-
				User types in she without first using dropdown box to select "add new gloss"
				Desired:
							Gloss1: he (still used for N other occurrences)
							Gloss2: she <-
				Bad (at least dubious) result:
							Gloss1: she <- (used for this and all other occurrences)

				Problem 3 -- Adding a missing alternative when there are not duplications.
					Gloss1: en=green <-
				User adds the French equivalent
				Desired:
					Gloss1: en=green, fr=vert <-
				Bad result:
					Gloss1: en=green
					Gloss2: en=green, fr=vert <-

				The logic used to be to look for glosses with all alternatives matching or else it
				creates a new one. So 2 would actually work, but 1 and 3 were both bad.

				New logic: keep track of the starting WfiAnalysis and WfiGloss.
				Assuming we haven't changed to a new WfiAnalysis based on other changes, if there
				is a WfiGloss that matches any of the existing alternatives, we switch to that.
				Otherwise we set the alternatives of the starting WfiGloss to whatever the user
				entered. This logic would work for all three examples above, but has problems
				with the following.

				Problem -- add a missing gloss where there are identical glosses in another ws.
					Gloss1: en=them <-
				User adds Spanish gloss
				Desired:
					Gloss1: en=them, es=ellas <-
				This works ok with above logic. But now in another location the user needs to
				use the masculine them in Spanish, so changes ellas to ellos
				Desired:
					Gloss1: en=them, es=ellas
					Gloss2: en=them, es=ellos <-
				Bad result:
					Gloss1: en=them, es=ellos <-

				Eventually, we'll probably want to display a dialog box to ask users what they really want.
				"There are 15 places where "XXX" analyzed as 3pp is currently glossed
				en->"them".  Would you like to
				<radio button, selected> change them all to en->"them" sp->"ellas"?
				<radio button> leave the others glossed en->"them" and let just this one
				be en->"them" sp->"ellas"?
				<radio button> see a concordance and choose which ones to change to
				en->"them" sp->"ellas"?
				*/

				// (LT-1428, LT-12472)
				// -----------------------------
				// When the user edits a gloss,
				// (1) If there is an existing gloss matching what they just changed it to
				//		then switch this instance to point to that one.
				// (2) Else if the gloss is used only in this instance, or if it matches on all WSs that are not blank in the gloss,
				//		then apply the edits directly to the gloss.
				// (3) Else, create a new gloss.
				//-------------------------------
				var gloss = fFoundAnalysis ? FindMatchingGloss() : null;

				if (gloss == null && m_sandbox.WordGlossReferenceCount == 1)
				{
					gloss = m_wg; // update the existing gloss.
				}

				if (gloss == null)
				{
					// Create one.
					var wgFactory = fdoCache.ServiceLocator.GetInstance<IWfiGlossFactory>();
					gloss = wgFactory.Create();
					m_wa.MeaningsOC.Add(gloss);
				}
				foreach (int wsId in m_choices.WritingSystemsForFlid(InterlinLineChoices.kflidWordGloss))
				{
					ITsString tssGloss = m_sda.get_MultiStringAlt(m_hvoSbWord, ktagSbWordGloss, wsId);
					if (!tssGloss.Equals(gloss.Form.get_String(wsId)))
					{
						gloss.Form.set_String(wsId, tssGloss);
					}
				}
				return gloss;
			}
示例#2
0
			/// <summary>
			/// Do the bulk of the computation, everything after initial error checking, which is now nonexistent.
			/// </summary>
			/// <returns>HVO of analysis (WfiWordform, WfiAnalyis, or WfiGloss)</returns>
			private int FinishItOff()
			{
				FdoCache fdoCache = m_caches.MainCache;
				IWordformInventory wfi = fdoCache.LangProject.WordformInventoryOA;
				// Enhance JohnT: this detects temporary, dummy objects at present, but it would be better
				// to make an interface method on ISilDataAccess (or possibly IVwCacheDa) to test for dummy IDs.
				// Note that IsValidObject considers dummy IDs to be valid, so it won't work here.
				if (fdoCache.IsDummyObject(m_hvoWordform))
				{
					// The 'wordform' we were analyzing was a dummy one made by the parser. Now it needs to
					// become real.
					Debug.Fail("Any basic wordform in the FocusBox should now already be a real wordform.");
					// Note: We currently don't use OnRequestConversionToReal here because that will currently
					// only do the conversion if our DummyRecordList has been created.
					// The side effect is that our DummyRecordList will do a full reload if we are in the Words area.
					m_hvoWordform = (wfi as IDummy).ConvertDummyToReal(
						WordformInventory.ConcordanceWordformsFlid(fdoCache), m_hvoWordform).Hvo;
				}
				if (m_hvoWordform == 0)
				{
					// first see if we can find a matching form
					m_hvoWordform = wfi.GetWordformId(m_tssForm);
					// The user selected a case form that did not previously have a WfiWordform.
					// Since he is confirming this analysis, we now need to create one.
					// Note: if in context of the wordforms DummyRecordList, the RecordList is
					// smart enough to handle inserting one object without having to Reload the whole list.
					if (m_hvoWordform == 0)
						m_hvoWordform = wfi.AddRealWordform(m_tssForm).Hvo;
				}
				CleanupCurrentAnnotation();	// if user confirmed alternate case, we may need to readjust InstanceOf.
				// If sandbox contains only an empty morpheme string, don't consider this to be a true analysis.
				// Assume that the user was not finished with his analysis (cf. LT-1621).
				if (m_sandbox.IsAnalysisEmpty)
				{
					return m_hvoWordform;
				}

				// Update the wordform with any additional wss.
				List<int> wordformWss = m_choices.OtherWritingSystemsForFlid(InterlinLineChoices.kflidWord, 0);
				// we need another way to detect the static ws for kflidWord.
				foreach (int wsId in wordformWss)
				{
					UpdateMlaIfDifferent(m_hvoSbWord, ktagSbWordForm, wsId, m_hvoWordform, (int)WfiWordform.WfiWordformTags.kflidForm);
				}

				// (LT-7807)
				// if we're in a special mode for adding monomorphemic words to lexicon
				// if we don't have a lexeme form match, create a new lex entry and sense OR
				// if we have a lexeme form, but no gloss match, create a new sense for the matching LexEntry OR
				// if we have a lexeme form and gloss match, just use it in the analysis.
				if (m_sandbox.ShouldAddWordGlossToLexicon)
				{
					IhMorphEntry.MorphItem matchingMorphItem = new IhMissingEntry.MorphItem(0, null);
					WfiWordform wf = new WfiWordform(fdoCache, m_hvoWordform);
					ITsString tssWf = wf.Form.GetAlternativeTss(m_sandbox.RawWordformWs);
					// go through the combo options for lex entries / senses to see if we can find any existing matches.
					int hvoSbMorph = m_sda.get_VecItem(m_hvoSbWord, ktagSbWordMorphs, 0);
					using (SandboxBase.IhMorphEntry handler = InterlinComboHandler.MakeCombo(Sandbox.ktagWordGlossIcon, m_sandbox, hvoSbMorph) as SandboxBase.IhMorphEntry)
					{
						List<IhMorphEntry.MorphItem> morphItems = handler.MorphItems;
						// see if we can use an existing Sense, if it matches the word gloss and word MSA
						foreach (IhMorphEntry.MorphItem morphItem in morphItems)
						{
							// skip lex senses that do not match word gloss and pos in the Sandbox
							if (!SbWordGlossMatchesSenseGloss(morphItem))
								continue;
							if (!SbWordPosMatchesSenseMsaPos(morphItem))
								continue;
							// found a LexSense matching our Word Gloss and MSA POS
							matchingMorphItem = morphItem;
							break;
						}
						// if we couldn't use an existing sense but we match a LexEntry form,
						// add a new sense to an existing entry.
						ILexEntry bestEntry = null;
						if (morphItems.Count > 0 && matchingMorphItem.m_hvoSense == 0)
						{
							// Tried using FindBestLexEntryAmongstHomographs() but it matches
							// only CitationForm which MorphItems doesn't know anything about,
							// and doesn't match against Allomorphs which MorphItems do track
							// so this could lead to a crash (LT-9430).
							//
							// Solution: if the user specified a category, see if we can find an entry
							// with a sense using that same category
							// otherwise just add the new sense to the first entry in MorphItems.
							IhMorphEntry.MorphItem bestMorphItem = morphItems[0];
							foreach (IhMorphEntry.MorphItem morphItem in morphItems)
							{
								// skip items that do not match word main pos in the Sandbox
								if (!SbWordMainPosMatchesSenseMsaMainPos(morphItem))
									continue;
								bestMorphItem = morphItem;
								break;
							}

							int hvoEntry = bestMorphItem.GetPrimaryOrOwningEntry(m_caches.MainCache);
							if (hvoEntry != 0)
								bestEntry = LexEntry.CreateFromDBObject(m_caches.MainCache, hvoEntry);
							// lookup this entry;
							matchingMorphItem = FindLexEntryMorphItem(morphItems, bestEntry);
						}

						if (matchingMorphItem.m_hvoMorph == 0)
						{
							// we didn't find a matching lex entry, so create a new entry
							ILexEntry newEntry;
							ILexSense newSense;
							IMoForm allomorph;
							handler.CreateNewEntry(true, out newEntry, out allomorph, out newSense);
						}
						else if (bestEntry != null)
						{
							// we found matching lex entry, so create a new sense for it
							ILexSense newSense = LexSense.CreateSense(bestEntry, new DummyGenericMSA(), "");
							// copy over any word glosses we're showing.
							foreach (int wsId in m_choices.WritingSystemsForFlid(InterlinLineChoices.kflidWordGloss))
							{
								UpdateMlaIfDifferent(m_hvoSbWord, ktagSbWordGloss, wsId, newSense.Hvo, (int)LexSense.LexSenseTags.kflidGloss);
							}
							// copy over the Word POS
							(newSense.MorphoSyntaxAnalysisRA as MoStemMsa).PartOfSpeechRAHvo =
								m_caches.RealHvo(m_sda.get_ObjectProp(m_hvoSbWord, ktagSbWordPos));
							handler.UpdateMorphEntry(matchingMorphItem.m_hvoMorph, bestEntry.Hvo, newSense.Hvo);
						}
						else
						{
							// we found a matching lex entry and sense, so select it.
							int iMorphItem = morphItems.IndexOf(matchingMorphItem);
							handler.HandleSelect(iMorphItem);
						}
					}
				}

				BuildMorphLists(); // Used later on in the code.
				m_hvoCategoryReal = m_caches.RealHvo(m_sda.get_ObjectProp(m_hvoSbWord, ktagSbWordPos));

				// We may need to create a new WfiAnalysis based on whether we have any sandbox gloss content.
				bool fNeedGloss = false;
				bool fWordGlossLineIsShowing = false; // Set to 'true' if the wrod gloss line is included in the m_choices fields.
				foreach (InterlinLineSpec ilc in m_choices)
				{
					if (ilc.Flid == InterlinLineChoices.kflidWordGloss)
					{
						fWordGlossLineIsShowing = true;
						break;
					}
				}
				if (fWordGlossLineIsShowing)
				{
					// flag that we need to create wfi gloss information if any configured word gloss lines have content.
					foreach (int wsId in m_choices.WritingSystemsForFlid(InterlinLineChoices.kflidWordGloss))
					{
						if (m_sda.get_MultiStringAlt(m_hvoSbWord, ktagSbWordGloss, wsId).Length > 0)
						{
							fNeedGloss = true;
							break;
						}
					}
				}

				// We decided to take this logic out (see LT-1653) because it is annoying when
				// deliberately removing a wrong morpheme breakdown guess.
				//				// We need to create (or find an existing) WfiAnalysis if we have any morphemes,
				//				// a word gloss, or a word category.
				//				bool fNeedAnalysis = fNeedGloss || m_cmorphs > 1 || m_hvoCategoryReal != 0;
				//
				//				// If we have exactly one morpheme, see if it has some non-trivial information
				//				// associated. If not, don't make an analysis.
				//				if (!fNeedGloss && m_cmorphs == 1 && m_hvoCategoryReal == 0 && m_analysisMsas[0] == 0
				//					&& m_analysisSenses[0] == 0 && m_analysisMorphs[0] == 0)
				//				{
				//					fNeedAnalysis = false;
				//				}
				//				// If there's no information at all, the 'analysis' is just the original wordform.
				//				if (!fNeedAnalysis)
				//					return m_hvoWordform;
				// OK, we have some information that corresponds to an analysis. Find or create
				// an analysis that matches.
				int wsVern = m_sandbox.RawWordformWs;
				m_hvoWfiAnalysis = FindMatchingAnalysis(true);
				bool fFoundAnalysis = m_hvoWfiAnalysis != 0;
				if (!fFoundAnalysis)
				{
					// Clear the checksum on the wordform. This allows the parser filer to re-evaluate it and
					// delete the old analysis if it is just a simpler, parser-generated form of the one we're now making.
					m_sdaMain.SetInt(m_hvoWordform, (int)WfiWordform.WfiWordformTags.kflidChecksum, 0);
					// Check whether there's a parser-generated analysis that the current settings
					// subsume.  If so, reuse that analysis by filling in the missing data (word gloss,
					// word category, and senses).
					int hvoPartialWfiAnal = FindMatchingAnalysis(false);
					bool fNewAnal = hvoPartialWfiAnal == 0;
					if (fNewAnal)
					{
						// Create one.
						m_hvoWfiAnalysis = m_sdaMain.MakeNewObject(WfiAnalysis.kclsidWfiAnalysis, m_hvoWordform,
							(int)WfiWordform.WfiWordformTags.kflidAnalyses, -1);
						// Use the following instead of -1 if the collection is changed to a sequence.
						//	m_sdaMain.get_VecSize(m_hvoWordform, (int)WfiWordform.WfiWordformTags.kflidAnalyses));
					}
					else
					{
						m_hvoWfiAnalysis = hvoPartialWfiAnal;
					}
					m_sdaMain.SetObjProp(m_hvoWfiAnalysis, (int)WfiAnalysis.WfiAnalysisTags.kflidCategory, m_hvoCategoryReal);
					for (int imorph = 0; imorph < m_cmorphs; imorph++)
					{
						int hvoMb;
						if (fNewAnal)
						{
							hvoMb = m_sdaMain.MakeNewObject(WfiMorphBundle.kclsidWfiMorphBundle, m_hvoWfiAnalysis,
								(int)WfiAnalysis.WfiAnalysisTags.kflidMorphBundles, imorph);
						}
						else
						{
							hvoMb = m_sdaMain.get_VecItem(m_hvoWfiAnalysis,
								(int)WfiAnalysis.WfiAnalysisTags.kflidMorphBundles, imorph);
						}
						// An undo operation can leave stale information in the sandbox.  If
						// that happens, the stored database id values are invalid.  Set them
						// all to zero if the morph is invalid.  (See LT-3824 for a crash
						// scenario.)  This fix prevents a crash, but doesn't do anything for
						// restoring the original values before the operation that is undone.
						if (m_analysisMorphs[imorph] != 0 &&
							!m_sdaMain.get_IsValidObject(m_analysisMorphs[imorph]))
						{
							m_analysisMorphs[imorph] = 0;
							m_analysisMsas[imorph] = 0;
							m_analysisSenses[imorph] = 0;
						}
						// Set the Morph of the bundle if we know a real one; otherwise, just set its Form
						if (m_analysisMorphs[imorph] == 0)
						{
							int hvoSbMorph = m_sda.get_VecItem(m_hvoSbWord, ktagSbWordMorphs, imorph);
							m_sdaMain.SetMultiStringAlt(hvoMb,
								(int)WfiMorphBundle.WfiMorphBundleTags.kflidForm, wsVern,
								m_sandbox.GetFullMorphForm(hvoSbMorph));
							// Copy any other wss over, without any funny business about morpheme breaks
							foreach (int ws in m_choices.OtherWritingSystemsForFlid(InterlinLineChoices.kflidMorphemes, 0))
							{
								m_sdaMain.SetMultiStringAlt(hvoMb,
									(int)WfiMorphBundle.WfiMorphBundleTags.kflidForm, ws,
									m_caches.DataAccess.get_MultiStringAlt(hvoSbMorph, ktagSbNamedObjName, ws));
							}
						}
						else
						{
							m_sdaMain.SetObjProp(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidMorph, m_analysisMorphs[imorph]);
						}
						// Set the MSA if we have one. Note that it is (pathologically) possible that the user has done
						// something in another window to destroy the MSA we remember, so don't try to set it if so.
						if (m_analysisMsas[imorph] != 0 && m_sdaMain.get_IsValidObject(m_analysisMsas[imorph]))
						{
							m_sdaMain.SetObjProp(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidMsa, m_analysisMsas[imorph]);
						}
						// Likewise the Sense
						if (m_analysisSenses[imorph] != 0)
						{
							m_sdaMain.SetObjProp(hvoMb, (int)WfiMorphBundle.WfiMorphBundleTags.kflidSense, m_analysisSenses[imorph]);
						}
					}
				}
				else if (fWordGlossLineIsShowing) // If the line is not showing at all, don't bother.
				{
					// (LT-1428) Since we're using an existing WfiAnalysis,
					// We will find or create a new WfiGloss (even for blank lines)
					// if WfiAnalysis already has WfiGlosses
					//	or m_hvoWordGloss is nonzero
					//	or Sandbox has gloss content.
					bool fSbGlossContent = fNeedGloss;
					int cGloss = m_sdaMain.get_VecSize(m_hvoWfiAnalysis,
						(int)WfiAnalysis.WfiAnalysisTags.kflidMeanings);
					fNeedGloss = cGloss > 0 || m_hvoWordGloss != 0 || fSbGlossContent;
				}

				if (m_hvoWfiAnalysis != 0)
					EnsureCorrectMorphForms();

				if (!fNeedGloss || m_fWantOnlyWfiAnalysis)
					return m_hvoWfiAnalysis;

				if (m_hvoWordGloss != 0)
				{
					// We may consider editing it instead of making a new one.
					// But ONLY if it belongs to the right analysis!!
					if (fdoCache.GetOwnerOfObject(m_hvoWordGloss) != m_hvoWfiAnalysis)
						m_hvoWordGloss = 0;
				}
				/* These are the types of things we are trying to accomplish here.
				Problem 1 -- Correcting a spelling mistake.
					Gloss1: mn <-
				User corrects spelling to men
				Desired:
					Gloss1: men <-
				Bad result:
					Gloss1: mn
					Gloss2: men <-

				Problem 2 -- Switching to a different gloss via typing.
					Gloss1: he <-
					Gloss2: she
				User types in she rather than using dropdown box to select it
				Desired:
					Gloss1: he
					Gloss2: she <-
				Bad result:
					Gloss1: she <-
					Gloss2: she

				Problem 2A
							Gloss1: he <-
				User types in she without first using dropdown box to select "add new gloss"
				Desired:
							Gloss1: he (still used for N other occurrences)
							Gloss2: she <-
				Bad (at least dubious) result:
							Gloss1: she <- (used for this and all other occurrences)

				Problem 3 -- Adding a missing alternative when there are not duplications.
					Gloss1: en=green <-
				User adds the French equivalent
				Desired:
					Gloss1: en=green, fr=vert <-
				Bad result:
					Gloss1: en=green
					Gloss2: en=green, fr=vert <-

				The logic used to be to look for glosses with all alternatives matching or else it
				creates a new one. So 2 would actually work, but 1 and 3 were both bad.

				New logic: keep track of the starting WfiAnalysis and WfiGloss.
				Assuming we haven't changed to a new WfiAnalysis based on other changes, if there
				is a WfiGloss that matches any of the existing alternatives, we switch to that.
				Otherwise we set the alternatives of the starting WfiGloss to whatever the user
				entered. This logic would work for all three examples above, but has problems
				with the following.

				Problem -- add a missing gloss where there are identical glosses in another ws.
					Gloss1: en=them <-
				User adds Spanish gloss
				Desired:
					Gloss1: en=them, es=ellas <-
				This works ok with above logic. But now in another location the user needs to
				use the masculine them in Spanish, so changes ellas to ellos
				Desired:
					Gloss1: en=them, es=ellas
					Gloss2: en=them, es=ellos <-
				Bad result:
					Gloss1: en=them, es=ellos <-

				Eventually, we'll probably want to display a dialog box to ask users what they really want.
				"There are 15 places where "XXX" analyzed as 3pp is currently glossed
				en->"them".  Would you like to
				<radio button, selected> change them all to en->"them" sp->"ellas"?
				<radio button> leave the others glossed en->"them" and let just this one
				be en->"them" sp->"ellas"?
				<radio button> see a concordance and choose which ones to change to
				en->"them" sp->"ellas"?
				*/

				// (LT-1428)
				// -----------------------------
				// When the user edits a gloss,
				// (1) If there is an existing gloss matching what they just changed it to
				//		then switch this instance to point to that one.
				// (2) Else if the gloss is used only in this instance
				//		then apply the edits directly to the gloss.
				// (3) Else, create a new gloss.
				//-------------------------------
				int hvoGloss = fFoundAnalysis ? FindMatchingGloss() : 0;

				if (hvoGloss == 0 && m_sandbox.WordGlossReferenceCount == 1)
				{
					hvoGloss = m_hvoWordGloss; // update the existing gloss.
				}

				if (hvoGloss == 0)
				{
					// Create one.
					hvoGloss = m_sdaMain.MakeNewObject(WfiGloss.kclsidWfiGloss, m_hvoWfiAnalysis,
						(int)WfiAnalysis.WfiAnalysisTags.kflidMeanings, -1);
					// use the following if changed to sequence.
					//	m_sdaMain.get_VecSize(m_hvoWfiAnalysis, (int)WfiAnalysis.WfiAnalysisTags.kflidMeanings));

				}
				foreach (int wsId in m_choices.WritingSystemsForFlid(InterlinLineChoices.kflidWordGloss))
				{
					ITsString tssGloss = m_sda.get_MultiStringAlt(m_hvoSbWord, ktagSbWordGloss, wsId);
					if (!tssGloss.Equals(m_sdaMain.get_MultiStringAlt(hvoGloss,
						(int)WfiGloss.WfiGlossTags.kflidForm, wsId)))
					{
						m_sdaMain.SetMultiStringAlt(hvoGloss, (int)WfiGloss.WfiGlossTags.kflidForm,
							wsId, tssGloss);
					}
				}
				return hvoGloss;
			}