final #123

Merged
p9o3yklam merged 10 commits from maziang into main 3 months ago

@ -0,0 +1,235 @@
# MagicWord - MarkText风格编辑器
基于MarkText开源项目的现代化Markdown编辑器集成了MagicWord的所有核心功能。
## 🎯 功能特性
### 核心编辑功能
- **现代化界面**: 基于MarkText的设计风格简洁优雅
- **多标签编辑**: 支持同时编辑多个文档
- **Markdown支持**: 原生支持Markdown语法高亮和预览
- **文件操作**: 支持新建、打开、保存、另存为等操作
- **主题切换**: 支持浅色/深色主题
### 学习模式集成
- **打字学习**: 集成MagicWord的打字学习功能
- **进度跟踪**: 实时显示学习进度
- **多格式支持**: 支持TXT、DOCX、PDF等格式导入学习
- **智能显示**: 根据打字进度逐步显示内容
### 实用工具
- **天气信息**: 实时显示天气状况
- **每日名言**: 显示励志名言和诗句
- **字数统计**: 实时统计文档字数和字符数
- **文件管理**: 侧边栏显示最近文件
### 高级功能
- **拖放支持**: 支持拖拽文件到编辑器
- **快捷键**: 完整的快捷键支持
- **自动保存**: 定期自动保存文档
- **错误恢复**: 异常关闭时的文档恢复
## 🚀 快速开始
### 方式一:直接运行启动器
```bash
python start_marktext.py
```
### 方式二:通过主程序启动
```bash
python src/main.py
```
### 方式三:运行编辑器模块
```bash
python src/marktext_editor_window.py
```
## 📋 使用说明
### 基本操作
1. **新建文档**: 点击"新建"按钮或使用快捷键 `Ctrl+N`
2. **打开文档**: 点击"打开"按钮或使用快捷键 `Ctrl+O`
3. **保存文档**: 点击"保存"按钮或使用快捷键 `Ctrl+S`
4. **另存为**: 使用快捷键 `Ctrl+Shift+S`
### 模式切换
- **编辑模式**: 默认模式,自由编辑文档
- **学习模式**: 切换到打字学习模式,支持多格式文件导入
### 文件导入学习
1. 点击"导入文件"按钮
2. 选择TXT、DOCX或PDF文件
3. 自动切换到学习模式
4. 开始打字学习,内容会逐步显示
### 天气和名言
- **自动更新**: 每10分钟自动获取最新天气和名言
- **手动刷新**: 在工具菜单中可以手动刷新
- **信息显示**: 底部状态栏实时显示
## 🎨 界面介绍
```
┌─────────────────────────────────────────────────────────────┐
│ 文件 编辑 模式 工具 主题 │
├─────────────────────────────────────────────────────────────┤
│ [新建][打开][保存][撤销][重做][剪切][复制][粘贴][学习模式] │
├─────────────┬───────────────────────────────────────────────┤
│ │ │
│ 侧边栏 │ 编辑器区域 │
│ 文件操作 │ (多标签) │
│ 学习模式 │ │
│ 实用工具 │ │
│ │ │
├─────────────┴───────────────────────────────────────────────┤
│ 天气: 北京 25°C 晴天 | 名言: 励志内容... | 字数: 1234 │
└─────────────────────────────────────────────────────────────┘
```
## ⌨️ 快捷键
| 快捷键 | 功能 |
|--------|------|
| Ctrl+N | 新建文档 |
| Ctrl+O | 打开文档 |
| Ctrl+S | 保存文档 |
| Ctrl+Shift+S | 另存为 |
| Ctrl+Z | 撤销 |
| Ctrl+Y | 重做 |
| Ctrl+X | 剪切 |
| Ctrl+C | 复制 |
| Ctrl+V | 粘贴 |
| Ctrl+Q | 退出应用 |
## 🔧 配置说明
### 环境变量
- `QT_PLUGIN_PATH`: Qt插件路径自动设置
- `QT_QPA_PLATFORM`: 平台类型(自动设置)
### 配置文件
- 主题设置:自动保存用户偏好
- 窗口状态:记住窗口大小和位置
- 最近文件:自动记录最近打开的文档
## 📁 项目结构
```
MagicWord/
├── src/
│ ├── marktext_editor_window.py # MarkText主窗口
│ ├── word_main_window.py # Word风格主窗口
│ ├── main.py # 主程序入口
│ ├── services/ # 服务模块
│ │ ├── network_service.py # 网络服务(天气、名言)
│ │ └── file_service.py # 文件服务
│ ├── ui/ # UI组件
│ │ ├── theme_manager.py # 主题管理
│ │ └── components/ # UI组件
│ └── learning_mode_window.py # 学习模式窗口
├── start_marktext.py # MarkText启动器
├── MARKTEXT_README.md # 本文档
└── requirements.txt # 依赖列表
```
## 🛠️ 开发说明
### 基于MarkText架构
本编辑器基于MarkText的开源项目架构结合MagicWord的功能需求进行定制开发
1. **模块化设计**: 采用组件化架构,便于扩展和维护
2. **信号槽机制**: 使用PyQt的信号槽机制实现组件间通信
3. **主题系统**: 集成MagicWord的主题管理器支持动态主题切换
4. **服务集成**: 整合现有的网络服务、文件服务等
### 核心类说明
#### MarkTextMainWindow
主窗口类,负责:
- UI布局管理
- 菜单和工具栏
- 多标签编辑器管理
- 模式切换
- 状态栏信息更新
#### MarkTextEditor
编辑器组件,负责:
- 文本编辑功能
- 文件加载和保存
- 内容变化通知
- 语法高亮(可扩展)
#### MarkTextSideBar
侧边栏组件,负责:
- 文件操作按钮
- 学习模式控制
- 实用工具集成
### 扩展开发
#### 添加新的编辑器功能
```python
class CustomEditor(MarkTextEditor):
def __init__(self, parent=None):
super().__init__(parent)
# 添加自定义功能
def custom_function(self):
# 实现自定义功能
pass
```
#### 添加新的侧边栏工具
```python
class CustomSideBar(MarkTextSideBar):
def __init__(self, parent=None):
super().__init__(parent)
# 添加自定义工具按钮
def add_custom_tool(self):
# 添加自定义工具
pass
```
## 🔍 故障排除
### 常见问题
1. **Qt插件路径错误**
- 检查PyQt5是否正确安装
- 运行启动器脚本自动设置路径
2. **依赖包缺失**
- 运行 `pip install -r requirements.txt`
- 检查Python版本兼容性
3. **网络功能异常**
- 检查网络连接
- 确认API服务可用性
4. **文件导入失败**
- 检查文件格式支持
- 确认文件权限
### 调试模式
运行应用时添加调试参数:
```bash
python start_marktext.py --debug
```
## 📞 支持
如有问题,请:
1. 检查本README的故障排除部分
2. 查看控制台错误信息
3. 提交Issue到项目仓库
## 📄 许可证
本项目基于MarkText开源项目遵循相应的开源协议。
---
**享受现代化的Markdown编辑体验** 🎉

