Browse Source

Add IME support to AvalonEdit

pull/18/merge
Siegfried Pammer 13 years ago
parent
commit
039f366cbf
  1. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
  2. 156
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeNativeWrapper.cs
  3. 100
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeSupport.cs
  4. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  5. 17
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs

@ -22,6 +22,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -22,6 +22,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{
readonly TextArea textArea;
readonly TextView textView;
readonly ImeSupport ime;
readonly CaretLayer caretAdorner;
bool visible;
@ -30,6 +31,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -30,6 +31,7 @@ namespace ICSharpCode.AvalonEdit.Editing
this.textArea = textArea;
this.textView = textArea.TextView;
position = new TextViewPosition(1, 1, 0);
ime = new ImeSupport(textArea);
caretAdorner = new CaretLayer(textView);
textView.InsertLayer(caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace);
@ -434,6 +436,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -434,6 +436,7 @@ namespace ICSharpCode.AvalonEdit.Editing
} else {
caretAdorner.Hide();
}
ime.UpdateCompositionWindow();
}
}

156
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeNativeWrapper.cs

@ -0,0 +1,156 @@ @@ -0,0 +1,156 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Native API required for IME support.
/// </summary>
static class ImeNativeWrapper
{
[StructLayout(LayoutKind.Sequential)]
struct CompositionForm
{
public int dwStyle;
public POINT ptCurrentPos;
public RECT rcArea;
}
[StructLayout(LayoutKind.Sequential)]
struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
class LOGFONT
{
public int lfHeight = 0;
public int lfWidth = 0;
public int lfEscapement = 0;
public int lfOrientation = 0;
public int lfWeight = 0;
public byte lfItalic = 0;
public byte lfUnderline = 0;
public byte lfStrikeOut = 0;
public byte lfCharSet = 0;
public byte lfOutPrecision = 0;
public byte lfClipPrecision = 0;
public byte lfQuality = 0;
public byte lfPitchAndFamily = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string lfFaceName = null;
}
const int CPS_CANCEL = 0x4;
const int NI_COMPOSITIONSTR = 0x15;
const int GCS_COMPSTR = 0x0008;
public const int WM_IME_COMPOSITION = 0x10F;
[DllImport("imm32.dll")]
static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue = 0);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref CompositionForm form);
public static IntPtr AssociateContext(HwndSource source, IntPtr hIMC)
{
if (source == null)
throw new ArgumentNullException("source");
return ImmAssociateContext(source.Handle, hIMC);
}
public static bool NotifyIme(IntPtr hIMC)
{
return ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL);
}
public static IntPtr GetContext(HwndSource source)
{
if (source == null)
return IntPtr.Zero;
return ImmGetContext(source.Handle);
}
public static bool ReleaseContext(HwndSource source, IntPtr hIMC)
{
return source != null && hIMC != IntPtr.Zero && ImmReleaseContext(source.Handle, hIMC);
}
public static bool SetCompositionWindow(HwndSource source, IntPtr hIMC, TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
Rect textViewBounds = textArea.TextView.GetBounds();
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
if (source != null) {
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
textViewBounds.Transform(transformToDevice);
characterBounds.Transform(transformToDevice);
}
CompositionForm form = new CompositionForm();
form.dwStyle = 0x0020;
form.ptCurrentPos.x = (int)Math.Max(characterBounds.Left, textViewBounds.Left);
form.ptCurrentPos.y = (int)Math.Max(characterBounds.Top, textViewBounds.Top);
form.rcArea.left = (int)textViewBounds.Left;
form.rcArea.top = (int)textViewBounds.Top;
form.rcArea.right = (int)textViewBounds.Right;
form.rcArea.bottom = (int)textViewBounds.Bottom;
return ImmSetCompositionWindow(hIMC, ref form);
}
static Rect GetBounds(this TextView textView)
{
Point location = textView.TranslatePoint(new Point(0,0), textView);
return new Rect(location, new Size(textView.ActualWidth, textView.ActualHeight));
}
static Rect GetCharacterBounds(this TextView textView, TextViewPosition pos, HwndSource source)
{
VisualLine vl = textView.GetVisualLine(pos.Line);
if (vl == null)
throw new Exception();
TextLine line = vl.GetTextLine(pos.VisualColumn);
double offset = vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop) - textView.ScrollOffset.Y;
Rect r = line.GetTextBounds(pos.VisualColumn, 1).First().Rectangle;
r.Offset(-textView.ScrollOffset.X, offset);
Point pointOnRootVisual = textView.TransformToAncestor(source.RootVisual).Transform(r.Location);
Point pointOnHwnd = pointOnRootVisual.TransformToDevice(source.RootVisual);
r.Location = pointOnHwnd;
return r;
}
}
}

