Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / System / Windows / Documents / ImmComposition.cs / 1 / ImmComposition.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
//
// Description:
// This class handles IMM32 IME's composition string and support level 3 input to TextBox and RichTextBox.
//
// History:
// 11/09/2004 : yutakas - created
//
//---------------------------------------------------------------------------
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Documents;
using System.Windows.Interop;
using System.Windows.Threading;
using System.Security;
using System.Security.Permissions;
using System.Text;
using MS.Win32;
using MS.Internal.Documents;
using MS.Internal.PresentationFramework;
using MS.Internal;
// 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.
//
///
/// Critical - This class exists purely to prevent the security exceptions from
/// percolating
/// TreatAsSafe: Ok to expose
///
[SecurityCritical,SecurityTreatAsSafe]
static ImmComposition()
{
}
//
// Creates a new ImmComposition instance.
//
///
/// Critical - calls critical code (UpdateSource)
///
[SecurityCritical]
internal ImmComposition(HwndSource source)
{
UpdateSource(null, source);
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Method
//
//------------------------------------------------------
//
// Create an instance of ImmComposition per source window.
//
///
/// Critical - gets HwndSource (protected), then creates a new
/// composition based on this.
///
[SecurityCritical]
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.
//
///
/// Critical: This code removes the handler for OnSourceChanged which is critical
/// TreatAsSafe:Removing the handler is a safe operation
///
[SecurityCritical,SecurityTreatAsSafe]
internal void OnDetach()
{
if (_editor != null)
{
PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged));
_editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange);
}
_editor = null;
}
//
// Callback from TextEditor when it gets focus.
//
///
/// Critical: This code calls into PresentationSource to remove and add source changed handlers
/// TreatAsSafe: Calling this is safe. Since this does not expose the presentation source. Also the
/// handler that is attached is private and critical
///
[SecurityCritical,SecurityTreatAsSafe]
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.
//
///
/// Critical - elevates to access protected resource (hwnd)
/// TreatAsSafe - forces the composition to complete, which at worst
/// causes someone's current input to commit. No additional
/// spoofing or information disclosure could occur.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void CompleteComposition()
{
UnregisterMouseListeners();
if (_source == null)
{
// Do nothing if HwndSource is already gone(disposed) or disconnected.
return;
}
_compositionModifiedByApp = true;
IntPtr hwnd = IntPtr.Zero;
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
hwnd = ((IWin32Window)_source).Handle;
}
finally
{
CodeAccessPermission.RevertAssert();
}
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()
{
_compositionModifiedByApp = 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
//
///
/// Critical - calls critical code - NewSource, OldSource and UpdateSource.
///
[SecurityCritical]
private void OnSourceChanged(object sender, SourceChangedEventArgs e)
{
HwndSource newSource = null;
HwndSource oldSource = null;
new UIPermission(PermissionState.Unrestricted).Assert(); // BlessedAssert
try
{
newSource = e.NewSource as HwndSource;
oldSource = e.OldSource as HwndSource;
}
finally
{
UIPermission.RevertAssert();
}
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.
//
///
/// Critical - Calls critical code (add/remove hook)
///
[SecurityCritical]
private void UpdateSource(HwndSource oldSource, HwndSource newSource)
{
if (_source != null)
{
Debug.Assert((oldSource == null) || (oldSource == _source));
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
_source.RemoveHook(new HwndSourceHook(ImmCompositionFilterMessage));
}
finally
{
UIPermission.RevertAssert();
}
_source.Disposed -= new EventHandler(OnHwndDisposed);
// Remove HwndSource from the list.
_list.Remove(_source);
_source = null;
}
if (newSource != null)
{
_list[newSource] = this;
_source = newSource;
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
_source.AddHook(new HwndSourceHook(ImmCompositionFilterMessage));
}
finally
{
UIPermission.RevertAssert();
}
_source.Disposed += new EventHandler(OnHwndDisposed);
}
// _source should always be a newSource.
Debug.Assert(newSource == _source);
}
//
// Window Hook to track WM_IME_ messages.
//
///
/// Critical - access raw Win32 messages, raw input, etc. and can be used to spoof input
///
[SecurityCritical]
private IntPtr ImmCompositionFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
IntPtr lret = IntPtr.Zero ;
switch (msg)
{
case NativeMethods.WM_IME_CHAR:
OnWmImeChar(wParam, ref handled);
break;
case NativeMethods.WM_IME_NOTIFY:
// we don't have to update handled.
OnWmImeNotify(hwnd, wParam);
break;
case NativeMethods.WM_IME_STARTCOMPOSITION:
case NativeMethods.WM_IME_ENDCOMPOSITION:
if (IsInKeyboardFocus && !IsReadOnly)
{
// Do Level 2 for legacy Chinese IMM32 IMEs.
if (!IsReadingWindowIme())
{
handled = true;
}
}
break;
case NativeMethods.WM_IME_COMPOSITION:
OnWmImeComposition(hwnd, lParam, ref handled);
break;
case NativeMethods.WM_IME_REQUEST:
lret = OnWmImeRequest(wParam, lParam, ref handled);
break;
case NativeMethods.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
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
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;
int[] clauseInfo = 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 / Marshal.SizeOf(typeof(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 / Marshal.SizeOf(typeof(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 clause information from hIMC.
//
if (((int)lParam & NativeMethods.GCS_COMPCLAUSE) != 0)
{
size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPCLAUSE, IntPtr.Zero, 0);
if (size > 0)
{
clauseInfo = new int[size / Marshal.SizeOf(typeof(int))];
// 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_COMPCLAUSE, clauseInfo, size);
}
}
//
// 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 / Marshal.SizeOf(typeof(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, clauseInfo, attributes);
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
handled = true;
}
//
// WM_IME_CHAR handler
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
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);
_compositionModifiedByApp = 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
//
///
/// Critical - accepts raw Win32 messages, calls unmanaged code, deals
/// with unmanaged data structures
///
[SecurityCritical]
private void OnWmImeNotify(IntPtr hwnd, IntPtr wParam)
{
int convmode;
int sentence;
IntPtr himc;
// we don't have to do anything if _editor is null.
if (!IsInKeyboardFocus)
{
return;
}
switch ( (int) wParam)
{
case 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 = new NativeMethods.POINT(0, 0);
}
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".
break;
case NativeMethods.IMN_SETCONVERSIONMODE:
// The Conversion Mode has been changed by IMM32 API. We need to update InputMethod.ImeConversionMode property.
ImeConversionModeValues imeConversionMode = 0;
convmode = 0;
sentence = 0;
himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
if (himc != IntPtr.Zero)
{
UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence);
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
// IME_CMODE_ALPHANUMERIC is 0.
if ((convmode & (NativeMethods.IME_CMODE_NATIVE | NativeMethods.IME_CMODE_KATAKANA)) == 0)
imeConversionMode |= ImeConversionModeValues.Alphanumeric;
if ((convmode & NativeMethods.IME_CMODE_NATIVE) != 0)
imeConversionMode |= ImeConversionModeValues.Native;
if ((convmode & NativeMethods.IME_CMODE_KATAKANA) != 0)
imeConversionMode |= ImeConversionModeValues.Katakana;
if ((convmode & NativeMethods.IME_CMODE_FULLSHAPE) != 0)
imeConversionMode |= ImeConversionModeValues.FullShape;
if ((convmode & NativeMethods.IME_CMODE_ROMAN) != 0)
imeConversionMode |= ImeConversionModeValues.Roman;
if ((convmode & NativeMethods.IME_CMODE_CHARCODE) != 0)
imeConversionMode |= ImeConversionModeValues.CharCode;
if ((convmode & NativeMethods.IME_CMODE_NOCONVERSION) != 0)
imeConversionMode |= ImeConversionModeValues.NoConversion;
if ((convmode & NativeMethods.IME_CMODE_EUDC) != 0)
imeConversionMode |= ImeConversionModeValues.Eudc;
if ((convmode & NativeMethods.IME_CMODE_SYMBOL) != 0)
imeConversionMode |= ImeConversionModeValues.Symbol;
if ((convmode & NativeMethods.IME_CMODE_FIXED) != 0)
imeConversionMode |= ImeConversionModeValues.Fixed;
InputMethod.Current.ImeConversionMode = imeConversionMode;
}
break;
case NativeMethods.IMN_SETSENTENCEMODE:
// The Sentence Mode has been changed by IMM32 API. We need to update InputMethod.ImeSentenceMode property.
ImeSentenceModeValues imeSentenceMode = 0;
convmode = 0;
sentence = 0;
himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
if (himc != IntPtr.Zero)
{
UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence);
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
// TF_SENTENCEMODE_ALPHANUMERIC is 0.
if (sentence == NativeMethods.IME_SMODE_NONE)
{
imeSentenceMode = ImeSentenceModeValues.None;
}
else
{
if ((sentence & NativeMethods.IME_SMODE_PLAURALCLAUSE) != 0)
imeSentenceMode |= ImeSentenceModeValues.PluralClause;
if ((sentence & NativeMethods.IME_SMODE_SINGLECONVERT) != 0)
imeSentenceMode |= ImeSentenceModeValues.SingleConversion;
if ((sentence & NativeMethods.IME_SMODE_AUTOMATIC) != 0)
imeSentenceMode |= ImeSentenceModeValues.Automatic;
if ((sentence & NativeMethods.IME_SMODE_PHRASEPREDICT) != 0)
imeSentenceMode |= ImeSentenceModeValues.PhrasePrediction;
if ((sentence & NativeMethods.IME_SMODE_CONVERSATION) != 0)
imeSentenceMode |= ImeSentenceModeValues.Conversation;
}
InputMethod.Current.ImeSentenceMode = imeSentenceMode;
}
break;
case NativeMethods.IMN_SETOPENSTATUS:
// The open status has been changed by IMM32 API. We need to update InputMethod.ImeState property.
himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
if (himc != IntPtr.Zero)
{
bool fOpen = UnsafeNativeMethods.ImmGetOpenStatus(new HandleRef(this, himc));
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
InputMethod.Current.ImeState = fOpen ? InputMethodState.On : InputMethodState.Off;
}
break;
}
}
//
// Use Level 2 for Chinese IME
//
///
/// Critical - elevates to access protected resources (hwnd)
/// TreatAsSafe - positions the composition window, which is safe to do
///
[SecurityCritical, SecurityTreatAsSafe]
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;
}
// get hwnd from _source.
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
hwnd = ((IWin32Window)_source).Handle;
}
finally
{
CodeAccessPermission.RevertAssert();
}
rectUi = UiScope.VisualContentBounds;
view = _editor.TextView;
//
//
// 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)
{
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.
//
///
/// Critical - unparents the composition window visually
/// TreatAsSafe - while a DOS, this doesn't present any specific security threat.
///
[SecurityCritical, SecurityTreatAsSafe]
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, int[] clauseInfo, 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);
_compositionModifiedByApp = 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(clauseInfo, attributes);
}
}
finally
{
_handlingImeMessage = false;
}
}
// Attempts to build a string containing zero or more result chars
// followed by zero or more composition chars.
//
// Will return null if an underlying TextBox has MaxLength property
// that is exceeded by the new content.
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);
if (!_editor.AcceptsRichContent)
{
int charsToReplaceCount;
if (_startComposition == null)
{
charsToReplaceCount = _editor.Selection.Start.GetOffsetToPosition(_editor.Selection.End);
}
else
{
charsToReplaceCount = _startComposition.GetOffsetToPosition(_endComposition);
}
compositionString = _editor._FilterText(compositionString, charsToReplaceCount);
}
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.
///
/// Critical - calls critical (TextCompositionManager) code.
/// TreatAsSafe - doesn't accept or return critical information.
///
[SecurityCritical, SecurityTreatAsSafe]
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 ||
_compositionModifiedByApp)
{
return true;
}
// UpdateCompositionText raises a PUBLIC EVENT....
UpdateCompositionText(composition, resultLength, true /* includeResultText */, out _startComposition, out _endComposition);
if (_compositionModifiedByApp)
{
return true;
}
RegisterMouseListeners();
return false;
}
// Raises a public TextInputUpdate event.
// Returns true if a listener handles the event or modifies document state.
///
/// Critical - calls critical (TextCompositionManager) code.
/// TreatAsSafe - doesn't accept or return critical information.
///
[SecurityCritical, SecurityTreatAsSafe]
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 ||
_compositionModifiedByApp)
{
return true;
}
// UpdateCompositionText raises a PUBLIC EVENT....
UpdateCompositionText(composition, resultLength, false /* includeResultText */, out _startComposition, out _endComposition);
if (_compositionModifiedByApp)
{
return true;
}
return false;
}
// Raises a public TextInput event.
// Returns true if a listener handles the event or modifies document state.
///
/// Critical - calls critical (TextCompositionManager) code.
/// TreatAsSafe - doesn't accept or return critical information.
///
[SecurityCritical, SecurityTreatAsSafe]
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 || _compositionModifiedByApp);
}
// 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 (_compositionModifiedByApp)
{
// 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;
UndoCloseAction undoCloseAction = UndoCloseAction.Rollback;
OpenCompositionUndoUnit();
try
{
_editor.Selection.BeginChange();
try
{
//
ITextRange range;
string text;
if (composition._ResultStart != null)
{
range = new TextRange(composition._ResultStart, composition._ResultEnd);
text = composition.Text;
}
else
{
range = new TextRange(composition._CompositionStart, composition._CompositionEnd);
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.
_compositionModifiedByApp = false;
_editor.Selection.EndChange();
}
undoCloseAction = UndoCloseAction.Commit;
}
finally
{
CloseCompositionUndoUnit(undoCloseAction, end);
}
}
// Decorates the composition with IME specified underlining.
private void SetCompositionAdorner(int[] clauseInfo, byte[] attributes)
{
if ((clauseInfo != null) && (attributes != null))
{
for (int i = 0; i < clauseInfo.Length - 1; i++)
{
ITextPointer startClause = _startComposition.CreatePointer(clauseInfo[i], LogicalDirection.Backward);
ITextPointer endClause = _startComposition.CreatePointer(clauseInfo[i + 1], LogicalDirection.Forward);
if (_compositionAdorner == null)
{
_compositionAdorner = new CompositionAdorner(_editor.TextView);
_compositionAdorner.Initialize(_editor.TextView);
}
//
//
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[clauseInfo[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_SOLID;
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();
}
// ToDo: 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(startClause, endClause, textServiceDisplayAttribute);
}
#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
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
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
//
///
/// Crtical: This code calls into PtrToStruct which is marked critical.It can also be used to spoof input
///
[SecurityCritical]
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 * Marshal.SizeOf(typeof(short))) + ((_maxSrounding + 1) * Marshal.SizeOf(typeof(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 = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING));
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 * Marshal.SizeOf(typeof(short)); // in byte count
reconv.dwTargetStrLen = target.Length; // in char count
reconv.dwTargetStrOffset = offsetStart * Marshal.SizeOf(typeof(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;
}
///
/// Crtical: unsafe code to manipulate pointer.
/// This should not be called unless we're sure the first param is the pointer to
/// NativeMethods.RECONVERTSTRING.
///
[SecurityCritical]
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 = ((TextPointer)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 = ((TextPointer)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
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
private IntPtr OnWmImeRequest_ConfirmReconvertString(IntPtr lParam, ref bool handled)
{
if (!IsInKeyboardFocus)
{
return IntPtr.Zero ;
}
if (!_isReconvReady)
{
return IntPtr.Zero ;
}
NativeMethods.RECONVERTSTRING reconv = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING));
// 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) / Marshal.SizeOf(typeof(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.
//
///
/// Critical - calls ImmGetProperty that could expose the current ime's capability.
/// TreatAsSafe - the return value says only if IME is near caret Chinese IME.
/// exposing this information is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
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.
//
///
/// Critical - calls critical code (InternalMouseEventHandler)
/// TreatAsSafe - the mouse input here is ignored, but rather the mouse
/// is directly queried, so there is no spoofing possibility
///
[SecurityCritical, SecurityTreatAsSafe]
private void OnMouseButtonEvent(object sender, MouseButtonEventArgs e)
{
e.Handled = InternalMouseEventHandler();
}
//
// Mouse was moved.
//
///
/// Critical - calls critical code (InternalMouseEventHandler)
/// TreatAsSafe - the mouse input here is ignored, but rather the mouse
/// is directly queried, so there is no spoofing possibility
///
[SecurityCritical, SecurityTreatAsSafe]
private void OnMouseEvent(object sender, MouseEventArgs e)
{
e.Handled = InternalMouseEventHandler();
}
//
// The mouse event handler to generate MSIME message to IME listeners.
//
///
/// Critical - calls unmanaged code, sends WM_* messages, simulates mouse
/// messages to the IME. This could result in input spoofing.
///
[SecurityCritical]
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;
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
hwnd = ((IWin32Window)_source).Handle;
}
finally
{
CodeAccessPermission.RevertAssert();
}
IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
IntPtr lret = IntPtr.Zero;
if (himc != IntPtr.Zero)
{
IntPtr hwndDefIme = IntPtr.Zero;
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();//Blessed Assert
try
{
hwndDefIme = UnsafeNativeMethods.ImmGetDefaultIMEWnd(new HandleRef(this, hwnd));
lret = UnsafeNativeMethods.SendMessage(hwndDefIme, s_MsImeMouseMessage, new IntPtr(wParam), himc);
}
finally
{
SecurityPermission.RevertAssert();
}
}
// 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)
{
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))
{
_compositionModifiedByApp = 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.
//
[SecurityCritical]
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.
//
///
/// Critical: This code registers a custom message and calls into a critical method
///
[SecurityCritical]
private static int 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 _compositionModifiedByApp;
// 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
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
//
// Description:
// This class handles IMM32 IME's composition string and support level 3 input to TextBox and RichTextBox.
//
// History:
// 11/09/2004 : yutakas - created
//
//---------------------------------------------------------------------------
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Documents;
using System.Windows.Interop;
using System.Windows.Threading;
using System.Security;
using System.Security.Permissions;
using System.Text;
using MS.Win32;
using MS.Internal.Documents;
using MS.Internal.PresentationFramework;
using MS.Internal;
// 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.
//
///
/// Critical - This class exists purely to prevent the security exceptions from
/// percolating
/// TreatAsSafe: Ok to expose
///
[SecurityCritical,SecurityTreatAsSafe]
static ImmComposition()
{
}
//
// Creates a new ImmComposition instance.
//
///
/// Critical - calls critical code (UpdateSource)
///
[SecurityCritical]
internal ImmComposition(HwndSource source)
{
UpdateSource(null, source);
}
#endregion Constructors
//------------------------------------------------------
//
// Internal Method
//
//------------------------------------------------------
//
// Create an instance of ImmComposition per source window.
//
///
/// Critical - gets HwndSource (protected), then creates a new
/// composition based on this.
///
[SecurityCritical]
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.
//
///
/// Critical: This code removes the handler for OnSourceChanged which is critical
/// TreatAsSafe:Removing the handler is a safe operation
///
[SecurityCritical,SecurityTreatAsSafe]
internal void OnDetach()
{
if (_editor != null)
{
PresentationSource.RemoveSourceChangedHandler(UiScope, new SourceChangedEventHandler(OnSourceChanged));
_editor.TextContainer.Change -= new TextContainerChangeEventHandler(OnTextContainerChange);
}
_editor = null;
}
//
// Callback from TextEditor when it gets focus.
//
///
/// Critical: This code calls into PresentationSource to remove and add source changed handlers
/// TreatAsSafe: Calling this is safe. Since this does not expose the presentation source. Also the
/// handler that is attached is private and critical
///
[SecurityCritical,SecurityTreatAsSafe]
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.
//
///
/// Critical - elevates to access protected resource (hwnd)
/// TreatAsSafe - forces the composition to complete, which at worst
/// causes someone's current input to commit. No additional
/// spoofing or information disclosure could occur.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void CompleteComposition()
{
UnregisterMouseListeners();
if (_source == null)
{
// Do nothing if HwndSource is already gone(disposed) or disconnected.
return;
}
_compositionModifiedByApp = true;
IntPtr hwnd = IntPtr.Zero;
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
hwnd = ((IWin32Window)_source).Handle;
}
finally
{
CodeAccessPermission.RevertAssert();
}
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()
{
_compositionModifiedByApp = 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
//
///
/// Critical - calls critical code - NewSource, OldSource and UpdateSource.
///
[SecurityCritical]
private void OnSourceChanged(object sender, SourceChangedEventArgs e)
{
HwndSource newSource = null;
HwndSource oldSource = null;
new UIPermission(PermissionState.Unrestricted).Assert(); // BlessedAssert
try
{
newSource = e.NewSource as HwndSource;
oldSource = e.OldSource as HwndSource;
}
finally
{
UIPermission.RevertAssert();
}
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.
//
///
/// Critical - Calls critical code (add/remove hook)
///
[SecurityCritical]
private void UpdateSource(HwndSource oldSource, HwndSource newSource)
{
if (_source != null)
{
Debug.Assert((oldSource == null) || (oldSource == _source));
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
_source.RemoveHook(new HwndSourceHook(ImmCompositionFilterMessage));
}
finally
{
UIPermission.RevertAssert();
}
_source.Disposed -= new EventHandler(OnHwndDisposed);
// Remove HwndSource from the list.
_list.Remove(_source);
_source = null;
}
if (newSource != null)
{
_list[newSource] = this;
_source = newSource;
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
_source.AddHook(new HwndSourceHook(ImmCompositionFilterMessage));
}
finally
{
UIPermission.RevertAssert();
}
_source.Disposed += new EventHandler(OnHwndDisposed);
}
// _source should always be a newSource.
Debug.Assert(newSource == _source);
}
//
// Window Hook to track WM_IME_ messages.
//
///
/// Critical - access raw Win32 messages, raw input, etc. and can be used to spoof input
///
[SecurityCritical]
private IntPtr ImmCompositionFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
IntPtr lret = IntPtr.Zero ;
switch (msg)
{
case NativeMethods.WM_IME_CHAR:
OnWmImeChar(wParam, ref handled);
break;
case NativeMethods.WM_IME_NOTIFY:
// we don't have to update handled.
OnWmImeNotify(hwnd, wParam);
break;
case NativeMethods.WM_IME_STARTCOMPOSITION:
case NativeMethods.WM_IME_ENDCOMPOSITION:
if (IsInKeyboardFocus && !IsReadOnly)
{
// Do Level 2 for legacy Chinese IMM32 IMEs.
if (!IsReadingWindowIme())
{
handled = true;
}
}
break;
case NativeMethods.WM_IME_COMPOSITION:
OnWmImeComposition(hwnd, lParam, ref handled);
break;
case NativeMethods.WM_IME_REQUEST:
lret = OnWmImeRequest(wParam, lParam, ref handled);
break;
case NativeMethods.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
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
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;
int[] clauseInfo = 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 / Marshal.SizeOf(typeof(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 / Marshal.SizeOf(typeof(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 clause information from hIMC.
//
if (((int)lParam & NativeMethods.GCS_COMPCLAUSE) != 0)
{
size = UnsafeNativeMethods.ImmGetCompositionString(new HandleRef(this, himc), NativeMethods.GCS_COMPCLAUSE, IntPtr.Zero, 0);
if (size > 0)
{
clauseInfo = new int[size / Marshal.SizeOf(typeof(int))];
// 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_COMPCLAUSE, clauseInfo, size);
}
}
//
// 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 / Marshal.SizeOf(typeof(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, clauseInfo, attributes);
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
handled = true;
}
//
// WM_IME_CHAR handler
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
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);
_compositionModifiedByApp = 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
//
///
/// Critical - accepts raw Win32 messages, calls unmanaged code, deals
/// with unmanaged data structures
///
[SecurityCritical]
private void OnWmImeNotify(IntPtr hwnd, IntPtr wParam)
{
int convmode;
int sentence;
IntPtr himc;
// we don't have to do anything if _editor is null.
if (!IsInKeyboardFocus)
{
return;
}
switch ( (int) wParam)
{
case 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 = new NativeMethods.POINT(0, 0);
}
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".
break;
case NativeMethods.IMN_SETCONVERSIONMODE:
// The Conversion Mode has been changed by IMM32 API. We need to update InputMethod.ImeConversionMode property.
ImeConversionModeValues imeConversionMode = 0;
convmode = 0;
sentence = 0;
himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
if (himc != IntPtr.Zero)
{
UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence);
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
// IME_CMODE_ALPHANUMERIC is 0.
if ((convmode & (NativeMethods.IME_CMODE_NATIVE | NativeMethods.IME_CMODE_KATAKANA)) == 0)
imeConversionMode |= ImeConversionModeValues.Alphanumeric;
if ((convmode & NativeMethods.IME_CMODE_NATIVE) != 0)
imeConversionMode |= ImeConversionModeValues.Native;
if ((convmode & NativeMethods.IME_CMODE_KATAKANA) != 0)
imeConversionMode |= ImeConversionModeValues.Katakana;
if ((convmode & NativeMethods.IME_CMODE_FULLSHAPE) != 0)
imeConversionMode |= ImeConversionModeValues.FullShape;
if ((convmode & NativeMethods.IME_CMODE_ROMAN) != 0)
imeConversionMode |= ImeConversionModeValues.Roman;
if ((convmode & NativeMethods.IME_CMODE_CHARCODE) != 0)
imeConversionMode |= ImeConversionModeValues.CharCode;
if ((convmode & NativeMethods.IME_CMODE_NOCONVERSION) != 0)
imeConversionMode |= ImeConversionModeValues.NoConversion;
if ((convmode & NativeMethods.IME_CMODE_EUDC) != 0)
imeConversionMode |= ImeConversionModeValues.Eudc;
if ((convmode & NativeMethods.IME_CMODE_SYMBOL) != 0)
imeConversionMode |= ImeConversionModeValues.Symbol;
if ((convmode & NativeMethods.IME_CMODE_FIXED) != 0)
imeConversionMode |= ImeConversionModeValues.Fixed;
InputMethod.Current.ImeConversionMode = imeConversionMode;
}
break;
case NativeMethods.IMN_SETSENTENCEMODE:
// The Sentence Mode has been changed by IMM32 API. We need to update InputMethod.ImeSentenceMode property.
ImeSentenceModeValues imeSentenceMode = 0;
convmode = 0;
sentence = 0;
himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
if (himc != IntPtr.Zero)
{
UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(this, himc), ref convmode, ref sentence);
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
// TF_SENTENCEMODE_ALPHANUMERIC is 0.
if (sentence == NativeMethods.IME_SMODE_NONE)
{
imeSentenceMode = ImeSentenceModeValues.None;
}
else
{
if ((sentence & NativeMethods.IME_SMODE_PLAURALCLAUSE) != 0)
imeSentenceMode |= ImeSentenceModeValues.PluralClause;
if ((sentence & NativeMethods.IME_SMODE_SINGLECONVERT) != 0)
imeSentenceMode |= ImeSentenceModeValues.SingleConversion;
if ((sentence & NativeMethods.IME_SMODE_AUTOMATIC) != 0)
imeSentenceMode |= ImeSentenceModeValues.Automatic;
if ((sentence & NativeMethods.IME_SMODE_PHRASEPREDICT) != 0)
imeSentenceMode |= ImeSentenceModeValues.PhrasePrediction;
if ((sentence & NativeMethods.IME_SMODE_CONVERSATION) != 0)
imeSentenceMode |= ImeSentenceModeValues.Conversation;
}
InputMethod.Current.ImeSentenceMode = imeSentenceMode;
}
break;
case NativeMethods.IMN_SETOPENSTATUS:
// The open status has been changed by IMM32 API. We need to update InputMethod.ImeState property.
himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
if (himc != IntPtr.Zero)
{
bool fOpen = UnsafeNativeMethods.ImmGetOpenStatus(new HandleRef(this, himc));
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc));
InputMethod.Current.ImeState = fOpen ? InputMethodState.On : InputMethodState.Off;
}
break;
}
}
//
// Use Level 2 for Chinese IME
//
///
/// Critical - elevates to access protected resources (hwnd)
/// TreatAsSafe - positions the composition window, which is safe to do
///
[SecurityCritical, SecurityTreatAsSafe]
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;
}
// get hwnd from _source.
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
hwnd = ((IWin32Window)_source).Handle;
}
finally
{
CodeAccessPermission.RevertAssert();
}
rectUi = UiScope.VisualContentBounds;
view = _editor.TextView;
//
//
// 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)
{
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.
//
///
/// Critical - unparents the composition window visually
/// TreatAsSafe - while a DOS, this doesn't present any specific security threat.
///
[SecurityCritical, SecurityTreatAsSafe]
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, int[] clauseInfo, 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);
_compositionModifiedByApp = 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(clauseInfo, attributes);
}
}
finally
{
_handlingImeMessage = false;
}
}
// Attempts to build a string containing zero or more result chars
// followed by zero or more composition chars.
//
// Will return null if an underlying TextBox has MaxLength property
// that is exceeded by the new content.
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);
if (!_editor.AcceptsRichContent)
{
int charsToReplaceCount;
if (_startComposition == null)
{
charsToReplaceCount = _editor.Selection.Start.GetOffsetToPosition(_editor.Selection.End);
}
else
{
charsToReplaceCount = _startComposition.GetOffsetToPosition(_endComposition);
}
compositionString = _editor._FilterText(compositionString, charsToReplaceCount);
}
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.
///
/// Critical - calls critical (TextCompositionManager) code.
/// TreatAsSafe - doesn't accept or return critical information.
///
[SecurityCritical, SecurityTreatAsSafe]
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 ||
_compositionModifiedByApp)
{
return true;
}
// UpdateCompositionText raises a PUBLIC EVENT....
UpdateCompositionText(composition, resultLength, true /* includeResultText */, out _startComposition, out _endComposition);
if (_compositionModifiedByApp)
{
return true;
}
RegisterMouseListeners();
return false;
}
// Raises a public TextInputUpdate event.
// Returns true if a listener handles the event or modifies document state.
///
/// Critical - calls critical (TextCompositionManager) code.
/// TreatAsSafe - doesn't accept or return critical information.
///
[SecurityCritical, SecurityTreatAsSafe]
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 ||
_compositionModifiedByApp)
{
return true;
}
// UpdateCompositionText raises a PUBLIC EVENT....
UpdateCompositionText(composition, resultLength, false /* includeResultText */, out _startComposition, out _endComposition);
if (_compositionModifiedByApp)
{
return true;
}
return false;
}
// Raises a public TextInput event.
// Returns true if a listener handles the event or modifies document state.
///
/// Critical - calls critical (TextCompositionManager) code.
/// TreatAsSafe - doesn't accept or return critical information.
///
[SecurityCritical, SecurityTreatAsSafe]
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 || _compositionModifiedByApp);
}
// 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 (_compositionModifiedByApp)
{
// 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;
UndoCloseAction undoCloseAction = UndoCloseAction.Rollback;
OpenCompositionUndoUnit();
try
{
_editor.Selection.BeginChange();
try
{
//
ITextRange range;
string text;
if (composition._ResultStart != null)
{
range = new TextRange(composition._ResultStart, composition._ResultEnd);
text = composition.Text;
}
else
{
range = new TextRange(composition._CompositionStart, composition._CompositionEnd);
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.
_compositionModifiedByApp = false;
_editor.Selection.EndChange();
}
undoCloseAction = UndoCloseAction.Commit;
}
finally
{
CloseCompositionUndoUnit(undoCloseAction, end);
}
}
// Decorates the composition with IME specified underlining.
private void SetCompositionAdorner(int[] clauseInfo, byte[] attributes)
{
if ((clauseInfo != null) && (attributes != null))
{
for (int i = 0; i < clauseInfo.Length - 1; i++)
{
ITextPointer startClause = _startComposition.CreatePointer(clauseInfo[i], LogicalDirection.Backward);
ITextPointer endClause = _startComposition.CreatePointer(clauseInfo[i + 1], LogicalDirection.Forward);
if (_compositionAdorner == null)
{
_compositionAdorner = new CompositionAdorner(_editor.TextView);
_compositionAdorner.Initialize(_editor.TextView);
}
//
//
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[clauseInfo[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_SOLID;
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();
}
// ToDo: 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(startClause, endClause, textServiceDisplayAttribute);
}
#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
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
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
//
///
/// Crtical: This code calls into PtrToStruct which is marked critical.It can also be used to spoof input
///
[SecurityCritical]
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 * Marshal.SizeOf(typeof(short))) + ((_maxSrounding + 1) * Marshal.SizeOf(typeof(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 = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING));
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 * Marshal.SizeOf(typeof(short)); // in byte count
reconv.dwTargetStrLen = target.Length; // in char count
reconv.dwTargetStrOffset = offsetStart * Marshal.SizeOf(typeof(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;
}
///
/// Crtical: unsafe code to manipulate pointer.
/// This should not be called unless we're sure the first param is the pointer to
/// NativeMethods.RECONVERTSTRING.
///
[SecurityCritical]
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 = ((TextPointer)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 = ((TextPointer)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
//
///
/// Critical - This can be used to spoof input and it takes IntPtr from untrusted sources
///
[SecurityCritical]
private IntPtr OnWmImeRequest_ConfirmReconvertString(IntPtr lParam, ref bool handled)
{
if (!IsInKeyboardFocus)
{
return IntPtr.Zero ;
}
if (!_isReconvReady)
{
return IntPtr.Zero ;
}
NativeMethods.RECONVERTSTRING reconv = (NativeMethods.RECONVERTSTRING)Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING));
// 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) / Marshal.SizeOf(typeof(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.
//
///
/// Critical - calls ImmGetProperty that could expose the current ime's capability.
/// TreatAsSafe - the return value says only if IME is near caret Chinese IME.
/// exposing this information is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
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.
//
///
/// Critical - calls critical code (InternalMouseEventHandler)
/// TreatAsSafe - the mouse input here is ignored, but rather the mouse
/// is directly queried, so there is no spoofing possibility
///
[SecurityCritical, SecurityTreatAsSafe]
private void OnMouseButtonEvent(object sender, MouseButtonEventArgs e)
{
e.Handled = InternalMouseEventHandler();
}
//
// Mouse was moved.
//
///
/// Critical - calls critical code (InternalMouseEventHandler)
/// TreatAsSafe - the mouse input here is ignored, but rather the mouse
/// is directly queried, so there is no spoofing possibility
///
[SecurityCritical, SecurityTreatAsSafe]
private void OnMouseEvent(object sender, MouseEventArgs e)
{
e.Handled = InternalMouseEventHandler();
}
//
// The mouse event handler to generate MSIME message to IME listeners.
//
///
/// Critical - calls unmanaged code, sends WM_* messages, simulates mouse
/// messages to the IME. This could result in input spoofing.
///
[SecurityCritical]
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;
new UIPermission(UIPermissionWindow.AllWindows).Assert();//Blessed Assert
try
{
hwnd = ((IWin32Window)_source).Handle;
}
finally
{
CodeAccessPermission.RevertAssert();
}
IntPtr himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd));
IntPtr lret = IntPtr.Zero;
if (himc != IntPtr.Zero)
{
IntPtr hwndDefIme = IntPtr.Zero;
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();//Blessed Assert
try
{
hwndDefIme = UnsafeNativeMethods.ImmGetDefaultIMEWnd(new HandleRef(this, hwnd));
lret = UnsafeNativeMethods.SendMessage(hwndDefIme, s_MsImeMouseMessage, new IntPtr(wParam), himc);
}
finally
{
SecurityPermission.RevertAssert();
}
}
// 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)
{
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))
{
_compositionModifiedByApp = 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.
//
[SecurityCritical]
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.
//
///
/// Critical: This code registers a custom message and calls into a critical method
///
[SecurityCritical]
private static int 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 _compositionModifiedByApp;
// 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
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- IdnMapping.cs
- TableCellsCollectionEditor.cs
- ProfileGroupSettingsCollection.cs
- HtmlShimManager.cs
- HwndHost.cs
- PageEventArgs.cs
- PointAnimationUsingPath.cs
- KeyboardEventArgs.cs
- ValueConversionAttribute.cs
- UTF7Encoding.cs
- ContentType.cs
- InstanceDataCollection.cs
- UniqueIdentifierService.cs
- NullableDecimalAverageAggregationOperator.cs
- EntitySqlQueryCacheEntry.cs
- DependentTransaction.cs
- OleDbError.cs
- sqlser.cs
- ResourceContainerWrapper.cs
- PointLight.cs
- SafeLibraryHandle.cs
- BitConverter.cs
- MemberJoinTreeNode.cs
- TextPointer.cs
- DataGridSortingEventArgs.cs
- ScriptResourceHandler.cs
- ToolboxItemAttribute.cs
- OrderedDictionary.cs
- TreeNode.cs
- Light.cs
- DefaultAuthorizationContext.cs
- ServicePointManager.cs
- MemoryStream.cs
- DataTemplate.cs
- SafeProcessHandle.cs
- Scene3D.cs
- TracedNativeMethods.cs
- HebrewNumber.cs
- ExtendedPropertyDescriptor.cs
- RequestSecurityTokenForGetBrowserToken.cs
- TemplateModeChangedEventArgs.cs
- XmlSchemaValidator.cs
- CharStorage.cs
- Code.cs
- ZipIOBlockManager.cs
- PeerNameResolver.cs
- SqlPersonalizationProvider.cs
- BaseAppDomainProtocolHandler.cs
- WebServiceFaultDesigner.cs
- NonBatchDirectoryCompiler.cs
- DebuggerAttributes.cs
- AudioFileOut.cs
- VirtualDirectoryMappingCollection.cs
- ComponentCollection.cs
- HierarchicalDataTemplate.cs
- Slider.cs
- TraceContextRecord.cs
- TraceXPathNavigator.cs
- FormsAuthenticationUserCollection.cs
- RuntimeComponentFilter.cs
- XmlSignificantWhitespace.cs
- ColumnMapCopier.cs
- diagnosticsswitches.cs
- HostedTransportConfigurationBase.cs
- Rfc2898DeriveBytes.cs
- MouseDevice.cs
- XamlValidatingReader.cs
- QuotedPrintableStream.cs
- ToolStripPanelCell.cs
- CookielessHelper.cs
- SmtpMail.cs
- ServiceEndpointElementCollection.cs
- XmlSerializerVersionAttribute.cs
- AssemblyCache.cs
- WaitHandleCannotBeOpenedException.cs
- BufferModesCollection.cs
- QilCloneVisitor.cs
- SmiSettersStream.cs
- formatter.cs
- DeferredSelectedIndexReference.cs
- FastEncoderStatics.cs
- WorkflowServiceNamespace.cs
- MsmqIntegrationReceiveParameters.cs
- dataobject.cs
- ContentPropertyAttribute.cs
- TextTreeTextElementNode.cs
- TrackingProfileCache.cs
- XmlSerializer.cs
- RowToFieldTransformer.cs
- Point3DValueSerializer.cs
- BrowserCapabilitiesFactory.cs
- GenericWebPart.cs
- OleDbMetaDataFactory.cs
- VisualTreeHelper.cs
- Expr.cs
- XmlILModule.cs
- TextContainerChangeEventArgs.cs
- SymmetricAlgorithm.cs
- GeometryGroup.cs
- Stylus.cs