feat(server,windows,engine): 新增顿号自定义,翻页方式移到快捷键页 (#1071)
- 设置「输入」页在智能标点上方新增「顿号自定义」:\ 和 / 各一个勾,至少保留一个, 未勾选的键按原样输出。新装默认两个都开;升级用户的配置里没有 / 这一项, 模板合并时补成关,保持原来只有 \ 出顿号。 - 新增 worker 消息 EnumerationCommaKeysChanged(38),载荷见 engine/contracts/enumeration_comma.h;旧 DLL 忽略它,保持只用 \。 - 选候选时按 / 改为按配置上屏「候选 + 、」;未作顿号的 \ 或 / 跟在候选后原样上屏。 小键盘 / 与小键盘 . 一样始终输出原字符。 - 「翻页方式」从「输入」页移到「快捷键」页。 - AtLeastOneChecks 从辅助码页移到公共绑定,两页共用。
This commit is contained in:
1 parent
58a27f3edb
commit
75d8c710a9
28 files changed
+350
-60
No files matched your search
@@ -241,7 +241,7 @@ Windows 端的全部一方源码都在本仓,一次 clone 就能拿到完整
|
||||
- **智能标点**(默认关闭,需在设置中开启):中文标点状态下,输入中文标点后按空格会把它转为对应英文标点(如 `,` + 空格 → `,`),再按同一标点键可撤回;设置中可分别开关四个子项:中文标点后按空格转换(`。,!?;:、`,以及单独出现的 `“”` `‘’` `【】` `《》` `()`)、同键转回中文(2 秒内再按同一标点键撤回)、数字后直出(数字后输入 `,` `.` `:` 直接输出英文)、字母后直出(字母后输入 `,` `.` `:` 直接输出英文)。括号引号这些成对标点只在**单独出现**时转换(需关闭「成对标点自动补全」,或输入右半符号);自动补全产生的一对不转换,按空格只落空格。
|
||||
- **成对标点自动补全**:输入左侧括号、引号等成对符号时,自动补上右侧符号,并把光标放在中间。
|
||||
- **以词定字**:开启后,按 `[` 上屏当前高亮候选的第一个汉字,按 `]` 上屏最后一个汉字。
|
||||
- 中文标点状态下,`\` 输入顿号 `、`,反引号输入间隔号 `·`,`Shift + 6` 输入省略号 `……`,`Shift + -` 输入破折号 `——`,`Shift + ,` / `Shift + .` 输入书名号 `《` / `》`。
|
||||
- 中文标点状态下,`\` 输入顿号 `、`,新安装时 `/` 也输入顿号(可在设置「输入 → 顿号自定义」中调整,至少保留一个,未勾选的键按原样输出),反引号输入间隔号 `·`,`Shift + 6` 输入省略号 `……`,`Shift + -` 输入破折号 `——`,`Shift + ,` / `Shift + .` 输入书名号 `《` / `》`。
|
||||
|
||||
### 实用功能快捷模式
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
// 中文标点状态下哪些键输出顿号「、」:反斜杠 \ 和斜杠 /,设置里各一个勾、至少留一个。没勾的键不再是
|
||||
// 中文标点,按原样输出。Server 把这组勾通过 EnumerationCommaKeysChanged 发给 TSF,TSF 据此决定吃不吃键、
|
||||
// 换成什么字。
|
||||
namespace FanyImeEnumerationComma
|
||||
{
|
||||
inline constexpr unsigned kBackslash = 1;
|
||||
inline constexpr unsigned kSlash = 2;
|
||||
// 旧 Server 不发这个消息,TSF 保持历史行为:只有 \ 出顿号。
|
||||
inline constexpr unsigned kLegacyKeys = kBackslash;
|
||||
|
||||
// EnumerationCommaKeysChanged 的载荷(一个字符):'1' 只有 \,'2' 只有 /,'3' 两个都是。两个都没勾不是
|
||||
// 合法配置,按 '1' 发,和 TSF 的历史行为一致。
|
||||
inline constexpr wchar_t PayloadFor(bool backslash, bool slash)
|
||||
{
|
||||
const unsigned keys = (backslash ? kBackslash : 0u) | (slash ? kSlash : 0u);
|
||||
return static_cast<wchar_t>(L'0' + (keys == 0 ? kLegacyKeys : keys));
|
||||
}
|
||||
|
||||
inline constexpr bool IsValidPayload(wchar_t payload)
|
||||
{
|
||||
return payload == L'1' || payload == L'2' || payload == L'3';
|
||||
}
|
||||
|
||||
inline constexpr unsigned KeysFromPayload(wchar_t payload)
|
||||
{
|
||||
return IsValidPayload(payload) ? static_cast<unsigned>(payload - L'0') : kLegacyKeys;
|
||||
}
|
||||
} // namespace FanyImeEnumerationComma
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "../date_time_input.h"
|
||||
#include "../direct_helpcode.h"
|
||||
#include "../enumeration_comma.h"
|
||||
#include "../ipc_negotiation.h"
|
||||
#include "../mid_sentence_helpcode.h"
|
||||
#include "../trilingual_input.h"
|
||||
@@ -44,7 +45,7 @@ int main()
|
||||
CHECK((FanyImeProtocol::Negotiate(FanyImeProtocol::Hello(7, 124), rCapabilities).capabilities &
|
||||
FanyImeProtocol::TemporaryJapaneseLongVowel) == 0);
|
||||
CHECK(FanyImeWorkerReplyType::TemporaryJapanesePolicyChanged == 36);
|
||||
CHECK(FanyImeWorkerReplyType::MaxKnown == 37);
|
||||
CHECK(FanyImeWorkerReplyType::MaxKnown == 38);
|
||||
for (wchar_t payload = L'0'; payload <= L'3'; ++payload)
|
||||
{
|
||||
CHECK(FanyImeTemporaryJapanese::IsValidPayload(payload));
|
||||
@@ -239,7 +240,17 @@ int main()
|
||||
CHECK(FanyImeWorkerReplyType::UiLessCandidatesChanged == 35);
|
||||
CHECK(FanyImeWorkerReplyType::TemporaryJapanesePolicyChanged == 36);
|
||||
CHECK(FanyImeWorkerReplyType::EndHelpcodeTriggersChanged == 37);
|
||||
CHECK(FanyImeWorkerReplyType::MaxKnown == FanyImeWorkerReplyType::EndHelpcodeTriggersChanged);
|
||||
CHECK(FanyImeWorkerReplyType::EnumerationCommaKeysChanged == 38);
|
||||
CHECK(FanyImeWorkerReplyType::MaxKnown == FanyImeWorkerReplyType::EnumerationCommaKeysChanged);
|
||||
// EnumerationCommaKeysChanged 的载荷:\ 和 / 各一位,两个都没勾按只有 \ 发。
|
||||
CHECK(FanyImeEnumerationComma::PayloadFor(true, false) == L'1' &&
|
||||
FanyImeEnumerationComma::PayloadFor(false, true) == L'2' &&
|
||||
FanyImeEnumerationComma::PayloadFor(true, true) == L'3' &&
|
||||
FanyImeEnumerationComma::PayloadFor(false, false) == L'1');
|
||||
CHECK(FanyImeEnumerationComma::KeysFromPayload(L'3') ==
|
||||
(FanyImeEnumerationComma::kBackslash | FanyImeEnumerationComma::kSlash) &&
|
||||
!FanyImeEnumerationComma::IsValidPayload(L'0') && !FanyImeEnumerationComma::IsValidPayload(L'4') &&
|
||||
FanyImeEnumerationComma::KeysFromPayload(L'x') == FanyImeEnumerationComma::kBackslash);
|
||||
{
|
||||
const auto full = FanyImeUiLessPage::Parse(FanyImeUiLessPage::Format(L"ni hao", L"你好,拟好", 1, 42));
|
||||
CHECK(full.preedit == L"ni hao" && full.page == L"你好,拟好" && full.selection == 1 && full.generation == 42);
|
||||
|
||||
@@ -463,7 +463,11 @@ constexpr std::uint32_t TemporaryJapanesePolicyChanged = 36;
|
||||
// AcceptsEndMarkerAt) and records them as FanyImeMidSentenceHelpcode::kEndMarker; with the uppercase bit it counts an
|
||||
// uppercase end block as no keys of the chunk. Older DLLs ignore the opcode and keep treating the keys as punctuation.
|
||||
constexpr std::uint32_t EndHelpcodeTriggersChanged = 37;
|
||||
constexpr std::uint32_t MaxKnown = EndHelpcodeTriggersChanged;
|
||||
// Which keys produce the enumeration comma 、 under Chinese punctuation. Payload one character '1'..'3', see
|
||||
// FanyImeEnumerationComma::PayloadFor: bit 1 '\', bit 2 '/'. A key whose bit is clear is not Chinese punctuation and
|
||||
// passes through as itself. Older DLLs ignore the opcode and keep '\' only.
|
||||
constexpr std::uint32_t EnumerationCommaKeysChanged = 38;
|
||||
constexpr std::uint32_t MaxKnown = EnumerationCommaKeysChanged;
|
||||
// Source compatibility for the Server's historical spellings.
|
||||
constexpr std::uint32_t SwitchToEn = SwitchToEnglish;
|
||||
constexpr std::uint32_t SwitchToCn = SwitchToChinese;
|
||||
|
||||
@@ -279,6 +279,9 @@ smart_punctuation_direct_letter = false
|
||||
smart_punctuation_repeat_to_chinese = false
|
||||
# 成对标点补全:输入左侧符号时自动补全右侧符号,并将光标置于中间
|
||||
paired_punctuation = true
|
||||
# 顿号自定义:中文标点下 \ 和 / 是否输出顿号「、」,至少保留一个;没开的键按原样输出
|
||||
enumeration_comma_backslash = true
|
||||
enumeration_comma_slash = true
|
||||
# 标点锁定,可选:"follow"(随中英文切换), "chinese"(始终中文标点), "english"(始终英文标点)
|
||||
punctuation_lock = "follow"
|
||||
# Vim 模式:在列出的应用里,交给应用的无修饰键 Esc 同时把输入法切到英文。按可执行文件名匹配,不区分大小写,
|
||||
|
||||
@@ -280,6 +280,9 @@ smart_punctuation_direct_letter = false
|
||||
smart_punctuation_repeat_to_chinese = false
|
||||
# 成对标点补全:输入左侧符号时自动补全右侧符号,并将光标置于中间
|
||||
paired_punctuation = true
|
||||
# 顿号自定义:中文标点下 \ 和 / 是否输出顿号「、」,至少保留一个;没开的键按原样输出
|
||||
enumeration_comma_backslash = true
|
||||
enumeration_comma_slash = true
|
||||
# 标点锁定,可选:"follow"(随中英文切换), "chinese"(始终中文标点), "english"(始终英文标点)
|
||||
punctuation_lock = "follow"
|
||||
# Vim 模式:在列出的应用里,交给应用的无修饰键 Esc 同时把输入法切到英文。按可执行文件名匹配,不区分大小写,
|
||||
|
||||
@@ -205,6 +205,8 @@ bool g_smart_punctuation_direct_digit_enabled = false;
|
||||
bool g_smart_punctuation_direct_letter_enabled = false;
|
||||
bool g_smart_punctuation_repeat_to_chinese_enabled = false;
|
||||
bool g_paired_punctuation_enabled = true;
|
||||
bool g_enumeration_comma_backslash_enabled = true;
|
||||
bool g_enumeration_comma_slash_enabled = true;
|
||||
std::string g_punctuation_lock = "follow";
|
||||
std::string g_candidate_window_layout = "vertical";
|
||||
bool g_candidate_window_follow_cursor = true;
|
||||
@@ -693,6 +695,14 @@ bool LoadImeConfig()
|
||||
g_smart_punctuation_repeat_to_chinese_enabled =
|
||||
tbl["input"]["smart_punctuation_repeat_to_chinese"].value_or(false);
|
||||
g_paired_punctuation_enabled = tbl["input"]["paired_punctuation"].value_or(true);
|
||||
g_enumeration_comma_backslash_enabled = tbl["input"]["enumeration_comma_backslash"].value_or(true);
|
||||
g_enumeration_comma_slash_enabled = tbl["input"]["enumeration_comma_slash"].value_or(true);
|
||||
// 至少留一个出顿号的键:手改成两个都关时按默认处理。
|
||||
if (!g_enumeration_comma_backslash_enabled && !g_enumeration_comma_slash_enabled)
|
||||
{
|
||||
g_enumeration_comma_backslash_enabled = true;
|
||||
g_enumeration_comma_slash_enabled = true;
|
||||
}
|
||||
{
|
||||
const std::string punctuation_lock = tbl["input"]["punctuation_lock"].value_or(std::string("follow"));
|
||||
g_punctuation_lock =
|
||||
|
||||
@@ -621,6 +621,14 @@ bool GetConfiguredSmartPunctuationRepeatToChineseEnabled();
|
||||
bool SetConfiguredSmartPunctuationRepeatToChineseEnabled(bool enabled);
|
||||
bool GetConfiguredPairedPunctuationEnabled();
|
||||
bool SetConfiguredPairedPunctuationEnabled(bool enabled);
|
||||
// 顿号自定义:中文标点下 \(input.enumeration_comma_backslash)和 /(input.enumeration_comma_slash)是否出「、」,
|
||||
// 新装默认都开(升级用户的 / 由模板合并补成关),至少留一个,关掉最后一个的写入不收。
|
||||
bool GetConfiguredEnumerationCommaBackslashEnabled();
|
||||
bool SetConfiguredEnumerationCommaBackslashEnabled(bool enabled);
|
||||
bool GetConfiguredEnumerationCommaSlashEnabled();
|
||||
bool SetConfiguredEnumerationCommaSlashEnabled(bool enabled);
|
||||
// Worker payload for EnumerationCommaKeysChanged (FanyImeEnumerationComma::PayloadFor).
|
||||
std::wstring FormatEnumerationCommaWorkerPayload();
|
||||
// "follow" | "chinese" | "english" — punctuation stays put when switching CN/EN.
|
||||
const std::string &GetConfiguredPunctuationLock();
|
||||
bool SetConfiguredPunctuationLock(const std::string &lock);
|
||||
|
||||
@@ -246,6 +246,8 @@ extern bool g_smart_punctuation_direct_digit_enabled;
|
||||
extern bool g_smart_punctuation_direct_letter_enabled;
|
||||
extern bool g_smart_punctuation_repeat_to_chinese_enabled;
|
||||
extern bool g_paired_punctuation_enabled;
|
||||
extern bool g_enumeration_comma_backslash_enabled;
|
||||
extern bool g_enumeration_comma_slash_enabled;
|
||||
extern std::string g_punctuation_lock;
|
||||
extern std::string g_candidate_window_layout;
|
||||
extern bool g_candidate_window_follow_cursor;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 按键行为配置:翻页键、方向键选词、以词定字、智能标点、配对标点与标点锁定,以及发给 TSF 的对应载荷。
|
||||
// 按键行为配置:翻页键、方向键选词、以词定字、智能标点、配对标点、顿号键与标点锁定,以及发给 TSF 的对应载荷。
|
||||
#include "config/ime_config_internal.h"
|
||||
#include <string>
|
||||
#include "engine/contracts/enumeration_comma.h"
|
||||
#include "utils/common_utils.h"
|
||||
#include "global/globals.h"
|
||||
|
||||
@@ -254,6 +255,51 @@ bool SetConfiguredPairedPunctuationEnabled(bool enabled)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetConfiguredEnumerationCommaBackslashEnabled()
|
||||
{
|
||||
return g_enumeration_comma_backslash_enabled;
|
||||
}
|
||||
|
||||
bool SetConfiguredEnumerationCommaBackslashEnabled(bool enabled)
|
||||
{
|
||||
// 至少留一个:设置页取消不掉最后一个,这里同样不收。
|
||||
if (!enabled && !g_enumeration_comma_slash_enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!WriteConfiguredValue("input", "enumeration_comma_backslash", enabled ? "true" : "false"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
g_enumeration_comma_backslash_enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetConfiguredEnumerationCommaSlashEnabled()
|
||||
{
|
||||
return g_enumeration_comma_slash_enabled;
|
||||
}
|
||||
|
||||
bool SetConfiguredEnumerationCommaSlashEnabled(bool enabled)
|
||||
{
|
||||
if (!enabled && !g_enumeration_comma_backslash_enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!WriteConfiguredValue("input", "enumeration_comma_slash", enabled ? "true" : "false"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
g_enumeration_comma_slash_enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring FormatEnumerationCommaWorkerPayload()
|
||||
{
|
||||
return std::wstring(1, FanyImeEnumerationComma::PayloadFor(g_enumeration_comma_backslash_enabled,
|
||||
g_enumeration_comma_slash_enabled));
|
||||
}
|
||||
|
||||
const std::string &GetConfiguredPunctuationLock()
|
||||
{
|
||||
return g_punctuation_lock;
|
||||
|
||||
@@ -107,6 +107,12 @@ std::string MergeTomlIntoTemplate(const std::string &template_text, std::map<std
|
||||
{
|
||||
user_values[radical_mode_id] = unicode_mode->second;
|
||||
}
|
||||
// 顿号自定义加 / 时,老配置里没有这个键:升级用户保持原来只有 \ 出顿号,/ 默认开只给新装用户。
|
||||
const auto enumeration_comma_slash_id = MakeTomlAssignmentId("input", "enumeration_comma_slash");
|
||||
if (!user_values.empty() && user_values.find(enumeration_comma_slash_id) == user_values.end())
|
||||
{
|
||||
user_values[enumeration_comma_slash_id] = "false";
|
||||
}
|
||||
struct ValuePatch
|
||||
{
|
||||
size_t begin;
|
||||
|
||||
@@ -865,6 +865,9 @@ void RegisteredPipeMonitorThread(HANDLE clientPipe, UINT pipeRole, uint64_t hand
|
||||
SendToTsfWorkerThreadClientViaNamedpipe(
|
||||
hello.client_id, Global::DataFromServerMsgTypeToTsfWorkerThread::PairedPunctuationChanged,
|
||||
GetConfiguredPairedPunctuationEnabled() ? L"1" : L"0");
|
||||
SendToTsfWorkerThreadClientViaNamedpipe(
|
||||
hello.client_id, Global::DataFromServerMsgTypeToTsfWorkerThread::EnumerationCommaKeysChanged,
|
||||
FormatEnumerationCommaWorkerPayload());
|
||||
SendToTsfWorkerThreadClientViaNamedpipe(
|
||||
hello.client_id, Global::DataFromServerMsgTypeToTsfWorkerThread::MicrosoftShuangpinChanged,
|
||||
IsConfiguredShuangpinSemicolonFinal() ? L"1" : L"0");
|
||||
|
||||
@@ -249,40 +249,6 @@ std::shared_ptr<Dropdown> SchemeDropdown(PageContext &context, std::string path)
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
// A group of config checkboxes where the last checked one cannot be unchecked.
|
||||
std::vector<std::shared_ptr<CheckOption>> AtLeastOneChecks(
|
||||
PageContext &context, const std::vector<std::pair<std::wstring, std::string>> &items)
|
||||
{
|
||||
auto checks = std::make_shared<std::vector<std::shared_ptr<CheckOption>>>();
|
||||
SettingsModel *model = &context.model;
|
||||
for (size_t i = 0; i < items.size(); ++i)
|
||||
{
|
||||
auto check = std::make_shared<CheckOption>(items[i].first, model->GetBool(items[i].second));
|
||||
const std::string path = items[i].second;
|
||||
// The group outlives none of its checks: the page and its controls live as long as
|
||||
// the window, so the checks simply share it.
|
||||
std::vector<std::shared_ptr<CheckOption>> *all = checks.get();
|
||||
CheckOption *self = check.get();
|
||||
check->SetOnChanged([checks, all, self, model, path](bool checked) {
|
||||
const bool anyChecked =
|
||||
std::any_of(all->begin(), all->end(), [](const auto &item) { return item->IsChecked(); });
|
||||
if (!anyChecked)
|
||||
{
|
||||
self->SetChecked(true);
|
||||
return;
|
||||
}
|
||||
model->Update(path, checked);
|
||||
});
|
||||
std::weak_ptr<CheckOption> weak = check;
|
||||
context.OnConfig([weak, model, path]() {
|
||||
if (auto control = weak.lock())
|
||||
control->SetChecked(model->GetBool(path));
|
||||
});
|
||||
checks->push_back(check);
|
||||
}
|
||||
return *checks;
|
||||
}
|
||||
|
||||
// The trigger keys of the shuangpin end helpcode (indirect) and the mid-sentence helpcode (indirect). Both
|
||||
// groups list the same keys in the same order, and a key belongs to one group at most: checking it on one
|
||||
// side unchecks it on the other, which the host does too (SetHelpcodeTrigger), even when that was the
|
||||
|
||||
@@ -1669,20 +1669,6 @@ VisualPtr BuildInputPage(PageContext &context)
|
||||
false);
|
||||
}
|
||||
|
||||
// ---- 翻页方式 ----
|
||||
{
|
||||
auto list = OptionList({BindCheck(context, L"- / =", "general.paging_minus_equal"),
|
||||
BindCheck(context, L", / .", "general.paging_comma_period"),
|
||||
BindCheck(context, L"[ / ]", "general.paging_brackets"),
|
||||
BindCheck(context, L"Shift+Tab / Tab", "general.paging_tab"),
|
||||
BindCheck(context, L"PageUp / PageDown", "general.paging_page_up_down"),
|
||||
BindCheck(context, L"上 / 下(移动候选项)", "general.candidate_arrow_navigation"),
|
||||
BindCheck(context, L"鼠标滚轮", "general.paging_mouse_wheel")});
|
||||
list->Add(
|
||||
Description(L"滚轮翻页依赖系统设置「当我悬停在非活动窗口上方时滚动它们」,该项关闭时滚轮不会到达候选窗口"));
|
||||
page.AddSection(Col({MakeTitle(L"翻页方式"), list}));
|
||||
}
|
||||
|
||||
// ---- 输入纠错 ----
|
||||
page.AddSection(
|
||||
Col({MakeTitleBlock(
|
||||
@@ -1882,6 +1868,17 @@ VisualPtr BuildInputPage(PageContext &context)
|
||||
OptionList({follow, chinese, english})}));
|
||||
}
|
||||
|
||||
// ---- 顿号自定义 ----
|
||||
{
|
||||
std::vector<VisualPtr> keys;
|
||||
for (auto &check : AtLeastOneChecks(context, {{L"\\ 反斜杠", "input.enumeration_comma_backslash"},
|
||||
{L"/ 斜杠", "input.enumeration_comma_slash"}}))
|
||||
keys.push_back(check);
|
||||
page.AddSection(Col({MakeTitle(L"顿号自定义"),
|
||||
Description(L"中文标点状态下用哪些键输出顿号「、」,至少保留一个;未勾选的键按原样输出"),
|
||||
OptionList(keys)}));
|
||||
}
|
||||
|
||||
// ---- 智能标点 ----
|
||||
{
|
||||
auto master = BindToggle(context, "input.smart_punctuation");
|
||||
|
||||
@@ -1002,7 +1002,7 @@ VisualPtr BuildShortcutPage(PageContext &context)
|
||||
SettingsModel *model = &context.model;
|
||||
|
||||
// .shortcut-intro: 13px / 1.6, muted, margin -4px 8px 18px
|
||||
auto intro = std::make_shared<Label>(L"各快捷键的生效范围见下方说明。翻页方式可在“输入”中启用或关闭。",
|
||||
auto intro = std::make_shared<Label>(L"各快捷键的生效范围见下方说明。",
|
||||
Font(13.0f, DWRITE_FONT_WEIGHT_NORMAL, 1.6f, true), TextRole::Muted);
|
||||
page.AddRaw(intro, 18.0f);
|
||||
intro->SetMargin({8.0f, -4.0f, 8.0f, 18.0f});
|
||||
@@ -1091,6 +1091,21 @@ VisualPtr BuildShortcutPage(PageContext &context)
|
||||
});
|
||||
}
|
||||
|
||||
// 翻页方式
|
||||
{
|
||||
auto column = std::make_shared<Column>(0.0f);
|
||||
column->Add(Heading(L"翻页方式", L"勾选启用对应的候选翻页键。滚轮翻页依赖系统设置「当我悬停在非活动窗口上方时"
|
||||
L"滚动它们」,该项关闭时滚轮不会到达候选窗口"));
|
||||
column->Add(Options({BindCheck(context, L"- / =", "general.paging_minus_equal"),
|
||||
BindCheck(context, L", / .", "general.paging_comma_period"),
|
||||
BindCheck(context, L"[ / ]", "general.paging_brackets"),
|
||||
BindCheck(context, L"Shift+Tab / Tab", "general.paging_tab"),
|
||||
BindCheck(context, L"PageUp / PageDown", "general.paging_page_up_down"),
|
||||
BindCheck(context, L"上 / 下(移动候选项)", "general.candidate_arrow_navigation"),
|
||||
BindCheck(context, L"鼠标滚轮", "general.paging_mouse_wheel")}));
|
||||
page.AddRaw(std::make_shared<FlatSection>(column), 18.0f);
|
||||
}
|
||||
|
||||
// 候选操作
|
||||
{
|
||||
auto column = std::make_shared<Column>(0.0f);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "utils/common_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace native_settings
|
||||
@@ -133,6 +134,39 @@ std::shared_ptr<CheckOption> BindCheck(PageContext &context, std::wstring label,
|
||||
return check;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<CheckOption>> AtLeastOneChecks(
|
||||
PageContext &context, const std::vector<std::pair<std::wstring, std::string>> &items)
|
||||
{
|
||||
auto checks = std::make_shared<std::vector<std::shared_ptr<CheckOption>>>();
|
||||
SettingsModel *model = &context.model;
|
||||
for (size_t i = 0; i < items.size(); ++i)
|
||||
{
|
||||
auto check = std::make_shared<CheckOption>(items[i].first, model->GetBool(items[i].second));
|
||||
const std::string path = items[i].second;
|
||||
// The group outlives none of its checks: the page and its controls live as long as
|
||||
// the window, so the checks simply share it.
|
||||
std::vector<std::shared_ptr<CheckOption>> *all = checks.get();
|
||||
CheckOption *self = check.get();
|
||||
check->SetOnChanged([checks, all, self, model, path](bool checked) {
|
||||
const bool anyChecked =
|
||||
std::any_of(all->begin(), all->end(), [](const auto &item) { return item->IsChecked(); });
|
||||
if (!anyChecked)
|
||||
{
|
||||
self->SetChecked(true);
|
||||
return;
|
||||
}
|
||||
model->Update(path, checked);
|
||||
});
|
||||
std::weak_ptr<CheckOption> weak = check;
|
||||
context.OnConfig([weak, model, path]() {
|
||||
if (auto control = weak.lock())
|
||||
control->SetChecked(model->GetBool(path));
|
||||
});
|
||||
checks->push_back(check);
|
||||
}
|
||||
return *checks;
|
||||
}
|
||||
|
||||
std::shared_ptr<TextField> BindText(PageContext &context, std::string path, TextField::Options options)
|
||||
{
|
||||
auto field = std::make_shared<TextField>(std::move(options));
|
||||
|
||||
@@ -75,6 +75,9 @@ std::shared_ptr<Dropdown> BindDropdown(PageContext &context, std::string path, s
|
||||
std::function<void(const std::string &value)> afterChange = {});
|
||||
std::shared_ptr<CheckOption> BindCheck(PageContext &context, std::wstring label, std::string path,
|
||||
std::function<void(bool checked)> afterChange = {});
|
||||
// A group of (label, path) config checkboxes where the last checked one cannot be unchecked.
|
||||
std::vector<std::shared_ptr<CheckOption>> AtLeastOneChecks(
|
||||
PageContext &context, const std::vector<std::pair<std::wstring, std::string>> &items);
|
||||
// Writes on commit (blur / Enter), like the page's `change` listeners.
|
||||
std::shared_ptr<TextField> BindText(PageContext &context, std::string path, TextField::Options options);
|
||||
std::shared_ptr<NumberStepper> BindStepper(PageContext &context, std::string path, int minimum, int maximum,
|
||||
|
||||
@@ -334,6 +334,8 @@ std::wstring BuildConfigMessage(bool refresh_skin_catalog)
|
||||
{"smart_punctuation_direct_letter", GetConfiguredSmartPunctuationDirectLetterEnabled()},
|
||||
{"smart_punctuation_repeat_to_chinese", GetConfiguredSmartPunctuationRepeatToChineseEnabled()},
|
||||
{"paired_punctuation", GetConfiguredPairedPunctuationEnabled()},
|
||||
{"enumeration_comma_backslash", GetConfiguredEnumerationCommaBackslashEnabled()},
|
||||
{"enumeration_comma_slash", GetConfiguredEnumerationCommaSlashEnabled()},
|
||||
{"punctuation_lock", GetConfiguredPunctuationLock()},
|
||||
{"fuzzy_pinyin", GetConfiguredFuzzyPinyinEnabled()},
|
||||
{"fuzzy_z_zh", fuzzy_rules.enabled(metasequoia::FuzzyPinyinRule::Z_ZH)},
|
||||
@@ -677,6 +679,10 @@ bool ApplyConfigUpdate(const json::object &data)
|
||||
return SetConfiguredSmartPunctuationRepeatToChineseEnabled(json::value_to<bool>(data.at("value")));
|
||||
if (path == "input.paired_punctuation")
|
||||
return SetConfiguredPairedPunctuationEnabled(json::value_to<bool>(data.at("value")));
|
||||
if (path == "input.enumeration_comma_backslash")
|
||||
return SetConfiguredEnumerationCommaBackslashEnabled(json::value_to<bool>(data.at("value")));
|
||||
if (path == "input.enumeration_comma_slash")
|
||||
return SetConfiguredEnumerationCommaSlashEnabled(json::value_to<bool>(data.at("value")));
|
||||
if (path == "input.punctuation_lock")
|
||||
return SetConfiguredPunctuationLock(json::value_to<std::string>(data.at("value")));
|
||||
constexpr std::string_view input_section_prefix = "input.";
|
||||
|
||||
@@ -465,6 +465,7 @@ LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARA
|
||||
const bool previous_smart_punctuation_repeat_to_chinese =
|
||||
GetConfiguredSmartPunctuationRepeatToChineseEnabled();
|
||||
const bool previous_paired_punctuation = GetConfiguredPairedPunctuationEnabled();
|
||||
const std::wstring previous_enumeration_comma = FormatEnumerationCommaWorkerPayload();
|
||||
const std::string previous_punctuation_lock = GetConfiguredPunctuationLock();
|
||||
const bool previous_tsf_diagnostic_log = GetConfiguredTsfDiagnosticLogEnabled();
|
||||
const bool previous_statistics_enabled = GetConfiguredStatisticsEnabled();
|
||||
@@ -614,6 +615,12 @@ LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARA
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::PairedPunctuationChanged,
|
||||
GetConfiguredPairedPunctuationEnabled() ? L"1" : L"0");
|
||||
}
|
||||
if (previous_enumeration_comma != FormatEnumerationCommaWorkerPayload())
|
||||
{
|
||||
BroadcastToTsfWorkerThreadViaNamedpipe(
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::EnumerationCommaKeysChanged,
|
||||
FormatEnumerationCommaWorkerPayload());
|
||||
}
|
||||
if (previous_punctuation_lock != GetConfiguredPunctuationLock())
|
||||
{
|
||||
BroadcastToTsfWorkerThreadViaNamedpipe(
|
||||
|
||||
@@ -1207,6 +1207,62 @@ TEST_CASE(shipped_template_carries_smart_punctuation_conversion_keys)
|
||||
REQUIRE(!parsed["input"]["smart_punctuation_repeat_to_chinese"].value_or(true));
|
||||
}
|
||||
|
||||
// 顿号自定义:\ 和 / 默认都出顿号,至少留一个(setter 拒绝关掉最后一个,手改成两个都关时按默认读),
|
||||
// 往返落盘,出厂模板带着两个键。
|
||||
TEST_CASE(enumeration_comma_keys_default_and_keep_at_least_one)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
const fs::path unique_root =
|
||||
fs::temp_directory_path() / (L"msime-顿号键测试-" + std::to_wstring(GetCurrentProcessId()));
|
||||
const fs::path local_app_data = unique_root / L"profile";
|
||||
const fs::path data_dir = local_app_data / L"metasequoiaime";
|
||||
|
||||
std::error_code ec;
|
||||
fs::remove_all(unique_root, ec);
|
||||
|
||||
SeedTemplate(data_dir);
|
||||
|
||||
{
|
||||
ScopedConfigLocation local_app_data_env(local_app_data);
|
||||
|
||||
InitImeConfig();
|
||||
REQUIRE(GetConfiguredEnumerationCommaBackslashEnabled());
|
||||
REQUIRE(GetConfiguredEnumerationCommaSlashEnabled());
|
||||
REQUIRE(FormatEnumerationCommaWorkerPayload() == L"3");
|
||||
|
||||
REQUIRE(SetConfiguredEnumerationCommaSlashEnabled(false));
|
||||
REQUIRE(FormatEnumerationCommaWorkerPayload() == L"1");
|
||||
// 只剩 \ 时关不掉它。
|
||||
REQUIRE(!SetConfiguredEnumerationCommaBackslashEnabled(false));
|
||||
REQUIRE(GetConfiguredEnumerationCommaBackslashEnabled());
|
||||
|
||||
InitImeConfig();
|
||||
REQUIRE(GetConfiguredEnumerationCommaBackslashEnabled());
|
||||
REQUIRE(!GetConfiguredEnumerationCommaSlashEnabled());
|
||||
REQUIRE(ReadText(data_dir / L"config.toml").find("enumeration_comma_slash = false") != std::string::npos);
|
||||
|
||||
REQUIRE(SetConfiguredEnumerationCommaSlashEnabled(true));
|
||||
REQUIRE(SetConfiguredEnumerationCommaBackslashEnabled(false));
|
||||
REQUIRE(FormatEnumerationCommaWorkerPayload() == L"2");
|
||||
|
||||
// 手改成两个都关:按默认(两个都开)读。
|
||||
WriteText(data_dir / L"config.toml",
|
||||
"[input]\nenumeration_comma_backslash = false\nenumeration_comma_slash = false\n");
|
||||
InitImeConfig();
|
||||
REQUIRE(GetConfiguredEnumerationCommaBackslashEnabled());
|
||||
REQUIRE(GetConfiguredEnumerationCommaSlashEnabled());
|
||||
}
|
||||
|
||||
fs::remove_all(unique_root, ec);
|
||||
|
||||
std::ifstream input(MSIME_DEFAULT_CONFIG_PATH, std::ios::binary);
|
||||
REQUIRE(static_cast<bool>(input));
|
||||
const std::string installed((std::istreambuf_iterator<char>(input)), {});
|
||||
const auto parsed = toml::parse(installed);
|
||||
REQUIRE(parsed["input"]["enumeration_comma_backslash"].value_or(false));
|
||||
REQUIRE(parsed["input"]["enumeration_comma_slash"].value_or(false));
|
||||
}
|
||||
|
||||
// 统计总开关:默认关闭(无文件 / 出厂模板 / 模板缺段三形态),能落盘并在重读后保持。
|
||||
// 出厂模板若不给 [statistics] 段,首次打开开关也必须成功——setter 不能假设段存在,
|
||||
// 否则设置页会报「保存失败」(先例:全拼纠错的 [quanpin] 段)。
|
||||
|
||||
@@ -33,6 +33,29 @@ TEST_CASE(radical_mode_upgrade_inherits_renamed_unicode_mode_switch)
|
||||
.find("radical_mode = true") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE(enumeration_comma_slash_defaults_on_only_for_new_installs)
|
||||
{
|
||||
std::ifstream input(MSIME_DEFAULT_CONFIG_PATH, std::ios::binary);
|
||||
REQUIRE(static_cast<bool>(input));
|
||||
const std::string installed((std::istreambuf_iterator<char>(input)), {});
|
||||
// 新装:没有旧配置,出厂模板里 \ 和 / 都开。
|
||||
const auto fresh = toml::parse(MergeConfigIntoTemplate(installed, "", ""));
|
||||
REQUIRE(fresh["input"]["enumeration_comma_backslash"].value_or(false));
|
||||
REQUIRE(fresh["input"]["enumeration_comma_slash"].value_or(false));
|
||||
// 升级:旧配置没有这两个键,保持原来只有 \ 出顿号。
|
||||
const std::string legacy = "[input]\npaired_punctuation = true\n";
|
||||
const auto upgraded = toml::parse(MergeConfigIntoTemplate(installed, legacy, legacy));
|
||||
REQUIRE(upgraded["input"]["enumeration_comma_backslash"].value_or(false));
|
||||
REQUIRE(!upgraded["input"]["enumeration_comma_slash"].value_or(true));
|
||||
// 已经有这个键(新装后的下一次升级,或用户在设置页改过)时以用户的值为准。
|
||||
const std::string chosen = legacy + "enumeration_comma_slash = true\n";
|
||||
REQUIRE(
|
||||
toml::parse(MergeConfigIntoTemplate(installed, chosen, installed))["input"]["enumeration_comma_slash"].value_or(
|
||||
false));
|
||||
const auto again = MergeConfigIntoTemplate(installed, installed, installed);
|
||||
REQUIRE(toml::parse(again)["input"]["enumeration_comma_slash"].value_or(false));
|
||||
}
|
||||
|
||||
TEST_CASE(shortcut_config_upgrade_preserves_word_selection_and_adds_defaults)
|
||||
{
|
||||
const std::string old_config =
|
||||
|
||||
@@ -68,7 +68,8 @@ TEST_CASE(ipc_pipe_ready_is_a_distinct_server_reply)
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::UiLessCandidatesChanged, 35u);
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::TemporaryJapanesePolicyChanged, 36u);
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::EndHelpcodeTriggersChanged, 37u);
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::MaxKnown, 37u);
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::EnumerationCommaKeysChanged, 38u);
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::MaxKnown, 38u);
|
||||
REQUIRE(Global::DataFromServerMsgTypeToTsfWorkerThread::FocusSessionReady >
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::PagingCommaPeriodChanged);
|
||||
REQUIRE(Global::DataFromServerMsgTypeToTsfWorkerThread::PipeReady >
|
||||
@@ -125,8 +126,10 @@ TEST_CASE(ipc_pipe_ready_is_a_distinct_server_reply)
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::AiContextUploadChanged);
|
||||
REQUIRE(Global::DataFromServerMsgTypeToTsfWorkerThread::EndHelpcodeTriggersChanged >
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::TemporaryJapanesePolicyChanged);
|
||||
REQUIRE(Global::DataFromServerMsgTypeToTsfWorkerThread::EnumerationCommaKeysChanged >
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::EndHelpcodeTriggersChanged);
|
||||
REQUIRE_EQ(Global::DataFromServerMsgTypeToTsfWorkerThread::MaxKnown,
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::EndHelpcodeTriggersChanged);
|
||||
Global::DataFromServerMsgTypeToTsfWorkerThread::EnumerationCommaKeysChanged);
|
||||
}
|
||||
|
||||
TEST_CASE(ipc_client_suspension_is_a_distinct_nonterminal_route_reset)
|
||||
|
||||
@@ -65,6 +65,28 @@ bool IsSystemCtrlSpaceToggleHeld()
|
||||
return (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0 && (GetAsyncKeyState(VK_SPACE) & 0x8000) != 0 &&
|
||||
(GetAsyncKeyState(VK_MENU) & 0x8000) == 0;
|
||||
}
|
||||
|
||||
// \ and / produce 、 as configured (engine/contracts/enumeration_comma.h). Returns false for
|
||||
// any other key, which then goes through the punctuation tables.
|
||||
bool ResolveEnumerationCommaKey(WCHAR wch, _Out_ bool *isEnumerationComma)
|
||||
{
|
||||
unsigned bit = 0;
|
||||
if (wch == L'\\')
|
||||
{
|
||||
bit = FanyImeEnumerationComma::kBackslash;
|
||||
}
|
||||
else if (wch == L'/')
|
||||
{
|
||||
bit = FanyImeEnumerationComma::kSlash;
|
||||
}
|
||||
else
|
||||
{
|
||||
*isEnumerationComma = false;
|
||||
return false;
|
||||
}
|
||||
*isEnumerationComma = (Global::EnumerationCommaKeys.load(std::memory_order_relaxed) & bit) != 0;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//+---------------------------------------------------------------------------
|
||||
@@ -75,6 +97,12 @@ bool IsSystemCtrlSpaceToggleHeld()
|
||||
|
||||
BOOL CCompositionProcessorEngine::IsPunctuation(WCHAR wch)
|
||||
{
|
||||
bool isEnumerationComma = false;
|
||||
if (ResolveEnumerationCommaKey(wch, &isEnumerationComma))
|
||||
{
|
||||
return isEnumerationComma ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(Global::PunctuationTable); i++)
|
||||
{
|
||||
if (Global::PunctuationTable[i]._Code == wch)
|
||||
@@ -117,6 +145,12 @@ BOOL CCompositionProcessorEngine::IsPunctuation(WCHAR wch)
|
||||
|
||||
const WCHAR *CCompositionProcessorEngine::GetPunctuation(WCHAR wch)
|
||||
{
|
||||
bool isEnumerationComma = false;
|
||||
if (ResolveEnumerationCommaKey(wch, &isEnumerationComma))
|
||||
{
|
||||
return isEnumerationComma ? L"、" : nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(Global::PunctuationTable); i++)
|
||||
{
|
||||
if (Global::PunctuationTable[i]._Code == wch)
|
||||
|
||||
@@ -455,6 +455,10 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
|
||||
{
|
||||
validFrame = FanyImeEndHelpcode::IsValidPayload(buf.data[0]) && buf.data[1] == L'\0';
|
||||
}
|
||||
if (validFrame && buf.msg_type == Global::DataToTsfWorkerThreadMsgType::EnumerationCommaKeysChanged)
|
||||
{
|
||||
validFrame = FanyImeEnumerationComma::IsValidPayload(buf.data[0]) && buf.data[1] == L'\0';
|
||||
}
|
||||
if (validFrame && (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::UpdateVoiceComposition ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::CommitVoiceComposition))
|
||||
{
|
||||
@@ -512,6 +516,7 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::DirectHelpcodeChanged ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::MidSentenceHelpcodeUppercaseChanged ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::EndHelpcodeTriggersChanged ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::EnumerationCommaKeysChanged ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::InputModeChanged ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::TrilingualCycleChanged ||
|
||||
buf.msg_type == Global::DataToTsfWorkerThreadMsgType::CapsLockChanged ||
|
||||
@@ -664,6 +669,11 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
|
||||
Global::EndHelpcodeTriggers.store(FanyImeEndHelpcode::FlagsFromPayload(buf.data[0]),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::EnumerationCommaKeysChanged)
|
||||
{
|
||||
Global::EnumerationCommaKeys.store(FanyImeEnumerationComma::KeysFromPayload(buf.data[0]),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::VModeChanged)
|
||||
{
|
||||
Global::VModeTrigger.store(FanyImeVModeInput::TriggerFromPayload(buf.data[0]), std::memory_order_relaxed);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "../../../engine/contracts/windows_ipc.h"
|
||||
#include "../../../engine/contracts/direct_helpcode.h"
|
||||
#include "../../../engine/contracts/enumeration_comma.h"
|
||||
#include "../../../engine/contracts/mid_sentence_helpcode.h"
|
||||
#include "../../../engine/contracts/v_mode_input.h"
|
||||
#include "../../../engine/contracts/temporary_japanese_input.h"
|
||||
@@ -279,6 +280,9 @@ inline std::atomic_bool SmartPunctuationDirectDigitEnabled{false};
|
||||
inline std::atomic_bool SmartPunctuationDirectLetterEnabled{false};
|
||||
// Default on until the Server sends the persisted setting.
|
||||
inline std::atomic_bool PairedPunctuationEnabled{true};
|
||||
// 中文标点下出顿号的键,FanyImeEnumerationComma::kBackslash / kSlash 的组合。Server 连上之前(以及不发这个
|
||||
// 消息的旧 Server)保持历史行为:只有 \。规则见 engine/contracts/enumeration_comma.h。
|
||||
inline std::atomic<unsigned> EnumerationCommaKeys{FanyImeEnumerationComma::kLegacyKeys};
|
||||
inline std::atomic_bool MicrosoftShuangpinEnabled{false};
|
||||
// 双拼句中辅助码开着、勾了反引号且当前是双拼:组合中的反引号按
|
||||
// engine/contracts/mid_sentence_helpcode.h 的形状规则当编码键吃掉,见 CompositionProcessorEngine_KeyClassify.cpp。
|
||||
|
||||
@@ -621,7 +621,8 @@ BOOL CMetasequoiaIME::_IsKeyEaten( //
|
||||
//
|
||||
// Punctuation
|
||||
//
|
||||
if (pCompositionProcessorEngine->IsPunctuation(wch))
|
||||
// Numpad '/' stays the application's '/' even while main-keyboard '/' produces 、.
|
||||
if (*pCodeOut != VK_DIVIDE && pCompositionProcessorEngine->IsPunctuation(wch))
|
||||
{
|
||||
const CANDIDATE_MODE candidateMode = freshCompositionState ? CANDIDATE_NONE : _candidateMode;
|
||||
if ((candidateMode == CANDIDATE_NONE || candidateMode == CANDIDATE_INCREMENTAL) && isPunctuation)
|
||||
|
||||
@@ -700,7 +700,9 @@ bool CMetasequoiaIME::_ClassifyDeferredKeyDown(_In_ ITfContext *pContext, WPARAM
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shadow.punctuationOpen && _pCompositionProcessorEngine->IsPunctuation(*classifiedWch))
|
||||
// Same numpad '/' exception as _IsKeyEaten.
|
||||
if (shadow.punctuationOpen && *classifiedCode != VK_DIVIDE &&
|
||||
_pCompositionProcessorEngine->IsPunctuation(*classifiedWch))
|
||||
{
|
||||
return setKeyState(CATEGORY_COMPOSING, FUNCTION_PUNCTUATION);
|
||||
}
|
||||
|
||||
@@ -1535,13 +1535,16 @@ HRESULT CMetasequoiaIME::_HandleCompositionPunctuation(TfEditCookie ec, _In_ ITf
|
||||
const std::wstring candidate(receivedData->candidate_string);
|
||||
const WCHAR preceding =
|
||||
candidate.empty() ? _GetPrecedingCharForSmartPunctuation(ec, pContext) : candidate.back();
|
||||
if (code == VK_DECIMAL || wch == L'/' || wch == L'-' || wch == L'+')
|
||||
if (code == VK_DECIMAL || code == VK_DIVIDE || wch == L'-' || wch == L'+')
|
||||
{
|
||||
punctuationStr = candidate + (code == VK_DECIMAL ? L'.' : wch);
|
||||
}
|
||||
else
|
||||
{
|
||||
punctuationStr = candidate + _ResolveSmartPunctuation(wch, preceding);
|
||||
// A key that is not Chinese punctuation (e.g. '/' or '\' not configured for 、)
|
||||
// still commits itself after the candidate.
|
||||
const std::wstring resolved = _ResolveSmartPunctuation(wch, preceding);
|
||||
punctuationStr = candidate + (resolved.empty() ? std::wstring(1, wch) : resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user