|
|
# 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,
|
|
|
QDialog, QLineEdit, QCheckBox, QPushButton, QListWidget,
|
|
|
QListWidgetItem, QScrollArea, QSizePolicy,
|
|
|
QGraphicsScene, QGraphicsView)
|
|
|
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, QTextBlockFormat
|
|
|
|
|
|
from ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit,
|
|
|
)
|
|
|
from services.network_service import NetworkService
|
|
|
from typing_logic import TypingLogic
|
|
|
from ui.word_style_ui import WeatherAPI
|
|
|
from file_parser import FileParser
|
|
|
from input_handler.input_processor import InputProcessor
|
|
|
from ui.calendar_widget import CalendarWidget
|
|
|
from ui.weather_floating_widget import WeatherFloatingWidget
|
|
|
from ui.quote_floating_widget import QuoteFloatingWidget
|
|
|
|
|
|
# 导入主题管理器
|
|
|
from ui.theme_manager import theme_manager
|
|
|
|
|
|
class WeatherFetchThread(QThread):
|
|
|
weather_fetched = pyqtSignal(dict)
|
|
|
|
|
|
def __init__(self):
|
|
|
super().__init__()
|
|
|
self.weather_api = WeatherAPI()
|
|
|
|
|
|
def run(self):
|
|
|
try:
|
|
|
# 使用智能定位获取天气数据,自动获取用户位置
|
|
|
weather_data = self.weather_api.get_weather_data()
|
|
|
|
|
|
if weather_data:
|
|
|
self.weather_fetched.emit(weather_data)
|
|
|
else:
|
|
|
# 使用模拟数据作为后备
|
|
|
mock_data = {
|
|
|
'city': '北京',
|
|
|
'temperature': 25,
|
|
|
'description': '晴天',
|
|
|
'humidity': 45,
|
|
|
'wind_scale': 2,
|
|
|
'forecast': []
|
|
|
}
|
|
|
self.weather_fetched.emit(mock_data)
|
|
|
except Exception as e:
|
|
|
self.weather_fetched.emit({'error': str(e)})
|
|
|
|
|
|
class QuoteFetchThread(QThread):
|
|
|
quote_fetched = pyqtSignal(dict)
|
|
|
|
|
|
def __init__(self):
|
|
|
super().__init__()
|
|
|
self.network_service = NetworkService()
|
|
|
|
|
|
def run(self):
|
|
|
try:
|
|
|
quote_data = self.network_service.get_daily_quote()
|
|
|
self.quote_fetched.emit(quote_data)
|
|
|
except Exception as e:
|
|
|
self.quote_fetched.emit({'error': str(e)})
|
|
|
|
|
|
class WordStyleMainWindow(QMainWindow):
|
|
|
def __init__(self):
|
|
|
super().__init__()
|
|
|
self.current_file_path = None
|
|
|
self.is_modified = False
|
|
|
self.typing_logic = None
|
|
|
self.is_loading_file = False # 添加文件加载标志
|
|
|
self.imported_content = "" # 存储导入的完整内容
|
|
|
self.displayed_chars = 0 # 已显示的字符数
|
|
|
self.extracted_images = [] # 存储提取的图片数据
|
|
|
self.image_list_widget = None # 图片列表控件
|
|
|
|
|
|
# 视图模式:"typing" - 打字模式,"learning" - 学习模式
|
|
|
self.view_mode = "typing" # 默认打字模式
|
|
|
|
|
|
# 初始化模式切换相关变量
|
|
|
self.typing_mode_content = "" # 打字模式下的内容
|
|
|
self.learning_progress = 0 # 学习进度
|
|
|
self.learning_text = "" # 学习模式下的文本内容
|
|
|
self.cursor_position = 0 # 光标位置
|
|
|
|
|
|
# 学习模式窗口引用和同步标记
|
|
|
self.learning_window = None # 学习模式窗口引用
|
|
|
self.sync_from_learning = False # 从学习模式同步内容的标记
|
|
|
|
|
|
# 统一文档内容管理
|
|
|
self.unified_document_content = "" # 统一文档内容
|
|
|
self.last_edit_mode = "typing" # 上次编辑模式
|
|
|
|
|
|
# 临时文件管理
|
|
|
self.temp_files = [] # 跟踪创建的临时文件
|
|
|
|
|
|
# 初始化输入处理器
|
|
|
self.input_processor = InputProcessor()
|
|
|
|
|
|
# 初始化网络服务和WeatherAPI
|
|
|
self.network_service = NetworkService()
|
|
|
self.weather_api = WeatherAPI()
|
|
|
|
|
|
# 初始化日历组件
|
|
|
self.calendar_widget = CalendarWidget(self)
|
|
|
self.calendar_widget.hide() # 默认隐藏
|
|
|
|
|
|
# 初始化天气悬浮窗口
|
|
|
self.weather_floating_widget = WeatherFloatingWidget(self)
|
|
|
self.weather_floating_widget.hide() # 默认隐藏
|
|
|
self.weather_floating_widget.closed.connect(self.on_weather_floating_closed)
|
|
|
self.weather_floating_widget.refresh_requested.connect(self.refresh_weather)
|
|
|
|
|
|
# 初始化每日谏言悬浮窗口
|
|
|
self.quote_floating_widget = QuoteFloatingWidget(self)
|
|
|
self.quote_floating_widget.hide() # 默认隐藏
|
|
|
self.quote_floating_widget.closed.connect(self.on_quote_floating_closed)
|
|
|
self.quote_floating_widget.refresh_requested.connect(self.refresh_daily_quote)
|
|
|
self.quote_floating_widget.insert_requested.connect(self.insert_quote_to_cursor)
|
|
|
|
|
|
# 设置窗口属性
|
|
|
self.setWindowTitle("文档1 - MagicWord")
|
|
|
self.setGeometry(100, 100, 1200, 800)
|
|
|
|
|
|
# 初始化主题
|
|
|
self.init_theme()
|
|
|
|
|
|
# 设置应用程序图标
|
|
|
self.set_window_icon()
|
|
|
|
|
|
# 初始化UI
|
|
|
self.setup_ui()
|
|
|
|
|
|
# 连接信号
|
|
|
self.connect_signals()
|
|
|
|
|
|
# 初始化网络服务
|
|
|
self.init_network_services()
|
|
|
|
|
|
# 初始化打字逻辑
|
|
|
self.init_typing_logic()
|
|
|
|
|
|
# 连接Ribbon的天气功能
|
|
|
self.ribbon.on_refresh_weather = self.refresh_weather
|
|
|
self.ribbon.on_city_changed = self.on_city_changed
|
|
|
|
|
|
# 初始化时刷新天气
|
|
|
self.refresh_weather()
|
|
|
|
|
|
def init_theme(self):
|
|
|
"""初始化主题"""
|
|
|
# 连接主题切换信号
|
|
|
theme_manager.theme_changed.connect(self.on_theme_changed)
|
|
|
|
|
|
# 启用系统主题自动检测
|
|
|
theme_manager.enable_auto_detection(True)
|
|
|
|
|
|
# 应用当前主题
|
|
|
self.apply_theme()
|
|
|
|
|
|
def apply_theme(self):
|
|
|
"""应用主题样式"""
|
|
|
is_dark = theme_manager.is_dark_theme()
|
|
|
|
|
|
# 应用主题样式表
|
|
|
stylesheet = theme_manager.get_theme_stylesheet(is_dark)
|
|
|
if stylesheet.strip(): # 只在有样式内容时应用
|
|
|
self.setStyleSheet(stylesheet)
|
|
|
|
|
|
# 更新组件样式
|
|
|
self.update_component_styles(is_dark)
|
|
|
|
|
|
def update_component_styles(self, is_dark):
|
|
|
"""更新组件样式"""
|
|
|
colors = theme_manager.get_current_theme_colors()
|
|
|
|
|
|
# 更新菜单栏样式 - 使用微软蓝
|
|
|
if hasattr(self, 'menubar'):
|
|
|
self.menubar.setStyleSheet("""
|
|
|
QMenuBar {
|
|
|
background-color: #0078d7;
|
|
|
border: 1px solid #005a9e;
|
|
|
font-size: 12px;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
|
|
|
QMenuBar::item {
|
|
|
background-color: transparent;
|
|
|
padding: 4px 10px;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
|
|
|
QMenuBar::item:selected {
|
|
|
background-color: #106ebe;
|
|
|
}
|
|
|
|
|
|
QMenuBar::item:pressed {
|
|
|
background-color: #005a9e;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
|
|
|
#startMenu {
|
|
|
background-color: white;
|
|
|
color: #000000;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 更新文件菜单样式
|
|
|
if hasattr(self, 'file_menu') and self.file_menu is not None:
|
|
|
self.file_menu.setStyleSheet(f"""
|
|
|
QMenu {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
font-size: 12px;
|
|
|
color: {colors['text']};
|
|
|
}}
|
|
|
|
|
|
QMenu::item {{
|
|
|
padding: 4px 20px;
|
|
|
color: {colors['text']};
|
|
|
background-color: transparent;
|
|
|
}}
|
|
|
|
|
|
QMenu::item:selected {{
|
|
|
background-color: {colors['surface_hover']};
|
|
|
}}
|
|
|
|
|
|
QMenu::item:pressed {{
|
|
|
background-color: {colors['accent']};
|
|
|
color: {colors['surface']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新视图菜单样式
|
|
|
if hasattr(self, 'view_menu') and self.view_menu is not None:
|
|
|
self.view_menu.setStyleSheet(f"""
|
|
|
QMenu {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
font-size: 12px;
|
|
|
color: {colors['text']};
|
|
|
}}
|
|
|
|
|
|
QMenu::item {{
|
|
|
padding: 4px 20px;
|
|
|
color: {colors['text']};
|
|
|
background-color: transparent;
|
|
|
}}
|
|
|
|
|
|
QMenu::item:selected {{
|
|
|
background-color: {colors['surface_hover']};
|
|
|
}}
|
|
|
|
|
|
QMenu::item:pressed {{
|
|
|
background-color: {colors['accent']};
|
|
|
color: {colors['surface']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新开始菜单样式
|
|
|
if hasattr(self, 'start_menu') and self.start_menu is not None:
|
|
|
self.start_menu.setStyleSheet(f"""
|
|
|
QMenu {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
font-size: 12px;
|
|
|
color: {colors['text']};
|
|
|
}}
|
|
|
|
|
|
QMenu::item {{
|
|
|
padding: 4px 20px;
|
|
|
color: {colors['text']};
|
|
|
background-color: transparent;
|
|
|
}}
|
|
|
|
|
|
QMenu::item:selected {{
|
|
|
background-color: {colors['surface_hover']};
|
|
|
}}
|
|
|
|
|
|
QMenu::item:pressed {{
|
|
|
background-color: {colors['accent']};
|
|
|
color: {colors['surface']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新文本编辑器样式
|
|
|
if hasattr(self, 'text_edit'):
|
|
|
self.text_edit.setStyleSheet(f"""
|
|
|
QTextEdit {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
border-radius: 0px;
|
|
|
font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif;
|
|
|
font-size: 12pt;
|
|
|
color: {colors['text']};
|
|
|
padding: 40px;
|
|
|
line-height: 1.5;
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新滚动区域样式
|
|
|
if hasattr(self, 'scroll_area'):
|
|
|
self.scroll_area.setStyleSheet(f"""
|
|
|
QScrollArea {{
|
|
|
background-color: {colors['background']};
|
|
|
border: none;
|
|
|
}}
|
|
|
QScrollArea QWidget {{
|
|
|
background-color: {colors['background']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新图片列表样式(如果已存在)
|
|
|
if hasattr(self, 'image_list_widget') and self.image_list_widget is not None:
|
|
|
self.image_list_widget.setStyleSheet(f"""
|
|
|
QListWidget {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
border-radius: 4px;
|
|
|
font-size: 11px;
|
|
|
color: {colors['text']};
|
|
|
}}
|
|
|
QListWidget::item {{
|
|
|
padding: 5px;
|
|
|
border-bottom: 1px solid {colors['border']};
|
|
|
color: {colors['text']};
|
|
|
}}
|
|
|
QListWidget::item:selected {{
|
|
|
background-color: {colors['accent']};
|
|
|
color: {colors['surface']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新功能区下拉框样式
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
self.update_ribbon_styles(is_dark)
|
|
|
|
|
|
# 更新日历组件样式
|
|
|
if hasattr(self, 'calendar_widget') and self.calendar_widget is not None:
|
|
|
# 日历组件有自己的主题管理机制,只需触发其主题更新
|
|
|
self.calendar_widget.apply_theme()
|
|
|
|
|
|
def update_ribbon_styles(self, is_dark):
|
|
|
"""更新功能区样式"""
|
|
|
colors = theme_manager.get_current_theme_colors()
|
|
|
|
|
|
# 更新字体下拉框样式
|
|
|
if hasattr(self.ribbon, 'font_combo'):
|
|
|
self.ribbon.font_combo.setStyleSheet(f"""
|
|
|
QComboBox {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
border-radius: 2px;
|
|
|
color: {colors['text']};
|
|
|
padding: 2px 5px;
|
|
|
}}
|
|
|
|
|
|
QComboBox:hover {{
|
|
|
background-color: {colors['surface_hover']};
|
|
|
border: 1px solid {colors['accent']};
|
|
|
}}
|
|
|
|
|
|
QComboBox::drop-down {{
|
|
|
border: none;
|
|
|
width: 15px;
|
|
|
}}
|
|
|
|
|
|
QComboBox::down-arrow {{
|
|
|
image: none;
|
|
|
border-left: 3px solid transparent;
|
|
|
border-right: 3px solid transparent;
|
|
|
border-top: 5px solid {colors['text']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
# 更新字体大小下拉框样式
|
|
|
if hasattr(self.ribbon, 'size_combo'):
|
|
|
self.ribbon.size_combo.setStyleSheet(f"""
|
|
|
QComboBox {{
|
|
|
background-color: {colors['surface']};
|
|
|
border: 1px solid {colors['border']};
|
|
|
border-radius: 2px;
|
|
|
color: {colors['text']};
|
|
|
padding: 2px 5px;
|
|
|
}}
|
|
|
|
|
|
QComboBox:hover {{
|
|
|
background-color: {colors['surface_hover']};
|
|
|
border: 1px solid {colors['accent']};
|
|
|
}}
|
|
|
|
|
|
QComboBox::drop-down {{
|
|
|
border: none;
|
|
|
width: 15px;
|
|
|
}}
|
|
|
|
|
|
QComboBox::down-arrow {{
|
|
|
image: none;
|
|
|
border-left: 3px solid transparent;
|
|
|
border-right: 3px solid transparent;
|
|
|
border-top: 5px solid {colors['text']};
|
|
|
}}
|
|
|
""")
|
|
|
|
|
|
def on_theme_changed(self, is_dark):
|
|
|
"""主题切换槽函数"""
|
|
|
self.apply_theme()
|
|
|
|
|
|
def set_window_icon(self):
|
|
|
"""设置窗口图标"""
|
|
|
# 使用我们创建的Word风格图标
|
|
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
# 尝试不同的图标文件
|
|
|
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):
|
|
|
self.setWindowIcon(QIcon(icon_path))
|
|
|
else:
|
|
|
# 如果图标文件不存在,创建简单的Word风格图标
|
|
|
icon = QIcon()
|
|
|
pixmap = QPixmap(32, 32)
|
|
|
pixmap.fill(QColor("#2B579A"))
|
|
|
icon.addPixmap(pixmap)
|
|
|
self.setWindowIcon(icon)
|
|
|
|
|
|
def on_city_changed(self, city):
|
|
|
"""城市选择变化处理"""
|
|
|
print(f"城市选择变化: {city}")
|
|
|
if city == '自动定位':
|
|
|
self.refresh_weather() # 重新自动定位
|
|
|
else:
|
|
|
# 手动选择城市
|
|
|
print(f"手动选择城市: {city}")
|
|
|
weather_data = self.weather_api.get_weather_data(city)
|
|
|
if weather_data:
|
|
|
print(f"获取到天气数据: {weather_data}")
|
|
|
# 直接传递原始数据,update_weather_display会处理嵌套结构
|
|
|
self.update_weather_display(weather_data)
|
|
|
# 同步更新天气悬浮窗口
|
|
|
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
|
|
|
self.weather_floating_widget.update_weather(weather_data)
|
|
|
else:
|
|
|
print(f"无法获取城市 {city} 的天气数据")
|
|
|
# 显示错误信息到天气悬浮窗口
|
|
|
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
|
|
|
self.weather_floating_widget.update_weather({'error': '无法获取天气数据'})
|
|
|
|
|
|
def refresh_weather(self):
|
|
|
"""刷新天气"""
|
|
|
current_city = self.ribbon.city_combo.currentText()
|
|
|
print(f"当前选择的城市: {current_city}")
|
|
|
if current_city == '自动定位':
|
|
|
# 使用自动定位
|
|
|
print("使用自动定位")
|
|
|
location_info = self.weather_api.get_current_location()
|
|
|
if location_info:
|
|
|
if 'note' in location_info:
|
|
|
# 检测到特殊网络环境
|
|
|
print(f"网络环境提示: {location_info['note']}")
|
|
|
if '教育网' in location_info['note'] and location_info['city'].lower() in ['beijing', '北京', 'haidian', '海淀']:
|
|
|
print("建议:教育网环境下北京定位可能不准确,可手动选择天津")
|
|
|
# 可以选择提示用户手动选择,或者使用上次的定位
|
|
|
# 使用定位到的城市获取天气
|
|
|
actual_city = location_info.get('city', '北京')
|
|
|
weather_data = self.weather_api.get_weather_data(actual_city)
|
|
|
else:
|
|
|
# 定位失败,使用默认城市
|
|
|
weather_data = self.weather_api.get_weather_data()
|
|
|
else:
|
|
|
# 使用选中的城市
|
|
|
print(f"使用选中的城市: {current_city}")
|
|
|
weather_data = self.weather_api.get_weather_data(current_city)
|
|
|
|
|
|
if weather_data:
|
|
|
print(f"更新天气信息: {weather_data}")
|
|
|
# 格式化数据以匹配状态栏期望的格式
|
|
|
formatted_data = {
|
|
|
'city': weather_data['city'],
|
|
|
'temperature': weather_data['current']['temp'],
|
|
|
'description': weather_data['current']['weather'],
|
|
|
'humidity': weather_data['current']['humidity'],
|
|
|
'wind_scale': weather_data['current']['wind_scale'],
|
|
|
'life_tips': weather_data.get('life_tips', [])
|
|
|
}
|
|
|
print(f"格式化后的数据: {formatted_data}")
|
|
|
self.update_weather_display(formatted_data)
|
|
|
|
|
|
def setup_ui(self):
|
|
|
"""设置Word风格的UI界面"""
|
|
|
|
|
|
# 创建菜单栏
|
|
|
self.create_menu_bar()
|
|
|
|
|
|
# 创建Ribbon功能区
|
|
|
self.ribbon = WordRibbon()
|
|
|
|
|
|
# 创建中心部件
|
|
|
central_widget = QWidget()
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
# 主布局
|
|
|
main_layout = QVBoxLayout()
|
|
|
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
main_layout.setSpacing(0)
|
|
|
|
|
|
# 添加Ribbon
|
|
|
main_layout.addWidget(self.ribbon)
|
|
|
|
|
|
# 创建文档编辑区域
|
|
|
self.create_document_area(main_layout)
|
|
|
|
|
|
# 创建状态栏
|
|
|
self.status_bar = WordStatusBar()
|
|
|
self.setStatusBar(self.status_bar)
|
|
|
|
|
|
central_widget.setLayout(main_layout)
|
|
|
|
|
|
# 设置样式
|
|
|
self.setStyleSheet("""
|
|
|
QMainWindow {
|
|
|
background-color: #f3f2f1;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 创建日历组件并添加到窗口中(默认隐藏)
|
|
|
try:
|
|
|
from ui.calendar_widget import CalendarWidget
|
|
|
self.calendar_widget = CalendarWidget(self)
|
|
|
self.calendar_widget.hide() # 默认隐藏
|
|
|
except Exception as e:
|
|
|
print(f"创建日历组件失败: {e}")
|
|
|
|
|
|
def create_menu_bar(self):
|
|
|
"""创建菜单栏"""
|
|
|
menubar = self.menuBar()
|
|
|
menubar.setNativeMenuBar(False)
|
|
|
self.menubar = menubar # 保存为实例变量以便后续样式更新
|
|
|
|
|
|
# 文件菜单
|
|
|
file_menu = menubar.addMenu('文件(F)')
|
|
|
self.file_menu = file_menu # 保存为实例变量
|
|
|
|
|
|
# 新建
|
|
|
new_action = QAction('新建(N)', self)
|
|
|
new_action.setShortcut('Ctrl+N')
|
|
|
new_action.triggered.connect(self.new_document)
|
|
|
file_menu.addAction(new_action)
|
|
|
|
|
|
# 导入文件 - 改为导入功能
|
|
|
open_action = QAction('导入文件(I)...', self)
|
|
|
open_action.setShortcut('Ctrl+O')
|
|
|
open_action.triggered.connect(self.import_file)
|
|
|
file_menu.addAction(open_action)
|
|
|
|
|
|
# 保存
|
|
|
save_action = QAction('保存(S)', self)
|
|
|
save_action.setShortcut('Ctrl+S')
|
|
|
save_action.triggered.connect(self.save_file)
|
|
|
file_menu.addAction(save_action)
|
|
|
|
|
|
# 另存为
|
|
|
save_as_action = QAction('另存为(A)...', self)
|
|
|
save_as_action.triggered.connect(self.save_as_file)
|
|
|
file_menu.addAction(save_as_action)
|
|
|
|
|
|
file_menu.addSeparator()
|
|
|
|
|
|
# 退出
|
|
|
exit_action = QAction('退出(X)', self)
|
|
|
exit_action.triggered.connect(self.close)
|
|
|
file_menu.addAction(exit_action)
|
|
|
|
|
|
# 开始菜单
|
|
|
start_menu = menubar.addMenu('开始(S)')
|
|
|
start_menu.setObjectName("startMenu")
|
|
|
self.start_menu = start_menu # 保存为实例变量
|
|
|
|
|
|
# 撤销
|
|
|
undo_action = QAction('撤销(U)', self)
|
|
|
undo_action.setShortcut('Ctrl+Z')
|
|
|
undo_action.triggered.connect(self.undo)
|
|
|
start_menu.addAction(undo_action)
|
|
|
|
|
|
# 重做
|
|
|
redo_action = QAction('重做(R)', self)
|
|
|
redo_action.setShortcut('Ctrl+Y')
|
|
|
redo_action.triggered.connect(self.redo)
|
|
|
start_menu.addAction(redo_action)
|
|
|
|
|
|
start_menu.addSeparator()
|
|
|
|
|
|
# 剪切
|
|
|
cut_action = QAction('剪切(T)', self)
|
|
|
cut_action.setShortcut('Ctrl+X')
|
|
|
cut_action.triggered.connect(self.cut)
|
|
|
start_menu.addAction(cut_action)
|
|
|
|
|
|
# 复制
|
|
|
copy_action = QAction('复制(C)', self)
|
|
|
copy_action.setShortcut('Ctrl+C')
|
|
|
copy_action.triggered.connect(self.copy)
|
|
|
start_menu.addAction(copy_action)
|
|
|
|
|
|
# 粘贴
|
|
|
paste_action = QAction('粘贴(P)', self)
|
|
|
paste_action.setShortcut('Ctrl+V')
|
|
|
paste_action.triggered.connect(self.paste)
|
|
|
start_menu.addAction(paste_action)
|
|
|
|
|
|
# 视图菜单
|
|
|
view_menu = menubar.addMenu('视图(V)')
|
|
|
self.view_menu = view_menu # 保存为实例变量
|
|
|
|
|
|
# 阅读视图
|
|
|
read_view_action = QAction('阅读视图', self)
|
|
|
read_view_action.triggered.connect(self.toggle_reading_view)
|
|
|
view_menu.addAction(read_view_action)
|
|
|
|
|
|
# 打印布局
|
|
|
print_layout_action = QAction('打印布局', self)
|
|
|
print_layout_action.setCheckable(True)
|
|
|
print_layout_action.setChecked(True)
|
|
|
print_layout_action.triggered.connect(self.toggle_print_layout)
|
|
|
view_menu.addAction(print_layout_action)
|
|
|
|
|
|
view_menu.addSeparator()
|
|
|
|
|
|
# 模式选择子菜单
|
|
|
theme_menu = view_menu.addMenu('模式')
|
|
|
|
|
|
# 白色模式
|
|
|
self.light_mode_action = QAction('白色模式', self)
|
|
|
self.light_mode_action.setCheckable(True)
|
|
|
self.light_mode_action.setChecked(not theme_manager.is_dark_theme()) # 根据当前主题设置
|
|
|
self.light_mode_action.triggered.connect(self.set_light_mode)
|
|
|
theme_menu.addAction(self.light_mode_action)
|
|
|
|
|
|
# 黑色模式
|
|
|
self.dark_mode_action = QAction('黑色模式', self)
|
|
|
self.dark_mode_action.setCheckable(True)
|
|
|
self.dark_mode_action.setChecked(theme_manager.is_dark_theme()) # 根据当前主题设置
|
|
|
self.dark_mode_action.triggered.connect(self.set_dark_mode)
|
|
|
theme_menu.addAction(self.dark_mode_action)
|
|
|
|
|
|
view_menu.addSeparator()
|
|
|
|
|
|
# 视图模式选择
|
|
|
view_mode_menu = view_menu.addMenu('视图模式')
|
|
|
|
|
|
# 打字模式
|
|
|
self.typing_mode_action = QAction('打字模式', self)
|
|
|
self.typing_mode_action.setCheckable(True)
|
|
|
self.typing_mode_action.setChecked(True) # 默认打字模式
|
|
|
self.typing_mode_action.triggered.connect(lambda: self.set_view_mode("typing"))
|
|
|
view_mode_menu.addAction(self.typing_mode_action)
|
|
|
|
|
|
# 学习模式
|
|
|
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)
|
|
|
|
|
|
view_menu.addSeparator()
|
|
|
|
|
|
# 附加工具功能
|
|
|
weather_menu = view_menu.addMenu('附加工具')
|
|
|
|
|
|
# 显示天气工具组
|
|
|
self.show_weather_tools_action = QAction('显示天气工具', self)
|
|
|
self.show_weather_tools_action.setCheckable(True)
|
|
|
self.show_weather_tools_action.setChecked(False) # 默认不显示
|
|
|
self.show_weather_tools_action.triggered.connect(self.toggle_weather_tools)
|
|
|
weather_menu.addAction(self.show_weather_tools_action)
|
|
|
|
|
|
# 显示每日一言工具组
|
|
|
self.show_quote_tools_action = QAction('显示每日一言工具', self)
|
|
|
self.show_quote_tools_action.setCheckable(True)
|
|
|
self.show_quote_tools_action.setChecked(False) # 默认不显示
|
|
|
self.show_quote_tools_action.triggered.connect(self.toggle_quote_tools)
|
|
|
weather_menu.addAction(self.show_quote_tools_action)
|
|
|
|
|
|
weather_menu.addSeparator()
|
|
|
|
|
|
# 刷新天气
|
|
|
refresh_weather_action = QAction('刷新天气', self)
|
|
|
refresh_weather_action.setShortcut('F5')
|
|
|
refresh_weather_action.triggered.connect(self.refresh_weather)
|
|
|
weather_menu.addAction(refresh_weather_action)
|
|
|
|
|
|
# 显示详细天气
|
|
|
show_weather_action = QAction('显示详细天气', self)
|
|
|
show_weather_action.triggered.connect(self.show_detailed_weather)
|
|
|
weather_menu.addAction(show_weather_action)
|
|
|
|
|
|
# 天气悬浮窗口
|
|
|
toggle_floating_weather_action = QAction('天气悬浮窗口', self)
|
|
|
toggle_floating_weather_action.triggered.connect(self.toggle_floating_weather)
|
|
|
weather_menu.addAction(toggle_floating_weather_action)
|
|
|
|
|
|
# 每日谏言悬浮窗口切换动作
|
|
|
toggle_floating_quote_action = QAction('每日谏言悬浮窗口', self)
|
|
|
toggle_floating_quote_action.triggered.connect(self.toggle_floating_quote)
|
|
|
weather_menu.addAction(toggle_floating_quote_action)
|
|
|
# 插入菜单
|
|
|
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)
|
|
|
|
|
|
# 插入天气信息功能
|
|
|
insert_weather_action = QAction('插入天气信息', self)
|
|
|
insert_weather_action.triggered.connect(self.insert_weather_info)
|
|
|
insert_menu.addAction(insert_weather_action)
|
|
|
|
|
|
# 插入每日一句名言功能
|
|
|
insert_quote_action = QAction('插入每日一句名言', self)
|
|
|
insert_quote_action.triggered.connect(self.insert_daily_quote)
|
|
|
insert_menu.addAction(insert_quote_action)
|
|
|
|
|
|
# 插入古诗词功能
|
|
|
insert_poetry_action = QAction('插入古诗词', self)
|
|
|
insert_poetry_action.triggered.connect(self.insert_chinese_poetry)
|
|
|
insert_menu.addAction(insert_poetry_action)
|
|
|
|
|
|
# 绘图菜单
|
|
|
paint_menu = menubar.addMenu('绘图(D)')
|
|
|
|
|
|
# 添加日历按钮
|
|
|
calendar_action = QAction('日历', self)
|
|
|
calendar_action.triggered.connect(self.toggle_calendar)
|
|
|
paint_menu.addAction(calendar_action)
|
|
|
|
|
|
# 设计菜单
|
|
|
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)')
|
|
|
|
|
|
# 引用菜单
|
|
|
reference_menu = menubar.addMenu('引用(R)')
|
|
|
|
|
|
# 邮件菜单
|
|
|
mail_menu = menubar.addMenu('邮件(M)')
|
|
|
|
|
|
# 审阅菜单
|
|
|
review_menu = menubar.addMenu('审阅(W)')
|
|
|
|
|
|
# 开发工具菜单
|
|
|
developer_menu = menubar.addMenu('开发工具(Q)')
|
|
|
|
|
|
# 帮助菜单
|
|
|
help_menu = menubar.addMenu('帮助(H)')
|
|
|
|
|
|
# 关于
|
|
|
about_action = QAction('关于 MagicWord', self)
|
|
|
about_action.triggered.connect(self.show_about)
|
|
|
help_menu.addAction(about_action)
|
|
|
|
|
|
|
|
|
def create_document_area(self, main_layout):
|
|
|
"""创建文档编辑区域"""
|
|
|
|
|
|
# 创建滚动区域
|
|
|
from PyQt5.QtWidgets import QScrollArea
|
|
|
|
|
|
self.scroll_area = QScrollArea() # 保存为实例变量
|
|
|
self.scroll_area.setWidgetResizable(True)
|
|
|
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
|
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
|
self.scroll_area.setStyleSheet("""
|
|
|
QScrollArea {
|
|
|
background-color: #e1e1e1;
|
|
|
border: none;
|
|
|
}
|
|
|
QScrollArea QWidget {
|
|
|
background-color: #e1e1e1;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 创建文档容器
|
|
|
document_container = QWidget()
|
|
|
document_layout = QVBoxLayout()
|
|
|
document_layout.setContentsMargins(50, 50, 50, 50)
|
|
|
|
|
|
# 创建文本编辑区域(使用Word风格的文本编辑器)
|
|
|
self.text_edit = WordTextEdit()
|
|
|
self.text_edit.setMinimumHeight(600)
|
|
|
self.text_edit.setStyleSheet("""
|
|
|
QTextEdit {
|
|
|
background-color: #ffffff;
|
|
|
border: 1px solid #d0d0d0;
|
|
|
border-radius: 0px;
|
|
|
font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif;
|
|
|
font-size: 12pt;
|
|
|
color: #000000;
|
|
|
padding: 40px;
|
|
|
line-height: 1.5;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 设置默认文档内容
|
|
|
self.text_edit.setPlainText("在此输入您的内容...")
|
|
|
|
|
|
# 连接输入处理器到文本编辑器
|
|
|
self.text_edit.set_input_processor(self.input_processor)
|
|
|
|
|
|
# 连接输入处理器的信号
|
|
|
self.input_processor.text_changed.connect(self.on_input_text_changed)
|
|
|
self.input_processor.key_pressed.connect(self.on_key_pressed)
|
|
|
|
|
|
document_layout.addWidget(self.text_edit)
|
|
|
|
|
|
# 创建图片显示区域
|
|
|
self.image_list_widget = QListWidget()
|
|
|
self.image_list_widget.setMaximumHeight(200)
|
|
|
self.image_list_widget.setStyleSheet("""
|
|
|
QListWidget {
|
|
|
background-color: #f8f8f8;
|
|
|
border: 1px solid #d0d0d0;
|
|
|
border-radius: 4px;
|
|
|
font-size: 11px;
|
|
|
}
|
|
|
QListWidget::item {
|
|
|
padding: 5px;
|
|
|
border-bottom: 1px solid #e0e0e0;
|
|
|
}
|
|
|
QListWidget::item:selected {
|
|
|
background-color: #e3f2fd;
|
|
|
color: #1976d2;
|
|
|
}
|
|
|
""")
|
|
|
self.image_list_widget.setVisible(False) # 默认隐藏
|
|
|
self.image_list_widget.itemDoubleClicked.connect(self.on_image_item_double_clicked)
|
|
|
|
|
|
document_layout.addWidget(self.image_list_widget)
|
|
|
document_container.setLayout(document_layout)
|
|
|
|
|
|
self.scroll_area.setWidget(document_container)
|
|
|
main_layout.addWidget(self.scroll_area)
|
|
|
|
|
|
def init_network_services(self):
|
|
|
"""初始化网络服务"""
|
|
|
# 获取天气信息
|
|
|
self.weather_thread = WeatherFetchThread()
|
|
|
self.weather_thread.weather_fetched.connect(self.update_weather_display)
|
|
|
self.weather_thread.start()
|
|
|
|
|
|
# 获取每日名言
|
|
|
self.quote_thread = QuoteFetchThread()
|
|
|
self.quote_thread.quote_fetched.connect(self.update_quote_display)
|
|
|
self.quote_thread.start()
|
|
|
|
|
|
def init_typing_logic(self):
|
|
|
"""初始化打字逻辑"""
|
|
|
# 使用默认内容初始化打字逻辑
|
|
|
default_content = "欢迎使用MagicWord隐私学习软件!\n\n这是一个仿Microsoft Word界面的学习工具。"
|
|
|
self.typing_logic = TypingLogic(default_content)
|
|
|
self.typing_logic.reset()
|
|
|
|
|
|
def connect_signals(self):
|
|
|
"""连接信号和槽"""
|
|
|
# 文本变化信号
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
|
|
|
# 光标位置变化信号,用于更新按钮状态
|
|
|
self.text_edit.cursorPositionChanged.connect(self.update_format_buttons)
|
|
|
|
|
|
# Ribbon按钮信号
|
|
|
# 标签栏已删除,相关代码已移除
|
|
|
|
|
|
# 字体设置信号
|
|
|
if hasattr(self.ribbon, 'font_combo'):
|
|
|
self.ribbon.font_combo.currentFontChanged.connect(self.on_font_changed)
|
|
|
self.ribbon.font_size_combo.currentTextChanged.connect(self.on_font_size_changed)
|
|
|
self.ribbon.bold_btn.clicked.connect(self.on_bold_clicked)
|
|
|
self.ribbon.italic_btn.clicked.connect(self.on_italic_clicked)
|
|
|
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)
|
|
|
if hasattr(self.ribbon, 'replace_btn'):
|
|
|
self.ribbon.replace_btn.clicked.connect(self.show_replace_dialog)
|
|
|
|
|
|
# 页面布局信号已在菜单中直接连接,无需在此重复连接
|
|
|
|
|
|
# 段落对齐按钮信号
|
|
|
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)
|
|
|
if hasattr(self.ribbon, 'refresh_weather_btn'):
|
|
|
self.ribbon.refresh_weather_btn.clicked.connect(self.refresh_weather)
|
|
|
if hasattr(self.ribbon, 'floating_weather_btn'):
|
|
|
self.ribbon.floating_weather_btn.clicked.connect(self.toggle_floating_weather)
|
|
|
if hasattr(self.ribbon, 'floating_quote_btn'):
|
|
|
self.ribbon.floating_quote_btn.clicked.connect(self.toggle_floating_quote)
|
|
|
|
|
|
# 日历组件信号
|
|
|
if hasattr(self, 'calendar_widget'):
|
|
|
self.calendar_widget.date_selected.connect(self.insert_date_to_editor)
|
|
|
|
|
|
def on_text_changed(self):
|
|
|
"""文本变化处理 - 根据视图模式处理文本变化"""
|
|
|
# 如果正在加载文件,跳过处理
|
|
|
if self.is_loading_file:
|
|
|
return
|
|
|
|
|
|
# 检查是否是从学习模式同步内容,避免递归调用
|
|
|
if hasattr(self, 'sync_from_learning') and self.sync_from_learning:
|
|
|
return
|
|
|
|
|
|
# 根据当前视图模式处理
|
|
|
if self.view_mode == "learning":
|
|
|
# 学习模式:需要导入文件才能打字
|
|
|
if not self.imported_content:
|
|
|
# 没有导入文件时,清空文本并提示
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
if current_text and current_text != "在此输入您的内容...":
|
|
|
self.text_edit.clear()
|
|
|
self.status_bar.showMessage("学习模式需要先导入文件才能开始打字", 3000)
|
|
|
return
|
|
|
|
|
|
# 学习模式下处理导入内容的逐步显示
|
|
|
self.handle_learning_mode_typing()
|
|
|
|
|
|
elif self.view_mode == "typing":
|
|
|
# 打字模式:可以自由打字,不自动处理内容
|
|
|
# 只在用户主动操作时处理,避免内容被覆盖
|
|
|
pass
|
|
|
|
|
|
# 标记文档为已修改
|
|
|
if not self.is_modified:
|
|
|
self.is_modified = True
|
|
|
self.update_window_title()
|
|
|
|
|
|
def on_input_text_changed(self, text):
|
|
|
"""输入处理器文本变化处理"""
|
|
|
if self.view_mode == "learning" and self.imported_content:
|
|
|
# 在学习模式下,根据输入处理器的状态更新显示
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
expected_text = self.imported_content[:len(text)]
|
|
|
|
|
|
# 如果文本不匹配,更新显示
|
|
|
if current_text != expected_text:
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
self.text_edit.setPlainText(expected_text)
|
|
|
# 保持光标在末尾
|
|
|
cursor.movePosition(QTextCursor.End)
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
def on_key_pressed(self, key):
|
|
|
"""按键按下处理"""
|
|
|
# 更新状态栏显示当前按键
|
|
|
if key in ['\b', '\x7f']:
|
|
|
self.status_bar.showMessage("退格键已处理", 1000)
|
|
|
else:
|
|
|
self.status_bar.showMessage(f"按键: {key}", 1000)
|
|
|
|
|
|
def handle_learning_mode_typing(self):
|
|
|
"""学习模式下的打字处理 - 从上次中断处继续显示学习文档C内容到文档A"""
|
|
|
if self.imported_content and self.typing_logic:
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
|
|
|
# 获取当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor_position = cursor.position()
|
|
|
|
|
|
# 只有在光标位于文本末尾时才显示新内容
|
|
|
if cursor_position == len(current_text):
|
|
|
# 使用输入处理器的状态来显示文本
|
|
|
input_text = self.input_processor.input_buffer
|
|
|
chars_to_show = len(input_text)
|
|
|
|
|
|
# 更新已显示字符数
|
|
|
self.displayed_chars = chars_to_show
|
|
|
|
|
|
# 保存当前学习进度,以便在模式切换时恢复
|
|
|
self.learning_progress = self.displayed_chars
|
|
|
|
|
|
# 更新打字逻辑中的进度信息
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.typed_chars = self.displayed_chars
|
|
|
self.typing_logic.current_index = self.displayed_chars
|
|
|
|
|
|
# 获取应该显示的文本部分(基于输入处理器的状态)
|
|
|
display_text = self.imported_content[:self.displayed_chars]
|
|
|
|
|
|
# 临时禁用文本变化信号,避免递归
|
|
|
self.text_edit.textChanged.disconnect(self.on_text_changed)
|
|
|
|
|
|
try:
|
|
|
# 完全重置文本内容,确保图片能正确插入
|
|
|
self.text_edit.clear()
|
|
|
self.text_edit.setPlainText(display_text)
|
|
|
|
|
|
# 重置图片插入记录,确保每次都能重新插入图片
|
|
|
if hasattr(self, 'inserted_images'):
|
|
|
self.inserted_images.clear()
|
|
|
|
|
|
# 在文本中插入图片(如果有的话)
|
|
|
self.insert_images_in_text()
|
|
|
|
|
|
# 恢复光标位置到文本末尾
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.movePosition(cursor.End)
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
# 更新打字逻辑(支持中文整词匹配)
|
|
|
if display_text:
|
|
|
result = self.typing_logic.check_input(display_text)
|
|
|
self.typing_logic.update_position(display_text)
|
|
|
|
|
|
# 错误处理(支持中文整词显示)
|
|
|
if not result['correct'] and display_text:
|
|
|
expected_char = result.get('expected', '')
|
|
|
# 如果是中文文本,显示更友好的错误信息
|
|
|
if self.typing_logic._is_chinese_char(expected_char):
|
|
|
# 获取期望的中文词组
|
|
|
expected_word = self.typing_logic._get_chinese_word_at(result['position'])
|
|
|
self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000)
|
|
|
else:
|
|
|
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
|
|
|
self.highlight_next_char(result['position'], expected_char)
|
|
|
|
|
|
# 更新统计信息
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
# 检查是否完成
|
|
|
if self.displayed_chars >= len(self.imported_content):
|
|
|
self.on_lesson_complete()
|
|
|
return
|
|
|
|
|
|
# 更新状态栏显示进度
|
|
|
progress_percentage = (self.displayed_chars / len(self.imported_content)) * 100
|
|
|
self.status_bar.showMessage(f"学习进度: {progress_percentage:.1f}% ({self.displayed_chars}/{len(self.imported_content)})", 2000)
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f"学习模式处理出错: {str(e)}")
|
|
|
import traceback
|
|
|
traceback.print_exc()
|
|
|
finally:
|
|
|
# 重新连接文本变化信号
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
self.text_edit.textChanged.disconnect(self.on_text_changed)
|
|
|
|
|
|
try:
|
|
|
# 完全重置文本内容,确保图片能正确插入
|
|
|
self.text_edit.clear()
|
|
|
self.text_edit.setPlainText(display_text)
|
|
|
|
|
|
# 重置图片插入记录,确保每次都能重新插入图片
|
|
|
if hasattr(self, 'inserted_images'):
|
|
|
self.inserted_images.clear()
|
|
|
|
|
|
# 打印调试信息
|
|
|
if hasattr(self.typing_logic, 'image_positions'):
|
|
|
print(f"当前有 {len(self.typing_logic.image_positions)} 张图片需要插入")
|
|
|
for img in self.typing_logic.image_positions:
|
|
|
print(f"图片位置: {img['start_pos']}, 文件名: {img['filename']}")
|
|
|
else:
|
|
|
print("没有图片位置信息")
|
|
|
|
|
|
# 在文本中插入图片(如果有的话)
|
|
|
self.insert_images_in_text()
|
|
|
|
|
|
# 恢复光标位置到文本末尾
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.movePosition(cursor.End)
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
# 更新打字逻辑(只检查已显示的部分)
|
|
|
if display_text:
|
|
|
result = self.typing_logic.check_input(display_text)
|
|
|
self.typing_logic.update_position(display_text)
|
|
|
|
|
|
# 错误处理
|
|
|
if not result['correct'] and display_text:
|
|
|
expected_char = result.get('expected', '')
|
|
|
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
|
|
|
self.highlight_next_char(result['position'], expected_char)
|
|
|
|
|
|
# 更新统计信息
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
# 检查是否完成
|
|
|
if self.displayed_chars >= len(self.imported_content):
|
|
|
self.on_lesson_complete()
|
|
|
return
|
|
|
|
|
|
# 更新状态栏显示进度
|
|
|
progress_percentage = (self.displayed_chars / len(self.imported_content)) * 100
|
|
|
self.status_bar.showMessage(f"逐步显示进度: {progress_percentage:.1f}% ({self.displayed_chars}/{len(self.imported_content)})", 2000)
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f"学习模式处理出错: {str(e)}")
|
|
|
import traceback
|
|
|
traceback.print_exc()
|
|
|
finally:
|
|
|
# 重新连接文本变化信号
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
|
|
|
# 如果有保存的学习进度,确保光标位置正确
|
|
|
if hasattr(self, 'learning_progress') and self.learning_progress > 0:
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.movePosition(cursor.End)
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
else:
|
|
|
# 如果光标位置没有超过显示的字符数,则正常处理打字逻辑
|
|
|
if current_text and current_text != "在此输入您的内容...": # 忽略默认文本
|
|
|
result = self.typing_logic.check_input(current_text)
|
|
|
self.typing_logic.update_position(current_text)
|
|
|
|
|
|
# 错误处理(支持中文整词显示)
|
|
|
if not result['correct']:
|
|
|
expected_char = result.get('expected', '')
|
|
|
# 如果是中文文本,显示更友好的错误信息
|
|
|
if self.typing_logic._is_chinese_char(expected_char):
|
|
|
# 获取期望的中文词组
|
|
|
expected_word = self.typing_logic._get_chinese_word_at(result['position'])
|
|
|
self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000)
|
|
|
else:
|
|
|
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
|
|
|
self.highlight_next_char(result['position'], expected_char)
|
|
|
|
|
|
# 更新统计信息
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
def handle_typing_mode_typing(self):
|
|
|
"""打字模式下的打字处理 - 允许自由输入到文档A"""
|
|
|
# 打字模式下,允许自由打字,不强制显示导入内容
|
|
|
if self.typing_logic and not self.is_loading_file:
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
if current_text and current_text != "在此输入您的内容...": # 忽略默认文本
|
|
|
result = self.typing_logic.check_input(current_text)
|
|
|
self.typing_logic.update_position(current_text)
|
|
|
|
|
|
# 错误处理(支持中文整词显示)
|
|
|
if not result['correct']:
|
|
|
expected_char = result.get('expected', '')
|
|
|
# 如果是中文文本,显示更友好的错误信息
|
|
|
if self.typing_logic._is_chinese_char(expected_char):
|
|
|
# 获取期望的中文词组
|
|
|
expected_word = self.typing_logic._get_chinese_word_at(result['position'])
|
|
|
self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000)
|
|
|
else:
|
|
|
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
|
|
|
self.highlight_next_char(result['position'], expected_char)
|
|
|
|
|
|
# 更新统计信息
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
# 保存打字内容到文档A
|
|
|
self.typing_mode_content = current_text
|
|
|
|
|
|
# 更新学习进度(用于打字模式显示)
|
|
|
if hasattr(self, 'learning_progress'):
|
|
|
self.learning_progress = len(current_text)
|
|
|
|
|
|
def on_text_changed_original(self):
|
|
|
"""文本变化处理 - 支持逐步显示模式和自由打字模式"""
|
|
|
# 如果正在加载文件,跳过处理
|
|
|
if self.is_loading_file:
|
|
|
return
|
|
|
|
|
|
# 如果有导入的内容,实现逐步显示
|
|
|
if self.imported_content and self.typing_logic:
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
|
|
|
# 计算应该显示的字符数(用户输入多少个字符就显示多少个)
|
|
|
input_length = len(current_text)
|
|
|
|
|
|
# 如果用户输入长度超过了导入内容长度,限制在导入内容长度内
|
|
|
if input_length > len(self.imported_content):
|
|
|
input_length = len(self.imported_content)
|
|
|
|
|
|
# 如果显示的字符数需要更新
|
|
|
if input_length != self.displayed_chars:
|
|
|
self.displayed_chars = input_length
|
|
|
|
|
|
# 获取应该显示的文本部分
|
|
|
display_text = self.imported_content[:self.displayed_chars]
|
|
|
|
|
|
# 临时禁用文本变化信号,避免递归
|
|
|
self.text_edit.textChanged.disconnect(self.on_text_changed)
|
|
|
|
|
|
# 保存当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
original_position = cursor.position()
|
|
|
|
|
|
# 只添加新字符,而不是重置整个文本
|
|
|
if len(display_text) > len(current_text):
|
|
|
# 需要添加新字符
|
|
|
new_chars = display_text[len(current_text):]
|
|
|
cursor.movePosition(cursor.End)
|
|
|
cursor.insertText(new_chars)
|
|
|
elif len(display_text) < len(current_text):
|
|
|
# 需要删除字符(用户按了删除键等情况)
|
|
|
cursor.setPosition(len(display_text))
|
|
|
cursor.movePosition(cursor.End, cursor.KeepAnchor)
|
|
|
cursor.removeSelectedText()
|
|
|
|
|
|
# 恢复光标位置
|
|
|
cursor.setPosition(min(original_position, len(display_text)))
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
# 在文本中插入图片(如果有的话)
|
|
|
# 注意:必须在更新文本后调用,且要处理图片插入对文本长度的影响
|
|
|
self.insert_images_in_text()
|
|
|
|
|
|
# 重新连接文本变化信号
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
|
|
|
# 更新打字逻辑(只检查已显示的部分)
|
|
|
if display_text:
|
|
|
result = self.typing_logic.check_input(display_text)
|
|
|
self.typing_logic.update_position(display_text)
|
|
|
|
|
|
# 错误处理(支持中文整词显示)
|
|
|
if not result['correct'] and display_text:
|
|
|
expected_char = result.get('expected', '')
|
|
|
# 如果是中文文本,显示更友好的错误信息
|
|
|
if self.typing_logic._is_chinese_char(expected_char):
|
|
|
# 获取期望的中文词组
|
|
|
expected_word = self.typing_logic._get_chinese_word_at(result['position'])
|
|
|
self.status_bar.showMessage(f"输入错误,期望词组: '{expected_word}'", 3000)
|
|
|
else:
|
|
|
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
|
|
|
self.highlight_next_char(result['position'], expected_char)
|
|
|
|
|
|
# 更新统计信息
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
# 检查当前位置是否有图片
|
|
|
self.check_and_show_image_at_position(self.displayed_chars)
|
|
|
|
|
|
# 在文本中插入图片(如果有的话)
|
|
|
self.insert_images_in_text()
|
|
|
|
|
|
# 检查是否完成
|
|
|
if self.displayed_chars >= len(self.imported_content):
|
|
|
self.on_lesson_complete()
|
|
|
return
|
|
|
|
|
|
# 更新状态栏显示进度
|
|
|
progress_percentage = (self.displayed_chars / len(self.imported_content)) * 100
|
|
|
self.status_bar.showMessage(f"逐步显示进度: {progress_percentage:.1f}% ({self.displayed_chars}/{len(self.imported_content)})", 2000)
|
|
|
else:
|
|
|
# 自由打字模式 - 没有导入内容时的处理
|
|
|
if self.typing_logic and not self.is_loading_file:
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
if current_text and current_text != "在此输入您的内容...": # 忽略默认文本
|
|
|
result = self.typing_logic.check_input(current_text)
|
|
|
self.typing_logic.update_position(current_text)
|
|
|
|
|
|
# 错误处理
|
|
|
if not result['correct']:
|
|
|
expected_char = result.get('expected', '')
|
|
|
self.status_bar.showMessage(f"输入错误,期望字符: '{expected_char}'", 2000)
|
|
|
self.highlight_next_char(result['position'], expected_char)
|
|
|
|
|
|
# 更新统计信息
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
# 标记文档为已修改
|
|
|
if not self.is_modified:
|
|
|
self.is_modified = True
|
|
|
self.update_window_title()
|
|
|
|
|
|
|
|
|
|
|
|
def on_font_changed(self, font):
|
|
|
"""字体更改处理"""
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
if cursor.hasSelection():
|
|
|
# 如果有选中文本,只更改选中文本的字体
|
|
|
fmt = cursor.charFormat()
|
|
|
fmt.setFontFamily(font.family())
|
|
|
cursor.setCharFormat(fmt)
|
|
|
else:
|
|
|
# 如果没有选中文本,更改整个文档的默认字体
|
|
|
self.text_edit.setFontFamily(font.family())
|
|
|
|
|
|
def on_font_size_changed(self, size):
|
|
|
"""字体大小更改处理"""
|
|
|
try:
|
|
|
font_size = int(size)
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
if cursor.hasSelection():
|
|
|
# 如果有选中文本,只更改选中文本的字体大小
|
|
|
fmt = cursor.charFormat()
|
|
|
fmt.setFontPointSize(font_size)
|
|
|
cursor.setCharFormat(fmt)
|
|
|
else:
|
|
|
# 如果没有选中文本,更改整个文档的默认字体大小
|
|
|
self.text_edit.setFontPointSize(font_size)
|
|
|
except ValueError:
|
|
|
pass # 忽略无效的字体大小
|
|
|
|
|
|
def on_bold_clicked(self, checked):
|
|
|
"""粗体按钮点击处理"""
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
if cursor.hasSelection():
|
|
|
# 如果有选中文本,只更改选中文本的粗体样式
|
|
|
fmt = cursor.charFormat()
|
|
|
fmt.setFontWeight(QFont.Bold if checked else QFont.Normal)
|
|
|
cursor.setCharFormat(fmt)
|
|
|
else:
|
|
|
# 如果没有选中文本,更改整个文档的默认粗体样式
|
|
|
self.text_edit.setFontWeight(QFont.Bold if checked else QFont.Normal)
|
|
|
|
|
|
def on_italic_clicked(self, checked):
|
|
|
"""斜体按钮点击处理"""
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
if cursor.hasSelection():
|
|
|
# 如果有选中文本,只更改选中文本的斜体样式
|
|
|
fmt = cursor.charFormat()
|
|
|
fmt.setFontItalic(checked)
|
|
|
cursor.setCharFormat(fmt)
|
|
|
else:
|
|
|
# 如果没有选中文本,更改整个文档的默认斜体样式
|
|
|
self.text_edit.setFontItalic(checked)
|
|
|
|
|
|
def on_underline_clicked(self, checked):
|
|
|
"""下划线按钮点击处理"""
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
if cursor.hasSelection():
|
|
|
# 如果有选中文本,只更改选中文本的下划线样式
|
|
|
fmt = cursor.charFormat()
|
|
|
fmt.setFontUnderline(checked)
|
|
|
cursor.setCharFormat(fmt)
|
|
|
else:
|
|
|
# 如果没有选中文本,更改整个文档的默认下划线样式
|
|
|
self.text_edit.setFontUnderline(checked)
|
|
|
|
|
|
def on_color_clicked(self):
|
|
|
"""字体颜色按钮点击处理 - 保留之前内容的颜色"""
|
|
|
from PyQt5.QtWidgets import QColorDialog
|
|
|
|
|
|
# 显示颜色选择对话框,默认使用当前文本颜色
|
|
|
current_color = self.text_edit.textColor()
|
|
|
color = QColorDialog.getColor(current_color, self, "选择字体颜色")
|
|
|
|
|
|
if color.isValid():
|
|
|
# 只设置后续输入的默认颜色,不影响已有内容
|
|
|
self.text_edit.setTextColor(color)
|
|
|
|
|
|
# 如果有选中文本,提示用户颜色只对新输入生效
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
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 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 insert_date_to_editor(self, date_str):
|
|
|
"""将选中的日期插入到编辑器中"""
|
|
|
# 获取当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
|
# 在光标位置插入日期字符串
|
|
|
cursor.insertText(date_str)
|
|
|
|
|
|
# 更新文本编辑器的光标
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
# 隐藏日历组件
|
|
|
if hasattr(self, 'calendar_widget'):
|
|
|
self.calendar_widget.hide()
|
|
|
|
|
|
# 更新状态栏提示
|
|
|
self.status_bar.showMessage(f"已插入日期: {date_str}", 2000)
|
|
|
|
|
|
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 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:
|
|
|
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', '未知城市')
|
|
|
|
|
|
# 从current字段获取温度和天气状况
|
|
|
current_data = weather_data.get('current', {})
|
|
|
temp = current_data.get('temp', 'N/A')
|
|
|
desc = current_data.get('weather', 'N/A')
|
|
|
|
|
|
# 获取温度范围信息
|
|
|
temp_range = ""
|
|
|
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':
|
|
|
temp_range = f" ({temp_min}°C~{temp_max}°C)"
|
|
|
|
|
|
# 在状态栏显示简要天气信息
|
|
|
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', [])}")
|
|
|
|
|
|
def refresh_weather(self):
|
|
|
"""手动刷新天气信息"""
|
|
|
try:
|
|
|
# 获取当前选择的城市
|
|
|
current_city = self.ribbon.city_combo.currentText()
|
|
|
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:
|
|
|
# 格式化天气数据为扁平结构,便于update_weather_display使用
|
|
|
formatted_data = {
|
|
|
'city': weather_data['city'],
|
|
|
'current': weather_data['current'],
|
|
|
'forecast': weather_data['forecast'],
|
|
|
'life_tips': weather_data.get('life_tips', [])
|
|
|
}
|
|
|
print(f"refresh_weather - 原始数据包含life_tips: {weather_data.get('life_tips', [])}")
|
|
|
print(f"refresh_weather - formatted_data包含life_tips: {formatted_data.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.status_bar.showMessage("天气数据已刷新", 2000)
|
|
|
else:
|
|
|
self.status_bar.showMessage("天气数据刷新失败,请检查API密钥", 3000)
|
|
|
# 显示错误信息到天气悬浮窗口
|
|
|
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
|
|
|
self.weather_floating_widget.update_weather({'error': '天气数据刷新失败'})
|
|
|
except Exception as e:
|
|
|
self.status_bar.showMessage(f"天气刷新失败: {str(e)}", 3000)
|
|
|
|
|
|
def show_detailed_weather(self):
|
|
|
"""显示详细天气信息对话框"""
|
|
|
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit
|
|
|
|
|
|
# 检查是否有天气数据
|
|
|
if not hasattr(self, 'current_weather_data') or not self.current_weather_data:
|
|
|
QMessageBox.information(self, "附加工具", "暂无天气数据,请先刷新天气信息")
|
|
|
return
|
|
|
|
|
|
weather_data = self.current_weather_data
|
|
|
print(f"详细天气对话框 - 天气数据: {weather_data}")
|
|
|
|
|
|
# 创建对话框
|
|
|
dialog = QDialog(self)
|
|
|
dialog.setWindowTitle("详细天气")
|
|
|
dialog.setMinimumWidth(400)
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# 城市信息
|
|
|
city_label = QLabel(f"<h2>{weather_data.get('city', '未知城市')}</h2>")
|
|
|
layout.addWidget(city_label)
|
|
|
|
|
|
# 当前天气信息
|
|
|
current_layout = QVBoxLayout()
|
|
|
current_layout.addWidget(QLabel("<b>当前天气:</b>"))
|
|
|
|
|
|
# 获取温度信息,支持嵌套结构
|
|
|
current_data = weather_data.get('current', {})
|
|
|
temp = current_data.get('temp', 'N/A')
|
|
|
if temp != 'N/A' and isinstance(temp, str):
|
|
|
temp = float(temp) if temp.replace('.', '').isdigit() else temp
|
|
|
|
|
|
# 从预报数据中获取最高和最低气温
|
|
|
temp_max = 'N/A'
|
|
|
temp_min = 'N/A'
|
|
|
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')
|
|
|
|
|
|
current_info = f"""
|
|
|
当前温度: {temp}°C
|
|
|
最高气温: {temp_max}°C
|
|
|
最低气温: {temp_min}°C
|
|
|
天气状况: {current_data.get('weather', 'N/A')}
|
|
|
"""
|
|
|
current_text = QTextEdit()
|
|
|
current_text.setPlainText(current_info.strip())
|
|
|
current_text.setReadOnly(True)
|
|
|
current_layout.addWidget(current_text)
|
|
|
|
|
|
layout.addLayout(current_layout)
|
|
|
|
|
|
# 生活提示信息(替换原来的天气预报)
|
|
|
life_tips = weather_data.get('life_tips', [])
|
|
|
print(f"详细天气对话框 - 生活提示: {life_tips}")
|
|
|
print(f"详细天气对话框 - 完整天气数据: {weather_data}")
|
|
|
if life_tips:
|
|
|
tips_layout = QVBoxLayout()
|
|
|
tips_layout.addWidget(QLabel("<b>生活提示:</b>"))
|
|
|
|
|
|
tips_text = QTextEdit()
|
|
|
tips_info = ""
|
|
|
for tip in life_tips:
|
|
|
tips_info += f"• {tip}\n"
|
|
|
|
|
|
tips_text.setPlainText(tips_info.strip())
|
|
|
tips_text.setReadOnly(True)
|
|
|
tips_layout.addWidget(tips_text)
|
|
|
layout.addLayout(tips_layout)
|
|
|
|
|
|
# 按钮
|
|
|
button_layout = QHBoxLayout()
|
|
|
refresh_button = QPushButton("刷新")
|
|
|
refresh_button.clicked.connect(lambda: self.refresh_weather_and_close(dialog))
|
|
|
close_button = QPushButton("关闭")
|
|
|
close_button.clicked.connect(dialog.close)
|
|
|
|
|
|
button_layout.addWidget(refresh_button)
|
|
|
button_layout.addWidget(close_button)
|
|
|
layout.addLayout(button_layout)
|
|
|
|
|
|
dialog.setLayout(layout)
|
|
|
dialog.exec_()
|
|
|
|
|
|
def refresh_weather_and_close(self, dialog):
|
|
|
"""刷新天气并关闭对话框"""
|
|
|
self.refresh_weather()
|
|
|
dialog.close()
|
|
|
|
|
|
def toggle_floating_weather(self):
|
|
|
"""切换天气悬浮窗口显示/隐藏"""
|
|
|
if hasattr(self, 'weather_floating_widget'):
|
|
|
if self.weather_floating_widget.isVisible():
|
|
|
self.weather_floating_widget.hide()
|
|
|
self.status_bar.showMessage("天气悬浮窗口已隐藏", 2000)
|
|
|
else:
|
|
|
self.weather_floating_widget.show()
|
|
|
# 确保窗口在屏幕内
|
|
|
self.weather_floating_widget.move(100, 100)
|
|
|
self.status_bar.showMessage("天气悬浮窗口已显示", 2000)
|
|
|
# 同步当前城市选择
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'city_combo'):
|
|
|
current_city = self.ribbon.city_combo.currentText()
|
|
|
self.weather_floating_widget.set_current_city(current_city)
|
|
|
# 如果有天气数据,更新显示
|
|
|
if hasattr(self, 'current_weather_data') and self.current_weather_data:
|
|
|
self.weather_floating_widget.update_weather(self.current_weather_data)
|
|
|
|
|
|
def on_weather_floating_closed(self):
|
|
|
"""天气悬浮窗口关闭时的处理"""
|
|
|
self.status_bar.showMessage("天气悬浮窗口已关闭", 2000)
|
|
|
|
|
|
def toggle_floating_quote(self):
|
|
|
"""切换每日谏言悬浮窗口显示/隐藏"""
|
|
|
if hasattr(self, 'quote_floating_widget'):
|
|
|
if self.quote_floating_widget.isVisible():
|
|
|
self.quote_floating_widget.hide()
|
|
|
self.status_bar.showMessage("每日谏言悬浮窗口已隐藏", 2000)
|
|
|
else:
|
|
|
self.quote_floating_widget.show()
|
|
|
# 确保窗口在屏幕内
|
|
|
self.quote_floating_widget.move(100, 100)
|
|
|
self.status_bar.showMessage("每日谏言悬浮窗口已显示", 2000)
|
|
|
|
|
|
def on_quote_floating_closed(self):
|
|
|
"""每日谏言悬浮窗口关闭时的处理"""
|
|
|
self.status_bar.showMessage("每日谏言悬浮窗口已关闭", 2000)
|
|
|
|
|
|
def insert_quote_to_cursor(self, quote_text):
|
|
|
"""将古诗句插入到光标位置"""
|
|
|
if hasattr(self, 'text_edit'):
|
|
|
# 获取当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
|
# 在光标位置插入文本
|
|
|
cursor.insertText(quote_text + "\n")
|
|
|
|
|
|
# 更新状态栏提示
|
|
|
# 从文本中提取诗句部分用于显示
|
|
|
quote_only = quote_text.split(" —— ")[0] if " —— " in quote_text else quote_text
|
|
|
self.status_bar.showMessage(f"已插入古诗句: {quote_only}", 3000)
|
|
|
|
|
|
def toggle_weather_tools(self, checked):
|
|
|
"""切换天气工具组显示"""
|
|
|
if checked:
|
|
|
# 显示天气工具组
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
self.ribbon.show_weather_group()
|
|
|
self.status_bar.showMessage("天气工具已显示", 2000)
|
|
|
else:
|
|
|
# 隐藏天气工具组
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
self.ribbon.hide_weather_group()
|
|
|
self.status_bar.showMessage("天气工具已隐藏", 2000)
|
|
|
|
|
|
def toggle_quote_tools(self, checked):
|
|
|
"""切换每日一言工具组显示"""
|
|
|
if checked:
|
|
|
# 显示每日一言工具组
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
self.ribbon.show_quote_group()
|
|
|
self.status_bar.showMessage("每日一言工具已显示", 2000)
|
|
|
# 如果当前没有显示内容,刷新一次
|
|
|
if hasattr(self.ribbon, 'quote_label') and self.ribbon.quote_label.text() == "每日一言: 暂无":
|
|
|
self.refresh_daily_quote()
|
|
|
else:
|
|
|
# 隐藏每日一言工具组
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
self.ribbon.hide_quote_group()
|
|
|
self.status_bar.showMessage("每日一言工具已隐藏", 2000)
|
|
|
|
|
|
def refresh_daily_quote(self):
|
|
|
"""刷新每日一言 - 使用WordRibbon中的API"""
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
# 直接调用WordRibbon中的刷新方法
|
|
|
self.ribbon.on_refresh_quote()
|
|
|
|
|
|
# 同时更新浮动窗口中的内容(如果浮动窗口存在且可见)
|
|
|
if hasattr(self, 'quote_floating_widget') and self.quote_floating_widget.isVisible():
|
|
|
# 调用浮动窗口的获取新内容方法
|
|
|
if hasattr(self.quote_floating_widget, 'fetch_and_update_quote'):
|
|
|
self.quote_floating_widget.fetch_and_update_quote()
|
|
|
|
|
|
def on_quote_fetched(self, quote_data):
|
|
|
"""处理名言获取成功"""
|
|
|
if 'error' not in quote_data:
|
|
|
content = quote_data.get('content', '获取名言失败')
|
|
|
author = quote_data.get('author', '未知')
|
|
|
quote_text = f"{content} — {author}"
|
|
|
|
|
|
# 更新Ribbon中的每日一言显示
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'quote_label'):
|
|
|
self.ribbon.quote_label.setText(f"{quote_text}")
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_bar.showMessage(f"每日名言: {quote_text}", 10000)
|
|
|
else:
|
|
|
# 处理错误情况
|
|
|
error_msg = quote_data.get('error', '获取名言失败')
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'quote_label'):
|
|
|
self.ribbon.quote_label.setText(f"获取失败")
|
|
|
self.status_bar.showMessage(f"每日名言获取失败: {error_msg}", 5000)
|
|
|
|
|
|
def on_quote_error(self, error_data):
|
|
|
"""处理名言获取错误"""
|
|
|
error_msg = error_data.get('error', '获取名言失败') if isinstance(error_data, dict) else str(error_data)
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'quote_label'):
|
|
|
self.ribbon.quote_label.setText(f"获取失败")
|
|
|
self.status_bar.showMessage(f"每日名言获取失败: {error_msg}", 5000)
|
|
|
|
|
|
def update_quote_display(self, quote_data):
|
|
|
"""更新名言显示"""
|
|
|
if 'error' not in quote_data:
|
|
|
content = quote_data.get('content', '获取名言失败')
|
|
|
author = quote_data.get('author', '未知')
|
|
|
self.status_bar.showMessage(f"每日名言: {content} - {author}", 10000)
|
|
|
|
|
|
def update_status_bar(self, stats):
|
|
|
"""更新状态栏统计信息"""
|
|
|
if stats:
|
|
|
# 获取打字统计信息
|
|
|
total_chars = stats.get('total_chars', 0)
|
|
|
typed_chars = stats.get('typed_chars', 0)
|
|
|
error_count = stats.get('error_count', 0)
|
|
|
accuracy_rate = stats.get('accuracy_rate', 0)
|
|
|
|
|
|
# 计算进度百分比
|
|
|
progress_text = ""
|
|
|
if total_chars > 0:
|
|
|
progress_percentage = (typed_chars / total_chars) * 100
|
|
|
progress_text = f"进度: {progress_percentage:.1f}%"
|
|
|
|
|
|
# 计算准确率
|
|
|
accuracy_text = f"准确率: {accuracy_rate:.1%}"
|
|
|
|
|
|
# 显示统计信息
|
|
|
status_text = f"{progress_text} | {accuracy_text} | 已输入: {typed_chars}/{total_chars} | 错误: {error_count}"
|
|
|
self.status_bar.showMessage(status_text, 0) # 0表示不自动消失
|
|
|
|
|
|
# 更新字数统计标签(如果存在)
|
|
|
if hasattr(self.status_bar, 'words_label'):
|
|
|
self.status_bar.words_label.setText(f"总字数: {total_chars}")
|
|
|
|
|
|
# 更新进度标签(如果存在)
|
|
|
if hasattr(self.status_bar, 'progress_label'):
|
|
|
self.status_bar.progress_label.setText(f"进度: {progress_percentage:.1f}%" if total_chars > 0 else "进度: 0%")
|
|
|
|
|
|
def update_window_title(self):
|
|
|
"""更新窗口标题"""
|
|
|
file_name = "文档1"
|
|
|
if self.current_file_path:
|
|
|
file_name = os.path.basename(self.current_file_path)
|
|
|
|
|
|
modified = "*" if self.is_modified else ""
|
|
|
self.setWindowTitle(f"{file_name}{modified} - MagicWord")
|
|
|
|
|
|
def new_document(self):
|
|
|
"""新建文档 - 根据当前视图模式处理"""
|
|
|
self.text_edit.clear()
|
|
|
self.current_file_path = None
|
|
|
self.is_modified = False
|
|
|
self.update_window_title()
|
|
|
|
|
|
# 重置导入内容和进度
|
|
|
self.imported_content = ""
|
|
|
self.displayed_chars = 0
|
|
|
if hasattr(self, 'learning_progress'):
|
|
|
delattr(self, 'learning_progress')
|
|
|
|
|
|
# 根据当前模式重置打字逻辑
|
|
|
if self.typing_logic:
|
|
|
if self.view_mode == "learning":
|
|
|
# 学习模式:重置为默认内容,需要导入文件
|
|
|
self.typing_logic.reset("欢迎使用MagicWord隐私学习软件!\n\n请先导入文件开始打字学习。")
|
|
|
self.status_bar.showMessage("新建文档 - 学习模式,请先导入文件开始打字学习", 3000)
|
|
|
elif self.view_mode == "typing":
|
|
|
# 打字模式:重置为默认内容,允许自由打字
|
|
|
self.typing_logic.reset("欢迎使用MagicWord隐私学习软件!\n\n这是一个仿Microsoft Word界面的学习工具。")
|
|
|
self.status_bar.showMessage("新建文档 - 打字模式,可以自由开始打字", 3000)
|
|
|
|
|
|
def import_file(self):
|
|
|
"""导入文件 - 根据模式决定是否立即显示"""
|
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
|
self, "导入文件", "",
|
|
|
"文档文件 (*.docx *.txt *.pdf *.html);;所有文件 (*.*)"
|
|
|
)
|
|
|
|
|
|
if file_path:
|
|
|
try:
|
|
|
# 使用新的转换方法,将文件转换为txt格式
|
|
|
parser = FileParser()
|
|
|
result = parser.parse_and_convert_to_txt(file_path)
|
|
|
|
|
|
if result['success']:
|
|
|
content = result['content']
|
|
|
txt_path = result['txt_path']
|
|
|
images = result['images']
|
|
|
|
|
|
# 如果是临时文件,添加到跟踪列表
|
|
|
if result.get('is_temp_file', False):
|
|
|
self.temp_files.append(txt_path)
|
|
|
|
|
|
# 存储完整内容
|
|
|
self.imported_content = content
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
# 如果有提取的图片,设置到打字逻辑中
|
|
|
if images:
|
|
|
image_data_dict = {}
|
|
|
for filename, image_data in images:
|
|
|
image_data_dict[filename] = image_data
|
|
|
|
|
|
# 创建图片位置信息(简化处理,将图片放在文本末尾)
|
|
|
image_positions = []
|
|
|
current_pos = len(content)
|
|
|
for i, (filename, _) in enumerate(images):
|
|
|
# 在文本末尾添加图片标记
|
|
|
content += f"\n\n[图片: {filename}]\n"
|
|
|
image_positions.append({
|
|
|
'start_pos': current_pos,
|
|
|
'end_pos': current_pos + len(f"[图片: {filename}]"),
|
|
|
'filename': filename
|
|
|
})
|
|
|
current_pos += len(f"\n\n[图片: {filename}]\n")
|
|
|
|
|
|
# 更新导入的内容
|
|
|
self.imported_content = content
|
|
|
|
|
|
# 设置图片数据到打字逻辑
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.set_image_data(image_data_dict)
|
|
|
self.typing_logic.set_image_positions(image_positions)
|
|
|
|
|
|
# 清空文本编辑器
|
|
|
self.text_edit.clear()
|
|
|
|
|
|
# 根据当前模式进行处理
|
|
|
if self.view_mode == "learning":
|
|
|
# 学习模式:重置打字逻辑并准备显示导入内容
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.reset(content) # 重置打字状态并设置新内容
|
|
|
|
|
|
self.status_bar.showMessage(f"已导入: {os.path.basename(txt_path)},开始打字逐步显示学习内容!", 5000)
|
|
|
else:
|
|
|
# 打字模式:直接显示完整内容
|
|
|
self.text_edit.setPlainText(content)
|
|
|
self.status_bar.showMessage(f"已导入: {os.path.basename(txt_path)}", 5000)
|
|
|
|
|
|
# 提取并显示图片(如果有)
|
|
|
if images:
|
|
|
self.extract_and_display_images(file_path=None, images=images)
|
|
|
|
|
|
else:
|
|
|
# 转换失败,显示错误信息
|
|
|
raise Exception(result['error'])
|
|
|
|
|
|
except Exception as e:
|
|
|
# 如果新转换方法失败,回退到原来的解析方法
|
|
|
try:
|
|
|
parser = FileParser()
|
|
|
content = parser.parse_file(file_path)
|
|
|
|
|
|
if content:
|
|
|
# 存储完整内容
|
|
|
self.imported_content = content
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
# 清空文本编辑器
|
|
|
self.text_edit.clear()
|
|
|
|
|
|
# 根据当前模式进行处理
|
|
|
if self.view_mode == "learning":
|
|
|
# 学习模式:重置打字逻辑并准备显示导入内容
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.reset(content) # 重置打字状态并设置新内容
|
|
|
|
|
|
self.status_bar.showMessage(f"已导入: {os.path.basename(file_path)},开始打字逐步显示学习内容!", 5000)
|
|
|
else:
|
|
|
# 打字模式:直接显示完整内容
|
|
|
self.text_edit.setPlainText(content)
|
|
|
self.status_bar.showMessage(f"已导入: {os.path.basename(file_path)}", 5000)
|
|
|
|
|
|
except Exception as fallback_e:
|
|
|
QMessageBox.critical(self, "错误", f"无法导入文件:\n{str(e)}\n\n回退方法也失败:\n{str(fallback_e)}")
|
|
|
return
|
|
|
|
|
|
# 设置当前文件路径(仅作为参考,不用于保存)
|
|
|
self.current_file_path = txt_path if 'txt_path' in locals() else file_path
|
|
|
self.is_modified = False
|
|
|
self.update_window_title()
|
|
|
|
|
|
# 更新字数统计
|
|
|
if hasattr(self.status_bar, 'words_label'):
|
|
|
self.status_bar.words_label.setText(f"总字数: {len(content)}")
|
|
|
|
|
|
except Exception as e:
|
|
|
# 如果新转换方法失败,回退到原来的解析方法
|
|
|
try:
|
|
|
parser = FileParser()
|
|
|
content = parser.parse_file(file_path)
|
|
|
|
|
|
if content:
|
|
|
# 设置文件加载标志
|
|
|
self.is_loading_file = True
|
|
|
|
|
|
# 存储完整内容
|
|
|
self.imported_content = content
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
# 创建空白副本 - 清空文本编辑器
|
|
|
self.text_edit.clear()
|
|
|
|
|
|
# 根据当前模式进行处理
|
|
|
if self.view_mode == "learning":
|
|
|
# 学习模式:设置学习内容到打字逻辑
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.reset(content) # 重置打字状态并设置新内容
|
|
|
|
|
|
self.status_bar.showMessage(f"已打开学习文件: {os.path.basename(file_path)},开始打字逐步显示学习内容!", 5000)
|
|
|
else:
|
|
|
# 打字模式:直接显示完整内容
|
|
|
self.text_edit.setPlainText(content)
|
|
|
self.status_bar.showMessage(f"已打开: {os.path.basename(file_path)}", 5000)
|
|
|
|
|
|
# 清除文件加载标志
|
|
|
self.is_loading_file = False
|
|
|
|
|
|
# 设置当前文件路径
|
|
|
self.current_file_path = file_path
|
|
|
self.is_modified = False
|
|
|
self.update_window_title()
|
|
|
|
|
|
# 更新字数统计
|
|
|
if hasattr(self.status_bar, 'words_label'):
|
|
|
self.status_bar.words_label.setText(f"总字数: {len(content)}")
|
|
|
|
|
|
except Exception as fallback_e:
|
|
|
# 确保在异常情况下也清除标志
|
|
|
self.is_loading_file = False
|
|
|
QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}\n\n回退方法也失败:\n{str(fallback_e)}")
|
|
|
|
|
|
# 清除文件加载标志
|
|
|
self.is_loading_file = False
|
|
|
|
|
|
# 设置当前文件路径
|
|
|
self.current_file_path = file_path
|
|
|
self.is_modified = False
|
|
|
self.update_window_title()
|
|
|
|
|
|
# 更新字数统计
|
|
|
if hasattr(self.status_bar, 'words_label'):
|
|
|
self.status_bar.words_label.setText(f"总字数: {len(content)}")
|
|
|
|
|
|
# 提取并显示图片(仅对.docx文件)
|
|
|
if file_path.lower().endswith('.docx'):
|
|
|
self.extract_and_display_images(file_path)
|
|
|
|
|
|
else:
|
|
|
QMessageBox.warning(self, "警告", "无法读取文件内容或文件为空")
|
|
|
|
|
|
except Exception as e:
|
|
|
# 确保在异常情况下也清除标志
|
|
|
self.is_loading_file = False
|
|
|
QMessageBox.critical(self, "错误", f"打开文件失败: {str(e)}")
|
|
|
print(f"文件打开错误详情: {e}") # 调试信息
|
|
|
|
|
|
def save_file(self):
|
|
|
"""保存文件"""
|
|
|
if self.current_file_path:
|
|
|
try:
|
|
|
# 如果是.docx文件,创建一个基本的Word文档
|
|
|
if self.current_file_path.endswith('.docx'):
|
|
|
from docx import Document
|
|
|
doc = Document()
|
|
|
doc.add_paragraph(self.text_edit.toPlainText())
|
|
|
doc.save(self.current_file_path)
|
|
|
else:
|
|
|
# 对于其他格式,保持原有逻辑
|
|
|
with open(self.current_file_path, 'w', encoding='utf-8') as f:
|
|
|
f.write(self.text_edit.toPlainText())
|
|
|
|
|
|
self.is_modified = False
|
|
|
self.update_window_title()
|
|
|
self.status_bar.showMessage("文件已保存", 3000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
|
|
|
else:
|
|
|
self.save_as_file()
|
|
|
|
|
|
def save_as_file(self):
|
|
|
"""另存为"""
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
self, "另存为", "", "Word文档 (*.docx);;文本文档 (*.txt);;所有文件 (*.*)"
|
|
|
)
|
|
|
|
|
|
if file_path:
|
|
|
# 如果用户没有指定扩展名,自动添加.docx扩展名
|
|
|
if not os.path.splitext(file_path)[1]:
|
|
|
file_path += ".docx"
|
|
|
|
|
|
try:
|
|
|
# 如果是.docx文件,创建一个基本的Word文档
|
|
|
if file_path.endswith('.docx'):
|
|
|
from docx import Document
|
|
|
doc = Document()
|
|
|
doc.add_paragraph(self.text_edit.toPlainText())
|
|
|
doc.save(file_path)
|
|
|
else:
|
|
|
# 对于其他格式,保持原有逻辑
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
f.write(self.text_edit.toPlainText())
|
|
|
|
|
|
self.current_file_path = file_path
|
|
|
self.is_modified = False
|
|
|
self.update_window_title()
|
|
|
self.status_bar.showMessage(f"已保存: {os.path.basename(file_path)}", 3000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
|
|
|
|
|
|
def undo(self):
|
|
|
"""撤销"""
|
|
|
self.text_edit.undo()
|
|
|
|
|
|
def redo(self):
|
|
|
"""重做"""
|
|
|
self.text_edit.redo()
|
|
|
|
|
|
def cut(self):
|
|
|
"""剪切"""
|
|
|
self.text_edit.cut()
|
|
|
|
|
|
def copy(self):
|
|
|
"""复制"""
|
|
|
self.text_edit.copy()
|
|
|
|
|
|
def paste(self):
|
|
|
"""粘贴"""
|
|
|
self.text_edit.paste()
|
|
|
|
|
|
def toggle_reading_view(self):
|
|
|
"""切换阅读视图"""
|
|
|
# 这里可以实现阅读视图的逻辑
|
|
|
self.status_bar.showMessage("阅读视图功能开发中...", 3000)
|
|
|
|
|
|
def toggle_print_layout(self):
|
|
|
"""切换打印布局"""
|
|
|
# 这里可以实现打印布局的逻辑
|
|
|
self.status_bar.showMessage("打印布局功能开发中...", 3000)
|
|
|
|
|
|
def set_view_mode(self, mode):
|
|
|
"""设置视图模式 - 打开学习模式窗口或切换到打字模式"""
|
|
|
if mode not in ["typing", "learning"]:
|
|
|
return
|
|
|
|
|
|
if mode == "learning":
|
|
|
# 学习模式:打开新的学习模式窗口
|
|
|
try:
|
|
|
from learning_mode_window import LearningModeWindow
|
|
|
|
|
|
# 准备传递给学习窗口的参数
|
|
|
imported_content = ""
|
|
|
current_position = 0
|
|
|
|
|
|
# 如果有已导入的内容,传递给学习窗口
|
|
|
if hasattr(self, 'imported_content') and self.imported_content:
|
|
|
imported_content = self.imported_content
|
|
|
if hasattr(self, 'learning_progress') and self.learning_progress > 0:
|
|
|
current_position = self.learning_progress
|
|
|
else:
|
|
|
# 如果没有导入内容,检查当前打字模式的内容
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
if current_text and current_text != "在此输入您的内容...":
|
|
|
# 将打字模式的内容作为学习模式的导入内容
|
|
|
imported_content = current_text
|
|
|
current_position = 0
|
|
|
self.imported_content = current_text
|
|
|
|
|
|
# 准备图片数据
|
|
|
image_data = None
|
|
|
image_positions = None
|
|
|
if hasattr(self, 'typing_logic') and self.typing_logic:
|
|
|
if hasattr(self.typing_logic, 'image_data'):
|
|
|
image_data = self.typing_logic.image_data
|
|
|
if hasattr(self.typing_logic, 'image_positions'):
|
|
|
image_positions = self.typing_logic.image_positions
|
|
|
|
|
|
# 创建学习模式窗口,传递导入内容和图片数据
|
|
|
self.learning_window = LearningModeWindow(self, imported_content, current_position, image_data, image_positions)
|
|
|
|
|
|
# 连接学习模式窗口的内容变化信号
|
|
|
self.learning_window.content_changed.connect(self.on_learning_content_changed)
|
|
|
|
|
|
# 连接学习模式窗口的关闭信号
|
|
|
self.learning_window.closed.connect(self.on_learning_mode_closed)
|
|
|
|
|
|
# 显示学习模式窗口
|
|
|
self.learning_window.show()
|
|
|
|
|
|
# 更新菜单状态
|
|
|
self.learning_mode_action.setChecked(True)
|
|
|
self.typing_mode_action.setChecked(False)
|
|
|
|
|
|
# 切换到打字模式(主窗口保持打字模式)
|
|
|
self.view_mode = "typing"
|
|
|
|
|
|
self.status_bar.showMessage("学习模式窗口已打开", 3000)
|
|
|
|
|
|
except ImportError as e:
|
|
|
QMessageBox.critical(self, "错误", f"无法加载学习模式窗口:\n{str(e)}")
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"打开学习模式窗口时出错:\n{str(e)}")
|
|
|
|
|
|
elif mode == "typing":
|
|
|
# 打字模式:保持当前窗口状态
|
|
|
self.view_mode = "typing"
|
|
|
self.typing_mode_action.setChecked(True)
|
|
|
self.learning_mode_action.setChecked(False)
|
|
|
self.status_bar.showMessage("当前为打字模式", 2000)
|
|
|
|
|
|
def on_learning_mode_closed(self):
|
|
|
"""学习模式窗口关闭时的回调"""
|
|
|
# 重置菜单状态
|
|
|
self.learning_mode_action.setChecked(False)
|
|
|
self.typing_mode_action.setChecked(True)
|
|
|
self.view_mode = "typing"
|
|
|
|
|
|
# 清除学习窗口引用
|
|
|
self.learning_window = None
|
|
|
|
|
|
self.status_bar.showMessage("学习模式窗口已关闭", 2000)
|
|
|
|
|
|
def on_learning_content_changed(self, new_content, position):
|
|
|
"""学习模式内容变化时的回调 - 只在末尾追加新内容"""
|
|
|
# 设置同步标记,防止递归调用
|
|
|
self.sync_from_learning = True
|
|
|
|
|
|
try:
|
|
|
# 只在末尾追加新输入的内容,不修改已有内容
|
|
|
if new_content:
|
|
|
# 直接在末尾追加新内容
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.movePosition(QTextCursor.End)
|
|
|
cursor.insertText(new_content)
|
|
|
|
|
|
# 更新导入内容(但不覆盖用户额外输入的内容)
|
|
|
if self.imported_content:
|
|
|
self.imported_content += new_content
|
|
|
else:
|
|
|
self.imported_content = new_content
|
|
|
|
|
|
self.learning_progress = len(self.imported_content) if self.imported_content else 0
|
|
|
|
|
|
# 重置打字逻辑(不追踪进度)
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.imported_content = self.imported_content
|
|
|
self.typing_logic.current_index = self.learning_progress
|
|
|
self.typing_logic.typed_chars = self.learning_progress
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_bar.showMessage(f"从学习模式同步新内容: {new_content}", 3000)
|
|
|
|
|
|
finally:
|
|
|
# 重置同步标记
|
|
|
self.sync_from_learning = False
|
|
|
|
|
|
def set_page_color(self, color):
|
|
|
"""设置页面颜色"""
|
|
|
color_map = {
|
|
|
'white': '#ffffff',
|
|
|
'light_blue': '#e6f3ff',
|
|
|
'light_yellow': '#fffde6',
|
|
|
'light_green': '#e6ffe6'
|
|
|
}
|
|
|
|
|
|
if color in color_map:
|
|
|
bg_color = color_map[color]
|
|
|
# 更新文本编辑区域的背景色
|
|
|
current_style = self.text_edit.styleSheet()
|
|
|
# 移除旧的背景色设置
|
|
|
import re
|
|
|
current_style = re.sub(r'background-color:\s*#[a-fA-F0-9]+;', '', current_style)
|
|
|
# 添加新的背景色设置
|
|
|
new_style = current_style + f"\nbackground-color: {bg_color};"
|
|
|
self.text_edit.setStyleSheet(new_style)
|
|
|
self.status_bar.showMessage(f"页面颜色已设置为{color}", 2000)
|
|
|
|
|
|
def set_page_margins(self, margin_type):
|
|
|
"""设置页面边距"""
|
|
|
margin_map = {
|
|
|
'normal': (50, 50, 50, 50),
|
|
|
'narrow': (20, 20, 20, 20),
|
|
|
'wide': (80, 80, 80, 80)
|
|
|
}
|
|
|
|
|
|
if margin_type in margin_map:
|
|
|
margins = margin_map[margin_type]
|
|
|
# 更新文档容器的边距
|
|
|
# text_edit的父级是document_layout,父级的父级是document_container
|
|
|
container = self.text_edit.parent().parent() # 获取文档容器
|
|
|
if container and hasattr(container, 'layout'):
|
|
|
layout = container.layout()
|
|
|
if layout:
|
|
|
layout.setContentsMargins(margins[0], margins[1], margins[2], margins[3])
|
|
|
self.status_bar.showMessage(f"页面边距已设置为{margin_type}", 2000)
|
|
|
|
|
|
def zoom_in(self):
|
|
|
"""放大"""
|
|
|
self.adjust_zoom_level(10)
|
|
|
|
|
|
def zoom_out(self):
|
|
|
"""缩小"""
|
|
|
self.adjust_zoom_level(-10)
|
|
|
|
|
|
def zoom_100(self):
|
|
|
"""实际大小"""
|
|
|
self.set_zoom_level(100)
|
|
|
|
|
|
def set_zoom_level(self, level):
|
|
|
"""设置缩放级别"""
|
|
|
if 10 <= level <= 500: # 限制缩放范围在10%到500%之间
|
|
|
# 获取当前字体大小并调整
|
|
|
current_font = self.text_edit.currentFont()
|
|
|
base_size = 12 # 基准字体大小
|
|
|
new_size = base_size * (level / 100)
|
|
|
|
|
|
# 应用新的字体大小
|
|
|
current_font.setPointSizeF(new_size)
|
|
|
self.text_edit.setFont(current_font)
|
|
|
|
|
|
self.status_bar.showMessage(f"缩放级别: {level}%", 2000)
|
|
|
|
|
|
def adjust_zoom_level(self, delta):
|
|
|
"""调整缩放级别"""
|
|
|
# 获取当前字体大小
|
|
|
current_font = self.text_edit.currentFont()
|
|
|
current_size = current_font.pointSizeF()
|
|
|
base_size = 12 # 基准字体大小
|
|
|
|
|
|
# 计算当前缩放百分比
|
|
|
current_zoom = (current_size / base_size) * 100
|
|
|
new_zoom = current_zoom + delta
|
|
|
|
|
|
# 限制缩放范围
|
|
|
if 10 <= new_zoom <= 500:
|
|
|
self.set_zoom_level(int(new_zoom))
|
|
|
|
|
|
def toggle_grid_lines(self):
|
|
|
"""切换网格线显示"""
|
|
|
self.status_bar.showMessage("网格线功能开发中...", 3000)
|
|
|
|
|
|
def toggle_ruler(self):
|
|
|
"""切换标尺显示"""
|
|
|
self.status_bar.showMessage("标尺功能开发中...", 3000)
|
|
|
|
|
|
def show_find_dialog(self):
|
|
|
"""显示查找对话框"""
|
|
|
# 创建查找对话框
|
|
|
dialog = QDialog(self)
|
|
|
dialog.setWindowTitle("查找")
|
|
|
dialog.setFixedSize(400, 150)
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# 查找内容输入
|
|
|
find_layout = QHBoxLayout()
|
|
|
find_label = QLabel("查找内容:")
|
|
|
self.find_edit = QLineEdit()
|
|
|
find_layout.addWidget(find_label)
|
|
|
find_layout.addWidget(self.find_edit)
|
|
|
layout.addLayout(find_layout)
|
|
|
|
|
|
# 选项
|
|
|
options_layout = QHBoxLayout()
|
|
|
self.case_sensitive_checkbox = QCheckBox("区分大小写")
|
|
|
self.whole_words_checkbox = QCheckBox("全字匹配")
|
|
|
options_layout.addWidget(self.case_sensitive_checkbox)
|
|
|
options_layout.addWidget(self.whole_words_checkbox)
|
|
|
options_layout.addStretch()
|
|
|
layout.addLayout(options_layout)
|
|
|
|
|
|
# 按钮
|
|
|
buttons_layout = QHBoxLayout()
|
|
|
find_next_btn = QPushButton("查找下一个")
|
|
|
find_next_btn.clicked.connect(lambda: self.find_text(dialog))
|
|
|
cancel_btn = QPushButton("取消")
|
|
|
cancel_btn.clicked.connect(dialog.close)
|
|
|
buttons_layout.addWidget(find_next_btn)
|
|
|
buttons_layout.addWidget(cancel_btn)
|
|
|
layout.addLayout(buttons_layout)
|
|
|
|
|
|
dialog.setLayout(layout)
|
|
|
dialog.exec_()
|
|
|
|
|
|
def find_text(self, dialog):
|
|
|
"""执行查找操作"""
|
|
|
search_text = self.find_edit.text()
|
|
|
if not search_text:
|
|
|
QMessageBox.warning(self, "查找", "请输入查找内容")
|
|
|
return
|
|
|
|
|
|
# 获取当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
start_pos = cursor.position()
|
|
|
|
|
|
# 设置查找选项
|
|
|
flags = QTextDocument.FindFlags()
|
|
|
if self.case_sensitive_checkbox.isChecked():
|
|
|
flags |= QTextDocument.FindCaseSensitively
|
|
|
if self.whole_words_checkbox.isChecked():
|
|
|
flags |= QTextDocument.FindWholeWords
|
|
|
|
|
|
# 执行查找
|
|
|
found_cursor = self.text_edit.document().find(search_text, start_pos, flags)
|
|
|
|
|
|
if found_cursor.isNull():
|
|
|
# 如果没找到,从文档开始处重新查找
|
|
|
found_cursor = self.text_edit.document().find(search_text, 0, flags)
|
|
|
|
|
|
if not found_cursor.isNull():
|
|
|
# 选中找到的文本
|
|
|
self.text_edit.setTextCursor(found_cursor)
|
|
|
self.text_edit.ensureCursorVisible()
|
|
|
else:
|
|
|
QMessageBox.information(self, "查找", f"找不到 '{search_text}'")
|
|
|
|
|
|
def show_replace_dialog(self):
|
|
|
"""显示替换对话框"""
|
|
|
# 创建替换对话框
|
|
|
dialog = QDialog(self)
|
|
|
dialog.setWindowTitle("替换")
|
|
|
dialog.setFixedSize(400, 200)
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# 查找内容输入
|
|
|
find_layout = QHBoxLayout()
|
|
|
find_label = QLabel("查找内容:")
|
|
|
self.replace_find_edit = QLineEdit()
|
|
|
find_layout.addWidget(find_label)
|
|
|
find_layout.addWidget(self.replace_find_edit)
|
|
|
layout.addLayout(find_layout)
|
|
|
|
|
|
# 替换为输入
|
|
|
replace_layout = QHBoxLayout()
|
|
|
replace_label = QLabel("替换为:")
|
|
|
self.replace_edit = QLineEdit()
|
|
|
replace_layout.addWidget(replace_label)
|
|
|
replace_layout.addWidget(self.replace_edit)
|
|
|
layout.addLayout(replace_layout)
|
|
|
|
|
|
# 选项
|
|
|
options_layout = QHBoxLayout()
|
|
|
self.replace_case_sensitive_checkbox = QCheckBox("区分大小写")
|
|
|
self.replace_whole_words_checkbox = QCheckBox("全字匹配")
|
|
|
options_layout.addWidget(self.replace_case_sensitive_checkbox)
|
|
|
options_layout.addWidget(self.replace_whole_words_checkbox)
|
|
|
options_layout.addStretch()
|
|
|
layout.addLayout(options_layout)
|
|
|
|
|
|
# 按钮
|
|
|
buttons_layout = QHBoxLayout()
|
|
|
find_next_btn = QPushButton("查找下一个")
|
|
|
replace_btn = QPushButton("替换")
|
|
|
replace_all_btn = QPushButton("全部替换")
|
|
|
cancel_btn = QPushButton("取消")
|
|
|
|
|
|
find_next_btn.clicked.connect(lambda: self.find_text_for_replace(dialog))
|
|
|
replace_btn.clicked.connect(lambda: self.replace_text(dialog))
|
|
|
replace_all_btn.clicked.connect(lambda: self.replace_all_text(dialog))
|
|
|
cancel_btn.clicked.connect(dialog.close)
|
|
|
|
|
|
buttons_layout.addWidget(find_next_btn)
|
|
|
buttons_layout.addWidget(replace_btn)
|
|
|
buttons_layout.addWidget(replace_all_btn)
|
|
|
buttons_layout.addWidget(cancel_btn)
|
|
|
layout.addLayout(buttons_layout)
|
|
|
|
|
|
dialog.setLayout(layout)
|
|
|
dialog.exec_()
|
|
|
|
|
|
def find_text_for_replace(self, dialog):
|
|
|
"""在替换对话框中执行查找操作"""
|
|
|
search_text = self.replace_find_edit.text()
|
|
|
if not search_text:
|
|
|
QMessageBox.warning(self, "查找", "请输入查找内容")
|
|
|
return
|
|
|
|
|
|
# 获取当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
start_pos = cursor.position()
|
|
|
|
|
|
# 设置查找选项
|
|
|
flags = QTextDocument.FindFlags()
|
|
|
if self.replace_case_sensitive_checkbox.isChecked():
|
|
|
flags |= QTextDocument.FindCaseSensitively
|
|
|
if self.replace_whole_words_checkbox.isChecked():
|
|
|
flags |= QTextDocument.FindWholeWords
|
|
|
|
|
|
# 执行查找
|
|
|
found_cursor = self.text_edit.document().find(search_text, start_pos, flags)
|
|
|
|
|
|
if found_cursor.isNull():
|
|
|
# 如果没找到,从文档开始处重新查找
|
|
|
found_cursor = self.text_edit.document().find(search_text, 0, flags)
|
|
|
|
|
|
if not found_cursor.isNull():
|
|
|
# 选中找到的文本
|
|
|
self.text_edit.setTextCursor(found_cursor)
|
|
|
self.text_edit.ensureCursorVisible()
|
|
|
else:
|
|
|
QMessageBox.information(self, "查找", f"找不到 '{search_text}'")
|
|
|
|
|
|
def replace_text(self, dialog):
|
|
|
"""替换当前选中的文本"""
|
|
|
search_text = self.replace_find_edit.text()
|
|
|
replace_text = self.replace_edit.text()
|
|
|
|
|
|
# 检查是否有选中的文本且与查找内容匹配
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
selected_text = cursor.selectedText()
|
|
|
|
|
|
# 检查是否匹配(考虑大小写敏感选项)
|
|
|
match = False
|
|
|
if self.replace_case_sensitive_checkbox.isChecked():
|
|
|
match = selected_text == search_text
|
|
|
else:
|
|
|
match = selected_text.lower() == search_text.lower()
|
|
|
|
|
|
if match:
|
|
|
# 替换选中的文本
|
|
|
cursor.insertText(replace_text)
|
|
|
# 继续查找下一个
|
|
|
self.find_text_for_replace(dialog)
|
|
|
else:
|
|
|
# 如果没有匹配的选中文本,执行查找
|
|
|
self.find_text_for_replace(dialog)
|
|
|
|
|
|
def replace_all_text(self, dialog):
|
|
|
"""替换所有匹配的文本"""
|
|
|
search_text = self.replace_find_edit.text()
|
|
|
replace_text = self.replace_edit.text()
|
|
|
|
|
|
if not search_text:
|
|
|
QMessageBox.warning(self, "替换", "请输入查找内容")
|
|
|
return
|
|
|
|
|
|
# 设置查找选项
|
|
|
flags = QTextDocument.FindFlags()
|
|
|
if self.replace_case_sensitive_checkbox.isChecked():
|
|
|
flags |= QTextDocument.FindCaseSensitively
|
|
|
if self.replace_whole_words_checkbox.isChecked():
|
|
|
flags |= QTextDocument.FindWholeWords
|
|
|
|
|
|
# 保存当前光标位置
|
|
|
original_cursor = self.text_edit.textCursor()
|
|
|
|
|
|
# 从文档开始处查找并替换所有匹配项
|
|
|
count = 0
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.movePosition(QTextCursor.Start)
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
while True:
|
|
|
found_cursor = self.text_edit.document().find(search_text, cursor, flags)
|
|
|
if found_cursor.isNull():
|
|
|
break
|
|
|
|
|
|
# 替换文本
|
|
|
found_cursor.insertText(replace_text)
|
|
|
count += 1
|
|
|
cursor = found_cursor
|
|
|
|
|
|
# 恢复原始光标位置
|
|
|
self.text_edit.setTextCursor(original_cursor)
|
|
|
|
|
|
# 显示替换结果
|
|
|
QMessageBox.information(self, "替换", f"已完成 {count} 处替换。")
|
|
|
|
|
|
def show_about(self):
|
|
|
"""显示关于对话框"""
|
|
|
# 创建自定义对话框
|
|
|
dialog = QDialog(self)
|
|
|
dialog.setWindowTitle("关于 MagicWord")
|
|
|
dialog.setModal(True)
|
|
|
dialog.setFixedSize(500, 400)
|
|
|
|
|
|
# 创建布局
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# 添加图标和标题布局
|
|
|
header_layout = QHBoxLayout()
|
|
|
|
|
|
# 添加应用程序图标
|
|
|
try:
|
|
|
icon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "icons", "app_icon_128X128.png")
|
|
|
if os.path.exists(icon_path):
|
|
|
icon_label = QLabel()
|
|
|
icon_label.setAlignment(Qt.AlignCenter)
|
|
|
pixmap = QPixmap(icon_path)
|
|
|
if not pixmap.isNull():
|
|
|
# 缩放图标到合适大小
|
|
|
scaled_pixmap = pixmap.scaled(80, 80, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
icon_label.setPixmap(scaled_pixmap)
|
|
|
header_layout.addWidget(icon_label)
|
|
|
except Exception as e:
|
|
|
print(f"加载图标失败: {e}")
|
|
|
|
|
|
# 添加标题和版本信息
|
|
|
title_layout = QVBoxLayout()
|
|
|
title_label = QLabel("MagicWord")
|
|
|
title_label.setStyleSheet("font-size: 24px; font-weight: bold; color: #0078d7;")
|
|
|
title_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
version_label = QLabel("版本 1.0")
|
|
|
version_label.setStyleSheet("font-size: 14px; color: #666;")
|
|
|
version_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
title_layout.addWidget(title_label)
|
|
|
title_layout.addWidget(version_label)
|
|
|
header_layout.addLayout(title_layout)
|
|
|
|
|
|
layout.addLayout(header_layout)
|
|
|
|
|
|
# 添加分隔线
|
|
|
separator = QFrame()
|
|
|
separator.setFrameShape(QFrame.HLine)
|
|
|
separator.setFrameShadow(QFrame.Sunken)
|
|
|
separator.setStyleSheet("color: #ddd;")
|
|
|
layout.addWidget(separator)
|
|
|
|
|
|
# 添加关于信息
|
|
|
about_text = QLabel(
|
|
|
"隐私学习软件\n\n"
|
|
|
"基于 Microsoft Word 界面设计\n\n"
|
|
|
"功能特色:\n"
|
|
|
"• 仿Word界面设计\n"
|
|
|
"• 隐私学习模式\n"
|
|
|
"• 多格式文档支持\n"
|
|
|
"• 实时进度跟踪\n"
|
|
|
"• 天气和名言显示"
|
|
|
)
|
|
|
about_text.setAlignment(Qt.AlignCenter)
|
|
|
about_text.setStyleSheet("font-size: 12px; line-height: 1.5;")
|
|
|
layout.addWidget(about_text)
|
|
|
|
|
|
# 添加按钮布局
|
|
|
button_layout = QHBoxLayout()
|
|
|
button_layout.addStretch() # 添加弹性空间使按钮居中
|
|
|
|
|
|
# 添加"沃式烁"按钮
|
|
|
woshishuo_button = QPushButton("沃式烁")
|
|
|
woshishuo_button.clicked.connect(lambda: self.show_woshishuo_image())
|
|
|
button_layout.addWidget(woshishuo_button)
|
|
|
|
|
|
# 添加OK按钮
|
|
|
ok_button = QPushButton("OK")
|
|
|
ok_button.clicked.connect(dialog.accept)
|
|
|
button_layout.addWidget(ok_button)
|
|
|
|
|
|
button_layout.addStretch() # 添加弹性空间使按钮居中
|
|
|
|
|
|
layout.addLayout(button_layout)
|
|
|
dialog.setLayout(layout)
|
|
|
|
|
|
# 显示对话框
|
|
|
dialog.exec_()
|
|
|
|
|
|
def show_woshishuo_image(self):
|
|
|
"""显示沃式烁图片"""
|
|
|
try:
|
|
|
# 图片路径
|
|
|
image_path = os.path.join(os.path.dirname(__file__), "ui", "114514.png")
|
|
|
|
|
|
# 检查图片是否存在
|
|
|
if not os.path.exists(image_path):
|
|
|
QMessageBox.warning(self, "错误", "图片文件不存在: 114514.png")
|
|
|
return
|
|
|
|
|
|
# 创建图片查看对话框
|
|
|
image_dialog = QDialog(self)
|
|
|
image_dialog.setWindowTitle("沃式烁")
|
|
|
image_dialog.setModal(True)
|
|
|
image_dialog.setFixedSize(500, 400)
|
|
|
|
|
|
# 创建布局
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# 添加图片标签
|
|
|
image_label = QLabel()
|
|
|
image_label.setAlignment(Qt.AlignCenter)
|
|
|
image_label.setScaledContents(True)
|
|
|
|
|
|
# 加载图片
|
|
|
pixmap = QPixmap(image_path)
|
|
|
if pixmap.isNull():
|
|
|
QMessageBox.warning(self, "错误", "无法加载图片文件")
|
|
|
return
|
|
|
|
|
|
# 缩放图片以适应对话框
|
|
|
scaled_pixmap = pixmap.scaled(450, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
image_label.setPixmap(scaled_pixmap)
|
|
|
|
|
|
layout.addWidget(image_label)
|
|
|
|
|
|
# 添加关闭按钮
|
|
|
close_button = QPushButton("关闭")
|
|
|
close_button.clicked.connect(image_dialog.accept)
|
|
|
layout.addWidget(close_button)
|
|
|
|
|
|
image_dialog.setLayout(layout)
|
|
|
image_dialog.exec_()
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.warning(self, "错误", f"显示图片时出错: {str(e)}")
|
|
|
|
|
|
def highlight_next_char(self, position, expected_char):
|
|
|
"""高亮显示下一个期望字符"""
|
|
|
if position < len(self.typing_logic.learning_content):
|
|
|
# 获取当前光标位置
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.setPosition(position)
|
|
|
|
|
|
# 选择下一个字符
|
|
|
cursor.movePosition(cursor.Right, cursor.KeepAnchor, 1)
|
|
|
|
|
|
# 设置高亮格式
|
|
|
format = QTextCharFormat()
|
|
|
format.setBackground(QColor(255, 255, 0, 128)) # 黄色半透明背景
|
|
|
format.setForeground(QColor(255, 0, 0)) # 红色文字
|
|
|
format.setFontWeight(QFont.Bold)
|
|
|
|
|
|
# 应用格式
|
|
|
cursor.setCharFormat(format)
|
|
|
|
|
|
def on_lesson_complete(self):
|
|
|
"""课程完成处理"""
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
QMessageBox.information(
|
|
|
self, "恭喜",
|
|
|
"恭喜完成本课程学习!\n\n"
|
|
|
f"准确率: {stats['accuracy_rate']*100:.1f}%\n"
|
|
|
f"总字符数: {stats['total_chars']}\n"
|
|
|
f"错误次数: {stats['error_count']}"
|
|
|
)
|
|
|
|
|
|
# 重置状态
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.reset()
|
|
|
|
|
|
def on_image_item_double_clicked(self, item):
|
|
|
"""双击图片项时显示大图"""
|
|
|
try:
|
|
|
# 获取图片索引
|
|
|
row = self.image_list_widget.row(item)
|
|
|
if 0 <= row < len(self.extracted_images):
|
|
|
image_filename, image_data = self.extracted_images[row]
|
|
|
self.show_image_viewer(image_filename, image_data)
|
|
|
except Exception as e:
|
|
|
self.status_bar.showMessage(f"显示图片失败: {str(e)}", 3000)
|
|
|
|
|
|
def set_light_mode(self):
|
|
|
"""设置为白色模式"""
|
|
|
theme_manager.set_dark_theme(False)
|
|
|
self.light_mode_action.setChecked(True)
|
|
|
self.dark_mode_action.setChecked(False)
|
|
|
self.status_bar.showMessage("已切换到白色模式", 2000)
|
|
|
|
|
|
def set_dark_mode(self):
|
|
|
"""设置为黑色模式"""
|
|
|
theme_manager.set_dark_theme(True)
|
|
|
self.light_mode_action.setChecked(False)
|
|
|
self.dark_mode_action.setChecked(True)
|
|
|
self.status_bar.showMessage("已切换到黑色模式", 2000)
|
|
|
|
|
|
def show_image_viewer(self, filename, image_data):
|
|
|
"""显示图片查看器 - 支持缩放功能"""
|
|
|
try:
|
|
|
# 创建自定义图片查看窗口
|
|
|
viewer = QDialog(self)
|
|
|
viewer.setWindowTitle(f"图片查看 - {filename}")
|
|
|
viewer.setModal(False)
|
|
|
|
|
|
# 设置窗口标志,保留标题栏以便用户可以移动和调整大小
|
|
|
viewer.setWindowFlags(Qt.Tool | Qt.WindowCloseButtonHint | Qt.WindowMinMaxButtonsHint)
|
|
|
|
|
|
# 设置窗口背景为黑色
|
|
|
viewer.setStyleSheet("""
|
|
|
QDialog {
|
|
|
background-color: #000000;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 创建场景和视图
|
|
|
scene = QGraphicsScene(viewer)
|
|
|
view = QGraphicsView(scene)
|
|
|
view.setStyleSheet("border: none;") # 移除视图边框
|
|
|
|
|
|
# 设置视图为可交互的,并启用滚动条
|
|
|
view.setDragMode(QGraphicsView.ScrollHandDrag)
|
|
|
view.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
|
|
|
|
|
|
# 创建布局
|
|
|
layout = QVBoxLayout()
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
layout.addWidget(view)
|
|
|
viewer.setLayout(layout)
|
|
|
|
|
|
# 加载图片
|
|
|
pixmap = QPixmap()
|
|
|
if not pixmap.loadFromData(image_data):
|
|
|
self.status_bar.showMessage(f"加载图片失败: {filename}", 3000)
|
|
|
return
|
|
|
|
|
|
# 将图片添加到场景
|
|
|
scene.addPixmap(pixmap)
|
|
|
|
|
|
# 设置视图大小和位置
|
|
|
if self:
|
|
|
parent_geometry = self.geometry()
|
|
|
screen_geometry = QApplication.primaryScreen().geometry()
|
|
|
|
|
|
# 设置窗口宽度与主窗口相同,高度为屏幕高度的40%
|
|
|
window_width = parent_geometry.width()
|
|
|
window_height = int(screen_geometry.height() * 0.4)
|
|
|
|
|
|
# 计算位置:显示在主窗口正上方
|
|
|
x = parent_geometry.x()
|
|
|
y = parent_geometry.y() - window_height
|
|
|
|
|
|
# 确保不会超出屏幕边界
|
|
|
if y < screen_geometry.top():
|
|
|
y = parent_geometry.y() + 50 # 如果上方空间不足,显示在下方
|
|
|
|
|
|
# 调整宽度确保不超出屏幕
|
|
|
if x + window_width > screen_geometry.right():
|
|
|
window_width = screen_geometry.right() - x
|
|
|
|
|
|
viewer.setGeometry(x, y, window_width, window_height)
|
|
|
|
|
|
viewer.show()
|
|
|
|
|
|
# 设置视图适应图片大小
|
|
|
view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
|
|
|
|
|
|
# 重写视图的滚轮事件以支持缩放
|
|
|
def wheelEvent(event):
|
|
|
factor = 1.2
|
|
|
if event.angleDelta().y() > 0:
|
|
|
view.scale(factor, factor)
|
|
|
else:
|
|
|
view.scale(1.0/factor, 1.0/factor)
|
|
|
|
|
|
view.wheelEvent = wheelEvent
|
|
|
|
|
|
# 添加双击重置视图功能
|
|
|
def mouseDoubleClickEvent(event):
|
|
|
view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
|
|
|
|
|
|
view.mouseDoubleClickEvent = mouseDoubleClickEvent
|
|
|
|
|
|
except Exception as e:
|
|
|
self.status_bar.showMessage(f"创建图片查看器失败: {str(e)}", 3000)
|
|
|
import traceback
|
|
|
traceback.print_exc()
|
|
|
|
|
|
def insert_images_in_text(self):
|
|
|
"""在文本中插入图片 - 修复图片显示逻辑"""
|
|
|
try:
|
|
|
if not self.typing_logic or not hasattr(self.typing_logic, 'image_positions'):
|
|
|
print("打字逻辑或图片位置信息不存在")
|
|
|
return
|
|
|
|
|
|
# 添加调试信息
|
|
|
print(f"当前显示字符数: {self.displayed_chars}")
|
|
|
print(f"图片位置信息数量: {len(self.typing_logic.image_positions)}")
|
|
|
print(f"图片数据数量: {len(self.typing_logic.image_data) if hasattr(self.typing_logic, 'image_data') else 0}")
|
|
|
|
|
|
# 检查是否已经插入过图片(避免重复插入)
|
|
|
if not hasattr(self, 'inserted_images'):
|
|
|
self.inserted_images = set()
|
|
|
|
|
|
# 获取当前显示的文本
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
current_length = len(current_text)
|
|
|
|
|
|
# 获取需要显示的图片列表
|
|
|
images_to_display = self.typing_logic.get_images_to_display(self.displayed_chars)
|
|
|
|
|
|
# 添加调试信息
|
|
|
print(f"需要显示的图片数量: {len(images_to_display)}")
|
|
|
if images_to_display:
|
|
|
for img in images_to_display:
|
|
|
print(f"图片信息: {img.get('filename', 'unknown')} at pos {img.get('start_pos', -1)}-{img.get('end_pos', -1)}")
|
|
|
|
|
|
# 检查当前显示位置是否有图片需要插入
|
|
|
for image_info in images_to_display:
|
|
|
image_key = f"{image_info['start_pos']}_{image_info['filename']}"
|
|
|
|
|
|
# 跳过已经插入过的图片
|
|
|
if image_key in self.inserted_images:
|
|
|
continue
|
|
|
|
|
|
# 当打字进度达到图片位置时插入图片 - 修复条件,确保图片能显示
|
|
|
if (self.displayed_chars >= image_info['start_pos'] or
|
|
|
(self.displayed_chars >= max(1, image_info['start_pos'] - 20) and self.displayed_chars > 0)):
|
|
|
# 在图片位置插入图片
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
|
# 计算图片应该插入的位置(基于原始内容位置)
|
|
|
insert_position = image_info['start_pos']
|
|
|
|
|
|
# 确保插入位置有效(不能超过当前显示内容长度)
|
|
|
if insert_position >= 0 and insert_position <= current_length:
|
|
|
cursor.setPosition(insert_position)
|
|
|
|
|
|
# 创建图片格式
|
|
|
image_format = QTextImageFormat()
|
|
|
|
|
|
# 获取图片数据(优先使用typing_logic中的数据)
|
|
|
image_data = None
|
|
|
if hasattr(self.typing_logic, 'image_data') and image_info['filename'] in self.typing_logic.image_data:
|
|
|
image_data = self.typing_logic.image_data[image_info['filename']]
|
|
|
else:
|
|
|
image_data = image_info.get('data')
|
|
|
|
|
|
if image_data:
|
|
|
# 加载图片数据
|
|
|
pixmap = QPixmap()
|
|
|
if pixmap.loadFromData(image_data):
|
|
|
# 调整图片大小
|
|
|
scaled_pixmap = pixmap.scaled(200, 150, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
|
|
|
# 将图片保存到临时文件(使用更稳定的路径)
|
|
|
import tempfile
|
|
|
import os
|
|
|
temp_dir = tempfile.gettempdir()
|
|
|
|
|
|
# 确保文件名安全
|
|
|
safe_filename = "".join(c for c in image_info['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(" ")
|
|
|
|
|
|
# 标记这张图片已经插入过
|
|
|
self.inserted_images.add(image_key)
|
|
|
|
|
|
# 记录插入成功
|
|
|
print(f"图片 {image_info['filename']} 已在位置 {insert_position} 插入")
|
|
|
else:
|
|
|
print(f"保存临时图片文件失败: {temp_file}")
|
|
|
else:
|
|
|
print(f"加载图片数据失败: {image_info['filename']}")
|
|
|
|
|
|
# 重新设置光标到文本末尾
|
|
|
cursor.movePosition(cursor.End)
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f"插入图片失败: {str(e)}")
|
|
|
import traceback
|
|
|
traceback.print_exc()
|
|
|
|
|
|
def check_and_show_image_at_position(self, position):
|
|
|
"""检查指定位置是否有图片并显示 - 现在只在文本中显示,不弹出窗口"""
|
|
|
# 这个方法现在不需要了,因为图片会直接插入到文本中
|
|
|
pass
|
|
|
|
|
|
def show_image_at_position(self, image_info):
|
|
|
"""在指定位置显示图片 - 现在不需要弹出窗口了"""
|
|
|
# 这个方法现在不需要了,因为图片会直接插入到文本中
|
|
|
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):
|
|
|
"""关闭事件处理"""
|
|
|
# 清理临时文件
|
|
|
self.cleanup_temp_files()
|
|
|
|
|
|
if self.is_modified:
|
|
|
reply = QMessageBox.question(
|
|
|
self, "确认退出",
|
|
|
"文档已修改,是否保存更改?",
|
|
|
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
|
|
|
)
|
|
|
|
|
|
if reply == QMessageBox.Save:
|
|
|
self.save_file()
|
|
|
event.accept()
|
|
|
elif reply == QMessageBox.Discard:
|
|
|
event.accept()
|
|
|
else:
|
|
|
event.ignore()
|
|
|
else:
|
|
|
event.accept()
|
|
|
|
|
|
def cleanup_temp_files(self):
|
|
|
"""清理临时文件"""
|
|
|
import os
|
|
|
for temp_file in self.temp_files:
|
|
|
try:
|
|
|
if os.path.exists(temp_file):
|
|
|
os.remove(temp_file)
|
|
|
print(f"已删除临时文件: {temp_file}")
|
|
|
except Exception as e:
|
|
|
print(f"删除临时文件失败 {temp_file}: {str(e)}")
|
|
|
self.temp_files.clear()
|
|
|
|
|
|
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'<div class="image-container">\n'
|
|
|
html_body += f'<img src="data:image/{img_type};base64,{img_base64}" alt="{img_name}" style="max-width: 100%; height: auto; margin: 10px 0;">\n'
|
|
|
html_body += f'<p class="image-caption">{img_name}</p>\n'
|
|
|
html_body += f'</div>\n'
|
|
|
else:
|
|
|
html_body += f'<p class="image-placeholder">[图片: {img_name}]</p>\n'
|
|
|
else:
|
|
|
# 普通文本,使用段落标签
|
|
|
if line.strip():
|
|
|
html_body += f'<p>{line}</p>\n'
|
|
|
else:
|
|
|
html_body += '<br>\n'
|
|
|
|
|
|
# 创建完整的HTML内容
|
|
|
html_content = f"""<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>MagicWord文档</title>
|
|
|
<style>
|
|
|
body {{
|
|
|
font-family: 'Calibri', 'Microsoft YaHei', '微软雅黑', sans-serif;
|
|
|
font-size: 12pt;
|
|
|
line-height: 1.6;
|
|
|
margin: 40px;
|
|
|
background-color: #ffffff;
|
|
|
color: #000000;
|
|
|
max-width: 800px;
|
|
|
margin: 0 auto;
|
|
|
padding: 20px;
|
|
|
}}
|
|
|
.container {{
|
|
|
padding: 20px;
|
|
|
}}
|
|
|
p {{
|
|
|
margin: 10px 0;
|
|
|
white-space: pre-wrap;
|
|
|
}}
|
|
|
.image-container {{
|
|
|
text-align: center;
|
|
|
margin: 20px 0;
|
|
|
padding: 15px;
|
|
|
border: 1px solid #e0e0e0;
|
|
|
border-radius: 5px;
|
|
|
background-color: #f9f9f9;
|
|
|
}}
|
|
|
.image-container img {{
|
|
|
max-width: 100%;
|
|
|
height: auto;
|
|
|
border-radius: 3px;
|
|
|
}}
|
|
|
.image-caption {{
|
|
|
margin-top: 8px;
|
|
|
font-size: 10pt;
|
|
|
color: #666;
|
|
|
font-style: italic;
|
|
|
}}
|
|
|
.image-placeholder {{
|
|
|
background-color: #f0f0f0;
|
|
|
border: 2px dashed #ccc;
|
|
|
padding: 20px;
|
|
|
text-align: center;
|
|
|
color: #999;
|
|
|
font-style: italic;
|
|
|
margin: 20px 0;
|
|
|
}}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
{html_body}
|
|
|
</div>
|
|
|
</body>
|
|
|
</html>"""
|
|
|
|
|
|
# 写入HTML文件
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
f.write(html_content)
|
|
|
|
|
|
self.status_bar.showMessage(f"已导出为HTML: {os.path.basename(file_path)}", 3000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"导出HTML失败: {str(e)}")
|
|
|
|
|
|
def export_as_pdf(self):
|
|
|
"""导出为PDF"""
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
self, "导出为PDF", "", "PDF文件 (*.pdf);;所有文件 (*.*)"
|
|
|
)
|
|
|
|
|
|
if file_path:
|
|
|
try:
|
|
|
from PyQt5.QtGui import QTextDocument
|
|
|
from PyQt5.QtPrintSupport import QPrinter
|
|
|
|
|
|
# 获取当前文本内容
|
|
|
content = self.text_edit.toPlainText()
|
|
|
|
|
|
# 处理图片标签 - 创建HTML内容以便更好地处理图片
|
|
|
html_content = "<html><body style='font-family: Calibri, Microsoft YaHei, 微软雅黑, sans-serif; font-size: 12pt; line-height: 1.6; margin: 40px;'>"
|
|
|
|
|
|
lines = content.split('\n')
|
|
|
for line in lines:
|
|
|
if line.strip().startswith('[图片:') and line.strip().endswith(']'):
|
|
|
# 提取图片文件名
|
|
|
img_name = line.strip()[4:-1].strip()
|
|
|
|
|
|
# 在PDF中显示图片占位符
|
|
|
html_content += f'<div style="text-align: center; margin: 20px 0; padding: 15px; border: 1px solid #e0e0e0; border-radius: 5px; background-color: #f9f9f9;">'
|
|
|
html_content += f'<div style="background-color: #f0f0f0; border: 2px dashed #ccc; padding: 20px; text-align: center; color: #999; font-style: italic;">'
|
|
|
html_content += f'[图片: {img_name}]'
|
|
|
html_content += f'</div>'
|
|
|
html_content += f'<p style="margin-top: 8px; font-size: 10pt; color: #666; font-style: italic;">{img_name}</p>'
|
|
|
html_content += f'</div>'
|
|
|
else:
|
|
|
# 普通文本,使用段落标签
|
|
|
if line.strip():
|
|
|
html_content += f'<p style="margin: 10px 0; white-space: pre-wrap;">{line}</p>'
|
|
|
else:
|
|
|
html_content += '<br>'
|
|
|
|
|
|
html_content += "</body></html>"
|
|
|
|
|
|
# 创建文本文档
|
|
|
doc = QTextDocument()
|
|
|
doc.setHtml(html_content)
|
|
|
|
|
|
# 设置文档样式
|
|
|
doc.setDefaultFont(self.text_edit.currentFont())
|
|
|
|
|
|
# 创建PDF打印机
|
|
|
printer = QPrinter(QPrinter.HighResolution)
|
|
|
printer.setOutputFormat(QPrinter.PdfFormat)
|
|
|
printer.setOutputFileName(file_path)
|
|
|
printer.setPageSize(QPrinter.A4)
|
|
|
printer.setPageMargins(20, 20, 20, 20, QPrinter.Millimeter)
|
|
|
|
|
|
# 打印文档到PDF
|
|
|
doc.print_(printer)
|
|
|
|
|
|
self.status_bar.showMessage(f"已导出为PDF: {os.path.basename(file_path)}", 3000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"导出PDF失败: {str(e)}")
|
|
|
|
|
|
def export_as_txt(self):
|
|
|
"""导出为TXT"""
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
self, "导出为TXT", "", "文本文档 (*.txt);;所有文件 (*.*)"
|
|
|
)
|
|
|
|
|
|
if file_path:
|
|
|
try:
|
|
|
# 获取当前文本内容
|
|
|
content = self.text_edit.toPlainText()
|
|
|
|
|
|
# 处理图片标签 - 在TXT中保留图片标记
|
|
|
processed_content = content
|
|
|
|
|
|
# 写入TXT文件
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
f.write(processed_content)
|
|
|
|
|
|
self.status_bar.showMessage(f"已导出为TXT: {os.path.basename(file_path)}", 3000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"导出TXT失败: {str(e)}")
|
|
|
|
|
|
def export_as_docx(self):
|
|
|
"""导出为DOCX"""
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
self, "导出为DOCX", "", "Word文档 (*.docx);;所有文件 (*.*)"
|
|
|
)
|
|
|
|
|
|
if file_path:
|
|
|
try:
|
|
|
import os
|
|
|
import tempfile
|
|
|
from docx import Document
|
|
|
from docx.shared import Inches
|
|
|
|
|
|
# 创建Word文档
|
|
|
doc = Document()
|
|
|
|
|
|
# 获取当前文本内容
|
|
|
content = self.text_edit.toPlainText()
|
|
|
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:
|
|
|
# 检测图片类型
|
|
|
if img_name.lower().endswith('.png'):
|
|
|
img_ext = '.png'
|
|
|
elif img_name.lower().endswith(('.jpg', '.jpeg')):
|
|
|
img_ext = '.jpg'
|
|
|
elif img_name.lower().endswith('.gif'):
|
|
|
img_ext = '.gif'
|
|
|
else:
|
|
|
img_ext = '.png' # 默认
|
|
|
|
|
|
# 创建临时文件
|
|
|
with tempfile.NamedTemporaryFile(mode='wb', suffix=img_ext, delete=False) as temp_file:
|
|
|
temp_file.write(img_data)
|
|
|
temp_img_path = temp_file.name
|
|
|
|
|
|
try:
|
|
|
# 在Word文档中添加图片
|
|
|
doc.add_picture(temp_img_path, width=Inches(4))
|
|
|
|
|
|
# 添加图片说明
|
|
|
doc.add_paragraph(f'图片: {img_name}')
|
|
|
|
|
|
finally:
|
|
|
# 清理临时文件
|
|
|
if os.path.exists(temp_img_path):
|
|
|
os.remove(temp_img_path)
|
|
|
else:
|
|
|
# 图片未找到,添加占位符文本
|
|
|
doc.add_paragraph(f'[图片: {img_name}]')
|
|
|
else:
|
|
|
# 普通文本
|
|
|
if line.strip():
|
|
|
doc.add_paragraph(line)
|
|
|
else:
|
|
|
# 空行,添加空段落
|
|
|
doc.add_paragraph()
|
|
|
|
|
|
# 保存文档
|
|
|
doc.save(file_path)
|
|
|
|
|
|
self.status_bar.showMessage(f"已导出为DOCX: {os.path.basename(file_path)}", 3000)
|
|
|
|
|
|
except ImportError:
|
|
|
QMessageBox.critical(self, "错误", "需要安装python-docx库才能导出DOCX文件")
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "错误", f"导出DOCX失败: {str(e)}")
|
|
|
|
|
|
def extract_and_display_images(self, file_path=None, images=None):
|
|
|
"""提取并显示Word文档中的图片 - 修复图片位置计算"""
|
|
|
try:
|
|
|
# 如果没有提供图片数据,则从文件中提取
|
|
|
if images is None:
|
|
|
if file_path is None:
|
|
|
return
|
|
|
# 提取图片
|
|
|
images = FileParser.extract_images_from_docx(file_path)
|
|
|
|
|
|
if not images:
|
|
|
return
|
|
|
|
|
|
# 清空之前的图片
|
|
|
self.extracted_images.clear()
|
|
|
self.image_list_widget.clear()
|
|
|
|
|
|
# 保存提取的图片
|
|
|
self.extracted_images.extend(images)
|
|
|
|
|
|
# 创建图片位置信息列表
|
|
|
image_positions = []
|
|
|
|
|
|
# 显示图片列表
|
|
|
self.image_list_widget.setVisible(True)
|
|
|
self.image_list_widget.setMaximumHeight(150)
|
|
|
|
|
|
# 添加图片项到列表
|
|
|
for index, (filename, image_data) in enumerate(images):
|
|
|
# 创建缩略图
|
|
|
pixmap = QPixmap()
|
|
|
if pixmap.loadFromData(image_data):
|
|
|
# 创建缩略图
|
|
|
thumbnail = pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
|
|
|
# 创建列表项
|
|
|
item = QListWidgetItem()
|
|
|
item.setText(f"{filename} ({pixmap.width()}x{pixmap.height()})")
|
|
|
item.setIcon(QIcon(thumbnail))
|
|
|
item.setData(Qt.UserRole, filename) # 保存文件名到数据
|
|
|
|
|
|
self.image_list_widget.addItem(item)
|
|
|
else:
|
|
|
# 如果无法加载图片,显示默认文本
|
|
|
item = QListWidgetItem(f"{filename} (无法预览)")
|
|
|
item.setData(Qt.UserRole, filename)
|
|
|
self.image_list_widget.addItem(item)
|
|
|
|
|
|
# 为每张图片创建位置信息 - 修复位置计算,确保早期显示
|
|
|
content_length = len(self.imported_content)
|
|
|
#if content_length == 0:
|
|
|
#content_length = len(content) if 'content' in locals() else 1000 # 备用长度
|
|
|
|
|
|
# 修复图片位置计算,确保图片能在用户早期打字时显示
|
|
|
if len(images) == 1:
|
|
|
# 只有一张图片,放在文档开始位置附近(前10%),确保用户能快速看到
|
|
|
start_pos = max(10, content_length // 10)
|
|
|
else:
|
|
|
# 多张图片:前几张放在较前位置,确保用户能看到
|
|
|
if index < 3:
|
|
|
# 前3张图片放在文档前30%
|
|
|
segment = content_length // 3
|
|
|
start_pos = max(10, segment * (index + 1) // 4)
|
|
|
else:
|
|
|
# 其余图片均匀分布
|
|
|
remaining_start = content_length // 2
|
|
|
remaining_index = index - 3
|
|
|
remaining_count = len(images) - 3
|
|
|
if remaining_count > 0:
|
|
|
segment = (content_length - remaining_start) // (remaining_count + 1)
|
|
|
start_pos = remaining_start + segment * (remaining_index + 1)
|
|
|
else:
|
|
|
start_pos = content_length // 2
|
|
|
|
|
|
end_pos = min(start_pos + 50, content_length)
|
|
|
|
|
|
image_positions.append({
|
|
|
'start_pos': start_pos,
|
|
|
'end_pos': end_pos,
|
|
|
'data': image_data,
|
|
|
'filename': filename
|
|
|
})
|
|
|
|
|
|
# 设置图片位置信息到打字逻辑
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.set_image_positions(image_positions)
|
|
|
|
|
|
# 设置图片数据到打字逻辑
|
|
|
image_data_dict = {}
|
|
|
for filename, image_data in images:
|
|
|
image_data_dict[filename] = image_data
|
|
|
self.typing_logic.set_image_data(image_data_dict)
|
|
|
|
|
|
# 添加调试信息
|
|
|
print(f"已设置 {len(image_positions)} 个图片位置和 {len(image_data_dict)} 个图片数据到打字逻辑")
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_bar.showMessage(f"已提取 {len(images)} 张图片,双击查看大图", 5000)
|
|
|
|
|
|
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}")
|
|
|
|
|
|
def resizeEvent(self, event):
|
|
|
"""窗口大小改变事件处理"""
|
|
|
super().resizeEvent(event)
|
|
|
|
|
|
# 如果日历组件可见,调整其大小和位置以适应窗口底部
|
|
|
if hasattr(self, 'calendar_widget') and self.calendar_widget.isVisible():
|
|
|
calendar_height = 350 # 增加高度以确保所有日期都能完整显示
|
|
|
self.calendar_widget.setGeometry(0, self.height() - calendar_height,
|
|
|
self.width(), calendar_height)
|
|
|
|
|
|
def toggle_calendar(self):
|
|
|
"""切换日历组件的显示/隐藏状态"""
|
|
|
if hasattr(self, 'calendar_widget'):
|
|
|
if self.calendar_widget.isVisible():
|
|
|
self.calendar_widget.hide()
|
|
|
else:
|
|
|
# 设置日历组件位置在窗口底部
|
|
|
calendar_height = 350 # 增加高度以确保所有日期都能完整显示
|
|
|
|
|
|
# 将日历组件放置在窗口底部,占据整个宽度
|
|
|
self.calendar_widget.setGeometry(0, self.height() - calendar_height,
|
|
|
self.width(), calendar_height)
|
|
|
self.calendar_widget.show()
|
|
|
self.calendar_widget.raise_() # 确保日历组件在最上层显示
|
|
|
|
|
|
def insert_weather_info(self):
|
|
|
"""在光标位置插入天气信息"""
|
|
|
# 检查是否处于打字模式
|
|
|
if self.view_mode != "typing":
|
|
|
self.status_bar.showMessage("请在打字模式下使用插入天气信息功能", 3000)
|
|
|
return
|
|
|
|
|
|
# 检查是否已经定位了天气(即是否有有效的天气数据)
|
|
|
if not hasattr(self, 'current_weather_data') or not self.current_weather_data:
|
|
|
# 弹出对话框提示用户先定位天气
|
|
|
QMessageBox.information(self, "附加工具", "先定位天气")
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
# 直接使用已经获取到的天气数据
|
|
|
weather_data = self.current_weather_data
|
|
|
|
|
|
# 格式化天气信息
|
|
|
if weather_data:
|
|
|
# 处理嵌套的天气数据结构
|
|
|
city = weather_data.get('city', '未知城市')
|
|
|
current_data = weather_data.get('current', {})
|
|
|
temp = current_data.get('temp', 'N/A')
|
|
|
desc = current_data.get('weather', 'N/A')
|
|
|
weather_info = f"天气: {desc}, 温度: {temp}°C, 城市: {city}"
|
|
|
else:
|
|
|
weather_info = "天气信息获取失败"
|
|
|
|
|
|
# 在光标位置插入天气信息
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.insertText(weather_info)
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_bar.showMessage("已插入天气信息", 2000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.warning(self, "错误", f"插入天气信息失败: {str(e)}")
|
|
|
|
|
|
def insert_daily_quote(self):
|
|
|
"""在光标位置插入每日一句名言"""
|
|
|
# 检查是否处于打字模式
|
|
|
if self.view_mode != "typing":
|
|
|
self.status_bar.showMessage("请在打字模式下使用插入每日一句名言功能", 3000)
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
# 使用与Ribbon界面相同的API获取每日一言,确保内容一致
|
|
|
from ui.word_style_ui import daily_sentence_API
|
|
|
quote_api = daily_sentence_API("https://api.nxvav.cn/api/yiyan")
|
|
|
quote_data = quote_api.get_sentence('json')
|
|
|
|
|
|
# 处理获取到的数据
|
|
|
if quote_data and isinstance(quote_data, dict):
|
|
|
quote_text = quote_data.get('yiyan', '暂无每日一言')
|
|
|
quote_info = quote_text
|
|
|
else:
|
|
|
quote_info = "每日一句名言获取失败"
|
|
|
|
|
|
# 在光标位置插入名言信息
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.insertText(quote_info)
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_bar.showMessage("已插入每日一句名言", 2000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.warning(self, "错误", f"插入每日一句名言失败: {str(e)}")
|
|
|
|
|
|
def insert_chinese_poetry(self):
|
|
|
"""在光标位置插入古诗词"""
|
|
|
# 检查是否处于打字模式
|
|
|
if self.view_mode != "typing":
|
|
|
self.status_bar.showMessage("请在打字模式下使用插入古诗词功能", 3000)
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
# 获取古诗词
|
|
|
poetry_data = self.ribbon.get_chinese_poetry()
|
|
|
|
|
|
# 在光标位置插入古诗词
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
cursor.insertText(poetry_data)
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_bar.showMessage("已插入古诗词", 2000)
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.warning(self, "错误", f"插入古诗词失败: {str(e)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
app = QApplication(sys.argv)
|
|
|
|
|
|
# 设置应用程序样式
|
|
|
app.setStyle('Windows')
|
|
|
|
|
|
# 创建并显示主窗口
|
|
|
window = WordStyleMainWindow()
|
|
|
window.show()
|
|
|
|
|
|
sys.exit(app.exec_()) |