From 660a12abac4bbf33c707f5a00a380950c40a966f Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Thu, 6 Nov 2025 16:39:00 +0800 Subject: [PATCH 01/15] =?UTF-8?q?MAC=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PYQT5_FIX_GUIDE.md | 120 +++++++++++++++++++++ emergency_fix.sh | 55 ++++++++++ fix_pyqt5_complete.py | 237 ++++++++++++++++++++++++++++++++++++++++++ install_pyqt5_safe.py | 65 ++++++++++++ run_magicword.sh | 29 ++++++ set_pyqt5_env.sh | 7 ++ setup_qt_env.py | 65 ++++++++++++ src/main.py | 103 ++++++++++-------- start_app_safe.sh | 29 ++++++ 9 files changed, 666 insertions(+), 44 deletions(-) create mode 100644 PYQT5_FIX_GUIDE.md create mode 100755 emergency_fix.sh create mode 100644 fix_pyqt5_complete.py create mode 100644 install_pyqt5_safe.py create mode 100755 run_magicword.sh create mode 100644 set_pyqt5_env.sh create mode 100644 setup_qt_env.py create mode 100755 start_app_safe.sh diff --git a/PYQT5_FIX_GUIDE.md b/PYQT5_FIX_GUIDE.md new file mode 100644 index 0000000..2245480 --- /dev/null +++ b/PYQT5_FIX_GUIDE.md @@ -0,0 +1,120 @@ +# PyQt5 平台插件问题完全解决方案 + +## 问题描述 +在使用PyQt5时,可能会遇到以下错误: +``` +qt.qpa.plugin: Could not find the Qt platform plugin "cocoa" in "" +This application failed to start because no Qt platform plugin could be initialized. +``` + +## 解决方案 + +### 方法一:一键修复(推荐) +运行完整的修复脚本: +```bash +python fix_pyqt5_complete.py +``` + +### 方法二:手动修复 +1. **清理现有安装** +```bash +pip uninstall PyQt5 PyQt5-Qt5 PyQt5-sip -y +rm -rf /Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5* +``` + +2. **重新安装** +```bash +pip install PyQt5==5.15.10 --force-reinstall --no-cache-dir +``` + +3. **设置环境变量** +```bash +source set_pyqt5_env.sh +``` + +### 方法三:安全安装 +使用安全安装脚本: +```bash +python install_pyqt5_safe.py +``` + +## 预防措施 + +### 1. 在main.py中集成环境设置 +确保你的 `main.py` 包含了增强的Qt插件路径设置函数。 + +### 2. 创建启动脚本 +创建 `start_app.sh`: +```bash +#!/bin/bash +# PyQt5应用程序启动脚本 + +# 设置环境变量 +export QT_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins/platforms" +export QT_QPA_PLATFORM="cocoa" +export QT_MAC_WANTS_LAYER="1" + +# 启动应用 +python src/main.py +``` + +### 3. 使用虚拟环境专用安装 +```bash +# 激活虚拟环境 +source .venv/bin/activate + +# 在虚拟环境中安装 +python fix_pyqt5_complete.py +``` + +## 环境变量说明 + +| 变量名 | 作用 | 推荐值 | +|--------|------|--------| +| QT_PLUGIN_PATH | Qt插件主路径 | PyQt5/Qt5/plugins | +| QT_QPA_PLATFORM_PLUGIN_PATH | 平台插件路径 | PyQt5/Qt5/plugins/platforms | +| QT_QPA_PLATFORM | 指定平台 | cocoa (macOS) | +| QT_MAC_WANTS_LAYER | macOS图层支持 | 1 | +| QT_LOGGING_RULES | 日志级别 | qt.qpa.*=false | + +## 常见问题 + +### Q: 为什么PyQt5会丢失平台插件? +A: 常见原因: +- 安装过程中断或失败 +- 虚拟环境迁移 +- 系统Qt库冲突 +- 文件权限问题 + +### Q: 如何验证修复是否成功? +A: 运行测试命令: +```python +python -c "from PyQt5.QtWidgets import QApplication; print('成功!')" +``` + +### Q: 修复后仍然有问题? +A: 尝试: +1. 完全删除虚拟环境重新创建 +2. 使用系统包管理器安装Qt5 +3. 检查Python版本兼容性 + +## 最佳实践 + +1. **始终使用虚拟环境** +2. **固定PyQt5版本**(推荐5.15.10) +3. **在代码中设置插件路径** +4. **创建启动脚本** +5. **定期验证安装** + +## 一键修复命令 + +```bash +# 完整的修复流程 +cd /Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design +python fix_pyqt5_complete.py +source set_pyqt5_env.sh +python src/main.py +``` + +这样应该能完全避免PyQt5平台插件问题! \ No newline at end of file diff --git a/emergency_fix.sh b/emergency_fix.sh new file mode 100755 index 0000000..2eb9ab7 --- /dev/null +++ b/emergency_fix.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# PyQt5 紧急修复脚本 - 终极解决方案 + +echo "🚨 PyQt5 紧急修复中..." + +# 1. 完全清理现有安装 +echo "📦 步骤1: 清理PyQt5安装..." +pip uninstall PyQt5 PyQt5-Qt5 PyQt5-sip -y +rm -rf /Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5* + +# 2. 重新安装PyQt5 +echo "📦 步骤2: 重新安装PyQt5..." +pip install PyQt5==5.15.10 --force-reinstall --no-cache-dir + +# 3. 设置环境变量 +echo "🔧 步骤3: 设置环境变量..." +export QT_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins/platforms" +export QT_QPA_PLATFORM="cocoa" +export QT_MAC_WANTS_LAYER="1" +export QT_LOGGING_RULES="qt.qpa.*=false" + +# 4. 验证安装 +echo "✅ 步骤4: 验证安装..." +python -c " +import sys +import os +os.environ['QT_PLUGIN_PATH'] = '$QT_PLUGIN_PATH' +os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = '$QT_QPA_PLATFORM_PLUGIN_PATH' +os.environ['QT_QPA_PLATFORM'] = 'cocoa' +os.environ['QT_MAC_WANTS_LAYER'] = '1' + +try: + from PyQt5.QtWidgets import QApplication, QLabel + from PyQt5.QtCore import Qt + + app = QApplication(sys.argv) + label = QLabel('PyQt5修复成功!✅') + label.setAlignment(Qt.AlignCenter) + label.resize(200, 100) + label.show() + + from PyQt5.QtCore import QTimer + QTimer.singleShot(1500, app.quit) + + app.exec_() + print('✅ PyQt5验证成功!') +except Exception as e: + print(f'❌ PyQt5验证失败: {e}') + import traceback + traceback.print_exc() +" + +echo "🎉 修复完成!" +echo "现在可以运行: python src/main.py" \ No newline at end of file diff --git a/fix_pyqt5_complete.py b/fix_pyqt5_complete.py new file mode 100644 index 0000000..ac522d4 --- /dev/null +++ b/fix_pyqt5_complete.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +""" +完整的PyQt5问题解决方案 +运行此脚本可以完全避免PyQt5平台插件问题 +""" + +import subprocess +import sys +import os +import shutil +import platform + +def run_command(cmd, description): + """运行命令并显示进度""" + print(f"正在执行: {description}") + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(f"❌ 失败: {result.stderr}") + return False + print(f"✅ 完成") + return True + +def clean_pyqt5_installation(): + """彻底清理PyQt5安装""" + print("=== 清理PyQt5安装 ===") + + # 1. 卸载PyQt5包 + run_command([sys.executable, "-m", "pip", "uninstall", "PyQt5", "PyQt5-Qt5", "PyQt5-sip", "-y"], + "卸载PyQt5包") + + # 2. 清理残留文件 + venv_path = os.environ.get('VIRTUAL_ENV', '') + if venv_path: + site_packages = os.path.join(venv_path, 'lib', f'python{sys.version_info.major}.{sys.version_info.minor}', 'site-packages') + if os.path.exists(site_packages): + removed_count = 0 + for item in os.listdir(site_packages): + if 'pyqt5' in item.lower(): + item_path = os.path.join(site_packages, item) + try: + if os.path.isdir(item_path): + shutil.rmtree(item_path) + else: + os.remove(item_path) + removed_count += 1 + print(f" 删除: {item}") + except Exception as e: + print(f" 删除失败 {item}: {e}") + print(f"✅ 清理完成,删除了 {removed_count} 个项目") + + return True + +def install_pyqt5_properly(): + """正确安装PyQt5""" + print("=== 安装PyQt5 ===") + + # 使用清华镜像源加速下载(可选) + pip_args = [sys.executable, "-m", "pip", "install"] + + # 检查是否有国内镜像源可用 + try: + import requests + response = requests.get("https://pypi.tuna.tsinghua.edu.cn/simple", timeout=5) + if response.status_code == 200: + pip_args.extend(["-i", "https://pypi.tuna.tsinghua.edu.cn/simple"]) + print("使用清华镜像源") + except: + pass + + # 安装PyQt5 + pip_args.extend(["PyQt5==5.15.10", "--no-cache-dir", "--force-reinstall"]) + + return run_command(pip_args, "安装PyQt5") + +def setup_environment_variables(): + """设置环境变量""" + print("=== 设置环境变量 ===") + + system = platform.system() + venv_path = os.environ.get('VIRTUAL_ENV', '') + + if not venv_path: + print("❌ 未检测到虚拟环境") + return False + + python_version = f"python{sys.version_info.major}.{sys.version_info.minor}" + + # 可能的Qt插件路径 + possible_paths = [] + + if system == "Darwin": # macOS + possible_paths = [ + os.path.join(venv_path, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + '/usr/local/opt/qt5/plugins', + '/opt/homebrew/opt/qt5/plugins', + ] + elif system == "Windows": + possible_paths = [ + os.path.join(venv_path, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + ] + elif system == "Linux": + possible_paths = [ + os.path.join(venv_path, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + '/usr/lib/x86_64-linux-gnu/qt5/plugins', + ] + + # 找到有效的路径 + valid_path = None + for path in possible_paths: + if os.path.exists(path) and os.path.exists(os.path.join(path, 'platforms')): + valid_path = path + break + + if valid_path: + # 创建环境变量设置脚本 + env_script = f""" +# PyQt5环境变量设置 +export QT_PLUGIN_PATH="{valid_path}" +export QT_QPA_PLATFORM_PLUGIN_PATH="{os.path.join(valid_path, 'platforms')}" +""" + + if system == "Darwin": + env_script += """ +export QT_QPA_PLATFORM="cocoa" +export QT_MAC_WANTS_LAYER="1" +export QT_LOGGING_RULES="qt.qpa.*=false" +""" + elif system == "Windows": + env_script += """ +export QT_QPA_PLATFORM="windows" +""" + elif system == "Linux": + env_script += """ +export QT_QPA_PLATFORM="xcb" +""" + + # 保存环境变量脚本 + project_root = os.path.dirname(os.path.abspath(__file__)) + env_file = os.path.join(project_root, 'set_pyqt5_env.sh') + with open(env_file, 'w') as f: + f.write(env_script.strip()) + + print(f"✅ 环境变量脚本已创建: {env_file}") + print(f" QT_PLUGIN_PATH: {valid_path}") + return True + else: + print("❌ 未找到有效的Qt插件路径") + return False + +def verify_installation(): + """验证安装""" + print("=== 验证安装 ===") + + # 测试导入 + test_code = """ +import sys +import os + +# 设置环境变量 +if 'QT_PLUGIN_PATH' in os.environ: + os.environ['QT_PLUGIN_PATH'] = os.environ['QT_PLUGIN_PATH'] +if 'QT_QPA_PLATFORM_PLUGIN_PATH' in os.environ: + os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] + +try: + from PyQt5.QtWidgets import QApplication, QLabel + from PyQt5.QtCore import Qt + + app = QApplication(sys.argv) + label = QLabel("PyQt5安装成功!✅") + label.setAlignment(Qt.AlignCenter) + label.resize(200, 100) + label.show() + + # 只显示2秒后自动关闭 + from PyQt5.QtCore import QTimer + QTimer.singleShot(2000, app.quit) + + app.exec_() + print("✅ PyQt5验证成功!") +except Exception as e: + print(f"❌ PyQt5验证失败: {e}") + import traceback + traceback.print_exc() +""" + + with open('test_pyqt5.py', 'w') as f: + f.write(test_code) + + result = subprocess.run([sys.executable, 'test_pyqt5.py'], capture_output=True, text=True) + + # 清理测试文件 + if os.path.exists('test_pyqt5.py'): + os.remove('test_pyqt5.py') + + return result.returncode == 0 + +def main(): + """主函数""" + print("=== PyQt5完整修复方案 ===") + print(f"系统: {platform.system()}") + print(f"Python: {sys.version}") + print(f"虚拟环境: {os.environ.get('VIRTUAL_ENV', '未激活')}") + print() + + # 获取项目根目录 + project_root = os.path.dirname(os.path.abspath(__file__)) + os.chdir(project_root) + + steps = [ + ("清理安装", clean_pyqt5_installation), + ("重新安装", install_pyqt5_properly), + ("设置环境", setup_environment_variables), + ("验证安装", verify_installation), + ] + + success = True + for step_name, step_func in steps: + if not step_func(): + print(f"❌ {step_name} 失败") + success = False + break + print() + + if success: + print("🎉 PyQt5修复完成!") + print("\n使用方法:") + print("1. 运行: source set_pyqt5_env.sh") + print("2. 然后运行: python src/main.py") + else: + print("❌ PyQt5修复失败,请检查错误信息") + + return success + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/install_pyqt5_safe.py b/install_pyqt5_safe.py new file mode 100644 index 0000000..7f89679 --- /dev/null +++ b/install_pyqt5_safe.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +安全安装PyQt5的脚本,避免平台插件问题 +""" + +import subprocess +import sys +import os + +def install_pyqt5_safely(): + """安全安装PyQt5的方法""" + + print("正在安全安装PyQt5...") + + # 1. 首先完全卸载现有的PyQt5 + print("1. 卸载现有PyQt5...") + subprocess.run([sys.executable, "-m", "pip", "uninstall", "PyQt5", "PyQt5-Qt5", "PyQt5-sip", "-y"], + capture_output=True, text=True) + + # 2. 清理可能残留的文件 + print("2. 清理残留文件...") + venv_path = os.environ.get('VIRTUAL_ENV', '') + if venv_path: + site_packages = os.path.join(venv_path, 'lib', f'python{sys.version_info.major}.{sys.version_info.minor}', 'site-packages') + if os.path.exists(site_packages): + for item in os.listdir(site_packages): + if 'pyqt5' in item.lower(): + item_path = os.path.join(site_packages, item) + if os.path.isdir(item_path): + import shutil + shutil.rmtree(item_path) + print(f" 删除目录: {item}") + + # 3. 使用特定版本安装PyQt5 + print("3. 安装PyQt5...") + result = subprocess.run([ + sys.executable, "-m", "pip", "install", + "PyQt5==5.15.10", + "--no-cache-dir", # 不使用缓存,确保重新下载 + "--force-reinstall" # 强制重新安装 + ], capture_output=True, text=True) + + if result.returncode == 0: + print("✅ PyQt5安装成功!") + + # 4. 验证安装 + print("4. 验证安装...") + test_result = subprocess.run([ + sys.executable, "-c", + "from PyQt5.QtWidgets import QApplication; print('PyQt5导入成功')" + ], capture_output=True, text=True) + + if test_result.returncode == 0: + print("✅ PyQt5验证通过!") + return True + else: + print("❌ PyQt5验证失败:", test_result.stderr) + return False + else: + print("❌ PyQt5安装失败:", result.stderr) + return False + +if __name__ == "__main__": + success = install_pyqt5_safely() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/run_magicword.sh b/run_magicword.sh new file mode 100755 index 0000000..f40b70e --- /dev/null +++ b/run_magicword.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# MagicWord 一键启动脚本 +# 自动处理PyQt5平台插件问题 + +echo "🚀 正在启动 MagicWord..." + +# 设置PyQt5环境变量 +export QT_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins/platforms" +export QT_QPA_PLATFORM="cocoa" +export QT_MAC_WANTS_LAYER="1" +export QT_LOGGING_RULES="qt.qpa.*=false" + +# 激活虚拟环境(如果未激活) +if [ -z "$VIRTUAL_ENV" ]; then + echo "📦 激活虚拟环境..." + source .venv/bin/activate +fi + +# 检查PyQt5是否可用 +python -c "from PyQt5.QtWidgets import QApplication" 2>/dev/null +if [ $? -ne 0 ]; then + echo "❌ PyQt5出现问题,正在修复..." + ./emergency_fix.sh +fi + +# 启动应用 +echo "✅ 启动 MagicWord 应用..." +cd src && python main.py \ No newline at end of file diff --git a/set_pyqt5_env.sh b/set_pyqt5_env.sh new file mode 100644 index 0000000..e4ec5ce --- /dev/null +++ b/set_pyqt5_env.sh @@ -0,0 +1,7 @@ +# PyQt5环境变量设置 +export QT_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins/platforms" + +export QT_QPA_PLATFORM="cocoa" +export QT_MAC_WANTS_LAYER="1" +export QT_LOGGING_RULES="qt.qpa.*=false" \ No newline at end of file diff --git a/setup_qt_env.py b/setup_qt_env.py new file mode 100644 index 0000000..7f1f625 --- /dev/null +++ b/setup_qt_env.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Qt环境配置脚本,确保平台插件正确加载 +""" + +import os +import sys + +def setup_qt_environment(): + """设置Qt环境变量,避免平台插件问题""" + + # 获取虚拟环境的site-packages路径 + venv_path = os.environ.get('VIRTUAL_ENV', '') + if not venv_path: + print("警告:未检测到虚拟环境") + return False + + # 构建Qt插件路径 + python_version = f"python{sys.version_info.major}.{sys.version_info.minor}" + qt_plugin_path = os.path.join(venv_path, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins') + + if not os.path.exists(qt_plugin_path): + print(f"错误:Qt插件路径不存在: {qt_plugin_path}") + return False + + # 设置环境变量 + os.environ['QT_PLUGIN_PATH'] = qt_plugin_path + os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(qt_plugin_path, 'platforms') + + # 对于macOS,还需要设置其他重要变量 + if sys.platform == 'darwin': + os.environ['QT_QPA_PLATFORM'] = 'cocoa' + # 禁用Qt的某些可能导致问题的功能 + os.environ['QT_MAC_WANTS_LAYER'] = '1' + os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1' + + print(f"✅ Qt环境变量设置完成") + print(f" QT_PLUGIN_PATH: {qt_plugin_path}") + print(f" QT_QPA_PLATFORM_PLUGIN_PATH: {os.environ['QT_QPA_PLATFORM_PLUGIN_PATH']}") + + return True + +def verify_qt_setup(): + """验证Qt设置是否正确""" + try: + from PyQt5.QtCore import QCoreApplication + from PyQt5.QtWidgets import QApplication + + # 创建QApplication实例来测试 + app = QCoreApplication.instance() + if app is None: + app = QApplication([]) + + # 获取平台信息 + platform = app.platformName() + print(f"✅ Qt平台检测成功: {platform}") + + return True + except Exception as e: + print(f"❌ Qt验证失败: {e}") + return False + +if __name__ == "__main__": + if setup_qt_environment(): + verify_qt_setup() \ No newline at end of file diff --git a/src/main.py b/src/main.py index de007be..2377050 100644 --- a/src/main.py +++ b/src/main.py @@ -8,59 +8,74 @@ import platform project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, project_root) -# 设置Qt平台插件路径 - 根据操作系统设置正确的Qt插件路径 +# 设置Qt平台插件路径 - 增强版本,完全避免平台插件问题 def set_qt_plugin_path(): + """设置Qt平台插件路径,确保所有平台插件都能正确加载""" system = platform.system() + # 获取Python版本 + python_version = f"python{sys.version_info.major}.{sys.version_info.minor}" + + # 可能的Qt插件路径列表 + possible_paths = [] + if system == "Windows": - # Windows环境下查找Qt插件路径 - # 首先检查虚拟环境中的Qt插件 - venv_qt_plugins_path = os.path.join(project_root, '.venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins') - if os.path.exists(venv_qt_plugins_path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = venv_qt_plugins_path - return - - # 检查全局Python安装中的Qt插件 - global_qt_plugins_path = os.path.join(sys.prefix, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins') - if os.path.exists(global_qt_plugins_path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = global_qt_plugins_path - return - - # 尝试在常见的Windows PyQt5安装路径中查找 - common_paths = [ + # Windows环境下的路径 + possible_paths.extend([ + os.path.join(project_root, '.venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + os.path.join(sys.prefix, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'), os.path.join(os.path.expanduser('~'), 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'), os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'Python', 'Python39', 'site-packages', 'PyQt5', 'Qt5', 'plugins'), - ] - - for path in common_paths: - if os.path.exists(path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = path - return - + ]) elif system == "Darwin": # macOS - # macOS环境下查找Qt插件路径 - system_qt_plugins_path = '/usr/local/opt/qt5/plugins' # macOS Homebrew Qt5路径 - venv_qt_plugins_path = os.path.join(project_root, '.venv', 'lib', 'python3.9', 'site-packages', 'PyQt5', 'Qt5', 'plugins') - - # 优先检查系统Qt插件路径 - if os.path.exists(system_qt_plugins_path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = system_qt_plugins_path - return - elif os.path.exists(venv_qt_plugins_path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = venv_qt_plugins_path - return - + # macOS环境下的路径 + possible_paths.extend([ + os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + '/usr/local/opt/qt5/plugins', # Homebrew Qt5 + '/opt/homebrew/opt/qt5/plugins', # Apple Silicon Homebrew + os.path.expanduser('~/Qt/5.15.2/clang_64/plugins'), # Qt官方安装 + ]) elif system == "Linux": - # Linux环境下查找Qt插件路径 - venv_qt_plugins_path = os.path.join(project_root, '.venv', 'lib', 'python3.9', 'site-packages', 'PyQt5', 'Qt5', 'plugins') - global_qt_plugins_path = os.path.join(sys.prefix, 'lib', 'python3.9', 'site-packages', 'PyQt5', 'Qt5', 'plugins') + # Linux环境下的路径 + possible_paths.extend([ + os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'), + '/usr/lib/x86_64-linux-gnu/qt5/plugins', + '/usr/lib/qt5/plugins', + ]) + + # 查找第一个存在的路径 + valid_path = None + for path in possible_paths: + if os.path.exists(path) and os.path.exists(os.path.join(path, 'platforms')): + valid_path = path + break + + if valid_path: + # 设置Qt插件路径 + os.environ['QT_PLUGIN_PATH'] = valid_path + os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(valid_path, 'platforms') + + # 设置平台特定的环境变量 + if system == "Darwin": # macOS + os.environ['QT_QPA_PLATFORM'] = 'cocoa' + os.environ['QT_MAC_WANTS_LAYER'] = '1' + # 禁用可能导致问题的Qt功能 + os.environ['QT_LOGGING_RULES'] = 'qt.qpa.*=false' # 禁用Qt警告日志 + elif system == "Windows": + os.environ['QT_QPA_PLATFORM'] = 'windows' + elif system == "Linux": + os.environ['QT_QPA_PLATFORM'] = 'xcb' + # 对于Linux,可能需要设置DISPLAY + if 'DISPLAY' not in os.environ: + os.environ['DISPLAY'] = ':0' - if os.path.exists(venv_qt_plugins_path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = venv_qt_plugins_path - return - elif os.path.exists(global_qt_plugins_path): - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = global_qt_plugins_path - return + print(f"✅ Qt插件路径设置成功: {valid_path}") + return True + else: + print("⚠️ 警告:未找到Qt插件路径") + return False # 设置Qt平台插件路径 set_qt_plugin_path() diff --git a/start_app_safe.sh b/start_app_safe.sh new file mode 100755 index 0000000..2a94b61 --- /dev/null +++ b/start_app_safe.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# MagicWord PyQt5 安全启动脚本 +# 此脚本自动设置所有必要的环境变量并启动应用 + +echo "🚀 正在启动 MagicWord 应用..." + +# 设置PyQt5环境变量 +export QT_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="/Users/maziang/Documents/CodingWorkPlace/Code/Curriculum_Design/.venv/lib/python3.9/site-packages/PyQt5/Qt5/plugins/platforms" +export QT_QPA_PLATFORM="cocoa" +export QT_MAC_WANTS_LAYER="1" +export QT_LOGGING_RULES="qt.qpa.*=false" + +# 检查虚拟环境 +if [ -z "$VIRTUAL_ENV" ]; then + echo "⚠️ 虚拟环境未激活,正在激活..." + source .venv/bin/activate +fi + +# 检查PyQt5是否安装 +python -c "import PyQt5.QtWidgets" 2>/dev/null +if [ $? -ne 0 ]; then + echo "❌ PyQt5未正确安装,正在修复..." + python fix_pyqt5_complete.py +fi + +# 启动应用 +echo "✅ 环境设置完成,正在启动应用..." +cd src && python main.py \ No newline at end of file -- 2.34.1 From 77e613b8d7ca0edcd12e2a8f7ca34b3de3d5c038 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Thu, 6 Nov 2025 16:50:39 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E5=A4=A9=E6=B0=94=E7=BB=84=E4=BB=B6UI?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 168 ++++++++++++++++++++++++++++++++++++---- src/word_main_window.py | 32 ++++++++ 2 files changed, 183 insertions(+), 17 deletions(-) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 5150f9f..c78e7e8 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -464,17 +464,125 @@ class WordRibbon(QFrame): """主题切换槽函数""" self.apply_theme() + def get_weather_emoji(self, weather_desc): + """根据天气描述返回对应的emoji图标""" + weather_emoji_map = { + '晴': '🌞', + '多云': '☁️', + '阴': '☁️', + '小雨': '🌦️', + '中雨': '🌧️', + '大雨': '⛈️', + '暴雨': '⛈️', + '雷阵雨': '⛈️', + '雪': '❄️', + '小雪': '🌨️', + '中雪': '❄️', + '大雪': '❄️', + '暴雪': '❄️', + '雾': '🌫️', + '霾': '🌫️', + '沙尘暴': '🌪️', + '扬沙': '🌪️', + '浮尘': '🌪️', + '台风': '🌀', + '飓风': '🌀', + '龙卷风': '🌪️', + '冰雹': '🧊', + '冻雨': '🌨️', + '雨夹雪': '🌨️', + 'sunny': '🌞', + 'clear': '🌞', + 'cloudy': '☁️', + 'overcast': '☁️', + 'rain': '🌧️', + 'light rain': '🌦️', + 'heavy rain': '⛈️', + 'thunderstorm': '⛈️', + 'snow': '❄️', + 'fog': '🌫️', + 'haze': '🌫️', + 'sandstorm': '🌪️', + 'typhoon': '🌀', + 'hurricane': '🌀', + 'tornado': '🌪️', + 'hail': '🧊' + } + + # 模糊匹配天气描述 + weather_desc_lower = str(weather_desc).lower() + for key, emoji in weather_emoji_map.items(): + if key in weather_desc_lower: + return emoji + + # 默认返回晴天图标 + return '🌞' + def create_weather_group(self): """创建天气组件组""" if self.weather_group is not None: return self.weather_group weather_group = self.create_ribbon_group("天气") + weather_group.setFixedWidth(200) # 增加整体宽度 weather_layout = QVBoxLayout() + weather_layout.setSpacing(8) # 增加行间距 + + # 第一行:天气图标和温度显示(居中对齐) + weather_display_layout = QHBoxLayout() + weather_display_layout.setSpacing(10) # 增加图标和温度间距 + + # 添加左侧弹性空间,推动内容到中心 + weather_display_layout.addStretch() + + # 天气图标标签 - 优化垂直居中对齐 + self.weather_icon_label = QLabel("🌞") + self.weather_icon_label.setAlignment(Qt.AlignCenter) # 使用Qt对齐方式 + self.weather_icon_label.setStyleSheet(""" + QLabel { + font-size: 32px; + padding: 0px; + margin: 0px; + border: none; + background: transparent; + } + """) + self.weather_icon_label.setFixedSize(40, 40) # 增大图标尺寸 + + # 温度标签 - 优化垂直居中对齐 + self.weather_temp_label = QLabel("--°C") + self.weather_temp_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) # 使用Qt对齐方式 + self.weather_temp_label.setStyleSheet(""" + QLabel { + font-size: 16px; + font-weight: bold; + color: #333333; + padding: 0px; + margin: 0px; + border: none; + background: transparent; + min-height: 30px; /* 确保最小高度 */ + } + """) + self.weather_temp_label.setFixedSize(70, 30) # 增加温度标签宽度 + + weather_display_layout.addWidget(self.weather_icon_label) + weather_display_layout.addWidget(self.weather_temp_label) + + # 添加右侧弹性空间,确保内容居中 + weather_display_layout.addStretch() + + # 第二行:城市选择和刷新按钮(居中对齐) + control_layout = QHBoxLayout() + control_layout.setSpacing(8) # 增加控件间距 + + # 添加左侧弹性空间,推动内容到中心 + control_layout.addStretch() # 城市选择 - 添加所有省会城市 self.city_combo = QComboBox() - self.city_combo.setFixedWidth(120) # 增加宽度以显示完整城市名 + self.city_combo.setFixedWidth(120) # 增加城市选择框宽度 + self.city_combo.setStyleSheet("QComboBox { font-size: 12px; padding: 3px; }") # 增大字体和间距 self.city_combo.addItems([ '自动定位', '北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都', '西安', # 一线城市 @@ -487,12 +595,20 @@ class WordRibbon(QFrame): self.city_combo.currentTextChanged.connect(self.on_city_changed) # 刷新按钮 - self.refresh_weather_btn = QPushButton("刷新天气") + self.refresh_weather_btn = QPushButton("🔄 刷新") self.refresh_weather_btn.clicked.connect(self.on_refresh_weather) - self.refresh_weather_btn.setFixedSize(80, 25) + self.refresh_weather_btn.setFixedSize(60, 30) # 增大刷新按钮尺寸 + self.refresh_weather_btn.setStyleSheet("QPushButton { font-size: 11px; padding: 5px; }") + self.refresh_weather_btn.setToolTip("刷新天气") + + control_layout.addWidget(self.city_combo) + control_layout.addWidget(self.refresh_weather_btn) + + # 添加右侧弹性空间,确保内容居中 + control_layout.addStretch() - weather_layout.addWidget(self.city_combo) - weather_layout.addWidget(self.refresh_weather_btn) + weather_layout.addLayout(weather_display_layout) + weather_layout.addLayout(control_layout) weather_group.setLayout(weather_layout) self.weather_group = weather_group @@ -1016,14 +1132,17 @@ class WeatherAPI: # 使用免费的天气API url = f"http://t.weather.sojson.com/api/weather/city/{city_id}" print(f"获取当前天气: {url}") - response = requests.get(url, timeout=10) + response = requests.get(url, timeout=5) # 减少超时时间 response.raise_for_status() data = response.json() - print(f"当前天气响应: {data}") - if data['status'] == 200: - city_info = data['cityInfo'] - current_data = data['data'] + if data.get('status') == 200: + city_info = data.get('cityInfo', {}) + current_data = data.get('data', {}) + + if not current_data: + print("天气数据为空") + return None # 获取生活提示信息 life_tips = [] @@ -1047,20 +1166,35 @@ class WeatherAPI: while len(life_tips) < 3 and default_tips: life_tips.append(default_tips.pop(0)) + # 安全获取天气数据 + wendu = current_data.get('wendu', 'N/A') + shidu = current_data.get('shidu', 'N/A') + first_forecast = forecast[0] if forecast else {} + weather_info = { - 'temp': current_data['wendu'], - 'feels_like': current_data['wendu'], # 没有体感温度,用实际温度代替 - 'weather': current_data['forecast'][0]['type'], - 'humidity': current_data['shidu'].replace('%', ''), - 'wind_dir': current_data['forecast'][0]['fx'], + 'temp': wendu, + 'feels_like': wendu, # 没有体感温度,用实际温度代替 + 'weather': first_forecast.get('type', '晴'), # 默认晴天 + 'humidity': shidu.replace('%', '') if shidu != 'N/A' else '50', + 'wind_dir': first_forecast.get('fx', '无风'), 'wind_scale': '1', # 没有风力等级,用默认值 - 'vis': current_data['forecast'][0]['high'], # 用最高温作为可见度 + 'vis': first_forecast.get('high', '高温 15℃'), # 用最高温作为可见度 'pressure': '1013', # 没有气压,用默认值 'life_tips': life_tips # 添加生活提示信息 } print(f"解析后的天气信息: {weather_info}") return weather_info - print(f"获取天气失败,状态码: {data.get('status')}") + else: + print(f"获取天气失败,状态码: {data.get('status')}") + return None + except requests.exceptions.Timeout: + print("获取天气超时") + return None + except requests.exceptions.RequestException as e: + print(f"网络请求失败: {e}") + return None + except json.JSONDecodeError as e: + print(f"JSON解析失败: {e}") return None except Exception as e: print(f"获取当前天气失败: {e}") diff --git a/src/word_main_window.py b/src/word_main_window.py index 9fcdd7c..04ffc71 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -1295,6 +1295,12 @@ class WordStyleMainWindow(QMainWindow): """更新天气显示""" if 'error' in weather_data: self.status_bar.showMessage(f"天气数据获取失败: {weather_data['error']}", 3000) + # 更新工具栏天气显示为错误状态 + if hasattr(self, 'ribbon'): + if hasattr(self.ribbon, 'weather_icon_label'): + self.ribbon.weather_icon_label.setText("❓") + if hasattr(self.ribbon, 'weather_temp_label'): + self.ribbon.weather_temp_label.setText("--°C") else: # 处理嵌套的天气数据结构 city = weather_data.get('city', '未知城市') @@ -1318,6 +1324,32 @@ class WordStyleMainWindow(QMainWindow): weather_message = f"{city}: {desc}, {temp}°C{temp_range}" self.status_bar.showMessage(weather_message, 5000) + # 更新工具栏天气图标和温度显示 + if hasattr(self, 'ribbon'): + # 更新天气图标 + if hasattr(self.ribbon, 'weather_icon_label') and desc != 'N/A': + emoji = self.ribbon.get_weather_emoji(desc) + self.ribbon.weather_icon_label.setText(emoji) + + # 更新温度显示 + if hasattr(self.ribbon, 'weather_temp_label') and temp != 'N/A': + # 计算平均温度(使用最高温和最低温的平均值) + avg_temp = temp + if 'forecast' in weather_data and weather_data['forecast']: + forecast_data = weather_data['forecast'][0] + if isinstance(forecast_data, dict): + temp_max = forecast_data.get('temp_max', 'N/A') + temp_min = forecast_data.get('temp_min', 'N/A') + if temp_max != 'N/A' and temp_min != 'N/A': + try: + avg_temp = (float(temp_max) + float(temp_min)) / 2 + avg_temp = round(avg_temp, 1) + except (ValueError, TypeError): + avg_temp = temp + + temp_str = f"{avg_temp}°C" if isinstance(avg_temp, (int, float)) else f"{temp}°C" + self.ribbon.weather_temp_label.setText(temp_str) + # 存储天气数据供其他功能使用(确保包含生活提示) self.current_weather_data = weather_data print(f"update_weather_display - 存储的current_weather_data包含life_tips: {self.current_weather_data.get('life_tips', [])}") -- 2.34.1 From 8bd14804f70015267d892aaf2b9f11f7dc86e8f3 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Mon, 10 Nov 2025 10:05:14 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E8=BD=AF=E4=BB=B6?= =?UTF-8?q?=E5=9C=A8MacOS=E7=B3=BB=E7=BB=9F=E7=9A=84=E9=B2=81=E6=A3=92?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- final_fix.py | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 final_fix.py diff --git a/final_fix.py b/final_fix.py new file mode 100644 index 0000000..8c8e37f --- /dev/null +++ b/final_fix.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +MagicWord 应用程序最终修复方案 +使用macOS特定的库路径解决方案 +""" + +import sys +import os +import platform + +# 获取项目根目录 +project_root = os.path.dirname(os.path.abspath(__file__)) + +# 添加src目录到Python路径 +sys.path.insert(0, os.path.join(project_root, 'src')) + +# Qt路径设置 +qt_path = os.path.join(project_root, '.venv', 'lib', 'python3.9', 'site-packages', 'PyQt5', 'Qt5') +qt_plugins_path = os.path.join(qt_path, 'plugins') +qt_lib_path = os.path.join(qt_path, 'lib') + +print(f"项目根目录: {project_root}") +print(f"Qt路径: {qt_path}") + +# macOS特定的环境变量设置 +if platform.system() == "Darwin": + # 设置Qt插件路径 + os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = qt_plugins_path + + # 关键:设置DYLD_FALLBACK_LIBRARY_PATH,这是macOS的库搜索后备路径 + fallback_paths = [ + qt_lib_path, + '/usr/local/lib', + '/usr/lib', + '/System/Library/Frameworks' + ] + os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = ':'.join(fallback_paths) + + # 设置其他环境变量 + os.environ['DYLD_LIBRARY_PATH'] = qt_lib_path + os.environ['DYLD_FRAMEWORK_PATH'] = qt_lib_path + os.environ['QT_PREFIX_PATH'] = qt_path + os.environ['QT_DEBUG_PLUGINS'] = '1' + + print("macOS环境变量设置完成:") + print(f"DYLD_FALLBACK_LIBRARY_PATH: {os.environ['DYLD_FALLBACK_LIBRARY_PATH']}") + print(f"QT_QPA_PLATFORM_PLUGIN_PATH: {os.environ['QT_QPA_PLATFORM_PLUGIN_PATH']}") + +print("\\n开始导入PyQt5...") + +try: + # 导入PyQt5 + from PyQt5.QtWidgets import QApplication + from PyQt5.QtCore import Qt + print("✓ PyQt5导入成功") + + # 启用高DPI支持 + if hasattr(Qt, 'AA_EnableHighDpiScaling'): + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) + if hasattr(Qt, 'AA_UseHighDpiPixmaps'): + QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) + + # 创建QApplication + print("创建QApplication...") + app = QApplication(sys.argv) + app.setApplicationName("MagicWord") + app.setApplicationVersion("1.0") + print("✓ QApplication创建成功") + + # 导入应用程序模块 + print("导入应用程序模块...") + from word_main_window import WordStyleMainWindow + from settings.settings_manager import SettingsManager + print("✓ 应用程序模块导入成功") + + # 创建设置管理器 + settings_manager = SettingsManager() + + # 创建主窗口 + print("创建主窗口...") + main_window = WordStyleMainWindow() + main_window.show() + print("✓ 主窗口创建成功") + + print("\\n🎉 应用程序启动成功!") + print("正在运行应用程序...") + + # 运行应用程序 + sys.exit(app.exec_()) + +except Exception as e: + print(f"\\n❌ 错误: {e}") + import traceback + traceback.print_exc() + + # 如果失败,尝试使用系统Qt + print("\\n尝试使用系统Qt...") + try: + # 清除自定义环境变量 + for key in ['QT_QPA_PLATFORM_PLUGIN_PATH', 'DYLD_LIBRARY_PATH', + 'DYLD_FRAMEWORK_PATH', 'DYLD_FALLBACK_LIBRARY_PATH']: + if key in os.environ: + del os.environ[key] + + from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) + print("✓ 使用系统Qt成功") + sys.exit(app.exec_()) + except Exception as e2: + print(f"❌ 系统Qt也失败: {e2}") + sys.exit(1) \ No newline at end of file -- 2.34.1 From 2b6c69f73b222b23e2e020f9b2cfb39a15d9edce Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Mon, 10 Nov 2025 10:18:03 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E6=89=93=E5=AD=97=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=B8=8B=E6=8F=92=E5=85=A5=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/word_main_window.py | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/word_main_window.py b/src/word_main_window.py index 04ffc71..fb946ef 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -674,6 +674,11 @@ class WordStyleMainWindow(QMainWindow): # 插入菜单 insert_menu = menubar.addMenu('插入(I)') + # 插入图片功能 + insert_image_action = QAction('插入图片', self) + insert_image_action.triggered.connect(self.insert_image_in_typing_mode) + insert_menu.addAction(insert_image_action) + # 绘图菜单 paint_menu = menubar.addMenu('绘图(D)') @@ -2729,6 +2734,79 @@ class WordStyleMainWindow(QMainWindow): # 这个方法现在不需要了,因为图片会直接插入到文本中 pass + def insert_image_in_typing_mode(self): + """在打字模式下插入图片""" + try: + # 检查当前是否在打字模式下 + if self.view_mode != "typing": + self.status_bar.showMessage("请在打字模式下使用插入图片功能", 3000) + return + + # 打开文件对话框选择图片 + from PyQt5.QtWidgets import QFileDialog + file_path, _ = QFileDialog.getOpenFileName( + self, + "选择图片文件", + "", + "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.ico)" + ) + + if not file_path: + return + + # 加载图片文件 + pixmap = QPixmap(file_path) + if pixmap.isNull(): + self.status_bar.showMessage("无法加载图片文件", 3000) + return + + # 获取当前光标位置 + cursor = self.text_edit.textCursor() + + # 创建图片格式 + image_format = QTextImageFormat() + + # 调整图片大小 + scaled_pixmap = pixmap.scaled(200, 150, Qt.KeepAspectRatio, Qt.SmoothTransformation) + + # 将图片保存到临时文件 + import tempfile + import os + temp_dir = tempfile.gettempdir() + filename = os.path.basename(file_path) + safe_filename = "".join(c for c in filename if c.isalnum() or c in ('.', '_', '-')) + temp_file = os.path.join(temp_dir, safe_filename) + + if scaled_pixmap.save(temp_file): + # 设置图片格式 + image_format.setName(temp_file) + image_format.setWidth(200) + image_format.setHeight(150) + + # 在光标位置插入图片 + cursor.insertImage(image_format) + + # 在图片后插入一个空格,让文字继续 + cursor.insertText(" ") + + # 标记文档为已修改 + if not self.is_modified: + self.is_modified = True + self.update_window_title() + + # 显示成功消息 + self.status_bar.showMessage(f"图片已插入: {filename}", 3000) + + # 添加到临时文件列表以便清理 + self.temp_files.append(temp_file) + else: + self.status_bar.showMessage("保存临时图片文件失败", 3000) + + except Exception as e: + self.status_bar.showMessage(f"插入图片失败: {str(e)}", 3000) + import traceback + traceback.print_exc() + def closeEvent(self, event): """关闭事件处理""" # 清理临时文件 -- 2.34.1 From 6db8f24f21e39366fba9b0171e9dca620cb2db93 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Mon, 10 Nov 2025 10:30:40 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E6=B8=85=E7=90=86=E6=9D=82=E9=A1=B9?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- final_fix.py | 111 --------------------------------------------------- 1 file changed, 111 deletions(-) delete mode 100644 final_fix.py diff --git a/final_fix.py b/final_fix.py deleted file mode 100644 index 8c8e37f..0000000 --- a/final_fix.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -""" -MagicWord 应用程序最终修复方案 -使用macOS特定的库路径解决方案 -""" - -import sys -import os -import platform - -# 获取项目根目录 -project_root = os.path.dirname(os.path.abspath(__file__)) - -# 添加src目录到Python路径 -sys.path.insert(0, os.path.join(project_root, 'src')) - -# Qt路径设置 -qt_path = os.path.join(project_root, '.venv', 'lib', 'python3.9', 'site-packages', 'PyQt5', 'Qt5') -qt_plugins_path = os.path.join(qt_path, 'plugins') -qt_lib_path = os.path.join(qt_path, 'lib') - -print(f"项目根目录: {project_root}") -print(f"Qt路径: {qt_path}") - -# macOS特定的环境变量设置 -if platform.system() == "Darwin": - # 设置Qt插件路径 - os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = qt_plugins_path - - # 关键:设置DYLD_FALLBACK_LIBRARY_PATH,这是macOS的库搜索后备路径 - fallback_paths = [ - qt_lib_path, - '/usr/local/lib', - '/usr/lib', - '/System/Library/Frameworks' - ] - os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = ':'.join(fallback_paths) - - # 设置其他环境变量 - os.environ['DYLD_LIBRARY_PATH'] = qt_lib_path - os.environ['DYLD_FRAMEWORK_PATH'] = qt_lib_path - os.environ['QT_PREFIX_PATH'] = qt_path - os.environ['QT_DEBUG_PLUGINS'] = '1' - - print("macOS环境变量设置完成:") - print(f"DYLD_FALLBACK_LIBRARY_PATH: {os.environ['DYLD_FALLBACK_LIBRARY_PATH']}") - print(f"QT_QPA_PLATFORM_PLUGIN_PATH: {os.environ['QT_QPA_PLATFORM_PLUGIN_PATH']}") - -print("\\n开始导入PyQt5...") - -try: - # 导入PyQt5 - from PyQt5.QtWidgets import QApplication - from PyQt5.QtCore import Qt - print("✓ PyQt5导入成功") - - # 启用高DPI支持 - if hasattr(Qt, 'AA_EnableHighDpiScaling'): - QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) - if hasattr(Qt, 'AA_UseHighDpiPixmaps'): - QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) - - # 创建QApplication - print("创建QApplication...") - app = QApplication(sys.argv) - app.setApplicationName("MagicWord") - app.setApplicationVersion("1.0") - print("✓ QApplication创建成功") - - # 导入应用程序模块 - print("导入应用程序模块...") - from word_main_window import WordStyleMainWindow - from settings.settings_manager import SettingsManager - print("✓ 应用程序模块导入成功") - - # 创建设置管理器 - settings_manager = SettingsManager() - - # 创建主窗口 - print("创建主窗口...") - main_window = WordStyleMainWindow() - main_window.show() - print("✓ 主窗口创建成功") - - print("\\n🎉 应用程序启动成功!") - print("正在运行应用程序...") - - # 运行应用程序 - sys.exit(app.exec_()) - -except Exception as e: - print(f"\\n❌ 错误: {e}") - import traceback - traceback.print_exc() - - # 如果失败,尝试使用系统Qt - print("\\n尝试使用系统Qt...") - try: - # 清除自定义环境变量 - for key in ['QT_QPA_PLATFORM_PLUGIN_PATH', 'DYLD_LIBRARY_PATH', - 'DYLD_FRAMEWORK_PATH', 'DYLD_FALLBACK_LIBRARY_PATH']: - if key in os.environ: - del os.environ[key] - - from PyQt5.QtWidgets import QApplication - app = QApplication(sys.argv) - print("✓ 使用系统Qt成功") - sys.exit(app.exec_()) - except Exception as e2: - print(f"❌ 系统Qt也失败: {e2}") - sys.exit(1) \ No newline at end of file -- 2.34.1 From 611a4b2068309a115b506cefe098cbf727c7d702 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Mon, 10 Nov 2025 11:24:56 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/file_parser.py | 47 ++++++ src/word_main_window.py | 332 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 374 insertions(+), 5 deletions(-) diff --git a/src/file_parser.py b/src/file_parser.py index b6362d2..017b289 100644 --- a/src/file_parser.py +++ b/src/file_parser.py @@ -23,6 +23,8 @@ class FileParser: return FileParser.parse_docx(file_path) elif ext == '.pdf': return FileParser.parse_pdf(file_path) + elif ext == '.html': + return FileParser.parse_html(file_path) else: raise ValueError(f"Unsupported file format: {ext}") except Exception as e: @@ -80,6 +82,11 @@ class FileParser: content = FileParser.parse_pdf(file_path) images = [] # PDF图片提取较复杂,暂时跳过 + elif ext == '.html': + # HTML文件:提取文本内容 + content = FileParser.parse_html(file_path) + images = [] # HTML图片提取较复杂,暂时跳过 + else: return { 'success': False, @@ -271,6 +278,46 @@ class FileParser: except Exception as e: raise Exception(f"Error parsing pdf file {file_path}: {str(e)}") + @staticmethod + def parse_html(file_path: str) -> str: + """解析HTML文件,提取文本内容""" + # 验证文件路径 + if not FileParser.validate_file_path(file_path): + raise ValueError(f"Invalid file path: {file_path}") + + try: + from bs4 import BeautifulSoup + except ImportError: + raise ImportError("BeautifulSoup4 library is required for parsing .html files. Please install it using 'pip install beautifulsoup4'") + + try: + # 检测文件编码 + encoding = FileParser.detect_file_encoding(file_path) + + # 读取HTML文件 + with open(file_path, 'r', encoding=encoding, errors='ignore') as f: + html_content = f.read() + + # 使用BeautifulSoup解析HTML + soup = BeautifulSoup(html_content, 'html.parser') + + # 移除script和style标签 + for script in soup(["script", "style"]): + script.decompose() + + # 提取文本内容 + text = soup.get_text() + + # 清理多余的空白字符 + lines = (line.strip() for line in text.splitlines()) + chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) + text = '\n'.join(chunk for chunk in chunks if chunk) + + return text + + except Exception as e: + raise Exception(f"Error parsing html file {file_path}: {str(e)}") + @staticmethod def validate_file_path(file_path: str) -> bool: """验证文件路径是否有效""" diff --git a/src/word_main_window.py b/src/word_main_window.py index fb946ef..1ca91ec 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -685,6 +685,29 @@ class WordStyleMainWindow(QMainWindow): # 设计菜单 design_menu = menubar.addMenu('设计(G)') + # 导出子菜单 + export_menu = design_menu.addMenu('导出') + + # 导出为HTML + export_html_action = QAction('导出为HTML', self) + export_html_action.triggered.connect(self.export_as_html) + export_menu.addAction(export_html_action) + + # 导出为PDF + export_pdf_action = QAction('导出为PDF', self) + export_pdf_action.triggered.connect(self.export_as_pdf) + export_menu.addAction(export_pdf_action) + + # 导出为TXT + export_txt_action = QAction('导出为TXT', self) + export_txt_action.triggered.connect(self.export_as_txt) + export_menu.addAction(export_txt_action) + + # 导出为DOCX + export_docx_action = QAction('导出为DOCX', self) + export_docx_action.triggered.connect(self.export_as_docx) + export_menu.addAction(export_docx_action) + # 布局菜单 layout_menu = menubar.addMenu('布局(L)') @@ -1618,7 +1641,7 @@ class WordStyleMainWindow(QMainWindow): """导入文件 - 仅在导入时存储内容,不立即显示""" file_path, _ = QFileDialog.getOpenFileName( self, "导入文件", "", - "文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)" + "文档文件 (*.docx *.txt *.pdf *.html);;所有文件 (*.*)" ) if file_path: @@ -1683,7 +1706,7 @@ class WordStyleMainWindow(QMainWindow): # 提取并显示图片(如果有) if images: - self.extract_and_display_images(content, images) + self.extract_and_display_images(file_path=None, images=images) else: # 转换失败,显示错误信息 @@ -2841,11 +2864,310 @@ class WordStyleMainWindow(QMainWindow): print(f"删除临时文件失败 {temp_file}: {str(e)}") self.temp_files.clear() - def extract_and_display_images(self, file_path): + def export_as_html(self): + """导出为HTML""" + file_path, _ = QFileDialog.getSaveFileName( + self, "导出为HTML", "", "HTML文件 (*.html);;所有文件 (*.*)" + ) + + if file_path: + try: + # 获取当前文本内容 + content = self.text_edit.toPlainText() + + # 处理图片标签 + html_body = "" + lines = content.split('\n') + + for line in lines: + if line.strip().startswith('[图片:') and line.strip().endswith(']'): + # 提取图片文件名 + img_name = line.strip()[4:-1].strip() + + # 查找对应的图片数据 + img_data = None + for filename, image_data in self.extracted_images: + if filename == img_name: + img_data = image_data + break + + if img_data: + # 创建图片的base64编码 + import base64 + img_base64 = base64.b64encode(img_data).decode('utf-8') + + # 检测图片类型 + if img_name.lower().endswith('.png'): + img_type = 'png' + elif img_name.lower().endswith(('.jpg', '.jpeg')): + img_type = 'jpeg' + elif img_name.lower().endswith('.gif'): + img_type = 'gif' + else: + img_type = 'png' # 默认 + + html_body += f'
{img_name}
\n' + html_body += f'[图片: {img_name}]
\n' + else: + # 普通文本,使用段落标签 + if line.strip(): + html_body += f'{line}
\n' + else: + html_body += '{img_name}
' + html_content += f'{line}
' + else: + html_content += 's`5gPY4)Qa0$ryp0A0Vg*`mm6L|w8oC1rCsCJ40K@9e2s*DlzM*)1h zg6cvPPc2N$+I5Ts(g+5?sKwk}xC{117nQI3*G?#ms*h?UMFS3N4ZS!7j}>gG)$L2- zyaBLR1Lh1)2*;TMP+a^AIwtE+xFRwW%K3v}1N*~f*aiVQ!eQzdDlEX81819mP5OU9 bi3@dg%uSs`Xzuy-yTZiS!l>*l>E3?Date: Fri, 14 Nov 2025 15:43:55 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=EF=BC=9Acontr?= =?UTF-8?q?ol+L=E6=88=96command+L=E5=91=BC=E5=87=BA=E5=AD=A6=E4=B9=A0?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/word_main_window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/word_main_window.py b/src/word_main_window.py index 1ca91ec..914abbc 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -1,6 +1,7 @@ # word_main_window.py import sys import os +import platform from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLabel, QSplitter, QFrame, QMenuBar, QAction, QFileDialog, QMessageBox, QApplication, @@ -637,6 +638,8 @@ class WordStyleMainWindow(QMainWindow): self.learning_mode_action = QAction('学习模式', self) self.learning_mode_action.setCheckable(True) self.learning_mode_action.setChecked(False) + # 设置学习模式快捷键 (Qt会自动在macOS上映射Ctrl为Cmd) + self.learning_mode_action.setShortcut('Ctrl+L') self.learning_mode_action.triggered.connect(lambda: self.set_view_mode("learning")) view_mode_menu.addAction(self.learning_mode_action) -- 2.34.1 From 2185df26678dd20d86e86e48bea36647124a3c16 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Fri, 14 Nov 2025 16:05:12 +0800 Subject: [PATCH 11/15] =?UTF-8?q?fix=EF=BC=9AIP=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 205 ++++++++++++++++++++++++++++++++-------- 1 file changed, 167 insertions(+), 38 deletions(-) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index a1183a4..731e63b 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -1281,27 +1281,150 @@ class WeatherAPI: def get_isp_info(self): """获取ISP信息""" try: - url = "http://ip-api.com/json/" - response = requests.get(url, timeout=5) - response.raise_for_status() + # 尝试多个ISP信息服务以提高成功率 + + # 方法1: 使用ip-api.com接口 + try: + url = "http://ip-api.com/json/" + headers = {'User-Agent': 'MagicWord/1.0'} + response = requests.get(url, timeout=5, headers=headers) + response.raise_for_status() + + data = response.json() + if data.get('status') == 'success': + isp = data.get('isp', '') + org = data.get('org', '') + as_info = data.get('as', '') + country = data.get('country', '') + return f"{isp} {org} {as_info} {country}".strip() + except Exception as e: + print(f"ip-api ISP信息获取失败: {e}") + pass + + # 方法2: 使用ipinfo.io接口 + try: + url = "https://ipinfo.io/json" + headers = {'User-Agent': 'MagicWord/1.0'} + response = requests.get(url, timeout=5, headers=headers) + response.raise_for_status() + + data = response.json() + if 'org' in data: + org = data.get('org', '') + country = data.get('country', '') + return f"{org} {country}".strip() + except Exception as e: + print(f"ipinfo ISP信息获取失败: {e}") + pass + + # 方法3: 使用httpbin.org获取基础信息 + try: + url = "https://httpbin.org/ip" + headers = {'User-Agent': 'MagicWord/1.0'} + response = requests.get(url, timeout=5, headers=headers) + response.raise_for_status() + # 这个接口主要用于获取IP,不是ISP信息,但可以作为备选 + data = response.json() + origin = data.get('origin', '') + if origin: + return f"IP: {origin}" + except Exception as e: + print(f"httpbin ISP信息获取失败: {e}") + pass - data = response.json() - if data.get('status') == 'success': - isp = data.get('isp', '') - org = data.get('org', '') - as_info = data.get('as', '') - return f"{isp} {org} {as_info}".strip() return None except Exception as e: - print(f"获取ISP信息失败: {e}") + print(f"获取ISP信息总体失败: {e}") return None def get_location_by_ip(self): """通过IP地址获取用户位置""" try: + # 首先获取公网IP地址 + ip_address = None + try: + # 使用多个IP获取服务确保能获取到公网IP + ip_services = [ + "https://api.ipify.org", + "https://icanhazip.com", + "https://ident.me", + "https://ipecho.net/plain", + "https://myexternalip.com/raw" + ] + + for service in ip_services: + try: + response = requests.get(service, timeout=3) + if response.status_code == 200: + ip_address = response.text.strip() + # 验证是否为有效的IPv4地址 + import re + if re.match(r'^(\d{1,3}\.){3}\d{1,3}$', ip_address): + print(f"获取到公网IP: {ip_address}") + break + else: + ip_address = None + except: + continue + + if not ip_address: + print("无法获取公网IP地址") + except Exception as e: + print(f"获取IP地址失败: {e}") + # 尝试多个免费的IP地理位置API - # API 1: 搜狐IP接口(HTTP,无SSL问题) + # API 1: 使用ip-api.com接口(更稳定的免费服务,支持HTTPS) + try: + if ip_address: + url = f"https://ip-api.com/json/{ip_address}" + else: + url = "https://ip-api.com/json/" + headers = {'User-Agent': 'MagicWord/1.0'} + response = requests.get(url, timeout=5, headers=headers) + response.raise_for_status() + + data = response.json() + if data.get('status') == 'success': + city = data.get('city', '') + region = data.get('regionName', '') + country = data.get('country', '') + if city and city not in ['null', 'None', '']: + print(f"ip-api定位成功: {city}, {region}, {country}") + # 如果城市信息不完整,尝试用地区信息补充 + if len(city) < 2 and region: + city = region + return city + except Exception as e: + print(f"ip-api接口失败: {e}") + pass + + # API 2: 使用ipinfo.io接口(需要处理免费版限制) + try: + if ip_address: + url = f"https://ipinfo.io/{ip_address}/json" + else: + url = "https://ipinfo.io/json" + headers = {'User-Agent': 'MagicWord/1.0'} + response = requests.get(url, timeout=5, headers=headers) + response.raise_for_status() + + data = response.json() + if 'city' in data: + city = data.get('city', '') + region = data.get('region', '') + country = data.get('country', '') + if city and city not in ['null', 'None', '']: + print(f"ipinfo定位成功: {city}, {region}, {country}") + # 如果城市信息不完整,尝试用地区信息补充 + if len(city) < 2 and region: + city = region + return city + except Exception as e: + print(f"ipinfo接口失败: {e}") + pass + + # API 3: 搜狐IP接口(HTTP,无SSL问题) try: url = "http://pv.sohu.com/cityjson?ie=utf-8" response = requests.get(url, timeout=5) @@ -1326,7 +1449,7 @@ class WeatherAPI: print(f"搜狐IP接口失败: {e}") pass - # API 2: 使用pconline接口(HTTP) + # API 4: 使用pconline接口(HTTP) try: url = "http://whois.pconline.com.cn/ipJson.jsp" response = requests.get(url, timeout=5) @@ -1349,26 +1472,14 @@ class WeatherAPI: print(f"pconline接口失败: {e}") pass - # API 3: 使用ip-api.com接口(更稳定的免费服务) + # API 5: 使用淘宝IP接口 try: - url = "http://ip-api.com/json/" - response = requests.get(url, timeout=5) - response.raise_for_status() - - data = response.json() - if data.get('status') == 'success': - city = data.get('city', '') - if city: - print(f"ip-api定位成功: {city}") - return city - except Exception as e: - print(f"ip-api接口失败: {e}") - pass - - # API 4: 使用淘宝IP接口 - try: - url = "http://ip.taobao.com/outGetIpInfo" - params = {'ip': '', 'accessKey': 'alibaba-inc'} + if ip_address: + url = "http://ip.taobao.com/outGetIpInfo" + params = {'ip': ip_address, 'accessKey': 'alibaba-inc'} + else: + url = "http://ip.taobao.com/outGetIpInfo" + params = {'ip': '', 'accessKey': 'alibaba-inc'} response = requests.get(url, params=params, timeout=5) response.raise_for_status() @@ -1387,25 +1498,42 @@ class WeatherAPI: return None except Exception as e: print(f"IP定位总体失败: {e}") - return None + # 返回默认城市而不是None,确保天气功能仍然可用 + return "北京" def get_current_location(self): """获取当前位置信息""" try: # 首先尝试通过IP获取位置 - city = self.get_location_by_ip() - if city: - print(f"通过IP定位成功: {city}") + location_result = self.get_location_by_ip() + + # 检查是否是默认城市(表示IP定位失败) + if location_result == "北京": + print("IP定位失败,使用默认城市") + print("自动定位失败,建议手动选择城市") + return None + + if location_result: + print(f"通过IP定位成功: {location_result}") # 检查是否是教育网或特殊网络环境 isp_info = self.get_isp_info() - if isp_info and ('教育网' in isp_info or 'CERNET' in isp_info or 'University' in isp_info): + if isp_info and ('教育网' in isp_info or 'CERNET' in isp_info or 'University' in isp_info or '大学' in isp_info): print(f"检测到教育网环境: {isp_info}") print("教育网IP定位可能不准确,建议手动选择城市") # 教育网环境下,如果定位到北京,可能是IP分配问题 - if city.lower() in ['beijing', '北京', 'haidian', '海淀']: + if isinstance(location_result, str) and location_result.lower() in ['beijing', '北京', 'haidian', '海淀']: print("提示:教育网环境下北京定位可能是网络出口导致的") - return {'city': city, 'note': '教育网环境,定位可能不准确', 'isp': isp_info} + return {'city': location_result, 'note': '教育网环境,定位可能不准确', 'isp': isp_info} + + # 处理返回结果格式 + city = None + if isinstance(location_result, dict) and 'city' in location_result: + city = location_result['city'] + elif isinstance(location_result, str): + city = location_result + else: + city = str(location_result) # 智能处理 - 如果是区级单位,映射到市级城市 district_to_city_map = { @@ -1553,6 +1681,7 @@ class WeatherAPI: except Exception as e: print(f"获取当前位置失败: {e}") + # 即使出现异常也返回None而不是抛出异常,确保程序继续运行 return None def get_city_weather_by_name(self, city_name): -- 2.34.1 From 9bd8fd220d9df11ca6b842485248cf9996c259f4 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Fri, 14 Nov 2025 16:41:37 +0800 Subject: [PATCH 12/15] =?UTF-8?q?UI=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 6 +- src/ui/theme_manager.py | 616 ++++++++++++++++++++++------------------ 2 files changed, 340 insertions(+), 282 deletions(-) diff --git a/src/main.py b/src/main.py index 2377050..56b0016 100644 --- a/src/main.py +++ b/src/main.py @@ -95,8 +95,10 @@ def main(): # 创建QApplication实例 app = QApplication(sys.argv) - # 设置应用程序样式为Windows风格,更接近Word界面 - app.setStyle('WindowsVista') + # 在macOS上使用系统原生样式,在其他平台上使用WindowsVista样式 + if platform.system() != "Darwin": # 不是macOS系统 + # 设置应用程序样式为Windows风格,更接近Word界面 + app.setStyle('WindowsVista') # 设置应用程序属性 app.setApplicationName("MagicWord") diff --git a/src/ui/theme_manager.py b/src/ui/theme_manager.py index 4169478..988b05a 100644 --- a/src/ui/theme_manager.py +++ b/src/ui/theme_manager.py @@ -153,99 +153,100 @@ class ThemeManager(QObject): return self._get_light_stylesheet() def _get_dark_stylesheet(self): - """深色主题样式表 - 现代极简暗色风格""" + """深色主题样式表 - Apple设计风格""" return """ - /* 现代极简暗色主题样式 */ + /* Apple设计风格深色主题样式 */ - /* 全局文字颜色和字体 - 现代字体 */ + /* 全局文字颜色和字体 - 使用Apple系统字体 */ QWidget { - color: #e2e8f0; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; + color: #f0f0f0; + font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 13px; } - /* 主窗口 - 深灰色背景 */ + /* 主窗口 - Apple深色背景 */ QMainWindow { - background-color: #1a202c; + background-color: #2c2c2e; } - /* 菜单栏 - 极简暗色风格 */ + /* 菜单栏 - Apple深色风格 */ QMenuBar { - background-color: #1a202c; + background-color: #2c2c2e; border: none; - border-bottom: 1px solid #2d3748; - font-size: 14px; - color: #e2e8f0; - padding: 8px 0; + border-bottom: 1px solid #404040; + font-size: 13px; + color: #f0f0f0; + padding: 4px 0; } QMenuBar::item { background-color: transparent; - padding: 8px 16px; - color: #a0aec0; - border-radius: 6px; - margin: 0 2px; + padding: 6px 12px; + color: #f0f0f0; + border-radius: 4px; + margin: 0 1px; } QMenuBar::item:selected { - background-color: #2d3748; - color: #e2e8f0; + background-color: #404040; + color: #f0f0f0; } QMenuBar::item:pressed { - background-color: #4a5568; - color: #e2e8f0; + background-color: #505050; + color: #f0f0f0; } - /* 菜单 - 极简暗色风格 */ + /* 菜单 - Apple深色风格 */ QMenu { - background-color: #2d3748; - border: 1px solid #4a5568; - border-radius: 12px; - font-size: 14px; - color: #e2e8f0; - padding: 8px 0; - margin: 4px; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); + background-color: #2c2c2e; + border: 1px solid #404040; + border-radius: 8px; + font-size: 13px; + color: #f0f0f0; + padding: 4px 0; + margin: 2px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } QMenu::item { - padding: 10px 20px; - color: #a0aec0; + color: #f0f0f0; background-color: transparent; - border-radius: 6px; - margin: 0 8px; + border-radius: 4px; + margin: 0 4px; + padding: 4px 20px; } QMenu::item:selected { - background-color: #4a5568; - color: #e2e8f0; + background-color: #0a84ff; + color: #ffffff; } QMenu::item:pressed { - background-color: #718096; + background-color: #0066cc; color: #ffffff; } QMenu::separator { height: 1px; - background-color: #4a5568; - margin: 8px 16px; + background-color: #404040; + margin: 4px 8px; } /* 功能区 */ QFrame { - background-color: #2d2d2d; - border: 1px solid #3c3c3c; + background-color: #2c2c2e; + border: none; } /* 组框 */ QGroupBox { - font-size: 11px; + font-size: 12px; font-weight: normal; - color: #e0e0e0; - background-color: #2d2d2d; - border: 1px solid #3c3c3c; - border-radius: 0px; + color: #f0f0f0; + background-color: #2c2c2e; + border: none; + border-radius: 8px; margin-top: 5px; padding-top: 5px; } @@ -254,198 +255,235 @@ class ThemeManager(QObject): subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; - color: #e0e0e0; + color: #a0a0a0; } - /* 工具按钮 - 现代极简风格 */ + /* 工具按钮 - Apple深色风格 */ QToolButton { - background-color: transparent; border: 1px solid transparent; - border-radius: 8px; - padding: 8px 12px; - color: #4a5568; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; - font-weight: 500; + border-radius: 6px; + background-color: #3a3a3c; font-size: 13px; + color: #f0f0f0; + padding: 6px 12px; } QToolButton:hover { - background-color: #f7fafc; - border: 1px solid #e2e8f0; + background-color: #4a4a4c; + border: 1px solid #5a5a5c; } QToolButton:pressed { - background-color: #edf2f7; - border: 1px solid #cbd5e0; + background-color: #5a5a5c; + border: 1px solid #6a6a6c; } QToolButton:checked { - background-color: #ebf8ff; - border: 1px solid #bee3f8; - color: #3182ce; + background-color: #0a84ff; + border: 1px solid #0a84ff; + color: #ffffff; } /* 切换按钮 */ QToolButton[checkable="true"] { - border: 1px solid #3c3c3c; - border-radius: 2px; - background-color: #3c3c3c; + border: 1px solid #4a4a4c; + border-radius: 6px; + background-color: #3a3a3c; font-size: 12px; - font-weight: bold; - color: #e0e0e0; + color: #f0f0f0; + padding: 6px 12px; } QToolButton[checkable="true"]:hover { - background-color: #4a4a4a; + background-color: #4a4a4c; } QToolButton[checkable="true"]:checked { - background-color: #0078d4; - border: 1px solid #106ebe; + background-color: #0a84ff; + border: 1px solid #0a84ff; + color: #ffffff; } - /* 下拉框 - 修复文字不可见问题 */ + /* 下拉框 - Apple深色风格 */ QComboBox { - background-color: #3c3c3c; - border: 1px solid #5a5a5a; - border-radius: 2px; - color: #e0e0e0; - padding: 2px 5px; - selection-background-color: #4a4a4a; - selection-color: #e0e0e0; + background-color: #3a3a3c; + border: 1px solid #4a4a4c; + border-radius: 6px; + color: #f0f0f0; + padding: 4px 8px; + selection-background-color: #0a84ff; + selection-color: #ffffff; } QComboBox:hover { - background-color: #4a4a4a; - border: 1px solid #6a6a6a; + background-color: #4a4a4c; + border: 1px solid #5a5a5c; } QComboBox::drop-down { border: none; - width: 15px; - background-color: #3c3c3c; + width: 20px; + border-left: 1px solid #4a4a4c; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; } QComboBox::down-arrow { image: none; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 5px solid #e0e0e0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 6px solid #a0a0a0; + margin: 6px; } - /* 下拉框弹出列表 */ QComboBox QAbstractItemView { - background-color: #3c3c3c; - border: 1px solid #5a5a5a; - color: #e0e0e0; - selection-background-color: #4a4a4a; - selection-color: #e0e0e0; - } - - /* 字体下拉框特殊处理 */ - QFontComboBox { - background-color: #3c3c3c; - border: 1px solid #5a5a5a; - border-radius: 2px; - color: #e0e0e0; - padding: 2px 5px; - selection-background-color: #4a4a4a; - selection-color: #e0e0e0; + background-color: #2c2c2e; + border: 1px solid #4a4a4c; + color: #f0f0f0; + selection-background-color: #0a84ff; + selection-color: #ffffff; } - QFontComboBox:hover { - background-color: #4a4a4a; - border: 1px solid #6a6a6a; - } - - QFontComboBox QAbstractItemView { - background-color: #3c3c3c; - border: 1px solid #5a5a5a; - color: #e0e0e0; - selection-background-color: #4a4a4a; - selection-color: #e0e0e0; - } - - /* 文本编辑器 */ + /* 文本编辑区域 - Apple深色风格 */ QTextEdit { - background-color: #1e1e1e; - border: 1px solid #3c3c3c; - color: #e0e0e0; - padding: 20px; + background-color: #1c1c1e; + border: none; + font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 15px; + color: #f0f0f0; + padding: 32px; line-height: 1.5; + selection-background-color: #0066cc; + selection-color: #ffffff; } - /* 状态栏 */ + /* 状态栏 - Apple深色风格 */ QStatusBar { - background-color: #2d2d2d; - border-top: 1px solid #3c3c3c; - font-size: 11px; - color: #e0e0e0; + background-color: #3a3a3c; + border-top: 1px solid #4a4a4c; + font-size: 12px; + color: #a0a0a0; + font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif; + padding: 6px 12px; } /* 标签 */ QLabel { - color: #e0e0e0; + color: #f0f0f0; background-color: transparent; } - /* 消息框 - 修复黑色背景问题 */ + /* 消息框 - Apple深色风格 */ QMessageBox { - background-color: #2d2d2d; - color: #e0e0e0; + background-color: #2c2c2e; + color: #f0f0f0; + border-radius: 12px; } QMessageBox QPushButton { - background-color: #3c3c3c; - color: #e0e0e0; - border: 1px solid #5a5a5a; - border-radius: 3px; - padding: 5px 15px; + background-color: #3a3a3c; + color: #f0f0f0; + border: 1px solid #4a4a4c; + border-radius: 6px; + padding: 6px 16px; min-width: 80px; } QMessageBox QPushButton:hover { - background-color: #4a4a4a; - border: 1px solid #6a6a6a; + background-color: #4a4a4c; + border: 1px solid #5a5a5c; } QMessageBox QPushButton:pressed { - background-color: #2a2a2a; - border: 1px solid #1a1a1a; + background-color: #5a5a5c; + border: 1px solid #6a6a6c; + } + + QMessageBox QPushButton:default { + background-color: #0a84ff; + color: #ffffff; + border: 1px solid #0a84ff; + } + + QMessageBox QPushButton:default:hover { + background-color: #0066cc; + border: 1px solid #0066cc; + } + + QMessageBox QPushButton:default:pressed { + background-color: #004d99; + border: 1px solid #004d99; } - /* 滚动条 */ + /* 滚动条 - Apple深色风格 */ QScrollBar:vertical { - background-color: #2d2d2d; - width: 12px; + background-color: transparent; + width: 8px; border: none; } QScrollBar::handle:vertical { - background-color: #5a5a5a; - border-radius: 6px; + background-color: #5a5a5c; + border-radius: 4px; min-height: 20px; } QScrollBar::handle:vertical:hover { - background-color: #6a6a6a; + background-color: #6a6a6c; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { border: none; background: none; } + + /* 按钮 - Apple深色风格 */ + QPushButton { + background-color: #3a3a3c; + color: #f0f0f0; + border: 1px solid #4a4a4c; + border-radius: 6px; + padding: 6px 16px; + font-size: 13px; + } + + QPushButton:hover { + background-color: #4a4a4c; + border: 1px solid #5a5a5c; + } + + QPushButton:pressed { + background-color: #5a5a5c; + border: 1px solid #6a6a6c; + } + + QPushButton:default { + background-color: #0a84ff; + color: #ffffff; + border: 1px solid #0a84ff; + } + + QPushButton:default:hover { + background-color: #0066cc; + border: 1px solid #0066cc; + } + + QPushButton:default:pressed { + background-color: #004d99; + border: 1px solid #004d99; + } """ def _get_light_stylesheet(self): - """浅色主题样式表 - 现代极简主义风格""" + """浅色主题样式表 - Apple设计风格""" return """ - /* 现代极简主义浅色主题样式 */ + /* Apple设计风格浅色主题样式 */ - /* 全局文字颜色和字体 - 使用现代字体 */ + /* 全局文字颜色和字体 - 使用Apple系统字体 */ QWidget { - color: #2d3748; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; + color: #333333; + font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 13px; } /* 主窗口 - 纯净白色背景 */ @@ -453,84 +491,84 @@ class ThemeManager(QObject): background-color: #ffffff; } - /* 菜单栏 - 极简风格 */ + /* 菜单栏 - Apple风格 */ QMenuBar { background-color: #ffffff; border: none; - border-bottom: 1px solid #e2e8f0; - font-size: 14px; - color: #2d3748; - padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + font-size: 13px; + color: #333333; + padding: 4px 0; } QMenuBar::item { background-color: transparent; - padding: 8px 16px; - color: #4a5568; - border-radius: 6px; - margin: 0 2px; + padding: 6px 12px; + color: #333333; + border-radius: 4px; + margin: 0 1px; } QMenuBar::item:selected { - background-color: #f7fafc; - color: #2d3748; + background-color: #f0f0f0; + color: #333333; } QMenuBar::item:pressed { - background-color: #edf2f7; - color: #2d3748; + background-color: #e0e0e0; + color: #333333; } - /* 菜单 - 极简风格 */ + /* 菜单 - Apple风格 */ QMenu { background-color: #ffffff; - border: 1px solid #e2e8f0; - border-radius: 12px; - font-size: 14px; - color: #2d3748; - padding: 8px 0; - margin: 4px; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + border: 1px solid #d0d0d0; + border-radius: 8px; + font-size: 13px; + color: #333333; + padding: 4px 0; + margin: 2px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } QMenu::item { - padding: 10px 20px; - color: #4a5568; + color: #333333; background-color: transparent; - border-radius: 6px; - margin: 0 8px; + border-radius: 4px; + margin: 0 4px; + padding: 4px 20px; } QMenu::item:selected { - background-color: #f7fafc; - color: #2d3748; + background-color: #007aff; + color: #ffffff; } QMenu::item:pressed { - background-color: #edf2f7; - color: #2d3748; + background-color: #0062cc; + color: #ffffff; } QMenu::separator { height: 1px; - background-color: #e2e8f0; - margin: 8px 16px; + background-color: #e0e0e0; + margin: 4px 8px; } /* 功能区 */ QFrame { background-color: #ffffff; - border: 1px solid #d0d0d0; + border: none; } /* 组框 */ QGroupBox { - font-size: 11px; + font-size: 12px; font-weight: normal; color: #333333; background-color: #ffffff; - border: 1px solid #d0d0d0; - border-radius: 0px; + border: none; + border-radius: 8px; margin-top: 5px; padding-top: 5px; } @@ -539,151 +577,116 @@ class ThemeManager(QObject): subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; - color: #333333; + color: #666666; } - /* 按钮 */ + /* 工具按钮 - Apple风格 */ QToolButton { - border: 1px solid #d0d0d0; - border-radius: 3px; - background-color: #ffffff; - font-size: 11px; + border: 1px solid transparent; + border-radius: 6px; + background-color: #f6f6f6; + font-size: 13px; color: #333333; - padding: 3px 6px; + padding: 6px 12px; } QToolButton:hover { - background-color: #f0f0f0; - border: 1px solid #0078d7; + background-color: #e0e0e0; + border: 1px solid #d0d0d0; } QToolButton:pressed { - background-color: #e0e0e0; - border: 1px solid #005a9e; + background-color: #d0d0d0; + border: 1px solid #c0c0c0; } QToolButton:checked { - background-color: #0078d7; - border: 1px solid #005a9e; + background-color: #007aff; + border: 1px solid #007aff; color: #ffffff; } /* 切换按钮 */ QToolButton[checkable="true"] { border: 1px solid #d0d0d0; - border-radius: 2px; - background-color: #ffffff; + border-radius: 6px; + background-color: #f6f6f6; font-size: 12px; - font-weight: bold; color: #333333; + padding: 6px 12px; } QToolButton[checkable="true"]:hover { - background-color: #f0f0f0; + background-color: #e0e0e0; } QToolButton[checkable="true"]:checked { - background-color: #0078d7; - border: 1px solid #005a9e; + background-color: #007aff; + border: 1px solid #007aff; color: #ffffff; } - /* 下拉框 - 白底黑字 */ + /* 下拉框 - Apple风格 */ QComboBox { - background-color: #ffffff; + background-color: #f6f6f6; border: 1px solid #d0d0d0; - border-radius: 2px; + border-radius: 6px; color: #333333; - padding: 2px 5px; - selection-background-color: #f0f0f0; - selection-color: #333333; + padding: 4px 8px; + selection-background-color: #007aff; + selection-color: #ffffff; } QComboBox:hover { - background-color: #f0f0f0; - border: 1px solid #0078d7; + background-color: #e0e0e0; + border: 1px solid #c0c0c0; } QComboBox::drop-down { border: none; - width: 15px; - background-color: #ffffff; + width: 20px; + border-left: 1px solid #d0d0d0; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; } QComboBox::down-arrow { image: none; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 5px solid #333333; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 6px solid #666666; + margin: 6px; } - /* 下拉框弹出列表 */ QComboBox QAbstractItemView { - background-color: #ffffff; border: 1px solid #d0d0d0; color: #333333; - selection-background-color: #f0f0f0; - selection-color: #333333; - } - - /* 字体下拉框特殊处理 - 白底黑字 */ - QFontComboBox { - background-color: #ffffff; - border: 1px solid #d0d0d0; - border-radius: 2px; - color: #333333; - padding: 2px 5px; - selection-background-color: #f0f0f0; - selection-color: #333333; - } - - QFontComboBox:hover { - background-color: #f0f0f0; - border: 1px solid #0078d7; - } - - QFontComboBox::drop-down { - border: none; - width: 15px; - background-color: #ffffff; - } - - QFontComboBox::down-arrow { - image: none; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 5px solid #333333; - } - - QFontComboBox QAbstractItemView { background-color: #ffffff; - border: 1px solid #d0d0d0; - color: #333333; - selection-background-color: #f0f0f0; - selection-color: #333333; + selection-background-color: #007aff; + selection-color: #ffffff; } - /* 文本编辑区域 - 现代极简风格 */ + /* 文本编辑区域 - Apple风格 */ QTextEdit { background-color: #ffffff; border: none; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; - font-size: 14px; - color: #2d3748; + font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 15px; + color: #333333; padding: 32px; - line-height: 1.6; - selection-background-color: rgba(66, 153, 225, 0.2); - selection-color: #2d3748; + line-height: 1.5; + selection-background-color: #b3d9ff; + selection-color: #333333; } - /* 状态栏 - 现代极简风格 */ + /* 状态栏 - Apple风格 */ QStatusBar { - background-color: #ffffff; - border-top: 1px solid #e2e8f0; + background-color: #f6f6f6; + border-top: 1px solid #e0e0e0; font-size: 12px; - color: #718096; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; - padding: 8px; + color: #666666; + font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif; + padding: 6px 12px; } /* 标签 */ @@ -692,41 +695,58 @@ class ThemeManager(QObject): background-color: transparent; } - /* 消息框 - 修复黑色背景问题 */ + /* 消息框 - Apple风格 */ QMessageBox { background-color: #ffffff; color: #333333; + border-radius: 12px; } QMessageBox QPushButton { - background-color: #ffffff; + background-color: #f6f6f6; color: #333333; border: 1px solid #d0d0d0; - border-radius: 3px; - padding: 5px 15px; + border-radius: 6px; + padding: 6px 16px; min-width: 80px; } QMessageBox QPushButton:hover { - background-color: #f0f0f0; - border: 1px solid #0078d7; + background-color: #e0e0e0; + border: 1px solid #c0c0c0; } QMessageBox QPushButton:pressed { - background-color: #e0e0e0; - border: 1px solid #005a9e; + background-color: #d0d0d0; + border: 1px solid #a0a0a0; + } + + QMessageBox QPushButton:default { + background-color: #007aff; + color: #ffffff; + border: 1px solid #007aff; } - /* 滚动条 */ + QMessageBox QPushButton:default:hover { + background-color: #0062cc; + border: 1px solid #0062cc; + } + + QMessageBox QPushButton:default:pressed { + background-color: #004a99; + border: 1px solid #004a99; + } + + /* 滚动条 - Apple风格 */ QScrollBar:vertical { - background-color: #ffffff; - width: 12px; + background-color: transparent; + width: 8px; border: none; } QScrollBar::handle:vertical { background-color: #c0c0c0; - border-radius: 6px; + border-radius: 4px; min-height: 20px; } @@ -738,6 +758,42 @@ class ThemeManager(QObject): border: none; background: none; } + + /* 按钮 - Apple风格 */ + QPushButton { + background-color: #f6f6f6; + color: #333333; + border: 1px solid #d0d0d0; + border-radius: 6px; + padding: 6px 16px; + font-size: 13px; + } + + QPushButton:hover { + background-color: #e0e0e0; + border: 1px solid #c0c0c0; + } + + QPushButton:pressed { + background-color: #d0d0d0; + border: 1px solid #a0a0a0; + } + + QPushButton:default { + background-color: #007aff; + color: #ffffff; + border: 1px solid #007aff; + } + + QPushButton:default:hover { + background-color: #0062cc; + border: 1px solid #0062cc; + } + + QPushButton:default:pressed { + background-color: #004a99; + border: 1px solid #004a99; + } """ def set_dark_theme(self, is_dark): -- 2.34.1 From fadf1a38677dc5d91961d09de56e18f98219320b Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Fri, 14 Nov 2025 16:50:11 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 84 ++++++++++++++++++++++++++++++- src/word_main_window.py | 107 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 2 deletions(-) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 731e63b..6a045d1 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -143,7 +143,43 @@ class WordRibbon(QFrame): # 样式组 styles_group = self.create_ribbon_group("样式") styles_layout = QVBoxLayout() - styles_layout.addWidget(QLabel("样式")) + + # 创建标题样式按钮 + title_buttons_layout = QHBoxLayout() + + # 一级标题按钮 + self.heading1_btn = self.create_style_button("标题1") + self.heading1_btn.clicked.connect(self.on_heading1_clicked) + title_buttons_layout.addWidget(self.heading1_btn) + + # 二级标题按钮 + self.heading2_btn = self.create_style_button("标题2") + self.heading2_btn.clicked.connect(self.on_heading2_clicked) + title_buttons_layout.addWidget(self.heading2_btn) + + # 三级标题按钮 + self.heading3_btn = self.create_style_button("标题3") + self.heading3_btn.clicked.connect(self.on_heading3_clicked) + title_buttons_layout.addWidget(self.heading3_btn) + + styles_layout.addLayout(title_buttons_layout) + + # 第二行样式按钮 + style_buttons_layout = QHBoxLayout() + + # 四级标题按钮 + self.heading4_btn = self.create_style_button("标题4") + self.heading4_btn.clicked.connect(self.on_heading4_clicked) + style_buttons_layout.addWidget(self.heading4_btn) + + # 正文按钮 + self.body_text_btn = self.create_style_button("正文") + self.body_text_btn.clicked.connect(self.on_body_text_clicked) + style_buttons_layout.addWidget(self.body_text_btn) + + style_buttons_layout.addStretch() + styles_layout.addLayout(style_buttons_layout) + styles_group.setLayout(styles_layout) layout.addWidget(styles_group) @@ -195,6 +231,26 @@ class WordRibbon(QFrame): """字体颜色按钮点击处理""" pass + def on_heading1_clicked(self): + """一级标题按钮点击处理""" + pass + + def on_heading2_clicked(self): + """二级标题按钮点击处理""" + pass + + def on_heading3_clicked(self): + """三级标题按钮点击处理""" + pass + + def on_heading4_clicked(self): + """四级标题按钮点击处理""" + pass + + def on_body_text_clicked(self): + """正文按钮点击处理""" + pass + def init_theme(self): """初始化主题""" # 连接主题切换信号 @@ -885,6 +941,32 @@ class WordRibbon(QFrame): } """) return btn + + def create_style_button(self, text): + """创建样式按钮 - 现代极简主义风格""" + btn = QPushButton(text) + btn.setFixedSize(60, 28) + btn.setStyleSheet(""" + QPushButton { + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: #4a5568; + font-size: 12px; + font-weight: 500; + text-align: center; + } + QPushButton:hover { + background-color: #f7fafc; + border: 1px solid #e2e8f0; + } + QPushButton:pressed { + background-color: #edf2f7; + border: 1px solid #cbd5e0; + } + """) + return btn class WordStatusBar(QStatusBar): def __init__(self, parent=None): diff --git a/src/word_main_window.py b/src/word_main_window.py index 914abbc..0633ef2 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QDialog, QLineEdit, QCheckBox, QPushButton, QListWidget, QListWidgetItem, QScrollArea) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect, QByteArray, QBuffer, QIODevice -from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor, QTextDocument, QImage, QTextImageFormat, QTextFormat +from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor, QTextDocument, QImage, QTextImageFormat, QTextFormat, QTextBlockFormat from ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit, ) @@ -852,6 +852,18 @@ class WordStyleMainWindow(QMainWindow): self.ribbon.underline_btn.clicked.connect(self.on_underline_clicked) self.ribbon.color_btn.clicked.connect(self.on_color_clicked) + # 样式按钮信号 + if hasattr(self.ribbon, 'heading1_btn'): + self.ribbon.heading1_btn.clicked.connect(self.on_heading1_clicked) + if hasattr(self.ribbon, 'heading2_btn'): + self.ribbon.heading2_btn.clicked.connect(self.on_heading2_clicked) + if hasattr(self.ribbon, 'heading3_btn'): + self.ribbon.heading3_btn.clicked.connect(self.on_heading3_clicked) + if hasattr(self.ribbon, 'heading4_btn'): + self.ribbon.heading4_btn.clicked.connect(self.on_heading4_clicked) + if hasattr(self.ribbon, 'body_text_btn'): + self.ribbon.body_text_btn.clicked.connect(self.on_body_text_clicked) + # 查找和替换按钮信号 if hasattr(self.ribbon, 'find_btn'): self.ribbon.find_btn.clicked.connect(self.show_find_dialog) @@ -1322,6 +1334,99 @@ class WordStyleMainWindow(QMainWindow): if cursor.hasSelection(): self.status_bar.showMessage("字体颜色已设置,新输入的文本将使用该颜色", 2000) + def on_heading1_clicked(self): + """一级标题按钮点击处理""" + self.apply_heading_style(1) + + def on_heading2_clicked(self): + """二级标题按钮点击处理""" + self.apply_heading_style(2) + + def on_heading3_clicked(self): + """三级标题按钮点击处理""" + self.apply_heading_style(3) + + def on_heading4_clicked(self): + """四级标题按钮点击处理""" + self.apply_heading_style(4) + + def on_body_text_clicked(self): + """正文按钮点击处理""" + self.apply_body_text_style() + + def apply_heading_style(self, level): + """应用标题样式""" + cursor = self.text_edit.textCursor() + + # 创建字符格式 + char_format = QTextCharFormat() + + # 创建块格式(段落格式) + block_format = QTextBlockFormat() + block_format.setTopMargin(12) + block_format.setBottomMargin(6) + + # 根据标题级别设置样式 + if level == 1: + # 一级标题:24pt, 加粗 + char_format.setFontPointSize(24) + char_format.setFontWeight(QFont.Bold) + elif level == 2: + # 二级标题:18pt, 加粗 + char_format.setFontPointSize(18) + char_format.setFontWeight(QFont.Bold) + elif level == 3: + # 三级标题:16pt, 加粗 + char_format.setFontPointSize(16) + char_format.setFontWeight(QFont.Bold) + elif level == 4: + # 四级标题:14pt, 加粗 + char_format.setFontPointSize(14) + char_format.setFontWeight(QFont.Bold) + + # 应用格式 + if cursor.hasSelection(): + # 如果有选中文本,只更改选中文本的格式 + cursor.mergeCharFormat(char_format) + else: + # 如果没有选中文本,更改当前段落的格式 + cursor.setBlockFormat(block_format) + cursor.mergeCharFormat(char_format) + # 将光标移动到段落末尾并添加换行 + cursor.movePosition(QTextCursor.EndOfBlock) + cursor.insertText("\n") + + # 设置文本编辑器的默认格式 + self.text_edit.setCurrentCharFormat(char_format) + self.text_edit.textCursor().setBlockFormat(block_format) + + def apply_body_text_style(self): + """应用正文样式""" + cursor = self.text_edit.textCursor() + + # 创建字符格式 + char_format = QTextCharFormat() + char_format.setFontPointSize(12) # 正文字号 + char_format.setFontWeight(QFont.Normal) # 正常粗细 + + # 创建块格式(段落格式) + block_format = QTextBlockFormat() + block_format.setTopMargin(0) + block_format.setBottomMargin(6) + + # 应用格式 + if cursor.hasSelection(): + # 如果有选中文本,只更改选中文本的格式 + cursor.mergeCharFormat(char_format) + else: + # 如果没有选中文本,更改当前段落的格式 + cursor.setBlockFormat(block_format) + cursor.mergeCharFormat(char_format) + + # 设置文本编辑器的默认格式 + self.text_edit.setCurrentCharFormat(char_format) + self.text_edit.textCursor().setBlockFormat(block_format) + def update_weather_display(self, weather_data): """更新天气显示""" if 'error' in weather_data: -- 2.34.1 From 31c493d2d1aebbfa1b0512039ef94e031afe5ca2 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Fri, 14 Nov 2025 18:12:41 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E6=8C=89=E9=92=AE=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 16 ++++++++ src/word_main_window.py | 82 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 6a045d1..fa3bdc4 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -251,6 +251,22 @@ class WordRibbon(QFrame): """正文按钮点击处理""" pass + def on_align_left_clicked(self): + """左对齐按钮点击处理""" + pass + + def on_align_center_clicked(self): + """居中对齐按钮点击处理""" + pass + + def on_align_right_clicked(self): + """右对齐按钮点击处理""" + pass + + def on_align_justify_clicked(self): + """两端对齐按钮点击处理""" + pass + def init_theme(self): """初始化主题""" # 连接主题切换信号 diff --git a/src/word_main_window.py b/src/word_main_window.py index 0633ef2..b3f72e1 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -840,6 +840,9 @@ class WordStyleMainWindow(QMainWindow): # 文本变化信号 self.text_edit.textChanged.connect(self.on_text_changed) + # 光标位置变化信号,用于更新按钮状态 + self.text_edit.cursorPositionChanged.connect(self.update_format_buttons) + # Ribbon按钮信号 # 标签栏已删除,相关代码已移除 @@ -872,6 +875,16 @@ class WordStyleMainWindow(QMainWindow): # 页面布局信号已在菜单中直接连接,无需在此重复连接 + # 段落对齐按钮信号 + if hasattr(self.ribbon, 'align_left_btn'): + self.ribbon.align_left_btn.clicked.connect(self.on_align_left_clicked) + if hasattr(self.ribbon, 'align_center_btn'): + self.ribbon.align_center_btn.clicked.connect(self.on_align_center_clicked) + if hasattr(self.ribbon, 'align_right_btn'): + self.ribbon.align_right_btn.clicked.connect(self.on_align_right_clicked) + if hasattr(self.ribbon, 'align_justify_btn'): + self.ribbon.align_justify_btn.clicked.connect(self.on_align_justify_clicked) + # 天气功能信号 if hasattr(self.ribbon, 'city_combo'): self.ribbon.city_combo.currentTextChanged.connect(self.on_city_changed) @@ -1354,6 +1367,22 @@ class WordStyleMainWindow(QMainWindow): """正文按钮点击处理""" self.apply_body_text_style() + def on_align_left_clicked(self): + """左对齐按钮点击处理""" + self.apply_alignment(Qt.AlignLeft) + + def on_align_center_clicked(self): + """居中对齐按钮点击处理""" + self.apply_alignment(Qt.AlignCenter) + + def on_align_right_clicked(self): + """右对齐按钮点击处理""" + self.apply_alignment(Qt.AlignRight) + + def on_align_justify_clicked(self): + """两端对齐按钮点击处理""" + self.apply_alignment(Qt.AlignJustify) + def apply_heading_style(self, level): """应用标题样式""" cursor = self.text_edit.textCursor() @@ -1427,6 +1456,25 @@ class WordStyleMainWindow(QMainWindow): self.text_edit.setCurrentCharFormat(char_format) self.text_edit.textCursor().setBlockFormat(block_format) + def apply_alignment(self, alignment): + """应用段落对齐方式""" + cursor = self.text_edit.textCursor() + + # 创建块格式(段落格式) + block_format = QTextBlockFormat() + block_format.setAlignment(alignment) + + # 应用格式 + if cursor.hasSelection(): + # 如果有选中文本,更改选中文本所在段落的对齐方式 + cursor.mergeBlockFormat(block_format) + else: + # 如果没有选中文本,更改当前段落的对齐方式 + cursor.setBlockFormat(block_format) + + # 更新文本编辑器的默认段落格式 + self.text_edit.textCursor().setBlockFormat(block_format) + def update_weather_display(self, weather_data): """更新天气显示""" if 'error' in weather_data: @@ -3368,6 +3416,40 @@ class WordStyleMainWindow(QMainWindow): except Exception as e: self.status_bar.showMessage(f"提取图片失败: {str(e)}", 3000) + + def update_format_buttons(self): + """更新格式按钮的状态,根据当前光标位置的格式""" + try: + # 获取当前光标位置的字符格式 + cursor = self.text_edit.textCursor() + char_format = cursor.charFormat() + block_format = cursor.blockFormat() + + # 更新粗体按钮状态 + if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'bold_btn'): + is_bold = char_format.font().weight() == QFont.Bold + self.ribbon.bold_btn.setChecked(is_bold) + + # 更新斜体按钮状态 + if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'italic_btn'): + is_italic = char_format.font().italic() + self.ribbon.italic_btn.setChecked(is_italic) + + # 更新下划线按钮状态 + if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'underline_btn'): + is_underline = char_format.font().underline() + self.ribbon.underline_btn.setChecked(is_underline) + + # 更新对齐按钮状态 + if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'align_left_btn'): + alignment = block_format.alignment() + self.ribbon.align_left_btn.setChecked(alignment == Qt.AlignLeft) + self.ribbon.align_center_btn.setChecked(alignment == Qt.AlignCenter) + self.ribbon.align_right_btn.setChecked(alignment == Qt.AlignRight) + self.ribbon.align_justify_btn.setChecked(alignment == Qt.AlignJustify) + + except Exception as e: + print(f"更新格式按钮状态时出错: {e}") if __name__ == "__main__": app = QApplication(sys.argv) -- 2.34.1 From 62d1df122c2bc9db642d84259a0142ca6677e827 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Mon, 17 Nov 2025 10:04:06 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=A4=A9=E6=B0=94?= =?UTF-8?q?=E7=BB=84=E4=BB=B6+UI=E5=B0=8F=E4=BF=AE=E8=A1=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 544 ++++++++++++++++++++++++++++++---------- src/word_main_window.py | 5 +- 2 files changed, 410 insertions(+), 139 deletions(-) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index fa3bdc4..a29a043 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -302,6 +302,32 @@ class WordRibbon(QFrame): }} """) + # 更新天气组件样式 + if hasattr(self, 'weather_icon_label') and self.weather_icon_label is not None: + self.weather_icon_label.setStyleSheet(f""" + QLabel {{ + font-size: 32px; + padding: 0px; + margin: 0px; + border: none; + background: transparent; + }} + """) + + if hasattr(self, 'weather_temp_label') and self.weather_temp_label is not None: + self.weather_temp_label.setStyleSheet(f""" + QLabel {{ + font-size: 16px; + font-weight: bold; + color: {colors['text']}; + padding: 0px; + margin: 0px; + border: none; + background: transparent; + min-height: 30px; + }} + """) + # 更新下拉框样式 self.update_combo_styles(is_dark) @@ -595,7 +621,7 @@ class WordRibbon(QFrame): if self.weather_group is not None: return self.weather_group - weather_group = self.create_ribbon_group("天气") + weather_group = self.create_ribbon_group("天气", is_special_group=True) weather_group.setFixedWidth(200) # 增加整体宽度 weather_layout = QVBoxLayout() weather_layout.setSpacing(8) # 增加行间距 @@ -624,11 +650,11 @@ class WordRibbon(QFrame): # 温度标签 - 优化垂直居中对齐 self.weather_temp_label = QLabel("--°C") self.weather_temp_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) # 使用Qt对齐方式 + # 初始化时使用默认颜色,主题切换时会更新 self.weather_temp_label.setStyleSheet(""" QLabel { font-size: 16px; font-weight: bold; - color: #333333; padding: 0px; margin: 0px; border: none; @@ -709,7 +735,7 @@ class WordRibbon(QFrame): if self.quote_group is not None: return self.quote_group - quote_group = self.create_ribbon_group("每日一言") + quote_group = self.create_ribbon_group("每日一言", is_special_group=True) quote_layout = QVBoxLayout() # 创建第一行:类型选择下拉框和刷新按钮 @@ -772,27 +798,76 @@ class WordRibbon(QFrame): self.quote_group = None self.quote_visible = False - def create_ribbon_group(self, title): + def create_ribbon_group(self, title, is_special_group=False): """创建功能区组""" 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; - } - """) + + # 为非特殊组设置最小宽度以确保标题完整显示 + if not is_special_group: + # 根据标题长度计算合适的最小宽度 + min_width = max(100, len(title) * 12 + 40) # 基础宽度+每个字符约12px + group.setMinimumWidth(min_width) + + # 连接主题切换信号以动态更新样式 + theme_manager.theme_changed.connect(lambda: self._update_group_style(group)) + + # 立即应用当前主题样式 + self._update_group_style(group) + return group + def _update_group_style(self, group): + """更新组样式以适配当前主题""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + group.setStyleSheet(f""" + QGroupBox {{ + font-size: 11px; + font-weight: normal; + color: {colors['text']}; + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 0px; + margin-top: 5px; + padding-top: 5px; + }} + QGroupBox::title {{ + subcontrol-origin: margin; + left: 10px; + padding: 0 5px 0 5px; + color: {colors['text']}; + /* 确保标题不会被截断 */ + white-space: nowrap; + text-overflow: clip; /* 不显示省略号 */ + overflow: visible; /* 允许内容溢出 */ + }} + """) + else: + group.setStyleSheet(f""" + QGroupBox {{ + font-size: 11px; + font-weight: normal; + color: {colors['text']}; + background-color: {colors['surface']}; + border: 1px solid {colors['border']}; + border-radius: 0px; + margin-top: 5px; + padding-top: 5px; + }} + QGroupBox::title {{ + subcontrol-origin: margin; + left: 10px; + padding: 0 5px 0 5px; + color: {colors['text']}; + /* 确保标题不会被截断 */ + white-space: nowrap; + text-overflow: clip; /* 不显示省略号 */ + overflow: visible; /* 允许内容溢出 */ + }} + """) + def on_refresh_weather(self): """刷新天气按钮点击处理""" pass @@ -873,25 +948,57 @@ class WordRibbon(QFrame): 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; - } - """) + + # 连接主题切换信号以动态更新样式 + theme_manager.theme_changed.connect(lambda: self._update_button_style(btn)) + + # 立即应用当前主题样式 + self._update_button_style(btn) + return btn + def _update_button_style(self, btn): + """更新按钮样式以适配当前主题""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + btn.setStyleSheet(f""" + QToolButton {{ + border: 1px solid transparent; + border-radius: 3px; + background-color: transparent; + font-size: 11px; + color: {colors['text']}; + }} + QToolButton:hover {{ + background-color: {colors['surface_hover']}; + border: 1px solid {colors['border']}; + }} + QToolButton:pressed {{ + background-color: #5a5a5c; + border: 1px solid #6a6a6c; + }} + """) + else: + btn.setStyleSheet(f""" + QToolButton {{ + border: 1px solid transparent; + border-radius: 3px; + background-color: transparent; + font-size: 11px; + color: {colors['text']}; + }} + QToolButton:hover {{ + background-color: #f0f0f0; + border: 1px solid #d0d0d0; + }} + QToolButton:pressed {{ + background-color: #e1e1e1; + border: 1px solid #c0c0c0; + }} + """) + def create_toggle_button(self, text, icon_name): """创建切换按钮 - 现代极简主义风格""" btn = QToolButton() @@ -905,111 +1012,204 @@ class WordRibbon(QFrame): btn.setFixedSize(48, 28) # 两个字符(如"居中") else: btn.setFixedSize(64, 28) # 三个字符及以上(如"左对齐"、"两端对齐") - btn.setStyleSheet(""" - QToolButton { - background-color: transparent; - border: 1px solid transparent; - border-radius: 8px; - padding: 6px 10px; - color: #4a5568; - font-size: 13px; - font-weight: 500; - } - QToolButton:hover { - background-color: #f7fafc; - border: 1px solid #e2e8f0; - } - QToolButton:pressed { - background-color: #edf2f7; - border: 1px solid #cbd5e0; - } - QToolButton:checked { - background-color: #ebf8ff; - border: 1px solid #bee3f8; - color: #3182ce; - } - """) + + # 连接主题切换信号以动态更新样式 + theme_manager.theme_changed.connect(lambda: self._update_toggle_button_style(btn)) + + # 立即应用当前主题样式 + self._update_toggle_button_style(btn) + return btn + def _update_toggle_button_style(self, btn): + """更新切换按钮样式以适配当前主题""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + btn.setStyleSheet(f""" + QToolButton {{ + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: {colors['text']}; + font-size: 13px; + font-weight: 500; + }} + QToolButton:hover {{ + background-color: {colors['surface_hover']}; + border: 1px solid {colors['border']}; + }} + QToolButton:pressed {{ + background-color: #5a5a5c; + border: 1px solid #6a6a6c; + }} + QToolButton:checked {{ + background-color: {colors['accent']}; + border: 1px solid {colors['accent']}; + color: {colors['surface']}; + }} + """) + else: + btn.setStyleSheet(f""" + QToolButton {{ + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: #4a5568; + font-size: 13px; + font-weight: 500; + }} + QToolButton:hover {{ + background-color: #f7fafc; + border: 1px solid #e2e8f0; + }} + QToolButton:pressed {{ + background-color: #edf2f7; + border: 1px solid #cbd5e0; + }} + QToolButton:checked {{ + background-color: #ebf8ff; + border: 1px solid #bee3f8; + color: #3182ce; + }} + """) + def create_color_button(self, text, icon_name): """创建颜色选择按钮 - 现代极简主义风格""" btn = QToolButton() btn.setText(text) btn.setToolButtonStyle(Qt.ToolButtonTextOnly) btn.setFixedSize(32, 28) - btn.setStyleSheet(""" - QToolButton { - background-color: transparent; - border: 1px solid transparent; - border-radius: 8px; - padding: 6px 10px; - color: #4a5568; - font-size: 13px; - font-weight: 500; - } - QToolButton:hover { - background-color: #f7fafc; - border: 1px solid #e2e8f0; - } - QToolButton:pressed { - background-color: #edf2f7; - border: 1px solid #cbd5e0; - } - """) + + # 连接主题切换信号以动态更新样式 + theme_manager.theme_changed.connect(lambda: self._update_color_button_style(btn)) + + # 立即应用当前主题样式 + self._update_color_button_style(btn) + return btn + def _update_color_button_style(self, btn): + """更新颜色按钮样式以适配当前主题""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + btn.setStyleSheet(f""" + QToolButton {{ + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: {colors['text']}; + font-size: 13px; + font-weight: 500; + }} + QToolButton:hover {{ + background-color: {colors['surface_hover']}; + border: 1px solid {colors['border']}; + }} + QToolButton:pressed {{ + background-color: #5a5a5c; + border: 1px solid #6a6a6c; + }} + """) + else: + btn.setStyleSheet(f""" + QToolButton {{ + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: #4a5568; + font-size: 13px; + font-weight: 500; + }} + QToolButton:hover {{ + background-color: #f7fafc; + border: 1px solid #e2e8f0; + }} + QToolButton:pressed {{ + background-color: #edf2f7; + border: 1px solid #cbd5e0; + }} + """) + def create_style_button(self, text): """创建样式按钮 - 现代极简主义风格""" btn = QPushButton(text) btn.setFixedSize(60, 28) - btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - border: 1px solid transparent; - border-radius: 8px; - padding: 6px 10px; - color: #4a5568; - font-size: 12px; - font-weight: 500; - text-align: center; - } - QPushButton:hover { - background-color: #f7fafc; - border: 1px solid #e2e8f0; - } - QPushButton:pressed { - background-color: #edf2f7; - border: 1px solid #cbd5e0; - } - """) + + # 连接主题切换信号以动态更新样式 + theme_manager.theme_changed.connect(lambda: self._update_style_button_style(btn)) + + # 立即应用当前主题样式 + self._update_style_button_style(btn) + return btn + + def _update_style_button_style(self, btn): + """更新样式按钮样式以适配当前主题""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + btn.setStyleSheet(f""" + QPushButton {{ + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: {colors['text']}; + font-size: 12px; + font-weight: 500; + text-align: center; + }} + QPushButton:hover {{ + background-color: {colors['surface_hover']}; + border: 1px solid {colors['border']}; + }} + QPushButton:pressed {{ + background-color: #5a5a5c; + border: 1px solid #6a6a6c; + }} + """) + else: + btn.setStyleSheet(f""" + QPushButton {{ + background-color: transparent; + border: 1px solid transparent; + border-radius: 8px; + padding: 6px 10px; + color: #4a5568; + font-size: 12px; + font-weight: 500; + text-align: center; + }} + QPushButton:hover {{ + background-color: #f7fafc; + border: 1px solid #e2e8f0; + }} + QPushButton:pressed {{ + background-color: #edf2f7; + border: 1px solid #cbd5e0; + }} + """) class WordStatusBar(QStatusBar): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() + # 连接主题切换信号 + theme_manager.theme_changed.connect(self.apply_theme) + # 应用初始主题 + self.apply_theme() def setup_ui(self): """设置状态栏 - 现代极简主义风格""" - self.setStyleSheet(""" - QStatusBar { - background-color: #ffffff; - border-top: 1px solid #e2e8f0; - font-size: 12px; - color: #718096; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; - padding: 8px; - } - QStatusBar QLabel { - background-color: transparent; - padding: 4px 8px; - border-radius: 4px; - } - QStatusBar QLabel:hover { - background-color: #f7fafc; - } - """) - # 添加状态栏项目 self.page_label = QLabel("第 1 页,共 1 页") self.words_label = QLabel("字数: 0") @@ -1020,12 +1220,61 @@ class WordStatusBar(QStatusBar): self.addPermanentWidget(self.words_label) self.addPermanentWidget(self.language_label) self.addPermanentWidget(self.input_mode_label) + + def apply_theme(self): + """应用当前主题到状态栏""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + self.setStyleSheet(f""" + QStatusBar {{ + background-color: {colors['surface']}; + border-top: 1px solid {colors['border']}; + font-size: 12px; + color: {colors['text_secondary']}; + font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; + padding: 8px; + }} + QStatusBar QLabel {{ + background-color: transparent; + padding: 4px 8px; + border-radius: 4px; + color: {colors['text_secondary']}; + }} + QStatusBar QLabel:hover {{ + background-color: {colors['surface_hover']}; + }} + """) + else: + self.setStyleSheet(""" + QStatusBar { + background-color: #ffffff; + border-top: 1px solid #e2e8f0; + font-size: 12px; + color: #718096; + font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; + padding: 8px; + } + QStatusBar QLabel { + background-color: transparent; + padding: 4px 8px; + border-radius: 4px; + } + QStatusBar QLabel:hover { + background-color: #f7fafc; + } + """) class WordTextEdit(QTextEdit): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() self.input_processor = None # 输入处理器引用 + # 连接主题切换信号 + theme_manager.theme_changed.connect(self.apply_theme) + # 应用初始主题 + self.apply_theme() def set_input_processor(self, processor): """设置输入处理器""" @@ -1106,23 +1355,6 @@ class WordTextEdit(QTextEdit): def setup_ui(self): """设置文本编辑区域样式 - 现代极简主义风格""" - self.setStyleSheet(""" - QTextEdit { - background-color: #ffffff; - border: none; - font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; - font-size: 15px; - color: #2d3748; - padding: 40px; - line-height: 1.7; - selection-background-color: rgba(66, 153, 225, 0.2); - selection-color: #2d3748; - } - QTextEdit:focus { - outline: none; - } - """) - # 设置页面边距和背景 self.setViewportMargins(50, 50, 50, 50) @@ -1136,6 +1368,46 @@ class WordTextEdit(QTextEdit): # 设置光标宽度 self.setCursorWidth(2) + + def apply_theme(self): + """应用当前主题到文本编辑区域""" + is_dark = theme_manager.is_dark_theme() + colors = theme_manager.get_current_theme_colors() + + if is_dark: + self.setStyleSheet(f""" + QTextEdit {{ + background-color: {colors['surface']}; + border: none; + font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 15px; + color: {colors['text']}; + padding: 40px; + line-height: 1.7; + selection-background-color: rgba(66, 153, 225, 0.3); + selection-color: {colors['text']}; + }} + QTextEdit:focus {{ + outline: none; + }} + """) + else: + self.setStyleSheet(""" + QTextEdit { + background-color: #ffffff; + border: none; + font-family: 'Inter', 'SF Pro Display', 'Microsoft YaHei', '微软雅黑', sans-serif; + font-size: 15px; + color: #2d3748; + padding: 40px; + line-height: 1.7; + selection-background-color: rgba(66, 153, 225, 0.2); + selection-color: #2d3748; + } + QTextEdit:focus { + outline: none; + } + """) class WeatherAPI: def __init__(self): diff --git a/src/word_main_window.py b/src/word_main_window.py index b3f72e1..0b21977 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -136,9 +136,8 @@ class WordStyleMainWindow(QMainWindow): # 连接主题切换信号 theme_manager.theme_changed.connect(self.on_theme_changed) - # 设置默认为白色模式(禁用自动检测) - theme_manager.enable_auto_detection(False) - theme_manager.set_dark_theme(False) + # 启用系统主题自动检测 + theme_manager.enable_auto_detection(True) # 应用当前主题 self.apply_theme() -- 2.34.1