TextEditorContextMenu.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Documents / TextEditorContextMenu.cs / 1305600 / TextEditorContextMenu.cs

                            //---------------------------------------------------------------------------- 
//
// File: TextEditorContextMenu.cs
//
// Copyright (C) Microsoft Corporation.  All rights reserved. 
//
// Description: A Component of TextEditor supporting the default ContextMenu. 
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Documents
{
    using MS.Internal;
    using System.Windows; 
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives; 
    using System.Windows.Input; 
    using System.Windows.Media;
    using System.Windows.Threading; 
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using MS.Win32; 
    using System.Windows.Interop;
 
    // A Component of TextEditor supporting the default ContextMenu. 
    internal static class TextEditorContextMenu
    { 
        //-----------------------------------------------------
        //
        //  Class Internal Methods
        // 
        //-----------------------------------------------------
 
        #region Class Internal Methods 

        // Registers all text editing command handlers for a given control type. 
        internal static void _RegisterClassHandlers(Type controlType, bool registerEventListeners)
        {
            if (registerEventListeners)
            { 
                EventManager.RegisterClassHandler(controlType, FrameworkElement.ContextMenuOpeningEvent, new ContextMenuEventHandler(OnContextMenuOpening));
            } 
        } 

        // Callback for FrameworkElement.ContextMenuOpeningEvent. 
        // If the control is using the default ContextMenu, we initialize it
        // here.
        /// 
        /// Critical - calls EditorContextMenu.AddMenuItems, using the UserInitiated 
        ///             bit in the event args, to add (critical) clipboard commands.
        /// TreatAsSafe - the bit is protected by UserIniatedRoutedEvent permission 
        ///  
        [SecurityCritical,SecurityTreatAsSafe]
        internal static void OnContextMenuOpening(object sender, ContextMenuEventArgs e) 
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
            const double KeyboardInvokedSentinel = -1.0; // e.CursorLeft has this value when the menu is invoked with the keyboard.
 
            if (This == null || This.TextView == null)
            { 
                return; 
            }
 
            // Get the mouse position that base on RenderScope which we will set
            // the caret on the RenderScope.
            Point renderScopeMouseDownPoint = Mouse.GetPosition(This.TextView.RenderScope);
            ContextMenu contextMenu = null; 
            bool startPositionCustomElementMenu = false;
 
            if (This.IsReadOnly) 
            {
                // If the TextEditor is ReadOnly, only take action if 
                // 1. The selection is non-empty AND
                // 2. The user clicked inside the selection.
                if ((e.CursorLeft != KeyboardInvokedSentinel && !This.Selection.Contains(renderScopeMouseDownPoint)) ||
                    (e.CursorLeft == KeyboardInvokedSentinel && This.Selection.IsEmpty)) 
                {
                    return; 
                } 
            }
            else if ((This.Selection.IsEmpty || e.TargetElement is TextElement) && 
                     e.TargetElement != null)
            {
                // Targeted element has its own ContextMenu, don't override it.
                contextMenu = (ContextMenu)e.TargetElement.GetValue(FrameworkElement.ContextMenuProperty); 
            }
            else if (e.CursorLeft == KeyboardInvokedSentinel) 
            { 
                // If the menu was invoked from the keyboard, walk up the tree
                // from the selection.Start looking for a custom menu. 
                TextPointer start = GetContentPosition(This.Selection.Start) as TextPointer;
                if (start != null)
                {
                    TextElement element = start.Parent as TextElement; 

                    while (element != null) 
                    { 
                        contextMenu = (ContextMenu)element.GetValue(FrameworkElement.ContextMenuProperty);
                        if (contextMenu != null) 
                        {
                            startPositionCustomElementMenu = true;
                            break;
                        } 
                        element = element.Parent as TextElement;
                    } 
                } 
            }
 
            // Update the selection caret.
            //
            // A negative offset for e.CursorLeft means the user invoked
            // the menu with a hotkey (shift-F10).  Don't mess with the caret 
            // unless the user right-clicked.
            if (e.CursorLeft != KeyboardInvokedSentinel) 
            { 
                if (!TextEditorMouse.IsPointWithinInteractiveArea(This, Mouse.GetPosition(This.UiScope)))
                { 
                    // Don't bring up a context menu if the user clicked on non-editable space.
                    return;
                }
 
                // Don't update the selection caret if we're bringing up a custom UIElement
                // ContextMenu. 
                if (contextMenu == null || !(e.TargetElement is UIElement)) 
                {
                    using (This.Selection.DeclareChangeBlock()) // NB: This raises a PUBLIC EVENT. 
                    {
                        // If we're not over the selection, move the caret.
                        if (!This.Selection.Contains(renderScopeMouseDownPoint))
                        { 
                            TextEditorMouse.SetCaretPositionOnMouseEvent(This, renderScopeMouseDownPoint, MouseButton.Right, 1 /* clickCount */);
                        } 
                    } 
                }
            } 

            if (contextMenu == null)
            {
                // If someone explicitly set it null -- don't mess with it. 
                if (This.UiScope.ReadLocalValue(FrameworkElement.ContextMenuProperty) == null)
                    return; 
 
                // Grab whatever's set to the UiScope's ContextMenu property.
                contextMenu = This.UiScope.ContextMenu; 
            }

            // If we are here, it means that either a custom context menu or our default context menu will be opened.
            // Setting this flag ensures that we dont loose selection highlight while the context menu is open. 
            This.IsContextMenuOpen = true;
 
            // If it's not null, someone's overriding our default -- don't mess with it. 
            if (contextMenu != null && !startPositionCustomElementMenu)
            { 
                // If the user previously raised the ContextMenu with the keyboard,
                // we've left h/v offsets non-zero, and they need to be cleared now
                // for mouse placement to work.
                contextMenu.HorizontalOffset = 0; 
                contextMenu.VerticalOffset = 0;
 
                // Since ContextMenuService doesn't open the menu, it won't fire a ContextMenuClosing event. 
                // We need to listen to the Closed event of the ContextMenu itself so we can clear the
                // IsContextMenuOpen flag.  We also do this for the default menu later in this method. 
                contextMenu.Closed += new RoutedEventHandler(OnContextMenuClosed);
                return;
            }
 
            // Complete the composition before creating the editor context menu.
            This.CompleteComposition(); 
 
            if (contextMenu == null)
            { 
                // It's a default null, so spin up a temporary ContextMenu now.
                contextMenu = new EditorContextMenu();
                ((EditorContextMenu)contextMenu).AddMenuItems(This, e.UserInitiated);
            } 
            contextMenu.Placement = PlacementMode.RelativePoint;
            contextMenu.PlacementTarget = This.UiScope; 
 
            ITextPointer position = null;
            LogicalDirection direction; 

            // Position the ContextMenu.

            SpellingError spellingError = (contextMenu is EditorContextMenu) ? This.GetSpellingErrorAtSelection() : null; 

            if (spellingError != null) 
            { 
                // If we have a matching speller error at the selection
                // start, position relative to the end of the error. 
                position = spellingError.End;
                direction = LogicalDirection.Backward;
            }
            else if (e.CursorLeft == KeyboardInvokedSentinel) 
            {
                // A negative offset for e.CursorLeft means the user invoked 
                // the menu with a hotkey (shift-F10).  Place the menu 
                // relative to Selection.Start.
                position = This.Selection.Start; 
                direction = LogicalDirection.Forward;
            }
            else
            { 
                direction = LogicalDirection.Forward;
            } 
 
            // Calculate coordinats for the ContextMenu.
            // They must be set relative to UIScope - as EditorContextMenu constructor assumes. 
            if (position != null && position.CreatePointer(direction).HasValidLayout)
            {
                double horizontalOffset;
                double verticalOffset; 

                GetClippedPositionOffsets(This, position, direction, out horizontalOffset, out verticalOffset); 
 
                contextMenu.HorizontalOffset = horizontalOffset;
                contextMenu.VerticalOffset = verticalOffset; 
            }
            else
            {
                Point uiScopeMouseDownPoint = Mouse.GetPosition(This.UiScope); 

                contextMenu.HorizontalOffset = uiScopeMouseDownPoint.X; 
                contextMenu.VerticalOffset = uiScopeMouseDownPoint.Y; 
            }
 
            // Since ContextMenuService doesn't open the menu, it won't fire a ContextMenuClosing event.
            // We need to listen to the Closed event of the ContextMenu itself so we can clear the
            // IsContextMenuOpen flag.
            contextMenu.Closed += new RoutedEventHandler(OnContextMenuClosed); 

            // This line raises a public event. 
            contextMenu.IsOpen = true; 

            e.Handled = true; 
        }

        #endregion Class Internal Methods
 
        //------------------------------------------------------
        // 
        //  Private Methods 
        //
        //----------------------------------------------------- 

        #region Private Methods

        // We listen to this event to reset TextEditor._isContextMenuOpen flag. 
        private static void OnContextMenuClosed(object sender, RoutedEventArgs e)
        { 
            UIElement placementTarget = ((ContextMenu)sender).PlacementTarget; 

            if (placementTarget != null) 
            {
                TextEditor This = TextEditor._GetTextEditor(placementTarget);

                if (This != null) 
                {
                    This.IsContextMenuOpen = false; 
                    This.Selection.UpdateCaretAndHighlight(); 
                    ((ContextMenu)sender).Closed -= new RoutedEventHandler(OnContextMenuClosed);
                } 
            }
        }

        ///  
        /// Calculates x, y offsets for a ContextMenu based on an ITextPointer and
        /// the viewports of its containers. 
        ///  
        /// 
        ///     SecurityCritical: This code asserts to get the containing HWND. 
        ///     TreatAsSafe: The HWND is not exposed, only its RECT.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private static void GetClippedPositionOffsets(TextEditor This, ITextPointer position, LogicalDirection direction, 
            out double horizontalOffset, out double verticalOffset)
        { 
            // GetCharacterRect will return the position that base on UiScope. 
            Rect positionRect = position.GetCharacterRect(direction);
 
            // Get the base offsets for our ContextMenu.
            horizontalOffset = positionRect.X;
            verticalOffset = positionRect.Y + positionRect.Height;
 
            // Clip to the child render scope.
            FrameworkElement element = This.TextView.RenderScope as FrameworkElement; 
            if (element != null) 
            {
                GeneralTransform transform = element.TransformToAncestor(This.UiScope); 
                if (transform != null)
                {
                    ClipToElement(element, transform, ref horizontalOffset, ref verticalOffset);
                } 
            }
 
            // Clip to parent visuals. 
            // This is unintuitive -- you might expect parents to have increasingly
            // larger viewports.  But any parent that behaves like a ScrollViewer 
            // will have a smaller view port that we need to clip against.
            for (Visual visual = This.UiScope; visual != null; visual = VisualTreeHelper.GetParent(visual) as Visual)
            {
                element = visual as FrameworkElement; 
                if (element != null)
                { 
                    GeneralTransform transform = visual.TransformToDescendant(This.UiScope); 
                    if (transform != null)
                    { 
                        ClipToElement(element, transform, ref horizontalOffset, ref verticalOffset);
                    }
                }
            } 

            // Clip to the window client rect. 
            PresentationSource source = PresentationSource.CriticalFromVisual(This.UiScope); 
            IWin32Window window = source as IWin32Window;
            if (window != null) 
            {
                IntPtr hwnd = IntPtr.Zero;
                new UIPermission(UIPermissionWindow.AllWindows).Assert(); // BlessedAssert
                try 
                {
                    hwnd = window.Handle; 
                } 
                finally
                { 
                    CodeAccessPermission.RevertAssert();
                }

                NativeMethods.RECT rc = new NativeMethods.RECT(0, 0, 0, 0); 
                SafeNativeMethods.GetClientRect(new HandleRef(null, hwnd), ref rc);
 
                // Convert to mil measure units. 
                Point minPoint = new Point(rc.left, rc.top);
                Point maxPoint = new Point(rc.right, rc.bottom); 

                CompositionTarget compositionTarget = source.CompositionTarget;
                minPoint = compositionTarget.TransformFromDevice.Transform(minPoint);
                maxPoint = compositionTarget.TransformFromDevice.Transform(maxPoint); 

                // Convert to local coordinates. 
                GeneralTransform transform = compositionTarget.RootVisual.TransformToDescendant(This.UiScope); 
                if (transform != null)
                { 
                    transform.TryTransform(minPoint, out minPoint);
                    transform.TryTransform(maxPoint, out maxPoint);

                    // Finally, do the clip. 
                    horizontalOffset = ClipToBounds(minPoint.X, horizontalOffset, maxPoint.X);
                    verticalOffset = ClipToBounds(minPoint.Y, verticalOffset, maxPoint.Y); 
                } 

                // ContextMenu code takes care of clipping to desktop. 
            }
        }

        // Clips a Point to the ActualWidth/Height of a containing FrameworkElement. 
        private static void ClipToElement(FrameworkElement element, GeneralTransform transform,
            ref double horizontalOffset, ref double verticalOffset) 
        { 
            Point minPoint;
            Point maxPoint; 

            Geometry clip = VisualTreeHelper.GetClip(element);

            if (clip != null) 
            {
                Rect bounds = clip.Bounds; 
                minPoint = new Point(bounds.X, bounds.Y); 
                maxPoint = new Point(bounds.X + bounds.Width, bounds.Y + bounds.Height);
            } 
            else
            {
                if (element.ActualWidth == 0 && element.ActualHeight == 0)
                { 
                    // Some elements, noteably Canvas, have a (0, 0) desired size
                    // and should be ignored. 
                    return; 
                }
 
                minPoint = new Point(0, 0);
                maxPoint = new Point(element.ActualWidth, element.ActualHeight);
            }
 
            transform.TryTransform(minPoint, out minPoint);
            transform.TryTransform(maxPoint, out maxPoint); 
 
            // NB: ClipToBounds will handle the case where transform flips a coordinate
            // axis.  In that case, minPoint.X will be > maxPoint.X. 
            horizontalOffset = ClipToBounds(minPoint.X, horizontalOffset, maxPoint.X);
            verticalOffset = ClipToBounds(minPoint.Y, verticalOffset, maxPoint.Y);
        }
 
        // Clips value to the range min to (max - 1).
        private static double ClipToBounds(double min, double value, double max) 
        { 
            // If we're clipping against something with an inverted coordinate axis
            // (the common case is an RTL control in an LTR environment), then 
            // "min" in the parent space is a max for the child.
            if (min > max)
            {
                double temp = min; 
                min = max;
                max = temp; 
            } 

            if (value < min) 
            {
                value = min;
            }
            else if (value >= max) 
            {
                value = max - 1; 
            } 

            return value; 
        }

        // Returns a position ajacent to the supplied position, skipping any
        // intermediate Inlines. 
        // This is useful for sliding inside the context of adjacent Hyperlinks,
        // Spans, etc. 
        private static ITextPointer GetContentPosition(ITextPointer position) 
        {
            while (position.GetAdjacentElement(LogicalDirection.Forward) is Inline) 
            {
                position = position.GetNextContextPosition(LogicalDirection.Forward);
            }
 
            return position;
        } 
 
        #endregion Private methods
 
        //------------------------------------------------------
        //
        //  Private Types
        // 
        //------------------------------------------------------
 
        #region Private Types 

        // Default ContextMenu for TextBox and RichTextBox. 
        private class EditorContextMenu : ContextMenu
        {
            // Initialize the context menu.
            // Creates a new instance. 
            /// 
            /// Critical - accepts a parameter which may be used to set the userInitiated 
            ///             bit on a command, which is used for security purposes later. 
            ///             Although there is a demand here to prevent non userinitiated
            ///             code paths to be blocked this function is not TreatAsSafe because 
            ///             we want to track any new callers to this call
            /// 
            [SecurityCritical]
            internal void AddMenuItems(TextEditor textEditor, bool userInitiated) 
            {
 
                // create a special menu item for paste which only works for user initiated paste 
                // within the confines of partial trust this cannot be done programmatically
                if (userInitiated == false) 
                {
                    SecurityHelper.DemandAllClipboardPermission();
                }
 
                if (!textEditor.IsReadOnly)
                { 
                    if (AddReconversionItems(textEditor)) 
                    {
                        AddSeparator(); 
                    }
                }

                if (AddSpellerItems(textEditor)) 
                {
                    AddSeparator(); 
                } 
                AddClipboardItems(textEditor, userInitiated);
            } 
            // Finalizer release the candidate list if it remains.
            ~EditorContextMenu()
            {
                ReleaseCandidateList(null); 
            }
 
            // Called when the ContextMenu is shutting down. 
            protected override void OnClosed(RoutedEventArgs e)
            { 
                base.OnClosed(e);

                // OnClick for the menu item might be called after the context menu is closed. It depends
                // on how this context menu is created. 
                // We will release CandidateList after all the current events are handled.
                DelayReleaseCandidateList(); 
            } 

            // Helper which appends a separator item. 
            private void AddSeparator()
            {
                this.Items.Add(new Separator());
            } 

            // Appends spell check related items. 
            // Returns false if no items are added. 
            private bool AddSpellerItems(TextEditor textEditor)
            { 
                SpellingError spellingError;
                MenuItem menuItem;

                spellingError = textEditor.GetSpellingErrorAtSelection(); 
                if (spellingError == null)
                    return false; 
 
                bool addedSuggestion = false;
 
                foreach (string suggestion in spellingError.Suggestions)
                {
                    menuItem = new EditorMenuItem();
                    TextBlock text = new TextBlock(); 
                    text.FontWeight = FontWeights.Bold;
                    text.Text = suggestion; 
                    menuItem.Header = text; 
                    menuItem.Command = EditingCommands.CorrectSpellingError;
                    menuItem.CommandParameter = suggestion; 
                    this.Items.Add(menuItem);
                    menuItem.CommandTarget = textEditor.UiScope;

                    addedSuggestion = true; 
                }
 
                if (!addedSuggestion) 
                {
                    menuItem = new EditorMenuItem(); 
                    menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_NoSpellingSuggestions);
                    menuItem.IsEnabled = false;
                    this.Items.Add(menuItem);
                } 

                AddSeparator(); 
 
                menuItem = new EditorMenuItem();
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_IgnoreAll); 
                menuItem.Command = EditingCommands.IgnoreSpellingError;
                this.Items.Add(menuItem);
                menuItem.CommandTarget = textEditor.UiScope;
 
                return true;
            } 
 
            // Add the description to the candidate string of Cicero's
            // reconversion if necessary. 
            private string GetMenuItemDescription(string suggestion)
            {
                if (suggestion.Length == 1)
                { 
                    if (suggestion[0] == 0x0020)
                    { 
                        return SR.Get(SRID.TextBox_ContextMenu_Description_SBCSSpace); 
                    }
                    else if (suggestion[0] == 0x3000) 
                    {
                        return SR.Get(SRID.TextBox_ContextMenu_Description_DBCSSpace);
                    }
 
                }
                return null; 
            } 

            // Appends Cicero reconversion related items. 
            // Returns false if no items are added.
            /// 
            /// Critical - calls unmanaged code.
            /// TreatAsSafe - does not expose the unmanaged interface. retrieve the candidate list and add menu item. 
            /// 
            [SecurityCritical, SecurityTreatAsSafe] 
            private bool AddReconversionItems(TextEditor textEditor) 
            {
                MenuItem menuItem; 
                TextStore textStore = textEditor.TextStore;

                if (textStore == null)
                { 
                    GC.SuppressFinalize(this);
                    return false; 
                } 

                ReleaseCandidateList(null); 
                _candidateList = new  SecurityCriticalDataClass(textStore.GetReconversionCandidateList());
                if (CandidateList == null)
                {
                    GC.SuppressFinalize(this); 
                    return false;
                } 
 
                int count = 0;
                CandidateList.GetCandidateNum(out count); 

                if (count > 0)
                {
                    // Like Winword, we show the first 5 candidates in the context menu. 
                    int i;
                    for (i = 0; i < 5 && i < count; i++) 
                    { 
                        string suggestion;
                        UnsafeNativeMethods.ITfCandidateString candString; 

                        CandidateList.GetCandidate(i, out candString);
                        candString.GetString(out suggestion);
 
                        menuItem = new ReconversionMenuItem(this, i);
                        menuItem.Header = suggestion; 
                        menuItem.InputGestureText = GetMenuItemDescription(suggestion); 
                        this.Items.Add(menuItem);
 
                        Marshal.ReleaseComObject(candString);
                    }
                }
 
                // Like Winword, we show "More" menu to open TIP's candidate list if there are more
                // than 5 candidates. 
                if (count > 5) 
                {
                    menuItem = new EditorMenuItem(); 
                    menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_More);
                    menuItem.Command = ApplicationCommands.CorrectionList;
                    this.Items.Add(menuItem);
                    menuItem.CommandTarget = textEditor.UiScope; 
                }
 
                return (count > 0) ? true : false; 
            }
 
            // Appends clipboard related items.
            // Returns false if no items are added.
            /// 
            /// Critical - accepts a parameter which may be used to set the userInitiated 
            ///             bit on a command
            ///             Although there is a demand here to prevent non userinitiated 
            ///             code paths to be blocked this function is not TreatAsSafe because 
            ///             we want to track any new callers to this call
            ///  
            [SecurityCritical]
            private bool AddClipboardItems(TextEditor textEditor, bool userInitiated)
            {
                MenuItem menuItem; 

                menuItem = new EditorMenuItem(); 
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_Cut); 
                menuItem.CommandTarget = textEditor.UiScope;
                menuItem.Command = ApplicationCommands.Cut; 
                this.Items.Add(menuItem);

                menuItem = new EditorMenuItem();
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_Copy); 
                menuItem.CommandTarget = textEditor.UiScope;
                menuItem.Command = ApplicationCommands.Copy; 
                this.Items.Add(menuItem); 

                // create a special menu item for paste which only works for user initiated paste 
                // within the confines of partial trust this cannot be done programmatically
                if (userInitiated == false)
                {
                    SecurityHelper.DemandAllClipboardPermission(); 
                }
                menuItem = new EditorMenuItem(); 
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_Paste); 
                menuItem.CommandTarget = textEditor.UiScope;
                menuItem.Command = ApplicationCommands.Paste; 
                this.Items.Add(menuItem);

                return true;
            } 

            ///  
            /// Critical - access CandidateList 
            /// TreatAsSafe - does not do anything for the unmanaged interface. It's just a null check.
            ///  
            [SecurityCritical, SecurityTreatAsSafe]
            private void DelayReleaseCandidateList()
            {
                if (CandidateList != null) 
                {
                    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ReleaseCandidateList), null); 
                } 
            }
 

            /// 
            /// Critical - calls unmanaged code.
            /// TreatAsSafe - just release it. 
            /// 
            [SecurityCritical, SecurityTreatAsSafe] 
            private object ReleaseCandidateList(object o) 
            {
                if (CandidateList != null) 
                {
                    Marshal.ReleaseComObject(CandidateList);
                    _candidateList = null;
 
                    // We released CandidateList and Finalizer does not need to be called.
                    GC.SuppressFinalize(this); 
                } 
                return null;
            } 

            // ReconversionMenuItem uses this to finalzie the candidate string.

            ///  
            /// Critical - calls unmanaged code and return critical ITfCandidateList
            ///  
            internal UnsafeNativeMethods.ITfCandidateList CandidateList 
            {
                 [SecurityCritical] 
                 get
                 {
                     if ( _candidateList == null)
                     { 
                         return null;
                     } 
 
                     return _candidateList.Value;
                 } 
            }

            // The candidate list for Cicero Reconversion.
            // We need to use same ITfCandidateList object for both listing up and finalizing because 
            // the index of the candidate string needs to match.
            private SecurityCriticalDataClass _candidateList; 
        } 

        // Default EditorContextMenu item base class. 
        // Used to distinguish our items from anything an application
        // may have added.
        private class EditorMenuItem : MenuItem
        { 
            internal EditorMenuItem() : base() {}
 
            ///  
            /// Critical - accepts a parameter which may be used to set the userInitiated
            ///             bit on a command, which is used for security purposes later. 
            /// 
            [SecurityCritical]
            internal override void OnClickCore(bool userInitiated)
            { 
                OnClickImpl(userInitiated);
            } 
 
        }
 
        // Reconversion menu item
        // We finalize the candidate in the context menu if one of reconversion menu item is selected.
        private class ReconversionMenuItem : EditorMenuItem
        { 
            internal ReconversionMenuItem(EditorContextMenu menu, int index) : base()
            { 
                _menu = menu; 
                _index = index;
            } 

            // OnClick handler.
            // This is called when the item is selected.
            ///  
            /// Critical - calls unmanaged code.
            ///  
            [SecurityCritical] 
            internal override void OnClickCore(bool userInitiated)
            { 
                Invariant.Assert(_menu.CandidateList != null);

                try
                { 
                    _menu.CandidateList.SetResult(_index, UnsafeNativeMethods.TfCandidateResult.CAND_FINALIZED);
                } 
                catch (COMException) 
                {
                    // When TextBox.MaxLength is smaller than the candidate item, 
                    // TextStore.SetText will reject the insert with E_FAIL and
                    // we end up here.  In this case, we want to silently eat the exception
                    // since it derives from user action and our code.
                    // Bug 107395 is tracking a fundamental fix to the problem, rather 
                    // than this workaround.
                } 
 
                // always passes in false for userInitiated. This won't call command manager.
                base.OnClickCore(false); 
            }

            // The index for this candidate string.
            private int _index; 

            // The context menu of this item. 
            private EditorContextMenu _menu; 
        }
 
        #endregion Private Types
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------- 
//
// File: TextEditorContextMenu.cs
//
// Copyright (C) Microsoft Corporation.  All rights reserved. 
//
// Description: A Component of TextEditor supporting the default ContextMenu. 
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Documents
{
    using MS.Internal;
    using System.Windows; 
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives; 
    using System.Windows.Input; 
    using System.Windows.Media;
    using System.Windows.Threading; 
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using MS.Win32; 
    using System.Windows.Interop;
 
    // A Component of TextEditor supporting the default ContextMenu. 
    internal static class TextEditorContextMenu
    { 
        //-----------------------------------------------------
        //
        //  Class Internal Methods
        // 
        //-----------------------------------------------------
 
        #region Class Internal Methods 

        // Registers all text editing command handlers for a given control type. 
        internal static void _RegisterClassHandlers(Type controlType, bool registerEventListeners)
        {
            if (registerEventListeners)
            { 
                EventManager.RegisterClassHandler(controlType, FrameworkElement.ContextMenuOpeningEvent, new ContextMenuEventHandler(OnContextMenuOpening));
            } 
        } 

        // Callback for FrameworkElement.ContextMenuOpeningEvent. 
        // If the control is using the default ContextMenu, we initialize it
        // here.
        /// 
        /// Critical - calls EditorContextMenu.AddMenuItems, using the UserInitiated 
        ///             bit in the event args, to add (critical) clipboard commands.
        /// TreatAsSafe - the bit is protected by UserIniatedRoutedEvent permission 
        ///  
        [SecurityCritical,SecurityTreatAsSafe]
        internal static void OnContextMenuOpening(object sender, ContextMenuEventArgs e) 
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
            const double KeyboardInvokedSentinel = -1.0; // e.CursorLeft has this value when the menu is invoked with the keyboard.
 
            if (This == null || This.TextView == null)
            { 
                return; 
            }
 
            // Get the mouse position that base on RenderScope which we will set
            // the caret on the RenderScope.
            Point renderScopeMouseDownPoint = Mouse.GetPosition(This.TextView.RenderScope);
            ContextMenu contextMenu = null; 
            bool startPositionCustomElementMenu = false;
 
            if (This.IsReadOnly) 
            {
                // If the TextEditor is ReadOnly, only take action if 
                // 1. The selection is non-empty AND
                // 2. The user clicked inside the selection.
                if ((e.CursorLeft != KeyboardInvokedSentinel && !This.Selection.Contains(renderScopeMouseDownPoint)) ||
                    (e.CursorLeft == KeyboardInvokedSentinel && This.Selection.IsEmpty)) 
                {
                    return; 
                } 
            }
            else if ((This.Selection.IsEmpty || e.TargetElement is TextElement) && 
                     e.TargetElement != null)
            {
                // Targeted element has its own ContextMenu, don't override it.
                contextMenu = (ContextMenu)e.TargetElement.GetValue(FrameworkElement.ContextMenuProperty); 
            }
            else if (e.CursorLeft == KeyboardInvokedSentinel) 
            { 
                // If the menu was invoked from the keyboard, walk up the tree
                // from the selection.Start looking for a custom menu. 
                TextPointer start = GetContentPosition(This.Selection.Start) as TextPointer;
                if (start != null)
                {
                    TextElement element = start.Parent as TextElement; 

                    while (element != null) 
                    { 
                        contextMenu = (ContextMenu)element.GetValue(FrameworkElement.ContextMenuProperty);
                        if (contextMenu != null) 
                        {
                            startPositionCustomElementMenu = true;
                            break;
                        } 
                        element = element.Parent as TextElement;
                    } 
                } 
            }
 
            // Update the selection caret.
            //
            // A negative offset for e.CursorLeft means the user invoked
            // the menu with a hotkey (shift-F10).  Don't mess with the caret 
            // unless the user right-clicked.
            if (e.CursorLeft != KeyboardInvokedSentinel) 
            { 
                if (!TextEditorMouse.IsPointWithinInteractiveArea(This, Mouse.GetPosition(This.UiScope)))
                { 
                    // Don't bring up a context menu if the user clicked on non-editable space.
                    return;
                }
 
                // Don't update the selection caret if we're bringing up a custom UIElement
                // ContextMenu. 
                if (contextMenu == null || !(e.TargetElement is UIElement)) 
                {
                    using (This.Selection.DeclareChangeBlock()) // NB: This raises a PUBLIC EVENT. 
                    {
                        // If we're not over the selection, move the caret.
                        if (!This.Selection.Contains(renderScopeMouseDownPoint))
                        { 
                            TextEditorMouse.SetCaretPositionOnMouseEvent(This, renderScopeMouseDownPoint, MouseButton.Right, 1 /* clickCount */);
                        } 
                    } 
                }
            } 

            if (contextMenu == null)
            {
                // If someone explicitly set it null -- don't mess with it. 
                if (This.UiScope.ReadLocalValue(FrameworkElement.ContextMenuProperty) == null)
                    return; 
 
                // Grab whatever's set to the UiScope's ContextMenu property.
                contextMenu = This.UiScope.ContextMenu; 
            }

            // If we are here, it means that either a custom context menu or our default context menu will be opened.
            // Setting this flag ensures that we dont loose selection highlight while the context menu is open. 
            This.IsContextMenuOpen = true;
 
            // If it's not null, someone's overriding our default -- don't mess with it. 
            if (contextMenu != null && !startPositionCustomElementMenu)
            { 
                // If the user previously raised the ContextMenu with the keyboard,
                // we've left h/v offsets non-zero, and they need to be cleared now
                // for mouse placement to work.
                contextMenu.HorizontalOffset = 0; 
                contextMenu.VerticalOffset = 0;
 
                // Since ContextMenuService doesn't open the menu, it won't fire a ContextMenuClosing event. 
                // We need to listen to the Closed event of the ContextMenu itself so we can clear the
                // IsContextMenuOpen flag.  We also do this for the default menu later in this method. 
                contextMenu.Closed += new RoutedEventHandler(OnContextMenuClosed);
                return;
            }
 
            // Complete the composition before creating the editor context menu.
            This.CompleteComposition(); 
 
            if (contextMenu == null)
            { 
                // It's a default null, so spin up a temporary ContextMenu now.
                contextMenu = new EditorContextMenu();
                ((EditorContextMenu)contextMenu).AddMenuItems(This, e.UserInitiated);
            } 
            contextMenu.Placement = PlacementMode.RelativePoint;
            contextMenu.PlacementTarget = This.UiScope; 
 
            ITextPointer position = null;
            LogicalDirection direction; 

            // Position the ContextMenu.

            SpellingError spellingError = (contextMenu is EditorContextMenu) ? This.GetSpellingErrorAtSelection() : null; 

            if (spellingError != null) 
            { 
                // If we have a matching speller error at the selection
                // start, position relative to the end of the error. 
                position = spellingError.End;
                direction = LogicalDirection.Backward;
            }
            else if (e.CursorLeft == KeyboardInvokedSentinel) 
            {
                // A negative offset for e.CursorLeft means the user invoked 
                // the menu with a hotkey (shift-F10).  Place the menu 
                // relative to Selection.Start.
                position = This.Selection.Start; 
                direction = LogicalDirection.Forward;
            }
            else
            { 
                direction = LogicalDirection.Forward;
            } 
 
            // Calculate coordinats for the ContextMenu.
            // They must be set relative to UIScope - as EditorContextMenu constructor assumes. 
            if (position != null && position.CreatePointer(direction).HasValidLayout)
            {
                double horizontalOffset;
                double verticalOffset; 

                GetClippedPositionOffsets(This, position, direction, out horizontalOffset, out verticalOffset); 
 
                contextMenu.HorizontalOffset = horizontalOffset;
                contextMenu.VerticalOffset = verticalOffset; 
            }
            else
            {
                Point uiScopeMouseDownPoint = Mouse.GetPosition(This.UiScope); 

                contextMenu.HorizontalOffset = uiScopeMouseDownPoint.X; 
                contextMenu.VerticalOffset = uiScopeMouseDownPoint.Y; 
            }
 
            // Since ContextMenuService doesn't open the menu, it won't fire a ContextMenuClosing event.
            // We need to listen to the Closed event of the ContextMenu itself so we can clear the
            // IsContextMenuOpen flag.
            contextMenu.Closed += new RoutedEventHandler(OnContextMenuClosed); 

            // This line raises a public event. 
            contextMenu.IsOpen = true; 

            e.Handled = true; 
        }

        #endregion Class Internal Methods
 
        //------------------------------------------------------
        // 
        //  Private Methods 
        //
        //----------------------------------------------------- 

        #region Private Methods

        // We listen to this event to reset TextEditor._isContextMenuOpen flag. 
        private static void OnContextMenuClosed(object sender, RoutedEventArgs e)
        { 
            UIElement placementTarget = ((ContextMenu)sender).PlacementTarget; 

            if (placementTarget != null) 
            {
                TextEditor This = TextEditor._GetTextEditor(placementTarget);

                if (This != null) 
                {
                    This.IsContextMenuOpen = false; 
                    This.Selection.UpdateCaretAndHighlight(); 
                    ((ContextMenu)sender).Closed -= new RoutedEventHandler(OnContextMenuClosed);
                } 
            }
        }

        ///  
        /// Calculates x, y offsets for a ContextMenu based on an ITextPointer and
        /// the viewports of its containers. 
        ///  
        /// 
        ///     SecurityCritical: This code asserts to get the containing HWND. 
        ///     TreatAsSafe: The HWND is not exposed, only its RECT.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private static void GetClippedPositionOffsets(TextEditor This, ITextPointer position, LogicalDirection direction, 
            out double horizontalOffset, out double verticalOffset)
        { 
            // GetCharacterRect will return the position that base on UiScope. 
            Rect positionRect = position.GetCharacterRect(direction);
 
            // Get the base offsets for our ContextMenu.
            horizontalOffset = positionRect.X;
            verticalOffset = positionRect.Y + positionRect.Height;
 
            // Clip to the child render scope.
            FrameworkElement element = This.TextView.RenderScope as FrameworkElement; 
            if (element != null) 
            {
                GeneralTransform transform = element.TransformToAncestor(This.UiScope); 
                if (transform != null)
                {
                    ClipToElement(element, transform, ref horizontalOffset, ref verticalOffset);
                } 
            }
 
            // Clip to parent visuals. 
            // This is unintuitive -- you might expect parents to have increasingly
            // larger viewports.  But any parent that behaves like a ScrollViewer 
            // will have a smaller view port that we need to clip against.
            for (Visual visual = This.UiScope; visual != null; visual = VisualTreeHelper.GetParent(visual) as Visual)
            {
                element = visual as FrameworkElement; 
                if (element != null)
                { 
                    GeneralTransform transform = visual.TransformToDescendant(This.UiScope); 
                    if (transform != null)
                    { 
                        ClipToElement(element, transform, ref horizontalOffset, ref verticalOffset);
                    }
                }
            } 

            // Clip to the window client rect. 
            PresentationSource source = PresentationSource.CriticalFromVisual(This.UiScope); 
            IWin32Window window = source as IWin32Window;
            if (window != null) 
            {
                IntPtr hwnd = IntPtr.Zero;
                new UIPermission(UIPermissionWindow.AllWindows).Assert(); // BlessedAssert
                try 
                {
                    hwnd = window.Handle; 
                } 
                finally
                { 
                    CodeAccessPermission.RevertAssert();
                }

                NativeMethods.RECT rc = new NativeMethods.RECT(0, 0, 0, 0); 
                SafeNativeMethods.GetClientRect(new HandleRef(null, hwnd), ref rc);
 
                // Convert to mil measure units. 
                Point minPoint = new Point(rc.left, rc.top);
                Point maxPoint = new Point(rc.right, rc.bottom); 

                CompositionTarget compositionTarget = source.CompositionTarget;
                minPoint = compositionTarget.TransformFromDevice.Transform(minPoint);
                maxPoint = compositionTarget.TransformFromDevice.Transform(maxPoint); 

                // Convert to local coordinates. 
                GeneralTransform transform = compositionTarget.RootVisual.TransformToDescendant(This.UiScope); 
                if (transform != null)
                { 
                    transform.TryTransform(minPoint, out minPoint);
                    transform.TryTransform(maxPoint, out maxPoint);

                    // Finally, do the clip. 
                    horizontalOffset = ClipToBounds(minPoint.X, horizontalOffset, maxPoint.X);
                    verticalOffset = ClipToBounds(minPoint.Y, verticalOffset, maxPoint.Y); 
                } 

                // ContextMenu code takes care of clipping to desktop. 
            }
        }

        // Clips a Point to the ActualWidth/Height of a containing FrameworkElement. 
        private static void ClipToElement(FrameworkElement element, GeneralTransform transform,
            ref double horizontalOffset, ref double verticalOffset) 
        { 
            Point minPoint;
            Point maxPoint; 

            Geometry clip = VisualTreeHelper.GetClip(element);

            if (clip != null) 
            {
                Rect bounds = clip.Bounds; 
                minPoint = new Point(bounds.X, bounds.Y); 
                maxPoint = new Point(bounds.X + bounds.Width, bounds.Y + bounds.Height);
            } 
            else
            {
                if (element.ActualWidth == 0 && element.ActualHeight == 0)
                { 
                    // Some elements, noteably Canvas, have a (0, 0) desired size
                    // and should be ignored. 
                    return; 
                }
 
                minPoint = new Point(0, 0);
                maxPoint = new Point(element.ActualWidth, element.ActualHeight);
            }
 
            transform.TryTransform(minPoint, out minPoint);
            transform.TryTransform(maxPoint, out maxPoint); 
 
            // NB: ClipToBounds will handle the case where transform flips a coordinate
            // axis.  In that case, minPoint.X will be > maxPoint.X. 
            horizontalOffset = ClipToBounds(minPoint.X, horizontalOffset, maxPoint.X);
            verticalOffset = ClipToBounds(minPoint.Y, verticalOffset, maxPoint.Y);
        }
 
        // Clips value to the range min to (max - 1).
        private static double ClipToBounds(double min, double value, double max) 
        { 
            // If we're clipping against something with an inverted coordinate axis
            // (the common case is an RTL control in an LTR environment), then 
            // "min" in the parent space is a max for the child.
            if (min > max)
            {
                double temp = min; 
                min = max;
                max = temp; 
            } 

            if (value < min) 
            {
                value = min;
            }
            else if (value >= max) 
            {
                value = max - 1; 
            } 

            return value; 
        }

        // Returns a position ajacent to the supplied position, skipping any
        // intermediate Inlines. 
        // This is useful for sliding inside the context of adjacent Hyperlinks,
        // Spans, etc. 
        private static ITextPointer GetContentPosition(ITextPointer position) 
        {
            while (position.GetAdjacentElement(LogicalDirection.Forward) is Inline) 
            {
                position = position.GetNextContextPosition(LogicalDirection.Forward);
            }
 
            return position;
        } 
 
        #endregion Private methods
 
        //------------------------------------------------------
        //
        //  Private Types
        // 
        //------------------------------------------------------
 
        #region Private Types 

        // Default ContextMenu for TextBox and RichTextBox. 
        private class EditorContextMenu : ContextMenu
        {
            // Initialize the context menu.
            // Creates a new instance. 
            /// 
            /// Critical - accepts a parameter which may be used to set the userInitiated 
            ///             bit on a command, which is used for security purposes later. 
            ///             Although there is a demand here to prevent non userinitiated
            ///             code paths to be blocked this function is not TreatAsSafe because 
            ///             we want to track any new callers to this call
            /// 
            [SecurityCritical]
            internal void AddMenuItems(TextEditor textEditor, bool userInitiated) 
            {
 
                // create a special menu item for paste which only works for user initiated paste 
                // within the confines of partial trust this cannot be done programmatically
                if (userInitiated == false) 
                {
                    SecurityHelper.DemandAllClipboardPermission();
                }
 
                if (!textEditor.IsReadOnly)
                { 
                    if (AddReconversionItems(textEditor)) 
                    {
                        AddSeparator(); 
                    }
                }

                if (AddSpellerItems(textEditor)) 
                {
                    AddSeparator(); 
                } 
                AddClipboardItems(textEditor, userInitiated);
            } 
            // Finalizer release the candidate list if it remains.
            ~EditorContextMenu()
            {
                ReleaseCandidateList(null); 
            }
 
            // Called when the ContextMenu is shutting down. 
            protected override void OnClosed(RoutedEventArgs e)
            { 
                base.OnClosed(e);

                // OnClick for the menu item might be called after the context menu is closed. It depends
                // on how this context menu is created. 
                // We will release CandidateList after all the current events are handled.
                DelayReleaseCandidateList(); 
            } 

            // Helper which appends a separator item. 
            private void AddSeparator()
            {
                this.Items.Add(new Separator());
            } 

            // Appends spell check related items. 
            // Returns false if no items are added. 
            private bool AddSpellerItems(TextEditor textEditor)
            { 
                SpellingError spellingError;
                MenuItem menuItem;

                spellingError = textEditor.GetSpellingErrorAtSelection(); 
                if (spellingError == null)
                    return false; 
 
                bool addedSuggestion = false;
 
                foreach (string suggestion in spellingError.Suggestions)
                {
                    menuItem = new EditorMenuItem();
                    TextBlock text = new TextBlock(); 
                    text.FontWeight = FontWeights.Bold;
                    text.Text = suggestion; 
                    menuItem.Header = text; 
                    menuItem.Command = EditingCommands.CorrectSpellingError;
                    menuItem.CommandParameter = suggestion; 
                    this.Items.Add(menuItem);
                    menuItem.CommandTarget = textEditor.UiScope;

                    addedSuggestion = true; 
                }
 
                if (!addedSuggestion) 
                {
                    menuItem = new EditorMenuItem(); 
                    menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_NoSpellingSuggestions);
                    menuItem.IsEnabled = false;
                    this.Items.Add(menuItem);
                } 

                AddSeparator(); 
 
                menuItem = new EditorMenuItem();
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_IgnoreAll); 
                menuItem.Command = EditingCommands.IgnoreSpellingError;
                this.Items.Add(menuItem);
                menuItem.CommandTarget = textEditor.UiScope;
 
                return true;
            } 
 
            // Add the description to the candidate string of Cicero's
            // reconversion if necessary. 
            private string GetMenuItemDescription(string suggestion)
            {
                if (suggestion.Length == 1)
                { 
                    if (suggestion[0] == 0x0020)
                    { 
                        return SR.Get(SRID.TextBox_ContextMenu_Description_SBCSSpace); 
                    }
                    else if (suggestion[0] == 0x3000) 
                    {
                        return SR.Get(SRID.TextBox_ContextMenu_Description_DBCSSpace);
                    }
 
                }
                return null; 
            } 

            // Appends Cicero reconversion related items. 
            // Returns false if no items are added.
            /// 
            /// Critical - calls unmanaged code.
            /// TreatAsSafe - does not expose the unmanaged interface. retrieve the candidate list and add menu item. 
            /// 
            [SecurityCritical, SecurityTreatAsSafe] 
            private bool AddReconversionItems(TextEditor textEditor) 
            {
                MenuItem menuItem; 
                TextStore textStore = textEditor.TextStore;

                if (textStore == null)
                { 
                    GC.SuppressFinalize(this);
                    return false; 
                } 

                ReleaseCandidateList(null); 
                _candidateList = new  SecurityCriticalDataClass(textStore.GetReconversionCandidateList());
                if (CandidateList == null)
                {
                    GC.SuppressFinalize(this); 
                    return false;
                } 
 
                int count = 0;
                CandidateList.GetCandidateNum(out count); 

                if (count > 0)
                {
                    // Like Winword, we show the first 5 candidates in the context menu. 
                    int i;
                    for (i = 0; i < 5 && i < count; i++) 
                    { 
                        string suggestion;
                        UnsafeNativeMethods.ITfCandidateString candString; 

                        CandidateList.GetCandidate(i, out candString);
                        candString.GetString(out suggestion);
 
                        menuItem = new ReconversionMenuItem(this, i);
                        menuItem.Header = suggestion; 
                        menuItem.InputGestureText = GetMenuItemDescription(suggestion); 
                        this.Items.Add(menuItem);
 
                        Marshal.ReleaseComObject(candString);
                    }
                }
 
                // Like Winword, we show "More" menu to open TIP's candidate list if there are more
                // than 5 candidates. 
                if (count > 5) 
                {
                    menuItem = new EditorMenuItem(); 
                    menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_More);
                    menuItem.Command = ApplicationCommands.CorrectionList;
                    this.Items.Add(menuItem);
                    menuItem.CommandTarget = textEditor.UiScope; 
                }
 
                return (count > 0) ? true : false; 
            }
 
            // Appends clipboard related items.
            // Returns false if no items are added.
            /// 
            /// Critical - accepts a parameter which may be used to set the userInitiated 
            ///             bit on a command
            ///             Although there is a demand here to prevent non userinitiated 
            ///             code paths to be blocked this function is not TreatAsSafe because 
            ///             we want to track any new callers to this call
            ///  
            [SecurityCritical]
            private bool AddClipboardItems(TextEditor textEditor, bool userInitiated)
            {
                MenuItem menuItem; 

                menuItem = new EditorMenuItem(); 
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_Cut); 
                menuItem.CommandTarget = textEditor.UiScope;
                menuItem.Command = ApplicationCommands.Cut; 
                this.Items.Add(menuItem);

                menuItem = new EditorMenuItem();
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_Copy); 
                menuItem.CommandTarget = textEditor.UiScope;
                menuItem.Command = ApplicationCommands.Copy; 
                this.Items.Add(menuItem); 

                // create a special menu item for paste which only works for user initiated paste 
                // within the confines of partial trust this cannot be done programmatically
                if (userInitiated == false)
                {
                    SecurityHelper.DemandAllClipboardPermission(); 
                }
                menuItem = new EditorMenuItem(); 
                menuItem.Header = SR.Get(SRID.TextBox_ContextMenu_Paste); 
                menuItem.CommandTarget = textEditor.UiScope;
                menuItem.Command = ApplicationCommands.Paste; 
                this.Items.Add(menuItem);

                return true;
            } 

            ///  
            /// Critical - access CandidateList 
            /// TreatAsSafe - does not do anything for the unmanaged interface. It's just a null check.
            ///  
            [SecurityCritical, SecurityTreatAsSafe]
            private void DelayReleaseCandidateList()
            {
                if (CandidateList != null) 
                {
                    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ReleaseCandidateList), null); 
                } 
            }
 

            /// 
            /// Critical - calls unmanaged code.
            /// TreatAsSafe - just release it. 
            /// 
            [SecurityCritical, SecurityTreatAsSafe] 
            private object ReleaseCandidateList(object o) 
            {
                if (CandidateList != null) 
                {
                    Marshal.ReleaseComObject(CandidateList);
                    _candidateList = null;
 
                    // We released CandidateList and Finalizer does not need to be called.
                    GC.SuppressFinalize(this); 
                } 
                return null;
            } 

            // ReconversionMenuItem uses this to finalzie the candidate string.

            ///  
            /// Critical - calls unmanaged code and return critical ITfCandidateList
            ///  
            internal UnsafeNativeMethods.ITfCandidateList CandidateList 
            {
                 [SecurityCritical] 
                 get
                 {
                     if ( _candidateList == null)
                     { 
                         return null;
                     } 
 
                     return _candidateList.Value;
                 } 
            }

            // The candidate list for Cicero Reconversion.
            // We need to use same ITfCandidateList object for both listing up and finalizing because 
            // the index of the candidate string needs to match.
            private SecurityCriticalDataClass _candidateList; 
        } 

        // Default EditorContextMenu item base class. 
        // Used to distinguish our items from anything an application
        // may have added.
        private class EditorMenuItem : MenuItem
        { 
            internal EditorMenuItem() : base() {}
 
            ///  
            /// Critical - accepts a parameter which may be used to set the userInitiated
            ///             bit on a command, which is used for security purposes later. 
            /// 
            [SecurityCritical]
            internal override void OnClickCore(bool userInitiated)
            { 
                OnClickImpl(userInitiated);
            } 
 
        }
 
        // Reconversion menu item
        // We finalize the candidate in the context menu if one of reconversion menu item is selected.
        private class ReconversionMenuItem : EditorMenuItem
        { 
            internal ReconversionMenuItem(EditorContextMenu menu, int index) : base()
            { 
                _menu = menu; 
                _index = index;
            } 

            // OnClick handler.
            // This is called when the item is selected.
            ///  
            /// Critical - calls unmanaged code.
            ///  
            [SecurityCritical] 
            internal override void OnClickCore(bool userInitiated)
            { 
                Invariant.Assert(_menu.CandidateList != null);

                try
                { 
                    _menu.CandidateList.SetResult(_index, UnsafeNativeMethods.TfCandidateResult.CAND_FINALIZED);
                } 
                catch (COMException) 
                {
                    // When TextBox.MaxLength is smaller than the candidate item, 
                    // TextStore.SetText will reject the insert with E_FAIL and
                    // we end up here.  In this case, we want to silently eat the exception
                    // since it derives from user action and our code.
                    // Bug 107395 is tracking a fundamental fix to the problem, rather 
                    // than this workaround.
                } 
 
                // always passes in false for userInitiated. This won't call command manager.
                base.OnClickCore(false); 
            }

            // The index for this candidate string.
            private int _index; 

            // The context menu of this item. 
            private EditorContextMenu _menu; 
        }
 
        #endregion Private Types
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK