123 #90

Merged
pshjeamgr merged 6 commits from main into llllllllllllllCC 6 months ago

@ -14,6 +14,10 @@ from src.file_parser import FileParser
from src.ui.theme_manager import theme_manager from src.ui.theme_manager import theme_manager
class LearningModeWindow(QMainWindow): class LearningModeWindow(QMainWindow):
# 定义内容变化信号
content_changed = pyqtSignal(str, int) # 参数:内容,位置
# 定义关闭信号
closed = pyqtSignal()
def __init__(self, parent=None, imported_content="", current_position=0): def __init__(self, parent=None, imported_content="", current_position=0):
""" """
学习模式窗口 学习模式窗口
@ -39,6 +43,9 @@ class LearningModeWindow(QMainWindow):
# 初始化打字逻辑 # 初始化打字逻辑
self.init_typing_logic() self.init_typing_logic()
# 初始化同步位置跟踪
self.last_sync_position = current_position
# 如果有导入内容,初始化显示 # 如果有导入内容,初始化显示
if self.imported_content: if self.imported_content:
self.initialize_with_imported_content() self.initialize_with_imported_content()
@ -335,6 +342,7 @@ class LearningModeWindow(QMainWindow):
文本变化处理 文本变化处理
- 根据导入的内容逐步显示 - 根据导入的内容逐步显示
- 更新学习进度 - 更新学习进度
- 同步内容到打字模式
""" """
# 如果正在加载文件,跳过处理 # 如果正在加载文件,跳过处理
if self.is_loading_file: if self.is_loading_file:
@ -382,6 +390,7 @@ class LearningModeWindow(QMainWindow):
self.status_label.setText(f"输入错误!期望字符: '{result.get('expected', '')}'") self.status_label.setText(f"输入错误!期望字符: '{result.get('expected', '')}'")
else: else:
# 输入正确,更新进度 # 输入正确,更新进度
old_position = self.current_position
self.current_position = len(current_text) self.current_position = len(current_text)
progress = (self.current_position / len(self.imported_content)) * 100 progress = (self.current_position / len(self.imported_content)) * 100
@ -389,6 +398,15 @@ class LearningModeWindow(QMainWindow):
f"进度: {progress:.1f}% ({self.current_position}/{len(self.imported_content)} 字符)" f"进度: {progress:.1f}% ({self.current_position}/{len(self.imported_content)} 字符)"
) )
# 只在用户新输入的字符上同步到打字模式
if self.parent_window and hasattr(self.parent_window, 'text_edit'):
# 获取用户这一轮新输入的字符(与上一轮相比的新内容)
if old_position < self.current_position:
new_input = expected_text[old_position:self.current_position]
if new_input: # 只有新输入内容时才同步
# 只同步新输入的内容,不传递整个文本
self.content_changed.emit(new_input, len(new_input))
# 检查是否完成 # 检查是否完成
if result.get('completed', False): if result.get('completed', False):
self.status_label.setText("恭喜!学习完成!") self.status_label.setText("恭喜!学习完成!")
@ -417,6 +435,9 @@ class LearningModeWindow(QMainWindow):
窗口关闭事件 窗口关闭事件
- 通知父窗口学习模式已关闭 - 通知父窗口学习模式已关闭
""" """
# 发射关闭信号
self.closed.emit()
if self.parent_window and hasattr(self.parent_window, 'on_learning_mode_closed'): if self.parent_window and hasattr(self.parent_window, 'on_learning_mode_closed'):
self.parent_window.on_learning_mode_closed() self.parent_window.on_learning_mode_closed()

