225 lines
7.9 KiB
C#
225 lines
7.9 KiB
C#
// 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;
|
|
using Draw = System.Drawing;
|
|
|
|
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.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 ImmCreateContext();
|
|
//
|
|
// [DllImport("imm32.dll")]
|
|
// [return: MarshalAs(UnmanagedType.Bool)]
|
|
// public static extern bool ImmDestroyContext(IntPtr hIMC);
|
|
|
|
// [DllImport("imm32.dll")]
|
|
// public 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 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 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(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);
|
|
}
|
|
}
|