Faction Test 100.4.0.0
This commit is contained in:
@@ -0,0 +1,963 @@
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user