Merge pull request #11884 from keymanapp/fix/windows/7870/add-is-text-selected-flag

fix(windows): add text selected bool emit backspace key when text selected in TSF
This commit is contained in:
rc-swag 2024-07-23 11:19:41 +10:00 committed by GitHub
commit 4e25bbe2e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 172 additions and 58 deletions

View file

@ -272,9 +272,12 @@ BOOL AITIP::ReadContext(PWSTR buf) {
}
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return FALSE;
if(_td->TIPGetContext && (*_td->TIPGetContext)(MAXCONTEXT-1, buf) == S_OK) { // I3575 // I4262
if(!_td) {
return FALSE;
}
if (_td->TIPGetContext && (*_td->TIPGetContext)(MAXCONTEXT - 1, buf, &isTextSelected) == S_OK) { // I3575 // I4262
if(ShouldDebug(sdmKeyboard)) {
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: full context [Updateable=%d] %s", _td->TIPFUpdateable, Debug_UnicodeString(buf));
}

View file

@ -39,6 +39,7 @@ class AITIP : public AIWin2000Unicode
{
private:
BOOL useLegacy;
BOOL isTextSelected;
BOOL PostKeys();
//TOUCH void PostTouchContext();
@ -67,7 +68,14 @@ public:
/* TIP interactions */
BOOL IsLegacy() { return useLegacy; }
BOOL IsLegacy() {
return useLegacy;
}
BOOL
IsTextSelected() {
return isTextSelected;
}
};
/**

View file

@ -151,7 +151,7 @@ public:
/* External interface functions */
typedef HRESULT (WINAPI *PKEYMANPROCESSOUTPUTFUNC)(int n, WCHAR *buf, int nbuf);
typedef HRESULT (WINAPI *PKEYMANGETCONTEXTFUNC)(int n, PWSTR buf);
typedef HRESULT (WINAPI *PKEYMANGETCONTEXTFUNC)(int n, PWSTR buf, BOOL* isTextSelected);
typedef BOOL (WINAPI *PTIPCALLBACK)(); // Tells the TIP to update its status (used to be done through 0x88)
typedef BOOL (WINAPI *PKeymanOutputBackspace)(HWND hwnd);

View file

@ -30,13 +30,25 @@ static void processAlert(AITIP* app) {
}
static void
processBack(AITIP* app, const unsigned int code_points_to_delete, const km_core_usv* delete_context) {
processBack(
AITIP* app,
const unsigned int code_points_to_delete,
const km_core_usv* delete_context,
BOOL* emitKeystroke,
WORD vkey) {
if (app->IsLegacy()) {
for (unsigned int i = 0; i < code_points_to_delete; i++) {
app->QueueAction(QIT_BACK, BK_DEFAULT);
}
}
else {
// If there is a selection emit the key (backspace)
// allowing the application handle clearing selected text in the correct manner.
if (app->IsTextSelected() && (vkey == VK_BACK)) {
*emitKeystroke = TRUE;
return;
}
km_core_usv const* delete_context_ptr = delete_context;
while (*delete_context_ptr) {
delete_context_ptr++;
@ -109,7 +121,7 @@ BOOL ProcessActions(BOOL* emitKeystroke)
_td->CoreProcessEventRun = FALSE;
processBack(_td->app, core_actions->code_points_to_delete, core_actions->deleted_context);
processBack(_td->app, core_actions->code_points_to_delete, core_actions->deleted_context, emitKeystroke, _td->state.vkey);
processUnicodeChar(_td->app, core_actions->output);
if (core_actions->persist_options != NULL) {
processPersistOpt(core_actions, _td->lpActiveKeyboard);

View file

@ -45,7 +45,20 @@ void DllRelease();
void InsertTextAtSelection(TfEditCookie ec, ITfContext *pContext, const WCHAR *pchText, ULONG cchText);
void DeleteLeftOfSelection(TfEditCookie ec, ITfContext *pContext, LONG n);
BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG n); // I4933
/**
* Retrieves the text to the left of the current selection in the context.
* In addition it also returns the output values of the TSF call 'GetSelection'.
*
* @param[in] ec The edit cookie.
* @param[in] pContext The text input context.
* @param[out] buf The buffer to store the text.
* @param[in] n The maximum number of characters to retrieve.
* @param[out] hrGetSelection The result of the 'GetSelection' call.
* @param[out] cFetched The number of selections fetched.
* @param[out] tfSelection The selection details.
* @return TRUE if successful, otherwise FALSE.
*/
BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG n, HRESULT *hrGetSelection, ULONG *cFetched, TF_SELECTION *tfSelection); // I4933
void GetDeadkeyFlags(TfEditCookie ec, ITfContext *pContext, PWSTR buf, int n);
//

View file

@ -1,18 +1,18 @@
/*
Name: inserttext
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Documentation:
Description:
Create Date: 19 Jun 2007
Modified Date: 28 Mar 2016
Authors: mcdurdin
Related Files:
Dependencies:
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
Bugs:
Todo:
Notes:
History: 19 Jun 2007 - mcdurdin - I890 - Fix deadkeys in TSF
19 Jun 2007 - mcdurdin - I890 - Fix crash with deadkeys - logging
11 Dec 2009 - mcdurdin - Header files
@ -67,7 +67,7 @@ void DePseudofy(WCHAR *pchText) // I3564
{
if(*pchText == PseudoMap[k])
{
*pchText = k;
*pchText = k;
break;
}
}
@ -78,7 +78,7 @@ void DePseudofy(WCHAR *pchText) // I3564
void InsertTextAtSelection(TfEditCookie ec, ITfContext *pContext, const WCHAR *pchText, ULONG cchText)
{
LogEnter();
ITfInsertAtSelection *pInsertAtSelection;
ITfRange *pRange;
TF_SELECTION tfSelection;
@ -89,7 +89,7 @@ void InsertTextAtSelection(TfEditCookie ec, ITfContext *pContext, const WCHAR *p
#ifdef DEBUG_PSEUDO // I3607
WCHAR *PseudoBuf = new WCHAR[cchText+1]; // I3564
wcsncpy_s(PseudoBuf, cchText+1, pchText, cchText);
PseudoBuf[cchText] = 0;
Pseudofy(PseudoBuf);
@ -133,7 +133,7 @@ void DeleteLeftOfSelection(TfEditCookie ec, ITfContext *pContext, LONG n)
if(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK || cFetched == 0)
return;
//TODO: log failures
if(tfSelection.range->ShiftStart(ec, -n, &outn, NULL) != S_OK) {
tfSelection.range->Release();
@ -163,20 +163,27 @@ char *debugstr(PWSTR buf) {
return bufout;
}
BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG n) // I4933
BOOL
GetLeftOfSelection(
TfEditCookie ec,
ITfContext *pContext,
WCHAR *buf,
LONG n,
HRESULT *hrGetSelection,
ULONG *cFetched,
TF_SELECTION *tfSelection) // I4933
{
LogEnter();
TF_SELECTION tfSelection = {0};
TF_STATUS tfStatus = {0};
ULONG cFetched;
TF_SELECTION tfSelectionLocal = {0};
ULONG cFetchedLocal;
LONG outn;
ITfRange *pRange, *pRangeEnd;
HRESULT hr;
/* // I4933
First we will try to see if there is any text in the control, and if not, then treat
it as transitory (that is, no ability to read context. This seems to happen in Firefox
it as transitory (that is, no ability to read context. This seems to happen in Firefox
with RICHEDIT controls - for example, SourceForge comment fields e.g. reported on page
https://sourceforge.net/p/greekpolytonicsp/discussion/general/thread/9b6fa46d/
This also happens in Internet Explorer in the same fields. It is unclear at this point
@ -201,11 +208,11 @@ BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG
return FALSE;
}
BOOL bTreatAsTransitory = FALSE;
if(!SUCCEEDED(hr = pRange->GetText(ec, 0, buf, n, &cFetched))) {
BOOL bTreatAsTransitory = FALSE;
if(!SUCCEEDED(hr = pRange->GetText(ec, 0, buf, n, &cFetchedLocal))) {
Log(L"GetLeftOfSelection: Exit -- Failed GetRange (all text to 63 chars) = %x", hr);
bTreatAsTransitory = TRUE;
} else if(cFetched == 0) {
} else if(cFetchedLocal == 0) {
Log(L"GetLeftOfSelection: Exit -- no text in edit control, treating as transitory");
bTreatAsTransitory = TRUE;
}
@ -218,55 +225,62 @@ BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG
return FALSE;
}
/*
/*
At this point, we know we can read content from the edit control, so we just need
to read the range to the left of the selection - up to (n) characters.
*/
if(!SUCCEEDED(hr = pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched))) // I3565
if (!SUCCEEDED(hr = *hrGetSelection = pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, tfSelection, cFetched))) // I3565
{
Log(L"GetLeftOfSelection: Exit -- Failed GetSelection = %x", hr); // I3565
return FALSE;
}
if(cFetched == 0) // I3565
// copy the values for local processing, preserving the function call variables
cFetchedLocal = *cFetched;
tfSelectionLocal = *tfSelection;
if (tfSelectionLocal.range) {
tfSelectionLocal.range->AddRef();
}
if(cFetchedLocal == 0) // I3565
{
Log(L"GetLeftOfSelection: Exit -- cFetched == 0"); // I3565
if(tfSelection.range != NULL)
tfSelection.range->Release();
Log(L"GetLeftOfSelection: Exit -- cFetchedLocal == 0"); // I3565
if(tfSelectionLocal.range != NULL)
tfSelectionLocal.range->Release();
return FALSE;
}
if(!SUCCEEDED(hr = tfSelection.range->Clone(&pRange))) // I3565
if(!SUCCEEDED(hr = tfSelectionLocal.range->Clone(&pRange))) // I3565
{
Log(L"GetLeftOfSelection: Failed range->Clone = %x", hr); // I3565
tfSelection.range->Release();
tfSelectionLocal.range->Release();
return FALSE;
}
if(!SUCCEEDED(hr = pRange->Collapse(ec, TF_ANCHOR_START))) // I3565
{
Log(L"GetLeftOfSelection: Failed range->Collapse = %x", hr); // I3565
tfSelection.range->Release();
tfSelectionLocal.range->Release();
pRange->Release();
return FALSE;
}
if(!SUCCEEDED(hr = pRange->ShiftStart(ec, -n, &outn, NULL))) // I3565
{
Log(L"GetLeftOfSelection: Failed range->ShiftStart = %x", hr); // I3565
tfSelection.range->Release();
tfSelectionLocal.range->Release();
pRange->Release();
return FALSE;
}
BOOL result = TRUE;
if(SUCCEEDED(hr = pRange->GetText(ec, 0, buf, n, &cFetched))) // I3565
if(SUCCEEDED(hr = pRange->GetText(ec, 0, buf, n, &cFetchedLocal))) // I3565
{
buf[cFetched] = 0;
buf[cFetchedLocal] = 0;
if(ShouldDebug()) {
char *p = debugstr(buf);
Log(L"GetLeftOfSelection(%d) = %hs [%d fetched]", n, p, cFetched);
Log(L"GetLeftOfSelection(%d) = %hs [%d fetched]", n, p, cFetchedLocal);
delete[] p;
}
#ifdef DEBUG_PSEUDO // I3607
@ -281,7 +295,7 @@ BOOL GetLeftOfSelection(TfEditCookie ec, ITfContext *pContext, WCHAR *buf, LONG
}
pRange->Release();
tfSelection.range->Release();
tfSelectionLocal.range->Release();
return result; // I4933
}

View file

@ -64,7 +64,14 @@ public:
STDMETHODIMP DoEditSession(TfEditCookie ec);
HRESULT WINAPI KeymanProcessOutput(int n, PWSTR buf, int nbuf); // I3567
HRESULT WINAPI KeymanGetContext(int n, PWSTR buf); // I3567
/**
* Retrieves the current context and checks if text is selected.
* @param[in] n The size of the buffer.
* @param[out] buf The buffer to store the context.
* @param[out] isTextSelected A flag indicating whether text is selected.
* @return S_OK if successful, otherwise an HRESULT error code.
*/
HRESULT WINAPI KeymanGetContext(int n, PWSTR buf, BOOL* isTextSelected); // I3567
HRESULT GetResult() { return _hr; }
private:
@ -75,6 +82,16 @@ private:
LPARAM _lParam; // I3589
DWORD _dwDeepIntegration; // I4375
TfEditCookie _ec;
/**
* Checks if any text is selected in the given context.
*
* @param[in] hrGetSelection The result of a call to GetSelection selection.
* @param[in] cFetched The number of selections fetched.
* @param[in] tfSelection The selection details.
* @param[out] isTextSelected A flag indicating whether text is selected.
* @return S_OK if successful, otherwise an HRESULT error code.
*/
HRESULT KeymanIsTextSelected(HRESULT hrGetSelection, ULONG cFetched, TF_SELECTION tfSelection, BOOL *isTextSelected);
};
#define KEYEVENT_EXTRAINFO_KEYMAN 0xF00F0000 // I4370
@ -153,11 +170,11 @@ extern "C" __declspec(dllexport) HRESULT WINAPI ExtKeymanProcessOutput(int n, WC
return res;
}
extern "C" __declspec(dllexport) HRESULT WINAPI ExtKeymanGetContext(int n, PWSTR buf) // I3567
extern "C" __declspec(dllexport) HRESULT WINAPI ExtKeymanGetContext(int n, PWSTR buf, BOOL* isTextSelected) // I3567
{
LogEnter();
HRESULT res;
if (ExtEditSession) res = ExtEditSession->KeymanGetContext(n, buf);
if (ExtEditSession) res = ExtEditSession->KeymanGetContext(n, buf, isTextSelected);
else res = E_FAIL; // I3567
return res;
}
@ -179,7 +196,7 @@ STDAPI CKeymanEditSession::DoEditSession(TfEditCookie ec)
ExtEditSession = NULL;
// Call keyman32.processtipkey; this may call "KeymanGetContext(n, buf)";
// Call keyman32.processtipkey; this may call "KeymanGetContext(n, buf, isTextSelected)";
// keyman32.processtipkey this will call "KeymanProcessOutput"
// which will delete n chrs from left of cursor and output a text string
// beeps, deadkeys, etc. will be managed from within keyman32.dll itself
@ -219,13 +236,17 @@ HRESULT WINAPI CKeymanEditSession::KeymanProcessOutput(int n, WCHAR *buf, int nb
}
HRESULT WINAPI CKeymanEditSession::KeymanGetContext(int n, PWSTR buf) // I3567
HRESULT WINAPI CKeymanEditSession::KeymanGetContext(int n, PWSTR buf, BOOL* isTextSelected) // I3567
{
LogEnter();
HRESULT hr;
HRESULT hr, hrGetSelection;
ULONG cFetched;
TF_SELECTION tfSelection = {0};
TF_STATUS tfStatus;
*isTextSelected = FALSE; // set to false before any early returns
if (_dwDeepIntegration == DEEPINTEGRATION_DISABLE) { // I4375
Log(L"KeymanGetContext: Exit: deep integration disabled by registry (or default)");
return S_FALSE;
@ -243,10 +264,51 @@ HRESULT WINAPI CKeymanEditSession::KeymanGetContext(int n, PWSTR buf) // I3567
return S_FALSE;
}
if (!GetLeftOfSelection(_ec, _pContext, buf, n)) { // I4933
if (!GetLeftOfSelection(_ec, _pContext, buf, n, &hrGetSelection, &cFetched, &tfSelection)) { // I4933
return S_FALSE; // I4933
}
// now check for selected text
if (!SUCCEEDED(hr = KeymanIsTextSelected(hrGetSelection, cFetched, tfSelection, isTextSelected))) {
Log(L"KeymanGetContext: Warning: KeymanIsTextSelected failed");
// Continue to return S_OK, even though checking if a text selection exists has failed.
// Reaching this point means the context in 'buf' is valid and will be passed to the caller.
// isTextSelected will be false, which is better than returning E_FAIL at this point.
}
return S_OK;
}
HRESULT
CKeymanEditSession::KeymanIsTextSelected(HRESULT hrGetSelection, ULONG cFetched, TF_SELECTION tfSelection, BOOL *isTextSelected) {
LogEnter();
HRESULT hr;
*isTextSelected = FALSE;
if (!SUCCEEDED(hrGetSelection)) {
return E_FAIL;
}
if (hrGetSelection == TF_E_NOSELECTION) {
// No selection is present
return S_OK;
}
if (cFetched > 0) {
// check if the range is empty
BOOL isEmpty = FALSE;
// Compare the start and end of the range
if (!SUCCEEDED(hr = tfSelection.range->IsEmpty(_ec, &isEmpty))) {
Log(L"KeymanIsTextSelected: Exit: Testing range->isEmpty");
return hr;
}
if (!isEmpty) {
*isTextSelected = TRUE;
}
tfSelection.range->Release();
}
return S_OK;
}

View file

@ -139,7 +139,9 @@ private:
//
typedef HRESULT(WINAPI *PKEYMANPROCESSOUTPUTFUNC)(int n, WCHAR *buf, int nbuf); // I3567
typedef HRESULT(WINAPI *PKEYMANGETCONTEXTFUNC)(int n, PWSTR buf); // I3567
// Adding a new interface call IsTextSelected could make sense however this would
// also require update TIPProcessKey to have another call back function as an argument
typedef HRESULT(WINAPI *PKEYMANGETCONTEXTFUNC)(int n, PWSTR buf, BOOL* isTextSelected); // I3567
class Keyman32Interface {
public:

View file

@ -1,18 +1,18 @@
/*
Name: kmkey
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Documentation:
Description:
Create Date: 19 Jun 2007
Modified Date: 1 Dec 2012
Authors: mcdurdin
Related Files:
Dependencies:
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
Bugs:
Todo:
Notes:
History: 19 Jun 2007 - mcdurdin - I890 - Deadkeys not working correctly in TSF
19 Jun 2007 - mcdurdin - I822 - TSF Addin not working
07 Sep 2009 - mcdurdin - I2095 - TSF addin is not threadsafe
@ -49,7 +49,7 @@ public:
STDMETHODIMP DoEditSession(TfEditCookie ec);
HRESULT WINAPI KeymanProcessOutput(int n, PWSTR buf, int nbuf); // I3567
HRESULT WINAPI KeymanGetContext(int n, PWSTR buf); // I3567
HRESULT WINAPI KeymanGetContext(int n, PWSTR buf, BOOL* isTextSelected); // I3567
HRESULT GetResult() { return _hr; }
private:
@ -77,7 +77,7 @@ BOOL CKMTipTextService::_KeymanProcessKeystroke(ITfContext *pContext, WPARAM wPa
}
else
{
if (pContext->RequestEditSession(_tfClientId, pEditSession,
if (pContext->RequestEditSession(_tfClientId, pEditSession,
fUpdate ? TF_ES_SYNC | TF_ES_READWRITE : TF_ES_SYNC | TF_ES_READ, &hr) != S_OK)
{
hr = E_FAIL;