@ -516,7 +516,7 @@ class LearningModeWindow(QMainWindow):
# 只在用户新输入的字符上同步到打字模式
if self.parent_window and hasattr(self.parent_window, 'text_edit'):
if self.parent_window:
# 获取用户这一轮新输入的字符(与上一轮相比的新内容)
if old_position < self.current_position:
new_input = expected_text[old_position:self.current_position]

@ -0,0 +1,153 @@
# main.py
import sys
import traceback
import os
import platform
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
# 设置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环境下的路径
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'),
])
elif system == "Darwin": # macOS
# 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环境下的路径
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'
print(f"✅ Qt插件路径设置成功: {valid_path}")
return True
else:
print("⚠️ 警告未找到Qt插件路径")
return False
# 设置Qt平台插件路径
set_qt_plugin_path()
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from word_main_window import WordStyleMainWindow
# 设置高DPI支持必须在QApplication创建之前
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
def main():
"""应用程序入口函数 - Word风格版本"""
try:
# 创建QApplication实例
app = QApplication(sys.argv)
# 在macOS上使用系统原生样式在其他平台上使用WindowsVista样式
if platform.system() != "Darwin": # 不是macOS系统
# 设置应用程序样式为Windows风格更接近Word界面
app.setStyle('WindowsVista')
# 设置应用程序属性
app.setApplicationName("MagicWord")
app.setApplicationVersion("1.0.0")
app.setOrganizationName("MagicWord")
# 设置窗口图标(如果存在)
icon_files = [
'app_icon_32*32.png',
'app_icon_64*64.png',
'app_icon_128*128.png',
'app_icon_256*256.png',
'app_icon.png'
]
icon_path = None
for icon_file in icon_files:
test_path = os.path.join(project_root, 'resources', 'icons', icon_file)
if os.path.exists(test_path):
icon_path = test_path
break
if icon_path and os.path.exists(icon_path):
app.setWindowIcon(QIcon(icon_path))
else:
# 使用默认图标
app.setWindowIcon(QIcon())
# 创建MarkText风格的主窗口基于MarkText架构
try:
from main import MarkTextMainWindow
main_window = MarkTextMainWindow()
print("✅ 已启动MarkText风格编辑器")
except ImportError as e:
print(f"⚠️ MarkText编辑器导入失败: {e}回退到Word风格")
# 回退到Word风格的主窗口
main_window = WordStyleMainWindow()
main_window.show()
# 启动事件循环并返回退出码
exit_code = app.exec_()
sys.exit(exit_code)
except Exception as e:
# 打印详细的错误信息
print(f"应用程序发生未捕获的异常: {e}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

@ -2,6 +2,7 @@
import requests
import json
import os
import time
from typing import Optional, Dict, Any
class NetworkService:
@ -11,138 +12,354 @@ class NetworkService:
self.api_key = None
self.cache = {}
self.session = requests.Session()
# 天气缓存相关属性
self._cached_weather_data = None # 缓存的天气数据
self._cached_location = None # 缓存的定位信息
self._weather_cache_timestamp = None # 缓存时间戳
def get_weather_info(self) -> Optional[Dict[str, Any]]:
def get_cached_weather_data(self):
"""获取缓存的天气数据"""
return self._cached_weather_data
def get_cached_location(self):
"""获取缓存的定位信息"""
return self._cached_location
def set_weather_cache(self, weather_data, location):
"""设置天气缓存"""
self._cached_weather_data = weather_data
self._cached_location = location
self._weather_cache_timestamp = time.time()
def clear_weather_cache(self):
"""清除天气缓存"""
self._cached_weather_data = None
self._cached_location = None
self._weather_cache_timestamp = None
def is_weather_cache_valid(self):
"""检查天气缓存是否有效30分钟内"""
if self._weather_cache_timestamp is None:
return False
return (time.time() - self._weather_cache_timestamp) < 1800 # 30分钟
def get_user_ip(self):
"""获取用户IP地址 - 使用多个备用服务"""
# 首先尝试获取本地IP
try:
import socket
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
if local_ip and not local_ip.startswith("127."):
print(f"获取到本地IP: {local_ip}")
return local_ip
except Exception as e:
print(f"获取本地IP失败: {e}")
# 如果本地IP获取失败使用备用外部服务
ip_services = [
"https://httpbin.org/ip",
"https://api.ipify.org?format=json",
"https://ipapi.co/json/"
]
for service in ip_services:
try:
print(f"尝试从 {service} 获取IP地址...")
response = self.session.get(service, timeout=3, verify=False)
if response.status_code == 200:
data = response.json()
ip = data.get("origin") or data.get("ip") or data.get("ip_address")
if ip:
print(f"成功从 {service} 获取IP: {ip}")
return ip
except Exception as e:
print(f"{service} 获取IP失败: {e}")
continue
print("所有IP获取服务都失败了使用默认IP")
return "8.8.8.8" # 使用Google DNS作为默认IP
def get_default_weather(self):
"""获取默认天气数据"""
return {
"city": "北京",
"temperature": 20,
"description": "晴天",
"humidity": 60,
"wind_speed": 3.5
}
def clear_weather_cache(self):
"""清除天气缓存"""
self._cached_weather_data = None
self._cached_location = None
self._weather_cache_time = None
print("天气缓存已清除")
def get_weather_info(self, use_cache: bool = True) -> Optional[Dict[str, Any]]:
"""获取天气信息,支持缓存机制"""
# 如果启用缓存且缓存有效,直接返回缓存数据
if use_cache and self.is_weather_cache_valid():
print("使用缓存的天气数据")
return self._cached_weather_data
# 如果没有指定城市,使用自动定位
return self.get_weather_info_by_city(None, use_cache)
def get_weather_info_by_city(self, city_name: Optional[str] = None, use_cache: bool = True) -> Optional[Dict[str, Any]]:
"""根据城市名获取天气信息"""
# 如果启用缓存且缓存有效,直接返回缓存数据
if use_cache and self.is_weather_cache_valid() and not city_name:
print("使用缓存的天气数据")
return self._cached_weather_data
# 实现天气信息获取逻辑
# 1. 获取用户IP地址
try:
ip_response = self.session.get("https://httpbin.org/ip", timeout=5, verify=False)
ip_data = ip_response.json()
ip = ip_data.get("origin", "")
# 如果指定了城市名直接使用该城市否则通过IP定位
if city_name:
print(f"使用指定城市: {city_name}")
city = city_name
# 清除缓存以确保获取新数据
if hasattr(self, 'clear_weather_cache'):
self.clear_weather_cache()
else:
# 1. 获取用户IP地址 - 使用多个备用服务
print("开始获取天气信息...")
ip = self.get_user_ip()
print(f"获取到的IP地址: {ip}")
if not ip:
print("无法获取IP地址使用默认天气数据")
return self.get_default_weather()
# 2. 根据IP获取地理位置
# 注意这里使用免费的IP地理位置API实际应用中可能需要更精确的服务
location_response = self.session.get(f"http://ip-api.com/json/{ip}", timeout=5, verify=False)
location_url = f"http://ip-api.com/json/{ip}"
print(f"请求地理位置: {location_url}")
location_response = self.session.get(location_url, timeout=5, verify=False)
location_data = location_response.json()
print(f"地理位置响应: {location_data}")
if location_data.get("status") != "success":
return None
print("地理位置获取失败,使用默认天气数据")
return self.get_default_weather()
city = location_data.get("city", "Unknown")
print(f"获取到的城市: {city}")
if not city:
print("无法获取城市名称,使用默认天气数据")
return self.get_default_weather()
# 保存定位信息到缓存
self._cached_location = {
"ip": ip,
"city": city,
"country": location_data.get("country", "Unknown"),
"region": location_data.get("regionName", "Unknown")
}
# 3. 调用天气API获取天气数据
# 注意这里使用OpenWeatherMap API作为示例需要API密钥
# 在实际应用中需要设置有效的API密钥
if self.api_key:
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
weather_response = self.session.get(weather_url, timeout=5, verify=False)
weather_data = weather_response.json()
# 4. 解析并格式化数据
if weather_response.status_code == 200:
formatted_weather = {
"city": city,
"temperature": weather_data["main"]["temp"],
"description": weather_data["weather"][0]["description"],
"humidity": weather_data["main"]["humidity"],
"wind_speed": weather_data["wind"]["speed"]
}
# 5. 返回天气信息字典
return formatted_weather
else:
# 当没有API密钥时使用免费的天气API获取真实数据
# 首先尝试获取城市ID需要映射城市名到ID
city_id_map = {
"Beijing": "101010100",
"Shanghai": "101020100",
"Tianjin": "101030100",
"Chongqing": "101040100",
"Hong Kong": "101320101",
"Macau": "101330101",
# 添加中文城市名映射
"北京": "101010100",
"上海": "101020100",
"天津": "101030100",
"重庆": "101040100",
"香港": "101320101",
"澳门": "101330101",
# 添加更多主要城市
"广州": "101280101",
"深圳": "101280601",
"杭州": "101210101",
"南京": "101190101",
"成都": "101270101",
"武汉": "101200101",
"西安": "101110101",
"沈阳": "101070101",
"青岛": "101120201",
"大连": "101070201",
"苏州": "101190401",
"无锡": "101190201"
}
# 尝试映射英文城市名到ID
city_id = city_id_map.get(city)
# 3. 调用天气API获取天气数据
# 注意这里使用OpenWeatherMap API作为示例需要API密钥
# 在实际应用中需要设置有效的API密钥
if self.api_key:
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
# 如果找到城市ID使用该ID获取天气否则使用默认北京ID
if city_id:
weather_city_id = city_id
print(f"使用城市ID获取天气: {city} -> {weather_city_id}")
else:
# 对于中国主要城市,直接使用拼音映射
city_pinyin_map = {
"Beijing": "北京",
"Shanghai": "上海",
"Tianjin": "天津",
"Chongqing": "重庆",
"Guangzhou": "广州",
"Shenzhen": "深圳",
"Hangzhou": "杭州",
"Nanjing": "南京",
"Chengdu": "成都",
"Wuhan": "武汉",
"Xi\'an": "西安",
"Shenyang": "沈阳"
}
chinese_city = city_pinyin_map.get(city, city)
weather_city_id = "101010100" # 默认北京ID
print(f"使用默认城市ID获取天气: {city} -> {weather_city_id}")
# 使用免费天气API获取天气数据
try:
# 使用和风天气免费API的替代方案 - sojson天气API
weather_url = f"http://t.weather.sojson.com/api/weather/city/{weather_city_id}"
print(f"请求天气数据: {weather_url}")
weather_response = self.session.get(weather_url, timeout=5, verify=False)
print(f"天气响应状态码: {weather_response.status_code}")
weather_data = weather_response.json()
print(f"天气数据响应: {weather_data}")
# 4. 解析并格式化数据
if weather_response.status_code == 200:
if weather_data.get("status") == 200:
# 解析天气数据
current_data = weather_data.get("data", {})
wendu = current_data.get("wendu", "N/A")
shidu = current_data.get("shidu", "N/A")
forecast = current_data.get("forecast", [])
# 获取第一个预报项作为当前天气
current_weather = forecast[0] if forecast else {}
weather_type = current_weather.get("type", "")
# 获取生活指数信息
lifetips = []
if current_weather:
# 从预报数据中提取生活提示
ganmao = current_weather.get("ganmao", "")
if ganmao:
lifetips.append(f"感冒指数: {ganmao}")
# 添加其他生活指数(基于天气类型推断)
if "" in weather_type:
lifetips.append("出行建议: 记得带伞")
elif "" in weather_type:
lifetips.append("出行建议: 适合户外活动")
elif "" in weather_type:
lifetips.append("出行建议: 注意防滑保暖")
elif "" in weather_type or "" in weather_type:
lifetips.append("健康提醒: 减少户外运动")
# 温度相关建议
temp = float(wendu) if wendu != "N/A" else 20
if temp > 30:
lifetips.append("穿衣建议: 注意防暑降温")
elif temp < 5:
lifetips.append("穿衣建议: 注意保暖防寒")
elif temp < 15:
lifetips.append("穿衣建议: 适当添加衣物")
else:
lifetips.append("穿衣建议: 天气舒适")
formatted_weather = {
"city": city,
"temperature": weather_data["main"]["temp"],
"description": weather_data["weather"][0]["description"],
"humidity": weather_data["main"]["humidity"],
"wind_speed": weather_data["wind"]["speed"]
"temperature": float(wendu) if wendu != "N/A" else 20,
"description": weather_type,
"humidity": shidu.replace("%", "") if shidu != "N/A" else "60",
"wind_speed": "3.5", # 默认风速
"lifetips": lifetips # 生活提示列表
}
# 5. 返回天气信息字典
print(f"成功获取天气数据: {formatted_weather}")
# 缓存天气数据
self.set_weather_cache(formatted_weather, self._cached_location)
return formatted_weather
else:
# 当没有API密钥时使用免费的天气API获取真实数据
# 首先尝试获取城市ID需要映射城市名到ID
city_id_map = {
"Beijing": "101010100",
"Shanghai": "101020100",
"Tianjin": "101030100",
"Chongqing": "101040100",
"Hong Kong": "101320101",
"Macau": "101330101"
}
# 尝试映射英文城市名到ID
city_id = city_id_map.get(city)
# 如果找不到映射,尝试直接使用城市名
if not city_id:
# 对于中国主要城市,直接使用拼音映射
city_pinyin_map = {
"Beijing": "北京",
"Shanghai": "上海",
"Tianjin": "天津",
"Chongqing": "重庆"
}
chinese_city = city_pinyin_map.get(city, city)
else:
print(f"天气API返回错误状态: {weather_data.get('status')}")
# 使用免费天气API
try:
# 使用和风天气免费API的替代方案 - sojson天气API
weather_url = f"http://t.weather.sojson.com/api/weather/city/101010100" # 默认北京
weather_response = self.session.get(weather_url, timeout=5, verify=False)
weather_data = weather_response.json()
if weather_data.get("status") == 200:
# 解析天气数据
current_data = weather_data.get("data", {})
wendu = current_data.get("wendu", "N/A")
shidu = current_data.get("shidu", "N/A")
forecast = current_data.get("forecast", [])
# 获取第一个预报项作为当前天气
current_weather = forecast[0] if forecast else {}
weather_type = current_weather.get("type", "")
formatted_weather = {
"city": city,
"temperature": float(wendu) if wendu != "N/A" else 20,
"description": weather_type,
"humidity": shidu.replace("%", "") if shidu != "N/A" else "60",
"wind_speed": "3.5" # 默认风速
}
return formatted_weather
except Exception as e:
print(f"获取免费天气数据时出错: {e}")
# 如果以上都失败,返回默认数据
return {
"city": city,
"temperature": 20,
"description": "晴天",
"humidity": 60,
"wind_speed": 3.5
}
except Exception as e:
print(f"获取天气信息时出错: {e}")
return None
except Exception as e:
print(f"获取免费天气数据时出错: {e}")
# 如果以上都失败,返回默认数据
default_weather = {
"city": city,
"temperature": 20,
"description": "晴天",
"humidity": 60,
"wind_speed": 3.5,
"lifetips": [
"穿衣建议: 天气舒适",
"出行建议: 适合户外活动",
"健康提醒: 保持良好心情"
]
}
print(f"使用默认天气数据 for {city}")
# 缓存默认天气数据
self.set_weather_cache(default_weather, self._cached_location)
return default_weather
def get_daily_quote(self) -> Optional[str]:
# 实现每日一句获取逻辑
# 1. 调用名言API
# 实现每日一句获取逻辑 - 使用古诗词API
try:
# 使用一个免费的名言API禁用SSL验证以避免证书问题
response = self.session.get("https://api.quotable.io/random", timeout=5, verify=False)
# 使用古诗词·一言API - 每次返回随机不同的诗词
response = self.session.get("https://v1.jinrishici.com/all.json", timeout=5, verify=False)
# 2. 解析返回的名言数据
# 2. 解析返回的古诗词数据
if response.status_code == 200:
quote_data = response.json()
content = quote_data.get("content", "")
author = quote_data.get("author", "")
poetry_data = response.json()
content = poetry_data.get('content', '')
author = poetry_data.get('author', '')
title = poetry_data.get('origin', '')
# 3. 格式化名言文本
formatted_quote = f'"{content}" - {author}'
# 3. 格式化古诗词文本
if content and author and title:
formatted_poetry = f"{content}{author}{title}"
elif content and author:
formatted_poetry = f"{content}{author}"
elif content:
formatted_poetry = content
else:
formatted_poetry = "暂无古诗词"
# 4. 返回名言字符串
return formatted_quote
# 4. 返回古诗词字符串
return formatted_poetry
else:
# 如果API调用失败返回默认名言
return "书山有路勤为径,学海无涯苦作舟。"
# 如果API调用失败返回默认古诗词
return "山重水复疑无路,柳暗花明又一村"
except Exception as e:
print(f"获取每日一句时出错: {e}")
# 出错时返回默认名言
return "书山有路勤为径,学海无涯苦作舟"
print(f"获取古诗词时出错: {e}")
# 出错时返回默认古诗词
return "山重水复疑无路,柳暗花明又一村"
def download_image(self, url: str) -> Optional[bytes]:

@ -333,6 +333,16 @@ class WordRibbon(QFrame):
}}
""")
# 更新名言标签样式
if hasattr(self, 'quote_label') and self.quote_label is not None:
self.quote_label.setStyleSheet(f"""
QLabel {{
color: {colors['text_secondary']};
font-style: italic;
font-size: 12px;
}}
""")
# 更新下拉框样式
self.update_combo_styles(is_dark)
@ -830,10 +840,10 @@ class WordRibbon(QFrame):
# 每日一言显示标签 - 增大尺寸
self.quote_label = QLabel("暂无")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; font-size: 10px; }")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; font-size: 12px; }")
self.quote_label.setWordWrap(True)
self.quote_label.setFixedWidth(250) # 增加到250像素宽度
self.quote_label.setMinimumHeight(40) # 设置最小高度,增加显示空间
self.quote_label.setFixedWidth(400) # 增加到400像素宽度
self.quote_label.setMinimumHeight(60) # 设置最小高度,增加显示空间
# 添加到主布局
quote_layout.addLayout(top_row_layout)

@ -168,6 +168,10 @@ class WordStyleMainWindow(QMainWindow):
# 初始化时刷新天气
self.refresh_weather()
# 初始化天气缓存相关属性
self.cached_weather_data = None
self.cached_location = None
def init_theme(self):
"""初始化主题"""
@ -933,6 +937,9 @@ class WordStyleMainWindow(QMainWindow):
self.quote_thread = QuoteFetchThread()
self.quote_thread.quote_fetched.connect(self.update_quote_display)
self.quote_thread.start()
# 获取初始天气数据并缓存
self.init_weather_data()
def init_typing_logic(self):
@ -1777,6 +1784,48 @@ class WordStyleMainWindow(QMainWindow):
self.current_weather_data = weather_data
print(f"update_weather_display - 存储的current_weather_data包含life_tips: {self.current_weather_data.get('life_tips', [])}")
def init_weather_data(self):
"""初始化天气数据,使用缓存机制"""
try:
print("初始化天气数据,使用缓存机制")
# 尝试从网络服务获取缓存的天气数据
cached_weather = self.network_service.get_cached_weather_data()
cached_location = self.network_service.get_cached_location()
if cached_weather and cached_location:
print(f"使用缓存的天气数据: {cached_weather}")
print(f"使用缓存的定位数据: {cached_location}")
# 格式化缓存数据
formatted_data = {
'city': cached_weather.get('city', cached_location.get('city', '未知城市')),
'current': cached_weather.get('current', {}),
'forecast': cached_weather.get('forecast', []),
'life_tips': cached_weather.get('life_tips', [])
}
# 更新显示
self.update_weather_display(formatted_data)
# 同步更新天气悬浮窗口
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
self.weather_floating_widget.update_weather(formatted_data)
# 保存到本地缓存
self.cached_weather_data = cached_weather
self.cached_location = cached_location
self.status_bar.showMessage("使用缓存天气数据", 2000)
else:
print("没有缓存数据,使用网络获取")
# 没有缓存数据,使用网络获取
self.refresh_weather()
except Exception as e:
print(f"初始化天气数据失败: {e}")
# 如果缓存获取失败,回退到网络获取
self.refresh_weather()
def refresh_weather(self):
"""手动刷新天气信息"""
try:
@ -1796,10 +1845,10 @@ class WordStyleMainWindow(QMainWindow):
print(f"刷新天气 - 当前选择的城市: {current_city}")
if current_city == '自动定位':
# 使用自动定位
# 使用自动定位,启用缓存
weather_data = self.weather_api.get_weather_data()
else:
# 使用选中的城市
# 使用选中的城市,启用缓存
weather_data = self.weather_api.get_weather_data(current_city)
if weather_data:
@ -1816,6 +1865,12 @@ class WordStyleMainWindow(QMainWindow):
# 同步更新天气悬浮窗口
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
self.weather_floating_widget.update_weather(formatted_data)
# 保存到网络服务缓存和本地缓存
self.network_service.set_weather_cache(weather_data, self.network_service.get_cached_location())
self.cached_weather_data = weather_data
self.cached_location = self.network_service.get_cached_location()
self.status_bar.showMessage("天气数据已刷新", 2000)
else:
self.status_bar.showMessage("天气数据刷新失败请检查API密钥", 3000)
@ -1829,14 +1884,17 @@ class WordStyleMainWindow(QMainWindow):
"""显示详细天气信息对话框"""
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit
# 检查是否有天气数据
if not hasattr(self, 'current_weather_data') or not self.current_weather_data:
# 首先尝试使用本地缓存的天气数据
if hasattr(self, 'cached_weather_data') and self.cached_weather_data:
weather_data = self.cached_weather_data
print(f"详细天气对话框 - 使用本地缓存天气数据: {weather_data}")
elif hasattr(self, 'current_weather_data') and self.current_weather_data:
weather_data = self.current_weather_data
print(f"详细天气对话框 - 使用当前天气数据: {weather_data}")
else:
QMessageBox.information(self, "附加工具", "暂无天气数据,请先刷新天气信息")
return
weather_data = self.current_weather_data
print(f"详细天气对话框 - 天气数据: {weather_data}")
# 创建对话框
dialog = QDialog(self)
dialog.setWindowTitle("详细天气")

@ -0,0 +1,169 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord - MarkText风格启动器
基于MarkText开源编辑器的现代化Markdown编辑器
集成MagicWord现有功能学习模式天气名言等
"""
import sys
import os
import platform
import traceback
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.abspath(__file__))
src_path = os.path.join(project_root, 'src')
sys.path.insert(0, project_root)
sys.path.insert(0, src_path)
# 设置Qt平台插件路径
def setup_qt_environment():
"""设置Qt环境变量"""
system = platform.system()
python_version = f"python{sys.version_info.major}.{sys.version_info.minor}"
possible_paths = []
if system == "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'),
])
elif system == "Darwin": # 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',
'/opt/homebrew/opt/qt5/plugins',
])
elif system == "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',
])
for path in possible_paths:
if os.path.exists(path) and os.path.exists(os.path.join(path, 'platforms')):
os.environ['QT_PLUGIN_PATH'] = path
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(path, 'platforms')
if system == "Darwin":
os.environ['QT_QPA_PLATFORM'] = 'cocoa'
os.environ['QT_MAC_WANTS_LAYER'] = '1'
elif system == "Windows":
os.environ['QT_QPA_PLATFORM'] = 'windows'
elif system == "Linux":
os.environ['QT_QPA_PLATFORM'] = 'xcb'
print(f"✅ Qt插件路径设置成功: {path}")
return True
print("⚠️ 警告未找到Qt插件路径")
return False
# 检查依赖
def check_dependencies():
"""检查必要的依赖"""
missing_deps = []
try:
import PyQt5
except ImportError:
missing_deps.append("PyQt5")
try:
import requests
except ImportError:
missing_deps.append("requests")
try:
import docx
except ImportError:
missing_deps.append("python-docx")
try:
import fitz # PyMuPDF
except ImportError:
missing_deps.append("PyMuPDF")
if missing_deps:
print(f"❌ 缺少依赖包: {', '.join(missing_deps)}")
print("请运行: pip install " + " ".join(missing_deps))
return False
return True
def main():
"""主函数"""
try:
print("🚀 启动MagicWord - MarkText风格编辑器")
# 检查依赖
if not check_dependencies():
sys.exit(1)
# 设置Qt环境
setup_qt_environment()
# 导入Qt相关模块
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
# 设置高DPI支持
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# 创建应用
app = QApplication(sys.argv)
app.setApplicationName("MagicWord")
app.setApplicationVersion("1.0.0")
app.setOrganizationName("MagicWord")
# 设置样式
if platform.system() != "Darwin":
app.setStyle('Fusion')
# 设置图标
icon_path = os.path.join(project_root, 'resources', 'icons', 'app_icon.png')
if os.path.exists(icon_path):
app.setWindowIcon(QIcon(icon_path))
# 导入并创建MarkText主窗口
try:
from main import MarkTextMainWindow
main_window = MarkTextMainWindow()
print("✅ MarkText编辑器启动成功")
except ImportError as e:
print(f"❌ MarkText编辑器导入失败: {e}")
print("正在尝试启动Word风格编辑器...")
try:
from word_main_window import WordStyleMainWindow
main_window = WordStyleMainWindow()
print("✅ Word风格编辑器启动成功")
except ImportError as e2:
print(f"❌ Word风格编辑器也启动失败: {e2}")
sys.exit(1)
# 显示窗口
main_window.show()
# 运行应用
exit_code = app.exec_()
print(f"👋 应用已退出,退出码: {exit_code}")
sys.exit(exit_code)
except KeyboardInterrupt:
print("\n👋 用户中断,正在退出...")
sys.exit(0)
except Exception as e:
print(f"❌ 发生未预期的错误: {e}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
简化测试脚本 - 验证学习模式内容同步功能
"""
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
from PyQt5.QtWidgets import QApplication, QTextEdit, QVBoxLayout, QWidget
from learning_mode_window import LearningModeWindow
class TestWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("测试主窗口")
self.setGeometry(100, 100, 600, 400)
# 创建布局
layout = QVBoxLayout()
# 创建文本编辑器
self.text_edit = QTextEdit()
self.text_edit.setPlainText("初始内容\n")
layout.addWidget(self.text_edit)
self.setLayout(layout)
# 创建学习模式窗口
self.learning_window = LearningModeWindow(parent=self)
self.learning_window.imported_content = "这是一段测试内容。"
self.learning_window.current_position = 0
self.learning_window.show()
# 连接内容同步信号
self.learning_window.content_changed.connect(self.on_content_changed)
print("测试开始...")
print(f"主窗口内容: {repr(self.text_edit.toPlainText())}")
print(f"学习窗口内容: {repr(self.learning_window.imported_content)}")
print(f"学习窗口当前位置: {self.learning_window.current_position}")
# 模拟用户输入正确内容
print("\n模拟用户输入正确内容...")
self.learning_window.current_position = 3 # 用户输入了3个字符
self.learning_window.on_text_changed() # 调用文本变化处理
def on_content_changed(self, new_content, position):
"""内容同步回调"""
print(f"收到内容同步信号: new_content={repr(new_content)}, position={position}")
# 在文本编辑器末尾添加新内容
current_text = self.text_edit.toPlainText()
self.text_edit.setPlainText(current_text + new_content)
print(f"主窗口更新后内容: {repr(self.text_edit.toPlainText())}")
def test_content_sync():
"""测试内容同步功能"""
app = QApplication(sys.argv)
test_window = TestWindow()
test_window.show()
print("\n测试完成!")
# 运行应用
sys.exit(app.exec_())
if __name__ == "__main__":
test_content_sync()

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
测试天气生活提示功能
"""
import sys
import os
# 添加src目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
from services.network_service import NetworkService
def test_weather_with_lifetips():
"""测试包含生活提示的天气功能"""
print("🌤 测试天气生活提示功能")
print("=" * 50)
# 创建网络服务实例
network_service = NetworkService()
# 获取天气信息
print("正在获取天气信息...")
weather_info = network_service.get_weather_info()
if weather_info:
print(f"✅ 成功获取天气数据:")
print(f"城市: {weather_info['city']}")
print(f"温度: {weather_info['temperature']}°C")
print(f"天气: {weather_info['description']}")
print(f"湿度: {weather_info['humidity']}%")
print(f"风速: {weather_info['wind_speed']}m/s")
# 显示生活提示
lifetips = weather_info.get('lifetips', [])
if lifetips:
print(f"\n🌟 生活提示 ({len(lifetips)}条):")
for i, tip in enumerate(lifetips, 1):
print(f" {i}. {tip}")
else:
print("⚠️ 未获取到生活提示")
# 模拟显示详细信息格式
print(f"\n📋 详细信息显示格式:")
weather_text = f"{weather_info['city']}: {weather_info['temperature']}°C, {weather_info['description']}"
weather_text += f"\n湿度: {weather_info['humidity']}%"
weather_text += f"\n风速: {weather_info['wind_speed']}m/s"
if lifetips:
weather_text += "\n\n🌟 生活提示:"
for tip in lifetips:
weather_text += f"\n{tip}"
print(weather_text)
else:
print("❌ 获取天气信息失败")
print("\n" + "=" * 50)
print("测试完成!")
if __name__ == "__main__":
test_weather_with_lifetips()
Loading…
Cancel
Save