100
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeSupport.cs

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Editing
{
class ImeSupport : IDisposable
{
TextArea textArea;
IntPtr currentContext;
IntPtr previousContext;
HwndSource hwndSource;
public ImeSupport(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this.textArea = textArea;
InputMethod.SetIsInputMethodSuspended(this.textArea, true);
textArea.GotKeyboardFocus += TextAreaGotKeyboardFocus;
textArea.LostKeyboardFocus += TextAreaLostKeyboardFocus;
currentContext = IntPtr.Zero;
previousContext = IntPtr.Zero;
}
public void Dispose()
{
if (textArea != null) {
textArea.GotKeyboardFocus -= TextAreaGotKeyboardFocus;
textArea.LostKeyboardFocus -= TextAreaLostKeyboardFocus;
textArea = null;
}
ClearContext();
}
void ClearContext()
{
if (hwndSource != null) {
hwndSource.RemoveHook(WndProc);
ImeNativeWrapper.AssociateContext(hwndSource, previousContext);
previousContext = IntPtr.Zero;
ImeNativeWrapper.ReleaseContext(hwndSource, currentContext);
hwndSource = null;
currentContext = IntPtr.Zero;
}
}
void TextAreaGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (this.textArea == null)
return;
if (e.OriginalSource != this.textArea)
return;
hwndSource = (HwndSource)PresentationSource.FromVisual(this.textArea);
if (hwndSource != null) {
currentContext = ImeNativeWrapper.GetContext(hwndSource);
previousContext = ImeNativeWrapper.AssociateContext(hwndSource, currentContext);
hwndSource.AddHook(WndProc);
}
}
void TextAreaLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (e.OriginalSource != this.textArea)
return;
if (currentContext != IntPtr.Zero)
ImeNativeWrapper.NotifyIme(currentContext);
ClearContext();
}
IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == ImeNativeWrapper.WM_IME_COMPOSITION)
UpdateCompositionWindow();
return IntPtr.Zero;
}
public void UpdateCompositionWindow()
{
if (currentContext != IntPtr.Zero && textArea != null) {
ImeNativeWrapper.SetCompositionWindow(hwndSource, currentContext, textArea);
}
}
}
}

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -147,7 +147,9 @@ @@ -147,7 +147,9 @@
<Compile Include="Editing\EmptySelection.cs">
<DependentUpon>Selection.cs</DependentUpon>
</Compile>
<Compile Include="Editing\ImeNativeWrapper.cs" />
<Compile Include="Editing\SelectionSegment.cs" />
<Compile Include="Editing\ImeSupport.cs" />
<Compile Include="Folding\AbstractFoldingStrategy.cs" />
<Compile Include="Folding\FoldingElementGenerator.cs" />
<Compile Include="Folding\FoldingManager.cs" />

17
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs

@ -381,5 +381,22 @@ namespace ICSharpCode.AvalonEdit @@ -381,5 +381,22 @@ namespace ICSharpCode.AvalonEdit
}
}
}
bool enableImeSupport;
/// <summary>
/// Gets/Sets whether the support for Input Method Editors (IME)
/// for non-alphanumeric scripts (Chinese, Japanese, Korean, ...) is enabled.
/// </summary>
[DefaultValue(true)]
public virtual bool EnableImeSupport {
get { return enableImeSupport; }
set {
if (enableImeSupport != value) {
enableImeSupport = value;
OnPropertyChanged("EnableImeSupport");
}
}
}
}
}

Loading…
Cancel
Save