283 lines
8.0 KiB
C++
283 lines
8.0 KiB
C++
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (C) 2003 Microsoft Corporation. All rights reserved.
|
|
//
|
|
// KeyHandler.cpp
|
|
//
|
|
// the handler routines for key events
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Globals.h"
|
|
#include "EditSession.h"
|
|
#include "TextService.h"
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CKeyHandlerEditSession
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
class CKeyHandlerEditSession : public CEditSessionBase
|
|
{
|
|
public:
|
|
CKeyHandlerEditSession(CTextService *pTextService, ITfContext *pContext, WPARAM wParam) : CEditSessionBase(pTextService, pContext)
|
|
{
|
|
_wParam = wParam;
|
|
}
|
|
|
|
// ITfEditSession
|
|
STDMETHODIMP DoEditSession(TfEditCookie ec);
|
|
|
|
private:
|
|
WPARAM _wParam;
|
|
};
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// DoEditSession
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CKeyHandlerEditSession::DoEditSession(TfEditCookie ec)
|
|
{
|
|
switch (_wParam)
|
|
{
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
return _pTextService->_HandleArrowKey(ec, _pContext, _wParam);
|
|
|
|
case VK_RETURN:
|
|
return _pTextService->_HandleReturnKey(ec, _pContext);
|
|
|
|
case VK_SPACE:
|
|
return _pTextService->_HandleSpaceKey(ec, _pContext);
|
|
|
|
default:
|
|
if (_wParam >= 'A' && _wParam <= 'Z')
|
|
return _pTextService->_HandleCharacterKey(ec, _pContext, _wParam);
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// IsRangeCovered
|
|
//
|
|
// Returns TRUE if pRangeTest is entirely contained within pRangeCover.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL IsRangeCovered(TfEditCookie ec, ITfRange *pRangeTest, ITfRange *pRangeCover)
|
|
{
|
|
LONG lResult;
|
|
|
|
if (pRangeCover->CompareStart(ec, pRangeTest, TF_ANCHOR_START, &lResult) != S_OK ||
|
|
lResult > 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (pRangeCover->CompareEnd(ec, pRangeTest, TF_ANCHOR_END, &lResult) != S_OK ||
|
|
lResult < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _HandleCharacterKey
|
|
//
|
|
// If the keystroke happens within a composition, eat the key and return S_OK.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CTextService::_HandleCharacterKey(TfEditCookie ec, ITfContext *pContext, WPARAM wParam)
|
|
{
|
|
ITfRange *pRangeComposition;
|
|
TF_SELECTION tfSelection;
|
|
ULONG cFetched;
|
|
WCHAR ch;
|
|
BOOL fCovered;
|
|
|
|
// Start the new compositon if there is no composition.
|
|
if (!_IsComposing())
|
|
_StartComposition(pContext);
|
|
|
|
//
|
|
// Assign VK_ value to the char. So the inserted the character is always
|
|
// uppercase.
|
|
//
|
|
ch = (WCHAR)wParam;
|
|
|
|
// first, test where a keystroke would go in the document if we did an insert
|
|
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK || cFetched != 1)
|
|
return S_FALSE;
|
|
|
|
// is the insertion point covered by a composition?
|
|
if (_pComposition->GetRange(&pRangeComposition) == S_OK)
|
|
{
|
|
fCovered = IsRangeCovered(ec, tfSelection.range, pRangeComposition);
|
|
|
|
pRangeComposition->Release();
|
|
|
|
if (!fCovered)
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// insert the text
|
|
// we use SetText here instead of InsertTextAtSelection because we've already started a composition
|
|
// we don't want to the app to adjust the insertion point inside our composition
|
|
if (tfSelection.range->SetText(ec, 0, &ch, 1) != S_OK)
|
|
goto Exit;
|
|
|
|
// update the selection, we'll make it an insertion point just past
|
|
// the inserted text.
|
|
tfSelection.range->Collapse(ec, TF_ANCHOR_END);
|
|
pContext->SetSelection(ec, 1, &tfSelection);
|
|
|
|
//
|
|
// set the display attribute to the composition range.
|
|
//
|
|
_SetCompositionDisplayAttributes(ec, pContext, _gaDisplayAttributeInput);
|
|
|
|
Exit:
|
|
tfSelection.range->Release();
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _HandleReturnKey
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CTextService::_HandleReturnKey(TfEditCookie ec, ITfContext *pContext)
|
|
{
|
|
// just terminate the composition
|
|
_TerminateComposition(ec, pContext);
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _HandleSpaceKey
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CTextService::_HandleSpaceKey(TfEditCookie ec, ITfContext *pContext)
|
|
{
|
|
//
|
|
// set the display attribute to the composition range.
|
|
//
|
|
// The real text service may have linguistic logic here and set
|
|
// the specific range to apply the display attribute rather than
|
|
// applying the display attribute to the entire composition range.
|
|
//
|
|
_SetCompositionDisplayAttributes(ec, pContext, _gaDisplayAttributeConverted);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _HandleArrowKey
|
|
//
|
|
// Update the selection within a composition.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CTextService::_HandleArrowKey(TfEditCookie ec, ITfContext *pContext, WPARAM wParam)
|
|
{
|
|
ITfRange *pRangeComposition;
|
|
LONG cch;
|
|
BOOL fEqual;
|
|
TF_SELECTION tfSelection;
|
|
ULONG cFetched;
|
|
|
|
// get the selection
|
|
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK ||
|
|
cFetched != 1)
|
|
{
|
|
// no selection?
|
|
return S_OK; // eat the keystroke
|
|
}
|
|
|
|
// get the composition range
|
|
if (_pComposition->GetRange(&pRangeComposition) != S_OK)
|
|
goto Exit;
|
|
|
|
// adjust the selection, we won't do anything fancy
|
|
if (wParam == VK_LEFT)
|
|
{
|
|
if (tfSelection.range->IsEqualStart(ec, pRangeComposition, TF_ANCHOR_START, &fEqual) == S_OK &&
|
|
!fEqual)
|
|
{
|
|
tfSelection.range->ShiftStart(ec, -1, &cch, NULL);
|
|
}
|
|
tfSelection.range->Collapse(ec, TF_ANCHOR_START);
|
|
}
|
|
else
|
|
{
|
|
// VK_RIGHT
|
|
if (tfSelection.range->IsEqualEnd(ec, pRangeComposition, TF_ANCHOR_END, &fEqual) == S_OK &&
|
|
!fEqual)
|
|
{
|
|
tfSelection.range->ShiftEnd(ec, +1, &cch, NULL);
|
|
}
|
|
tfSelection.range->Collapse(ec, TF_ANCHOR_END);
|
|
}
|
|
|
|
pContext->SetSelection(ec, 1, &tfSelection);
|
|
|
|
pRangeComposition->Release();
|
|
|
|
Exit:
|
|
tfSelection.range->Release();
|
|
return S_OK; // eat the keystroke
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _InvokeKeyHandler
|
|
//
|
|
// This text service is interested in handling keystrokes to demonstrate the
|
|
// use the compositions. Some apps will cancel compositions if they receive
|
|
// keystrokes while a compositions is ongoing.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CTextService::_InvokeKeyHandler(ITfContext *pContext, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CKeyHandlerEditSession *pEditSession;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
// we'll insert a char ourselves in place of this keystroke
|
|
if ((pEditSession = new CKeyHandlerEditSession(this, pContext, wParam)) == NULL)
|
|
goto Exit;
|
|
|
|
// we need a lock to do our work
|
|
// nb: this method is one of the few places where it is legal to use
|
|
// the TF_ES_SYNC flag
|
|
hr = pContext->RequestEditSession(_tfClientId, pEditSession, TF_ES_SYNC | TF_ES_READWRITE, &hr);
|
|
|
|
pEditSession->Release();
|
|
|
|
Exit:
|
|
return hr;
|
|
}
|
|
|