diff --git a/build_macos_app.py b/build_macos_app.py new file mode 100644 index 0000000..c0ff9f8 --- /dev/null +++ b/build_macos_app.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +MagicWord macOS M系列芯片打包脚本 +用于构建macOS Apple Silicon原生应用程序 +""" + +import os +import sys +import subprocess +import platform +import shutil +import plistlib +from datetime import datetime + +def run_command(command, shell=False, cwd=None): + """运行命令并返回结果""" + try: + result = subprocess.run(command, shell=shell, capture_output=True, text=True, encoding='utf-8', cwd=cwd) + return result.returncode, result.stdout, result.stderr + except Exception as e: + return -1, "", str(e) + +def check_system(): + """检查系统是否为macOS Apple Silicon""" + if platform.system() != "Darwin": + print("错误: 此脚本仅支持macOS系统") + return False + + # 检查是否为Apple Silicon + machine = platform.machine() + if machine not in ['arm64', 'aarch64']: + print(f"警告: 当前为 {machine} 架构,建议Apple Silicon (arm64) 以获得最佳性能") + + print(f"系统信息: macOS {platform.mac_ver()[0]}, {machine}") + return True + +def clean_build_dirs(): + """清理构建目录""" + print("清理构建目录...") + dirs_to_clean = ['build', 'dist', '__pycache__', '*.egg-info'] + + for dir_name in dirs_to_clean: + if '*' in dir_name: + import glob + for path in glob.glob(dir_name): + if os.path.isdir(path): + shutil.rmtree(path, ignore_errors=True) + elif os.path.exists(dir_name): + if os.path.isdir(dir_name): + shutil.rmtree(dir_name, ignore_errors=True) + else: + os.remove(dir_name) + + # 清理src目录下的__pycache__ + for root, dirs, files in os.walk('src'): + for dir_name in dirs: + if dir_name == '__pycache__': + cache_path = os.path.join(root, dir_name) + shutil.rmtree(cache_path, ignore_errors=True) + print(f"清理缓存: {cache_path}") + +def install_dependencies(): + """安装依赖""" + print("安装项目依赖...") + + # 首先确保pip是最新的 + run_command([sys.executable, "-m", "pip", "install", "--upgrade", "pip"]) + + # 安装requirements.txt中的依赖 + code, stdout, stderr = run_command([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"]) + if code != 0: + print(f"依赖安装失败: {stderr}") + return False + + # 安装PyInstaller + print("安装PyInstaller...") + code, stdout, stderr = run_command([sys.executable, "-m", "pip", "install", "pyinstaller"]) + if code != 0: + print(f"PyInstaller安装失败: {stderr}") + return False + + print("依赖安装成功") + return True + +def build_macos_app(): + """构建macOS应用包""" + print("构建macOS应用包...") + + # PyInstaller命令 - 针对macOS优化 + pyinstaller_cmd = [ + "pyinstaller", + "--name", "MagicWord", + "--version", "0.3.0", + "--distpath", "dist", + "--workpath", "build", + "--specpath", ".", + # macOS特定的数据文件路径格式 + "--add-data", "resources:resources", + "--add-data", "src:src", + # 隐藏导入模块 + "--hidden-import", "PyQt5", + "--hidden-import", "PyQt5.QtCore", + "--hidden-import", "PyQt5.QtGui", + "--hidden-import", "PyQt5.QtWidgets", + "--hidden-import", "requests", + "--hidden-import", "beautifulsoup4", + "--hidden-import", "python-docx", + "--hidden-import", "PyPDF2", + "--hidden-import", "ebooklib", + "--hidden-import", "chardet", + "--hidden-import", "PIL", + # macOS应用包选项 + "--windowed", # 无控制台窗口 + "--osx-bundle-identifier", "com.magicword.app", + "--target-architecture", "arm64", # Apple Silicon + "--noconfirm", + "src/main.py" + ] + + print("运行PyInstaller...") + code, stdout, stderr = run_command(pyinstaller_cmd) + + if code != 0: + print(f"构建失败: {stderr}") + print("尝试通用架构...") + # 尝试通用架构 + pyinstaller_cmd[-2] = "--target-architecture" + pyinstaller_cmd[-1] = "universal2" + code, stdout, stderr = run_command(pyinstaller_cmd) + if code != 0: + print(f"通用架构构建也失败: {stderr}") + return False + + print("macOS应用包构建成功") + return True + +def create_app_bundle(): + """创建macOS应用束""" + print("创建macOS应用束...") + + app_path = "dist/MagicWord.app" + if not os.path.exists(app_path): + print(f"错误: 找不到应用包 {app_path}") + return False + + # 创建Info.plist文件 + info_plist = { + 'CFBundleName': 'MagicWord', + 'CFBundleDisplayName': 'MagicWord - 隐私学习软件', + 'CFBundleIdentifier': 'com.magicword.app', + 'CFBundleVersion': '0.3.0', + 'CFBundleShortVersionString': '0.3.0', + 'CFBundleExecutable': 'MagicWord', + 'CFBundlePackageType': 'APPL', + 'CFBundleSignature': '????', + 'LSMinimumSystemVersion': '11.0', # macOS Big Sur及更高版本 + 'NSHighResolutionCapable': True, + 'NSHumanReadableCopyright': 'Copyright © 2024 MagicWord Team. All rights reserved.', + 'CFBundleDocumentTypes': [ + { + 'CFBundleTypeName': 'Text Document', + 'CFBundleTypeExtensions': ['txt', 'docx', 'pdf'], + 'CFBundleTypeRole': 'Editor' + } + ] + } + + plist_path = os.path.join(app_path, "Contents", "Info.plist") + with open(plist_path, 'wb') as f: + plistlib.dump(info_plist, f) + + # 复制图标文件 + icon_files = [ + 'resources/icons/app_icon_128X128.png', + 'resources/icons/app_icon_256X256.png', + 'resources/icons/app_icon_32X32.png', + 'resources/icons/app_icon_64X64.png' + ] + + resources_dir = os.path.join(app_path, "Contents", "Resources") + os.makedirs(resources_dir, exist_ok=True) + + for icon_file in icon_files: + if os.path.exists(icon_file): + shutil.copy2(icon_file, resources_dir) + print(f"复制图标: {icon_file}") + + print("macOS应用束创建完成") + return True + +def create_dmg(): + """创建DMG安装包""" + print("创建DMG安装包...") + + app_path = "dist/MagicWord.app" + if not os.path.exists(app_path): + print(f"错误: 找不到应用包 {app_path}") + return False + + # 创建发布目录 + release_dir = "macos_release" + if os.path.exists(release_dir): + shutil.rmtree(release_dir) + os.makedirs(release_dir) + + # 复制应用到发布目录 + release_app_path = os.path.join(release_dir, "MagicWord.app") + shutil.copytree(app_path, release_app_path) + + # 创建应用程序链接 + applications_link = os.path.join(release_dir, "Applications") + os.symlink("/Applications", applications_link) + + # 创建README文件 + readme_content = f"""# MagicWord 0.3.0 for macOS + +## 安装说明 + +1. 将 MagicWord.app 拖拽到 Applications 文件夹 +2. 首次运行时,如果出现安全提示,请前往 系统设置 > 隐私与安全性 允许应用运行 +3. 或者右键点击应用选择"打开" + +## 系统要求 + +- macOS Big Sur (11.0) 或更高版本 +- Apple Silicon (M1/M2/M3) 或 Intel 处理器 + +## 功能特性 + +- 隐私学习:通过打字练习来学习文档内容 +- 支持多种文档格式:TXT, DOCX, PDF +- 智能打字模式 +- 美观的Word风格界面 + +## 版本信息 + +构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} +平台: {platform.system()} {platform.machine()} +Python版本: {platform.python_version()} + +## 技术支持 + +如有问题,请查看项目文档或联系开发团队。 +""" + + with open(os.path.join(release_dir, "README.txt"), "w") as f: + f.write(readme_content) + + # 创建DMG文件(如果系统支持) + dmg_name = f"MagicWord-0.3.0-macOS-{platform.machine()}.dmg" + dmg_path = os.path.join("dist", dmg_name) + + # 使用hdiutil创建DMG + create_dmg_cmd = [ + "hdiutil", "create", + "-volname", "MagicWord", + "-srcfolder", release_dir, + "-ov", + "-format", "UDZO", + dmg_path + ] + + code, stdout, stderr = run_command(create_dmg_cmd) + if code == 0: + print(f"DMG创建成功: {dmg_path}") + return True + else: + print(f"DMG创建失败: {stderr}") + print("已创建应用包,可手动打包DMG") + return False + +def main(): + """主函数""" + print("=== MagicWord macOS打包脚本 ===") + print(f"构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + # 检查系统 + if not check_system(): + return False + + # 清理构建目录 + clean_build_dirs() + + # 安装依赖 + if not install_dependencies(): + print("依赖安装失败") + return False + + # 构建应用 + if not build_macos_app(): + print("应用构建失败") + return False + + # 创建应用束 + if not create_app_bundle(): + print("应用束创建失败") + return False + + # 创建DMG + create_dmg() + + print("\n=== 构建完成 ===") + print("应用位置: dist/MagicWord.app") + print("如需安装,请将应用拖拽到Applications文件夹") + print("首次运行时可能需要允许未知来源的应用") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/build_simple.py b/build_simple.py new file mode 100644 index 0000000..4c7bd88 --- /dev/null +++ b/build_simple.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +MagicWord macOS简化打包脚本 +""" + +import os +import sys +import subprocess +import shutil + +def run_command(cmd): + """运行命令""" + print(f"运行: {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(f"错误: {result.stderr}") + return False + return True + +def main(): + print("=== MagicWord macOS打包 ===") + + # 清理旧的构建文件 + print("清理构建文件...") + for folder in ['build', 'dist', '__pycache__']: + if os.path.exists(folder): + shutil.rmtree(folder) + + # 安装依赖 + print("安装依赖...") + if not run_command([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"]): + return + + if not run_command([sys.executable, "-m", "pip", "install", "pyinstaller"]): + return + + # 构建命令 - 包含所有必要的图片和图标文件 + cmd = [ + sys.executable, "-m", "PyInstaller", + "--name", "MagicWord", + "--distpath", "dist", + "--workpath", "build", + # 资源文件 + "--add-data", "resources:resources", + # UI图片文件 + "--add-data", "src/ui/UI.png:ui", + "--add-data", "src/ui/114514.png:ui", + # 图标文件 + "--add-data", "resources/icons/app_icon_32X32.png:resources/icons", + "--add-data", "resources/icons/app_icon_64X64.png:resources/icons", + "--add-data", "resources/icons/app_icon_128X128.png:resources/icons", + "--add-data", "resources/icons/app_icon_256X256.png:resources/icons", + # 隐藏导入模块 + "--hidden-import", "PyQt5", + "--hidden-import", "PyQt5.QtCore", + "--hidden-import", "PyQt5.QtGui", + "--hidden-import", "PyQt5.QtWidgets", + "--hidden-import", "requests", + "--hidden-import", "beautifulsoup4", + "--hidden-import", "python-docx", + "--hidden-import", "PyPDF2", + "--hidden-import", "ebooklib", + "--hidden-import", "chardet", + "--hidden-import", "PIL", + # macOS应用选项 + "--windowed", + "--osx-bundle-identifier", "com.magicword.app", + "--icon", "resources/icons/app_icon_128X128.png", + "src/main.py" + ] + + print("构建应用...") + if run_command(cmd): + print("✅ 构建成功!") + print(f"应用位置: {os.path.abspath('dist/MagicWord.app')}") + print("\n安装步骤:") + print("1. 将 MagicWord.app 拖拽到 Applications 文件夹") + print("2. 首次运行时,右键点击应用选择'打开'") + else: + print("❌ 构建失败!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/quick_build_macos.sh b/quick_build_macos.sh new file mode 100755 index 0000000..bf8b1db --- /dev/null +++ b/quick_build_macos.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# MagicWord macOS快速打包脚本 + +echo "=== MagicWord macOS打包脚本 ===" +echo "开始打包进程..." + +# 检查Python版本 +python_version=$(python3 --version 2>/dev/null || python --version) +echo "Python版本: $python_version" + +# 安装依赖 +echo "安装依赖..." +pip3 install -r requirements.txt +pip3 install pyinstaller + +# 清理旧的构建文件 +echo "清理旧的构建文件..." +rm -rf build dist __pycache__ *.spec +find src -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true + +# 构建应用 +echo "构建macOS应用..." +python3 -m pyinstaller \\ + --name "MagicWord" \\ + --distpath "dist" \\ + --workpath "build" \\ + --add-data "resources:resources" \\ + --hidden-import "PyQt5" \\ + --hidden-import "PyQt5.QtCore" \\ + --hidden-import "PyQt5.QtGui" \\ + --hidden-import "PyQt5.QtWidgets" \\ + --hidden-import "requests" \\ + --hidden-import "beautifulsoup4" \\ + --hidden-import "python-docx" \\ + --hidden-import "PyPDF2" \\ + --hidden-import "ebooklib" \\ + --hidden-import "chardet" \\ + --hidden-import "PIL" \\ + --windowed \\ + --osx-bundle-identifier "com.magicword.app" \\ + --target-architecture arm64 \\ + --noconfirm \\ + src/main.py + +# 检查构建结果 +if [ -d "dist/MagicWord.app" ]; then + echo "✅ 构建成功!" + echo "应用位置: dist/MagicWord.app" + echo "" + echo "安装步骤:" + echo "1. 将 MagicWord.app 拖拽到 Applications 文件夹" + echo "2. 首次运行时,右键点击应用选择'打开'" + echo "3. 或者在 系统设置 > 隐私与安全性 中允许应用运行" +else + echo "❌ 构建失败!" + exit 1 +fi + +# 可选:创建DMG +read -p "是否创建DMG安装包?(y/n): " create_dmg +if [[ $create_dmg =~ ^[Yy]$ ]]; then + echo "创建DMG安装包..." + + # 创建发布目录 + release_dir="macos_release" + rm -rf "$release_dir" + mkdir -p "$release_dir" + + # 复制应用 + cp -r "dist/MagicWord.app" "$release_dir/" + + # 创建Applications链接 + ln -s "/Applications" "$release_dir/Applications" + + # 创建README + cat > "$release_dir/README.txt" << 'EOF' +MagicWord for macOS + +安装说明: +1. 将 MagicWord.app 拖拽到 Applications 文件夹 +2. 首次运行时,右键点击应用选择"打开" +3. 或者在 系统设置 > 隐私与安全性 中允许应用运行 + +系统要求: +- macOS Big Sur (11.0) 或更高版本 +- Apple Silicon (M1/M2/M3) 推荐 + +功能特性: +- 隐私学习:通过打字练习来学习文档内容 +- 支持多种文档格式:TXT, DOCX, PDF +- 智能打字模式 +- 美观的Word风格界面 +EOF + + # 创建DMG + dmg_name="MagicWord-0.3.0-macOS-arm64.dmg" + hdiutil create -volname "MagicWord" -srcfolder "$release_dir" -ov -format UDZO "$dmg_name" + + if [ -f "$dmg_name" ]; then + echo "✅ DMG创建成功: $dmg_name" + else + echo "⚠️ DMG创建失败,但应用包已准备就绪" + fi +fi + +echo "=== 打包完成 ===" \ No newline at end of file diff --git a/src/learning_mode_window.py b/src/learning_mode_window.py new file mode 100644 index 0000000..1ec350b --- /dev/null +++ b/src/learning_mode_window.py @@ -0,0 +1,434 @@ +# learning_mode_window.py +import sys +import os +from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QTextEdit, QLabel, QFrame, QMenuBar, + QAction, QFileDialog, QMessageBox, QApplication, + QSplitter, QScrollArea, QStatusBar, QProgressBar) +from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect +from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor + +from src.ui.components import CustomTitleBar, TextDisplayWidget +from src.typing_logic import TypingLogic +from src.file_parser import FileParser +from src.ui.theme_manager import theme_manager + +class LearningModeWindow(QMainWindow): + def __init__(self, parent=None, imported_content="", current_position=0): + """ + 学习模式窗口 + - 顶部显示UI.png图片 + - 下方显示输入字符页面 + - 输入字符显示导入的文件内容 + + 参数: + parent: 父窗口 + imported_content: 从主窗口传递的导入内容 + current_position: 当前学习进度位置 + """ + super().__init__(parent) + self.parent_window = parent + self.imported_content = imported_content + self.current_position = current_position + self.typing_logic = None + self.is_loading_file = False + + # 初始化UI + self.initUI() + + # 初始化打字逻辑 + self.init_typing_logic() + + # 如果有导入内容,初始化显示 + if self.imported_content: + self.initialize_with_imported_content() + + def initialize_with_imported_content(self): + """ + 使用从主窗口传递的导入内容初始化学习模式窗口 + """ + if not self.imported_content: + return + + # 重置打字逻辑 + if self.typing_logic: + self.typing_logic.reset(self.imported_content) + + # 显示已学习的内容 + display_text = self.imported_content[:self.current_position] + self.text_display_widget.text_display.setPlainText(display_text) + + # 更新状态 + progress = (self.current_position / len(self.imported_content)) * 100 + self.status_label.setText(f"继续学习 - 进度: {self.current_position}/{len(self.imported_content)} 字符") + self.progress_label.setText(f"进度: {progress:.1f}%") + + # 设置光标位置到文本末尾 + cursor = self.text_display_widget.text_display.textCursor() + cursor.movePosition(cursor.End) + self.text_display_widget.text_display.setTextCursor(cursor) + + def initUI(self): + """ + 初始化学习模式窗口UI + - 设置窗口标题和大小 + - 创建顶部图片区域 + - 创建下方输入区域 + """ + # 设置窗口属性 + self.setWindowTitle("学习模式 - MagicWord") + self.setGeometry(200, 200, 900, 700) + self.setWindowFlags(Qt.Window) # 独立窗口 + + # 创建中央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.create_top_image_area(main_layout) + + # 创建分隔线 + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Sunken) + separator.setStyleSheet("background-color: #d0d0d0;") + main_layout.addWidget(separator) + + # 创建输入区域 + self.create_input_area(main_layout) + + # 创建菜单栏 + self.create_menu_bar() + + # 创建状态栏 + self.create_status_bar() + + # 如果有导入内容,更新状态栏显示 + if self.imported_content: + progress = (self.current_position / len(self.imported_content)) * 100 + self.status_label.setText(f"继续学习 - 进度: {self.current_position}/{len(self.imported_content)} 字符") + self.progress_label.setText(f"进度: {progress:.1f}%") + + def create_top_image_area(self, main_layout): + """ + 创建顶部图片区域 + - 加载并显示UI.png图片,完全铺满窗口上方 + - 窗口大小自动适配图片大小 + """ + # 创建图片标签 + self.image_label = QLabel() + self.image_label.setAlignment(Qt.AlignCenter) + self.image_label.setStyleSheet(""" + QLabel { + background-color: #f8f9fa; + border: none; + margin: 0px; + padding: 0px; + } + """) + + # 加载UI.png图片 + ui_image_path = os.path.join(os.path.dirname(__file__), 'ui', 'UI.png') + if os.path.exists(ui_image_path): + pixmap = QPixmap(ui_image_path) + + # 保存原始图片尺寸 + self.original_pixmap = pixmap + + # 设置图片完全铺满标签 + self.image_label.setPixmap(pixmap) + self.image_label.setScaledContents(True) # 关键:让图片缩放填充整个标签 + + # 设置图片标签的尺寸策略,使其可以扩展 + from PyQt5.QtWidgets import QSizePolicy + self.image_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + # 设置图片区域的最小高度为图片高度的1/3,确保图片可见 + min_height = max(200, pixmap.height() // 3) + self.image_label.setMinimumHeight(min_height) + + # 重新设置窗口大小以适配图片 + self.resize(pixmap.width(), self.height()) + else: + self.image_label.setText("UI图片未找到") + self.image_label.setStyleSheet(""" + QLabel { + background-color: #f8f9fa; + border: none; + color: #666666; + font-size: 14px; + padding: 20px; + } + """) + self.image_label.setMinimumHeight(200) + + # 直接添加图片标签到主布局,不使用滚动区域 + main_layout.addWidget(self.image_label) + + def resizeEvent(self, event): + """ + 窗口大小改变事件 + - 动态调整图片显示区域 + """ + super().resizeEvent(event) + + # 获取窗口宽度 + window_width = self.width() + + # 如果图片标签存在,调整其大小以适配窗口 + if hasattr(self, 'image_label') and self.image_label: + # 设置图片标签的固定宽度为窗口宽度 + self.image_label.setFixedWidth(window_width) + + # 根据窗口宽度计算合适的高度(保持图片比例) + if hasattr(self, 'original_pixmap') and self.original_pixmap: + original_width = self.original_pixmap.width() + original_height = self.original_pixmap.height() + + # 计算保持比例的高度 + new_height = int(window_width * original_height / original_width) + self.image_label.setFixedHeight(new_height) + else: + # 如果没有原始图片,使用默认高度 + self.image_label.setFixedHeight(300) + + def create_input_area(self, main_layout): + """ + 创建输入区域 + - 创建文本显示组件 + - 设置与主系统相同的样式 + """ + # 创建文本显示组件(复用主系统的组件) + self.text_display_widget = TextDisplayWidget(self) + + # 设置样式,使其与主系统保持一致 + self.text_display_widget.setStyleSheet(""" + TextDisplayWidget { + background-color: white; + border: 1px solid #d0d0d0; + border-radius: 0px; + } + """) + + # 连接文本变化信号 + self.text_display_widget.text_display.textChanged.connect(self.on_text_changed) + + main_layout.addWidget(self.text_display_widget, 1) # 占据剩余空间 + + def create_menu_bar(self): + """ + 创建菜单栏 + - 文件菜单:导入、退出 + - 帮助菜单:关于 + """ + menu_bar = self.menuBar() + + # 文件菜单 + file_menu = menu_bar.addMenu('文件') + + # 导入动作 + import_action = QAction('导入文件', self) + import_action.setShortcut('Ctrl+O') + import_action.triggered.connect(self.import_file) + file_menu.addAction(import_action) + + file_menu.addSeparator() + + # 退出动作 + exit_action = QAction('退出学习模式', self) + exit_action.setShortcut('Ctrl+Q') + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + # 帮助菜单 + help_menu = menu_bar.addMenu('帮助') + + about_action = QAction('关于', self) + about_action.triggered.connect(self.show_about) + help_menu.addAction(about_action) + + def create_status_bar(self): + """ + 创建状态栏 + - 显示当前状态和学习进度 + """ + self.status_bar = self.statusBar() + + # 创建状态标签 + self.status_label = QLabel("就绪 - 请先导入文件开始学习") + self.progress_label = QLabel("进度: 0%") + + # 添加到状态栏 + self.status_bar.addWidget(self.status_label) + self.status_bar.addPermanentWidget(self.progress_label) + + def init_typing_logic(self): + """ + 初始化打字逻辑 + - 创建打字逻辑实例 + - 设置默认内容 + """ + # 创建打字逻辑实例 + self.typing_logic = TypingLogic("欢迎使用学习模式!\n\n请先导入文件开始打字学习。") + + # 设置文本显示组件的打字逻辑 + if hasattr(self.text_display_widget, 'set_typing_logic'): + self.text_display_widget.set_typing_logic(self.typing_logic) + + def import_file(self): + """ + 导入文件 + - 打开文件对话框选择文件 + - 解析文件内容 + - 重置打字逻辑 + """ + file_path, _ = QFileDialog.getOpenFileName( + self, "导入学习文件", "", + "文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)" + ) + + if file_path: + try: + self.is_loading_file = True + + # 使用文件解析器 + parser = FileParser() + content = parser.parse_file(file_path) + + if content: + # 存储导入的内容 + self.imported_content = content + self.current_position = 0 + + # 重置打字逻辑 + if self.typing_logic: + self.typing_logic.reset(content) + + # 清空文本显示 + self.text_display_widget.text_display.clear() + + # 更新状态 + self.status_label.setText(f"已导入: {os.path.basename(file_path)}") + self.progress_label.setText(f"进度: 0% (0/{len(content)} 字符)") + + # 显示成功消息 + QMessageBox.information(self, "导入成功", + f"文件导入成功!\n文件: {os.path.basename(file_path)}\n字符数: {len(content)}\n\n开始打字以显示学习内容。") + + else: + QMessageBox.warning(self, "导入失败", "无法解析文件内容,请检查文件格式。") + + except Exception as e: + QMessageBox.critical(self, "导入错误", f"导入文件时出错:\n{str(e)}") + + finally: + self.is_loading_file = False + + def on_text_changed(self): + """ + 文本变化处理 + - 根据导入的内容逐步显示 + - 更新学习进度 + """ + # 如果正在加载文件,跳过处理 + if self.is_loading_file: + return + + # 如果没有导入内容,清空文本 + if not self.imported_content: + current_text = self.text_display_widget.text_display.toPlainText() + if current_text: + self.text_display_widget.text_display.clear() + self.status_label.setText("请先导入文件开始学习") + return + + # 获取当前文本 + current_text = self.text_display_widget.text_display.toPlainText() + + # 始终确保显示的是导入的文件内容,而不是用户输入的文字 + expected_text = self.imported_content[:len(current_text)] + + # 如果当前文本与期望文本不一致,强制恢复到期望文本 + if current_text != expected_text: + cursor = self.text_display_widget.text_display.textCursor() + self.text_display_widget.text_display.setPlainText(expected_text) + self.text_display_widget.text_display.setTextCursor(cursor) + + # 显示错误信息 + if len(current_text) > 0: + expected_char = self.imported_content[len(current_text)-1] if len(current_text) <= len(self.imported_content) else '' + self.status_label.setText(f"输入错误!期望字符: '{expected_char}'") + return + + # 检查输入是否正确 + if self.typing_logic and len(current_text) > 0: + result = self.typing_logic.check_input(current_text) + + if not result['correct']: + # 输入错误,恢复到正确的状态 + expected_text = self.imported_content[:len(current_text)] + if current_text != expected_text: + cursor = self.text_display_widget.text_display.textCursor() + self.text_display_widget.text_display.setPlainText(expected_text) + self.text_display_widget.text_display.setTextCursor(cursor) + + # 显示错误信息 + self.status_label.setText(f"输入错误!期望字符: '{result.get('expected', '')}'") + else: + # 输入正确,更新进度 + self.current_position = len(current_text) + progress = (self.current_position / len(self.imported_content)) * 100 + + self.progress_label.setText( + f"进度: {progress:.1f}% ({self.current_position}/{len(self.imported_content)} 字符)" + ) + + # 检查是否完成 + if result.get('completed', False): + self.status_label.setText("恭喜!学习完成!") + QMessageBox.information(self, "学习完成", "恭喜您完成了所有学习内容!") + else: + self.status_label.setText("继续输入以显示更多内容...") + + def show_about(self): + """ + 显示关于对话框 + """ + QMessageBox.about(self, "关于学习模式", + "MagicWord 学习模式\n\n" + "功能特点:\n" + "• 顶部显示UI界面图片\n" + "• 下方为打字输入区域\n" + "• 导入文件后逐步显示内容\n" + "• 实时显示学习进度\n\n" + "使用方法:\n" + "1. 点击'文件'->'导入文件'选择学习材料\n" + "2. 在下方文本区域开始打字\n" + "3. 系统会根据您的输入逐步显示内容") + + def closeEvent(self, event): + """ + 窗口关闭事件 + - 通知父窗口学习模式已关闭 + """ + if self.parent_window and hasattr(self.parent_window, 'on_learning_mode_closed'): + self.parent_window.on_learning_mode_closed() + + event.accept() + + def keyPressEvent(self, event): + """ + 按键事件处理 + - 处理特殊按键 + """ + # 处理Escape键关闭窗口 + if event.key() == Qt.Key_Escape: + self.close() + else: + super().keyPressEvent(event) \ No newline at end of file diff --git a/src/ui/114514.png b/src/ui/114514.png new file mode 100644 index 0000000..bbcf243 Binary files /dev/null and b/src/ui/114514.png differ diff --git a/src/ui/UI.png b/src/ui/UI.png new file mode 100644 index 0000000..de2ba66 Binary files /dev/null and b/src/ui/UI.png differ diff --git a/src/ui/components.py b/src/ui/components.py new file mode 100644 index 0000000..3746898 --- /dev/null +++ b/src/ui/components.py @@ -0,0 +1,492 @@ +# ui/components.py +from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QTextEdit, QProgressBar +from PyQt5.QtCore import Qt + +class CustomTitleBar(QWidget): + def __init__(self, parent=None): + """ + 自定义标题栏 + - 创建标题栏UI元素 + - 添加窗口控制按钮 + """ + super().__init__(parent) + self.parent = parent + self.setup_ui() + + def setup_ui(self): + """ + 设置标题栏UI + - 初始化所有UI组件 + - 设置组件属性和样式 + """ + # 创建水平布局 + layout = QHBoxLayout() + layout.setContentsMargins(10, 5, 10, 5) + layout.setSpacing(10) + + # 创建标题标签 + self.title_label = QLabel("MagicWord") + self.title_label.setStyleSheet("color: #333333; font-size: 12px; font-weight: normal;") + + # 创建控制按钮 + self.minimize_button = QPushButton("—") + self.maximize_button = QPushButton("□") + self.close_button = QPushButton("×") + + # 设置按钮样式 + button_style = """ + QPushButton { + background-color: transparent; + border: none; + color: #333333; + font-size: 12px; + font-weight: normal; + width: 30px; + height: 30px; + } + QPushButton:hover { + background-color: #d0d0d0; + } + """ + + self.minimize_button.setStyleSheet(button_style) + self.maximize_button.setStyleSheet(button_style) + self.close_button.setStyleSheet(button_style + "QPushButton:hover { background-color: #ff5555; color: white; }") + + # 添加组件到布局 + layout.addWidget(self.title_label) + layout.addStretch() + layout.addWidget(self.minimize_button) + layout.addWidget(self.maximize_button) + layout.addWidget(self.close_button) + + self.setLayout(layout) + + # 连接按钮事件 + self.minimize_button.clicked.connect(self.minimize_window) + self.maximize_button.clicked.connect(self.maximize_window) + self.close_button.clicked.connect(self.close_window) + + # 设置标题栏样式 + self.setStyleSheet(""" + CustomTitleBar { + background-color: #f0f0f0; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-bottom: 1px solid #d0d0d0; + } + """) + + def minimize_window(self): + """ + 最小化窗口 + - 触发窗口最小化事件 + """ + if self.parent: + self.parent.showMinimized() + + def maximize_window(self): + """ + 最大化窗口 + - 切换窗口最大化状态 + """ + if self.parent: + if self.parent.isMaximized(): + self.parent.showNormal() + self.maximize_button.setText("□") + else: + self.parent.showMaximized() + self.maximize_button.setText("❐") + + def close_window(self): + """ + 关闭窗口 + - 触发窗口关闭事件 + """ + if self.parent: + self.parent.close() + +class ProgressBarWidget(QWidget): + def __init__(self, parent=None): + """ + 进度条组件 + - 显示打字练习进度 + - 显示统计信息 + """ + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + """ + 设置进度条UI + - 初始化所有UI组件 + - 设置组件属性和样式 + """ + # 创建垂直布局 + layout = QVBoxLayout() + layout.setContentsMargins(10, 10, 10, 10) + layout.setSpacing(5) + + # 创建水平布局用于统计信息 + stats_layout = QHBoxLayout() + stats_layout.setSpacing(15) + + # 创建统计信息标签 + self.wpm_label = QLabel("WPM: 0") + self.accuracy_label = QLabel("准确率: 0%") + self.time_label = QLabel("用时: 0s") + + # 设置标签样式 + label_style = "font-size: 12px; font-weight: normal; color: #333333;" + self.wpm_label.setStyleSheet(label_style) + self.accuracy_label.setStyleSheet(label_style) + self.time_label.setStyleSheet(label_style) + + # 添加标签到统计布局 + stats_layout.addWidget(self.wpm_label) + stats_layout.addWidget(self.accuracy_label) + stats_layout.addWidget(self.time_label) + stats_layout.addStretch() + + # 创建进度条 + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.setTextVisible(True) + self.progress_bar.setFormat("进度: %p%") + + # 设置进度条样式 + self.progress_bar.setStyleSheet(""" + QProgressBar { + border: 1px solid #c0c0c0; + border-radius: 0px; + text-align: center; + background-color: #f0f0f0; + } + QProgressBar::chunk { + background-color: #0078d7; + border-radius: 0px; + } + """) + + # 添加组件到主布局 + layout.addLayout(stats_layout) + layout.addWidget(self.progress_bar) + + self.setLayout(layout) + + def update_progress(self, progress: float): + """ + 更新进度条 + - 设置进度值 + - 更新显示 + """ + self.progress_bar.setValue(int(progress)) + + def update_stats(self, wpm: int, accuracy: float, time_elapsed: int): + """ + 更新统计信息 + - wpm: 每分钟字数 + - accuracy: 准确率(%) + - time_elapsed: 用时(秒) + """ + self.wpm_label.setText(f"WPM: {wpm}") + self.accuracy_label.setText(f"准确率: {accuracy:.1f}%") + self.time_label.setText(f"用时: {time_elapsed}s") + +class StatsDisplayWidget(QWidget): + def __init__(self, parent=None): + """ + 统计信息显示组件 + - 显示准确率、WPM等统计信息 + """ + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + """ + 设置统计信息显示UI + - 初始化所有UI组件 + - 设置组件属性和样式 + """ + # 创建水平布局 + layout = QHBoxLayout() + layout.setContentsMargins(10, 5, 10, 5) + layout.setSpacing(15) + + # 创建统计信息标签 + self.wpm_label = QLabel("WPM: 0") + self.accuracy_label = QLabel("准确率: 0%") + + # 设置标签样式 + label_style = "font-size: 12px; font-weight: normal; color: #333333;" + self.wpm_label.setStyleSheet(label_style) + self.accuracy_label.setStyleSheet(label_style) + + # 添加组件到布局 + layout.addWidget(self.wpm_label) + layout.addWidget(self.accuracy_label) + layout.addStretch() + + self.setLayout(layout) + + # 设置样式 + self.setStyleSheet(""" + StatsDisplayWidget { + background-color: #f0f0f0; + border-bottom: 1px solid #d0d0d0; + } + """) + + def update_stats(self, wpm: int, accuracy: float): + """ + 更新统计信息 + - wpm: 每分钟字数 + - accuracy: 准确率(%) + """ + self.wpm_label.setText(f"WPM: {wpm}") + self.accuracy_label.setText(f"准确率: {accuracy:.1f}%") + +class QuoteDisplayWidget(QWidget): + def __init__(self, parent=None): + """ + 每日一言显示组件 + - 显示每日一言功能 + """ + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + """ + 设置每日一言显示UI + - 初始化所有UI组件 + - 设置组件属性和样式 + """ + # 创建水平布局 + layout = QHBoxLayout() + layout.setContentsMargins(10, 5, 10, 5) + layout.setSpacing(15) + + # 创建每日一言标签 + 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.quote_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; + } + """) + + # 添加组件到布局 + layout.addWidget(self.quote_label) + layout.addStretch() + layout.addWidget(self.refresh_quote_button) + + self.setLayout(layout) + + # 设置样式 + self.setStyleSheet(""" + QuoteDisplayWidget { + background-color: #f0f0f0; + border-bottom: 1px solid #d0d0d0; + } + """) + + def update_quote(self, quote: str): + """ + 更新每日一言 + - quote: 每日一言内容 + """ + self.quote_label.setText(f"每日一言: {quote}") + +class WeatherDisplayWidget(QWidget): + def __init__(self, parent=None): + """ + 天气显示组件 + - 显示天气信息 + """ + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + """ + 设置天气显示UI + - 初始化所有UI组件 + - 设置组件属性和样式 + """ + # 创建水平布局 + layout = QHBoxLayout() + layout.setContentsMargins(10, 5, 10, 5) + layout.setSpacing(15) + + # 创建天气信息标签 + self.weather_label = QLabel("天气: 暂无") + + # 设置标签样式 + label_style = "font-size: 12px; font-weight: normal; color: #333333;" + self.weather_label.setStyleSheet(label_style) + + # 创建天气刷新按钮 + self.refresh_weather_button = QPushButton("刷新") + self.refresh_weather_button.setStyleSheet(""" + QPushButton { + background-color: #0078d7; + color: white; + border: none; + padding: 5px 10px; + border-radius: 3px; + font-size: 12px; + } + QPushButton:hover { + background-color: #005a9e; + } + """) + + # 添加组件到布局 + layout.addWidget(self.weather_label) + layout.addStretch() + layout.addWidget(self.refresh_weather_button) + + self.setLayout(layout) + + # 设置样式 + self.setStyleSheet(""" + WeatherDisplayWidget { + background-color: #f0f0f0; + border-bottom: 1px solid #d0d0d0; + } + """) + + def update_weather(self, weather_info: dict): + """ + 更新天气信息 + - weather_info: 天气信息字典 + """ + if weather_info: + city = weather_info.get("city", "未知") + temperature = weather_info.get("temperature", "N/A") + description = weather_info.get("description", "N/A") + self.weather_label.setText(f"天气: {city} {temperature}°C {description}") + else: + self.weather_label.setText("天气: 获取失败") + +class TextDisplayWidget(QWidget): + def __init__(self, parent=None): + """ + 文本显示组件 + - 显示待练习文本 + - 高亮当前字符 + - 显示用户输入反馈 + """ + super().__init__(parent) + self.text_content = "" + self.current_index = 0 + self.setup_ui() + + def setup_ui(self): + """ + 设置文本显示UI + - 初始化文本显示区域 + - 设置样式和布局 + """ + # 创建垂直布局 + layout = QVBoxLayout() + layout.setContentsMargins(20, 20, 20, 20) + + # 创建文本显示区域 + self.text_display = QTextEdit() + self.text_display.setReadOnly(False) # 设置为可编辑 + self.text_display.setLineWrapMode(QTextEdit.WidgetWidth) + + # 设置文本显示样式 + self.text_display.setStyleSheet(""" + QTextEdit { + font-family: 'Calibri', 'Segoe UI', 'Microsoft YaHei', sans-serif; + font-size: 12pt; + border: 1px solid #d0d0d0; + border-radius: 0px; + padding: 15px; + background-color: white; + color: black; + } + """) + + # 添加组件到布局 + layout.addWidget(self.text_display) + self.setLayout(layout) + + def set_text(self, text: str): + """ + 设置显示文本 + - text: 要显示的文本内容 + """ + self.text_content = text + self.current_index = 0 + # 初始不显示内容,通过打字逐步显示 + self.text_display.setHtml("") + + def highlight_character(self, position: int): + """ + 高亮指定位置的字符 + - position: 字符位置索引 + """ + if 0 <= position < len(self.text_content): + self.current_index = position + # 不再直接高亮字符,而是通过用户输入来显示内容 + pass + + def _update_display(self, user_input: str = ""): + """ + 更新文本显示 + - user_input: 用户输入文本(可选) + """ + # 导入需要的模块 + from PyQt5.QtGui import QTextCursor + + if not self.text_content: + self.text_display.clear() + return + + # 简单显示文本,不使用任何高亮 + if user_input: + # 只显示用户已输入的部分文本 + displayed_text = self.text_content[:len(user_input)] + else: + # 没有用户输入,不显示任何内容 + displayed_text = "" + + # 更新文本显示 + self.text_display.setPlainText(displayed_text) + + # 安全地滚动到光标位置 + if user_input and displayed_text: + try: + cursor = self.text_display.textCursor() + # 将光标定位到文本末尾 + cursor.setPosition(len(displayed_text)) + self.text_display.setTextCursor(cursor) + self.text_display.ensureCursorVisible() + except Exception: + # 如果光标定位失败,忽略错误 + pass + + def show_user_input(self, input_text: str): + """ + 显示用户输入的文本 + - input_text: 用户输入的文本 + """ + self._update_display(input_text) \ No newline at end of file diff --git a/src/ui/theme_manager.py b/src/ui/theme_manager.py index 2b99e10..6b42bb1 100644 --- a/src/ui/theme_manager.py +++ b/src/ui/theme_manager.py @@ -169,10 +169,10 @@ class ThemeManager(QObject): /* 菜单栏 */ QMenuBar { - background-color: #2d2d2d; - border: 1px solid #3c3c3c; + background-color: #0078d7; + border: 1px solid #005a9e; font-size: 12px; - color: #e0e0e0; + color: #ffffff; } QMenuBar::item { @@ -182,7 +182,7 @@ class ThemeManager(QObject): } QMenuBar::item:selected { - background-color: #3c3c3c; + background-color: #106ebe; } /* 菜单 */ @@ -356,6 +356,31 @@ class ThemeManager(QObject): background-color: transparent; } + /* 消息框 - 修复黑色背景问题 */ + QMessageBox { + background-color: #2d2d2d; + color: #e0e0e0; + } + + QMessageBox QPushButton { + background-color: #3c3c3c; + color: #e0e0e0; + border: 1px solid #5a5a5a; + border-radius: 3px; + padding: 5px 15px; + min-width: 80px; + } + + QMessageBox QPushButton:hover { + background-color: #4a4a4a; + border: 1px solid #6a6a6a; + } + + QMessageBox QPushButton:pressed { + background-color: #2a2a2a; + border: 1px solid #1a1a1a; + } + /* 滚动条 */ QScrollBar:vertical { background-color: #2d2d2d; @@ -396,10 +421,10 @@ class ThemeManager(QObject): /* 菜单栏 */ QMenuBar { - background-color: #ffffff; - border: 1px solid #d0d0d0; + background-color: #0078d7; + border: 1px solid #005a9e; font-size: 12px; - color: #333333; + color: #ffffff; } QMenuBar::item { @@ -409,7 +434,7 @@ class ThemeManager(QObject): } QMenuBar::item:selected { - background-color: #f0f0f0; + background-color: #106ebe; } /* 菜单 */ @@ -598,6 +623,31 @@ class ThemeManager(QObject): background-color: transparent; } + /* 消息框 - 修复黑色背景问题 */ + QMessageBox { + background-color: #ffffff; + color: #333333; + } + + QMessageBox QPushButton { + background-color: #ffffff; + color: #333333; + border: 1px solid #d0d0d0; + border-radius: 3px; + padding: 5px 15px; + min-width: 80px; + } + + QMessageBox QPushButton:hover { + background-color: #f0f0f0; + border: 1px solid #0078d7; + } + + QMessageBox QPushButton:pressed { + background-color: #e0e0e0; + border: 1px solid #005a9e; + } + /* 滚动条 */ QScrollBar:vertical { background-color: #ffffff; diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 4e34b64..5150f9f 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -1632,8 +1632,8 @@ class daily_sentence_API: params['json'] = 'true' # 默认为 JSON try: - # 发送 GET 请求 - response = requests.get(self.api_url, params=params) + # 发送 GET 请求,禁用SSL验证以避免证书问题 + response = requests.get(self.api_url, params=params, verify=False, timeout=10) response.raise_for_status() # 如果请求失败(如 4xx 或 5xx),抛出异常 # 根据格式类型处理响应 @@ -1641,6 +1641,69 @@ class daily_sentence_API: return response.json() # 解析为字典 else: return response.text # 返回文本内容 + except requests.exceptions.SSLError as e: + print(f"SSL连接失败: {e}") + # 尝试使用备用API + return self.get_sentence_from_backup_api(format_type) except requests.exceptions.RequestException as e: print(f"请求失败: {e}") + # 尝试使用备用API + return self.get_sentence_from_backup_api(format_type) + except Exception as e: + print(f"未知错误: {e}") return None + + def get_sentence_from_backup_api(self, format_type='json'): + """使用备用API获取每日一言""" + try: + # 备用API:古诗词API + response = requests.get("https://v1.jinrishici.com/all.json", timeout=5) + if response.status_code == 200: + data = response.json() + content = data.get('content', '山重水复疑无路,柳暗花明又一村。') + author = data.get('author', '陆游') + title = data.get('origin', '游山西村') + + # 格式化返回数据,与原API格式保持一致 + if format_type == 'json': + return { + 'yiyan': f"{content} — {author}《{title}》", + 'id': 'backup_001', + 'createTime': '2024-01-01', + 'nick': '古诗词' + } + else: + return f"{content} — {author}《{title}》" + else: + # 如果备用API也失败,返回默认内容 + return self.get_default_sentence(format_type) + except Exception as e: + print(f"备用API请求失败: {e}") + return self.get_default_sentence(format_type) + + def get_default_sentence(self, format_type='json'): + """获取默认的每日一言内容""" + default_sentences = [ + "学而不思则罔,思而不学则殆。 — 孔子《论语》", + "天行健,君子以自强不息。 — 《周易》", + "千里之行,始于足下。 — 老子《道德经》", + "读书破万卷,下笔如有神。 — 杜甫《奉赠韦左丞丈二十二韵》", + "海内存知己,天涯若比邻。 — 王勃《送杜少府之任蜀州》" + ] + + import random + import time + + # 使用当前时间作为随机种子,确保每天显示不同的内容 + random.seed(int(time.time() / 86400)) # 每天变化一次 + sentence = random.choice(default_sentences) + + if format_type == 'json': + return { + 'yiyan': sentence, + 'id': 'default_001', + 'createTime': '2024-01-01', + 'nick': '经典名言' + } + else: + return sentence \ No newline at end of file diff --git a/src/word_main_window.py b/src/word_main_window.py index fbf8eb3..2a7f7da 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -131,6 +131,10 @@ class WordStyleMainWindow(QMainWindow): # 连接主题切换信号 theme_manager.theme_changed.connect(self.on_theme_changed) + # 设置默认为白色模式(禁用自动检测) + theme_manager.enable_auto_detection(False) + theme_manager.set_dark_theme(False) + # 应用当前主题 self.apply_theme() @@ -150,30 +154,35 @@ class WordStyleMainWindow(QMainWindow): """更新组件样式""" colors = theme_manager.get_current_theme_colors() - # 更新菜单栏样式 + # 更新菜单栏样式 - 使用微软蓝 if hasattr(self, 'menubar'): - self.menubar.setStyleSheet(f""" - QMenuBar {{ - background-color: {colors['surface']}; - border: 1px solid {colors['border']}; + self.menubar.setStyleSheet(""" + QMenuBar { + background-color: #0078d7; + border: 1px solid #005a9e; font-size: 12px; - color: {colors['text']}; - }} + color: #ffffff; + } - QMenuBar::item {{ + QMenuBar::item { background-color: transparent; padding: 4px 10px; - color: {colors['text']}; - }} + color: #ffffff; + } - QMenuBar::item:selected {{ - background-color: {colors['surface_hover']}; - }} + QMenuBar::item:selected { + background-color: #106ebe; + } - QMenuBar::item:pressed {{ - background-color: {colors['accent']}; - color: {colors['surface']}; - }} + QMenuBar::item:pressed { + background-color: #005a9e; + color: #ffffff; + } + + #startMenu { + background-color: white; + color: #000000; + } """) # 更新文件菜单样式 @@ -538,6 +547,7 @@ class WordStyleMainWindow(QMainWindow): # 开始菜单 start_menu = menubar.addMenu('开始(S)') + start_menu.setObjectName("startMenu") self.start_menu = start_menu # 保存为实例变量 # 撤销 @@ -657,24 +667,30 @@ class WordStyleMainWindow(QMainWindow): show_weather_action = QAction('显示详细天气', self) show_weather_action.triggered.connect(self.show_detailed_weather) weather_menu.addAction(show_weather_action) - #插入 + # 插入菜单 insert_menu = menubar.addMenu('插入(I)') - #绘图 + + # 绘图菜单 paint_menu = menubar.addMenu('绘图(D)') - #设计 + + # 设计菜单 design_menu = menubar.addMenu('设计(G)') - #布局 + + # 布局菜单 layout_menu = menubar.addMenu('布局(L)') - #引用 + + # 引用菜单 reference_menu = menubar.addMenu('引用(R)') - #邮件 + + # 邮件菜单 mail_menu = menubar.addMenu('邮件(M)') - #审阅 + + # 审阅菜单 review_menu = menubar.addMenu('审阅(W)') - #视图 - view_menu = menubar.addMenu('视图(V)') - #开发工具 + + # 开发工具菜单 developer_menu = menubar.addMenu('开发工具(Q)') + # 帮助菜单 help_menu = menubar.addMenu('帮助(H)') @@ -1822,132 +1838,59 @@ class WordStyleMainWindow(QMainWindow): self.status_bar.showMessage("打印布局功能开发中...", 3000) def set_view_mode(self, mode): - """设置视图模式 - 实现文档A和学习文档C的正确交互""" + """设置视图模式 - 打开学习模式窗口或切换到打字模式""" if mode not in ["typing", "learning"]: return - - # 保存当前模式的内容 - current_content = self.text_edit.toPlainText() - - # 根据当前模式保存特定信息 - if self.view_mode == "typing": - # 打字模式:保存打字内容到文档A - self.typing_mode_content = current_content - print(f"保存打字模式内容到文档A,长度: {len(self.typing_mode_content)}") - elif self.view_mode == "learning": - # 学习模式:保存当前学习进度和内容 - self.learning_text = current_content - if hasattr(self, 'displayed_chars') and self.imported_content: - self.learning_progress = self.displayed_chars - print(f"保存学习模式内容,学习进度: {self.learning_progress}/{len(self.imported_content) if self.imported_content else 0}") - - # 更新模式 - self.view_mode = mode - self.last_edit_mode = mode - # 更新菜单项状态 - self.typing_mode_action.setChecked(mode == "typing") - self.learning_mode_action.setChecked(mode == "learning") - - # 临时禁用文本变化信号,避免递归 - self.text_edit.textChanged.disconnect(self.on_text_changed) - - try: - if mode == "typing": - # 打字模式:显示学习模式下已输入的内容(文档A) - self.status_bar.showMessage("切换到打字模式 - 显示已输入的内容", 3000) - - # 设置文档A的内容(学习模式下已输入的内容) - self.text_edit.clear() - if hasattr(self, 'learning_progress') and self.learning_progress > 0: - # 显示学习模式下已输入的内容 - display_text = self.imported_content[:self.learning_progress] - self.text_edit.setPlainText(display_text) - else: - # 如果没有学习进度,显示默认提示 - self.text_edit.setPlainText("请先在学习模式下输入内容") + if mode == "learning": + # 学习模式:打开新的学习模式窗口 + try: + from learning_mode_window import LearningModeWindow - # 设置光标位置到文档末尾 - cursor = self.text_edit.textCursor() - cursor.movePosition(cursor.End) - self.text_edit.setTextCursor(cursor) + # 准备传递给学习窗口的参数 + imported_content = "" + current_position = 0 - # 重置打字逻辑,准备接受新的输入 - if self.typing_logic: + # 如果有已导入的内容,传递给学习窗口 + if hasattr(self, 'imported_content') and self.imported_content: + imported_content = self.imported_content if hasattr(self, 'learning_progress') and self.learning_progress > 0: - # 使用已输入的内容作为打字逻辑的基础 - self.typing_logic.reset(self.imported_content) - # 设置当前位置到已输入的末尾 - self.typing_logic.current_index = self.learning_progress - self.typing_logic.typed_chars = self.learning_progress - else: - self.typing_logic.reset("") + current_position = self.learning_progress - # 重置显示字符计数 - self.displayed_chars = 0 + # 创建学习模式窗口,直接传递导入内容 + self.learning_window = LearningModeWindow(self, imported_content, current_position) - # 重置图片插入记录 - if hasattr(self, 'inserted_images'): - self.inserted_images.clear() + # 显示学习模式窗口 + self.learning_window.show() - elif mode == "learning": - # 学习模式:从上次中断的地方继续显示学习文档C的内容 - if not self.imported_content: - self.status_bar.showMessage("学习模式需要导入文件 - 请先打开一个文件", 3000) - # 清空文本编辑器,等待导入文件 - self.text_edit.clear() - if self.typing_logic: - self.typing_logic.reset("在此输入您的内容...") - # 重置显示字符计数 - self.displayed_chars = 0 - # 重置图片插入记录 - if hasattr(self, 'inserted_images'): - self.inserted_images.clear() - else: - # 检查是否有保存的学习进度 - if hasattr(self, 'learning_progress') and self.learning_progress > 0: - self.status_bar.showMessage(f"切换到学习模式 - 从上次中断处继续 ({self.learning_progress}/{len(self.imported_content)})", 3000) - # 恢复学习进度 - self.displayed_chars = self.learning_progress - - # 重置打字逻辑,准备接受导入内容 - if self.typing_logic: - self.typing_logic.reset(self.imported_content) - - # 获取应该显示的学习文档C的内容部分 - display_text = self.imported_content[:self.displayed_chars] - - # 设置文本内容 - self.text_edit.clear() - self.text_edit.setPlainText(display_text) - - # 设置光标位置到文本末尾 - cursor = self.text_edit.textCursor() - cursor.movePosition(cursor.End) - self.text_edit.setTextCursor(cursor) - - # 重新插入图片 - if hasattr(self, 'inserted_images'): - self.inserted_images.clear() - self.insert_images_in_text() - else: - self.status_bar.showMessage("切换到学习模式 - 准备显示学习文档C内容", 3000) - # 重置打字逻辑,准备接受导入内容 - if self.typing_logic: - self.typing_logic.reset(self.imported_content) - - # 重置显示字符计数 - self.displayed_chars = 0 - - # 清空文本编辑器,等待用户开始打字 - self.text_edit.clear() - - # 重置图片插入记录 - if hasattr(self, 'inserted_images'): - self.inserted_images.clear() - finally: - # 重新连接文本变化信号 - self.text_edit.textChanged.connect(self.on_text_changed) + # 更新菜单状态 + self.learning_mode_action.setChecked(True) + self.typing_mode_action.setChecked(False) + + # 切换到打字模式(主窗口保持打字模式) + self.view_mode = "typing" + + self.status_bar.showMessage("学习模式窗口已打开", 3000) + + except ImportError as e: + QMessageBox.critical(self, "错误", f"无法加载学习模式窗口:\n{str(e)}") + except Exception as e: + QMessageBox.critical(self, "错误", f"打开学习模式窗口时出错:\n{str(e)}") + + elif mode == "typing": + # 打字模式:保持当前窗口状态 + self.view_mode = "typing" + self.typing_mode_action.setChecked(True) + self.learning_mode_action.setChecked(False) + self.status_bar.showMessage("当前为打字模式", 2000) + + def on_learning_mode_closed(self): + """学习模式窗口关闭时的回调""" + # 重置菜单状态 + self.learning_mode_action.setChecked(False) + self.typing_mode_action.setChecked(True) + self.view_mode = "typing" + self.status_bar.showMessage("学习模式窗口已关闭", 2000) def set_page_color(self, color): """设置页面颜色""" @@ -2264,10 +2207,59 @@ class WordStyleMainWindow(QMainWindow): def show_about(self): """显示关于对话框""" - QMessageBox.about( - self, "关于 MagicWord", - "MagicWord - 隐私学习软件\n\n" - "版本: 2.0\n" + # 创建自定义对话框 + dialog = QDialog(self) + dialog.setWindowTitle("关于 MagicWord") + dialog.setModal(True) + dialog.setFixedSize(500, 400) + + # 创建布局 + layout = QVBoxLayout() + + # 添加图标和标题布局 + header_layout = QHBoxLayout() + + # 添加应用程序图标 + try: + icon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "icons", "app_icon_128X128.png") + if os.path.exists(icon_path): + icon_label = QLabel() + icon_label.setAlignment(Qt.AlignCenter) + pixmap = QPixmap(icon_path) + if not pixmap.isNull(): + # 缩放图标到合适大小 + scaled_pixmap = pixmap.scaled(80, 80, Qt.KeepAspectRatio, Qt.SmoothTransformation) + icon_label.setPixmap(scaled_pixmap) + header_layout.addWidget(icon_label) + except Exception as e: + print(f"加载图标失败: {e}") + + # 添加标题和版本信息 + title_layout = QVBoxLayout() + title_label = QLabel("MagicWord") + title_label.setStyleSheet("font-size: 24px; font-weight: bold; color: #0078d7;") + title_label.setAlignment(Qt.AlignCenter) + + version_label = QLabel("版本 1.0") + version_label.setStyleSheet("font-size: 14px; color: #666;") + version_label.setAlignment(Qt.AlignCenter) + + title_layout.addWidget(title_label) + title_layout.addWidget(version_label) + header_layout.addLayout(title_layout) + + layout.addLayout(header_layout) + + # 添加分隔线 + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setFrameShadow(QFrame.Sunken) + separator.setStyleSheet("color: #ddd;") + layout.addWidget(separator) + + # 添加关于信息 + about_text = QLabel( + "隐私学习软件\n\n" "基于 Microsoft Word 界面设计\n\n" "功能特色:\n" "• 仿Word界面设计\n" @@ -2276,6 +2268,79 @@ class WordStyleMainWindow(QMainWindow): "• 实时进度跟踪\n" "• 天气和名言显示" ) + about_text.setAlignment(Qt.AlignCenter) + about_text.setStyleSheet("font-size: 12px; line-height: 1.5;") + layout.addWidget(about_text) + + # 添加按钮布局 + button_layout = QHBoxLayout() + button_layout.addStretch() # 添加弹性空间使按钮居中 + + # 添加"沃式烁"按钮 + woshishuo_button = QPushButton("沃式烁") + woshishuo_button.clicked.connect(lambda: self.show_woshishuo_image()) + button_layout.addWidget(woshishuo_button) + + # 添加OK按钮 + ok_button = QPushButton("OK") + ok_button.clicked.connect(dialog.accept) + button_layout.addWidget(ok_button) + + button_layout.addStretch() # 添加弹性空间使按钮居中 + + layout.addLayout(button_layout) + dialog.setLayout(layout) + + # 显示对话框 + dialog.exec_() + + def show_woshishuo_image(self): + """显示沃式烁图片""" + try: + # 图片路径 + image_path = os.path.join(os.path.dirname(__file__), "ui", "114514.png") + + # 检查图片是否存在 + if not os.path.exists(image_path): + QMessageBox.warning(self, "错误", "图片文件不存在: 114514.png") + return + + # 创建图片查看对话框 + image_dialog = QDialog(self) + image_dialog.setWindowTitle("沃式烁") + image_dialog.setModal(True) + image_dialog.setFixedSize(500, 400) + + # 创建布局 + layout = QVBoxLayout() + + # 添加图片标签 + image_label = QLabel() + image_label.setAlignment(Qt.AlignCenter) + image_label.setScaledContents(True) + + # 加载图片 + pixmap = QPixmap(image_path) + if pixmap.isNull(): + QMessageBox.warning(self, "错误", "无法加载图片文件") + return + + # 缩放图片以适应对话框 + scaled_pixmap = pixmap.scaled(450, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) + image_label.setPixmap(scaled_pixmap) + + layout.addWidget(image_label) + + # 添加关闭按钮 + close_button = QPushButton("关闭") + close_button.clicked.connect(image_dialog.accept) + layout.addWidget(close_button) + + image_dialog.setLayout(layout) + image_dialog.exec_() + + except Exception as e: + QMessageBox.warning(self, "错误", f"显示图片时出错: {str(e)}") def highlight_next_char(self, position, expected_char): """高亮显示下一个期望字符""" @@ -2337,49 +2402,122 @@ class WordStyleMainWindow(QMainWindow): self.status_bar.showMessage("已切换到黑色模式", 2000) def show_image_viewer(self, filename, image_data): - """显示图片查看器""" + """显示图片查看器 - 图片真正铺满整个窗口上方""" try: - # 创建图片查看窗口 + # 创建自定义图片查看窗口 viewer = QDialog(self) viewer.setWindowTitle(f"图片查看 - {filename}") viewer.setModal(False) - viewer.resize(800, 600) - # 创建布局 - layout = QVBoxLayout() + # 移除窗口边框和标题栏装饰,设置为工具窗口样式 + viewer.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) - # 创建滚动区域 - scroll_area = QScrollArea() - scroll_area.setWidgetResizable(True) + # 设置窗口背景为黑色,完全无边距 + viewer.setStyleSheet(""" + QDialog { + background-color: #000000; + border: none; + margin: 0px; + padding: 0px; + } + """) - # 创建图片标签 + # 创建布局,完全移除边距 + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) # 移除布局边距 + layout.setSpacing(0) # 移除组件间距 + layout.setAlignment(Qt.AlignCenter) # 布局居中对齐 + + # 创建图片标签,设置为完全填充模式 image_label = QLabel() image_label.setAlignment(Qt.AlignCenter) + image_label.setScaledContents(True) # 关键:允许图片缩放以填充标签 + image_label.setMinimumSize(1, 1) # 设置最小尺寸 + image_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # 设置大小策略为扩展 + image_label.setStyleSheet(""" + QLabel { + border: none; + margin: 0px; + padding: 0px; + background-color: #000000; + } + """) # 加载图片 pixmap = QPixmap() - if pixmap.loadFromData(image_data): - image_label.setPixmap(pixmap) - image_label.setScaledContents(False) - - # 设置标签大小为图片实际大小 - image_label.setFixedSize(pixmap.size()) - else: - image_label.setText("无法加载图片") + if not pixmap.loadFromData(image_data): + self.status_bar.showMessage(f"加载图片失败: {filename}", 3000) + return - scroll_area.setWidget(image_label) - layout.addWidget(scroll_area) + layout.addWidget(image_label) + viewer.setLayout(layout) - # 添加关闭按钮 - close_btn = QPushButton("关闭") - close_btn.clicked.connect(viewer.close) - layout.addWidget(close_btn) + # 计算位置和大小 + if self: + parent_geometry = self.geometry() + screen_geometry = QApplication.primaryScreen().geometry() + + # 设置窗口宽度与主窗口相同,高度为屏幕高度的40% + window_width = parent_geometry.width() + window_height = int(screen_geometry.height() * 0.4) + + # 计算位置:显示在主窗口正上方 + x = parent_geometry.x() + y = parent_geometry.y() - window_height + + # 确保不会超出屏幕边界 + if y < screen_geometry.top(): + y = parent_geometry.y() + 50 # 如果上方空间不足,显示在下方 + + # 调整宽度确保不超出屏幕 + if x + window_width > screen_geometry.right(): + window_width = screen_geometry.right() - x + + viewer.setGeometry(x, y, window_width, window_height) - viewer.setLayout(layout) viewer.show() + # 关键:强制图片立即填充整个标签区域 + def force_image_fill(): + try: + if pixmap and not pixmap.isNull(): + # 获取标签的实际大小 + label_size = image_label.size() + if label_size.width() > 10 and label_size.height() > 10: # 确保尺寸有效 + # 完全填充,忽略宽高比,真正铺满 + scaled_pixmap = pixmap.scaled( + label_size, + Qt.IgnoreAspectRatio, # 关键:忽略宽高比,强制填充 + Qt.SmoothTransformation + ) + image_label.setPixmap(scaled_pixmap) + print(f"图片已强制缩放至 {label_size.width()}x{label_size.height()}") + + # 确保标签完全填充布局 + image_label.setMinimumSize(label_size) + except Exception as e: + print(f"图片缩放失败: {e}") + + # 使用多个定时器确保图片正确填充 + from PyQt5.QtCore import QTimer + QTimer.singleShot(50, force_image_fill) # 50毫秒后执行 + QTimer.singleShot(200, force_image_fill) # 200毫秒后执行 + QTimer.singleShot(1000, force_image_fill) # 1000毫秒后再执行一次 + + # 连接窗口大小变化事件 + viewer.resizeEvent = lambda event: force_image_fill() + + # 添加点击关闭功能 + def close_viewer(): + viewer.close() + + image_label.mousePressEvent = lambda event: close_viewer() + viewer.mousePressEvent = lambda event: close_viewer() + except Exception as e: self.status_bar.showMessage(f"创建图片查看器失败: {str(e)}", 3000) + import traceback + traceback.print_exc() def insert_images_in_text(self): """在文本中插入图片 - 修复图片显示逻辑"""