You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
538 lines
17 KiB
538 lines
17 KiB
//
|
|
// property.cpp
|
|
//
|
|
// Property code.
|
|
//
|
|
|
|
#include "globals.h"
|
|
#include "mark.h"
|
|
#include "editsess.h"
|
|
#include "pstore.h"
|
|
|
|
// callback code for CPropertyEditSession
|
|
#define VIEW_CASE_PROPERTY 0
|
|
#define SET_CASE_PROPERTY 1
|
|
#define VIEW_CUSTOM_PROPERTY 2
|
|
#define SET_CUSTOM_PROPERTY 3
|
|
|
|
const TCHAR c_szWorkerWndClass[] = TEXT("Mark Worker Wnd Class");
|
|
|
|
class CPropertyEditSession : public CEditSessionBase
|
|
{
|
|
public:
|
|
CPropertyEditSession(CMarkTextService *pMark, ITfContext *pContext, ULONG ulCallback) : CEditSessionBase(pContext)
|
|
{
|
|
_pMark = pMark;
|
|
_pMark->AddRef();
|
|
_ulCallback = ulCallback;
|
|
}
|
|
~CPropertyEditSession()
|
|
{
|
|
_pMark->Release();
|
|
}
|
|
|
|
// ITfEditSession
|
|
STDMETHODIMP DoEditSession(TfEditCookie ec)
|
|
{
|
|
switch (_ulCallback)
|
|
{
|
|
case VIEW_CASE_PROPERTY:
|
|
_pMark->_ViewCaseProperty(ec, _pContext);
|
|
break;
|
|
case SET_CASE_PROPERTY:
|
|
_pMark->_SetCaseProperty(ec, _pContext);
|
|
break;
|
|
case VIEW_CUSTOM_PROPERTY:
|
|
_pMark->_ViewCustomProperty(ec, _pContext);
|
|
break;
|
|
case SET_CUSTOM_PROPERTY:
|
|
_pMark->_SetCustomProperty(ec, _pContext);
|
|
break;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
private:
|
|
CMarkTextService *_pMark;
|
|
ULONG _ulCallback;
|
|
};
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _RequestEditSession
|
|
//
|
|
// Helper function. Schedules an edit session for a particular property
|
|
// related callback.
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CMarkTextService::_RequestPropertyEditSession(ULONG ulCallback)
|
|
{
|
|
ITfDocumentMgr *pFocusDoc;
|
|
ITfContext *pContext;
|
|
CPropertyEditSession *pPropertyEditSession;
|
|
HRESULT hr;
|
|
|
|
// get the focus document
|
|
if (_pThreadMgr->GetFocus(&pFocusDoc) != S_OK)
|
|
return;
|
|
|
|
if (pFocusDoc == NULL)
|
|
return; // no focus
|
|
|
|
// we want the topmost context, since the main doc context could be
|
|
// superceded by a modal tip context
|
|
if (pFocusDoc->GetTop(&pContext) != S_OK)
|
|
{
|
|
pContext = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
if (pPropertyEditSession = new CPropertyEditSession(this, pContext, ulCallback))
|
|
{
|
|
// we need a document write lock
|
|
// the CPropertyEditSession will do all the work when the
|
|
// CPropertyEditSession::DoEditSession method is called by the context
|
|
pContext->RequestEditSession(_tfClientId, pPropertyEditSession, TF_ES_READWRITE | TF_ES_ASYNCDONTCARE, &hr);
|
|
|
|
pPropertyEditSession->Release();
|
|
}
|
|
|
|
Exit:
|
|
SafeRelease(pContext);
|
|
pFocusDoc->Release();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _SetCaseProperty
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CMarkTextService::_SetCaseProperty(TfEditCookie ec, ITfContext *pContext)
|
|
{
|
|
TF_SELECTION tfSelection;
|
|
ITfProperty *pCaseProperty;
|
|
ITfRange *pRangeChar;
|
|
WCHAR ch;
|
|
ULONG cchRead;
|
|
ULONG cFetched;
|
|
VARIANT varValue;
|
|
|
|
// get the case property
|
|
if (pContext->GetProperty(c_guidCaseProperty, &pCaseProperty) != S_OK)
|
|
return;
|
|
|
|
// get the selection
|
|
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK ||
|
|
cFetched != 1)
|
|
{
|
|
// no selection or something went wrong
|
|
tfSelection.range = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// get a helper range ready for the loop
|
|
if (tfSelection.range->Clone(&pRangeChar) != S_OK)
|
|
goto Exit;
|
|
|
|
// set the value char-by-char over the selection
|
|
while (TRUE)
|
|
{
|
|
// read one char, the TF_TF_MOVESTART flag will advance the start anchor
|
|
if (tfSelection.range->GetText(ec, TF_TF_MOVESTART, &ch, 1, &cchRead) != S_OK)
|
|
break;
|
|
|
|
// any more text to read?
|
|
if (cchRead != 1)
|
|
break;
|
|
|
|
// make pRange cover just the one char we read
|
|
if (pRangeChar->ShiftEndToRange(ec, tfSelection.range, TF_ANCHOR_START) != S_OK)
|
|
break;
|
|
|
|
// set the value
|
|
varValue.vt = VT_I4;
|
|
varValue.lVal = (ch >= 'A' && ch <= 'Z');
|
|
|
|
if (pCaseProperty->SetValue(ec, pRangeChar, &varValue) != S_OK)
|
|
break;
|
|
|
|
// advance pRange for next iteration
|
|
if (pRangeChar->Collapse(ec, TF_ANCHOR_END) != S_OK)
|
|
break;
|
|
}
|
|
|
|
pRangeChar->Release();
|
|
|
|
Exit:
|
|
SafeRelease(tfSelection.range);
|
|
pCaseProperty->Release();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _Menu_OnSetCaseProperty
|
|
//
|
|
// Callback for the "Set Case Property" menu item.
|
|
// Set the value for a private "case" property over the text covered by the
|
|
// selection. The case property is private to this text service, which defines
|
|
// it as:
|
|
//
|
|
// static compact, per character
|
|
// VT_I4, !0 => character is within 'A' - 'Z', 0 => anything else.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void CMarkTextService::_Menu_OnSetCaseProperty(CMarkTextService *_this)
|
|
{
|
|
_this->_RequestPropertyEditSession(SET_CASE_PROPERTY);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _ViewCaseProperty
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CMarkTextService::_ViewCaseProperty(TfEditCookie ec, ITfContext *pContext)
|
|
{
|
|
TF_SELECTION tfSelection;
|
|
ITfProperty *pCaseProperty;
|
|
ULONG cchRead;
|
|
LONG cch;
|
|
ULONG cFetched;
|
|
ULONG i;
|
|
VARIANT varValue;
|
|
|
|
// get the case property
|
|
if (pContext->GetProperty(c_guidCaseProperty, &pCaseProperty) != S_OK)
|
|
return;
|
|
|
|
// get the selection
|
|
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK ||
|
|
cFetched != 1)
|
|
{
|
|
// no selection or something went wrong
|
|
tfSelection.range = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
// grab the text
|
|
if (tfSelection.range->GetText(ec, 0, _achDisplayText, ARRAYSIZE(_achDisplayText)-1, &cchRead) != S_OK)
|
|
goto Exit;
|
|
|
|
// prepare for the loop
|
|
if (tfSelection.range->Collapse(ec, TF_ANCHOR_START) != S_OK)
|
|
goto Exit;
|
|
|
|
// get the property value char-by-char over the selection
|
|
for (i=0; i < cchRead; i++)
|
|
{
|
|
// advance pRange for next iteration, cover the next char
|
|
if (tfSelection.range->ShiftStartToRange(ec, tfSelection.range, TF_ANCHOR_END) != S_OK)
|
|
break;
|
|
if (tfSelection.range->ShiftEnd(ec, 1, &cch, NULL) != S_OK)
|
|
break;
|
|
if (cch != 1) // hit a region boundary?
|
|
break;
|
|
|
|
switch (pCaseProperty->GetValue(ec, tfSelection.range, &varValue))
|
|
{
|
|
case S_OK:
|
|
// the property value has been set, use it
|
|
// 'U' --> uppercase
|
|
// 'L' --> lowercase
|
|
_achDisplayPropertyText[i] = varValue.lVal ? 'U' : 'L';
|
|
break;
|
|
case S_FALSE:
|
|
// no property value set, varValue.vt == VT_EMPTY
|
|
// '?' --> no value
|
|
_achDisplayPropertyText[i] = '?';
|
|
break;
|
|
default:
|
|
// error
|
|
// '!' --> error
|
|
_achDisplayPropertyText[i] = '!';
|
|
break;
|
|
}
|
|
}
|
|
for (; i<cchRead; i++) // error case
|
|
{
|
|
_achDisplayPropertyText[i] = '!';
|
|
}
|
|
|
|
_achDisplayPropertyText[cchRead] = '\0';
|
|
_achDisplayText[cchRead] = '\0';
|
|
|
|
// we can't change the focus while holding a lock
|
|
// so postpone the UI until we've released our lock
|
|
PostMessage(_hWorkerWnd, CMarkTextService::WM_DISPLAY_PROPERTY, 0, 0);
|
|
|
|
Exit:
|
|
SafeRelease(tfSelection.range);
|
|
pCaseProperty->Release();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _Menu_OnViewCaseProperty
|
|
//
|
|
// Menu callback. Displays a popup with "case" property values over the
|
|
// current selection.
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void CMarkTextService::_Menu_OnViewCaseProperty(CMarkTextService *_this)
|
|
{
|
|
_this->_RequestPropertyEditSession(VIEW_CASE_PROPERTY);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _ViewCustomProperty
|
|
//
|
|
// Display the value of this text service's custom property over the text
|
|
// covered by the selection.
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CMarkTextService::_ViewCustomProperty(TfEditCookie ec, ITfContext *pContext)
|
|
{
|
|
TF_SELECTION tfSelection;
|
|
ITfProperty *pCustomProperty;
|
|
ITfRange *pSelRange;
|
|
ITfRange *pPropertySpanRange;
|
|
ULONG cchRead;
|
|
ULONG cFetched;
|
|
LONG cch;
|
|
VARIANT varValue;
|
|
HRESULT hr;
|
|
|
|
// get the case property
|
|
if (pContext->GetProperty(c_guidCustomProperty, &pCustomProperty) != S_OK)
|
|
return;
|
|
|
|
// get the selection
|
|
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK ||
|
|
cFetched != 1)
|
|
{
|
|
// no selection or something went wrong
|
|
pSelRange = NULL;
|
|
goto Exit;
|
|
}
|
|
// free up tfSelection so we can re-use it below
|
|
pSelRange = tfSelection.range;
|
|
|
|
// the selection may not exactly match a span of text covered by the
|
|
// custom property....so we'll return the value over the start anchor of
|
|
// the selection.
|
|
|
|
// we need to collapse the range because GetValue will return VT_EMPTY
|
|
// if the query range is not completely covered by the property span
|
|
if (pSelRange->Collapse(ec, TF_ANCHOR_START) != S_OK)
|
|
goto Exit;
|
|
// the query range must also cover at least one char
|
|
if (pSelRange->ShiftEnd(ec, 1, &cch, NULL) != S_OK)
|
|
goto Exit;
|
|
|
|
hr = pCustomProperty->GetValue(ec, pSelRange, &varValue);
|
|
|
|
switch (hr)
|
|
{
|
|
case S_OK:
|
|
// there's a value at the selection start anchor
|
|
// let's find out exactly what text is covered
|
|
_achDisplayText[0] = '\0';
|
|
if (pCustomProperty->FindRange(ec, pSelRange, &pPropertySpanRange, TF_ANCHOR_START) == S_OK)
|
|
{
|
|
if (pPropertySpanRange->GetText(ec, 0, _achDisplayText, ARRAYSIZE(_achDisplayText)-1, &cchRead) != S_OK)
|
|
{
|
|
cchRead = 0;
|
|
}
|
|
_achDisplayText[cchRead] = '\0';
|
|
// let's update the selection to give the user feedback
|
|
tfSelection.range = pPropertySpanRange;
|
|
pContext->SetSelection(ec, 1, &tfSelection);
|
|
pPropertySpanRange->Release();
|
|
}
|
|
// write the value
|
|
wsprintfW(_achDisplayPropertyText, L"%i", varValue.lVal);
|
|
break;
|
|
|
|
case S_FALSE:
|
|
// the property has no value, varValue.vt == VT_EMPTY
|
|
_achDisplayText[0] = '\0';
|
|
SafeStringCopy(_achDisplayPropertyText, ARRAYSIZE(_achDisplayPropertyText), L"- No Value -");
|
|
break;
|
|
default:
|
|
goto Exit; // error
|
|
}
|
|
|
|
// we can't change the focus while holding a lock
|
|
// so postpone the UI until we've released our lock
|
|
PostMessage(_hWorkerWnd, CMarkTextService::WM_DISPLAY_PROPERTY, 0, 0);
|
|
|
|
Exit:
|
|
SafeRelease(pSelRange);
|
|
pCustomProperty->Release();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _Menu_OnViewCustomProperty
|
|
//
|
|
// Menu callback for "View Custom Property".
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void CMarkTextService::_Menu_OnViewCustomProperty(CMarkTextService *_this)
|
|
{
|
|
_this->_RequestPropertyEditSession(VIEW_CUSTOM_PROPERTY);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _InitWorkerWnd
|
|
//
|
|
// Called from Activate. Create a worker window to receive private windows
|
|
// messages.
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CMarkTextService::_InitWorkerWnd()
|
|
{
|
|
WNDCLASS wc;
|
|
|
|
memset(&wc, 0, sizeof(wc));
|
|
wc.lpfnWndProc = _WorkerWndProc;
|
|
wc.hInstance = g_hInst;
|
|
wc.lpszClassName = c_szWorkerWndClass;
|
|
|
|
if (RegisterClass(&wc) == 0)
|
|
return FALSE;
|
|
|
|
_hWorkerWnd = CreateWindow(c_szWorkerWndClass, TEXT("Mark Worker Wnd"),
|
|
0, 0, 0, 0, 0, NULL, NULL, g_hInst, this);
|
|
|
|
return (_hWorkerWnd != NULL);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _UninitWorkerWnd
|
|
//
|
|
// Called from Deactivate. Destroy the worker window.
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CMarkTextService::_UninitWorkerWnd()
|
|
{
|
|
if (_hWorkerWnd != NULL)
|
|
{
|
|
DestroyWindow(_hWorkerWnd);
|
|
_hWorkerWnd = NULL;
|
|
}
|
|
UnregisterClass(c_szWorkerWndClass, g_hInst);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _WorkerWndProc
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
LRESULT CALLBACK CMarkTextService::_WorkerWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CMarkTextService *_this;
|
|
int cch;
|
|
char achText[128];
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
// save the this pointer we originally passed into CreateWindow
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA,
|
|
(LONG_PTR)((CREATESTRUCT *)lParam)->lpCreateParams);
|
|
return 0;
|
|
|
|
case WM_DISPLAY_PROPERTY:
|
|
_this = (CMarkTextService *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
// bring up a message box with the contents of _achDisplayText
|
|
|
|
// first, convert from unicode
|
|
cch = WideCharToMultiByte(CP_ACP, 0, _this->_achDisplayText, wcslen(_this->_achDisplayText),
|
|
achText, ARRAYSIZE(achText)-1, NULL, NULL);
|
|
|
|
if (cch < ARRAYSIZE(achText) - 1)
|
|
{
|
|
achText[cch++] = '\n';
|
|
}
|
|
if (cch < ARRAYSIZE(achText) - 1)
|
|
{
|
|
cch += WideCharToMultiByte(CP_ACP, 0, _this->_achDisplayPropertyText, wcslen(_this->_achDisplayPropertyText),
|
|
achText+cch, ARRAYSIZE(achText)-cch-1, NULL, NULL);
|
|
}
|
|
achText[cch] = '\0';
|
|
|
|
// bring up the display
|
|
MessageBoxA(NULL, achText, "Property View", MB_OK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _Menu_OnSetCustomProperty
|
|
//
|
|
// Callback for the "Set Custom Property" menu item.
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void CMarkTextService::_Menu_OnSetCustomProperty(CMarkTextService *_this)
|
|
{
|
|
_this->_RequestPropertyEditSession(SET_CUSTOM_PROPERTY);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _SetCustomProperty
|
|
//
|
|
// Assign a custom property to the text covered by the selection.
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CMarkTextService::_SetCustomProperty(TfEditCookie ec, ITfContext *pContext)
|
|
{
|
|
TF_SELECTION tfSelection;
|
|
ITfProperty *pCustomProperty;
|
|
CCustomPropertyStore *pCustomPropertyStore;
|
|
ULONG cFetched;
|
|
|
|
// get the case property
|
|
if (pContext->GetProperty(c_guidCustomProperty, &pCustomProperty) != S_OK)
|
|
return;
|
|
|
|
// get the selection
|
|
if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK ||
|
|
cFetched != 1)
|
|
{
|
|
// no selection or something went wrong
|
|
tfSelection.range = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
if ((pCustomPropertyStore = new CCustomPropertyStore) == NULL)
|
|
goto Exit;
|
|
|
|
pCustomProperty->SetValueStore(ec, tfSelection.range, pCustomPropertyStore);
|
|
|
|
// TSF will hold a reference to pCustomPropertyStore is the SetValueStore succeeded
|
|
// but we need to release ours
|
|
pCustomPropertyStore->Release();
|
|
|
|
Exit:
|
|
pCustomProperty->Release();
|
|
SafeRelease(tfSelection.range);
|
|
}
|