Init
This commit is contained in:
commit
2bb9ba42a1
29 files changed
+4393
No files matched your search
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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_
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": [
|
||||
"fmt"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user