// // 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 (; iRelease(); } //+--------------------------------------------------------------------------- // // _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); }