File: System\Windows\Documents\ImmComposition.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description:
//  This class handles IMM32 IME's composition string and support level 3 input to TextBox and RichTextBox.
//
 
using System.Runtime.InteropServices;
using System.Collections;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Interop;
using MS.Win32;
using MS.Internal.Documents;
using MS.Internal;
using MS.Internal.Interop;
 
// Enable presharp pragma warning suppress directives.
#pragma warning disable 1634, 1691
 
namespace System.Windows.Documents
{
    //------------------------------------------------------
    //
    //  ImmComposition class
    //
    //------------------------------------------------------
 
    //
    // This class handles IMM32 IME's composition string and
    // support level 3 input to TextBox and RichTextBox.
    //
    internal class ImmComposition
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        //
        // Creates a new ImmComposition instance.
        //
        static ImmComposition()
        {
        }
 
        //
        // Creates a new ImmComposition instance.
        //
        internal ImmComposition(HwndSource source)
        {
            UpdateSource(null, source);
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Internal Method
        //
        //------------------------------------------------------
 
        //
        // Create an instance of ImmComposition per source window.
        //
        internal static ImmComposition GetImmComposition(FrameworkElement scope)
        {
            HwndSource source = PresentationSource.CriticalFromVisual(scope) as HwndSource;
 
            ImmComposition immComposition = null;
 
            if (source != null)
            {
                lock (_list)
                {
                    immComposition = (ImmComposition)_list[source];
 
                    if (immComposition == null)
                    {
                        immComposition = new ImmComposition(source);
                        _list[source] = immComposition;
                    }
                }
            }
 
            return immComposition;
        }
 
        //
        // This is called when TextEditor is detached.
        // We need to remove event handlers.
        //
        internal void OnDetach(TextEditor editor)
        {
            if (editor != _editor)
            {
                // ignore calls from editors that aren't the one we're attached to
                return;
            }
 
            if (_editor != null)
            {
                PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged));
                _editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange);
            }
 
            _editor = null;
        }
 
        //
        // Callback from TextEditor when it gets focus.
        //
        internal void OnGotFocus(TextEditor editor)
        {
            if (editor == _editor)
            {
                // If an event listener does a reentrant SetFocus, we can get
                // here without a matching OnLostFocus.  Early out so
                // that we don't attach too many handlers.
                return;
            }
 
            // remove source changed handler for previous editor.
            if (_editor != null)
            {
                PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged));
                _editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange);
            }
 
            // Update the current focus TextEditor, RenderScope and UiScope.
            _editor = editor;
 
            // we need to track the source change.
            PresentationSource.AddSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged));
 
            _editor.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange);
 
            // Update the current composition window position.
            UpdateNearCaretCompositionWindow();
        }
 
        //
        // Callback from TextEditor when it lost focus.
        //
        internal void OnLostFocus()
        {
            if (_editor == null)
                return;
 
            _losingFocus = true;
            try
            {
                // complete the composition string when it lost focus.
                CompleteComposition();
            }
            finally
            {
                _losingFocus = false;
            }
        }
 
        //
        // Callback from TextEditor when the layout is updated
        //
        internal void OnLayoutUpdated()
        {
            if (_updateCompWndPosAtNextLayoutUpdate && IsReadingWindowIme())
            {
                UpdateNearCaretCompositionWindow();
            }
            _updateCompWndPosAtNextLayoutUpdate = false;
        }
 
        //
        // complete the composition string by calling ImmNotifyIME.
        //
        internal void CompleteComposition()
        {
            UnregisterMouseListeners();
 
            if (_source == null)
            {
                // Do nothing if HwndSource is already gone(disposed) or disconnected.
                return;
            }
 
            _compositionModifiedByEventListener = true;
 
            IntPtr hwnd = IntPtr.Zero;
 
            hwnd = ((IWin32Window)_source).Handle;
 
            IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
            if (himc != IntPtr.Zero)
            {
                UnsafeNativeMethods.ImmNotifyIME(new HandleRef(this, himc), NativeMethods.NI_COMPOSITIONSTR, NativeMethods.CPS_COMPLETE, 0);
 
                UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
            }
 
            if (_compositionAdorner != null)
            {
                _compositionAdorner.Uninitialize();
                _compositionAdorner = null;
            }
 
            _startComposition = null;
            _endComposition = null;
        }
 
        // Called as the selection changes.
        // We can't modify document state here in any way.
        internal void OnSelectionChange()
        {
            _compositionModifiedByEventListener = true;
        }
 
        // Callback for TextSelection.Changed event.
        internal void OnSelectionChanged()
        {
            if (!this.IsInKeyboardFocus)
            {
                return;
            }
 
            // Update the current composition window position.
            UpdateNearCaretCompositionWindow();
        }
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        //
        // Returns true if we're in the middle of an ongoing composition.
        //
        internal bool IsComposition
        {
            get
            {
                return _startComposition != null;
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        // SourceChanged callback
        //
        private void OnSourceChanged(object sender, SourceChangedEventArgs e)
        {
            HwndSource newSource = null;
            HwndSource oldSource = null;
 
            newSource = e.NewSource as HwndSource;
            oldSource = e.OldSource as HwndSource;
 
            UpdateSource(oldSource, newSource);
 
            // Clean up the old source changed event handler that was connected with UiScope.
            if (oldSource != null && UiScope != null)
            {
                // Remove the source changed event handler here.
                // Ohterwise, we'll get the leak of the SourceChangedEventHandler.
                // New source changed event handler will be added by getting OnGotFocus on new UiScope.
                PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged));
            }
        }
 
        //
        // Update _list and _source with new source.
        //
        private void UpdateSource(HwndSource oldSource, HwndSource newSource)
        {
            // If this object is moving directly from one source to another
            // without passing through null, problems could arise.  This object
            // becomes the ImmComposition for the new source (_list[newSource] = this),
            // but the new source may already have an ImmComposition.  This can
            // lead to confusion when handling composition messages or when
            // shutting down the source.
            // Fortunately this can't happen - the visual tree doesn't support re-parenting
            // in one step (you have to detach child, then reattach it to a new parent).
            // If that should change, this method will need some rethinking.
            Debug.Assert(oldSource == null || newSource == null,
                        "ImmComposition doesn't support changing source directly");
 
            // Detach the TextEditor.  This avoids leaks and crashes (DevDiv2 1162020, 1201925).
            OnDetach(_editor);
 
            if (_source != null)
            {
                Debug.Assert((oldSource == null) || (oldSource == _source));
 
                _source.RemoveHook(new HwndSourceHook(ImmCompositionFilterMessage));
 
                _source.Disposed -= new EventHandler(OnHwndDisposed);
 
                // Remove HwndSource from the list.
                _list.Remove(_source);
                _source = null;
            }
 
            if (newSource != null)
            {
                _list[newSource] = this;
                _source = newSource;
                _source.AddHook(new HwndSourceHook(ImmCompositionFilterMessage));
                _source.Disposed += new EventHandler(OnHwndDisposed);
            }
 
            // _source should always be a newSource.
            Debug.Assert(newSource == _source);
        }
 
        //
        // Window Hook to track WM_IME_ messages.
        //
        private IntPtr ImmCompositionFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            IntPtr lret = IntPtr.Zero;
            switch ((WindowMessage)msg)
            {
                case WindowMessage.WM_IME_CHAR:
                    OnWmImeChar(wParam, ref handled);
                    break;
 
                case WindowMessage.WM_IME_NOTIFY:
                    // we don't have to update handled.
                    OnWmImeNotify(hwnd, wParam);
                    break;
 
                case WindowMessage.WM_IME_STARTCOMPOSITION:
                case WindowMessage.WM_IME_ENDCOMPOSITION:
                    if (IsInKeyboardFocus && !IsReadOnly)
                    {
                        // Do Level 2 for legacy Chinese IMM32 IMEs.
                        if (!IsReadingWindowIme())
                        {
                            handled = true;
                        }
                    }
 
                    break;
 
                case WindowMessage.WM_IME_COMPOSITION:
                    OnWmImeComposition(hwnd, lParam, ref handled);
                    break;
 
                case WindowMessage.WM_IME_REQUEST:
                    lret = OnWmImeRequest(wParam, lParam, ref handled);
                    break;
 
                case WindowMessage.WM_INPUTLANGCHANGE:
                    // Set the composition window position (reading window position) for
                    // legacy Chinese IMM32 IMEs.
                    if (IsReadingWindowIme())
                    {
                        UpdateNearCaretCompositionWindow();
                    }
                    break;
            }
 
            return lret;
        }
 
        //
        // WM_IME_COMPOSITION handler
        //
        private void OnWmImeComposition(IntPtr hwnd, IntPtr lParam, ref bool handled)
        {
            IntPtr himc;
            int size;
            int cursorPos = 0;
            int deltaStart = 0;
            char[] result = null;
            char[] composition = null;
            byte[] attributes = null;
 
            if (IsReadingWindowIme())
            {
                // Don't handle WM_IME_COMPOSITION for Chinese Legacy IMEs.
                return;
            }
 
            if (!IsInKeyboardFocus && !_losingFocus)
            {
                // Don't handle WM_IME_COMPOSITION if we don't have a focus.
                return;
            }
 
            if (IsReadOnly)
            {
                // Don't handle WM_IME_COMPOSITION if it is readonly.
                return;
            }
 
 
            himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
            if (himc == IntPtr.Zero)
            {
                // we don't do anything with NULL-HIMC.
                return;
            }
 
            //
            // Get the result string from hIMC.
            //
            if (((int)lParam & NativeMethods.GCS_RESULTSTR) != 0)
            {
                size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_RESULTSTR, IntPtr.Zero, 0);
                if (size > 0)
                {
                    result = new char[size / sizeof(short)];
 
                    // 3rd param is out and contains actual result of this call.
                    // suppress Presharp 6031.
#pragma warning suppress 6031
                    UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_RESULTSTR, result, size);
                }
            }
 
            //
            // Get the composition string from hIMC.
            //
            if (((int)lParam & NativeMethods.GCS_COMPSTR) != 0)
            {
                size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPSTR, IntPtr.Zero, 0);
                if (size > 0)
                {
                    composition = new char[size / sizeof(short)];
                    // 3rd param is out and contains actual result of this call.
                    // suppress Presharp 6031.
#pragma warning suppress 6031
                    UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPSTR, composition, size);
 
                    //
                    // Get the caret position from hIMC.
                    //
                    if (((int)lParam & NativeMethods.GCS_CURSORPOS) != 0)
                    {
                        cursorPos = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_CURSORPOS, IntPtr.Zero, 0);
                    }
 
                    //
                    // Get the delta start position from hIMC.
                    //
                    if (((int)lParam & NativeMethods.GCS_DELTASTART) != 0)
                    {
                        deltaStart = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_DELTASTART, IntPtr.Zero, 0);
                    }
 
                    //
                    // Get the attribute information from hIMC.
                    //
                    if (((int)lParam & NativeMethods.GCS_COMPATTR) != 0)
                    {
                        size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPATTR, IntPtr.Zero, 0);
                        if (size > 0)
                        {
                            attributes = new byte[size / sizeof(byte)];
                            // 3rd param is out and contains actual result of this call.
                            // suppress Presharp 6031.
#pragma warning suppress 6031
                            UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPATTR, attributes, size);
                        }
                    }
                }
            }
 
            UpdateCompositionString(result, composition, cursorPos, deltaStart, attributes);
 
            UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
            handled = true;
        }
 
        //
        // WM_IME_CHAR handler
        //
        private void OnWmImeChar(IntPtr wParam, ref bool handled)
        {
            if (!IsInKeyboardFocus && !_losingFocus)
            {
                // Don't handle WM_IME_CAHR if we don't have a focus.
                return;
            }
 
            if (IsReadOnly)
            {
                // Don't handle WM_IME_CAHR if it is readonly.
                return;
            }
 
            if (_handlingImeMessage)
            {
                // We will be called reentrantly while completing compositions
                // in response to application listeners.  In that case, don't
                // propegate events to listeners.
                return;
            }
 
            _handlingImeMessage = true;
            try
            {
                int resultLength;
                string compositionString = BuildCompositionString(null, new char[] { (char)wParam }, out resultLength);
 
                if (compositionString == null)
                {
                    CompleteComposition();
                }
                else
                {
                    FrameworkTextComposition composition = TextStore.CreateComposition(_editor, this);
                    _compositionModifiedByEventListener = false;
                    _caretOffset = 1;
 
                    //
                    // Raise TextInputStart.
                    //
                    bool handledbyApp = RaiseTextInputStartEvent(composition, resultLength, compositionString);
 
                    if (handledbyApp)
                    {
                        CompleteComposition();
                    }
                    else
                    {
                        //
                        // Raise TextInput.
                        //
                        bool handledByApp = RaiseTextInputEvent(composition, compositionString);
 
                        if (handledByApp)
                        {
                            CompleteComposition();
                            goto Exit;
                        }
                    }
                }
            }
            finally
            {
                _handlingImeMessage = false;
            }
 
            // the string has been finalized. Update the reading window position for
            // legacy Chinese IMEs.
            if (IsReadingWindowIme())
            {
                UpdateNearCaretCompositionWindow();
            }
 
        Exit:
            handled = true;
        }
 
        //
        // WM_IME_NOTIFY handler
        //
        private void OnWmImeNotify(IntPtr hwnd, IntPtr wParam)
        {
            IntPtr himc;
 
            // we don't have to do anything if _editor is null.
            if (!IsInKeyboardFocus)
            {
                return;
            }
 
            if ((int)wParam == NativeMethods.IMN_OPENCANDIDATE)
            {
                himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
                if (himc != IntPtr.Zero)
                {
                    NativeMethods.CANDIDATEFORM candform = new NativeMethods.CANDIDATEFORM();
                    //
                    // At IMN_OPENCANDIDATE, we need to set the candidate window location to hIMC.
                    //
                    if (IsReadingWindowIme())
                    {
                        // Level 2 for Chinese legacy IMEs.
                        // We have already set the composition form. The candidate window will follow it.
                        candform.dwIndex = 0;
                        candform.dwStyle = NativeMethods.CFS_DEFAULT;
                        candform.rcArea.left = 0;
                        candform.rcArea.right = 0;
                        candform.rcArea.top = 0;
                        candform.rcArea.bottom = 0;
                        candform.ptCurrentPos = default;
                    }
                    else
                    {
                        ITextView view;
                        ITextPointer startNavigator;
                        ITextPointer endNavigator;
                        ITextPointer caretNavigator;
                        GeneralTransform transform;
                        Point milPointTopLeft;
                        Point milPointBottomRight;
                        Point milPointCaret;
                        Rect rectStart;
                        Rect rectEnd;
                        Rect rectCaret;
                        CompositionTarget compositionTarget;
 
                        compositionTarget = _source.CompositionTarget;
 
                        if (_startComposition != null)
                        {
                            startNavigator = _startComposition.CreatePointer();
                        }
                        else
                        {
                            startNavigator = _editor.Selection.Start.CreatePointer();
                        }
 
                        if (_endComposition != null)
                        {
                            endNavigator = _endComposition.CreatePointer();
                        }
                        else
                        {
                            endNavigator = _editor.Selection.End.CreatePointer();
                        }
 
                        if (_startComposition != null)
                        {
                            caretNavigator = _caretOffset > 0 ? _startComposition.CreatePointer(_caretOffset, LogicalDirection.Forward) : _endComposition;
                        }
                        else
                        {
                            caretNavigator = _editor.Selection.End.CreatePointer();
                        }
 
                        ITextPointer startPosition = startNavigator.CreatePointer(LogicalDirection.Forward);
                        ITextPointer endPosition = endNavigator.CreatePointer(LogicalDirection.Backward);
                        ITextPointer caretPosition = caretNavigator.CreatePointer(LogicalDirection.Forward);
 
                        // We need to update the layout before getting rect. It could be dirty.
                        if (!startPosition.ValidateLayout() ||
                            !endPosition.ValidateLayout() ||
                            !caretPosition.ValidateLayout())
                        {
                            return;
                        }
 
                        view = TextEditor.GetTextView(RenderScope);
 
                        rectStart = view.GetRectangleFromTextPosition(startPosition);
                        rectEnd = view.GetRectangleFromTextPosition(endPosition);
                        rectCaret = view.GetRectangleFromTextPosition(caretPosition);
 
                        // Take the "extended" union of the first and last char's bounding box.
                        milPointTopLeft = new Point(Math.Min(rectStart.Left, rectEnd.Left), Math.Min(rectStart.Top, rectEnd.Top));
                        milPointBottomRight = new Point(Math.Max(rectStart.Left, rectEnd.Left), Math.Max(rectStart.Bottom, rectEnd.Bottom));
                        milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom);
 
                        // Transform to root visual coordinates.
                        transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual);
                        transform.TryTransform(milPointTopLeft, out milPointTopLeft);
                        transform.TryTransform(milPointBottomRight, out milPointBottomRight);
                        transform.TryTransform(milPointCaret, out milPointCaret);
 
                        // Transform to device units.
                        milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft);
                        milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight);
                        milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret);
 
                        // Build CANDIDATEFORM. CANDIDATEFORM is window coodidate.
                        candform.dwIndex = 0;
                        candform.dwStyle = NativeMethods.CFS_EXCLUDE;
                        candform.rcArea.left = ConvertToInt32(milPointTopLeft.X);
                        candform.rcArea.right = ConvertToInt32(milPointBottomRight.X);
                        candform.rcArea.top = ConvertToInt32(milPointTopLeft.Y);
                        candform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y);
                        candform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y));
                    }
                    // Call IMM32 to set new candidate position to hIMC.
                    // ImmSetCandidateWindow fails when
                    //  - candform.dwIndex is invalid (over 4).
                    //  - himc belongs to other threads.
                    //  - fail to lock IMC.
                    // Those cases are ignorable for us.
                    // In addition, it does not set win32 last error and we have no clue to handle error.
