Faction Test 100.9.0.0
This commit is contained in:
@@ -1,201 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
public unsafe struct IMEString : IEnumerable<char>
|
||||
{
|
||||
internal const int IMECharBufferSize = 64;
|
||||
|
||||
public static readonly IMEString Empty = new IMEString((List<char>)null);
|
||||
|
||||
internal struct Enumerator : IEnumerator<char>
|
||||
{
|
||||
private IMEString _imeString;
|
||||
private char _currentCharacter;
|
||||
private int _currentIndex;
|
||||
|
||||
public Enumerator(IMEString imeString)
|
||||
{
|
||||
_imeString = imeString;
|
||||
_currentCharacter = '\0';
|
||||
_currentIndex = -1;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
int size = _imeString.Count;
|
||||
|
||||
_currentIndex++;
|
||||
|
||||
if (_currentIndex == size)
|
||||
return false;
|
||||
|
||||
fixed (char* ptr = _imeString.buffer)
|
||||
{
|
||||
_currentCharacter = *(ptr + _currentIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentIndex = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public char Current { get { return _currentCharacter; } }
|
||||
object IEnumerator.Current { get { return Current; } }
|
||||
}
|
||||
|
||||
public int Count { get { return _size; } }
|
||||
|
||||
public char this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index >= Count || index < 0)
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
fixed (char* ptr = buffer)
|
||||
{
|
||||
return *(ptr + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _size;
|
||||
|
||||
fixed char buffer[IMECharBufferSize];
|
||||
|
||||
public IMEString(string characters)
|
||||
{
|
||||
if (string.IsNullOrEmpty(characters))
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_size = characters.Length;
|
||||
if (_size > IMECharBufferSize)
|
||||
_size = IMECharBufferSize - 1;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
for (var i = 0; i < _size; i++)
|
||||
{
|
||||
*ptr = characters[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMEString(List<char> characters)
|
||||
{
|
||||
if (characters == null || characters.Count == 0)
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_size = characters.Count;
|
||||
if (_size > IMECharBufferSize)
|
||||
_size = IMECharBufferSize - 1;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
for (var i = 0; i < _size; i++)
|
||||
{
|
||||
*ptr = characters[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMEString(char[] characters, int count)
|
||||
{
|
||||
if (characters == null || count <= 0)
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_size = count;
|
||||
if (_size > IMECharBufferSize)
|
||||
_size = IMECharBufferSize - 1;
|
||||
|
||||
if (_size > characters.Length)
|
||||
_size = characters.Length;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
for (var i = 0; i < _size; i++)
|
||||
{
|
||||
*ptr = characters[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMEString(IntPtr bStrPtr)
|
||||
{
|
||||
if (bStrPtr == IntPtr.Zero)
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var ptrSrc = (char*)bStrPtr;
|
||||
|
||||
int i = 0;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
|
||||
while (ptrSrc[i] != '\0')
|
||||
{
|
||||
*ptr = ptrSrc[i];
|
||||
i++;
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
_size = i;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
fixed (char* ptr = buffer)
|
||||
{
|
||||
return new string(ptr, 0, _size);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr ToIntPtr()
|
||||
{
|
||||
fixed (char* ptr = buffer)
|
||||
{
|
||||
return (IntPtr)ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<char> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments for the <see cref="IImmService.TextComposition" /> event.
|
||||
/// </summary>
|
||||
public struct IMETextCompositionEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
// Construct a TextCompositionEventArgs with composition infos.
|
||||
/// </summary>
|
||||
public IMETextCompositionEventArgs(IMEString compositionText,
|
||||
int cursorPosition,
|
||||
IMEString[] candidateList = null,
|
||||
int candidatePageStart = 0,
|
||||
int candidatePageSize = 0,
|
||||
int candidateSelection = 0)
|
||||
{
|
||||
CompositionText = compositionText;
|
||||
CursorPosition = cursorPosition;
|
||||
|
||||
CandidateList = candidateList;
|
||||
CandidatePageStart = candidatePageStart;
|
||||
CandidatePageSize = candidatePageSize;
|
||||
CandidateSelection = candidateSelection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full string as it's composed by the IMM.
|
||||
/// </summary>
|
||||
public readonly IMEString CompositionText;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the cursor inside the composed string.
|
||||
/// </summary>
|
||||
public readonly int CursorPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The candidate text list for the composition.
|
||||
/// This property is only supported on WindowsDX and WindowsUniversal.
|
||||
/// If the composition string does not generate candidates this array is empty.
|
||||
/// </summary>
|
||||
public readonly IMEString[] CandidateList;
|
||||
|
||||
/// <summary>
|
||||
/// First candidate index of current page.
|
||||
/// </summary>
|
||||
public readonly int CandidatePageStart;
|
||||
|
||||
/// <summary>
|
||||
/// How many candidates should display per page.
|
||||
/// </summary>
|
||||
public readonly int CandidatePageSize;
|
||||
|
||||
/// <summary>
|
||||
/// The selected candidate index.
|
||||
/// </summary>
|
||||
public readonly int CandidateSelection;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace ImeSharp
|
||||
{
|
||||
public struct IMETextInputEventArgs
|
||||
{
|
||||
public IMETextInputEventArgs(char character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
|
||||
public readonly char Character;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>C# wrapper for Windows IME APIs. Its goal is to support both IMM32 and TSF.</Description>
|
||||
<PackageTags>IME;netcoreapp3.1;net5.0;net6.0;winforms;windows;tsf;imm32</PackageTags>
|
||||
<PackageId>ImeSharp</PackageId>
|
||||
<PackageProjectUrl>https://github.com/ryancheung/ImeSharp</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/ryancheung/ImeSharp</RepositoryUrl>
|
||||
<Authors>ryancheung</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BaseOutputPath>..\Artifacts</BaseOutputPath>
|
||||
<AssemblyName>ImeSharp</AssemblyName>
|
||||
<RootNamespace>ImeSharp</RootNamespace>
|
||||
<LangVersion>5</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TsfSharp" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,349 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
internal class Imm32Manager
|
||||
{
|
||||
|
||||
// If the system is IMM enabled, this is true.
|
||||
private static bool _immEnabled = SafeSystemMetrics.IsImmEnabled;
|
||||
|
||||
public static bool ImmEnabled { get { return _immEnabled; } }
|
||||
|
||||
public const int LANG_CHINESE = 0x04;
|
||||
public const int LANG_KOREAN = 0x12;
|
||||
public const int LANG_JAPANESE = 0x11;
|
||||
|
||||
public static int PRIMARYLANGID(int lgid)
|
||||
{
|
||||
return ((ushort)(lgid) & 0x3ff);
|
||||
}
|
||||
|
||||
static Imm32Manager()
|
||||
{
|
||||
SetCurrentCulture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return true if the current keyboard layout is a real IMM32-IME.
|
||||
/// </summary>
|
||||
public static bool IsImm32ImeCurrent()
|
||||
{
|
||||
if (!_immEnabled)
|
||||
return false;
|
||||
|
||||
IntPtr hkl = NativeMethods.GetKeyboardLayout(0);
|
||||
|
||||
return IsImm32Ime(hkl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return true if the keyboard layout is a real IMM32-IME.
|
||||
/// </summary>
|
||||
public static bool IsImm32Ime(IntPtr hkl)
|
||||
{
|
||||
if (hkl == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
return ((NativeMethods.IntPtrToInt32(hkl) & 0xf0000000) == 0xe0000000);
|
||||
}
|
||||
|
||||
private static int _inputLanguageId;
|
||||
|
||||
internal static void SetCurrentCulture()
|
||||
{
|
||||
var hkl = NativeMethods.GetKeyboardLayout(0);
|
||||
_inputLanguageId = NativeMethods.IntPtrToInt32(hkl) & 0xFFFF;
|
||||
}
|
||||
|
||||
private IntPtr _windowHandle;
|
||||
|
||||
private IntPtr _defaultImc;
|
||||
private IntPtr DefaultImc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_defaultImc == IntPtr.Zero)
|
||||
{
|
||||
IntPtr himc = NativeMethods.ImmCreateContext();
|
||||
|
||||
// Store the default imc to _defaultImc.
|
||||
_defaultImc = himc;
|
||||
}
|
||||
return _defaultImc;
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmCompositionStringHandler _compositionStringHandler;
|
||||
private static ImmCompositionIntHandler _compositionCursorHandler;
|
||||
|
||||
private bool _lastImmOpenStatus;
|
||||
|
||||
public Imm32Manager(IntPtr windowHandle)
|
||||
{
|
||||
_windowHandle = windowHandle;
|
||||
|
||||
_compositionStringHandler = new ImmCompositionStringHandler(DefaultImc, NativeMethods.GCS_COMPSTR);
|
||||
_compositionCursorHandler = new ImmCompositionIntHandler(DefaultImc, NativeMethods.GCS_CURSORPOS);
|
||||
}
|
||||
|
||||
public static Imm32Manager Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var defaultImm32Manager = InputMethod.DefaultImm32Manager;
|
||||
|
||||
if (defaultImm32Manager == null)
|
||||
{
|
||||
defaultImm32Manager = new Imm32Manager(InputMethod.WindowHandle);
|
||||
InputMethod.DefaultImm32Manager = defaultImm32Manager;
|
||||
}
|
||||
|
||||
return defaultImm32Manager;
|
||||
}
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (DefaultImc != IntPtr.Zero)
|
||||
{
|
||||
// Create a temporary system caret
|
||||
NativeMethods.CreateCaret(_windowHandle, IntPtr.Zero, 2, 10);
|
||||
NativeMethods.ImmAssociateContext(_windowHandle, _defaultImc);
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
NativeMethods.ImmAssociateContext(_windowHandle, IntPtr.Zero);
|
||||
NativeMethods.DestroyCaret();
|
||||
}
|
||||
|
||||
const int kCaretMargin = 1;
|
||||
|
||||
// Set candidate window position.
|
||||
// Borrowed from https://github.com/chromium/chromium/blob/master/ui/base/ime/win/imm32_manager.cc
|
||||
public void SetCandidateWindow(TsfSharp.Rect caretRect)
|
||||
{
|
||||
int x = caretRect.Left;
|
||||
int y = caretRect.Top;
|
||||
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE)
|
||||
{
|
||||
// Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
|
||||
// when a user disables TSF (Text Service Framework) and CUAS (Cicero
|
||||
// Unaware Application Support).
|
||||
// On the other hand, when a user enables TSF and CUAS, Chinese IMEs
|
||||
// ignore the position of the current system caret and uses the
|
||||
// parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
|
||||
// parameter CFS_CANDIDATEPOS.
|
||||
// Therefore, we do not only call ::ImmSetCandidateWindow() but also
|
||||
// set the positions of the temporary system caret.
|
||||
var candidateForm = new NativeMethods.CANDIDATEFORM();
|
||||
candidateForm.dwStyle = NativeMethods.CFS_CANDIDATEPOS;
|
||||
candidateForm.ptCurrentPos.X = x;
|
||||
candidateForm.ptCurrentPos.Y = y;
|
||||
NativeMethods.ImmSetCandidateWindow(_defaultImc, ref candidateForm);
|
||||
}
|
||||
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_JAPANESE)
|
||||
NativeMethods.SetCaretPos(x, caretRect.Bottom);
|
||||
else
|
||||
NativeMethods.SetCaretPos(x, y);
|
||||
|
||||
// Set composition window position also to ensure move the candidate window position.
|
||||
var compositionForm = new NativeMethods.COMPOSITIONFORM();
|
||||
compositionForm.dwStyle = NativeMethods.CFS_POINT;
|
||||
compositionForm.ptCurrentPos.X = x;
|
||||
compositionForm.ptCurrentPos.Y = y;
|
||||
NativeMethods.ImmSetCompositionWindow(_defaultImc, ref compositionForm);
|
||||
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_KOREAN)
|
||||
{
|
||||
// Chinese IMEs and Japanese IMEs require the upper-left corner of
|
||||
// the caret to move the position of their candidate windows.
|
||||
// On the other hand, Korean IMEs require the lower-left corner of the
|
||||
// caret to move their candidate windows.
|
||||
y += kCaretMargin;
|
||||
}
|
||||
|
||||
// Need to return here since some Chinese IMEs would stuck if set
|
||||
// candidate window position with CFS_EXCLUDE style.
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) return;
|
||||
|
||||
// Japanese IMEs and Korean IMEs also use the rectangle given to
|
||||
// ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
|
||||
// to move their candidate windows when a user disables TSF and CUAS.
|
||||
// Therefore, we also set this parameter here.
|
||||
var excludeRectangle = new NativeMethods.CANDIDATEFORM();
|
||||
compositionForm.dwStyle = NativeMethods.CFS_EXCLUDE;
|
||||
compositionForm.ptCurrentPos.X = x;
|
||||
compositionForm.ptCurrentPos.Y = y;
|
||||
compositionForm.rcArea.Left = x;
|
||||
compositionForm.rcArea.Top = y;
|
||||
compositionForm.rcArea.Right = caretRect.Right;
|
||||
compositionForm.rcArea.Bottom = caretRect.Bottom;
|
||||
NativeMethods.ImmSetCandidateWindow(_defaultImc, ref excludeRectangle);
|
||||
}
|
||||
|
||||
internal bool ProcessMessage(IntPtr hWnd, uint msg, ref IntPtr wParam, ref IntPtr lParam)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case NativeMethods.WM_INPUTLANGCHANGE:
|
||||
SetCurrentCulture();
|
||||
break;
|
||||
case NativeMethods.WM_IME_SETCONTEXT:
|
||||
if (wParam.ToInt32() == 1 && InputMethod.Enabled)
|
||||
{
|
||||
// Must re-associate ime context or things won't work.
|
||||
NativeMethods.ImmAssociateContext(_windowHandle, DefaultImc);
|
||||
|
||||
if (_lastImmOpenStatus)
|
||||
NativeMethods.ImmSetOpenStatus(DefaultImc, true);
|
||||
|
||||
var lParam64 = lParam.ToInt64();
|
||||
if (!InputMethod.ShowOSImeWindow)
|
||||
lParam64 &= ~NativeMethods.ISC_SHOWUICANDIDATEWINDOW;
|
||||
else
|
||||
lParam64 &= ~NativeMethods.ISC_SHOWUICOMPOSITIONWINDOW;
|
||||
lParam = (IntPtr)(int)lParam64;
|
||||
}
|
||||
break;
|
||||
case NativeMethods.WM_KILLFOCUS:
|
||||
_lastImmOpenStatus = NativeMethods.ImmGetOpenStatus(DefaultImc);
|
||||
break;
|
||||
case NativeMethods.WM_IME_NOTIFY:
|
||||
IMENotify(wParam.ToInt32());
|
||||
if (!InputMethod.ShowOSImeWindow)
|
||||
return true;
|
||||
break;
|
||||
case NativeMethods.WM_IME_STARTCOMPOSITION:
|
||||
//Debug.WriteLine("NativeMethods.WM_IME_STARTCOMPOSITION");
|
||||
IMEStartComposion(lParam.ToInt32());
|
||||
// Force to not show composition window, `lParam64 &= ~ISC_SHOWUICOMPOSITIONWINDOW` don't work sometime.
|
||||
return true;
|
||||
case NativeMethods.WM_IME_COMPOSITION:
|
||||
//Debug.WriteLine("NativeMethods.WM_IME_COMPOSITION");
|
||||
IMEComposition(lParam.ToInt32());
|
||||
break;
|
||||
case NativeMethods.WM_IME_ENDCOMPOSITION:
|
||||
//Debug.WriteLine("NativeMethods.WM_IME_ENDCOMPOSITION");
|
||||
IMEEndComposition(lParam.ToInt32());
|
||||
if (!InputMethod.ShowOSImeWindow)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void IMENotify(int WParam)
|
||||
{
|
||||
switch (WParam)
|
||||
{
|
||||
case NativeMethods.IMN_OPENCANDIDATE:
|
||||
case NativeMethods.IMN_CHANGECANDIDATE:
|
||||
IMEChangeCandidate();
|
||||
break;
|
||||
case NativeMethods.IMN_CLOSECANDIDATE:
|
||||
InputMethod.ClearCandidates();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void IMEChangeCandidate()
|
||||
{
|
||||
if (TextServicesLoader.ServicesInstalled) // TSF is enabled
|
||||
{
|
||||
if (!TextStore.Current.SupportUIElement) // But active IME not support UIElement
|
||||
UpdateCandidates(); // We have to fetch candidate list here.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal candidate list fetch in IMM32
|
||||
UpdateCandidates();
|
||||
// Send event on candidate updates
|
||||
InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value);
|
||||
|
||||
if (InputMethod.CandidateList != null)
|
||||
ArrayPool<IMEString>.Shared.Return(InputMethod.CandidateList);
|
||||
}
|
||||
|
||||
private unsafe void UpdateCandidates()
|
||||
{
|
||||
uint length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, IntPtr.Zero, 0);
|
||||
if (length > 0)
|
||||
{
|
||||
IntPtr pointer = Marshal.AllocHGlobal((int)length);
|
||||
length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, pointer, length);
|
||||
NativeMethods.CANDIDATELIST* cList = (NativeMethods.CANDIDATELIST*)pointer;
|
||||
|
||||
var selection = (int)cList->dwSelection;
|
||||
var pageStart = (int)cList->dwPageStart;
|
||||
var pageSize = (int)cList->dwPageSize;
|
||||
|
||||
selection -= pageStart;
|
||||
|
||||
IMEString[] candidates = ArrayPool<IMEString>.Shared.Rent(pageSize);
|
||||
|
||||
int i, j;
|
||||
for (i = pageStart, j = 0; i < cList->dwCount && j < pageSize; i++, j++)
|
||||
{
|
||||
int sOffset = Marshal.ReadInt32(pointer, 24 + 4 * i);
|
||||
candidates[j] = new IMEString(pointer + sOffset);
|
||||
}
|
||||
|
||||
//Debug.WriteLine("IMM========IMM");
|
||||
//Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, candidates:", pageStart, pageSize, selection);
|
||||
//for (int k = 0; k < candidates.Length; k++)
|
||||
// Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : "");
|
||||
//Debug.WriteLine("IMM++++++++IMM");
|
||||
|
||||
InputMethod.CandidatePageStart = pageStart;
|
||||
InputMethod.CandidatePageSize = pageSize;
|
||||
InputMethod.CandidateSelection = selection;
|
||||
InputMethod.CandidateList = candidates;
|
||||
|
||||
Marshal.FreeHGlobal(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearComposition()
|
||||
{
|
||||
_compositionStringHandler.Clear();
|
||||
}
|
||||
|
||||
private void IMEStartComposion(int lParam)
|
||||
{
|
||||
InputMethod.OnTextCompositionStarted(this);
|
||||
ClearComposition();
|
||||
}
|
||||
|
||||
private void IMEComposition(int lParam)
|
||||
{
|
||||
if (_compositionStringHandler.Update(lParam))
|
||||
{
|
||||
_compositionCursorHandler.Update();
|
||||
|
||||
InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void IMEEndComposition(int lParam)
|
||||
{
|
||||
InputMethod.ClearCandidates();
|
||||
ClearComposition();
|
||||
|
||||
InputMethod.OnTextCompositionEnded(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
internal abstract class ImmCompositionResultHandler
|
||||
{
|
||||
protected IntPtr _imeContext;
|
||||
|
||||
public int Flag { get; private set; }
|
||||
|
||||
internal ImmCompositionResultHandler(IntPtr imeContext, int flag)
|
||||
{
|
||||
this.Flag = flag;
|
||||
_imeContext = imeContext;
|
||||
}
|
||||
|
||||
internal virtual void Update() { }
|
||||
|
||||
internal bool Update(int lParam)
|
||||
{
|
||||
if ((lParam & Flag) == Flag)
|
||||
{
|
||||
Update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImmCompositionStringHandler : ImmCompositionResultHandler
|
||||
{
|
||||
internal const int BufferSize = 1024;
|
||||
private byte[] _byteBuffer = new byte[BufferSize];
|
||||
private int _byteCount;
|
||||
|
||||
private char[] _charBuffer = new char[BufferSize / 2];
|
||||
private int _charCount;
|
||||
|
||||
public char[] Values { get { return _charBuffer; } }
|
||||
public int Count { get { return _charCount; } }
|
||||
|
||||
public char this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index >= _charCount || index < 0)
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
return _charBuffer[index];
|
||||
}
|
||||
}
|
||||
|
||||
internal ImmCompositionStringHandler(IntPtr imeContext, int flag) : base(imeContext, flag)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (_charCount <= 0)
|
||||
return string.Empty;
|
||||
|
||||
return new string(_charBuffer, 0, _charCount);
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
Array.Clear(_byteBuffer, 0, _byteCount);
|
||||
_byteCount = 0;
|
||||
|
||||
Array.Clear(_charBuffer, 0, _charCount);
|
||||
_charCount = 0;
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
_byteCount = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0);
|
||||
IntPtr pointer = Marshal.AllocHGlobal(_byteCount);
|
||||
|
||||
try
|
||||
{
|
||||
Array.Clear(_byteBuffer, 0, _byteCount);
|
||||
|
||||
if (_byteCount > 0)
|
||||
{
|
||||
NativeMethods.ImmGetCompositionString(_imeContext, Flag, pointer, _byteCount);
|
||||
|
||||
Marshal.Copy(pointer, _byteBuffer, 0, _byteCount);
|
||||
|
||||
Array.Clear(_charBuffer, 0, _charCount);
|
||||
_charCount = Encoding.Unicode.GetChars(_byteBuffer, 0, _byteCount, _charBuffer, 0);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImmCompositionIntHandler : ImmCompositionResultHandler
|
||||
{
|
||||
public int Value { get; private set; }
|
||||
|
||||
internal ImmCompositionIntHandler(IntPtr imeContext, int flag) : base(imeContext, flag) { }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
Value = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
public static class InputMethod
|
||||
{
|
||||
private static IntPtr _windowHandle;
|
||||
public static IntPtr WindowHandle { get { return _windowHandle; } }
|
||||
|
||||
private static IntPtr _prevWndProc;
|
||||
private static NativeMethods.WndProcDelegate _wndProcDelegate;
|
||||
|
||||
private static TextServicesContext _textServicesContext;
|
||||
internal static TextServicesContext TextServicesContext
|
||||
{
|
||||
get { return _textServicesContext; }
|
||||
set { _textServicesContext = value; }
|
||||
}
|
||||
|
||||
private static TextStore _defaultTextStore;
|
||||
internal static TextStore DefaultTextStore
|
||||
{
|
||||
get { return _defaultTextStore; }
|
||||
set { _defaultTextStore = value; }
|
||||
}
|
||||
|
||||
private static Imm32Manager _defaultImm32Manager;
|
||||
internal static Imm32Manager DefaultImm32Manager
|
||||
{
|
||||
get { return _defaultImm32Manager; }
|
||||
set { _defaultImm32Manager = value; }
|
||||
}
|
||||
|
||||
private static bool _enabled;
|
||||
public static bool Enabled
|
||||
{
|
||||
get { return _enabled; }
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
EnableOrDisableInputMethod(_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
internal static TsfSharp.Rect TextInputRect;
|
||||
|
||||
/// <summary>
|
||||
/// Set the position of the candidate window rendered by the OS.
|
||||
/// Let the OS render the candidate window by set param "showOSImeWindow" to <c>true</c> on <see cref="Initialize"/>.
|
||||
/// </summary>
|
||||
public static void SetTextInputRect(int x, int y, int width, int height)
|
||||
{
|
||||
if (!_showOSImeWindow) return;
|
||||
|
||||
TextInputRect.Left = x;
|
||||
TextInputRect.Top = y;
|
||||
TextInputRect.Right = x + width;
|
||||
TextInputRect.Bottom = y + height;
|
||||
|
||||
if (Imm32Manager.ImmEnabled)
|
||||
Imm32Manager.Current.SetCandidateWindow(TextInputRect);
|
||||
}
|
||||
|
||||
private static bool _showOSImeWindow = false;
|
||||
|
||||
/// <summary>
|
||||
/// Return if let OS render IME Candidate window or not.
|
||||
/// </summary>
|
||||
public static bool ShowOSImeWindow { get { return _showOSImeWindow; } }
|
||||
|
||||
internal static int CandidatePageStart;
|
||||
internal static int CandidatePageSize;
|
||||
internal static int CandidateSelection;
|
||||
internal static IMEString[] CandidateList;
|
||||
|
||||
internal static void ClearCandidates()
|
||||
{
|
||||
CandidateList = null;
|
||||
CandidatePageStart = 0;
|
||||
CandidatePageSize = 0;
|
||||
CandidateSelection = 0;
|
||||
}
|
||||
|
||||
public static event EventHandler<IMETextCompositionEventArgs> TextComposition;
|
||||
public static event EventHandler<IMETextInputEventArgs> TextInput;
|
||||
public static event EventHandler<string> CommitTextComposition;
|
||||
|
||||
public static TextInputCallback TextInputCallback { get; set; }
|
||||
public static TextCompositionCallback TextCompositionCallback { get; set; }
|
||||
public static CommitTextCompositionCallback CommitTextCompositionCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize InputMethod with a Window Handle.
|
||||
/// Let the OS render the candidate window by set <see paramref="showOSImeWindow"/> to <c>true</c>.
|
||||
/// </summary>
|
||||
public static void Initialize(IntPtr windowHandle, bool showOSImeWindow = true)
|
||||
{
|
||||
if (_windowHandle != IntPtr.Zero)
|
||||
throw new InvalidOperationException("InputMethod can only be initialized once!");
|
||||
|
||||
_windowHandle = windowHandle;
|
||||
_showOSImeWindow = showOSImeWindow;
|
||||
|
||||
_wndProcDelegate = new NativeMethods.WndProcDelegate(WndProc);
|
||||
_prevWndProc = (IntPtr)NativeMethods.SetWindowLongPtr(_windowHandle, NativeMethods.GWL_WNDPROC,
|
||||
Marshal.GetFunctionPointerForDelegate(_wndProcDelegate));
|
||||
}
|
||||
|
||||
internal static void OnTextInput(object sender, char character)
|
||||
{
|
||||
if (TextInput != null)
|
||||
TextInput.Invoke(sender, new IMETextInputEventArgs(character));
|
||||
|
||||
if (TextInputCallback != null)
|
||||
TextInputCallback(character);
|
||||
}
|
||||
|
||||
// Some Chinese IME only send composition start event but no composition update event.
|
||||
// We need this to ensure candidate window position can be set in time.
|
||||
internal static void OnTextCompositionStarted(object sender)
|
||||
{
|
||||
if (TextComposition != null)
|
||||
TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0));
|
||||
|
||||
if (TextCompositionCallback != null)
|
||||
TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0);
|
||||
}
|
||||
|
||||
// On text composition update.
|
||||
internal static void OnTextComposition(object sender, IMEString compositionText, int cursorPos)
|
||||
{
|
||||
if (compositionText.Count == 0) // Crash guard
|
||||
cursorPos = 0;
|
||||
|
||||
if (cursorPos > compositionText.Count) // Another crash guard
|
||||
cursorPos = compositionText.Count;
|
||||
|
||||
if (TextComposition != null)
|
||||
{
|
||||
TextComposition.Invoke(sender,
|
||||
new IMETextCompositionEventArgs(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection));
|
||||
}
|
||||
|
||||
if (TextCompositionCallback != null)
|
||||
TextCompositionCallback(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection);
|
||||
}
|
||||
|
||||
internal static void OnTextCompositionResult(object sender, string compositionResult)
|
||||
{
|
||||
if (CommitTextComposition != null)
|
||||
CommitTextComposition.Invoke(sender, compositionResult);
|
||||
|
||||
if (CommitTextCompositionCallback != null)
|
||||
CommitTextCompositionCallback(compositionResult);
|
||||
}
|
||||
|
||||
internal static void OnTextCompositionEnded(object sender)
|
||||
{
|
||||
if (TextComposition != null)
|
||||
TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0));
|
||||
|
||||
if (TextCompositionCallback != null)
|
||||
TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0);
|
||||
}
|
||||
|
||||
private static void EnableOrDisableInputMethod(bool bEnabled)
|
||||
{
|
||||
// InputMethod enable/disabled status was changed on the current focus Element.
|
||||
if (TextServicesLoader.ServicesInstalled)
|
||||
{
|
||||
if (bEnabled)
|
||||
TextServicesContext.Current.SetFocusOnDefaultTextStore();
|
||||
else
|
||||
TextServicesContext.Current.SetFocusOnEmptyDim();
|
||||
}
|
||||
|
||||
// Under IMM32 enabled system, we associate default hIMC or null hIMC.
|
||||
if (Imm32Manager.ImmEnabled)
|
||||
{
|
||||
if (bEnabled)
|
||||
Imm32Manager.Current.Enable();
|
||||
else
|
||||
Imm32Manager.Current.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (Imm32Manager.ImmEnabled)
|
||||
{
|
||||
if (Imm32Manager.Current.ProcessMessage(hWnd, msg, ref wParam, ref lParam))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case NativeMethods.WM_DESTROY:
|
||||
TextServicesContext.Current.Uninitialize(true);
|
||||
break;
|
||||
case NativeMethods.WM_CHAR:
|
||||
{
|
||||
if (InputMethod.Enabled)
|
||||
InputMethod.OnTextInput(null, (char)wParam.ToInt32());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NativeMethods.CallWindowProc(_prevWndProc, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom windows message pumping to fix frame stuck issue.
|
||||
/// Normally, you need call this method in <see cref="Application.Idle" /> handler.
|
||||
/// </summary>
|
||||
public static void PumpMessage()
|
||||
{
|
||||
if (!Enabled) return;
|
||||
if (!TextServicesLoader.ServicesInstalled) return;
|
||||
|
||||
bool result;
|
||||
var msg = new NativeMethods.NativeMessage();
|
||||
|
||||
do
|
||||
{
|
||||
result = NativeMethods.PeekMessage(out msg, _windowHandle, 0, 0, NativeMethods.PM_REMOVE);
|
||||
|
||||
if (result)
|
||||
{
|
||||
NativeMethods.TranslateMessage(ref msg);
|
||||
NativeMethods.DispatchMessage(ref msg);
|
||||
}
|
||||
} while (result);
|
||||
|
||||
NativeMethods.PostMessage(_windowHandle, NativeMethods.WM_NULL, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ImeSharp.Native
|
||||
{
|
||||
public partial class NativeMethods
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public const int S_OK = 0x00000000;
|
||||
public const int S_FALSE = 0x00000001;
|
||||
public const int E_FAIL = unchecked((int)0x80004005);
|
||||
public const int E_INVALIDARG = unchecked((int)0x80070057);
|
||||
public const int E_NOTIMPL = unchecked((int)0x80004001);
|
||||
|
||||
public const int WM_KEYFIRST = 0x0100;
|
||||
public const int WM_KEYDOWN = 0x0100;
|
||||
public const int WM_KEYUP = 0x0101;
|
||||
public const int WM_CHAR = 0x0102;
|
||||
public const int WM_DEADCHAR = 0x0103;
|
||||
public const int WM_SYSKEYDOWN = 0x0104;
|
||||
public const int WM_SYSKEYUP = 0x0105;
|
||||
public const int WM_SYSCHAR = 0x0106;
|
||||
public const int WM_SYSDEADCHAR = 0x0107;
|
||||
public const int WM_UNICHAR = 0x0109;
|
||||
public const int WM_KEYLAST = 0x0109;
|
||||
public const int UNICODE_NOCHAR = 0xFFFF;
|
||||
|
||||
public const int WM_NOTIFY = 0x004E;
|
||||
public const int WM_INPUTLANGCHANGEREQUEST = 0x0050;
|
||||
public const int WM_INPUTLANGCHANGE = 0x0051;
|
||||
public const int WM_TCARD = 0x0052;
|
||||
public const int WM_HELP = 0x0053;
|
||||
public const int WM_USERCHANGED = 0x0054;
|
||||
public const int WM_NOTIFYFORMAT = 0x0055;
|
||||
|
||||
public const int GWL_WNDPROC = -4;
|
||||
|
||||
public const int WM_ACTIVATE = 0x0006;
|
||||
// WM_ACTIVATE state values
|
||||
public const int WA_INACTIVE = 0;
|
||||
public const int WA_ACTIVE = 1;
|
||||
public const int WA_CLICKACTIVE = 2;
|
||||
|
||||
public const int WM_SETFOCUS = 0x0007;
|
||||
public const int WM_KILLFOCUS = 0x0008;
|
||||
|
||||
public const int WM_DESTROY = 0x0002;
|
||||
public const int WM_NULL = 0x0000;
|
||||
public const int WM_QUIT = 0x0012;
|
||||
|
||||
public const int CLSCTX_INPROC_SERVER = 0x1;
|
||||
|
||||
public const int PM_NOREMOVE = 0x0000;
|
||||
public const int PM_REMOVE = 0x0001;
|
||||
public const int PM_NOYIELD = 0x0002;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Structs
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeMessage
|
||||
{
|
||||
public IntPtr handle;
|
||||
public uint msg;
|
||||
public IntPtr wParam;
|
||||
public IntPtr lParam;
|
||||
public uint time;
|
||||
public int ptX;
|
||||
public int ptY;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int GetSystemMetrics(SM nIndex);
|
||||
|
||||
// We have this wrapper because casting IntPtr to int may
|
||||
// generate OverflowException when one of high 32 bits is set.
|
||||
public static int IntPtrToInt32(IntPtr intPtr)
|
||||
{
|
||||
return unchecked((int)intPtr.ToInt64());
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr GetKeyboardLayout(int dwLayout);
|
||||
|
||||
public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
// This static method is required because legacy OSes do not support
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
|
||||
else
|
||||
return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Unicode)]
|
||||
private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool GetWindowRect(IntPtr hwnd, out TsfSharp.Rect lpRect);
|
||||
|
||||
[DllImport("user32", ExactSpelling = true, SetLastError = true)]
|
||||
public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, ref TsfSharp.Rect rect, [MarshalAs(UnmanagedType.U4)] int cPoints);
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, StringBuilder lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool TranslateMessage(ref NativeMessage lpMsg);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr DispatchMessage(ref NativeMessage lpmsg);
|
||||
|
||||
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern bool PeekMessage(out NativeMessage msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
|
||||
|
||||
|
||||
[DllImport("ole32.dll", ExactSpelling = true, EntryPoint = "CoCreateInstance", PreserveSig = true)]
|
||||
public static extern int CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pUnkOuter, int dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ImeSharp.Native
|
||||
{
|
||||
public partial class NativeMethods
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public const int WM_IME_SETCONTEXT = 0x0281;
|
||||
public const int WM_IME_NOTIFY = 0x0282;
|
||||
public const int WM_IME_CONTROL = 0x0283;
|
||||
public const int WM_IME_COMPOSITIONFULL = 0x0284;
|
||||
public const int WM_IME_SELECT = 0x0285;
|
||||
public const int WM_IME_CHAR = 0x0286;
|
||||
public const int WM_IME_REQUEST = 0x0288;
|
||||
public const int WM_IME_KEYDOWN = 0x0290;
|
||||
public const int WM_IME_KEYUP = 0x0291;
|
||||
public const int WM_IME_STARTCOMPOSITION = 0x010D;
|
||||
public const int WM_IME_ENDCOMPOSITION = 0x010E;
|
||||
public const int WM_IME_COMPOSITION = 0x010F;
|
||||
public const int WM_IME_KEYLAST = 0x010F;
|
||||
|
||||
// wParam of report message WM_IME_NOTIFY
|
||||
public const int IMN_CLOSESTATUSWINDOW = 0x0001;
|
||||
public const int IMN_OPENSTATUSWINDOW = 0x0002;
|
||||
public const int IMN_CHANGECANDIDATE = 0x0003;
|
||||
public const int IMN_CLOSECANDIDATE = 0x0004;
|
||||
public const int IMN_OPENCANDIDATE = 0x0005;
|
||||
public const int IMN_SETCONVERSIONMODE = 0x0006;
|
||||
public const int IMN_SETSENTENCEMODE = 0x0007;
|
||||
public const int IMN_SETOPENSTATUS = 0x0008;
|
||||
public const int IMN_SETCANDIDATEPOS = 0x0009;
|
||||
public const int IMN_SETCOMPOSITIONFONT = 0x000A;
|
||||
public const int IMN_SETCOMPOSITIONWINDOW = 0x000B;
|
||||
public const int IMN_SETSTATUSWINDOWPOS = 0x000C;
|
||||
public const int IMN_GUIDELINE = 0x000D;
|
||||
public const int IMN_PRIVATE = 0x000E;
|
||||
|
||||
// wParam of report message WM_IME_REQUEST
|
||||
public const int IMR_COMPOSITIONWINDOW = 0x0001;
|
||||
public const int IMR_CANDIDATEWINDOW = 0x0002;
|
||||
public const int IMR_COMPOSITIONFONT = 0x0003;
|
||||
public const int IMR_RECONVERTSTRING = 0x0004;
|
||||
public const int IMR_CONFIRMRECONVERTSTRING = 0x0005;
|
||||
public const int IMR_QUERYCHARPOSITION = 0x0006;
|
||||
public const int IMR_DOCUMENTFEED = 0x0007;
|
||||
|
||||
// parameter of ImmGetCompositionString
|
||||
public const int GCS_COMPREADSTR = 0x0001;
|
||||
public const int GCS_COMPREADATTR = 0x0002;
|
||||
public const int GCS_COMPREADCLAUSE = 0x0004;
|
||||
public const int GCS_COMPSTR = 0x0008;
|
||||
public const int GCS_COMPATTR = 0x0010;
|
||||
public const int GCS_COMPCLAUSE = 0x0020;
|
||||
public const int GCS_CURSORPOS = 0x0080;
|
||||
public const int GCS_DELTASTART = 0x0100;
|
||||
public const int GCS_RESULTREADSTR = 0x0200;
|
||||
public const int GCS_RESULTREADCLAUSE = 0x0400;
|
||||
public const int GCS_RESULTSTR = 0x0800;
|
||||
public const int GCS_RESULTCLAUSE = 0x1000;
|
||||
|
||||
public const int GCS_COMP = (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE);
|
||||
public const int GCS_COMPREAD = (GCS_COMPREADSTR | GCS_COMPREADATTR | GCS_COMPREADCLAUSE);
|
||||
public const int GCS_RESULT = (GCS_RESULTSTR | GCS_RESULTCLAUSE);
|
||||
public const int GCS_RESULTREAD = (GCS_RESULTREADSTR | GCS_RESULTREADCLAUSE);
|
||||
|
||||
public const int CFS_CANDIDATEPOS = 0x0040;
|
||||
public const int CFS_POINT = 0x0002;
|
||||
public const int CFS_EXCLUDE = 0x0080;
|
||||
|
||||
// lParam for WM_IME_SETCONTEXT
|
||||
public const long ISC_SHOWUICANDIDATEWINDOW = 0x00000001;
|
||||
public const long ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000;
|
||||
public const long ISC_SHOWUIGUIDELINE = 0x40000000;
|
||||
public const long ISC_SHOWUIALLCANDIDATEWINDOW = 0x0000000F;
|
||||
public const long ISC_SHOWUIALL = 0xC000000F;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct CANDIDATELIST
|
||||
{
|
||||
public uint dwSize;
|
||||
public uint dwStyle;
|
||||
public uint dwCount;
|
||||
public uint dwSelection;
|
||||
public uint dwPageStart;
|
||||
public uint dwPageSize;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)]
|
||||
public fixed uint dwOffset[1];
|
||||
}
|
||||
|
||||
// CANDIDATEFORM structures
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CANDIDATEFORM
|
||||
{
|
||||
public int dwIndex;
|
||||
public int dwStyle;
|
||||
public TsfSharp.Point ptCurrentPos;
|
||||
public TsfSharp.Rect rcArea;
|
||||
}
|
||||
|
||||
// COMPOSITIONFORM structures
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct COMPOSITIONFORM
|
||||
{
|
||||
public int dwStyle;
|
||||
public TsfSharp.Point ptCurrentPos;
|
||||
public TsfSharp.Rect rcArea;
|
||||
}
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmCreateContext();
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmDestroyContext(IntPtr hIMC);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
|
||||
|
||||
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern uint ImmGetCandidateList(IntPtr hIMC, uint deIndex, IntPtr candidateList, uint dwBufLen);
|
||||
|
||||
[DllImport("imm32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int ImmGetCompositionString(IntPtr hIMC, int CompositionStringFlag, IntPtr buffer, int bufferLength);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmGetContext(IntPtr hWnd);
|
||||
|
||||
[DllImport("Imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmGetOpenStatus(IntPtr hIMC);
|
||||
|
||||
[DllImport("Imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmSetOpenStatus(IntPtr hIMC, bool open);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmSetCandidateWindow(IntPtr hIMC, ref CANDIDATEFORM candidateForm);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern int ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM compForm);
|
||||
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool DestroyCaret();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool SetCaretPos(int x, int y);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ImeSharp.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// SystemMetrics. SM_*
|
||||
/// </summary>
|
||||
public enum SM
|
||||
{
|
||||
CXSCREEN = 0,
|
||||
CYSCREEN = 1,
|
||||
CXVSCROLL = 2,
|
||||
CYHSCROLL = 3,
|
||||
CYCAPTION = 4,
|
||||
CXBORDER = 5,
|
||||
CYBORDER = 6,
|
||||
CXFIXEDFRAME = 7,
|
||||
CYFIXEDFRAME = 8,
|
||||
CYVTHUMB = 9,
|
||||
CXHTHUMB = 10,
|
||||
CXICON = 11,
|
||||
CYICON = 12,
|
||||
CXCURSOR = 13,
|
||||
CYCURSOR = 14,
|
||||
CYMENU = 15,
|
||||
CXFULLSCREEN = 16,
|
||||
CYFULLSCREEN = 17,
|
||||
CYKANJIWINDOW = 18,
|
||||
MOUSEPRESENT = 19,
|
||||
CYVSCROLL = 20,
|
||||
CXHSCROLL = 21,
|
||||
DEBUG = 22,
|
||||
SWAPBUTTON = 23,
|
||||
CXMIN = 28,
|
||||
CYMIN = 29,
|
||||
CXSIZE = 30,
|
||||
CYSIZE = 31,
|
||||
CXFRAME = 32,
|
||||
CXSIZEFRAME = CXFRAME,
|
||||
CYFRAME = 33,
|
||||
CYSIZEFRAME = CYFRAME,
|
||||
CXMINTRACK = 34,
|
||||
CYMINTRACK = 35,
|
||||
CXDOUBLECLK = 36,
|
||||
CYDOUBLECLK = 37,
|
||||
CXICONSPACING = 38,
|
||||
CYICONSPACING = 39,
|
||||
MENUDROPALIGNMENT = 40,
|
||||
PENWINDOWS = 41,
|
||||
DBCSENABLED = 42,
|
||||
CMOUSEBUTTONS = 43,
|
||||
SECURE = 44,
|
||||
CXEDGE = 45,
|
||||
CYEDGE = 46,
|
||||
CXMINSPACING = 47,
|
||||
CYMINSPACING = 48,
|
||||
CXSMICON = 49,
|
||||
CYSMICON = 50,
|
||||
CYSMCAPTION = 51,
|
||||
CXSMSIZE = 52,
|
||||
CYSMSIZE = 53,
|
||||
CXMENUSIZE = 54,
|
||||
CYMENUSIZE = 55,
|
||||
ARRANGE = 56,
|
||||
CXMINIMIZED = 57,
|
||||
CYMINIMIZED = 58,
|
||||
CXMAXTRACK = 59,
|
||||
CYMAXTRACK = 60,
|
||||
CXMAXIMIZED = 61,
|
||||
CYMAXIMIZED = 62,
|
||||
NETWORK = 63,
|
||||
CLEANBOOT = 67,
|
||||
CXDRAG = 68,
|
||||
CYDRAG = 69,
|
||||
SHOWSOUNDS = 70,
|
||||
CXMENUCHECK = 71,
|
||||
CYMENUCHECK = 72,
|
||||
SLOWMACHINE = 73,
|
||||
MIDEASTENABLED = 74,
|
||||
MOUSEWHEELPRESENT = 75,
|
||||
XVIRTUALSCREEN = 76,
|
||||
YVIRTUALSCREEN = 77,
|
||||
CXVIRTUALSCREEN = 78,
|
||||
CYVIRTUALSCREEN = 79,
|
||||
CMONITORS = 80,
|
||||
SAMEDISPLAYFORMAT = 81,
|
||||
IMMENABLED = 82,
|
||||
CXFOCUSBORDER = 83,
|
||||
CYFOCUSBORDER = 84,
|
||||
TABLETPC = 86,
|
||||
MEDIACENTER = 87,
|
||||
REMOTESESSION = 0x1000,
|
||||
REMOTECONTROL = 0x2001,
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains properties that are queries into the system's various settings.
|
||||
/// </summary>
|
||||
internal sealed class SafeSystemMetrics
|
||||
{
|
||||
|
||||
private SafeSystemMetrics()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CXDOUBLECLK
|
||||
/// </summary>
|
||||
public static int DoubleClickDeltaX
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CXDOUBLECLK); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CYDOUBLECLK
|
||||
/// </summary>
|
||||
public static int DoubleClickDeltaY
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CYDOUBLECLK); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CXDRAG
|
||||
/// </summary>
|
||||
public static int DragDeltaX
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CXDRAG); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CYDRAG
|
||||
/// </summary>
|
||||
public static int DragDeltaY
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CYDRAG); }
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Is an IMM enabled ? Maps to SM_IMMENABLED
|
||||
///</summary>
|
||||
public static bool IsImmEnabled
|
||||
{
|
||||
get { return (NativeMethods.GetSystemMetrics(SM.IMMENABLED) != 0); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace ImeSharp
|
||||
{
|
||||
public delegate void TextInputCallback(char character);
|
||||
public delegate void TextCompositionCallback(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection);
|
||||
public delegate void CommitTextCompositionCallback(string text);
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using ImeSharp.Native;
|
||||
using TsfSharp;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// TextServicesContext class
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// This class manages the ITfThreadMgr, EmptyDim and the reference to
|
||||
/// the default TextStore.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
internal class TextServicesContext
|
||||
{
|
||||
public const int TF_POPF_ALL = 0x0001;
|
||||
public const int TF_INVALID_COOKIE = -1;
|
||||
public static readonly Guid IID_ITfUIElementSink = new Guid(0xea1ea136, 0x19df, 0x11d7, 0xa6, 0xd2, 0x00, 0x06, 0x5b, 0x84, 0x43, 0x5c);
|
||||
public static readonly Guid IID_ITfTextEditSink = new Guid(0x8127d409, 0xccd3, 0x4683, 0x96, 0x7a, 0xb4, 0x3d, 0x5b, 0x48, 0x2b, 0xf7);
|
||||
|
||||
|
||||
public static TextServicesContext Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (InputMethod.TextServicesContext == null)
|
||||
InputMethod.TextServicesContext = new TextServicesContext();
|
||||
|
||||
return InputMethod.TextServicesContext;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a TextServicesContext.
|
||||
/// </summary>
|
||||
private TextServicesContext()
|
||||
{
|
||||
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
|
||||
Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!");
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Releases all unmanaged resources allocated by the
|
||||
/// TextServicesContext.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// if appDomainShutdown == false, this method must be called on the
|
||||
/// Dispatcher thread. Otherwise, the caller is an AppDomain.Shutdown
|
||||
/// listener, and is calling from a worker thread.
|
||||
/// </remarks>
|
||||
public void Uninitialize(bool appDomainShutdown)
|
||||
{
|
||||
// Unregister DefaultTextStore.
|
||||
if (_defaultTextStore != null)
|
||||
{
|
||||
UnadviseSinks();
|
||||
if (_defaultTextStore.DocumentManager != null)
|
||||
{
|
||||
_defaultTextStore.DocumentManager.Pop(TF_POPF_ALL);
|
||||
_defaultTextStore.DocumentManager.Dispose();
|
||||
_defaultTextStore.DocumentManager = null;
|
||||
}
|
||||
|
||||
_defaultTextStore = null;
|
||||
}
|
||||
|
||||
// Free up any remaining textstores.
|
||||
if (_istimactivated == true)
|
||||
{
|
||||
// Shut down the thread manager when the last TextStore goes away.
|
||||
// On XP, if we're called on a worker thread (during AppDomain shutdown)
|
||||
// we can't call call any methods on _threadManager. The problem is
|
||||
// that there's no proxy registered for ITfThreadMgr on OS versions
|
||||
// previous to Vista. Not calling Deactivate will leak the IMEs, but
|
||||
// in practice (1) they're singletons, so it's not unbounded; and (2)
|
||||
// most applications will share the thread with other AppDomains that
|
||||
// have a UI, in which case the IME won't be released until the process
|
||||
// shuts down in any case. In theory we could also work around this
|
||||
// problem by creating our own XP proxy/stub implementation, which would
|
||||
// be added to WPF setup....
|
||||
if (!appDomainShutdown || System.Environment.OSVersion.Version.Major >= 6)
|
||||
{
|
||||
_threadManager.Deactivate();
|
||||
}
|
||||
_istimactivated = false;
|
||||
}
|
||||
|
||||
// Release the empty dim.
|
||||
if (_dimEmpty != null)
|
||||
{
|
||||
if (_dimEmpty != null)
|
||||
{
|
||||
_dimEmpty.Dispose();
|
||||
}
|
||||
_dimEmpty = null;
|
||||
}
|
||||
|
||||
// Release the ThreadManager.
|
||||
// We don't do this in UnregisterTextStore because someone may have
|
||||
// called get_ThreadManager after the last TextStore was unregistered.
|
||||
if (_threadManager != null)
|
||||
{
|
||||
if (_threadManager != null)
|
||||
{
|
||||
_threadManager.Dispose();
|
||||
}
|
||||
_threadManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by framework's TextStore class. This method registers a
|
||||
// document with TSF. The TextServicesContext must maintain this list
|
||||
// to ensure all native resources are released after gc or uninitialization.
|
||||
public void RegisterTextStore(TextStore defaultTextStore)
|
||||
{
|
||||
_defaultTextStore = defaultTextStore;
|
||||
|
||||
ITfThreadMgrEx threadManager = ThreadManager;
|
||||
|
||||
if (threadManager != null)
|
||||
{
|
||||
ITfDocumentMgr doc;
|
||||
int editCookie = TF_INVALID_COOKIE;
|
||||
|
||||
// Activate TSF on this thread if this is the first TextStore.
|
||||
if (_istimactivated == false)
|
||||
{
|
||||
//temp variable created to retrieve the value
|
||||
// which is then stored in the critical data.
|
||||
if (InputMethod.ShowOSImeWindow)
|
||||
_clientId = threadManager.Activate();
|
||||
else
|
||||
_clientId = threadManager.ActivateEx(TfTmaeFlags.Uielementenabledonly);
|
||||
|
||||
_istimactivated = true;
|
||||
}
|
||||
|
||||
// Create a TSF document.
|
||||
doc = threadManager.CreateDocumentMgr();
|
||||
_defaultTextStore.DocumentManager = doc;
|
||||
|
||||
doc.CreateContext(_clientId, 0 /* flags */, _defaultTextStore, out _editContext, out editCookie);
|
||||
_defaultTextStore.EditCookie = editCookie;
|
||||
_contextOwnerServices = _editContext.QueryInterface<ITfContextOwnerServices>();
|
||||
|
||||
doc.Push(_editContext);
|
||||
|
||||
AdviseSinks();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetFocusOnDefaultTextStore()
|
||||
{
|
||||
SetFocusOnDim(TextStore.Current.DocumentManager);
|
||||
}
|
||||
|
||||
public void SetFocusOnEmptyDim()
|
||||
{
|
||||
SetFocusOnDim(EmptyDocumentManager);
|
||||
}
|
||||
|
||||
|
||||
#endregion public Methods
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// The default ITfThreadMgrEx object.
|
||||
/// </summary>
|
||||
public ITfThreadMgrEx ThreadManager
|
||||
{
|
||||
// The ITfThreadMgr for this thread.
|
||||
get
|
||||
{
|
||||
if (_threadManager == null)
|
||||
{
|
||||
ITfThreadMgr threadMgr = null;
|
||||
try
|
||||
{
|
||||
// This might fail in CoreRT
|
||||
threadMgr = Tsf.GetThreadMgr();
|
||||
}
|
||||
catch (SharpGen.Runtime.SharpGenException)
|
||||
{
|
||||
threadMgr = null;
|
||||
}
|
||||
|
||||
// Dispose previous ITfThreadMgr in case something weird happens
|
||||
if (threadMgr != null)
|
||||
{
|
||||
if (threadMgr.IsThreadFocus)
|
||||
threadMgr.Deactivate();
|
||||
threadMgr.Dispose();
|
||||
}
|
||||
|
||||
_threadManager = TextServicesLoader.Load();
|
||||
|
||||
_uiElementMgr = _threadManager.QueryInterface<ITfUIElementMgr>();
|
||||
}
|
||||
|
||||
return _threadManager;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the created ITfContext object.
|
||||
/// </summary>
|
||||
public ITfContext EditContext
|
||||
{
|
||||
get { return _editContext; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the created ITfUIElementMgr object.
|
||||
/// </summary>
|
||||
public ITfUIElementMgr UIElementMgr
|
||||
{
|
||||
get { return _uiElementMgr; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the created ITfContextOwnerServices object.
|
||||
/// </summary>
|
||||
public ITfContextOwnerServices ContextOwnerServices
|
||||
{
|
||||
get { return _contextOwnerServices; }
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Events
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
private void SetFocusOnDim(ITfDocumentMgr dim)
|
||||
{
|
||||
ITfThreadMgrEx threadmgr = ThreadManager;
|
||||
|
||||
if (threadmgr != null)
|
||||
{
|
||||
ITfDocumentMgr prevDocMgr = threadmgr.AssociateFocus(InputMethod.WindowHandle, dim);
|
||||
}
|
||||
}
|
||||
|
||||
private void AdviseSinks()
|
||||
{
|
||||
var source = _uiElementMgr.QueryInterface<ITfSource>();
|
||||
var guid = IID_ITfUIElementSink;
|
||||
int sinkCookie = source.AdviseSink(guid, _defaultTextStore);
|
||||
_defaultTextStore.UIElementSinkCookie = sinkCookie;
|
||||
source.Dispose();
|
||||
|
||||
source = _editContext.QueryInterface<ITfSource>();
|
||||
guid = IID_ITfTextEditSink;
|
||||
sinkCookie = source.AdviseSink(guid, _defaultTextStore);
|
||||
_defaultTextStore.TextEditSinkCookie = sinkCookie;
|
||||
source.Dispose();
|
||||
}
|
||||
|
||||
private void UnadviseSinks()
|
||||
{
|
||||
var source = _uiElementMgr.QueryInterface<ITfSource>();
|
||||
|
||||
if (_defaultTextStore.UIElementSinkCookie != TF_INVALID_COOKIE)
|
||||
{
|
||||
source.UnadviseSink(_defaultTextStore.UIElementSinkCookie);
|
||||
_defaultTextStore.UIElementSinkCookie = TF_INVALID_COOKIE;
|
||||
}
|
||||
source.Dispose();
|
||||
|
||||
source = _editContext.QueryInterface<ITfSource>();
|
||||
if (_defaultTextStore.TextEditSinkCookie != TF_INVALID_COOKIE)
|
||||
{
|
||||
source.UnadviseSink(_defaultTextStore.TextEditSinkCookie);
|
||||
_defaultTextStore.TextEditSinkCookie = TF_INVALID_COOKIE;
|
||||
}
|
||||
source.Dispose();
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
// Create an empty dim on demand.
|
||||
private ITfDocumentMgr EmptyDocumentManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dimEmpty == null)
|
||||
{
|
||||
ITfThreadMgrEx threadManager = ThreadManager;
|
||||
if (threadManager == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ITfDocumentMgr dimEmptyTemp;
|
||||
// Create a TSF document.
|
||||
dimEmptyTemp = threadManager.CreateDocumentMgr();
|
||||
_dimEmpty = dimEmptyTemp;
|
||||
}
|
||||
return _dimEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Fields
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private TextStore _defaultTextStore;
|
||||
|
||||
private ITfContext _editContext;
|
||||
private ITfUIElementMgr _uiElementMgr;
|
||||
private ITfContextOwnerServices _contextOwnerServices;
|
||||
|
||||
// This is true if thread manager is activated.
|
||||
private bool _istimactivated;
|
||||
|
||||
// The root TSF object, created on demand.
|
||||
private ITfThreadMgrEx _threadManager;
|
||||
|
||||
// TSF ClientId from Activate call.
|
||||
private int _clientId;
|
||||
|
||||
// The empty dim for this thread. Created on demand.
|
||||
private ITfDocumentMgr _dimEmpty;
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Microsoft.Win32;
|
||||
using ImeSharp.Native;
|
||||
using TsfSharp;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
// Creates ITfThreadMgr instances, the root object of the Text Services
|
||||
// Framework.
|
||||
internal class TextServicesLoader
|
||||
{
|
||||
public static readonly Guid CLSID_TF_ThreadMgr = new Guid("529a9e6b-6587-4f23-ab9e-9c7d683e3c50");
|
||||
public static readonly Guid IID_ITfThreadMgr = new Guid("aa80e801-2021-11d2-93e0-0060b067b86e");
|
||||
public static readonly Guid IID_ITfThreadMgrEx = new Guid("3e90ade3-7594-4cb0-bb58-69628f5f458c");
|
||||
public static readonly Guid IID_ITfThreadMgr2 = new Guid("0AB198EF-6477-4EE8-8812-6780EDB82D5E");
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Constructors
|
||||
|
||||
// Private ctor to prevent anyone from instantiating this static class.
|
||||
private TextServicesLoader() { }
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Loads an instance of the Text Services Framework.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// May return null if no text services are available.
|
||||
/// </returns>
|
||||
public static ITfThreadMgrEx Load()
|
||||
{
|
||||
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
|
||||
Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!");
|
||||
|
||||
if (ServicesInstalled)
|
||||
{
|
||||
// NB: a COMException here means something went wrong initialzing Cicero.
|
||||
// Cicero will throw an exception if it doesn't think it should have been
|
||||
// loaded (no TIPs to run), you can check that in msctf.dll's NoTipsInstalled
|
||||
// which lives in nt\windows\advcore\ctf\lib\immxutil.cpp. If that's the
|
||||
// problem, ServicesInstalled is out of sync with Cicero's thinking.
|
||||
IntPtr ret;
|
||||
var hr = NativeMethods.CoCreateInstance(CLSID_TF_ThreadMgr,
|
||||
IntPtr.Zero,
|
||||
NativeMethods.CLSCTX_INPROC_SERVER,
|
||||
IID_ITfThreadMgrEx, out ret);
|
||||
|
||||
if (hr == NativeMethods.S_OK)
|
||||
return new ITfThreadMgrEx(ret);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return true if current OS version is Windows 7 or below.
|
||||
/// </summary>
|
||||
public static bool IsWindows7OrBelow()
|
||||
{
|
||||
if (Environment.OSVersion.Version.Major <= 5)
|
||||
return true;
|
||||
|
||||
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor <= 1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informs the caller if text services are installed for the current user.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if one or more text services are installed for the current user, otherwise false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// If this method returns false, TextServicesLoader.Load is guarenteed to return null.
|
||||
/// Callers can use this information to avoid overhead that would otherwise be
|
||||
/// required to support text services.
|
||||
/// </remarks>
|
||||
public static bool ServicesInstalled
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (s_servicesInstalledLock)
|
||||
{
|
||||
if (s_servicesInstalled == InstallState.Unknown)
|
||||
{
|
||||
s_servicesInstalled = TIPsWantToRun() ? InstallState.Installed : InstallState.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
return (s_servicesInstalled == InstallState.Installed);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public Properties
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Events
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Private Methods
|
||||
|
||||
//
|
||||
// This method tries to stop Avalon from loading Cicero when there are no TIPs to run.
|
||||
// The perf tradeoff is a typically small number of registry checks versus loading and
|
||||
// initializing cicero.
|
||||
//
|
||||
// The Algorithm:
|
||||
//
|
||||
// Do a quick check vs. the global disable flag, return false if it is set.
|
||||
// For each key under HKLM\SOFTWARE\Microsoft\CTF\TIP (a TIP or category clsid)
|
||||
// If the the key has a LanguageProfile subkey (it's a TIP clsid)
|
||||
// Iterate under the matching TIP entry in HKCU.
|
||||
// For each key under the LanguageProfile (a particular LANGID)
|
||||
// For each key under the LANGID (an assembly GUID)
|
||||
// Try to read the Enable value.
|
||||
// If the value is set non-zero, then stop all processing and return true.
|
||||
// If the value is set zero, continue.
|
||||
// If the value does not exist, continue (default is disabled).
|
||||
// If any Enable values were found under HKCU for the TIP, then stop all processing and return false.
|
||||
// Else, no Enable values have been found thus far and we keep going to investigate HKLM.
|
||||
// Iterate under the TIP entry in HKLM.
|
||||
// For each key under the LanguageProfile (a particular LANGID)
|
||||
// For each key under the LANGID (an assembly GUID)
|
||||
// Try to read the Enable value.
|
||||
// If the value is set non-zero, then stop all processing and return true.
|
||||
// If the value does not exist, then stop all processing and return true (default is enabled).
|
||||
// If the value is set zero, continue.
|
||||
// If we finish iterating all entries under HKLM without returning true, return false.
|
||||
//
|
||||
|
||||
private static bool TIPsWantToRun()
|
||||
{
|
||||
object obj;
|
||||
RegistryKey key;
|
||||
bool tipsWantToRun = false;
|
||||
|
||||
key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\CTF", false);
|
||||
|
||||
// Is cicero disabled completely for the current user?
|
||||
if (key != null)
|
||||
{
|
||||
obj = key.GetValue("Disable Thread Input Manager");
|
||||
|
||||
if (obj is int && (int)obj != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through all the TIP entries for machine and current user.
|
||||
tipsWantToRun = IterateSubKeys(Registry.LocalMachine, "SOFTWARE\\Microsoft\\CTF\\TIP", new IterateHandler(SingleTIPWantsToRun), true) == EnableState.Enabled;
|
||||
|
||||
return tipsWantToRun;
|
||||
}
|
||||
|
||||
// Returns EnableState.Enabled if one or more TIPs are installed and
|
||||
// enabled for the current user.
|
||||
private static EnableState SingleTIPWantsToRun(RegistryKey keyLocalMachine, string subKeyName, bool localMachine)
|
||||
{
|
||||
EnableState result;
|
||||
|
||||
if (subKeyName.Length != CLSIDLength)
|
||||
return EnableState.Disabled;
|
||||
|
||||
// We want subkey\LanguageProfile key.
|
||||
// Loop through all the langid entries for TIP.
|
||||
|
||||
// First, check current user.
|
||||
result = IterateSubKeys(Registry.CurrentUser, "SOFTWARE\\Microsoft\\CTF\\TIP\\" + subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), false);
|
||||
|
||||
// Any explicit value short circuits the process.
|
||||
// Otherwise check local machine.
|
||||
if (result == EnableState.None || result == EnableState.Error)
|
||||
{
|
||||
result = IterateSubKeys(keyLocalMachine, subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), true);
|
||||
|
||||
if (result == EnableState.None)
|
||||
{
|
||||
result = EnableState.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns EnableState.Enabled if the supplied subkey is a valid LANGID key with enabled
|
||||
// cicero assembly.
|
||||
private static EnableState IsLangidEnabled(RegistryKey key, string subKeyName, bool localMachine)
|
||||
{
|
||||
if (subKeyName.Length != LANGIDLength)
|
||||
return EnableState.Error;
|
||||
|
||||
// Loop through all the assembly entries for the langid
|
||||
return IterateSubKeys(key, subKeyName, new IterateHandler(IsAssemblyEnabled), localMachine);
|
||||
}
|
||||
|
||||
// Returns EnableState.Enabled if the supplied assembly key is enabled.
|
||||
private static EnableState IsAssemblyEnabled(RegistryKey key, string subKeyName, bool localMachine)
|
||||
{
|
||||
RegistryKey subKey;
|
||||
object obj;
|
||||
|
||||
if (subKeyName.Length != CLSIDLength)
|
||||
return EnableState.Error;
|
||||
|
||||
// Open the local machine assembly key.
|
||||
subKey = key.OpenSubKey(subKeyName);
|
||||
|
||||
if (subKey == null)
|
||||
return EnableState.Error;
|
||||
|
||||
// Try to read the "Enable" value.
|
||||
obj = subKey.GetValue("Enable");
|
||||
|
||||
if (obj is int)
|
||||
{
|
||||
return ((int)obj == 0) ? EnableState.Disabled : EnableState.Enabled;
|
||||
}
|
||||
|
||||
return EnableState.None;
|
||||
}
|
||||
|
||||
// Calls the supplied delegate on each of the children of keyBase.
|
||||
private static EnableState IterateSubKeys(RegistryKey keyBase, string subKey, IterateHandler handler, bool localMachine)
|
||||
{
|
||||
RegistryKey key;
|
||||
string[] subKeyNames;
|
||||
EnableState state;
|
||||
|
||||
key = keyBase.OpenSubKey(subKey, false);
|
||||
|
||||
if (key == null)
|
||||
return EnableState.Error;
|
||||
|
||||
subKeyNames = key.GetSubKeyNames();
|
||||
state = EnableState.Error;
|
||||
|
||||
foreach (string name in subKeyNames)
|
||||
{
|
||||
switch (handler(key, name, localMachine))
|
||||
{
|
||||
case EnableState.Error:
|
||||
break;
|
||||
case EnableState.None:
|
||||
if (localMachine) // For lm, want to return here right away.
|
||||
return EnableState.None;
|
||||
|
||||
// For current user, remember that we found no Enable value.
|
||||
if (state == EnableState.Error)
|
||||
{
|
||||
state = EnableState.None;
|
||||
}
|
||||
break;
|
||||
case EnableState.Disabled:
|
||||
state = EnableState.Disabled;
|
||||
break;
|
||||
case EnableState.Enabled:
|
||||
return EnableState.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Fields
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Private Fields
|
||||
|
||||
// String consts used to validate registry entires.
|
||||
private const int CLSIDLength = 38; // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
private const int LANGIDLength = 10; // 0x12345678
|
||||
|
||||
// Status of a TIP assembly.
|
||||
private enum EnableState
|
||||
{
|
||||
Error, // Invalid entry.
|
||||
None, // No explicit Enable entry on the assembly.
|
||||
Enabled, // Assembly is enabled.
|
||||
Disabled // Assembly is disabled.
|
||||
};
|
||||
|
||||
// Callback delegate for the IterateSubKeys method.
|
||||
private delegate EnableState IterateHandler(RegistryKey key, string subKeyName, bool localMachine);
|
||||
|
||||
// Install state.
|
||||
private enum InstallState
|
||||
{
|
||||
Unknown, // Haven't checked to see if any TIPs are installed yet.
|
||||
Installed, // Checked and installed.
|
||||
NotInstalled // Checked and not installed.
|
||||
}
|
||||
|
||||
// Cached install state value.
|
||||
// Writes are not thread safe, but we don't mind the neglible perf hit
|
||||
// of potentially writing it twice.
|
||||
private static InstallState s_servicesInstalled = InstallState.Unknown;
|
||||
private static object s_servicesInstalledLock = new object();
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -1,963 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using ImeSharp.Native;
|
||||
using SharpGen.Runtime;
|
||||
using SharpGen.Runtime.Win32;
|
||||
using TsfSharp;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
internal class TextStore : CallbackBase,
|
||||
ITextStoreACP,
|
||||
ITfContextOwnerCompositionSink,
|
||||
ITfTextEditSink,
|
||||
ITfUIElementSink
|
||||
{
|
||||
public static readonly Guid IID_ITextStoreACPSink = new Guid(0x22d44c94, 0xa419, 0x4542, 0xa2, 0x72, 0xae, 0x26, 0x09, 0x3e, 0xce, 0xcf);
|
||||
public static readonly Guid GUID_PROP_COMPOSING = new Guid("e12ac060-af15-11d2-afc5-00105a2799b5");
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Constructors
|
||||
|
||||
// Creates a TextStore instance.
|
||||
public TextStore(IntPtr windowHandle)
|
||||
{
|
||||
_windowHandle = windowHandle;
|
||||
|
||||
|
||||
_viewCookie = Environment.TickCount;
|
||||
|
||||
_editCookie = Tsf.TF_INVALID_COOKIE;
|
||||
_uiElementSinkCookie = Tsf.TF_INVALID_COOKIE;
|
||||
_textEditSinkCookie = Tsf.TF_INVALID_COOKIE;
|
||||
|
||||
_IMEStringPool = ArrayPool<IMEString>.Shared;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Methods - ITextStoreACP
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region ITextStoreACP
|
||||
|
||||
public void AdviseSink(Guid riid, IUnknown obj, int flags)
|
||||
{
|
||||
ITextStoreACPSink sink;
|
||||
|
||||
if (riid != IID_ITextStoreACPSink)
|
||||
throw new COMException("TextStore_CONNECT_E_CANNOTCONNECT");
|
||||
|
||||
sink = (obj as ComObject).QueryInterface<ITextStoreACPSink>();
|
||||
|
||||
if (sink == null)
|
||||
throw new COMException("TextStore_E_NOINTERFACE");
|
||||
|
||||
// It's legal to replace existing sink.
|
||||
if (_sink != null)
|
||||
_sink.Dispose();
|
||||
|
||||
(obj as ComObject).Dispose();
|
||||
|
||||
_sink = sink;
|
||||
}
|
||||
|
||||
public void UnadviseSink(IUnknown obj)
|
||||
{
|
||||
var sink = (obj as ComObject).QueryInterface<ITextStoreACPSink>();
|
||||
if (sink.NativePointer != _sink.NativePointer)
|
||||
throw new COMException("TextStore_CONNECT_E_NOCONNECTION");
|
||||
|
||||
_sink.Release();
|
||||
_sink = null;
|
||||
}
|
||||
|
||||
private bool _LockDocument(TsfSharp.TsLfFlags dwLockFlags)
|
||||
{
|
||||
if (_locked)
|
||||
return false;
|
||||
|
||||
_locked = true;
|
||||
_lockFlags = dwLockFlags;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResetIfRequired()
|
||||
{
|
||||
if (!_commited)
|
||||
return;
|
||||
|
||||
_commited = false;
|
||||
|
||||
TsTextchange textChange;
|
||||
textChange.AcpStart = 0;
|
||||
textChange.AcpOldEnd = _inputBuffer.Count;
|
||||
textChange.AcpNewEnd = 0;
|
||||
_inputBuffer.Clear();
|
||||
|
||||
_sink.OnTextChange(0, textChange);
|
||||
|
||||
_acpStart = _acpEnd = 0;
|
||||
_sink.OnSelectionChange();
|
||||
_commitStart = _commitLength = 0;
|
||||
|
||||
//Debug.WriteLine("TextStore reset!!!");
|
||||
}
|
||||
|
||||
private void _UnlockDocument()
|
||||
{
|
||||
Result hr;
|
||||
_locked = false;
|
||||
_lockFlags = 0;
|
||||
|
||||
ResetIfRequired();
|
||||
|
||||
//if there is a queued lock, grant it
|
||||
if (_lockRequestQueue.Count > 0)
|
||||
{
|
||||
hr = RequestLock(_lockRequestQueue.Dequeue());
|
||||
}
|
||||
|
||||
//if any layout changes occurred during the lock, notify the manager
|
||||
if (_layoutChanged)
|
||||
{
|
||||
_layoutChanged = false;
|
||||
_sink.OnLayoutChange(TsLayoutCode.TsLcChange, _viewCookie);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _IsLocked(TsfSharp.TsLfFlags dwLockType)
|
||||
{
|
||||
return _locked && (_lockFlags & dwLockType) != 0;
|
||||
}
|
||||
|
||||
public Result RequestLock(TsfSharp.TsLfFlags dwLockFlags)
|
||||
{
|
||||
Result hrSession;
|
||||
|
||||
if (_sink == null)
|
||||
throw new COMException("TextStore_NoSink");
|
||||
|
||||
if (dwLockFlags == 0)
|
||||
throw new COMException("TextStore_BadLockFlags");
|
||||
|
||||
hrSession = Result.Fail;
|
||||
|
||||
if (_locked)
|
||||
{
|
||||
//the document is locked
|
||||
|
||||
if ((dwLockFlags & TsfSharp.TsLfFlags.Sync) == TsfSharp.TsLfFlags.Sync)
|
||||
{
|
||||
/*
|
||||
The caller wants an immediate lock, but this cannot be granted because
|
||||
the document is already locked.
|
||||
*/
|
||||
hrSession = (int)TsErrors.TsESynchronous;
|
||||
}
|
||||
else
|
||||
{
|
||||
//the request is asynchronous
|
||||
|
||||
//Queue the lock request
|
||||
_lockRequestQueue.Enqueue(dwLockFlags);
|
||||
hrSession = (int)TsErrors.TsSAsync;
|
||||
}
|
||||
|
||||
return hrSession;
|
||||
}
|
||||
|
||||
//lock the document
|
||||
_LockDocument(dwLockFlags);
|
||||
|
||||
//call OnLockGranted
|
||||
hrSession = _sink.OnLockGranted(dwLockFlags);
|
||||
|
||||
//unlock the document
|
||||
_UnlockDocument();
|
||||
|
||||
return hrSession;
|
||||
}
|
||||
|
||||
public TsStatus GetStatus()
|
||||
{
|
||||
TsStatus status = new TsStatus();
|
||||
status.DynamicFlags = 0;
|
||||
status.StaticFlags = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void QueryInsert(int acpTestStart, int acpTestEnd, uint cch, out int acpResultStart, out int acpResultEnd)
|
||||
{
|
||||
acpResultStart = acpResultEnd = 0;
|
||||
|
||||
// Fix possible crash
|
||||
if (_inputBuffer.Count == 0)
|
||||
return;
|
||||
|
||||
//Queryins
|
||||
if (acpTestStart > _inputBuffer.Count || acpTestEnd > _inputBuffer.Count)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
//Microsoft Pinyin seems does not init the result value, so we set the test value here, in case crash
|
||||
acpResultStart = acpTestStart;
|
||||
acpResultEnd = acpTestEnd;
|
||||
}
|
||||
|
||||
public uint GetSelection(uint index, ref TsSelectionAcp selection)
|
||||
{
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
//return NativeMethods.TS_E_NOLOCK;
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
//check the requested index
|
||||
if (-1 == (int)index)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index > 1)
|
||||
{
|
||||
/*
|
||||
The index is too high. This app only supports one selection.
|
||||
*/
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
}
|
||||
|
||||
selection.AcpStart = _acpStart;
|
||||
selection.AcpEnd = _acpEnd;
|
||||
selection.Style.InterimCharFlag = _interimChar;
|
||||
|
||||
if (_interimChar)
|
||||
{
|
||||
/*
|
||||
fInterimChar will be set when an intermediate character has been
|
||||
set. One example of when this will happen is when an IME is being
|
||||
used to enter characters and a character has been set, but the IME
|
||||
is still active.
|
||||
*/
|
||||
selection.Style.Ase = TsActiveSelEnd.TsAeNone;
|
||||
}
|
||||
else
|
||||
{
|
||||
selection.Style.Ase = _activeSelectionEnd;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void SetSelection(uint count, ref TsSelectionAcp selections)
|
||||
{
|
||||
//this implementaiton only supports a single selection
|
||||
if (count != 1)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Readwrite))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
//return NativeMethods.TS_E_NOLOCK;
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
_acpStart = selections.AcpStart;
|
||||
_acpEnd = selections.AcpEnd;
|
||||
_interimChar = selections.Style.InterimCharFlag;
|
||||
|
||||
if (_interimChar)
|
||||
{
|
||||
/*
|
||||
fInterimChar will be set when an intermediate character has been
|
||||
set. One example of when this will happen is when an IME is being
|
||||
used to enter characters and a character has been set, but the IME
|
||||
is still active.
|
||||
*/
|
||||
_activeSelectionEnd = TsActiveSelEnd.TsAeNone;
|
||||
}
|
||||
else
|
||||
{
|
||||
_activeSelectionEnd = selections.Style.Ase;
|
||||
}
|
||||
|
||||
//if the selection end is at the start of the selection, reverse the parameters
|
||||
int lStart = _acpStart;
|
||||
int lEnd = _acpEnd;
|
||||
|
||||
if (TsActiveSelEnd.TsAeStart == _activeSelectionEnd)
|
||||
{
|
||||
lStart = _acpEnd;
|
||||
lEnd = _acpStart;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void GetText(int acpStart, int acpEnd, System.IntPtr pchPlain, uint cchPlainReq, out uint cchPlainRet,
|
||||
ref TsfSharp.TsRuninfo rgRunInfo, uint cRunInfoReq, out uint cRunInfoRet, out int acpNext)
|
||||
{
|
||||
cchPlainRet = 0;
|
||||
cRunInfoRet = 0;
|
||||
acpNext = 0;
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
bool fDoText = cchPlainReq > 0;
|
||||
bool fDoRunInfo = cRunInfoReq > 0;
|
||||
int cchTotal;
|
||||
|
||||
cchPlainRet = 0;
|
||||
acpNext = acpStart;
|
||||
|
||||
cchTotal = _inputBuffer.Count;
|
||||
|
||||
//validate the start pos
|
||||
if ((acpStart < 0) || (acpStart > cchTotal))
|
||||
{
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
}
|
||||
else
|
||||
{
|
||||
//are we at the end of the document
|
||||
if (acpStart == cchTotal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
int cchReq;
|
||||
|
||||
/*
|
||||
acpEnd will be -1 if all of the text up to the end is being requested.
|
||||
*/
|
||||
|
||||
if (acpEnd >= acpStart)
|
||||
{
|
||||
cchReq = acpEnd - acpStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
cchReq = cchTotal - acpStart;
|
||||
}
|
||||
|
||||
if (fDoText)
|
||||
{
|
||||
if (cchReq > cchPlainReq)
|
||||
{
|
||||
cchReq = (int)cchPlainReq;
|
||||
}
|
||||
|
||||
//extract the specified text range
|
||||
if (pchPlain != IntPtr.Zero && cchPlainReq > 0)
|
||||
{
|
||||
//_inputBuffer.CopyTo(acpStart, pchPlain, 0, cchReq);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var ptr = (char*)pchPlain;
|
||||
|
||||
for (int i = acpStart; i < cchReq; i++)
|
||||
{
|
||||
*ptr = _inputBuffer[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//it is possible that only the length of the text is being requested
|
||||
cchPlainRet = (uint)cchReq;
|
||||
|
||||
if (fDoRunInfo)
|
||||
{
|
||||
/*
|
||||
Runs are used to separate text characters from formatting characters.
|
||||
|
||||
In this example, sequences inside and including the <> are treated as
|
||||
control sequences and are not displayed.
|
||||
|
||||
Plain text = "Text formatting."
|
||||
Actual text = "Text <B><I>formatting</I></B>."
|
||||
|
||||
If all of this text were requested, the run sequence would look like this:
|
||||
|
||||
prgRunInfo[0].type = TS_RT_PLAIN; //"Text "
|
||||
prgRunInfo[0].uCount = 5;
|
||||
|
||||
prgRunInfo[1].type = TS_RT_HIDDEN; //<B><I>
|
||||
prgRunInfo[1].uCount = 6;
|
||||
|
||||
prgRunInfo[2].type = TS_RT_PLAIN; //"formatting"
|
||||
prgRunInfo[2].uCount = 10;
|
||||
|
||||
prgRunInfo[3].type = TS_RT_HIDDEN; //</B></I>
|
||||
prgRunInfo[3].uCount = 8;
|
||||
|
||||
prgRunInfo[4].type = TS_RT_PLAIN; //"."
|
||||
prgRunInfo[4].uCount = 1;
|
||||
|
||||
TS_RT_OPAQUE is used to indicate characters or character sequences
|
||||
that are in the document, but are used privately by the application
|
||||
and do not map to text. Runs of text tagged with TS_RT_OPAQUE should
|
||||
NOT be included in the pchPlain or cchPlainOut [out] parameters.
|
||||
*/
|
||||
|
||||
/*
|
||||
This implementation is plain text, so the text only consists of one run.
|
||||
If there were multiple runs, it would be an error to have consecuative runs
|
||||
of the same type.
|
||||
*/
|
||||
rgRunInfo.Type = TsRunType.TsRtPlain;
|
||||
rgRunInfo.Count = (uint)cchReq;
|
||||
}
|
||||
|
||||
acpNext = acpStart + cchReq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TsTextchange SetText(int dwFlags, int acpStart, int acpEnd, string pchText, uint cch)
|
||||
{
|
||||
/*
|
||||
dwFlags can be:
|
||||
TS_ST_CORRECTION
|
||||
*/
|
||||
TsTextchange change = new TsTextchange();
|
||||
|
||||
//set the selection to the specified range
|
||||
TsSelectionAcp tsa = new TsSelectionAcp();
|
||||
tsa.AcpStart = acpStart;
|
||||
tsa.AcpEnd = acpEnd;
|
||||
tsa.Style.Ase = TsActiveSelEnd.TsAeStart;
|
||||
tsa.Style.InterimCharFlag = false;
|
||||
|
||||
SetSelection(1, ref tsa);
|
||||
|
||||
int start, end;
|
||||
InsertTextAtSelection(TsIasFlags.Noquery, pchText, cch, out start, out end, out change);
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
public IDataObject GetFormattedText(int startIndex, int endIndex)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public IUnknown GetEmbedded(int index, Guid guidService, Guid riid)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public RawBool QueryInsertEmbedded(Guid guidService, ref Formatetc formatEtc)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public TsTextchange InsertEmbedded(int flags, int startIndex, int endIndex, TsfSharp.IDataObject dataObjectRef)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void InsertTextAtSelection(TsfSharp.TsIasFlags dwFlags, string pchText, uint cch, out int pacpStart, out int pacpEnd, out TsfSharp.TsTextchange pChange)
|
||||
{
|
||||
pacpStart = pacpEnd = 0;
|
||||
pChange = new TsTextchange();
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Readwrite))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
int acpStart;
|
||||
int acpOldEnd;
|
||||
int acpNewEnd;
|
||||
|
||||
acpOldEnd = _acpEnd;
|
||||
|
||||
//set the start point after the insertion
|
||||
acpStart = _acpStart;
|
||||
|
||||
//set the end point after the insertion
|
||||
acpNewEnd = _acpStart + (int)cch;
|
||||
|
||||
if ((dwFlags & TsIasFlags.Queryonly) == TsIasFlags.Queryonly)
|
||||
{
|
||||
pacpStart = acpStart;
|
||||
pacpEnd = acpOldEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
//insert the text
|
||||
_inputBuffer.RemoveRange(acpStart, acpOldEnd - acpStart);
|
||||
_inputBuffer.InsertRange(acpStart, pchText);
|
||||
|
||||
//set the selection
|
||||
_acpStart = acpStart;
|
||||
_acpEnd = acpNewEnd;
|
||||
|
||||
if ((dwFlags & TsIasFlags.Noquery) != TsIasFlags.Noquery)
|
||||
{
|
||||
pacpStart = acpStart;
|
||||
pacpEnd = acpNewEnd;
|
||||
}
|
||||
|
||||
//set the TS_TEXTCHANGE members
|
||||
pChange.AcpStart = acpStart;
|
||||
pChange.AcpOldEnd = acpOldEnd;
|
||||
pChange.AcpNewEnd = acpNewEnd;
|
||||
|
||||
//defer the layout change notification until the document is unlocked
|
||||
_layoutChanged = true;
|
||||
}
|
||||
|
||||
public void InsertEmbeddedAtSelection(int flags, IDataObject obj, out int startIndex, out int endIndex, out TsTextchange change)
|
||||
{
|
||||
startIndex = endIndex = 0;
|
||||
change = new TsTextchange();
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void RequestSupportedAttrs(int flags, uint cFilterAttrs, ref Guid filterAttributes)
|
||||
{
|
||||
}
|
||||
|
||||
public void RequestAttrsAtPosition(int index, uint cFilterAttrs, ref Guid filterAttributes, int flags)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
|
||||
public void RequestAttrsTransitioningAtPosition(int position, uint cFilterAttrs, ref Guid filterAttributes, int flags)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void FindNextAttrTransition(int startIndex, int haltIndex, uint cFilterAttrs, ref Guid filterAttributes, int flags, out int acpNext, out RawBool found, out int foundOffset)
|
||||
{
|
||||
acpNext = 0;
|
||||
found = false;
|
||||
foundOffset = 0;
|
||||
}
|
||||
|
||||
public uint RetrieveRequestedAttrs(uint ulCount, ref TsfSharp.TsAttrval aAttrValsRef)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int GetEndACP()
|
||||
{
|
||||
int acp = 0;
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
acp = _inputBuffer.Count;
|
||||
|
||||
return acp;
|
||||
}
|
||||
|
||||
public int GetActiveView()
|
||||
{
|
||||
return _viewCookie;
|
||||
}
|
||||
|
||||
public int GetACPFromPoint(int viewCookie, TsfSharp.Point tsfPoint, int dwFlags)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void GetTextExt(int viewCookie, int acpStart, int acpEnd, out Rect rect, out RawBool clipped)
|
||||
{
|
||||
clipped = false;
|
||||
rect = InputMethod.TextInputRect;
|
||||
|
||||
if (_viewCookie != viewCookie)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
//According to Microsoft's doc, an ime should not make empty request,
|
||||
//but some ime draw comp text themseleves, when empty req will be make
|
||||
//Check empty request
|
||||
//if (acpStart == acpEnd) {
|
||||
// return E_INVALIDARG;
|
||||
//}
|
||||
|
||||
NativeMethods.MapWindowPoints(_windowHandle, IntPtr.Zero, ref rect, 2);
|
||||
}
|
||||
|
||||
public Rect GetScreenExt(int viewCookie)
|
||||
{
|
||||
Rect rect = new Rect();
|
||||
|
||||
if (_viewCookie != viewCookie)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
NativeMethods.GetWindowRect(_windowHandle, out rect);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public IntPtr GetWnd(int viewCookie)
|
||||
{
|
||||
if (viewCookie != _viewCookie)
|
||||
{
|
||||
throw new COMException("", Result.False.Code);
|
||||
}
|
||||
|
||||
return _windowHandle;
|
||||
}
|
||||
|
||||
#endregion ITextStoreACP2
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Public Methods - ITfContextOwnerCompositionSink
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region ITfContextOwnerCompositionSink
|
||||
|
||||
public RawBool OnStartComposition(ITfCompositionView view)
|
||||
{
|
||||
// Return true in ok to start the composition.
|
||||
RawBool ok = true;
|
||||
_compositionStart = _compositionLength = 0;
|
||||
_currentComposition.Clear();
|
||||
|
||||
InputMethod.OnTextCompositionStarted(this);
|
||||
_compViews.Add(view);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void OnUpdateComposition(ITfCompositionView view, ITfRange rangeNew)
|
||||
{
|
||||
var range = view.Range;
|
||||
var rangeacp = range.QueryInterface<ITfRangeACP>();
|
||||
|
||||
rangeacp.GetExtent(out _compositionStart, out _compositionLength);
|
||||
rangeacp.Dispose();
|
||||
range.Dispose();
|
||||
_compViews.Add(view);
|
||||
}
|
||||
|
||||
public void OnEndComposition(ITfCompositionView view)
|
||||
{
|
||||
var range = view.Range;
|
||||
var rangeacp = range.QueryInterface<ITfRangeACP>();
|
||||
|
||||
rangeacp.GetExtent(out _commitStart, out _commitLength);
|
||||
rangeacp.Dispose();
|
||||
range.Dispose();
|
||||
|
||||
// Ensure composition string reset
|
||||
_compositionStart = _compositionLength = 0;
|
||||
_currentComposition.Clear();
|
||||
|
||||
InputMethod.ClearCandidates();
|
||||
InputMethod.OnTextCompositionEnded(this);
|
||||
view.Dispose();
|
||||
foreach(var item in _compViews)
|
||||
item.Dispose();
|
||||
_compViews.Clear();
|
||||
}
|
||||
|
||||
#endregion ITfContextOwnerCompositionSink
|
||||
|
||||
#region ITfTextEditSink
|
||||
|
||||
public void OnEndEdit(ITfContext context, int ecReadOnly, ITfEditRecord editRecord)
|
||||
{
|
||||
ITfProperty property = context.GetProperty(GUID_PROP_COMPOSING);
|
||||
|
||||
ITfRangeACP rangeACP = TextServicesContext.Current.ContextOwnerServices.CreateRange(_compositionStart, _compositionStart + _compositionLength);
|
||||
Variant val = property.GetValue(ecReadOnly, rangeACP);
|
||||
property.Dispose();
|
||||
rangeACP.Dispose();
|
||||
if (val.Value == null || (int)val.Value == 0)
|
||||
{
|
||||
if (_commitLength == 0 || _inputBuffer.Count == 0)
|
||||
return;
|
||||
|
||||
//Debug.WriteLine("Composition result: {0}", new object[] { new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()) });
|
||||
|
||||
_commited = true;
|
||||
for (int i = 0; i < _commitLength; i++)
|
||||
InputMethod.OnTextCompositionResult(this, new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()));
|
||||
}
|
||||
|
||||
if (_commited)
|
||||
return;
|
||||
|
||||
if (_inputBuffer.Count == 0 && _compositionLength > 0) // Composition just ended
|
||||
return;
|
||||
|
||||
_currentComposition.Clear();
|
||||
for (int i = 0; i < _compositionLength; i++)
|
||||
_currentComposition.Add(_inputBuffer[_compositionStart + i]);
|
||||
|
||||
InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd);
|
||||
|
||||
//var compStr = new string(_currentComposition.ToArray());
|
||||
//compStr = compStr.Insert(_acpEnd, "|");
|
||||
//Debug.WriteLine("Composition string: {0}, cursor pos: {1}", compStr, _acpEnd);
|
||||
}
|
||||
|
||||
#endregion ITfTextEditSink
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Public Methods - ITfUIElementSink
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region ITfUIElementSink
|
||||
|
||||
public RawBool BeginUIElement(int dwUIElementId)
|
||||
{
|
||||
// Hide OS rendered Candidate list Window
|
||||
RawBool pbShow = InputMethod.ShowOSImeWindow;
|
||||
|
||||
OnUIElement(dwUIElementId, true);
|
||||
|
||||
return pbShow;
|
||||
}
|
||||
|
||||
public void UpdateUIElement(int dwUIElementId)
|
||||
{
|
||||
OnUIElement(dwUIElementId, false);
|
||||
}
|
||||
|
||||
public void EndUIElement(int dwUIElementId)
|
||||
{
|
||||
}
|
||||
|
||||
private void OnUIElement(int uiElementId, bool onStart)
|
||||
{
|
||||
if (InputMethod.ShowOSImeWindow || !_supportUIElement) return;
|
||||
|
||||
ITfUIElement uiElement = TextServicesContext.Current.UIElementMgr.GetUIElement(uiElementId);
|
||||
|
||||
ITfCandidateListUIElementBehavior candList;
|
||||
|
||||
try
|
||||
{
|
||||
candList = uiElement.QueryInterface<ITfCandidateListUIElementBehavior>();
|
||||
}
|
||||
catch (SharpGenException)
|
||||
{
|
||||
_supportUIElement = false;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
uiElement.Dispose();
|
||||
}
|
||||
|
||||
uint selection = 0;
|
||||
uint currentPage = 0;
|
||||
uint count = 0;
|
||||
uint pageCount = 0;
|
||||
uint pageStart = 0;
|
||||
uint pageSize = 0;
|
||||
uint i, j;
|
||||
|
||||
selection = candList.GetSelection();
|
||||
currentPage = candList.GetCurrentPage();
|
||||
|
||||
count = candList.GetCount();
|
||||
|
||||
pageCount = candList.GetPageIndex(null, 0);
|
||||
|
||||
if (pageCount > 0)
|
||||
{
|
||||
uint[] pageStartIndexes = ArrayPool<uint>.Shared.Rent((int)pageCount);
|
||||
pageCount = candList.GetPageIndex(pageStartIndexes, pageCount);
|
||||
pageStart = pageStartIndexes[currentPage];
|
||||
|
||||
if (pageStart >= count - 1)
|
||||
{
|
||||
candList.Abort();
|
||||
ArrayPool<uint>.Shared.Return(pageStartIndexes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPage < pageCount - 1)
|
||||
pageSize = Math.Min(count, pageStartIndexes[currentPage + 1]) - pageStart;
|
||||
else
|
||||
pageSize = count - pageStart;
|
||||
|
||||
ArrayPool<uint>.Shared.Return(pageStartIndexes);
|
||||
}
|
||||
|
||||
selection -= pageStart;
|
||||
|
||||
IMEString[] candidates = _IMEStringPool.Rent((int)pageSize);
|
||||
|
||||
IntPtr bStrPtr;
|
||||
for (i = pageStart, j = 0; i < count && j < pageSize; i++, j++)
|
||||
{
|
||||
bStrPtr = candList.GetString(i);
|
||||
candidates[j] = new IMEString(bStrPtr);
|
||||
}
|
||||
|
||||
//Debug.WriteLine("TSF========TSF");
|
||||
//Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, currentPage: {3} candidates:", pageStart, pageSize, selection, currentPage);
|
||||
//for (int k = 0; k < candidates.Length; k++)
|
||||
// Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : "");
|
||||
//Debug.WriteLine("TSF++++++++TSF");
|
||||
|
||||
InputMethod.CandidatePageStart = (int)pageStart;
|
||||
InputMethod.CandidatePageSize = (int)pageSize;
|
||||
InputMethod.CandidateSelection = (int)selection;
|
||||
InputMethod.CandidateList = candidates;
|
||||
|
||||
if (_currentComposition != null)
|
||||
{
|
||||
InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd);
|
||||
_IMEStringPool.Return(candidates);
|
||||
}
|
||||
|
||||
candList.Dispose();
|
||||
}
|
||||
|
||||
#endregion ITfUIElementSink
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Public Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
public static TextStore Current
|
||||
{
|
||||
get
|
||||
{
|
||||
TextStore defaultTextStore = InputMethod.DefaultTextStore;
|
||||
if (defaultTextStore == null)
|
||||
{
|
||||
defaultTextStore = InputMethod.DefaultTextStore = new TextStore(InputMethod.WindowHandle);
|
||||
|
||||
defaultTextStore.Register();
|
||||
}
|
||||
|
||||
return defaultTextStore;
|
||||
}
|
||||
}
|
||||
|
||||
public ITfDocumentMgr DocumentManager
|
||||
{
|
||||
get { return _documentMgr; }
|
||||
set { _documentMgr = value; }
|
||||
}
|
||||
|
||||
// EditCookie for ITfContext.
|
||||
public int EditCookie
|
||||
{
|
||||
// get { return _editCookie; }
|
||||
set { _editCookie = value; }
|
||||
}
|
||||
|
||||
public int UIElementSinkCookie
|
||||
{
|
||||
get { return _uiElementSinkCookie; }
|
||||
set { _uiElementSinkCookie = value; }
|
||||
}
|
||||
|
||||
public int TextEditSinkCookie
|
||||
{
|
||||
get { return _textEditSinkCookie; }
|
||||
set { _textEditSinkCookie = value; }
|
||||
}
|
||||
|
||||
public bool SupportUIElement { get { return _supportUIElement; } }
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
// This function calls TextServicesContext to create TSF document and start transitory extension.
|
||||
private void Register()
|
||||
{
|
||||
// Create TSF document and advise the sink to it.
|
||||
TextServicesContext.Current.RegisterTextStore(this);
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Fields
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
// The TSF document object. This is a native resource.
|
||||
private ITfDocumentMgr _documentMgr;
|
||||
|
||||
private int _viewCookie;
|
||||
|
||||
// The edit cookie TSF returns from CreateContext.
|
||||
private int _editCookie;
|
||||
private int _uiElementSinkCookie;
|
||||
private int _textEditSinkCookie;
|
||||
|
||||
private ITextStoreACPSink _sink;
|
||||
private IntPtr _windowHandle;
|
||||
private int _acpStart;
|
||||
private int _acpEnd;
|
||||
private bool _interimChar;
|
||||
private TsActiveSelEnd _activeSelectionEnd;
|
||||
private List<char> _inputBuffer = new List<char>();
|
||||
|
||||
private bool _locked;
|
||||
private TsLfFlags _lockFlags;
|
||||
private Queue<TsLfFlags> _lockRequestQueue = new Queue<TsLfFlags>();
|
||||
private bool _layoutChanged;
|
||||
|
||||
private List<char> _currentComposition = new List<char>();
|
||||
private int _compositionStart;
|
||||
private int _compositionLength;
|
||||
private int _commitStart;
|
||||
private int _commitLength;
|
||||
private bool _commited;
|
||||
|
||||
private bool _supportUIElement = true;
|
||||
private List<ITfCompositionView> _compViews = new List<ITfCompositionView>();
|
||||
|
||||
private ArrayPool<IMEString> _IMEStringPool;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,11 @@ namespace Microsoft.Xna.Framework {
|
||||
/// Used for displaying uncommitted IME text.
|
||||
/// </summary>
|
||||
public event EventHandler<TextEditingEventArgs> TextEditing;
|
||||
|
||||
/// <summary>
|
||||
/// Used for when a key is pressed, including modifiers.
|
||||
/// </summary>
|
||||
public event EventHandler<TextInputEventArgs> KeyDown;
|
||||
#endif
|
||||
|
||||
#endregion Events
|
||||
@@ -158,6 +163,11 @@ namespace Microsoft.Xna.Framework {
|
||||
EventHelpers.Raise(this, TextInput, e);
|
||||
}
|
||||
|
||||
protected void OnKeyDown(object sender, TextInputEventArgs e)
|
||||
{
|
||||
EventHelpers.Raise(this, KeyDown, e);
|
||||
}
|
||||
|
||||
protected void OnTextEditing(object sender, TextEditingEventArgs e)
|
||||
{
|
||||
EventHelpers.Raise(this, TextEditing, e);
|
||||
|
||||
@@ -75,6 +75,8 @@ internal static class Sdl
|
||||
TextEditing = 0x302,
|
||||
TextInput = 0x303,
|
||||
|
||||
TextEditingExt = 0x305,
|
||||
|
||||
MouseMotion = 0x400,
|
||||
MouseButtonDown = 0x401,
|
||||
MouseButtonup = 0x402,
|
||||
@@ -139,6 +141,8 @@ internal static class Sdl
|
||||
[FieldOffset(0)]
|
||||
public Keyboard.TextEditingEvent Edit;
|
||||
[FieldOffset(0)]
|
||||
public Keyboard.TextEditingExtEvent EditExt;
|
||||
[FieldOffset(0)]
|
||||
public Keyboard.TextInputEvent Text;
|
||||
[FieldOffset(0)]
|
||||
public Mouse.WheelEvent Wheel;
|
||||
@@ -241,8 +245,8 @@ internal static class Sdl
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate bool d_sdl_istextinputactive();
|
||||
public static d_sdl_istextinputactive SDL_IsTextInputActive = FuncLoader.LoadFunction<d_sdl_istextinputactive>(NativeLibrary, "SDL_IsTextInputActive");
|
||||
public delegate bool d_sdl_istextinputshown();
|
||||
public static d_sdl_istextinputshown SDL_IsTextInputShown = FuncLoader.LoadFunction<d_sdl_istextinputshown>(NativeLibrary, "SDL_IsTextInputShown");
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void d_sdl_starttextinput();
|
||||
@@ -841,6 +845,17 @@ internal static class Sdl
|
||||
public int Length;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct TextEditingExtEvent
|
||||
{
|
||||
public EventType Type;
|
||||
public uint Timestamp;
|
||||
public uint WindowId;
|
||||
public byte* Text;
|
||||
public int Start;
|
||||
public int Length;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct TextInputEvent
|
||||
{
|
||||
|
||||
@@ -147,14 +147,7 @@ namespace Microsoft.Xna.Framework
|
||||
character = '\0';
|
||||
}
|
||||
|
||||
if (char.IsControl(character) ||
|
||||
key == Keys.Left ||
|
||||
key == Keys.Right ||
|
||||
key == Keys.Up ||
|
||||
key == Keys.Down)
|
||||
{
|
||||
_view.CallTextInput(character, key);
|
||||
}
|
||||
_view.CallKeyDown(character, key);
|
||||
}
|
||||
else if (ev.Type == Sdl.EventType.KeyUp)
|
||||
{
|
||||
@@ -164,10 +157,24 @@ namespace Microsoft.Xna.Framework
|
||||
else if (ev.Type == Sdl.EventType.TextEditing)
|
||||
{
|
||||
string text;
|
||||
unsafe { text = ReadString(ev.Text.Text); }
|
||||
unsafe
|
||||
{
|
||||
text = ReadString(ev.Edit.Text);
|
||||
}
|
||||
|
||||
_view.CallTextEditing(text, ev.Edit.Start, ev.Edit.Length);
|
||||
}
|
||||
else if (ev.Type == Sdl.EventType.TextEditingExt)
|
||||
{
|
||||
string text;
|
||||
unsafe
|
||||
{
|
||||
text = ReadString(ev.EditExt.Text);
|
||||
Sdl.Free((IntPtr)ev.EditExt.Text);
|
||||
}
|
||||
|
||||
_view.CallTextEditing(text, ev.EditExt.Start, ev.EditExt.Length);
|
||||
}
|
||||
else if (ev.Type == Sdl.EventType.TextInput)
|
||||
{
|
||||
string text;
|
||||
|
||||
@@ -117,7 +117,8 @@ namespace Microsoft.Xna.Framework
|
||||
* By default SDL2 will hide IME popups since it probably assumes the game will implement their own suggestions box.
|
||||
* We don't want that, so this hint will allow the system native IME popups to show up when typing in the game.
|
||||
*/
|
||||
Sdl.SetHint("SDL_HINT_IME_SHOW_UI", "1");
|
||||
Sdl.SetHint("SDL_IME_SHOW_UI", "1");
|
||||
Sdl.SetHint("SDL_IME_SUPPORT_EXTENDED_TEXT", "1");
|
||||
|
||||
// when running NUnit tests entry assembly can be null
|
||||
if (Assembly.GetEntryAssembly() != null)
|
||||
@@ -339,6 +340,11 @@ namespace Microsoft.Xna.Framework
|
||||
OnTextInput(this, new TextInputEventArgs(c, key));
|
||||
}
|
||||
|
||||
public void CallKeyDown(char c, Keys key = Keys.None)
|
||||
{
|
||||
OnKeyDown(this, new TextInputEventArgs(c, key));
|
||||
}
|
||||
|
||||
public void CallTextEditing(string text, int start, int length)
|
||||
{
|
||||
OnTextEditing(this, new TextEditingEventArgs(text, start, length));
|
||||
|
||||
@@ -4,6 +4,11 @@ namespace Microsoft.Xna.Framework
|
||||
{
|
||||
public static class TextInput
|
||||
{
|
||||
public static bool IsTextInputShown()
|
||||
{
|
||||
return Sdl.SDL_IsTextInputShown();
|
||||
}
|
||||
|
||||
public static void StartTextInput()
|
||||
{
|
||||
Sdl.SDL_StartTextInput();
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user