This commit is contained in:
fanlusky committed 2026-06-11 15:55:59 +08:00
commit 2bb9ba42a1
29 files changed
+4393

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
CompileFlags:
Add: [
"-std=c++17", # needed when using MSVC
# Macros
-DUNICODE,
-D_UNICODE,
-DFANY_DEBUG,
# Include paths
"-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\IMECodes\\RelatedProjects\\TsfEditControl\\include",
# Third-party include paths via vcpkg
"-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\IMECodes\\RelatedProjects\\TsfEditControl\\build\\vcpkg_installed\\x64-windows\\include",
-Wno-unused-includes,
-Wno-microsoft-goto,
# -Wno-unused-value,
-Wno-nonportable-include-path,
-Wno-microsoft-extra-qualification,
-Wno-invalid-token-paste,
] # ignore some specific warnings
Diagnostics:
UnusedIncludes: None # This would not work in Windows, why?
# Includes:
# IgnoreHeader: ["Private.h", "Globals.h"] # Suppress unused includes waraning
+5
View File
@@ -0,0 +1,5 @@
/build/
/out/
/.vs/
/.vscode/
/CMakeUserPresets.json
@@ -0,0 +1,92 @@
cmake_minimum_required(VERSION 3.25)
project(TsfEditControl VERSION 0.1.0 LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_definitions(
/D_UNICODE=1
/DUNICODE=1
)
find_package(fmt CONFIG REQUIRED)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(TSF_EDIT_CONTROL_SOURCES
"./src/DisplayAttribute.cpp"
"./src/TextContainer.cpp"
"./src/TextEditor.cpp"
"./src/TextEditSink.cpp"
"./src/TextInputCtrl.cpp"
"./src/TextLayout.cpp"
"./src/TextStore.cpp"
"./src/TsfD2DTextBox.cpp"
"./src/TsfTextServices.cpp"
)
set(TSF_EDIT_CONTROL_HEADERS
"./include/TsfD2DTextBox.h"
"./src/DisplayAttribute.h"
"./src/private.h"
"./src/TextContainer.h"
"./src/TextEditor.h"
"./src/TextEditSink.h"
"./src/TextInputCtrl.h"
"./src/TextLayout.h"
"./src/TextStore.h"
"./src/TsfTextServices.h"
)
add_library(TsfEditControl STATIC
${TSF_EDIT_CONTROL_SOURCES}
${TSF_EDIT_CONTROL_HEADERS}
)
add_library(TsfD2DTextBox ALIAS TsfEditControl)
if(MSVC)
target_compile_options(TsfEditControl PRIVATE /W4 /permissive-)
endif()
target_include_directories(TsfEditControl
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>"
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries(TsfEditControl PUBLIC
d2d1.lib
dwrite.lib
imm32.lib
fmt::fmt-header-only
)
add_executable(TsfEditControlDemo WIN32
"./demo/TsfD2DTextBoxDemo.cpp"
)
target_include_directories(TsfEditControlDemo PRIVATE "./include")
target_link_libraries(TsfEditControlDemo PRIVATE TsfEditControl)
install(TARGETS TsfEditControl
EXPORT TsfEditControlTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(FILES "./include/TsfD2DTextBox.h" DESTINATION include)
install(EXPORT TsfEditControlTargets
FILE TsfEditControlTargets.cmake
NAMESPACE TsfEditControl::
DESTINATION lib/cmake/TsfEditControl
)
@@ -0,0 +1,29 @@
{
"version": 2,
"configurePresets": [
{
"name": "vcpkg",
"displayName": "Visual Studio 2022 + vcpkg",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build",
"environment": {
"VCPKG_ROOT": "C:/Users/SonnyCalcr/scoop/apps/vcpkg/current"
},
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "C:/Users/SonnyCalcr/scoop/apps/vcpkg/current/scripts/buildsystems/vcpkg.cmake"
}
}
],
"buildPresets": [
{
"name": "debug",
"configurePreset": "vcpkg",
"configuration": "Debug"
},
{
"name": "release",
"configurePreset": "vcpkg",
"configuration": "Release"
}
]
}
+82
View File
@@ -0,0 +1,82 @@
# TsfEditControl
一个独立的 Win32 TSF 编辑控件工程,当前实现基于 Direct2D / DirectWrite 渲染,并把 TSF 文本输入、组合串绘制、候选框定位和软换行都收进了控件内部。
## 当前能力
- Direct2D / DirectWrite 文本渲染
- TSF 文本服务接入
- preedit / display attribute 绘制
- 候选框位置上报
- 软换行
- 基础选区、插入点与鼠标命中
- 独立 demo 宿主程序
## 目录结构
- `include/TsfD2DTextBox.h`
当前公开头文件。
- `src/`
控件内部实现与 TSF 支撑代码。
- `demo/TsfD2DTextBoxDemo.cpp`
最小宿主示例。
- `CMakeLists.txt`
顶层工程文件,可直接单独配置。
- `vcpkg.json`
当前依赖清单。
- `CMakePresets.json`
预置的 VS2022 + vcpkg 配置。
## 生成目标
- `TsfEditControl`
静态库目标。
- `TsfD2DTextBox`
兼容别名目标,方便旧命名逐步迁移。
- `TsfEditControlDemo`
demo 可执行文件。
## 构建
如果你已经在本机装好了 vcpkg,并且路径和 preset 里的设置一致,可以直接执行:
```powershell
cmake --preset vcpkg
cmake --build --preset debug
```
生成后的 demo 默认在:
```text
build/bin/Debug/TsfEditControlDemo.exe
```
## 现阶段说明
当前对外类型名还是 `TsfD2DTextBox`,这是为了先保持 API 稳定。后续如果你决定正式把它演进成通用 edit 控件,我们可以再一起做一轮命名收口,把:
- 文件名
- 类名
- CMake target 名
- demo 名称
统一成 `TsfEditControl` 风格。
## 脚本
`scripts/` 目录里现在提供了几份 PowerShell 脚本:
- `./scripts/configure.ps1`
一键执行 CMake configure。
- `./scripts/build.ps1`
一键配置并编译,默认 `Debug`。
- `./scripts/run-demo.ps1`
一键配置、编译并启动 demo。
示例:
```powershell
./scripts/build.ps1
./scripts/build.ps1 -Configuration Release
./scripts/run-demo.ps1
```
@@ -0,0 +1,171 @@
#include <Windows.h>
#include "TsfD2DTextBox.h"
namespace
{
const wchar_t *kWindowClass = L"TsfD2DTextBoxDemoWindow";
const wchar_t *kWindowTitle = L"TsfD2DTextBox Demo";
constexpr int kOuterMargin = 20;
constexpr int kSectionGap = 16;
constexpr int kSearchTitleHeight = 24;
constexpr int kRoundedBoxHeight = 68;
constexpr int kRoundedInset = 10;
constexpr int kRoundedRadius = 18;
TsfD2DTextBox *g_editorTextBox = nullptr;
TsfD2DTextBox *g_searchTextBox = nullptr;
RECT g_searchOuterRect = {};
RECT MakeRect(int left, int top, int right, int bottom)
{
RECT rc = {left, top, right, bottom};
return rc;
}
void LayoutControls(HWND hwnd)
{
RECT rcClient = {};
GetClientRect(hwnd, &rcClient);
const int width = rcClient.right - rcClient.left;
const int height = rcClient.bottom - rcClient.top;
g_searchOuterRect = MakeRect(kOuterMargin, height - kOuterMargin - kRoundedBoxHeight, width - kOuterMargin,
height - kOuterMargin);
RECT editorBounds = MakeRect(kOuterMargin, kOuterMargin, width - kOuterMargin,
g_searchOuterRect.top - kSectionGap - kSearchTitleHeight);
RECT searchBounds = MakeRect(g_searchOuterRect.left + kRoundedInset, g_searchOuterRect.top + kRoundedInset,
g_searchOuterRect.right - kRoundedInset, g_searchOuterRect.bottom - kRoundedInset);
if (g_editorTextBox)
{
g_editorTextBox->Move(editorBounds.left, editorBounds.top, editorBounds.right - editorBounds.left,
editorBounds.bottom - editorBounds.top);
}
if (g_searchTextBox)
{
g_searchTextBox->Move(searchBounds.left, searchBounds.top, searchBounds.right - searchBounds.left,
searchBounds.bottom - searchBounds.top);
}
}
void PaintDemoBackground(HWND hwnd)
{
PAINTSTRUCT ps = {};
HDC hdc = BeginPaint(hwnd, &ps);
RECT rcClient = {};
GetClientRect(hwnd, &rcClient);
HBRUSH backgroundBrush = CreateSolidBrush(RGB(245, 246, 248));
FillRect(hdc, &rcClient, backgroundBrush);
DeleteObject(backgroundBrush);
HBRUSH roundedBrush = CreateSolidBrush(RGB(255, 255, 255));
HPEN roundedPen = CreatePen(PS_SOLID, 1, RGB(210, 214, 220));
HGDIOBJ oldBrush = SelectObject(hdc, roundedBrush);
HGDIOBJ oldPen = SelectObject(hdc, roundedPen);
RoundRect(hdc, g_searchOuterRect.left, g_searchOuterRect.top, g_searchOuterRect.right, g_searchOuterRect.bottom,
kRoundedRadius, kRoundedRadius);
SelectObject(hdc, oldPen);
SelectObject(hdc, oldBrush);
DeleteObject(roundedPen);
DeleteObject(roundedBrush);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(104, 112, 124));
TextOutW(hdc, g_searchOuterRect.left + 14, g_searchOuterRect.top - kSearchTitleHeight,
L"Rounded Single-Line Text Box", 29);
EndPaint(hwnd, &ps);
}
LRESULT CALLBACK DemoWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
g_editorTextBox = new TsfD2DTextBox();
g_searchTextBox = new TsfD2DTextBox();
RECT emptyBounds = MakeRect(0, 0, 0, 0);
g_editorTextBox->Create(GetModuleHandle(NULL), hwnd, emptyBounds);
g_searchTextBox->Create(GetModuleHandle(NULL), hwnd, emptyBounds);
LayoutControls(hwnd);
return 0;
}
case WM_SHOWWINDOW:
if (wParam && g_editorTextBox && g_editorTextBox->GetHwnd())
{
SetFocus(g_editorTextBox->GetHwnd());
}
return 0;
case WM_SETFOCUS:
if (g_editorTextBox && g_editorTextBox->GetHwnd())
{
SetFocus(g_editorTextBox->GetHwnd());
}
return 0;
case WM_SIZE:
LayoutControls(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
return 0;
case WM_PAINT:
PaintDemoBackground(hwnd);
return 0;
case WM_DESTROY:
delete g_editorTextBox;
delete g_searchTextBox;
g_editorTextBox = nullptr;
g_searchTextBox = nullptr;
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
} // namespace
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow)
{
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
TsfD2DTextBox::RegisterClass(hInstance);
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = DemoWndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcex.lpszClassName = kWindowClass;
RegisterClassEx(&wcex);
HWND hwnd = CreateWindow(kWindowClass, kWindowTitle, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
960, 640, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
return static_cast<int>(msg.wParam);
}
@@ -0,0 +1,22 @@
#pragma once
#include <Windows.h>
class CTextInputCtrl;
class TsfD2DTextBox
{
public:
TsfD2DTextBox();
~TsfD2DTextBox();
static ATOM RegisterClass(HINSTANCE hInstance);
HWND Create(HINSTANCE hInstance, HWND hwndParent, const RECT &bounds);
void Move(int x, int y, int width, int height);
HWND GetHwnd() const;
void SetFont(HWND hwndOwner);
private:
CTextInputCtrl *_impl;
};
@@ -0,0 +1,23 @@
param(
[ValidateSet('Debug', 'Release')]
[string]$Configuration = 'Debug'
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
Set-Location $projectRoot
Write-Host "[TsfEditControl] Configuring project..." -ForegroundColor Cyan
cmake --preset vcpkg
if ($LASTEXITCODE -ne 0) {
throw "cmake configure failed with exit code $LASTEXITCODE"
}
$buildPreset = $Configuration.ToLowerInvariant()
Write-Host "[TsfEditControl] Building ($Configuration)..." -ForegroundColor Cyan
cmake --build --preset $buildPreset
if ($LASTEXITCODE -ne 0) {
throw "cmake build failed with exit code $LASTEXITCODE"
}
Write-Host "[TsfEditControl] Build completed." -ForegroundColor Green
@@ -0,0 +1,16 @@
param(
[ValidateSet('Debug', 'Release')]
[string]$Configuration = 'Debug'
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
Set-Location $projectRoot
Write-Host "[TsfEditControl] Configuring project only..." -ForegroundColor Cyan
cmake --preset vcpkg
if ($LASTEXITCODE -ne 0) {
throw "cmake configure failed with exit code $LASTEXITCODE"
}
Write-Host "[TsfEditControl] Configure completed for $Configuration." -ForegroundColor Green
@@ -0,0 +1,22 @@
param(
[ValidateSet('Debug', 'Release')]
[string]$Configuration = 'Debug'
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
Set-Location $projectRoot
$buildScript = Join-Path $PSScriptRoot 'build.ps1'
& $buildScript -Configuration $Configuration
if ($LASTEXITCODE -ne 0) {
throw "build step failed with exit code $LASTEXITCODE"
}
$exePath = Join-Path $projectRoot "build\bin\$Configuration\TsfEditControlDemo.exe"
if (-not (Test-Path $exePath)) {
throw "demo executable not found: $exePath"
}
Write-Host "[TsfEditControl] Launching demo..." -ForegroundColor Cyan
Start-Process -FilePath $exePath
@@ -0,0 +1,211 @@
#include "DisplayAttribute.h"
#include <Windows.h>
#include <msctf.h>
ITfDisplayAttributeMgr *g_pdam = NULL;
void PrintColor(const TF_DA_COLOR &color)
{
if (color.type == TF_CT_COLORREF)
{
COLORREF cr = color.cr;
UNREFERENCED_PARAMETER(cr);
}
}
void PrintDisplayAttribute(const TF_DISPLAYATTRIBUTE &da)
{
UNREFERENCED_PARAMETER(da);
}
CDispAttrProps *GetDispAttrProps()
{
IEnumGUID *pEnumProp = NULL;
CDispAttrProps *pProps = NULL;
ITfCategoryMgr *pcat;
HRESULT hr = E_FAIL;
if (SUCCEEDED(hr = CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr,
(void **)&pcat)))
{
hr = pcat->EnumItemsInCategory(GUID_TFCAT_DISPLAYATTRIBUTEPROPERTY, &pEnumProp);
pcat->Release();
}
if (SUCCEEDED(hr) && pEnumProp)
{
GUID guidProp;
pProps = new CDispAttrProps;
pProps->Add(GUID_PROP_ATTRIBUTE);
while (pEnumProp->Next(1, &guidProp, NULL) == S_OK)
{
if (!IsEqualGUID(guidProp, GUID_PROP_ATTRIBUTE))
pProps->Add(guidProp);
}
}
if (pEnumProp)
pEnumProp->Release();
return pProps;
}
HRESULT InitDisplayAttrbute()
{
if (g_pdam)
g_pdam->Release();
g_pdam = NULL;
if (FAILED(CoCreateInstance(CLSID_TF_DisplayAttributeMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr,
(void **)&g_pdam)))
{
return E_FAIL;
}
return S_OK;
}
HRESULT UninitDisplayAttrbute()
{
if (g_pdam)
{
g_pdam->Release();
g_pdam = NULL;
}
return S_OK;
}
HRESULT GetDisplayAttributeTrackPropertyRange(TfEditCookie ec, ITfContext *pic, ITfRange *pRange,
ITfReadOnlyProperty **ppProp, CDispAttrProps *pDispAttrProps)
{
ITfReadOnlyProperty *pProp = NULL;
HRESULT hr = E_FAIL;
GUID *pguidProp = NULL;
const GUID **ppguidProp;
ULONG ulNumProp = 0;
ULONG i;
if (!pDispAttrProps)
goto Exit;
pguidProp = pDispAttrProps->GetPropTable();
if (!pguidProp)
goto Exit;
ulNumProp = pDispAttrProps->Count();
if (!ulNumProp)
goto Exit;
if ((ppguidProp = (const GUID **)LocalAlloc(LMEM_ZEROINIT, sizeof(GUID *) * ulNumProp)) == NULL)
return E_OUTOFMEMORY;
for (i = 0; i < ulNumProp; i++)
{
ppguidProp[i] = pguidProp++;
}
if (SUCCEEDED(hr = pic->TrackProperties(ppguidProp, ulNumProp, 0, NULL, &pProp)))
{
*ppProp = pProp;
}
LocalFree(ppguidProp);
Exit:
return hr;
}
HRESULT GetDisplayAttributeData(TfEditCookie ec, ITfReadOnlyProperty *pProp, ITfRange *pRange, TF_DISPLAYATTRIBUTE *pda,
TfGuidAtom *pguid)
{
VARIANT var;
IEnumTfPropertyValue *pEnumPropertyVal = NULL;
TF_PROPERTYVAL tfPropVal = {};
GUID guid;
TfGuidAtom gaVal = TF_INVALID_GUIDATOM;
ITfDisplayAttributeInfo *pDAI = NULL;
HRESULT hr = E_FAIL;
VariantInit(&var);
ITfCategoryMgr *pcat = NULL;
if (FAILED(hr = CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr,
(void **)&pcat)))
{
return hr;
}
hr = S_FALSE;
if (SUCCEEDED(pProp->GetValue(ec, pRange, &var)))
{
if ((var.vt == VT_UNKNOWN) && var.punkVal &&
SUCCEEDED(var.punkVal->QueryInterface(IID_IEnumTfPropertyValue, (void **)&pEnumPropertyVal)))
{
while (pEnumPropertyVal->Next(1, &tfPropVal, NULL) == S_OK)
{
if (tfPropVal.varValue.vt == VT_EMPTY)
continue;
if ((tfPropVal.varValue.vt != VT_I4) && (tfPropVal.varValue.vt != VT_UI4))
{
VariantClear(&tfPropVal.varValue);
continue;
}
gaVal = (TfGuidAtom)tfPropVal.varValue.lVal;
pcat->GetGUID(gaVal, &guid);
if ((g_pdam != NULL) && SUCCEEDED(g_pdam->GetDisplayAttributeInfo(guid, &pDAI, NULL)))
{
if (pda)
{
pDAI->GetAttributeInfo(pda);
PrintDisplayAttribute(*pda);
}
if (pguid)
{
*pguid = gaVal;
}
pDAI->Release();
hr = S_OK;
VariantClear(&tfPropVal.varValue);
break;
}
VariantClear(&tfPropVal.varValue);
}
pEnumPropertyVal->Release();
}
else if ((var.vt == VT_I4) || (var.vt == VT_UI4))
{
gaVal = (TfGuidAtom)var.lVal;
if (SUCCEEDED(pcat->GetGUID(gaVal, &guid)) && (g_pdam != NULL) &&
SUCCEEDED(g_pdam->GetDisplayAttributeInfo(guid, &pDAI, NULL)))
{
if (pda)
{
pDAI->GetAttributeInfo(pda);
PrintDisplayAttribute(*pda);
}
if (pguid)
{
*pguid = gaVal;
}
pDAI->Release();
hr = S_OK;
}
}
VariantClear(&var);
}
pcat->Release();
return hr;
}
@@ -0,0 +1,78 @@
//
// dispattr.h
//
#pragma once
#include "ctffunc.h"
#define MAX_DISPATTRGUID 10
class CDispAttrProps
{
public:
CDispAttrProps()
{
for (int i = 0; i < MAX_DISPATTRGUID; i++)
{
_guid[i] = GUID_NULL;
_rgpguid[i] = &_guid[i];
}
_nCount = 0;
}
void Add(REFGUID rguid)
{
if (!FindGuid(rguid) && _nCount < MAX_DISPATTRGUID)
{
_guid[_nCount] = rguid;
_nCount++;
}
}
BOOL FindGuid(REFGUID rguid)
{
int i;
for (i = 0; i < _nCount; i++)
{
if (IsEqualGUID(_guid[i], rguid))
{
return TRUE;
}
}
return FALSE;
}
int Count()
{
return _nCount;
}
GUID *GetPropTable()
{
return _guid;
}
const GUID **GetPropTablePointer()
{
return _rgpguid;
}
private:
GUID _guid[MAX_DISPATTRGUID];
const GUID *_rgpguid[MAX_DISPATTRGUID];
int _nCount;
};
CDispAttrProps *GetDispAttrProps();
HRESULT InitDisplayAttrbute();
HRESULT UninitDisplayAttrbute();
HRESULT GetDisplayAttributeTrackPropertyRange(TfEditCookie ec, ITfContext *pic, ITfRange *pRange,
ITfReadOnlyProperty **ppProp, CDispAttrProps *pDispAttrProps);
HRESULT GetDisplayAttributeData(TfEditCookie ec, ITfReadOnlyProperty *pProp, ITfRange *pRange, TF_DISPLAYATTRIBUTE *pda,
TfClientId *pguid);
HRESULT GetAttributeColor(TF_DA_COLOR *pdac, COLORREF *pcr);
HRESULT SetAttributeColor(TF_DA_COLOR *pdac, COLORREF cr);
HRESULT SetAttributeSysColor(TF_DA_COLOR *pdac, int nIndex);
HRESULT ClearAttributeColor(TF_DA_COLOR *pdac);
@@ -0,0 +1,98 @@
#include "TextContainer.h"
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextContainer::InsertText(int nPos, const WCHAR *psz, UINT nCnt)
{
if (!EnsureBuffer(_nTextSize + nCnt))
{
return FALSE;
}
memmove(_psz + nPos + nCnt, _psz + nPos, (_nTextSize - nPos) * sizeof(WCHAR));
memcpy(_psz + nPos, psz, nCnt * sizeof(WCHAR));
_nTextSize += nCnt;
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextContainer::RemoveText(int nPos, UINT nCnt)
{
if (!nCnt)
return TRUE;
if (nPos + nCnt - 1 > _nTextSize)
nCnt = _nTextSize - nPos;
memmove(_psz + nPos, _psz + nPos + nCnt, (_nTextSize - nPos - nCnt) * sizeof(WCHAR));
_nTextSize -= nCnt;
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextContainer::GetText(int nPos, WCHAR *psz, UINT nCnt)
{
if (!nCnt)
return FALSE;
if (nPos + nCnt - 1 > _nTextSize)
nCnt = _nTextSize - nPos;
memcpy(psz, _psz + nPos, nCnt * sizeof(WCHAR));
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextContainer::EnsureBuffer(UINT nNewTextSize)
{
if (!nNewTextSize)
{
if (_psz)
LocalFree(_psz);
_psz = NULL;
_nBufferSize = 0;
_nTextSize = 0;
return FALSE;
}
if (nNewTextSize <= _nTextSize)
goto Exit;
if (_psz)
{
void *pvNew = LocalReAlloc(_psz, nNewTextSize * sizeof(WCHAR), LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!pvNew)
return FALSE;
_psz = (WCHAR *)pvNew;
}
else
{
_psz = (WCHAR *)LocalAlloc(LPTR, nNewTextSize * sizeof(WCHAR));
if (!_psz)
return FALSE;
}
_nBufferSize = nNewTextSize;
Exit:
return TRUE;
}
@@ -0,0 +1,46 @@
#pragma once
#include <Windows.h>
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
class CTextContainer
{
public:
CTextContainer()
{
_psz = NULL;
_nBufferSize = 0;
_nTextSize = 0;
}
~CTextContainer()
{
if (_psz)
LocalFree(_psz);
}
BOOL InsertText(int nPos, const WCHAR *psz, UINT nCnt);
BOOL RemoveText(int nPos, UINT nCnt);
BOOL GetText(int nPos, WCHAR *psz, UINT nBuffSize);
UINT GetTextLength()
{
return _nTextSize;
}
const WCHAR *GetTextBuffer()
{
return _psz;
}
private:
BOOL EnsureBuffer(UINT nNewTextSize);
WCHAR *_psz;
UINT _nBufferSize;
UINT _nTextSize;
};
@@ -0,0 +1,206 @@
#include "TextEditSink.h"
#include "TextEditor.h"
#include "DisplayAttribute.h"
//////////////////////////////////////////////////////////////////////////////
//
// CTextEditSink
//
//////////////////////////////////////////////////////////////////////////////
//+---------------------------------------------------------------------------
//
// IUnknown
//
//----------------------------------------------------------------------------
STDAPI CTextEditSink::QueryInterface(REFIID riid, void **ppvObj)
{
*ppvObj = NULL;
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITfTextEditSink))
{
*ppvObj = (ITfTextEditSink *)this;
}
if (*ppvObj)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDAPI_(ULONG) CTextEditSink::AddRef()
{
return ++_cRef;
}
STDAPI_(ULONG) CTextEditSink::Release()
{
long cr;
cr = --_cRef;
if (cr == 0)
{
delete this;
}
return cr;
}
//+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------
CTextEditSink::CTextEditSink(CTextEditor *pEditor)
{
_cRef = 1;
_dwEditCookie = TES_INVALID_COOKIE;
_pEditor = pEditor;
}
//+---------------------------------------------------------------------------
//
// EndEdit
//
//----------------------------------------------------------------------------
STDAPI CTextEditSink::OnEndEdit(ITfContext *pic, TfEditCookie ecReadOnly, ITfEditRecord *pEditRecord)
{
CDispAttrProps *pDispAttrProps = GetDispAttrProps();
if (pDispAttrProps)
{
IEnumTfRanges *pEnum;
if (SUCCEEDED(pEditRecord->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, pDispAttrProps->GetPropTablePointer(),
pDispAttrProps->Count(), &pEnum)) &&
pEnum)
{
ITfRange *pRange;
if (pEnum->Next(1, &pRange, NULL) == S_OK)
{
// We check if there is a range to be changed.
pRange->Release();
_pEditor->ClearCompositionRenderInfo();
// We read the display attribute for entire range.
// It could be optimized by filtering the only delta with ITfEditRecord interface.
ITfRange *pRangeEntire = NULL;
ITfRange *pRangeEnd = NULL;
if (SUCCEEDED(pic->GetStart(ecReadOnly, &pRangeEntire)) &&
SUCCEEDED(pic->GetEnd(ecReadOnly, &pRangeEnd)) &&
SUCCEEDED(pRangeEntire->ShiftEndToRange(ecReadOnly, pRangeEnd, TF_ANCHOR_END)))
{
IEnumTfRanges *pEnumRanges;
ITfReadOnlyProperty *pProp = NULL;
GetDisplayAttributeTrackPropertyRange(ecReadOnly, pic, pRangeEntire, &pProp, pDispAttrProps);
if (SUCCEEDED(pProp->EnumRanges(ecReadOnly, &pEnumRanges, pRangeEntire)))
{
while (pEnumRanges->Next(1, &pRange, NULL) == S_OK)
{
TF_DISPLAYATTRIBUTE da;
TfGuidAtom guid;
if (GetDisplayAttributeData(ecReadOnly, pProp, pRange, &da, &guid) == S_OK)
{
ITfRangeACP *pRangeACP;
if (pRange->QueryInterface(IID_ITfRangeACP, (void **)&pRangeACP) == S_OK)
{
LONG nStart;
LONG nEnd;
pRangeACP->GetExtent(&nStart, &nEnd);
_pEditor->AddCompositionRenderInfo(nStart, nStart + nEnd, &da);
pRangeACP->Release();
}
}
}
}
}
if (pRangeEntire)
pRangeEntire->Release();
if (pRangeEnd)
pRangeEnd->Release();
}
pEnum->Release();
}
delete pDispAttrProps;
}
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CTextEditSink::Advise
//
//----------------------------------------------------------------------------
HRESULT CTextEditSink::_Advise(ITfContext *pic)
{
HRESULT hr;
ITfSource *source = NULL;
_pic = NULL;
hr = E_FAIL;
if (FAILED(pic->QueryInterface(IID_ITfSource, (void **)&source)))
goto Exit;
if (FAILED(source->AdviseSink(IID_ITfTextEditSink, (ITfTextEditSink *)this, &_dwEditCookie)))
goto Exit;
_pic = pic;
_pic->AddRef();
hr = S_OK;
Exit:
if (source)
source->Release();
return hr;
}
//+---------------------------------------------------------------------------
//
// CTextEditSink::Unadvise
//
//----------------------------------------------------------------------------
HRESULT CTextEditSink::_Unadvise()
{
HRESULT hr;
ITfSource *source = NULL;
hr = E_FAIL;
if (_pic == NULL)
goto Exit;
if (FAILED(_pic->QueryInterface(IID_ITfSource, (void **)&source)))
goto Exit;
if (FAILED(source->UnadviseSink(_dwEditCookie)))
goto Exit;
hr = S_OK;
Exit:
if (source)
source->Release();
if (_pic)
{
_pic->Release();
_pic = NULL;
}
return hr;
}
@@ -0,0 +1,35 @@
#pragma once
#include "private.h"
#define TES_INVALID_COOKIE ((DWORD)(-1))
class CTextEditor;
class CTextEditSink : public ITfTextEditSink
{
public:
CTextEditSink(CTextEditor *pEditor);
virtual ~CTextEditSink() {};
//
// IUnknown methods
//
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
//
// ITfTextEditSink
//
STDMETHODIMP OnEndEdit(ITfContext *pic, TfEditCookie ecReadOnly, ITfEditRecord *pEditRecord);
HRESULT _Advise(ITfContext *pic);
HRESULT _Unadvise();
private:
long _cRef;
ITfContext *_pic;
DWORD _dwEditCookie;
CTextEditor *_pEditor;
};
@@ -0,0 +1,486 @@
#include <string>
#include "TextEditor.h"
extern ITfThreadMgr *g_pThreadMgr;
extern TfClientId g_TfClientId;
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::MoveSelection(UINT nSelStart, UINT nSelEnd)
{
UINT nTextLength = GetTextLength();
if (nSelStart >= nTextLength)
nSelStart = nTextLength;
if (nSelEnd >= nTextLength)
nSelEnd = nTextLength;
_nSelStart = nSelStart;
_nSelEnd = nSelEnd;
_layout.ResetCaretBlink();
_pTextStore->OnSelectionChange();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::MoveSelectionNext()
{
UINT nTextLength = GetTextLength();
if (_nSelEnd < nTextLength)
_nSelEnd++;
_nSelStart = _nSelEnd;
_layout.ResetCaretBlink();
_pTextStore->OnSelectionChange();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::MoveSelectionPrev()
{
if (_nSelStart > 0)
_nSelStart--;
_nSelEnd = _nSelStart;
_layout.ResetCaretBlink();
_pTextStore->OnSelectionChange();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::MoveSelectionAtPoint(POINT pt)
{
BOOL bRet = FALSE;
UINT nSel = _layout.CharPosFromPoint(pt);
if (nSel != (UINT)-1)
{
MoveSelection(nSel, nSel);
bRet = TRUE;
}
return bRet;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::MoveSelectionUpDown(BOOL bUp)
{
RECT rc;
if (!_layout.RectFromCharPos(_nSelStart, &rc))
return FALSE;
POINT pt;
pt.x = rc.left;
if (bUp)
{
pt.y = rc.top - ((rc.bottom - rc.top) / 2);
if (pt.y < 0)
return FALSE;
}
else
{
pt.y = rc.bottom + ((rc.bottom - rc.top) / 2);
}
return MoveSelectionAtPoint(pt);
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::MoveSelectionToLineFirstEnd(BOOL bFirst)
{
BOOL bRet = FALSE;
UINT nSel;
if (bFirst)
{
nSel = _layout.FineFirstEndCharPosInLine(_nSelStart, TRUE);
}
else
{
nSel = _layout.FineFirstEndCharPosInLine(_nSelEnd, FALSE);
}
if (nSel != (UINT)-1)
{
MoveSelection(nSel, nSel);
bRet = TRUE;
}
return bRet;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::InsertAtSelection(LPCWSTR psz)
{
LONG lOldSelEnd = _nSelEnd;
if (!RemoveText(_nSelStart, _nSelEnd - _nSelStart))
return FALSE;
if (!InsertText(_nSelStart, psz, lstrlen(psz)))
return FALSE;
_nSelStart += lstrlen(psz);
_nSelEnd = _nSelStart;
_layout.ResetCaretBlink();
UpdateLayout();
_pTextStore->OnTextChange(_nSelStart, lOldSelEnd, _nSelEnd);
_pTextStore->OnSelectionChange();
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::DeleteAtSelection(BOOL fBack)
{
// Check if we are in composition state
if (_pTextStore->GetCurrentCompositionView() && GetTextLength() == 0)
{
TerminateCompositionString();
return TRUE;
}
if (!fBack && (_nSelEnd < GetTextLength()))
{
if (!RemoveText(_nSelEnd, 1))
return FALSE;
_layout.ResetCaretBlink();
UpdateLayout();
_pTextStore->OnTextChange(_nSelEnd, _nSelEnd + 1, _nSelEnd);
}
if (fBack && (_nSelStart > 0))
{
if (!RemoveText(_nSelStart - 1, 1))
return FALSE;
_nSelStart--;
_nSelEnd = _nSelStart;
_layout.ResetCaretBlink();
UpdateLayout();
_pTextStore->OnTextChange(_nSelStart, _nSelStart + 1, _nSelStart);
_pTextStore->OnSelectionChange();
}
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::DeleteSelection()
{
ULONG nSelOldEnd = _nSelEnd;
RemoveText(_nSelStart, _nSelEnd - _nSelStart);
_nSelEnd = _nSelStart;
_layout.ResetCaretBlink();
UpdateLayout();
_pTextStore->OnTextChange(_nSelStart, nSelOldEnd, _nSelStart);
_pTextStore->OnSelectionChange();
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::InitializeRenderResources(IDWriteFactory *pDWriteFactory)
{
return _layout.Initialize(pDWriteFactory);
}
void CTextEditor::SetFont(const LOGFONT *plf)
{
if (!plf)
{
return;
}
_lfCurrentFont = *plf;
_fHasFont = TRUE;
UpdateLayout();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::Render(ID2D1HwndRenderTarget *pRenderTarget)
{
if (!_fHasFont)
{
return;
}
UpdateLayout();
_layout.Render(pRenderTarget, GetTextBuffer(), GetTextLength(), _nSelStart, _nSelEnd, _pCompositionRenderInfo,
_nCompositionRenderInfo);
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::UpdateLayout()
{
if (!_hwnd || !_fHasFont)
{
return;
}
RECT rcClient = {};
GetClientRect(_hwnd, &rcClient);
const FLOAT dpi = static_cast<FLOAT>(GetDpiForWindow(_hwnd));
_layout.Layout(GetTextBuffer(), GetTextLength(), &_lfCurrentFont, static_cast<FLOAT>(max(rcClient.right, 1L)), dpi,
dpi);
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::InitTSF()
{
_pTextStore = new CTextStore(this);
if (!_pTextStore)
{
return FALSE;
}
if (FAILED(g_pThreadMgr->CreateDocumentMgr(&_pDocumentMgr)))
{
return FALSE;
}
if (FAILED(_pDocumentMgr->CreateContext(g_TfClientId, 0, (ITextStoreACP *)_pTextStore, &_pInputContext,
&_ecTextStore)))
{
return FALSE;
}
if (FAILED(_pDocumentMgr->Push(_pInputContext)))
{
return FALSE;
}
ITfDocumentMgr *pDocumentMgrPrev;
g_pThreadMgr->AssociateFocus(_hwnd, _pDocumentMgr, &pDocumentMgrPrev);
if (pDocumentMgrPrev)
pDocumentMgrPrev->Release();
_pTextEditSink = new CTextEditSink(this);
if (!_pTextEditSink)
{
return FALSE;
}
_pTextEditSink->_Advise(_pInputContext);
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::UninitTSF()
{
if (_pTextEditSink)
{
_pTextEditSink->_Unadvise();
_pTextEditSink->Release();
_pTextEditSink = NULL;
}
if (_pDocumentMgr)
{
_pDocumentMgr->Pop(TF_POPF_ALL);
}
if (_pInputContext)
{
_pInputContext->Release();
_pInputContext = NULL;
}
if (_pDocumentMgr)
{
_pDocumentMgr->Release();
_pDocumentMgr = NULL;
}
if (_pTextStore)
{
_pTextStore->Release();
_pTextStore = NULL;
}
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::SetFocusDocumentMgr()
{
if (_pDocumentMgr)
{
// g_pThreadMgr->SetFocus(_pDocumentMgr);
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::ClearCompositionRenderInfo()
{
if (_pCompositionRenderInfo)
{
LocalFree(_pCompositionRenderInfo);
_pCompositionRenderInfo = NULL;
_nCompositionRenderInfo = 0;
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextEditor::AddCompositionRenderInfo(int nStart, int nEnd, TF_DISPLAYATTRIBUTE *pda)
{
if (_pCompositionRenderInfo)
{
void *pvNew =
LocalReAlloc(_pCompositionRenderInfo, (_nCompositionRenderInfo + 1) * sizeof(COMPOSITIONRENDERINFO),
LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!pvNew)
return FALSE;
_pCompositionRenderInfo = (COMPOSITIONRENDERINFO *)pvNew;
}
else
{
_pCompositionRenderInfo =
(COMPOSITIONRENDERINFO *)LocalAlloc(LPTR, (_nCompositionRenderInfo + 1) * sizeof(COMPOSITIONRENDERINFO));
if (!_pCompositionRenderInfo)
return FALSE;
}
_pCompositionRenderInfo[_nCompositionRenderInfo].nStart = nStart;
_pCompositionRenderInfo[_nCompositionRenderInfo].nEnd = nEnd;
_pCompositionRenderInfo[_nCompositionRenderInfo].da = *pda;
_nCompositionRenderInfo++;
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::TerminateCompositionString()
{
if (_pTextStore->GetCurrentCompositionView())
{
ITfContextOwnerCompositionServices *pCompositionServices;
if (_pInputContext->QueryInterface(IID_ITfContextOwnerCompositionServices, (void **)&pCompositionServices) ==
S_OK)
{
pCompositionServices->TerminateComposition(_pTextStore->GetCurrentCompositionView());
pCompositionServices->Release();
}
// Clear composition render info
ClearCompositionRenderInfo();
InvalidateRect();
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextEditor::AleartMouseSink(POINT pt, DWORD dwBtnState, BOOL *pbEaten)
{
BOOL bRet = FALSE;
UINT nSel = _layout.ExactCharPosFromPoint(pt);
if (nSel == (UINT)-1)
{
return;
}
RECT rc;
if (!_layout.RectFromCharPos(nSel, &rc))
{
return;
}
int nPos = (pt.x - rc.left) * 4 / (rc.right - rc.left) + 2;
UINT uEdge = nSel + (nPos / 4);
UINT uQuadrant = nPos % 4;
_pTextStore->OnMouseEvent(uEdge, uQuadrant, dwBtnState, pbEaten);
}
@@ -0,0 +1,131 @@
#pragma once
#include "TextLayout.h"
#include "TextContainer.h"
#include "TextStore.h"
#include "TextEditSink.h"
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
class CTextEditor : public CTextContainer
{
public:
CTextEditor()
{
_nSelStart = 0;
_nSelEnd = 0;
_pTextStore = NULL;
_pDocumentMgr = NULL;
ZeroMemory(&_lfCurrentFont, sizeof(_lfCurrentFont));
_fHasFont = FALSE;
_pCompositionRenderInfo = NULL;
_nCompositionRenderInfo = 0;
}
~CTextEditor()
{
if (_pTextStore)
{
_pTextStore->Release();
_pTextStore = NULL;
}
if (_pDocumentMgr)
{
_pDocumentMgr->Release();
_pDocumentMgr = NULL;
}
}
void MoveSelection(UINT nSelStart, UINT nSelEnd);
BOOL MoveSelectionAtPoint(POINT pt);
BOOL InsertAtSelection(LPCWSTR psz);
BOOL DeleteAtSelection(BOOL fBack);
BOOL DeleteSelection();
void MoveSelectionNext();
void MoveSelectionPrev();
BOOL MoveSelectionUpDown(BOOL bUp);
BOOL MoveSelectionToLineFirstEnd(BOOL bFirst);
BOOL InitializeRenderResources(IDWriteFactory *pDWriteFactory);
void SetFont(const LOGFONT *plf);
void Render(ID2D1HwndRenderTarget *pRenderTarget);
void UpdateLayout();
UINT GetSelectionStart()
{
return _nSelStart;
}
UINT GetSelectionEnd()
{
return _nSelEnd;
}
void BlinkCaret()
{
_layout.ToggleCaretBlink();
}
void SetInterimCaret(BOOL fSet)
{
_layout.SetInterimCaret(fSet, _nSelStart);
}
void SetWnd(HWND hwnd)
{
_hwnd = hwnd;
}
HWND GetWnd()
{
return _hwnd;
}
BOOL InitTSF();
BOOL UninitTSF();
void SetFocusDocumentMgr();
void InvalidateRect()
{
::InvalidateRect(_hwnd, NULL, FALSE);
}
int GetLineHeight()
{
return _layout.GetLineHeight();
}
CTextLayout *GetLayout()
{
return &_layout;
}
void ClearCompositionRenderInfo();
BOOL AddCompositionRenderInfo(int nStart, int nEnd, TF_DISPLAYATTRIBUTE *pda);
void TerminateCompositionString();
void AleartMouseSink(POINT pt, DWORD dwBtnState, BOOL *pbEaten);
private:
UINT _nSelStart;
UINT _nSelEnd;
HWND _hwnd;
CTextLayout _layout;
LOGFONT _lfCurrentFont;
BOOL _fHasFont;
CTextStore *_pTextStore;
ITfDocumentMgr *_pDocumentMgr;
ITfContext *_pInputContext;
TfEditCookie _ecTextStore;
CTextEditSink *_pTextEditSink;
COMPOSITIONRENDERINFO *_pCompositionRenderInfo;
int _nCompositionRenderInfo;
};
@@ -0,0 +1,643 @@
#include "TextInputCtrl.h"
#include "commdlg.h"
#include "TsfTextServices.h"
#define TEXTINPUTCTRL_CLASSNAME TEXT("TextInputCtrlClassName")
#define TIMERID_CARET 0x100
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
ATOM CTextInputCtrl::RegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)s_WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_IBEAM);
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = TEXTINPUTCTRL_CLASSNAME;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
HWND CTextInputCtrl::Create(HWND hwndParent)
{
_hwnd = CreateWindow(TEXTINPUTCTRL_CLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwndParent, NULL, g_hInst,
this);
return _hwnd;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
LRESULT CALLBACK CTextInputCtrl::s_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
CTextInputCtrl *ptic;
switch (message)
{
case WM_CREATE:
SetThis(hwnd, ((CREATESTRUCT *)lParam)->lpCreateParams);
SetTimer(hwnd, TIMERID_CARET, GetCaretBlinkTime(), NULL);
GetThis(hwnd)->OnCreate(hwnd, wParam, lParam);
break;
case WM_DESTROY:
ptic = GetThis(hwnd);
if (ptic)
{
ptic->OnDestroy();
}
break;
case WM_SETFOCUS:
ptic = GetThis(hwnd);
if (ptic)
{
ptic->OnSetFocus(wParam, lParam);
}
break;
case WM_PAINT:
ptic = GetThis(hwnd);
BeginPaint(hwnd, &ps);
if (ptic)
ptic->OnPaint();
EndPaint(hwnd, &ps);
break;
case WM_ERASEBKGND:
return 1;
case WM_SIZE:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnSize();
break;
case WM_KEYDOWN:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnKeyDown(wParam, lParam);
break;
case WM_LBUTTONDOWN:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnLButtonDown(wParam, lParam);
break;
case WM_LBUTTONUP:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnLButtonUp(wParam, lParam);
break;
case WM_RBUTTONDOWN:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnRButtonDown(wParam, lParam);
break;
case WM_RBUTTONUP:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnRButtonUp(wParam, lParam);
break;
case WM_MOUSEMOVE:
ptic = GetThis(hwnd);
if (ptic)
ptic->OnMouseMove(wParam, lParam);
break;
case WM_IME_COMPOSITION:
if (lParam & GCS_RESULTSTR)
{
HIMC himc = ImmGetContext(hwnd);
if (himc)
{
LONG nSize = ImmGetCompositionString(himc, GCS_RESULTSTR, NULL, 0);
if (nSize)
{
LPWSTR psz = (LPWSTR)LocalAlloc(LPTR, nSize + sizeof(WCHAR));
if (psz)
{
ImmGetCompositionString(himc, GCS_RESULTSTR, psz, nSize);
LocalFree(psz);
}
}
}
ImmReleaseContext(hwnd, himc);
//
// If we don't want to receive WM_IME_CHAR or WM_CHAR with
// this result string, we should not call DefWindowProc()
// with GCS_RESULTSTR, GCS_RESULTREADSTR, GCS_RESULTCLAUSE and
// GCS_RESULTREADCLAUSE flags.
//
// lParam &= ~(GCS_RESULTCLAUSE |
// GCS_RESULTREADCLAUSE |
// GCS_RESULTREADSTR |
// GCS_RESULTSTR);
// if (!lParam)
// break;
//
}
return DefWindowProc(hwnd, message, wParam, lParam);
case WM_IME_CHAR:
//
// wParam is a character of the result string.
// if we don't want to receive WM_CHAR message for this character,
// we should not call DefWindowProc().
//
return DefWindowProc(hwnd, message, wParam, lParam);
case WM_CHAR:
//
// wParam is a character of the result string.
//
switch ((WCHAR)wParam)
{
case 0x08:
case 0x0a:
return 0;
default:
break;
}
ptic = GetThis(hwnd);
if (ptic)
{
WCHAR wc[2];
wc[0] = (WCHAR)wParam;
wc[1] = L'\0';
ptic->_editor.InsertAtSelection(wc);
InvalidateRect(hwnd, NULL, FALSE);
}
break;
case WM_TIMER:
if (wParam == TIMERID_CARET)
{
ptic = GetThis(hwnd);
if (ptic)
{
ptic->_editor.BlinkCaret();
InvalidateRect(hwnd, NULL, FALSE);
}
}
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextInputCtrl::CreateDeviceResources()
{
if (_pRenderTarget)
{
return TRUE;
}
RECT rc = {};
GetClientRect(_hwnd, &rc);
if (!_pD2DFactory && FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &_pD2DFactory)))
{
return FALSE;
}
if (!_pDWriteFactory &&
FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
reinterpret_cast<IUnknown **>(&_pDWriteFactory))))
{
return FALSE;
}
if (!_editor.InitializeRenderResources(_pDWriteFactory))
{
return FALSE;
}
const D2D1_SIZE_U size = D2D1::SizeU(max(rc.right, 1L), max(rc.bottom, 1L));
if (FAILED(_pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(_hwnd, size, D2D1_PRESENT_OPTIONS_IMMEDIATELY), &_pRenderTarget)))
{
return FALSE;
}
const FLOAT dpi = static_cast<FLOAT>(GetDpiForWindow(_hwnd));
_pRenderTarget->SetDpi(dpi, dpi);
return TRUE;
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::DiscardDeviceResources()
{
if (_pRenderTarget)
{
_pRenderTarget->Release();
_pRenderTarget = NULL;
}
if (_pDWriteFactory)
{
_pDWriteFactory->Release();
_pDWriteFactory = NULL;
}
if (_pD2DFactory)
{
_pD2DFactory->Release();
_pD2DFactory = NULL;
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnPaint()
{
if (!CreateDeviceResources())
{
return;
}
const FLOAT dpi = static_cast<FLOAT>(GetDpiForWindow(_hwnd));
_pRenderTarget->SetDpi(dpi, dpi);
_pRenderTarget->BeginDraw();
_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
_editor.Render(_pRenderTarget);
const HRESULT hr = _pRenderTarget->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources();
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnSize()
{
if (_pRenderTarget)
{
RECT rc = {};
GetClientRect(_hwnd, &rc);
const FLOAT dpi = static_cast<FLOAT>(GetDpiForWindow(_hwnd));
_pRenderTarget->SetDpi(dpi, dpi);
_pRenderTarget->Resize(D2D1::SizeU(max(rc.right, 1L), max(rc.bottom, 1L)));
}
_editor.UpdateLayout();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnKeyDown(WPARAM wParam, LPARAM lParam)
{
UINT nSelStart;
UINT nSelEnd;
switch (0xff & wParam)
{
case VK_LEFT:
if (GetKeyState(VK_SHIFT) & 0x80)
{
nSelStart = _editor.GetSelectionStart();
nSelEnd = _editor.GetSelectionEnd();
if (nSelStart > 0)
{
_editor.MoveSelection(nSelStart - 1, nSelEnd);
}
}
else
{
_editor.MoveSelectionPrev();
}
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_RIGHT:
if (GetKeyState(VK_SHIFT) & 0x80)
{
nSelStart = _editor.GetSelectionStart();
nSelEnd = _editor.GetSelectionEnd();
_editor.MoveSelection(nSelStart, nSelEnd + 1);
}
else
{
_editor.MoveSelectionNext();
}
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_UP:
_editor.MoveSelectionUpDown(TRUE);
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_DOWN:
_editor.MoveSelectionUpDown(FALSE);
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_HOME:
_editor.MoveSelectionToLineFirstEnd(TRUE);
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_END:
_editor.MoveSelectionToLineFirstEnd(FALSE);
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_DELETE:
nSelStart = _editor.GetSelectionStart();
nSelEnd = _editor.GetSelectionEnd();
if (nSelStart == nSelEnd)
{
_editor.DeleteAtSelection(FALSE);
}
else
{
_editor.DeleteSelection();
}
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_BACK:
nSelStart = _editor.GetSelectionStart();
nSelEnd = _editor.GetSelectionEnd();
if (nSelStart == nSelEnd)
{
_editor.DeleteAtSelection(TRUE);
}
else
{
_editor.DeleteSelection();
}
InvalidateRect(_hwnd, NULL, FALSE);
break;
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnCreate(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (!InitializeTsfTextServices(g_hInst))
{
return;
}
_editor.SetWnd(hwnd);
_editor.SetFont(&_lfCurrentFont);
_editor.InitTSF();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnDestroy()
{
_editor.UninitTSF();
DiscardDeviceResources();
UninitializeTsfTextServices();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnSetFocus(WPARAM wParam, LPARAM lParam)
{
_editor.SetFocusDocumentMgr();
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
SetFocus(_hwnd);
if (AleartMouseSink(lParam))
{
return;
}
_editor.TerminateCompositionString();
POINT pt;
_uSelDragStart = (UINT)-1;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
if (_editor.MoveSelectionAtPoint(pt))
{
InvalidateRect(_hwnd, NULL, FALSE);
_uSelDragStart = _editor.GetSelectionStart();
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnLButtonUp(WPARAM wParam, LPARAM lParam)
{
if (AleartMouseSink(lParam))
{
return;
}
UINT nSelStart = _editor.GetSelectionStart();
UINT nSelEnd = _editor.GetSelectionEnd();
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
if (_editor.MoveSelectionAtPoint(pt))
{
UINT nNewSelStart = _editor.GetSelectionStart();
UINT nNewSelEnd = _editor.GetSelectionEnd();
_editor.MoveSelection(min(nSelStart, nNewSelStart), max(nSelEnd, nNewSelEnd));
InvalidateRect(_hwnd, NULL, FALSE);
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnRButtonDown(WPARAM wParam, LPARAM lParam)
{
if (AleartMouseSink(lParam))
{
return;
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnRButtonUp(WPARAM wParam, LPARAM lParam)
{
if (AleartMouseSink(lParam))
{
return;
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::OnMouseMove(WPARAM wParam, LPARAM lParam)
{
if (AleartMouseSink(lParam))
{
return;
}
if (wParam & MK_LBUTTON)
{
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
if (_editor.MoveSelectionAtPoint(pt))
{
UINT nNewSelStart = _editor.GetSelectionStart();
UINT nNewSelEnd = _editor.GetSelectionEnd();
_editor.MoveSelection(min(_uSelDragStart, nNewSelStart), max(_uSelDragStart, nNewSelEnd));
InvalidateRect(_hwnd, NULL, FALSE);
}
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
void CTextInputCtrl::SetFont(HWND hwndParent)
{
CHOOSEFONT cf;
LOGFONT lf = _lfCurrentFont;
cf.lStructSize = sizeof(CHOOSEFONT);
cf.hwndOwner = hwndParent;
cf.lpLogFont = &lf;
cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
cf.rgbColors = RGB(0, 0, 0);
cf.lCustData = 0;
cf.lpfnHook = NULL;
cf.lpTemplateName = NULL;
cf.hInstance = NULL;
cf.lpszStyle = NULL;
cf.nFontType = SCREEN_FONTTYPE;
cf.nSizeMin = 0;
cf.nSizeMax = 0;
if (ChooseFont(&cf))
{
_lfCurrentFont = lf;
_editor.SetFont(&_lfCurrentFont);
InvalidateRect(_hwnd, NULL, FALSE);
}
}
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
BOOL CTextInputCtrl::AleartMouseSink(LPARAM lParam)
{
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
BOOL bEaten = FALSE;
DWORD dwBtnState = 0;
if (GetKeyState(VK_LBUTTON) & 0x80)
dwBtnState = MK_LBUTTON;
else if (GetKeyState(VK_RBUTTON) & 0x80)
dwBtnState = MK_RBUTTON;
_editor.AleartMouseSink(pt, dwBtnState, &bEaten);
return bEaten;
}
@@ -0,0 +1,81 @@
#pragma once
#include "TextEditor.h"
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
class CTextInputCtrl
{
public:
CTextInputCtrl()
{
_hwnd = NULL;
_pD2DFactory = NULL;
_pDWriteFactory = NULL;
_pRenderTarget = NULL;
HFONT hfontTemp = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
GetObject(hfontTemp, sizeof(LOGFONT), &_lfCurrentFont);
_lfCurrentFont.lfHeight = -27;
wcscpy_s(_lfCurrentFont.lfFaceName, LF_FACESIZE, L"Noto Sans SC");
}
static ATOM RegisterClass(HINSTANCE hInstance);
HWND Create(HWND hwndParent);
static void SetThis(HWND hWnd, LPVOID lp)
{
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lp);
}
static CTextInputCtrl *GetThis(HWND hWnd)
{
CTextInputCtrl *p = (CTextInputCtrl *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
return p;
}
static LRESULT CALLBACK s_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnCreate(HWND hwnd, WPARAM wParam, LPARAM lParam);
void OnDestroy();
void OnSetFocus(WPARAM wParam, LPARAM lParam);
void OnPaint();
void OnSize();
void OnKeyDown(WPARAM wParam, LPARAM lParam);
void OnLButtonDown(WPARAM wParam, LPARAM lParam);
void OnLButtonUp(WPARAM wParam, LPARAM lParam);
void OnRButtonDown(WPARAM wParam, LPARAM lParam);
void OnRButtonUp(WPARAM wParam, LPARAM lParam);
void OnMouseMove(WPARAM wParam, LPARAM lParam);
void Move(int x, int y, int nWidth, int nHeight)
{
if (IsWindow(_hwnd))
MoveWindow(_hwnd, x, y, nWidth, nHeight, TRUE);
}
HWND GetWnd()
{
return _hwnd;
}
const LOGFONT *GetFont()
{
return &_lfCurrentFont;
}
void SetFont(HWND hwndParent);
private:
BOOL CreateDeviceResources();
void DiscardDeviceResources();
BOOL AleartMouseSink(LPARAM lParam);
HWND _hwnd;
ID2D1Factory *_pD2DFactory;
IDWriteFactory *_pDWriteFactory;
ID2D1HwndRenderTarget *_pRenderTarget;
CTextEditor _editor;
LOGFONT _lfCurrentFont;
UINT _uSelDragStart;
};
@@ -0,0 +1,666 @@
#include "TextLayout.h"
#include <cmath>
namespace
{
constexpr FLOAT kEditorPaddingPixels = 3.0f;
constexpr FLOAT kCaretWidthPixels = 2.0f;
float DipsFromLogFontHeight(const LOGFONT *plf, FLOAT dpiY)
{
LONG logicalHeight = plf ? plf->lfHeight : 0;
if (logicalHeight == 0)
{
return 16.0f;
}
const FLOAT pixels = static_cast<FLOAT>(abs(logicalHeight));
return pixels * 96.0f / max(dpiY, 1.0f);
}
} // namespace
BOOL CTextLayout::Initialize(IDWriteFactory *pDWriteFactory)
{
if (_pDWriteFactory == pDWriteFactory)
{
return TRUE;
}
if (_pDWriteFactory)
{
_pDWriteFactory->Release();
_pDWriteFactory = NULL;
}
if (!pDWriteFactory)
{
return FALSE;
}
_pDWriteFactory = pDWriteFactory;
_pDWriteFactory->AddRef();
return TRUE;
}
BOOL CTextLayout::EnsureTextFormat(const LOGFONT *plf, FLOAT dpiY)
{
if (!_pDWriteFactory || !plf)
{
return FALSE;
}
if (_pTextFormat)
{
_pTextFormat->Release();
_pTextFormat = NULL;
}
const DWRITE_FONT_WEIGHT fontWeight = plf->lfWeight >= FW_BOLD ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL;
const DWRITE_FONT_STYLE fontStyle = plf->lfItalic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
const DWRITE_FONT_STRETCH fontStretch = DWRITE_FONT_STRETCH_NORMAL;
const FLOAT fontSize = DipsFromLogFontHeight(plf, dpiY);
if (FAILED(_pDWriteFactory->CreateTextFormat(plf->lfFaceName, NULL, fontWeight, fontStyle, fontStretch, fontSize,
L"", &_pTextFormat)))
{
return FALSE;
}
_pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
_pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
_pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
return TRUE;
}
BOOL CTextLayout::Layout(const WCHAR *psz, UINT nCnt, const LOGFONT *plf, FLOAT layoutWidthPixels, FLOAT dpiX, FLOAT dpiY)
{
Clear();
if (!EnsureTextFormat(plf, dpiY))
{
return FALSE;
}
_dpiX = max(dpiX, 1.0f);
_dpiY = max(dpiY, 1.0f);
_paddingLeftDips = PixelsToDipsX(kEditorPaddingPixels);
_paddingTopDips = PixelsToDipsY(kEditorPaddingPixels);
_paddingRightDips = PixelsToDipsX(kEditorPaddingPixels);
_paddingBottomDips = PixelsToDipsY(kEditorPaddingPixels);
_layoutWidth = max(PixelsToDipsX(layoutWidthPixels) - _paddingLeftDips - _paddingRightDips, 1.0f);
const WCHAR *layoutText = psz ? psz : L"";
UINT layoutLength = nCnt;
BOOL usesPlaceholder = FALSE;
if (layoutLength == 0)
{
layoutText = L" ";
layoutLength = 1;
usesPlaceholder = TRUE;
}
if (FAILED(_pDWriteFactory->CreateTextLayout(layoutText, layoutLength, _pTextFormat, _layoutWidth, 100000.0f,
&_pTextLayout)))
{
return FALSE;
}
_pTextLayout->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
DWRITE_TEXT_METRICS textMetrics = {};
if (FAILED(_pTextLayout->GetMetrics(&textMetrics)))
{
Clear();
return FALSE;
}
UINT32 actualLineCount = 0;
if (_pTextLayout->GetLineMetrics(NULL, 0, &actualLineCount) != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
{
actualLineCount = max(actualLineCount, 1u);
}
if (actualLineCount == 0)
{
actualLineCount = 1;
}
DWRITE_LINE_METRICS *lineMetrics =
static_cast<DWRITE_LINE_METRICS *>(LocalAlloc(LPTR, sizeof(DWRITE_LINE_METRICS) * actualLineCount));
if (!lineMetrics)
{
Clear();
return FALSE;
}
if (FAILED(_pTextLayout->GetLineMetrics(lineMetrics, actualLineCount, &actualLineCount)))
{
LocalFree(lineMetrics);
Clear();
return FALSE;
}
_nLineCnt = actualLineCount;
_prgLines = static_cast<LINEINFO *>(LocalAlloc(LPTR, _nLineCnt * sizeof(LINEINFO)));
if (!_prgLines)
{
LocalFree(lineMetrics);
Clear();
return FALSE;
}
UINT32 textPos = 0;
_nLineHeight = 0;
_lineHeightDips = 0.0f;
for (UINT i = 0; i < _nLineCnt; i++)
{
LINEINFO &line = _prgLines[i];
line.prgCharInfo = NULL;
const UINT32 lineLength = lineMetrics[i].length;
const UINT32 visibleLength = usesPlaceholder ? 0U : (lineLength - lineMetrics[i].newlineLength);
line.nPos = textPos;
line.nCnt = visibleLength;
const FLOAT lineHeightDips = max(lineMetrics[i].height, 1.0f);
_lineHeightDips = max(_lineHeightDips, lineHeightDips);
_nLineHeight = max(_nLineHeight, static_cast<int>(DipsToPixelsY(lineHeightDips)));
if (visibleLength > 0)
{
line.prgCharInfo = static_cast<CHARINFO *>(LocalAlloc(LPTR, visibleLength * sizeof(CHARINFO)));
if (!line.prgCharInfo)
{
LocalFree(lineMetrics);
Clear();
return FALSE;
}
for (UINT32 j = 0; j < visibleLength; j++)
{
FLOAT hitX = 0.0f;
FLOAT hitY = 0.0f;
DWRITE_HIT_TEST_METRICS hitMetrics = {};
if (FAILED(_pTextLayout->HitTestTextPosition(textPos + j, FALSE, &hitX, &hitY, &hitMetrics)))
{
LocalFree(lineMetrics);
Clear();
return FALSE;
}
D2D1_RECT_F &rc = line.prgCharInfo[j].rc;
rc.left = _paddingLeftDips + hitX;
rc.top = _paddingTopDips + hitY;
rc.right = _paddingLeftDips + hitX + hitMetrics.width;
rc.bottom = _paddingTopDips + hitY + hitMetrics.height;
if (rc.right <= rc.left)
{
rc.right = rc.left + (1.0f * 96.0f / _dpiX);
}
if (rc.bottom <= rc.top)
{
rc.bottom = rc.top + lineHeightDips;
}
}
}
textPos += lineLength;
}
LocalFree(lineMetrics);
if (_nLineHeight == 0)
{
_nLineHeight = max(1L, DipsToPixelsY(max(textMetrics.height, 1.0f)));
}
if (_lineHeightDips == 0.0f)
{
_lineHeightDips = max(textMetrics.height, 1.0f);
}
return TRUE;
}
BOOL CTextLayout::Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz, UINT nCnt, UINT nSelStart, UINT nSelEnd,
const COMPOSITIONRENDERINFO *pCompositionRenderInfo, UINT nCompositionRenderInfo)
{
if (!pRenderTarget || !_pTextFormat)
{
return FALSE;
}
ID2D1SolidColorBrush *pTextBrush = NULL;
ID2D1SolidColorBrush *pSelectionBrush = NULL;
ID2D1SolidColorBrush *pCaretBrush = NULL;
ID2D1SolidColorBrush *pCompositionBrush = NULL;
const D2D1_COLOR_F textColor = ToColorF(GetSysColor(COLOR_WINDOWTEXT));
const D2D1_COLOR_F selectionColor = ToColorF(GetSysColor(COLOR_HIGHLIGHT));
if (FAILED(pRenderTarget->CreateSolidColorBrush(textColor, &pTextBrush)) ||
FAILED(pRenderTarget->CreateSolidColorBrush(selectionColor, &pSelectionBrush)) ||
FAILED(pRenderTarget->CreateSolidColorBrush(textColor, &pCaretBrush)) ||
FAILED(pRenderTarget->CreateSolidColorBrush(textColor, &pCompositionBrush)))
{
if (pTextBrush)
pTextBrush->Release();
if (pSelectionBrush)
pSelectionBrush->Release();
if (pCaretBrush)
pCaretBrush->Release();
if (pCompositionBrush)
pCompositionBrush->Release();
return FALSE;
}
for (UINT i = 0; i < _nLineCnt; i++)
{
const LINEINFO &line = _prgLines[i];
if ((nSelEnd >= line.nPos) && (nSelStart <= line.nPos + line.nCnt))
{
UINT nSelStartInLine = 0;
UINT nSelEndInLine = line.nCnt;
if (nSelStart > line.nPos)
nSelStartInLine = nSelStart - line.nPos;
if (nSelEnd < line.nPos + line.nCnt)
nSelEndInLine = nSelEnd - line.nPos;
if (nSelStartInLine != nSelEndInLine)
{
for (UINT j = nSelStartInLine; j < nSelEndInLine; j++)
{
const D2D1_RECT_F &rc = line.prgCharInfo[j].rc;
pRenderTarget->FillRectangle(D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), pSelectionBrush);
}
}
}
}
if (_pTextLayout && nCnt > 0)
{
pRenderTarget->DrawTextLayout(D2D1::Point2F(_paddingLeftDips, _paddingTopDips), _pTextLayout, pTextBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
for (UINT i = 0; i < _nLineCnt; i++)
{
const LINEINFO &line = _prgLines[i];
for (UINT j = 0; j < nCompositionRenderInfo; j++)
{
const COMPOSITIONRENDERINFO &composition = pCompositionRenderInfo[j];
if ((composition.nEnd >= line.nPos) && (composition.nStart <= line.nPos + line.nCnt))
{
UINT nCompStartInLine = 0;
UINT nCompEndInLine = line.nCnt;
if (composition.nStart > line.nPos)
nCompStartInLine = composition.nStart - line.nPos;
if (composition.nEnd < line.nPos + line.nCnt)
nCompEndInLine = composition.nEnd - line.nPos;
D2D1_RECT_F underlineRect = {};
BOOL hasUnderlineRect = FALSE;
for (UINT k = nCompStartInLine; k < nCompEndInLine; k++)
{
const D2D1_RECT_F &rc = line.prgCharInfo[k].rc;
if (composition.da.crBk.type != TF_CT_NONE)
{
pCompositionBrush->SetColor(GetAttributeColor(&composition.da.crBk, GetSysColor(COLOR_WINDOW)));
pRenderTarget->FillRectangle(D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), pCompositionBrush);
}
WCHAR ch = psz[line.nPos + k];
D2D1_COLOR_F compositionTextColor = GetAttributeColor(&composition.da.crText, GetSysColor(COLOR_WINDOWTEXT));
if (composition.da.crText.type == TF_CT_NONE)
{
compositionTextColor = textColor;
}
pCompositionBrush->SetColor(compositionTextColor);
pRenderTarget->DrawTextW(&ch, 1, _pTextFormat,
D2D1::RectF(rc.left, rc.top, rc.right + PixelsToDipsX(2.0f), rc.bottom),
pCompositionBrush, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT,
DWRITE_MEASURING_MODE_NATURAL);
if (!hasUnderlineRect)
{
underlineRect = rc;
hasUnderlineRect = TRUE;
}
else
{
underlineRect.left = min(underlineRect.left, rc.left);
underlineRect.top = min(underlineRect.top, rc.top);
underlineRect.right = max(underlineRect.right, rc.right);
underlineRect.bottom = max(underlineRect.bottom, rc.bottom);
}
}
if (hasUnderlineRect && composition.da.lsStyle != TF_LS_NONE)
{
const BOOL bClause = composition.nEnd <= static_cast<int>(line.nPos + line.nCnt);
DrawUnderline(pRenderTarget, &composition.da, underlineRect, bClause);
}
}
}
}
if (_nLineCnt == 0)
{
_rcCaret.left = _paddingLeftDips;
_rcCaret.top = _paddingTopDips;
_rcCaret.right = _paddingLeftDips + PixelsToDipsX(kCaretWidthPixels);
_rcCaret.bottom = _paddingTopDips + _lineHeightDips;
}
else if (nSelStart == nSelEnd)
{
RectFromCharPosDips(nSelStart, &_rcCaret);
}
if ((nSelStart == nSelEnd) && (_fInterimCaret || _fCaretVisible))
{
pRenderTarget->FillRectangle(
D2D1::RectF(_rcCaret.left, _rcCaret.top,
max(_rcCaret.right, _rcCaret.left + PixelsToDipsX(kCaretWidthPixels)),
_rcCaret.bottom),
pCaretBrush);
}
pCompositionBrush->Release();
pCaretBrush->Release();
pSelectionBrush->Release();
pTextBrush->Release();
return TRUE;
}
void CTextLayout::ToggleCaretBlink()
{
_fCaretVisible = !_fCaretVisible;
}
void CTextLayout::ResetCaretBlink()
{
_fCaretVisible = TRUE;
}
void CTextLayout::SetInterimCaret(BOOL fSet, UINT nPos)
{
_fInterimCaret = fSet;
if (_fInterimCaret)
{
RectFromCharPosDips(nPos, &_rcCaret);
}
else
{
_rcCaret = D2D1::RectF();
}
}
BOOL CTextLayout::RectFromCharPos(UINT nPos, RECT *prc)
{
D2D1_RECT_F rc = {};
if (!RectFromCharPosDips(nPos, &rc))
{
memset(prc, 0, sizeof(*prc));
return FALSE;
}
prc->left = DipsToPixelsX(rc.left);
prc->top = DipsToPixelsY(rc.top);
prc->right = DipsToPixelsX(rc.right);
prc->bottom = DipsToPixelsY(rc.bottom);
if (prc->right <= prc->left)
{
prc->right = prc->left + 1;
}
if (prc->bottom <= prc->top)
{
prc->bottom = prc->top + max(_nLineHeight, 1);
}
return TRUE;
}
BOOL CTextLayout::RectFromCharPosDips(UINT nPos, D2D1_RECT_F *prc)
{
*prc = D2D1::RectF();
if ((_nLineCnt > 0) && (_prgLines[0].nCnt == 0))
{
prc->left = _paddingLeftDips;
prc->top = _paddingTopDips;
prc->right = _paddingLeftDips + PixelsToDipsX(kCaretWidthPixels);
prc->bottom = _paddingTopDips + _lineHeightDips;
return TRUE;
}
for (UINT i = 0; i < _nLineCnt; i++)
{
if ((_prgLines[i].nCnt > 0) && (nPos == _prgLines[i].nPos))
{
*prc = _prgLines[i].prgCharInfo[0].rc;
prc->right = prc->left;
return TRUE;
}
if (nPos < _prgLines[i].nPos)
continue;
if (nPos >= _prgLines[i].nPos + _prgLines[i].nCnt)
{
if (((nPos - _prgLines[i].nPos) > 0) && (nPos == _prgLines[i].nPos + _prgLines[i].nCnt))
{
*prc = _prgLines[i].prgCharInfo[nPos - _prgLines[i].nPos - 1].rc;
prc->left = prc->right;
return TRUE;
}
continue;
}
*prc = _prgLines[i].prgCharInfo[nPos - _prgLines[i].nPos].rc;
return TRUE;
}
prc->left = _paddingLeftDips;
prc->right = _paddingLeftDips + PixelsToDipsX(kCaretWidthPixels);
prc->top = _paddingTopDips + (_nLineCnt * _lineHeightDips);
prc->bottom = prc->top + _lineHeightDips;
return TRUE;
}
UINT CTextLayout::CharPosFromPoint(POINT pt)
{
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x));
const FLOAT dipY = PixelsToDipsY(static_cast<FLOAT>(pt.y));
for (UINT i = 0; i < _nLineCnt; i++)
{
for (UINT j = 0; j < _prgLines[i].nCnt; j++)
{
const D2D1_RECT_F &rc = _prgLines[i].prgCharInfo[j].rc;
if ((dipX >= rc.left) && (dipX < rc.right) && (dipY >= rc.top) && (dipY < rc.bottom))
{
const FLOAT nWidth = _prgLines[i].prgCharInfo[j].GetWidth();
if (dipX > rc.left + (nWidth * 3.0f / 4.0f))
{
return _prgLines[i].nPos + j + 1;
}
return _prgLines[i].nPos + j;
}
}
}
return static_cast<UINT>(-1);
}
UINT CTextLayout::ExactCharPosFromPoint(POINT pt)
{
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x));
const FLOAT dipY = PixelsToDipsY(static_cast<FLOAT>(pt.y));
for (UINT i = 0; i < _nLineCnt; i++)
{
for (UINT j = 0; j < _prgLines[i].nCnt; j++)
{
const D2D1_RECT_F &rc = _prgLines[i].prgCharInfo[j].rc;
if ((dipX >= rc.left) && (dipX < rc.right) && (dipY >= rc.top) && (dipY < rc.bottom))
{
return _prgLines[i].nPos + j;
}
}
}
return static_cast<UINT>(-1);
}
UINT CTextLayout::FineFirstEndCharPosInLine(UINT uCurPos, BOOL bFirst)
{
for (UINT i = 0; i < _nLineCnt; i++)
{
if ((_prgLines[i].nPos <= uCurPos) && (_prgLines[i].nPos + _prgLines[i].nCnt >= uCurPos))
{
if (bFirst)
{
return _prgLines[i].nPos;
}
return _prgLines[i].nPos + _prgLines[i].nCnt;
}
}
return static_cast<UINT>(-1);
}
void CTextLayout::Clear()
{
if (_pTextLayout)
{
_pTextLayout->Release();
_pTextLayout = NULL;
}
if (_prgLines)
{
for (UINT i = 0; i < _nLineCnt; i++)
{
if (_prgLines[i].prgCharInfo)
{
LocalFree(_prgLines[i].prgCharInfo);
}
}
LocalFree(_prgLines);
_prgLines = NULL;
}
_nLineCnt = 0;
}
FLOAT CTextLayout::PixelsToDipsX(FLOAT value) const
{
return value * 96.0f / max(_dpiX, 1.0f);
}
FLOAT CTextLayout::PixelsToDipsY(FLOAT value) const
{
return value * 96.0f / max(_dpiY, 1.0f);
}
LONG CTextLayout::DipsToPixelsX(FLOAT value) const
{
return static_cast<LONG>(std::lround(value * _dpiX / 96.0f));
}
LONG CTextLayout::DipsToPixelsY(FLOAT value) const
{
return static_cast<LONG>(std::lround(value * _dpiY / 96.0f));
}
void CTextLayout::DrawUnderline(ID2D1HwndRenderTarget *pRenderTarget, const TF_DISPLAYATTRIBUTE *pda,
const D2D1_RECT_F &rc, BOOL bClause)
{
ID2D1SolidColorBrush *pBrush = NULL;
const D2D1_COLOR_F lineColor = GetAttributeColor(&pda->crLine, GetSysColor(COLOR_WINDOWTEXT));
if (FAILED(pRenderTarget->CreateSolidColorBrush(lineColor, &pBrush)))
{
return;
}
FLOAT strokeWidth = max(PixelsToDipsY(1.0f), _lineHeightDips / 24.0f);
if (pda->fBoldLine)
{
strokeWidth *= 1.5f;
}
const FLOAT left = static_cast<FLOAT>(rc.left);
const FLOAT right = static_cast<FLOAT>(rc.right - (bClause ? strokeWidth : 0.0f));
const FLOAT baseline = static_cast<FLOAT>(rc.bottom) - (strokeWidth / 2.0f);
switch (pda->lsStyle)
{
case TF_LS_DOT:
case TF_LS_DASH:
case TF_LS_SQUIGGLE:
{
if (pda->lsStyle == TF_LS_SQUIGGLE)
{
const FLOAT halfWave = max(PixelsToDipsX(3.0f), strokeWidth * 3.0f);
const FLOAT amplitude = max(PixelsToDipsY(0.75f), strokeWidth * 0.6f);
FLOAT x = left;
BOOL rise = TRUE;
while (x < right)
{
const FLOAT endX = min(x + halfWave, right);
const FLOAT y0 = rise ? baseline + amplitude : baseline - amplitude;
const FLOAT y1 = rise ? baseline - amplitude : baseline + amplitude;
pRenderTarget->DrawLine(D2D1::Point2F(x, y0), D2D1::Point2F(endX, y1), pBrush, strokeWidth);
rise = !rise;
x = endX;
}
}
else
{
const FLOAT segment = pda->lsStyle == TF_LS_DASH ? max(PixelsToDipsX(5.0f), strokeWidth * 4.0f)
: max(PixelsToDipsX(1.0f), strokeWidth);
const FLOAT gap = pda->lsStyle == TF_LS_DASH ? max(PixelsToDipsX(3.0f), strokeWidth * 2.0f)
: max(PixelsToDipsX(2.0f), strokeWidth * 1.5f);
for (FLOAT x = left; x < right; x += segment + gap)
{
const FLOAT endX = min(x + segment, right);
pRenderTarget->DrawLine(D2D1::Point2F(x, baseline), D2D1::Point2F(endX, baseline), pBrush, strokeWidth);
}
}
break;
}
case TF_LS_SOLID:
pRenderTarget->DrawLine(D2D1::Point2F(left, baseline), D2D1::Point2F(right, baseline), pBrush, strokeWidth);
break;
case TF_LS_NONE:
default:
break;
}
pBrush->Release();
}
D2D1_COLOR_F CTextLayout::GetAttributeColor(const TF_DA_COLOR *pdac, COLORREF fallbackColor)
{
if (!pdac)
{
return ToColorF(fallbackColor);
}
switch (pdac->type)
{
case TF_CT_SYSCOLOR:
return ToColorF(GetSysColor(pdac->nIndex));
case TF_CT_COLORREF:
return ToColorF(pdac->cr);
case TF_CT_NONE:
default:
return ToColorF(fallbackColor);
}
}
D2D1_COLOR_F CTextLayout::ToColorF(COLORREF color)
{
return D2D1::ColorF(GetRValue(color) / 255.0f, GetGValue(color) / 255.0f, GetBValue(color) / 255.0f, 1.0f);
}
@@ -0,0 +1,155 @@
#pragma once
#include <Windows.h>
#include <d2d1.h>
#include <dwrite.h>
#include <msctf.h>
typedef struct
{
UINT nStart;
UINT nEnd;
TF_DISPLAYATTRIBUTE da;
} COMPOSITIONRENDERINFO;
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
struct CHARINFO
{
D2D1_RECT_F rc;
FLOAT GetWidth()
{
return rc.right - rc.left;
}
};
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
typedef struct
{
UINT nPos;
UINT nCnt;
CHARINFO *prgCharInfo;
} LINEINFO;
//----------------------------------------------------------------
//
//
//
//----------------------------------------------------------------
class CTextLayout
{
public:
CTextLayout()
{
_pDWriteFactory = NULL;
_pTextFormat = NULL;
_pTextLayout = NULL;
_prgLines = NULL;
_nLineCnt = 0;
_fInterimCaret = FALSE;
_fCaretVisible = TRUE;
_layoutWidth = 0.0f;
_nLineHeight = 0;
_lineHeightDips = 0.0f;
_dpiX = 96.0f;
_dpiY = 96.0f;
_paddingLeftDips = 0.0f;
_paddingTopDips = 0.0f;
_paddingRightDips = 0.0f;
_paddingBottomDips = 0.0f;
_rcCaret = D2D1::RectF();
}
~CTextLayout()
{
Clear();
if (_pTextFormat)
{
_pTextFormat->Release();
}
if (_pDWriteFactory)
{
_pDWriteFactory->Release();
}
}
BOOL Initialize(IDWriteFactory *pDWriteFactory);
BOOL Layout(const WCHAR *psz, UINT nCnt, const LOGFONT *plf, FLOAT layoutWidthPixels, FLOAT dpiX, FLOAT dpiY);
BOOL Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz, UINT nCnt, UINT nSelStart, UINT nSelEnd,
const COMPOSITIONRENDERINFO *pCompositionRenderInfo, UINT nCompositionRenderInfo);
BOOL RectFromCharPos(UINT nPos, RECT *prc);
UINT CharPosFromPoint(POINT pt);
UINT ExactCharPosFromPoint(POINT pt);
UINT FineFirstEndCharPosInLine(UINT uCurPos, BOOL bFirst);
void ToggleCaretBlink();
void ResetCaretBlink();
void SetInterimCaret(BOOL fSet, UINT uPos);
BOOL IsInterimCaret()
{
return _fInterimCaret;
}
LONG GetPaddingLeftPixels() const
{
return DipsToPixelsX(_paddingLeftDips);
}
LONG GetPaddingTopPixels() const
{
return DipsToPixelsY(_paddingTopDips);
}
LONG GetPaddingRightPixels() const
{
return DipsToPixelsX(_paddingRightDips);
}
LONG GetPaddingBottomPixels() const
{
return DipsToPixelsY(_paddingBottomDips);
}
int GetLineHeight()
{
return _nLineHeight;
}
private:
BOOL EnsureTextFormat(const LOGFONT *plf, FLOAT dpiY);
BOOL RectFromCharPosDips(UINT nPos, D2D1_RECT_F *prc);
FLOAT PixelsToDipsX(FLOAT value) const;
FLOAT PixelsToDipsY(FLOAT value) const;
LONG DipsToPixelsX(FLOAT value) const;
LONG DipsToPixelsY(FLOAT value) const;
void DrawUnderline(ID2D1HwndRenderTarget *pRenderTarget, const TF_DISPLAYATTRIBUTE *pda, const D2D1_RECT_F &rc,
BOOL bClause);
D2D1_COLOR_F GetAttributeColor(const TF_DA_COLOR *pdac, COLORREF fallbackColor);
D2D1_COLOR_F ToColorF(COLORREF color);
void Clear();
IDWriteFactory *_pDWriteFactory;
IDWriteTextFormat *_pTextFormat;
IDWriteTextLayout *_pTextLayout;
int _nLineHeight;
FLOAT _lineHeightDips;
FLOAT _layoutWidth;
FLOAT _dpiX;
FLOAT _dpiY;
FLOAT _paddingLeftDips;
FLOAT _paddingTopDips;
FLOAT _paddingRightDips;
FLOAT _paddingBottomDips;
LINEINFO *_prgLines;
UINT _nLineCnt;
BOOL _fInterimCaret;
BOOL _fCaretVisible;
D2D1_RECT_F _rcCaret;
};
@@ -0,0 +1,745 @@
#include "TextStore.h"
#include "TextEditor.h"
#include "TextInputCtrl.h"
#include "InputScope.h"
#include "tsattrs.h"
//+---------------------------------------------------------------------------
//
// IUnknown
//
//----------------------------------------------------------------------------
STDAPI CTextStore::QueryInterface(REFIID riid, void **ppvObj)
{
*ppvObj = NULL;
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITextStoreACP))
{
*ppvObj = (ITextStoreACP *)this;
}
else if (IsEqualIID(riid, IID_ITfContextOwnerCompositionSink))
{
*ppvObj = (ITfContextOwnerCompositionSink *)this;
}
else if (IsEqualIID(riid, IID_ITfMouseTrackerACP))
{
*ppvObj = (ITfMouseTrackerACP *)this;
}
if (*ppvObj)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDAPI_(ULONG) CTextStore::AddRef()
{
return ++_cRef;
}
STDAPI_(ULONG) CTextStore::Release()
{
long cr;
cr = --_cRef;
if (cr == 0)
{
delete this;
}
return cr;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::AdviseSink(REFIID riid, IUnknown *punk, DWORD dwMask)
{
if (!IsEqualGUID(riid, IID_ITextStoreACPSink))
{
return TS_E_NOOBJECT;
}
if (FAILED(punk->QueryInterface(IID_ITextStoreACPSink, (void **)&_pSink)))
{
return E_NOINTERFACE;
}
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::UnadviseSink(IUnknown *punk)
{
// we're dealing with TSF. We don't have to check punk is same instance of _pSink.
_pSink->Release();
_pSink = NULL;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::RequestLock(DWORD dwLockFlags, HRESULT *phrSession)
{
if (!_pSink)
{
return E_UNEXPECTED;
}
*phrSession = _pSink->OnLockGranted(dwLockFlags);
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetStatus(TS_STATUS *pdcs)
{
pdcs->dwDynamicFlags = 0;
pdcs->dwStaticFlags = 0;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::QueryInsert(LONG acpInsertStart, LONG acpInsertEnd, ULONG cch, LONG *pacpResultStart,
LONG *pacpResultEnd)
{
*pacpResultStart = acpInsertStart;
*pacpResultEnd = acpInsertEnd;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP *pSelection, ULONG *pcFetched)
{
*pcFetched = 0;
if ((ulCount > 0) && ((ulIndex == 0) || (ulIndex == TS_DEFAULT_SELECTION)))
{
pSelection[0].acpStart = _pEditor->GetSelectionStart();
pSelection[0].acpEnd = _pEditor->GetSelectionEnd();
pSelection[0].style.ase = TS_AE_END;
pSelection[0].style.fInterimChar = _pEditor->GetLayout()->IsInterimCaret();
*pcFetched = 1;
}
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP *pSelection)
{
if (ulCount > 0)
{
_pEditor->MoveSelection(pSelection[0].acpStart, pSelection[0].acpEnd);
_pEditor->UpdateLayout();
_pEditor->InvalidateRect();
_pEditor->SetInterimCaret(pSelection->style.fInterimChar);
}
return S_OK;
;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetText(LONG acpStart, LONG acpEnd, __out_ecount(cchPlainReq) WCHAR *pchPlain, ULONG cchPlainReq,
ULONG *pcchPlainOut, TS_RUNINFO *prgRunInfo, ULONG ulRunInfoReq, ULONG *pulRunInfoOut,
LONG *pacpNext)
{
if ((cchPlainReq == 0) && (ulRunInfoReq == 0))
{
return S_OK;
}
if (acpEnd == -1)
acpEnd = _pEditor->GetTextLength();
acpEnd = min(acpEnd, acpStart + (int)cchPlainReq);
if ((acpStart != acpEnd) && !_pEditor->GetText(acpStart, pchPlain, acpEnd - acpStart))
{
return E_FAIL;
}
*pcchPlainOut = acpEnd - acpStart;
if (ulRunInfoReq)
{
prgRunInfo[0].uCount = acpEnd - acpStart;
prgRunInfo[0].type = TS_RT_PLAIN;
*pulRunInfoOut = 1;
}
*pacpNext = acpEnd;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd, __in_ecount(cch) const WCHAR *pchText, ULONG cch,
TS_TEXTCHANGE *pChange)
{
// Check the composition status
if (cch == 0 && _pCurrentCompositionView)
{
_pEditor->TerminateCompositionString();
}
LONG acpRemovingEnd;
if (acpStart > (LONG)_pEditor->GetTextLength())
return E_INVALIDARG;
acpRemovingEnd = min(acpEnd, (LONG)_pEditor->GetTextLength() + 1);
if (!_pEditor->RemoveText(acpStart, acpRemovingEnd - acpStart))
return E_FAIL;
if (!_pEditor->InsertText(acpStart, pchText, cch))
return E_FAIL;
pChange->acpStart = acpStart;
pChange->acpOldEnd = acpEnd;
pChange->acpNewEnd = acpStart + cch;
// Update selection after text change
_pEditor->MoveSelection(acpStart + cch, acpStart + cch);
_pEditor->UpdateLayout();
_pEditor->InvalidateRect();
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetFormattedText(LONG acpStart, LONG acpEnd, IDataObject **ppDataObject)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid, IUnknown **ppunk)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd, IDataObject *pDataObject,
TS_TEXTCHANGE *pChange)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs)
{
PrepareAttributes(cFilterAttrs, paFilterAttrs);
if (!_nAttrVals)
return S_FALSE;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs,
DWORD dwFlags)
{
PrepareAttributes(cFilterAttrs, paFilterAttrs);
if (!_nAttrVals)
return S_FALSE;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs,
DWORD dwFlags)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt, ULONG cFilterAttrs,
const TS_ATTRID *paFilterAttrs, DWORD dwFlags, LONG *pacpNext, BOOL *pfFound,
LONG *plFoundOffset)
{
*pacpNext = 0;
*pfFound = FALSE;
*plFoundOffset = 0;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL *paAttrVals, ULONG *pcFetched)
{
*pcFetched = 0;
for (int i = 0; (i < (int)ulCount) && (i < _nAttrVals); i++)
{
paAttrVals[i] = _attrval[i];
(*pcFetched)++;
}
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetEndACP(LONG *pacp)
{
*pacp = _pEditor->GetTextLength();
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetActiveView(TsViewCookie *pvcView)
{
*pvcView = 0;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT *pt, DWORD dwFlags, LONG *pacp)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT *prc, BOOL *pfClipped)
{
RECT rcStart;
RECT rcEnd;
if (_pEditor->GetLayout()->RectFromCharPos(acpStart, &rcStart) &&
_pEditor->GetLayout()->RectFromCharPos(acpEnd, &rcEnd))
{
if (rcStart.top == rcEnd.top)
{
prc->left = min(rcStart.left, rcEnd.left);
prc->right = max(rcStart.right, rcEnd.right);
}
else
{
RECT rcClient;
GetClientRect(_pEditor->GetWnd(), &rcClient);
prc->left = _pEditor->GetLayout()->GetPaddingLeftPixels();
prc->right = max(prc->left, rcClient.right - _pEditor->GetLayout()->GetPaddingRightPixels());
}
prc->top = min(rcStart.top, rcEnd.top);
prc->bottom = max(rcStart.bottom, rcEnd.bottom);
}
else
{
prc->left = 0;
prc->right = 0;
prc->top = 0;
prc->bottom = 0;
}
ClientToScreen(_pEditor->GetWnd(), (POINT *)&prc->left);
ClientToScreen(_pEditor->GetWnd(), (POINT *)&prc->right);
*pfClipped = FALSE;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetScreenExt(TsViewCookie vcView, RECT *prc)
{
GetClientRect(_pEditor->GetWnd(), prc);
ClientToScreen(_pEditor->GetWnd(), (POINT *)&prc->left);
ClientToScreen(_pEditor->GetWnd(), (POINT *)&prc->right);
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::GetWnd(TsViewCookie vcView, HWND *phwnd)
{
*phwnd = _pEditor->GetWnd();
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::QueryInsertEmbedded(const GUID *pguidService, const FORMATETC *pFormatEtc, BOOL *pfInsertable)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::InsertTextAtSelection(DWORD dwFlags, __in_ecount(cch) const WCHAR *pchText, ULONG cch,
LONG *pacpStart, LONG *pacpEnd, TS_TEXTCHANGE *pChange)
{
LONG acpStart = _pEditor->GetSelectionStart();
LONG acpEnd = _pEditor->GetSelectionEnd();
if (dwFlags & TS_IAS_QUERYONLY)
{
*pacpStart = acpStart;
*pacpEnd = acpStart + cch;
return S_OK;
}
if (!_pEditor->RemoveText(acpStart, acpEnd - acpStart))
return E_FAIL;
if (pchText && !_pEditor->InsertText(acpStart, pchText, cch))
return E_FAIL;
if (pacpStart)
{
*pacpStart = acpStart;
}
if (pacpEnd)
{
*pacpEnd = acpStart + cch;
}
if (pChange)
{
pChange->acpStart = acpStart;
pChange->acpOldEnd = acpEnd;
pChange->acpNewEnd = acpStart + cch;
}
_pEditor->MoveSelection(acpStart, acpStart + cch);
_pEditor->UpdateLayout();
_pEditor->InvalidateRect();
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject *pDataObject, LONG *pacpStart, LONG *pacpEnd,
TS_TEXTCHANGE *pChange)
{
return E_NOTIMPL;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::AdviseMouseSink(ITfRangeACP *range, ITfMouseSink *pSink, DWORD *pdwCookie)
{
DWORD dwCookie;
if (!_prgMouseSinks)
{
_prgMouseSinks = (MOUSESINK *)LocalAlloc(LPTR, sizeof(MOUSESINK));
if (!_prgMouseSinks)
{
return E_OUTOFMEMORY;
}
_nMouseSinks = 1;
dwCookie = 1;
}
else
{
UINT i;
for (i = 0; i < _nMouseSinks; i++)
{
if (_prgMouseSinks[i].pRange == NULL)
{
dwCookie = i + 1;
break;
}
}
if (i == _nMouseSinks)
{
void *pvNew =
LocalReAlloc(_prgMouseSinks, (_nMouseSinks + 1) * sizeof(MOUSESINK), LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!pvNew)
{
return E_OUTOFMEMORY;
}
_prgMouseSinks = (MOUSESINK *)pvNew;
_nMouseSinks++;
dwCookie = _nMouseSinks;
}
}
_prgMouseSinks[dwCookie - 1].pRange = range;
_prgMouseSinks[dwCookie - 1].pMouseSink = pSink;
_prgMouseSinks[dwCookie - 1].pRange->AddRef();
_prgMouseSinks[dwCookie - 1].pMouseSink->AddRef();
*pdwCookie = dwCookie;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::UnadviseMouseSink(DWORD dwCookie)
{
_prgMouseSinks[dwCookie - 1].pRange->Release();
_prgMouseSinks[dwCookie - 1].pRange = NULL;
_prgMouseSinks[dwCookie - 1].pMouseSink->Release();
_prgMouseSinks[dwCookie - 1].pMouseSink = NULL;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
#define IF_ATTR_FONT_STYLE_HEIGHT 1
#define IF_ATTR_FONT_SIZEPTS 2
#define IF_ATTR_TEXT_READONLY 3
#define IF_ATTR_TEXT_ORIENTATION 4
#define IF_ATTR_TEXT_VERTICALWRITING 5
const GUID *c_rgSupportedAttr[5] = {&TSATTRID_Font_Style_Height, &TSATTRID_Font_SizePts, &TSATTRID_Text_ReadOnly,
&TSATTRID_Text_Orientation, &TSATTRID_Text_VerticalWriting};
void CTextStore::PrepareAttributes(ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs)
{
_nAttrVals = 0;
memset(_attrval, 0, sizeof(_attrval));
for (int i = 0; i < ARRAYSIZE(c_rgSupportedAttr); i++)
{
if (cFilterAttrs)
{
BOOL fFound = FALSE;
for (ULONG j = 0; j < cFilterAttrs; j++)
{
if (IsEqualGUID(*c_rgSupportedAttr[i], paFilterAttrs[j]))
{
fFound = TRUE;
break;
}
}
if (!fFound)
{
continue;
}
}
_attrval[_nAttrVals].idAttr = *c_rgSupportedAttr[i];
_attrval[_nAttrVals].dwOverlapId = i + 1;
switch (i + 1)
{
case IF_ATTR_FONT_STYLE_HEIGHT:
_attrval[_nAttrVals].varValue.vt = VT_I4;
_attrval[_nAttrVals].varValue.lVal = _pEditor->GetLineHeight();
break;
case IF_ATTR_FONT_SIZEPTS:
_attrval[_nAttrVals].varValue.vt = VT_I4;
_attrval[_nAttrVals].varValue.lVal = (int)((double)_pEditor->GetLineHeight() / 96.0 * 72.0);
break;
case IF_ATTR_TEXT_READONLY:
_attrval[_nAttrVals].varValue.vt = VT_BOOL;
_attrval[_nAttrVals].varValue.bVal = FALSE;
break;
case IF_ATTR_TEXT_ORIENTATION:
_attrval[_nAttrVals].varValue.vt = VT_I4;
_attrval[_nAttrVals].varValue.lVal = 0;
break;
case IF_ATTR_TEXT_VERTICALWRITING:
_attrval[_nAttrVals].varValue.vt = VT_BOOL;
_attrval[_nAttrVals].varValue.bVal = FALSE;
break;
}
_nAttrVals++;
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::OnStartComposition(ITfCompositionView *pComposition, BOOL *pfOk)
{
if (_pCurrentCompositionView)
{
_pCurrentCompositionView->Release();
_pCurrentCompositionView = NULL;
}
_pCurrentCompositionView = pComposition;
_pCurrentCompositionView->AddRef();
*pfOk = TRUE;
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::OnUpdateComposition(ITfCompositionView *pComposition, ITfRange *pRangeNew)
{
if (_pCurrentCompositionView)
{
_pCurrentCompositionView->Release();
_pCurrentCompositionView = NULL;
}
_pCurrentCompositionView = pComposition;
_pCurrentCompositionView->AddRef();
return S_OK;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
STDAPI CTextStore::OnEndComposition(ITfCompositionView *pComposition)
{
if (_pCurrentCompositionView)
{
_pCurrentCompositionView->Release();
_pCurrentCompositionView = NULL;
}
// Reset selection to current position
LONG nSelStart = _pEditor->GetSelectionStart();
_pEditor->MoveSelection(nSelStart, nSelStart);
return S_OK;
}
@@ -0,0 +1,151 @@
#pragma once
#include <msctf.h>
#include "initguid.h"
class CTextEditor;
typedef struct
{
ITfRange *pRange;
ITfMouseSink *pMouseSink;
} MOUSESINK;
class CTextStore : public ITextStoreACP, public ITfContextOwnerCompositionSink, public ITfMouseTrackerACP
{
public:
CTextStore(CTextEditor *pEditor)
{
_prgMouseSinks = NULL;
_nMouseSinks = 0;
_pEditor = pEditor;
_pCurrentCompositionView = NULL;
_cRef = 1;
}
~CTextStore()
{
}
//
// IUnknown methods
//
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
//
// ITextStoreACP
//
STDMETHODIMP AdviseSink(REFIID riid, IUnknown *punk, DWORD dwMask);
STDMETHODIMP UnadviseSink(IUnknown *punk);
STDMETHODIMP RequestLock(DWORD dwLockFlags, HRESULT *phrSession);
STDMETHODIMP GetStatus(TS_STATUS *pdcs);
STDMETHODIMP QueryInsert(LONG acpInsertStart, LONG acpInsertEnd, ULONG cch, LONG *pacpResultStart,
LONG *pacpResultEnd);
STDMETHODIMP GetSelection(ULONG ulIndex, ULONG ulCount, TS_SELECTION_ACP *pSelection, ULONG *pcFetched);
STDMETHODIMP SetSelection(ULONG ulCount, const TS_SELECTION_ACP *pSelection);
STDMETHODIMP GetText(LONG acpStart, LONG acpEnd, __out_ecount(cchPlainReq) WCHAR *pchPlain, ULONG cchPlainReq,
ULONG *pcchPlainOut, TS_RUNINFO *prgRunInfo, ULONG ulRunInfoReq, ULONG *pulRunInfoOut,
LONG *pacpNext);
STDMETHODIMP SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd, __in_ecount(cch) const WCHAR *pchText, ULONG cch,
TS_TEXTCHANGE *pChange);
STDMETHODIMP GetFormattedText(LONG acpStart, LONG acpEnd, IDataObject **ppDataObject);
STDMETHODIMP GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid, IUnknown **ppunk);
STDMETHODIMP InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd, IDataObject *pDataObject,
TS_TEXTCHANGE *pChange);
STDMETHODIMP RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs);
STDMETHODIMP RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs, DWORD dwFlags);
STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG acpPos, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs,
DWORD dwFlags);
STDMETHODIMP FindNextAttrTransition(LONG acpStart, LONG acpHalt, ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs,
DWORD dwFlags, LONG *pacpNext, BOOL *pfFound, LONG *plFoundOffset);
STDMETHODIMP RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL *paAttrVals, ULONG *pcFetched);
STDMETHODIMP GetEndACP(LONG *pacp);
STDMETHODIMP GetActiveView(TsViewCookie *pvcView);
STDMETHODIMP GetACPFromPoint(TsViewCookie vcView, const POINT *pt, DWORD dwFlags, LONG *pacp);
STDMETHODIMP GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd, RECT *prc, BOOL *pfClipped);
STDMETHODIMP GetScreenExt(TsViewCookie vcView, RECT *prc);
STDMETHODIMP GetWnd(TsViewCookie vcView, HWND *phwnd);
STDMETHODIMP QueryInsertEmbedded(const GUID *pguidService, const FORMATETC *pFormatEtc, BOOL *pfInsertable);
STDMETHODIMP InsertTextAtSelection(DWORD dwFlags, __in_ecount(cch) const WCHAR *pchText, ULONG cch, LONG *pacpStart,
LONG *pacpEnd, TS_TEXTCHANGE *pChange);
STDMETHODIMP InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject *pDataObject, LONG *pacpStart, LONG *pacpEnd,
TS_TEXTCHANGE *pChange);
//
// ITfContextOwnerCompositionSink
//
STDMETHODIMP OnStartComposition(ITfCompositionView *pComposition, BOOL *pfOk);
STDMETHODIMP OnUpdateComposition(ITfCompositionView *pComposition, ITfRange *pRangeNew);
STDMETHODIMP OnEndComposition(ITfCompositionView *pComposition);
//
// ITfMouseTrackerACP
//
STDMETHODIMP AdviseMouseSink(ITfRangeACP *range, ITfMouseSink *pSink, DWORD *pdwCookie);
STDMETHODIMP UnadviseMouseSink(DWORD dwCookie);
void OnSelectionChange()
{
if (_pSink)
_pSink->OnSelectionChange();
}
void OnTextChange(LONG acpStart, LONG acpOldEnd, LONG acpNewEnd)
{
if (_pSink)
{
TS_TEXTCHANGE textChange;
textChange.acpStart = acpStart;
textChange.acpOldEnd = acpOldEnd;
textChange.acpNewEnd = acpNewEnd;
_pSink->OnTextChange(0, &textChange);
}
}
void OnLayoutChange()
{
if (_pSink)
{
_pSink->OnLayoutChange(TS_LC_CHANGE, 0x01);
}
}
ITfCompositionView *GetCurrentCompositionView()
{
return _pCurrentCompositionView;
}
void OnMouseEvent(UINT uEdge, UINT uQuadrant, DWORD dwBtnState, BOOL *pbEaten)
{
*pbEaten = FALSE;
for (UINT i = 1; i < _nMouseSinks; i++)
{
if (_prgMouseSinks->pMouseSink)
{
_prgMouseSinks->pMouseSink->OnMouseEvent(uEdge, uQuadrant, dwBtnState, pbEaten);
if (*pbEaten)
break;
}
}
}
private:
void PrepareAttributes(ULONG cFilterAttrs, const TS_ATTRID *paFilterAttrs);
CTextEditor *_pEditor;
TS_ATTRVAL _attrval[8];
int _nAttrVals;
ITextStoreACPSink *_pSink;
ITfCompositionView *_pCurrentCompositionView;
MOUSESINK *_prgMouseSinks;
UINT _nMouseSinks;
long _cRef;
};
@@ -0,0 +1,58 @@
#include "TsfD2DTextBox.h"
#include "TextInputCtrl.h"
#include "TsfTextServices.h"
TsfD2DTextBox::TsfD2DTextBox()
{
_impl = new CTextInputCtrl();
}
TsfD2DTextBox::~TsfD2DTextBox()
{
delete _impl;
_impl = NULL;
}
ATOM TsfD2DTextBox::RegisterClass(HINSTANCE hInstance)
{
g_hInst = hInstance;
return CTextInputCtrl::RegisterClass(hInstance);
}
HWND TsfD2DTextBox::Create(HINSTANCE hInstance, HWND hwndParent, const RECT &bounds)
{
if (!_impl)
{
return NULL;
}
g_hInst = hInstance;
HWND hwnd = _impl->Create(hwndParent);
if (hwnd)
{
_impl->Move(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top);
}
return hwnd;
}
void TsfD2DTextBox::Move(int x, int y, int width, int height)
{
if (_impl)
{
_impl->Move(x, y, width, height);
}
}
HWND TsfD2DTextBox::GetHwnd() const
{
return _impl ? _impl->GetWnd() : NULL;
}
void TsfD2DTextBox::SetFont(HWND hwndOwner)
{
if (_impl)
{
_impl->SetFont(hwndOwner);
}
}
@@ -0,0 +1,78 @@
#include "TsfTextServices.h"
#include "DisplayAttribute.h"
HINSTANCE g_hInst = NULL;
ITfThreadMgr *g_pThreadMgr = NULL;
TfClientId g_TfClientId = TF_CLIENTID_NULL;
namespace
{
LONG g_serviceRefCount = 0;
}
BOOL InitializeTsfTextServices(HINSTANCE hInstance)
{
if (InterlockedIncrement(&g_serviceRefCount) > 1)
{
g_hInst = hInstance;
return TRUE;
}
g_hInst = hInstance;
if (FAILED(CoCreateInstance(CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr,
reinterpret_cast<void **>(&g_pThreadMgr))))
{
InterlockedDecrement(&g_serviceRefCount);
return FALSE;
}
if (FAILED(g_pThreadMgr->Activate(&g_TfClientId)))
{
g_pThreadMgr->Release();
g_pThreadMgr = NULL;
g_TfClientId = TF_CLIENTID_NULL;
InterlockedDecrement(&g_serviceRefCount);
return FALSE;
}
if (FAILED(InitDisplayAttrbute()))
{
g_pThreadMgr->Deactivate();
g_pThreadMgr->Release();
g_pThreadMgr = NULL;
g_TfClientId = TF_CLIENTID_NULL;
InterlockedDecrement(&g_serviceRefCount);
return FALSE;
}
return TRUE;
}
void UninitializeTsfTextServices()
{
LONG refCount = InterlockedDecrement(&g_serviceRefCount);
if (refCount > 0)
{
return;
}
if (refCount < 0)
{
g_serviceRefCount = 0;
return;
}
UninitDisplayAttrbute();
if (g_pThreadMgr)
{
g_pThreadMgr->Deactivate();
g_pThreadMgr->Release();
g_pThreadMgr = NULL;
}
g_TfClientId = TF_CLIENTID_NULL;
g_hInst = NULL;
}
@@ -0,0 +1,11 @@
#pragma once
#include <Windows.h>
#include <msctf.h>
extern HINSTANCE g_hInst;
extern ITfThreadMgr *g_pThreadMgr;
extern TfClientId g_TfClientId;
BOOL InitializeTsfTextServices(HINSTANCE hInstance);
void UninitializeTsfTextServices();
@@ -0,0 +1,25 @@
#ifndef _PRIVATE_H_
#define _PRIVATE_H_
#define _OLEAUT32_
#include <windows.h>
#include <windowsx.h>
#include <imm.h>
#include <ole2.h>
#include <msctf.h>
#include <rpc.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef STRSAFE_NO_DEPRECATE
#define STRSAFE_NO_DEPRECATE
#endif
#include <strsafe.h>
#include <common.ver>
// #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
#endif // _PRIVATE_H_
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": [
"fmt"
]
}