#pragma warning suppress 6031
                    UnsafeNativeMethods.ImmSetCandidateWindow(new HandleRef(this, himc), ref candform);
                    UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
                }
 
                // We want to pass this message to DefWindowProc.
                // We don't update "handled".
            }
        }
 
        //
        // Use Level 2 for Chinese IME
        //
        private void UpdateNearCaretCompositionWindow()
        {
            ITextView view;
            Rect rectUi;
            GeneralTransform transform;
            Point milPointTopLeft;
            Point milPointBottomRight;
            Point milPointCaret;
            Rect rectCaret;
            CompositionTarget compositionTarget;
            IntPtr hwnd;
 
            if (!IsInKeyboardFocus)
            {
                return;
            }
 
            if (_source == null)
            {
                return;
            }
 
            hwnd = ((IWin32Window)_source).Handle;
 
            rectUi = UiScope.VisualContentBounds;
            view = _editor.TextView;
 
            //
            //   We need to update Layout to calculate the correct position of the composition window.
            //   We can wait until LayoutChanged but we need to know if Layout is being updated or not.
            //
            // RenderScope.UpdateLayout();
 
            // During incremental layout update, the region of the view covered by
            // the selection may not be ready yet.
            if (!_editor.Selection.End.HasValidLayout)
            {
                _updateCompWndPosAtNextLayoutUpdate = true;
                return;
            }
 
            compositionTarget = _source.CompositionTarget;
 
            // HwndSource.CompositionTarget may return null if the target hwnd is being destroyed and disposed.
            if (compositionTarget == null || compositionTarget.RootVisual == null)
            {
                return;
            }
 
            // If the mouse click happens before rendering, the seleciton move notification is generated.
            // However the visual tree is not completely connected yet. We need to check it.
            if (!compositionTarget.RootVisual.IsAncestorOf(RenderScope))
            {
                return;
            }
 
            IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
            if (himc != IntPtr.Zero)
            {
                rectCaret = view.GetRectangleFromTextPosition(_editor.Selection.End.CreatePointer(LogicalDirection.Backward));
 
                // Take the points of the renderScope.
                milPointTopLeft = new Point(rectUi.Left, rectUi.Top);
                milPointBottomRight = new Point(rectUi.Right, rectUi.Bottom);
 
                // Take the "extended" union of the first and last char's bounding box.
                // milPointCaret = new Point(rectCaret.Left, rectCaret.Top);
                milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom);
 
                // Transform to root visual coordinates.
                transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual);
                transform.TryTransform(milPointTopLeft, out milPointTopLeft);
                transform.TryTransform(milPointBottomRight, out milPointBottomRight);
                transform.TryTransform(milPointCaret, out milPointCaret);
 
                // Transform to device units.
                milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft);
                milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight);
                milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret);
 
                // Build COMPOSITIONFORM. COMPOSITIONFORM is window coodidate.
                NativeMethods.COMPOSITIONFORM compform = new NativeMethods.COMPOSITIONFORM();
                compform.dwStyle = NativeMethods.CFS_RECT;
                compform.rcArea.left = ConvertToInt32(milPointTopLeft.X);
                compform.rcArea.right = ConvertToInt32(milPointBottomRight.X);
                compform.rcArea.top = ConvertToInt32(milPointTopLeft.Y);
                compform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y);
                compform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y));
 
                // Call IMM32 to set new candidate position to hIMC.
                // ImmSetCompositionWindow fails when
                //  - himc belongs to other threads.
                //  - fail to lock IMC.
                // Those cases are ignorable for us.
                // In addition, it does not set win32 last error and we have no clue to handle error.
                UnsafeNativeMethods.ImmSetCompositionWindow(new HandleRef(this, himc), ref compform);
                UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
            }
        }
 
        //
        // Hwnd disposed callback.
        //
        private void OnHwndDisposed(object sender, EventArgs args)
        {
            UpdateSource(_source, null);
        }
 
        //
        // update the composition string on the scope
        //
        private void UpdateCompositionString(char[] resultChars, char[] compositionChars, int caretOffset, int deltaStart, byte[] attributes)
        {
            if (_handlingImeMessage)
            {
                // We will be called reentrantly while completing compositions
                // in response to application listeners.  In that case, don't
                // propegate events to listeners.
                return;
            }
 
            _handlingImeMessage = true;
            try
            {
                //
                // Remove any existing composition adorner for display attribute.
                //
                if (_compositionAdorner != null)
                {
                    _compositionAdorner.Uninitialize();
                    _compositionAdorner = null;
                }
 
                //
                // Build up an array of resultChars + compositionChars -- the complete span of changing text.
                //
                int resultLength;
                string compositionString = BuildCompositionString(resultChars, compositionChars, out resultLength);
 
                if (compositionString == null)
                {
                    CompleteComposition();
                    return;
                }
 
                //
                // Remember where the IME placed the caret.
                //
                RecordCaretOffset(caretOffset, attributes, compositionString.Length);
 
                FrameworkTextComposition composition = TextStore.CreateComposition(_editor, this);
                _compositionModifiedByEventListener = false;
 
                if (_startComposition == null)
                {
                    Invariant.Assert(_endComposition == null);
 
                    //
                    // Raise TextInputStart.
                    //
                    bool handledbyApp = RaiseTextInputStartEvent(composition, resultLength, compositionString);
 
                    if (handledbyApp)
                    {
                        CompleteComposition();
                        return;
                    }
                }
                else if (compositionChars != null)
                {
                    //
                    // Raise TextInputUpdate.
                    //
                    bool handledByApp = RaiseTextInputUpdateEvent(composition, resultLength, compositionString);
 
                    if (handledByApp)
                    {
                        CompleteComposition();
                        return;
                    }
                }
 
                if (compositionChars == null)
                {
                    //
                    // Raise TextInput.
                    //
                    bool handledByApp = RaiseTextInputEvent(composition, compositionString);
 
                    if (handledByApp)
                    {
                        CompleteComposition();
                        return;
                    }
                }
 
                if (_startComposition != null)
                {
                    SetCompositionAdorner(attributes);
                }
            }
            finally
            {
                _handlingImeMessage = false;
            }
        }
 
 
        /// <summary>
        /// Attempts to build a string containing zero or more result chars followed by zero or more composition chars.
        /// </summary>
        /// <param name="resultChars"></param>
        /// <param name="compositionChars"></param>
        /// <param name="resultLength"></param>
        /// <returns></returns>
        private string BuildCompositionString(char[] resultChars, char[] compositionChars, out int resultLength)
        {
            int compositionLength = compositionChars == null ? 0 : compositionChars.Length;
            resultLength = resultChars == null ? 0 : resultChars.Length;
            char[] compositionText;
 
            if (resultChars == null)
            {
                compositionText = compositionChars;
            }
            else if (compositionChars == null)
            {
                compositionText = resultChars;
            }
            else
            {
                compositionText = new char[resultLength + compositionLength];
                Array.Copy(resultChars, 0, compositionText, 0, resultLength);
                Array.Copy(compositionChars, 0, compositionText, resultLength, compositionLength);
            }
 
            string compositionString = new string(compositionText);
 
            int originalLength = (compositionText == null) ? 0 : compositionText.Length;
            return (compositionString.Length == originalLength) ? compositionString : null;
        }
 
        // Caches the IME specified caret offset.
        // Value is the offset in unicode code points from the composition start.
        private void RecordCaretOffset(int caretOffset, byte[] attributes, int compositionLength)
        {
            // Use the suggested value if it is on ATTR_INPUT, otherwise set the caret at the end of
            // composition string. So it always stays where the new char is inserted.
            if ((attributes != null) &&
                // If the next char of the cursorPos is INPUTATTR.
                (((caretOffset >= 0) &&
                  (caretOffset < attributes.Length) &&
                  (attributes[caretOffset] == NativeMethods.ATTR_INPUT)) ||
                // If the prev char os the cursorPos is INPUTATTR.
                 ((caretOffset > 0) &&
                  ((caretOffset - 1) < attributes.Length) &&
                  (attributes[caretOffset - 1] == NativeMethods.ATTR_INPUT))))
            {
                _caretOffset = caretOffset;
            }
            else
            {
                _caretOffset = -1;
            }
        }
 
        // Raises a public TextInputStart event.
        // Returns true if a listener handles the event or modifies document state.
        private bool RaiseTextInputStartEvent(FrameworkTextComposition composition, int resultLength, string compositionString)
        {
            composition.Stage = TextCompositionStage.None;
            composition.SetCompositionPositions(_editor.Selection.Start, _editor.Selection.End, compositionString);
 
            // PUBLIC event:
            bool handled = TextCompositionManager.StartComposition(composition);
 
            if (handled ||
                composition.PendingComplete ||
                _compositionModifiedByEventListener)
            {
                return true;
            }
 
            // UpdateCompositionText raises a PUBLIC EVENT....
            UpdateCompositionText(composition, resultLength, true /* includeResultText */, out _startComposition, out _endComposition);
 
            if (_compositionModifiedByEventListener)
            {
                return true;
            }
 
            RegisterMouseListeners();
 
            return false;
        }
 
        // Raises a public TextInputUpdate event.
        // Returns true if a listener handles the event or modifies document state.
        private bool RaiseTextInputUpdateEvent(FrameworkTextComposition composition, int resultLength, string compositionString)
        {
            composition.Stage = TextCompositionStage.Started;
            composition.SetCompositionPositions(_startComposition, _endComposition, compositionString);
 
            // PUBLIC event:
            bool handled = TextCompositionManager.UpdateComposition(composition);
 
            if (handled ||
                composition.PendingComplete ||
                _compositionModifiedByEventListener)
            {
                return true;
            }
 
            // UpdateCompositionText raises a PUBLIC EVENT....
            UpdateCompositionText(composition, resultLength, false /* includeResultText */, out _startComposition, out _endComposition);
 
            if (_compositionModifiedByEventListener)
            {
                return true;
            }
 
            return false;
        }
 
        // Raises a public TextInput event.
        // Returns true if a listener handles the event or modifies document state.
        private bool RaiseTextInputEvent(FrameworkTextComposition composition, string compositionString)
        {
            composition.Stage = TextCompositionStage.Started;
            composition.SetResultPositions(_startComposition, _endComposition, compositionString);
 
            _startComposition = null;
            _endComposition = null;
 
            UnregisterMouseListeners();
 
            _handledByEditorListener = false;
 
            // PUBLIC event:
            TextCompositionManager.CompleteComposition(composition);
 
            _compositionUndoUnit = null;
 
            return (!_handledByEditorListener || composition.PendingComplete || _compositionModifiedByEventListener);
        }
 
        // Inserts composition text into the document.
        // Raises public text, selection changed events.
        // Called by default editor TextInputEvent handler.
        internal void UpdateCompositionText(FrameworkTextComposition composition)
        {
            ITextPointer start;
            ITextPointer end;
 
            UpdateCompositionText(composition, 0, true /* includeResultText */
                                                                              , out start, out end);
        }
 
        // Inserts composition text into the document.
        // Raises public text, selection changed events.
        // Returns the position of the inserted text.  If includeResultText is
        // true, start/end will cover all the inserted text.  Otherwise, text
        // from offset 0 to resultLength is omitted from start/end.
        internal void UpdateCompositionText(FrameworkTextComposition composition, int resultLength, bool includeResultText, out ITextPointer start, out ITextPointer end)
        {
            start = null;
            end = null;
 
            if (_compositionModifiedByEventListener)
            {
                // If the app has modified the document since this event was raised
                // (by hooking a TextInput event), then we don't know what to do,
                // so do nothing.
                return;
            }
 
            _handledByEditorListener = true;
 
            bool isTextFiltered = false;
 
            UndoCloseAction undoCloseAction = UndoCloseAction.Rollback;
            OpenCompositionUndoUnit();
 
            try
            {
                _editor.Selection.BeginChange();
                try
                {
                    // this code duplicated in TextStore.UpdateCompositionText
 
                    ITextRange range;
                    string text;
 
                    // DevDiv.1106868 We need to set ignoreTextUnitBoundaries to true in the TextRange constructor in case we are dealing with
                    // a supplementary character (a pair of surrogate characters that together form a single character), otherwise TextRange
                    // will break us out of the compound sequence
                    if (composition._ResultStart != null)
                    {
                        //
                        // If we're here it means composition is being finalized
                        //
                        range = new TextRange(composition._ResultStart, composition._ResultEnd, true /* ignoreTextUnitBoundaries */);
                        text = this._editor._FilterText(composition.Text, range);
                        isTextFiltered = (text != composition.Text);
                        if (isTextFiltered)
                        {
                            // If text was filtered we need to update the caret to point
                            // past the updated text (_caretOffset == text.Length), but we should
                            // also keep in mind that IMM's are free to have put caret in
                            // any position so we're chosing minimum of both.
                            _caretOffset = Math.Min(_caretOffset, text.Length);
                        }
                    }
                    else
                    {
                        range = new TextRange(composition._CompositionStart, composition._CompositionEnd, true /* ignoreTextUnitBoundaries */);
                        text = composition.CompositionText;
                    }
 
                    _editor.SetText(range, text, InputLanguageManager.Current.CurrentInputLanguage);
 
                    if (includeResultText)
                    {
                        start = range.Start;
                    }
                    else
                    {
                        start = range.Start.CreatePointer(resultLength, LogicalDirection.Forward);
                    }
                    end = range.End;
 
                    ITextPointer caretPosition = _caretOffset >= 0 ? start.CreatePointer(_caretOffset, LogicalDirection.Forward) : end;
                    _editor.Selection.Select(caretPosition, caretPosition);
                }
                finally
                {
                    // We're about to raise the public event.
                    // Set a flag so we can detect app changes.
                    _compositionModifiedByEventListener = false;
 
                    _editor.Selection.EndChange();
                    if (isTextFiltered)
                    {
                        _compositionModifiedByEventListener = true;
                    }
                }
 
                undoCloseAction = UndoCloseAction.Commit;
            }
            finally
            {
                CloseCompositionUndoUnit(undoCloseAction, end);
            }
        }
 
        // Decorates the composition with IME specified underlining.
        private void SetCompositionAdorner(byte[] attributes)
        {
            if (attributes != null)
            {
                int startOffset = 0;
                for (int i = 0; i < attributes.Length; i++)
                {
                    if ((i + 1) < attributes.Length)
                    {
                        if (attributes[i] == attributes[i + 1])
                        {
                            continue;
                        }
                    }
                    ITextPointer startAttribute = _startComposition.CreatePointer(startOffset, LogicalDirection.Backward);
                    ITextPointer endAttribute = _startComposition.CreatePointer(i + 1, LogicalDirection.Forward);
 
                    if (_compositionAdorner == null)
                    {
                        _compositionAdorner = new CompositionAdorner(_editor.TextView);
                        _compositionAdorner.Initialize(_editor.TextView);
                    }
 
                    //
                    // Need a design of the composition rendering.
                    //
 
                    UnsafeNativeMethods.TF_DISPLAYATTRIBUTE displayAttribute = new UnsafeNativeMethods.TF_DISPLAYATTRIBUTE();
 
                    displayAttribute.crLine.type = UnsafeNativeMethods.TF_DA_COLORTYPE.TF_CT_COLORREF;
                    displayAttribute.crLine.indexOrColorRef = 0;
                    displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_NONE;
                    displayAttribute.fBoldLine = false;
 
                    switch (attributes[i])
                    {
                        case NativeMethods.ATTR_INPUT:
                            displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_DOT;
                            break;
 
                        case NativeMethods.ATTR_TARGET_CONVERTED:
                            displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID;
                            displayAttribute.fBoldLine = true;
                            break;
 
                        case NativeMethods.ATTR_CONVERTED:
                            displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_DOT;
                            break;
 
                        case NativeMethods.ATTR_TARGET_NOTCONVERTED:
                            displayAttribute.lsStyle = UnsafeNativeMethods.TF_DA_LINESTYLE.TF_LS_SOLID;
                            break;
 
                        case NativeMethods.ATTR_INPUT_ERROR:
                            break;
 
                        case NativeMethods.ATTR_FIXEDCONVERTED:
                            break;
                    }
 
#if UNUSED_IME_HIGHLIGHT_LAYER
                                // Demand create the highlight layer.
                                if (_highlightLayer == null)
                                {
                                    _highlightLayer = new DisplayAttributeHighlightLayer();
                                }
 
                                // Need to pass the foreground and background color of the composition
                                _highlightLayer.Add(startClause, endClause, /*TextDecorationCollection:*/null);
#endif
 
                    TextServicesDisplayAttribute textServiceDisplayAttribute = new TextServicesDisplayAttribute(displayAttribute);
 
                    // Add the attribute range into CompositionAdorner.
                    _compositionAdorner.AddAttributeRange(startAttribute, endAttribute, textServiceDisplayAttribute);
                    startOffset = i + 1;
                }
 
#if UNUSED_IME_HIGHLIGHT_LAYER
                            if (_highlightLayer != null)
                            {
                                _editor.TextContainer.Highlights.AddLayer(_highlightLayer);
                            }
#endif
 
                if (_compositionAdorner != null)
                {
                    // Update the layout to get the acurated rectangle from calling GetRectangleFromTextPosition
                    _editor.TextView.RenderScope.UpdateLayout();
 
                    // Invalidate the composition adorner to apply the composition attribute ranges.
                    _compositionAdorner.InvalidateAdorner();
                }
            }
        }
 
        // Start listening mouse event for MSIME mouse operation.
        private void RegisterMouseListeners()
        {
            UiScope.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnMouseButtonEvent);
            UiScope.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(OnMouseButtonEvent);
            UiScope.PreviewMouseRightButtonDown += new MouseButtonEventHandler(OnMouseButtonEvent);
            UiScope.PreviewMouseRightButtonUp += new MouseButtonEventHandler(OnMouseButtonEvent);
            UiScope.PreviewMouseMove += new MouseEventHandler(OnMouseEvent);
        }
 
        // Stop listening mouse event for MSIME mouse operation.
        private void UnregisterMouseListeners()
        {
            if (this.UiScope != null)
            {
                UiScope.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseButtonEvent);
                UiScope.PreviewMouseLeftButtonUp -= new MouseButtonEventHandler(OnMouseButtonEvent);
                UiScope.PreviewMouseRightButtonDown -= new MouseButtonEventHandler(OnMouseButtonEvent);
                UiScope.PreviewMouseRightButtonUp -= new MouseButtonEventHandler(OnMouseButtonEvent);
                UiScope.PreviewMouseMove -= new MouseEventHandler(OnMouseEvent);
            }
        }
 
        //
        // WM_IME_REQUEST handler
        //
        private IntPtr OnWmImeRequest(IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            IntPtr lret = IntPtr.Zero;
 
            switch ((int)wParam)
            {
                case NativeMethods.IMR_RECONVERTSTRING:
                    lret = OnWmImeRequest_ReconvertString(lParam, ref handled, false);
                    break;
 
                case NativeMethods.IMR_CONFIRMRECONVERTSTRING:
                    lret = OnWmImeRequest_ConfirmReconvertString(lParam, ref handled);
                    break;
 
                case NativeMethods.IMR_QUERYCHARPOSITION:
                    break;
 
                case NativeMethods.IMR_DOCUMENTFEED:
                    lret = OnWmImeRequest_ReconvertString(lParam, ref handled, true);
                    break;
            }
 
            return lret;
        }
 
        //
        // WM_IME_REQUEST/IMR_RECONVERTSTRING handler
        //
        private IntPtr OnWmImeRequest_ReconvertString(IntPtr lParam, ref bool handled, bool fDocFeed)
        {
            if (!fDocFeed)
            {
                _isReconvReady = false;
            }
 
            if (!IsInKeyboardFocus)
            {
                return IntPtr.Zero;
            }
 
            ITextRange range;
 
            //
            // If there is the composition string, we use it. Otherwise we use the current selection.
            //
            if (fDocFeed && (_startComposition != null) && (_endComposition != null))
            {
                range = new TextRange(_startComposition, _endComposition);
            }
            else
            {
                range = _editor.Selection;
            }
 
            string target = range.Text;
 
            int requestSize = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)) + (target.Length * sizeof(short)) + ((_maxSrounding + 1) * sizeof(short) * 2);
            IntPtr lret = new IntPtr(requestSize);
 
            if (lParam != IntPtr.Zero)
            {
                int offsetStart;
                string surrounding = GetSurroundingText(range, out offsetStart);
 
                // Create RECONVERTSTRING structure from lParam.
                NativeMethods.RECONVERTSTRING reconv = Marshal.PtrToStructure<NativeMethods.RECONVERTSTRING>(lParam);
 
                reconv.dwSize = requestSize;
                reconv.dwVersion = 0;                                                         // must be 0
                reconv.dwStrLen = surrounding.Length;                                         // in char count
                reconv.dwStrOffset = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING));   // in byte count
                reconv.dwCompStrLen = target.Length;                                          // in char count
                reconv.dwCompStrOffset = offsetStart * sizeof(short);                         // in byte count
                reconv.dwTargetStrLen = target.Length;                                        // in char count
                reconv.dwTargetStrOffset = offsetStart * sizeof(short);                       // in byte count
 
                if (!fDocFeed)
                {
                    //
                    // If this is IMR_RECONVERTSTRING, we cache it. So we can refer it later when we get
                    // IMR_CONFIRMRECONVERTSTRING message.
                    //
                    _reconv = reconv;
                    _isReconvReady = true;
                }
 
                // Copy the strucuture back to lParam.
                Marshal.StructureToPtr(reconv, lParam, true);
 
                StoreSurroundingText(lParam, surrounding);
            }
 
            handled = true;
            return lret;
        }
 
        private unsafe static void StoreSurroundingText(IntPtr reconv, string surrounding)
        {
            // Copy the string to the pointer right after the structure.
            byte* p = (byte*)reconv.ToPointer();
            p += Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING));
            Marshal.Copy(surrounding.ToCharArray(), 0, new IntPtr((void*)p), surrounding.Length);
        }
 
        //
        // Get the surrounding text of the given range.
        // The offsetStart is out param to return the offset of the given range in the returned surrounding text.
        //
        private static string GetSurroundingText(ITextRange range, out int offsetStart)
        {
            ITextPointer navigator;
            bool done;
            string surrounding = "";
            int bufLength;
 
            //
            // Get the previous text of the given range.
            //
            navigator = range.Start.CreatePointer();
            done = false;
            bufLength = _maxSrounding;
            while (!done && (bufLength > 0))
            {
                switch (navigator.GetPointerContext(LogicalDirection.Backward))
                {
                    case TextPointerContext.Text:
                        char[] buffer = new char[bufLength];
                        int copied = navigator.GetTextInRun(LogicalDirection.Backward, buffer, 0, buffer.Length);
                        Invariant.Assert(copied != 0);
                        navigator.MoveByOffset(0 - copied);
                        bufLength -= copied;
                        surrounding = surrounding.Insert(0, new string(buffer, 0, copied));
                        break;
 
                    case TextPointerContext.EmbeddedElement:
                        done = true;
                        break;
 
                    case TextPointerContext.ElementStart:
                    case TextPointerContext.ElementEnd:
                        // ignore the inline element.
                        if (!navigator.GetElementType(LogicalDirection.Backward).IsSubclassOf(typeof(Inline)))
                        {
                            done = true;
                        }
                        navigator.MoveToNextContextPosition(LogicalDirection.Backward);
                        break;
 
 
                    case TextPointerContext.None:
                        done = true;
                        break;
 
                    default:
                        navigator.MoveToNextContextPosition(LogicalDirection.Backward);
                        break;
                }
            }
 
            // offsetStart is the amount of the current surroundingText.
            offsetStart = surrounding.Length;
 
            //
            // add the text in the given range.
            //
            surrounding += range.Text;
 
            //
            // Get the following text of the given range.
            //
            navigator = range.End.CreatePointer();
            done = false;
            bufLength = _maxSrounding;
            while (!done && (bufLength > 0))
            {
                switch (navigator.GetPointerContext(LogicalDirection.Forward))
                {
                    case TextPointerContext.Text:
                        char[] buffer = new char[bufLength];
                        int copied = navigator.GetTextInRun(LogicalDirection.Forward, buffer, 0, buffer.Length);
                        navigator.MoveByOffset(copied);
                        bufLength -= copied;
                        surrounding += new string(buffer, 0, copied);
                        break;
 
                    case TextPointerContext.EmbeddedElement:
                        done = true;
                        break;
 
                    case TextPointerContext.ElementStart:
                    case TextPointerContext.ElementEnd:
                        // ignore the inline element.
                        if (!navigator.GetElementType(LogicalDirection.Forward).IsSubclassOf(typeof(Inline)))
                        {
                            done = true;
                        }
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break;
 
                    case TextPointerContext.None:
                        done = true;
                        break;
 
                    default:
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break;
                }
            }
 
            return surrounding;
        }
 
        //
        // WM_IME_REQUEST/IMR_CONFIRMRECONVERTSTRING handler
        //
        private IntPtr OnWmImeRequest_ConfirmReconvertString(IntPtr lParam, ref bool handled)
        {
            if (!IsInKeyboardFocus)
            {
                return IntPtr.Zero;
            }
 
            if (!_isReconvReady)
            {
                return IntPtr.Zero;
            }
 
            NativeMethods.RECONVERTSTRING reconv = Marshal.PtrToStructure<NativeMethods.RECONVERTSTRING>(lParam);
 
            // If the entire string in RECONVERTSTRING has been changed, we don't handle it.
            if (_reconv.dwStrLen != reconv.dwStrLen)
            {
                handled = true;
                return IntPtr.Zero;
            }
 
            //
            // If the new CompStr was suggested by IME, we need to adjust the selection with it.
            //
            if ((_reconv.dwCompStrLen != reconv.dwCompStrLen) ||
                (_reconv.dwCompStrOffset != reconv.dwCompStrOffset))
            {
                ITextRange range = _editor.Selection;
 
                //
                // Create the start point from the selection
                //
                ITextPointer start = range.Start.CreatePointer(LogicalDirection.Backward);
 
                // Move the start point to new  dwCompStrOffset.
                start = MoveToNextCharPos(start, (reconv.dwCompStrOffset - _reconv.dwCompStrOffset) / sizeof(short));
                // Create the end position and move this as dwCompStrLen.
                ITextPointer end = start.CreatePointer(LogicalDirection.Forward);
                end = MoveToNextCharPos(end, reconv.dwCompStrLen);
 
                // Update the selection with new start and end.
                _editor.Selection.Select(start, end);
            }
 
            _isReconvReady = false;
            handled = true;
            return new IntPtr(1);
        }
 
        //
        // Move the TextPointer by offset in char count.
        //
        private static ITextPointer MoveToNextCharPos(ITextPointer position, int offset)
        {
            bool done = false;
            if (offset < 0)
            {
                while ((offset < 0) && !done)
                {
                    switch (position.GetPointerContext(LogicalDirection.Backward))
                    {
                        case TextPointerContext.Text:
                            offset++;
                            break;
                        case TextPointerContext.None:
                            done = true;
                            break;
                    }
                    position.MoveByOffset(-1);
                }
            }
            else if (offset > 0)
            {
                while ((offset > 0) && !done)
                {
                    switch (position.GetPointerContext(LogicalDirection.Forward))
                    {
                        case TextPointerContext.Text:
                            offset--;
                            break;
                        case TextPointerContext.None:
                            done = true;
                            break;
                    }
                    position.MoveByOffset(1);
                }
            }
 
            return position;
        }
 
        //
        // Move the TextPointer by offset in char count.
        //
        private bool IsReadingWindowIme()
        {
            int prop = UnsafeNativeMethods.ImmGetProperty(new HandleRef(this, SafeNativeMethods.GetKeyboardLayout(0)), NativeMethods.IGP_PROPERTY);
            return (((prop & NativeMethods.IME_PROP_AT_CARET) == 0) || ((prop & NativeMethods.IME_PROP_SPECIAL_UI) != 0));
        }
 
        //
        // Mouse Button state was changed.
        //
        private void OnMouseButtonEvent(object sender, MouseButtonEventArgs e)
        {
            e.Handled = InternalMouseEventHandler();
        }
 
        //
        // Mouse was moved.
        //
        private void OnMouseEvent(object sender, MouseEventArgs e)
        {
            e.Handled = InternalMouseEventHandler();
        }
 
        //
        // The mouse event handler to generate MSIME message to IME listeners.
        //
        private bool InternalMouseEventHandler()
        {
            int btnState = 0;
            if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                btnState = 1; // IMEMOUSE_LDOWN
            }
            if (Mouse.RightButton == MouseButtonState.Pressed)
            {
                btnState = 2; // IMEMOUSE_RDOWN
            }
 
            Point point = Mouse.GetPosition(RenderScope);
            ITextView view;
            ITextPointer positionCurrent;
            ITextPointer positionNext;
            Rect rectCurrent;
            Rect rectNext;
 
            view = TextEditor.GetTextView(RenderScope);
            // Validate layout information on TextView
            if (!view.Validate(point))
            {
                return false;
            }
 
            // Do the hittest.
            positionCurrent = view.GetTextPositionFromPoint(point, false);
            if (positionCurrent == null)
            {
                return false;
            }
 
            rectCurrent = view.GetRectangleFromTextPosition(positionCurrent);
 
            positionNext = positionCurrent.CreatePointer();
            if (positionNext == null)
            {
                return false;
            }
 
            if (point.X - rectCurrent.Left >= 0)
            {
                positionNext.MoveToNextInsertionPosition(LogicalDirection.Forward);
            }
            else
            {
                positionNext.MoveToNextInsertionPosition(LogicalDirection.Backward);
            }
 
            rectNext = view.GetRectangleFromTextPosition(positionNext);
 
            int edge;
            int quadrant;
            edge = _editor.TextContainer.Start.GetOffsetToPosition(positionCurrent);
            int startComposition = _editor.TextContainer.Start.GetOffsetToPosition(_startComposition);
            int endComposition = _editor.TextContainer.Start.GetOffsetToPosition(_endComposition);
 
            //
            // IMEs care about only the composition string range.
            //
 
            if (edge < startComposition)
            {
                return false;
            }
 
            if (edge > endComposition)
            {
                return false;
            }
 
            if (rectNext.Left == rectCurrent.Left)
            {
                // if rectNext.Left == rectCurrent.Left, the width of char is 0 and mouse click points there.
                // there is no quadrent. So we alwasys make it 0.
                quadrant = 0;
            }
            else
            {
                if (point.X - rectCurrent.Left >= 0)
                {
                    if ((((point.X - rectCurrent.Left) * 4) / (rectNext.Left - rectCurrent.Left)) <= 1)
                        quadrant = 2;
                    else
                        quadrant = 3;
                }
                else
                {
                    if (((point.X - rectNext.Left) * 4) / (rectCurrent.Left - rectNext.Left) <= 3)
                        quadrant = 0;
                    else
                        quadrant = 1;
                }
            }
 
            //
            // IMEs care about only the composition string range.
            // If the quadrant is outside of the range, we don't do SendMessage.
            //
 
            if ((edge == startComposition) && (quadrant <= 1))
            {
                return false;
            }
 
            if ((edge == endComposition) && (quadrant >= 2))
            {
                return false;
            }
 
            //
            // The edge must be relative to the composition string.
            //
            edge -= startComposition;
 
            int wParam = (edge << 16) + (quadrant << 8) + btnState;
 
            IntPtr hwnd = IntPtr.Zero;
 
            hwnd = ((IWin32Window)_source).Handle;
 
            IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
 
            IntPtr lret = IntPtr.Zero;
            if (himc != IntPtr.Zero)
            {
                IntPtr hwndDefIme = IntPtr.Zero;
                hwndDefIme = UnsafeNativeMethods.ImmGetDefaultIMEWnd(new HandleRef(this, hwnd));
                lret = UnsafeNativeMethods.SendMessage(hwndDefIme, s_MsImeMouseMessage, new IntPtr(wParam), himc);
            }
 
            // We eat this event if IME handled.
            return (lret != IntPtr.Zero) ? true : false;
        }
 
        // Opens a composition undo unit. Opens the compsed composition undo unit if it exist on the top
        // of the stack. Otherwise, create new composition undo unit and add it to the undo manager and
        // making it as the opened undo unit.
        private void OpenCompositionUndoUnit()
        {
            UndoManager undoManager;
            DependencyObject parent;
 
            parent = _editor.TextContainer.Parent;
            undoManager = UndoManager.GetUndoManager(parent);
 
            if (undoManager != null && undoManager.IsEnabled && undoManager.OpenedUnit == null)
            {
                if (_compositionUndoUnit != null && _compositionUndoUnit == undoManager.LastUnit && !_compositionUndoUnit.Locked)
                {
                    // Opens a closed composition undo unit on the top of the stack.
                    undoManager.Reopen(_compositionUndoUnit);
                }
                else
                {
                    _compositionUndoUnit = new TextParentUndoUnit(_editor.Selection);
 
                    // Add the given composition undo unit to the undo manager and making it
                    // as the opened undo unit.
                    undoManager.Open(_compositionUndoUnit);
                }
            }
            else
            {
                _compositionUndoUnit = null;
            }
        }
 
        // Closes an opened composition unit and adding it to the containing unit's undo stack.
        private void CloseCompositionUndoUnit(UndoCloseAction undoCloseAction, ITextPointer compositionEnd)
        {
            UndoManager undoManager;
            DependencyObject parent;
 
            parent = _editor.TextContainer.Parent;
            undoManager = UndoManager.GetUndoManager(parent);
 
            if (undoManager != null && undoManager.IsEnabled && undoManager.OpenedUnit != null)
            {
                if (_compositionUndoUnit != null)
                {
                    // Closes an opened composition unit and commit it to add the composition
                    // undo unit into the containing unit's undo stack.
                    if (undoCloseAction == UndoCloseAction.Commit)
                    {
                        _compositionUndoUnit.RecordRedoSelectionState(compositionEnd, compositionEnd);
                    }
                    undoManager.Close(_compositionUndoUnit, undoCloseAction);
                }
            }
            else
            {
                _compositionUndoUnit = null;
            }
        }
 
        // Converts a double into a 32 bit integer, truncating values that
        // exceed Int32.MinValue or Int32.MaxValue.
        private int ConvertToInt32(double value)
        {
            int i;
 
            if (Double.IsNaN(value))
            {
                // (int)value is 0x80000000. So we should assign Int32.MinValue.
                i = Int32.MinValue;
            }
            else if (value < Int32.MinValue)
            {
                i = Int32.MinValue;
            }
            else if (value > Int32.MaxValue)
            {
                i = Int32.MaxValue;
            }
            else
            {
                i = Convert.ToInt32(value);
            }
 
            return i;
        }
 
        private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args)
        {
            if (args.IMECharCount > 0 && (args.TextChange == TextChangeType.ContentAdded || args.TextChange == TextChangeType.ContentRemoved))
            {
                _compositionModifiedByEventListener = true;
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
        private UIElement RenderScope
        {
            get { return _editor.TextView == null ? null : _editor.TextView.RenderScope; }
        }
 
        private FrameworkElement UiScope
        {
            get { return (_editor == null) ? null : _editor.UiScope; }
        }
 
        private bool IsReadOnly
        {
            get
            {
                return ((bool)UiScope.GetValue(TextEditor.IsReadOnlyProperty) || _editor.IsReadOnly);
            }
        }
 
        private bool IsInKeyboardFocus
        {
            get
            {
                if (_editor == null)
                {
                    return false;
                }
 
                if (UiScope == null)
                {
                    return false;
                }
 
                if (!UiScope.IsKeyboardFocused)
                {
                    return false;
                }
                return true;
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        //
        // HwndSource of this instance of ImmComposition.
        //
        private HwndSource _source;
 
        //
        // TextEditor of the current focus element.
        //
        private TextEditor _editor;
 
        //
        // The current start position of the compositon string.
        // This is null if the composition string does not exist.
        //
        private ITextPointer _startComposition;
 
        //
        // The current end position of the compositon string.
        // This is null if the composition string does not exist.
        //
        private ITextPointer _endComposition;
 
        //
        // The offset in chars from the start of the composition to the IME caret.
        //
        private int _caretOffset;
 
#if UNUSED_IME_HIGHLIGHT_LAYER
        //
        // Highlight layer forLevel3 composition drawing.
        //
        private DisplayAttributeHighlightLayer _highlightLayer;
#endif
 
        //
        // CompositionAdorner for displaying the composition attributes.
        //
        private CompositionAdorner _compositionAdorner;
 
        //
        // List of ImmComposition instances.
        //
        private static Hashtable _list = new Hashtable(1);
 
        //
        // Dash length of the compositon string underline.
        //
        private const double _dashLength = 2.0;
 
        //
        // Max surrounding char count for RECONVERTSTRING/DOCFEED.
        //
        private const int _maxSrounding = 0x20;
 
        //
        //  Cached RECONVERTSTRING structure for IMR_CONFIRMRECONVERTSTRING message handling.
        //
        private NativeMethods.RECONVERTSTRING _reconv;
 
        //
        //  True if the cached RECONVERTSTRING structure is ready.
        //
        private bool _isReconvReady;
 
        //
        //  MSIME mouse operation message.
        //
        private static WindowMessage s_MsImeMouseMessage = UnsafeNativeMethods.RegisterWindowMessage("MSIMEMouseOperation");
 
        // This is the composition undo unit.
        private TextParentUndoUnit _compositionUndoUnit;
 
        // Reentry flag, set true while handling WM_IME_UPDATE, WM_IME_CHAR.
        private bool _handlingImeMessage;
 
        // If this is true, call UpdateNearCaretCompositionWindow() at the next layout update.
        private bool _updateCompWndPosAtNextLayoutUpdate;
 
        // Set true if an application listener modified the document content
        // or selection inside a TextInput* event.
        private bool _compositionModifiedByEventListener;
 
        // Flag set true when TextInput events are handled by the default
        // TextEditor listener -- not intercepted by an application listener.
        private bool _handledByEditorListener;
 
        // Set true while completing a composition from OnLostFocus.
        private bool _losingFocus;
 
        #endregion Private Fields
    }
}