@ -1,482 +1,9 @@
import sys def on_learning_mode_closed(self):
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, QAction,
QFileDialog, QVBoxLayout, QWidget, QLabel, QStatusBar, QMessageBox)
from PyQt5.QtGui import QFont, QTextCharFormat, QColor, QTextCursor
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
# 添加项目根目录到Python路径
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
# 导入自定义UI组件
from src.ui.components import CustomTitleBar, ProgressBarWidget, TextDisplayWidget, StatsDisplayWidget, QuoteDisplayWidget, WeatherDisplayWidget
from src.file_parser import FileParser
from src.typing_logic import TypingLogic
from src.services.network_service import NetworkService
class WeatherFetchThread(QThread):
"""天气信息获取线程"""
weather_fetched = pyqtSignal(object) # 天气信息获取成功信号
error_occurred = pyqtSignal(str) # 错误发生信号
def __init__(self):
super().__init__()
self.network_service = NetworkService()
def run(self):
try:
weather_info = self.network_service.get_weather_info()
if weather_info:
# 格式化天气信息
formatted_info = (
f"天气: {weather_info['city']} - "
f"{weather_info['description']} - "
f"温度: {weather_info['temperature']}°C - "
f"湿度: {weather_info['humidity']}% - "
f"风速: {weather_info['wind_speed']} m/s"
)
self.weather_fetched.emit(formatted_info)
else:
self.error_occurred.emit("无法获取天气信息")
except Exception as e:
self.error_occurred.emit(f"获取天气信息时出错: {str(e)}")
class MainWindow(QMainWindow):
def __init__(self):
""" """
初始化主窗口 学习模式窗口关闭回调
- 设置窗口标题为"隐私学习软件 - 仿Word" - 清除学习窗口引用
- 设置窗口大小为800x600 - 更新菜单状态
- 初始化学习内容存储变量
- 初始化当前输入位置
- 调用initUI()方法
""" """
super().__init__() self.learning_window = None
self.learning_content = "" self.learning_mode_action.setChecked(False)
self.current_position = 0 self.typing_mode_action.setChecked(True)
self.typing_logic = None
self.text_edit = None
self.status_bar = None
self.title_bar = None
self.progress_bar_widget = None
self.text_display_widget = None
self.initUI()
def initUI(self):
"""
创建和布局所有UI组件
- 创建自定义标题栏
- 创建文本显示组件
- 调用createMenuBar()创建菜单
- 创建状态栏并显示"就绪"
"""
# 设置窗口属性
self.setWindowTitle("隐私学习软件 - 仿Word")
self.setGeometry(100, 100, 800, 600)
self.setWindowFlags(Qt.FramelessWindowHint) # 移除默认标题栏
# 创建中央widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建主布局
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
central_widget.setLayout(main_layout)
# 创建自定义标题栏
self.title_bar = CustomTitleBar(self)
main_layout.addWidget(self.title_bar)
# 创建统计信息显示组件(默认隐藏)
self.stats_display = StatsDisplayWidget(self)
self.stats_display.setVisible(False) # 默认隐藏
main_layout.addWidget(self.stats_display)
# 创建每日一言显示组件(默认隐藏)
self.quote_display = QuoteDisplayWidget(self)
self.quote_display.setVisible(False) # 默认隐藏
main_layout.addWidget(self.quote_display)
# 创建天气显示组件(默认隐藏)
self.weather_display = WeatherDisplayWidget(self)
self.weather_display.setVisible(False) # 默认隐藏
main_layout.addWidget(self.weather_display)
# 创建文本显示组件
self.text_display_widget = TextDisplayWidget(self)
main_layout.addWidget(self.text_display_widget)
# 连接文本显示组件的文本变化信号
self.text_display_widget.text_display.textChanged.connect(self.onTextChanged)
# 创建菜单栏
self.createMenuBar()
# 创建状态栏
self.status_bar = self.statusBar()
self.status_bar.showMessage("就绪")
def createTopFunctionArea(self, main_layout):
"""
创建顶部功能区域
- 显示准确率WPM等统计信息
- 显示每日一言功能
"""
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import Qt
# 创建顶部功能区域widget
top_widget = QWidget()
top_widget.setStyleSheet("""
QWidget {
background-color: #f0f0f0;
border-bottom: 1px solid #d0d0d0;
}
""")
# 创建水平布局
top_layout = QHBoxLayout()
top_layout.setContentsMargins(10, 5, 10, 5)
top_layout.setSpacing(15)
# 创建统计信息标签
self.wpm_label = QLabel("WPM: 0")
self.accuracy_label = QLabel("准确率: 0%")
self.quote_label = QLabel("每日一言: 暂无")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; }")
# 设置标签样式
label_style = "font-size: 12px; font-weight: normal; color: #333333;"
self.wpm_label.setStyleSheet(label_style)
self.accuracy_label.setStyleSheet(label_style)
# 创建每日一言刷新按钮
self.refresh_quote_button = QPushButton("刷新")
self.refresh_quote_button.setStyleSheet("""
QPushButton {
background-color: #0078d7;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
font-size: 12px;
}
QPushButton:hover {
background-color: #005a9e;
}
""")
self.refresh_quote_button.clicked.connect(self.refresh_daily_quote)
# 添加组件到布局
top_layout.addWidget(self.wpm_label)
top_layout.addWidget(self.accuracy_label)
top_layout.addStretch()
top_layout.addWidget(self.quote_label)
top_layout.addWidget(self.refresh_quote_button)
top_widget.setLayout(top_layout)
main_layout.addWidget(top_widget)
def createMenuBar(self):
"""
创建菜单栏和所有菜单项
- 文件菜单打开(Ctrl+O)保存(Ctrl+S)退出(Ctrl+Q)
- 视图菜单显示统计信息显示每日一言
- 帮助菜单关于
- 为每个菜单项连接对应的槽函数
"""
menu_bar = self.menuBar()
# 文件菜单
file_menu = menu_bar.addMenu('文件')
# 打开动作
open_action = QAction('打开', self)
open_action.setShortcut('Ctrl+O')
open_action.triggered.connect(self.openFile)
file_menu.addAction(open_action)
# 保存动作
save_action = QAction('保存', self)
save_action.setShortcut('Ctrl+S')
save_action.triggered.connect(self.saveFile)
file_menu.addAction(save_action)
# 分隔线
file_menu.addSeparator()
# 退出动作
exit_action = QAction('退出', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 视图菜单
view_menu = menu_bar.addMenu('视图')
# 显示统计信息动作
self.stats_action = QAction('显示统计信息', self)
self.stats_action.setCheckable(True)
self.stats_action.setChecked(True)
self.stats_action.triggered.connect(self.toggleStatsDisplay)
view_menu.addAction(self.stats_action)
# 显示每日一言动作
self.quote_action = QAction('显示每日一言', self)
self.quote_action.setCheckable(True)
self.quote_action.setChecked(True)
self.quote_action.triggered.connect(self.toggleQuoteDisplay)
view_menu.addAction(self.quote_action)
# 显示天气信息动作
self.weather_action = QAction('显示天气', self)
self.weather_action.setCheckable(True)
self.weather_action.setChecked(True)
self.weather_action.triggered.connect(self.toggleWeatherDisplay)
view_menu.addAction(self.weather_action)
# 帮助菜单
help_menu = menu_bar.addMenu('帮助')
# 关于动作
about_action = QAction('关于', self)
about_action.triggered.connect(self.showAbout)
help_menu.addAction(about_action)
def toggleStatsDisplay(self, checked):
"""
切换统计信息显示
- checked: 是否显示统计信息
"""
self.stats_display.setVisible(checked)
def toggleQuoteDisplay(self, checked):
"""
切换每日一言显示
- checked: 是否显示每日一言
"""
self.quote_display.setVisible(checked)
# 如果启用显示且quote为空则刷新一次
if checked and not self.quote_display.quote_label.text():
self.refresh_daily_quote()
def toggleWeatherDisplay(self, checked):
"""切换天气信息显示"""
self.weather_display.setVisible(checked)
# 如果启用显示且天气信息为空,则刷新一次
if checked and not self.weather_display.weather_label.text():
self.refresh_weather_info()
def openFile(self):
"""
打开文件选择对话框并加载选中的文件
- 显示文件选择对话框过滤条件*.txt, *.docx, *.pdf
- 如果用户选择了文件调用FileParser.parse_file(file_path)
- 成功时将内容存储但不直接显示重置打字状态
- 失败时显示错误消息框
"""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(
self,
"打开文件",
"",
"文本文件 (*.txt);;Word文档 (*.docx);;PDF文件 (*.pdf);;所有文件 (*)",
options=options
)
if file_path:
try:
# 解析文件内容
content = FileParser.parse_file(file_path)
self.learning_content = content
# 在文本显示组件中设置内容(初始为空,通过打字逐步显示)
if self.text_display_widget:
self.text_display_widget.set_text(content) # 设置文件内容
# 重置打字状态
self.typing_logic = TypingLogic(content)
self.current_position = 0
# 更新状态栏
self.status_bar.showMessage(f"已打开文件: {file_path},开始打字以显示内容")
except Exception as e:
# 显示错误消息框
QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
def saveFile(self):
"""
保存当前内容到文件
- 显示保存文件对话框
- 将文本区域内容写入选定文件
- 返回操作结果
"""
options = QFileDialog.Options()
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存文件",
"",
"文本文件 (*.txt);;所有文件 (*)",
options=options
)
if file_path:
try:
# 获取文本编辑区域的内容
content = self.text_edit.toPlainText()
# 写入文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
# 更新状态栏
self.status_bar.showMessage(f"文件已保存: {file_path}")
return True
except Exception as e:
# 显示错误消息框
QMessageBox.critical(self, "错误", f"无法保存文件:\n{str(e)}")
return False
return False
def showAbout(self):
"""
显示关于对话框
- 显示消息框包含软件名称版本描述
"""
QMessageBox.about(
self,
"关于",
"隐私学习软件 - 仿Word\n\n"
"版本: 1.0\n\n"
"这是一个用于隐私学习的打字练习软件,\n"
"可以加载文档并进行打字练习,\n"
"帮助提高打字速度和准确性。"
)
def refresh_daily_quote(self):
"""
刷新每日一言
- 从网络API获取名言
- 更新显示
"""
import requests
import json
from PyQt5.QtCore import Qt
from src.constants import QUOTE_API_URL
try:
# 发送请求获取每日一言
response = requests.get(QUOTE_API_URL, timeout=5)
if response.status_code == 200:
data = response.json()
quote_content = data.get('content', '暂无内容')
quote_author = data.get('author', '未知作者')
# 更新显示
self.quote_label.setText(f"每日一言: {quote_content}{quote_author}")
# 同时更新统计信息显示组件中的每日一言
if hasattr(self, 'stats_display') and self.stats_display:
self.stats_display.update_quote(f"{quote_content}{quote_author}")
else:
self.quote_label.setText("每日一言: 获取失败")
# 同时更新统计信息显示组件中的每日一言
if hasattr(self, 'stats_display') and self.stats_display:
self.stats_display.update_quote("获取失败")
except Exception as e:
self.quote_label.setText("每日一言: 获取失败")
# 同时更新统计信息显示组件中的每日一言
if hasattr(self, 'stats_display') and self.stats_display:
self.stats_display.update_quote("获取失败")
def onTextChanged(self):
"""
处理用户输入变化事件打字练习
- 获取文本显示组件中的文本
- 使用TypingLogic.check_input检查输入
- 根据结果更新文本显示组件
- 更新统计数据展示
"""
# 防止递归调用
if getattr(self, '_processing_text_change', False):
return
if not self.typing_logic:
return
# 设置标志防止递归
self._processing_text_change = True
try:
# 获取当前输入文本
current_text = self.text_display_widget.text_display.toPlainText()
# 检查输入是否正确
result = self.typing_logic.check_input(current_text)
is_correct = result["correct"]
expected_char = result["expected"]
# 更新文本显示组件
if self.text_display_widget:
# 显示用户输入反馈
self.text_display_widget.show_user_input(current_text)
# 不再高亮下一个字符,因为内容通过打字逐步显示
# 计算统计数据
stats = self.typing_logic.get_statistics()
accuracy = stats['accuracy_rate'] * 100 # 转换为百分比
# 可以根据需要添加更多统计数据的计算
wpm = 0 # 暂时设置为0后续可以实现WPM计算
# 更新状态栏
self.status_bar.showMessage(f"WPM: {wpm:.1f} | 准确率: {accuracy:.1f}%")
# 更新统计信息显示组件
if hasattr(self, 'stats_display') and self.stats_display.isVisible():
self.stats_display.update_stats(int(wpm), accuracy)
# 更新每日一言显示组件(如果需要)
if hasattr(self, 'quote_display') and self.quote_display.isVisible() and not self.quote_display.quote_label.text():
self.refresh_daily_quote()
# 更新顶部功能区的统计数据(如果仍然存在)
if hasattr(self, 'wpm_label') and self.wpm_label:
self.wpm_label.setText(f"WPM: {wpm:.1f}")
if hasattr(self, 'accuracy_label') and self.accuracy_label:
self.accuracy_label.setText(f"准确率: {accuracy:.1f}%")
finally:
# 清除递归防止标志
self._processing_text_change = False
def refresh_daily_quote(self):
"""刷新每日一言"""
# 创建并启动获取名言的线程
self.quote_thread = QuoteFetchThread()
self.quote_thread.quote_fetched.connect(self.on_quote_fetched)
self.quote_thread.error_occurred.connect(self.on_quote_error)
self.quote_thread.start()
def refresh_weather_info(self):
"""刷新天气信息"""
# 创建并启动获取天气信息的线程
self.weather_thread = WeatherFetchThread()
self.weather_thread.weather_fetched.connect(self.on_weather_fetched)
self.weather_thread.error_occurred.connect(self.on_weather_error)
self.weather_thread.start()
def on_weather_fetched(self, weather_info):
"""处理天气信息获取成功"""
# 更新天气显示组件
if hasattr(self, 'weather_display') and self.weather_display:
self.weather_display.update_weather(weather_info)
def on_weather_error(self, error_msg):
"""处理天气信息获取错误"""
# 更新天气显示组件
if hasattr(self, 'weather_display') and self.weather_display:
self.weather_display.update_weather(error_msg)

