View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0001760 | NoesisGUI | C++ SDK | public | 2020-07-27 03:53 | 2020-09-17 11:08 |
| Reporter | nikobarli | Assigned To | sfernandez | ||
| Priority | normal | Severity | block | ||
| Status | resolved | Resolution | fixed | ||
| Product Version | 2.2.6 | ||||
| Target Version | 3.0.5 | Fixed in Version | 3.0.5 | ||
| Summary | 0001760: [TSF] When used in tablet, tapping any part of the window makes keyboard appear | ||||
| Description | When used in tablet, tapping any part of the window (not only text boxes and password boxes) makes touch keyboard appears. | ||||
| Steps To Reproduce | Create any XAML (<grid background="white"></grid> is sufficient). Tap on any part of it -> touch keyboard appears It seems that TSF still considered it is focusing on a document even after gThreadMgr->SetFocus(gDisabledDocumentMgr) is called. | ||||
| Attached Files | TSF.cpp (41,944 bytes)
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////
//#undef NS_LOG_TRACE
//#define NS_LOG_TRACE(...) NS_LOG_(NS_LOG_LEVEL_TRACE, __VA_ARGS__)
#include "TSF.h"
#include <NsGui/TextBox.h>
#include <NsGui/PasswordBox.h>
#include <NsGui/CompositionUnderline.h>
#include <NsGui/ScrollViewer.h>
#include <NsGui/Decorator.h>
#include <NsGui/Keyboard.h>
#include <NsGui/IntegrationAPI.h>
#include <NsCore/UTF8.h>
#include <NsCore/Log.h>
#include <NsCore/Vector.h>
#include <NsCore/Delegate.h>
#include <NsDrawing/Rect.h>
#include <msctf.h>
#include <ctffunc.h>
#define VIEW_COOKIE 0
#define V(x) \
NS_MACRO_BEGIN \
HRESULT hr = (x); \
NS_ASSERT(SUCCEEDED(hr)); \
NS_MACRO_END
using namespace Noesis;
using namespace NoesisApp;
namespace
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Helper class to manage COM pointers lifetime
////////////////////////////////////////////////////////////////////////////////////////////////////
template<class T>
class ComPtr
{
public:
ComPtr(): mPtr(0) { }
ComPtr(T* ptr): mPtr(ptr) { if (mPtr != 0) { mPtr->AddRef(); } }
~ComPtr() { if (mPtr != 0) { mPtr->Release(); } }
operator T*() const { return mPtr; }
T* operator ->() const { NS_ASSERT(mPtr != 0); return mPtr; }
T*& Ref() { return mPtr; }
private:
T* mPtr;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
void CopyUTF16(Vector<char, 256>& dest, const WCHAR* text, ULONG count)
{
Vector<WCHAR, 256> source;
source.Resize(count + 1);
wcsncpy_s(source.Data(), source.Size(), text, count);
source[count] = L'\0';
uint32_t numChars = UTF8::UTF16To8((const uint16_t*)source.Data(), dest.Data(), 256);
if (numChars > 256)
{
dest.Reserve(numChars);
UTF8::UTF16To8((const uint16_t*)source.Data(), dest.Data(), numChars);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementation of Text Framework Services text store ACP interface
////////////////////////////////////////////////////////////////////////////////////////////////////
class TsfTextStore: public ITextStoreACP, public ITfContextOwnerCompositionSink,
public ITfTextEditSink
{
public:
TsfTextStore(): mRefs(1), mSink(0), mCategoryMgr(0), mDisplayAttributeMgr(0), mWindow(0),
mLockType(0), mTextLength(0), mReconversionNeeded(false)
{
V(CoCreateInstance(CLSID_TF_CategoryMgr, 0, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr,
(void**)&mCategoryMgr));
V(CoCreateInstance(CLSID_TF_DisplayAttributeMgr, 0, CLSCTX_INPROC_SERVER,
IID_ITfDisplayAttributeMgr, (void**)&mDisplayAttributeMgr));
}
virtual ~TsfTextStore() = 0
{
if (mDisplayAttributeMgr != 0)
{
mDisplayAttributeMgr->Release();
mDisplayAttributeMgr = 0;
}
if (mCategoryMgr != 0)
{
mCategoryMgr->Release();
mCategoryMgr = 0;
}
}
bool IsEnabled() const
{
return mWindow != 0;
}
void OnTextChanged(uint32_t start, uint32_t removed, uint32_t added)
{
if (mSink != 0 && !HasReadLock() && !HasWriteLock())
{
NS_LOG_TRACE("~> OnTextChanged(start=%u, removed=%u, added=%u", start, removed, added);
TS_TEXTCHANGE change = { LONG(start), LONG(start + removed), LONG(start + added) };
mSink->OnTextChange(0, &change);
}
}
void OnSelectionChanged()
{
if (mSink != 0 && !HasReadLock() && !HasWriteLock())
{
NS_LOG_TRACE("~> OnSelectionChanged()");
mSink->OnSelectionChange();
}
}
void OnLayoutChanged()
{
if (mSink != 0 && !HasReadLock() && !HasWriteLock())
{
NS_LOG_TRACE("~> OnLayoutChanged()");
mSink->OnLayoutChange(TS_LC_CHANGE, VIEW_COOKIE);
}
}
// From IUnknown
//@{
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj) override
{
*ppvObj = NULL;
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITextStoreACP))
{
*ppvObj = static_cast<ITextStoreACP*>(this);
}
else if (IsEqualIID(riid, IID_ITfContextOwnerCompositionSink))
{
*ppvObj = static_cast<ITfContextOwnerCompositionSink*>(this);
}
else if (IsEqualIID(riid, IID_ITfTextEditSink))
{
*ppvObj = static_cast<ITfTextEditSink*>(this);
}
if (*ppvObj != 0)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef() override
{
return ++mRefs;
}
STDMETHODIMP_(ULONG) Release() override
{
ULONG newRefs;
newRefs = --mRefs;
if (newRefs == 0)
{
delete this;
}
return newRefs;
}
//@}
// From ITextStoreACP
//@{
STDMETHODIMP AdviseSink(REFIID riid, IUnknown* punk, DWORD) override
{
if (!IsEqualGUID(riid, IID_ITextStoreACPSink))
{
return TS_E_NOOBJECT;
}
if (FAILED(punk->QueryInterface<ITextStoreACPSink>(&mSink)))
{
return E_NOINTERFACE;
}
return S_OK;
}
STDMETHODIMP UnadviseSink(IUnknown*) override
{
// we're dealing with TSF. We don't have to check punk is same instance of mSink.
if (mSink != 0)
{
mSink->Release();
mSink = NULL;
}
return S_OK;
}
STDMETHODIMP RequestLock(DWORD dwLockFlags, HRESULT* phrSession) override
{
if (mSink == 0 || !IsEnabled())
{
return E_FAIL;
}
if (phrSession == 0)
{
return E_INVALIDARG;
}
NS_LOG_TRACE("RequestLock(lockFlags=%ld)", dwLockFlags);
if (mLockType != 0)
{
if ((dwLockFlags & TS_LF_SYNC) != 0)
{
NS_LOG_TRACE("--> Error: Already locked");
// Can't lock synchronously
*phrSession = TS_E_SYNCHRONOUS;
return S_OK;
}
else
{
NS_LOG_TRACE("--> Queue lock");
// Queue an asynchronous lock
mLockQueue.PushBack(dwLockFlags & TS_LF_READWRITE);
*phrSession = TS_S_ASYNC;
return S_OK;
}
}
// Lock
NS_LOG_TRACE("--> Lock Begin");
mLockType = dwLockFlags & TS_LF_READWRITE;
*phrSession = mSink->OnLockGranted(mLockType);
mLockType = 0;
NS_LOG_TRACE("--> Lock End");
// Handle pending lock requests
while (!mLockQueue.Empty())
{
NS_LOG_TRACE("RequestLock(lockFlags=%ld) [queued]", mLockQueue.Front());
NS_LOG_TRACE("--> Lock Begin");
mLockType = mLockQueue.Front();
mLockQueue.Erase(mLockQueue.Begin());
mSink->OnLockGranted(mLockType);
mLockType = 0;
NS_LOG_TRACE("--> Lock End");
}
return S_OK;
}
STDMETHODIMP GetStatus(TS_STATUS* pdcs) override
{
if (pdcs == 0)
{
return E_INVALIDARG;
}
// We use transitory contexts and we don't support hidden text
pdcs->dwDynamicFlags = 0;
pdcs->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;
return S_OK;
}
STDMETHODIMP QueryInsert(LONG acpInsertStart, LONG acpInsertEnd, ULONG cch,
LONG* pacpResultStart, LONG* pacpResultEnd) override
{
if (pacpResultStart == 0 || pacpResultEnd == 0)
{
return E_INVALIDARG;
}
NS_LOG_TRACE("QueryInsert(acpStart=%ld, acpEnd=%ld, cch=%lu)", acpInsertStart,
acpInsertEnd, cch);
*pacpResultStart = acpInsertStart;
*pacpResultEnd = acpInsertStart + cch;
return S_OK;
}
STDMETHODIMP GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP* pSelection,
ULONG* pcFetched) override
{
if (ulCount == 0 || pcFetched == 0)
{
return E_INVALIDARG;
}
if (!HasReadLock())
{
return TS_E_NOLOCK;
}
NS_LOG_TRACE("GetSelection(ulIndex=%lu, ulCount=%lu)", ulIndex, ulCount);
*pcFetched = 0;
if (ulCount > 0 && (ulIndex == 0 || ulIndex == TS_DEFAULT_SELECTION))
{
pSelection[0].acpStart = GetSelectionStart();
pSelection[0].acpEnd = GetSelectionLength() + pSelection[0].acpStart;
pSelection[0].style.ase = TS_AE_END;
pSelection[0].style.fInterimChar = FALSE;
*pcFetched = 1;
NS_LOG_TRACE("--> acpStart=%ld, acpEnd=%ld", pSelection[0].acpStart,
pSelection[0].acpEnd);
}
return S_OK;
}
STDMETHODIMP SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) override
{
if (!HasWriteLock())
{
return TF_E_NOLOCK;
}
NS_LOG_TRACE("SetSelection(ulCount=%lu, Sel.acpStart=%ld, Sel.acpEnd=%ld)", ulCount,
pSelection[0].acpStart, pSelection[0].acpEnd);
if (ulCount > 0)
{
SetSelectionStart(pSelection[0].acpStart);
SetSelectionLength(pSelection[0].acpEnd - pSelection[0].acpStart);
}
return S_OK;
}
STDMETHODIMP GetText(LONG acpStart, LONG acpEnd, __out_ecount(cchPlainReq) WCHAR* pchPlain,
ULONG cchPlainReq, ULONG* pcchPlainOut, TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
ULONG* pulRunInfoOut, LONG* pacpNext) override
{
if (pcchPlainOut == 0 || pulRunInfoOut == 0 || pacpNext == 0)
{
return E_INVALIDARG;
}
if (pchPlain == 0 && cchPlainReq != 0)
{
return E_INVALIDARG;
}
if (prgRunInfo == 0 && ulRunInfoReq != 0)
{
return E_INVALIDARG;
}
if (acpStart < 0 || acpEnd < -1)
{
return E_INVALIDARG;
}
if (!HasReadLock())
{
return TF_E_NOLOCK;
}
*((uint16_t*)pchPlain) = 0;
if (cchPlainReq == 0 && ulRunInfoReq == 0)
{
return S_OK;
}
NS_LOG_TRACE("GetText(acpStart=%ld, acpEnd=%ld, cchPlainReq=%lu)",
acpStart, acpEnd, cchPlainReq);
const char* text = GetText();
uint32_t textLength = GetTextLength();
if (acpEnd == -1)
{
acpEnd = textLength;
}
acpEnd = Min(acpEnd, acpStart + (int)cchPlainReq);
if (acpStart < acpEnd)
{
UTF8::Advance(text, acpStart);
UTF8::UTF8To16(text, (uint16_t*)pchPlain, acpEnd - acpStart + 1);
}
NS_LOG_TRACE("--> text=\"%s\", cch=%ld", text, acpEnd - acpStart);
*pcchPlainOut = acpEnd - acpStart;
if (ulRunInfoReq != 0)
{
prgRunInfo[0].uCount = acpEnd - acpStart;
prgRunInfo[0].type = TS_RT_PLAIN;
*pulRunInfoOut = 1;
}
*pacpNext = acpEnd;
return S_OK;
}
STDMETHODIMP SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
__in_ecount(cch) const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) override
{
if (acpStart < 0 || acpEnd < 0)
{
return E_INVALIDARG;
}
if (!HasWriteLock())
{
return TF_E_NOLOCK;
}
int textLength = GetTextLength();
if (acpStart > textLength)
{
return E_INVALIDARG;
}
NS_LOG_TRACE("SetText(flags=%ld, acpStart=%ld, acpEnd=%ld)", dwFlags, acpStart, acpEnd);
// A reconversion can be initiated when user presses the Space key and some text is,
// selected, in that case the conversion candidate list is shown and we are finished here
if (TryReconversion(pchText, cch))
{
return S_OK;
}
SetSelectionStart(acpStart);
SetSelectionLength(acpEnd - acpStart);
Vector<char, 256> buffer;
CopyUTF16(buffer, pchText, cch);
NS_LOG_TRACE("--> text=\"%s\", cch=%lu", buffer.Data(), cch);
SetSelectedText(buffer.Data());
if (pChange != 0)
{
pChange->acpStart = acpStart;
pChange->acpOldEnd = acpEnd;
pChange->acpNewEnd = acpStart + cch;
}
return S_OK;
}
STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**) override
{
return E_NOTIMPL;
}
STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**) override
{
// We don't support any embedded objects.
return E_NOTIMPL;
}
STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*) override
{
// We don't support any embedded objects.
return E_NOTIMPL;
}
STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID* paFilterAttrs) override
{
if (paFilterAttrs == 0)
{
return E_INVALIDARG;
}
// We don't support attributes
return E_FAIL;
}
STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD) override
{
// We don't support any document attributes. This method just returns S_OK, and subsequently
// called RetrieveRequestedAttrs() returns 0 as the number of supported attributes
return S_OK;
}
STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD) override
{
// We don't support any document attributes. This method just returns S_OK, and subsequently
// called RetrieveRequestedAttrs() returns 0 as the number of supported attributes
return S_OK;
}
STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*, DWORD,
LONG* pacpNext, BOOL* pfFound, LONG* plFoundOffset) override
{
if (pacpNext == 0 || pfFound == 0 || plFoundOffset == 0)
{
return E_INVALIDARG;
}
// We don't support any attributes, so we always return "not found"
*pacpNext = 0;
*pfFound = FALSE;
*plFoundOffset = 0;
return S_OK;
}
STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL* paAttrVals, ULONG* pcFetched) override
{
if (paAttrVals == 0 || pcFetched == 0)
{
return E_INVALIDARG;
}
*pcFetched = 0;
return S_OK;
}
STDMETHODIMP GetEndACP(LONG* pacp) override
{
if (pacp == 0)
{
return E_INVALIDARG;
}
if (!HasReadLock())
{
return TS_E_NOLOCK;
}
*pacp = (LONG)GetTextLength();
return S_OK;
}
STDMETHODIMP GetActiveView(TsViewCookie* pvcView) override
{
// We support only one view
*pvcView = VIEW_COOKIE;
return S_OK;
}
STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*) override
{
return E_NOTIMPL;
}
STDMETHODIMP GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT* prc,
BOOL* pfClipped) override
{
if (prc == 0 || pfClipped == 0 || vcView != VIEW_COOKIE)
{
return E_INVALIDARG;
}
if (acpStart < 0 || acpEnd < 0)
{
return E_INVALIDARG;
}
if (!HasReadLock())
{
return TF_E_NOLOCK;
}
NS_LOG_TRACE("GetTextExt(vcView=%ld, acpStart=%ld, acpEnd=%ld)", vcView, acpStart, acpEnd);
// Make sure control is measured and arranged correctly
UpdateLayout();
// Get bounding box of the selected text
Rect bounds = GetRangeBounds(acpStart, acpEnd);
// Add some space for the underlines
float fontSize = GetFontSize();
bounds.height += Max(1.0f, fontSize * 0.1f);
// Convert bounding box vertices to client coordinates
Visual* textPresenter = GetTextPresenter();
Point topLeft = textPresenter->PointToScreen(bounds.GetTopLeft());
Point bottomRight = textPresenter->PointToScreen(bounds.GetBottomRight());
prc->left = (LONG)Round(topLeft.x);
prc->right = (LONG)Round(bottomRight.x);
prc->top = (LONG)Round(topLeft.y);
prc->bottom = (LONG)Round(bottomRight.y);
ClientToScreen(mWindow, (POINT*)&prc->left);
ClientToScreen(mWindow, (POINT*)&prc->right);
NS_LOG_TRACE("--> (%ld, %ld), %ldx%ld",
prc->left, prc->top, prc->right - prc->left, prc->bottom - prc->top);
*pfClipped = FALSE;
return S_OK;
}
STDMETHODIMP GetScreenExt(TsViewCookie vcView, RECT* prc) override
{
if (prc == 0 || vcView != VIEW_COOKIE)
{
return E_INVALIDARG;
}
NS_LOG_TRACE("GetScreenExt(vcView=%ld)", vcView);
GetClientRect(mWindow, prc);
ClientToScreen(mWindow, (POINT*)&prc->left);
ClientToScreen(mWindow, (POINT*)&prc->right);
return S_OK;
}
STDMETHODIMP GetWnd(TsViewCookie vcView, HWND* phWnd) override
{
if (phWnd == 0 || vcView != VIEW_COOKIE)
{
return E_INVALIDARG;
}
*phWnd = mWindow;
return S_OK;
}
STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*) override
{
// We don't support any embedded objects.
return E_NOTIMPL;
}
STDMETHODIMP InsertTextAtSelection(DWORD dwFlags, __in_ecount(cch) const WCHAR* pchText,
ULONG cch, LONG* pacpStart, LONG* pacpEnd, TS_TEXTCHANGE* pChange) override
{
NS_LOG_TRACE("InsertTextAtSelection(flags=%ld)", dwFlags);
LONG acpStart = (LONG)GetSelectionStart();
LONG acpEnd = (LONG)GetSelectionLength() + acpStart;
if ((dwFlags & TS_IAS_QUERYONLY) != 0)
{
if (!HasReadLock())
{
return TF_E_NOLOCK;
}
if (pacpStart != 0)
{
*pacpStart = acpStart;
}
if (pacpEnd != 0)
{
*pacpEnd = acpEnd;
}
NS_LOG_TRACE("--> QueryOnly: acpStart=%ld, acpEnd=%ld", acpStart, acpEnd);
return S_OK;
}
if (pchText == 0)
{
return E_INVALIDARG;
}
if (!HasWriteLock())
{
return TS_E_NOLOCK;
}
if (pchText != 0 && cch > 0)
{
Vector<char, 256> buffer;
CopyUTF16(buffer, pchText, cch);
SetSelectedText(buffer.Data());
NS_LOG_TRACE("--> text=\"%s\", cch=%lu", buffer.Data(), cch);
}
if (pacpStart != 0)
{
*pacpStart = acpStart;
}
if (pacpEnd != 0)
{
*pacpEnd = acpStart + cch;
}
if (pChange != 0)
{
pChange->acpStart = acpStart;
pChange->acpOldEnd = acpEnd;
pChange->acpNewEnd = acpStart + cch;
}
SetSelectionStart(acpStart);
SetSelectionLength(cch);
NS_LOG_TRACE("--> acpStart=%ld, acpEnd=%ld", acpStart, acpStart + cch);
return S_OK;
}
STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
TS_TEXTCHANGE*) override
{
// We don't support any embedded objects.
return E_NOTIMPL;
}
//@}
// From ITfContextOwnerCompositionSink
//@{
STDMETHODIMP OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) override
{
NS_LOG_TRACE("OnStartComposition(composition=%p)", pComposition);
if (pfOk != 0)
{
*pfOk = TRUE;
}
return S_OK;
}
STDMETHODIMP OnUpdateComposition(ITfCompositionView * pComposition, ITfRange* pRange) override
{
NS_LOG_TRACE("OnUpdateComposition(composition=%p, range=%p)", pComposition, pRange);
return S_OK;
}
STDMETHODIMP OnEndComposition(ITfCompositionView* pComposition) override
{
NS_LOG_TRACE("OnEndComposition(composition=%p)", pComposition);
return S_OK;
}
//@}
// From ITfTextEditSink
//@{
STDMETHODIMP OnEndEdit(ITfContext* context, TfEditCookie ecReadOnly,
ITfEditRecord* pEditRecord) override
{
if (context == 0 || pEditRecord == 0)
{
return E_INVALIDARG;
}
if (!HasWriteLock())
{
// nothing modified
return S_OK;
}
NS_LOG_TRACE("OnEndEdit(context=%p, editCookie=%ld, editRecor=%p)", context, ecReadOnly,
pEditRecord);
const GUID* guids[2] = { &GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE };
ComPtr<ITfReadOnlyProperty> trackProperty;
V(context->TrackProperties(guids, 2, NULL, 0, &trackProperty.Ref()));
ClearCompositionUnderlines();
ComPtr<ITfRange> startEndRange;
ComPtr<ITfRange> endRange;
V(context->GetStart(ecReadOnly, &startEndRange.Ref()));
V(context->GetEnd(ecReadOnly, &endRange.Ref()));
V(startEndRange->ShiftEndToRange(ecReadOnly, endRange, TF_ANCHOR_END));
ComPtr<IEnumTfRanges> ranges;
V(trackProperty->EnumRanges(ecReadOnly, &ranges.Ref(), startEndRange));
for (;;)
{
ULONG fetched;
ComPtr<ITfRange> range;
if (ranges->Next(1, &range.Ref(), &fetched) != S_OK)
{
break;
}
VARIANT value;
ComPtr<IEnumTfPropertyValue> enumPropertyValue;
V(trackProperty->GetValue(ecReadOnly, range, &value));
NS_ASSERT(value.punkVal != 0);
V(value.punkVal->QueryInterface<IEnumTfPropertyValue>(&enumPropertyValue.Ref()));
bool isComposing = false;
bool hasDisplayAttribute = false;
TF_PROPERTYVAL propertyValue = TF_PROPERTYVAL();
TF_DISPLAYATTRIBUTE displayAttribute = TF_DISPLAYATTRIBUTE();
while (enumPropertyValue->Next(1, &propertyValue, &fetched) == S_OK)
{
if (IsEqualGUID(propertyValue.guidId, GUID_PROP_COMPOSING))
{
isComposing = propertyValue.varValue.lVal == TRUE;
}
else if (IsEqualGUID(propertyValue.guidId, GUID_PROP_ATTRIBUTE))
{
TfGuidAtom atom = static_cast<TfGuidAtom>(propertyValue.varValue.lVal);
hasDisplayAttribute = GetDisplayAttribute(atom, &displayAttribute);
}
VariantClear(&propertyValue.varValue);
}
ComPtr<ITfRangeACP> acpRange;
V(range->QueryInterface<ITfRangeACP>(&acpRange.Ref()));
LONG start, length;
V(acpRange->GetExtent(&start, &length));
if (isComposing)
{
CompositionLineStyle style = (CompositionLineStyle)displayAttribute.lsStyle;
CompositionUnderline underline = { uint32_t(start), uint32_t(start + length), style,
displayAttribute.fBoldLine == TRUE ? true : false };
AddCompositionUnderline(underline);
NS_LOG_TRACE("--> Underline: %u-%u, %d, %s", underline.start, underline.end,
(int)style, underline.bold ? "Bold" : "Normal");
}
VariantClear(&value);
}
if (mReconversionNeeded)
{
mReconversionNeeded = false;
DoReconversion();
}
return S_OK;
}
//@}
protected:
void AssociateWindow(HWND hWnd)
{
mWindow = hWnd;
}
virtual int32_t GetSelectionStart() const = 0;
virtual void SetSelectionStart(int32_t start) = 0;
virtual int32_t GetSelectionLength() const = 0;
virtual void SetSelectionLength(int32_t len) = 0;
virtual void SetSelectedText(const char* text) = 0;
virtual const char* GetText() const = 0;
virtual uint32_t GetTextLength() const = 0;
virtual Rect GetRangeBounds(uint32_t start, uint32_t end) const = 0;
virtual float GetFontSize() const = 0;
virtual void UpdateLayout() = 0;
virtual Visual* GetTextPresenter() const = 0;
virtual void AddCompositionUnderline(const CompositionUnderline& underline) const = 0;
virtual void ClearCompositionUnderlines() const = 0;
virtual bool TryReconversion() = 0;
virtual void DoReconversion() = 0;
private:
bool HasReadLock() const
{
return (mLockType & TS_LF_READ) == TS_LF_READ;
}
bool HasWriteLock() const
{
return (mLockType & TS_LF_READWRITE) == TS_LF_READWRITE;
}
bool IsSpaceChar(WCHAR ch)
{
const WCHAR halfWidthSpace = 0x0020;
const WCHAR fullWidthSpace = 0x3000;
return ch == halfWidthSpace || ch == fullWidthSpace;
}
bool TryReconversion(const WCHAR* text, ULONG len)
{
if (GetSelectionLength() > 0)
{
if ((len == 0 || len == 1) && IsSpaceChar(*text))
{
if (TryReconversion())
{
mReconversionNeeded = true;
return true;
}
}
}
return false;
}
bool GetDisplayAttribute(TfGuidAtom atom, TF_DISPLAYATTRIBUTE* attribute) const
{
GUID guid;
if (FAILED(mCategoryMgr->GetGUID(atom, &guid)))
{
return false;
}
ComPtr<ITfDisplayAttributeInfo> info;
if (FAILED(mDisplayAttributeMgr->GetDisplayAttributeInfo(guid, &info.Ref(), NULL)))
{
return false;
}
return SUCCEEDED(info->GetAttributeInfo(attribute));
}
private:
ULONG mRefs;
ITextStoreACPSink* mSink;
ITfCategoryMgr* mCategoryMgr;
ITfDisplayAttributeMgr* mDisplayAttributeMgr;
HWND mWindow;
DWORD mLockType;
uint32_t mTextLength;
bool mReconversionNeeded;
Vector<DWORD> mLockQueue;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
class TsfTextBoxTextStore final: public TsfTextStore
{
public:
TsfTextBoxTextStore(): mEditContext(0), mEditCookie(TF_INVALID_COOKIE), mReconversion(0),
mReconversionRange(0), mControl(0), mTextLength(0)
{
}
~TsfTextBoxTextStore()
{
if (mEditContext != 0)
{
mEditContext->Release();
mEditContext = 0;
mEditCookie = TF_INVALID_COOKIE;
}
if (mReconversionRange != 0)
{
mReconversionRange->Release();
mReconversionRange = 0;
}
if (mReconversion != 0)
{
mReconversion->Release();
mReconversion = 0;
}
}
void SetEditContext(ITfContext* editContext, TfEditCookie editCookie,
ITfFnReconversion* reconversion)
{
mEditContext = editContext;
mEditContext->AddRef();
mEditCookie = editCookie;
mReconversion = reconversion;
mReconversion->AddRef();
}
void AssociateControl(HWND hWnd, TextBox* control)
{
if (mControl != 0)
{
UnregisterScrollChanged();
UnregisterSelectionChanged();
UnregisterTextChanged();
mTextLength = 0;
}
AssociateWindow(hWnd);
mControl = control;
if (mControl != 0)
{
mTextLength = UTF8::Length(GetText());
RegisterTextChanged();
RegisterSelectionChanged();
RegisterScrollChanged();
}
}
TextBox* GetControl() const
{
return mControl;
}
protected:
int32_t GetSelectionStart() const override
{
NS_ASSERT(mControl != 0);
return mControl->GetSelectionStart();
}
void SetSelectionStart(int32_t start) override
{
NS_ASSERT(mControl != 0);
mControl->SetSelectionStart(start);
}
int32_t GetSelectionLength() const override
{
NS_ASSERT(mControl != 0);
return mControl->GetSelectionLength();
}
void SetSelectionLength(int32_t len) override
{
NS_ASSERT(mControl != 0);
mControl->SetSelectionLength(len);
}
void SetSelectedText(const char* text) override
{
NS_ASSERT(mControl != 0);
mControl->SetSelectedText(text);
}
const char* GetText() const override
{
NS_ASSERT(mControl != 0);
return mControl->GetText();
}
uint32_t GetTextLength() const
{
return mTextLength;
}
Rect GetRangeBounds(uint32_t start, uint32_t end) const override
{
NS_ASSERT(mControl != 0);
return mControl->GetRangeBounds(start, end);
}
float GetFontSize() const override
{
NS_ASSERT(mControl != 0);
return mControl->GetFontSize();
}
void UpdateLayout() override
{
NS_ASSERT(mControl != 0);
mControl->UpdateLayout();
}
Visual* GetTextPresenter() const override
{
return mControl->GetTextView();
}
void AddCompositionUnderline(const CompositionUnderline& underline) const override
{
mControl->AddCompositionUnderline(underline);
}
void ClearCompositionUnderlines() const override
{
mControl->ClearCompositionUnderlines();
}
bool TryReconversion() override
{
NS_ASSERT(mEditContext != 0);
NS_ASSERT(mReconversion != 0);
NS_LOG_TRACE("TryReconversion()");
ULONG fetched = 0;
TF_SELECTION selection = TF_SELECTION();
V(mEditContext->GetSelection(mEditCookie, TF_DEFAULT_SELECTION, 1, &selection, &fetched));
NS_ASSERT(fetched > 0);
BOOL convertable = FALSE;
ComPtr<ITfRange> range;
V(mReconversion->QueryRange(selection.range, &range.Ref(), &convertable));
if (convertable != FALSE && range != 0)
{
mReconversionRange = (ITfRange*)range;
mReconversionRange->AddRef();
return true;
}
return false;
}
void DoReconversion() override
{
NS_ASSERT(mReconversion != 0);
NS_ASSERT(mReconversionRange != 0);
NS_LOG_TRACE("DoReconversion()");
V(mReconversion->Reconvert(mReconversionRange));
mReconversionRange->Release();
mReconversionRange = 0;
}
private:
void RegisterTextChanged()
{
NS_ASSERT(mControl != 0);
mControl->TextChanged() += MakeDelegate(this, &TsfTextBoxTextStore::OnControlTextChanged);
}
void UnregisterTextChanged()
{
NS_ASSERT(mControl != 0);
mControl->TextChanged() -= MakeDelegate(this, &TsfTextBoxTextStore::OnControlTextChanged);
}
void OnControlTextChanged(BaseComponent*, const RoutedEventArgs&)
{
uint32_t newLength = UTF8::Length(GetText());
uint32_t removed = mTextLength > newLength ? mTextLength - newLength : 0;
uint32_t added = mTextLength < newLength ? newLength - mTextLength : 0;
mTextLength = newLength;
OnTextChanged(GetSelectionStart(), removed, added);
}
void RegisterSelectionChanged()
{
NS_ASSERT(mControl != 0);
mControl->SelectionChanged() += MakeDelegate(this,
&TsfTextBoxTextStore::OnControlSelectionChanged);
}
void UnregisterSelectionChanged()
{
NS_ASSERT(mControl != 0);
mControl->SelectionChanged() -= MakeDelegate(this,
&TsfTextBoxTextStore::OnControlSelectionChanged);
}
void OnControlSelectionChanged(BaseComponent*, const RoutedEventArgs&)
{
OnSelectionChanged();
}
void RegisterScrollChanged()
{
NS_ASSERT(mControl != 0);
mControl->AddHandler(ScrollViewer::ScrollChangedEvent, MakeDelegate(this,
&TsfTextBoxTextStore::OnScrollChanged));
}
void UnregisterScrollChanged()
{
NS_ASSERT(mControl != 0);
mControl->RemoveHandler(ScrollViewer::ScrollChangedEvent, MakeDelegate(this,
&TsfTextBoxTextStore::OnScrollChanged));
}
void OnScrollChanged(BaseComponent*, const RoutedEventArgs&)
{
OnLayoutChanged();
}
private:
ITfContext* mEditContext;
TfEditCookie mEditCookie;
ITfFnReconversion* mReconversion;
ITfRange* mReconversionRange;
TextBox* mControl;
uint32_t mTextLength;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
int gInitCount = 0;
ITfThreadMgr* gThreadMgr = 0;
TfClientId gClientId = TF_CLIENTID_NULL;
ITfDocumentMgr* gTextBoxDocumentMgr = 0;
TsfTextBoxTextStore* gTextBoxStore = 0;
DWORD gTextBoxEditCookie = TF_INVALID_COOKIE;
HWND gCurrentWindow = 0;
////////////////////////////////////////////////////////////////////////////////////////////////////
void SetCompartmentValue(ITfCompartmentMgr* compartmentMgr, const GUID& key, DWORD value)
{
ComPtr<ITfCompartment> compartment;
V(compartmentMgr->GetCompartment(key, &compartment.Ref()));
VARIANT variant;
::VariantInit(&variant);
variant.vt = VT_I4;
variant.lVal = value;
V(compartment->SetValue(gClientId, &variant));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateTextBoxDocument()
{
V(gThreadMgr->CreateDocumentMgr(&gTextBoxDocumentMgr));
gTextBoxStore = new TsfTextBoxTextStore();
TfEditCookie inputCookie;
ComPtr<ITfContext> inputContext;
V(gTextBoxDocumentMgr->CreateContext(gClientId, 0, (ITextStoreACP*)gTextBoxStore,
&inputContext.Ref(), &inputCookie));
ComPtr<ITfFunctionProvider> functionProvider;
V(gThreadMgr->GetFunctionProvider(GUID_SYSTEM_FUNCTIONPROVIDER,
&functionProvider.Ref()));
ComPtr<ITfFnReconversion> reconversion;
V(functionProvider->GetFunction(GUID_NULL, IID_ITfFnReconversion,
(IUnknown**)&reconversion.Ref()));
gTextBoxStore->SetEditContext(inputContext, inputCookie, reconversion);
V(gTextBoxDocumentMgr->Push(inputContext));
ComPtr<ITfSource> source;
V(inputContext->QueryInterface<ITfSource>(&source.Ref()));
V(source->AdviseSink(IID_ITfTextEditSink, (ITfTextEditSink*)gTextBoxStore,
&gTextBoxEditCookie));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Japanese IME expects the default value of this compartment to be TF_SENTENCEMODE_PHRASEPREDICT
// like IMM32 implementation. This value does not affect other language's IME behaviors
void ConfigureJapaneseIME()
{
ComPtr<ITfCompartmentMgr> compartmentMgr;
V(gThreadMgr->QueryInterface<ITfCompartmentMgr>(&compartmentMgr.Ref()));
SetCompartmentValue(compartmentMgr, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE,
TF_SENTENCEMODE_PHRASEPREDICT);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void DestroyTextBoxDocument()
{
if (gTextBoxDocumentMgr != 0)
{
ComPtr<ITfContext> inputContext;
V(gTextBoxDocumentMgr->GetTop(&inputContext.Ref()));
if (gTextBoxEditCookie != TF_INVALID_COOKIE)
{
ComPtr<ITfSource> source;
V(inputContext->QueryInterface<ITfSource>(&source.Ref()));
V(source->UnadviseSink(gTextBoxEditCookie));
gTextBoxEditCookie = TF_INVALID_COOKIE;
}
gTextBoxDocumentMgr->Pop(TF_POPF_ALL);
gTextBoxDocumentMgr->Release();
gTextBoxDocumentMgr = 0;
}
if (gTextBoxStore != 0)
{
gTextBoxStore->Release();
gTextBoxStore = 0;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CancelComposition()
{
ComPtr<ITfContext> inputContext;
V(gTextBoxDocumentMgr->GetBase(&inputContext.Ref()));
ComPtr<ITfContextOwnerCompositionServices> owner;
V(inputContext->QueryInterface<ITfContextOwnerCompositionServices>(&owner.Ref()));
V(owner->TerminateComposition(0));
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::Init()
{
if (gInitCount++ == 0)
{
V(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
V(CoCreateInstance(CLSID_TF_ThreadMgr, 0, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr,
(void**)&gThreadMgr));
V(gThreadMgr->Activate(&gClientId));
CreateTextBoxDocument();
ConfigureJapaneseIME();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::Shutdown()
{
if (gInitCount > 0 && --gInitCount == 0)
{
gCurrentWindow = 0;
DestroyTextBoxDocument();
if (gThreadMgr != 0)
{
V(gThreadMgr->Deactivate());
gThreadMgr->Release();
gThreadMgr = 0;
gClientId = TF_CLIENTID_NULL;
}
CoUninitialize();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::ActivateWindow(void* hWnd)
{
NS_LOG_TRACE("ActivateWindow(hWnd=%p)", hWnd);
NS_ASSERT(gCurrentWindow == 0 || gCurrentWindow == hWnd);
gCurrentWindow = (HWND)hWnd;
// NOTE: When hWnd is activated, the associated Noesis View automatically restores keyboard
// focus to the corresponding control. If it is a TextBox, then OnShowKeyboard will be called.
// But calls to gThreadMgr->SetFocus() inside WM_ACTIVATE seem to have no effect, so we need to
// do it later. We enqueue a move message and we will call SetFocus() inside TSF::MoveWindow().
PostMessage(gCurrentWindow, WM_EXITSIZEMOVE, 0, 0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::DeactivateWindow(void* hWnd)
{
NS_LOG_TRACE("DeactivateWindow(hWnd=%p)", hWnd);
NS_ASSERT(gCurrentWindow == hWnd);
gCurrentWindow = 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::MoveWindow(void* hWnd)
{
NS_LOG_TRACE("MoveWindow(hWnd=%p)", hWnd);
if (gCurrentWindow == hWnd && gThreadMgr != 0)
{
ITfDocumentMgr* doc;
NS_ASSERT(gTextBoxStore != 0);
if (gTextBoxStore->IsEnabled())
{
V(gThreadMgr->AssociateFocus(gCurrentWindow, gTextBoxDocumentMgr, &doc));
gTextBoxStore->OnLayoutChanged();
}
else
{
V(gThreadMgr->AssociateFocus(gCurrentWindow, NULL, &doc));
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::ShowKeyboard(UIElement* focused)
{
if (gCurrentWindow != 0)
{
ITfDocumentMgr* doc;
TextBox* textBox = DynamicCast<TextBox*>(focused);
if (textBox != 0)
{
gTextBoxStore->AssociateControl(gCurrentWindow, textBox);
V(gThreadMgr->AssociateFocus(gCurrentWindow, gTextBoxDocumentMgr, &doc));
return;
}
V(gThreadMgr->AssociateFocus(gCurrentWindow, NULL, &doc));
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void TSF::HideKeyboard()
{
if (gTextBoxStore->IsEnabled())
{
CancelComposition();
gTextBoxStore->AssociateControl(0, 0);
}
if (gCurrentWindow != 0)
{
ITfDocumentMgr* doc;
V(gThreadMgr->AssociateFocus(gCurrentWindow, NULL, &doc));
}
}
| ||||
| Platform | Any | ||||
|
I wanted to take a look on this and want to make sure I have all the information. Is this happening when using a windows device with our TSF implementation, right? |
|
|
Hi Sergio, Yes, it is happening when using a windows device with the TSF implementation. |
|
|
Another way to reproduce the problem is using your iPad or Android tablets, and use Microsoft RDP client to RDP to your windows PC. Then run Noesis application on your windows PC and operate from the iOS/Android tablet. |
|
|
Thanks for the tip, unfortunately I installed Microsoft RDP on my Android phone and I'm not able to reproduce the problem. Could you please enable trace logging in TSF.ccp (uncomment first two define lines) and attach here the logs generated until touch keyboard appears, I want to follow which events are happening in that case. Thanks for your help. |
|
|
Hi, here is the logs: [NOESIS][] MoveWindow(hWnd=0000000000A51F4A) (G:\Athene_Noesis3.0\libz\Noesis\Native\Src\Packages\App\Win32Display\Src\TSF.cpp:1551) Note that it doesn't reproduce if you tap at buttons. I tap at an empty grid to reproduce it. |
|
|
One more question, is this only happening when you spawn new windows on the same application? I mean, if you try it with a single window application like our samples does the virtual keyboard also appear when clicking anywhere in the window? |
|
|
Yes, I tried with your RSS Reader sample app. It happens as well. |
|
|
I may have found a way to solve this and did some changes to TSF code. |
|
|
Hi Sergio, Yes, it seems to work fine ! Thank you. |
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2020-07-27 03:53 | nikobarli | New Issue | |
| 2020-07-27 11:55 | sfernandez | Assigned To | => sfernandez |
| 2020-07-27 11:55 | sfernandez | Status | new => assigned |
| 2020-07-27 11:55 | sfernandez | Target Version | => 3.0.4 |
| 2020-08-02 10:56 | jsantos | Target Version | 3.0.4 => 3.0.5 |
| 2020-08-25 10:25 | sfernandez | Status | assigned => feedback |
| 2020-08-25 10:25 | sfernandez | Note Added: 0006591 | |
| 2020-08-27 08:43 | nikobarli | Note Added: 0006592 | |
| 2020-08-27 08:43 | nikobarli | Status | feedback => assigned |
| 2020-08-27 08:54 | nikobarli | Note Added: 0006593 | |
| 2020-08-27 12:23 | sfernandez | Status | assigned => feedback |
| 2020-08-27 12:23 | sfernandez | Note Added: 0006594 | |
| 2020-08-27 14:24 | nikobarli | Note Added: 0006596 | |
| 2020-08-27 14:24 | nikobarli | Status | feedback => assigned |
| 2020-09-03 10:52 | sfernandez | Status | assigned => feedback |
| 2020-09-03 10:52 | sfernandez | Note Added: 0006613 | |
| 2020-09-05 06:09 | nikobarli | Note Added: 0006616 | |
| 2020-09-05 06:09 | nikobarli | Status | feedback => assigned |
| 2020-09-14 14:09 | sfernandez | Status | assigned => feedback |
| 2020-09-14 14:09 | sfernandez | Note Added: 0006629 | |
| 2020-09-14 14:09 | sfernandez | File Added: TSF.cpp | |
| 2020-09-15 07:10 | nikobarli | Note Added: 0006630 | |
| 2020-09-15 07:10 | nikobarli | Status | feedback => assigned |
| 2020-09-15 07:11 | nikobarli | Note Edited: 0006630 | |
| 2020-09-17 11:08 | sfernandez | Status | assigned => resolved |
| 2020-09-17 11:08 | sfernandez | Resolution | open => fixed |
| 2020-09-17 11:08 | sfernandez | Fixed in Version | => 3.0.5 |