From a4c1489461400f277daeaa900ecc070d12708627 Mon Sep 17 00:00:00 2001 From: Horse861 <929110464@qq.com> Date: Thu, 16 Oct 2025 11:15:22 +0800 Subject: [PATCH] UI-fix --- src/file_parser.py | 55 ++- src/main.py | 27 +- src/services/network_service.py | 10 +- src/ui/word_style_ui.py | 371 ++++++++++++++++++ src/word_main_window.py | 655 ++++++++++++++++++++++++++++++++ 5 files changed, 1085 insertions(+), 33 deletions(-) create mode 100644 src/ui/word_style_ui.py create mode 100644 src/word_main_window.py diff --git a/src/file_parser.py b/src/file_parser.py index 0e3d171..3a3691c 100644 --- a/src/file_parser.py +++ b/src/file_parser.py @@ -33,18 +33,8 @@ class FileParser: if not FileParser.validate_file_path(file_path): raise ValueError(f"Invalid file path: {file_path}") - # 导入工具函数来检测编码 - try: - from src.utils.helper_functions import Utils - except ImportError: - # 如果无法导入,使用默认方法检测编码 - import chardet - with open(file_path, 'rb') as f: - raw_data = f.read(1024) - encoding = chardet.detect(raw_data)['encoding'] or 'utf-8' - else: - # 使用工具函数检测编码 - encoding = Utils.detect_encoding(file_path) + # 尝试多种编码方式读取文件 + encoding = FileParser.detect_file_encoding(file_path) # 读取文件内容 with open(file_path, 'r', encoding=encoding, errors='ignore') as f: @@ -55,6 +45,47 @@ class FileParser: return content + @staticmethod + def detect_file_encoding(file_path: str) -> str: + """检测文件编码""" + # 首先尝试UTF-8 + try: + with open(file_path, 'r', encoding='utf-8') as f: + f.read() + return 'utf-8' + except UnicodeDecodeError: + pass + + # 尝试GBK(中文Windows常用) + try: + with open(file_path, 'r', encoding='gbk') as f: + f.read() + return 'gbk' + except UnicodeDecodeError: + pass + + # 尝试GB2312 + try: + with open(file_path, 'r', encoding='gb2312') as f: + f.read() + return 'gb2312' + except UnicodeDecodeError: + pass + + # 尝试使用chardet(如果可用) + try: + import chardet + with open(file_path, 'rb') as f: + raw_data = f.read(1024) + result = chardet.detect(raw_data) + if result and result['encoding']: + return result['encoding'] + except ImportError: + pass + + # 默认返回UTF-8 + return 'utf-8' + @staticmethod def parse_docx(file_path: str) -> str: diff --git a/src/main.py b/src/main.py index b8c620a..a49832d 100644 --- a/src/main.py +++ b/src/main.py @@ -20,30 +20,25 @@ elif os.path.exists(venv_qt_plugins_path): from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import Qt from PyQt5.QtGui import QIcon -from src.main_window import MainWindow +from word_main_window import WordStyleMainWindow # 设置高DPI支持(必须在QApplication创建之前) QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) def main(): - """ - 应用程序主入口点 - - 创建QApplication实例 - - 设置应用程序属性 - - 创建MainWindow实例 - - 显示窗口 - - 启动事件循环 - - 返回退出码 - """ + """应用程序入口函数 - Word风格版本""" try: # 创建QApplication实例 app = QApplication(sys.argv) + # 设置应用程序样式为Windows风格,更接近Word界面 + app.setStyle('WindowsVista') + # 设置应用程序属性 - app.setApplicationName("隐私学习软件") - app.setApplicationVersion("0.1.0") - app.setOrganizationName("MagicWord Team") + app.setApplicationName("MagicWord") + app.setApplicationVersion("2.0") + app.setOrganizationName("MagicWord") # 设置窗口图标(如果存在) icon_path = os.path.join(project_root, 'resources', 'icons', 'app_icon.png') @@ -53,9 +48,9 @@ def main(): # 使用默认图标 app.setWindowIcon(QIcon()) - # 创建主窗口 - window = MainWindow() - window.show() + # 创建Word风格的主窗口 + main_window = WordStyleMainWindow() + main_window.show() # 启动事件循环并返回退出码 exit_code = app.exec_() diff --git a/src/services/network_service.py b/src/services/network_service.py index e8ee040..60e7f4a 100644 --- a/src/services/network_service.py +++ b/src/services/network_service.py @@ -17,13 +17,13 @@ class NetworkService: # 实现天气信息获取逻辑 # 1. 获取用户IP地址 try: - ip_response = self.session.get("https://httpbin.org/ip", timeout=5) + ip_response = self.session.get("https://httpbin.org/ip", timeout=5, verify=False) ip_data = ip_response.json() ip = ip_data.get("origin", "") # 2. 根据IP获取地理位置 # 注意:这里使用免费的IP地理位置API,实际应用中可能需要更精确的服务 - location_response = self.session.get(f"http://ip-api.com/json/{ip}", timeout=5) + location_response = self.session.get(f"http://ip-api.com/json/{ip}", timeout=5, verify=False) location_data = location_response.json() if location_data.get("status") != "success": @@ -36,7 +36,7 @@ class NetworkService: # 在实际应用中,需要设置有效的API密钥 if self.api_key: weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn" - weather_response = self.session.get(weather_url, timeout=5) + weather_response = self.session.get(weather_url, timeout=5, verify=False) weather_data = weather_response.json() # 4. 解析并格式化数据 @@ -68,8 +68,8 @@ class NetworkService: # 实现每日一句获取逻辑 # 1. 调用名言API try: - # 使用一个免费的名言API - response = self.session.get("https://api.quotable.io/random", timeout=5) + # 使用一个免费的名言API,禁用SSL验证以避免证书问题 + response = self.session.get("https://api.quotable.io/random", timeout=5, verify=False) # 2. 解析返回的名言数据 if response.status_code == 200: diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py new file mode 100644 index 0000000..b82c8bc --- /dev/null +++ b/src/ui/word_style_ui.py @@ -0,0 +1,371 @@ +# word_style_ui.py +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QTabWidget, QFrame, QTextEdit, + QToolButton, QMenuBar, QStatusBar, QGroupBox, + QComboBox, QSpinBox, QFontComboBox, QToolBar) +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtGui import QFont, QIcon, QPalette, QColor + +class WordRibbonTab(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + layout = QHBoxLayout() + layout.setSpacing(10) + layout.setContentsMargins(10, 5, 10, 5) + self.setLayout(layout) + +class WordRibbon(QFrame): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + self.setFrameStyle(QFrame.NoFrame) + self.setFixedHeight(120) + + # 主布局 + main_layout = QVBoxLayout() + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # 标签栏 + self.tab_bar = QWidget() + self.tab_bar.setFixedHeight(25) + self.tab_bar.setStyleSheet(""" + QWidget { + background-color: #f3f2f1; + border-bottom: 1px solid #e1e1e1; + } + """) + + tab_layout = QHBoxLayout() + tab_layout.setContentsMargins(10, 0, 0, 0) + tab_layout.setSpacing(0) + + # 创建标签按钮 + self.tabs = {} + tab_names = ['文件', '开始', '插入', '设计', '布局', '引用', '邮件', '审阅', '视图', '帮助'] + for name in tab_names: + tab_btn = QPushButton(name) + tab_btn.setFlat(True) + tab_btn.setFixedHeight(25) + tab_btn.setStyleSheet(""" + QPushButton { + background-color: transparent; + border: none; + padding: 5px 15px; + font-size: 12px; + color: #333333; + } + QPushButton:hover { + background-color: #e1e1e1; + } + QPushButton:checked { + background-color: #ffffff; + border: 1px solid #d0d0d0; + border-bottom: 1px solid #ffffff; + } + """) + self.tabs[name] = tab_btn + tab_layout.addWidget(tab_btn) + + self.tab_bar.setLayout(tab_layout) + + # 功能区 + self.ribbon_area = QFrame() + self.ribbon_area.setStyleSheet(""" + QFrame { + background-color: #ffffff; + border: 1px solid #d0d0d0; + border-top: none; + } + """) + + ribbon_layout = QHBoxLayout() + ribbon_layout.setContentsMargins(10, 5, 10, 5) + ribbon_layout.setSpacing(15) + + # 开始标签的内容(最常用的功能) + self.setup_home_tab(ribbon_layout) + + self.ribbon_area.setLayout(ribbon_layout) + + main_layout.addWidget(self.tab_bar) + main_layout.addWidget(self.ribbon_area) + self.setLayout(main_layout) + + # 设置默认选中的标签 + self.tabs['开始'].setChecked(True) + + def setup_home_tab(self, layout): + """设置开始标签的功能区内容""" + + # 剪贴板组 + clipboard_group = self.create_ribbon_group("剪贴板") + paste_btn = self.create_ribbon_button("粘贴", "Ctrl+V", "paste") + cut_btn = self.create_ribbon_button("剪切", "Ctrl+X", "cut") + copy_btn = self.create_ribbon_button("复制", "Ctrl+C", "copy") + + clipboard_layout = QVBoxLayout() + clipboard_layout.addWidget(paste_btn) + + small_btn_layout = QHBoxLayout() + small_btn_layout.addWidget(cut_btn) + small_btn_layout.addWidget(copy_btn) + clipboard_layout.addLayout(small_btn_layout) + + clipboard_group.setLayout(clipboard_layout) + layout.addWidget(clipboard_group) + + # 字体组 + font_group = self.create_ribbon_group("字体") + + # 字体选择 + font_layout = QHBoxLayout() + self.font_combo = QFontComboBox() + self.font_combo.setFixedWidth(120) + self.font_size_combo = QComboBox() + self.font_size_combo.addItems(['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24', '26', '28', '36', '48', '72']) + self.font_size_combo.setFixedWidth(50) + self.font_size_combo.setCurrentText('12') + + font_layout.addWidget(self.font_combo) + font_layout.addWidget(self.font_size_combo) + + # 字体样式按钮 + font_style_layout = QHBoxLayout() + self.bold_btn = self.create_toggle_button("B", "bold") + self.italic_btn = self.create_toggle_button("I", "italic") + self.underline_btn = self.create_toggle_button("U", "underline") + + font_style_layout.addWidget(self.bold_btn) + font_style_layout.addWidget(self.italic_btn) + font_style_layout.addWidget(self.underline_btn) + + font_main_layout = QVBoxLayout() + font_main_layout.addLayout(font_layout) + font_main_layout.addLayout(font_style_layout) + + font_group.setLayout(font_main_layout) + layout.addWidget(font_group) + + # 段落组 + paragraph_group = self.create_ribbon_group("段落") + + # 对齐方式 + align_layout = QHBoxLayout() + self.align_left_btn = self.create_toggle_button("左对齐", "align_left") + self.align_center_btn = self.create_toggle_button("居中", "align_center") + self.align_right_btn = self.create_toggle_button("右对齐", "align_right") + self.align_justify_btn = self.create_toggle_button("两端对齐", "align_justify") + + align_layout.addWidget(self.align_left_btn) + align_layout.addWidget(self.align_center_btn) + align_layout.addWidget(self.align_right_btn) + align_layout.addWidget(self.align_justify_btn) + + paragraph_layout = QVBoxLayout() + paragraph_layout.addLayout(align_layout) + + paragraph_group.setLayout(paragraph_layout) + layout.addWidget(paragraph_group) + + # 样式组 + styles_group = self.create_ribbon_group("样式") + styles_layout = QVBoxLayout() + styles_layout.addWidget(QLabel("样式")) + styles_group.setLayout(styles_layout) + layout.addWidget(styles_group) + + # 编辑组 + editing_group = self.create_ribbon_group("编辑") + find_btn = self.create_ribbon_button("查找", "Ctrl+F", "find") + replace_btn = self.create_ribbon_button("替换", "Ctrl+H", "replace") + + editing_layout = QVBoxLayout() + editing_layout.addWidget(find_btn) + editing_layout.addWidget(replace_btn) + editing_group.setLayout(editing_layout) + layout.addWidget(editing_group) + + layout.addStretch() + + def create_ribbon_group(self, title): + """创建功能区组""" + group = QGroupBox(title) + group.setStyleSheet(""" + QGroupBox { + font-size: 11px; + font-weight: normal; + color: #333333; + border: 1px solid #e1e1e1; + border-radius: 0px; + margin-top: 5px; + padding-top: 5px; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 5px 0 5px; + } + """) + return group + + def create_ribbon_button(self, text, shortcut, icon_name): + """创建功能区按钮""" + btn = QToolButton() + btn.setText(text) + btn.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + btn.setFixedSize(60, 60) + btn.setStyleSheet(""" + QToolButton { + border: 1px solid transparent; + border-radius: 3px; + background-color: transparent; + font-size: 11px; + color: #333333; + } + QToolButton:hover { + background-color: #f0f0f0; + border: 1px solid #d0d0d0; + } + QToolButton:pressed { + background-color: #e1e1e1; + border: 1px solid #c0c0c0; + } + """) + return btn + + def create_toggle_button(self, text, icon_name): + """创建切换按钮""" + btn = QToolButton() + btn.setText(text) + btn.setCheckable(True) + btn.setToolButtonStyle(Qt.ToolButtonTextOnly) + btn.setFixedSize(30, 25) + btn.setStyleSheet(""" + QToolButton { + border: 1px solid #d0d0d0; + border-radius: 2px; + background-color: transparent; + font-size: 12px; + font-weight: bold; + color: #333333; + } + QToolButton:hover { + background-color: #f0f0f0; + } + QToolButton:checked { + background-color: #e1e1e1; + border: 1px solid #c0c0c0; + } + """) + return btn + +class WordStatusBar(QStatusBar): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + """设置状态栏""" + self.setStyleSheet(""" + QStatusBar { + background-color: #f3f2f1; + border-top: 1px solid #d0d0d0; + font-size: 11px; + color: #333333; + } + """) + + # 添加状态栏项目 + self.page_label = QLabel("第 1 页,共 1 页") + self.words_label = QLabel("字数: 0") + self.language_label = QLabel("中文(中国)") + self.input_mode_label = QLabel("插入") + + self.addPermanentWidget(self.page_label) + self.addPermanentWidget(self.words_label) + self.addPermanentWidget(self.language_label) + self.addPermanentWidget(self.input_mode_label) + +class WordTextEdit(QTextEdit): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + """设置文本编辑区域样式""" + self.setStyleSheet(""" + QTextEdit { + background-color: #ffffff; + border: 1px solid #d0d0d0; + font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 12pt; + color: #000000; + padding: 20px; + line-height: 1.5; + } + """) + + # 设置页面边距和背景 + self.setViewportMargins(50, 50, 50, 50) + + # 设置默认字体 + font = QFont("Calibri", 12) + font.setStyleStrategy(QFont.PreferAntialias) + self.setFont(font) + + # 启用自动换行 + self.setLineWrapMode(QTextEdit.WidgetWidth) + + # 设置光标宽度 + 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() + + 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 \ No newline at end of file diff --git a/src/word_main_window.py b/src/word_main_window.py new file mode 100644 index 0000000..60074b9 --- /dev/null +++ b/src/word_main_window.py @@ -0,0 +1,655 @@ +# 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_()) \ No newline at end of file