diff --git a/src/input_handler/input_processor.py b/src/input_handler/input_processor.py index 673a5fb..545b5ca 100644 --- a/src/input_handler/input_processor.py +++ b/src/input_handler/input_processor.py @@ -60,6 +60,27 @@ class InputProcessor(QObject): # 6. 返回处理结果 return True + def process_text_commit(self, text: str) -> bool: + """处理输入法提交的完整文本(支持中文输入)""" + if not text: + return False + + # 逐个处理提交的字符 + for char in text: + self.input_buffer += char + self.current_position += 1 + # 发送每个字符的text_changed信号 + self.text_changed.emit(char) + + # 发送完整的文本提交信号 + self.key_pressed.emit(f"text_commit:{text}") + + # 检查是否完成输入 + if self.expected_text and self.input_buffer == self.expected_text: + self.input_completed.emit() + + return True + def get_current_input(self) -> str: # 实现获取当前输入逻辑 diff --git a/src/typing_logic.py b/src/typing_logic.py index 6d020fb..df2ca00 100644 --- a/src/typing_logic.py +++ b/src/typing_logic.py @@ -19,6 +19,7 @@ class TypingLogic: """ 检查用户输入与学习材料的匹配情况 - 逐字符比较逻辑 + - 支持中文整词匹配 - 进度跟踪 - 准确率计算 - 返回字典包含: @@ -42,13 +43,31 @@ class TypingLogic: user_text = user_text[:self.total_chars] current_position = len(user_text) - # 检查当前输入是否正确 + # 检查当前输入是否正确(支持中文整词匹配) correct = True expected_char = '' if self.current_index < self.total_chars: expected_char = self.learning_content[self.current_index] - if len(user_text) > self.current_index and user_text[self.current_index] != expected_char: - correct = False + + # 中文整词匹配优化 + # 如果当前位置是中文文本的开始,尝试进行整词匹配 + if self._is_chinese_text_start(self.current_index): + # 获取期望的中文词组 + expected_word = self._get_chinese_word_at(self.current_index) + # 获取用户输入的对应部分 + user_word = user_text[self.current_index:min(self.current_index + len(expected_word), len(user_text))] + + # 如果用户输入的词组与期望词组匹配,则认为是正确的 + if user_word == expected_word: + correct = True + else: + # 如果整词不匹配,回退到逐字符匹配 + if len(user_text) > self.current_index and user_text[self.current_index] != expected_char: + correct = False + else: + # 非中文词组开始位置,使用逐字符匹配 + if len(user_text) > self.current_index and user_text[self.current_index] != expected_char: + correct = False else: # 已经完成所有输入 # 恢复原始的typed_chars值用于准确率计算 @@ -78,21 +97,35 @@ class TypingLogic: "accuracy": accuracy } - def update_position(self, user_text: str): + def update_position(self, user_text: str) -> dict: """ - 更新当前索引和错误计数 - - 根据用户输入更新当前位置 - - 计算并更新错误计数 + 更新当前位置并计算错误数 + - 支持中文整词匹配 + - 逐字符比较逻辑(回退机制) + - 错误计数 + - 位置更新 + - 返回字典包含: + * new_position: 整数,新的位置 + * error_count: 整数,错误数 + * completed: 布尔值,是否完成 """ - new_position = len(user_text) + # 更新当前索引位置 + self.current_index = len(user_text) + + # 重置错误计数 + self.error_count = 0 + + # 使用中文整词匹配优化错误计算 + self._calculate_errors_with_chinese_support(user_text) - # 计算新增的错误数 - for i in range(self.current_index, min(new_position, self.total_chars)): - if user_text[i] != self.learning_content[i]: - self.error_count += 1 + # 检查是否完成 + completed = self.current_index >= self.total_chars - # 更新当前索引 - self.current_index = new_position + return { + "new_position": self.current_index, + "error_count": self.error_count, + "completed": completed + } def get_expected_text(self, length: int = 10) -> str: """ @@ -236,4 +269,85 @@ class TypingLogic: return 0.0 finally: # 清除递归保护标志 - self._calculating_accuracy = False \ No newline at end of file + self._calculating_accuracy = False + + def _is_chinese_text_start(self, index: int) -> bool: + """ + 判断指定位置是否是中文文本的开始 + - 检查当前字符是否为中文 + - 检查前一个字符是否为非中文或空格 + """ + if index < 0 or index >= len(self.learning_content): + return False + + current_char = self.learning_content[index] + + # 检查当前字符是否为中文 + if not self._is_chinese_char(current_char): + return False + + # 如果是第一个字符,或者是紧跟在非中文字符后的中文,则认为是中文文本的开始 + if index == 0: + return True + + prev_char = self.learning_content[index - 1] + return not self._is_chinese_char(prev_char) or prev_char.isspace() + + def _is_chinese_char(self, char: str) -> bool: + """ + 判断字符是否为中文 + - 使用Unicode范围判断中文字符 + """ + return '\u4e00' <= char <= '\u9fff' or '\u3400' <= char <= '\u4dbf' + + def _get_chinese_word_at(self, index: int) -> str: + """ + 获取指定位置开始的中文词组 + - 从当前位置开始,连续获取中文字符 + - 遇到非中文字符或字符串结束则停止 + """ + if index < 0 or index >= len(self.learning_content): + return "" + + word = "" + current_index = index + + while current_index < len(self.learning_content): + char = self.learning_content[current_index] + if self._is_chinese_char(char): + word += char + current_index += 1 + else: + break + + return word + + def _calculate_errors_with_chinese_support(self, user_text: str) -> None: + """ + 使用中文整词匹配优化错误计算 + - 优先尝试中文整词匹配 + - 整词匹配失败时回退到逐字符匹配 + """ + i = 0 + while i < min(len(user_text), len(self.learning_content)): + # 检查当前位置是否为中文文本开始 + if self._is_chinese_text_start(i): + # 获取期望的中文词组 + expected_word = self._get_chinese_word_at(i) + # 获取用户输入的对应部分 + user_word = user_text[i:min(i + len(expected_word), len(user_text))] + + # 如果整词匹配成功,跳过整个词组 + if user_word == expected_word: + i += len(expected_word) + continue + else: + # 整词匹配失败,回退到逐字符匹配 + if user_text[i] != self.learning_content[i]: + self.error_count += 1 + else: + # 非中文文本,使用逐字符匹配 + if user_text[i] != self.learning_content[i]: + self.error_count += 1 + + i += 1 \ No newline at end of file diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 0716239..6073d44 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -5,7 +5,7 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QSpinBox, QFontComboBox, QToolBar, QSizePolicy) from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QFont, QIcon, QPalette, QColor +from PyQt5.QtGui import QFont, QIcon, QPalette, QColor, QInputMethodEvent import requests import json from datetime import datetime @@ -502,6 +502,48 @@ class WordTextEdit(QTextEdit): # 让父类处理实际的文本编辑逻辑 super().keyPressEvent(event) + def inputMethodEvent(self, event): + """重写输入法事件处理,支持中文输入法""" + if event.type() == QInputMethodEvent.InputMethod: + # 获取输入法事件中的文本 + commit_string = event.commitString() + preedit_string = event.preeditString() + + # 处理提交的文本(用户选择了候选词) + if commit_string: + if self.input_processor: + # 使用新的process_text_commit方法处理完整的提交文本 + self.input_processor.process_text_commit(commit_string) + + # 让父类处理实际的文本插入 + super().inputMethodEvent(event) + + # 触发文本变化信号,让学习模式能够处理 + self.textChanged.emit() + + # 处理预编辑文本(用户正在输入拼音) + elif preedit_string: + # 在预编辑阶段,我们只显示预编辑文本,不传递给输入处理器 + # 这样可以避免在学习模式下出现错误的匹配 + super().inputMethodEvent(event) + else: + # 其他情况,使用默认处理 + super().inputMethodEvent(event) + else: + # 非输入法事件,使用默认处理 + super().inputMethodEvent(event) + + def inputMethodQuery(self, query): + """重写输入法查询,提供更好的输入法支持""" + if query == Qt.ImEnabled: + return True # 启用输入法支持 + elif query == Qt.ImCursorRectangle: + # 返回光标位置,帮助输入法定位候选词窗口 + cursor_rect = self.cursorRect() + return cursor_rect + else: + return super().inputMethodQuery(query) + def setup_ui(self): """设置文本编辑区域样式""" self.setStyleSheet(""" @@ -530,65 +572,6 @@ class WordTextEdit(QTextEdit): # 设置光标宽度 self.setCursorWidth(2) -class WordStyleToolBar(QToolBar): - def __init__(self, parent=None): - super().__init__(parent) - self.setup_ui() - - def setup_ui(self): - """设置快速访问工具栏""" - self.setFixedHeight(30) - self.setStyleSheet(""" - QToolBar { - background-color: #f3f2f1; - border-bottom: 1px solid #d0d0d0; - spacing: 5px; - } - """) - - # 快速访问按钮 - save_btn = self.create_quick_button("保存", "Ctrl+S") - undo_btn = self.create_quick_button("撤销", "Ctrl+Z") - redo_btn = self.create_quick_button("重做", "Ctrl+Y") - - self.addWidget(save_btn) - self.addWidget(undo_btn) - self.addWidget(redo_btn) - self.addSeparator() - - # 添加弹性空间,将后面的按钮推到最右侧 - # 创建一个具有扩展策略的QWidget来实现spacer效果 - spacer = QWidget() - spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.addWidget(spacer) - - # 添加批注、编辑、共享按钮到最右侧 - comment_btn = self.create_quick_button("批注", "") - edit_btn = self.create_quick_button("编辑", "") - share_btn = self.create_quick_button("共享", "") - - self.addWidget(comment_btn) - self.addWidget(edit_btn) - self.addWidget(share_btn) - - def create_quick_button(self, text, shortcut): - """创建快速访问按钮""" - btn = QToolButton() - btn.setText(text) - btn.setToolButtonStyle(Qt.ToolButtonTextOnly) - btn.setFixedSize(40, 25) - btn.setStyleSheet(""" - QToolButton { - border: none; - background-color: transparent; - font-size: 11px; - color: #333333; - } - QToolButton:hover { - background-color: #e1e1e1; - } - """) - return btn class WeatherAPI: def __init__(self): self.api_key = "f3d9201bf5974ed39caf0d6fe9567322" diff --git a/src/word_main_window.py b/src/word_main_window.py index dbf3ffc..b4bfc9a 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -10,7 +10,7 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect, QByteArray, QBu from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor, QTextDocument, QImage, QTextImageFormat, QTextFormat from ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit, - WordStyleToolBar) + ) from services.network_service import NetworkService from typing_logic import TypingLogic from ui.word_style_ui import WeatherAPI @@ -213,10 +213,6 @@ class WordStyleMainWindow(QMainWindow): # 创建菜单栏 self.create_menu_bar() - # 创建快速访问工具栏 - self.quick_toolbar = WordStyleToolBar() - self.addToolBar(Qt.TopToolBarArea, self.quick_toolbar) - # 创建Ribbon功能区 self.ribbon = WordRibbon() @@ -646,15 +642,21 @@ class WordStyleMainWindow(QMainWindow): cursor.movePosition(cursor.End) self.text_edit.setTextCursor(cursor) - # 更新打字逻辑(只检查已显示的部分) + # 更新打字逻辑(支持中文整词匹配) if display_text: result = self.typing_logic.check_input(display_text) self.typing_logic.update_position(display_text) - # 错误处理 + # 错误处理(支持中文整词显示) if not result['correct'] and display_text: expected_char = result.get('expected', '') - self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) + # 如果是中文文本,显示更友好的错误信息 + if self.typing_logic._is_chinese_char(expected_char): + # 获取期望的中文词组 + expected_word = self.typing_logic._get_chinese_word_at(result['position']) + self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000) + else: + self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) self.highlight_next_char(result['position'], expected_char) # 更新统计信息 @@ -747,10 +749,16 @@ class WordStyleMainWindow(QMainWindow): result = self.typing_logic.check_input(current_text) self.typing_logic.update_position(current_text) - # 错误处理 + # 错误处理(支持中文整词显示) if not result['correct']: expected_char = result.get('expected', '') - self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) + # 如果是中文文本,显示更友好的错误信息 + if self.typing_logic._is_chinese_char(expected_char): + # 获取期望的中文词组 + expected_word = self.typing_logic._get_chinese_word_at(result['position']) + self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000) + else: + self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) self.highlight_next_char(result['position'], expected_char) # 更新统计信息 @@ -766,10 +774,16 @@ class WordStyleMainWindow(QMainWindow): result = self.typing_logic.check_input(current_text) self.typing_logic.update_position(current_text) - # 错误处理 + # 错误处理(支持中文整词显示) if not result['correct']: expected_char = result.get('expected', '') - self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) + # 如果是中文文本,显示更友好的错误信息 + if self.typing_logic._is_chinese_char(expected_char): + # 获取期望的中文词组 + expected_word = self.typing_logic._get_chinese_word_at(result['position']) + self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000) + else: + self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) self.highlight_next_char(result['position'], expected_char) # 更新统计信息 @@ -842,10 +856,16 @@ class WordStyleMainWindow(QMainWindow): result = self.typing_logic.check_input(display_text) self.typing_logic.update_position(display_text) - # 错误处理 + # 错误处理(支持中文整词显示) if not result['correct'] and display_text: expected_char = result.get('expected', '') - self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) + # 如果是中文文本,显示更友好的错误信息 + if self.typing_logic._is_chinese_char(expected_char): + # 获取期望的中文词组 + expected_word = self.typing_logic._get_chinese_word_at(result['position']) + self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000) + else: + self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000) self.highlight_next_char(result['position'], expected_char) # 更新统计信息