1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
|
// 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 Tango.Scripting.Editors;
using Tango.Scripting.Editors.Document;
using Tango.Scripting.Editors.Rendering;
using Tango.Scripting.Editors.Utils;
using Draw = System.Drawing;
namespace Tango.Scripting.Editors.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.Unicode)]
struct LOGFONT
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string lfFaceName;
}
const int CPS_CANCEL = 0x4;
const int NI_COMPOSITIONSTR = 0x15;
const int GCS_COMPSTR = 0x0008;
public const int WM_IME_COMPOSITION = 0x10F;
public const int WM_IME_SETCONTEXT = 0x281;
public const int WM_INPUTLANGCHANGE = 0x51;
[DllImport("imm32.dll")]
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
internal static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll")]
internal static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
[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 ImmSetCompositionWindow(IntPtr hIMC, ref CompositionForm form);
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT font);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmGetCompositionFont(IntPtr hIMC, out LOGFONT font);
[DllImport("msctf.dll")]
static extern int TF_CreateThreadMgr(out ITfThreadMgr threadMgr);
[ThreadStatic] static bool textFrameworkThreadMgrInitialized;
[ThreadStatic] static ITfThreadMgr textFrameworkThreadMgr;
public static ITfThreadMgr GetTextFrameworkThreadManager()
{
if (!textFrameworkThreadMgrInitialized) {
textFrameworkThreadMgrInitialized = true;
TF_CreateThreadMgr(out textFrameworkThreadMgr);
}
return textFrameworkThreadMgr;
}
public static bool NotifyIme(IntPtr hIMC)
{
return ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL);
}
public static bool SetCompositionWindow(HwndSource source, IntPtr hIMC, TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
Rect textViewBounds = textArea.TextView.GetBounds(source);
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
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);
}
public static bool SetCompositionFont(HwndSource source, IntPtr hIMC, TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
LOGFONT lf = new LOGFONT();
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
lf.lfFaceName = textArea.FontFamily.Source;
lf.lfHeight = (int)characterBounds.Height;
return ImmSetCompositionFont(hIMC, ref lf);
}
static Rect GetBounds(this TextView textView, HwndSource source)
{
// this may happen during layout changes in AvalonDock, so we just return an empty rectangle
// in those cases. It should be refreshed immediately.
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
return EMPTY_RECT;
Rect displayRect = new Rect(0, 0, textView.ActualWidth, textView.ActualHeight);
return textView
.TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
.TransformToDevice(source.RootVisual); // rect on HWND
}
static readonly Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
static Rect GetCharacterBounds(this TextView textView, TextViewPosition pos, HwndSource source)
{
VisualLine vl = textView.GetVisualLine(pos.Line);
if (vl == null)
return EMPTY_RECT;
// this may happen during layout changes in AvalonDock, so we just return an empty rectangle
// in those cases. It should be refreshed immediately.
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
return EMPTY_RECT;
TextLine line = vl.GetTextLine(pos.VisualColumn);
Rect displayRect;
// calculate the display rect for the current character
if (pos.VisualColumn < vl.VisualLengthWithEndOfLineMarker) {
displayRect = line.GetTextBounds(pos.VisualColumn, 1).First().Rectangle;
displayRect.Offset(0, vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop));
} else {
// if we are in virtual space, we just use one wide-space as character width
displayRect = new Rect(vl.GetVisualPosition(pos.VisualColumn, VisualYPosition.TextTop),
new Size(textView.WideSpaceWidth, textView.DefaultLineHeight));
}
// adjust to current scrolling
displayRect.Offset(-textView.ScrollOffset);
return textView
.TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
.TransformToDevice(source.RootVisual); // rect on HWND
}
}
[ComImport, Guid("aa80e801-2021-11d2-93e0-0060b067b86e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ITfThreadMgr
{
void Activate(out int clientId);
void Deactivate();
void CreateDocumentMgr(out IntPtr docMgr);
void EnumDocumentMgrs(out IntPtr enumDocMgrs);
void GetFocus(out IntPtr docMgr);
void SetFocus(IntPtr docMgr);
void AssociateFocus(IntPtr hwnd, IntPtr newDocMgr, out IntPtr prevDocMgr);
void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus);
void GetFunctionProvider(ref Guid classId, out IntPtr funcProvider);
void EnumFunctionProviders(out IntPtr enumProviders);
void GetGlobalCompartment(out IntPtr compartmentMgr);
}
}
|