|
|
|
|
@ -1,13 +1,14 @@
|
|
|
|
|
# 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)
|
|
|
|
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect, QByteArray, QBuffer, QIODevice
|
|
|
|
|
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor, QTextDocument, QImage, QTextImageFormat, QTextFormat
|
|
|
|
|
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor, QTextDocument, QImage, QTextImageFormat, QTextFormat, QTextBlockFormat
|
|
|
|
|
|
|
|
|
|
from ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit,
|
|
|
|
|
)
|
|
|
|
|
@ -135,9 +136,8 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 连接主题切换信号
|
|
|
|
|
theme_manager.theme_changed.connect(self.on_theme_changed)
|
|
|
|
|
|
|
|
|
|
# 设置默认为白色模式(禁用自动检测)
|
|
|
|
|
theme_manager.enable_auto_detection(False)
|
|
|
|
|
theme_manager.set_dark_theme(False)
|
|
|
|
|
# 启用系统主题自动检测
|
|
|
|
|
theme_manager.enable_auto_detection(True)
|
|
|
|
|
|
|
|
|
|
# 应用当前主题
|
|
|
|
|
self.apply_theme()
|
|
|
|
|
@ -637,6 +637,8 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
self.learning_mode_action = QAction('学习模式', self)
|
|
|
|
|
self.learning_mode_action.setCheckable(True)
|
|
|
|
|
self.learning_mode_action.setChecked(False)
|
|
|
|
|
# 设置学习模式快捷键 (Qt会自动在macOS上映射Ctrl为Cmd)
|
|
|
|
|
self.learning_mode_action.setShortcut('Ctrl+L')
|
|
|
|
|
self.learning_mode_action.triggered.connect(lambda: self.set_view_mode("learning"))
|
|
|
|
|
view_mode_menu.addAction(self.learning_mode_action)
|
|
|
|
|
|
|
|
|
|
@ -674,12 +676,40 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 插入菜单
|
|
|
|
|
insert_menu = menubar.addMenu('插入(I)')
|
|
|
|
|
|
|
|
|
|
# 插入图片功能
|
|
|
|
|
insert_image_action = QAction('插入图片', self)
|
|
|
|
|
insert_image_action.triggered.connect(self.insert_image_in_typing_mode)
|
|
|
|
|
insert_menu.addAction(insert_image_action)
|
|
|
|
|
|
|
|
|
|
# 绘图菜单
|
|
|
|
|
paint_menu = menubar.addMenu('绘图(D)')
|
|
|
|
|
|
|
|
|
|
# 设计菜单
|
|
|
|
|
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)')
|
|
|
|
|
|
|
|
|
|
@ -809,6 +839,9 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 文本变化信号
|
|
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
|
|
|
|
|
|
|
# 光标位置变化信号,用于更新按钮状态
|
|
|
|
|
self.text_edit.cursorPositionChanged.connect(self.update_format_buttons)
|
|
|
|
|
|
|
|
|
|
# Ribbon按钮信号
|
|
|
|
|
# 标签栏已删除,相关代码已移除
|
|
|
|
|
|
|
|
|
|
@ -821,6 +854,18 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
self.ribbon.underline_btn.clicked.connect(self.on_underline_clicked)
|
|
|
|
|
self.ribbon.color_btn.clicked.connect(self.on_color_clicked)
|
|
|
|
|
|
|
|
|
|
# 样式按钮信号
|
|
|
|
|
if hasattr(self.ribbon, 'heading1_btn'):
|
|
|
|
|
self.ribbon.heading1_btn.clicked.connect(self.on_heading1_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'heading2_btn'):
|
|
|
|
|
self.ribbon.heading2_btn.clicked.connect(self.on_heading2_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'heading3_btn'):
|
|
|
|
|
self.ribbon.heading3_btn.clicked.connect(self.on_heading3_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'heading4_btn'):
|
|
|
|
|
self.ribbon.heading4_btn.clicked.connect(self.on_heading4_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'body_text_btn'):
|
|
|
|
|
self.ribbon.body_text_btn.clicked.connect(self.on_body_text_clicked)
|
|
|
|
|
|
|
|
|
|
# 查找和替换按钮信号
|
|
|
|
|
if hasattr(self.ribbon, 'find_btn'):
|
|
|
|
|
self.ribbon.find_btn.clicked.connect(self.show_find_dialog)
|
|
|
|
|
@ -829,6 +874,16 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
# 页面布局信号已在菜单中直接连接,无需在此重复连接
|
|
|
|
|
|
|
|
|
|
# 段落对齐按钮信号
|
|
|
|
|
if hasattr(self.ribbon, 'align_left_btn'):
|
|
|
|
|
self.ribbon.align_left_btn.clicked.connect(self.on_align_left_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'align_center_btn'):
|
|
|
|
|
self.ribbon.align_center_btn.clicked.connect(self.on_align_center_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'align_right_btn'):
|
|
|
|
|
self.ribbon.align_right_btn.clicked.connect(self.on_align_right_clicked)
|
|
|
|
|
if hasattr(self.ribbon, 'align_justify_btn'):
|
|
|
|
|
self.ribbon.align_justify_btn.clicked.connect(self.on_align_justify_clicked)
|
|
|
|
|
|
|
|
|
|
# 天气功能信号
|
|
|
|
|
if hasattr(self.ribbon, 'city_combo'):
|
|
|
|
|
self.ribbon.city_combo.currentTextChanged.connect(self.on_city_changed)
|
|
|
|
|
@ -1291,10 +1346,144 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
if cursor.hasSelection():
|
|
|
|
|
self.status_bar.showMessage("字体颜色已设置,新输入的文本将使用该颜色", 2000)
|
|
|
|
|
|
|
|
|
|
def on_heading1_clicked(self):
|
|
|
|
|
"""一级标题按钮点击处理"""
|
|
|
|
|
self.apply_heading_style(1)
|
|
|
|
|
|
|
|
|
|
def on_heading2_clicked(self):
|
|
|
|
|
"""二级标题按钮点击处理"""
|
|
|
|
|
self.apply_heading_style(2)
|
|
|
|
|
|
|
|
|
|
def on_heading3_clicked(self):
|
|
|
|
|
"""三级标题按钮点击处理"""
|
|
|
|
|
self.apply_heading_style(3)
|
|
|
|
|
|
|
|
|
|
def on_heading4_clicked(self):
|
|
|
|
|
"""四级标题按钮点击处理"""
|
|
|
|
|
self.apply_heading_style(4)
|
|
|
|
|
|
|
|
|
|
def on_body_text_clicked(self):
|
|
|
|
|
"""正文按钮点击处理"""
|
|
|
|
|
self.apply_body_text_style()
|
|
|
|
|
|
|
|
|
|
def on_align_left_clicked(self):
|
|
|
|
|
"""左对齐按钮点击处理"""
|
|
|
|
|
self.apply_alignment(Qt.AlignLeft)
|
|
|
|
|
|
|
|
|
|
def on_align_center_clicked(self):
|
|
|
|
|
"""居中对齐按钮点击处理"""
|
|
|
|
|
self.apply_alignment(Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
def on_align_right_clicked(self):
|
|
|
|
|
"""右对齐按钮点击处理"""
|
|
|
|
|
self.apply_alignment(Qt.AlignRight)
|
|
|
|
|
|
|
|
|
|
def on_align_justify_clicked(self):
|
|
|
|
|
"""两端对齐按钮点击处理"""
|
|
|
|
|
self.apply_alignment(Qt.AlignJustify)
|
|
|
|
|
|
|
|
|
|
def apply_heading_style(self, level):
|
|
|
|
|
"""应用标题样式"""
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
|
|
|
|
|
# 创建字符格式
|
|
|
|
|
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', '未知城市')
|
|
|
|
|
@ -1318,6 +1507,32 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
weather_message = f"{city}: {desc}, {temp}°C{temp_range}"
|
|
|
|
|
self.status_bar.showMessage(weather_message, 5000)
|
|
|
|
|
|
|
|
|
|
# 更新工具栏天气图标和温度显示
|
|
|
|
|
if hasattr(self, 'ribbon'):
|
|
|
|
|
# 更新天气图标
|
|
|
|
|
if hasattr(self.ribbon, 'weather_icon_label') and desc != 'N/A':
|
|
|
|
|
emoji = self.ribbon.get_weather_emoji(desc)
|
|
|
|
|
self.ribbon.weather_icon_label.setText(emoji)
|
|
|
|
|
|
|
|
|
|
# 更新温度显示
|
|
|
|
|
if hasattr(self.ribbon, 'weather_temp_label') and temp != 'N/A':
|
|
|
|
|
# 计算平均温度(使用最高温和最低温的平均值)
|
|
|
|
|
avg_temp = temp
|
|
|
|
|
if 'forecast' in weather_data and weather_data['forecast']:
|
|
|
|
|
forecast_data = weather_data['forecast'][0]
|
|
|
|
|
if isinstance(forecast_data, dict):
|
|
|
|
|
temp_max = forecast_data.get('temp_max', 'N/A')
|
|
|
|
|
temp_min = forecast_data.get('temp_min', 'N/A')
|
|
|
|
|
if temp_max != 'N/A' and temp_min != 'N/A':
|
|
|
|
|
try:
|
|
|
|
|
avg_temp = (float(temp_max) + float(temp_min)) / 2
|
|
|
|
|
avg_temp = round(avg_temp, 1)
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
avg_temp = temp
|
|
|
|
|
|
|
|
|
|
temp_str = f"{avg_temp}°C" if isinstance(avg_temp, (int, float)) else f"{temp}°C"
|
|
|
|
|
self.ribbon.weather_temp_label.setText(temp_str)
|
|
|
|
|
|
|
|
|
|
# 存储天气数据供其他功能使用(确保包含生活提示)
|
|
|
|
|
self.current_weather_data = weather_data
|
|
|
|
|
print(f"update_weather_display - 存储的current_weather_data包含life_tips: {self.current_weather_data.get('life_tips', [])}")
|
|
|
|
|
@ -1581,7 +1796,7 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
"""导入文件 - 仅在导入时存储内容,不立即显示"""
|
|
|
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
|
|
|
self, "导入文件", "",
|
|
|
|
|
"文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)"
|
|
|
|
|
"文档文件 (*.docx *.txt *.pdf *.html);;所有文件 (*.*)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if file_path:
|
|
|
|
|
@ -1646,7 +1861,7 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
# 提取并显示图片(如果有)
|
|
|
|
|
if images:
|
|
|
|
|
self.extract_and_display_images(content, images)
|
|
|
|
|
self.extract_and_display_images(file_path=None, images=images)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# 转换失败,显示错误信息
|
|
|
|
|
@ -2697,6 +2912,79 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 这个方法现在不需要了,因为图片会直接插入到文本中
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def insert_image_in_typing_mode(self):
|
|
|
|
|
"""在打字模式下插入图片"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查当前是否在打字模式下
|
|
|
|
|
if self.view_mode != "typing":
|
|
|
|
|
self.status_bar.showMessage("请在打字模式下使用插入图片功能", 3000)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 打开文件对话框选择图片
|
|
|
|
|
from PyQt5.QtWidgets import QFileDialog
|
|
|
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
|
|
|
self,
|
|
|
|
|
"选择图片文件",
|
|
|
|
|
"",
|
|
|
|
|
"图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.ico)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not file_path:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 加载图片文件
|
|
|
|
|
pixmap = QPixmap(file_path)
|
|
|
|
|
if pixmap.isNull():
|
|
|
|
|
self.status_bar.showMessage("无法加载图片文件", 3000)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取当前光标位置
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
|
|
|
|
|
# 创建图片格式
|
|
|
|
|
image_format = QTextImageFormat()
|
|
|
|
|
|
|
|
|
|
# 调整图片大小
|
|
|
|
|
scaled_pixmap = pixmap.scaled(200, 150, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
|
|
|
|
|
|
|
|
# 将图片保存到临时文件
|
|
|
|
|
import tempfile
|
|
|
|
|
import os
|
|
|
|
|
temp_dir = tempfile.gettempdir()
|
|
|
|
|
filename = os.path.basename(file_path)
|
|
|
|
|
safe_filename = "".join(c for c in filename if c.isalnum() or c in ('.', '_', '-'))
|
|
|
|
|
temp_file = os.path.join(temp_dir, safe_filename)
|
|
|
|
|
|
|
|
|
|
if scaled_pixmap.save(temp_file):
|
|
|
|
|
# 设置图片格式
|
|
|
|
|
image_format.setName(temp_file)
|
|
|
|
|
image_format.setWidth(200)
|
|
|
|
|
image_format.setHeight(150)
|
|
|
|
|
|
|
|
|
|
# 在光标位置插入图片
|
|
|
|
|
cursor.insertImage(image_format)
|
|
|
|
|
|
|
|
|
|
# 在图片后插入一个空格,让文字继续
|
|
|
|
|
cursor.insertText(" ")
|
|
|
|
|
|
|
|
|
|
# 标记文档为已修改
|
|
|
|
|
if not self.is_modified:
|
|
|
|
|
self.is_modified = True
|
|
|
|
|
self.update_window_title()
|
|
|
|
|
|
|
|
|
|
# 显示成功消息
|
|
|
|
|
self.status_bar.showMessage(f"图片已插入: {filename}", 3000)
|
|
|
|
|
|
|
|
|
|
# 添加到临时文件列表以便清理
|
|
|
|
|
self.temp_files.append(temp_file)
|
|
|
|
|
else:
|
|
|
|
|
self.status_bar.showMessage("保存临时图片文件失败", 3000)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.status_bar.showMessage(f"插入图片失败: {str(e)}", 3000)
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
"""关闭事件处理"""
|
|
|
|
|
# 清理临时文件
|
|
|
|
|
@ -2731,11 +3019,310 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
print(f"删除临时文件失败 {temp_file}: {str(e)}")
|
|
|
|
|
self.temp_files.clear()
|
|
|
|
|
|
|
|
|
|
def extract_and_display_images(self, file_path):
|
|
|
|
|
def export_as_html(self):
|
|
|
|
|
"""导出为HTML"""
|
|
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
|
|
self, "导出为HTML", "", "HTML文件 (*.html);;所有文件 (*.*)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if file_path:
|
|
|
|
|
try:
|
|
|
|
|
# 获取当前文本内容
|
|
|
|
|
content = self.text_edit.toPlainText()
|
|
|
|
|
|
|
|
|
|
# 处理图片标签
|
|
|
|
|
html_body = ""
|
|
|
|
|
lines = content.split('\n')
|
|
|
|
|
|
|
|
|
|
for line in lines:
|
|
|
|
|
if line.strip().startswith('[图片:') and line.strip().endswith(']'):
|
|
|
|
|
# 提取图片文件名
|
|
|
|
|
img_name = line.strip()[4:-1].strip()
|
|
|
|
|
|
|
|
|
|
# 查找对应的图片数据
|
|
|
|
|
img_data = None
|
|
|
|
|
for filename, image_data in self.extracted_images:
|
|
|
|
|
if filename == img_name:
|
|
|
|
|
img_data = image_data
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if img_data:
|
|
|
|
|
# 创建图片的base64编码
|
|
|
|
|
import base64
|
|
|
|
|
img_base64 = base64.b64encode(img_data).decode('utf-8')
|
|
|
|
|
|
|
|
|
|
# 检测图片类型
|
|
|
|
|
if img_name.lower().endswith('.png'):
|
|
|
|
|
img_type = 'png'
|
|
|
|
|
elif img_name.lower().endswith(('.jpg', '.jpeg')):
|
|
|
|
|
img_type = 'jpeg'
|
|
|
|
|
elif img_name.lower().endswith('.gif'):
|
|
|
|
|
img_type = 'gif'
|
|
|
|
|
else:
|
|
|
|
|
img_type = 'png' # 默认
|
|
|
|
|
|
|
|
|
|
html_body += f'<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;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
|
|
|
}}
|
|
|
|
|
.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:
|
|
|
|
|
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:
|
|
|
|
|
# 创建临时图片文件
|
|
|
|
|
import tempfile
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
# 检测图片类型
|
|
|
|
|
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:
|
|
|
|
|
# 提取图片
|
|
|
|
|
images = FileParser.extract_images_from_docx(file_path)
|
|
|
|
|
# 如果没有提供图片数据,则从文件中提取
|
|
|
|
|
if images is None:
|
|
|
|
|
if file_path is None:
|
|
|
|
|
return
|
|
|
|
|
# 提取图片
|
|
|
|
|
images = FileParser.extract_images_from_docx(file_path)
|
|
|
|
|
|
|
|
|
|
if not images:
|
|
|
|
|
return
|
|
|
|
|
@ -2828,6 +3415,40 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.status_bar.showMessage(f"提取图片失败: {str(e)}", 3000)
|
|
|
|
|
|
|
|
|
|
def update_format_buttons(self):
|
|
|
|
|
"""更新格式按钮的状态,根据当前光标位置的格式"""
|
|
|
|
|
try:
|
|
|
|
|
# 获取当前光标位置的字符格式
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
char_format = cursor.charFormat()
|
|
|
|
|
block_format = cursor.blockFormat()
|
|
|
|
|
|
|
|
|
|
# 更新粗体按钮状态
|
|
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'bold_btn'):
|
|
|
|
|
is_bold = char_format.font().weight() == QFont.Bold
|
|
|
|
|
self.ribbon.bold_btn.setChecked(is_bold)
|
|
|
|
|
|
|
|
|
|
# 更新斜体按钮状态
|
|
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'italic_btn'):
|
|
|
|
|
is_italic = char_format.font().italic()
|
|
|
|
|
self.ribbon.italic_btn.setChecked(is_italic)
|
|
|
|
|
|
|
|
|
|
# 更新下划线按钮状态
|
|
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'underline_btn'):
|
|
|
|
|
is_underline = char_format.font().underline()
|
|
|
|
|
self.ribbon.underline_btn.setChecked(is_underline)
|
|
|
|
|
|
|
|
|
|
# 更新对齐按钮状态
|
|
|
|
|
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'align_left_btn'):
|
|
|
|
|
alignment = block_format.alignment()
|
|
|
|
|
self.ribbon.align_left_btn.setChecked(alignment == Qt.AlignLeft)
|
|
|
|
|
self.ribbon.align_center_btn.setChecked(alignment == Qt.AlignCenter)
|
|
|
|
|
self.ribbon.align_right_btn.setChecked(alignment == Qt.AlignRight)
|
|
|
|
|
self.ribbon.align_justify_btn.setChecked(alignment == Qt.AlignJustify)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"更新格式按钮状态时出错: {e}")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
|
|