protected override void HandleChooser()
        {
            // grammar/phonemes/phonological features/[...] (click chooser button)
            using (PhonologicalFeatureChooserDlg dlg = new PhonologicalFeatureChooserDlg())
            {
                IFsFeatStruc originalFs       = null;
                Slice        parentSlice      = Slice;
                int          parentSliceClass = parentSlice.Object.ClassID;
                int          owningFlid       = (parentSlice as PhonologicalFeatureListDlgLauncherSlice).Flid;
                switch (parentSliceClass)
                {
                case PhPhonemeTags.kClassId:
                    IPhPhoneme phoneme = parentSlice.Object as IPhPhoneme;
                    if (phoneme.FeaturesOA != null)
                    {
                        originalFs = phoneme.FeaturesOA;
                    }
                    break;

                case PhNCFeaturesTags.kClassId:
                    IPhNCFeatures features = parentSlice.Object as IPhNCFeatures;
                    if (features.FeaturesOA != null)
                    {
                        originalFs = features.FeaturesOA;
                    }
                    break;
                }

                if (originalFs == null)
                {
                    dlg.SetDlgInfo(m_cache, m_mediator, parentSlice.Object, owningFlid);
                }
                else
                {
                    dlg.SetDlgInfo(m_cache, m_mediator, originalFs);
                }

                DialogResult result = dlg.ShowDialog(parentSlice.FindForm());
                if (result == DialogResult.OK)
                {
                    if (dlg.FS != null)
                    {
                        m_obj = dlg.FS;
                        m_PhonologicalFeatureListDlgLauncherView.UpdateFS(dlg.FS);
                    }
                }
                else if (result != DialogResult.Cancel)
                {
                    dlg.HandleJump();
                }
            }
        }
		protected override void m_treeCombo_AfterSelect(object sender, TreeViewEventArgs e)
		{
			HvoTreeNode selectedNode = e.Node as HvoTreeNode;
			PopupTree pt = GetPopupTree();

			switch (selectedNode.Hvo)
			{
				case kChoosePhonologicaFeatures:
					// Only launch the dialog by a mouse click (or simulated mouse click).
					if (e.Action != TreeViewAction.ByMouse)
						break;
					// Force the PopupTree to Hide() to trigger popupTree_PopupTreeClosed().
					// This will effectively revert the list selection to a previous confirmed state.
					// Whatever happens below, we don't want to actually leave the "Choose phonological features" node selected!
					// This is at least required if the user selects "Cancel" from the dialog below.
					// N.B. the above does not seem to be true; therefore we check for cancel and an empty result
					// and force the combo text to be what it should be.
					pt.Hide();
					using (PhonologicalFeatureChooserDlg dlg = new PhonologicalFeatureChooserDlg())
					{
						Cache.DomainDataByFlid.BeginUndoTask(LexTextControls.ksUndoInsertPhonologicalFeature, LexTextControls.ksRedoInsertPhonologicalFeature);
						var fs = CreateEmptyFeatureStructureInAnnotation(null);
						dlg.SetDlgInfo(Cache, m_mediator, fs);
						dlg.ShowIgnoreInsteadOfDontCare = true;
						dlg.SetHelpTopic("khtptoolBulkEditPhonemesChooserDlg");

						DialogResult result = dlg.ShowDialog(ParentForm);
						if (result == DialogResult.OK)
						{
							if (dlg.FS != null)
							{
								var sFeatures = dlg.FS.LongName;
								if (string.IsNullOrEmpty(sFeatures))
								{
									// user did not select anything in chooser; we want to show the last known node
									// in the dropdown, not "choose phonological feature".
									SetComboTextToLastConfirmedSelection();
								}
								else if (!pt.Nodes.ContainsKey(sFeatures))
								{
									var newSelectedNode = new HvoTreeNode(fs.LongNameTSS, fs.Hvo);
									pt.Nodes.Add(newSelectedNode);
									LoadPopupTree(fs.Hvo);
									selectedNode = newSelectedNode;
								}
							}
						}
						else if (result != DialogResult.Cancel)
						{
							dlg.HandleJump();
						}
						else if (result == DialogResult.Cancel)
						{
							// The user canceled out of the chooser; we want to show the last known node
							// in the dropdown, not "choose phonological feature".
							SetComboTextToLastConfirmedSelection();
						}
						Cache.DomainDataByFlid.EndUndoTask();
					}
					break;
				default:
					break;
			}
			// FWR-3432 - If we get here and we still haven't got a valid Hvo, don't continue
			// on to the base method. It'll crash.
			if (selectedNode.Hvo == kChoosePhonologicaFeatures)
				return;
			base.m_treeCombo_AfterSelect(sender, e);
		}
		void link_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			RuleInsertType type = (RuleInsertType)e.Link.LinkData;

			string optStr = GetOptionString(type);
			using (new UndoRedoTaskHelper(m_cache, string.Format(MEStrings.ksRuleUndoInsert, optStr),
				string.Format(MEStrings.ksRuleRedoInsert, optStr)))
			{
				int data = -1;
				switch (type)
				{
					case RuleInsertType.PHONEME:
						Set<int> phonemes = new Set<int>(m_cache.LangProject.PhonologicalDataOA.PhonemeSetsOS[0].PhonemesOC.HvoArray);
						int phonemeHvo = DisplayChooser(MEStrings.ksRulePhonemeOpt, MEStrings.ksRulePhonemeChooserLink,
							"phonemeEdit", "RulePhonemeFlatList", phonemes);
						if (phonemeHvo != 0)
							data = phonemeHvo;
						break;

					case RuleInsertType.NATURAL_CLASS:
						Set<int> natClasses = new Set<int>(m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.HvoArray);
						int ncHvo = DisplayChooser(MEStrings.ksRuleNCOpt, MEStrings.ksRuleNCChooserLink,
							"naturalClassedit", "RuleNaturalClassFlatList", natClasses);
						if (ncHvo != 0)
							data = ncHvo;
						break;

					case RuleInsertType.FEATURES:
						using (PhonologicalFeatureChooserDlg featChooser = new PhonologicalFeatureChooserDlg())
						{
							featChooser.SetDlgInfo(m_cache, m_mediator);
							if (this.Parent is SIL.FieldWorks.XWorks.MorphologyEditor.RegRuleFormulaControl)
								featChooser.SetHelpTopic("khtpChoose-Grammar-PhonFeats-RegRuleFormulaControl");
							else if (this.Parent is SIL.FieldWorks.XWorks.MorphologyEditor.MetaRuleFormulaControl)
								featChooser.SetHelpTopic("khtpChoose-Grammar-PhonFeats-MetaRuleFormulaControl");
							else if (this.Parent is SIL.FieldWorks.XWorks.MorphologyEditor.AffixRuleFormulaControl)
								featChooser.SetHelpTopic("khtpChoose-LexiconEdit-PhonFeats-AffixRuleFormulaControl");
							DialogResult res = featChooser.ShowDialog();
							if (res == DialogResult.OK)
							{
								PhNCFeatures featNC = new PhNCFeatures();
								m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.Append(featNC);
								featNC.Name.UserDefaultWritingSystem = string.Format(MEStrings.ksRuleNCFeatsName, m_ruleName);
								featNC.FeaturesOA = new FsFeatStruc();
								featChooser.FS = featNC.FeaturesOA;
								featChooser.UpdateFeatureStructure();
								featNC.FeaturesOA.NotifyNew();
								featNC.NotifyNew();
								data = featNC.Hvo;
							}
							else if (res != DialogResult.Cancel)
							{
								featChooser.HandleJump();
							}
						}
						break;

					case RuleInsertType.WORD_BOUNDARY:
						data = m_cache.GetIdFromGuid(LangProject.kguidPhRuleWordBdry);
						break;

					case RuleInsertType.MORPHEME_BOUNDARY:
						data = m_cache.GetIdFromGuid(LangProject.kguidPhRuleMorphBdry);
						break;

					case RuleInsertType.INDEX:
						// put the clicked index in the data field
						data = (int)e.Link.Tag;
						break;

					default:
						data = 0;
						break;
				}

				Insert(this, new RuleInsertEventArgs(type, data));
			}
		}
		/// <summary>
		/// Sets the phonological features for the currently selected natural class simple context with
		/// a feature-based natural class.
		/// </summary>
		public void SetContextFeatures()
		{
			SelectionHelper sel = SelectionHelper.Create(m_view);
			bool reconstruct;

			using (var featChooser = new PhonologicalFeatureChooserDlg())
			{
				var ctxt = (IPhSimpleContextNC) CurrentContext;
				var natClass = (IPhNCFeatures) ctxt.FeatureStructureRA;
				featChooser.Title = MEStrings.ksRuleFeatsChooserTitle;
				if (m_obj is IPhSegRuleRHS)
				{
					featChooser.ShowFeatureConstraintValues = true;
					if (natClass.FeaturesOA != null)
					{
						var rule = m_obj as IPhSegRuleRHS;
						featChooser.SetDlgInfo(m_cache, Mediator, rule.OwningRule, ctxt);
					}
					else
						featChooser.SetDlgInfo(m_cache, Mediator, natClass, PhNCFeaturesTags.kflidFeatures);
				}
				else
				{
					if (natClass.FeaturesOA != null)
						featChooser.SetDlgInfo(m_cache, Mediator, natClass.FeaturesOA);
					else
						featChooser.SetDlgInfo(m_cache, Mediator);
				}
				// FWR-2405: Setting the Help topic requires that the Mediator be already set!
				featChooser.SetHelpTopic("khtpChoose-Grammar-PhonRules-SetPhonologicalFeatures");
				DialogResult res = featChooser.ShowDialog();
				if (res != DialogResult.Cancel)
					featChooser.HandleJump();
				reconstruct = res == DialogResult.OK;
			}

			m_view.Select();
			if (reconstruct)
			{
				m_view.RootBox.Reconstruct();
				sel.RestoreSelectionAndScrollPos();
			}
		}
		/// <summary>
		/// Handles the Insert event of the m_insertionControl control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="InsertEventArgs"/> instance containing the event data.</param>
		private void m_insertionControl_Insert(object sender, InsertEventArgs e)
		{
			var option = (InsertOption) e.Option;

			var undo = string.Format(MEStrings.ksRuleUndoInsert, option);
			var redo = string.Format(MEStrings.ksRuleRedoInsert, option);

			SelectionHelper sel = SelectionHelper.Create(m_view);
			int cellId = -1;
			int cellIndex = -1;
			switch (option.Type)
			{
				case RuleInsertType.Phoneme:
					IEnumerable<IPhPhoneme> phonemes = m_cache.LangProject.PhonologicalDataOA.PhonemeSetsOS[0].PhonemesOC.OrderBy(ph => ph.ShortName);
					ICmObject phonemeObj = DisplayChooser(MEStrings.ksRulePhonemeOpt, MEStrings.ksRulePhonemeChooserLink,
						"phonemeEdit", "RulePhonemeFlatList", phonemes);
					var phoneme = phonemeObj as IPhPhoneme;
					if (phoneme == null)
						return;
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertPhoneme(phoneme, sel, out cellIndex);
						});
					break;

				case RuleInsertType.NaturalClass:
					IEnumerable<IPhNaturalClass> natClasses = m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.OrderBy(natc => natc.ShortName);
					ICmObject ncObj = DisplayChooser(MEStrings.ksRuleNCOpt, MEStrings.ksRuleNCChooserLink,
						"naturalClassedit", "RuleNaturalClassFlatList", natClasses);
					var nc = ncObj as IPhNaturalClass;
					if (nc == null)
						return;
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertNC(nc, sel, out cellIndex);
						});
					break;

				case RuleInsertType.Features:
					using (var featChooser = new PhonologicalFeatureChooserDlg())
					{
						SetupPhonologicalFeatureChoooserDlg(featChooser);
						featChooser.SetHelpTopic(FeatureChooserHelpTopic);
						DialogResult res = featChooser.ShowDialog();
						if (res == DialogResult.OK)
						{
							UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
								{
									IPhNCFeatures featNC = m_cache.ServiceLocator.GetInstance<IPhNCFeaturesFactory>().Create();
									m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.Add(featNC);
									featNC.Name.SetUserWritingSystem(string.Format(MEStrings.ksRuleNCFeatsName, RuleName));
									featNC.FeaturesOA = m_cache.ServiceLocator.GetInstance<IFsFeatStrucFactory>().Create();
									IPhSimpleContextNC ctxt;
									cellId = InsertNC(featNC, sel, out cellIndex, out ctxt);
									featChooser.Context = ctxt;
									featChooser.UpdateFeatureStructure();
								});
						}
						else if (res != DialogResult.Cancel)
						{
							featChooser.HandleJump();
						}
					}
					break;

				case RuleInsertType.WordBoundary:
					IPhBdryMarker wordBdry = m_cache.ServiceLocator.GetInstance<IPhBdryMarkerRepository>().GetObject(LangProjectTags.kguidPhRuleWordBdry);
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertBdry(wordBdry, sel, out cellIndex);
						});
					break;

				case RuleInsertType.MorphemeBoundary:
					IPhBdryMarker morphBdry = m_cache.ServiceLocator.GetInstance<IPhBdryMarkerRepository>().GetObject(LangProjectTags.kguidPhRuleMorphBdry);
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertBdry(morphBdry, sel, out cellIndex);
						});
					break;

				case RuleInsertType.Index:
					// put the clicked index in the data field
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertIndex((int) e.Suboption, sel, out cellIndex);
						});
					break;

				case RuleInsertType.Column:
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertColumn(sel);
						});
					break;

				case RuleInsertType.Variable:
					UndoableUnitOfWorkHelper.Do(undo, redo, m_cache.ActionHandlerAccessor, () =>
						{
							cellId = InsertVariable(sel, out cellIndex);
						});
					break;
			}

			m_view.Select();
			if (cellId != -1)
			{
				// reconstruct the view and place the cursor after the newly added item
				ReconstructView(cellId, cellIndex, false);
			}
		}
		/// <summary>
		/// Handle launching of the phonological feature editor.
		/// </summary>
		protected override void HandleChooser()
		{
			VectorReferenceLauncher vrl = null;
			using (PhonologicalFeatureChooserDlg dlg = new PhonologicalFeatureChooserDlg())
			{
				IFsFeatStruc originalFs = null;
				Slice parentSlice = Slice;
				int parentSliceClass = parentSlice.Object.ClassID;
				int owningFlid = (parentSlice as PhonologicalFeatureListDlgLauncherSlice).Flid;
				switch (parentSliceClass)
				{
					case PhPhoneme.kclsidPhPhoneme:
						IPhPhoneme phoneme = parentSlice.Object as IPhPhoneme;
						if (phoneme.FeaturesOAHvo != 0)
							originalFs = phoneme.FeaturesOA;
						break;
					case PhNCFeatures.kclsidPhNCFeatures:
						IPhNCFeatures features = parentSlice.Object as IPhNCFeatures;
						if (features.FeaturesOAHvo != 0)
							originalFs = features.FeaturesOA;
						break;
				}

				int longNameOldLen = 0;
				if (originalFs != null && originalFs.LongName != null)
					longNameOldLen = originalFs.LongName.Length;

				if (originalFs == null)
					dlg.SetDlgInfo(m_cache, m_mediator, parentSlice.Object, owningFlid);
				else
					dlg.SetDlgInfo(m_cache, m_mediator, originalFs);

				DialogResult result = dlg.ShowDialog(parentSlice.FindForm());
				if (result == DialogResult.OK)
				{
					if (dlg.FS != null)
					{
						int tagLongName = m_cache.VwCacheDaAccessor.GetVirtualHandlerName("FsFeatStruc", "LongNameTSS").Tag;
						m_obj = dlg.FS;
						m_cache.PropChanged(null, PropChangeType.kpctNotifyAll, parentSlice.Object.Hvo, owningFlid,
											  0, 1, 0);
						m_PhonologicalFeatureListDlgLauncherView.UpdateFS(dlg.FS);
						dlg.FS.UpdateFeatureLongName(tagLongName, longNameOldLen);
					}
				}
				else if (result != DialogResult.Cancel)
				{
					if (vrl == null)
					{
						dlg.HandleJump();
					}
					else
					{
						vrl.HandleExternalChooser();
					}
				}
			}
		}
        /// <summary>
        /// Handle launching of the phonological feature editor.
        /// </summary>
        protected override void HandleChooser()
        {
            VectorReferenceLauncher vrl = null;

            using (PhonologicalFeatureChooserDlg dlg = new PhonologicalFeatureChooserDlg())
            {
                IFsFeatStruc originalFs       = null;
                Slice        parentSlice      = Slice;
                int          parentSliceClass = parentSlice.Object.ClassID;
                int          owningFlid       = (parentSlice as PhonologicalFeatureListDlgLauncherSlice).Flid;
                switch (parentSliceClass)
                {
                case PhPhoneme.kclsidPhPhoneme:
                    IPhPhoneme phoneme = parentSlice.Object as IPhPhoneme;
                    if (phoneme.FeaturesOAHvo != 0)
                    {
                        originalFs = phoneme.FeaturesOA;
                    }
                    break;

                case PhNCFeatures.kclsidPhNCFeatures:
                    IPhNCFeatures features = parentSlice.Object as IPhNCFeatures;
                    if (features.FeaturesOAHvo != 0)
                    {
                        originalFs = features.FeaturesOA;
                    }
                    break;
                }

                int longNameOldLen = 0;
                if (originalFs != null && originalFs.LongName != null)
                {
                    longNameOldLen = originalFs.LongName.Length;
                }

                if (originalFs == null)
                {
                    dlg.SetDlgInfo(m_cache, m_mediator, parentSlice.Object, owningFlid);
                }
                else
                {
                    dlg.SetDlgInfo(m_cache, m_mediator, originalFs);
                }

                DialogResult result = dlg.ShowDialog(parentSlice.FindForm());
                if (result == DialogResult.OK)
                {
                    if (dlg.FS != null)
                    {
                        int tagLongName = m_cache.VwCacheDaAccessor.GetVirtualHandlerName("FsFeatStruc", "LongNameTSS").Tag;
                        m_obj = dlg.FS;
                        m_cache.PropChanged(null, PropChangeType.kpctNotifyAll, parentSlice.Object.Hvo, owningFlid,
                                            0, 1, 0);
                        m_PhonologicalFeatureListDlgLauncherView.UpdateFS(dlg.FS);
                        dlg.FS.UpdateFeatureLongName(tagLongName, longNameOldLen);
                    }
                }
                else if (result != DialogResult.Cancel)
                {
                    if (vrl == null)
                    {
                        dlg.HandleJump();
                    }
                    else
                    {
                        vrl.HandleExternalChooser();
                    }
                }
            }
        }