You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Curriculum_Design/src/word_main_window.py

655 lines
24 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# word_main_window.py
import sys
import os
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTextEdit, QLabel, QSplitter, QFrame, QMenuBar,
QAction, QFileDialog, QMessageBox, QApplication)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat
from ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit,
WordStyleToolBar)
from services.network_service import NetworkService
from typing_logic import TypingLogic
from file_parser import FileParser
class WeatherFetchThread(QThread):
weather_fetched = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.network_service = NetworkService()
def run(self):
try:
weather_data = self.network_service.get_weather()
self.weather_fetched.emit(weather_data)
except Exception as e:
self.weather_fetched.emit({'error': str(e)})
class QuoteFetchThread(QThread):
quote_fetched = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.network_service = NetworkService()
def run(self):
try:
quote_data = self.network_service.get_daily_quote()
self.quote_fetched.emit(quote_data)
except Exception as e:
self.quote_fetched.emit({'error': str(e)})
class WordStyleMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.current_file_path = None
self.is_modified = False
self.typing_logic = None
self.is_loading_file = False # 添加文件加载标志
self.imported_content = "" # 存储导入的完整内容
self.displayed_chars = 0 # 已显示的字符数
self.setup_ui()
self.network_service = NetworkService()
# 设置窗口属性
self.setWindowTitle("文档1 - MagicWord")
self.setGeometry(100, 100, 1200, 800)
# 设置应用程序图标
self.set_window_icon()
# 初始化UI
self.setup_ui()
# 初始化网络服务
self.init_network_services()
# 初始化打字逻辑
self.init_typing_logic()
# 连接信号和槽
self.connect_signals()
def set_window_icon(self):
"""设置窗口图标"""
# 创建简单的Word风格图标
icon = QIcon()
pixmap = QPixmap(32, 32)
pixmap.fill(QColor("#2B579A"))
icon.addPixmap(pixmap)
self.setWindowIcon(icon)
def setup_ui(self):
"""设置Word风格的UI界面"""
# 创建菜单栏
self.create_menu_bar()
# 创建快速访问工具栏
self.quick_toolbar = WordStyleToolBar()
self.addToolBar(Qt.TopToolBarArea, self.quick_toolbar)
# 创建Ribbon功能区
self.ribbon = WordRibbon()
# 创建中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 添加Ribbon
main_layout.addWidget(self.ribbon)
# 创建文档编辑区域
self.create_document_area(main_layout)
# 创建状态栏
self.status_bar = WordStatusBar()
self.setStatusBar(self.status_bar)
central_widget.setLayout(main_layout)
# 设置样式
self.setStyleSheet("""
QMainWindow {
background-color: #f3f2f1;
}
""")
def create_menu_bar(self):
"""创建菜单栏"""
menubar = self.menuBar()
menubar.setStyleSheet("""
QMenuBar {
background-color: #f3f2f1;
border-bottom: 1px solid #d0d0d0;
font-size: 12px;
color: #333333;
}
QMenuBar::item:selected {
background-color: #e1e1e1;
}
""")
# 文件菜单
file_menu = menubar.addMenu('文件(F)')
file_menu.setStyleSheet("""
QMenu {
background-color: #ffffff;
border: 1px solid #d0d0d0;
font-size: 12px;
}
QMenu::item:selected {
background-color: #e1e1e1;
}
""")
# 新建
new_action = QAction('新建(N)', self)
new_action.setShortcut('Ctrl+N')
new_action.triggered.connect(self.new_document)
file_menu.addAction(new_action)
# 打开
open_action = QAction('打开(O)...', self)
open_action.setShortcut('Ctrl+O')
open_action.triggered.connect(self.open_file)
file_menu.addAction(open_action)
# 保存
save_action = QAction('保存(S)', self)
save_action.setShortcut('Ctrl+S')
save_action.triggered.connect(self.save_file)
file_menu.addAction(save_action)
# 另存为
save_as_action = QAction('另存为(A)...', self)
save_as_action.triggered.connect(self.save_as_file)
file_menu.addAction(save_as_action)
file_menu.addSeparator()
# 退出
exit_action = QAction('退出(X)', self)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 编辑菜单
edit_menu = menubar.addMenu('编辑(E)')
# 撤销
undo_action = QAction('撤销(U)', self)
undo_action.setShortcut('Ctrl+Z')
undo_action.triggered.connect(self.undo)
edit_menu.addAction(undo_action)
# 重做
redo_action = QAction('重做(R)', self)
redo_action.setShortcut('Ctrl+Y')
redo_action.triggered.connect(self.redo)
edit_menu.addAction(redo_action)
edit_menu.addSeparator()
# 剪切
cut_action = QAction('剪切(T)', self)
cut_action.setShortcut('Ctrl+X')
cut_action.triggered.connect(self.cut)
edit_menu.addAction(cut_action)
# 复制
copy_action = QAction('复制(C)', self)
copy_action.setShortcut('Ctrl+C')
copy_action.triggered.connect(self.copy)
edit_menu.addAction(copy_action)
# 粘贴
paste_action = QAction('粘贴(P)', self)
paste_action.setShortcut('Ctrl+V')
paste_action.triggered.connect(self.paste)
edit_menu.addAction(paste_action)
# 视图菜单
view_menu = menubar.addMenu('视图(V)')
# 阅读视图
read_view_action = QAction('阅读视图', self)
read_view_action.triggered.connect(self.toggle_reading_view)
view_menu.addAction(read_view_action)
# 打印布局
print_layout_action = QAction('打印布局', self)
print_layout_action.setChecked(True)
print_layout_action.triggered.connect(self.toggle_print_layout)
view_menu.addAction(print_layout_action)
# 帮助菜单
help_menu = menubar.addMenu('帮助(H)')
# 关于
about_action = QAction('关于 MagicWord', self)
about_action.triggered.connect(self.show_about)
help_menu.addAction(about_action)
def create_document_area(self, main_layout):
"""创建文档编辑区域"""
# 创建滚动区域
from PyQt5.QtWidgets import QScrollArea
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setStyleSheet("""
QScrollArea {
background-color: #e1e1e1;
border: none;
}
QScrollArea QWidget {
background-color: #e1e1e1;
}
""")
# 创建文档容器
document_container = QWidget()
document_layout = QVBoxLayout()
document_layout.setContentsMargins(50, 50, 50, 50)
# 创建文本编辑区域使用Word风格的文本编辑器
self.text_edit = WordTextEdit()
self.text_edit.setMinimumHeight(600)
self.text_edit.setStyleSheet("""
QTextEdit {
background-color: #ffffff;
border: 1px solid #d0d0d0;
border-radius: 0px;
font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 12pt;
color: #000000;
padding: 40px;
line-height: 1.5;
}
""")
# 设置默认文档内容
self.text_edit.setPlainText("在此输入您的内容...")
document_layout.addWidget(self.text_edit)
document_container.setLayout(document_layout)
scroll_area.setWidget(document_container)
main_layout.addWidget(scroll_area)
def init_network_services(self):
"""初始化网络服务"""
# 获取天气信息
self.weather_thread = WeatherFetchThread()
self.weather_thread.weather_fetched.connect(self.update_weather_display)
self.weather_thread.start()
# 获取每日名言
self.quote_thread = QuoteFetchThread()
self.quote_thread.quote_fetched.connect(self.update_quote_display)
self.quote_thread.start()
def init_typing_logic(self):
"""初始化打字逻辑"""
# 使用默认内容初始化打字逻辑
default_content = "欢迎使用MagicWord隐私学习软件\n\n这是一个仿Microsoft Word界面的学习工具。"
self.typing_logic = TypingLogic(default_content)
self.typing_logic.reset()
def connect_signals(self):
"""连接信号和槽"""
# 文本变化信号
self.text_edit.textChanged.connect(self.on_text_changed)
# Ribbon按钮信号
if hasattr(self.ribbon, 'tabs'):
for tab_name, tab_btn in self.ribbon.tabs.items():
tab_btn.clicked.connect(lambda checked, name=tab_name: self.on_tab_changed(name))
def on_text_changed(self):
"""文本变化处理 - 逐步显示模式"""
# 如果正在加载文件,跳过处理
if self.is_loading_file:
return
# 如果有导入的内容,实现逐步显示
if self.imported_content and self.typing_logic:
current_text = self.text_edit.toPlainText()
# 计算应该显示的字符数(用户输入多少个字符就显示多少个)
input_length = len(current_text)
# 如果用户输入长度超过了导入内容长度,限制在导入内容长度内
if input_length > len(self.imported_content):
input_length = len(self.imported_content)
# 如果显示的字符数需要更新
if input_length != self.displayed_chars:
self.displayed_chars = input_length
# 获取应该显示的文本部分
display_text = self.imported_content[:self.displayed_chars]
# 临时禁用文本变化信号,避免递归
self.text_edit.textChanged.disconnect(self.on_text_changed)
# 更新文本编辑器内容
self.text_edit.setPlainText(display_text)
# 重新连接文本变化信号
self.text_edit.textChanged.connect(self.on_text_changed)
# 将光标移动到末尾
cursor = self.text_edit.textCursor()
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)
self.highlight_next_char(result['position'], expected_char)
# 更新统计信息
stats = self.typing_logic.get_statistics()
self.update_status_bar(stats)
# 检查是否完成
if self.displayed_chars >= len(self.imported_content):
self.on_lesson_complete()
return
# 更新状态栏显示进度
progress_percentage = (self.displayed_chars / len(self.imported_content)) * 100
self.status_bar.showMessage(f"逐步显示进度: {progress_percentage:.1f}% ({self.displayed_chars}/{len(self.imported_content)})", 2000)
# 标记文档为已修改
if not self.is_modified:
self.is_modified = True
self.update_window_title()
def on_tab_changed(self, tab_name):
"""标签切换处理"""
# 更新标签状态
for name, btn in self.ribbon.tabs.items():
btn.setChecked(name == tab_name)
# 这里可以根据不同标签切换不同的功能区内容
print(f"切换到标签: {tab_name}")
def update_weather_display(self, weather_data):
"""更新天气显示"""
if 'error' in weather_data:
self.status_bar.showMessage(f"天气信息获取失败: {weather_data['error']}", 3000)
else:
temp = weather_data.get('temperature', 'N/A')
desc = weather_data.get('description', 'N/A')
self.status_bar.showMessage(f"天气: {desc}, {temp}°C", 5000)
def update_quote_display(self, quote_data):
"""更新名言显示"""
if 'error' not in quote_data:
content = quote_data.get('content', '获取名言失败')
author = quote_data.get('author', '未知')
self.status_bar.showMessage(f"每日名言: {content} - {author}", 10000)
def update_status_bar(self, stats):
"""更新状态栏统计信息"""
if stats:
# 获取打字统计信息
total_chars = stats.get('total_chars', 0)
typed_chars = stats.get('typed_chars', 0)
error_count = stats.get('error_count', 0)
accuracy_rate = stats.get('accuracy_rate', 0)
# 计算进度百分比
progress_text = ""
if total_chars > 0:
progress_percentage = (typed_chars / total_chars) * 100
progress_text = f"进度: {progress_percentage:.1f}%"
# 计算准确率
accuracy_text = f"准确率: {accuracy_rate:.1%}"
# 显示统计信息
status_text = f"{progress_text} | {accuracy_text} | 已输入: {typed_chars}/{total_chars} | 错误: {error_count}"
self.status_bar.showMessage(status_text, 0) # 0表示不自动消失
# 更新字数统计标签(如果存在)
if hasattr(self.status_bar, 'words_label'):
self.status_bar.words_label.setText(f"总字数: {total_chars}")
# 更新进度标签(如果存在)
if hasattr(self.status_bar, 'progress_label'):
self.status_bar.progress_label.setText(f"进度: {progress_percentage:.1f}%" if total_chars > 0 else "进度: 0%")
def update_window_title(self):
"""更新窗口标题"""
file_name = "文档1"
if self.current_file_path:
file_name = os.path.basename(self.current_file_path)
modified = "*" if self.is_modified else ""
self.setWindowTitle(f"{file_name}{modified} - MagicWord")
def new_document(self):
"""新建文档"""
self.text_edit.clear()
self.current_file_path = None
self.is_modified = False
self.update_window_title()
if self.typing_logic:
self.typing_logic.reset()
def open_file(self):
"""打开文件并设置为打字学习内容 - 逐步显示模式"""
file_path, _ = QFileDialog.getOpenFileName(
self, "打开文件", "",
"文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)"
)
if file_path:
try:
# 解析文件
parser = FileParser()
content = parser.parse_file(file_path)
if content:
# 设置文件加载标志
self.is_loading_file = True
# 存储完整内容但不立即显示
self.imported_content = content
self.displayed_chars = 0
# 设置学习内容到打字逻辑
if self.typing_logic:
self.typing_logic.reset(content) # 重置打字状态并设置新内容
# 清空文本编辑器,准备逐步显示
self.text_edit.clear()
# 清除文件加载标志
self.is_loading_file = False
# 设置当前文件路径
self.current_file_path = file_path
self.is_modified = False
self.update_window_title()
# 更新状态栏
self.status_bar.showMessage(f"已打开学习文件: {os.path.basename(file_path)},开始打字逐步显示学习内容!", 5000)
# 更新字数统计
if hasattr(self.status_bar, 'words_label'):
self.status_bar.words_label.setText(f"总字数: {len(content)}")
else:
QMessageBox.warning(self, "警告", "无法读取文件内容或文件为空")
except Exception as e:
# 确保在异常情况下也清除标志
self.is_loading_file = False
QMessageBox.critical(self, "错误", f"打开文件失败: {str(e)}")
print(f"文件打开错误详情: {e}") # 调试信息
def save_file(self):
"""保存文件"""
if self.current_file_path:
try:
with open(self.current_file_path, 'w', encoding='utf-8') as f:
f.write(self.text_edit.toPlainText())
self.is_modified = False
self.update_window_title()
self.status_bar.showMessage("文件已保存", 3000)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
else:
self.save_as_file()
def save_as_file(self):
"""另存为"""
file_path, _ = QFileDialog.getSaveFileName(
self, "另存为", "", "文本文档 (*.txt);;所有文件 (*.*)"
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.text_edit.toPlainText())
self.current_file_path = file_path
self.is_modified = False
self.update_window_title()
self.status_bar.showMessage(f"已保存: {os.path.basename(file_path)}", 3000)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
def undo(self):
"""撤销"""
self.text_edit.undo()
def redo(self):
"""重做"""
self.text_edit.redo()
def cut(self):
"""剪切"""
self.text_edit.cut()
def copy(self):
"""复制"""
self.text_edit.copy()
def paste(self):
"""粘贴"""
self.text_edit.paste()
def toggle_reading_view(self):
"""切换阅读视图"""
# 这里可以实现阅读视图的逻辑
self.status_bar.showMessage("阅读视图功能开发中...", 3000)
def toggle_print_layout(self):
"""切换打印布局"""
# 这里可以实现打印布局的逻辑
self.status_bar.showMessage("打印布局功能开发中...", 3000)
def show_about(self):
"""显示关于对话框"""
QMessageBox.about(
self, "关于 MagicWord",
"MagicWord - 隐私学习软件\n\n"
"版本: 2.0\n"
"基于 Microsoft Word 界面设计\n\n"
"功能特色:\n"
"• 仿Word界面设计\n"
"• 隐私学习模式\n"
"• 多格式文档支持\n"
"• 实时进度跟踪\n"
"• 天气和名言显示"
)
def highlight_next_char(self, position, expected_char):
"""高亮显示下一个期望字符"""
if position < len(self.typing_logic.learning_content):
# 获取当前光标位置
cursor = self.text_edit.textCursor()
cursor.setPosition(position)
# 选择下一个字符
cursor.movePosition(cursor.Right, cursor.KeepAnchor, 1)
# 设置高亮格式
format = QTextCharFormat()
format.setBackground(QColor(255, 255, 0, 128)) # 黄色半透明背景
format.setForeground(QColor(255, 0, 0)) # 红色文字
format.setFontWeight(QFont.Bold)
# 应用格式
cursor.setCharFormat(format)
def on_lesson_complete(self):
"""课程完成处理"""
stats = self.typing_logic.get_statistics()
QMessageBox.information(
self, "恭喜",
"恭喜完成本课程学习!\n\n"
f"准确率: {stats['accuracy_rate']*100:.1f}%\n"
f"总字符数: {stats['total_chars']}\n"
f"错误次数: {stats['error_count']}"
)
# 重置状态
if self.typing_logic:
self.typing_logic.reset()
def closeEvent(self, event):
"""关闭事件处理"""
if self.is_modified:
reply = QMessageBox.question(
self, "确认退出",
"文档已修改,是否保存更改?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
if reply == QMessageBox.Save:
self.save_file()
event.accept()
elif reply == QMessageBox.Discard:
event.accept()
else:
event.ignore()
else:
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
# 设置应用程序样式
app.setStyle('Windows')
# 创建并显示主窗口
window = WordStyleMainWindow()
window.show()
sys.exit(app.exec_())