@ -83,6 +83,10 @@ class WordStyleMainWindow(QMainWindow):
self.learning_text = "" # 学习模式下的文本内容 self.learning_text = "" # 学习模式下的文本内容
self.cursor_position = 0 # 光标位置 self.cursor_position = 0 # 光标位置
# 学习模式窗口引用和同步标记
self.learning_window = None # 学习模式窗口引用
self.sync_from_learning = False # 从学习模式同步内容的标记
# 统一文档内容管理 # 统一文档内容管理
self.unified_document_content = "" # 统一文档内容 self.unified_document_content = "" # 统一文档内容
self.last_edit_mode = "typing" # 上次编辑模式 self.last_edit_mode = "typing" # 上次编辑模式
@ -836,6 +840,10 @@ class WordStyleMainWindow(QMainWindow):
# 如果正在加载文件,跳过处理 # 如果正在加载文件,跳过处理
if self.is_loading_file: if self.is_loading_file:
return return
# 检查是否是从学习模式同步内容,避免递归调用
if hasattr(self, 'sync_from_learning') and self.sync_from_learning:
return
# 根据当前视图模式处理 # 根据当前视图模式处理
if self.view_mode == "learning": if self.view_mode == "learning":
@ -852,8 +860,9 @@ class WordStyleMainWindow(QMainWindow):
self.handle_learning_mode_typing() self.handle_learning_mode_typing()
elif self.view_mode == "typing": elif self.view_mode == "typing":
# 打字模式:可以自由打字 # 打字模式:可以自由打字,不自动处理内容
self.handle_typing_mode_typing() # 只在用户主动操作时处理,避免内容被覆盖
pass
# 标记文档为已修改 # 标记文档为已修改
if not self.is_modified: if not self.is_modified:
@ -1856,10 +1865,24 @@ class WordStyleMainWindow(QMainWindow):
imported_content = self.imported_content imported_content = self.imported_content
if hasattr(self, 'learning_progress') and self.learning_progress > 0: if hasattr(self, 'learning_progress') and self.learning_progress > 0:
current_position = self.learning_progress current_position = self.learning_progress
else:
# 如果没有导入内容,检查当前打字模式的内容
current_text = self.text_edit.toPlainText()
if current_text and current_text != "在此输入您的内容...":
# 将打字模式的内容作为学习模式的导入内容
imported_content = current_text
current_position = 0
self.imported_content = current_text
# 创建学习模式窗口,直接传递导入内容 # 创建学习模式窗口,直接传递导入内容
self.learning_window = LearningModeWindow(self, imported_content, current_position) self.learning_window = LearningModeWindow(self, imported_content, current_position)
# 连接学习模式窗口的内容变化信号
self.learning_window.content_changed.connect(self.on_learning_content_changed)
# 连接学习模式窗口的关闭信号
self.learning_window.closed.connect(self.on_learning_mode_closed)
# 显示学习模式窗口 # 显示学习模式窗口
self.learning_window.show() self.learning_window.show()
@ -1890,8 +1913,46 @@ class WordStyleMainWindow(QMainWindow):
self.learning_mode_action.setChecked(False) self.learning_mode_action.setChecked(False)
self.typing_mode_action.setChecked(True) self.typing_mode_action.setChecked(True)
self.view_mode = "typing" self.view_mode = "typing"
# 清除学习窗口引用
self.learning_window = None
self.status_bar.showMessage("学习模式窗口已关闭", 2000) self.status_bar.showMessage("学习模式窗口已关闭", 2000)
def on_learning_content_changed(self, new_content, position):
"""学习模式内容变化时的回调 - 只在末尾追加新内容"""
# 设置同步标记,防止递归调用
self.sync_from_learning = True
try:
# 只在末尾追加新输入的内容,不修改已有内容
if new_content:
# 直接在末尾追加新内容
cursor = self.text_edit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(new_content)
# 更新导入内容(但不覆盖用户额外输入的内容)
if self.imported_content:
self.imported_content += new_content
else:
self.imported_content = new_content
self.learning_progress = len(self.imported_content) if self.imported_content else 0
# 重置打字逻辑(不追踪进度)
if self.typing_logic:
self.typing_logic.imported_content = self.imported_content
self.typing_logic.current_index = self.learning_progress
self.typing_logic.typed_chars = self.learning_progress
# 更新状态栏
self.status_bar.showMessage(f"从学习模式同步新内容: {new_content}", 3000)
finally:
# 重置同步标记
self.sync_from_learning = False
def set_page_color(self, color): def set_page_color(self, color):
"""设置页面颜色""" """设置页面颜色"""
color_map = { color_map = {

Binary file not shown.
Loading…
Cancel
Save