From 7b83802bb264d5ee009bfd18d3283c35f4e87829 Mon Sep 17 00:00:00 2001 From: fanlumaster <1730976608@qq.com> Date: Thu, 12 Oct 2023 23:53:17 +0800 Subject: [PATCH] First commit. --- .clang-format | 6 + .clangd | 2 + .gitignore | 1 + ActiveLanguageProfileNotifySink.cpp | 118 ++ BaseDictionaryEngine.cpp | 118 ++ BaseDictionaryEngine.h | 41 + BaseWindow.cpp | 662 +++++++ BaseWindow.h | 160 ++ ButtonWindow.cpp | 90 + ButtonWindow.h | 27 + CMakeDraft.txt | 85 + CMakeLists.txt | 98 ++ CandidateListUIPresenter.cpp | 1305 ++++++++++++++ CandidateListUIPresenter.h | 139 ++ CandidateWindow.cpp | 1438 +++++++++++++++ CandidateWindow.h | 118 ++ Compartment.cpp | 343 ++++ Compartment.h | 61 + Composition.cpp | 377 ++++ CompositionProcessorEngine.cpp | 1940 +++++++++++++++++++++ CompositionProcessorEngine.h | 195 +++ Define.h | 48 + Dictionary/.gitattributes | 3 + Dictionary/SampleIMESimplifiedQuanPin.txt | Bin 0 -> 1574528 bytes DictionaryParser.cpp | 222 +++ DictionaryParser.h | 50 + DictionarySearch.cpp | 250 +++ DictionarySearch.h | 76 + DisplayAttribute.cpp | 115 ++ DisplayAttributeInfo.cpp | 227 +++ DisplayAttributeInfo.h | 87 + DisplayAttributeProvider.cpp | 81 + DisplayString.h | 43 + DllMain.cpp | 54 + EditSession.cpp | 99 ++ EditSession.h | 33 + EndComposition.cpp | 98 ++ EnumDisplayAttributeInfo.cpp | 226 +++ EnumDisplayAttributeInfo.h | 36 + EnumTfCandidates.cpp | 146 ++ EnumTfCandidates.h | 40 + File.cpp | 254 +++ File.h | 70 + FileMapping.cpp | 87 + FileMapping.h | 46 + FunctionProviderSink.cpp | 55 + GetTextExtentEditSession.cpp | 43 + GetTextExtentEditSession.h | 41 + Globals.cpp | 460 +++++ Globals.h | 162 ++ KeyEventSink.cpp | 457 +++++ KeyHandler.cpp | 635 +++++++ KeyHandlerEditSession.cpp | 46 + KeyHandlerEditSession.h | 30 + KeyStateCategory.cpp | 370 ++++ KeyStateCategory.h | 236 +++ LanguageBar.cpp | 662 +++++++ LanguageBar.h | 74 + Private.h | 22 + README.md | 13 + RegKey.cpp | 278 +++ RegKey.h | 45 + Register.cpp | 283 +++ SampleIME.cpp | 432 +++++ SampleIME.def | 7 + SampleIME.h | 248 +++ SampleIME.rc | 122 ++ SampleIME.vcxproj | 267 +++ SampleIME.vcxproj.filters | 301 ++++ SampleIMEBaseStructure.cpp | 325 ++++ SampleIMEBaseStructure.h | 271 +++ SampleIMEStructureArray.h | 80 + ScrollBarWindow.cpp | 650 +++++++ ScrollBarWindow.h | 220 +++ SearchCandidateProvider.cpp | 210 +++ SearchCandidateProvider.h | 50 + Server.cpp | 285 +++ ShadowWindow.cpp | 370 ++++ ShadowWindow.h | 47 + StartComposition.cpp | 133 ++ TableDictionaryEngine.cpp | 120 ++ TableDictionaryEngine.h | 30 + TextEditSink.cpp | 134 ++ TfInputProcessorProfile.cpp | 54 + TfInputProcessorProfile.h | 22 + TfTextLayoutSink.cpp | 220 +++ TfTextLayoutSink.h | 46 + ThreadFocusSink.cpp | 106 ++ ThreadMgrEventSink.cpp | 178 ++ TipCandidateList.cpp | 185 ++ TipCandidateList.h | 43 + TipCandidateString.cpp | 128 ++ TipCandidateString.h | 39 + image/DoubleSingleByteOff.ico | Bin 0 -> 17542 bytes image/DoubleSingleByteOn.ico | Bin 0 -> 17542 bytes image/ImeModeOff.ico | Bin 0 -> 39766 bytes image/ImeModeOn.ico | Bin 0 -> 39766 bytes image/PunctuationOff.ico | Bin 0 -> 17542 bytes image/PunctuationOn.ico | Bin 0 -> 17542 bytes image/SampleIme.ico | Bin 0 -> 39766 bytes lcompile.ps1 | 28 + resource.h | 29 + stdafx.h | 9 + 103 files changed, 19016 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 ActiveLanguageProfileNotifySink.cpp create mode 100644 BaseDictionaryEngine.cpp create mode 100644 BaseDictionaryEngine.h create mode 100644 BaseWindow.cpp create mode 100644 BaseWindow.h create mode 100644 ButtonWindow.cpp create mode 100644 ButtonWindow.h create mode 100644 CMakeDraft.txt create mode 100644 CMakeLists.txt create mode 100644 CandidateListUIPresenter.cpp create mode 100644 CandidateListUIPresenter.h create mode 100644 CandidateWindow.cpp create mode 100644 CandidateWindow.h create mode 100644 Compartment.cpp create mode 100644 Compartment.h create mode 100644 Composition.cpp create mode 100644 CompositionProcessorEngine.cpp create mode 100644 CompositionProcessorEngine.h create mode 100644 Define.h create mode 100644 Dictionary/.gitattributes create mode 100644 Dictionary/SampleIMESimplifiedQuanPin.txt create mode 100644 DictionaryParser.cpp create mode 100644 DictionaryParser.h create mode 100644 DictionarySearch.cpp create mode 100644 DictionarySearch.h create mode 100644 DisplayAttribute.cpp create mode 100644 DisplayAttributeInfo.cpp create mode 100644 DisplayAttributeInfo.h create mode 100644 DisplayAttributeProvider.cpp create mode 100644 DisplayString.h create mode 100644 DllMain.cpp create mode 100644 EditSession.cpp create mode 100644 EditSession.h create mode 100644 EndComposition.cpp create mode 100644 EnumDisplayAttributeInfo.cpp create mode 100644 EnumDisplayAttributeInfo.h create mode 100644 EnumTfCandidates.cpp create mode 100644 EnumTfCandidates.h create mode 100644 File.cpp create mode 100644 File.h create mode 100644 FileMapping.cpp create mode 100644 FileMapping.h create mode 100644 FunctionProviderSink.cpp create mode 100644 GetTextExtentEditSession.cpp create mode 100644 GetTextExtentEditSession.h create mode 100644 Globals.cpp create mode 100644 Globals.h create mode 100644 KeyEventSink.cpp create mode 100644 KeyHandler.cpp create mode 100644 KeyHandlerEditSession.cpp create mode 100644 KeyHandlerEditSession.h create mode 100644 KeyStateCategory.cpp create mode 100644 KeyStateCategory.h create mode 100644 LanguageBar.cpp create mode 100644 LanguageBar.h create mode 100644 Private.h create mode 100644 README.md create mode 100644 RegKey.cpp create mode 100644 RegKey.h create mode 100644 Register.cpp create mode 100644 SampleIME.cpp create mode 100644 SampleIME.def create mode 100644 SampleIME.h create mode 100644 SampleIME.rc create mode 100644 SampleIME.vcxproj create mode 100644 SampleIME.vcxproj.filters create mode 100644 SampleIMEBaseStructure.cpp create mode 100644 SampleIMEBaseStructure.h create mode 100644 SampleIMEStructureArray.h create mode 100644 ScrollBarWindow.cpp create mode 100644 ScrollBarWindow.h create mode 100644 SearchCandidateProvider.cpp create mode 100644 SearchCandidateProvider.h create mode 100644 Server.cpp create mode 100644 ShadowWindow.cpp create mode 100644 ShadowWindow.h create mode 100644 StartComposition.cpp create mode 100644 TableDictionaryEngine.cpp create mode 100644 TableDictionaryEngine.h create mode 100644 TextEditSink.cpp create mode 100644 TfInputProcessorProfile.cpp create mode 100644 TfInputProcessorProfile.h create mode 100644 TfTextLayoutSink.cpp create mode 100644 TfTextLayoutSink.h create mode 100644 ThreadFocusSink.cpp create mode 100644 ThreadMgrEventSink.cpp create mode 100644 TipCandidateList.cpp create mode 100644 TipCandidateList.h create mode 100644 TipCandidateString.cpp create mode 100644 TipCandidateString.h create mode 100644 image/DoubleSingleByteOff.ico create mode 100644 image/DoubleSingleByteOn.ico create mode 100644 image/ImeModeOff.ico create mode 100644 image/ImeModeOn.ico create mode 100644 image/PunctuationOff.ico create mode 100644 image/PunctuationOn.ico create mode 100644 image/SampleIme.ico create mode 100644 lcompile.ps1 create mode 100644 resource.h create mode 100644 stdafx.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..c25c7b69 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +--- +BasedOnStyle: Microsoft +IndentWidth: 4 + +# 不要给头文件排序 +SortIncludes: false diff --git a/.clangd b/.clangd new file mode 100644 index 00000000..88c953e7 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-Wno-microsoft-goto, -Wno-unused-value] # 忽略某些警告 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3722ac63 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/ActiveLanguageProfileNotifySink.cpp b/ActiveLanguageProfileNotifySink.cpp new file mode 100644 index 00000000..97174bcf --- /dev/null +++ b/ActiveLanguageProfileNotifySink.cpp @@ -0,0 +1,118 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "SampleIME.h" +#include "CompositionProcessorEngine.h" + +BOOL CSampleIME::VerifySampleIMECLSID(_In_ REFCLSID clsid) +{ + if (IsEqualCLSID(clsid, Global::SampleIMECLSID)) + { + return TRUE; + } + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// ITfActiveLanguageProfileNotifySink::OnActivated +// +// Sink called by the framework when changes activate language profile. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnActivated(_In_ REFCLSID clsid, _In_ REFGUID guidProfile, _In_ BOOL isActivated) +{ + guidProfile; + + if (FALSE == VerifySampleIMECLSID(clsid)) + { + return S_OK; + } + + if (isActivated) + { + _AddTextProcessorEngine(); + } + + if (nullptr == _pCompositionProcessorEngine) + { + return S_OK; + } + + if (isActivated) + { + _pCompositionProcessorEngine->ShowAllLanguageBarIcons(); + + _pCompositionProcessorEngine->ConversionModeCompartmentUpdated(_pThreadMgr); + } + else + { + _DeleteCandidateList(FALSE, nullptr); + + _pCompositionProcessorEngine->HideAllLanguageBarIcons(); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _InitActiveLanguageProfileNotifySink +// +// Advise a active language profile notify sink. +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_InitActiveLanguageProfileNotifySink() +{ + ITfSource *pSource = nullptr; + BOOL ret = FALSE; + + if (_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource) != S_OK) + { + return ret; + } + + if (pSource->AdviseSink(IID_ITfActiveLanguageProfileNotifySink, (ITfActiveLanguageProfileNotifySink *)this, + &_activeLanguageProfileNotifySinkCookie) != S_OK) + { + _activeLanguageProfileNotifySinkCookie = TF_INVALID_COOKIE; + goto Exit; + } + + ret = TRUE; + +Exit: + pSource->Release(); + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _UninitActiveLanguageProfileNotifySink +// +// Unadvise a active language profile notify sink. Assumes we have advised one already. +//---------------------------------------------------------------------------- + +void CSampleIME::_UninitActiveLanguageProfileNotifySink() +{ + ITfSource *pSource = nullptr; + + if (_activeLanguageProfileNotifySinkCookie == TF_INVALID_COOKIE) + { + return; // never Advised + } + + if (_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource) == S_OK) + { + pSource->UnadviseSink(_activeLanguageProfileNotifySinkCookie); + pSource->Release(); + } + + _activeLanguageProfileNotifySinkCookie = TF_INVALID_COOKIE; +} diff --git a/BaseDictionaryEngine.cpp b/BaseDictionaryEngine.cpp new file mode 100644 index 00000000..0b94732e --- /dev/null +++ b/BaseDictionaryEngine.cpp @@ -0,0 +1,118 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "BaseDictionaryEngine.h" +#include "Globals.h" + +//+--------------------------------------------------------------------------- +// ctor +//---------------------------------------------------------------------------- + +CBaseDictionaryEngine::CBaseDictionaryEngine(LCID locale, _In_ CFile *pDictionaryFile) +{ + _locale = locale; + _pDictionaryFile = pDictionaryFile; +} + +//+--------------------------------------------------------------------------- +// dtor +//---------------------------------------------------------------------------- + +CBaseDictionaryEngine::~CBaseDictionaryEngine() +{ +} + +//+--------------------------------------------------------------------------- +// SortListItemByFindKeyCode +//---------------------------------------------------------------------------- + +VOID CBaseDictionaryEngine::SortListItemByFindKeyCode(_Inout_ CSampleImeArray *pItemList) +{ + MergeSortByFindKeyCode(pItemList, 0, pItemList->Count() - 1); +} + +//+--------------------------------------------------------------------------- +// MergeSortByFindKeyCode +// +// Mergesort the array of element in CCandidateListItem::_FindKeyCode +// +//---------------------------------------------------------------------------- + +VOID CBaseDictionaryEngine::MergeSortByFindKeyCode(_Inout_ CSampleImeArray *pItemList, int leftRange, int rightRange) +{ + int candidateCount = CalculateCandidateCount(leftRange, rightRange); + + if (candidateCount > 2) + { + int mid = leftRange + (candidateCount / 2); + + MergeSortByFindKeyCode(pItemList, leftRange, mid); + MergeSortByFindKeyCode(pItemList, mid, rightRange); + + CSampleImeArray ListItemTemp; + + int leftRangeTemp = 0; + int midTemp = 0; + for (leftRangeTemp = leftRange, midTemp = mid; leftRangeTemp != mid || midTemp != rightRange;) + { + CStringRange* psrgLeftTemp = nullptr; + CStringRange* psrgMidTemp = nullptr; + + psrgLeftTemp = &pItemList->GetAt(leftRangeTemp)->_FindKeyCode; + psrgMidTemp = &pItemList->GetAt(midTemp)->_FindKeyCode; + + CCandidateListItem* pLI = nullptr; + pLI = ListItemTemp.Append(); + if (pLI) + { + if (leftRangeTemp == mid) + { + *pLI = *pItemList->GetAt(midTemp++); + } + else if (midTemp == rightRange || CStringRange::Compare(_locale, psrgLeftTemp, psrgMidTemp) != CSTR_GREATER_THAN) + { + *pLI = *pItemList->GetAt(leftRangeTemp++); + } + else + { + *pLI = *pItemList->GetAt(midTemp++); + } + } + } + + leftRangeTemp = leftRange; + for (UINT count = 0; count < ListItemTemp.Count(); count++) + { + *pItemList->GetAt(leftRangeTemp++) = *ListItemTemp.GetAt(count); + } + } + else if (candidateCount == 2) + { + CStringRange *psrgLeft = nullptr; + CStringRange *psrgLeftNext = nullptr; + + psrgLeft = &pItemList->GetAt(leftRange )->_FindKeyCode; + psrgLeftNext = &pItemList->GetAt(leftRange+1)->_FindKeyCode; + + if (CStringRange::Compare(_locale, psrgLeft, psrgLeftNext) == CSTR_GREATER_THAN) + { + CCandidateListItem ListItem; + ListItem = *pItemList->GetAt(leftRange); + *pItemList->GetAt(leftRange ) = *pItemList->GetAt(leftRange+1); + *pItemList->GetAt(leftRange+1) = ListItem; + } + } +} + +int CBaseDictionaryEngine::CalculateCandidateCount(int leftRange, int rightRange) +{ + assert(leftRange >= 0); + assert(rightRange >= 0); + + return (rightRange - leftRange + 1); +} \ No newline at end of file diff --git a/BaseDictionaryEngine.h b/BaseDictionaryEngine.h new file mode 100644 index 00000000..b63d499a --- /dev/null +++ b/BaseDictionaryEngine.h @@ -0,0 +1,41 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "File.h" +#include "SampleIMEBaseStructure.h" + +class CBaseDictionaryEngine +{ +public: + CBaseDictionaryEngine(LCID locale, _In_ CFile *pDictionaryFile); + virtual ~CBaseDictionaryEngine(); + + virtual VOID CollectWord(_In_ CStringRange *psrgKeyCode, _Out_ CSampleImeArray *pasrgWordString) + { + psrgKeyCode; + pasrgWordString = nullptr; + } + + virtual VOID CollectWord(_In_ CStringRange *psrgKeyCode, _Out_ CSampleImeArray *pItemList) + { + psrgKeyCode; + pItemList = nullptr; + } + + virtual VOID SortListItemByFindKeyCode(_Inout_ CSampleImeArray *pItemList); + +protected: + CFile* _pDictionaryFile; + LCID _locale; + +private: + VOID MergeSortByFindKeyCode(_Inout_ CSampleImeArray *pItemList, int leftRange, int rightRange); + int CalculateCandidateCount(int leftRange, int rightRange); +}; diff --git a/BaseWindow.cpp b/BaseWindow.cpp new file mode 100644 index 00000000..4e7aa193 --- /dev/null +++ b/BaseWindow.cpp @@ -0,0 +1,662 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "BaseWindow.h" + +#define idTimer_UIObject 39772 + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::ctor +// +//---------------------------------------------------------------------------- + +CBaseWindow::CBaseWindow() +{ + _wndHandle = nullptr; + _pParentWnd = nullptr; + _pUIWnd = nullptr; + + _pTimerUIObj = nullptr; + _pUIObjCapture = nullptr; + + _enableVirtualWnd = TRUE; + _visibleVirtualWnd = TRUE; + _RectOfVirtualWnd.left = 0; + _RectOfVirtualWnd.top = 0; + _RectOfVirtualWnd.right = 0; + _RectOfVirtualWnd.bottom = 0; +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::dtor +// +//---------------------------------------------------------------------------- + +CBaseWindow::~CBaseWindow() +{ + _SetThis(_wndHandle, nullptr); +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_InitWindowClass +// +//---------------------------------------------------------------------------- + +/* static */ +BOOL CBaseWindow::_InitWindowClass(_In_ LPCWSTR lpwszClassName, _Out_ ATOM *patom) +{ + WNDCLASS wc; + + wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_IME; + wc.lpfnWndProc = CBaseWindow::_WindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = Global::dllInstanceHandle; + wc.hIcon = nullptr; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); + wc.lpszMenuName = nullptr; + wc.lpszClassName = lpwszClassName; + + *patom = RegisterClass(&wc); + + return (*patom != 0); +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_UninitClass +// +//---------------------------------------------------------------------------- + +/* static */ +void CBaseWindow::_UninitWindowClass(ATOM atom) +{ + if (atom != 0) + { + UnregisterClass((LPCTSTR)atom, Global::dllInstanceHandle); + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_Create +// +//---------------------------------------------------------------------------- + +BOOL CBaseWindow::_Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, _In_opt_ CBaseWindow *pParentWnd, int wndWidth, int wndHeight, _In_opt_ HWND parentWndHandle) +{ + _pParentWnd = pParentWnd; + + if (atom != 0) + { + // create real window + + _wndHandle = CreateWindowEx(dwExStyle, + (LPCTSTR)atom, + NULL, + dwStyle, + 0, 0, + wndWidth, wndHeight, + _pParentWnd ? _pParentWnd->_GetWnd() : parentWndHandle, // parentWndHandle + NULL, + Global::dllInstanceHandle, + this); // lpParam + + if (!_wndHandle) + { + return FALSE; + } + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_Destroy +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_Destroy() +{ + if (_wndHandle != nullptr) + { + DestroyWindow(_wndHandle); + _wndHandle = nullptr; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_Move +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_Move(int x, int y) +{ + if (_wndHandle != nullptr) + { + SetWindowPos(_wndHandle, 0, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); + } + else + { + int dx = x - _RectOfVirtualWnd.left; + int dy = y - _RectOfVirtualWnd.top; + + _RectOfVirtualWnd.left += dx; + _RectOfVirtualWnd.top += dy; + _RectOfVirtualWnd.right += dx; + _RectOfVirtualWnd.bottom += dy; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_Resize +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_Resize(int x, int y, int cx, int cy) +{ + if (_wndHandle != nullptr) + { + MoveWindow(_wndHandle, x, y, cx, cy, TRUE); + } + else + { + _RectOfVirtualWnd.left = x; + _RectOfVirtualWnd.top = y; + _RectOfVirtualWnd.right = x + cx; + _RectOfVirtualWnd.bottom = y + cy; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_Show +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_Show(BOOL isShowWnd) +{ + if (_wndHandle != nullptr) + { + if (isShowWnd) + { + ShowWindow(_wndHandle, SW_SHOWNA); + } + else + { + ShowWindow(_wndHandle, SW_HIDE); + } + } + else + { + _visibleVirtualWnd = isShowWnd; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_IsWindowVisible +// +//---------------------------------------------------------------------------- + +BOOL CBaseWindow::_IsWindowVisible() +{ + if (_wndHandle != nullptr) + { + return IsWindowVisible(_wndHandle); + } + else + { + return _visibleVirtualWnd; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_Enable +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_Enable(BOOL enableWindowReceiveInput) +{ + if (_wndHandle != nullptr) + { + EnableWindow(_wndHandle, enableWindowReceiveInput); + } + else + { + _enableVirtualWnd = enableWindowReceiveInput; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_IsEnabled +// +//---------------------------------------------------------------------------- + +BOOL CBaseWindow::_IsEnabled() +{ + if (_wndHandle != nullptr) + { + return IsWindowEnabled(_wndHandle); + } + else + { + return _enableVirtualWnd; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_InvalidateRect +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_InvalidateRect() +{ + if (_wndHandle != nullptr) + { + InvalidateRect(_wndHandle, NULL, TRUE); + } + else + { + CBaseWindow *pobj = _pParentWnd; + while (pobj != nullptr) + { + if (pobj->_wndHandle) + { + InvalidateRect(pobj->_wndHandle, &_RectOfVirtualWnd, TRUE); + break; + } + pobj = pobj->_pParentWnd; + } + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_GetWindowRect +// +//---------------------------------------------------------------------------- + +BOOL CBaseWindow::_GetWindowRect(_Inout_ LPRECT lpRect) +{ + if (_wndHandle != nullptr) + { + return GetWindowRect(_wndHandle, lpRect); + } + else + { + *lpRect = _RectOfVirtualWnd; + return TRUE; + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_GetClientRect +// +//---------------------------------------------------------------------------- + +BOOL CBaseWindow::_GetClientRect(_Inout_ LPRECT lpRect) +{ + if (_wndHandle != nullptr) + { + return GetClientRect(_wndHandle, lpRect); + } + else + { + *lpRect = _RectOfVirtualWnd; + return TRUE; + } +} + +//+--------------------------------------------------------------------------- +// +// _GetWindowExtent +// +//---------------------------------------------------------------------------- + +HRESULT CBaseWindow::_GetWindowExtent(_In_ const RECT *prcTextExtent, _In_opt_ RECT *prcCandidateExtent, _Inout_ POINT *pptCandidate) +{ + RECT rcWorkArea = {0, 0, 0, 0}; + + // Get work area + GetWorkAreaFromPoint(*(LPPOINT)&prcTextExtent->left, &rcWorkArea); + + // Calc candidate window extent + if (prcCandidateExtent) + { + CalcFitPointAroundTextExtent(prcTextExtent, &rcWorkArea, prcCandidateExtent, pptCandidate); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// CalcFitPointAroundTextExtent +// +//---------------------------------------------------------------------------- + +void CBaseWindow::CalcFitPointAroundTextExtent(_In_ const RECT *prcTextExtent, _In_ const RECT *prcWorkArea, _In_ const RECT *prcWindow, _Out_ POINT *ppt) +{ + RECT rcTargetWindow[2]; + DWORD dwFlags[2]; + + // set rcTargetWindow[0] which rectangle attached on bottom side of text extent + rcTargetWindow[0] = *prcWindow; + OffsetRect(&rcTargetWindow[0], prcTextExtent->left, prcTextExtent->bottom); + + // set rcTargetWindow[1] which rectangle attached on top side of text extent + rcTargetWindow[1] = *prcWindow; + OffsetRect(&rcTargetWindow[1], prcTextExtent->left, prcTextExtent->top - (prcWindow->bottom - prcWindow->top)); + + // + // check target rectangle fit in workarea + // + for (DWORD i = 0; i < ARRAYSIZE(rcTargetWindow); i++) + { + dwFlags[i] = RectInRect(prcWorkArea, &rcTargetWindow[i]); + + // generally, rcTargetWindow[0] or rcTargetWindow[1] have neither of these flags + // but in the case where window is just complete way off the screen e.g. user + // starts input, then drag window way off screen, both of these will be true + if ((dwFlags[i] & RECT_OVER_TOP) || (dwFlags[i] & RECT_OVER_BOTTOM)) + { + continue; + } + + if (dwFlags[i] == RECT_INSIDE) + { + *ppt = *(POINT*)&rcTargetWindow[i].left; + return; + } + else if ((dwFlags[i] & RECT_OVER_LEFT) != 0) + { + ppt->x = 0; + ppt->y = rcTargetWindow[i].top; + return; + } + else if ((dwFlags[i] & RECT_OVER_RIGHT) != 0) + { + ppt->x = prcWorkArea->right - (prcWindow->right - prcWindow->left); + ppt->y = rcTargetWindow[i].top; + return; + } + } + + // if both rcTargetWindow have RECT_OVER_TOP or RECT_OVER_BOTTOM, + // then "dock" the window to top or bottom of working area. + if ((dwFlags[0] & RECT_OVER_TOP) != 0) + { + ppt->y = 0; + } + else + { + ppt->y = prcWorkArea->bottom - (prcWindow->bottom - prcWindow->top); + } + + // dock to left/right edge if RECT_OVER_LEFT or RECT_OVER_RIGHT. + // else just stay where we are + if ((dwFlags[0] & RECT_OVER_LEFT) != 0) + { + ppt->x = 0; + } + else if ((dwFlags[0] & RECT_OVER_RIGHT) != 0) + { + ppt->x = prcWorkArea->right - (prcWindow->right - prcWindow->left); + } + else + { + ppt->x = prcTextExtent->left; + } + + return; +} + +//+--------------------------------------------------------------------------- +// +// RectInRect +// +//---------------------------------------------------------------------------- + +DWORD CBaseWindow::RectInRect(_In_ const RECT *prcLimit, _In_ const RECT *prcTarget) +{ + DWORD dwFlags = 0; + // Check if prcTarget is entirely inside prcLimit + if (prcLimit->left <= prcTarget->left && prcTarget->right <= prcLimit->right && + prcLimit->top <= prcTarget->top && prcTarget->bottom <= prcLimit->bottom) + { + return RECT_INSIDE; + } + + // Check horizontal range. Target can be + // - wider than the limit (assert here since it should never happen) + // - entirely outside the limit (RECT_OVERLEFT or RECT_OVERRIGHT) + // - partially inside the limit (RECT_OVERLEFT or RECT_OVERRIGHT) + if (prcTarget->left < prcLimit->left && prcTarget->right > prcLimit->right) + { + assert(FALSE); + dwFlags |= RECT_TOO_WIDE; + } + else if (prcTarget->left < prcLimit->left) + { + dwFlags |= RECT_OVER_LEFT; + } + else if (prcTarget->right > prcLimit->right) + { + dwFlags |= RECT_OVER_RIGHT; + } + + // Check vertical range. Target can be + // - taller than the limit (assert here since it should never happen) + // - entirely outside the limit (RECT_OVERTOP or RECT_OVERBOTTOM) + // - partially inside the limit (RECT_OVERTOP or RECT_OVERBOTTOM) + if (prcTarget->top < prcLimit->top && prcTarget->bottom > prcLimit->bottom) + { + assert(FALSE); + dwFlags |= RECT_TOO_TALL; + } + else if (prcTarget->top < prcLimit->top) + { + dwFlags |= RECT_OVER_TOP; + } + else if (prcTarget->bottom > prcLimit->bottom) + { + dwFlags |= RECT_OVER_BOTTOM; + } + + return dwFlags; +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_NotifyCommand +// +//---------------------------------------------------------------------------- + +LRESULT CBaseWindow::_NotifyCommand(UINT uMsg, DWORD dwSB, int nPos) +{ + CBaseWindow* pUIWnd = _GetUIWnd(); + if (pUIWnd && pUIWnd->_GetWnd()) + { + WPARAM wParam = MAKEWPARAM(dwSB, nPos); + if (pUIWnd->_GetWnd()) + { + return SendMessage(pUIWnd->_GetWnd(), uMsg, wParam, (LPARAM)0); + } + } + return 0; +} + +//+--------------------------------------------------------------------------- +// +// _SetCaptureObject +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_SetCaptureObject(_In_opt_ CBaseWindow *pUIObj) +{ + CBaseWindow* pUIWnd = _GetTopmostUIWnd(); + if (nullptr == pUIWnd) + { + return; + } + + pUIWnd->_pUIObjCapture = pUIObj; + if (pUIObj != nullptr) + { + SetCapture(pUIWnd->_GetWnd()); + } + else + { + ReleaseCapture(); + } +} + +//+--------------------------------------------------------------------------- +// +// _SetTimerObject +// +//---------------------------------------------------------------------------- + +void CBaseWindow::_SetTimerObject(_In_opt_ CBaseWindow *pUIObj, UINT uElapse) +{ + CBaseWindow* pUIWnd = _GetTopmostUIWnd(); + if (nullptr == pUIWnd) + { + return; + } + + pUIWnd->_pTimerUIObj = pUIObj; + if (pUIObj != nullptr) + { + SetTimer(pUIWnd->_GetWnd(), idTimer_UIObject, uElapse, NULL); + } + else + { + KillTimer(pUIWnd->_GetWnd(), idTimer_UIObject); + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::_WindowProc +// +// CBaseWindow window proc. +//---------------------------------------------------------------------------- + +/* static */ +LRESULT CALLBACK CBaseWindow::_WindowProc(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + if (uMsg == WM_CREATE) + { + _SetThis(wndHandle, ((CREATESTRUCT*)lParam)->lpCreateParams); + } + + CBaseWindow* pv = _GetThis(wndHandle); + if (!pv) + { + return DefWindowProc(wndHandle, uMsg, wParam, lParam); + } + + if (uMsg == WM_TIMER) + { + switch (wParam) + { + case idTimer_UIObject: + if (pv->_GetTimerObject() != nullptr) + { + pv->_GetTimerObject()->_OnTimer(); + } + break; + } + return 0; + } + else + { + return pv->_WindowProcCallback(wndHandle, uMsg, wParam, lParam); + } +} + +//+--------------------------------------------------------------------------- +// +// CBaseWindow::GetWorkAreaFromPoint +// +//---------------------------------------------------------------------------- + +void CBaseWindow::GetWorkAreaFromPoint(_In_ const POINT& ptPoint, _Out_ LPRECT lprcWorkArea) +{ + if (lprcWorkArea == nullptr) + { + return; + } + + lprcWorkArea->left = 0; + lprcWorkArea->top = 0; + lprcWorkArea->right = 0; + lprcWorkArea->bottom = 0; + + HMONITOR hMonitor = MonitorFromPoint(ptPoint, MONITOR_DEFAULTTONEAREST); + if (hMonitor) + { + MONITORINFO MonitorInfo = {0}; + + MonitorInfo.cbSize = sizeof(MONITORINFO); + if (GetMonitorInfo(hMonitor, &MonitorInfo)) + { + *lprcWorkArea = MonitorInfo.rcWork; + return; + } + } + + SystemParametersInfo(SPI_GETWORKAREA, 0, lprcWorkArea, 0); + return; +} + +BOOL CBaseWindow::_IsTimer() +{ + CBaseWindow* pobj = _GetTopmostUIWnd(); + if (pobj != nullptr) + { + return (pobj->_pTimerUIObj != nullptr); + } + return FALSE; +} + +BOOL CBaseWindow::_IsCapture() +{ + CBaseWindow* pobj = _GetTopmostUIWnd(); + if (pobj != nullptr) + { + return (pobj->_pUIObjCapture != nullptr); + } + return FALSE; +} + +CBaseWindow* CBaseWindow::_GetTopmostUIWnd() +{ + CBaseWindow* pobj = this; + + while (pobj->_pParentWnd != nullptr) + { + pobj = pobj->_pParentWnd; + } + + return pobj->_pUIWnd; +} \ No newline at end of file diff --git a/BaseWindow.h b/BaseWindow.h new file mode 100644 index 00000000..a93719d7 --- /dev/null +++ b/BaseWindow.h @@ -0,0 +1,160 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#define RECT_INSIDE (0x0001) +#define RECT_OVER_LEFT (0x0002) +#define RECT_OVER_TOP (0x0004) +#define RECT_OVER_RIGHT (0x0008) +#define RECT_OVER_BOTTOM (0x0010) +#define RECT_TOO_WIDE (0x0020) +#define RECT_TOO_TALL (0x0040) +#define RECT_ERROR (0x0080) + +class CStringRange; + +//+--------------------------------------------------------------------------- +// +// CBaseWindow +// +//---------------------------------------------------------------------------- + +class CBaseWindow +{ +public: + CBaseWindow(); + virtual ~CBaseWindow(); + + static BOOL _InitWindowClass(_In_ LPCWSTR lpwszClassName, _Out_ ATOM *patom); + static void _UninitWindowClass(ATOM atom); + + virtual BOOL _Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, _In_opt_ CBaseWindow *pParentWnd = nullptr, int wndWidth = 0, int wndHeight = 0, _In_opt_ HWND parentWndHandle = nullptr); + virtual void _Destroy(); + + virtual void _Move(int x, int y); + virtual void _Resize(int x, int y, int cx, int cy); + virtual void _Show(BOOL isShowWnd); + virtual BOOL _IsWindowVisible(); + virtual void _Enable(BOOL enableWindowReceiveInput); + virtual BOOL _IsEnabled(); + virtual void _InvalidateRect(); + virtual BOOL _GetWindowRect(_Inout_ LPRECT lpRect); + virtual BOOL _GetClientRect(_Inout_ LPRECT lpRect); + + virtual LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) = 0; + virtual void _OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps) { dcHandle; pps; } + virtual void _OnLButtonDown(POINT pt) { pt; } + virtual void _OnLButtonUp(POINT pt) { pt; } + virtual void _OnMouseMove(POINT pt) { pt; } + virtual void _OnTimer() { } + + CBaseWindow* _GetTopmostUIWnd(); + + HRESULT _GetWindowExtent(_In_ const RECT *prcTextExtent, _In_opt_ RECT *prcCandidateExtent, _Inout_ POINT *pptCandidate); + + HWND _GetWnd() + { + return _wndHandle; + } + + CBaseWindow *_GetParent() + { + return _pParentWnd; + } + + void _SetUIWnd(_In_ CBaseWindow *pUIWnd) + { + _pUIWnd = pUIWnd; + } + + CBaseWindow* _GetUIWnd() + { + return _pUIWnd; + } + + CBaseWindow *_GetCaptureObject() + { + return _pUIObjCapture; + } + + CBaseWindow *_GetTimerObject() + { + return _pTimerUIObj; + } + + UINT _GetScrollDelay() + { + return (GetDoubleClickTime() * 4 / 5); + } + + UINT _GetScrollSpeed() + { + return (_GetScrollDelay() / 8); + } + +protected: + LRESULT _NotifyCommand(UINT uMsg, DWORD dwSB, int nPos); + + void _StartCapture() + { + _SetCaptureObject(this); + } + + void _EndCapture() + { + _SetCaptureObject(nullptr); + } + + BOOL _IsCapture(); + + void _StartTimer(UINT uElapse) + { + _SetTimerObject(this, uElapse); + } + + void _EndTimer() + { + _SetTimerObject(nullptr); + } + + BOOL _IsTimer(); + +private: + void _SetCaptureObject(_In_opt_ CBaseWindow *pUIObj); + void _SetTimerObject(_In_opt_ CBaseWindow *pUIObj, UINT uElapse = 0); + + void GetWorkAreaFromPoint(_In_ const POINT& ptPoint, _Out_ LPRECT lprcWorkArea); + void CalcFitPointAroundTextExtent(_In_ const RECT *prcTextExtent, _In_ const RECT *prcWorkArea, _In_ const RECT *prcWindow, _Out_ POINT *ppt); + DWORD RectInRect(_In_ const RECT *prcLimit, _In_ const RECT *prcTarget); + + static LRESULT CALLBACK _WindowProc(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); + + static CBaseWindow *_GetThis(_In_ HWND wndHandle) + { + return (CBaseWindow *)GetWindowLongPtr(wndHandle, GWLP_USERDATA); + } + + static void _SetThis(_In_ HWND wndHandle, _In_opt_ void *lpv) + { + SetWindowLongPtr(wndHandle, GWLP_USERDATA, (LONG_PTR)lpv); + } + +private: + HWND _wndHandle; + + CBaseWindow *_pParentWnd; + CBaseWindow *_pUIWnd; + + CBaseWindow *_pTimerUIObj; + CBaseWindow *_pUIObjCapture; + + BOOL _enableVirtualWnd; + BOOL _visibleVirtualWnd; + RECT _RectOfVirtualWnd; +}; diff --git a/ButtonWindow.cpp b/ButtonWindow.cpp new file mode 100644 index 00000000..0f2aef1b --- /dev/null +++ b/ButtonWindow.cpp @@ -0,0 +1,90 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "BaseWindow.h" +#include "ButtonWindow.h" + +//+--------------------------------------------------------------------------- +// +// CButtonWindow +// +//---------------------------------------------------------------------------- + +CButtonWindow::CButtonWindow() +{ + typeOfControl = 0; +} + +//+--------------------------------------------------------------------------- +// +// ~CButtonWindow +// +//---------------------------------------------------------------------------- + +CButtonWindow::~CButtonWindow() +{ +} + +//+--------------------------------------------------------------------------- +// +// _OnPaint +// +//---------------------------------------------------------------------------- + +void CButtonWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps) +{ + dcHandle; + pps; +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonDown +// +//---------------------------------------------------------------------------- + +void CButtonWindow::_OnLButtonDown(POINT pt) +{ + pt; + + typeOfControl = DFCS_PUSHED; + _StartCapture(); +} + +//+--------------------------------------------------------------------------- +// +// _WindowProcCallback +// +//---------------------------------------------------------------------------- +LRESULT CALLBACK CButtonWindow::_WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + wndHandle; + uMsg; + wParam; + lParam; + + return 0; +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonUp +// +//---------------------------------------------------------------------------- + +void CButtonWindow::_OnLButtonUp(POINT pt) +{ + pt; + + if (_IsCapture()) + { + _EndCapture(); + } + + typeOfControl = 0; +} diff --git a/ButtonWindow.h b/ButtonWindow.h new file mode 100644 index 00000000..e2fa300d --- /dev/null +++ b/ButtonWindow.h @@ -0,0 +1,27 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "BaseWindow.h" + +class CButtonWindow : public CBaseWindow +{ +public: + CButtonWindow(); + virtual ~CButtonWindow(); + + LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); + + virtual void _OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps); + virtual void _OnLButtonDown(POINT pt); + virtual void _OnLButtonUp(POINT pt); + +protected: + UINT typeOfControl; // DFCS_PUSHED, DFCS_INACTIVE or others for DrawFrameControl +}; diff --git a/CMakeDraft.txt b/CMakeDraft.txt new file mode 100644 index 00000000..a2c6d423 --- /dev/null +++ b/CMakeDraft.txt @@ -0,0 +1,85 @@ +"./ActiveLanguageProfileNotifySink.cpp" +"./BaseDictionaryEngine.cpp" +"./BaseDictionaryEngine.h" +"./BaseWindow.cpp" +"./BaseWindow.h" +"./ButtonWindow.cpp" +"./ButtonWindow.h" +"./CandidateListUIPresenter.cpp" +"./CandidateListUIPresenter.h" +"./CandidateWindow.cpp" +"./CandidateWindow.h" +"./Compartment.cpp" +"./Compartment.h" +"./Composition.cpp" +"./CompositionProcessorEngine.cpp" +"./CompositionProcessorEngine.h" +"./Define.h" +"./DictionaryParser.cpp" +"./DictionaryParser.h" +"./DictionarySearch.cpp" +"./DictionarySearch.h" +"./DisplayAttribute.cpp" +"./DisplayAttributeInfo.cpp" +"./DisplayAttributeInfo.h" +"./DisplayAttributeProvider.cpp" +"./DisplayString.h" +"./DllMain.cpp" +"./EditSession.cpp" +"./EditSession.h" +"./EndComposition.cpp" +"./EnumDisplayAttributeInfo.cpp" +"./EnumDisplayAttributeInfo.h" +"./EnumTfCandidates.cpp" +"./EnumTfCandidates.h" +"./File.cpp" +"./File.h" +"./FileMapping.cpp" +"./FileMapping.h" +"./FunctionProviderSink.cpp" +"./GetTextExtentEditSession.cpp" +"./GetTextExtentEditSession.h" +"./Globals.cpp" +"./Globals.h" +"./KeyEventSink.cpp" +"./KeyHandler.cpp" +"./KeyHandlerEditSession.cpp" +"./KeyHandlerEditSession.h" +"./KeyStateCategory.cpp" +"./KeyStateCategory.h" +"./LanguageBar.cpp" +"./LanguageBar.h" +"./Private.h" +"./Register.cpp" +"./RegKey.cpp" +"./RegKey.h" +"./resource.h" +"./SampleIME.cpp" +"./SampleIME.def" +"./SampleIME.h" +"./SampleIME.rc" +"./SampleIMEBaseStructure.cpp" +"./SampleIMEBaseStructure.h" +"./SampleIMEStructureArray.h" +"./ScrollBarWindow.cpp" +"./ScrollBarWindow.h" +"./SearchCandidateProvider.cpp" +"./SearchCandidateProvider.h" +"./Server.cpp" +"./ShadowWindow.cpp" +"./ShadowWindow.h" +"./StartComposition.cpp" +"./stdafx.h" +"./TableDictionaryEngine.cpp" +"./TableDictionaryEngine.h" +"./TextEditSink.cpp" +"./TfInputProcessorProfile.cpp" +"./TfInputProcessorProfile.h" +"./TfTextLayoutSink.cpp" +"./TfTextLayoutSink.h" +"./ThreadFocusSink.cpp" +"./ThreadMgrEventSink.cpp" +"./TipCandidateList.cpp" +"./TipCandidateList.h" +"./TipCandidateString.cpp" +"./TipCandidateString.h" diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..6a67d7d9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,98 @@ +cmake_minimum_required(VERSION 3.25.0) + +project(fanytsfime) + +# http://www.utf8everywhere.org/ +add_definitions( + /D_UNICODE=1 + /DUNICODE=1 +) + +add_library(SampleIME SHARED + "./ActiveLanguageProfileNotifySink.cpp" + "./BaseDictionaryEngine.cpp" + "./BaseDictionaryEngine.h" + "./BaseWindow.cpp" + "./BaseWindow.h" + "./ButtonWindow.cpp" + "./ButtonWindow.h" + "./CandidateListUIPresenter.cpp" + "./CandidateListUIPresenter.h" + "./CandidateWindow.cpp" + "./CandidateWindow.h" + "./Compartment.cpp" + "./Compartment.h" + "./Composition.cpp" + "./CompositionProcessorEngine.cpp" + "./CompositionProcessorEngine.h" + "./Define.h" + "./DictionaryParser.cpp" + "./DictionaryParser.h" + "./DictionarySearch.cpp" + "./DictionarySearch.h" + "./DisplayAttribute.cpp" + "./DisplayAttributeInfo.cpp" + "./DisplayAttributeInfo.h" + "./DisplayAttributeProvider.cpp" + "./DisplayString.h" + "./DllMain.cpp" + "./EditSession.cpp" + "./EditSession.h" + "./EndComposition.cpp" + "./EnumDisplayAttributeInfo.cpp" + "./EnumDisplayAttributeInfo.h" + "./EnumTfCandidates.cpp" + "./EnumTfCandidates.h" + "./File.cpp" + "./File.h" + "./FileMapping.cpp" + "./FileMapping.h" + "./FunctionProviderSink.cpp" + "./GetTextExtentEditSession.cpp" + "./GetTextExtentEditSession.h" + "./Globals.cpp" + "./Globals.h" + "./KeyEventSink.cpp" + "./KeyHandler.cpp" + "./KeyHandlerEditSession.cpp" + "./KeyHandlerEditSession.h" + "./KeyStateCategory.cpp" + "./KeyStateCategory.h" + "./LanguageBar.cpp" + "./LanguageBar.h" + "./Private.h" + "./Register.cpp" + "./RegKey.cpp" + "./RegKey.h" + "./resource.h" + "./SampleIME.cpp" + "./SampleIME.def" + "./SampleIME.h" + "./SampleIME.rc" + "./SampleIMEBaseStructure.cpp" + "./SampleIMEBaseStructure.h" + "./SampleIMEStructureArray.h" + "./ScrollBarWindow.cpp" + "./ScrollBarWindow.h" + "./SearchCandidateProvider.cpp" + "./SearchCandidateProvider.h" + "./Server.cpp" + "./ShadowWindow.cpp" + "./ShadowWindow.h" + "./StartComposition.cpp" + "./stdafx.h" + "./TableDictionaryEngine.cpp" + "./TableDictionaryEngine.h" + "./TextEditSink.cpp" + "./TfInputProcessorProfile.cpp" + "./TfInputProcessorProfile.h" + "./TfTextLayoutSink.cpp" + "./TfTextLayoutSink.h" + "./ThreadFocusSink.cpp" + "./ThreadMgrEventSink.cpp" + "./TipCandidateList.cpp" + "./TipCandidateList.h" + "./TipCandidateString.cpp" + "./TipCandidateString.h" +) + diff --git a/CandidateListUIPresenter.cpp b/CandidateListUIPresenter.cpp new file mode 100644 index 00000000..66bfb6c7 --- /dev/null +++ b/CandidateListUIPresenter.cpp @@ -0,0 +1,1305 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "SampleIME.h" +#include "CandidateWindow.h" +#include "CandidateListUIPresenter.h" +#include "CompositionProcessorEngine.h" +#include "SampleIMEBaseStructure.h" + +////////////////////////////////////////////////////////////////////// +// +// CSampleIME candidate key handler methods +// +////////////////////////////////////////////////////////////////////// + +const int MOVEUP_ONE = -1; +const int MOVEDOWN_ONE = 1; +const int MOVETO_TOP = 0; +const int MOVETO_BOTTOM = -1; +//+--------------------------------------------------------------------------- +// +// _HandleCandidateFinalize +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCandidateFinalize(TfEditCookie ec, _In_ ITfContext *pContext) +{ + HRESULT hr = S_OK; + DWORD_PTR candidateLen = 0; + const WCHAR* pCandidateString = nullptr; + CStringRange candidateString; + + if (nullptr == _pCandidateListUIPresenter) + { + goto NoPresenter; + } + + candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString); + + candidateString.Set(pCandidateString, candidateLen); + + if (candidateLen) + { + hr = _AddComposingAndChar(ec, pContext, &candidateString); + + if (FAILED(hr)) + { + return hr; + } + } + +NoPresenter: + + _HandleComplete(ec, pContext); + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCandidateConvert +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCandidateConvert(TfEditCookie ec, _In_ ITfContext *pContext) +{ + return _HandleCandidateWorker(ec, pContext); +} + +//+--------------------------------------------------------------------------- +// +// _HandleCandidateWorker +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCandidateWorker(TfEditCookie ec, _In_ ITfContext *pContext) +{ + HRESULT hrReturn = E_FAIL; + DWORD_PTR candidateLen = 0; + const WCHAR* pCandidateString = nullptr; + BSTR pbstr = nullptr; + CStringRange candidateString; + CSampleImeArray candidatePhraseList; + + if (nullptr == _pCandidateListUIPresenter) + { + hrReturn = S_OK; + goto Exit; + } + + candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString); + if (0 == candidateLen) + { + hrReturn = S_FALSE; + goto Exit; + } + + candidateString.Set(pCandidateString, candidateLen); + + BOOL fMakePhraseFromText = _pCompositionProcessorEngine->IsMakePhraseFromText(); + if (fMakePhraseFromText) + { + _pCompositionProcessorEngine->GetCandidateStringInConverted(candidateString, &candidatePhraseList); + LCID locale = _pCompositionProcessorEngine->GetLocale(); + + _pCandidateListUIPresenter->RemoveSpecificCandidateFromList(locale, candidatePhraseList, candidateString); + } + + // We have a candidate list if candidatePhraseList.Cnt is not 0 + // If we are showing reverse conversion, use CCandidateListUIPresenter + CANDIDATE_MODE tempCandMode = CANDIDATE_NONE; + CCandidateListUIPresenter* pTempCandListUIPresenter = nullptr; + if (candidatePhraseList.Count()) + { + tempCandMode = CANDIDATE_WITH_NEXT_COMPOSITION; + + pTempCandListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow, + CATEGORY_CANDIDATE, + _pCompositionProcessorEngine->GetCandidateListIndexRange(), + FALSE); + if (nullptr == pTempCandListUIPresenter) + { + hrReturn = E_OUTOFMEMORY; + goto Exit; + } + } + + // call _Start*Line for CCandidateListUIPresenter or CReadingLine + // we don't cache the document manager object so get it from pContext. + ITfDocumentMgr* pDocumentMgr = nullptr; + HRESULT hrStartCandidateList = E_FAIL; + if (pContext->GetDocumentMgr(&pDocumentMgr) == S_OK) + { + ITfRange* pRange = nullptr; + if (_pComposition->GetRange(&pRange) == S_OK) + { + if (pTempCandListUIPresenter) + { + hrStartCandidateList = pTempCandListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, _pCompositionProcessorEngine->GetCandidateWindowWidth()); + } + + pRange->Release(); + } + pDocumentMgr->Release(); + } + + // set up candidate list if it is being shown + if (SUCCEEDED(hrStartCandidateList)) + { + pTempCandListUIPresenter->_SetTextColor(RGB(0, 0x80, 0), GetSysColor(COLOR_WINDOW)); // Text color is green + pTempCandListUIPresenter->_SetFillColor((HBRUSH)(COLOR_WINDOW+1)); // Background color is window + pTempCandListUIPresenter->_SetText(&candidatePhraseList, FALSE); + + // Add composing character + hrReturn = _AddComposingAndChar(ec, pContext, &candidateString); + + // close candidate list + if (_pCandidateListUIPresenter) + { + _pCandidateListUIPresenter->_EndCandidateList(); + delete _pCandidateListUIPresenter; + _pCandidateListUIPresenter = nullptr; + + _candidateMode = CANDIDATE_NONE; + _isCandidateWithWildcard = FALSE; + } + + if (hrReturn == S_OK) + { + // copy temp candidate + _pCandidateListUIPresenter = pTempCandListUIPresenter; + + _candidateMode = tempCandMode; + _isCandidateWithWildcard = FALSE; + } + } + else + { + hrReturn = _HandleCandidateFinalize(ec, pContext); + } + + if (pbstr) + { + SysFreeString(pbstr); + } + +Exit: + return hrReturn; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCandidateArrowKey +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCandidateArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, _In_ KEYSTROKE_FUNCTION keyFunction) +{ + ec; + pContext; + + _pCandidateListUIPresenter->AdviseUIChangedByArrowKey(keyFunction); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCandidateSelectByNumber +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCandidateSelectByNumber(TfEditCookie ec, _In_ ITfContext *pContext, _In_ UINT uCode) +{ + int iSelectAsNumber = _pCompositionProcessorEngine->GetCandidateListIndexRange()->GetIndex(uCode); + if (iSelectAsNumber == -1) + { + return S_FALSE; + } + + if (_pCandidateListUIPresenter) + { + if (_pCandidateListUIPresenter->_SetSelectionInPage(iSelectAsNumber)) + { + return _HandleCandidateConvert(ec, pContext); + } + } + + return S_FALSE; +} + +//+--------------------------------------------------------------------------- +// +// _HandlePhraseFinalize +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandlePhraseFinalize(TfEditCookie ec, _In_ ITfContext *pContext) +{ + HRESULT hr = S_OK; + + DWORD phraseLen = 0; + const WCHAR* pPhraseString = nullptr; + + phraseLen = (DWORD)_pCandidateListUIPresenter->_GetSelectedCandidateString(&pPhraseString); + + CStringRange phraseString; + phraseString.Set(pPhraseString, phraseLen); + + if (phraseLen) + { + if ((hr = _AddCharAndFinalize(ec, pContext, &phraseString)) != S_OK) + { + return hr; + } + } + + _HandleComplete(ec, pContext); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandlePhraseArrowKey +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandlePhraseArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, _In_ KEYSTROKE_FUNCTION keyFunction) +{ + ec; + pContext; + + _pCandidateListUIPresenter->AdviseUIChangedByArrowKey(keyFunction); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandlePhraseSelectByNumber +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandlePhraseSelectByNumber(TfEditCookie ec, _In_ ITfContext *pContext, _In_ UINT uCode) +{ + int iSelectAsNumber = _pCompositionProcessorEngine->GetCandidateListIndexRange()->GetIndex(uCode); + if (iSelectAsNumber == -1) + { + return S_FALSE; + } + + if (_pCandidateListUIPresenter) + { + if (_pCandidateListUIPresenter->_SetSelectionInPage(iSelectAsNumber)) + { + return _HandlePhraseFinalize(ec, pContext); + } + } + + return S_FALSE; +} + +////////////////////////////////////////////////////////////////////// +// +// CCandidateListUIPresenter class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CCandidateListUIPresenter::CCandidateListUIPresenter(_In_ CSampleIME *pTextService, ATOM atom, KEYSTROKE_CATEGORY Category, _In_ CCandidateRange *pIndexRange, BOOL hideWindow) : CTfTextLayoutSink(pTextService) +{ + _atom = atom; + + _pIndexRange = pIndexRange; + + _parentWndHandle = nullptr; + _pCandidateWnd = nullptr; + + _Category = Category; + + _updatedFlags = 0; + + _uiElementId = (DWORD)-1; + _isShowMode = TRUE; // store return value from BeginUIElement + _hideWindow = hideWindow; // Hide window flag from [Configuration] CandidateList.Phrase.HideWindow + + _pTextService = pTextService; + _pTextService->AddRef(); + + _refCount = 1; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CCandidateListUIPresenter::~CCandidateListUIPresenter() +{ + _EndCandidateList(); + _pTextService->Release(); +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::IUnknown::QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (CTfTextLayoutSink::QueryInterface(riid, ppvObj) == S_OK) + { + return S_OK; + } + + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_ITfUIElement) || + IsEqualIID(riid, IID_ITfCandidateListUIElement)) + { + *ppvObj = (ITfCandidateListUIElement*)this; + } + else if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfCandidateListUIElementBehavior)) + { + *ppvObj = (ITfCandidateListUIElementBehavior*)this; + } + else if (IsEqualIID(riid, __uuidof(ITfIntegratableCandidateListUIElement))) + { + *ppvObj = (ITfIntegratableCandidateListUIElement*)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::IUnknown::AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CCandidateListUIPresenter::AddRef() +{ + CTfTextLayoutSink::AddRef(); + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::IUnknown::Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CCandidateListUIPresenter::Release() +{ + CTfTextLayoutSink::Release(); + + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::ITfUIElement::GetDescription +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetDescription(BSTR *pbstr) +{ + if (pbstr) + { + *pbstr = SysAllocString(L"Cand"); + } + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::ITfUIElement::GetGUID +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetGUID(GUID *pguid) +{ + *pguid = Global::SampleIMEGuidCandUIElement; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::ITfUIElement::Show +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::Show(BOOL showCandidateWindow) +{ + if (showCandidateWindow) + { + ToShowCandidateWindow(); + } + else + { + ToHideCandidateWindow(); + } + return S_OK; +} + +HRESULT CCandidateListUIPresenter::ToShowCandidateWindow() +{ + if (_hideWindow) + { + _pCandidateWnd->_Show(FALSE); + } + else + { + _MoveWindowToTextExt(); + + _pCandidateWnd->_Show(TRUE); + } + + return S_OK; +} + +HRESULT CCandidateListUIPresenter::ToHideCandidateWindow() +{ + if (_pCandidateWnd) + { + _pCandidateWnd->_Show(FALSE); + } + + _updatedFlags = TF_CLUIE_SELECTION | TF_CLUIE_CURRENTPAGE; + _UpdateUIElement(); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::ITfUIElement::IsShown +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::IsShown(BOOL *pIsShow) +{ + *pIsShow = _pCandidateWnd->_IsWindowVisible(); + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetUpdatedFlags +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetUpdatedFlags(DWORD *pdwFlags) +{ + *pdwFlags = _updatedFlags; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetDocumentMgr +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetDocumentMgr(ITfDocumentMgr **ppdim) +{ + *ppdim = nullptr; + + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetCount +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetCount(UINT *pCandidateCount) +{ + if (_pCandidateWnd) + { + *pCandidateCount = _pCandidateWnd->_GetCount(); + } + else + { + *pCandidateCount = 0; + } + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetSelection +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetSelection(UINT *pSelectedCandidateIndex) +{ + if (_pCandidateWnd) + { + *pSelectedCandidateIndex = _pCandidateWnd->_GetSelection(); + } + else + { + *pSelectedCandidateIndex = 0; + } + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetString +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetString(UINT uIndex, BSTR *pbstr) +{ + if (!_pCandidateWnd || (uIndex > _pCandidateWnd->_GetCount())) + { + return E_FAIL; + } + + DWORD candidateLen = 0; + const WCHAR* pCandidateString = nullptr; + + candidateLen = _pCandidateWnd->_GetCandidateString(uIndex, &pCandidateString); + + *pbstr = (candidateLen == 0) ? nullptr : SysAllocStringLen(pCandidateString, candidateLen); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetPageIndex +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetPageIndex(UINT *pIndex, UINT uSize, UINT *puPageCnt) +{ + if (!_pCandidateWnd) + { + if (pIndex) + { + *pIndex = 0; + } + *puPageCnt = 0; + return S_OK; + } + return _pCandidateWnd->_GetPageIndex(pIndex, uSize, puPageCnt); +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::SetPageIndex +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::SetPageIndex(UINT *pIndex, UINT uPageCnt) +{ + if (!_pCandidateWnd) + { + return E_FAIL; + } + return _pCandidateWnd->_SetPageIndex(pIndex, uPageCnt); +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElement::GetCurrentPage +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetCurrentPage(UINT *puPage) +{ + if (!_pCandidateWnd) + { + *puPage = 0; + return S_OK; + } + return _pCandidateWnd->_GetCurrentPage(puPage); +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElementBehavior::SetSelection +// It is related of the mouse clicking behavior upon the suggestion window +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::SetSelection(UINT nIndex) +{ + if (_pCandidateWnd) + { + _pCandidateWnd->_SetSelection(nIndex); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElementBehavior::Finalize +// It is related of the mouse clicking behavior upon the suggestion window +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::Finalize(void) +{ + _CandidateChangeNotification(CAND_ITEM_SELECT); + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfCandidateListUIElementBehavior::Abort +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::Abort(void) +{ + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfIntegratableCandidateListUIElement::SetIntegrationStyle +// To show candidateNumbers on the suggestion window +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::SetIntegrationStyle(GUID guidIntegrationStyle) +{ + return (guidIntegrationStyle == GUID_INTEGRATIONSTYLE_SEARCHBOX) ? S_OK : E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfIntegratableCandidateListUIElement::GetSelectionStyle +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::GetSelectionStyle(_Out_ TfIntegratableCandidateListSelectionStyle *ptfSelectionStyle) +{ + *ptfSelectionStyle = STYLE_ACTIVE_SELECTION; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfIntegratableCandidateListUIElement::OnKeyDown +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::OnKeyDown(_In_ WPARAM wParam, _In_ LPARAM lParam, _Out_ BOOL *pIsEaten) +{ + wParam; + lParam; + + *pIsEaten = TRUE; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfIntegratableCandidateListUIElement::ShowCandidateNumbers +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::ShowCandidateNumbers(_Out_ BOOL *pIsShow) +{ + *pIsShow = TRUE; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfIntegratableCandidateListUIElement::FinalizeExactCompositionString +// +//---------------------------------------------------------------------------- + +STDAPI CCandidateListUIPresenter::FinalizeExactCompositionString() +{ + return E_NOTIMPL; +} + + +//+--------------------------------------------------------------------------- +// +// _StartCandidateList +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateListUIPresenter::_StartCandidateList(TfClientId tfClientId, _In_ ITfDocumentMgr *pDocumentMgr, _In_ ITfContext *pContextDocument, TfEditCookie ec, _In_ ITfRange *pRangeComposition, UINT wndWidth) +{ + pDocumentMgr;tfClientId; + + HRESULT hr = E_FAIL; + + if (FAILED(_StartLayout(pContextDocument, ec, pRangeComposition))) + { + goto Exit; + } + + BeginUIElement(); + + hr = MakeCandidateWindow(pContextDocument, wndWidth); + if (FAILED(hr)) + { + goto Exit; + } + + Show(_isShowMode); + + RECT rcTextExt; + if (SUCCEEDED(_GetTextExt(&rcTextExt))) + { + _LayoutChangeNotification(&rcTextExt); + } + +Exit: + if (FAILED(hr)) + { + _EndCandidateList(); + } + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _EndCandidateList +// +//---------------------------------------------------------------------------- + +void CCandidateListUIPresenter::_EndCandidateList() +{ + EndUIElement(); + + DisposeCandidateWindow(); + + _EndLayout(); +} + +//+--------------------------------------------------------------------------- +// +// _SetText +// +//---------------------------------------------------------------------------- + +void CCandidateListUIPresenter::_SetText(_In_ CSampleImeArray *pCandidateList, BOOL isAddFindKeyCode) +{ + AddCandidateToCandidateListUI(pCandidateList, isAddFindKeyCode); + + SetPageIndexWithScrollInfo(pCandidateList); + + if (_isShowMode) + { + _pCandidateWnd->_InvalidateRect(); + } + else + { + _updatedFlags = TF_CLUIE_COUNT | + TF_CLUIE_SELECTION | + TF_CLUIE_STRING | + TF_CLUIE_PAGEINDEX | + TF_CLUIE_CURRENTPAGE; + _UpdateUIElement(); + } +} + +void CCandidateListUIPresenter::AddCandidateToCandidateListUI(_In_ CSampleImeArray *pCandidateList, BOOL isAddFindKeyCode) +{ + for (UINT index = 0; index < pCandidateList->Count(); index++) + { + _pCandidateWnd->_AddString(pCandidateList->GetAt(index), isAddFindKeyCode); + } +} + +void CCandidateListUIPresenter::SetPageIndexWithScrollInfo(_In_ CSampleImeArray *pCandidateList) +{ + UINT candCntInPage = _pIndexRange->Count(); + UINT bufferSize = pCandidateList->Count() / candCntInPage + 1; + UINT* puPageIndex = new (std::nothrow) UINT[ bufferSize ]; + if (puPageIndex != nullptr) + { + for (UINT i = 0; i < bufferSize; i++) + { + puPageIndex[i] = i * candCntInPage; + } + + _pCandidateWnd->_SetPageIndex(puPageIndex, bufferSize); + delete [] puPageIndex; + } + _pCandidateWnd->_SetScrollInfo(pCandidateList->Count(), candCntInPage); // nMax:range of max, nPage:number of items in page + +} +//+--------------------------------------------------------------------------- +// +// _ClearList +// +//---------------------------------------------------------------------------- + +void CCandidateListUIPresenter::_ClearList() +{ + _pCandidateWnd->_ClearList(); + _pCandidateWnd->_InvalidateRect(); +} + +//+--------------------------------------------------------------------------- +// +// _SetTextColor +// _SetFillColor +// +//---------------------------------------------------------------------------- + +void CCandidateListUIPresenter::_SetTextColor(COLORREF crColor, COLORREF crBkColor) +{ + _pCandidateWnd->_SetTextColor(crColor, crBkColor); +} + +void CCandidateListUIPresenter::_SetFillColor(HBRUSH hBrush) +{ + _pCandidateWnd->_SetFillColor(hBrush); +} + +//+--------------------------------------------------------------------------- +// +// _GetSelectedCandidateString +// +//---------------------------------------------------------------------------- + +DWORD_PTR CCandidateListUIPresenter::_GetSelectedCandidateString(_Outptr_result_maybenull_ const WCHAR **ppwchCandidateString) +{ + return _pCandidateWnd->_GetSelectedCandidateString(ppwchCandidateString); +} + +//+--------------------------------------------------------------------------- +// +// _MoveSelection +// +//---------------------------------------------------------------------------- + +BOOL CCandidateListUIPresenter::_MoveSelection(_In_ int offSet) +{ + BOOL ret = _pCandidateWnd->_MoveSelection(offSet, TRUE); + if (ret) + { + if (_isShowMode) + { + _pCandidateWnd->_InvalidateRect(); + } + else + { + _updatedFlags = TF_CLUIE_SELECTION; + _UpdateUIElement(); + } + } + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _SetSelection +// +//---------------------------------------------------------------------------- + +BOOL CCandidateListUIPresenter::_SetSelection(_In_ int selectedIndex) +{ + BOOL ret = _pCandidateWnd->_SetSelection(selectedIndex, TRUE); + if (ret) + { + if (_isShowMode) + { + _pCandidateWnd->_InvalidateRect(); + } + else + { + _updatedFlags = TF_CLUIE_SELECTION | + TF_CLUIE_CURRENTPAGE; + _UpdateUIElement(); + } + } + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _MovePage +// +//---------------------------------------------------------------------------- + +BOOL CCandidateListUIPresenter::_MovePage(_In_ int offSet) +{ + BOOL ret = _pCandidateWnd->_MovePage(offSet, TRUE); + if (ret) + { + if (_isShowMode) + { + _pCandidateWnd->_InvalidateRect(); + } + else + { + _updatedFlags = TF_CLUIE_SELECTION | + TF_CLUIE_CURRENTPAGE; + _UpdateUIElement(); + } + } + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _MoveWindowToTextExt +// +//---------------------------------------------------------------------------- + +void CCandidateListUIPresenter::_MoveWindowToTextExt() +{ + RECT rc; + + if (FAILED(_GetTextExt(&rc))) + { + return; + } + + _pCandidateWnd->_Move(rc.left, rc.bottom); +} +//+--------------------------------------------------------------------------- +// +// _LayoutChangeNotification +// +//---------------------------------------------------------------------------- + +VOID CCandidateListUIPresenter::_LayoutChangeNotification(_In_ RECT *lpRect) +{ + RECT rectCandidate = {0, 0, 0, 0}; + POINT ptCandidate = {0, 0}; + + _pCandidateWnd->_GetClientRect(&rectCandidate); + _pCandidateWnd->_GetWindowExtent(lpRect, &rectCandidate, &ptCandidate); + _pCandidateWnd->_Move(ptCandidate.x, ptCandidate.y); +} + +//+--------------------------------------------------------------------------- +// +// _LayoutDestroyNotification +// +//---------------------------------------------------------------------------- + +VOID CCandidateListUIPresenter::_LayoutDestroyNotification() +{ + _EndCandidateList(); +} + +//+--------------------------------------------------------------------------- +// +// _CandidateChangeNotifiction +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateListUIPresenter::_CandidateChangeNotification(_In_ enum CANDWND_ACTION action) +{ + HRESULT hr = E_FAIL; + + TfClientId tfClientId = _pTextService->_GetClientId(); + ITfThreadMgr* pThreadMgr = nullptr; + ITfDocumentMgr* pDocumentMgr = nullptr; + ITfContext* pContext = nullptr; + + _KEYSTROKE_STATE KeyState; + KeyState.Category = _Category; + KeyState.Function = FUNCTION_FINALIZE_CANDIDATELIST; + + if (CAND_ITEM_SELECT != action) + { + goto Exit; + } + + pThreadMgr = _pTextService->_GetThreadMgr(); + if (nullptr == pThreadMgr) + { + goto Exit; + } + + hr = pThreadMgr->GetFocus(&pDocumentMgr); + if (FAILED(hr)) + { + goto Exit; + } + + hr = pDocumentMgr->GetTop(&pContext); + if (FAILED(hr)) + { + pDocumentMgr->Release(); + goto Exit; + } + + CKeyHandlerEditSession *pEditSession = new (std::nothrow) CKeyHandlerEditSession(_pTextService, pContext, 0, 0, KeyState); + if (nullptr != pEditSession) + { + HRESULT hrSession = S_OK; + hr = pContext->RequestEditSession(tfClientId, pEditSession, TF_ES_SYNC | TF_ES_READWRITE, &hrSession); + if (hrSession == TF_E_SYNCHRONOUS || hrSession == TS_E_READONLY) + { + hr = pContext->RequestEditSession(tfClientId, pEditSession, TF_ES_ASYNC | TF_ES_READWRITE, &hrSession); + } + pEditSession->Release(); + } + + pContext->Release(); + pDocumentMgr->Release(); + +Exit: + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _CandWndCallback +// +//---------------------------------------------------------------------------- + +// static +HRESULT CCandidateListUIPresenter::_CandWndCallback(_In_ void *pv, _In_ enum CANDWND_ACTION action) +{ + CCandidateListUIPresenter* fakeThis = (CCandidateListUIPresenter*)pv; + + return fakeThis->_CandidateChangeNotification(action); +} + +//+--------------------------------------------------------------------------- +// +// _UpdateUIElement +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateListUIPresenter::_UpdateUIElement() +{ + HRESULT hr = S_OK; + + ITfThreadMgr* pThreadMgr = _pTextService->_GetThreadMgr(); + if (nullptr == pThreadMgr) + { + return S_OK; + } + + ITfUIElementMgr* pUIElementMgr = nullptr; + + hr = pThreadMgr->QueryInterface(IID_ITfUIElementMgr, (void **)&pUIElementMgr); + if (hr == S_OK) + { + pUIElementMgr->UpdateUIElement(_uiElementId); + pUIElementMgr->Release(); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// OnSetThreadFocus +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateListUIPresenter::OnSetThreadFocus() +{ + if (_isShowMode) + { + Show(TRUE); + } + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// OnKillThreadFocus +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateListUIPresenter::OnKillThreadFocus() +{ + if (_isShowMode) + { + Show(FALSE); + } + return S_OK; +} + +void CCandidateListUIPresenter::RemoveSpecificCandidateFromList(_In_ LCID Locale, _Inout_ CSampleImeArray &candidateList, _In_ CStringRange &candidateString) +{ + for (UINT index = 0; index < candidateList.Count();) + { + CCandidateListItem* pLI = candidateList.GetAt(index); + + if (CStringRange::Compare(Locale, &candidateString, &pLI->_ItemString) == CSTR_EQUAL) + { + candidateList.RemoveAt(index); + continue; + } + + index++; + } +} + +void CCandidateListUIPresenter::AdviseUIChangedByArrowKey(_In_ KEYSTROKE_FUNCTION arrowKey) +{ + switch (arrowKey) + { + case FUNCTION_MOVE_UP: + { + _MoveSelection(MOVEUP_ONE); + break; + } + case FUNCTION_MOVE_DOWN: + { + _MoveSelection(MOVEDOWN_ONE); + break; + } + case FUNCTION_MOVE_PAGE_UP: + { + _MovePage(MOVEUP_ONE); + break; + } + case FUNCTION_MOVE_PAGE_DOWN: + { + _MovePage(MOVEDOWN_ONE); + break; + } + case FUNCTION_MOVE_PAGE_TOP: + { + _SetSelection(MOVETO_TOP); + break; + } + case FUNCTION_MOVE_PAGE_BOTTOM: + { + _SetSelection(MOVETO_BOTTOM); + break; + } + default: + break; + } +} + +HRESULT CCandidateListUIPresenter::BeginUIElement() +{ + HRESULT hr = S_OK; + + ITfThreadMgr* pThreadMgr = _pTextService->_GetThreadMgr(); + if (nullptr ==pThreadMgr) + { + hr = E_FAIL; + goto Exit; + } + + ITfUIElementMgr* pUIElementMgr = nullptr; + hr = pThreadMgr->QueryInterface(IID_ITfUIElementMgr, (void **)&pUIElementMgr); + if (hr == S_OK) + { + pUIElementMgr->BeginUIElement(this, &_isShowMode, &_uiElementId); + pUIElementMgr->Release(); + } + +Exit: + return hr; +} + +HRESULT CCandidateListUIPresenter::EndUIElement() +{ + HRESULT hr = S_OK; + + ITfThreadMgr* pThreadMgr = _pTextService->_GetThreadMgr(); + if ((nullptr == pThreadMgr) || (-1 == _uiElementId)) + { + hr = E_FAIL; + goto Exit; + } + + ITfUIElementMgr* pUIElementMgr = nullptr; + hr = pThreadMgr->QueryInterface(IID_ITfUIElementMgr, (void **)&pUIElementMgr); + if (hr == S_OK) + { + pUIElementMgr->EndUIElement(_uiElementId); + pUIElementMgr->Release(); + } + +Exit: + return hr; +} + +HRESULT CCandidateListUIPresenter::MakeCandidateWindow(_In_ ITfContext *pContextDocument, _In_ UINT wndWidth) +{ + HRESULT hr = S_OK; + + if (nullptr != _pCandidateWnd) + { + return hr; + } + + _pCandidateWnd = new (std::nothrow) CCandidateWindow(_CandWndCallback, this, _pIndexRange, _pTextService->_IsStoreAppMode()); + if (_pCandidateWnd == nullptr) + { + hr = E_OUTOFMEMORY; + goto Exit; + } + + HWND parentWndHandle = nullptr; + ITfContextView* pView = nullptr; + if (SUCCEEDED(pContextDocument->GetActiveView(&pView))) + { + pView->GetWnd(&parentWndHandle); + } + + if (!_pCandidateWnd->_Create(_atom, wndWidth, parentWndHandle)) + { + hr = E_OUTOFMEMORY; + goto Exit; + } + +Exit: + return hr; +} + +void CCandidateListUIPresenter::DisposeCandidateWindow() +{ + if (nullptr == _pCandidateWnd) + { + return; + } + + _pCandidateWnd->_Destroy(); + + delete _pCandidateWnd; + _pCandidateWnd = nullptr; +} \ No newline at end of file diff --git a/CandidateListUIPresenter.h b/CandidateListUIPresenter.h new file mode 100644 index 00000000..e618f944 --- /dev/null +++ b/CandidateListUIPresenter.h @@ -0,0 +1,139 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "KeyHandlerEditSession.h" +#include "CandidateWindow.h" +#include "TfTextLayoutSink.h" +#include "SampleIME.h" +#include "SampleIMEBaseStructure.h" + +class CReadingLine; + +//+--------------------------------------------------------------------------- +// +// CCandidateListUIPresenter +// +// ITfCandidateListUIElement / ITfIntegratableCandidateListUIElement is used for +// UILess mode support +// ITfCandidateListUIElementBehavior sends the Selection behavior message to +// 3rd party IME. +//---------------------------------------------------------------------------- + +class CCandidateListUIPresenter : public CTfTextLayoutSink, + public ITfCandidateListUIElementBehavior, + public ITfIntegratableCandidateListUIElement +{ +public: + CCandidateListUIPresenter(_In_ CSampleIME *pTextService, ATOM atom, + KEYSTROKE_CATEGORY Category, + _In_ CCandidateRange *pIndexRange, + BOOL hideWindow); + virtual ~CCandidateListUIPresenter(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfUIElement + STDMETHODIMP GetDescription(BSTR *pbstr); + STDMETHODIMP GetGUID(GUID *pguid); + STDMETHODIMP Show(BOOL showCandidateWindow); + STDMETHODIMP IsShown(BOOL *pIsShow); + + // ITfCandidateListUIElement + STDMETHODIMP GetUpdatedFlags(DWORD *pdwFlags); + STDMETHODIMP GetDocumentMgr(ITfDocumentMgr **ppdim); + STDMETHODIMP GetCount(UINT *pCandidateCount); + STDMETHODIMP GetSelection(UINT *pSelectedCandidateIndex); + STDMETHODIMP GetString(UINT uIndex, BSTR *pbstr); + STDMETHODIMP GetPageIndex(UINT *pIndex, UINT uSize, UINT *puPageCnt); + STDMETHODIMP SetPageIndex(UINT *pIndex, UINT uPageCnt); + STDMETHODIMP GetCurrentPage(UINT *puPage); + + // ITfCandidateListUIElementBehavior methods + STDMETHODIMP SetSelection(UINT nIndex); + STDMETHODIMP Finalize(void); + STDMETHODIMP Abort(void); + + // ITfIntegratableCandidateListUIElement + STDMETHODIMP SetIntegrationStyle(GUID guidIntegrationStyle); + STDMETHODIMP GetSelectionStyle(_Out_ TfIntegratableCandidateListSelectionStyle *ptfSelectionStyle); + STDMETHODIMP OnKeyDown(_In_ WPARAM wParam, _In_ LPARAM lParam, _Out_ BOOL *pIsEaten); + STDMETHODIMP ShowCandidateNumbers(_Out_ BOOL *pIsShow); + STDMETHODIMP FinalizeExactCompositionString(); + + virtual HRESULT _StartCandidateList(TfClientId tfClientId, _In_ ITfDocumentMgr *pDocumentMgr, _In_ ITfContext *pContextDocument, TfEditCookie ec, _In_ ITfRange *pRangeComposition, UINT wndWidth); + void _EndCandidateList(); + + void _SetText(_In_ CSampleImeArray *pCandidateList, BOOL isAddFindKeyCode); + void _ClearList(); + VOID _SetTextColor(COLORREF crColor, COLORREF crBkColor); + VOID _SetFillColor(HBRUSH hBrush); + + DWORD_PTR _GetSelectedCandidateString(_Outptr_result_maybenull_ const WCHAR **ppwchCandidateString); + BOOL _SetSelectionInPage(int nPos) { return _pCandidateWnd->_SetSelectionInPage(nPos); } + + BOOL _MoveSelection(_In_ int offSet); + BOOL _SetSelection(_In_ int selectedIndex); + BOOL _MovePage(_In_ int offSet); + + void _MoveWindowToTextExt(); + + // CTfTextLayoutSink + virtual VOID _LayoutChangeNotification(_In_ RECT *lpRect); + virtual VOID _LayoutDestroyNotification(); + + // Event for ITfThreadFocusSink + virtual HRESULT OnSetThreadFocus(); + virtual HRESULT OnKillThreadFocus(); + + void RemoveSpecificCandidateFromList(_In_ LCID Locale, _Inout_ CSampleImeArray &candidateList, _In_ CStringRange &srgCandidateString); + void AdviseUIChangedByArrowKey(_In_ KEYSTROKE_FUNCTION arrowKey); + +private: + virtual HRESULT CALLBACK _CandidateChangeNotification(_In_ enum CANDWND_ACTION action); + + static HRESULT _CandWndCallback(_In_ void *pv, _In_ enum CANDWND_ACTION action); + + friend COLORREF _AdjustTextColor(_In_ COLORREF crColor, _In_ COLORREF crBkColor); + + HRESULT _UpdateUIElement(); + + HRESULT ToShowCandidateWindow(); + + HRESULT ToHideCandidateWindow(); + + HRESULT BeginUIElement(); + HRESULT EndUIElement(); + + HRESULT MakeCandidateWindow(_In_ ITfContext *pContextDocument, _In_ UINT wndWidth); + void DisposeCandidateWindow(); + + void AddCandidateToCandidateListUI(_In_ CSampleImeArray *pCandidateList, BOOL isAddFindKeyCode); + + void SetPageIndexWithScrollInfo(_In_ CSampleImeArray *pCandidateList); + +protected: + CCandidateWindow *_pCandidateWnd; + BOOL _isShowMode; + BOOL _hideWindow; + +private: + + HWND _parentWndHandle; + ATOM _atom; + CCandidateRange* _pIndexRange; + KEYSTROKE_CATEGORY _Category; + DWORD _updatedFlags; + DWORD _uiElementId; + CSampleIME* _pTextService; + LONG _refCount; +}; diff --git a/CandidateWindow.cpp b/CandidateWindow.cpp new file mode 100644 index 00000000..da58b13c --- /dev/null +++ b/CandidateWindow.cpp @@ -0,0 +1,1438 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "BaseWindow.h" +#include "CandidateWindow.h" + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CCandidateWindow::CCandidateWindow(_In_ CANDWNDCALLBACK pfnCallback, _In_ void *pv, _In_ CCandidateRange *pIndexRange, _In_ BOOL isStoreAppMode) +{ + _currentSelection = 0; + + _SetTextColor(CANDWND_ITEM_COLOR, GetSysColor(COLOR_WINDOW)); // text color is black + _SetFillColor((HBRUSH)(COLOR_WINDOW+1)); + + _pIndexRange = pIndexRange; + + _pfnCallback = pfnCallback; + _pObj = pv; + + _pShadowWnd = nullptr; + + _cyRow = CANDWND_ROW_WIDTH; + _cxTitle = 0; + + _pVScrollBarWnd = nullptr; + + _wndWidth = 0; + + _dontAdjustOnEmptyItemPage = FALSE; + + _isStoreAppMode = isStoreAppMode; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CCandidateWindow::~CCandidateWindow() +{ + _ClearList(); + _DeleteShadowWnd(); + _DeleteVScrollBarWnd(); +} + +//+--------------------------------------------------------------------------- +// +// _Create +// +// CandidateWinow is the top window +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_Create(ATOM atom, _In_ UINT wndWidth, _In_opt_ HWND parentWndHandle) +{ + BOOL ret = FALSE; + _wndWidth = wndWidth; + + ret = _CreateMainWindow(atom, parentWndHandle); + if (FALSE == ret) + { + goto Exit; + } + + ret = _CreateBackGroundShadowWindow(); + if (FALSE == ret) + { + goto Exit; + } + + ret = _CreateVScrollWindow(); + if (FALSE == ret) + { + goto Exit; + } + + _ResizeWindow(); + +Exit: + return TRUE; +} + +BOOL CCandidateWindow::_CreateMainWindow(ATOM atom, _In_opt_ HWND parentWndHandle) +{ + _SetUIWnd(this); + + if (!CBaseWindow::_Create(atom, + WS_EX_TOPMOST | WS_EX_TOOLWINDOW, + WS_BORDER | WS_POPUP, + NULL, 0, 0, parentWndHandle)) + { + return FALSE; + } + + return TRUE; +} + +BOOL CCandidateWindow::_CreateBackGroundShadowWindow() +{ + _pShadowWnd = new (std::nothrow) CShadowWindow(this); + if (_pShadowWnd == nullptr) + { + return FALSE; + } + + if (!_pShadowWnd->_Create(Global::AtomShadowWindow, + WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_LAYERED, + WS_DISABLED | WS_POPUP, this)) + { + _DeleteShadowWnd(); + return FALSE; + } + + return TRUE; +} + +BOOL CCandidateWindow::_CreateVScrollWindow() +{ + BOOL ret = FALSE; + + SHELL_MODE shellMode = _isStoreAppMode ? STOREAPP : DESKTOP; + CScrollBarWindowFactory* pFactory = CScrollBarWindowFactory::Instance(); + _pVScrollBarWnd = pFactory->MakeScrollBarWindow(shellMode); + + if (_pVScrollBarWnd == nullptr) + { + _DeleteShadowWnd(); + goto Exit; + } + + _pVScrollBarWnd->_SetUIWnd(this); + + if (!_pVScrollBarWnd->_Create(Global::AtomScrollBarWindow, WS_EX_TOPMOST | WS_EX_TOOLWINDOW, WS_CHILD, this)) + { + _DeleteVScrollBarWnd(); + _DeleteShadowWnd(); + goto Exit; + } + + ret = TRUE; + +Exit: + pFactory->Release(); + return ret; +} + +void CCandidateWindow::_ResizeWindow() +{ + SIZE size = {0, 0}; + + _cxTitle = max(_cxTitle, size.cx + 2 * GetSystemMetrics(SM_CXFRAME)); + + int candidateListPageCnt = _pIndexRange->Count(); + CBaseWindow::_Resize(0, 0, _cxTitle, _cyRow * candidateListPageCnt); + + RECT rcCandRect = {0, 0, 0, 0}; + _GetClientRect(&rcCandRect); + + int letf = rcCandRect.right - GetSystemMetrics(SM_CXVSCROLL) * 2 - CANDWND_BORDER_WIDTH; + int top = rcCandRect.top + CANDWND_BORDER_WIDTH; + int width = GetSystemMetrics(SM_CXVSCROLL) * 2; + int height = rcCandRect.bottom - rcCandRect.top - CANDWND_BORDER_WIDTH * 2; + + _pVScrollBarWnd->_Resize(letf, top, width, height); +} + +//+--------------------------------------------------------------------------- +// +// _Move +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_Move(int x, int y) +{ + CBaseWindow::_Move(x, y); +} + +//+--------------------------------------------------------------------------- +// +// _Show +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_Show(BOOL isShowWnd) +{ + if (_pShadowWnd) + { + _pShadowWnd->_Show(isShowWnd); + } + CBaseWindow::_Show(isShowWnd); +} + +//+--------------------------------------------------------------------------- +// +// _SetTextColor +// _SetFillColor +// +//---------------------------------------------------------------------------- + +VOID CCandidateWindow::_SetTextColor(_In_ COLORREF crColor, _In_ COLORREF crBkColor) +{ + _crTextColor = _AdjustTextColor(crColor, crBkColor); + _crBkColor = crBkColor; +} + +VOID CCandidateWindow::_SetFillColor(_In_ HBRUSH hBrush) +{ + _brshBkColor = hBrush; +} + +//+--------------------------------------------------------------------------- +// +// _WindowProcCallback +// +// Cand window proc. +//---------------------------------------------------------------------------- + +const int PageCountPosition = 1; +const int StringPosition = 4; + +LRESULT CALLBACK CCandidateWindow::_WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + switch (uMsg) + { + case WM_CREATE: + { + HDC dcHandle = nullptr; + + dcHandle = GetDC(wndHandle); + if (dcHandle) + { + HFONT hFontOld = (HFONT)SelectObject(dcHandle, Global::defaultlFontHandle); + GetTextMetrics(dcHandle, &_TextMetric); + + _cxTitle = _TextMetric.tmMaxCharWidth * _wndWidth; + SelectObject(dcHandle, hFontOld); + ReleaseDC(wndHandle, dcHandle); + } + } + return 0; + + case WM_DESTROY: + _DeleteShadowWnd(); + return 0; + + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS* pWndPos = (WINDOWPOS*)lParam; + + // move shadow + if (_pShadowWnd) + { + _pShadowWnd->_OnOwnerWndMoved((pWndPos->flags & SWP_NOSIZE) == 0); + } + + // move v-scroll + if (_pVScrollBarWnd) + { + _pVScrollBarWnd->_OnOwnerWndMoved((pWndPos->flags & SWP_NOSIZE) == 0); + } + + _FireMessageToLightDismiss(wndHandle, pWndPos); + } + break; + + case WM_WINDOWPOSCHANGING: + { + WINDOWPOS* pWndPos = (WINDOWPOS*)lParam; + + // show/hide shadow + if (_pShadowWnd) + { + if ((pWndPos->flags & SWP_HIDEWINDOW) != 0) + { + _pShadowWnd->_Show(FALSE); + } + + // don't go behaind of shadow + if (((pWndPos->flags & SWP_NOZORDER) == 0) && (pWndPos->hwndInsertAfter == _pShadowWnd->_GetWnd())) + { + pWndPos->flags |= SWP_NOZORDER; + } + + _pShadowWnd->_OnOwnerWndMoved((pWndPos->flags & SWP_NOSIZE) == 0); + } + + // show/hide v-scroll + if (_pVScrollBarWnd) + { + if ((pWndPos->flags & SWP_HIDEWINDOW) != 0) + { + _pVScrollBarWnd->_Show(FALSE); + } + + _pVScrollBarWnd->_OnOwnerWndMoved((pWndPos->flags & SWP_NOSIZE) == 0); + } + } + break; + + case WM_SHOWWINDOW: + // show/hide shadow + if (_pShadowWnd) + { + _pShadowWnd->_Show((BOOL)wParam); + } + + // show/hide v-scroll + if (_pVScrollBarWnd) + { + _pVScrollBarWnd->_Show((BOOL)wParam); + } + break; + + case WM_PAINT: + { + HDC dcHandle = nullptr; + PAINTSTRUCT ps; + + dcHandle = BeginPaint(wndHandle, &ps); + _OnPaint(dcHandle, &ps); + _DrawBorder(wndHandle, CANDWND_BORDER_WIDTH*2); + EndPaint(wndHandle, &ps); + } + return 0; + + case WM_SETCURSOR: + { + POINT cursorPoint; + + GetCursorPos(&cursorPoint); + MapWindowPoints(NULL, wndHandle, &cursorPoint, 1); + + // handle mouse message + _HandleMouseMsg(HIWORD(lParam), cursorPoint); + } + return 1; + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + { + POINT point; + + POINTSTOPOINT(point, MAKEPOINTS(lParam)); + + // handle mouse message + _HandleMouseMsg(uMsg, point); + } + // we processes this message, it should return zero. + return 0; + + case WM_MOUSEACTIVATE: + { + WORD mouseEvent = HIWORD(lParam); + if (mouseEvent == WM_LBUTTONDOWN || + mouseEvent == WM_RBUTTONDOWN || + mouseEvent == WM_MBUTTONDOWN) + { + return MA_NOACTIVATE; + } + } + break; + + case WM_POINTERACTIVATE: + return PA_NOACTIVATE; + + case WM_VSCROLL: + _OnVScroll(LOWORD(wParam), HIWORD(wParam)); + return 0; + } + + return DefWindowProc(wndHandle, uMsg, wParam, lParam); +} + +//+--------------------------------------------------------------------------- +// +// _HandleMouseMsg +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_HandleMouseMsg(_In_ UINT mouseMsg, _In_ POINT point) +{ + switch (mouseMsg) + { + case WM_MOUSEMOVE: + _OnMouseMove(point); + break; + case WM_LBUTTONDOWN: + _OnLButtonDown(point); + break; + case WM_LBUTTONUP: + _OnLButtonUp(point); + break; + } +} + +//+--------------------------------------------------------------------------- +// +// _OnPaint +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pPaintStruct) +{ + SetBkMode(dcHandle, TRANSPARENT); + + HFONT hFontOld = (HFONT)SelectObject(dcHandle, Global::defaultlFontHandle); + + FillRect(dcHandle, &pPaintStruct->rcPaint, _brshBkColor); + + UINT currentPageIndex = 0; + UINT currentPage = 0; + + if (FAILED(_GetCurrentPage(¤tPage))) + { + goto cleanup; + } + + _AdjustPageIndex(currentPage, currentPageIndex); + + _DrawList(dcHandle, currentPageIndex, &pPaintStruct->rcPaint); + +cleanup: + SelectObject(dcHandle, hFontOld); +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonDown +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_OnLButtonDown(POINT pt) +{ + RECT rcWindow = {0, 0, 0, 0};; + _GetClientRect(&rcWindow); + + int cyLine = _cyRow; + + UINT candidateListPageCnt = _pIndexRange->Count(); + UINT index = 0; + int currentPage = 0; + + if (FAILED(_GetCurrentPage(¤tPage))) + { + return; + } + + // Hit test on list items + index = *_PageIndex.GetAt(currentPage); + + for (UINT pageCount = 0; (index < _candidateList.Count()) && (pageCount < candidateListPageCnt); index++, pageCount++) + { + RECT rc = {0, 0, 0, 0}; + + rc.left = rcWindow.left; + rc.right = rcWindow.right - GetSystemMetrics(SM_CXVSCROLL) * 2; + rc.top = rcWindow.top + (pageCount * cyLine); + rc.bottom = rcWindow.top + ((pageCount + 1) * cyLine); + + if (PtInRect(&rc, pt) && _pfnCallback) + { + SetCursor(LoadCursor(NULL, IDC_HAND)); + _currentSelection = index; + _pfnCallback(_pObj, CAND_ITEM_SELECT); + return; + } + } + + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + if (_pVScrollBarWnd) + { + RECT rc = {0, 0, 0, 0}; + + _pVScrollBarWnd->_GetClientRect(&rc); + MapWindowPoints(_GetWnd(), _pVScrollBarWnd->_GetWnd(), &pt, 1); + + if (PtInRect(&rc, pt)) + { + _pVScrollBarWnd->_OnLButtonDown(pt); + } + } +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonUp +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_OnLButtonUp(POINT pt) +{ + if (nullptr == _pVScrollBarWnd) + { + return; + } + + RECT rc = {0, 0, 0, 0}; + _pVScrollBarWnd->_GetClientRect(&rc); + MapWindowPoints(_GetWnd(), _pVScrollBarWnd->_GetWnd(), &pt, 1); + + if (_IsCapture()) + { + _pVScrollBarWnd->_OnLButtonUp(pt); + } + else if (PtInRect(&rc, pt)) + { + _pVScrollBarWnd->_OnLButtonUp(pt); + } +} + +//+--------------------------------------------------------------------------- +// +// _OnMouseMove +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_OnMouseMove(POINT pt) +{ + RECT rcWindow = {0, 0, 0, 0}; + + _GetClientRect(&rcWindow); + + RECT rc = {0, 0, 0, 0}; + + rc.left = rcWindow.left; + rc.right = rcWindow.right - GetSystemMetrics(SM_CXVSCROLL) * 2; + + rc.top = rcWindow.top; + rc.bottom = rcWindow.bottom; + + if (PtInRect(&rc, pt)) + { + SetCursor(LoadCursor(NULL, IDC_HAND)); + return; + } + + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + if (_pVScrollBarWnd) + { + _pVScrollBarWnd->_GetClientRect(&rc); + MapWindowPoints(_GetWnd(), _pVScrollBarWnd->_GetWnd(), &pt, 1); + + if (PtInRect(&rc, pt)) + { + _pVScrollBarWnd->_OnMouseMove(pt); + } + } +} + +//+--------------------------------------------------------------------------- +// +// _OnVScroll +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_OnVScroll(DWORD dwSB, _In_ DWORD nPos) +{ + switch (dwSB) + { + case SB_LINEDOWN: + _SetSelectionOffset(+1); + _InvalidateRect(); + break; + case SB_LINEUP: + _SetSelectionOffset(-1); + _InvalidateRect(); + break; + case SB_PAGEDOWN: + _MovePage(+1, FALSE); + _InvalidateRect(); + break; + case SB_PAGEUP: + _MovePage(-1, FALSE); + _InvalidateRect(); + break; + case SB_THUMBPOSITION: + _SetSelection(nPos, FALSE); + _InvalidateRect(); + break; + } +} + +//+--------------------------------------------------------------------------- +// +// _DrawList +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_DrawList(_In_ HDC dcHandle, _In_ UINT iIndex, _In_ RECT *prc) +{ + int pageCount = 0; + int candidateListPageCnt = _pIndexRange->Count(); + + int cxLine = _TextMetric.tmAveCharWidth; + int cyLine = max(_cyRow, _TextMetric.tmHeight); + int cyOffset = (cyLine == _cyRow ? (cyLine-_TextMetric.tmHeight)/2 : 0); + + RECT rc; + + const size_t lenOfPageCount = 16; + for (; + (iIndex < _candidateList.Count()) && (pageCount < candidateListPageCnt); + iIndex++, pageCount++) + { + WCHAR pageCountString[lenOfPageCount] = {'\0'}; + CCandidateListItem* pItemList = nullptr; + + rc.top = prc->top + pageCount * cyLine; + rc.bottom = rc.top + cyLine; + + rc.left = prc->left + PageCountPosition * cxLine; + rc.right = prc->left + StringPosition * cxLine; + + // Number Font Color And BK + SetTextColor(dcHandle, CANDWND_NUM_COLOR); + SetBkColor(dcHandle, GetSysColor(COLOR_3DHIGHLIGHT)); + + StringCchPrintf(pageCountString, ARRAYSIZE(pageCountString), L"%d", (LONG)*_pIndexRange->GetAt(pageCount)); + ExtTextOut(dcHandle, PageCountPosition * cxLine, pageCount * cyLine + cyOffset, ETO_OPAQUE, &rc, pageCountString, lenOfPageCount, NULL); + + rc.left = prc->left + StringPosition * cxLine; + rc.right = prc->right; + + // Candidate Font Color And BK + if (_currentSelection != iIndex) + { + SetTextColor(dcHandle, _crTextColor); + SetBkColor(dcHandle, GetSysColor(COLOR_3DHIGHLIGHT)); + } + else + { + SetTextColor(dcHandle, CANDWND_SELECTED_ITEM_COLOR); + SetBkColor(dcHandle, CANDWND_SELECTED_BK_COLOR); + } + + pItemList = _candidateList.GetAt(iIndex); + ExtTextOut(dcHandle, StringPosition * cxLine, pageCount * cyLine + cyOffset, ETO_OPAQUE, &rc, pItemList->_ItemString.Get(), (DWORD)pItemList->_ItemString.GetLength(), NULL); + } + for (; (pageCount < candidateListPageCnt); pageCount++) + { + rc.top = prc->top + pageCount * cyLine; + rc.bottom = rc.top + cyLine; + + rc.left = prc->left + PageCountPosition * cxLine; + rc.right = prc->left + StringPosition * cxLine; + + FillRect(dcHandle, &rc, (HBRUSH)(COLOR_3DHIGHLIGHT+1)); + } +} + +//+--------------------------------------------------------------------------- +// +// _DrawBorder +// +//---------------------------------------------------------------------------- +void CCandidateWindow::_DrawBorder(_In_ HWND wndHandle, _In_ int cx) +{ + RECT rcWnd; + + HDC dcHandle = GetWindowDC(wndHandle); + + GetWindowRect(wndHandle, &rcWnd); + // zero based + OffsetRect(&rcWnd, -rcWnd.left, -rcWnd.top); + + HPEN hPen = CreatePen(PS_DOT, cx, CANDWND_BORDER_COLOR); + HPEN hPenOld = (HPEN)SelectObject(dcHandle, hPen); + HBRUSH hBorderBrush = (HBRUSH)GetStockObject(NULL_BRUSH); + HBRUSH hBorderBrushOld = (HBRUSH)SelectObject(dcHandle, hBorderBrush); + + Rectangle(dcHandle, rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom); + + SelectObject(dcHandle, hPenOld); + SelectObject(dcHandle, hBorderBrushOld); + DeleteObject(hPen); + DeleteObject(hBorderBrush); + ReleaseDC(wndHandle, dcHandle); + +} + +//+--------------------------------------------------------------------------- +// +// _AddString +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_AddString(_Inout_ CCandidateListItem *pCandidateItem, _In_ BOOL isAddFindKeyCode) +{ + DWORD_PTR dwItemString = pCandidateItem->_ItemString.GetLength(); + const WCHAR* pwchString = nullptr; + if (dwItemString) + { + pwchString = new (std::nothrow) WCHAR[ dwItemString ]; + if (!pwchString) + { + return; + } + memcpy((void*)pwchString, pCandidateItem->_ItemString.Get(), dwItemString * sizeof(WCHAR)); + } + + DWORD_PTR itemWildcard = pCandidateItem->_FindKeyCode.GetLength(); + const WCHAR* pwchWildcard = nullptr; + if (itemWildcard && isAddFindKeyCode) + { + pwchWildcard = new (std::nothrow) WCHAR[ itemWildcard ]; + if (!pwchWildcard) + { + if (pwchString) + { + delete [] pwchString; + } + return; + } + memcpy((void*)pwchWildcard, pCandidateItem->_FindKeyCode.Get(), itemWildcard * sizeof(WCHAR)); + } + + CCandidateListItem* pLI = nullptr; + pLI = _candidateList.Append(); + if (!pLI) + { + if (pwchString) + { + delete [] pwchString; + pwchString = nullptr; + } + if (pwchWildcard) + { + delete [] pwchWildcard; + pwchWildcard = nullptr; + } + return; + } + + if (pwchString) + { + pLI->_ItemString.Set(pwchString, dwItemString); + } + if (pwchWildcard) + { + pLI->_FindKeyCode.Set(pwchWildcard, itemWildcard); + } + + return; +} + +//+--------------------------------------------------------------------------- +// +// _ClearList +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_ClearList() +{ + for (UINT index = 0; index < _candidateList.Count(); index++) + { + CCandidateListItem* pItemList = nullptr; + pItemList = _candidateList.GetAt(index); + delete [] pItemList->_ItemString.Get(); + delete [] pItemList->_FindKeyCode.Get(); + } + _currentSelection = 0; + _candidateList.Clear(); + _PageIndex.Clear(); +} + +//+--------------------------------------------------------------------------- +// +// _SetScrollInfo +// +//---------------------------------------------------------------------------- + +void CCandidateWindow::_SetScrollInfo(_In_ int nMax, _In_ int nPage) +{ + CScrollInfo si; + si.nMax = nMax; + si.nPage = nPage; + si.nPos = 0; + + if (_pVScrollBarWnd) + { + _pVScrollBarWnd->_SetScrollInfo(&si); + } +} + +//+--------------------------------------------------------------------------- +// +// _GetCandidateString +// +//---------------------------------------------------------------------------- + +DWORD CCandidateWindow::_GetCandidateString(_In_ int iIndex, _Outptr_result_maybenull_z_ const WCHAR **ppwchCandidateString) +{ + CCandidateListItem* pItemList = nullptr; + + if (iIndex < 0 ) + { + *ppwchCandidateString = nullptr; + return 0; + } + + UINT index = static_cast(iIndex); + + if (index >= _candidateList.Count()) + { + *ppwchCandidateString = nullptr; + return 0; + } + + pItemList = _candidateList.GetAt(iIndex); + if (ppwchCandidateString) + { + *ppwchCandidateString = pItemList->_ItemString.Get(); + } + return (DWORD)pItemList->_ItemString.GetLength(); +} + +//+--------------------------------------------------------------------------- +// +// _GetSelectedCandidateString +// +//---------------------------------------------------------------------------- + +DWORD CCandidateWindow::_GetSelectedCandidateString(_Outptr_result_maybenull_ const WCHAR **ppwchCandidateString) +{ + CCandidateListItem* pItemList = nullptr; + + if (_currentSelection >= _candidateList.Count()) + { + *ppwchCandidateString = nullptr; + return 0; + } + + pItemList = _candidateList.GetAt(_currentSelection); + if (ppwchCandidateString) + { + *ppwchCandidateString = pItemList->_ItemString.Get(); + } + return (DWORD)pItemList->_ItemString.GetLength(); +} + +//+--------------------------------------------------------------------------- +// +// _SetSelectionInPage +// +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_SetSelectionInPage(int nPos) +{ + if (nPos < 0) + { + return FALSE; + } + + UINT pos = static_cast(nPos); + + if (pos >= _candidateList.Count()) + { + return FALSE; + } + + int currentPage = 0; + if (FAILED(_GetCurrentPage(¤tPage))) + { + return FALSE; + } + + _currentSelection = *_PageIndex.GetAt(currentPage) + nPos; + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _MoveSelection +// +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_MoveSelection(_In_ int offSet, _In_ BOOL isNotify) +{ + if (_currentSelection + offSet >= _candidateList.Count()) + { + return FALSE; + } + + _currentSelection += offSet; + + _dontAdjustOnEmptyItemPage = TRUE; + + if (_pVScrollBarWnd && isNotify) + { + _pVScrollBarWnd->_ShiftLine(offSet, isNotify); + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _SetSelection +// +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_SetSelection(_In_ int selectedIndex, _In_ BOOL isNotify) +{ + if (selectedIndex == -1) + { + selectedIndex = _candidateList.Count() - 1; + } + + if (selectedIndex < 0) + { + return FALSE; + } + + int candCnt = static_cast(_candidateList.Count()); + if (selectedIndex >= candCnt) + { + return FALSE; + } + + _currentSelection = static_cast(selectedIndex); + + BOOL ret = _AdjustPageIndexForSelection(); + + if (_pVScrollBarWnd && isNotify) + { + _pVScrollBarWnd->_ShiftPosition(selectedIndex, isNotify); + } + + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _SetSelection +// +//---------------------------------------------------------------------------- +void CCandidateWindow::_SetSelection(_In_ int nIndex) +{ + _currentSelection = nIndex; +} + +//+--------------------------------------------------------------------------- +// +// _MovePage +// +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_MovePage(_In_ int offSet, _In_ BOOL isNotify) +{ + if (offSet == 0) + { + return TRUE; + } + + int currentPage = 0; + int selectionOffset = 0; + int newPage = 0; + + if (FAILED(_GetCurrentPage(¤tPage))) + { + return FALSE; + } + + newPage = currentPage + offSet; + if ((newPage < 0) || (newPage >= static_cast(_PageIndex.Count()))) + { + return FALSE; + } + + // If current selection is at the top of the page AND + // we are on the "default" page border, then we don't + // want adjustment to eliminate empty entries. + // + // We do this for keeping behavior inline with downlevel. + if (_currentSelection % _pIndexRange->Count() == 0 && + _currentSelection == *_PageIndex.GetAt(currentPage)) + { + _dontAdjustOnEmptyItemPage = TRUE; + } + + selectionOffset = _currentSelection - *_PageIndex.GetAt(currentPage); + _currentSelection = *_PageIndex.GetAt(newPage) + selectionOffset; + _currentSelection = _candidateList.Count() > _currentSelection ? _currentSelection : _candidateList.Count() - 1; + + // adjust scrollbar position + if (_pVScrollBarWnd && isNotify) + { + _pVScrollBarWnd->_ShiftPage(offSet, isNotify); + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _SetSelectionOffset +// +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_SetSelectionOffset(_In_ int offSet) +{ + if (_currentSelection + offSet >= _candidateList.Count()) + { + return FALSE; + } + + BOOL fCurrentPageHasEmptyItems = FALSE; + BOOL fAdjustPageIndex = TRUE; + + _CurrentPageHasEmptyItems(&fCurrentPageHasEmptyItems); + + int newOffset = _currentSelection + offSet; + + // For SB_LINEUP and SB_LINEDOWN, we need to special case if CurrentPageHasEmptyItems. + // CurrentPageHasEmptyItems if we are on the last page. + if ((offSet == 1 || offSet == -1) && + fCurrentPageHasEmptyItems && _PageIndex.Count() > 1) + { + int iPageIndex = *_PageIndex.GetAt(_PageIndex.Count() - 1); + // Moving on the last page and last page has empty items. + if (newOffset >= iPageIndex) + { + fAdjustPageIndex = FALSE; + } + // Moving across page border. + else if (newOffset < iPageIndex) + { + fAdjustPageIndex = TRUE; + } + + _dontAdjustOnEmptyItemPage = TRUE; + } + + _currentSelection = newOffset; + + if (fAdjustPageIndex) + { + return _AdjustPageIndexForSelection(); + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _GetPageIndex +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateWindow::_GetPageIndex(UINT *pIndex, _In_ UINT uSize, _Inout_ UINT *puPageCnt) +{ + HRESULT hr = S_OK; + + if (uSize > _PageIndex.Count()) + { + uSize = _PageIndex.Count(); + } + else + { + hr = S_FALSE; + } + + if (pIndex) + { + for (UINT i = 0; i < uSize; i++) + { + *pIndex = *_PageIndex.GetAt(i); + pIndex++; + } + } + + *puPageCnt = _PageIndex.Count(); + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _SetPageIndex +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateWindow::_SetPageIndex(UINT *pIndex, _In_ UINT uPageCnt) +{ + uPageCnt; + + _PageIndex.Clear(); + + for (UINT i = 0; i < uPageCnt; i++) + { + UINT *pLastNewPageIndex = _PageIndex.Append(); + if (pLastNewPageIndex != nullptr) + { + *pLastNewPageIndex = *pIndex; + pIndex++; + } + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _GetCurrentPage +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateWindow::_GetCurrentPage(_Inout_ UINT *pCurrentPage) +{ + HRESULT hr = S_OK; + + if (pCurrentPage == nullptr) + { + hr = E_INVALIDARG; + goto Exit; + } + + *pCurrentPage = 0; + + if (_PageIndex.Count() == 0) + { + hr = E_UNEXPECTED; + goto Exit; + } + + if (_PageIndex.Count() == 1) + { + *pCurrentPage = 0; + goto Exit; + } + + UINT i = 0; + for (i = 1; i < _PageIndex.Count(); i++) + { + UINT uPageIndex = *_PageIndex.GetAt(i); + + if (uPageIndex > _currentSelection) + { + break; + } + } + + *pCurrentPage = i - 1; + +Exit: + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _GetCurrentPage +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateWindow::_GetCurrentPage(_Inout_ int *pCurrentPage) +{ + HRESULT hr = E_FAIL; + UINT needCastCurrentPage = 0; + + if (nullptr == pCurrentPage) + { + goto Exit; + } + + *pCurrentPage = 0; + + hr = _GetCurrentPage(&needCastCurrentPage); + if (FAILED(hr)) + { + goto Exit; + } + + hr = UIntToInt(needCastCurrentPage, pCurrentPage); + if (FAILED(hr)) + { + goto Exit; + } + +Exit: + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _AdjustPageIndexForSelection +// +//---------------------------------------------------------------------------- + +BOOL CCandidateWindow::_AdjustPageIndexForSelection() +{ + UINT candidateListPageCnt = _pIndexRange->Count(); + UINT* pNewPageIndex = nullptr; + UINT newPageCnt = 0; + + if (_candidateList.Count() < candidateListPageCnt) + { + // no needed to restruct page index + return TRUE; + } + + // B is number of pages before the current page + // A is number of pages after the current page + // uNewPageCount is A + B + 1; + // A is (uItemsAfter - 1) / candidateListPageCnt + 1 -> + // (_CandidateListCount - _currentSelection - CandidateListPageCount - 1) / candidateListPageCnt + 1-> + // (_CandidateListCount - _currentSelection - 1) / candidateListPageCnt + // B is (uItemsBefore - 1) / candidateListPageCnt + 1 -> + // (_currentSelection - 1) / candidateListPageCnt + 1 + // A + B is (_CandidateListCount - 2) / candidateListPageCnt + 1 + + BOOL isBefore = _currentSelection; + BOOL isAfter = _candidateList.Count() > _currentSelection + candidateListPageCnt; + + // only have current page + if (!isBefore && !isAfter) + { + newPageCnt = 1; + } + // only have after pages; just count the total number of pages + else if (!isBefore && isAfter) + { + newPageCnt = (_candidateList.Count() - 1) / candidateListPageCnt + 1; + } + // we are at the last page + else if (isBefore && !isAfter) + { + newPageCnt = 2 + (_currentSelection - 1) / candidateListPageCnt; + } + else if (isBefore && isAfter) + { + newPageCnt = (_candidateList.Count() - 2) / candidateListPageCnt + 2; + } + + pNewPageIndex = new (std::nothrow) UINT[ newPageCnt ]; + if (pNewPageIndex == nullptr) + { + return FALSE; + } + pNewPageIndex[0] = 0; + UINT firstPage = _currentSelection % candidateListPageCnt; + if (firstPage && newPageCnt > 1) + { + pNewPageIndex[1] = firstPage; + } + + for (UINT i = firstPage ? 2 : 1; i < newPageCnt; ++i) + { + pNewPageIndex[i] = pNewPageIndex[i - 1] + candidateListPageCnt; + } + + _SetPageIndex(pNewPageIndex, newPageCnt); + + delete [] pNewPageIndex; + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _AdjustTextColor +// +//---------------------------------------------------------------------------- + +COLORREF _AdjustTextColor(_In_ COLORREF crColor, _In_ COLORREF crBkColor) +{ + if (!Global::IsTooSimilar(crColor, crBkColor)) + { + return crColor; + } + else + { + return crColor ^ RGB(255, 255, 255); + } +} + +//+--------------------------------------------------------------------------- +// +// _CurrentPageHasEmptyItems +// +//---------------------------------------------------------------------------- + +HRESULT CCandidateWindow::_CurrentPageHasEmptyItems(_Inout_ BOOL *hasEmptyItems) +{ + int candidateListPageCnt = _pIndexRange->Count(); + UINT currentPage = 0; + + if (FAILED(_GetCurrentPage(¤tPage))) + { + return S_FALSE; + } + + if ((currentPage == 0 || currentPage == _PageIndex.Count()-1) && + (_PageIndex.Count() > 0) && + (*_PageIndex.GetAt(currentPage) > (UINT)(_candidateList.Count() - candidateListPageCnt))) + { + *hasEmptyItems = TRUE; + } + else + { + *hasEmptyItems = FALSE; + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _FireMessageToLightDismiss +// fire EVENT_OBJECT_IME_xxx to let LightDismiss know about IME window. +//---------------------------------------------------------------------------- + +void CCandidateWindow::_FireMessageToLightDismiss(_In_ HWND wndHandle, _In_ WINDOWPOS *pWndPos) +{ + if (nullptr == pWndPos) + { + return; + } + + BOOL isShowWnd = ((pWndPos->flags & SWP_SHOWWINDOW) != 0); + BOOL isHide = ((pWndPos->flags & SWP_HIDEWINDOW) != 0); + BOOL needResize = ((pWndPos->flags & SWP_NOSIZE) == 0); + BOOL needMove = ((pWndPos->flags & SWP_NOMOVE) == 0); + BOOL needRedraw = ((pWndPos->flags & SWP_NOREDRAW) == 0); + + if (isShowWnd) + { + NotifyWinEvent(EVENT_OBJECT_IME_SHOW, wndHandle, OBJID_CLIENT, CHILDID_SELF); + } + else if (isHide) + { + NotifyWinEvent(EVENT_OBJECT_IME_HIDE, wndHandle, OBJID_CLIENT, CHILDID_SELF); + } + else if (needResize || needMove || needRedraw) + { + if (IsWindowVisible(wndHandle)) + { + NotifyWinEvent(EVENT_OBJECT_IME_CHANGE, wndHandle, OBJID_CLIENT, CHILDID_SELF); + } + } + +} + +HRESULT CCandidateWindow::_AdjustPageIndex(_Inout_ UINT & currentPage, _Inout_ UINT & currentPageIndex) +{ + HRESULT hr = E_FAIL; + UINT candidateListPageCnt = _pIndexRange->Count(); + + currentPageIndex = *_PageIndex.GetAt(currentPage); + + BOOL hasEmptyItems = FALSE; + if (FAILED(_CurrentPageHasEmptyItems(&hasEmptyItems))) + { + goto Exit; + } + + if (FALSE == hasEmptyItems) + { + goto Exit; + } + + if (TRUE == _dontAdjustOnEmptyItemPage) + { + goto Exit; + } + + UINT tempSelection = _currentSelection; + + // Last page + UINT candNum = _candidateList.Count(); + UINT pageNum = _PageIndex.Count(); + + if ((currentPageIndex > candNum - candidateListPageCnt) && (pageNum > 0) && (currentPage == (pageNum - 1))) + { + _currentSelection = candNum - candidateListPageCnt; + + _AdjustPageIndexForSelection(); + + _currentSelection = tempSelection; + + if (FAILED(_GetCurrentPage(¤tPage))) + { + goto Exit; + } + + currentPageIndex = *_PageIndex.GetAt(currentPage); + } + // First page + else if ((currentPageIndex < candidateListPageCnt) && (currentPage == 0)) + { + _currentSelection = 0; + + _AdjustPageIndexForSelection(); + + _currentSelection = tempSelection; + } + + _dontAdjustOnEmptyItemPage = FALSE; + hr = S_OK; + +Exit: + return hr; +} +void CCandidateWindow::_DeleteShadowWnd() +{ + if (nullptr != _pShadowWnd) + { + delete _pShadowWnd; + _pShadowWnd = nullptr; + } +} + +void CCandidateWindow::_DeleteVScrollBarWnd() +{ + if (nullptr != _pVScrollBarWnd) + { + delete _pVScrollBarWnd; + _pVScrollBarWnd = nullptr; + } +} \ No newline at end of file diff --git a/CandidateWindow.h b/CandidateWindow.h new file mode 100644 index 00000000..a7c8f274 --- /dev/null +++ b/CandidateWindow.h @@ -0,0 +1,118 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "private.h" +#include "BaseWindow.h" +#include "ShadowWindow.h" +#include "ScrollBarWindow.h" +#include "SampleIMEBaseStructure.h" + +enum CANDWND_ACTION +{ + CAND_ITEM_SELECT +}; + +typedef HRESULT (*CANDWNDCALLBACK)(void *pv, enum CANDWND_ACTION action); + +class CCandidateWindow : public CBaseWindow +{ +public: + CCandidateWindow(_In_ CANDWNDCALLBACK pfnCallback, _In_ void *pv, _In_ CCandidateRange *pIndexRange, _In_ BOOL isStoreAppMode); + virtual ~CCandidateWindow(); + + BOOL _Create(ATOM atom, _In_ UINT wndWidth, _In_opt_ HWND parentWndHandle); + + void _Move(int x, int y); + void _Show(BOOL isShowWnd); + + VOID _SetTextColor(_In_ COLORREF crColor, _In_ COLORREF crBkColor); + VOID _SetFillColor(_In_ HBRUSH hBrush); + + LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); + void _OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps); + void _OnLButtonDown(POINT pt); + void _OnLButtonUp(POINT pt); + void _OnMouseMove(POINT pt); + void _OnVScroll(DWORD dwSB, _In_ DWORD nPos); + + void _AddString(_Inout_ CCandidateListItem *pCandidateItem, _In_ BOOL isAddFindKeyCode); + void _ClearList(); + UINT _GetCount() + { + return _candidateList.Count(); + } + UINT _GetSelection() + { + return _currentSelection; + } + void _SetScrollInfo(_In_ int nMax, _In_ int nPage); + + DWORD _GetCandidateString(_In_ int iIndex, _Outptr_result_maybenull_z_ const WCHAR **ppwchCandidateString); + DWORD _GetSelectedCandidateString(_Outptr_result_maybenull_ const WCHAR **ppwchCandidateString); + + BOOL _MoveSelection(_In_ int offSet, _In_ BOOL isNotify); + BOOL _SetSelection(_In_ int iPage, _In_ BOOL isNotify); + void _SetSelection(_In_ int nIndex); + BOOL _MovePage(_In_ int offSet, _In_ BOOL isNotify); + BOOL _SetSelectionInPage(int nPos); + + HRESULT _GetPageIndex(UINT *pIndex, _In_ UINT uSize, _Inout_ UINT *puPageCnt); + HRESULT _SetPageIndex(UINT *pIndex, _In_ UINT uPageCnt); + HRESULT _GetCurrentPage(_Inout_ UINT *pCurrentPage); + HRESULT _GetCurrentPage(_Inout_ int *pCurrentPage); + +private: + void _HandleMouseMsg(_In_ UINT mouseMsg, _In_ POINT point); + void _DrawList(_In_ HDC dcHandle, _In_ UINT iIndex, _In_ RECT *prc); + void _DrawBorder(_In_ HWND wndHandle, _In_ int cx); + BOOL _SetSelectionOffset(_In_ int offSet); + BOOL _AdjustPageIndexForSelection(); + HRESULT _CurrentPageHasEmptyItems(_Inout_ BOOL *pfHasEmptyItems); + + // LightDismiss feature support, it will fire messages lightdismiss-related to the light dismiss layout. + void _FireMessageToLightDismiss(_In_ HWND wndHandle, _In_ WINDOWPOS *pWndPos); + + BOOL _CreateMainWindow(ATOM atom, _In_opt_ HWND parentWndHandle); + BOOL _CreateBackGroundShadowWindow(); + BOOL _CreateVScrollWindow(); + + HRESULT _AdjustPageIndex(_Inout_ UINT & currentPage, _Inout_ UINT & currentPageIndex); + + void _ResizeWindow(); + void _DeleteShadowWnd(); + void _DeleteVScrollBarWnd(); + + friend COLORREF _AdjustTextColor(_In_ COLORREF crColor, _In_ COLORREF crBkColor); + +private: + UINT _currentSelection; + CSampleImeArray _candidateList; + CSampleImeArray _PageIndex; + + COLORREF _crTextColor; + COLORREF _crBkColor; + HBRUSH _brshBkColor; + + TEXTMETRIC _TextMetric; + int _cyRow; + int _cxTitle; + UINT _wndWidth; + + CCandidateRange* _pIndexRange; + + CANDWNDCALLBACK _pfnCallback; + void* _pObj; + + CShadowWindow* _pShadowWnd; + CScrollBarWindow* _pVScrollBarWnd; + + BOOL _dontAdjustOnEmptyItemPage; + BOOL _isStoreAppMode; +}; diff --git a/Compartment.cpp b/Compartment.cpp new file mode 100644 index 00000000..46595cb1 --- /dev/null +++ b/Compartment.cpp @@ -0,0 +1,343 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Compartment.h" +#include "Globals.h" + +////////////////////////////////////////////////////////////////////// +// +// CCompartment +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// ctor +//---------------------------------------------------------------------------- + +CCompartment::CCompartment(_In_ IUnknown* punk, TfClientId tfClientId, _In_ REFGUID guidCompartment) +{ + _guidCompartment = guidCompartment; + + _punk = punk; + _punk->AddRef(); + + _tfClientId = tfClientId; +} + +//+--------------------------------------------------------------------------- +// dtor +//---------------------------------------------------------------------------- + +CCompartment::~CCompartment() +{ + _punk->Release(); +} + +//+--------------------------------------------------------------------------- +// _GetCompartment +//---------------------------------------------------------------------------- + +HRESULT CCompartment::_GetCompartment(_Outptr_ ITfCompartment **ppCompartment) +{ + HRESULT hr = S_OK; + ITfCompartmentMgr* pCompartmentMgr = nullptr; + + hr = _punk->QueryInterface(IID_ITfCompartmentMgr, (void **)&pCompartmentMgr); + if (SUCCEEDED(hr)) + { + hr = pCompartmentMgr->GetCompartment(_guidCompartment, ppCompartment); + pCompartmentMgr->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// _GetCompartmentBOOL +//---------------------------------------------------------------------------- + +HRESULT CCompartment::_GetCompartmentBOOL(_Out_ BOOL &flag) +{ + HRESULT hr = S_OK; + ITfCompartment* pCompartment = nullptr; + flag = FALSE; + + if ((hr = _GetCompartment(&pCompartment)) == S_OK) + { + VARIANT var; + if ((hr = pCompartment->GetValue(&var)) == S_OK) + { + if (var.vt == VT_I4) // Even VT_EMPTY, GetValue() can succeed + { + flag = (BOOL)var.lVal; + } + else + { + hr = S_FALSE; + } + } + pCompartment->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// _SetCompartmentBOOL +//---------------------------------------------------------------------------- + +HRESULT CCompartment::_SetCompartmentBOOL(_In_ BOOL flag) +{ + HRESULT hr = S_OK; + ITfCompartment* pCompartment = nullptr; + + hr = _GetCompartment(&pCompartment); + if (SUCCEEDED(hr)) + { + VARIANT var; + var.vt = VT_I4; + var.lVal = flag; + hr = pCompartment->SetValue(_tfClientId, &var); + pCompartment->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// _GetCompartmentDWORD +//---------------------------------------------------------------------------- + +HRESULT CCompartment::_GetCompartmentDWORD(_Out_ DWORD &dw) +{ + HRESULT hr = S_OK; + ITfCompartment* pCompartment = nullptr; + dw = 0; + + hr = _GetCompartment(&pCompartment); + if (SUCCEEDED(hr)) + { + VARIANT var; + if ((hr = pCompartment->GetValue(&var)) == S_OK) + { + if (var.vt == VT_I4) // Even VT_EMPTY, GetValue() can succeed + { + dw = (DWORD)var.lVal; + } + else + { + hr = S_FALSE; + } + } + pCompartment->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// _SetCompartmentDWORD +//---------------------------------------------------------------------------- + +HRESULT CCompartment::_SetCompartmentDWORD(_In_ DWORD dw) +{ + HRESULT hr = S_OK; + ITfCompartment* pCompartment = nullptr; + + hr = _GetCompartment(&pCompartment); + if (SUCCEEDED(hr)) + { + VARIANT var; + var.vt = VT_I4; + var.lVal = dw; + hr = pCompartment->SetValue(_tfClientId, &var); + pCompartment->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _ClearCompartment +// +//---------------------------------------------------------------------------- + +HRESULT CCompartment::_ClearCompartment() +{ + if (IsEqualGUID(_guidCompartment, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE)) + { + return S_FALSE; + } + + HRESULT hr = S_OK; + ITfCompartmentMgr* pCompartmentMgr = nullptr; + + if ((hr = _punk->QueryInterface(IID_ITfCompartmentMgr, (void **)&pCompartmentMgr)) == S_OK) + { + hr = pCompartmentMgr->ClearCompartment(_tfClientId, _guidCompartment); + pCompartmentMgr->Release(); + } + + return hr; +} + +////////////////////////////////////////////////////////////////////// +// +// CCompartmentEventSink +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// ctor +//---------------------------------------------------------------------------- + +CCompartmentEventSink::CCompartmentEventSink(_In_ CESCALLBACK pfnCallback, _In_ void *pv) +{ + _pfnCallback = pfnCallback; + _pv = pv; + _refCount = 1; +} + +//+--------------------------------------------------------------------------- +// dtor +//---------------------------------------------------------------------------- + +CCompartmentEventSink::~CCompartmentEventSink() +{ +} + +//+--------------------------------------------------------------------------- +// +// QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CCompartmentEventSink::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + return E_INVALIDARG; + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfCompartmentEventSink)) + { + *ppvObj = (CCompartmentEventSink *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + + +//+--------------------------------------------------------------------------- +// +// AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CCompartmentEventSink::AddRef() +{ + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CCompartmentEventSink::Release() +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// OnChange +// +//---------------------------------------------------------------------------- + +STDAPI CCompartmentEventSink::OnChange(_In_ REFGUID guidCompartment) +{ + return _pfnCallback(_pv, guidCompartment); +} + +//+--------------------------------------------------------------------------- +// +// _Advise +// +//---------------------------------------------------------------------------- + +HRESULT CCompartmentEventSink::_Advise(_In_ IUnknown *punk, _In_ REFGUID guidCompartment) +{ + HRESULT hr = S_OK; + ITfCompartmentMgr* pCompartmentMgr = nullptr; + ITfSource* pSource = nullptr; + + hr = punk->QueryInterface(IID_ITfCompartmentMgr, (void **)&pCompartmentMgr); + if (FAILED(hr)) + { + return hr; + } + + hr = pCompartmentMgr->GetCompartment(guidCompartment, &_pCompartment); + if (SUCCEEDED(hr)) + { + hr = _pCompartment->QueryInterface(IID_ITfSource, (void **)&pSource); + if (SUCCEEDED(hr)) + { + hr = pSource->AdviseSink(IID_ITfCompartmentEventSink, this, &_dwCookie); + pSource->Release(); + } + } + + pCompartmentMgr->Release(); + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _Unadvise +// +//---------------------------------------------------------------------------- + +HRESULT CCompartmentEventSink::_Unadvise() +{ + HRESULT hr = S_OK; + ITfSource* pSource = nullptr; + + hr = _pCompartment->QueryInterface(IID_ITfSource, (void **)&pSource); + if (SUCCEEDED(hr)) + { + hr = pSource->UnadviseSink(_dwCookie); + pSource->Release(); + } + + _pCompartment->Release(); + _pCompartment = nullptr; + _dwCookie = 0; + + return hr; +} diff --git a/Compartment.h b/Compartment.h new file mode 100644 index 00000000..9fde0698 --- /dev/null +++ b/Compartment.h @@ -0,0 +1,61 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +class CCompartment +{ +public: + CCompartment(_In_ IUnknown *punk, TfClientId tfClientId, _In_ REFGUID guidCompartment); + ~CCompartment(); + + HRESULT _GetCompartment(_Outptr_ ITfCompartment **ppCompartment); + HRESULT _GetCompartmentBOOL(_Out_ BOOL &flag); + HRESULT _SetCompartmentBOOL(_In_ BOOL flag); + HRESULT _GetCompartmentDWORD(_Out_ DWORD &dw); + HRESULT _SetCompartmentDWORD(_In_ DWORD dw); + HRESULT _ClearCompartment(); + + VOID _GetGUID(GUID *pguid) + { + *pguid = _guidCompartment; + } + +private: + GUID _guidCompartment; + IUnknown* _punk; + TfClientId _tfClientId; +}; + +typedef HRESULT (*CESCALLBACK)(void *pv, REFGUID guidCompartment); + +class CCompartmentEventSink : public ITfCompartmentEventSink +{ +public: + CCompartmentEventSink(_In_ CESCALLBACK pfnCallback, _In_ void *pv); + ~CCompartmentEventSink(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfCompartmentEventSink + STDMETHODIMP OnChange(_In_ REFGUID guid); + + // function + HRESULT _Advise(_In_ IUnknown *punk, _In_ REFGUID guidCompartment); + HRESULT _Unadvise(); + +private: + ITfCompartment *_pCompartment; + DWORD _dwCookie; + CESCALLBACK _pfnCallback; + void *_pv; + + LONG _refCount; +}; diff --git a/Composition.cpp b/Composition.cpp new file mode 100644 index 00000000..9b9ac253 --- /dev/null +++ b/Composition.cpp @@ -0,0 +1,377 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "SampleIME.h" +#include "CompositionProcessorEngine.h" + +//+--------------------------------------------------------------------------- +// +// ITfCompositionSink::OnCompositionTerminated +// +// Callback for ITfCompositionSink. The system calls this method whenever +// someone other than this service ends a composition. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnCompositionTerminated(TfEditCookie ecWrite, _In_ ITfComposition *pComposition) +{ + // Clear dummy composition + _RemoveDummyCompositionForComposing(ecWrite, pComposition); + + // Clear display attribute and end composition, _EndComposition will release composition for us + ITfContext* pContext = _pContext; + if (pContext) + { + pContext->AddRef(); + } + + _EndComposition(_pContext); + + _DeleteCandidateList(FALSE, pContext); + + if (pContext) + { + pContext->Release(); + pContext = nullptr; + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _IsComposing +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_IsComposing() +{ + return _pComposition != nullptr; +} + +//+--------------------------------------------------------------------------- +// +// _SetComposition +// +//---------------------------------------------------------------------------- + +void CSampleIME::_SetComposition(_In_ ITfComposition *pComposition) +{ + _pComposition = pComposition; +} + +//+--------------------------------------------------------------------------- +// +// _AddComposingAndChar +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_AddComposingAndChar(TfEditCookie ec, _In_ ITfContext *pContext, _In_ CStringRange *pstrAddString) +{ + HRESULT hr = S_OK; + + ULONG fetched = 0; + TF_SELECTION tfSelection; + + if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched) != S_OK || fetched == 0) + return S_FALSE; + + // + // make range start to selection + // + ITfRange* pAheadSelection = nullptr; + hr = pContext->GetStart(ec, &pAheadSelection); + if (SUCCEEDED(hr)) + { + hr = pAheadSelection->ShiftEndToRange(ec, tfSelection.range, TF_ANCHOR_START); + if (SUCCEEDED(hr)) + { + ITfRange* pRange = nullptr; + BOOL exist_composing = _FindComposingRange(ec, pContext, pAheadSelection, &pRange); + + _SetInputString(ec, pContext, pRange, pstrAddString, exist_composing); + + if (pRange) + { + pRange->Release(); + } + } + } + + tfSelection.range->Release(); + + if (pAheadSelection) + { + pAheadSelection->Release(); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _AddCharAndFinalize +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_AddCharAndFinalize(TfEditCookie ec, _In_ ITfContext *pContext, _In_ CStringRange *pstrAddString) +{ + HRESULT hr = E_FAIL; + + ULONG fetched = 0; + TF_SELECTION tfSelection; + + if ((hr = pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) != S_OK || fetched != 1) + return hr; + + // 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 + hr = tfSelection.range->SetText(ec, 0, pstrAddString->Get(), (LONG)pstrAddString->GetLength()); + if (hr == S_OK) + { + // 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); + } + + tfSelection.range->Release(); + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _FindComposingRange +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_FindComposingRange(TfEditCookie ec, _In_ ITfContext *pContext, _In_ ITfRange *pSelection, _Outptr_result_maybenull_ ITfRange **ppRange) +{ + if (ppRange == nullptr) + { + return FALSE; + } + + *ppRange = nullptr; + + // find GUID_PROP_COMPOSING + ITfProperty* pPropComp = nullptr; + IEnumTfRanges* enumComp = nullptr; + + HRESULT hr = pContext->GetProperty(GUID_PROP_COMPOSING, &pPropComp); + if (FAILED(hr) || pPropComp == nullptr) + { + return FALSE; + } + + hr = pPropComp->EnumRanges(ec, &enumComp, pSelection); + if (FAILED(hr) || enumComp == nullptr) + { + pPropComp->Release(); + return FALSE; + } + + BOOL isCompExist = FALSE; + VARIANT var; + ULONG fetched = 0; + + while (enumComp->Next(1, ppRange, &fetched) == S_OK && fetched == 1) + { + hr = pPropComp->GetValue(ec, *ppRange, &var); + if (hr == S_OK) + { + if (var.vt == VT_I4 && var.lVal != 0) + { + isCompExist = TRUE; + break; + } + } + (*ppRange)->Release(); + *ppRange = nullptr; + } + + pPropComp->Release(); + enumComp->Release(); + + return isCompExist; +} + +//+--------------------------------------------------------------------------- +// +// _SetInputString +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_SetInputString(TfEditCookie ec, _In_ ITfContext *pContext, _Out_opt_ ITfRange *pRange, _In_ CStringRange *pstrAddString, BOOL exist_composing) +{ + ITfRange* pRangeInsert = nullptr; + if (!exist_composing) + { + _InsertAtSelection(ec, pContext, pstrAddString, &pRangeInsert); + if (pRangeInsert == nullptr) + { + return S_OK; + } + pRange = pRangeInsert; + } + if (pRange != nullptr) + { + pRange->SetText(ec, 0, pstrAddString->Get(), (LONG)pstrAddString->GetLength()); + } + + _SetCompositionLanguage(ec, pContext); + + _SetCompositionDisplayAttributes(ec, pContext, _gaDisplayAttributeInput); + + // update the selection, we'll make it an insertion point just past + // the inserted text. + ITfRange* pSelection = nullptr; + TF_SELECTION sel; + + if ((pRange != nullptr) && (pRange->Clone(&pSelection) == S_OK)) + { + pSelection->Collapse(ec, TF_ANCHOR_END); + + sel.range = pSelection; + sel.style.ase = TF_AE_NONE; + sel.style.fInterimChar = FALSE; + pContext->SetSelection(ec, 1, &sel); + pSelection->Release(); + } + + if (pRangeInsert) + { + pRangeInsert->Release(); + } + + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _InsertAtSelection +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_InsertAtSelection(TfEditCookie ec, _In_ ITfContext *pContext, _In_ CStringRange *pstrAddString, _Outptr_ ITfRange **ppCompRange) +{ + ITfRange* rangeInsert = nullptr; + ITfInsertAtSelection* pias = nullptr; + HRESULT hr = S_OK; + + if (ppCompRange == nullptr) + { + hr = E_INVALIDARG; + goto Exit; + } + + *ppCompRange = nullptr; + + hr = pContext->QueryInterface(IID_ITfInsertAtSelection, (void **)&pias); + if (FAILED(hr)) + { + goto Exit; + } + + hr = pias->InsertTextAtSelection(ec, TF_IAS_QUERYONLY, pstrAddString->Get(), (LONG)pstrAddString->GetLength(), &rangeInsert); + + if ( FAILED(hr) || rangeInsert == nullptr) + { + rangeInsert = nullptr; + pias->Release(); + goto Exit; + } + + *ppCompRange = rangeInsert; + pias->Release(); + hr = S_OK; + +Exit: + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _RemoveDummyCompositionForComposing +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_RemoveDummyCompositionForComposing(TfEditCookie ec, _In_ ITfComposition *pComposition) +{ + HRESULT hr = S_OK; + + ITfRange* pRange = nullptr; + + if (pComposition) + { + hr = pComposition->GetRange(&pRange); + if (SUCCEEDED(hr)) + { + pRange->SetText(ec, 0, nullptr, 0); + pRange->Release(); + } + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _SetCompositionLanguage +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_SetCompositionLanguage(TfEditCookie ec, _In_ ITfContext *pContext) +{ + HRESULT hr = S_OK; + BOOL ret = TRUE; + + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + LANGID langidProfile = 0; + pCompositionProcessorEngine->GetLanguageProfile(&langidProfile); + + ITfRange* pRangeComposition = nullptr; + ITfProperty* pLanguageProperty = nullptr; + + // we need a range and the context it lives in + hr = _pComposition->GetRange(&pRangeComposition); + if (FAILED(hr)) + { + ret = FALSE; + goto Exit; + } + + // get our the language property + hr = pContext->GetProperty(GUID_PROP_LANGID, &pLanguageProperty); + if (FAILED(hr)) + { + ret = FALSE; + goto Exit; + } + + VARIANT var; + var.vt = VT_I4; // we're going to set DWORD + var.lVal = langidProfile; + + hr = pLanguageProperty->SetValue(ec, pRangeComposition, &var); + if (FAILED(hr)) + { + ret = FALSE; + goto Exit; + } + + pLanguageProperty->Release(); + pRangeComposition->Release(); + +Exit: + return ret; +} diff --git a/CompositionProcessorEngine.cpp b/CompositionProcessorEngine.cpp new file mode 100644 index 00000000..2133dc6c --- /dev/null +++ b/CompositionProcessorEngine.cpp @@ -0,0 +1,1940 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "SampleIME.h" +#include "CompositionProcessorEngine.h" +#include "TableDictionaryEngine.h" +#include "DictionarySearch.h" +#include "TfInputProcessorProfile.h" +#include "Globals.h" +#include "Compartment.h" +#include "LanguageBar.h" +#include "RegKey.h" + +////////////////////////////////////////////////////////////////////// +// +// CSampleIME implementation. +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// _AddTextProcessorEngine +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_AddTextProcessorEngine() +{ + LANGID langid = 0; + CLSID clsid = GUID_NULL; + GUID guidProfile = GUID_NULL; + + // Get default profile. + CTfInputProcessorProfile profile; + + if (FAILED(profile.CreateInstance())) + { + return FALSE; + } + + if (FAILED(profile.GetCurrentLanguage(&langid))) + { + return FALSE; + } + + if (FAILED(profile.GetDefaultLanguageProfile(langid, GUID_TFCAT_TIP_KEYBOARD, &clsid, &guidProfile))) + { + return FALSE; + } + + // Is this already added? + if (_pCompositionProcessorEngine != nullptr) + { + LANGID langidProfile = 0; + GUID guidLanguageProfile = GUID_NULL; + + guidLanguageProfile = _pCompositionProcessorEngine->GetLanguageProfile(&langidProfile); + if ((langid == langidProfile) && IsEqualGUID(guidProfile, guidLanguageProfile)) + { + return TRUE; + } + } + + // Create composition processor engine + if (_pCompositionProcessorEngine == nullptr) + { + _pCompositionProcessorEngine = new (std::nothrow) CCompositionProcessorEngine(); + } + if (!_pCompositionProcessorEngine) + { + return FALSE; + } + + // setup composition processor engine + if (FALSE == _pCompositionProcessorEngine->SetupLanguageProfile(langid, guidProfile, _GetThreadMgr(), _GetClientId(), _IsSecureMode(), _IsComLess())) + { + return FALSE; + } + + return TRUE; +} + +////////////////////////////////////////////////////////////////////// +// +// CompositionProcessorEngine implementation. +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CCompositionProcessorEngine::CCompositionProcessorEngine() +{ + _pTableDictionaryEngine = nullptr; + _pDictionaryFile = nullptr; + + _langid = 0xffff; + _guidProfile = GUID_NULL; + _tfClientId = TF_CLIENTID_NULL; + + _pLanguageBar_IMEMode = nullptr; + _pLanguageBar_DoubleSingleByte = nullptr; + _pLanguageBar_Punctuation = nullptr; + + _pCompartmentConversion = nullptr; + _pCompartmentKeyboardOpenEventSink = nullptr; + _pCompartmentConversionEventSink = nullptr; + _pCompartmentDoubleSingleByteEventSink = nullptr; + _pCompartmentPunctuationEventSink = nullptr; + + _hasWildcardIncludedInKeystrokeBuffer = FALSE; + + _isWildcard = FALSE; + _isDisableWildcardAtFirst = FALSE; + _hasMakePhraseFromText = FALSE; + _isKeystrokeSort = FALSE; + + _candidateListPhraseModifier = 0; + + _candidateWndWidth = CAND_WIDTH; + + InitKeyStrokeTable(); +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CCompositionProcessorEngine::~CCompositionProcessorEngine() +{ + if (_pTableDictionaryEngine) + { + delete _pTableDictionaryEngine; + _pTableDictionaryEngine = nullptr; + } + + if (_pLanguageBar_IMEMode) + { + _pLanguageBar_IMEMode->CleanUp(); + _pLanguageBar_IMEMode->Release(); + _pLanguageBar_IMEMode = nullptr; + } + if (_pLanguageBar_DoubleSingleByte) + { + _pLanguageBar_DoubleSingleByte->CleanUp(); + _pLanguageBar_DoubleSingleByte->Release(); + _pLanguageBar_DoubleSingleByte = nullptr; + } + if (_pLanguageBar_Punctuation) + { + _pLanguageBar_Punctuation->CleanUp(); + _pLanguageBar_Punctuation->Release(); + _pLanguageBar_Punctuation = nullptr; + } + + if (_pCompartmentConversion) + { + delete _pCompartmentConversion; + _pCompartmentConversion = nullptr; + } + if (_pCompartmentKeyboardOpenEventSink) + { + _pCompartmentKeyboardOpenEventSink->_Unadvise(); + delete _pCompartmentKeyboardOpenEventSink; + _pCompartmentKeyboardOpenEventSink = nullptr; + } + if (_pCompartmentConversionEventSink) + { + _pCompartmentConversionEventSink->_Unadvise(); + delete _pCompartmentConversionEventSink; + _pCompartmentConversionEventSink = nullptr; + } + if (_pCompartmentDoubleSingleByteEventSink) + { + _pCompartmentDoubleSingleByteEventSink->_Unadvise(); + delete _pCompartmentDoubleSingleByteEventSink; + _pCompartmentDoubleSingleByteEventSink = nullptr; + } + if (_pCompartmentPunctuationEventSink) + { + _pCompartmentPunctuationEventSink->_Unadvise(); + delete _pCompartmentPunctuationEventSink; + _pCompartmentPunctuationEventSink = nullptr; + } + + if (_pDictionaryFile) + { + delete _pDictionaryFile; + _pDictionaryFile = nullptr; + } +} + +//+--------------------------------------------------------------------------- +// +// SetupLanguageProfile +// +// Setup language profile for Composition Processor Engine. +// param +// [in] LANGID langid = Specify language ID +// [in] GUID guidLanguageProfile - Specify GUID language profile which GUID is as same as Text Service Framework language profile. +// [in] ITfThreadMgr - pointer ITfThreadMgr. +// [in] tfClientId - TfClientId value. +// [in] isSecureMode - secure mode +// returns +// If setup succeeded, returns true. Otherwise returns false. +// N.B. For reverse conversion, ITfThreadMgr is NULL, TfClientId is 0 and isSecureMode is ignored. +//+--------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::SetupLanguageProfile(LANGID langid, REFGUID guidLanguageProfile, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, BOOL isSecureMode, BOOL isComLessMode) +{ + BOOL ret = TRUE; + if ((tfClientId == 0) && (pThreadMgr == nullptr)) + { + ret = FALSE; + goto Exit; + } + + _isComLessMode = isComLessMode; + _langid = langid; + _guidProfile = guidLanguageProfile; + _tfClientId = tfClientId; + + SetupPreserved(pThreadMgr, tfClientId); + InitializeSampleIMECompartment(pThreadMgr, tfClientId); + SetupPunctuationPair(); + SetupLanguageBar(pThreadMgr, tfClientId, isSecureMode); + SetupKeystroke(); + SetupConfiguration(); + SetupDictionaryFile(); + +Exit: + return ret; +} + +//+--------------------------------------------------------------------------- +// +// AddVirtualKey +// Add virtual key code to Composition Processor Engine for used to parse keystroke data. +// param +// [in] uCode - Specify virtual key code. +// returns +// State of Text Processor Engine. +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::AddVirtualKey(WCHAR wch) +{ + if (!wch) + { + return FALSE; + } + + // + // append one keystroke in buffer. + // + DWORD_PTR srgKeystrokeBufLen = _keystrokeBuffer.GetLength(); + PWCHAR pwch = new (std::nothrow) WCHAR[ srgKeystrokeBufLen + 1 ]; + if (!pwch) + { + return FALSE; + } + + memcpy(pwch, _keystrokeBuffer.Get(), srgKeystrokeBufLen * sizeof(WCHAR)); + pwch[ srgKeystrokeBufLen ] = wch; + + if (_keystrokeBuffer.Get()) + { + delete [] _keystrokeBuffer.Get(); + } + + _keystrokeBuffer.Set(pwch, srgKeystrokeBufLen + 1); + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// RemoveVirtualKey +// Remove stored virtual key code. +// param +// [in] dwIndex - Specified index. +// returns +// none. +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::RemoveVirtualKey(DWORD_PTR dwIndex) +{ + DWORD_PTR srgKeystrokeBufLen = _keystrokeBuffer.GetLength(); + + if (dwIndex + 1 < srgKeystrokeBufLen) + { + // shift following eles left + memmove((BYTE*)_keystrokeBuffer.Get() + (dwIndex * sizeof(WCHAR)), + (BYTE*)_keystrokeBuffer.Get() + ((dwIndex + 1) * sizeof(WCHAR)), + (srgKeystrokeBufLen - dwIndex - 1) * sizeof(WCHAR)); + } + + _keystrokeBuffer.Set(_keystrokeBuffer.Get(), srgKeystrokeBufLen - 1); +} + +//+--------------------------------------------------------------------------- +// +// PurgeVirtualKey +// Purge stored virtual key code. +// param +// none. +// returns +// none. +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::PurgeVirtualKey() +{ + if (_keystrokeBuffer.Get()) + { + delete [] _keystrokeBuffer.Get(); + _keystrokeBuffer.Set(NULL, 0); + } +} + +WCHAR CCompositionProcessorEngine::GetVirtualKey(DWORD_PTR dwIndex) +{ + if (dwIndex < _keystrokeBuffer.GetLength()) + { + return *(_keystrokeBuffer.Get() + dwIndex); + } + return 0; +} +//+--------------------------------------------------------------------------- +// +// GetReadingStrings +// Retrieves string from Composition Processor Engine. +// param +// [out] pReadingStrings - Specified returns pointer of CUnicodeString. +// returns +// none +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::GetReadingStrings(_Inout_ CSampleImeArray *pReadingStrings, _Out_ BOOL *pIsWildcardIncluded) +{ + CStringRange oneKeystroke; + + _hasWildcardIncludedInKeystrokeBuffer = FALSE; + + if (pReadingStrings->Count() == 0 && _keystrokeBuffer.GetLength()) + { + CStringRange* pNewString = nullptr; + + pNewString = pReadingStrings->Append(); + if (pNewString) + { + *pNewString = _keystrokeBuffer; + } + + for (DWORD index = 0; index < _keystrokeBuffer.GetLength(); index++) + { + oneKeystroke.Set(_keystrokeBuffer.Get() + index, 1); + + if (IsWildcard() && IsWildcardChar(*oneKeystroke.Get())) + { + _hasWildcardIncludedInKeystrokeBuffer = TRUE; + } + } + } + + *pIsWildcardIncluded = _hasWildcardIncludedInKeystrokeBuffer; +} + +//+--------------------------------------------------------------------------- +// +// GetCandidateList +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::GetCandidateList(_Inout_ CSampleImeArray *pCandidateList, BOOL isIncrementalWordSearch, BOOL isWildcardSearch) +{ + if (!IsDictionaryAvailable()) + { + return; + } + + if (isIncrementalWordSearch) + { + CStringRange wildcardSearch; + DWORD_PTR keystrokeBufLen = _keystrokeBuffer.GetLength() + 2; + PWCHAR pwch = new (std::nothrow) WCHAR[ keystrokeBufLen ]; + if (!pwch) + { + return; + } + + // check keystroke buffer already has wildcard char which end user want wildcard serach + DWORD wildcardIndex = 0; + BOOL isFindWildcard = FALSE; + + if (IsWildcard()) + { + for (wildcardIndex = 0; wildcardIndex < _keystrokeBuffer.GetLength(); wildcardIndex++) + { + if (IsWildcardChar(*(_keystrokeBuffer.Get() + wildcardIndex))) + { + isFindWildcard = TRUE; + break; + } + } + } + + StringCchCopyN(pwch, keystrokeBufLen, _keystrokeBuffer.Get(), _keystrokeBuffer.GetLength()); + + if (!isFindWildcard) + { + // add wildcard char for incremental search + StringCchCat(pwch, keystrokeBufLen, L"*"); + } + + size_t len = 0; + if (StringCchLength(pwch, STRSAFE_MAX_CCH, &len) == S_OK) + { + wildcardSearch.Set(pwch, len); + } + else + { + return; + } + + _pTableDictionaryEngine->CollectWordForWildcard(&wildcardSearch, pCandidateList); + + if (0 >= pCandidateList->Count()) + { + return; + } + + if (IsKeystrokeSort()) + { + _pTableDictionaryEngine->SortListItemByFindKeyCode(pCandidateList); + } + + // Incremental search would show keystroke data from all candidate list items + // but wont show identical keystroke data for user inputted. + for (UINT index = 0; index < pCandidateList->Count(); index++) + { + CCandidateListItem *pLI = pCandidateList->GetAt(index); + DWORD_PTR keystrokeBufferLen = 0; + + if (IsWildcard()) + { + keystrokeBufferLen = wildcardIndex; + } + else + { + keystrokeBufferLen = _keystrokeBuffer.GetLength(); + } + + CStringRange newFindKeyCode; + newFindKeyCode.Set(pLI->_FindKeyCode.Get() + keystrokeBufferLen, pLI->_FindKeyCode.GetLength() - keystrokeBufferLen); + pLI->_FindKeyCode.Set(newFindKeyCode); + } + + delete [] pwch; + } + else if (isWildcardSearch) + { + _pTableDictionaryEngine->CollectWordForWildcard(&_keystrokeBuffer, pCandidateList); + } + else + { + _pTableDictionaryEngine->CollectWord(&_keystrokeBuffer, pCandidateList); + } + + for (UINT index = 0; index < pCandidateList->Count();) + { + CCandidateListItem *pLI = pCandidateList->GetAt(index); + CStringRange startItemString; + CStringRange endItemString; + + startItemString.Set(pLI->_ItemString.Get(), 1); + endItemString.Set(pLI->_ItemString.Get() + pLI->_ItemString.GetLength() - 1, 1); + + index++; + } +} + +//+--------------------------------------------------------------------------- +// +// GetCandidateStringInConverted +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::GetCandidateStringInConverted(CStringRange &searchString, _In_ CSampleImeArray *pCandidateList) +{ + if (!IsDictionaryAvailable()) + { + return; + } + + // Search phrase from SECTION_TEXT's converted string list + CStringRange wildcardSearch; + DWORD_PTR srgKeystrokeBufLen = searchString.GetLength() + 2; + PWCHAR pwch = new (std::nothrow) WCHAR[ srgKeystrokeBufLen ]; + if (!pwch) + { + return; + } + + StringCchCopyN(pwch, srgKeystrokeBufLen, searchString.Get(), searchString.GetLength()); + StringCchCat(pwch, srgKeystrokeBufLen, L"*"); + + // add wildcard char + size_t len = 0; + if (StringCchLength(pwch, STRSAFE_MAX_CCH, &len) != S_OK) + { + return; + } + wildcardSearch.Set(pwch, len); + + _pTableDictionaryEngine->CollectWordFromConvertedStringForWildcard(&wildcardSearch, pCandidateList); + + if (IsKeystrokeSort()) + { + _pTableDictionaryEngine->SortListItemByFindKeyCode(pCandidateList); + } + + wildcardSearch.Clear(); + delete [] pwch; +} + +//+--------------------------------------------------------------------------- +// +// IsPunctuation +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::IsPunctuation(WCHAR wch) +{ + for (int i = 0; i < ARRAYSIZE(Global::PunctuationTable); i++) + { + if (Global::PunctuationTable[i]._Code == wch) + { + return TRUE; + } + } + + for (UINT j = 0; j < _PunctuationPair.Count(); j++) + { + CPunctuationPair* pPuncPair = _PunctuationPair.GetAt(j); + + if (pPuncPair->_punctuation._Code == wch) + { + return TRUE; + } + } + + for (UINT k = 0; k < _PunctuationNestPair.Count(); k++) + { + CPunctuationNestPair* pPuncNestPair = _PunctuationNestPair.GetAt(k); + + if (pPuncNestPair->_punctuation_begin._Code == wch) + { + return TRUE; + } + if (pPuncNestPair->_punctuation_end._Code == wch) + { + return TRUE; + } + } + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// GetPunctuationPair +// +//---------------------------------------------------------------------------- + +WCHAR CCompositionProcessorEngine::GetPunctuation(WCHAR wch) +{ + for (int i = 0; i < ARRAYSIZE(Global::PunctuationTable); i++) + { + if (Global::PunctuationTable[i]._Code == wch) + { + return Global::PunctuationTable[i]._Punctuation; + } + } + + for (UINT j = 0; j < _PunctuationPair.Count(); j++) + { + CPunctuationPair* pPuncPair = _PunctuationPair.GetAt(j); + + if (pPuncPair->_punctuation._Code == wch) + { + if (! pPuncPair->_isPairToggle) + { + pPuncPair->_isPairToggle = TRUE; + return pPuncPair->_punctuation._Punctuation; + } + else + { + pPuncPair->_isPairToggle = FALSE; + return pPuncPair->_pairPunctuation; + } + } + } + + for (UINT k = 0; k < _PunctuationNestPair.Count(); k++) + { + CPunctuationNestPair* pPuncNestPair = _PunctuationNestPair.GetAt(k); + + if (pPuncNestPair->_punctuation_begin._Code == wch) + { + if (pPuncNestPair->_nestCount++ == 0) + { + return pPuncNestPair->_punctuation_begin._Punctuation; + } + else + { + return pPuncNestPair->_pairPunctuation_begin; + } + } + if (pPuncNestPair->_punctuation_end._Code == wch) + { + if (--pPuncNestPair->_nestCount == 0) + { + return pPuncNestPair->_punctuation_end._Punctuation; + } + else + { + return pPuncNestPair->_pairPunctuation_end; + } + } + } + return 0; +} + +//+--------------------------------------------------------------------------- +// +// IsDoubleSingleByte +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::IsDoubleSingleByte(WCHAR wch) +{ + if (L' ' <= wch && wch <= L'~') + { + return TRUE; + } + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// SetupKeystroke +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetupKeystroke() +{ + SetKeystrokeTable(&_KeystrokeComposition); + return; +} + +//+--------------------------------------------------------------------------- +// +// SetKeystrokeTable +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetKeystrokeTable(_Inout_ CSampleImeArray<_KEYSTROKE> *pKeystroke) +{ + for (int i = 0; i < 26; i++) + { + _KEYSTROKE* pKS = nullptr; + + pKS = pKeystroke->Append(); + if (!pKS) + { + break; + } + *pKS = _keystrokeTable[i]; + } +} + +//+--------------------------------------------------------------------------- +// +// SetupPreserved +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetupPreserved(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId) +{ + TF_PRESERVEDKEY preservedKeyImeMode; + preservedKeyImeMode.uVKey = VK_SHIFT; + preservedKeyImeMode.uModifiers = _TF_MOD_ON_KEYUP_SHIFT_ONLY; + SetPreservedKey(Global::SampleIMEGuidImeModePreserveKey, preservedKeyImeMode, Global::ImeModeDescription, &_PreservedKey_IMEMode); + + TF_PRESERVEDKEY preservedKeyDoubleSingleByte; + preservedKeyDoubleSingleByte.uVKey = VK_SPACE; + preservedKeyDoubleSingleByte.uModifiers = TF_MOD_SHIFT; + SetPreservedKey(Global::SampleIMEGuidDoubleSingleBytePreserveKey, preservedKeyDoubleSingleByte, Global::DoubleSingleByteDescription, &_PreservedKey_DoubleSingleByte); + + TF_PRESERVEDKEY preservedKeyPunctuation; + preservedKeyPunctuation.uVKey = VK_OEM_PERIOD; + preservedKeyPunctuation.uModifiers = TF_MOD_CONTROL; + SetPreservedKey(Global::SampleIMEGuidPunctuationPreserveKey, preservedKeyPunctuation, Global::PunctuationDescription, &_PreservedKey_Punctuation); + + InitPreservedKey(&_PreservedKey_IMEMode, pThreadMgr, tfClientId); + InitPreservedKey(&_PreservedKey_DoubleSingleByte, pThreadMgr, tfClientId); + InitPreservedKey(&_PreservedKey_Punctuation, pThreadMgr, tfClientId); + + return; +} + +//+--------------------------------------------------------------------------- +// +// SetKeystrokeTable +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetPreservedKey(const CLSID clsid, TF_PRESERVEDKEY & tfPreservedKey, _In_z_ LPCWSTR pwszDescription, _Out_ XPreservedKey *pXPreservedKey) +{ + pXPreservedKey->Guid = clsid; + + TF_PRESERVEDKEY *ptfPsvKey1 = pXPreservedKey->TSFPreservedKeyTable.Append(); + if (!ptfPsvKey1) + { + return; + } + *ptfPsvKey1 = tfPreservedKey; + + size_t srgKeystrokeBufLen = 0; + if (StringCchLength(pwszDescription, STRSAFE_MAX_CCH, &srgKeystrokeBufLen) != S_OK) + { + return; + } + pXPreservedKey->Description = new (std::nothrow) WCHAR[srgKeystrokeBufLen + 1]; + if (!pXPreservedKey->Description) + { + return; + } + + StringCchCopy((LPWSTR)pXPreservedKey->Description, srgKeystrokeBufLen, pwszDescription); + + return; +} +//+--------------------------------------------------------------------------- +// +// InitPreservedKey +// +// Register a hot key. +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::InitPreservedKey(_In_ XPreservedKey *pXPreservedKey, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId) +{ + ITfKeystrokeMgr *pKeystrokeMgr = nullptr; + + if (IsEqualGUID(pXPreservedKey->Guid, GUID_NULL)) + { + return FALSE; + } + + if (pThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, (void **)&pKeystrokeMgr) != S_OK) + { + return FALSE; + } + + for (UINT i = 0; i < pXPreservedKey->TSFPreservedKeyTable.Count(); i++) + { + TF_PRESERVEDKEY preservedKey = *pXPreservedKey->TSFPreservedKeyTable.GetAt(i); + preservedKey.uModifiers &= 0xffff; + + size_t lenOfDesc = 0; + if (StringCchLength(pXPreservedKey->Description, STRSAFE_MAX_CCH, &lenOfDesc) != S_OK) + { + return FALSE; + } + pKeystrokeMgr->PreserveKey(tfClientId, pXPreservedKey->Guid, &preservedKey, pXPreservedKey->Description, static_cast(lenOfDesc)); + } + + pKeystrokeMgr->Release(); + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// CheckShiftKeyOnly +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::CheckShiftKeyOnly(_In_ CSampleImeArray *pTSFPreservedKeyTable) +{ + for (UINT i = 0; i < pTSFPreservedKeyTable->Count(); i++) + { + TF_PRESERVEDKEY *ptfPsvKey = pTSFPreservedKeyTable->GetAt(i); + + if (((ptfPsvKey->uModifiers & (_TF_MOD_ON_KEYUP_SHIFT_ONLY & 0xffff0000)) && !Global::IsShiftKeyDownOnly) || + ((ptfPsvKey->uModifiers & (_TF_MOD_ON_KEYUP_CONTROL_ONLY & 0xffff0000)) && !Global::IsControlKeyDownOnly) || + ((ptfPsvKey->uModifiers & (_TF_MOD_ON_KEYUP_ALT_ONLY & 0xffff0000)) && !Global::IsAltKeyDownOnly) ) + { + return FALSE; + } + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// OnPreservedKey +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::OnPreservedKey(REFGUID rguid, _Out_ BOOL *pIsEaten, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId) +{ + if (IsEqualGUID(rguid, _PreservedKey_IMEMode.Guid)) + { + if (!CheckShiftKeyOnly(&_PreservedKey_IMEMode.TSFPreservedKeyTable)) + { + *pIsEaten = FALSE; + return; + } + BOOL isOpen = FALSE; + CCompartment CompartmentKeyboardOpen(pThreadMgr, tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + CompartmentKeyboardOpen._GetCompartmentBOOL(isOpen); + CompartmentKeyboardOpen._SetCompartmentBOOL(isOpen ? FALSE : TRUE); + + *pIsEaten = TRUE; + } + else if (IsEqualGUID(rguid, _PreservedKey_DoubleSingleByte.Guid)) + { + if (!CheckShiftKeyOnly(&_PreservedKey_DoubleSingleByte.TSFPreservedKeyTable)) + { + *pIsEaten = FALSE; + return; + } + BOOL isDouble = FALSE; + CCompartment CompartmentDoubleSingleByte(pThreadMgr, tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + CompartmentDoubleSingleByte._GetCompartmentBOOL(isDouble); + CompartmentDoubleSingleByte._SetCompartmentBOOL(isDouble ? FALSE : TRUE); + *pIsEaten = TRUE; + } + else if (IsEqualGUID(rguid, _PreservedKey_Punctuation.Guid)) + { + if (!CheckShiftKeyOnly(&_PreservedKey_Punctuation.TSFPreservedKeyTable)) + { + *pIsEaten = FALSE; + return; + } + BOOL isPunctuation = FALSE; + CCompartment CompartmentPunctuation(pThreadMgr, tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + CompartmentPunctuation._GetCompartmentBOOL(isPunctuation); + CompartmentPunctuation._SetCompartmentBOOL(isPunctuation ? FALSE : TRUE); + *pIsEaten = TRUE; + } + else + { + *pIsEaten = FALSE; + } + *pIsEaten = TRUE; +} + +//+--------------------------------------------------------------------------- +// +// SetupConfiguration +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetupConfiguration() +{ + _isWildcard = TRUE; + _isDisableWildcardAtFirst = TRUE; + _hasMakePhraseFromText = TRUE; + _isKeystrokeSort = TRUE; + _candidateWndWidth = CAND_WIDTH; + + SetInitialCandidateListRange(); + + SetDefaultCandidateTextFont(); + + return; +} + +//+--------------------------------------------------------------------------- +// +// SetupLanguageBar +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetupLanguageBar(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, BOOL isSecureMode) +{ + DWORD dwEnable = 1; + CreateLanguageBarButton(dwEnable, GUID_LBI_INPUTMODE, Global::LangbarImeModeDescription, Global::ImeModeDescription, Global::ImeModeOnIcoIndex, Global::ImeModeOffIcoIndex, &_pLanguageBar_IMEMode, isSecureMode); + CreateLanguageBarButton(dwEnable, Global::SampleIMEGuidLangBarDoubleSingleByte, Global::LangbarDoubleSingleByteDescription, Global::DoubleSingleByteDescription, Global::DoubleSingleByteOnIcoIndex, Global::DoubleSingleByteOffIcoIndex, &_pLanguageBar_DoubleSingleByte, isSecureMode); + CreateLanguageBarButton(dwEnable, Global::SampleIMEGuidLangBarPunctuation, Global::LangbarPunctuationDescription, Global::PunctuationDescription, Global::PunctuationOnIcoIndex, Global::PunctuationOffIcoIndex, &_pLanguageBar_Punctuation, isSecureMode); + + InitLanguageBar(_pLanguageBar_IMEMode, pThreadMgr, tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + InitLanguageBar(_pLanguageBar_DoubleSingleByte, pThreadMgr, tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + InitLanguageBar(_pLanguageBar_Punctuation, pThreadMgr, tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + + _pCompartmentConversion = new (std::nothrow) CCompartment(pThreadMgr, tfClientId, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION); + _pCompartmentKeyboardOpenEventSink = new (std::nothrow) CCompartmentEventSink(CompartmentCallback, this); + _pCompartmentConversionEventSink = new (std::nothrow) CCompartmentEventSink(CompartmentCallback, this); + _pCompartmentDoubleSingleByteEventSink = new (std::nothrow) CCompartmentEventSink(CompartmentCallback, this); + _pCompartmentPunctuationEventSink = new (std::nothrow) CCompartmentEventSink(CompartmentCallback, this); + + if (_pCompartmentKeyboardOpenEventSink) + { + _pCompartmentKeyboardOpenEventSink->_Advise(pThreadMgr, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + } + if (_pCompartmentConversionEventSink) + { + _pCompartmentConversionEventSink->_Advise(pThreadMgr, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION); + } + if (_pCompartmentDoubleSingleByteEventSink) + { + _pCompartmentDoubleSingleByteEventSink->_Advise(pThreadMgr, Global::SampleIMEGuidCompartmentDoubleSingleByte); + } + if (_pCompartmentPunctuationEventSink) + { + _pCompartmentPunctuationEventSink->_Advise(pThreadMgr, Global::SampleIMEGuidCompartmentPunctuation); + } + + return; +} + +//+--------------------------------------------------------------------------- +// +// CreateLanguageBarButton +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::CreateLanguageBarButton(DWORD dwEnable, GUID guidLangBar, _In_z_ LPCWSTR pwszDescriptionValue, _In_z_ LPCWSTR pwszTooltipValue, DWORD dwOnIconIndex, DWORD dwOffIconIndex, _Outptr_result_maybenull_ CLangBarItemButton **ppLangBarItemButton, BOOL isSecureMode) +{ + dwEnable; + + if (ppLangBarItemButton) + { + *ppLangBarItemButton = new (std::nothrow) CLangBarItemButton(guidLangBar, pwszDescriptionValue, pwszTooltipValue, dwOnIconIndex, dwOffIconIndex, isSecureMode); + } + + return; +} + +//+--------------------------------------------------------------------------- +// +// InitLanguageBar +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::InitLanguageBar(_In_ CLangBarItemButton *pLangBarItemButton, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, REFGUID guidCompartment) +{ + if (pLangBarItemButton) + { + if (pLangBarItemButton->_AddItem(pThreadMgr) == S_OK) + { + if (pLangBarItemButton->_RegisterCompartment(pThreadMgr, tfClientId, guidCompartment)) + { + return TRUE; + } + } + } + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// SetupDictionaryFile +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::SetupDictionaryFile() +{ + // Not yet registered + // Register CFileMapping + WCHAR wszFileName[MAX_PATH] = {'\0'}; + DWORD cchA = GetModuleFileName(Global::dllInstanceHandle, wszFileName, ARRAYSIZE(wszFileName)); + size_t iDicFileNameLen = cchA + wcslen(TEXTSERVICE_DIC); + WCHAR *pwszFileName = new (std::nothrow) WCHAR[iDicFileNameLen + 1]; + if (!pwszFileName) + { + goto ErrorExit; + } + *pwszFileName = L'\0'; + + // find the last '/' + while (cchA--) + { + WCHAR wszChar = wszFileName[cchA]; + if (wszChar == '\\' || wszChar == '/') + { + StringCchCopyN(pwszFileName, iDicFileNameLen + 1, wszFileName, cchA + 1); + StringCchCatN(pwszFileName, iDicFileNameLen + 1, TEXTSERVICE_DIC, wcslen(TEXTSERVICE_DIC)); + break; + } + } + + // create CFileMapping object + if (_pDictionaryFile == nullptr) + { + _pDictionaryFile = new (std::nothrow) CFileMapping(); + if (!_pDictionaryFile) + { + goto ErrorExit; + } + } + if (!(_pDictionaryFile)->CreateFile(pwszFileName, GENERIC_READ, OPEN_EXISTING, FILE_SHARE_READ)) + { + goto ErrorExit; + } + + _pTableDictionaryEngine = new (std::nothrow) CTableDictionaryEngine(GetLocale(), _pDictionaryFile); + if (!_pTableDictionaryEngine) + { + goto ErrorExit; + } + + delete []pwszFileName; + return TRUE; +ErrorExit: + if (pwszFileName) + { + delete []pwszFileName; + } + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// GetDictionaryFile +// +//---------------------------------------------------------------------------- + +CFile* CCompositionProcessorEngine::GetDictionaryFile() +{ + return _pDictionaryFile; +} + +//+--------------------------------------------------------------------------- +// +// SetupPunctuationPair +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::SetupPunctuationPair() +{ + // Punctuation pair + const int pair_count = 2; + CPunctuationPair punc_quotation_mark(L'"', 0x201C, 0x201D); + CPunctuationPair punc_apostrophe(L'\'', 0x2018, 0x2019); + + CPunctuationPair puncPairs[pair_count] = { + punc_quotation_mark, + punc_apostrophe, + }; + + for (int i = 0; i < pair_count; ++i) + { + CPunctuationPair *pPuncPair = _PunctuationPair.Append(); + *pPuncPair = puncPairs[i]; + } + + // Punctuation nest pair + CPunctuationNestPair punc_angle_bracket(L'<', 0x300A, 0x3008, L'>', 0x300B, 0x3009); + + CPunctuationNestPair* pPuncNestPair = _PunctuationNestPair.Append(); + *pPuncNestPair = punc_angle_bracket; +} + +void CCompositionProcessorEngine::InitializeSampleIMECompartment(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId) +{ + // set initial mode + CCompartment CompartmentKeyboardOpen(pThreadMgr, tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + CompartmentKeyboardOpen._SetCompartmentBOOL(TRUE); + + CCompartment CompartmentDoubleSingleByte(pThreadMgr, tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + CompartmentDoubleSingleByte._SetCompartmentBOOL(FALSE); + + CCompartment CompartmentPunctuation(pThreadMgr, tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + CompartmentPunctuation._SetCompartmentBOOL(TRUE); + + PrivateCompartmentsUpdated(pThreadMgr); +} +//+--------------------------------------------------------------------------- +// +// CompartmentCallback +// +//---------------------------------------------------------------------------- + +// static +HRESULT CCompositionProcessorEngine::CompartmentCallback(_In_ void *pv, REFGUID guidCompartment) +{ + CCompositionProcessorEngine* fakeThis = (CCompositionProcessorEngine*)pv; + if (nullptr == fakeThis) + { + return E_INVALIDARG; + } + + ITfThreadMgr* pThreadMgr = nullptr; + HRESULT hr = CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, (void**)&pThreadMgr); + if (FAILED(hr)) + { + return E_FAIL; + } + + if (IsEqualGUID(guidCompartment, Global::SampleIMEGuidCompartmentDoubleSingleByte) || + IsEqualGUID(guidCompartment, Global::SampleIMEGuidCompartmentPunctuation)) + { + fakeThis->PrivateCompartmentsUpdated(pThreadMgr); + } + else if (IsEqualGUID(guidCompartment, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION) || + IsEqualGUID(guidCompartment, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE)) + { + fakeThis->ConversionModeCompartmentUpdated(pThreadMgr); + } + else if (IsEqualGUID(guidCompartment, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE)) + { + fakeThis->KeyboardOpenCompartmentUpdated(pThreadMgr); + } + + pThreadMgr->Release(); + pThreadMgr = nullptr; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// UpdatePrivateCompartments +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::ConversionModeCompartmentUpdated(_In_ ITfThreadMgr *pThreadMgr) +{ + if (!_pCompartmentConversion) + { + return; + } + + DWORD conversionMode = 0; + if (FAILED(_pCompartmentConversion->_GetCompartmentDWORD(conversionMode))) + { + return; + } + + BOOL isDouble = FALSE; + CCompartment CompartmentDoubleSingleByte(pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + if (SUCCEEDED(CompartmentDoubleSingleByte._GetCompartmentBOOL(isDouble))) + { + if (!isDouble && (conversionMode & TF_CONVERSIONMODE_FULLSHAPE)) + { + CompartmentDoubleSingleByte._SetCompartmentBOOL(TRUE); + } + else if (isDouble && !(conversionMode & TF_CONVERSIONMODE_FULLSHAPE)) + { + CompartmentDoubleSingleByte._SetCompartmentBOOL(FALSE); + } + } + BOOL isPunctuation = FALSE; + CCompartment CompartmentPunctuation(pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + if (SUCCEEDED(CompartmentPunctuation._GetCompartmentBOOL(isPunctuation))) + { + if (!isPunctuation && (conversionMode & TF_CONVERSIONMODE_SYMBOL)) + { + CompartmentPunctuation._SetCompartmentBOOL(TRUE); + } + else if (isPunctuation && !(conversionMode & TF_CONVERSIONMODE_SYMBOL)) + { + CompartmentPunctuation._SetCompartmentBOOL(FALSE); + } + } + + BOOL fOpen = FALSE; + CCompartment CompartmentKeyboardOpen(pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + if (SUCCEEDED(CompartmentKeyboardOpen._GetCompartmentBOOL(fOpen))) + { + if (fOpen && !(conversionMode & TF_CONVERSIONMODE_NATIVE)) + { + CompartmentKeyboardOpen._SetCompartmentBOOL(FALSE); + } + else if (!fOpen && (conversionMode & TF_CONVERSIONMODE_NATIVE)) + { + CompartmentKeyboardOpen._SetCompartmentBOOL(TRUE); + } + } +} + +//+--------------------------------------------------------------------------- +// +// PrivateCompartmentsUpdated() +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::PrivateCompartmentsUpdated(_In_ ITfThreadMgr *pThreadMgr) +{ + if (!_pCompartmentConversion) + { + return; + } + + DWORD conversionMode = 0; + DWORD conversionModePrev = 0; + if (FAILED(_pCompartmentConversion->_GetCompartmentDWORD(conversionMode))) + { + return; + } + + conversionModePrev = conversionMode; + + BOOL isDouble = FALSE; + CCompartment CompartmentDoubleSingleByte(pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + if (SUCCEEDED(CompartmentDoubleSingleByte._GetCompartmentBOOL(isDouble))) + { + if (!isDouble && (conversionMode & TF_CONVERSIONMODE_FULLSHAPE)) + { + conversionMode &= ~TF_CONVERSIONMODE_FULLSHAPE; + } + else if (isDouble && !(conversionMode & TF_CONVERSIONMODE_FULLSHAPE)) + { + conversionMode |= TF_CONVERSIONMODE_FULLSHAPE; + } + } + + BOOL isPunctuation = FALSE; + CCompartment CompartmentPunctuation(pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + if (SUCCEEDED(CompartmentPunctuation._GetCompartmentBOOL(isPunctuation))) + { + if (!isPunctuation && (conversionMode & TF_CONVERSIONMODE_SYMBOL)) + { + conversionMode &= ~TF_CONVERSIONMODE_SYMBOL; + } + else if (isPunctuation && !(conversionMode & TF_CONVERSIONMODE_SYMBOL)) + { + conversionMode |= TF_CONVERSIONMODE_SYMBOL; + } + } + + if (conversionMode != conversionModePrev) + { + _pCompartmentConversion->_SetCompartmentDWORD(conversionMode); + } +} + +//+--------------------------------------------------------------------------- +// +// KeyboardOpenCompartmentUpdated +// +//---------------------------------------------------------------------------- + +void CCompositionProcessorEngine::KeyboardOpenCompartmentUpdated(_In_ ITfThreadMgr *pThreadMgr) +{ + if (!_pCompartmentConversion) + { + return; + } + + DWORD conversionMode = 0; + DWORD conversionModePrev = 0; + if (FAILED(_pCompartmentConversion->_GetCompartmentDWORD(conversionMode))) + { + return; + } + + conversionModePrev = conversionMode; + + BOOL isOpen = FALSE; + CCompartment CompartmentKeyboardOpen(pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + if (SUCCEEDED(CompartmentKeyboardOpen._GetCompartmentBOOL(isOpen))) + { + if (isOpen && !(conversionMode & TF_CONVERSIONMODE_NATIVE)) + { + conversionMode |= TF_CONVERSIONMODE_NATIVE; + } + else if (!isOpen && (conversionMode & TF_CONVERSIONMODE_NATIVE)) + { + conversionMode &= ~TF_CONVERSIONMODE_NATIVE; + } + } + + if (conversionMode != conversionModePrev) + { + _pCompartmentConversion->_SetCompartmentDWORD(conversionMode); + } +} + + +////////////////////////////////////////////////////////////////////// +// +// XPreservedKey implementation. +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// UninitPreservedKey +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::XPreservedKey::UninitPreservedKey(_In_ ITfThreadMgr *pThreadMgr) +{ + ITfKeystrokeMgr* pKeystrokeMgr = nullptr; + + if (IsEqualGUID(Guid, GUID_NULL)) + { + return FALSE; + } + + if (FAILED(pThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, (void **)&pKeystrokeMgr))) + { + return FALSE; + } + + for (UINT i = 0; i < TSFPreservedKeyTable.Count(); i++) + { + TF_PRESERVEDKEY pPreservedKey = *TSFPreservedKeyTable.GetAt(i); + pPreservedKey.uModifiers &= 0xffff; + + pKeystrokeMgr->UnpreserveKey(Guid, &pPreservedKey); + } + + pKeystrokeMgr->Release(); + + return TRUE; +} + +CCompositionProcessorEngine::XPreservedKey::XPreservedKey() +{ + Guid = GUID_NULL; + Description = nullptr; +} + +CCompositionProcessorEngine::XPreservedKey::~XPreservedKey() +{ + ITfThreadMgr* pThreadMgr = nullptr; + + HRESULT hr = CoCreateInstance(CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, (void**)&pThreadMgr); + if (SUCCEEDED(hr)) + { + UninitPreservedKey(pThreadMgr); + pThreadMgr->Release(); + pThreadMgr = nullptr; + } + + if (Description) + { + delete [] Description; + } +} +//+--------------------------------------------------------------------------- +// +// CSampleIME::CreateInstance +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::CreateInstance(REFCLSID rclsid, REFIID riid, _Outptr_result_maybenull_ LPVOID* ppv, _Out_opt_ HINSTANCE* phInst, BOOL isComLessMode) +{ + HRESULT hr = S_OK; + if (phInst == nullptr) + { + return E_INVALIDARG; + } + + *phInst = nullptr; + + if (!isComLessMode) + { + hr = ::CoCreateInstance(rclsid, + NULL, + CLSCTX_INPROC_SERVER, + riid, + ppv); + } + else + { + hr = CSampleIME::ComLessCreateInstance(rclsid, riid, ppv, phInst); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// CSampleIME::ComLessCreateInstance +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::ComLessCreateInstance(REFGUID rclsid, REFIID riid, _Outptr_result_maybenull_ void **ppv, _Out_opt_ HINSTANCE *phInst) +{ + HRESULT hr = S_OK; + HINSTANCE sampleIMEDllHandle = nullptr; + WCHAR wchPath[MAX_PATH] = {'\0'}; + WCHAR szExpandedPath[MAX_PATH] = {'\0'}; + DWORD dwCnt = 0; + *ppv = nullptr; + + hr = phInst ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + *phInst = nullptr; + hr = CSampleIME::GetComModuleName(rclsid, wchPath, ARRAYSIZE(wchPath)); + if (SUCCEEDED(hr)) + { + dwCnt = ExpandEnvironmentStringsW(wchPath, szExpandedPath, ARRAYSIZE(szExpandedPath)); + hr = (0 < dwCnt && dwCnt <= ARRAYSIZE(szExpandedPath)) ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + sampleIMEDllHandle = LoadLibraryEx(szExpandedPath, NULL, 0); + hr = sampleIMEDllHandle ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + *phInst = sampleIMEDllHandle; + FARPROC pfn = GetProcAddress(sampleIMEDllHandle, "DllGetClassObject"); + hr = pfn ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + IClassFactory *pClassFactory = nullptr; + hr = ((HRESULT (STDAPICALLTYPE *)(REFCLSID rclsid, REFIID riid, LPVOID *ppv))(pfn))(rclsid, IID_IClassFactory, (void **)&pClassFactory); + if (SUCCEEDED(hr) && pClassFactory) + { + hr = pClassFactory->CreateInstance(NULL, riid, ppv); + pClassFactory->Release(); + } + } + } + } + } + } + + if (!SUCCEEDED(hr) && phInst && *phInst) + { + FreeLibrary(*phInst); + *phInst = 0; + } + return hr; +} + +//+--------------------------------------------------------------------------- +// +// CSampleIME::GetComModuleName +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::GetComModuleName(REFGUID rclsid, _Out_writes_(cchPath)WCHAR* wchPath, DWORD cchPath) +{ + HRESULT hr = S_OK; + + CRegKey key; + WCHAR wchClsid[CLSID_STRLEN + 1]; + hr = CLSIDToString(rclsid, wchClsid) ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + WCHAR wchKey[MAX_PATH]; + hr = StringCchPrintfW(wchKey, ARRAYSIZE(wchKey), L"CLSID\\%s\\InProcServer32", wchClsid); + if (SUCCEEDED(hr)) + { + hr = (key.Open(HKEY_CLASSES_ROOT, wchKey, KEY_READ) == ERROR_SUCCESS) ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + WCHAR wszModel[MAX_PATH]; + ULONG cch = ARRAYSIZE(wszModel); + hr = (key.QueryStringValue(L"ThreadingModel", wszModel, &cch) == ERROR_SUCCESS) ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + if (CompareStringOrdinal(wszModel, + -1, + L"Apartment", + -1, + TRUE) == CSTR_EQUAL) + { + hr = (key.QueryStringValue(NULL, wchPath, &cchPath) == ERROR_SUCCESS) ? S_OK : E_FAIL; + } + else + { + hr = E_FAIL; + } + } + } + } + } + + return hr; +} + +void CCompositionProcessorEngine::InitKeyStrokeTable() +{ + for (int i = 0; i < 26; i++) + { + _keystrokeTable[i].VirtualKey = 'A' + i; + _keystrokeTable[i].Modifiers = 0; + _keystrokeTable[i].Function = FUNCTION_INPUT; + } +} + +void CCompositionProcessorEngine::ShowAllLanguageBarIcons() +{ + SetLanguageBarStatus(TF_LBI_STATUS_HIDDEN, FALSE); +} + +void CCompositionProcessorEngine::HideAllLanguageBarIcons() +{ + SetLanguageBarStatus(TF_LBI_STATUS_HIDDEN, TRUE); +} + +void CCompositionProcessorEngine::SetInitialCandidateListRange() +{ + for (DWORD i = 1; i <= 10; i++) + { + DWORD* pNewIndexRange = nullptr; + + pNewIndexRange = _candidateListIndexRange.Append(); + if (pNewIndexRange != nullptr) + { + if (i != 10) + { + *pNewIndexRange = i; + } + else + { + *pNewIndexRange = 0; + } + } + } +} + +void CCompositionProcessorEngine::SetDefaultCandidateTextFont() +{ + // Candidate Text Font + if (Global::defaultlFontHandle == nullptr) + { + WCHAR fontName[50] = {'\0'}; + LoadString(Global::dllInstanceHandle, IDS_DEFAULT_FONT, fontName, 50); + Global::defaultlFontHandle = CreateFont(-MulDiv(10, GetDeviceCaps(GetDC(NULL), LOGPIXELSY), 72), 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0, 0, 0, 0, 0, fontName); + if (!Global::defaultlFontHandle) + { + LOGFONT lf; + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, 0); + // Fall back to the default GUI font on failure. + Global::defaultlFontHandle = CreateFont(-MulDiv(10, GetDeviceCaps(GetDC(NULL), LOGPIXELSY), 72), 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0, 0, 0, 0, 0, lf.lfFaceName); + } + } +} + +////////////////////////////////////////////////////////////////////// +// +// CCompositionProcessorEngine +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// CCompositionProcessorEngine::IsVirtualKeyNeed +// +// Test virtual key code need to the Composition Processor Engine. +// param +// [in] uCode - Specify virtual key code. +// [in/out] pwch - char code +// [in] fComposing - Specified composing. +// [in] fCandidateMode - Specified candidate mode. +// [out] pKeyState - Returns function regarding virtual key. +// returns +// If engine need this virtual key code, returns true. Otherwise returns false. +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::IsVirtualKeyNeed(UINT uCode, _In_reads_(1) WCHAR *pwch, BOOL fComposing, CANDIDATE_MODE candidateMode, BOOL hasCandidateWithWildcard, _Out_opt_ _KEYSTROKE_STATE *pKeyState) +{ + if (pKeyState) + { + pKeyState->Category = CATEGORY_NONE; + pKeyState->Function = FUNCTION_NONE; + } + + if (candidateMode == CANDIDATE_ORIGINAL || candidateMode == CANDIDATE_PHRASE || candidateMode == CANDIDATE_WITH_NEXT_COMPOSITION) + { + fComposing = FALSE; + } + + if (fComposing || candidateMode == CANDIDATE_INCREMENTAL || candidateMode == CANDIDATE_NONE) + { + if (IsVirtualKeyKeystrokeComposition(uCode, pKeyState, FUNCTION_NONE)) + { + return TRUE; + } + else if ((IsWildcard() && IsWildcardChar(*pwch) && !IsDisableWildcardAtFirst()) || + (IsWildcard() && IsWildcardChar(*pwch) && IsDisableWildcardAtFirst() && _keystrokeBuffer.GetLength())) + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_COMPOSING; + pKeyState->Function = FUNCTION_INPUT; + } + return TRUE; + } + else if (_hasWildcardIncludedInKeystrokeBuffer && uCode == VK_SPACE) + { + if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_CONVERT_WILDCARD; } return TRUE; + } + } + + if (candidateMode == CANDIDATE_ORIGINAL || candidateMode == CANDIDATE_PHRASE || candidateMode == CANDIDATE_WITH_NEXT_COMPOSITION) + { + BOOL isRetCode = TRUE; + if (IsVirtualKeyKeystrokeCandidate(uCode, pKeyState, candidateMode, &isRetCode, &_KeystrokeCandidate)) + { + return isRetCode; + } + + if (hasCandidateWithWildcard) + { + if (IsVirtualKeyKeystrokeCandidate(uCode, pKeyState, candidateMode, &isRetCode, &_KeystrokeCandidateWildcard)) + { + return isRetCode; + } + } + + // Candidate list could not handle key. We can try to restart the composition. + if (IsVirtualKeyKeystrokeComposition(uCode, pKeyState, FUNCTION_INPUT)) + { + if (candidateMode != CANDIDATE_ORIGINAL) + { + return TRUE; + } + else + { + if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_FINALIZE_CANDIDATELIST_AND_INPUT; } + return TRUE; + } + } + } + + // CANDIDATE_INCREMENTAL should process Keystroke.Candidate virtual keys. + else if (candidateMode == CANDIDATE_INCREMENTAL) + { + BOOL isRetCode = TRUE; + if (IsVirtualKeyKeystrokeCandidate(uCode, pKeyState, candidateMode, &isRetCode, &_KeystrokeCandidate)) + { + return isRetCode; + } + } + + if (!fComposing && candidateMode != CANDIDATE_ORIGINAL && candidateMode != CANDIDATE_PHRASE && candidateMode != CANDIDATE_WITH_NEXT_COMPOSITION) + { + if (IsVirtualKeyKeystrokeComposition(uCode, pKeyState, FUNCTION_INPUT)) + { + return TRUE; + } + } + + // System pre-defined keystroke + if (fComposing) + { + if ((candidateMode != CANDIDATE_INCREMENTAL)) + { + switch (uCode) + { + case VK_LEFT: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_LEFT; } return TRUE; + case VK_RIGHT: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_RIGHT; } return TRUE; + case VK_RETURN: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_FINALIZE_CANDIDATELIST; } return TRUE; + case VK_ESCAPE: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_CANCEL; } return TRUE; + case VK_BACK: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_BACKSPACE; } return TRUE; + + case VK_UP: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_UP; } return TRUE; + case VK_DOWN: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_DOWN; } return TRUE; + case VK_PRIOR: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_PAGE_UP; } return TRUE; + case VK_NEXT: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_PAGE_DOWN; } return TRUE; + + case VK_HOME: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_PAGE_TOP; } return TRUE; + case VK_END: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_MOVE_PAGE_BOTTOM; } return TRUE; + + case VK_SPACE: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_CONVERT; } return TRUE; + } + } + else if ((candidateMode == CANDIDATE_INCREMENTAL)) + { + switch (uCode) + { + // VK_LEFT, VK_RIGHT - set *pIsEaten = FALSE for application could move caret left or right. + // and for CUAS, invoke _HandleCompositionCancel() edit session due to ignore CUAS default key handler for send out terminate composition + case VK_LEFT: + case VK_RIGHT: + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION; + pKeyState->Function = FUNCTION_CANCEL; + } + } + return FALSE; + + case VK_RETURN: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_FINALIZE_CANDIDATELIST; } return TRUE; + case VK_ESCAPE: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_CANCEL; } return TRUE; + + // VK_BACK - remove one char from reading string. + case VK_BACK: if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_BACKSPACE; } return TRUE; + + case VK_UP: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_UP; } return TRUE; + case VK_DOWN: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_DOWN; } return TRUE; + case VK_PRIOR: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_UP; } return TRUE; + case VK_NEXT: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_DOWN; } return TRUE; + + case VK_HOME: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_TOP; } return TRUE; + case VK_END: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_BOTTOM; } return TRUE; + + case VK_SPACE: + { + if (candidateMode == CANDIDATE_INCREMENTAL) + { + if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_CONVERT; } return TRUE; + } + else + { + if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_CONVERT; } return TRUE; + } + } + } + } + } + + if ((candidateMode == CANDIDATE_ORIGINAL) || (candidateMode == CANDIDATE_WITH_NEXT_COMPOSITION)) + { + switch (uCode) + { + case VK_UP: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_UP; } return TRUE; + case VK_DOWN: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_DOWN; } return TRUE; + case VK_PRIOR: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_UP; } return TRUE; + case VK_NEXT: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_DOWN; } return TRUE; + case VK_HOME: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_TOP; } return TRUE; + case VK_END: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_MOVE_PAGE_BOTTOM; } return TRUE; + case VK_RETURN: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_FINALIZE_CANDIDATELIST; } return TRUE; + case VK_SPACE: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_CONVERT; } return TRUE; + case VK_BACK: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_CANCEL; } return TRUE; + + case VK_ESCAPE: + { + if (candidateMode == CANDIDATE_WITH_NEXT_COMPOSITION) + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION; + pKeyState->Function = FUNCTION_FINALIZE_TEXTSTORE; + } + return TRUE; + } + else + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_CANDIDATE; + pKeyState->Function = FUNCTION_CANCEL; + } + return TRUE; + } + } + } + + if (candidateMode == CANDIDATE_WITH_NEXT_COMPOSITION) + { + if (IsVirtualKeyKeystrokeComposition(uCode, NULL, FUNCTION_NONE)) + { + if (pKeyState) { pKeyState->Category = CATEGORY_COMPOSING; pKeyState->Function = FUNCTION_FINALIZE_TEXTSTORE_AND_INPUT; } return TRUE; + } + } + } + + if (candidateMode == CANDIDATE_PHRASE) + { + switch (uCode) + { + case VK_UP: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_MOVE_UP; } return TRUE; + case VK_DOWN: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_MOVE_DOWN; } return TRUE; + case VK_PRIOR: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_MOVE_PAGE_UP; } return TRUE; + case VK_NEXT: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_MOVE_PAGE_DOWN; } return TRUE; + case VK_HOME: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_MOVE_PAGE_TOP; } return TRUE; + case VK_END: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_MOVE_PAGE_BOTTOM; } return TRUE; + case VK_RETURN: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_FINALIZE_CANDIDATELIST; } return TRUE; + case VK_SPACE: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_CONVERT; } return TRUE; + case VK_ESCAPE: if (pKeyState) { pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_CANCEL; } return TRUE; + case VK_BACK: if (pKeyState) { pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_CANCEL; } return TRUE; + } + } + + if (IsKeystrokeRange(uCode, pKeyState, candidateMode)) + { + return TRUE; + } + else if (pKeyState && pKeyState->Category != CATEGORY_NONE) + { + return FALSE; + } + + if (*pwch && !IsVirtualKeyKeystrokeComposition(uCode, pKeyState, FUNCTION_NONE)) + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION; + pKeyState->Function = FUNCTION_FINALIZE_TEXTSTORE; + } + return FALSE; + } + + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// CCompositionProcessorEngine::IsVirtualKeyKeystrokeComposition +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::IsVirtualKeyKeystrokeComposition(UINT uCode, _Out_opt_ _KEYSTROKE_STATE *pKeyState, KEYSTROKE_FUNCTION function) +{ + if (pKeyState == nullptr) + { + return FALSE; + } + + pKeyState->Category = CATEGORY_NONE; + pKeyState->Function = FUNCTION_NONE; + + for (UINT i = 0; i < _KeystrokeComposition.Count(); i++) + { + _KEYSTROKE *pKeystroke = nullptr; + + pKeystroke = _KeystrokeComposition.GetAt(i); + + if ((pKeystroke->VirtualKey == uCode) && Global::CheckModifiers(Global::ModifiersValue, pKeystroke->Modifiers)) + { + if (function == FUNCTION_NONE) + { + pKeyState->Category = CATEGORY_COMPOSING; + pKeyState->Function = pKeystroke->Function; + return TRUE; + } + else if (function == pKeystroke->Function) + { + pKeyState->Category = CATEGORY_COMPOSING; + pKeyState->Function = pKeystroke->Function; + return TRUE; + } + } + } + + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// CCompositionProcessorEngine::IsVirtualKeyKeystrokeCandidate +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::IsVirtualKeyKeystrokeCandidate(UINT uCode, _In_ _KEYSTROKE_STATE *pKeyState, CANDIDATE_MODE candidateMode, _Out_ BOOL *pfRetCode, _In_ CSampleImeArray<_KEYSTROKE> *pKeystrokeMetric) +{ + if (pfRetCode == nullptr) + { + return FALSE; + } + *pfRetCode = FALSE; + + for (UINT i = 0; i < pKeystrokeMetric->Count(); i++) + { + _KEYSTROKE *pKeystroke = nullptr; + + pKeystroke = pKeystrokeMetric->GetAt(i); + + if ((pKeystroke->VirtualKey == uCode) && Global::CheckModifiers(Global::ModifiersValue, pKeystroke->Modifiers)) + { + *pfRetCode = TRUE; + if (pKeyState) + { + pKeyState->Category = (candidateMode == CANDIDATE_ORIGINAL ? CATEGORY_CANDIDATE : + candidateMode == CANDIDATE_PHRASE ? CATEGORY_PHRASE : CATEGORY_CANDIDATE); + + pKeyState->Function = pKeystroke->Function; + } + return TRUE; + } + } + + return FALSE; +} + +//+--------------------------------------------------------------------------- +// +// CCompositionProcessorEngine::IsKeyKeystrokeRange +// +//---------------------------------------------------------------------------- + +BOOL CCompositionProcessorEngine::IsKeystrokeRange(UINT uCode, _Out_ _KEYSTROKE_STATE *pKeyState, CANDIDATE_MODE candidateMode) +{ + if (pKeyState == nullptr) + { + return FALSE; + } + + pKeyState->Category = CATEGORY_NONE; + pKeyState->Function = FUNCTION_NONE; + + if (_candidateListIndexRange.IsRange(uCode)) + { + if (candidateMode == CANDIDATE_PHRASE) + { + // Candidate phrase could specify modifier + if ((GetCandidateListPhraseModifier() == 0 && Global::ModifiersValue == 0) || + (GetCandidateListPhraseModifier() != 0 && Global::CheckModifiers(Global::ModifiersValue, GetCandidateListPhraseModifier()))) + { + pKeyState->Category = CATEGORY_PHRASE; pKeyState->Function = FUNCTION_SELECT_BY_NUMBER; + return TRUE; + } + else + { + pKeyState->Category = CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION; pKeyState->Function = FUNCTION_FINALIZE_TEXTSTORE_AND_INPUT; + return FALSE; + } + } + else if (candidateMode == CANDIDATE_WITH_NEXT_COMPOSITION) + { + // Candidate phrase could specify modifier + if ((GetCandidateListPhraseModifier() == 0 && Global::ModifiersValue == 0) || + (GetCandidateListPhraseModifier() != 0 && Global::CheckModifiers(Global::ModifiersValue, GetCandidateListPhraseModifier()))) + { + pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_SELECT_BY_NUMBER; + return TRUE; + } + // else next composition + } + else if (candidateMode != CANDIDATE_NONE) + { + pKeyState->Category = CATEGORY_CANDIDATE; pKeyState->Function = FUNCTION_SELECT_BY_NUMBER; + return TRUE; + } + } + return FALSE; +} \ No newline at end of file diff --git a/CompositionProcessorEngine.h b/CompositionProcessorEngine.h new file mode 100644 index 00000000..8b6ac5d1 --- /dev/null +++ b/CompositionProcessorEngine.h @@ -0,0 +1,195 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "sal.h" +#include "TableDictionaryEngine.h" +#include "KeyHandlerEditSession.h" +#include "SampleIMEBaseStructure.h" +#include "FileMapping.h" +#include "Compartment.h" +#include "define.h" + +class CCompositionProcessorEngine +{ +public: + CCompositionProcessorEngine(void); + ~CCompositionProcessorEngine(void); + + BOOL SetupLanguageProfile(LANGID langid, REFGUID guidLanguageProfile, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, BOOL isSecureMode, BOOL isComLessMode); + + // Get language profile. + GUID GetLanguageProfile(LANGID *plangid) + { + *plangid = _langid; + return _guidProfile; + } + // Get locale + LCID GetLocale() + { + return MAKELCID(_langid, SORT_DEFAULT); + } + + BOOL IsVirtualKeyNeed(UINT uCode, _In_reads_(1) WCHAR *pwch, BOOL fComposing, CANDIDATE_MODE candidateMode, BOOL hasCandidateWithWildcard, _Out_opt_ _KEYSTROKE_STATE *pKeyState); + + BOOL AddVirtualKey(WCHAR wch); + void RemoveVirtualKey(DWORD_PTR dwIndex); + void PurgeVirtualKey(); + + DWORD_PTR GetVirtualKeyLength() { return _keystrokeBuffer.GetLength(); } + WCHAR GetVirtualKey(DWORD_PTR dwIndex); + + void GetReadingStrings(_Inout_ CSampleImeArray *pReadingStrings, _Out_ BOOL *pIsWildcardIncluded); + void GetCandidateList(_Inout_ CSampleImeArray *pCandidateList, BOOL isIncrementalWordSearch, BOOL isWildcardSearch); + void GetCandidateStringInConverted(CStringRange &searchString, _In_ CSampleImeArray *pCandidateList); + + // Preserved key handler + void OnPreservedKey(REFGUID rguid, _Out_ BOOL *pIsEaten, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId); + + // Punctuation + BOOL IsPunctuation(WCHAR wch); + WCHAR GetPunctuation(WCHAR wch); + + BOOL IsDoubleSingleByte(WCHAR wch); + BOOL IsWildcard() { return _isWildcard; } + BOOL IsDisableWildcardAtFirst() { return _isDisableWildcardAtFirst; } + BOOL IsWildcardChar(WCHAR wch) { return ((IsWildcardOneChar(wch) || IsWildcardAllChar(wch)) ? TRUE : FALSE); } + BOOL IsWildcardOneChar(WCHAR wch) { return (wch==L'?' ? TRUE : FALSE); } + BOOL IsWildcardAllChar(WCHAR wch) { return (wch==L'*' ? TRUE : FALSE); } + BOOL IsMakePhraseFromText() { return _hasMakePhraseFromText; } + BOOL IsKeystrokeSort() { return _isKeystrokeSort; } + + // Dictionary engine + BOOL IsDictionaryAvailable() { return (_pTableDictionaryEngine ? TRUE : FALSE); } + + // Language bar control + void SetLanguageBarStatus(DWORD status, BOOL isSet); + + void ConversionModeCompartmentUpdated(_In_ ITfThreadMgr *pThreadMgr); + + void ShowAllLanguageBarIcons(); + void HideAllLanguageBarIcons(); + + inline CCandidateRange *GetCandidateListIndexRange() { return &_candidateListIndexRange; } + inline UINT GetCandidateListPhraseModifier() { return _candidateListPhraseModifier; } + inline UINT GetCandidateWindowWidth() { return _candidateWndWidth; } + +private: + void InitKeyStrokeTable(); + BOOL InitLanguageBar(_In_ CLangBarItemButton *pLanguageBar, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, REFGUID guidCompartment); + + struct _KEYSTROKE; + BOOL IsVirtualKeyKeystrokeComposition(UINT uCode, _Out_opt_ _KEYSTROKE_STATE *pKeyState, KEYSTROKE_FUNCTION function); + BOOL IsVirtualKeyKeystrokeCandidate(UINT uCode, _In_ _KEYSTROKE_STATE *pKeyState, CANDIDATE_MODE candidateMode, _Out_ BOOL *pfRetCode, _In_ CSampleImeArray<_KEYSTROKE> *pKeystrokeMetric); + BOOL IsKeystrokeRange(UINT uCode, _Out_ _KEYSTROKE_STATE *pKeyState, CANDIDATE_MODE candidateMode); + + void SetupKeystroke(); + void SetupPreserved(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId); + void SetupConfiguration(); + void SetupLanguageBar(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, BOOL isSecureMode); + void SetKeystrokeTable(_Inout_ CSampleImeArray<_KEYSTROKE> *pKeystroke); + void SetupPunctuationPair(); + void CreateLanguageBarButton(DWORD dwEnable, GUID guidLangBar, _In_z_ LPCWSTR pwszDescriptionValue, _In_z_ LPCWSTR pwszTooltipValue, DWORD dwOnIconIndex, DWORD dwOffIconIndex, _Outptr_result_maybenull_ CLangBarItemButton **ppLangBarItemButton, BOOL isSecureMode); + void SetInitialCandidateListRange(); + void SetDefaultCandidateTextFont(); + void InitializeSampleIMECompartment(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId); + + class XPreservedKey; + void SetPreservedKey(const CLSID clsid, TF_PRESERVEDKEY & tfPreservedKey, _In_z_ LPCWSTR pwszDescription, _Out_ XPreservedKey *pXPreservedKey); + BOOL InitPreservedKey(_In_ XPreservedKey *pXPreservedKey, _In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId); + BOOL CheckShiftKeyOnly(_In_ CSampleImeArray *pTSFPreservedKeyTable); + + static HRESULT CompartmentCallback(_In_ void *pv, REFGUID guidCompartment); + void PrivateCompartmentsUpdated(_In_ ITfThreadMgr *pThreadMgr); + void KeyboardOpenCompartmentUpdated(_In_ ITfThreadMgr *pThreadMgr); + + + BOOL SetupDictionaryFile(); + CFile* GetDictionaryFile(); + +private: + struct _KEYSTROKE + { + UINT VirtualKey; + UINT Modifiers; + KEYSTROKE_FUNCTION Function; + + _KEYSTROKE() + { + VirtualKey = 0; + Modifiers = 0; + Function = FUNCTION_NONE; + } + }; + _KEYSTROKE _keystrokeTable[26]; + + CTableDictionaryEngine* _pTableDictionaryEngine; + CStringRange _keystrokeBuffer; + + BOOL _hasWildcardIncludedInKeystrokeBuffer; + + LANGID _langid; + GUID _guidProfile; + TfClientId _tfClientId; + + CSampleImeArray<_KEYSTROKE> _KeystrokeComposition; + CSampleImeArray<_KEYSTROKE> _KeystrokeCandidate; + CSampleImeArray<_KEYSTROKE> _KeystrokeCandidateWildcard; + CSampleImeArray<_KEYSTROKE> _KeystrokeCandidateSymbol; + CSampleImeArray<_KEYSTROKE> _KeystrokeSymbol; + + // Preserved key data + class XPreservedKey + { + public: + XPreservedKey(); + ~XPreservedKey(); + BOOL UninitPreservedKey(_In_ ITfThreadMgr *pThreadMgr); + + public: + CSampleImeArray TSFPreservedKeyTable; + GUID Guid; + LPCWSTR Description; + }; + + XPreservedKey _PreservedKey_IMEMode; + XPreservedKey _PreservedKey_DoubleSingleByte; + XPreservedKey _PreservedKey_Punctuation; + + // Punctuation data + CSampleImeArray _PunctuationPair; + CSampleImeArray _PunctuationNestPair; + + // Language bar data + CLangBarItemButton* _pLanguageBar_IMEMode; + CLangBarItemButton* _pLanguageBar_DoubleSingleByte; + CLangBarItemButton* _pLanguageBar_Punctuation; + + // Compartment + CCompartment* _pCompartmentConversion; + CCompartmentEventSink* _pCompartmentConversionEventSink; + CCompartmentEventSink* _pCompartmentKeyboardOpenEventSink; + CCompartmentEventSink* _pCompartmentDoubleSingleByteEventSink; + CCompartmentEventSink* _pCompartmentPunctuationEventSink; + + // Configuration data + BOOL _isWildcard : 1; + BOOL _isDisableWildcardAtFirst : 1; + BOOL _hasMakePhraseFromText : 1; + BOOL _isKeystrokeSort : 1; + BOOL _isComLessMode : 1; + CCandidateRange _candidateListIndexRange; + UINT _candidateListPhraseModifier; + UINT _candidateWndWidth; + + CFileMapping* _pDictionaryFile; + + static const int OUT_OF_FILE_INDEX = -1; +}; + diff --git a/Define.h b/Define.h new file mode 100644 index 00000000..2e7fe01a --- /dev/null +++ b/Define.h @@ -0,0 +1,48 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once +#include "resource.h" + +#define TEXTSERVICE_MODEL L"Apartment" +#define TEXTSERVICE_LANGID MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED) +#define TEXTSERVICE_ICON_INDEX -IDIS_SAMPLEIME +#define TEXTSERVICE_DIC L"SampleIMESimplifiedQuanPin.txt" + +#define IME_MODE_ON_ICON_INDEX IDI_IME_MODE_ON +#define IME_MODE_OFF_ICON_INDEX IDI_IME_MODE_OFF +#define IME_DOUBLE_ON_INDEX IDI_DOUBLE_SINGLE_BYTE_ON +#define IME_DOUBLE_OFF_INDEX IDI_DOUBLE_SINGLE_BYTE_OFF +#define IME_PUNCTUATION_ON_INDEX IDI_PUNCTUATION_ON +#define IME_PUNCTUATION_OFF_INDEX IDI_PUNCTUATION_OFF + +#define SAMPLEIME_FONT_DEFAULT L"Microsoft YaHei UI" + +//--------------------------------------------------------------------- +// defined Candidated Window +//--------------------------------------------------------------------- +#define CANDWND_ROW_WIDTH (30) +#define CANDWND_BORDER_COLOR (RGB(0x00, 0x00, 0x00)) +#define CANDWND_BORDER_WIDTH (2) +#define CANDWND_NUM_COLOR (RGB(0xB4, 0xB4, 0xB4)) +#define CANDWND_SELECTED_ITEM_COLOR (RGB(0xFF, 0xFF, 0xFF)) +#define CANDWND_SELECTED_BK_COLOR (RGB(0xA6, 0xA6, 0x00)) +#define CANDWND_ITEM_COLOR (RGB(0x00, 0x00, 0x00)) + +//--------------------------------------------------------------------- +// defined modifier +//--------------------------------------------------------------------- +#define _TF_MOD_ON_KEYUP_SHIFT_ONLY (0x00010000 | TF_MOD_ON_KEYUP) +#define _TF_MOD_ON_KEYUP_CONTROL_ONLY (0x00020000 | TF_MOD_ON_KEYUP) +#define _TF_MOD_ON_KEYUP_ALT_ONLY (0x00040000 | TF_MOD_ON_KEYUP) + +#define CAND_WIDTH (13) // * tmMaxCharWidth + +//--------------------------------------------------------------------- +// string length of CLSID +//--------------------------------------------------------------------- +#define CLSID_STRLEN (38) // strlen("{xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}") \ No newline at end of file diff --git a/Dictionary/.gitattributes b/Dictionary/.gitattributes new file mode 100644 index 00000000..c7f878dc --- /dev/null +++ b/Dictionary/.gitattributes @@ -0,0 +1,3 @@ +# The *.txt files in this directory are UTF-16-encoded and should +# therefore be treated as pure binary data. +*.txt binary diff --git a/Dictionary/SampleIMESimplifiedQuanPin.txt b/Dictionary/SampleIMESimplifiedQuanPin.txt new file mode 100644 index 0000000000000000000000000000000000000000..f73046fa01b3ac89963615420c042d134a81f77d GIT binary patch literal 1574528 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 BiiD!Z`{)4PM(u*XDmwARqXLC{%dBte%X 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%0AyY1eBT2}{c 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%0D{nOr+Z-f_DV*t_^zHhNLW1Zcx*{p7X-E>k1d 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 EKkqqHH2?qr literal 0 HcmV?d00001 diff --git a/DictionaryParser.cpp b/DictionaryParser.cpp new file mode 100644 index 00000000..9c128691 --- /dev/null +++ b/DictionaryParser.cpp @@ -0,0 +1,222 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "DictionaryParser.h" +#include "SampleIMEBaseStructure.h" + +//--------------------------------------------------------------------- +// +// ctor +// +//--------------------------------------------------------------------- + +CDictionaryParser::CDictionaryParser(LCID locale) +{ + _locale = locale; +} + +//--------------------------------------------------------------------- +// +// dtor +// +//--------------------------------------------------------------------- + +CDictionaryParser::~CDictionaryParser() +{ +} + +//--------------------------------------------------------------------- +// +// ParseLine +// +// dwBufLen - in character count +// +//--------------------------------------------------------------------- + +BOOL CDictionaryParser::ParseLine(_In_reads_(dwBufLen) LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _Out_ CParserStringRange *psrgKeyword, _Inout_opt_ CSampleImeArray *pValue) +{ + LPCWSTR pwszKeyWordDelimiter = nullptr; + pwszKeyWordDelimiter = GetToken(pwszBuffer, dwBufLen, Global::KeywordDelimiter, psrgKeyword); + if (!(pwszKeyWordDelimiter)) + { + return FALSE; // End of file + } + + dwBufLen -= (pwszKeyWordDelimiter - pwszBuffer); + pwszBuffer = pwszKeyWordDelimiter + 1; + dwBufLen--; + + // Get value. + if (pValue) + { + if (dwBufLen) + { + CParserStringRange* psrgValue = pValue->Append(); + if (!psrgValue) + { + return FALSE; + } + psrgValue->Set(pwszBuffer, dwBufLen); + RemoveWhiteSpaceFromBegin(psrgValue); + RemoveWhiteSpaceFromEnd(psrgValue); + RemoveStringDelimiter(psrgValue); + } + } + + return TRUE; +} + +//--------------------------------------------------------------------- +// +// GetToken +// +// dwBufLen - in character count +// +// return - pointer of delimiter which specified chDelimiter +// +//--------------------------------------------------------------------- +_Ret_maybenull_ +LPCWSTR CDictionaryParser::GetToken(_In_reads_(dwBufLen) LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _In_ const WCHAR chDelimiter, _Out_ CParserStringRange *psrgValue) +{ + WCHAR ch = '\0'; + + psrgValue->Set(pwszBuffer, dwBufLen); + + ch = *pwszBuffer; + while ((ch) && (ch != chDelimiter) && dwBufLen) + { + dwBufLen--; + pwszBuffer++; + + if (ch == Global::StringDelimiter) + { + while (*pwszBuffer && (*pwszBuffer != Global::StringDelimiter) && dwBufLen) + { + dwBufLen--; + pwszBuffer++; + } + if (*pwszBuffer && dwBufLen) + { + dwBufLen--; + pwszBuffer++; + } + else + { + return nullptr; + } + } + ch = *pwszBuffer; + } + + if (*pwszBuffer && dwBufLen) + { + LPCWSTR pwszStart = psrgValue->Get(); + + psrgValue->Set(pwszStart, pwszBuffer - pwszStart); + + RemoveWhiteSpaceFromBegin(psrgValue); + RemoveWhiteSpaceFromEnd(psrgValue); + RemoveStringDelimiter(psrgValue); + + return pwszBuffer; + } + + RemoveWhiteSpaceFromBegin(psrgValue); + RemoveWhiteSpaceFromEnd(psrgValue); + RemoveStringDelimiter(psrgValue); + + return nullptr; +} + +//--------------------------------------------------------------------- +// +// RemoveWhiteSpaceFromBegin +// RemoveWhiteSpaceFromEnd +// RemoveStringDelimiter +// +//--------------------------------------------------------------------- + +BOOL CDictionaryParser::RemoveWhiteSpaceFromBegin(_Inout_opt_ CStringRange *pString) +{ + DWORD_PTR dwIndexTrace = 0; // in char + + if (pString == nullptr) + { + return FALSE; + } + + if (SkipWhiteSpace(_locale, pString->Get(), pString->GetLength(), &dwIndexTrace) != S_OK) + { + return FALSE; + } + + pString->Set(pString->Get() + dwIndexTrace, pString->GetLength() - dwIndexTrace); + return TRUE; +} + +BOOL CDictionaryParser::RemoveWhiteSpaceFromEnd(_Inout_opt_ CStringRange *pString) +{ + if (pString == nullptr) + { + return FALSE; + } + + DWORD_PTR dwTotalBufLen = pString->GetLength(); + LPCWSTR pwszEnd = pString->Get() + dwTotalBufLen - 1; + + while (dwTotalBufLen && (IsSpace(_locale, *pwszEnd) || *pwszEnd == L'\r' || *pwszEnd == L'\n')) + { + pwszEnd--; + dwTotalBufLen--; + } + + pString->Set(pString->Get(), dwTotalBufLen); + return TRUE; +} + +BOOL CDictionaryParser::RemoveStringDelimiter(_Inout_opt_ CStringRange *pString) +{ + if (pString == nullptr) + { + return FALSE; + } + + if (pString->GetLength() >= 2) + { + if ((*pString->Get() == Global::StringDelimiter) && (*(pString->Get()+pString->GetLength()-1) == Global::StringDelimiter)) + { + pString->Set(pString->Get()+1, pString->GetLength()-2); + return TRUE; + } + } + + return FALSE; +} + +//--------------------------------------------------------------------- +// +// GetOneLine +// +// dwBufLen - in character count +// +//--------------------------------------------------------------------- + +DWORD_PTR CDictionaryParser::GetOneLine(_In_z_ LPCWSTR pwszBuffer, DWORD_PTR dwBufLen) +{ + DWORD_PTR dwIndexTrace = 0; // in char + + if (FAILED(FindChar(L'\r', pwszBuffer, dwBufLen, &dwIndexTrace))) + { + if (FAILED(FindChar(L'\0', pwszBuffer, dwBufLen, &dwIndexTrace))) + { + return dwBufLen; + } + } + + return dwIndexTrace; +} diff --git a/DictionaryParser.h b/DictionaryParser.h new file mode 100644 index 00000000..e943fc81 --- /dev/null +++ b/DictionaryParser.h @@ -0,0 +1,50 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "Globals.h" + +////////////////////////////////////////////////////////////////////// +// + +class CParserStringRange : public CStringRange +{ +public: + CParserStringRange() : CStringRange() + { + _fEscapeIncluded = FALSE; + } + + BOOL _fEscapeIncluded; // flag. This string range included escape sequence. +}; + +////////////////////////////////////////////////////////////////////// +// + +class CDictionaryParser +{ +public: + CDictionaryParser(LCID locale); + virtual ~CDictionaryParser(); + + BOOL ParseLine(_In_reads_(dwBufLen) LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _Out_ CParserStringRange *psrgKeyword, _Inout_opt_ CSampleImeArray *pValue = nullptr); + + // dwBufLen - in character count + _Ret_maybenull_ + LPCWSTR GetToken(_In_reads_(dwBufLen) LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _In_ const WCHAR chDelimiter, _Out_ CParserStringRange *srgKeyWord); + +protected: + BOOL RemoveWhiteSpaceFromBegin(_Inout_opt_ CStringRange *pString); + BOOL RemoveWhiteSpaceFromEnd(_Inout_opt_ CStringRange *pString); + BOOL RemoveStringDelimiter(_Inout_opt_ CStringRange *pString); + + DWORD_PTR GetOneLine(_In_z_ LPCWSTR pwszBuffer, DWORD_PTR dwBufLen); + + LCID _locale; // used for CompareString +}; diff --git a/DictionarySearch.cpp b/DictionarySearch.cpp new file mode 100644 index 00000000..a97a1389 --- /dev/null +++ b/DictionarySearch.cpp @@ -0,0 +1,250 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "DictionarySearch.h" +#include "SampleIMEBaseStructure.h" + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CDictionarySearch::CDictionarySearch(LCID locale, _In_ CFile *pFile, _In_ CStringRange *pSearchKeyCode) : CDictionaryParser(locale) +{ + _pFile = pFile; + _pSearchKeyCode = pSearchKeyCode; + _charIndex = 0; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CDictionarySearch::~CDictionarySearch() +{ +} + +//+--------------------------------------------------------------------------- +// +// FindPhrase +// +//---------------------------------------------------------------------------- + +BOOL CDictionarySearch::FindPhrase(_Out_ CDictionaryResult **ppdret) +{ + return FindWorker(FALSE, ppdret, FALSE); // NO WILDCARD +} + +//+--------------------------------------------------------------------------- +// +// FindPhraseForWildcard +// +//---------------------------------------------------------------------------- + +BOOL CDictionarySearch::FindPhraseForWildcard(_Out_ CDictionaryResult **ppdret) +{ + return FindWorker(FALSE, ppdret, TRUE); // Wildcard +} + +//+--------------------------------------------------------------------------- +// +// FindConvertedStringForWildcard +// +//---------------------------------------------------------------------------- + +BOOL CDictionarySearch::FindConvertedStringForWildcard(CDictionaryResult **ppdret) +{ + return FindWorker(TRUE, ppdret, TRUE); // Wildcard +} + +//+--------------------------------------------------------------------------- +// +// FindWorker +// +//---------------------------------------------------------------------------- + +BOOL CDictionarySearch::FindWorker(BOOL isTextSearch, _Out_ CDictionaryResult **ppdret, BOOL isWildcardSearch) +{ + DWORD_PTR dwTotalBufLen = GetBufferInWCharLength(); // in char + if (dwTotalBufLen == 0) + { + return FALSE; + } + + const WCHAR *pwch = GetBufferInWChar(); + DWORD_PTR indexTrace = 0; // in char + *ppdret = nullptr; + BOOL isFound = FALSE; + DWORD_PTR bufLenOneLine = 0; + +TryAgain: + bufLenOneLine = GetOneLine(&pwch[indexTrace], dwTotalBufLen); + if (bufLenOneLine == 0) + { + goto FindNextLine; + } + else + { + CParserStringRange keyword; + DWORD_PTR bufLen = 0; + LPWSTR pText = nullptr; + + if (!ParseLine(&pwch[indexTrace], bufLenOneLine, &keyword)) + { + return FALSE; // error + } + + if (!isTextSearch) + { + // Compare Dictionary key code and input key code + if (!isWildcardSearch) + { + if (CStringRange::Compare(_locale, &keyword, _pSearchKeyCode) != CSTR_EQUAL) + { + if (bufLen) + { + delete [] pText; + } + goto FindNextLine; + } + } + else + { + // Wildcard search + if (!CStringRange::WildcardCompare(_locale, _pSearchKeyCode, &keyword)) + { + if (bufLen) + { + delete [] pText; + } + goto FindNextLine; + } + } + } + else + { + // Compare Dictionary converted string and input string + CSampleImeArray convertedStrings; + if (!ParseLine(&pwch[indexTrace], bufLenOneLine, &keyword, &convertedStrings)) + { + if (bufLen) + { + delete [] pText; + } + return FALSE; + } + if (convertedStrings.Count() == 1) + { + CStringRange* pTempString = convertedStrings.GetAt(0); + + if (!isWildcardSearch) + { + if (CStringRange::Compare(_locale, pTempString, _pSearchKeyCode) != CSTR_EQUAL) + { + if (bufLen) + { + delete [] pText; + } + goto FindNextLine; + } + } + else + { + // Wildcard search + if (!CStringRange::WildcardCompare(_locale, _pSearchKeyCode, pTempString)) + { + if (bufLen) + { + delete [] pText; + } + goto FindNextLine; + } + } + } + else + { + if (bufLen) + { + delete [] pText; + } + goto FindNextLine; + } + } + + if (bufLen) + { + delete [] pText; + } + + // Prepare return's CDictionaryResult + *ppdret = new (std::nothrow) CDictionaryResult(); + if (!*ppdret) + { + return FALSE; + } + + CSampleImeArray valueStrings; + if (!ParseLine(&pwch[indexTrace], bufLenOneLine, &keyword, &valueStrings)) + { + if (*ppdret) + { + delete *ppdret; + *ppdret = nullptr; + } + return FALSE; + } + + (*ppdret)->_FindKeyCode = keyword; + (*ppdret)->_SearchKeyCode = *_pSearchKeyCode; + + for (UINT i = 0; i < valueStrings.Count(); i++) + { + CStringRange* findPhrase = (*ppdret)->_FindPhraseList.Append(); + if (findPhrase) + { + *findPhrase = *valueStrings.GetAt(i); + } + } + + // Seek to next line + isFound = TRUE; + } + +FindNextLine: + dwTotalBufLen -= bufLenOneLine; + if (dwTotalBufLen == 0) + { + indexTrace += bufLenOneLine; + _charIndex += indexTrace; + + if (!isFound && *ppdret) + { + delete *ppdret; + *ppdret = nullptr; + } + return (isFound ? TRUE : FALSE); // End of file + } + + indexTrace += bufLenOneLine; + if (pwch[indexTrace] == L'\r' || pwch[indexTrace] == L'\n' || pwch[indexTrace] == L'\0') + { + bufLenOneLine = 1; + goto FindNextLine; + } + + if (isFound) + { + _charIndex += indexTrace; + return TRUE; + } + + goto TryAgain; +} diff --git a/DictionarySearch.h b/DictionarySearch.h new file mode 100644 index 00000000..20ac3d03 --- /dev/null +++ b/DictionarySearch.h @@ -0,0 +1,76 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +#include "File.h" +#include "DictionaryParser.h" +#include "SampleIMEBaseStructure.h" + +class CDictionaryResult; + +////////////////////////////////////////////////////////////////////// +// +// CDictionarySearch declaration. +// +////////////////////////////////////////////////////////////////////// + +class CDictionarySearch : CDictionaryParser +{ +public: + CDictionarySearch(LCID locale, _In_ CFile *pFile, _In_ CStringRange *pSearchKeyCode); + virtual ~CDictionarySearch(); + + BOOL FindPhrase(_Out_ CDictionaryResult **ppdret); + BOOL FindPhraseForWildcard(_Out_ CDictionaryResult **ppdret); + + BOOL FindConvertedStringForWildcard(CDictionaryResult **ppdret); + + CStringRange* _pSearchKeyCode; + + DWORD_PTR _charIndex; // in character. Always point start of line in dictionary file. + +private: + BOOL FindWorker(BOOL isTextSearch, _Out_ CDictionaryResult **ppdret, BOOL isWildcardSearch); + + DWORD_PTR GetBufferInWCharLength() + { + return (_pFile->GetFileSize() / sizeof(WCHAR)) - _charIndex; // in char count as a returned length. + } + + const WCHAR* GetBufferInWChar() + { + return _pFile->GetReadBufferPointer() + _charIndex; + } + + CFile* _pFile; +}; + +////////////////////////////////////////////////////////////////////// +// +// CDictionaryResult declaration. +// +////////////////////////////////////////////////////////////////////// + +class CDictionaryResult +{ +public: + CDictionaryResult() { } + virtual ~CDictionaryResult() { } + + CDictionaryResult& operator=(CDictionaryResult& dret) + { + _FindKeyCode = dret._FindKeyCode; + _FindPhraseList = dret._FindPhraseList; + return *this; + } + + CStringRange _SearchKeyCode; + CStringRange _FindKeyCode; + CSampleImeArray _FindPhraseList; +}; diff --git a/DisplayAttribute.cpp b/DisplayAttribute.cpp new file mode 100644 index 00000000..acc04dff --- /dev/null +++ b/DisplayAttribute.cpp @@ -0,0 +1,115 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "globals.h" +#include "SampleIME.h" + +//+--------------------------------------------------------------------------- +// +// _ClearCompositionDisplayAttributes +// +//---------------------------------------------------------------------------- + +void CSampleIME::_ClearCompositionDisplayAttributes(TfEditCookie ec, _In_ ITfContext *pContext) +{ + ITfRange* pRangeComposition = nullptr; + ITfProperty* pDisplayAttributeProperty = nullptr; + + // get the compositon range. + if (FAILED(_pComposition->GetRange(&pRangeComposition))) + { + return; + } + + // get our the display attribute property + if (SUCCEEDED(pContext->GetProperty(GUID_PROP_ATTRIBUTE, &pDisplayAttributeProperty))) + { + // clear the value over the range + pDisplayAttributeProperty->Clear(ec, pRangeComposition); + + pDisplayAttributeProperty->Release(); + } + + pRangeComposition->Release(); +} + +//+--------------------------------------------------------------------------- +// +// _SetCompositionDisplayAttributes +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_SetCompositionDisplayAttributes(TfEditCookie ec, _In_ ITfContext *pContext, TfGuidAtom gaDisplayAttribute) +{ + ITfRange* pRangeComposition = nullptr; + ITfProperty* pDisplayAttributeProperty = nullptr; + HRESULT hr = S_OK; + + // we need a range and the context it lives in + hr = _pComposition->GetRange(&pRangeComposition); + if (FAILED(hr)) + { + return FALSE; + } + + hr = E_FAIL; + + // get our the display attribute property + if (SUCCEEDED(pContext->GetProperty(GUID_PROP_ATTRIBUTE, &pDisplayAttributeProperty))) + { + VARIANT var; + // set the value over the range + // the application will use this guid atom to lookup the acutal rendering information + var.vt = VT_I4; // we're going to set a TfGuidAtom + var.lVal = gaDisplayAttribute; + + hr = pDisplayAttributeProperty->SetValue(ec, pRangeComposition, &var); + + pDisplayAttributeProperty->Release(); + } + + pRangeComposition->Release(); + return (hr == S_OK); +} + +//+--------------------------------------------------------------------------- +// +// _InitDisplayAttributeGuidAtom +// +// Because it's expensive to map our display attribute GUID to a TSF +// TfGuidAtom, we do it once when Activate is called. +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_InitDisplayAttributeGuidAtom() +{ + ITfCategoryMgr* pCategoryMgr = nullptr; + HRESULT hr = CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, (void**)&pCategoryMgr); + + if (FAILED(hr)) + { + return FALSE; + } + + // register the display attribute for input text. + hr = pCategoryMgr->RegisterGUID(Global::SampleIMEGuidDisplayAttributeInput, &_gaDisplayAttributeInput); + if (FAILED(hr)) + { + goto Exit; + } + // register the display attribute for the converted text. + hr = pCategoryMgr->RegisterGUID(Global::SampleIMEGuidDisplayAttributeConverted, &_gaDisplayAttributeConverted); + if (FAILED(hr)) + { + goto Exit; + } + +Exit: + pCategoryMgr->Release(); + + return (hr == S_OK); +} diff --git a/DisplayAttributeInfo.cpp b/DisplayAttributeInfo.cpp new file mode 100644 index 00000000..d394594c --- /dev/null +++ b/DisplayAttributeInfo.cpp @@ -0,0 +1,227 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "globals.h" +#include "DisplayAttributeInfo.h" +#include "TfInputProcessorProfile.h" + +//+--------------------------------------------------------------------------- +// +// The registry key and values +// +//---------------------------------------------------------------------------- + +// the registry values of the custmized display attributes +const WCHAR CDisplayAttributeInfoInput::_s_szValueName[] = L"DisplayAttributeInput"; +const WCHAR CDisplayAttributeInfoConverted::_s_szValueName[] = L"DisplayAttributeConverted"; + +// The descriptions +const WCHAR CDisplayAttributeInfoInput::_s_szDescription[] = L"Sample IME Text Service Display Attribute Input"; +const WCHAR CDisplayAttributeInfoConverted::_s_szDescription[] = L"Sample IME Text Service Display Attribute Converted"; + +//+--------------------------------------------------------------------------- +// +// DisplayAttribute +// +//---------------------------------------------------------------------------- + +const TF_DISPLAYATTRIBUTE CDisplayAttributeInfoInput::_s_DisplayAttribute = +{ + { TF_CT_COLORREF, RGB(0, 103,206) }, // text color + { TF_CT_NONE, 0 }, // background color (TF_CT_NONE => app default) + TF_LS_DOT, // underline style + FALSE, // underline boldness + { TF_CT_COLORREF, RGB(0, 103,206) }, // underline color + TF_ATTR_INPUT // attribute info +}; + +const TF_DISPLAYATTRIBUTE CDisplayAttributeInfoConverted::_s_DisplayAttribute = +{ + { TF_CT_COLORREF, RGB(255, 255, 255) }, // text color + { TF_CT_COLORREF, RGB( 0, 255, 255) }, // background color (TF_CT_NONE => app default) + TF_LS_NONE, // underline style + FALSE, // underline boldness + { TF_CT_NONE, 0 }, // underline color + TF_ATTR_TARGET_CONVERTED // attribute info +}; + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CDisplayAttributeInfo::CDisplayAttributeInfo() +{ + DllAddRef(); + + _pguid = nullptr; + _pDisplayAttribute = nullptr; + _pValueName = nullptr; + + _refCount = 1; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CDisplayAttributeInfo::~CDisplayAttributeInfo() +{ + DllRelease(); +} + +//+--------------------------------------------------------------------------- +// +// QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CDisplayAttributeInfo::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + return E_INVALIDARG; + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfDisplayAttributeInfo)) + { + *ppvObj = (ITfDisplayAttributeInfo *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//+--------------------------------------------------------------------------- +// +// AddRef +// +//---------------------------------------------------------------------------- + +ULONG CDisplayAttributeInfo::AddRef(void) +{ + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// Release +// +//---------------------------------------------------------------------------- + +ULONG CDisplayAttributeInfo::Release(void) +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeInfo::GetGUID +// +//---------------------------------------------------------------------------- + +STDAPI CDisplayAttributeInfo::GetGUID(_Out_ GUID *pguid) +{ + if (pguid == nullptr) + return E_INVALIDARG; + + if (_pguid == nullptr) + return E_FAIL; + + *pguid = *_pguid; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeInfo::GetDescription +// +//---------------------------------------------------------------------------- + +STDAPI CDisplayAttributeInfo::GetDescription(_Out_ BSTR *pbstrDesc) +{ + BSTR tempDesc; + + if (pbstrDesc == nullptr) + { + return E_INVALIDARG; + } + + *pbstrDesc = nullptr; + + if ((tempDesc = SysAllocString(_pDescription)) == nullptr) + { + return E_OUTOFMEMORY; + } + + *pbstrDesc = tempDesc; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeInfo::GetAttributeInfo +// +//---------------------------------------------------------------------------- + +STDAPI CDisplayAttributeInfo::GetAttributeInfo(_Out_ TF_DISPLAYATTRIBUTE *ptfDisplayAttr) +{ + if (ptfDisplayAttr == nullptr) + { + return E_INVALIDARG; + } + + // return the default display attribute. + *ptfDisplayAttr = *_pDisplayAttribute; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeInfo::SetAttributeInfo +// +//---------------------------------------------------------------------------- + +STDAPI CDisplayAttributeInfo::SetAttributeInfo(_In_ const TF_DISPLAYATTRIBUTE *ptfDisplayAttr) +{ + ptfDisplayAttr; + + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeInfo::Reset +// +//---------------------------------------------------------------------------- + +STDAPI CDisplayAttributeInfo::Reset() +{ + return SetAttributeInfo(_pDisplayAttribute); +} diff --git a/DisplayAttributeInfo.h b/DisplayAttributeInfo.h new file mode 100644 index 00000000..441a11ca --- /dev/null +++ b/DisplayAttributeInfo.h @@ -0,0 +1,87 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +//+--------------------------------------------------------------------------- +// +// CDisplayAttributeInfo class +// +//---------------------------------------------------------------------------- + +class CDisplayAttributeInfo : public ITfDisplayAttributeInfo +{ +public: + CDisplayAttributeInfo(); + ~CDisplayAttributeInfo(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfDisplayAttributeInfo + STDMETHODIMP GetGUID(_Out_ GUID *pguid); + STDMETHODIMP GetDescription(_Out_ BSTR *pbstrDesc); + STDMETHODIMP GetAttributeInfo(_Out_ TF_DISPLAYATTRIBUTE *pTSFDisplayAttr); + STDMETHODIMP SetAttributeInfo(_In_ const TF_DISPLAYATTRIBUTE *ptfDisplayAttr); + STDMETHODIMP Reset(); + +protected: + const GUID* _pguid; + const TF_DISPLAYATTRIBUTE* _pDisplayAttribute; + const WCHAR* _pDescription; + const WCHAR* _pValueName; + +private: + LONG _refCount; // COM ref count +}; + +//+--------------------------------------------------------------------------- +// +// CDisplayAttributeInfoInput class +// +//---------------------------------------------------------------------------- + +class CDisplayAttributeInfoInput : public CDisplayAttributeInfo +{ +public: + CDisplayAttributeInfoInput() + { + _pguid = &Global::SampleIMEGuidDisplayAttributeInput; + _pDisplayAttribute = &_s_DisplayAttribute; + _pDescription = _s_szDescription; + _pValueName = _s_szValueName; + } + + static const TF_DISPLAYATTRIBUTE _s_DisplayAttribute; + static const WCHAR _s_szDescription[]; + static const WCHAR _s_szValueName[]; +}; + +//+--------------------------------------------------------------------------- +// +// CDisplayAttributeInfoConverted class +// +//---------------------------------------------------------------------------- + +class CDisplayAttributeInfoConverted : public CDisplayAttributeInfo +{ +public: + CDisplayAttributeInfoConverted() + { + _pguid = &Global::SampleIMEGuidDisplayAttributeConverted; + _pDisplayAttribute = &_s_DisplayAttribute; + _pDescription = _s_szDescription; + _pValueName = _s_szValueName; + } + + static const TF_DISPLAYATTRIBUTE _s_DisplayAttribute; + static const WCHAR _s_szDescription[]; + static const WCHAR _s_szValueName[]; +}; diff --git a/DisplayAttributeProvider.cpp b/DisplayAttributeProvider.cpp new file mode 100644 index 00000000..81e266fb --- /dev/null +++ b/DisplayAttributeProvider.cpp @@ -0,0 +1,81 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "globals.h" +#include "SampleIME.h" +#include "DisplayAttributeInfo.h" +#include "EnumDisplayAttributeInfo.h" + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeProvider::EnumDisplayAttributeInfo +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::EnumDisplayAttributeInfo(__RPC__deref_out_opt IEnumTfDisplayAttributeInfo **ppEnum) +{ + CEnumDisplayAttributeInfo* pAttributeEnum = nullptr; + + if (ppEnum == nullptr) + { + return E_INVALIDARG; + } + + *ppEnum = nullptr; + + pAttributeEnum = new (std::nothrow) CEnumDisplayAttributeInfo(); + if (pAttributeEnum == nullptr) + { + return E_OUTOFMEMORY; + } + + *ppEnum = pAttributeEnum; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfDisplayAttributeProvider::GetDisplayAttributeInfo +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::GetDisplayAttributeInfo(__RPC__in REFGUID guidInfo, __RPC__deref_out_opt ITfDisplayAttributeInfo **ppInfo) +{ + if (ppInfo == nullptr) + { + return E_INVALIDARG; + } + + *ppInfo = nullptr; + + // Which display attribute GUID? + if (IsEqualGUID(guidInfo, Global::SampleIMEGuidDisplayAttributeInput)) + { + *ppInfo = new (std::nothrow) CDisplayAttributeInfoInput(); + if ((*ppInfo) == nullptr) + { + return E_OUTOFMEMORY; + } + } + else if (IsEqualGUID(guidInfo, Global::SampleIMEGuidDisplayAttributeConverted)) + { + *ppInfo = new (std::nothrow) CDisplayAttributeInfoConverted(); + if ((*ppInfo) == nullptr) + { + return E_OUTOFMEMORY; + } + } + else + { + return E_INVALIDARG; + } + + + return S_OK; +} diff --git a/DisplayString.h b/DisplayString.h new file mode 100644 index 00000000..2036bf84 --- /dev/null +++ b/DisplayString.h @@ -0,0 +1,43 @@ +////////////////////////////////////////////////////////////////////// +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// +// DisplayString.h +// +// CDisplayString declaration. +// +////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "StringRange.h" +#include "PointerArray.h" + +class CDisplayString +{ +public: + CDisplayString() { } + ~CDisplayString() { } + + int Count() { } + // VOID SetDisplayString(int iIndex, WCHAR* pchText, USHORT cchMax, TF_DISPLAYATTRIBUTE tfDisplayAttribute) { } + // VOID GetDisplayString(int iIndex, WCHAR* pchText, USHORT cchMax, USHORT* pch, TF_DISPLAYATTRIBUTE tfDisplayAttribute) { } + VOID SetLogicalFont(LOGFONTW LogFont) { } + VOID GetLogicalFont(LOGFONTW* pLogFont) { } + +private: + //typedef struct _DISPLAY_STRING { + // CStringRange _StringRange; // Unicode string. + // // Length and MaximumLength is in character count. + // // Buffer doesn't add zero terminate. + // TF_DISPLAYATTRIBUTE _tfDisplayAttribute; // Display attribute for each array. + //} DISPLAY_STRING; + + // + // Array of DISPLAY_STRING + // + //CPointerArray _pDisplayString; + + // Logical font + LOGFONTW _logfont; +}; diff --git a/DllMain.cpp b/DllMain.cpp new file mode 100644 index 00000000..c3a48751 --- /dev/null +++ b/DllMain.cpp @@ -0,0 +1,54 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" + +//+--------------------------------------------------------------------------- +// +// DllMain +// +//---------------------------------------------------------------------------- + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID pvReserved) +{ + pvReserved; + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + + Global::dllInstanceHandle = hInstance; + + if (!InitializeCriticalSectionAndSpinCount(&Global::CS, 0)) + { + return FALSE; + } + + if (!Global::RegisterWindowClass()) { + return FALSE; + } + + break; + + case DLL_PROCESS_DETACH: + + DeleteCriticalSection(&Global::CS); + + break; + + case DLL_THREAD_ATTACH: + + break; + + case DLL_THREAD_DETACH: + + break; + } + + return TRUE; +} diff --git a/EditSession.cpp b/EditSession.cpp new file mode 100644 index 00000000..0e8830b5 --- /dev/null +++ b/EditSession.cpp @@ -0,0 +1,99 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "EditSession.h" +#include "SampleIME.h" + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CEditSessionBase::CEditSessionBase(_In_ CSampleIME *pTextService, _In_ ITfContext *pContext) +{ + _refCount = 1; + _pContext = pContext; + _pContext->AddRef(); + + _pTextService = pTextService; + _pTextService->AddRef(); +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CEditSessionBase::~CEditSessionBase() +{ + _pContext->Release(); + _pTextService->Release(); +} + +//+--------------------------------------------------------------------------- +// +// QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CEditSessionBase::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfEditSession)) + { + *ppvObj = (ITfLangBarItemButton *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//+--------------------------------------------------------------------------- +// +// AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CEditSessionBase::AddRef(void) +{ + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CEditSessionBase::Release(void) +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} diff --git a/EditSession.h b/EditSession.h new file mode 100644 index 00000000..23c0fd9c --- /dev/null +++ b/EditSession.h @@ -0,0 +1,33 @@ +// 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) Microsoft Corporation. All rights reserved + + +#pragma once + +class CSampleIME; + +class CEditSessionBase : public ITfEditSession +{ +public: + CEditSessionBase(_In_ CSampleIME *pTextService, _In_ ITfContext *pContext); + virtual ~CEditSessionBase(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfEditSession + virtual STDMETHODIMP DoEditSession(TfEditCookie ec) = 0; + +protected: + ITfContext *_pContext; + CSampleIME *_pTextService; + +private: + LONG _refCount; // COM ref count +}; diff --git a/EndComposition.cpp b/EndComposition.cpp new file mode 100644 index 00000000..1f037cdb --- /dev/null +++ b/EndComposition.cpp @@ -0,0 +1,98 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "EditSession.h" +#include "SampleIME.h" + +////////////////////////////////////////////////////////////////////// +// +// ITfEditSession +// CEditSessionBase +// CEndCompositionEditSession class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// CEndCompositionEditSession +// +//---------------------------------------------------------------------------- + +class CEndCompositionEditSession : public CEditSessionBase +{ +public: + CEndCompositionEditSession(_In_ CSampleIME *pTextService, _In_ ITfContext *pContext) : CEditSessionBase(pTextService, pContext) + { + } + + // ITfEditSession + STDMETHODIMP DoEditSession(TfEditCookie ec) + { + _pTextService->_TerminateComposition(ec, _pContext, TRUE); + return S_OK; + } + +}; + +////////////////////////////////////////////////////////////////////// +// +// CSampleIME class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// _TerminateComposition +// +//---------------------------------------------------------------------------- + +void CSampleIME::_TerminateComposition(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isCalledFromDeactivate) +{ + isCalledFromDeactivate; + + if (_pComposition != nullptr) + { + // remove the display attribute from the composition range. + _ClearCompositionDisplayAttributes(ec, pContext); + + if (FAILED(_pComposition->EndComposition(ec))) + { + // if we fail to EndComposition, then we need to close the reverse reading window. + _DeleteCandidateList(TRUE, pContext); + } + + _pComposition->Release(); + _pComposition = nullptr; + + if (_pContext) + { + _pContext->Release(); + _pContext = nullptr; + } + } +} + +//+--------------------------------------------------------------------------- +// +// _EndComposition +// +//---------------------------------------------------------------------------- + +void CSampleIME::_EndComposition(_In_opt_ ITfContext *pContext) +{ + CEndCompositionEditSession *pEditSession = new (std::nothrow) CEndCompositionEditSession(this, pContext); + HRESULT hr = S_OK; + + if (nullptr != pEditSession) + { + pContext->RequestEditSession(_tfClientId, pEditSession, TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &hr); + pEditSession->Release(); + } +} + diff --git a/EnumDisplayAttributeInfo.cpp b/EnumDisplayAttributeInfo.cpp new file mode 100644 index 00000000..d3b5915b --- /dev/null +++ b/EnumDisplayAttributeInfo.cpp @@ -0,0 +1,226 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "globals.h" +#include "SampleIME.h" +#include "DisplayAttributeInfo.h" +#include "EnumDisplayAttributeInfo.h" + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CEnumDisplayAttributeInfo::CEnumDisplayAttributeInfo() +{ + DllAddRef(); + + _index = 0; + _refCount = 1; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CEnumDisplayAttributeInfo::~CEnumDisplayAttributeInfo() +{ + DllRelease(); +} + +//+--------------------------------------------------------------------------- +// +// QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CEnumDisplayAttributeInfo::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + return E_INVALIDARG; + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_IEnumTfDisplayAttributeInfo)) + { + *ppvObj = (IEnumTfDisplayAttributeInfo *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + + +//+--------------------------------------------------------------------------- +// +// AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CEnumDisplayAttributeInfo::AddRef() +{ + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CEnumDisplayAttributeInfo::Release() +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// IEnumTfDisplayAttributeInfo::Clone +// +// Returns a copy of the object. +//---------------------------------------------------------------------------- + +STDAPI CEnumDisplayAttributeInfo::Clone(_Out_ IEnumTfDisplayAttributeInfo **ppEnum) +{ + CEnumDisplayAttributeInfo* pClone = nullptr; + + if (ppEnum == nullptr) + { + return E_INVALIDARG; + } + + *ppEnum = nullptr; + + pClone = new (std::nothrow) CEnumDisplayAttributeInfo(); + if ((pClone) == nullptr) + { + return E_OUTOFMEMORY; + } + + // the clone should match this object's state + pClone->_index = _index; + + *ppEnum = pClone; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// IEnumTfDisplayAttributeInfo::Next +// +// Returns an array of display attribute info objects supported by this service. +//---------------------------------------------------------------------------- + +const int MAX_DISPLAY_ATTRIBUTE_INFO = 2; + +STDAPI CEnumDisplayAttributeInfo::Next(ULONG ulCount, __RPC__out_ecount_part(ulCount, *pcFetched) ITfDisplayAttributeInfo **rgInfo, __RPC__out ULONG *pcFetched) +{ + ULONG fetched; + + fetched = 0; + + if (ulCount == 0) + { + return S_OK; + } + if (rgInfo == nullptr) + { + return E_INVALIDARG; + } + *rgInfo = nullptr; + + while (fetched < ulCount) + { + ITfDisplayAttributeInfo* pDisplayAttributeInfo = nullptr; + + if (_index == 0) + { + pDisplayAttributeInfo = new (std::nothrow) CDisplayAttributeInfoInput(); + if ((pDisplayAttributeInfo) == nullptr) + { + return E_OUTOFMEMORY; + } + } + else if (_index == 1) + { + pDisplayAttributeInfo = new (std::nothrow) CDisplayAttributeInfoConverted(); + if ((pDisplayAttributeInfo) == nullptr) + { + return E_OUTOFMEMORY; + } + + } + else + { + break; + } + + *rgInfo = pDisplayAttributeInfo; + rgInfo++; + fetched++; + _index++; + } + + if (pcFetched != nullptr) + { + // technically this is only legal if ulCount == 1, but we won't check + *pcFetched = fetched; + } + + return (fetched == ulCount) ? S_OK : S_FALSE; +} + +//+--------------------------------------------------------------------------- +// +// IEnumTfDisplayAttributeInfo::Reset +// +// Resets the enumeration. +//---------------------------------------------------------------------------- + +STDAPI CEnumDisplayAttributeInfo::Reset() +{ + _index = 0; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// IEnumTfDisplayAttributeInfo::Skip +// +// Skips past objects in the enumeration. +//---------------------------------------------------------------------------- + +STDAPI CEnumDisplayAttributeInfo::Skip(ULONG ulCount) +{ + if ((ulCount + _index) > MAX_DISPLAY_ATTRIBUTE_INFO || (ulCount + _index) < ulCount) + { + _index = MAX_DISPLAY_ATTRIBUTE_INFO; + return S_FALSE; + } + _index += ulCount; + return S_OK; +} diff --git a/EnumDisplayAttributeInfo.h b/EnumDisplayAttributeInfo.h new file mode 100644 index 00000000..fafc2040 --- /dev/null +++ b/EnumDisplayAttributeInfo.h @@ -0,0 +1,36 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +//+--------------------------------------------------------------------------- +// +// CEnumDisplayAttributeInfo +// +//---------------------------------------------------------------------------- + +class CEnumDisplayAttributeInfo : public IEnumTfDisplayAttributeInfo +{ +public: + CEnumDisplayAttributeInfo(); + ~CEnumDisplayAttributeInfo(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // IEnumTfDisplayAttributeInfo + STDMETHODIMP Clone(_Out_ IEnumTfDisplayAttributeInfo **ppEnum); + STDMETHODIMP Next(ULONG ulCount, __RPC__out_ecount_part(ulCount, *pcFetched) ITfDisplayAttributeInfo **rgInfo, __RPC__out ULONG *pcFetched); + STDMETHODIMP Reset(); + STDMETHODIMP Skip(ULONG ulCount); + +private: + LONG _index; // next display attribute to enum + LONG _refCount; // COM ref count +}; diff --git a/EnumTfCandidates.cpp b/EnumTfCandidates.cpp new file mode 100644 index 00000000..f0270881 --- /dev/null +++ b/EnumTfCandidates.cpp @@ -0,0 +1,146 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "private.h" +#include "EnumTfCandidates.h" + +HRESULT CEnumTfCandidates::CreateInstance(_Out_ CEnumTfCandidates **ppobj, _In_ const CSampleImeArray &rgelm, UINT currentNum) +{ + if (ppobj == nullptr) + { + return E_INVALIDARG; + } + *ppobj = nullptr; + + *ppobj = new (std::nothrow) CEnumTfCandidates(rgelm, currentNum); + if (*ppobj == nullptr) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT CEnumTfCandidates::CreateInstance(REFIID riid, _Out_ void **ppvObj, _In_ const CSampleImeArray &rgelm, UINT currentNum) +{ + if (ppvObj == nullptr) + { + return E_POINTER; + } + *ppvObj = nullptr; + + *ppvObj = new (std::nothrow) CEnumTfCandidates(rgelm, currentNum); + if (*ppvObj == nullptr) + { + return E_OUTOFMEMORY; + } + + return ((CEnumTfCandidates*)(*ppvObj))->QueryInterface(riid, ppvObj); +} + +CEnumTfCandidates::CEnumTfCandidates(_In_ const CSampleImeArray &rgelm, UINT currentNum) +{ + _refCount = 0; + _rgelm = rgelm; + _currentCandidateStrIndex = currentNum; +} + +CEnumTfCandidates::~CEnumTfCandidates() +{ +} + +// +// IUnknown methods +// +STDMETHODIMP CEnumTfCandidates::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_POINTER; + } + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, __uuidof(IEnumTfCandidates))) + { + *ppvObj = (IEnumTfCandidates*)this; + } + + if (*ppvObj == nullptr) + { + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) CEnumTfCandidates::AddRef() +{ + return (ULONG)InterlockedIncrement(&_refCount); +} + +STDMETHODIMP_(ULONG) CEnumTfCandidates::Release() +{ + ULONG cRef = (ULONG)InterlockedDecrement(&_refCount); + if (0 < cRef) + { + return cRef; + } + + delete this; + return 0; +} + + +// +// IEnumTfCandidates methods +// +STDMETHODIMP CEnumTfCandidates::Next(ULONG ulCount, _Out_ ITfCandidateString **ppObj, _Out_ ULONG *pcFetched) +{ + ULONG fetched = 0; + if (ppObj == nullptr) + { + return E_INVALIDARG; + } + *ppObj = nullptr; + + while ((fetched < ulCount) && (_currentCandidateStrIndex < _rgelm.Count())) + { + *ppObj = *_rgelm.GetAt(_currentCandidateStrIndex); + _currentCandidateStrIndex++; + fetched++; + } + + if (pcFetched) + { + *pcFetched = fetched; + } + + return (fetched == ulCount) ? S_OK : S_FALSE; +} + +STDMETHODIMP CEnumTfCandidates::Skip(ULONG ulCount) +{ + while ((0 < ulCount) && (_currentCandidateStrIndex < _rgelm.Count())) + { + _currentCandidateStrIndex++; + ulCount--; + } + + return (0 < ulCount) ? S_FALSE : S_OK; +} + +STDMETHODIMP CEnumTfCandidates::Reset() +{ + _currentCandidateStrIndex = 0; + return S_OK; +} + +STDMETHODIMP CEnumTfCandidates::Clone(_Out_ IEnumTfCandidates **ppEnum) +{ + return CreateInstance(__uuidof(IEnumTfCandidates), (void**)ppEnum, _rgelm, _currentCandidateStrIndex); +} \ No newline at end of file diff --git a/EnumTfCandidates.h b/EnumTfCandidates.h new file mode 100644 index 00000000..5a9d6ddf --- /dev/null +++ b/EnumTfCandidates.h @@ -0,0 +1,40 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "Globals.h" + +class CEnumTfCandidates : public IEnumTfCandidates +{ +protected: + // constructor/destructor + CEnumTfCandidates(_In_ const CSampleImeArray &rgelm, UINT currentNum); + + virtual ~CEnumTfCandidates(void); + +public: + // create instance + static HRESULT CreateInstance(_Out_ CEnumTfCandidates **ppobj, _In_ const CSampleImeArray &rgelm, UINT currentNum = 0); + static HRESULT CreateInstance(REFIID riid, _Out_ void **ppvObj, _In_ const CSampleImeArray &rgelm, UINT currentNum = 0); + + // IUnknown methods + virtual STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + virtual STDMETHODIMP_(ULONG) AddRef(void); + virtual STDMETHODIMP_(ULONG) Release(void); + + // IEnumTfCandidates methods + virtual STDMETHODIMP Next(ULONG ulCount, _Out_ ITfCandidateString **ppObj, _Out_ ULONG *pcFetched); + virtual STDMETHODIMP Skip(ULONG ulCount); + virtual STDMETHODIMP Reset(); + virtual STDMETHODIMP Clone(_Out_ IEnumTfCandidates **ppEnum); + +protected: + LONG _refCount; + CSampleImeArray _rgelm; + UINT _currentCandidateStrIndex; +}; diff --git a/File.cpp b/File.cpp new file mode 100644 index 00000000..59f1d137 --- /dev/null +++ b/File.cpp @@ -0,0 +1,254 @@ +// 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) Microsoft Corporation. All rights reserved + +#ifndef UNICODE +#define UNICODE +#endif // !UNICODE + +#ifndef _UNICODE +#define _UNICODE +#endif // !UNICODE + +#include "Private.h" +#include "File.h" +#include "SampleIMEBaseStructure.h" + +//--------------------------------------------------------------------- +// +// ctor +// +//--------------------------------------------------------------------- + +CFile::CFile(UINT codePage) +{ + _codePage = codePage; + _fileHandle = nullptr; + _pReadBuffer = nullptr; + _fileSize = 0; + _filePosPointer = 0; + _pFileName = nullptr; +} + +//--------------------------------------------------------------------- +// +// dtor +// +//--------------------------------------------------------------------- + +CFile::~CFile() +{ + if (_pReadBuffer) + { + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + } + if (_fileHandle) + { + CloseHandle(_fileHandle); + _fileHandle = nullptr; + } + if (_pFileName) + { + delete[] _pFileName; + _pFileName = nullptr; + } +} + +//--------------------------------------------------------------------- +// +// CreateFile +// +//--------------------------------------------------------------------- + +BOOL CFile::CreateFile(_In_ PCWSTR pFileName, DWORD desiredAccess, DWORD creationDisposition, DWORD sharedMode, + _Inout_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD flagsAndAttributes, + _Inout_opt_ HANDLE templateFileHandle) +{ + size_t fullPathLen = wcslen(pFileName); + if (!_pFileName) + { + _pFileName = new (std::nothrow) WCHAR[fullPathLen + 1]; + } + if (!_pFileName) + { + return FALSE; + } + + StringCchCopyN(_pFileName, fullPathLen + 1, pFileName, fullPathLen); + + _fileHandle = ::CreateFile(pFileName, desiredAccess, sharedMode, lpSecurityAttributes, creationDisposition, + flagsAndAttributes, templateFileHandle); + + if (_fileHandle == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + _fileSize = ::GetFileSize(_fileHandle, NULL); + + return TRUE; +} + +//--------------------------------------------------------------------- +// +// SetupReadBuffer +// +//--------------------------------------------------------------------- + +BOOL CFile::SetupReadBuffer() +{ + const WCHAR *pWideBuffer = nullptr; + + _pReadBuffer = (const WCHAR *)new (std::nothrow) BYTE[_fileSize]; + if (!_pReadBuffer) + { + return FALSE; + } + + DWORD dwNumberOfByteRead = 0; + if (!ReadFile(_fileHandle, (LPVOID)_pReadBuffer, (DWORD)_fileSize, &dwNumberOfByteRead, NULL)) + { + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + return FALSE; + } + + if (!IsTextUnicode(_pReadBuffer, dwNumberOfByteRead, NULL)) + { + // This is ASCII file. + // Read file with Unicode conversion. + int wideLength = 0; + + wideLength = MultiByteToWideChar(_codePage, 0, (LPCSTR)_pReadBuffer, dwNumberOfByteRead, NULL, 0); + if (wideLength <= 0) + { + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + return FALSE; + } + + pWideBuffer = new (std::nothrow) WCHAR[wideLength]; + if (!pWideBuffer) + { + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + return FALSE; + } + + wideLength = + MultiByteToWideChar(_codePage, 0, (LPCSTR)_pReadBuffer, (DWORD)_fileSize, (LPWSTR)pWideBuffer, wideLength); + if (wideLength <= 0) + { + delete[] pWideBuffer; + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + return FALSE; + } + + _fileSize = wideLength * sizeof(WCHAR); + delete[] _pReadBuffer; + _pReadBuffer = pWideBuffer; + } + else if (_fileSize > sizeof(WCHAR)) + { + // Read file in allocated buffer + pWideBuffer = new (std::nothrow) WCHAR[_fileSize / sizeof(WCHAR) - 1]; + if (!pWideBuffer) + { + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + return FALSE; + } + + // skip unicode byte-order signature + SetFilePointer(_fileHandle, sizeof(WCHAR), NULL, FILE_BEGIN); + + if (!ReadFile(_fileHandle, (LPVOID)pWideBuffer, (DWORD)(_fileSize - sizeof(WCHAR)), &dwNumberOfByteRead, NULL)) + { + delete[] pWideBuffer; + delete[] _pReadBuffer; + _pReadBuffer = nullptr; + return FALSE; + } + + _fileSize -= sizeof(WCHAR); + delete[] _pReadBuffer; + _pReadBuffer = pWideBuffer; + } + else + { + return FALSE; + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// IsEndOfFile +// +//---------------------------------------------------------------------------- + +BOOL CFile::IsEndOfFile() +{ + return _fileSize == _filePosPointer ? TRUE : FALSE; +} + +//+--------------------------------------------------------------------------- +// +// NextLine +// +//---------------------------------------------------------------------------- + +VOID CFile::NextLine() +{ + DWORD_PTR totalBufLen = GetBufferInWCharLength(); + if (totalBufLen == 0) + { + goto SetEOF; + } + const WCHAR *pwch = GetBufferInWChar(); + + DWORD_PTR indexTrace = 0; // in char + + if (FindChar(L'\r', pwch, totalBufLen, &indexTrace) != S_OK) + { + goto SetEOF; + } + if (indexTrace >= DWORD_MAX - 1) + { + goto SetEOF; + } + + indexTrace++; // skip CR + totalBufLen -= indexTrace; + if (totalBufLen == 0) + { + goto SetEOF; + } + + if (pwch[indexTrace] != L'\n') + { + _filePosPointer += (indexTrace * sizeof(WCHAR)); + return; + } + + indexTrace++; + totalBufLen--; + if (totalBufLen == 0) + { + goto SetEOF; + } + + _filePosPointer += (indexTrace * sizeof(WCHAR)); + + return; + +SetEOF: + _filePosPointer = _fileSize; + return; +} diff --git a/File.h b/File.h new file mode 100644 index 00000000..97b73c3f --- /dev/null +++ b/File.h @@ -0,0 +1,70 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +// #include "Private.h" + +class CFile +{ +public: + CFile(UINT codePage = CP_ACP); + virtual ~CFile(); + + BOOL CreateFile(_In_ PCWSTR pFileName, DWORD desiredAccess, DWORD creationDisposition, + DWORD sharedMode = 0, _Inout_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr, DWORD flagsAndAttributes = 0, _Inout_opt_ HANDLE templateFileHandle = nullptr); + + BOOL IsEndOfFile(); + VOID NextLine(); + + _Ret_maybenull_ + const WCHAR *GetReadBufferPointer() + { + if (!_pReadBuffer) + { + if (!SetupReadBuffer()) + { + return nullptr; + } + } + return _pReadBuffer; + } + + DWORD_PTR GetFileSize() { return _fileSize;} + + LPCWSTR GetFileName() { return _pFileName;} + +protected: + virtual BOOL SetupReadBuffer(); + +protected: + const WCHAR* _pReadBuffer; // read buffer memory. + DWORD_PTR _fileSize; // in byte. + HANDLE _fileHandle; // file handle for CreateFile + +private: + DWORD_PTR GetBufferInWCharLength() + { + if (_filePosPointer == 0 && _fileSize > 0) + { + // skip Unicode byte order mark + GetReadBufferPointer(); + } + return(_fileSize - _filePosPointer) / sizeof(WCHAR); // in char count as a returned length. + } + + const WCHAR *GetBufferInWChar() + { + const WCHAR *pwch = GetReadBufferPointer(); + return(const WCHAR*)((BYTE*)pwch + _filePosPointer); + } + +private: + UINT _codePage; // used for MultiByteToWideChar + DWORD_PTR _filePosPointer; // in byte. Always point start of line. + LPWSTR _pFileName; +}; diff --git a/FileMapping.cpp b/FileMapping.cpp new file mode 100644 index 00000000..fe9c1793 --- /dev/null +++ b/FileMapping.cpp @@ -0,0 +1,87 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "FileMapping.h" +#include "Globals.h" + +//--------------------------------------------------------------------- +// +// ctor +// +//--------------------------------------------------------------------- + +CFileMapping::CFileMapping() : CFile() +{ + _fileMappingHandle = nullptr; + _pMapBuffer = nullptr; +} + +//--------------------------------------------------------------------- +// +// dtor +// +//--------------------------------------------------------------------- + +CFileMapping::~CFileMapping() +{ + if (_pMapBuffer) + { + UnmapViewOfFile(_pMapBuffer); + _pMapBuffer = nullptr; + _pReadBuffer = nullptr; + } + if (_fileMappingHandle) + { + CloseHandle(_fileMappingHandle); + _fileMappingHandle = nullptr; + } +} + +//--------------------------------------------------------------------- +// +// SetupReadBuffer +// +//--------------------------------------------------------------------- + +BOOL CFileMapping::SetupReadBuffer() +{ + if (_fileSize > sizeof(WCHAR)) + { + // + // Read file in file mapping + // + _fileMappingHandle = CreateFileMapping(_fileHandle, NULL, PAGE_READONLY, 0, 0, NULL); + if (_fileMappingHandle) + { + _pMapBuffer = (const WCHAR *)MapViewOfFile(_fileMappingHandle, FILE_MAP_READ, 0, 0, 0); + if (_pMapBuffer) + { + if (IsTextUnicode(_pMapBuffer, (int)_fileSize, NULL)) + { + _pReadBuffer = (WCHAR*)_pMapBuffer; + + // skip Unicode byte order mark + if (*((WCHAR*)_pMapBuffer) == Global::UnicodeByteOrderMark) + { + _pReadBuffer++; + _fileSize--; + } + return TRUE; + } + + UnmapViewOfFile(_pReadBuffer); + _pReadBuffer = nullptr; + } + + CloseHandle(_fileMappingHandle); + _fileMappingHandle = nullptr; + } + } + + return FALSE; +} diff --git a/FileMapping.h b/FileMapping.h new file mode 100644 index 00000000..c5e3ea5b --- /dev/null +++ b/FileMapping.h @@ -0,0 +1,46 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "File.h" + +class CFileMapping : public CFile +{ +public: + CFileMapping(); + virtual ~CFileMapping(); + + BOOL CreateFile(_In_ PCWSTR pFileName, DWORD desiredAccess, DWORD creationDisposition, + DWORD sharedMode = 0, _Inout_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr, DWORD flagsAndAttributes = 0, _Inout_opt_ HANDLE templateFileHandle = nullptr) + + { + return CFile::CreateFile(pFileName, desiredAccess, creationDisposition, + sharedMode, lpSecurityAttributes, flagsAndAttributes, templateFileHandle); + } + + BOOL IsEndOfFile() + { + return CFile::IsEndOfFile(); + } + VOID NextLine() + { + CFile::NextLine(); + } + + const WCHAR *GetReadBufferPointer() { return CFile::GetReadBufferPointer(); } + DWORD_PTR GetFileSize() { return CFile::GetFileSize(); } + + LPCWSTR GetFileName() { return CFile::GetFileName(); } + +protected: + BOOL SetupReadBuffer(); + +private: + HANDLE _fileMappingHandle; // file handle for CreateFileMapping + const VOID *_pMapBuffer; // read buffer memory. +}; diff --git a/FunctionProviderSink.cpp b/FunctionProviderSink.cpp new file mode 100644 index 00000000..d3e68a32 --- /dev/null +++ b/FunctionProviderSink.cpp @@ -0,0 +1,55 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "SampleIME.h" +#include "SearchCandidateProvider.h" + +//+--------------------------------------------------------------------------- +// +// _InitFunctionProviderSink +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_InitFunctionProviderSink() +{ + ITfSourceSingle* pSourceSingle = nullptr; + BOOL ret = FALSE; + if (SUCCEEDED(_pThreadMgr->QueryInterface(IID_ITfSourceSingle, (void **)&pSourceSingle))) + { + IUnknown* punk = nullptr; + if (SUCCEEDED(QueryInterface(IID_IUnknown, (void **)&punk))) + { + if (SUCCEEDED(pSourceSingle->AdviseSingleSink(_tfClientId, IID_ITfFunctionProvider, punk))) + { + if (SUCCEEDED(CSearchCandidateProvider::CreateInstance(&_pITfFnSearchCandidateProvider, (ITfTextInputProcessorEx*)this))) + { + ret = TRUE; + } + } + punk->Release(); + } + pSourceSingle->Release(); + } + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _UninitFunctionProviderSink +// +//---------------------------------------------------------------------------- + +void CSampleIME::_UninitFunctionProviderSink() +{ + ITfSourceSingle* pSourceSingle = nullptr; + if (SUCCEEDED(_pThreadMgr->QueryInterface(IID_ITfSourceSingle, (void **)&pSourceSingle))) + { + pSourceSingle->UnadviseSingleSink(_tfClientId, IID_ITfFunctionProvider); + pSourceSingle->Release(); + } +} diff --git a/GetTextExtentEditSession.cpp b/GetTextExtentEditSession.cpp new file mode 100644 index 00000000..cf0733f6 --- /dev/null +++ b/GetTextExtentEditSession.cpp @@ -0,0 +1,43 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "EditSession.h" +#include "GetTextExtentEditSession.h" +#include "TfTextLayoutSink.h" + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CGetTextExtentEditSession::CGetTextExtentEditSession(_In_ CSampleIME *pTextService, _In_ ITfContext *pContext, _In_ ITfContextView *pContextView, _In_ ITfRange *pRangeComposition, _In_ CTfTextLayoutSink *pTfTextLayoutSink) : CEditSessionBase(pTextService, pContext) +{ + _pContextView = pContextView; + _pRangeComposition = pRangeComposition; + _pTfTextLayoutSink = pTfTextLayoutSink; +} + +//+--------------------------------------------------------------------------- +// +// ITfEditSession::DoEditSession +// +//---------------------------------------------------------------------------- + +STDAPI CGetTextExtentEditSession::DoEditSession(TfEditCookie ec) +{ + RECT rc = {0, 0, 0, 0}; + BOOL isClipped = TRUE; + + if (SUCCEEDED(_pContextView->GetTextExt(ec, _pRangeComposition, &rc, &isClipped))) + { + _pTfTextLayoutSink->_LayoutChangeNotification(&rc); + } + + return S_OK; +} diff --git a/GetTextExtentEditSession.h b/GetTextExtentEditSession.h new file mode 100644 index 00000000..3a94a0a9 --- /dev/null +++ b/GetTextExtentEditSession.h @@ -0,0 +1,41 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "EditSession.h" + +class CSampleIME; +class CTfTextLayoutSink; + +////////////////////////////////////////////////////////////////////// +// +// ITfEditSession +// CEditSessionBase +// CGetTextExtentEditSession class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// CGetTextExtentEditSession +// +//---------------------------------------------------------------------------- + +class CGetTextExtentEditSession : public CEditSessionBase +{ +public: + CGetTextExtentEditSession(_In_ CSampleIME *pTextService, _In_ ITfContext *pContext, _In_ ITfContextView *pContextView, _In_ ITfRange *pRangeComposition, _In_ CTfTextLayoutSink *pTextLayoutSink); + + // ITfEditSession + STDMETHODIMP DoEditSession(TfEditCookie ec); + +private: + ITfContextView* _pContextView; + ITfRange* _pRangeComposition; + CTfTextLayoutSink* _pTfTextLayoutSink; +}; diff --git a/Globals.cpp b/Globals.cpp new file mode 100644 index 00000000..16ed6203 --- /dev/null +++ b/Globals.cpp @@ -0,0 +1,460 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "resource.h" +#include "BaseWindow.h" +#include "define.h" +#include "SampleIMEBaseStructure.h" + +namespace Global { +HINSTANCE dllInstanceHandle; + +LONG dllRefCount = -1; + +CRITICAL_SECTION CS; +HFONT defaultlFontHandle; // Global font object we use everywhere + +//--------------------------------------------------------------------- +// SampleIME CLSID +//--------------------------------------------------------------------- +// {D2291A80-84D8-4641-9AB2-BDD1472C846B} +extern const CLSID SampleIMECLSID = { + 0xd2291a80, + 0x84d8, + 0x4641, + { 0x9a, 0xb2, 0xbd, 0xd1, 0x47, 0x2c, 0x84, 0x6b } +}; + +//--------------------------------------------------------------------- +// Profile GUID +//--------------------------------------------------------------------- +// {83955C0E-2C09-47a5-BCF3-F2B98E11EE8B} +extern const GUID SampleIMEGuidProfile = { + 0x83955c0e, + 0x2c09, + 0x47a5, + { 0xbc, 0xf3, 0xf2, 0xb9, 0x8e, 0x11, 0xee, 0x8b } +}; + +//--------------------------------------------------------------------- +// PreserveKey GUID +//--------------------------------------------------------------------- +// {4B62B54B-F828-43B5-9095-A96DF9CBDF38} +extern const GUID SampleIMEGuidImeModePreserveKey = { + 0x4b62b54b, + 0xf828, + 0x43b5, + { 0x90, 0x95, 0xa9, 0x6d, 0xf9, 0xcb, 0xdf, 0x38 } +}; + +// {5A08D6C4-4563-4E46-8DDB-65E75C4E73A3} +extern const GUID SampleIMEGuidDoubleSingleBytePreserveKey = { + 0x5a08d6c4, + 0x4563, + 0x4e46, + { 0x8d, 0xdb, 0x65, 0xe7, 0x5c, 0x4e, 0x73, 0xa3 } +}; + +// {175F062E-B961-4AED-A3DF-59F78A02862D} +extern const GUID SampleIMEGuidPunctuationPreserveKey = { + 0x175f062e, + 0xb961, + 0x4aed, + { 0xa3, 0xdf, 0x59, 0xf7, 0x8a, 0x2, 0x86, 0x2d } +}; + +//--------------------------------------------------------------------- +// Compartments +//--------------------------------------------------------------------- +// {101011C5-CF72-4F0C-A515-153019593F10} +extern const GUID SampleIMEGuidCompartmentDoubleSingleByte = { + 0x101011c5, + 0xcf72, + 0x4f0c, + { 0xa5, 0x15, 0x15, 0x30, 0x19, 0x59, 0x3f, 0x10 } +}; + +// {DD321BCC-A7F8-4561-9B61-9B3508C9BA97} +extern const GUID SampleIMEGuidCompartmentPunctuation = { + 0xdd321bcc, + 0xa7f8, + 0x4561, + { 0x9b, 0x61, 0x9b, 0x35, 0x8, 0xc9, 0xba, 0x97 } +}; + + +//--------------------------------------------------------------------- +// LanguageBars +//--------------------------------------------------------------------- + +// {89BE500C-9462-4070-9DB0-B467BB051327} +extern const GUID SampleIMEGuidLangBarIMEMode = { + 0x89be500c, + 0x9462, + 0x4070, + { 0x9d, 0xb0, 0xb4, 0x67, 0xbb, 0x5, 0x13, 0x27 } +}; + +// {6A11D9DE-46DB-455B-A257-2EB615746BF4} +extern const GUID SampleIMEGuidLangBarDoubleSingleByte = { + 0x6a11d9de, + 0x46db, + 0x455b, + { 0xa2, 0x57, 0x2e, 0xb6, 0x15, 0x74, 0x6b, 0xf4 } +}; + +// {F29C731A-A51E-49FB-8A3C-EE51752912E2} +extern const GUID SampleIMEGuidLangBarPunctuation = { + 0xf29c731a, + 0xa51e, + 0x49fb, + { 0x8a, 0x3c, 0xee, 0x51, 0x75, 0x29, 0x12, 0xe2 } +}; + +// {4C802E2C-8140-4436-A5E5-F7C544EBC9CD} +extern const GUID SampleIMEGuidDisplayAttributeInput = { + 0x4c802e2c, + 0x8140, + 0x4436, + { 0xa5, 0xe5, 0xf7, 0xc5, 0x44, 0xeb, 0xc9, 0xcd } +}; + +// {9A1CC683-F2A7-4701-9C6E-2DA69A5CD474} +extern const GUID SampleIMEGuidDisplayAttributeConverted = { + 0x9a1cc683, + 0xf2a7, + 0x4701, + { 0x9c, 0x6e, 0x2d, 0xa6, 0x9a, 0x5c, 0xd4, 0x74 } +}; + + +//--------------------------------------------------------------------- +// UI element +//--------------------------------------------------------------------- + +// {84B0749F-8DE7-4732-907A-3BCB150A01A8} +extern const GUID SampleIMEGuidCandUIElement = { + 0x84b0749f, + 0x8de7, + 0x4732, + { 0x90, 0x7a, 0x3b, 0xcb, 0x15, 0xa, 0x1, 0xa8 } +}; + +//--------------------------------------------------------------------- +// Unicode byte order mark +//--------------------------------------------------------------------- +extern const WCHAR UnicodeByteOrderMark = 0xFEFF; + +//--------------------------------------------------------------------- +// dictionary table delimiter +//--------------------------------------------------------------------- +extern const WCHAR KeywordDelimiter = L'='; +extern const WCHAR StringDelimiter = L'\"'; + +//--------------------------------------------------------------------- +// defined item in setting file table [PreservedKey] section +//--------------------------------------------------------------------- +extern const WCHAR ImeModeDescription[] = L"Chinese/English input (Shift)"; +extern const int ImeModeOnIcoIndex = IME_MODE_ON_ICON_INDEX; +extern const int ImeModeOffIcoIndex = IME_MODE_OFF_ICON_INDEX; + +extern const WCHAR DoubleSingleByteDescription[] = L"Double/Single byte (Shift+Space)"; +extern const int DoubleSingleByteOnIcoIndex = IME_DOUBLE_ON_INDEX; +extern const int DoubleSingleByteOffIcoIndex = IME_DOUBLE_OFF_INDEX; + +extern const WCHAR PunctuationDescription[] = L"Chinese/English punctuation (Ctrl+.)"; +extern const int PunctuationOnIcoIndex = IME_PUNCTUATION_ON_INDEX; +extern const int PunctuationOffIcoIndex = IME_PUNCTUATION_OFF_INDEX; + +//--------------------------------------------------------------------- +// defined item in setting file table [LanguageBar] section +//--------------------------------------------------------------------- +extern const WCHAR LangbarImeModeDescription[] = L"Conversion mode"; +extern const WCHAR LangbarDoubleSingleByteDescription[] = L"Character width"; +extern const WCHAR LangbarPunctuationDescription[] = L"Punctuation"; + +//--------------------------------------------------------------------- +// windows class / titile / atom +//--------------------------------------------------------------------- +extern const WCHAR CandidateClassName[] = L"SampleIME.CandidateWindow"; +ATOM AtomCandidateWindow; + +extern const WCHAR ShadowClassName[] = L"SampleIME.ShadowWindow"; +ATOM AtomShadowWindow; + +extern const WCHAR ScrollBarClassName[] = L"SampleIME.ScrollBarWindow"; +ATOM AtomScrollBarWindow; + +BOOL RegisterWindowClass() +{ + if (!CBaseWindow::_InitWindowClass(CandidateClassName, &AtomCandidateWindow)) + { + return FALSE; + } + if (!CBaseWindow::_InitWindowClass(ShadowClassName, &AtomShadowWindow)) + { + return FALSE; + } + if (!CBaseWindow::_InitWindowClass(ScrollBarClassName, &AtomScrollBarWindow)) + { + return FALSE; + } + return TRUE; +} + +//--------------------------------------------------------------------- +// defined full width characters for Double/Single byte conversion +//--------------------------------------------------------------------- +extern const WCHAR FullWidthCharTable[] = { + // ! " # $ % & ' ( ) * + , - . / + 0x3000, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F, + // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F, + // @ A B C D E F G H I J K L M N 0 + 0xFF20, 0xFF21, 0xFF22, 0xFF23, 0xFF24, 0xFF25, 0xFF26, 0xFF27, 0xFF28, 0xFF29, 0xFF2A, 0xFF2B, 0xFF2C, 0xFF2D, 0xFF2E, 0xFF2F, + // P Q R S T U V W X Y Z [ \ ] ^ _ + 0xFF30, 0xFF31, 0xFF32, 0xFF33, 0xFF34, 0xFF35, 0xFF36, 0xFF37, 0xFF38, 0xFF39, 0xFF3A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F, + // ' a b c d e f g h i j k l m n o + 0xFF40, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, + // p q r s t u v w x y z { | } ~ + 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, 0xFF58, 0xFF59, 0xFF5A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFF5E +}; + +//--------------------------------------------------------------------- +// defined punctuation characters +//--------------------------------------------------------------------- +extern const struct _PUNCTUATION PunctuationTable[14] = { + {L'!', 0xFF01}, + {L'$', 0xFFE5}, + {L'&', 0x2014}, + {L'(', 0xFF08}, + {L')', 0xFF09}, + {L',', 0xFF0C}, + {L'.', 0x3002}, + {L':', 0xFF1A}, + {L';', 0xFF1B}, + {L'?', 0xFF1F}, + {L'@', 0x00B7}, + {L'\\', 0x3001}, + {L'^', 0x2026}, + {L'_', 0x2014} +}; + +//+--------------------------------------------------------------------------- +// +// CheckModifiers +// +//---------------------------------------------------------------------------- + +#define TF_MOD_ALLALT (TF_MOD_RALT | TF_MOD_LALT | TF_MOD_ALT) +#define TF_MOD_ALLCONTROL (TF_MOD_RCONTROL | TF_MOD_LCONTROL | TF_MOD_CONTROL) +#define TF_MOD_ALLSHIFT (TF_MOD_RSHIFT | TF_MOD_LSHIFT | TF_MOD_SHIFT) +#define TF_MOD_RLALT (TF_MOD_RALT | TF_MOD_LALT) +#define TF_MOD_RLCONTROL (TF_MOD_RCONTROL | TF_MOD_LCONTROL) +#define TF_MOD_RLSHIFT (TF_MOD_RSHIFT | TF_MOD_LSHIFT) + +#define CheckMod(m0, m1, mod) \ + if (m1 & TF_MOD_ ## mod ##) \ +{ \ + if (!(m0 & TF_MOD_ ## mod ##)) \ +{ \ + return FALSE; \ +} \ +} \ + else \ +{ \ + if ((m1 ^ m0) & TF_MOD_RL ## mod ##) \ +{ \ + return FALSE; \ +} \ +} \ + + + +BOOL CheckModifiers(UINT modCurrent, UINT mod) +{ + mod &= ~TF_MOD_ON_KEYUP; + + if (mod & TF_MOD_IGNORE_ALL_MODIFIER) + { + return TRUE; + } + + if (modCurrent == mod) + { + return TRUE; + } + + if (modCurrent && !mod) + { + return FALSE; + } + + CheckMod(modCurrent, mod, ALT); + CheckMod(modCurrent, mod, SHIFT); + CheckMod(modCurrent, mod, CONTROL); + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// UpdateModifiers +// +// wParam - virtual-key code +// lParam - [0-15] Repeat count +// [16-23] Scan code +// [24] Extended key +// [25-28] Reserved +// [29] Context code +// [30] Previous key state +// [31] Transition state +//---------------------------------------------------------------------------- + +USHORT ModifiersValue = 0; +BOOL IsShiftKeyDownOnly = FALSE; +BOOL IsControlKeyDownOnly = FALSE; +BOOL IsAltKeyDownOnly = FALSE; + +BOOL UpdateModifiers(WPARAM wParam, LPARAM lParam) +{ + // high-order bit : key down + // low-order bit : toggled + SHORT sksMenu = GetKeyState(VK_MENU); + SHORT sksCtrl = GetKeyState(VK_CONTROL); + SHORT sksShft = GetKeyState(VK_SHIFT); + + switch (wParam & 0xff) + { + case VK_MENU: + // is VK_MENU down? + if (sksMenu & 0x8000) + { + // is extended key? + if (lParam & 0x01000000) + { + ModifiersValue |= (TF_MOD_RALT | TF_MOD_ALT); + } + else + { + ModifiersValue |= (TF_MOD_LALT | TF_MOD_ALT); + } + + // is previous key state up? + if (!(lParam & 0x40000000)) + { + // is VK_CONTROL and VK_SHIFT up? + if (!(sksCtrl & 0x8000) && !(sksShft & 0x8000)) + { + IsAltKeyDownOnly = TRUE; + } + else + { + IsShiftKeyDownOnly = FALSE; + IsControlKeyDownOnly = FALSE; + IsAltKeyDownOnly = FALSE; + } + } + } + break; + + case VK_CONTROL: + // is VK_CONTROL down? + if (sksCtrl & 0x8000) + { + // is extended key? + if (lParam & 0x01000000) + { + ModifiersValue |= (TF_MOD_RCONTROL | TF_MOD_CONTROL); + } + else + { + ModifiersValue |= (TF_MOD_LCONTROL | TF_MOD_CONTROL); + } + + // is previous key state up? + if (!(lParam & 0x40000000)) + { + // is VK_SHIFT and VK_MENU up? + if (!(sksShft & 0x8000) && !(sksMenu & 0x8000)) + { + IsControlKeyDownOnly = TRUE; + } + else + { + IsShiftKeyDownOnly = FALSE; + IsControlKeyDownOnly = FALSE; + IsAltKeyDownOnly = FALSE; + } + } + } + break; + + case VK_SHIFT: + // is VK_SHIFT down? + if (sksShft & 0x8000) + { + // is scan code 0x36(right shift)? + if (((lParam >> 16) & 0x00ff) == 0x36) + { + ModifiersValue |= (TF_MOD_RSHIFT | TF_MOD_SHIFT); + } + else + { + ModifiersValue |= (TF_MOD_LSHIFT | TF_MOD_SHIFT); + } + + // is previous key state up? + if (!(lParam & 0x40000000)) + { + // is VK_MENU and VK_CONTROL up? + if (!(sksMenu & 0x8000) && !(sksCtrl & 0x8000)) + { + IsShiftKeyDownOnly = TRUE; + } + else + { + IsShiftKeyDownOnly = FALSE; + IsControlKeyDownOnly = FALSE; + IsAltKeyDownOnly = FALSE; + } + } + } + break; + + default: + IsShiftKeyDownOnly = FALSE; + IsControlKeyDownOnly = FALSE; + IsAltKeyDownOnly = FALSE; + break; + } + + if (!(sksMenu & 0x8000)) + { + ModifiersValue &= ~TF_MOD_ALLALT; + } + if (!(sksCtrl & 0x8000)) + { + ModifiersValue &= ~TF_MOD_ALLCONTROL; + } + if (!(sksShft & 0x8000)) + { + ModifiersValue &= ~TF_MOD_ALLSHIFT; + } + + return TRUE; +} + +//--------------------------------------------------------------------- +// override CompareElements +//--------------------------------------------------------------------- +BOOL CompareElements(LCID locale, const CStringRange* pElement1, const CStringRange* pElement2) +{ + return (CStringRange::Compare(locale, (CStringRange*)pElement1, (CStringRange*)pElement2) == CSTR_EQUAL) ? TRUE : FALSE; +} +} \ No newline at end of file diff --git a/Globals.h b/Globals.h new file mode 100644 index 00000000..0996e6d3 --- /dev/null +++ b/Globals.h @@ -0,0 +1,162 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "private.h" +#include "define.h" +#include "SampleIMEBaseStructure.h" + +void DllAddRef(); +void DllRelease(); + + +namespace Global { +//--------------------------------------------------------------------- +// inline +//--------------------------------------------------------------------- + +inline void SafeRelease(_In_ IUnknown *punk) +{ + if (punk != nullptr) + { + punk->Release(); + } +} + +inline void QuickVariantInit(_Inout_ VARIANT *pvar) +{ + pvar->vt = VT_EMPTY; +} + +inline void QuickVariantClear(_Inout_ VARIANT *pvar) +{ + switch (pvar->vt) + { + // some ovbious VTs that don't need to call VariantClear. + case VT_EMPTY: + case VT_NULL: + case VT_I2: + case VT_I4: + case VT_R4: + case VT_R8: + case VT_CY: + case VT_DATE: + case VT_I1: + case VT_UI1: + case VT_UI2: + case VT_UI4: + case VT_I8: + case VT_UI8: + case VT_INT: + case VT_UINT: + case VT_BOOL: + break; + + // Call release for VT_UNKNOWN. + case VT_UNKNOWN: + SafeRelease(pvar->punkVal); + break; + + default: + // we call OleAut32 for other VTs. + VariantClear(pvar); + break; + } + pvar->vt = VT_EMPTY; +} + +//+--------------------------------------------------------------------------- +// +// IsTooSimilar +// +// Return TRUE if the colors cr1 and cr2 are so similar that they +// are hard to distinguish. Used for deciding to use reverse video +// selection instead of system selection colors. +// +//---------------------------------------------------------------------------- + +inline BOOL IsTooSimilar(COLORREF cr1, COLORREF cr2) +{ + if ((cr1 | cr2) & 0xFF000000) // One color and/or the other isn't RGB, so algorithm doesn't apply + { + return FALSE; + } + + LONG DeltaR = abs(GetRValue(cr1) - GetRValue(cr2)); + LONG DeltaG = abs(GetGValue(cr1) - GetGValue(cr2)); + LONG DeltaB = abs(GetBValue(cr1) - GetBValue(cr2)); + + return DeltaR + DeltaG + DeltaB < 80; +} + +//--------------------------------------------------------------------- +// extern +//--------------------------------------------------------------------- +extern HINSTANCE dllInstanceHandle; + +extern ATOM AtomCandidateWindow; +extern ATOM AtomShadowWindow; +extern ATOM AtomScrollBarWindow; + +BOOL RegisterWindowClass(); + +extern LONG dllRefCount; + +extern CRITICAL_SECTION CS; +extern HFONT defaultlFontHandle; // Global font object we use everywhere + +extern const CLSID SampleIMECLSID; +extern const CLSID SampleIMEGuidProfile; +extern const CLSID SampleIMEGuidImeModePreserveKey; +extern const CLSID SampleIMEGuidDoubleSingleBytePreserveKey; +extern const CLSID SampleIMEGuidPunctuationPreserveKey; + +LRESULT CALLBACK ThreadKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); +BOOL CheckModifiers(UINT uModCurrent, UINT uMod); +BOOL UpdateModifiers(WPARAM wParam, LPARAM lParam); + +extern USHORT ModifiersValue; +extern BOOL IsShiftKeyDownOnly; +extern BOOL IsControlKeyDownOnly; +extern BOOL IsAltKeyDownOnly; + +extern const GUID SampleIMEGuidCompartmentDoubleSingleByte; +extern const GUID SampleIMEGuidCompartmentPunctuation; + +extern const WCHAR FullWidthCharTable[]; +extern const struct _PUNCTUATION PunctuationTable[14]; + +extern const GUID SampleIMEGuidLangBarIMEMode; +extern const GUID SampleIMEGuidLangBarDoubleSingleByte; +extern const GUID SampleIMEGuidLangBarPunctuation; + +extern const GUID SampleIMEGuidDisplayAttributeInput; +extern const GUID SampleIMEGuidDisplayAttributeConverted; + +extern const GUID SampleIMEGuidCandUIElement; + +extern const WCHAR UnicodeByteOrderMark; +extern const WCHAR KeywordDelimiter; +extern const WCHAR StringDelimiter; + +extern const WCHAR ImeModeDescription[]; +extern const int ImeModeOnIcoIndex; +extern const int ImeModeOffIcoIndex; + +extern const WCHAR DoubleSingleByteDescription[]; +extern const int DoubleSingleByteOnIcoIndex; +extern const int DoubleSingleByteOffIcoIndex; + +extern const WCHAR PunctuationDescription[]; +extern const int PunctuationOnIcoIndex; +extern const int PunctuationOffIcoIndex; + +extern const WCHAR LangbarImeModeDescription[]; +extern const WCHAR LangbarDoubleSingleByteDescription[]; +extern const WCHAR LangbarPunctuationDescription[]; +} \ No newline at end of file diff --git a/KeyEventSink.cpp b/KeyEventSink.cpp new file mode 100644 index 00000000..41475912 --- /dev/null +++ b/KeyEventSink.cpp @@ -0,0 +1,457 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "SampleIME.h" +#include "CandidateListUIPresenter.h" +#include "CompositionProcessorEngine.h" +#include "KeyHandlerEditSession.h" +#include "Compartment.h" + +// 0xF003, 0xF004 are the keys that the touch keyboard sends for next/previous +#define THIRDPARTY_NEXTPAGE static_cast(0xF003) +#define THIRDPARTY_PREVPAGE static_cast(0xF004) + +// Because the code mostly works with VKeys, here map a WCHAR back to a VKKey for certain +// vkeys that the IME handles specially +__inline UINT VKeyFromVKPacketAndWchar(UINT vk, WCHAR wch) +{ + UINT vkRet = vk; + if (LOWORD(vk) == VK_PACKET) + { + if (wch == L' ') + { + vkRet = VK_SPACE; + } + else if ((wch >= L'0') && (wch <= L'9')) + { + vkRet = static_cast(wch); + } + else if ((wch >= L'a') && (wch <= L'z')) + { + vkRet = (UINT)(L'A') + ((UINT)(L'z') - static_cast(wch)); + } + else if ((wch >= L'A') && (wch <= L'Z')) + { + vkRet = static_cast(wch); + } + else if (wch == THIRDPARTY_NEXTPAGE) + { + vkRet = VK_NEXT; + } + else if (wch == THIRDPARTY_PREVPAGE) + { + vkRet = VK_PRIOR; + } + } + return vkRet; +} + +//+--------------------------------------------------------------------------- +// +// _IsKeyEaten +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_IsKeyEaten(_In_ ITfContext *pContext, UINT codeIn, _Out_ UINT *pCodeOut, _Out_writes_(1) WCHAR *pwch, _Out_opt_ _KEYSTROKE_STATE *pKeyState) +{ + pContext; + + *pCodeOut = codeIn; + + BOOL isOpen = FALSE; + CCompartment CompartmentKeyboardOpen(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + CompartmentKeyboardOpen._GetCompartmentBOOL(isOpen); + + BOOL isDoubleSingleByte = FALSE; + CCompartment CompartmentDoubleSingleByte(_pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + CompartmentDoubleSingleByte._GetCompartmentBOOL(isDoubleSingleByte); + + BOOL isPunctuation = FALSE; + CCompartment CompartmentPunctuation(_pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + CompartmentPunctuation._GetCompartmentBOOL(isPunctuation); + + if (pKeyState) + { + pKeyState->Category = CATEGORY_NONE; + pKeyState->Function = FUNCTION_NONE; + } + if (pwch) + { + *pwch = L'\0'; + } + + // if the keyboard is disabled, we don't eat keys. + if (_IsKeyboardDisabled()) + { + return FALSE; + } + + // + // Map virtual key to character code + // + BOOL isTouchKeyboardSpecialKeys = FALSE; + WCHAR wch = ConvertVKey(codeIn); + *pCodeOut = VKeyFromVKPacketAndWchar(codeIn, wch); + if ((wch == THIRDPARTY_NEXTPAGE) || (wch == THIRDPARTY_PREVPAGE)) + { + // We always eat the above softkeyboard special keys + isTouchKeyboardSpecialKeys = TRUE; + if (pwch) + { + *pwch = wch; + } + } + + // if the keyboard is closed, we don't eat keys, with the exception of the touch keyboard specials keys + if (!isOpen && !isDoubleSingleByte && !isPunctuation) + { + return isTouchKeyboardSpecialKeys; + } + + if (pwch) + { + *pwch = wch; + } + + // + // Get composition engine + // + CCompositionProcessorEngine *pCompositionProcessorEngine; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + if (isOpen) + { + // + // The candidate or phrase list handles the keys through ITfKeyEventSink. + // + // eat only keys that CKeyHandlerEditSession can handles. + // + if (pCompositionProcessorEngine->IsVirtualKeyNeed(*pCodeOut, pwch, _IsComposing(), _candidateMode, _isCandidateWithWildcard, pKeyState)) + { + return TRUE; + } + } + + // + // Punctuation + // + if (pCompositionProcessorEngine->IsPunctuation(wch)) + { + if ((_candidateMode == CANDIDATE_NONE) && isPunctuation) + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_COMPOSING; + pKeyState->Function = FUNCTION_PUNCTUATION; + } + return TRUE; + } + } + + // + // Double/Single byte + // + if (isDoubleSingleByte && pCompositionProcessorEngine->IsDoubleSingleByte(wch)) + { + if (_candidateMode == CANDIDATE_NONE) + { + if (pKeyState) + { + pKeyState->Category = CATEGORY_COMPOSING; + pKeyState->Function = FUNCTION_DOUBLE_SINGLE_BYTE; + } + return TRUE; + } + } + + return isTouchKeyboardSpecialKeys; +} + +//+--------------------------------------------------------------------------- +// +// ConvertVKey +// +//---------------------------------------------------------------------------- + +WCHAR CSampleIME::ConvertVKey(UINT code) +{ + // + // Map virtual key to scan code + // + UINT scanCode = 0; + scanCode = MapVirtualKey(code, 0); + + // + // Keyboard state + // + BYTE abKbdState[256] = {'\0'}; + if (!GetKeyboardState(abKbdState)) + { + return 0; + } + + // + // Map virtual key to character code + // + WCHAR wch = '\0'; + if (ToUnicode(code, scanCode, abKbdState, &wch, 1, 0) == 1) + { + return wch; + } + + return 0; +} + +//+--------------------------------------------------------------------------- +// +// _IsKeyboardDisabled +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_IsKeyboardDisabled() +{ + ITfDocumentMgr* pDocMgrFocus = nullptr; + ITfContext* pContext = nullptr; + BOOL isDisabled = FALSE; + + if ((_pThreadMgr->GetFocus(&pDocMgrFocus) != S_OK) || + (pDocMgrFocus == nullptr)) + { + // if there is no focus document manager object, the keyboard + // is disabled. + isDisabled = TRUE; + } + else if ((pDocMgrFocus->GetTop(&pContext) != S_OK) || + (pContext == nullptr)) + { + // if there is no context object, the keyboard is disabled. + isDisabled = TRUE; + } + else + { + CCompartment CompartmentKeyboardDisabled(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_DISABLED); + CompartmentKeyboardDisabled._GetCompartmentBOOL(isDisabled); + + CCompartment CompartmentEmptyContext(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_EMPTYCONTEXT); + CompartmentEmptyContext._GetCompartmentBOOL(isDisabled); + } + + if (pContext) + { + pContext->Release(); + } + + if (pDocMgrFocus) + { + pDocMgrFocus->Release(); + } + + return isDisabled; +} + +//+--------------------------------------------------------------------------- +// +// ITfKeyEventSink::OnSetFocus +// +// Called by the system whenever this service gets the keystroke device focus. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnSetFocus(BOOL fForeground) +{ + fForeground; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfKeyEventSink::OnTestKeyDown +// +// Called by the system to query this service wants a potential keystroke. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnTestKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) +{ + Global::UpdateModifiers(wParam, lParam); + + _KEYSTROKE_STATE KeystrokeState; + WCHAR wch = '\0'; + UINT code = 0; + *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, &KeystrokeState); + + if (KeystrokeState.Category == CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION) + { + // + // Invoke key handler edit session + // + KeystrokeState.Category = CATEGORY_COMPOSING; + + _InvokeKeyHandler(pContext, code, wch, (DWORD)lParam, KeystrokeState); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfKeyEventSink::OnKeyDown +// +// Called by the system to offer this service a keystroke. If *pIsEaten == TRUE +// on exit, the application will not handle the keystroke. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) +{ + Global::UpdateModifiers(wParam, lParam); + + _KEYSTROKE_STATE KeystrokeState; + WCHAR wch = '\0'; + UINT code = 0; + + *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, &KeystrokeState); + + if (*pIsEaten) + { + bool needInvokeKeyHandler = true; + // + // Invoke key handler edit session + // + if (code == VK_ESCAPE) + { + KeystrokeState.Category = CATEGORY_COMPOSING; + } + + // Always eat THIRDPARTY_NEXTPAGE and THIRDPARTY_PREVPAGE keys, but don't always process them. + if ((wch == THIRDPARTY_NEXTPAGE) || (wch == THIRDPARTY_PREVPAGE)) + { + needInvokeKeyHandler = !((KeystrokeState.Category == CATEGORY_NONE) && (KeystrokeState.Function == FUNCTION_NONE)); + } + + if (needInvokeKeyHandler) + { + _InvokeKeyHandler(pContext, code, wch, (DWORD)lParam, KeystrokeState); + } + } + else if (KeystrokeState.Category == CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION) + { + // Invoke key handler edit session + KeystrokeState.Category = CATEGORY_COMPOSING; + _InvokeKeyHandler(pContext, code, wch, (DWORD)lParam, KeystrokeState); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfKeyEventSink::OnTestKeyUp +// +// Called by the system to query this service wants a potential keystroke. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnTestKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) +{ + if (pIsEaten == nullptr) + { + return E_INVALIDARG; + } + + Global::UpdateModifiers(wParam, lParam); + + WCHAR wch = '\0'; + UINT code = 0; + + *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, NULL); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfKeyEventSink::OnKeyUp +// +// Called by the system to offer this service a keystroke. If *pIsEaten == TRUE +// on exit, the application will not handle the keystroke. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten) +{ + Global::UpdateModifiers(wParam, lParam); + + WCHAR wch = '\0'; + UINT code = 0; + + *pIsEaten = _IsKeyEaten(pContext, (UINT)wParam, &code, &wch, NULL); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfKeyEventSink::OnPreservedKey +// +// Called when a hotkey (registered by us, or by the system) is typed. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnPreservedKey(ITfContext *pContext, REFGUID rguid, BOOL *pIsEaten) +{ + pContext; + + CCompositionProcessorEngine *pCompositionProcessorEngine; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + pCompositionProcessorEngine->OnPreservedKey(rguid, pIsEaten, _GetThreadMgr(), _GetClientId()); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _InitKeyEventSink +// +// Advise a keystroke sink. +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_InitKeyEventSink() +{ + ITfKeystrokeMgr* pKeystrokeMgr = nullptr; + HRESULT hr = S_OK; + + if (FAILED(_pThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, (void **)&pKeystrokeMgr))) + { + return FALSE; + } + + hr = pKeystrokeMgr->AdviseKeyEventSink(_tfClientId, (ITfKeyEventSink *)this, TRUE); + + pKeystrokeMgr->Release(); + + return (hr == S_OK); +} + +//+--------------------------------------------------------------------------- +// +// _UninitKeyEventSink +// +// Unadvise a keystroke sink. Assumes we have advised one already. +//---------------------------------------------------------------------------- + +void CSampleIME::_UninitKeyEventSink() +{ + ITfKeystrokeMgr* pKeystrokeMgr = nullptr; + + if (FAILED(_pThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, (void **)&pKeystrokeMgr))) + { + return; + } + + pKeystrokeMgr->UnadviseKeyEventSink(_tfClientId); + + pKeystrokeMgr->Release(); +} diff --git a/KeyHandler.cpp b/KeyHandler.cpp new file mode 100644 index 00000000..ae47bb80 --- /dev/null +++ b/KeyHandler.cpp @@ -0,0 +1,635 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "EditSession.h" +#include "SampleIME.h" +#include "CandidateListUIPresenter.h" +#include "CompositionProcessorEngine.h" + +////////////////////////////////////////////////////////////////////// +// +// CSampleIME class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// _IsRangeCovered +// +// Returns TRUE if pRangeTest is entirely contained within pRangeCover. +// +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_IsRangeCovered(TfEditCookie ec, _In_ ITfRange *pRangeTest, _In_ ITfRange *pRangeCover) +{ + LONG lResult = 0;; + + if (FAILED(pRangeCover->CompareStart(ec, pRangeTest, TF_ANCHOR_START, &lResult)) + || (lResult > 0)) + { + return FALSE; + } + + if (FAILED(pRangeCover->CompareEnd(ec, pRangeTest, TF_ANCHOR_END, &lResult)) + || (lResult < 0)) + { + return FALSE; + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _DeleteCandidateList +// +//---------------------------------------------------------------------------- + +VOID CSampleIME::_DeleteCandidateList(BOOL isForce, _In_opt_ ITfContext *pContext) +{ + isForce;pContext; + + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + pCompositionProcessorEngine->PurgeVirtualKey(); + + if (_pCandidateListUIPresenter) + { + _pCandidateListUIPresenter->_EndCandidateList(); + + _candidateMode = CANDIDATE_NONE; + _isCandidateWithWildcard = FALSE; + } +} + +//+--------------------------------------------------------------------------- +// +// _HandleComplete +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleComplete(TfEditCookie ec, _In_ ITfContext *pContext) +{ + _DeleteCandidateList(FALSE, pContext); + + // just terminate the composition + _TerminateComposition(ec, pContext); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCancel +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCancel(TfEditCookie ec, _In_ ITfContext *pContext) +{ + _RemoveDummyCompositionForComposing(ec, _pComposition); + + _DeleteCandidateList(FALSE, pContext); + + _TerminateComposition(ec, pContext); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionInput +// +// If the keystroke happens within a composition, eat the key and return S_OK. +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionInput(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch) +{ + ITfRange* pRangeComposition = nullptr; + TF_SELECTION tfSelection; + ULONG fetched = 0; + BOOL isCovered = TRUE; + + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + if ((_pCandidateListUIPresenter != nullptr) && (_candidateMode != CANDIDATE_INCREMENTAL)) + { + _HandleCompositionFinalize(ec, pContext, FALSE); + } + + // Start the new (std::nothrow) compositon if there is no composition. + if (!_IsComposing()) + { + _StartComposition(pContext); + } + + // first, test where a keystroke would go in the document if we did an insert + if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched) != S_OK || fetched != 1) + { + return S_FALSE; + } + + // is the insertion point covered by a composition? + if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) + { + isCovered = _IsRangeCovered(ec, tfSelection.range, pRangeComposition); + + pRangeComposition->Release(); + + if (!isCovered) + { + goto Exit; + } + } + + // Add virtual key to composition processor engine + pCompositionProcessorEngine->AddVirtualKey(wch); + + _HandleCompositionInputWorker(pCompositionProcessorEngine, ec, pContext); + +Exit: + tfSelection.range->Release(); + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionInputWorker +// +// If the keystroke happens within a composition, eat the key and return S_OK. +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionInputWorker(_In_ CCompositionProcessorEngine *pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext *pContext) +{ + HRESULT hr = S_OK; + CSampleImeArray readingStrings; + BOOL isWildcardIncluded = TRUE; + + // + // Get reading string from composition processor engine + // + pCompositionProcessorEngine->GetReadingStrings(&readingStrings, &isWildcardIncluded); + + for (UINT index = 0; index < readingStrings.Count(); index++) + { + hr = _AddComposingAndChar(ec, pContext, readingStrings.GetAt(index)); + if (FAILED(hr)) + { + return hr; + } + } + + // + // Get candidate string from composition processor engine + // + CSampleImeArray candidateList; + + pCompositionProcessorEngine->GetCandidateList(&candidateList, TRUE, FALSE); + + if ((candidateList.Count())) + { + hr = _CreateAndStartCandidate(pCompositionProcessorEngine, ec, pContext); + if (SUCCEEDED(hr)) + { + _pCandidateListUIPresenter->_ClearList(); + _pCandidateListUIPresenter->_SetText(&candidateList, TRUE); + } + } + else if (_pCandidateListUIPresenter) + { + _pCandidateListUIPresenter->_ClearList(); + } + else if (readingStrings.Count() && isWildcardIncluded) + { + hr = _CreateAndStartCandidate(pCompositionProcessorEngine, ec, pContext); + if (SUCCEEDED(hr)) + { + _pCandidateListUIPresenter->_ClearList(); + } + } + return hr; +} +//+--------------------------------------------------------------------------- +// +// _CreateAndStartCandidate +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_CreateAndStartCandidate(_In_ CCompositionProcessorEngine *pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext *pContext) +{ + HRESULT hr = S_OK; + + if (((_candidateMode == CANDIDATE_PHRASE) && (_pCandidateListUIPresenter)) + || ((_candidateMode == CANDIDATE_NONE) && (_pCandidateListUIPresenter))) + { + // Recreate candidate list + _pCandidateListUIPresenter->_EndCandidateList(); + delete _pCandidateListUIPresenter; + _pCandidateListUIPresenter = nullptr; + + _candidateMode = CANDIDATE_NONE; + _isCandidateWithWildcard = FALSE; + } + + if (_pCandidateListUIPresenter == nullptr) + { + _pCandidateListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow, + CATEGORY_CANDIDATE, + pCompositionProcessorEngine->GetCandidateListIndexRange(), + FALSE); + if (!_pCandidateListUIPresenter) + { + return E_OUTOFMEMORY; + } + + _candidateMode = CANDIDATE_INCREMENTAL; + _isCandidateWithWildcard = FALSE; + + // we don't cache the document manager object. So get it from pContext. + ITfDocumentMgr* pDocumentMgr = nullptr; + if (SUCCEEDED(pContext->GetDocumentMgr(&pDocumentMgr))) + { + // get the composition range. + ITfRange* pRange = nullptr; + if (SUCCEEDED(_pComposition->GetRange(&pRange))) + { + hr = _pCandidateListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, pCompositionProcessorEngine->GetCandidateWindowWidth()); + pRange->Release(); + } + pDocumentMgr->Release(); + } + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionFinalize +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionFinalize(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isCandidateList) +{ + HRESULT hr = S_OK; + + if (isCandidateList && _pCandidateListUIPresenter) + { + // Finalize selected candidate string from CCandidateListUIPresenter + DWORD_PTR candidateLen = 0; + const WCHAR *pCandidateString = nullptr; + + candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString); + + CStringRange candidateString; + candidateString.Set(pCandidateString, candidateLen); + + if (candidateLen) + { + // Finalize character + hr = _AddCharAndFinalize(ec, pContext, &candidateString); + if (FAILED(hr)) + { + return hr; + } + } + } + else + { + // Finalize current text store strings + if (_IsComposing()) + { + ULONG fetched = 0; + TF_SELECTION tfSelection; + + if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1) + { + return S_FALSE; + } + + ITfRange* pRangeComposition = nullptr; + if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) + { + if (_IsRangeCovered(ec, tfSelection.range, pRangeComposition)) + { + _EndComposition(pContext); + } + + pRangeComposition->Release(); + } + + tfSelection.range->Release(); + } + } + + _HandleCancel(ec, pContext); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionConvert +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionConvert(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isWildcardSearch) +{ + HRESULT hr = S_OK; + + CSampleImeArray candidateList; + + // + // Get candidate string from composition processor engine + // + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + pCompositionProcessorEngine->GetCandidateList(&candidateList, FALSE, isWildcardSearch); + + // If there is no candlidate listin the current reading string, we don't do anything. Just wait for + // next char to be ready for the conversion with it. + int nCount = candidateList.Count(); + if (nCount) + { + if (_pCandidateListUIPresenter) + { + _pCandidateListUIPresenter->_EndCandidateList(); + delete _pCandidateListUIPresenter; + _pCandidateListUIPresenter = nullptr; + + _candidateMode = CANDIDATE_NONE; + _isCandidateWithWildcard = FALSE; + } + + // + // create an instance of the candidate list class. + // + if (_pCandidateListUIPresenter == nullptr) + { + _pCandidateListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow, + CATEGORY_CANDIDATE, + pCompositionProcessorEngine->GetCandidateListIndexRange(), + FALSE); + if (!_pCandidateListUIPresenter) + { + return E_OUTOFMEMORY; + } + + _candidateMode = CANDIDATE_ORIGINAL; + } + + _isCandidateWithWildcard = isWildcardSearch; + + // we don't cache the document manager object. So get it from pContext. + ITfDocumentMgr* pDocumentMgr = nullptr; + if (SUCCEEDED(pContext->GetDocumentMgr(&pDocumentMgr))) + { + // get the composition range. + ITfRange* pRange = nullptr; + if (SUCCEEDED(_pComposition->GetRange(&pRange))) + { + hr = _pCandidateListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, pCompositionProcessorEngine->GetCandidateWindowWidth()); + pRange->Release(); + } + pDocumentMgr->Release(); + } + if (SUCCEEDED(hr)) + { + _pCandidateListUIPresenter->_SetText(&candidateList, FALSE); + } + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionBackspace +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionBackspace(TfEditCookie ec, _In_ ITfContext *pContext) +{ + ITfRange* pRangeComposition = nullptr; + TF_SELECTION tfSelection; + ULONG fetched = 0; + BOOL isCovered = TRUE; + + // Start the new (std::nothrow) compositon if there is no composition. + if (!_IsComposing()) + { + return S_OK; + } + + // first, test where a keystroke would go in the document if we did an insert + if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1) + { + return S_FALSE; + } + + // is the insertion point covered by a composition? + if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) + { + isCovered = _IsRangeCovered(ec, tfSelection.range, pRangeComposition); + + pRangeComposition->Release(); + + if (!isCovered) + { + goto Exit; + } + } + + // + // Add virtual key to composition processor engine + // + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + DWORD_PTR vKeyLen = pCompositionProcessorEngine->GetVirtualKeyLength(); + + if (vKeyLen) + { + pCompositionProcessorEngine->RemoveVirtualKey(vKeyLen - 1); + + if (pCompositionProcessorEngine->GetVirtualKeyLength()) + { + _HandleCompositionInputWorker(pCompositionProcessorEngine, ec, pContext); + } + else + { + _HandleCancel(ec, pContext); + } + } + +Exit: + tfSelection.range->Release(); + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionArrowKey +// +// Update the selection within a composition. +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, KEYSTROKE_FUNCTION keyFunction) +{ + ITfRange* pRangeComposition = nullptr; + TF_SELECTION tfSelection; + ULONG fetched = 0; + + // get the selection + if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) + || fetched != 1) + { + // no selection, eat the keystroke + return S_OK; + } + + // get the composition range + if (FAILED(_pComposition->GetRange(&pRangeComposition))) + { + goto Exit; + } + + // For incremental candidate list + if (_pCandidateListUIPresenter) + { + _pCandidateListUIPresenter->AdviseUIChangedByArrowKey(keyFunction); + } + + pContext->SetSelection(ec, 1, &tfSelection); + + pRangeComposition->Release(); + +Exit: + tfSelection.range->Release(); + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionPunctuation +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionPunctuation(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch) +{ + HRESULT hr = S_OK; + + if (_candidateMode != CANDIDATE_NONE && _pCandidateListUIPresenter) + { + DWORD_PTR candidateLen = 0; + const WCHAR* pCandidateString = nullptr; + + candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString); + + CStringRange candidateString; + candidateString.Set(pCandidateString, candidateLen); + + if (candidateLen) + { + _AddComposingAndChar(ec, pContext, &candidateString); + } + } + // + // Get punctuation char from composition processor engine + // + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + WCHAR punctuation = pCompositionProcessorEngine->GetPunctuation(wch); + + CStringRange punctuationString; + punctuationString.Set(&punctuation, 1); + + // Finalize character + hr = _AddCharAndFinalize(ec, pContext, &punctuationString); + if (FAILED(hr)) + { + return hr; + } + + _HandleCancel(ec, pContext); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _HandleCompositionDoubleSingleByte +// +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_HandleCompositionDoubleSingleByte(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch) +{ + HRESULT hr = S_OK; + + WCHAR fullWidth = Global::FullWidthCharTable[wch - 0x20]; + + CStringRange fullWidthString; + fullWidthString.Set(&fullWidth, 1); + + // Finalize character + hr = _AddCharAndFinalize(ec, pContext, &fullWidthString); + if (FAILED(hr)) + { + return hr; + } + + _HandleCancel(ec, pContext); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _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. +// +// param +// [in] uCode - virtual key code of WM_KEYDOWN wParam +// [in] dwFlags - WM_KEYDOWN lParam +// [in] dwKeyFunction - Function regarding virtual key +//---------------------------------------------------------------------------- + +HRESULT CSampleIME::_InvokeKeyHandler(_In_ ITfContext *pContext, UINT code, WCHAR wch, DWORD flags, _KEYSTROKE_STATE keyState) +{ + flags; + + CKeyHandlerEditSession* pEditSession = nullptr; + HRESULT hr = E_FAIL; + + // we'll insert a char ourselves in place of this keystroke + pEditSession = new (std::nothrow) CKeyHandlerEditSession(this, pContext, code, wch, keyState); + if (pEditSession == nullptr) + { + goto Exit; + } + + // + // Call CKeyHandlerEditSession::DoEditSession(). + // + // Do not specify TF_ES_SYNC so edit session is not invoked on WinWord + // + hr = pContext->RequestEditSession(_tfClientId, pEditSession, TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &hr); + + pEditSession->Release(); + +Exit: + return hr; +} diff --git a/KeyHandlerEditSession.cpp b/KeyHandlerEditSession.cpp new file mode 100644 index 00000000..4d17c420 --- /dev/null +++ b/KeyHandlerEditSession.cpp @@ -0,0 +1,46 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "KeyHandlerEditSession.h" +#include "EditSession.h" +#include "SampleIME.h" +#include "CompositionProcessorEngine.h" +#include "KeyStateCategory.h" + +////////////////////////////////////////////////////////////////////// +// +// ITfEditSession +// CEditSessionBase +// CKeyHandlerEditSession class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// CKeyHandlerEditSession::DoEditSession +// +//---------------------------------------------------------------------------- + +STDAPI CKeyHandlerEditSession::DoEditSession(TfEditCookie ec) +{ + HRESULT hResult = S_OK; + + CKeyStateCategoryFactory* pKeyStateCategoryFactory = CKeyStateCategoryFactory::Instance(); + CKeyStateCategory* pKeyStateCategory = pKeyStateCategoryFactory->MakeKeyStateCategory(_KeyState.Category, _pTextService); + + if (pKeyStateCategory) + { + KeyHandlerEditSessionDTO keyHandlerEditSessioDTO(ec, _pContext, _uCode,_wch, _KeyState.Function); + hResult = pKeyStateCategory->KeyStateHandler(_KeyState.Function, keyHandlerEditSessioDTO); + + pKeyStateCategory->Release(); + pKeyStateCategoryFactory->Release(); + } + + return hResult; +} diff --git a/KeyHandlerEditSession.h b/KeyHandlerEditSession.h new file mode 100644 index 00000000..4d92baa3 --- /dev/null +++ b/KeyHandlerEditSession.h @@ -0,0 +1,30 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "EditSession.h" +#include "Globals.h" + +class CKeyHandlerEditSession : public CEditSessionBase +{ +public: + CKeyHandlerEditSession(CSampleIME *pTextService, ITfContext *pContext, UINT uCode, WCHAR wch, _KEYSTROKE_STATE keyState) : CEditSessionBase(pTextService, pContext) + { + _uCode = uCode; + _wch = wch; + _KeyState = keyState; + } + + // ITfEditSession + STDMETHODIMP DoEditSession(TfEditCookie ec); + +private: + UINT _uCode; // virtual key code + WCHAR _wch; // character code + _KEYSTROKE_STATE _KeyState; // key function regarding virtual key +}; diff --git a/KeyStateCategory.cpp b/KeyStateCategory.cpp new file mode 100644 index 00000000..b9deb55b --- /dev/null +++ b/KeyStateCategory.cpp @@ -0,0 +1,370 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "KeyStateCategory.h" + +CKeyStateCategoryFactory* CKeyStateCategoryFactory::_instance; + +CKeyStateCategoryFactory::CKeyStateCategoryFactory() +{ + _instance = nullptr; +} + +CKeyStateCategoryFactory* CKeyStateCategoryFactory::Instance() +{ + if (nullptr == _instance) + { + _instance = new (std::nothrow) CKeyStateCategoryFactory(); + } + + return _instance; +} + +CKeyStateCategory* CKeyStateCategoryFactory::MakeKeyStateCategory(KEYSTROKE_CATEGORY keyCategory, _In_ CSampleIME *pTextService) +{ + CKeyStateCategory* pKeyState = nullptr; + + switch (keyCategory) + { + case CATEGORY_NONE: + pKeyState = new (std::nothrow) CKeyStateNull(pTextService); + break; + + case CATEGORY_COMPOSING: + pKeyState = new (std::nothrow) CKeyStateComposing(pTextService); + break; + + case CATEGORY_CANDIDATE: + pKeyState = new (std::nothrow) CKeyStateCandidate(pTextService); + break; + + case CATEGORY_PHRASE: + pKeyState = new (std::nothrow) CKeyStatePhrase(pTextService); + break; + + default: + pKeyState = new (std::nothrow) CKeyStateNull(pTextService); + break; + } + return pKeyState; +} + +void CKeyStateCategoryFactory::Release() +{ + if (_instance) + { + delete _instance; + _instance = nullptr; + } +} + +/* +class CKeyStateCategory +*/ +CKeyStateCategory::CKeyStateCategory(_In_ CSampleIME *pTextService) +{ + _pTextService = pTextService; +} + +CKeyStateCategory::~CKeyStateCategory(void) +{ +} + +HRESULT CKeyStateCategory::KeyStateHandler(KEYSTROKE_FUNCTION function, KeyHandlerEditSessionDTO dto) +{ + switch(function) + { + case FUNCTION_INPUT: + return HandleKeyInput(dto); + + case FUNCTION_FINALIZE_TEXTSTORE_AND_INPUT: + return HandleKeyFinalizeTextStoreAndInput(dto); + + case FUNCTION_FINALIZE_TEXTSTORE: + return HandleKeyFinalizeTextStore(dto); + + case FUNCTION_FINALIZE_CANDIDATELIST_AND_INPUT: + return HandleKeyFinalizeCandidatelistAndInput(dto); + + case FUNCTION_FINALIZE_CANDIDATELIST: + return HandleKeyFinalizeCandidatelist(dto); + + case FUNCTION_CONVERT: + return HandleKeyConvert(dto); + + case FUNCTION_CONVERT_WILDCARD: + return HandleKeyConvertWildCard(dto); + + case FUNCTION_CANCEL: + return HandleKeyCancel(dto); + + case FUNCTION_BACKSPACE: + return HandleKeyBackspace(dto); + + case FUNCTION_MOVE_LEFT: + case FUNCTION_MOVE_RIGHT: + return HandleKeyArrow(dto); + + case FUNCTION_MOVE_UP: + case FUNCTION_MOVE_DOWN: + case FUNCTION_MOVE_PAGE_UP: + case FUNCTION_MOVE_PAGE_DOWN: + case FUNCTION_MOVE_PAGE_TOP: + case FUNCTION_MOVE_PAGE_BOTTOM: + return HandleKeyArrow(dto); + + case FUNCTION_DOUBLE_SINGLE_BYTE: + return HandleKeyDoubleSingleByte(dto); + + case FUNCTION_PUNCTUATION: + return HandleKeyPunctuation(dto); + + case FUNCTION_SELECT_BY_NUMBER: + return HandleKeySelectByNumber(dto); + + } + return E_INVALIDARG; +} + +void CKeyStateCategory::Release() +{ + delete this; +} + +// _HandleCompositionInput +HRESULT CKeyStateCategory::HandleKeyInput(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +// HandleKeyFinalizeTextStore +HRESULT CKeyStateCategory::HandleKeyFinalizeTextStore(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} +// HandleKeyCompositionFinalizeTextStoreAndInput +HRESULT CKeyStateCategory::HandleKeyFinalizeTextStoreAndInput(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +// HandleKeyCompositionFinalizeCandidatelistAndInput +HRESULT CKeyStateCategory::HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +// HandleKeyCompositionFinalizeCandidatelist +HRESULT CKeyStateCategory::HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +// HandleKeyConvert +HRESULT CKeyStateCategory::HandleKeyConvert(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +// HandleKeyConvertWildCard +HRESULT CKeyStateCategory::HandleKeyConvertWildCard(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +//_HandleCancel +HRESULT CKeyStateCategory::HandleKeyCancel(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +//_HandleCompositionBackspace +HRESULT CKeyStateCategory::HandleKeyBackspace(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +//_HandleCompositionArrowKey +HRESULT CKeyStateCategory::HandleKeyArrow(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +//_HandleCompositionDoubleSingleByte +HRESULT CKeyStateCategory::HandleKeyDoubleSingleByte(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +//_HandleCompositionPunctuation +HRESULT CKeyStateCategory::HandleKeyPunctuation(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +HRESULT CKeyStateCategory::HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto) +{ + dto; + return E_NOTIMPL; +} + +/* +class CKeyStateComposing +*/ +CKeyStateComposing::CKeyStateComposing(_In_ CSampleIME *pTextService) : CKeyStateCategory(pTextService) +{ +} + +HRESULT CKeyStateComposing::HandleKeyInput(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionInput(dto.ec, dto.pContext, dto.wch); +} + +HRESULT CKeyStateComposing::HandleKeyFinalizeTextStoreAndInput(KeyHandlerEditSessionDTO dto) +{ + _pTextService->_HandleCompositionFinalize(dto.ec, dto.pContext, FALSE); + return _pTextService->_HandleCompositionInput(dto.ec, dto.pContext, dto.wch); +} + +HRESULT CKeyStateComposing::HandleKeyFinalizeTextStore(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionFinalize(dto.ec, dto.pContext, FALSE); +} + +HRESULT CKeyStateComposing::HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto) +{ + _pTextService->_HandleCompositionFinalize(dto.ec, dto.pContext, TRUE); + return _pTextService->_HandleCompositionInput(dto.ec, dto.pContext, dto.wch); +} + +HRESULT CKeyStateComposing::HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionFinalize(dto.ec, dto.pContext, TRUE); +} + +HRESULT CKeyStateComposing::HandleKeyConvert(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionConvert(dto.ec, dto.pContext, FALSE); +} + +HRESULT CKeyStateComposing::HandleKeyConvertWildCard(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionConvert(dto.ec, dto.pContext, TRUE); +} + +HRESULT CKeyStateComposing::HandleKeyCancel(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCancel(dto.ec, dto.pContext); +} + +HRESULT CKeyStateComposing::HandleKeyBackspace(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionBackspace(dto.ec, dto.pContext); +} + +HRESULT CKeyStateComposing::HandleKeyArrow(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionArrowKey(dto.ec, dto.pContext, dto.arrowKey); +} + +HRESULT CKeyStateComposing::HandleKeyDoubleSingleByte(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionDoubleSingleByte(dto.ec, dto.pContext, dto.wch); +} + +HRESULT CKeyStateComposing::HandleKeyPunctuation(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCompositionPunctuation(dto.ec, dto.pContext, dto.wch); +} + +/* +class CKeyStateCandidate +*/ +CKeyStateCandidate::CKeyStateCandidate(_In_ CSampleIME *pTextService) : CKeyStateCategory(pTextService) +{ +} + +// _HandleCandidateInput +HRESULT CKeyStateCandidate::HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCandidateFinalize(dto.ec, dto.pContext); +} + +// HandleKeyFinalizeCandidatelistAndInput +HRESULT CKeyStateCandidate::HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto) +{ + _pTextService->_HandleCandidateFinalize(dto.ec, dto.pContext); + return _pTextService->_HandleCompositionInput(dto.ec, dto.pContext, dto.wch); +} + +//_HandleCandidateConvert +HRESULT CKeyStateCandidate::HandleKeyConvert(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCandidateConvert(dto.ec, dto.pContext); +} + +//_HandleCancel +HRESULT CKeyStateCandidate::HandleKeyCancel(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCancel(dto.ec, dto.pContext); +} + +//_HandleCandidateArrowKey +HRESULT CKeyStateCandidate::HandleKeyArrow(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCandidateArrowKey(dto.ec, dto.pContext, dto.arrowKey); +} + +//_HandleCandidateSelectByNumber +HRESULT CKeyStateCandidate::HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCandidateSelectByNumber(dto.ec, dto.pContext, dto.code); +} + +/* +class CKeyStatePhrase +*/ + +CKeyStatePhrase::CKeyStatePhrase(_In_ CSampleIME *pTextService) : CKeyStateCategory(pTextService) +{ +} + +//HandleKeyFinalizeCandidatelist +HRESULT CKeyStatePhrase::HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandlePhraseFinalize(dto.ec, dto.pContext); +} + +//HandleKeyCancel +HRESULT CKeyStatePhrase::HandleKeyCancel(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandleCancel(dto.ec, dto.pContext); +} + +//HandleKeyArrow +HRESULT CKeyStatePhrase::HandleKeyArrow(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandlePhraseArrowKey(dto.ec, dto.pContext, dto.arrowKey); +} + +//HandleKeySelectByNumber +HRESULT CKeyStatePhrase::HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto) +{ + return _pTextService->_HandlePhraseSelectByNumber(dto.ec, dto.pContext, dto.code); +} \ No newline at end of file diff --git a/KeyStateCategory.h b/KeyStateCategory.h new file mode 100644 index 00000000..36b256a4 --- /dev/null +++ b/KeyStateCategory.h @@ -0,0 +1,236 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once +#include "Globals.h" +#include "Private.h" +#include "SampleIME.h" + +class CKeyStateCategory; + +class CKeyStateCategoryFactory +{ +public: + static CKeyStateCategoryFactory* Instance(); + CKeyStateCategory* MakeKeyStateCategory(KEYSTROKE_CATEGORY keyCategory, _In_ CSampleIME *pTextService); + void Release(); + +protected: + CKeyStateCategoryFactory(); + +private: + static CKeyStateCategoryFactory* _instance; + +}; + +typedef struct KeyHandlerEditSessionDTO +{ + KeyHandlerEditSessionDTO::KeyHandlerEditSessionDTO(TfEditCookie tFEC, _In_ ITfContext *pTfContext, UINT virualCode, WCHAR inputChar, KEYSTROKE_FUNCTION arrowKeyFunction) + { + ec = tFEC; + pContext = pTfContext; + code = virualCode; + wch = inputChar; + arrowKey = arrowKeyFunction; + } + + TfEditCookie ec; + ITfContext* pContext; + UINT code; + WCHAR wch; + KEYSTROKE_FUNCTION arrowKey; +}KeyHandlerEditSessionDTO; + +class CKeyStateCategory +{ +public: + CKeyStateCategory(_In_ CSampleIME *pTextService); + +protected: + ~CKeyStateCategory(void); + +public: + HRESULT KeyStateHandler(KEYSTROKE_FUNCTION function, KeyHandlerEditSessionDTO dto); + void Release(void); + +protected: + // HandleKeyInput + virtual HRESULT HandleKeyInput(KeyHandlerEditSessionDTO dto); + + // HandleKeyFinalizeTextStoreAndInput + virtual HRESULT HandleKeyFinalizeTextStoreAndInput(KeyHandlerEditSessionDTO dto); + + // HandleKeyFinalizeTextStore + virtual HRESULT HandleKeyFinalizeTextStore(KeyHandlerEditSessionDTO dto); + + // HandleKeyFinalizeCandidatelistAndInput + virtual HRESULT HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto); + + // HandleKeyFinalizeCandidatelist + virtual HRESULT HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto); + + // HandleKeyConvert + virtual HRESULT HandleKeyConvert(KeyHandlerEditSessionDTO dto); + + // HandleKeyConvertWild + virtual HRESULT HandleKeyConvertWildCard(KeyHandlerEditSessionDTO dto); + + // HandleKeyCancel + virtual HRESULT HandleKeyCancel(KeyHandlerEditSessionDTO dto); + + // HandleKeyBackspace + virtual HRESULT HandleKeyBackspace(KeyHandlerEditSessionDTO dto); + + // HandleKeyArrow + virtual HRESULT HandleKeyArrow(KeyHandlerEditSessionDTO dto); + + // HandleKeyDoubleSingleByte + virtual HRESULT HandleKeyDoubleSingleByte(KeyHandlerEditSessionDTO dto); + + // HandleKeyPunctuation + virtual HRESULT HandleKeyPunctuation(KeyHandlerEditSessionDTO dto); + + // HandleKeySelectByNumber + virtual HRESULT HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto); + +protected: + CSampleIME* _pTextService; +}; + +class CKeyStateComposing : public CKeyStateCategory +{ +public: + CKeyStateComposing(_In_ CSampleIME *pTextService); + +protected: + // _HandleCompositionInput + HRESULT HandleKeyInput(KeyHandlerEditSessionDTO dto); + + // HandleKeyCompositionFinalizeTextStoreAndInput + HRESULT HandleKeyFinalizeTextStoreAndInput(KeyHandlerEditSessionDTO dto); + + // HandleKeyFinalizeTextStore + HRESULT HandleKeyFinalizeTextStore(KeyHandlerEditSessionDTO dto); + + // HandleKeyCompositionFinalizeCandidatelistAndInput + HRESULT HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto); + + // HandleKeyCompositionFinalizeCandidatelist + HRESULT HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto); + + // HandleCompositionConvert + HRESULT HandleKeyConvert(KeyHandlerEditSessionDTO dto); + + // HandleKeyCompositionConvertWildCard + HRESULT HandleKeyConvertWildCard(KeyHandlerEditSessionDTO dto); + + // HandleCancel + HRESULT HandleKeyCancel(KeyHandlerEditSessionDTO dto); + + // HandleCompositionBackspace + HRESULT HandleKeyBackspace(KeyHandlerEditSessionDTO dto); + + // HandleArrowKey + HRESULT HandleKeyArrow(KeyHandlerEditSessionDTO dto); + + // HandleKeyDoubleSingleByte + HRESULT HandleKeyDoubleSingleByte(KeyHandlerEditSessionDTO dto); + + // HandleKeyCompositionPunctuation + HRESULT HandleKeyPunctuation(KeyHandlerEditSessionDTO dto); +}; + +class CKeyStateCandidate : public CKeyStateCategory +{ +public: + CKeyStateCandidate(_In_ CSampleIME *pTextService); + +protected: + // HandleKeyFinalizeCandidatelist + HRESULT HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto); + + // HandleKeyFinalizeCandidatelistAndInput + HRESULT HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto); + + //_HandleCandidateConvert + HRESULT HandleKeyConvert(KeyHandlerEditSessionDTO dto); + + //_HandleCancel + HRESULT HandleKeyCancel(KeyHandlerEditSessionDTO dto); + + //_HandleCandidateArrowKey + HRESULT HandleKeyArrow(KeyHandlerEditSessionDTO dto); + + //_HandleCandidateSelectByNumber + HRESULT HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto); +}; + +class CKeyStatePhrase : public CKeyStateCategory +{ +public: + CKeyStatePhrase(_In_ CSampleIME *pTextService); + +protected: + //_HandleCancel + HRESULT HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto); + + //_HandleCancel + HRESULT HandleKeyCancel(KeyHandlerEditSessionDTO dto); + + //_HandlePhraseArrowKey + HRESULT HandleKeyArrow(KeyHandlerEditSessionDTO dto); + + //_HandlePhraseSelectByNumber + HRESULT HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto); +}; + +//degeneration class +class CKeyStateNull : public CKeyStateCategory +{ +public: + CKeyStateNull(_In_ CSampleIME *pTextService) : CKeyStateCategory(pTextService) {}; + +protected: + // _HandleNullInput + HRESULT HandleKeyInput(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyInput(dto); }; + + // HandleKeyNullFinalizeTextStoreAndInput + HRESULT HandleKeyFinalizeTextStoreAndInput(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyFinalizeTextStoreAndInput(dto); }; + + // HandleKeyFinalizeTextStore + HRESULT HandleKeyFinalizeTextStore(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyFinalizeTextStore(dto); }; + + // HandleKeyNullFinalizeCandidatelistAndInput + HRESULT HandleKeyFinalizeCandidatelistAndInput(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyFinalizeCandidatelistAndInput(dto); }; + + // HandleKeyNullFinalizeCandidatelist + HRESULT HandleKeyFinalizeCandidatelist(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyFinalizeCandidatelist(dto); }; + + //_HandleNullConvert + HRESULT HandleKeyConvert(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyConvert(dto); }; + + //_HandleNullCancel + HRESULT HandleKeyCancel(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyCancel(dto); }; + + // HandleKeyNullConvertWild + HRESULT HandleKeyConvertWildCard(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyConvertWildCard(dto); }; + + //_HandleNullBackspace + HRESULT HandleKeyBackspace(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyBackspace(dto); }; + + //_HandleNullArrowKey + HRESULT HandleKeyArrow(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyArrow(dto); }; + + // HandleKeyDoubleSingleByte + HRESULT HandleKeyDoubleSingleByte(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyDoubleSingleByte(dto); }; + + // HandleKeyPunctuation + HRESULT HandleKeyPunctuation(KeyHandlerEditSessionDTO dto) { return __super::HandleKeyPunctuation(dto); }; + + //_HandleNullCandidateSelectByNumber + HRESULT HandleKeySelectByNumber(KeyHandlerEditSessionDTO dto) { return __super::HandleKeySelectByNumber(dto); }; +}; \ No newline at end of file diff --git a/LanguageBar.cpp b/LanguageBar.cpp new file mode 100644 index 00000000..1dcc4f37 --- /dev/null +++ b/LanguageBar.cpp @@ -0,0 +1,662 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "SampleIME.h" +#include "CompositionProcessorEngine.h" +#include "LanguageBar.h" +#include "Globals.h" +#include "Compartment.h" + +//+--------------------------------------------------------------------------- +// +// CSampleIME::_UpdateLanguageBarOnSetFocus +// +//---------------------------------------------------------------------------- + +void CSampleIME::_UpdateLanguageBarOnSetFocus(_In_ ITfDocumentMgr *pDocMgrFocus) +{ + BOOL needDisableButtons = FALSE; + + if (!pDocMgrFocus) + { + needDisableButtons = TRUE; + } + else + { + IEnumTfContexts* pEnumContext = nullptr; + + if (FAILED(pDocMgrFocus->EnumContexts(&pEnumContext)) || !pEnumContext) + { + needDisableButtons = TRUE; + } + else + { + ULONG fetched = 0; + ITfContext* pContext = nullptr; + + if (FAILED(pEnumContext->Next(1, &pContext, &fetched)) || fetched != 1) + { + needDisableButtons = TRUE; + } + + if (!pContext) + { + // context is not associated + needDisableButtons = TRUE; + } + else + { + pContext->Release(); + } + } + + if (pEnumContext) + { + pEnumContext->Release(); + } + } + + CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr; + pCompositionProcessorEngine = _pCompositionProcessorEngine; + + pCompositionProcessorEngine->SetLanguageBarStatus(TF_LBI_STATUS_DISABLED, needDisableButtons); +} + +//+--------------------------------------------------------------------------- +// +// CCompositionProcessorEngine::SetLanguageBarStatus +// +//---------------------------------------------------------------------------- + +VOID CCompositionProcessorEngine::SetLanguageBarStatus(DWORD status, BOOL isSet) +{ + if (_pLanguageBar_IMEMode) { + _pLanguageBar_IMEMode->SetStatus(status, isSet); + } + if (_pLanguageBar_DoubleSingleByte) { + _pLanguageBar_DoubleSingleByte->SetStatus(status, isSet); + } + if (_pLanguageBar_Punctuation) { + _pLanguageBar_Punctuation->SetStatus(status, isSet); + } +} + +//+--------------------------------------------------------------------------- +// +// CLangBarItemButton::ctor +// +//---------------------------------------------------------------------------- + +CLangBarItemButton::CLangBarItemButton(REFGUID guidLangBar, LPCWSTR description, LPCWSTR tooltip, DWORD onIconIndex, DWORD offIconIndex, BOOL isSecureMode) +{ + DWORD bufLen = 0; + + DllAddRef(); + + // initialize TF_LANGBARITEMINFO structure. + _tfLangBarItemInfo.clsidService = Global::SampleIMECLSID; // This LangBarItem belongs to this TextService. + _tfLangBarItemInfo.guidItem = guidLangBar; // GUID of this LangBarItem. + _tfLangBarItemInfo.dwStyle = (TF_LBI_STYLE_BTN_BUTTON | TF_LBI_STYLE_SHOWNINTRAY); // This LangBar is a button type. + _tfLangBarItemInfo.ulSort = 0; // The position of this LangBar Item is not specified. + StringCchCopy(_tfLangBarItemInfo.szDescription, ARRAYSIZE(_tfLangBarItemInfo.szDescription), description); // Set the description of this LangBar Item. + + // Initialize the sink pointer to NULL. + _pLangBarItemSink = nullptr; + + // Initialize ICON index and file name. + _onIconIndex = onIconIndex; + _offIconIndex = offIconIndex; + + // Initialize compartment. + _pCompartment = nullptr; + _pCompartmentEventSink = nullptr; + + _isAddedToLanguageBar = FALSE; + _isSecureMode = isSecureMode; + _status = 0; + + _refCount = 1; + + // Initialize Tooltip + _pTooltipText = nullptr; + if (tooltip) + { + size_t len = 0; + if (StringCchLength(tooltip, STRSAFE_MAX_CCH, &len) != S_OK) + { + len = 0; + } + bufLen = static_cast(len) + 1; + _pTooltipText = (LPCWSTR) new (std::nothrow) WCHAR[ bufLen ]; + if (_pTooltipText) + { + StringCchCopy((LPWSTR)_pTooltipText, bufLen, tooltip); + } + } +} + +//+--------------------------------------------------------------------------- +// +// CLangBarItemButton::dtor +// +//---------------------------------------------------------------------------- + +CLangBarItemButton::~CLangBarItemButton() +{ + DllRelease(); + CleanUp(); +} + +//+--------------------------------------------------------------------------- +// +// CLangBarItemButton::CleanUp +// +//---------------------------------------------------------------------------- + +void CLangBarItemButton::CleanUp() +{ + if (_pTooltipText) + { + delete [] _pTooltipText; + _pTooltipText = nullptr; + } + + ITfThreadMgr* pThreadMgr = nullptr; + HRESULT hr = CoCreateInstance(CLSID_TF_ThreadMgr, + NULL, + CLSCTX_INPROC_SERVER, + IID_ITfThreadMgr, + (void**)&pThreadMgr); + if (SUCCEEDED(hr)) + { + _UnregisterCompartment(pThreadMgr); + + _RemoveItem(pThreadMgr); + pThreadMgr->Release(); + pThreadMgr = nullptr; + } + + if (_pCompartment) + { + delete _pCompartment; + _pCompartment = nullptr; + } + + if (_pCompartmentEventSink) + { + delete _pCompartmentEventSink; + _pCompartmentEventSink = nullptr; + } +} + +//+--------------------------------------------------------------------------- +// +// CLangBarItemButton::QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfLangBarItem) || + IsEqualIID(riid, IID_ITfLangBarItemButton)) + { + *ppvObj = (ITfLangBarItemButton *)this; + } + else if (IsEqualIID(riid, IID_ITfSource)) + { + *ppvObj = (ITfSource *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + + +//+--------------------------------------------------------------------------- +// +// CLangBarItemButton::AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CLangBarItemButton::AddRef() +{ + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// CLangBarItemButton::Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CLangBarItemButton::Release() +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// GetInfo +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::GetInfo(_Out_ TF_LANGBARITEMINFO *pInfo) +{ + _tfLangBarItemInfo.dwStyle |= TF_LBI_STYLE_SHOWNINTRAY; + *pInfo = _tfLangBarItemInfo; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// GetStatus +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::GetStatus(_Out_ DWORD *pdwStatus) +{ + if (pdwStatus == nullptr) + { + E_INVALIDARG; + } + + *pdwStatus = _status; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// SetStatus +// +//---------------------------------------------------------------------------- + +void CLangBarItemButton::SetStatus(DWORD dwStatus, BOOL fSet) +{ + BOOL isChange = FALSE; + + if (fSet) + { + if (!(_status & dwStatus)) + { + _status |= dwStatus; + isChange = TRUE; + } + } + else + { + if (_status & dwStatus) + { + _status &= ~dwStatus; + isChange = TRUE; + } + } + + if (isChange && _pLangBarItemSink) + { + _pLangBarItemSink->OnUpdate(TF_LBI_STATUS | TF_LBI_ICON); + } + + return; +} + +//+--------------------------------------------------------------------------- +// +// Show +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::Show(BOOL fShow) +{ + fShow; + if (_pLangBarItemSink) + { + _pLangBarItemSink->OnUpdate(TF_LBI_STATUS); + } + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// GetTooltipString +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::GetTooltipString(_Out_ BSTR *pbstrToolTip) +{ + *pbstrToolTip = SysAllocString(_pTooltipText); + + return (*pbstrToolTip == nullptr) ? E_OUTOFMEMORY : S_OK; +} + +//+--------------------------------------------------------------------------- +// +// OnClick +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::OnClick(TfLBIClick click, POINT pt, _In_ const RECT *prcArea) +{ + click;pt; + prcArea; + + BOOL isOn = FALSE; + + _pCompartment->_GetCompartmentBOOL(isOn); + _pCompartment->_SetCompartmentBOOL(isOn ? FALSE : TRUE); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// InitMenu +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::InitMenu(_In_ ITfMenu *pMenu) +{ + pMenu; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// OnMenuSelect +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::OnMenuSelect(UINT wID) +{ + wID; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// GetIcon +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::GetIcon(_Out_ HICON *phIcon) +{ + BOOL isOn = FALSE; + + if (!_pCompartment) + { + return E_FAIL; + } + if (!phIcon) + { + return E_FAIL; + } + *phIcon = nullptr; + + _pCompartment->_GetCompartmentBOOL(isOn); + + DWORD status = 0; + GetStatus(&status); + + // If IME is working on the UAC mode, the size of ICON should be 24 x 24. + int desiredSize = 16; + if (_isSecureMode) // detect UAC mode + { + desiredSize = _isSecureMode ? 24 : 16; + } + + if (isOn && !(status & TF_LBI_STATUS_DISABLED)) + { + if (Global::dllInstanceHandle) + { + *phIcon = reinterpret_cast(LoadImage(Global::dllInstanceHandle, MAKEINTRESOURCE(_onIconIndex), IMAGE_ICON, desiredSize, desiredSize, 0)); + } + } + else + { + if (Global::dllInstanceHandle) + { + *phIcon = reinterpret_cast(LoadImage(Global::dllInstanceHandle, MAKEINTRESOURCE(_offIconIndex), IMAGE_ICON, desiredSize, desiredSize, 0)); + } + } + + return (*phIcon != NULL) ? S_OK : E_FAIL; +} + +//+--------------------------------------------------------------------------- +// +// GetText +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::GetText(_Out_ BSTR *pbstrText) +{ + *pbstrText = SysAllocString(_tfLangBarItemInfo.szDescription); + + return (*pbstrText == nullptr) ? E_OUTOFMEMORY : S_OK; +} + +//+--------------------------------------------------------------------------- +// +// AdviseSink +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::AdviseSink(__RPC__in REFIID riid, __RPC__in_opt IUnknown *punk, __RPC__out DWORD *pdwCookie) +{ + // We allow only ITfLangBarItemSink interface. + if (!IsEqualIID(IID_ITfLangBarItemSink, riid)) + { + return CONNECT_E_CANNOTCONNECT; + } + + // We support only one sink once. + if (_pLangBarItemSink != nullptr) + { + return CONNECT_E_ADVISELIMIT; + } + + // Query the ITfLangBarItemSink interface and store it into _pLangBarItemSink. + if (punk == nullptr) + { + return E_INVALIDARG; + } + if (punk->QueryInterface(IID_ITfLangBarItemSink, (void **)&_pLangBarItemSink) != S_OK) + { + _pLangBarItemSink = nullptr; + return E_NOINTERFACE; + } + + // return our cookie. + *pdwCookie = _cookie; + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// UnadviseSink +// +//---------------------------------------------------------------------------- + +STDAPI CLangBarItemButton::UnadviseSink(DWORD dwCookie) +{ + // Check the given cookie. + if (dwCookie != _cookie) + { + return CONNECT_E_NOCONNECTION; + } + + // If there is nno connected sink, we just fail. + if (_pLangBarItemSink == nullptr) + { + return CONNECT_E_NOCONNECTION; + } + + _pLangBarItemSink->Release(); + _pLangBarItemSink = nullptr; + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _AddItem +// +//---------------------------------------------------------------------------- + +HRESULT CLangBarItemButton::_AddItem(_In_ ITfThreadMgr *pThreadMgr) +{ + HRESULT hr = S_OK; + ITfLangBarItemMgr* pLangBarItemMgr = nullptr; + + if (_isAddedToLanguageBar) + { + return S_OK; + } + + hr = pThreadMgr->QueryInterface(IID_ITfLangBarItemMgr, (void **)&pLangBarItemMgr); + if (SUCCEEDED(hr)) + { + hr = pLangBarItemMgr->AddItem(this); + if (SUCCEEDED(hr)) + { + _isAddedToLanguageBar = TRUE; + } + pLangBarItemMgr->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _RemoveItem +// +//---------------------------------------------------------------------------- + +HRESULT CLangBarItemButton::_RemoveItem(_In_ ITfThreadMgr *pThreadMgr) +{ + HRESULT hr = S_OK; + ITfLangBarItemMgr* pLangBarItemMgr = nullptr; + + if (!_isAddedToLanguageBar) + { + return S_OK; + } + + hr = pThreadMgr->QueryInterface(IID_ITfLangBarItemMgr, (void **)&pLangBarItemMgr); + if (SUCCEEDED(hr)) + { + hr = pLangBarItemMgr->RemoveItem(this); + if (SUCCEEDED(hr)) + { + _isAddedToLanguageBar = FALSE; + } + pLangBarItemMgr->Release(); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// _RegisterCompartment +// +//---------------------------------------------------------------------------- + +BOOL CLangBarItemButton::_RegisterCompartment(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, REFGUID guidCompartment) +{ + _pCompartment = new (std::nothrow) CCompartment(pThreadMgr, tfClientId, guidCompartment); + if (_pCompartment) + { + // Advice ITfCompartmentEventSink + _pCompartmentEventSink = new (std::nothrow) CCompartmentEventSink(_CompartmentCallback, this); + if (_pCompartmentEventSink) + { + _pCompartmentEventSink->_Advise(pThreadMgr, guidCompartment); + } + else + { + delete _pCompartment; + _pCompartment = nullptr; + } + } + + return _pCompartment ? TRUE : FALSE; +} + +//+--------------------------------------------------------------------------- +// +// _UnregisterCompartment +// +//---------------------------------------------------------------------------- + +BOOL CLangBarItemButton::_UnregisterCompartment(_In_ ITfThreadMgr *pThreadMgr) +{ + pThreadMgr; + if (_pCompartment) + { + // Unadvice ITfCompartmentEventSink + if (_pCompartmentEventSink) + { + _pCompartmentEventSink->_Unadvise(); + } + + // clear ITfCompartment + _pCompartment->_ClearCompartment(); + } + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _CompartmentCallback +// +//---------------------------------------------------------------------------- + +// static +HRESULT CLangBarItemButton::_CompartmentCallback(_In_ void *pv, REFGUID guidCompartment) +{ + CLangBarItemButton* fakeThis = (CLangBarItemButton*)pv; + + GUID guid = GUID_NULL; + fakeThis->_pCompartment->_GetGUID(&guid); + + if (IsEqualGUID(guid, guidCompartment)) + { + if (fakeThis->_pLangBarItemSink) + { + fakeThis->_pLangBarItemSink->OnUpdate(TF_LBI_STATUS | TF_LBI_ICON); + } + } + + return S_OK; +} diff --git a/LanguageBar.h b/LanguageBar.h new file mode 100644 index 00000000..491cf0c6 --- /dev/null +++ b/LanguageBar.h @@ -0,0 +1,74 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +class CCompartment; +class CCompartmentEventSink; + +class CLangBarItemButton : public ITfLangBarItemButton, + public ITfSource +{ +public: + CLangBarItemButton(REFGUID guidLangBar, LPCWSTR description, LPCWSTR tooltip, DWORD onIconIndex, DWORD offIconIndex, BOOL isSecureMode); + ~CLangBarItemButton(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfLangBarItem + STDMETHODIMP GetInfo(_Out_ TF_LANGBARITEMINFO *pInfo); + STDMETHODIMP GetStatus(_Out_ DWORD *pdwStatus); + STDMETHODIMP Show(BOOL fShow); + STDMETHODIMP GetTooltipString(_Out_ BSTR *pbstrToolTip); + + // ITfLangBarItemButton + STDMETHODIMP OnClick(TfLBIClick click, POINT pt, _In_ const RECT *prcArea); + STDMETHODIMP InitMenu(_In_ ITfMenu *pMenu); + STDMETHODIMP OnMenuSelect(UINT wID); + STDMETHODIMP GetIcon(_Out_ HICON *phIcon); + STDMETHODIMP GetText(_Out_ BSTR *pbstrText); + + // ITfSource + STDMETHODIMP AdviseSink(__RPC__in REFIID riid, __RPC__in_opt IUnknown *punk, __RPC__out DWORD *pdwCookie); + STDMETHODIMP UnadviseSink(DWORD dwCookie); + + // Add/Remove languagebar item + HRESULT _AddItem(_In_ ITfThreadMgr *pThreadMgr); + HRESULT _RemoveItem(_In_ ITfThreadMgr *pThreadMgr); + + // Register compartment for button On/Off switch + BOOL _RegisterCompartment(_In_ ITfThreadMgr *pThreadMgr, TfClientId tfClientId, REFGUID guidCompartment); + BOOL _UnregisterCompartment(_In_ ITfThreadMgr *pThreadMgr); + + void CleanUp(); + + void SetStatus(DWORD dwStatus, BOOL fSet); + +private: + ITfLangBarItemSink* _pLangBarItemSink; + + TF_LANGBARITEMINFO _tfLangBarItemInfo; + LPCWSTR _pTooltipText; + DWORD _onIconIndex; + DWORD _offIconIndex; + + BOOL _isAddedToLanguageBar; + BOOL _isSecureMode; + DWORD _status; + + CCompartment* _pCompartment; + CCompartmentEventSink* _pCompartmentEventSink; + static HRESULT _CompartmentCallback(_In_ void *pv, REFGUID guidCompartment); + + // The cookie for the sink to CLangBarItemButton. + static const DWORD _cookie = 0; + + LONG _refCount; +}; diff --git a/Private.h b/Private.h new file mode 100644 index 00000000..3ddfecb5 --- /dev/null +++ b/Private.h @@ -0,0 +1,22 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "stdafx.h" +#include "sal.h" + +#include +#include +#include + +#include +#include + +#include "initguid.h" +#include "msctf.h" +#include "ctffunc.h" diff --git a/README.md b/README.md new file mode 100644 index 00000000..b3c02a7e --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +how to install ime + +``` +regsvr32 "C:\Program Files\SampleIME\SampleIME.dll" +regsvr32 "C:\Program Files (x86)\SampleIME\SampleIME.dll" +``` + +how to uninstall ime + +```shell +regsvr32 /u "C:\Program Files\SampleIME\SampleIME.dll" +regsvr32 /u "C:\Program Files (x86)\SampleIME\SampleIME.dll" +``` diff --git a/RegKey.cpp b/RegKey.cpp new file mode 100644 index 00000000..63f387ce --- /dev/null +++ b/RegKey.cpp @@ -0,0 +1,278 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "RegKey.h" + +//--------------------------------------------------------------------- +// +// ctor +// +//--------------------------------------------------------------------- + +CRegKey::CRegKey() +{ + _keyHandle = nullptr; +} + +//--------------------------------------------------------------------- +// +// dtor +// +//--------------------------------------------------------------------- + +CRegKey::~CRegKey() +{ + Close(); +} + + +//--------------------------------------------------------------------- +// +// operator +// +//--------------------------------------------------------------------- + +HKEY CRegKey::GetHKEY() +{ + return _keyHandle; +} + +//--------------------------------------------------------------------- +// +// Create +// +//--------------------------------------------------------------------- + +LONG CRegKey::Create(_In_ HKEY hKeyPresent, _In_ LPCWSTR pwszKeyName, _In_reads_opt_(255) LPWSTR pwszClass, DWORD dwOptions, REGSAM samDesired, _Inout_ LPSECURITY_ATTRIBUTES lpSecAttr, _Out_opt_ LPDWORD lpdwDisposition) +{ + DWORD disposition = 0; + HKEY keyHandle = nullptr; + + LONG res = RegCreateKeyEx(hKeyPresent, pwszKeyName, 0, + pwszClass, dwOptions, samDesired, lpSecAttr, &keyHandle, &disposition); + + if (lpdwDisposition != nullptr) + { + *lpdwDisposition = disposition; + } + + if (res == ERROR_SUCCESS) + { + Close(); + _keyHandle = keyHandle; + } + + return res; +} + +//--------------------------------------------------------------------- +// +// Open +// +//--------------------------------------------------------------------- + +LONG CRegKey::Open(_In_ HKEY hKeyParent, _In_ LPCWSTR pwszKeyName, REGSAM samDesired) +{ + HKEY keyHandle = nullptr; + + LONG res = RegOpenKeyEx(hKeyParent, pwszKeyName, 0, samDesired, &keyHandle); + if (res == ERROR_SUCCESS) + { + Close(); + _keyHandle = keyHandle; + } + return res; +} + +//--------------------------------------------------------------------- +// +// Close +// +//--------------------------------------------------------------------- + +LONG CRegKey::Close() +{ + LONG res = ERROR_SUCCESS; + if (_keyHandle) + { + res = RegCloseKey(_keyHandle); + _keyHandle = nullptr; + } + return res; +} + +//--------------------------------------------------------------------- +// +// DeleteSubKey +// RecurseDeleteKey +// +//--------------------------------------------------------------------- + +LONG CRegKey::DeleteSubKey(_In_ LPCWSTR pwszSubKey) +{ + return RegDeleteKey(_keyHandle, pwszSubKey); +} + +LONG CRegKey::RecurseDeleteKey(_In_ LPCWSTR pwszSubKey) +{ + CRegKey key; + LONG res = key.Open(_keyHandle, pwszSubKey, KEY_READ | KEY_WRITE); + if (res != ERROR_SUCCESS) + { + return res; + } + FILETIME time; + WCHAR subKeyName[256] = {'\0'}; + DWORD subKeyNameSize = ARRAYSIZE(subKeyName); + + while (RegEnumKeyEx(key.GetHKEY(), 0, subKeyName, &subKeyNameSize, NULL, NULL, NULL, &time) == ERROR_SUCCESS) + { + subKeyName[ARRAYSIZE(subKeyName)-1] = L'\0'; + res = key.RecurseDeleteKey(subKeyName); + if (res != ERROR_SUCCESS) + { + return res; + } + subKeyNameSize = ARRAYSIZE(subKeyName); + } + + key.Close(); + + return DeleteSubKey(pwszSubKey); +} + +//--------------------------------------------------------------------- +// +// DeleteValue +// +//--------------------------------------------------------------------- + +LONG CRegKey::DeleteValue(_In_ LPCWSTR pwszValue) +{ + return RegDeleteValue(_keyHandle, pwszValue); +} + +//--------------------------------------------------------------------- +// +// QueryStingValue +// SetStringValue +// +//--------------------------------------------------------------------- + +LONG CRegKey::QueryStringValue(_In_opt_ LPCWSTR pwszValueName, _Out_writes_opt_(*pnChars) LPWSTR pwszValue, _Inout_ ULONG *pnChars) +{ + LONG res = 0; + DWORD dataType = REG_NONE; + ULONG pwszValueSize = 0; + + if (pnChars == nullptr) + { + return E_INVALIDARG; + } + + pwszValueSize = (*pnChars)*sizeof(WCHAR); + *pnChars = 0; + + res = RegQueryValueEx(_keyHandle, pwszValueName, NULL, &dataType, (LPBYTE)pwszValue, &pwszValueSize); + if (res != ERROR_SUCCESS) + { + return res; + } + if ((dataType != REG_SZ) && (dataType != REG_EXPAND_SZ)) + { + return ERROR_INVALID_DATA; + } + + *pnChars = pwszValueSize / sizeof(WCHAR); + + return ERROR_SUCCESS; +} + +LONG CRegKey::SetStringValue(_In_opt_ LPCWSTR pwszValueName, _In_ LPCWSTR pwszValue, DWORD dwType) +{ + size_t lenOfValue = 0; + if (pwszValue == nullptr) + { + return ERROR_INVALID_PARAMETER; + } + + if (StringCchLength(pwszValue, STRSAFE_MAX_CCH, &lenOfValue) != S_OK) + { + return ERROR_INVALID_PARAMETER; + } + DWORD len = static_cast(lenOfValue); + return RegSetValueEx(_keyHandle, pwszValueName, NULL, dwType, (LPBYTE)pwszValue, (++len)*sizeof(WCHAR)); +} + +//--------------------------------------------------------------------- +// +// QueryDWORDValue +// SetDWORDValue +// +//--------------------------------------------------------------------- + +LONG CRegKey::QueryDWORDValue(_In_opt_ LPCWSTR pwszValueName, _Out_ DWORD &dwValue) +{ + LONG res = 0; + DWORD dataType = REG_NONE; + ULONG pwszValueSize = 0; + + pwszValueSize = sizeof(DWORD); + res = RegQueryValueEx(_keyHandle, pwszValueName, NULL, &dataType, (LPBYTE)(&dwValue), &pwszValueSize); + if (res != ERROR_SUCCESS) + { + return res; + } + if (dataType != REG_DWORD) + { + return ERROR_INVALID_DATA; + } + + return ERROR_SUCCESS; +} + +LONG CRegKey::SetDWORDValue(_In_opt_ LPCWSTR pwszValueName, DWORD dwValue) +{ + return RegSetValueEx(_keyHandle, pwszValueName, NULL, REG_DWORD, (LPBYTE)(&dwValue), sizeof(DWORD)); +} + +//--------------------------------------------------------------------- +// +// QueryBinaryValue +// SetBinaryValue +// +//--------------------------------------------------------------------- + +LONG CRegKey::QueryBinaryValue(_In_opt_ LPCWSTR pwszValueName, _Out_writes_opt_(cbData) BYTE* lpData, DWORD cbData) +{ + LONG res = 0; + DWORD dataType = REG_NONE; + ULONG pwszValueSize = 0; + + pwszValueSize = cbData; + res = RegQueryValueEx(_keyHandle, pwszValueName, NULL, &dataType, lpData, &pwszValueSize); + if (res != ERROR_SUCCESS) + { + return res; + } + if (dataType != REG_BINARY) + { + return ERROR_INVALID_DATA; + } + if (pwszValueSize != cbData) + { + return ERROR_INVALID_DATA; + } + + return ERROR_SUCCESS; +} + +LONG CRegKey::SetBinaryValue(_In_opt_ LPCWSTR pwszValueName, _In_reads_(cbData) BYTE* lpData, DWORD cbData) +{ + return RegSetValueEx(_keyHandle, pwszValueName, NULL, REG_BINARY, lpData, cbData); +} diff --git a/RegKey.h b/RegKey.h new file mode 100644 index 00000000..cb251fd9 --- /dev/null +++ b/RegKey.h @@ -0,0 +1,45 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +class CRegKey +{ +public: + CRegKey(); + ~CRegKey(); + + HKEY GetHKEY(); + + LONG Create(_In_ HKEY hKeyPresent, _In_ LPCWSTR pwszKeyName, + _In_reads_opt_(255) LPWSTR pwszClass = REG_NONE, + DWORD dwOptions = REG_OPTION_NON_VOLATILE, + REGSAM samDesired = KEY_READ | KEY_WRITE, + _Inout_ LPSECURITY_ATTRIBUTES lpSecAttr = nullptr, + _Out_opt_ LPDWORD lpdwDisposition = nullptr); + + LONG Open(_In_ HKEY hKeyParent, _In_ LPCWSTR pwszKeyName, + REGSAM samDesired = KEY_READ | KEY_WRITE); + + LONG Close(); + + LONG DeleteSubKey(_In_ LPCWSTR pwszSubKey); + LONG RecurseDeleteKey(_In_ LPCWSTR pwszSubKey); + LONG DeleteValue(_In_ LPCWSTR pwszValue); + + LONG QueryStringValue(_In_opt_ LPCWSTR pwszValueName, _Out_writes_opt_(*pnChars) LPWSTR pwszValue, _Inout_ ULONG *pnChars); + LONG SetStringValue(_In_opt_ LPCWSTR pwszValueName, _In_ LPCWSTR pwszValue, DWORD dwType = REG_SZ); + + LONG QueryDWORDValue(_In_opt_ LPCWSTR pwszValueName, _Out_ DWORD &dwValue); + LONG SetDWORDValue(_In_opt_ LPCWSTR pwszValueName, DWORD dwValue); + + LONG QueryBinaryValue(_In_opt_ LPCWSTR pwszValueName, _Out_writes_opt_(cbData) BYTE* lpData, DWORD cbData); + LONG SetBinaryValue(_In_opt_ LPCWSTR pwszValueName, _In_reads_(cbData) BYTE* lpData, DWORD cbData); + +private: + HKEY _keyHandle; +}; diff --git a/Register.cpp b/Register.cpp new file mode 100644 index 00000000..f9ef2b9a --- /dev/null +++ b/Register.cpp @@ -0,0 +1,283 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" + +static const WCHAR RegInfo_Prefix_CLSID[] = L"CLSID\\"; +static const WCHAR RegInfo_Key_InProSvr32[] = L"InProcServer32"; +static const WCHAR RegInfo_Key_ThreadModel[] = L"ThreadingModel"; + +static const WCHAR TEXTSERVICE_DESC[] = L"Sample IME"; + +static const GUID SupportCategories[] = { + GUID_TFCAT_TIP_KEYBOARD, + GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER, + GUID_TFCAT_TIPCAP_UIELEMENTENABLED, + GUID_TFCAT_TIPCAP_SECUREMODE, + GUID_TFCAT_TIPCAP_COMLESS, + GUID_TFCAT_TIPCAP_INPUTMODECOMPARTMENT, + GUID_TFCAT_TIPCAP_IMMERSIVESUPPORT, + GUID_TFCAT_TIPCAP_SYSTRAYSUPPORT, +}; +//+--------------------------------------------------------------------------- +// +// RegisterProfiles +// +//---------------------------------------------------------------------------- + +BOOL RegisterProfiles() +{ + HRESULT hr = S_FALSE; + + ITfInputProcessorProfileMgr *pITfInputProcessorProfileMgr = nullptr; + hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, + IID_ITfInputProcessorProfileMgr, (void**)&pITfInputProcessorProfileMgr); + if (FAILED(hr)) + { + return FALSE; + } + + WCHAR achIconFile[MAX_PATH] = {'\0'}; + DWORD cchA = 0; + cchA = GetModuleFileName(Global::dllInstanceHandle, achIconFile, MAX_PATH); + cchA = cchA >= MAX_PATH ? (MAX_PATH - 1) : cchA; + achIconFile[cchA] = '\0'; + + size_t lenOfDesc = 0; + hr = StringCchLength(TEXTSERVICE_DESC, STRSAFE_MAX_CCH, &lenOfDesc); + if (hr != S_OK) + { + goto Exit; + } + hr = pITfInputProcessorProfileMgr->RegisterProfile(Global::SampleIMECLSID, + TEXTSERVICE_LANGID, + Global::SampleIMEGuidProfile, + TEXTSERVICE_DESC, + static_cast(lenOfDesc), + achIconFile, + cchA, + (UINT)TEXTSERVICE_ICON_INDEX, NULL, 0, TRUE, 0); + + if (FAILED(hr)) + { + goto Exit; + } + +Exit: + if (pITfInputProcessorProfileMgr) + { + pITfInputProcessorProfileMgr->Release(); + } + + return (hr == S_OK); +} + +//+--------------------------------------------------------------------------- +// +// UnregisterProfiles +// +//---------------------------------------------------------------------------- + +void UnregisterProfiles() +{ + HRESULT hr = S_OK; + + ITfInputProcessorProfileMgr *pITfInputProcessorProfileMgr = nullptr; + hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, + IID_ITfInputProcessorProfileMgr, (void**)&pITfInputProcessorProfileMgr); + if (FAILED(hr)) + { + goto Exit; + } + + hr = pITfInputProcessorProfileMgr->UnregisterProfile(Global::SampleIMECLSID, TEXTSERVICE_LANGID, Global::SampleIMEGuidProfile, 0); + if (FAILED(hr)) + { + goto Exit; + } + +Exit: + if (pITfInputProcessorProfileMgr) + { + pITfInputProcessorProfileMgr->Release(); + } + + return; +} + +//+--------------------------------------------------------------------------- +// +// RegisterCategories +// +//---------------------------------------------------------------------------- + +BOOL RegisterCategories() +{ + ITfCategoryMgr* pCategoryMgr = nullptr; + HRESULT hr = S_OK; + + hr = CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, (void**)&pCategoryMgr); + if (FAILED(hr)) + { + return FALSE; + } + + for each(GUID guid in SupportCategories) + { + hr = pCategoryMgr->RegisterCategory(Global::SampleIMECLSID, guid, Global::SampleIMECLSID); + } + + pCategoryMgr->Release(); + + return (hr == S_OK); +} + +//+--------------------------------------------------------------------------- +// +// UnregisterCategories +// +//---------------------------------------------------------------------------- + +void UnregisterCategories() +{ + ITfCategoryMgr* pCategoryMgr = S_OK; + HRESULT hr = S_OK; + + hr = CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, (void**)&pCategoryMgr); + if (FAILED(hr)) + { + return; + } + + for each(GUID guid in SupportCategories) + { + pCategoryMgr->UnregisterCategory(Global::SampleIMECLSID, guid, Global::SampleIMECLSID); + } + + pCategoryMgr->Release(); + + return; +} + +//+--------------------------------------------------------------------------- +// +// RecurseDeleteKey +// +// RecurseDeleteKey is necessary because on NT RegDeleteKey doesn't work if the +// specified key has subkeys +//---------------------------------------------------------------------------- + +LONG RecurseDeleteKey(_In_ HKEY hParentKey, _In_ LPCTSTR lpszKey) +{ + HKEY regKeyHandle = nullptr; + LONG res = 0; + FILETIME time; + WCHAR stringBuffer[256] = {'\0'}; + DWORD size = ARRAYSIZE(stringBuffer); + + if (RegOpenKey(hParentKey, lpszKey, ®KeyHandle) != ERROR_SUCCESS) + { + return ERROR_SUCCESS; + } + + res = ERROR_SUCCESS; + while (RegEnumKeyEx(regKeyHandle, 0, stringBuffer, &size, NULL, NULL, NULL, &time) == ERROR_SUCCESS) + { + stringBuffer[ARRAYSIZE(stringBuffer)-1] = '\0'; + res = RecurseDeleteKey(regKeyHandle, stringBuffer); + if (res != ERROR_SUCCESS) + { + break; + } + size = ARRAYSIZE(stringBuffer); + } + RegCloseKey(regKeyHandle); + + return res == ERROR_SUCCESS ? RegDeleteKey(hParentKey, lpszKey) : res; +} + +//+--------------------------------------------------------------------------- +// +// RegisterServer +// +//---------------------------------------------------------------------------- + +BOOL RegisterServer() +{ + DWORD copiedStringLen = 0; + HKEY regKeyHandle = nullptr; + HKEY regSubkeyHandle = nullptr; + BOOL ret = FALSE; + WCHAR achIMEKey[ARRAYSIZE(RegInfo_Prefix_CLSID) + CLSID_STRLEN] = {'\0'}; + WCHAR achFileName[MAX_PATH] = {'\0'}; + + if (!CLSIDToString(Global::SampleIMECLSID, achIMEKey + ARRAYSIZE(RegInfo_Prefix_CLSID) - 1)) + { + return FALSE; + } + + memcpy(achIMEKey, RegInfo_Prefix_CLSID, sizeof(RegInfo_Prefix_CLSID) - sizeof(WCHAR)); + + if (RegCreateKeyEx(HKEY_CLASSES_ROOT, achIMEKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, ®KeyHandle, &copiedStringLen) == ERROR_SUCCESS) + { + if (RegSetValueEx(regKeyHandle, NULL, 0, REG_SZ, (const BYTE *)TEXTSERVICE_DESC, (_countof(TEXTSERVICE_DESC))*sizeof(WCHAR)) != ERROR_SUCCESS) + { + goto Exit; + } + + if (RegCreateKeyEx(regKeyHandle, RegInfo_Key_InProSvr32, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, ®SubkeyHandle, &copiedStringLen) == ERROR_SUCCESS) + { + copiedStringLen = GetModuleFileNameW(Global::dllInstanceHandle, achFileName, ARRAYSIZE(achFileName)); + copiedStringLen = (copiedStringLen >= (MAX_PATH - 1)) ? MAX_PATH : (++copiedStringLen); + if (RegSetValueEx(regSubkeyHandle, NULL, 0, REG_SZ, (const BYTE *)achFileName, (copiedStringLen)*sizeof(WCHAR)) != ERROR_SUCCESS) + { + goto Exit; + } + if (RegSetValueEx(regSubkeyHandle, RegInfo_Key_ThreadModel, 0, REG_SZ, (const BYTE *)TEXTSERVICE_MODEL, (_countof(TEXTSERVICE_MODEL)) * sizeof(WCHAR)) != ERROR_SUCCESS) + { + goto Exit; + } + + ret = TRUE; + } + } + +Exit: + if (regSubkeyHandle) + { + RegCloseKey(regSubkeyHandle); + regSubkeyHandle = nullptr; + } + if (regKeyHandle) + { + RegCloseKey(regKeyHandle); + regKeyHandle = nullptr; + } + + return ret; +} + +//+--------------------------------------------------------------------------- +// +// UnregisterServer +// +//---------------------------------------------------------------------------- + +void UnregisterServer() +{ + WCHAR achIMEKey[ARRAYSIZE(RegInfo_Prefix_CLSID) + CLSID_STRLEN] = {'\0'}; + + if (!CLSIDToString(Global::SampleIMECLSID, achIMEKey + ARRAYSIZE(RegInfo_Prefix_CLSID) - 1)) + { + return; + } + + memcpy(achIMEKey, RegInfo_Prefix_CLSID, sizeof(RegInfo_Prefix_CLSID) - sizeof(WCHAR)); + + RecurseDeleteKey(HKEY_CLASSES_ROOT, achIMEKey); +} diff --git a/SampleIME.cpp b/SampleIME.cpp new file mode 100644 index 00000000..374f6da8 --- /dev/null +++ b/SampleIME.cpp @@ -0,0 +1,432 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "globals.h" +#include "SampleIME.h" +#include "CandidateListUIPresenter.h" +#include "CompositionProcessorEngine.h" +#include "Compartment.h" + +//+--------------------------------------------------------------------------- +// +// CreateInstance +// +//---------------------------------------------------------------------------- + +/* static */ +HRESULT CSampleIME::CreateInstance(_In_ IUnknown *pUnkOuter, REFIID riid, _Outptr_ void **ppvObj) +{ + CSampleIME* pSampleIME = nullptr; + HRESULT hr = S_OK; + + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + + *ppvObj = nullptr; + + if (nullptr != pUnkOuter) + { + return CLASS_E_NOAGGREGATION; + } + + pSampleIME = new (std::nothrow) CSampleIME(); + if (pSampleIME == nullptr) + { + return E_OUTOFMEMORY; + } + + hr = pSampleIME->QueryInterface(riid, ppvObj); + + pSampleIME->Release(); + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CSampleIME::CSampleIME() +{ + DllAddRef(); + + _pThreadMgr = nullptr; + + _threadMgrEventSinkCookie = TF_INVALID_COOKIE; + + _pTextEditSinkContext = nullptr; + _textEditSinkCookie = TF_INVALID_COOKIE; + + _activeLanguageProfileNotifySinkCookie = TF_INVALID_COOKIE; + + _dwThreadFocusSinkCookie = TF_INVALID_COOKIE; + + _pComposition = nullptr; + + _pCompositionProcessorEngine = nullptr; + + _candidateMode = CANDIDATE_NONE; + _pCandidateListUIPresenter = nullptr; + _isCandidateWithWildcard = FALSE; + + _pDocMgrLastFocused = nullptr; + + _pSIPIMEOnOffCompartment = nullptr; + _dwSIPIMEOnOffCompartmentSinkCookie = 0; + _msgWndHandle = nullptr; + + _pContext = nullptr; + + _refCount = 1; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CSampleIME::~CSampleIME() +{ + if (_pCandidateListUIPresenter) + { + delete _pCandidateListUIPresenter; + _pCandidateListUIPresenter = nullptr; + } + DllRelease(); +} + +//+--------------------------------------------------------------------------- +// +// QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfTextInputProcessor)) + { + *ppvObj = (ITfTextInputProcessor *)this; + } + else if (IsEqualIID(riid, IID_ITfTextInputProcessorEx)) + { + *ppvObj = (ITfTextInputProcessorEx *)this; + } + else if (IsEqualIID(riid, IID_ITfThreadMgrEventSink)) + { + *ppvObj = (ITfThreadMgrEventSink *)this; + } + else if (IsEqualIID(riid, IID_ITfTextEditSink)) + { + *ppvObj = (ITfTextEditSink *)this; + } + else if (IsEqualIID(riid, IID_ITfKeyEventSink)) + { + *ppvObj = (ITfKeyEventSink *)this; + } + else if (IsEqualIID(riid, IID_ITfActiveLanguageProfileNotifySink)) + { + *ppvObj = (ITfActiveLanguageProfileNotifySink *)this; + } + else if (IsEqualIID(riid, IID_ITfCompositionSink)) + { + *ppvObj = (ITfKeyEventSink *)this; + } + else if (IsEqualIID(riid, IID_ITfDisplayAttributeProvider)) + { + *ppvObj = (ITfDisplayAttributeProvider *)this; + } + else if (IsEqualIID(riid, IID_ITfThreadFocusSink)) + { + *ppvObj = (ITfThreadFocusSink *)this; + } + else if (IsEqualIID(riid, IID_ITfFunctionProvider)) + { + *ppvObj = (ITfFunctionProvider *)this; + } + else if (IsEqualIID(riid, IID_ITfFunction)) + { + *ppvObj = (ITfFunction *)this; + } + else if (IsEqualIID(riid, IID_ITfFnGetPreferredTouchKeyboardLayout)) + { + *ppvObj = (ITfFnGetPreferredTouchKeyboardLayout *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + + +//+--------------------------------------------------------------------------- +// +// AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CSampleIME::AddRef() +{ + return ++_refCount; +} + +//+--------------------------------------------------------------------------- +// +// Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CSampleIME::Release() +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// ITfTextInputProcessorEx::ActivateEx +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::ActivateEx(ITfThreadMgr *pThreadMgr, TfClientId tfClientId, DWORD dwFlags) +{ + _pThreadMgr = pThreadMgr; + _pThreadMgr->AddRef(); + + _tfClientId = tfClientId; + _dwActivateFlags = dwFlags; + + if (!_InitThreadMgrEventSink()) + { + goto ExitError; + } + + ITfDocumentMgr* pDocMgrFocus = nullptr; + if (SUCCEEDED(_pThreadMgr->GetFocus(&pDocMgrFocus)) && (pDocMgrFocus != nullptr)) + { + _InitTextEditSink(pDocMgrFocus); + pDocMgrFocus->Release(); + } + + if (!_InitKeyEventSink()) + { + goto ExitError; + } + + if (!_InitActiveLanguageProfileNotifySink()) + { + goto ExitError; + } + + if (!_InitThreadFocusSink()) + { + goto ExitError; + } + + if (!_InitDisplayAttributeGuidAtom()) + { + goto ExitError; + } + + if (!_InitFunctionProviderSink()) + { + goto ExitError; + } + + if (!_AddTextProcessorEngine()) + { + goto ExitError; + } + + return S_OK; + +ExitError: + Deactivate(); + return E_FAIL; +} + +//+--------------------------------------------------------------------------- +// +// ITfTextInputProcessorEx::Deactivate +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::Deactivate() +{ + if (_pCompositionProcessorEngine) + { + delete _pCompositionProcessorEngine; + _pCompositionProcessorEngine = nullptr; + } + + ITfContext* pContext = _pContext; + if (_pContext) + { + pContext->AddRef(); + _EndComposition(_pContext); + } + + if (_pCandidateListUIPresenter) + { + delete _pCandidateListUIPresenter; + _pCandidateListUIPresenter = nullptr; + + if (pContext) + { + pContext->Release(); + } + + _candidateMode = CANDIDATE_NONE; + _isCandidateWithWildcard = FALSE; + } + + _UninitFunctionProviderSink(); + + _UninitThreadFocusSink(); + + _UninitActiveLanguageProfileNotifySink(); + + _UninitKeyEventSink(); + + _UninitThreadMgrEventSink(); + + CCompartment CompartmentKeyboardOpen(_pThreadMgr, _tfClientId, GUID_COMPARTMENT_KEYBOARD_OPENCLOSE); + CompartmentKeyboardOpen._ClearCompartment(); + + CCompartment CompartmentDoubleSingleByte(_pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentDoubleSingleByte); + CompartmentDoubleSingleByte._ClearCompartment(); + + CCompartment CompartmentPunctuation(_pThreadMgr, _tfClientId, Global::SampleIMEGuidCompartmentPunctuation); + CompartmentDoubleSingleByte._ClearCompartment(); + + if (_pThreadMgr != nullptr) + { + _pThreadMgr->Release(); + } + + _tfClientId = TF_CLIENTID_NULL; + + if (_pDocMgrLastFocused) + { + _pDocMgrLastFocused->Release(); + _pDocMgrLastFocused = nullptr; + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfFunctionProvider::GetType +// +//---------------------------------------------------------------------------- +HRESULT CSampleIME::GetType(__RPC__out GUID *pguid) +{ + HRESULT hr = E_INVALIDARG; + if (pguid) + { + *pguid = Global::SampleIMECLSID; + hr = S_OK; + } + return hr; +} + +//+--------------------------------------------------------------------------- +// +// ITfFunctionProvider::::GetDescription +// +//---------------------------------------------------------------------------- +HRESULT CSampleIME::GetDescription(__RPC__deref_out_opt BSTR *pbstrDesc) +{ + HRESULT hr = E_INVALIDARG; + if (pbstrDesc != nullptr) + { + *pbstrDesc = nullptr; + hr = E_NOTIMPL; + } + return hr; +} + +//+--------------------------------------------------------------------------- +// +// ITfFunctionProvider::::GetFunction +// +//---------------------------------------------------------------------------- +HRESULT CSampleIME::GetFunction(__RPC__in REFGUID rguid, __RPC__in REFIID riid, __RPC__deref_out_opt IUnknown **ppunk) +{ + HRESULT hr = E_NOINTERFACE; + + if ((IsEqualGUID(rguid, GUID_NULL)) + && (IsEqualGUID(riid, __uuidof(ITfFnSearchCandidateProvider)))) + { + hr = _pITfFnSearchCandidateProvider->QueryInterface(riid, (void**)ppunk); + } + else if (IsEqualGUID(rguid, GUID_NULL)) + { + hr = QueryInterface(riid, (void **)ppunk); + } + + return hr; +} + +//+--------------------------------------------------------------------------- +// +// ITfFunction::GetDisplayName +// +//---------------------------------------------------------------------------- +HRESULT CSampleIME::GetDisplayName(_Out_ BSTR *pbstrDisplayName) +{ + HRESULT hr = E_INVALIDARG; + if (pbstrDisplayName != nullptr) + { + *pbstrDisplayName = nullptr; + hr = E_NOTIMPL; + } + return hr; +} + +//+--------------------------------------------------------------------------- +// +// ITfFnGetPreferredTouchKeyboardLayout::GetLayout +// The tkblayout will be Optimized layout. +//---------------------------------------------------------------------------- +HRESULT CSampleIME::GetLayout(_Out_ TKBLayoutType *ptkblayoutType, _Out_ WORD *pwPreferredLayoutId) +{ + HRESULT hr = E_INVALIDARG; + if ((ptkblayoutType != nullptr) && (pwPreferredLayoutId != nullptr)) + { + *ptkblayoutType = TKBLT_OPTIMIZED; + *pwPreferredLayoutId = TKBL_OPT_SIMPLIFIED_CHINESE_PINYIN; + hr = S_OK; + } + return hr; +} \ No newline at end of file diff --git a/SampleIME.def b/SampleIME.def new file mode 100644 index 00000000..8342c9cb --- /dev/null +++ b/SampleIME.def @@ -0,0 +1,7 @@ +LIBRARY SampleIME.dll + +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/SampleIME.h b/SampleIME.h new file mode 100644 index 00000000..b361acd2 --- /dev/null +++ b/SampleIME.h @@ -0,0 +1,248 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "KeyHandlerEditSession.h" +#include "SampleIMEBaseStructure.h" + +class CLangBarItemButton; +class CCandidateListUIPresenter; +class CCompositionProcessorEngine; + +const DWORD WM_CheckGlobalCompartment = WM_USER; +LRESULT CALLBACK CSampleIME_WindowProc(HWND wndHandle, UINT uMsg, WPARAM wParam, LPARAM lParam); + +class CSampleIME : public ITfTextInputProcessorEx, + public ITfThreadMgrEventSink, + public ITfTextEditSink, + public ITfKeyEventSink, + public ITfCompositionSink, + public ITfDisplayAttributeProvider, + public ITfActiveLanguageProfileNotifySink, + public ITfThreadFocusSink, + public ITfFunctionProvider, + public ITfFnGetPreferredTouchKeyboardLayout +{ +public: + CSampleIME(); + ~CSampleIME(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfTextInputProcessor + STDMETHODIMP Activate(ITfThreadMgr *pThreadMgr, TfClientId tfClientId) { + return ActivateEx(pThreadMgr, tfClientId, 0); + } + // ITfTextInputProcessorEx + STDMETHODIMP ActivateEx(ITfThreadMgr *pThreadMgr, TfClientId tfClientId, DWORD dwFlags); + STDMETHODIMP Deactivate(); + + // ITfThreadMgrEventSink + STDMETHODIMP OnInitDocumentMgr(_In_ ITfDocumentMgr *pDocMgr); + STDMETHODIMP OnUninitDocumentMgr(_In_ ITfDocumentMgr *pDocMgr); + STDMETHODIMP OnSetFocus(_In_ ITfDocumentMgr *pDocMgrFocus, _In_ ITfDocumentMgr *pDocMgrPrevFocus); + STDMETHODIMP OnPushContext(_In_ ITfContext *pContext); + STDMETHODIMP OnPopContext(_In_ ITfContext *pContext); + + // ITfTextEditSink + STDMETHODIMP OnEndEdit(__RPC__in_opt ITfContext *pContext, TfEditCookie ecReadOnly, __RPC__in_opt ITfEditRecord *pEditRecord); + + // ITfKeyEventSink + STDMETHODIMP OnSetFocus(BOOL fForeground); + STDMETHODIMP OnTestKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten); + STDMETHODIMP OnKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten); + STDMETHODIMP OnTestKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten); + STDMETHODIMP OnKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL *pIsEaten); + STDMETHODIMP OnPreservedKey(ITfContext *pContext, REFGUID rguid, BOOL *pIsEaten); + + // ITfCompositionSink + STDMETHODIMP OnCompositionTerminated(TfEditCookie ecWrite, _In_ ITfComposition *pComposition); + + // ITfDisplayAttributeProvider + STDMETHODIMP EnumDisplayAttributeInfo(__RPC__deref_out_opt IEnumTfDisplayAttributeInfo **ppEnum); + STDMETHODIMP GetDisplayAttributeInfo(__RPC__in REFGUID guidInfo, __RPC__deref_out_opt ITfDisplayAttributeInfo **ppInfo); + + // ITfActiveLanguageProfileNotifySink + STDMETHODIMP OnActivated(_In_ REFCLSID clsid, _In_ REFGUID guidProfile, _In_ BOOL isActivated); + + // ITfThreadFocusSink + STDMETHODIMP OnSetThreadFocus(); + STDMETHODIMP OnKillThreadFocus(); + + // ITfFunctionProvider + STDMETHODIMP GetType(__RPC__out GUID *pguid); + STDMETHODIMP GetDescription(__RPC__deref_out_opt BSTR *pbstrDesc); + STDMETHODIMP GetFunction(__RPC__in REFGUID rguid, __RPC__in REFIID riid, __RPC__deref_out_opt IUnknown **ppunk); + + // ITfFunction + STDMETHODIMP GetDisplayName(_Out_ BSTR *pbstrDisplayName); + + // ITfFnGetPreferredTouchKeyboardLayout, it is the Optimized layout feature. + STDMETHODIMP GetLayout(_Out_ TKBLayoutType *ptkblayoutType, _Out_ WORD *pwPreferredLayoutId); + + // CClassFactory factory callback + static HRESULT CreateInstance(_In_ IUnknown *pUnkOuter, REFIID riid, _Outptr_ void **ppvObj); + + // utility function for thread manager. + ITfThreadMgr* _GetThreadMgr() { return _pThreadMgr; } + TfClientId _GetClientId() { return _tfClientId; } + + // functions for the composition object. + void _SetComposition(_In_ ITfComposition *pComposition); + void _TerminateComposition(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isCalledFromDeactivate = FALSE); + void _SaveCompositionContext(_In_ ITfContext *pContext); + + // key event handlers for composition/candidate/phrase common objects. + HRESULT _HandleComplete(TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _HandleCancel(TfEditCookie ec, _In_ ITfContext *pContext); + + // key event handlers for composition object. + HRESULT _HandleCompositionInput(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch); + HRESULT _HandleCompositionFinalize(TfEditCookie ec, _In_ ITfContext *pContext, BOOL fCandidateList); + HRESULT _HandleCompositionConvert(TfEditCookie ec, _In_ ITfContext *pContext, BOOL isWildcardSearch); + HRESULT _HandleCompositionBackspace(TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _HandleCompositionArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, KEYSTROKE_FUNCTION keyFunction); + HRESULT _HandleCompositionPunctuation(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch); + HRESULT _HandleCompositionDoubleSingleByte(TfEditCookie ec, _In_ ITfContext *pContext, WCHAR wch); + + // key event handlers for candidate object. + HRESULT _HandleCandidateFinalize(TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _HandleCandidateConvert(TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _HandleCandidateArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, _In_ KEYSTROKE_FUNCTION keyFunction); + HRESULT _HandleCandidateSelectByNumber(TfEditCookie ec, _In_ ITfContext *pContext, _In_ UINT uCode); + + // key event handlers for phrase object. + HRESULT _HandlePhraseFinalize(TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _HandlePhraseArrowKey(TfEditCookie ec, _In_ ITfContext *pContext, _In_ KEYSTROKE_FUNCTION keyFunction); + HRESULT _HandlePhraseSelectByNumber(TfEditCookie ec, _In_ ITfContext *pContext, _In_ UINT uCode); + + BOOL _IsSecureMode(void) { return (_dwActivateFlags & TF_TMAE_SECUREMODE) ? TRUE : FALSE; } + BOOL _IsComLess(void) { return (_dwActivateFlags & TF_TMAE_COMLESS) ? TRUE : FALSE; } + BOOL _IsStoreAppMode(void) { return (_dwActivateFlags & TF_TMF_IMMERSIVEMODE) ? TRUE : FALSE; }; + + CCompositionProcessorEngine* GetCompositionProcessorEngine() { return (_pCompositionProcessorEngine); }; + + // comless helpers + static HRESULT CSampleIME::CreateInstance(REFCLSID rclsid, REFIID riid, _Outptr_result_maybenull_ LPVOID* ppv, _Out_opt_ HINSTANCE* phInst, BOOL isComLessMode); + static HRESULT CSampleIME::ComLessCreateInstance(REFGUID rclsid, REFIID riid, _Outptr_result_maybenull_ void **ppv, _Out_opt_ HINSTANCE *phInst); + static HRESULT CSampleIME::GetComModuleName(REFGUID rclsid, _Out_writes_(cchPath)WCHAR* wchPath, DWORD cchPath); + +private: + // functions for the composition object. + HRESULT _HandleCompositionInputWorker(_In_ CCompositionProcessorEngine *pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _CreateAndStartCandidate(_In_ CCompositionProcessorEngine *pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext *pContext); + HRESULT _HandleCandidateWorker(TfEditCookie ec, _In_ ITfContext *pContext); + + void _StartComposition(_In_ ITfContext *pContext); + void _EndComposition(_In_opt_ ITfContext *pContext); + BOOL _IsComposing(); + BOOL _IsKeyboardDisabled(); + + HRESULT _AddComposingAndChar(TfEditCookie ec, _In_ ITfContext *pContext, _In_ CStringRange *pstrAddString); + HRESULT _AddCharAndFinalize(TfEditCookie ec, _In_ ITfContext *pContext, _In_ CStringRange *pstrAddString); + + BOOL _FindComposingRange(TfEditCookie ec, _In_ ITfContext *pContext, _In_ ITfRange *pSelection, _Outptr_result_maybenull_ ITfRange **ppRange); + HRESULT _SetInputString(TfEditCookie ec, _In_ ITfContext *pContext, _Out_opt_ ITfRange *pRange, _In_ CStringRange *pstrAddString, BOOL exist_composing); + HRESULT _InsertAtSelection(TfEditCookie ec, _In_ ITfContext *pContext, _In_ CStringRange *pstrAddString, _Outptr_ ITfRange **ppCompRange); + + HRESULT _RemoveDummyCompositionForComposing(TfEditCookie ec, _In_ ITfComposition *pComposition); + + // Invoke key handler edit session + HRESULT _InvokeKeyHandler(_In_ ITfContext *pContext, UINT code, WCHAR wch, DWORD flags, _KEYSTROKE_STATE keyState); + + // function for the language property + BOOL _SetCompositionLanguage(TfEditCookie ec, _In_ ITfContext *pContext); + + // function for the display attribute + void _ClearCompositionDisplayAttributes(TfEditCookie ec, _In_ ITfContext *pContext); + BOOL _SetCompositionDisplayAttributes(TfEditCookie ec, _In_ ITfContext *pContext, TfGuidAtom gaDisplayAttribute); + BOOL _InitDisplayAttributeGuidAtom(); + + BOOL _InitThreadMgrEventSink(); + void _UninitThreadMgrEventSink(); + + BOOL _InitTextEditSink(_In_ ITfDocumentMgr *pDocMgr); + + void _UpdateLanguageBarOnSetFocus(_In_ ITfDocumentMgr *pDocMgrFocus); + + BOOL _InitKeyEventSink(); + void _UninitKeyEventSink(); + + BOOL _InitActiveLanguageProfileNotifySink(); + void _UninitActiveLanguageProfileNotifySink(); + + BOOL _IsKeyEaten(_In_ ITfContext *pContext, UINT codeIn, _Out_ UINT *pCodeOut, _Out_writes_(1) WCHAR *pwch, _Out_opt_ _KEYSTROKE_STATE *pKeyState); + + BOOL _IsRangeCovered(TfEditCookie ec, _In_ ITfRange *pRangeTest, _In_ ITfRange *pRangeCover); + VOID _DeleteCandidateList(BOOL fForce, _In_opt_ ITfContext *pContext); + + WCHAR ConvertVKey(UINT code); + + BOOL _InitThreadFocusSink(); + void _UninitThreadFocusSink(); + + BOOL _InitFunctionProviderSink(); + void _UninitFunctionProviderSink(); + + BOOL _AddTextProcessorEngine(); + + BOOL VerifySampleIMECLSID(_In_ REFCLSID clsid); + + friend LRESULT CALLBACK CSampleIME_WindowProc(HWND wndHandle, UINT uMsg, WPARAM wParam, LPARAM lParam); + +private: + ITfThreadMgr* _pThreadMgr; + TfClientId _tfClientId; + DWORD _dwActivateFlags; + + // The cookie of ThreadMgrEventSink + DWORD _threadMgrEventSinkCookie; + + ITfContext* _pTextEditSinkContext; + DWORD _textEditSinkCookie; + + // The cookie of ActiveLanguageProfileNotifySink + DWORD _activeLanguageProfileNotifySinkCookie; + + // The cookie of ThreadFocusSink + DWORD _dwThreadFocusSinkCookie; + + // Composition Processor Engine object. + CCompositionProcessorEngine* _pCompositionProcessorEngine; + + // Language bar item object. + CLangBarItemButton* _pLangBarItem; + + // the current composition object. + ITfComposition* _pComposition; + + // guidatom for the display attibute. + TfGuidAtom _gaDisplayAttributeInput; + TfGuidAtom _gaDisplayAttributeConverted; + + CANDIDATE_MODE _candidateMode; + CCandidateListUIPresenter *_pCandidateListUIPresenter; + BOOL _isCandidateWithWildcard : 1; + + ITfDocumentMgr* _pDocMgrLastFocused; + + ITfContext* _pContext; + + ITfCompartment* _pSIPIMEOnOffCompartment; + DWORD _dwSIPIMEOnOffCompartmentSinkCookie; + + HWND _msgWndHandle; + + LONG _refCount; + + // Support the search integration + ITfFnSearchCandidateProvider* _pITfFnSearchCandidateProvider; +}; diff --git a/SampleIME.rc b/SampleIME.rc new file mode 100644 index 00000000..04b15abe --- /dev/null +++ b/SampleIME.rc @@ -0,0 +1,122 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDIS_SAMPLEIME ICON "image\\SampleIme.ico" +IDI_IME_MODE_ON ICON "image\\ImeModeOn.ico" +IDI_IME_MODE_OFF ICON "image\\ImeModeOff.ico" +IDI_DOUBLE_SINGLE_BYTE_ON ICON "image\\DoubleSingleByteOn.ico" +IDI_DOUBLE_SINGLE_BYTE_OFF ICON "image\\DoubleSingleByteOff.ico" +IDI_PUNCTUATION_ON ICON "image\\PunctuationOn.ico" +IDI_PUNCTUATION_OFF ICON "image\\PunctuationOff.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +IDR_VERSION2 VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "MSFT" + VALUE "FileDescription", "The Sample code of Windows 8 IME" + VALUE "FileVersion", "1.0.0.1" + VALUE "InternalName", "SampleIM.dll" + VALUE "LegalCopyright", "Copyright (C)" + VALUE "OriginalFilename", "SampleIM.dll" + VALUE "ProductName", "SampleIME" + VALUE "ProductVersion", "1.0.0.1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDIS_SAMPLEIME "Chinese Simplified QuanPin (version 6.0)" + IDS_DEFAULT_FONT "Microsoft YaHei UI" +END + +STRINGTABLE +BEGIN + IDS_IME_MODE "Chinese/English input (Shift)" + IDS_DOUBLE_SINGLE_BYTE "Double/Single byte (Shift+Space)" + IDS_PUNCTUATION "Chinese/English punctuation (Ctrl+.)" +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/SampleIME.vcxproj b/SampleIME.vcxproj new file mode 100644 index 00000000..cb15b955 --- /dev/null +++ b/SampleIME.vcxproj @@ -0,0 +1,267 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {0C8FE010-5146-44D8-AF99-82702C15FEA5} + Win32Proj + SampleIME + SampleIME + $(VCTargetsPath11) + + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + + + + + + + + + + + + + + + + + true + + + false + + + false + + + false + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;SampleIME_EXPORTS;%(PreprocessorDefinitions) + $(MSBuildProgramFiles32)\Windows Kits\8.0\Include\um;$(MSBuildProgramFiles32)\Windows Kits\8.0\Include\shared;%(AdditionalIncludeDirectories) + Default + true + + + Windows + true + $(MSBuildProgramFiles32)\Windows Kits\8.0\Lib\win8\um\x86;%(AdditionalLibraryDirectories) + %(AdditionalDependencies) + SampleIME.def + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;SampleIME_EXPORTS;%(PreprocessorDefinitions) + $(MSBuildProgramFiles32)\Windows Kits\8.0\Include\um;$(MSBuildProgramFiles32)\Windows Kits\8.0\Include\shared;%(AdditionalIncludeDirectories) + Default + + + Windows + true + $(MSBuildProgramFiles32)\Windows Kits\8.0\Lib\win8\um\x64;%(AdditionalLibraryDirectories) + shlwapi.lib;%(AdditionalDependencies) + SampleIME.def + + + + + Level4 + + + Disabled + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;SampleIME_EXPORTS;%(PreprocessorDefinitions) + $(MSBuildProgramFiles32)\Windows Kits\8.0\Include\um;$(MSBuildProgramFiles32)\Windows Kits\8.0\Include\shared;%(AdditionalIncludeDirectories) + false + true + + + Windows + true + false + false + $(MSBuildProgramFiles32)\Windows Kits\8.0\Lib\win8\um\x86;%(AdditionalLibraryDirectories) + shlwapi.lib;%(AdditionalDependencies) + SampleIME.def + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;SampleIME_EXPORTS;%(PreprocessorDefinitions) + $(MSBuildProgramFiles32)\Windows Kits\8.0\Include\um;$(MSBuildProgramFiles32)\Windows Kits\8.0\Include\shared;%(AdditionalIncludeDirectories) + false + + + Windows + true + false + false + $(MSBuildProgramFiles32)\Windows Kits\8.0\Lib\win8\um\x64;%(AdditionalLibraryDirectories) + shlwapi.lib;%(AdditionalDependencies) + SampleIME.def + + + + + + \ No newline at end of file diff --git a/SampleIME.vcxproj.filters b/SampleIME.vcxproj.filters new file mode 100644 index 00000000..8eea86ce --- /dev/null +++ b/SampleIME.vcxproj.filters @@ -0,0 +1,301 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {fe9b9e3e-f048-48b1-ae0b-718fc88cff49} + + + + + Source Files + + + Dictionary + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/SampleIMEBaseStructure.cpp b/SampleIMEBaseStructure.cpp new file mode 100644 index 00000000..e4e989f0 --- /dev/null +++ b/SampleIMEBaseStructure.cpp @@ -0,0 +1,325 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Globals.h" + +//--------------------------------------------------------------------- +// +// CLSIDToString +// +//--------------------------------------------------------------------- + +const BYTE GuidSymbols[] = { + 3, 2, 1, 0, '-', 5, 4, '-', 7, 6, '-', 8, 9, '-', 10, 11, 12, 13, 14, 15 +}; + +const WCHAR HexDigits[] = L"0123456789ABCDEF"; + +BOOL CLSIDToString(REFGUID refGUID, _Out_writes_(39) WCHAR *pCLSIDString) +{ + WCHAR* pTemp = pCLSIDString; + const BYTE* pBytes = (const BYTE *) &refGUID; + + DWORD j = 0; + pTemp[j++] = L'{'; + for (int i = 0; i < sizeof(GuidSymbols) && j < (CLSID_STRLEN - 2); i++) + { + if (GuidSymbols[i] == '-') + { + pTemp[j++] = L'-'; + } + else + { + pTemp[j++] = HexDigits[ (pBytes[GuidSymbols[i]] & 0xF0) >> 4 ]; + pTemp[j++] = HexDigits[ (pBytes[GuidSymbols[i]] & 0x0F) ]; + } + } + + pTemp[j++] = L'}'; + pTemp[j] = L'\0'; + + return TRUE; +} + +//--------------------------------------------------------------------- +// +// SkipWhiteSpace +// +// dwBufLen - in character count +// +//--------------------------------------------------------------------- + +HRESULT SkipWhiteSpace(LCID locale, _In_ LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _Out_ DWORD_PTR *pdwIndex) +{ + DWORD_PTR index = 0; + + *pdwIndex = 0; + while (*pwszBuffer && IsSpace(locale, *pwszBuffer) && dwBufLen) + { + dwBufLen--; + pwszBuffer++; + index++; + } + + if (*pwszBuffer && dwBufLen) + { + *pdwIndex = index; + return S_OK; + } + + return E_FAIL; +} + +//--------------------------------------------------------------------- +// +// FindChar +// +// dwBufLen - in character count +// +//--------------------------------------------------------------------- + +HRESULT FindChar(WCHAR wch, _In_ LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _Out_ DWORD_PTR *pdwIndex) +{ + DWORD_PTR index = 0; + + *pdwIndex = 0; + while (*pwszBuffer && (*pwszBuffer != wch) && dwBufLen) + { + dwBufLen--; + pwszBuffer++; + index++; + } + + if (*pwszBuffer && dwBufLen) + { + *pdwIndex = index; + return S_OK; + } + + return E_FAIL; +} + +//--------------------------------------------------------------------- +// +// IsSpace +// +//--------------------------------------------------------------------- + +BOOL IsSpace(LCID locale, WCHAR wch) +{ + WORD wCharType = 0; + + GetStringTypeEx(locale, CT_CTYPE1, &wch, 1, &wCharType); + return (wCharType & C1_SPACE); +} + +CStringRange::CStringRange() +{ + _stringBufLen = 0; + _pStringBuf = nullptr; +} + +CStringRange::~CStringRange() +{ +} + +const WCHAR *CStringRange::Get() const +{ + return _pStringBuf; +} + +const DWORD_PTR CStringRange::GetLength() const +{ + return _stringBufLen; +} + +void CStringRange::Clear() +{ + _stringBufLen = 0; + _pStringBuf = nullptr; +} + +void CStringRange::Set(const WCHAR *pwch, DWORD_PTR dwLength) +{ + _stringBufLen = dwLength; + _pStringBuf = pwch; +} + +void CStringRange::Set(CStringRange &sr) +{ + *this = sr; +} + +CStringRange& CStringRange::operator =(const CStringRange& sr) +{ + _stringBufLen = sr._stringBufLen; + _pStringBuf = sr._pStringBuf; + return *this; +} + +void CStringRange::CharNext(_Inout_ CStringRange* pCharNext) +{ + if (!_stringBufLen) + { + pCharNext->_stringBufLen = 0; + pCharNext->_pStringBuf = nullptr; + } + else + { + pCharNext->_stringBufLen = _stringBufLen; + pCharNext->_pStringBuf = _pStringBuf; + + while (pCharNext->_stringBufLen) + { + BOOL isSurrogate = (IS_HIGH_SURROGATE(*pCharNext->_pStringBuf) || IS_LOW_SURROGATE(*pCharNext->_pStringBuf)); + pCharNext->_stringBufLen--; + pCharNext->_pStringBuf++; + if (!isSurrogate) + { + break; + } + } + } +} + +int CStringRange::Compare(LCID locale, _In_ CStringRange* pString1, _In_ CStringRange* pString2) +{ + return CompareString(locale, + NORM_IGNORECASE, + pString1->Get(), + (DWORD)pString1->GetLength(), + pString2->Get(), + (DWORD)pString2->GetLength()); +} + +BOOL CStringRange::WildcardCompare(LCID locale, _In_ CStringRange* stringWithWildcard, _In_ CStringRange* targetString) +{ + if (stringWithWildcard->GetLength() == 0) + { + return targetString->GetLength() == 0 ? TRUE : FALSE; + } + + CStringRange stringWithWildcard_next; + CStringRange targetString_next; + stringWithWildcard->CharNext(&stringWithWildcard_next); + targetString->CharNext(&targetString_next); + + if (*stringWithWildcard->Get() == L'*') + { + return WildcardCompare(locale, &stringWithWildcard_next, targetString) || ((targetString->GetLength() != 0) && WildcardCompare(locale, stringWithWildcard, &targetString_next)); + } + if (*stringWithWildcard->Get() == L'?') + { + return ((targetString->GetLength() != 0) && WildcardCompare(locale, &stringWithWildcard_next, &targetString_next)); + } + + BOOL isSurrogate1 = (IS_HIGH_SURROGATE(*stringWithWildcard->Get()) || IS_LOW_SURROGATE(*stringWithWildcard->Get())); + BOOL isSurrogate2 = (IS_HIGH_SURROGATE(*targetString->Get()) || IS_LOW_SURROGATE(*targetString->Get())); + + return ((CompareString(locale, + NORM_IGNORECASE, + stringWithWildcard->Get(), + (isSurrogate1 ? 2 : 1), + targetString->Get(), + (isSurrogate2 ? 2 : 1)) == CSTR_EQUAL) && WildcardCompare(locale, &stringWithWildcard_next, &targetString_next)); +} + +CCandidateRange::CCandidateRange(void) +{ +} + + +CCandidateRange::~CCandidateRange(void) +{ +} + + +BOOL CCandidateRange::IsRange(UINT vKey) +{ + DWORD value = vKey - L'0'; + + for (UINT i = 0; i < _CandidateListIndexRange.Count(); i++) + { + if (value == *_CandidateListIndexRange.GetAt(i)) + { + return TRUE; + } + else if ((VK_NUMPAD0 <= vKey) && (vKey <= VK_NUMPAD9)) + { + if ((vKey-VK_NUMPAD0) == *_CandidateListIndexRange.GetAt(i)) + { + return TRUE; + } + } + } + return FALSE; +} + +int CCandidateRange::GetIndex(UINT vKey) +{ + DWORD value = vKey - L'0'; + + for (UINT i = 0; i < _CandidateListIndexRange.Count(); i++) + { + if (value == *_CandidateListIndexRange.GetAt(i)) + { + return i; + } + else if ((VK_NUMPAD0 <= vKey) && (vKey <= VK_NUMPAD9)) + { + if ((vKey-VK_NUMPAD0) == *_CandidateListIndexRange.GetAt(i)) + { + return i; + } + } + } + return -1; +} + +CPunctuationPair::CPunctuationPair() +{ + _punctuation._Code = 0; + _punctuation._Punctuation = 0; + _pairPunctuation = 0; + _isPairToggle = FALSE; +} + +CPunctuationPair::CPunctuationPair(WCHAR code, WCHAR punctuation, WCHAR pair) +{ + _punctuation._Code = code; + _punctuation._Punctuation = punctuation; + _pairPunctuation = pair; + _isPairToggle = FALSE; +} + +CPunctuationNestPair::CPunctuationNestPair() +{ + _punctuation_begin._Code = 0; + _punctuation_begin._Punctuation = 0; + _pairPunctuation_begin = 0; + + _punctuation_end._Code = 0; + _punctuation_end._Punctuation = 0; + _pairPunctuation_end = 0; + + _nestCount = 0; +} + +CPunctuationNestPair::CPunctuationNestPair(WCHAR codeBegin, WCHAR punctuationBegin, WCHAR pairBegin, + WCHAR codeEnd, WCHAR punctuationEnd, WCHAR pairEnd) +{ + pairEnd;punctuationEnd; + _punctuation_begin._Code = codeBegin; + _punctuation_begin._Punctuation = punctuationBegin; + _pairPunctuation_begin = pairBegin; + + _punctuation_end._Code = codeEnd; + _punctuation_end._Punctuation = punctuationBegin; + _pairPunctuation_end = pairBegin; + + _nestCount = 0; +} \ No newline at end of file diff --git a/SampleIMEBaseStructure.h b/SampleIMEBaseStructure.h new file mode 100644 index 00000000..9d76c3d9 --- /dev/null +++ b/SampleIMEBaseStructure.h @@ -0,0 +1,271 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "stdafx.h" +#include +#include "assert.h" +#include + +using std::cout; +using std::endl; + +//--------------------------------------------------------------------- +// defined keyword +//--------------------------------------------------------------------- +template +struct _DEFINED_KEYWORD +{ + LPCWSTR _pwszKeyword; + VALUE _value; +}; + +//--------------------------------------------------------------------- +// enum +//--------------------------------------------------------------------- +enum KEYSTROKE_CATEGORY +{ + CATEGORY_NONE = 0, + CATEGORY_COMPOSING, + CATEGORY_CANDIDATE, + CATEGORY_PHRASE, + CATEGORY_PHRASEFROMKEYSTROKE, + CATEGORY_INVOKE_COMPOSITION_EDIT_SESSION +}; + +enum KEYSTROKE_FUNCTION +{ + FUNCTION_NONE = 0, + FUNCTION_INPUT, + + FUNCTION_CANCEL, + FUNCTION_FINALIZE_TEXTSTORE, + FUNCTION_FINALIZE_TEXTSTORE_AND_INPUT, + FUNCTION_FINALIZE_CANDIDATELIST, + FUNCTION_FINALIZE_CANDIDATELIST_AND_INPUT, + FUNCTION_CONVERT, + FUNCTION_CONVERT_WILDCARD, + FUNCTION_SELECT_BY_NUMBER, + FUNCTION_BACKSPACE, + FUNCTION_MOVE_LEFT, + FUNCTION_MOVE_RIGHT, + FUNCTION_MOVE_UP, + FUNCTION_MOVE_DOWN, + FUNCTION_MOVE_PAGE_UP, + FUNCTION_MOVE_PAGE_DOWN, + FUNCTION_MOVE_PAGE_TOP, + FUNCTION_MOVE_PAGE_BOTTOM, + + // Function Double/Single byte + FUNCTION_DOUBLE_SINGLE_BYTE, + + // Function Punctuation + FUNCTION_PUNCTUATION +}; + +//--------------------------------------------------------------------- +// candidate list +//--------------------------------------------------------------------- +enum CANDIDATE_MODE +{ + CANDIDATE_NONE = 0, + CANDIDATE_ORIGINAL, + CANDIDATE_PHRASE, + CANDIDATE_INCREMENTAL, + CANDIDATE_WITH_NEXT_COMPOSITION +}; + +//--------------------------------------------------------------------- +// structure +//--------------------------------------------------------------------- +struct _KEYSTROKE_STATE +{ + KEYSTROKE_CATEGORY Category; + KEYSTROKE_FUNCTION Function; +}; + +struct _PUNCTUATION +{ + WCHAR _Code; + WCHAR _Punctuation; +}; + +BOOL CLSIDToString(REFGUID refGUID, _Out_writes_ (39) WCHAR *pCLSIDString); + +HRESULT SkipWhiteSpace(LCID locale, _In_ LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _Out_ DWORD_PTR *pdwIndex); +HRESULT FindChar(WCHAR wch, _In_ LPCWSTR pwszBuffer, DWORD_PTR dwBufLen, _Out_ DWORD_PTR *pdwIndex); + +BOOL IsSpace(LCID locale, WCHAR wch); + +template +class CSampleImeArray +{ + typedef typename std::vector CSampleImeInnerArray; + typedef typename std::vector::iterator CSampleImeInnerIter; + +public: + CSampleImeArray(): _innerVect() + { + } + + explicit CSampleImeArray(size_t count): _innerVect(count) + { + } + + virtual ~CSampleImeArray() + { + } + + inline T* GetAt(size_t index) + { + assert(index >= 0); + assert(index < _innerVect.size()); + + T& curT = _innerVect.at(index); + + return &(curT); + } + + inline const T* GetAt(size_t index) const + { + assert(index >= 0); + assert(index < _innerVect.size()); + + T& curT = _innerVect.at(index); + + return &(curT); + } + + void RemoveAt(size_t index) + { + assert(index >= 0); + assert(index < _innerVect.size()); + + CSampleImeInnerIter iter = _innerVect.begin(); + _innerVect.erase(iter + index); + } + + UINT Count() const + { + return static_cast(_innerVect.size()); + } + + T* Append() + { + T newT; + _innerVect.push_back(newT); + T& backT = _innerVect.back(); + + return &(backT); + } + + void reserve(size_t Count) + { + _innerVect.reserve(Count); + } + + void Clear() + { + _innerVect.clear(); + } + +private: + CSampleImeInnerArray _innerVect; +}; + +class CCandidateRange +{ +public: + CCandidateRange(void); + ~CCandidateRange(void); + + BOOL IsRange(UINT vKey); + int GetIndex(UINT vKey); + + inline int Count() const + { + return _CandidateListIndexRange.Count(); + } + inline DWORD *GetAt(int index) + { + return _CandidateListIndexRange.GetAt(index); + } + inline DWORD *Append() + { + return _CandidateListIndexRange.Append(); + } + +private: + CSampleImeArray _CandidateListIndexRange; +}; + +class CStringRange +{ +public: + CStringRange(); + ~CStringRange(); + + const WCHAR *Get() const; + const DWORD_PTR GetLength() const; + void Clear(); + void Set(const WCHAR *pwch, DWORD_PTR dwLength); + void Set(CStringRange &sr); + CStringRange& operator=(const CStringRange& sr); + void CharNext(_Inout_ CStringRange* pCharNext); + static int Compare(LCID locale, _In_ CStringRange* pString1, _In_ CStringRange* pString2); + static BOOL WildcardCompare(LCID locale, _In_ CStringRange* stringWithWildcard, _In_ CStringRange* targetString); + +protected: + DWORD_PTR _stringBufLen; // Length is in character count. + const WCHAR *_pStringBuf; // Buffer which is not add zero terminate. +}; + +//--------------------------------------------------------------------- +// CCandidateListItem +// _ItemString - candidate string +// _FindKeyCode - tailing string +//--------------------------------------------------------------------- +struct CCandidateListItem +{ + CStringRange _ItemString; + CStringRange _FindKeyCode; + + CCandidateListItem& CCandidateListItem::operator =( const CCandidateListItem& rhs) + { + _ItemString = rhs._ItemString; + _FindKeyCode = rhs._FindKeyCode; + return *this; + } +}; + +class CPunctuationPair +{ +public: + CPunctuationPair(); + CPunctuationPair(WCHAR code, WCHAR punctuation, WCHAR pair); + + struct _PUNCTUATION _punctuation; + WCHAR _pairPunctuation; + BOOL _isPairToggle; +}; + +class CPunctuationNestPair +{ +public: + CPunctuationNestPair(); + CPunctuationNestPair(WCHAR wchCode_begin, WCHAR wch_begin, WCHAR wchPair_begin, + WCHAR wchCode_end, WCHAR wch_end, WCHAR wchPair_end); + + struct _PUNCTUATION _punctuation_begin; + WCHAR _pairPunctuation_begin; + + struct _PUNCTUATION _punctuation_end; + WCHAR _pairPunctuation_end; + + int _nestCount; +}; \ No newline at end of file diff --git a/SampleIMEStructureArray.h b/SampleIMEStructureArray.h new file mode 100644 index 00000000..6b74cd49 --- /dev/null +++ b/SampleIMEStructureArray.h @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////// +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// +// CSampleIMESampleIMEStructureArray.h +// +// CSampleIMEStructureArray declaration. +// +////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +template +class CSampleIMEStructureArray +{ + typedef typename std::vector value_type; + typedef const T& CONST_REF; + typedef typename value_type CSampleIMEArray; + typedef typename value_type::iterator CSampleIMEIter; + +public: + CSampleIMEStructureArray(): _imeVector() + { + } + + explicit CSampleIMEStructureArray(size_t iCount): _imeVector(iCount) + { + } + + CSampleIMEStructureArray(size_t iCount, CONST_REF tVal): _imeVector(iCount, tVal) + { + } + + virtual ~CSampleIMEStructureArray() {} + + inline CONST_REF GetAt(size_t iIndex) const + { + assert(iIndex <= _imeVector.size()); + assert(_imeVector.size() > 0); + + return _imeVector[iIndex]; + } + + inline T& GetAt(size_t iIndex) + { + assert(iIndex <= _imeVector.size()); + assert(_imeVector.size() > 0); + + return _imeVector[iIndex]; + } + + void RemoveAt(size_t iIndex, size_t iElements) + { + assert(iIndex <= _imeVector.size()); + assert(_imeVector.size() > 0); + + CSampleIMEIter beginIter = _imeVector.begin() + iIndex; + CSampleIMEIter lastIter = beginIter + iElements - 1; + + _imeVector.erase(beginIter, lastIter); + } + + size_t Count() const { return _imeVector.size(); } + + void Append(const T& tVal) + { + _imeVector.push_back(tVal); + } + + void Clear() + { + _imeVector.clear(); + } + +private: + CSampleIMEArray _imeVector; // the actual array of data + CSampleIMEIter _imeIter; +}; diff --git a/ScrollBarWindow.cpp b/ScrollBarWindow.cpp new file mode 100644 index 00000000..0e63fdd2 --- /dev/null +++ b/ScrollBarWindow.cpp @@ -0,0 +1,650 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "BaseWindow.h" +#include "ScrollBarWindow.h" + +////////////////////////////////////////////////////////////////////// +// +// CScrollBarWindowFactory class. +// +////////////////////////////////////////////////////////////////////// +CScrollBarWindowFactory* CScrollBarWindowFactory::_instance; + +CScrollBarWindowFactory::CScrollBarWindowFactory() +{ + _instance = nullptr; +} + +CScrollBarWindowFactory* CScrollBarWindowFactory::Instance() +{ + if (nullptr == _instance) + { + _instance = new (std::nothrow) CScrollBarWindowFactory(); + } + + return _instance; +} + +CScrollBarWindow* CScrollBarWindowFactory::MakeScrollBarWindow(SHELL_MODE shellMode) +{ + CScrollBarWindow* pScrollBarWindow = nullptr; + + switch (shellMode) + { + case STOREAPP: + pScrollBarWindow = new (std::nothrow) CScrollBarWindow(); + break; + + case DESKTOP: + pScrollBarWindow = new (std::nothrow) CScrollBarNullWindow(); + break; + + default: + pScrollBarWindow = new (std::nothrow) CScrollBarNullWindow(); + break; + } + return pScrollBarWindow; +} + +void CScrollBarWindowFactory::Release() +{ + if (_instance) + { + delete _instance; + _instance = nullptr; + } +} + +////////////////////////////////////////////////////////////////////// +// +// CScrollBarWindow class. +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// ctor +// +//---------------------------------------------------------------------------- + +CScrollBarWindow::CScrollBarWindow() +{ + _pBtnUp = nullptr; + _pBtnDn = nullptr; + + _scrollDir = SCROLL_NONE_DIR; + + _sizeOfScrollBtn.cx = GetSystemMetrics(SM_CXVSCROLL) * 2; + _sizeOfScrollBtn.cy = GetSystemMetrics(SM_CYVSCROLL) * 2; +} + +//+--------------------------------------------------------------------------- +// +// dtor +// +//---------------------------------------------------------------------------- + +CScrollBarWindow::~CScrollBarWindow() +{ + if (_pBtnUp) + { + delete _pBtnUp; + _pBtnUp = nullptr; + } + if (_pBtnDn) + { + delete _pBtnDn; + _pBtnDn = nullptr; + } +} + +//+--------------------------------------------------------------------------- +// +// _Create +// +//---------------------------------------------------------------------------- + +BOOL CScrollBarWindow::_Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, CBaseWindow *pParent, int wndWidth, int wndHeight) +{ + if (!CBaseWindow::_Create(atom, dwExStyle, dwStyle, pParent, wndWidth, wndHeight)) + { + return FALSE; + } + + _pBtnUp = new (std::nothrow) CScrollButtonWindow(dwStyle & WS_HSCROLL ? DFCS_SCROLLLEFT : DFCS_SCROLLUP); + if (_pBtnUp == nullptr) + { + return FALSE; + } + + _pBtnUp->_Create(NULL, 0, 0, this); + _pBtnUp->_SetUIWnd(_GetUIWnd()); + + _pBtnDn = new (std::nothrow) CScrollButtonWindow(dwStyle & WS_HSCROLL ? DFCS_SCROLLRIGHT : DFCS_SCROLLDOWN); + if (_pBtnDn == nullptr) + { + delete _pBtnUp; + _pBtnUp = nullptr; + return FALSE; + } + + _pBtnDn->_Create(NULL, 0, 0, this); + _pBtnDn->_SetUIWnd(_GetUIWnd()); + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _WindowProcCallback +// +// Scrollbar window proc. +//---------------------------------------------------------------------------- + +LRESULT CALLBACK CScrollBarWindow::_WindowProcCallback(_In_ HWND wndHandle, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + switch (uMsg) + { + case WM_PAINT: + { + HDC dcHandle = nullptr; + PAINTSTRUCT ps; + + dcHandle = BeginPaint(wndHandle, &ps); + + // paint itself at first + _OnPaint(dcHandle, &ps); + + // paint all children + _pBtnUp->_OnPaint(dcHandle, &ps); + _pBtnDn->_OnPaint(dcHandle, &ps); + + EndPaint(wndHandle, &ps); + } + return 0; + } + + return DefWindowProc(wndHandle, uMsg, wParam, lParam); +} + +//+--------------------------------------------------------------------------- +// +// _OnPaint +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps) +{ + HBRUSH hBrush = nullptr; + CBaseWindow* pUIWnd = _GetUIWnd(); + + if (pUIWnd != nullptr && pUIWnd->_GetWnd()) + { + hBrush = (HBRUSH)DefWindowProc(pUIWnd->_GetWnd(), WM_CTLCOLORSCROLLBAR, (WPARAM)dcHandle, (LPARAM)pUIWnd->_GetWnd()); + } + + if (hBrush == nullptr) + { + hBrush = GetSysColorBrush(COLOR_SCROLLBAR); + } + + FillRect(dcHandle, &pps->rcPaint, hBrush); +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonDown +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_OnLButtonDown(POINT pt) +{ + _StartCapture(); + + RECT rc = {0, 0, 0, 0}; + + _pBtnUp->_GetClientRect(&rc); + if (PtInRect(&rc, pt)) + { + _pBtnUp->_OnLButtonDown(pt); + } + else + { + _pBtnDn->_GetClientRect(&rc); + if (PtInRect(&rc, pt)) + { + _pBtnDn->_OnLButtonDown(pt); + } + } +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonUp +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_OnLButtonUp(POINT pt) +{ + if (_IsCapture()) + { + CBaseWindow* pUIWnd = _GetTopmostUIWnd(); + if (pUIWnd) + { + CBaseWindow *pCapture = pUIWnd->_GetCaptureObject(); + if (pCapture && pCapture != this) + { + pCapture->_OnLButtonUp(pt); + } + } + } + else + { + RECT rc = {0, 0, 0, 0}; + + _pBtnUp->_GetClientRect(&rc); + if (PtInRect(&rc, pt)) + { + _pBtnUp->_OnLButtonUp(pt); + } + else + { + _pBtnDn->_GetClientRect(&rc); + if (PtInRect(&rc, pt)) + { + _pBtnDn->_OnLButtonUp(pt); + } + } + } + + if (_IsCapture()) + { + _EndCapture(); + } + if (_IsTimer()) + { + _EndTimer(); + } + + _scrollDir = SCROLL_NONE_DIR; + _InvalidateRect(); +} + +//+--------------------------------------------------------------------------- +// +// _OnMouseMove +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_OnMouseMove(POINT pt) +{ + RECT rc = {0, 0, 0, 0}; + + _pBtnUp->_GetClientRect(&rc); + if (PtInRect(&rc, pt)) + { + _pBtnUp->_OnMouseMove(pt); + } + else + { + _pBtnDn->_GetClientRect(&rc); + if (PtInRect(&rc, pt)) + { + _pBtnDn->_OnMouseMove(pt); + } + } +} + +//+--------------------------------------------------------------------------- +// +// _OnOwnerWndMoved +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_OnOwnerWndMoved(BOOL isResized) +{ + isResized; + + if (IsWindow(_GetWnd()) && IsWindowVisible(_GetWnd())) + { + _AdjustWindowPos(); + } +} + +//+--------------------------------------------------------------------------- +// +// _Resize +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_Resize(int x, int y, int cx, int cy) +{ + CBaseWindow::_Resize(x, y, cx, cy); + + RECT rc = {0, 0, 0, 0}; + + _GetBtnUpRect(&rc); + _pBtnUp->_Resize(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top); + + _GetBtnDnRect(&rc); + _pBtnDn->_Resize(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top); +} + +//+--------------------------------------------------------------------------- +// +// _Show +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_Show(BOOL isShowWnd) +{ + if (_IsWindowVisible() != isShowWnd) + { + CBaseWindow::_Show(isShowWnd); + + _pBtnUp->_Show(isShowWnd); + _pBtnDn->_Show(isShowWnd); + } +} + +//+--------------------------------------------------------------------------- +// +// _Enable +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_Enable(BOOL isEnable) +{ + if (_IsEnabled() != isEnable) + { + CBaseWindow::_Enable(isEnable); + + _pBtnUp->_Enable(isEnable); + _pBtnDn->_Enable(isEnable); + } +} + +//+--------------------------------------------------------------------------- +// +// _AdjustWindowPos +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_AdjustWindowPos() +{ + if (!IsWindow(_GetWnd())) + { + return; + } + + RECT rc = {0, 0, 0, 0}; + CBaseWindow* pParent = _GetParent(); + if (pParent == nullptr) + { + return; + } + + GetWindowRect(pParent->_GetWnd(), &rc); + SetWindowPos(_GetWnd(), pParent->_GetWnd(), + rc.left, + rc.top, + rc.right - rc.left, + rc.bottom - rc.top, + SWP_NOOWNERZORDER | SWP_NOACTIVATE); +} + +//+--------------------------------------------------------------------------- +// +// _SetScrollInfo +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_SetScrollInfo(_In_ CScrollInfo *lpsi) +{ + _scrollInfo = *lpsi; + + BOOL isEnable = (_scrollInfo.nMax > _scrollInfo.nPage); + + _Enable(isEnable); + + _scrollDir = SCROLL_NONE_DIR; + + _SetCurPos(_scrollInfo.nPos, -1); +} + +//+--------------------------------------------------------------------------- +// +// _GetScrollInfo +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_GetScrollInfo(_Out_ CScrollInfo *lpsi) +{ + *lpsi = _scrollInfo; +} + +//+--------------------------------------------------------------------------- +// +// _GetBtnUpRect +// +//---------------------------------------------------------------------------- + +BOOL CScrollBarWindow::_GetBtnUpRect(_Out_ RECT *prc) +{ + RECT rc = {0, 0, 0, 0}; + + _GetClientRect(&rc); + + if (prc == nullptr) + { + return FALSE; + } + + prc->left = rc.left; + prc->top = rc.top; + prc->right = rc.right; + prc->bottom = rc.top + min(_sizeOfScrollBtn.cy, (rc.bottom - rc.top)/2); + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _GetBtnDnRect +// +//---------------------------------------------------------------------------- + +BOOL CScrollBarWindow::_GetBtnDnRect(_Out_ RECT *prc) +{ + RECT rc = {0, 0, 0, 0}; + + _GetClientRect(&rc); + + if (prc == nullptr) + { + return FALSE; + } + + prc->left = rc.left; + prc->top = rc.bottom - min(_sizeOfScrollBtn.cy, (rc.bottom - rc.top)/2); + prc->right = rc.right; + prc->bottom = rc.bottom; + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _GetScrollArea +// +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_GetScrollArea(_Out_ RECT *prc) +{ + RECT rcBtnUp = {0, 0, 0, 0}; + RECT rcBtnDn = {0, 0, 0, 0}; + + _GetBtnUpRect(&rcBtnUp); + _GetBtnDnRect(&rcBtnDn); + + RECT rc = {0, 0, 0, 0}; + + _GetClientRect(&rc); + + if (prc == nullptr) + { + return; + } + + prc->left = rc.left; + prc->top = rc.top + (rcBtnUp.bottom - rcBtnUp.top); + prc->right = rc.right; + prc->bottom = rc.bottom - (rcBtnDn.bottom - rcBtnDn.top); +} + +//+--------------------------------------------------------------------------- +// +// _SetCurPos +// +// param: dwSB - SB_xxx for WM_VSCROLL or WM_HSCROLL. +// if -1 is specified, function doesn't send WM_xSCROLL message +//---------------------------------------------------------------------------- + +void CScrollBarWindow::_SetCurPos(int nPos, int dwSB) +{ + int posMax = (_scrollInfo.nMax <= _scrollInfo.nPage) ? 0 : _scrollInfo.nMax - _scrollInfo.nPage; + + nPos = min(nPos, posMax); + nPos = max(nPos, 0); + + _scrollInfo.nPos = nPos; + + if (_IsWindowVisible()) { + _InvalidateRect(); + } + + if ((_IsCapture() && dwSB != -1) || (dwSB == SB_THUMBPOSITION)) + { + _NotifyCommand(WM_VSCROLL, dwSB, nPos); + } +} + +////////////////////////////////////////////////////////////////////// +// +// CScrollButtonWindow class. +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// _OnPaint +// +//---------------------------------------------------------------------------- + +void CScrollButtonWindow::_OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps) +{ + pps; + + RECT rc = {0, 0, 0, 0}; + + _GetClientRect(&rc); + + DrawFrameControl(dcHandle, &rc, DFC_SCROLL, subTypeOfControl | typeOfControl | (!_IsEnabled() ? DFCS_INACTIVE : 0)); +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonDown +// +//---------------------------------------------------------------------------- + +void CScrollButtonWindow::_OnLButtonDown(POINT pt) +{ + CButtonWindow::_OnLButtonDown(pt); + + CScrollBarWindow* pParent = (CScrollBarWindow*)_GetParent(); // more secure w/ dynamic_cast + if (pParent == nullptr) + { + return; + } + + switch (subTypeOfControl) + { + case DFCS_SCROLLDOWN: + _NotifyCommand(WM_VSCROLL, SB_LINEDOWN, 0); + pParent->_ShiftLine(+1, FALSE); + break; + case DFCS_SCROLLUP: + _NotifyCommand(WM_VSCROLL, SB_LINEUP, 0); + pParent->_ShiftLine(-1, FALSE); + break; + } + + _StartTimer(_GetScrollDelay()); +} + +//+--------------------------------------------------------------------------- +// +// _OnLButtonUp +// +//---------------------------------------------------------------------------- + +void CScrollButtonWindow::_OnLButtonUp(POINT pt) +{ + CButtonWindow::_OnLButtonUp(pt); + + _InvalidateRect(); + + if (_IsTimer()) + { + _EndTimer(); + } +} + +//+--------------------------------------------------------------------------- +// +// _OnTimer : Speed up page Down/Up while holding Down/Up Button +// +//---------------------------------------------------------------------------- + +void CScrollButtonWindow::_OnTimer() +{ + POINT pt = {0, 0}; + CScrollBarWindow* pParent = (CScrollBarWindow*)_GetParent(); // more secure w/ dynamic_cast + if (pParent == nullptr) + { + return; + } + + // start another faster timer + _StartTimer(_GetScrollSpeed()); + + GetCursorPos(&pt); + MapWindowPoints(HWND_DESKTOP, pParent->_GetWnd(), &pt, 1); + + RECT rc = {0, 0, 0, 0}; + + _GetClientRect(&rc); + + if (PtInRect(&rc, pt)) + { + switch (subTypeOfControl) + { + case DFCS_SCROLLDOWN: + _NotifyCommand(WM_VSCROLL, SB_LINEDOWN, 0); + pParent->_ShiftLine(+1, FALSE); + break; + case DFCS_SCROLLUP: + _NotifyCommand(WM_VSCROLL, SB_LINEUP, 0); + pParent->_ShiftLine(-1, FALSE); + break; + } + } +} \ No newline at end of file diff --git a/ScrollBarWindow.h b/ScrollBarWindow.h new file mode 100644 index 00000000..2bfc1bba --- /dev/null +++ b/ScrollBarWindow.h @@ -0,0 +1,220 @@ +////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2005 Microsoft Corporation. All rights reserved. +// +// ScrollBarWindow.h +// +// CScrollBarWindow declaration. +// +// +////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "BaseWindow.h" +#include "ButtonWindow.h" + +class CScrollButtonWindow; + +enum SCROLLBAR_DIRECTION +{ + SCROLL_NONE_DIR, + SCROLL_PAGEDOWN, // or page left + SCROLL_PAGEUP // or page right +}; + +enum SHELL_MODE +{ + STOREAPP, + DESKTOP, +}; + +struct CScrollInfo +{ + CScrollInfo() + { + nMax = 0; + nPage = 0; + nPos = 0; + } + + int nMax; + int nPage; + int nPos; +}; + +class CScrollBarWindow; + +class CScrollBarWindowFactory +{ +public: + static CScrollBarWindowFactory* Instance(); + CScrollBarWindow* MakeScrollBarWindow(SHELL_MODE shellMode); + void Release(); + +protected: + CScrollBarWindowFactory(); + +private: + static CScrollBarWindowFactory* _instance; + +}; +////////////////////////////////////////////////////////////////////// +// +// CScrollBarWindow declaration. +// +////////////////////////////////////////////////////////////////////// + +class CScrollBarWindow : public CBaseWindow +{ +public: + CScrollBarWindow(); + virtual ~CScrollBarWindow(); + + BOOL _Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, CBaseWindow *pParent = nullptr, int wndWidth = 0, int wndHeight = 0); + + void _Resize(int x, int y, int cx, int cy); + void _Show(BOOL isShowWnd); + void _Enable(BOOL isEnable); + + LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); + virtual void _OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps); + virtual void _OnLButtonDown(POINT pt); + virtual void _OnLButtonUp(POINT pt); + virtual void _OnMouseMove(POINT pt); + virtual void _OnTimer() { }; + + virtual void _OnOwnerWndMoved(BOOL isResized); + + virtual void _SetScrollInfo(_In_ CScrollInfo *lpsi); + virtual void _GetScrollInfo(_Out_ CScrollInfo *lpsi); + + virtual BOOL _GetBtnUpRect(_Out_ RECT *prc); + virtual BOOL _GetBtnDnRect(_Out_ RECT *prc); + + virtual void _ShiftLine(int nLine, BOOL isNotify) + { + DWORD dwSB = 0; + + dwSB = (nLine > 0 ? SB_LINEDOWN : + nLine < 0 ? SB_LINEUP : 0); + + _SetCurPos(_scrollInfo.nPos + nLine, isNotify ? dwSB : -1); + } + + virtual void _ShiftPage(int nPage, BOOL isNotify) + { + DWORD dwSB = 0; + + dwSB = (nPage > 0 ? SB_PAGEDOWN : + nPage < 0 ? SB_PAGEUP : 0); + + _SetCurPos(_scrollInfo.nPos + _scrollInfo.nPage * nPage, isNotify ? dwSB : -1); + } + + virtual void _ShiftPosition(int iPos, BOOL isNotify) + { + _SetCurPos(iPos, isNotify ? SB_THUMBPOSITION : -1); + } + + virtual void _GetScrollArea(_Out_ RECT *prc); + virtual void _SetCurPos(int nPos, int dwSB); + +private: + void _AdjustWindowPos(); + + CScrollButtonWindow* _pBtnUp; + CScrollButtonWindow* _pBtnDn; + + CScrollInfo _scrollInfo; + SIZE _sizeOfScrollBtn; + SCROLLBAR_DIRECTION _scrollDir; +}; + + +////////////////////////////////////////////////////////////////////// +// +// CScrollBarNullWindow declaration. +// +////////////////////////////////////////////////////////////////////// + +class CScrollBarNullWindow : public CScrollBarWindow +{ +public: + CScrollBarNullWindow() { }; + virtual ~CScrollBarNullWindow() { }; + + virtual BOOL _Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, _In_opt_ CBaseWindow *pParent = nullptr, int wndWidth = 0, int wndHeight = 0) + { + atom; dwExStyle; dwStyle; pParent; wndWidth; wndHeight; + return TRUE; + }; + + virtual void _Destroy() { }; + + virtual void _Move(int x, int y) { x;y; }; + virtual void _Resize(int x, int y, int cx, int cy) { x; y; cx; cy; }; + virtual void _Show(BOOL isShowWnd) { isShowWnd; }; + virtual BOOL _IsWindowVisible() { return TRUE; }; + virtual void _Enable(BOOL isEnable) { isEnable; }; + virtual BOOL _IsEnabled() { return TRUE; }; + virtual void _InvalidateRect() { }; + virtual BOOL _GetWindowRect(_Out_ LPRECT lpRect) { lpRect->bottom = 0; lpRect->left = 0; lpRect->right = 0; lpRect->top = 0; return TRUE; }; + virtual BOOL _GetClientRect(_Out_ LPRECT lpRect) { lpRect->bottom = 0; lpRect->left = 0; lpRect->right = 0; lpRect->top = 0; return TRUE; }; + + virtual LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) + { + wndHandle; uMsg; wParam; lParam; + return 0; + }; + virtual void _OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps) { dcHandle;pps; }; + virtual void _OnLButtonDown(POINT pt) { pt; }; + virtual void _OnLButtonUp(POINT pt) { pt; }; + virtual void _OnMouseMove(POINT pt) { pt; }; + virtual void _OnTimer() { }; + + virtual void _OnOwnerWndMoved(BOOL isResized) { isResized; }; + + virtual void _SetScrollInfo(_In_ CScrollInfo *lpsi) { lpsi; }; + virtual void _GetScrollInfo(_In_ CScrollInfo *lpsi) { lpsi; }; + + virtual BOOL _GetBtnUpRect(_Out_ RECT *prc) { prc->bottom = 0; prc->left = 0; prc->right = 0; prc->top = 0; return TRUE; }; + virtual BOOL _GetBtnDnRect(_Out_ RECT *prc) { prc->bottom = 0; prc->left = 0; prc->right = 0; prc->top = 0; return TRUE; }; + + virtual void _ShiftLine(int nLine, BOOL isNotify) { nLine; isNotify; }; + virtual void _ShiftPage(int nPage, BOOL isNotify) { nPage; isNotify; }; + virtual void _ShiftPosition(int iPos, BOOL isNotify) { iPos; isNotify; }; + virtual void _GetScrollArea(_Out_ RECT *prc) { prc->bottom = 0; prc->left = 0; prc->right = 0; prc->top = 0; }; + virtual void _SetCurPos(int nPos, DWORD dwSB) { nPos; dwSB; }; +}; + +////////////////////////////////////////////////////////////////////// +// +// CScrollButtonWondow declaration. +// +////////////////////////////////////////////////////////////////////// + +class CScrollButtonWindow : public CButtonWindow +{ +public: + CScrollButtonWindow(UINT uSubType) + { + subTypeOfControl = uSubType; + } + virtual ~CScrollButtonWindow() + { + } + + LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) + { + wndHandle;uMsg;wParam;lParam; + return 0; + } + virtual void _OnPaint(_In_ HDC dcHandle, _In_ PAINTSTRUCT *pps); + virtual void _OnLButtonDown(POINT pt); + virtual void _OnLButtonUp(POINT pt); + virtual void _OnTimer(); + +private: + UINT subTypeOfControl; // DFCS_SCROLLxxx for DrawFrameControl +}; diff --git a/SearchCandidateProvider.cpp b/SearchCandidateProvider.cpp new file mode 100644 index 00000000..ebd680f9 --- /dev/null +++ b/SearchCandidateProvider.cpp @@ -0,0 +1,210 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "private.h" +#include "SearchCandidateProvider.h" +#include +#include "SampleIME.h" +#include "CompositionProcessorEngine.h" +#include "TipCandidateList.h" +#include "TipCandidateString.h" + +/*------------------------------------------------------------------------------ + +create instance of CSearchCandidateProvider + +------------------------------------------------------------------------------*/ +HRESULT CSearchCandidateProvider::CreateInstance(_Outptr_ ITfFnSearchCandidateProvider **ppobj, _In_ ITfTextInputProcessorEx *ptip) +{ + if (ppobj == nullptr) + { + return E_INVALIDARG; + } + *ppobj = nullptr; + + *ppobj = new (std::nothrow) CSearchCandidateProvider(ptip); + if (nullptr == *ppobj) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +/*------------------------------------------------------------------------------ + +create instance of CSearchCandidateProvider + +------------------------------------------------------------------------------*/ +HRESULT CSearchCandidateProvider::CreateInstance(REFIID riid, _Outptr_ void **ppvObj, _In_ ITfTextInputProcessorEx *ptip) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + *ppvObj = nullptr; + + *ppvObj = new (std::nothrow) CSearchCandidateProvider(ptip); + if (nullptr == *ppvObj) + { + return E_OUTOFMEMORY; + } + + return ((CSearchCandidateProvider*)(*ppvObj))->QueryInterface(riid, ppvObj); +} + +/*------------------------------------------------------------------------------ + +constructor of CSearchCandidateProvider + +------------------------------------------------------------------------------*/ +CSearchCandidateProvider::CSearchCandidateProvider(_In_ ITfTextInputProcessorEx *ptip) +{ + assert(ptip != nullptr); + + _pTip = ptip; + _refCount = 0; +} + +/*------------------------------------------------------------------------------ + +destructor of CSearchCandidateProvider + +------------------------------------------------------------------------------*/ +CSearchCandidateProvider::~CSearchCandidateProvider(void) +{ +} + +/*------------------------------------------------------------------------------ + +query interface +(IUnknown method) + +------------------------------------------------------------------------------*/ +STDMETHODIMP CSearchCandidateProvider::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_POINTER; + } + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, __uuidof(ITfFnSearchCandidateProvider))) + { + *ppvObj = (ITfFnSearchCandidateProvider*)this; + } + else if (IsEqualIID(riid, IID_ITfFunction)) + { + *ppvObj = (ITfFunction*)this; + } + + if (*ppvObj == nullptr) + { + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +/*------------------------------------------------------------------------------ + +increment reference count +(IUnknown method) + +------------------------------------------------------------------------------*/ +STDMETHODIMP_(ULONG) CSearchCandidateProvider::AddRef() +{ + return (ULONG)InterlockedIncrement(&_refCount); +} + +/*------------------------------------------------------------------------------ + +decrement reference count and release object +(IUnknown method) + +------------------------------------------------------------------------------*/ +STDMETHODIMP_(ULONG) CSearchCandidateProvider::Release() +{ + ULONG ref = (ULONG)InterlockedDecrement(&_refCount); + if (0 < ref) + { + return ref; + } + + delete this; + return 0; +} + +STDMETHODIMP CSearchCandidateProvider::GetDisplayName(_Out_ BSTR *pbstrName) +{ + if (pbstrName == nullptr) + { + return E_INVALIDARG; + } + + *pbstrName = SysAllocString(L"SearchCandidateProvider"); + return S_OK; +} + +STDMETHODIMP CSearchCandidateProvider::GetSearchCandidates(BSTR bstrQuery, BSTR bstrApplicationID, _Outptr_result_maybenull_ ITfCandidateList **pplist) +{ + bstrApplicationID;bstrQuery; + HRESULT hr = E_FAIL; + *pplist = nullptr; + + if (nullptr == _pTip) + { + return hr; + } + + CCompositionProcessorEngine* pCompositionProcessorEngine = ((CSampleIME*)_pTip)->GetCompositionProcessorEngine(); + if (nullptr == pCompositionProcessorEngine) + { + return hr; + } + + CSampleImeArray candidateList; + pCompositionProcessorEngine->GetCandidateList(&candidateList, TRUE, FALSE); + + int cCand = min(candidateList.Count(), FAKECANDIDATENUMBER); + if (0 < cCand) + { + hr = CTipCandidateList::CreateInstance(pplist, cCand); + if (FAILED(hr)) + { + return hr; + } + for (int iCand = 0; iCand < cCand; iCand++) + { + ITfCandidateString* pCandStr = nullptr; + CTipCandidateString::CreateInstance(IID_ITfCandidateString, (void**)&pCandStr); + + ((CTipCandidateString*)pCandStr)->SetIndex(iCand); + ((CTipCandidateString*)pCandStr)->SetString(candidateList.GetAt(iCand)->_ItemString.Get(), candidateList.GetAt(iCand)->_ItemString.GetLength()); + + ((CTipCandidateList*)(*pplist))->SetCandidate(&pCandStr); + } + } + hr = S_OK; + + return hr; +} + +/*------------------------------------------------------------------------------ + +set result +(ITfFnSearchCandidateProvider method) + +------------------------------------------------------------------------------*/ +STDMETHODIMP CSearchCandidateProvider::SetResult(BSTR bstrQuery, BSTR bstrApplicationID, BSTR bstrResult) +{ + bstrQuery;bstrApplicationID;bstrResult; + + return E_NOTIMPL; +} + diff --git a/SearchCandidateProvider.h b/SearchCandidateProvider.h new file mode 100644 index 00000000..3c57a514 --- /dev/null +++ b/SearchCandidateProvider.h @@ -0,0 +1,50 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +// +// CSearchCandidateProvider +// +// TfFnSearchCandidateProvider is used for search integration feature, CSearchCandidateProvider +// implements this interface that can be called by search integration, and offer the candidate list +// as search integration suggestion. +// +#define FAKECANDIDATENUMBER (16) +class CSearchCandidateProvider : public ITfFnSearchCandidateProvider +{ +protected: + // constructor/destructor + CSearchCandidateProvider(_In_ ITfTextInputProcessorEx *ptip); + virtual ~CSearchCandidateProvider(); + +public: + // create instance + static HRESULT CreateInstance(_Outptr_ ITfFnSearchCandidateProvider **ppobj, _In_ ITfTextInputProcessorEx *ptip); + static HRESULT CreateInstance(REFIID riid, _Outptr_ void **ppvObj, _In_ ITfTextInputProcessorEx *ptip); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfFunction methods + STDMETHODIMP GetDisplayName(_Out_ BSTR *pbstrName); + + // ITfFnSearchCandidateProvider methods + // + // GetSearchCandidates is responsible for supporting the candidates to caller, search integration + // SetResult is not used + // + STDMETHODIMP GetSearchCandidates(BSTR bstrQuery, BSTR bstrApplicationID, _Outptr_result_maybenull_ ITfCandidateList **pplist); + STDMETHODIMP SetResult(BSTR bstrQuery, BSTR bstrApplicationID, BSTR bstrResult); + +private: + LONG _refCount; + ITfTextInputProcessorEx* _pTip; +}; + diff --git a/Server.cpp b/Server.cpp new file mode 100644 index 00000000..01f990b6 --- /dev/null +++ b/Server.cpp @@ -0,0 +1,285 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "SampleIME.h" + +// from Register.cpp +BOOL RegisterProfiles(); +void UnregisterProfiles(); +BOOL RegisterCategories(); +void UnregisterCategories(); +BOOL RegisterServer(); +void UnregisterServer(); + +void FreeGlobalObjects(void); + +class CClassFactory; +static CClassFactory* classFactoryObjects[1] = { nullptr }; + +//+--------------------------------------------------------------------------- +// +// DllAddRef +// +//---------------------------------------------------------------------------- + +void DllAddRef(void) +{ + InterlockedIncrement(&Global::dllRefCount); +} + +//+--------------------------------------------------------------------------- +// +// DllRelease +// +//---------------------------------------------------------------------------- + +void DllRelease(void) +{ + if (InterlockedDecrement(&Global::dllRefCount) < 0) + { + EnterCriticalSection(&Global::CS); + + if (nullptr != classFactoryObjects[0]) + { + FreeGlobalObjects(); + } + assert(Global::dllRefCount == -1); + + LeaveCriticalSection(&Global::CS); + } +} + +//+--------------------------------------------------------------------------- +// +// CClassFactory declaration with IClassFactory Interface +// +//---------------------------------------------------------------------------- + +class CClassFactory : public IClassFactory +{ +public: + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // IClassFactory methods + STDMETHODIMP CreateInstance(_In_opt_ IUnknown *pUnkOuter, _In_ REFIID riid, _COM_Outptr_ void **ppvObj); + STDMETHODIMP LockServer(BOOL fLock); + + // Constructor + CClassFactory(REFCLSID rclsid, HRESULT (*pfnCreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObj)) + : _rclsid(rclsid) + { + _pfnCreateInstance = pfnCreateInstance; + } + +public: + REFCLSID _rclsid; + HRESULT (*_pfnCreateInstance)(IUnknown *pUnkOuter, REFIID riid, _COM_Outptr_ void **ppvObj); +private: + CClassFactory& operator=(const CClassFactory& rhn) {rhn;}; +}; + +//+--------------------------------------------------------------------------- +// +// CClassFactory::QueryInterface +// +//---------------------------------------------------------------------------- + +STDAPI CClassFactory::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (IsEqualIID(riid, IID_IClassFactory) || IsEqualIID(riid, IID_IUnknown)) + { + *ppvObj = this; + DllAddRef(); + return NOERROR; + } + *ppvObj = nullptr; + + return E_NOINTERFACE; +} + +//+--------------------------------------------------------------------------- +// +// CClassFactory::AddRef +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CClassFactory::AddRef() +{ + DllAddRef(); + return (Global::dllRefCount + 1); +} + +//+--------------------------------------------------------------------------- +// +// CClassFactory::Release +// +//---------------------------------------------------------------------------- + +STDAPI_(ULONG) CClassFactory::Release() +{ + DllRelease(); + return (Global::dllRefCount + 1); +} + +//+--------------------------------------------------------------------------- +// +// CClassFactory::CreateInstance +// +//---------------------------------------------------------------------------- + +STDAPI CClassFactory::CreateInstance(_In_opt_ IUnknown *pUnkOuter, _In_ REFIID riid, _COM_Outptr_ void **ppvObj) +{ + return _pfnCreateInstance(pUnkOuter, riid, ppvObj); +} + +//+--------------------------------------------------------------------------- +// +// CClassFactory::LockServer +// +//---------------------------------------------------------------------------- + +STDAPI CClassFactory::LockServer(BOOL fLock) +{ + if (fLock) + { + DllAddRef(); + } + else + { + DllRelease(); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// BuildGlobalObjects +// +//---------------------------------------------------------------------------- + +void BuildGlobalObjects(void) +{ + classFactoryObjects[0] = new (std::nothrow) CClassFactory(Global::SampleIMECLSID, CSampleIME::CreateInstance); +} + +//+--------------------------------------------------------------------------- +// +// FreeGlobalObjects +// +//---------------------------------------------------------------------------- + +void FreeGlobalObjects(void) +{ + for (int i = 0; i < ARRAYSIZE(classFactoryObjects); i++) + { + if (nullptr != classFactoryObjects[i]) + { + delete classFactoryObjects[i]; + classFactoryObjects[i] = nullptr; + } + } + + DeleteObject(Global::defaultlFontHandle); +} + +//+--------------------------------------------------------------------------- +// +// DllGetClassObject +// +//---------------------------------------------------------------------------- +_Check_return_ +STDAPI DllGetClassObject( + _In_ REFCLSID rclsid, + _In_ REFIID riid, + _Outptr_ void** ppv) +{ + if (classFactoryObjects[0] == nullptr) + { + EnterCriticalSection(&Global::CS); + + // need to check ref again after grabbing mutex + if (classFactoryObjects[0] == nullptr) + { + BuildGlobalObjects(); + } + + LeaveCriticalSection(&Global::CS); + } + + if (IsEqualIID(riid, IID_IClassFactory) || + IsEqualIID(riid, IID_IUnknown)) + { + for (int i = 0; i < ARRAYSIZE(classFactoryObjects); i++) + { + if (nullptr != classFactoryObjects[i] && + IsEqualGUID(rclsid, classFactoryObjects[i]->_rclsid)) + { + *ppv = (void *)classFactoryObjects[i]; + DllAddRef(); // class factory holds DLL ref count + return NOERROR; + } + } + } + + *ppv = nullptr; + + return CLASS_E_CLASSNOTAVAILABLE; +} + +//+--------------------------------------------------------------------------- +// +// DllCanUnloadNow +// +//---------------------------------------------------------------------------- + +STDAPI DllCanUnloadNow(void) +{ + if (Global::dllRefCount >= 0) + { + return S_FALSE; + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// DllUnregisterServer +// +//---------------------------------------------------------------------------- + +STDAPI DllUnregisterServer(void) +{ + UnregisterProfiles(); + UnregisterCategories(); + UnregisterServer(); + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// DllRegisterServer +// +//---------------------------------------------------------------------------- + +STDAPI DllRegisterServer(void) +{ + if ((!RegisterServer()) || (!RegisterProfiles()) || (!RegisterCategories())) + { + DllUnregisterServer(); + return E_FAIL; + } + return S_OK; +} + diff --git a/ShadowWindow.cpp b/ShadowWindow.cpp new file mode 100644 index 00000000..9c99ee8a --- /dev/null +++ b/ShadowWindow.cpp @@ -0,0 +1,370 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "BaseWindow.h" +#include "ShadowWindow.h" + +#define SHADOW_ALPHANUMBER (5) +#define ALPHA(x) (255 - (x)) + +//// Gray scale values for the shadow grades +#define GS01 255 +#define GS02 254 +#define GS03 253 +#define GS04 252 +#define GS05 250 +#define GS06 246 +#define GS07 245 +#define GS08 242 +#define GS09 241 +#define GS10 227 +#define GS11 217 +#define GS12 213 +#define GS13 212 +#define GS14 199 +#define GS15 180 +#define GS16 172 +#define GS17 171 +#define GS18 155 +#define GS19 144 +#define GS20 142 + +// pre-computed alpha values for the shadow +const BYTE AlphaTable[SHADOW_ALPHANUMBER] = { + ALPHA(GS04), ALPHA(GS09), ALPHA(GS13), ALPHA(GS17), ALPHA(GS20) +}; + +const BYTE CornerShadowAlphaMetric[SHADOW_ALPHANUMBER][SHADOW_ALPHANUMBER] = { + ALPHA(GS08), ALPHA(GS06), ALPHA(GS05), ALPHA(GS03), ALPHA(GS02), + ALPHA(GS11), ALPHA(GS10), ALPHA(GS09), ALPHA(GS05), ALPHA(GS02), + ALPHA(GS15), ALPHA(GS14), ALPHA(GS10), ALPHA(GS07), ALPHA(GS03), + ALPHA(GS18), ALPHA(GS15), ALPHA(GS11), ALPHA(GS08), ALPHA(GS03), + ALPHA(GS19), ALPHA(GS16), ALPHA(GS12), ALPHA(GS09), ALPHA(GS04), +}; + + +//+--------------------------------------------------------------------------- +// +// _Create +// +//---------------------------------------------------------------------------- + +BOOL CShadowWindow::_Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, _In_opt_ CBaseWindow *pParent, int wndWidth, int wndHeight) +{ + if (!CBaseWindow::_Create(atom, dwExStyle, dwStyle, pParent, wndWidth, wndHeight)) + { + return FALSE; + } + + return _Initialize(); +} + +//+--------------------------------------------------------------------------- +// +// _WindowProcCallback +// +// Shadow window proc. +//---------------------------------------------------------------------------- + +LRESULT CALLBACK CShadowWindow::_WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_PAINT: + { + HDC dcHandle; + PAINTSTRUCT ps; + + dcHandle = BeginPaint(wndHandle, &ps); + + HBRUSH hBrush = CreateSolidBrush(_color); + FillRect(dcHandle, &ps.rcPaint, hBrush); + DeleteObject(hBrush); + + EndPaint(wndHandle, &ps); + } + return 0; + + case WM_SETTINGCHANGE: + _OnSettingChange(); + break; + } + + return DefWindowProc(wndHandle, uMsg, wParam, lParam); +} + +//+--------------------------------------------------------------------------- +// +// _OnSettingChange +// +//---------------------------------------------------------------------------- + +void CShadowWindow::_OnSettingChange() +{ + _InitSettings(); + + DWORD dwWndStyleEx = GetWindowLong(_GetWnd(), GWL_EXSTYLE); + + if (_isGradient) + { + SetWindowLong(_GetWnd(), GWL_EXSTYLE, (dwWndStyleEx | WS_EX_LAYERED)); + } + else + { + SetWindowLong(_GetWnd(), GWL_EXSTYLE, (dwWndStyleEx & ~WS_EX_LAYERED)); + } + + _AdjustWindowPos(); + _InitShadow(); +} + +//+--------------------------------------------------------------------------- +// +// _OnOwnerWndMoved +// +//---------------------------------------------------------------------------- + +void CShadowWindow::_OnOwnerWndMoved(BOOL isResized) +{ + if (IsWindow(_GetWnd()) && _IsWindowVisible()) + { + _AdjustWindowPos(); + if (isResized) + { + _InitShadow(); + } + } +} + + +//+--------------------------------------------------------------------------- +// +// _Show +// +//---------------------------------------------------------------------------- + +void CShadowWindow::_Show(BOOL isShowWnd) +{ + _OnOwnerWndMoved(TRUE); + CBaseWindow::_Show(isShowWnd); +} + +//+--------------------------------------------------------------------------- +// +// _Initialize +// +//---------------------------------------------------------------------------- + +BOOL CShadowWindow::_Initialize() +{ + _InitSettings(); + + return TRUE; +} + +//+--------------------------------------------------------------------------- +// +// _InitSettings +// +//---------------------------------------------------------------------------- + +void CShadowWindow::_InitSettings() +{ + HDC dcHandle = GetDC(nullptr); + + // device caps + int cBitsPixelScreen = GetDeviceCaps(dcHandle, BITSPIXEL); + + _isGradient = cBitsPixelScreen > 8; + + ReleaseDC(nullptr, dcHandle); + + if (_isGradient) + { + _color = RGB(0, 0, 0); + _sizeShift.cx = SHADOW_ALPHANUMBER; + _sizeShift.cy = SHADOW_ALPHANUMBER; + } + else + { + _color = RGB(128, 128, 128); + _sizeShift.cx = 2; + _sizeShift.cy = 2; + } +} + +//+--------------------------------------------------------------------------- +// +// _AdjustWindowPos +// +//---------------------------------------------------------------------------- + +void CShadowWindow::_AdjustWindowPos() +{ + if (!IsWindow(_GetWnd())) + { + return; + } + + HWND hWndOwner = _pWndOwner->_GetWnd(); + RECT rc = {0, 0, 0, 0}; + + GetWindowRect(hWndOwner, &rc); + SetWindowPos(_GetWnd(), hWndOwner, + rc.left + _sizeShift.cx, + rc.top + _sizeShift.cy, + rc.right - rc.left, + rc.bottom - rc.top, + SWP_NOOWNERZORDER | SWP_NOACTIVATE); +} + +//+--------------------------------------------------------------------------- +// +// _InitShadow +// +//---------------------------------------------------------------------------- + +#define GETRGBALPHA(_x_, _y_) ((RGBALPHA *)pDIBits + (_y_) * size.cx + (_x_)) + +void CShadowWindow::_InitShadow() +{ + typedef struct _RGBAPLHA { + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbAlpha; + } RGBALPHA; + + HDC dcScreenHandle = nullptr; + HDC dcLayeredHandle = nullptr; + RECT rcWindow = {0, 0, 0, 0}; + SIZE size = {0, 0}; + BITMAPINFO bitmapInfo; + HBITMAP bitmapMemHandle = nullptr; + HBITMAP bitmapOldHandle = nullptr; + void* pDIBits = nullptr; + int i = 0; + int j = 0; + POINT ptSrc = {0, 0}; + POINT ptDst = {0, 0}; + BLENDFUNCTION Blend; + + if (!_isGradient) + { + return; + } + + SetWindowLong(_GetWnd(), GWL_EXSTYLE, (GetWindowLong(_GetWnd(), GWL_EXSTYLE) | WS_EX_LAYERED)); + + _GetWindowRect(&rcWindow); + size.cx = rcWindow.right - rcWindow.left; + size.cy = rcWindow.bottom - rcWindow.top; + + dcScreenHandle = GetDC(nullptr); + if (dcScreenHandle == nullptr) { + return; + } + + dcLayeredHandle = CreateCompatibleDC(dcScreenHandle); + if (dcLayeredHandle == nullptr) { + ReleaseDC(nullptr, dcScreenHandle); + return; + } + + // create bitmap + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biWidth = size.cx; + bitmapInfo.bmiHeader.biHeight = size.cy; + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + bitmapInfo.bmiHeader.biSizeImage = 0; + bitmapInfo.bmiHeader.biXPelsPerMeter = 100; + bitmapInfo.bmiHeader.biYPelsPerMeter = 100; + bitmapInfo.bmiHeader.biClrUsed = 0; + bitmapInfo.bmiHeader.biClrImportant = 0; + + bitmapMemHandle = CreateDIBSection(dcScreenHandle, &bitmapInfo, DIB_RGB_COLORS, &pDIBits, nullptr, 0); + if (pDIBits == nullptr || bitmapMemHandle == nullptr) { + ReleaseDC(nullptr, dcScreenHandle); + DeleteDC(dcLayeredHandle); + return; + } + + memset(pDIBits, 0, ((((32 * size.cx) + 31) & ~31) / 8) * size.cy); + + // edges + for (i = 0; i < SHADOW_ALPHANUMBER; i++) { + RGBALPHA *ppxl; + BYTE bAlpha = AlphaTable[i]; + + // bottom + if (i <= (size.cy + 1)/2) { + for (j = SHADOW_ALPHANUMBER; j < size.cx - SHADOW_ALPHANUMBER; j++) { + ppxl = GETRGBALPHA(j, i); + ppxl->rgbAlpha = bAlpha; + } + } + + // right + if (i <= (size.cx + 1)/2) { + for (j = SHADOW_ALPHANUMBER; j < size.cy - SHADOW_ALPHANUMBER; j++) { + ppxl = GETRGBALPHA(size.cx - 1 - i, j); + ppxl->rgbAlpha = bAlpha; + } + } + } + + // corners + for (i = 0; i < SHADOW_ALPHANUMBER; i++) { + for (j = 0; j < SHADOW_ALPHANUMBER; j++) { + RGBALPHA *ppxl; + BYTE bAlpha; + bAlpha = CornerShadowAlphaMetric[i][SHADOW_ALPHANUMBER - j - 1]; + + // top-right + if ((i <= (size.cy + 1)/2) && (j <= (size.cx + 1)/2)) { + ppxl = GETRGBALPHA(size.cx - 1 - j, size.cy - 1 - i); + ppxl->rgbAlpha = bAlpha; + } + + // bottom-left + if ((i <= (size.cy + 1)/2) && (j <= (size.cx + 1)/2)) { + ppxl = GETRGBALPHA(j, i + 1); + ppxl->rgbAlpha = bAlpha; + } + + // bottom-right + if ((i <= (size.cy + 1)/2) && (j <= (size.cx + 1)/2)) { + ppxl = GETRGBALPHA(size.cx - 1 - j, i + 1); + ppxl->rgbAlpha = bAlpha; + } + } + } + + ptSrc.x = 0; + ptSrc.y = 0; + ptDst.x = rcWindow.left; + ptDst.y = rcWindow.top; + Blend.BlendOp = AC_SRC_OVER; + Blend.BlendFlags = 0; + Blend.SourceConstantAlpha = 255; + Blend.AlphaFormat = AC_SRC_ALPHA; + + bitmapOldHandle = (HBITMAP)SelectObject(dcLayeredHandle, bitmapMemHandle); + + UpdateLayeredWindow(_GetWnd(), dcScreenHandle, nullptr, &size, dcLayeredHandle, &ptSrc, 0, &Blend, ULW_ALPHA); + + SelectObject(dcLayeredHandle, bitmapOldHandle); + + // done + ReleaseDC(nullptr, dcScreenHandle); + DeleteDC(dcLayeredHandle); + DeleteObject(bitmapMemHandle); +} diff --git a/ShadowWindow.h b/ShadowWindow.h new file mode 100644 index 00000000..ff7d7142 --- /dev/null +++ b/ShadowWindow.h @@ -0,0 +1,47 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "BaseWindow.h" + +class CShadowWindow : public CBaseWindow +{ +public: + CShadowWindow(_In_ CBaseWindow *pWndOwner) + { + _pWndOwner = pWndOwner; + _color = RGB(0, 0, 0); + _sizeShift.cx = 0; + _sizeShift.cy = 0; + _isGradient = FALSE; + } + virtual ~CShadowWindow() + { + } + + BOOL _Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, _In_opt_ CBaseWindow *pParent = nullptr, int wndWidth = 0, int wndHeight = 0); + + void _Show(BOOL isShowWnd); + + LRESULT CALLBACK _WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, WPARAM wParam, LPARAM lParam); + + void _OnSettingChange(); + void _OnOwnerWndMoved(BOOL isResized); + +private: + BOOL _Initialize(); + void _InitSettings(); + void _AdjustWindowPos(); + void _InitShadow(); + +private: + CBaseWindow* _pWndOwner; + COLORREF _color; + SIZE _sizeShift; + BOOL _isGradient; +}; diff --git a/StartComposition.cpp b/StartComposition.cpp new file mode 100644 index 00000000..99e23820 --- /dev/null +++ b/StartComposition.cpp @@ -0,0 +1,133 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "EditSession.h" +#include "SampleIME.h" + +//+--------------------------------------------------------------------------- +// +// CStartCompositinoEditSession +// +//---------------------------------------------------------------------------- + +class CStartCompositionEditSession : public CEditSessionBase +{ +public: + CStartCompositionEditSession(_In_ CSampleIME *pTextService, _In_ ITfContext *pContext) : CEditSessionBase(pTextService, pContext) + { + } + + // ITfEditSession + STDMETHODIMP DoEditSession(TfEditCookie ec); +}; + +//+--------------------------------------------------------------------------- +// +// ITfEditSession::DoEditSession +// +//---------------------------------------------------------------------------- + +STDAPI CStartCompositionEditSession::DoEditSession(TfEditCookie ec) +{ + ITfInsertAtSelection* pInsertAtSelection = nullptr; + ITfRange* pRangeInsert = nullptr; + ITfContextComposition* pContextComposition = nullptr; + ITfComposition* pComposition = nullptr; + + if (FAILED(_pContext->QueryInterface(IID_ITfInsertAtSelection, (void **)&pInsertAtSelection))) + { + goto Exit; + } + + if (FAILED(pInsertAtSelection->InsertTextAtSelection(ec, TF_IAS_QUERYONLY, NULL, 0, &pRangeInsert))) + { + goto Exit; + } + + if (FAILED(_pContext->QueryInterface(IID_ITfContextComposition, (void **)&pContextComposition))) + { + goto Exit; + } + + if (SUCCEEDED(pContextComposition->StartComposition(ec, pRangeInsert, _pTextService, &pComposition)) && (nullptr != pComposition)) + { + _pTextService->_SetComposition(pComposition); + + // set selection to the adjusted range + TF_SELECTION tfSelection; + tfSelection.range = pRangeInsert; + tfSelection.style.ase = TF_AE_NONE; + tfSelection.style.fInterimChar = FALSE; + + _pContext->SetSelection(ec, 1, &tfSelection); + _pTextService->_SaveCompositionContext(_pContext); + } + +Exit: + if (nullptr != pContextComposition) + { + pContextComposition->Release(); + } + + if (nullptr != pRangeInsert) + { + pRangeInsert->Release(); + } + + if (nullptr != pInsertAtSelection) + { + pInsertAtSelection->Release(); + } + + return S_OK; +} + +////////////////////////////////////////////////////////////////////// +// +// CSampleIME class +// +////////////////////////////////////////////////////////////////////// + +//+--------------------------------------------------------------------------- +// +// _StartComposition +// +// this starts the new (std::nothrow) composition at the selection of the current +// focus context. +//---------------------------------------------------------------------------- + +void CSampleIME::_StartComposition(_In_ ITfContext *pContext) +{ + CStartCompositionEditSession* pStartCompositionEditSession = new (std::nothrow) CStartCompositionEditSession(this, pContext); + + if (nullptr != pStartCompositionEditSession) + { + HRESULT hr = S_OK; + pContext->RequestEditSession(_tfClientId, pStartCompositionEditSession, TF_ES_SYNC | TF_ES_READWRITE, &hr); + + pStartCompositionEditSession->Release(); + } +} + +//+--------------------------------------------------------------------------- +// +// _SaveCompositionContext +// +// this saves the context _pComposition belongs to; we need this to clear +// text attribute in case composition has not been terminated on +// deactivation +//---------------------------------------------------------------------------- + +void CSampleIME::_SaveCompositionContext(_In_ ITfContext *pContext) +{ + assert(_pContext == nullptr); + + pContext->AddRef(); + _pContext = pContext; +} diff --git a/TableDictionaryEngine.cpp b/TableDictionaryEngine.cpp new file mode 100644 index 00000000..0a8922cc --- /dev/null +++ b/TableDictionaryEngine.cpp @@ -0,0 +1,120 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "TableDictionaryEngine.h" +#include "DictionarySearch.h" + +//+--------------------------------------------------------------------------- +// +// CollectWord +// +//---------------------------------------------------------------------------- + +VOID CTableDictionaryEngine::CollectWord(_In_ CStringRange *pKeyCode, _Inout_ CSampleImeArray *pWordStrings) +{ + CDictionaryResult* pdret = nullptr; + CDictionarySearch dshSearch(_locale, _pDictionaryFile, pKeyCode); + + while (dshSearch.FindPhrase(&pdret)) + { + for (UINT index = 0; index < pdret->_FindPhraseList.Count(); index++) + { + CStringRange* pPhrase = nullptr; + pPhrase = pWordStrings->Append(); + if (pPhrase) + { + *pPhrase = *pdret->_FindPhraseList.GetAt(index); + } + } + + delete pdret; + pdret = nullptr; + } +} + +VOID CTableDictionaryEngine::CollectWord(_In_ CStringRange *pKeyCode, _Inout_ CSampleImeArray *pItemList) +{ + CDictionaryResult* pdret = nullptr; + CDictionarySearch dshSearch(_locale, _pDictionaryFile, pKeyCode); + + while (dshSearch.FindPhrase(&pdret)) + { + for (UINT iIndex = 0; iIndex < pdret->_FindPhraseList.Count(); iIndex++) + { + CCandidateListItem* pLI = nullptr; + pLI = pItemList->Append(); + if (pLI) + { + pLI->_ItemString.Set(*pdret->_FindPhraseList.GetAt(iIndex)); + pLI->_FindKeyCode.Set(pdret->_FindKeyCode.Get(), pdret->_FindKeyCode.GetLength()); + } + } + + delete pdret; + pdret = nullptr; + } +} + +//+--------------------------------------------------------------------------- +// +// CollectWordForWildcard +// +//---------------------------------------------------------------------------- + +VOID CTableDictionaryEngine::CollectWordForWildcard(_In_ CStringRange *pKeyCode, _Inout_ CSampleImeArray *pItemList) +{ + CDictionaryResult* pdret = nullptr; + CDictionarySearch dshSearch(_locale, _pDictionaryFile, pKeyCode); + + while (dshSearch.FindPhraseForWildcard(&pdret)) + { + for (UINT iIndex = 0; iIndex < pdret->_FindPhraseList.Count(); iIndex++) + { + CCandidateListItem* pLI = nullptr; + pLI = pItemList->Append(); + if (pLI) + { + pLI->_ItemString.Set(*pdret->_FindPhraseList.GetAt(iIndex)); + pLI->_FindKeyCode.Set(pdret->_FindKeyCode.Get(), pdret->_FindKeyCode.GetLength()); + } + } + + delete pdret; + pdret = nullptr; + } +} + +//+--------------------------------------------------------------------------- +// +// CollectWordFromConvertedStringForWildcard +// +//---------------------------------------------------------------------------- + +VOID CTableDictionaryEngine::CollectWordFromConvertedStringForWildcard(_In_ CStringRange *pString, _Inout_ CSampleImeArray *pItemList) +{ + CDictionaryResult* pdret = nullptr; + CDictionarySearch dshSearch(_locale, _pDictionaryFile, pString); + + while (dshSearch.FindConvertedStringForWildcard(&pdret)) // TAIL ALL CHAR MATCH + { + for (UINT index = 0; index < pdret->_FindPhraseList.Count(); index++) + { + CCandidateListItem* pLI = nullptr; + pLI = pItemList->Append(); + if (pLI) + { + pLI->_ItemString.Set(*pdret->_FindPhraseList.GetAt(index)); + pLI->_FindKeyCode.Set(pdret->_FindKeyCode.Get(), pdret->_FindKeyCode.GetLength()); + } + } + + delete pdret; + pdret = nullptr; + } +} + diff --git a/TableDictionaryEngine.h b/TableDictionaryEngine.h new file mode 100644 index 00000000..bea5efd2 --- /dev/null +++ b/TableDictionaryEngine.h @@ -0,0 +1,30 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "BaseDictionaryEngine.h" + +class CTableDictionaryEngine : public CBaseDictionaryEngine +{ +public: + CTableDictionaryEngine(LCID locale, _In_ CFile *pDictionaryFile) : CBaseDictionaryEngine(locale, pDictionaryFile) { } + virtual ~CTableDictionaryEngine() { } + + // Collect word from phrase string. + // param + // [in] psrgKeyCode - Specified key code pointer + // [out] pasrgWordString - Specified returns pointer of word as CStringRange. + // returns + // none. + VOID CollectWord(_In_ CStringRange *pKeyCode, _Inout_ CSampleImeArray *pWordStrings); + VOID CollectWord(_In_ CStringRange *pKeyCode, _Inout_ CSampleImeArray *pItemList); + + VOID CollectWordForWildcard(_In_ CStringRange *psrgKeyCode, _Inout_ CSampleImeArray *pItemList); + + VOID CollectWordFromConvertedStringForWildcard(_In_ CStringRange *pString, _Inout_ CSampleImeArray *pItemList); +}; diff --git a/TextEditSink.cpp b/TextEditSink.cpp new file mode 100644 index 00000000..b8e5f075 --- /dev/null +++ b/TextEditSink.cpp @@ -0,0 +1,134 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "globals.h" +#include "SampleIME.h" + +//+--------------------------------------------------------------------------- +// +// ITfTextEditSink::OnEndEdit +// +// Called by the system whenever anyone releases a write-access document lock. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnEndEdit(__RPC__in_opt ITfContext *pContext, TfEditCookie ecReadOnly, __RPC__in_opt ITfEditRecord *pEditRecord) +{ + BOOL isSelectionChanged; + + // + // did the selection change? + // The selection change includes the movement of caret as well. + // The caret position is represent as the empty selection range when + // there is no selection. + // + if (pEditRecord == nullptr) + { + return E_INVALIDARG; + } + if (SUCCEEDED(pEditRecord->GetSelectionStatus(&isSelectionChanged)) && + isSelectionChanged) + { + // If the selection is moved to out side of the current composition, + // we terminate the composition. This TextService supports only one + // composition in one context object. + if (_IsComposing()) + { + TF_SELECTION tfSelection; + ULONG fetched = 0; + + if (pContext == nullptr) + { + return E_INVALIDARG; + } + if (FAILED(pContext->GetSelection(ecReadOnly, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1) + { + return S_FALSE; + } + + ITfRange* pRangeComposition = nullptr; + if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) + { + if (!_IsRangeCovered(ecReadOnly, tfSelection.range, pRangeComposition)) + { + _EndComposition(pContext); + } + + pRangeComposition->Release(); + } + + tfSelection.range->Release(); + } + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// _InitTextEditSink +// +// Init a text edit sink on the topmost context of the document. +// Always release any previous sink. +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_InitTextEditSink(_In_ ITfDocumentMgr *pDocMgr) +{ + ITfSource* pSource = nullptr; + BOOL ret = TRUE; + + // clear out any previous sink first + if (_textEditSinkCookie != TF_INVALID_COOKIE) + { + if (SUCCEEDED(_pTextEditSinkContext->QueryInterface(IID_ITfSource, (void **)&pSource))) + { + pSource->UnadviseSink(_textEditSinkCookie); + pSource->Release(); + } + + _pTextEditSinkContext->Release(); + _pTextEditSinkContext = nullptr; + _textEditSinkCookie = TF_INVALID_COOKIE; + } + + if (pDocMgr == nullptr) + { + return TRUE; // caller just wanted to clear the previous sink + } + + if (FAILED(pDocMgr->GetTop(&_pTextEditSinkContext))) + { + return FALSE; + } + + if (_pTextEditSinkContext == nullptr) + { + return TRUE; // empty document, no sink possible + } + + ret = FALSE; + if (SUCCEEDED(_pTextEditSinkContext->QueryInterface(IID_ITfSource, (void **)&pSource))) + { + if (SUCCEEDED(pSource->AdviseSink(IID_ITfTextEditSink, (ITfTextEditSink *)this, &_textEditSinkCookie))) + { + ret = TRUE; + } + else + { + _textEditSinkCookie = TF_INVALID_COOKIE; + } + pSource->Release(); + } + + if (ret == FALSE) + { + _pTextEditSinkContext->Release(); + _pTextEditSinkContext = nullptr; + } + + return ret; +} diff --git a/TfInputProcessorProfile.cpp b/TfInputProcessorProfile.cpp new file mode 100644 index 00000000..cad5506a --- /dev/null +++ b/TfInputProcessorProfile.cpp @@ -0,0 +1,54 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "TfInputProcessorProfile.h" + +CTfInputProcessorProfile::CTfInputProcessorProfile() +{ + _pInputProcessorProfile = nullptr; +} + +CTfInputProcessorProfile::~CTfInputProcessorProfile() +{ + if (_pInputProcessorProfile) { + _pInputProcessorProfile->Release(); + _pInputProcessorProfile = nullptr; + } +} + +HRESULT CTfInputProcessorProfile::CreateInstance() +{ + HRESULT hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER, + IID_ITfInputProcessorProfiles, (void**)&_pInputProcessorProfile); + + return hr; +} + +HRESULT CTfInputProcessorProfile::GetCurrentLanguage(_Out_ LANGID *plangid) +{ + if (_pInputProcessorProfile) + { + return _pInputProcessorProfile->GetCurrentLanguage(plangid); + } + else + { + return E_FAIL; + } +} + +HRESULT CTfInputProcessorProfile::GetDefaultLanguageProfile(LANGID langid, REFGUID catid, _Out_ CLSID *pclsid, _Out_ GUID *pguidProfile) +{ + if (_pInputProcessorProfile) + { + return _pInputProcessorProfile->GetDefaultLanguageProfile(langid, catid, pclsid, pguidProfile); + } + else + { + return E_FAIL; + } +} diff --git a/TfInputProcessorProfile.h b/TfInputProcessorProfile.h new file mode 100644 index 00000000..212b5f12 --- /dev/null +++ b/TfInputProcessorProfile.h @@ -0,0 +1,22 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +class CTfInputProcessorProfile +{ +public: + CTfInputProcessorProfile(); + ~CTfInputProcessorProfile(); + + HRESULT CreateInstance(); + HRESULT GetCurrentLanguage(_Out_ LANGID *plangid); + HRESULT GetDefaultLanguageProfile(LANGID langid, REFGUID catid, _Out_ CLSID *pclsid, _Out_ GUID *pguidProfile); + +private: + ITfInputProcessorProfiles* _pInputProcessorProfile; +}; diff --git a/TfTextLayoutSink.cpp b/TfTextLayoutSink.cpp new file mode 100644 index 00000000..0ca396df --- /dev/null +++ b/TfTextLayoutSink.cpp @@ -0,0 +1,220 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "TfTextLayoutSink.h" +#include "SampleIME.h" +#include "GetTextExtentEditSession.h" + +CTfTextLayoutSink::CTfTextLayoutSink(_In_ CSampleIME *pTextService) +{ + _pTextService = pTextService; + _pTextService->AddRef(); + + _pRangeComposition = nullptr; + _pContextDocument = nullptr; + _tfEditCookie = TF_INVALID_EDIT_COOKIE; + + _dwCookieTextLayoutSink = TF_INVALID_COOKIE; + + _refCount = 1; + + DllAddRef(); +} + +CTfTextLayoutSink::~CTfTextLayoutSink() +{ + if (_pTextService) + { + _pTextService->Release(); + } + + DllRelease(); +} + +STDAPI CTfTextLayoutSink::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfTextLayoutSink)) + { + *ppvObj = (ITfTextLayoutSink *)this; + } + + if (*ppvObj) + { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +STDAPI_(ULONG) CTfTextLayoutSink::AddRef() +{ + return ++_refCount; +} + +STDAPI_(ULONG) CTfTextLayoutSink::Release() +{ + LONG cr = --_refCount; + + assert(_refCount >= 0); + + if (_refCount == 0) + { + delete this; + } + + return cr; +} + +//+--------------------------------------------------------------------------- +// +// ITfTextLayoutSink::OnLayoutChange +// +//---------------------------------------------------------------------------- + +STDAPI CTfTextLayoutSink::OnLayoutChange(_In_ ITfContext *pContext, TfLayoutCode lcode, _In_ ITfContextView *pContextView) +{ + // we're interested in only document context. + if (pContext != _pContextDocument) + { + return S_OK; + } + + switch (lcode) + { + case TF_LC_CHANGE: + { + CGetTextExtentEditSession* pEditSession = nullptr; + pEditSession = new (std::nothrow) CGetTextExtentEditSession(_pTextService, pContext, pContextView, _pRangeComposition, this); + if (nullptr != (pEditSession)) + { + HRESULT hr = S_OK; + pContext->RequestEditSession(_pTextService->_GetClientId(), pEditSession, TF_ES_SYNC | TF_ES_READ, &hr); + + pEditSession->Release(); + } + } + break; + + case TF_LC_DESTROY: + _LayoutDestroyNotification(); + break; + + } + return S_OK; +} + +HRESULT CTfTextLayoutSink::_StartLayout(_In_ ITfContext *pContextDocument, TfEditCookie ec, _In_ ITfRange *pRangeComposition) +{ + _pContextDocument = pContextDocument; + _pContextDocument->AddRef(); + + _pRangeComposition = pRangeComposition; + _pRangeComposition->AddRef(); + + _tfEditCookie = ec; + + return _AdviseTextLayoutSink(); +} + +VOID CTfTextLayoutSink::_EndLayout() +{ + if (_pRangeComposition) + { + _pRangeComposition->Release(); + _pRangeComposition = nullptr; + } + + if (_pContextDocument) + { + _UnadviseTextLayoutSink(); + _pContextDocument->Release(); + _pContextDocument = nullptr; + } +} + +HRESULT CTfTextLayoutSink::_AdviseTextLayoutSink() +{ + HRESULT hr = S_OK; + ITfSource* pSource = nullptr; + + hr = _pContextDocument->QueryInterface(IID_ITfSource, (void **)&pSource); + if (FAILED(hr)) + { + return hr; + } + + hr = pSource->AdviseSink(IID_ITfTextLayoutSink, (ITfTextLayoutSink *)this, &_dwCookieTextLayoutSink); + if (FAILED(hr)) + { + pSource->Release(); + return hr; + } + + pSource->Release(); + + return hr; +} + +HRESULT CTfTextLayoutSink::_UnadviseTextLayoutSink() +{ + HRESULT hr = S_OK; + ITfSource* pSource = nullptr; + + if (nullptr == _pContextDocument) + { + return E_FAIL; + } + + hr = _pContextDocument->QueryInterface(IID_ITfSource, (void **)&pSource); + if (FAILED(hr)) + { + return hr; + } + + hr = pSource->UnadviseSink(_dwCookieTextLayoutSink); + if (FAILED(hr)) + { + pSource->Release(); + return hr; + } + + pSource->Release(); + + return hr; +} + +HRESULT CTfTextLayoutSink::_GetTextExt(_Out_ RECT *lpRect) +{ + HRESULT hr = S_OK; + BOOL isClipped = TRUE; + ITfContextView* pContextView = nullptr; + + hr = _pContextDocument->GetActiveView(&pContextView); + if (FAILED(hr)) + { + return hr; + } + + if (FAILED(hr = pContextView->GetTextExt(_tfEditCookie, _pRangeComposition, lpRect, &isClipped))) + { + return hr; + } + + pContextView->Release(); + + return S_OK; +} diff --git a/TfTextLayoutSink.h b/TfTextLayoutSink.h new file mode 100644 index 00000000..a1f6b0fe --- /dev/null +++ b/TfTextLayoutSink.h @@ -0,0 +1,46 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +class CSampleIME; + +class CTfTextLayoutSink : public ITfTextLayoutSink +{ +public: + CTfTextLayoutSink(_In_ CSampleIME *pTextService); + virtual ~CTfTextLayoutSink(); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfTextLayoutSink + STDMETHODIMP OnLayoutChange(_In_ ITfContext *pContext, TfLayoutCode lcode, _In_ ITfContextView *pContextView); + + HRESULT _StartLayout(_In_ ITfContext *pContextDocument, TfEditCookie ec, _In_ ITfRange *pRangeComposition); + VOID _EndLayout(); + + HRESULT _GetTextExt(_Out_ RECT *lpRect); + ITfContext* _GetContextDocument() { return _pContextDocument; }; + + virtual VOID _LayoutChangeNotification(_In_ RECT *lpRect) = 0; + virtual VOID _LayoutDestroyNotification() = 0; + +private: + HRESULT _AdviseTextLayoutSink(); + HRESULT _UnadviseTextLayoutSink(); + +private: + ITfRange* _pRangeComposition; + ITfContext* _pContextDocument; + TfEditCookie _tfEditCookie; + CSampleIME* _pTextService; + DWORD _dwCookieTextLayoutSink; + LONG _refCount; +}; diff --git a/ThreadFocusSink.cpp b/ThreadFocusSink.cpp new file mode 100644 index 00000000..ca206649 --- /dev/null +++ b/ThreadFocusSink.cpp @@ -0,0 +1,106 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "SampleIME.h" +#include "CandidateListUIPresenter.h" + +//+--------------------------------------------------------------------------- +// +// ITfTextLayoutSink::OnSetThreadFocus +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnSetThreadFocus() +{ + if (_pCandidateListUIPresenter) + { + ITfDocumentMgr* pCandidateListDocumentMgr = nullptr; + ITfContext* pTfContext = _pCandidateListUIPresenter->_GetContextDocument(); + + if ((nullptr != pTfContext) && SUCCEEDED(pTfContext->GetDocumentMgr(&pCandidateListDocumentMgr))) + { + if (pCandidateListDocumentMgr == _pDocMgrLastFocused) + { + _pCandidateListUIPresenter->OnSetThreadFocus(); + } + + pCandidateListDocumentMgr->Release(); + } + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfTextLayoutSink::OnKillThreadFocus +// +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnKillThreadFocus() +{ + if (_pCandidateListUIPresenter) + { + ITfDocumentMgr* pCandidateListDocumentMgr = nullptr; + ITfContext* pTfContext = _pCandidateListUIPresenter->_GetContextDocument(); + + if ((nullptr != pTfContext) && SUCCEEDED(pTfContext->GetDocumentMgr(&pCandidateListDocumentMgr))) + { + if (_pDocMgrLastFocused) + { + _pDocMgrLastFocused->Release(); + _pDocMgrLastFocused = nullptr; + } + _pDocMgrLastFocused = pCandidateListDocumentMgr; + if (_pDocMgrLastFocused) + { + _pDocMgrLastFocused->AddRef(); + } + } + _pCandidateListUIPresenter->OnKillThreadFocus(); + } + return S_OK; +} + +BOOL CSampleIME::_InitThreadFocusSink() +{ + ITfSource* pSource = nullptr; + + if (FAILED(_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource))) + { + return FALSE; + } + + if (FAILED(pSource->AdviseSink(IID_ITfThreadFocusSink, (ITfThreadFocusSink *)this, &_dwThreadFocusSinkCookie))) + { + pSource->Release(); + return FALSE; + } + + pSource->Release(); + + return TRUE; +} + +void CSampleIME::_UninitThreadFocusSink() +{ + ITfSource* pSource = nullptr; + + if (FAILED(_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource))) + { + return; + } + + if (FAILED(pSource->UnadviseSink(_dwThreadFocusSinkCookie))) + { + pSource->Release(); + return; + } + + pSource->Release(); +} \ No newline at end of file diff --git a/ThreadMgrEventSink.cpp b/ThreadMgrEventSink.cpp new file mode 100644 index 00000000..d77ee4c6 --- /dev/null +++ b/ThreadMgrEventSink.cpp @@ -0,0 +1,178 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "Private.h" +#include "Globals.h" +#include "SampleIME.h" +#include "CandidateListUIPresenter.h" + +//+--------------------------------------------------------------------------- +// +// ITfThreadMgrEventSink::OnInitDocumentMgr +// +// Sink called by the framework just before the first context is pushed onto +// a document. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnInitDocumentMgr(_In_ ITfDocumentMgr *pDocMgr) +{ + pDocMgr; + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfThreadMgrEventSink::OnUninitDocumentMgr +// +// Sink called by the framework just after the last context is popped off a +// document. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnUninitDocumentMgr(_In_ ITfDocumentMgr *pDocMgr) +{ + pDocMgr; + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfThreadMgrEventSink::OnSetFocus +// +// Sink called by the framework when focus changes from one document to +// another. Either document may be NULL, meaning previously there was no +// focus document, or now no document holds the input focus. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnSetFocus(_In_ ITfDocumentMgr *pDocMgrFocus, _In_ ITfDocumentMgr *pDocMgrPrevFocus) +{ + pDocMgrPrevFocus; + + _InitTextEditSink(pDocMgrFocus); + + _UpdateLanguageBarOnSetFocus(pDocMgrFocus); + + // + // We have to hide/unhide candidate list depending on whether they are + // associated with pDocMgrFocus. + // + if (_pCandidateListUIPresenter) + { + ITfDocumentMgr* pCandidateListDocumentMgr = nullptr; + ITfContext* pTfContext = _pCandidateListUIPresenter->_GetContextDocument(); + if ((nullptr != pTfContext) && SUCCEEDED(pTfContext->GetDocumentMgr(&pCandidateListDocumentMgr))) + { + if (pCandidateListDocumentMgr != pDocMgrFocus) + { + _pCandidateListUIPresenter->OnKillThreadFocus(); + } + else + { + _pCandidateListUIPresenter->OnSetThreadFocus(); + } + + pCandidateListDocumentMgr->Release(); + } + } + + if (_pDocMgrLastFocused) + { + _pDocMgrLastFocused->Release(); + _pDocMgrLastFocused = nullptr; + } + + _pDocMgrLastFocused = pDocMgrFocus; + + if (_pDocMgrLastFocused) + { + _pDocMgrLastFocused->AddRef(); + } + + return S_OK; +} + +//+--------------------------------------------------------------------------- +// +// ITfThreadMgrEventSink::OnPushContext +// +// Sink called by the framework when a context is pushed. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnPushContext(_In_ ITfContext *pContext) +{ + pContext; + + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// ITfThreadMgrEventSink::OnPopContext +// +// Sink called by the framework when a context is popped. +//---------------------------------------------------------------------------- + +STDAPI CSampleIME::OnPopContext(_In_ ITfContext *pContext) +{ + pContext; + + return E_NOTIMPL; +} + +//+--------------------------------------------------------------------------- +// +// _InitThreadMgrEventSink +// +// Advise our sink. +//---------------------------------------------------------------------------- + +BOOL CSampleIME::_InitThreadMgrEventSink() +{ + ITfSource* pSource = nullptr; + BOOL ret = FALSE; + + if (FAILED(_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource))) + { + return ret; + } + + if (FAILED(pSource->AdviseSink(IID_ITfThreadMgrEventSink, (ITfThreadMgrEventSink *)this, &_threadMgrEventSinkCookie))) + { + _threadMgrEventSinkCookie = TF_INVALID_COOKIE; + goto Exit; + } + + ret = TRUE; + +Exit: + pSource->Release(); + return ret; +} + +//+--------------------------------------------------------------------------- +// +// _UninitThreadMgrEventSink +// +// Unadvise our sink. +//---------------------------------------------------------------------------- + +void CSampleIME::_UninitThreadMgrEventSink() +{ + ITfSource* pSource = nullptr; + + if (_threadMgrEventSinkCookie == TF_INVALID_COOKIE) + { + return; + } + + if (SUCCEEDED(_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource))) + { + pSource->UnadviseSink(_threadMgrEventSinkCookie); + pSource->Release(); + } + + _threadMgrEventSinkCookie = TF_INVALID_COOKIE; +} diff --git a/TipCandidateList.cpp b/TipCandidateList.cpp new file mode 100644 index 00000000..eb52d151 --- /dev/null +++ b/TipCandidateList.cpp @@ -0,0 +1,185 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "private.h" +#include "TipCandidateList.h" +#include "EnumTfCandidates.h" +#include "TipCandidateString.h" + +HRESULT CTipCandidateList::CreateInstance(_Outptr_ ITfCandidateList **ppobj, size_t candStrReserveSize) +{ + if (ppobj == nullptr) + { + return E_INVALIDARG; + } + *ppobj = nullptr; + + *ppobj = new (std::nothrow) CTipCandidateList(candStrReserveSize); + if (*ppobj == nullptr) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT CTipCandidateList::CreateInstance(REFIID riid, _Outptr_ void **ppvObj, size_t candStrReserveSize) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + *ppvObj = nullptr; + + *ppvObj = new (std::nothrow) CTipCandidateList(candStrReserveSize); + if (*ppvObj == nullptr) + { + return E_OUTOFMEMORY; + } + + return ((CTipCandidateList*)(*ppvObj))->QueryInterface(riid, ppvObj); +} + +CTipCandidateList::CTipCandidateList(size_t candStrReserveSize) +{ + _refCount = 0; + + if (0 < candStrReserveSize) + { + _tfCandStrList.reserve(candStrReserveSize); + } +} + +CTipCandidateList::~CTipCandidateList() +{ +} + +STDMETHODIMP CTipCandidateList::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_POINTER; + } + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown)) + { + *ppvObj = (ITfCandidateList*)this; + } + else if (IsEqualIID(riid, IID_ITfCandidateList)) + { + *ppvObj = (ITfCandidateList*)this; + } + + if (*ppvObj == nullptr) + { + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) CTipCandidateList::AddRef() +{ + return (ULONG)InterlockedIncrement((LONG*)&_refCount); +} + +STDMETHODIMP_(ULONG) CTipCandidateList::Release() +{ + ULONG cRefT = (ULONG)InterlockedDecrement((LONG*)&_refCount); + if (0 < cRefT) + { + return cRefT; + } + + delete this; + + return 0; +} + +STDMETHODIMP CTipCandidateList::EnumCandidates(_Outptr_ IEnumTfCandidates **ppEnum) +{ + return CEnumTfCandidates::CreateInstance(IID_IEnumTfCandidates, (void**)ppEnum, _tfCandStrList); +} + +STDMETHODIMP CTipCandidateList::GetCandidate(ULONG nIndex, _Outptr_result_maybenull_ ITfCandidateString **ppCandStr) +{ + if (ppCandStr == nullptr) + { + return E_POINTER; + } + *ppCandStr = nullptr; + + ULONG sizeCandStr = (ULONG)_tfCandStrList.Count(); + if (sizeCandStr <= nIndex) + { + return E_FAIL; + } + + for (UINT i = 0; i < _tfCandStrList.Count(); i++) + { + ITfCandidateString** ppCandStrCur = _tfCandStrList.GetAt(i); + ULONG indexCur = 0; + if ((nullptr != ppCandStrCur) && (SUCCEEDED((*ppCandStrCur)->GetIndex(&indexCur)))) + { + if (nIndex == indexCur) + { + BSTR bstr; + CTipCandidateString* pTipCandidateStrCur = (CTipCandidateString*)(*ppCandStrCur); + pTipCandidateStrCur->GetString(&bstr); + + CTipCandidateString::CreateInstance(IID_ITfCandidateString, (void**)ppCandStr); + + if (nullptr != (*ppCandStr)) + { + CTipCandidateString* pTipCandidateStr = (CTipCandidateString*)(*ppCandStr); + pTipCandidateStr->SetString((LPCWSTR)bstr, SysStringLen(bstr)); + } + + SysFreeString(bstr); + + break; + } + } + } + return S_OK; +} + +STDMETHODIMP CTipCandidateList::GetCandidateNum(_Out_ ULONG *pnCnt) +{ + if (pnCnt == nullptr) + { + return E_POINTER; + } + + *pnCnt = (ULONG)(_tfCandStrList.Count()); + return S_OK; +} + +STDMETHODIMP CTipCandidateList::SetResult(ULONG nIndex, TfCandidateResult imcr) +{ + nIndex;imcr; + + return E_NOTIMPL; +} + +STDMETHODIMP CTipCandidateList::SetCandidate(_In_ ITfCandidateString **ppCandStr) +{ + if (ppCandStr == nullptr) + { + return E_POINTER; + } + + ITfCandidateString** ppCandLast = _tfCandStrList.Append(); + if (ppCandLast) + { + *ppCandLast = *ppCandStr; + } + + return S_OK; +} diff --git a/TipCandidateList.h b/TipCandidateList.h new file mode 100644 index 00000000..535d54b6 --- /dev/null +++ b/TipCandidateList.h @@ -0,0 +1,43 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "SampleIMEBaseStructure.h" + +// +// CTipCandidateList +// + +class CTipCandidateList : public ITfCandidateList +{ +protected: + CTipCandidateList(size_t candStrReserveSize); + virtual ~CTipCandidateList(); + +public: + static HRESULT CreateInstance(_Outptr_ ITfCandidateList **ppobj, size_t candStrReserveSize = 0); + static HRESULT CreateInstance(REFIID riid, _Outptr_ void **ppvObj, size_t candStrReserveSize = 0); + + // IUnknown methods + virtual STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + virtual STDMETHODIMP_(ULONG) AddRef(); + virtual STDMETHODIMP_(ULONG) Release(); + + // ITfCandidateList methods + virtual STDMETHODIMP EnumCandidates(_Outptr_ IEnumTfCandidates **ppEnum); + virtual STDMETHODIMP GetCandidate(ULONG nIndex, _Outptr_result_maybenull_ ITfCandidateString **ppCandStr); + virtual STDMETHODIMP GetCandidateNum(_Out_ ULONG *pnCnt); + virtual STDMETHODIMP SetResult(ULONG nIndex, TfCandidateResult imcr); + + virtual STDMETHODIMP SetCandidate(_In_ ITfCandidateString **ppCandStr); + +protected: + long _refCount; + CSampleImeArray _tfCandStrList; +}; + diff --git a/TipCandidateString.cpp b/TipCandidateString.cpp new file mode 100644 index 00000000..1d1fee1a --- /dev/null +++ b/TipCandidateString.cpp @@ -0,0 +1,128 @@ +// 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) Microsoft Corporation. All rights reserved + +#include "private.h" +#include "TipCandidateString.h" + +HRESULT CTipCandidateString::CreateInstance(_Outptr_ CTipCandidateString **ppobj) +{ + if (ppobj == nullptr) + { + return E_INVALIDARG; + } + *ppobj = nullptr; + + *ppobj = new (std::nothrow) CTipCandidateString(); + if (*ppobj == nullptr) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT CTipCandidateString::CreateInstance(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_INVALIDARG; + } + *ppvObj = nullptr; + + *ppvObj = new (std::nothrow) CTipCandidateString(); + if (*ppvObj == nullptr) + { + return E_OUTOFMEMORY; + } + + return ((CTipCandidateString*)(*ppvObj))->QueryInterface(riid, ppvObj); +} + +CTipCandidateString::CTipCandidateString(void) +{ + _refCount = 0; + _index = 0; +} + +CTipCandidateString::~CTipCandidateString() +{ +} + +// IUnknown methods +STDMETHODIMP CTipCandidateString::QueryInterface(REFIID riid, _Outptr_ void **ppvObj) +{ + if (ppvObj == nullptr) + { + return E_POINTER; + } + *ppvObj = nullptr; + + if (IsEqualIID(riid, IID_IUnknown)) + { + *ppvObj = (CTipCandidateString*)this; + } + else if (IsEqualIID(riid, IID_ITfCandidateString)) + { + *ppvObj = (CTipCandidateString*)this; + } + + if (*ppvObj == nullptr) + { + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) CTipCandidateString::AddRef(void) +{ + return (ULONG)InterlockedIncrement((LONG*)&_refCount); +} + +STDMETHODIMP_(ULONG) CTipCandidateString::Release(void) +{ + ULONG refT = (ULONG)InterlockedDecrement((LONG*)&_refCount); + if (0 < refT) + { + return refT; + } + + delete this; + + return 0; +} + +// ITfCandidateString methods +STDMETHODIMP CTipCandidateString::GetString(BSTR *pbstr) +{ + *pbstr = SysAllocString(_candidateStr.c_str()); + return S_OK; +} + +STDMETHODIMP CTipCandidateString::GetIndex(_Out_ ULONG *pnIndex) +{ + if (pnIndex == nullptr) + { + return E_POINTER; + } + + *pnIndex = _index; + return S_OK; +} + +STDMETHODIMP CTipCandidateString::SetIndex(ULONG uIndex) +{ + _index = uIndex; + return S_OK; +} + +STDMETHODIMP CTipCandidateString::SetString(_In_ const WCHAR *pch, DWORD_PTR length) +{ + _candidateStr.assign(pch, 0, length); + return S_OK; +} diff --git a/TipCandidateString.h b/TipCandidateString.h new file mode 100644 index 00000000..5faa8b4c --- /dev/null +++ b/TipCandidateString.h @@ -0,0 +1,39 @@ +// 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) Microsoft Corporation. All rights reserved + +#pragma once + +#include "private.h" +#include + +class CTipCandidateString : public ITfCandidateString +{ +protected: + CTipCandidateString(); + virtual ~CTipCandidateString(); + +public: + static HRESULT CreateInstance(_Outptr_ CTipCandidateString **ppobj); + static HRESULT CreateInstance(REFIID riid, _Outptr_ void **ppvObj); + + // IUnknown methods + virtual STDMETHODIMP QueryInterface(REFIID riid, _Outptr_ void **ppvObj); + virtual STDMETHODIMP_(ULONG) AddRef(); + virtual STDMETHODIMP_(ULONG) Release(); + + // ITfCandidateString methods + virtual STDMETHODIMP GetString(BSTR *pbstr); + virtual STDMETHODIMP GetIndex(_Out_ ULONG *pnIndex); + + virtual STDMETHODIMP SetIndex(ULONG uIndex); + virtual STDMETHODIMP SetString(_In_ const WCHAR *pch, DWORD_PTR length); + +protected: + long _refCount; + int _index; + std::wstring _candidateStr; +}; \ No newline at end of file diff --git a/image/DoubleSingleByteOff.ico b/image/DoubleSingleByteOff.ico new file mode 100644 index 0000000000000000000000000000000000000000..1f87d9a1530a5ab7115365ed795227998f631e28 GIT binary patch literal 17542 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 C+}_^+ literal 0 HcmV?d00001 diff --git a/image/DoubleSingleByteOn.ico b/image/DoubleSingleByteOn.ico new file mode 100644 index 0000000000000000000000000000000000000000..a1405b72581b690b4cf970fbf3190978b2fdce1f GIT binary patch literal 17542 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 UQH^~oV*l%PUA^NI!6)DSKkdZp4gdfE literal 0 HcmV?d00001 diff --git a/image/ImeModeOff.ico b/image/ImeModeOff.ico new file mode 100644 index 0000000000000000000000000000000000000000..5bfbd09326c86aa00b6ecf01e79c1c1f2ea4b89b GIT binary patch literal 39766 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 literal 0 HcmV?d00001 diff --git a/image/PunctuationOff.ico b/image/PunctuationOff.ico new file mode 100644 index 0000000000000000000000000000000000000000..e3003693c746d36d2725f770421cd13c979db0b8 GIT binary patch literal 17542 zcmeI3OKco>9moIsFec8!37`-mp<0E~ssvF9f}lcbxquL~f&&~VhX_<9;sysQ#37|Y zY9zpjOF1GqH5N#fs2YS+Xk#UC>w&-pHSznk-k0}1vpd7*`=1%Fyd%(gxChsZ>UWs(%!1P_NhhVsUL< zhNI>Oia&)y!CzY|SzkDcHYk_N$WUCDK{D-!e;pJK#m`(WZ)K1_HyZE#SmZd4pUq}N zz6*TL`T2Zd-4_l@cYCEHzn!07@D~@C{N?2pKa*MAB*R|u9@IMRnYp?7Ei&YCIqS~{ z#m8RhC|}La&RH2278d=brDcC*CF8HI2779+cn@lw_@9}Xu`2enQ)N;0&ePW=>PdZ3 zpqI8z0^KxhZfrChU->5h*LPh((}LXTwoux9HN)x4^G zRrhY}qZHQ|df^WEU+N-*;!%GwsTYkn zuPc75uaFJ4+9Be9=a>{Hf4y)keyg61WY9j^yq6*wwvS1@XvBFv;=fadm6gb+JAGlJ zKLq|9Cx5+gi+}Z|0sour&~HrYMI+AZ5&vx&mNxCBc5dsmLljeshZ_IIf7DxVlwoP9 z|Ckh~etY2#_z!w(@xNJy{$f%u8gX6^>W3ijwrilRvA=sBR!vtr)cq5uZjNyuYv5P| z#~L`+z~O4Zw}&zPoJYw`n3ozL%{x7Dk{6B8{@zLG*Qs+G%o_gf zIPQm;eP$IMS#nq9-!^W}2Y0I|}SY|t8F4 z4f@&!^ED@YW;_lb*gqee{JbXv{k-&Wi)X*??Cfj}9;(d7T>8{s00!tPKmLjD`i9s3 z63pk*$RIyTV=vCNw0 z<+&~NGhOB1K;Pgm;HYRHS%9|v4cdm@+r}B_L(}E6w@x45OFB+NzrV;HcnutX;`~SO zyoL_0dT(@UIdKL%Gk$`TXn~;7A z`f2E6$5I;lvSSjE|8*lTy(X0(fX}kdWfoay-@5W&_;B$X`LWBsaM7y+9v9sd>h;3H>*(K8J$DINH4lV- z@V>`J9?*eo;h_h`)pDg$8v(Y}YrjmPf4eWqVV7ig;#X7iVCkU!oG^F?3m&{s574hd zU%9ROzUr4gHnj4y|15p{wi){ECLV&BR?!Rfz}Bjah&KA@msHpL*vsqo*E>SQ&t~xM zlVV3(54uK-Q#@C0Ekk!iw9!Xby#4+d$lt>L-X+@Wft<-I`;_J4x<5cyjlMV@iw{Ux%FBdZ1I zjYGo%^v1FGII-;p_YNt>3I;h8Lqqz?V+Q)ll?M72pl@ZAoRU}j4f^)pfc{|nPxOQM z&Pp8^?ahQ`j-iXS8{-o%I3-hKD}!?l{D+?_`D0i)1fDK(zCvDk?%ut7XT`UA3Ub~^E|*oT`GIOv z=(LDO(fdh7aC`vSY#xF(@pIYSy7d=g=c-*#Ej5zKtiXHm3U!4CUv=`VgDzUgvjjiq z#2#)NJ`UZ-;r${yc=Y`F^GS5G(|YwzHr7LTJwJg%J*8c@)B129NWUp`@qTa&^V=}I z53_z)2zFW@%sQEOLdwS-Ec}BrFva(`n1y!2`Wmh&&hW3S_J3ESICGi*Y2A+_n7L`T z;U@6oVa*;Jjs2T-3BTeR_&KXj_J~y;w$Q9&u)ag`2mEVx=AH%rx8eUQ^}5GiU$gN~ z{p28r@XI#<_42O+4RC&q-c7M1sXls;L->igDd~grRp{tF#W$FFs_(=mHm>iPgysPB z_26-2hez1M%ful>aK5U#Nww=BcF)@upwKLT{^G}?mw$&bT zA0>|7US7^D;&=2gYj@!Pv}_~Y?e$=-=8f7%iuWaQf%=6OvElFFJSp5!j1Udw3;JF0 zlaSru4+wG*`5>iUoEcAw9GudAS4?kkq68WP9tJ*6ZS@gwTH31jqeJ>El`81S`KEdt zY!&u8!T%2-OY}~316fBUZ?9vce(>ug`%E@?CYxPtK|7kYsHbz;4-Pyup_4(TGstGq zO9tdia1Fav5|Q3IY2zDq4u=k&Rjx*#u4G_8c-TsPb2LB5gL^c>--&0t@9f*8Y>u9v zK`!-)>PI!Z(MBAqi9BugV!^_cBs1kj&4$_Il%ucx?vWu;08VTifDxWx9vUi z=xS8{vzr4Q!+#E4*1wK!nzOU>HTJdY0m*@`G7hzl%Y7MSI1|v04C literal 0 HcmV?d00001 diff --git a/image/PunctuationOn.ico b/image/PunctuationOn.ico new file mode 100644 index 0000000000000000000000000000000000000000..14486840fbe2bab44a65efa762fbd4cc936df5f7 GIT binary patch literal 17542 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 DeCj2u literal 0 HcmV?d00001 diff --git a/image/SampleIme.ico b/image/SampleIme.ico new file mode 100644 index 0000000000000000000000000000000000000000..21845ba82a72ec9526d47cf3573b85d9a6c7afa3 GIT binary patch literal 39766 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 ga-Fxr9(m}+{Hp