Signed-off-by: kevin <kevin@lmve.net>

This commit is contained in:
2026-05-10 23:09:28 +08:00
parent 39074ae258
commit 0900a540cb
5 changed files with 150 additions and 3 deletions
@@ -0,0 +1,30 @@
## 2026-05-03 工作日志
### 添加中文输入法到 CannedMessageModule
**修改的文件:**
- `src/modules/CannedMessageModule.h` - 添加 InputMode::CHINESE 枚举值,添加中文输入相关成员变量和方法声明
- `src/modules/CannedMessageModule.cpp` - 实现中文输入法逻辑
**实现的功能:**
1.`InputMode` 枚举中添加 `CHINESE`,支持4种输入模式切换(DIGIT/LOWER/UPPER/CHINESE
2. 修改 `#` 键切换逻辑:`(m + 1) % 4` 支持4种模式
3.`handleFreeTextInput()` 中添加中文模式检查,调用 `handleChineseInput()`
4. 实现 `handleChineseInput()` 方法:
- 数字键1-6选择常用汉字(我/你/他/是/的/好)
- `*` 键删除最后一个字符(正确处理UTF-8中文编码)
- SELECT键切换回字母输入模式
5. 实现 `drawChineseInput()` 方法:绘制中文输入界面
**使用方式:**
- 在文本输入界面按 `#` 键切换到中文模式
- 按数字键1-6选择汉字
-`*` 键删除字符
- 按 SELECT 键退出中文模式
**限制:**
- 当前只支持6个常用汉字
- 未实现拼音输入法(需要更大的字典)
- 后续可扩展为支持翻页(按"0"键)和更多汉字
@@ -57,6 +57,12 @@ const char *const CannedMessageModule::t9LetterMap[][5] = {
#include "graphics/fonts/ChineseFont12x12.h"
#include <Throttle.h>
// === 中文输入法:常用汉字列表(6个)===
// 在 handleChineseInput() 和 drawChineseInput() 方法中硬编码使用
// 格式:数字键 -> 汉字
static const char* chineseChars[] = {"", "", "", "", "", ""};
// Remove Canned message screen if no action is taken for some milliseconds
#define INACTIVATE_AFTER_MS 20000
@@ -786,6 +792,11 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (runState != CANNED_MESSAGE_RUN_STATE_FREETEXT)
return false;
// === 中文输入模式处理 ===
if (inputMode == InputMode::CHINESE) {
return handleChineseInput(event);
}
#if defined(USE_VIRTUAL_KEYBOARD)
// Cancel (dismiss freetext screen)
if (event->inputEvent == INPUT_BROKER_LEFT) {
@@ -961,12 +972,12 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
return true;
}
// '#' key: cycle input mode DIGIT -> LOWER -> UPPER -> DIGIT ...
// '#' key: cycle input mode DIGIT -> LOWER -> UPPER -> CHINESE -> DIGIT ...
if (event->kbchar == '#') {
// First, commit any pending multi-tap character
commitMultiTap();
int m = static_cast<int>(inputMode);
inputMode = static_cast<InputMode>((m + 1) % 3);
inputMode = static_cast<InputMode>((m + 1) % 4); // 修改为 %4,支持4种模式
LOG_DEBUG("[T9] Input mode: %d", (int)inputMode);
lastTouchMillis = millis();
// Use REDRAW_ONLY: commitMultiTap() already triggered REGENERATE_FRAMESET,
@@ -1080,6 +1091,96 @@ int CannedMessageModule::handleEmotePickerInput(const InputEvent *event)
return 0;
}
// === 中文输入法:数字选择汉字 ===
bool CannedMessageModule::handleChineseInput(const InputEvent *event)
{
// 数字键1-6:选择对应的汉字
if (event->kbchar >= '1' && event->kbchar <= '6') {
// 常用汉字列表(6个)
static const char* chineseChars[] = {"", "", "", "", "", ""};
int index = event->kbchar - '1';
// 插入选中的汉字到 freetext
String chineseChar = chineseChars[index];
if (cursor >= freetext.length()) {
freetext += chineseChar;
} else {
freetext = freetext.substring(0, cursor) + chineseChar + freetext.substring(cursor);
}
cursor += chineseChar.length();
lastTouchMillis = millis();
screen->forceDisplay();
return true;
}
// * 键或 BACK 键:删除最后一个字符
if (event->kbchar == '*' || event->inputEvent == INPUT_BROKER_BACK) {
if (freetext.length() > 0) {
// 简化处理:删除最后1-3个字节(UTF-8中文是3字节)
// 找到最后一个UTF-8字符的起始位置
int lastPos = freetext.length() - 1;
while (lastPos >= 0 && (freetext[lastPos] & 0xC0) == 0x80) {
lastPos--; // 向前找到UTF-8字符的起始字节
}
if (lastPos >= 0) {
freetext.remove(lastPos);
if (cursor > lastPos) {
cursor = lastPos;
}
}
}
lastTouchMillis = millis();
screen->forceDisplay();
return true;
}
// SELECT 键:切换回其他输入模式(或确认输入)
if (isSelectEvent(event)) {
// 切换到小写字母模式,继续输入
inputMode = InputMode::LOWER;
lastTouchMillis = millis();
screen->forceDisplay();
return true;
}
// 其他按键:不处理
return false;
}
// === 绘制中文输入界面 ===
void CannedMessageModule::drawChineseInput(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// 常用汉字列表(6个)
static const char* chineseChars[] = {"", "", "", "", "", ""};
// 设置字体(使用支持中文的字体)
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
// 绘制标题
display->drawString(0, 0, "中文输入:");
// 绘制已输入的文本
if (freetext.length() > 0) {
display->drawString(0, 16, "文本:" + freetext);
}
// 绘制候选汉字(在屏幕底部)
int startY = display->getHeight() - 16; // 从底部向上绘制
int xPos = 0;
for (int i = 0; i < 6; i++) {
String label = String(i+1) + ":" + String(chineseChars[i]);
display->drawString(xPos, startY, label);
xPos += 40; // 每个候选字间隔40像素
if (xPos > display->getWidth() - 40) {
xPos = 0;
startY -= 16; // 换行
}
}
}
void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies)
{
lastDest = dest;
@@ -1906,6 +2007,13 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla
void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
this->displayHeight = display->getHeight(); // Store display height for later use
// === 中文输入模式界面 ===
if (inputMode == InputMode::CHINESE) {
drawChineseInput(display, state, x, y);
return;
}
char buffer[50];
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -194,7 +194,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
#endif
// === Multi-tap T9 input method (for TCA9535 numpad) ===
enum class InputMode : uint8_t { DIGIT, LOWER, UPPER };
enum class InputMode : uint8_t { DIGIT, LOWER, UPPER, CHINESE };
InputMode inputMode = InputMode::LOWER; // 默认小写字母模式
uint8_t multiTapKey = 0xFF; // 上一次按的数字键 ('0'-'9'), 0xFF = no pending key
uint8_t multiTapIndex = 0; // 当前循环索引
@@ -212,6 +212,15 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
bool handleFreeTextInput(const InputEvent *event);
bool commitMultiTap(); // Returns true if a character was actually committed
void showMultiTapPreview();
// === 中文输入法相关方法 ===
bool handleChineseInput(const InputEvent *event); // 处理中文输入模式的按键
void drawChineseInput(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); // 绘制中文输入界面
// === 中文输入法相关成员变量 ===
String chinesePinyin; // 当前输入的拼音
String chineseCandidates; // 候选汉字字符串(用逗号分隔)
int chineseCandidateIndex; // 当前选中的候选字索引
#if defined(USE_VIRTUAL_KEYBOARD)
Letter keyboard[2][4][10] = {{{{"Q", 20, 0, 0, 0, 0},