|
|
|
|
@ -4,9 +4,10 @@ import os
|
|
|
|
|
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
|
|
|
QTextEdit, QLabel, QSplitter, QFrame, QMenuBar,
|
|
|
|
|
QAction, QFileDialog, QMessageBox, QApplication,
|
|
|
|
|
QDialog, QLineEdit, QCheckBox, QPushButton)
|
|
|
|
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect
|
|
|
|
|
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor, QTextDocument
|
|
|
|
|
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 ui.word_style_ui import (WordRibbon, WordStatusBar, WordTextEdit,
|
|
|
|
|
WordStyleToolBar)
|
|
|
|
|
@ -66,6 +67,21 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
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.unified_document_content = "" # 统一文档内容
|
|
|
|
|
self.last_edit_mode = "typing" # 上次编辑模式
|
|
|
|
|
|
|
|
|
|
# 初始化网络服务和WeatherAPI
|
|
|
|
|
self.network_service = NetworkService()
|
|
|
|
|
@ -342,6 +358,25 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
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('附加工具')
|
|
|
|
|
|
|
|
|
|
@ -425,6 +460,30 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
self.text_edit.setPlainText("在此输入您的内容...")
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
scroll_area.setWidget(document_container)
|
|
|
|
|
@ -480,7 +539,212 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
self.ribbon.refresh_weather_btn.clicked.connect(self.refresh_weather)
|
|
|
|
|
|
|
|
|
|
def on_text_changed(self):
|
|
|
|
|
"""文本变化处理 - 逐步显示模式"""
|
|
|
|
|
"""文本变化处理 - 根据视图模式处理文本变化"""
|
|
|
|
|
# 如果正在加载文件,跳过处理
|
|
|
|
|
if self.is_loading_file:
|
|
|
|
|
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":
|
|
|
|
|
# 打字模式:可以自由打字
|
|
|
|
|
self.handle_typing_mode_typing()
|
|
|
|
|
|
|
|
|
|
# 标记文档为已修改
|
|
|
|
|
if not self.is_modified:
|
|
|
|
|
self.is_modified = True
|
|
|
|
|
self.update_window_title()
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
# 计算应该显示的字符数(基于当前已显示的字符数)
|
|
|
|
|
chars_to_show = min(self.displayed_chars + 1, len(self.imported_content))
|
|
|
|
|
|
|
|
|
|
# 更新已显示字符数
|
|
|
|
|
self.displayed_chars = chars_to_show
|
|
|
|
|
|
|
|
|
|
# 保存当前学习进度,以便在模式切换时恢复
|
|
|
|
|
self.learning_progress = 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', '')
|
|
|
|
|
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', '')
|
|
|
|
|
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', '')
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
def on_text_changed_original(self):
|
|
|
|
|
"""文本变化处理 - 支持逐步显示模式和自由打字模式"""
|
|
|
|
|
# 如果正在加载文件,跳过处理
|
|
|
|
|
if self.is_loading_file:
|
|
|
|
|
return
|
|
|
|
|
@ -506,17 +770,32 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 临时禁用文本变化信号,避免递归
|
|
|
|
|
self.text_edit.textChanged.disconnect(self.on_text_changed)
|
|
|
|
|
|
|
|
|
|
# 更新文本编辑器内容
|
|
|
|
|
self.text_edit.setPlainText(display_text)
|
|
|
|
|
# 保存当前光标位置
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
original_position = cursor.position()
|
|
|
|
|
|
|
|
|
|
# 重新连接文本变化信号
|
|
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
|
|
# 只添加新字符,而不是重置整个文本
|
|
|
|
|
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 = self.text_edit.textCursor()
|
|
|
|
|
cursor.movePosition(cursor.End)
|
|
|
|
|
# 恢复光标位置
|
|
|
|
|
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)
|
|
|
|
|
@ -532,6 +811,9 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
stats = self.typing_logic.get_statistics()
|
|
|
|
|
self.update_status_bar(stats)
|
|
|
|
|
|
|
|
|
|
# 检查当前位置是否有图片
|
|
|
|
|
self.check_and_show_image_at_position(self.displayed_chars)
|
|
|
|
|
|
|
|
|
|
# 检查是否完成
|
|
|
|
|
if self.displayed_chars >= len(self.imported_content):
|
|
|
|
|
self.on_lesson_complete()
|
|
|
|
|
@ -540,6 +822,23 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 更新状态栏显示进度
|
|
|
|
|
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:
|
|
|
|
|
@ -849,17 +1148,29 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
# 根据当前模式重置打字逻辑
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset()
|
|
|
|
|
if self.view_mode == "learning":
|
|
|
|
|
# 学习模式:重置为默认内容
|
|
|
|
|
self.typing_logic.reset("欢迎使用MagicWord隐私学习软件!\n\n这是一个仿Microsoft Word界面的学习工具。")
|
|
|
|
|
self.imported_content = ""
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
self.status_bar.showMessage("新建文档 - 学习模式,请先导入文件开始打字学习", 3000)
|
|
|
|
|
elif self.view_mode == "typing":
|
|
|
|
|
# 打字模式:重置为默认内容,允许自由打字
|
|
|
|
|
self.typing_logic.reset("欢迎使用MagicWord隐私学习软件!\n\n这是一个仿Microsoft Word界面的学习工具。")
|
|
|
|
|
self.imported_content = ""
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
self.status_bar.showMessage("新建文档 - 打字模式,可以自由开始打字", 3000)
|
|
|
|
|
|
|
|
|
|
def open_file(self):
|
|
|
|
|
"""打开文件并设置为打字学习内容 - 逐步显示模式"""
|
|
|
|
|
"""打开文件 - 创建空白副本并在学习模式下显示导入内容"""
|
|
|
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
|
|
|
self, "打开文件", "",
|
|
|
|
|
"文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)"
|
|
|
|
|
@ -879,13 +1190,24 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
self.imported_content = content
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
|
|
|
|
# 设置学习内容到打字逻辑
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset(content) # 重置打字状态并设置新内容
|
|
|
|
|
|
|
|
|
|
# 清空文本编辑器,准备逐步显示
|
|
|
|
|
# 创建空白副本 - 清空文本编辑器
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
elif self.view_mode == "typing":
|
|
|
|
|
# 打字模式:也设置内容,但允许自由打字
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset("") # 重置打字状态,但内容为空,允许自由打字
|
|
|
|
|
|
|
|
|
|
self.status_bar.showMessage(f"已创建空白副本,可以自由打字", 5000)
|
|
|
|
|
|
|
|
|
|
# 清除文件加载标志
|
|
|
|
|
self.is_loading_file = False
|
|
|
|
|
|
|
|
|
|
@ -894,12 +1216,13 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
self.is_modified = False
|
|
|
|
|
self.update_window_title()
|
|
|
|
|
|
|
|
|
|
# 更新状态栏
|
|
|
|
|
self.status_bar.showMessage(f"已打开学习文件: {os.path.basename(file_path)},开始打字逐步显示学习内容!", 5000)
|
|
|
|
|
|
|
|
|
|
# 更新字数统计
|
|
|
|
|
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, "警告", "无法读取文件内容或文件为空")
|
|
|
|
|
@ -995,6 +1318,122 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
# 这里可以实现打印布局的逻辑
|
|
|
|
|
self.status_bar.showMessage("打印布局功能开发中...", 3000)
|
|
|
|
|
|
|
|
|
|
def set_view_mode(self, mode):
|
|
|
|
|
"""设置视图模式 - 实现文档A和学习文档C的正确交互"""
|
|
|
|
|
if mode not in ["typing", "learning"]:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 保存当前模式的内容
|
|
|
|
|
current_content = self.text_edit.toPlainText()
|
|
|
|
|
|
|
|
|
|
# 根据当前模式保存特定信息
|
|
|
|
|
if self.view_mode == "typing":
|
|
|
|
|
# 打字模式:保存打字内容到文档A
|
|
|
|
|
self.typing_mode_content = current_content
|
|
|
|
|
print(f"保存打字模式内容到文档A,长度: {len(self.typing_mode_content)}")
|
|
|
|
|
elif self.view_mode == "learning":
|
|
|
|
|
# 学习模式:保存当前学习进度和内容
|
|
|
|
|
self.learning_text = current_content
|
|
|
|
|
if hasattr(self, 'displayed_chars') and self.imported_content:
|
|
|
|
|
self.learning_progress = self.displayed_chars
|
|
|
|
|
print(f"保存学习模式内容,学习进度: {self.learning_progress}/{len(self.imported_content) if self.imported_content else 0}")
|
|
|
|
|
|
|
|
|
|
# 更新模式
|
|
|
|
|
self.view_mode = mode
|
|
|
|
|
self.last_edit_mode = mode
|
|
|
|
|
|
|
|
|
|
# 更新菜单项状态
|
|
|
|
|
self.typing_mode_action.setChecked(mode == "typing")
|
|
|
|
|
self.learning_mode_action.setChecked(mode == "learning")
|
|
|
|
|
|
|
|
|
|
# 临时禁用文本变化信号,避免递归
|
|
|
|
|
self.text_edit.textChanged.disconnect(self.on_text_changed)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if mode == "typing":
|
|
|
|
|
# 打字模式:显示文档A的内容
|
|
|
|
|
self.status_bar.showMessage("切换到打字模式 - 显示文档A内容", 3000)
|
|
|
|
|
|
|
|
|
|
# 设置文档A的内容
|
|
|
|
|
self.text_edit.clear()
|
|
|
|
|
if self.typing_mode_content:
|
|
|
|
|
self.text_edit.setPlainText(self.typing_mode_content)
|
|
|
|
|
|
|
|
|
|
# 设置光标位置到文档末尾
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
cursor.movePosition(cursor.End)
|
|
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
|
|
|
|
# 重置打字逻辑,准备接受新的输入
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset("")
|
|
|
|
|
|
|
|
|
|
# 重置显示字符计数
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
|
|
|
|
# 重置图片插入记录
|
|
|
|
|
if hasattr(self, 'inserted_images'):
|
|
|
|
|
self.inserted_images.clear()
|
|
|
|
|
|
|
|
|
|
elif mode == "learning":
|
|
|
|
|
# 学习模式:从上次中断的地方继续显示学习文档C的内容
|
|
|
|
|
if not self.imported_content:
|
|
|
|
|
self.status_bar.showMessage("学习模式需要导入文件 - 请先打开一个文件", 3000)
|
|
|
|
|
# 清空文本编辑器,等待导入文件
|
|
|
|
|
self.text_edit.clear()
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset("在此输入您的内容...")
|
|
|
|
|
# 重置显示字符计数
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
# 重置图片插入记录
|
|
|
|
|
if hasattr(self, 'inserted_images'):
|
|
|
|
|
self.inserted_images.clear()
|
|
|
|
|
else:
|
|
|
|
|
# 检查是否有保存的学习进度
|
|
|
|
|
if hasattr(self, 'learning_progress') and self.learning_progress > 0:
|
|
|
|
|
self.status_bar.showMessage(f"切换到学习模式 - 从上次中断处继续 ({self.learning_progress}/{len(self.imported_content)})", 3000)
|
|
|
|
|
# 恢复学习进度
|
|
|
|
|
self.displayed_chars = self.learning_progress
|
|
|
|
|
|
|
|
|
|
# 重置打字逻辑,准备接受导入内容
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset(self.imported_content)
|
|
|
|
|
|
|
|
|
|
# 获取应该显示的学习文档C的内容部分
|
|
|
|
|
display_text = self.imported_content[:self.displayed_chars]
|
|
|
|
|
|
|
|
|
|
# 设置文本内容
|
|
|
|
|
self.text_edit.clear()
|
|
|
|
|
self.text_edit.setPlainText(display_text)
|
|
|
|
|
|
|
|
|
|
# 设置光标位置到文本末尾
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
cursor.movePosition(cursor.End)
|
|
|
|
|
self.text_edit.setTextCursor(cursor)
|
|
|
|
|
|
|
|
|
|
# 重新插入图片
|
|
|
|
|
if hasattr(self, 'inserted_images'):
|
|
|
|
|
self.inserted_images.clear()
|
|
|
|
|
self.insert_images_in_text()
|
|
|
|
|
else:
|
|
|
|
|
self.status_bar.showMessage("切换到学习模式 - 准备显示学习文档C内容", 3000)
|
|
|
|
|
# 重置打字逻辑,准备接受导入内容
|
|
|
|
|
if self.typing_logic:
|
|
|
|
|
self.typing_logic.reset(self.imported_content)
|
|
|
|
|
|
|
|
|
|
# 重置显示字符计数
|
|
|
|
|
self.displayed_chars = 0
|
|
|
|
|
|
|
|
|
|
# 清空文本编辑器,等待用户开始打字
|
|
|
|
|
self.text_edit.clear()
|
|
|
|
|
|
|
|
|
|
# 重置图片插入记录
|
|
|
|
|
if hasattr(self, 'inserted_images'):
|
|
|
|
|
self.inserted_images.clear()
|
|
|
|
|
finally:
|
|
|
|
|
# 重新连接文本变化信号
|
|
|
|
|
self.text_edit.textChanged.connect(self.on_text_changed)
|
|
|
|
|
|
|
|
|
|
def set_page_color(self, color):
|
|
|
|
|
"""设置页面颜色"""
|
|
|
|
|
color_map = {
|
|
|
|
|
@ -1357,6 +1796,156 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
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 show_image_viewer(self, filename, image_data):
|
|
|
|
|
"""显示图片查看器"""
|
|
|
|
|
try:
|
|
|
|
|
# 创建图片查看窗口
|
|
|
|
|
viewer = QDialog(self)
|
|
|
|
|
viewer.setWindowTitle(f"图片查看 - {filename}")
|
|
|
|
|
viewer.setModal(False)
|
|
|
|
|
viewer.resize(800, 600)
|
|
|
|
|
|
|
|
|
|
# 创建布局
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
|
|
|
|
# 创建滚动区域
|
|
|
|
|
scroll_area = QScrollArea()
|
|
|
|
|
scroll_area.setWidgetResizable(True)
|
|
|
|
|
|
|
|
|
|
# 创建图片标签
|
|
|
|
|
image_label = QLabel()
|
|
|
|
|
image_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
# 加载图片
|
|
|
|
|
pixmap = QPixmap()
|
|
|
|
|
if pixmap.loadFromData(image_data):
|
|
|
|
|
image_label.setPixmap(pixmap)
|
|
|
|
|
image_label.setScaledContents(False)
|
|
|
|
|
|
|
|
|
|
# 设置标签大小为图片实际大小
|
|
|
|
|
image_label.setFixedSize(pixmap.size())
|
|
|
|
|
else:
|
|
|
|
|
image_label.setText("无法加载图片")
|
|
|
|
|
|
|
|
|
|
scroll_area.setWidget(image_label)
|
|
|
|
|
layout.addWidget(scroll_area)
|
|
|
|
|
|
|
|
|
|
# 添加关闭按钮
|
|
|
|
|
close_btn = QPushButton("关闭")
|
|
|
|
|
close_btn.clicked.connect(viewer.close)
|
|
|
|
|
layout.addWidget(close_btn)
|
|
|
|
|
|
|
|
|
|
viewer.setLayout(layout)
|
|
|
|
|
viewer.show()
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.status_bar.showMessage(f"创建图片查看器失败: {str(e)}", 3000)
|
|
|
|
|
|
|
|
|
|
def insert_images_in_text(self):
|
|
|
|
|
"""在文本中插入图片 - 修复图片显示逻辑"""
|
|
|
|
|
try:
|
|
|
|
|
if not self.typing_logic or not hasattr(self.typing_logic, 'image_positions'):
|
|
|
|
|
print("打字逻辑或图片位置信息不存在")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 检查是否已经插入过图片(避免重复插入)
|
|
|
|
|
if not hasattr(self, 'inserted_images'):
|
|
|
|
|
self.inserted_images = set()
|
|
|
|
|
|
|
|
|
|
# 获取当前显示的文本
|
|
|
|
|
current_text = self.text_edit.toPlainText()
|
|
|
|
|
current_length = len(current_text)
|
|
|
|
|
|
|
|
|
|
# 检查当前显示位置是否有图片需要插入
|
|
|
|
|
for image_info in self.typing_logic.image_positions:
|
|
|
|
|
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'] and current_length >= image_info['start_pos']:
|
|
|
|
|
# 在图片位置插入图片
|
|
|
|
|
cursor = self.text_edit.textCursor()
|
|
|
|
|
|
|
|
|
|
# 计算图片应该插入的位置(相对于当前文本)
|
|
|
|
|
insert_position = min(image_info['start_pos'], current_length)
|
|
|
|
|
|
|
|
|
|
# 确保插入位置有效
|
|
|
|
|
if insert_position >= 0 and insert_position <= current_length:
|
|
|
|
|
cursor.setPosition(insert_position)
|
|
|
|
|
|
|
|
|
|
# 创建图片格式
|
|
|
|
|
image_format = QTextImageFormat()
|
|
|
|
|
|
|
|
|
|
# 加载图片数据
|
|
|
|
|
pixmap = QPixmap()
|
|
|
|
|
if pixmap.loadFromData(image_info['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 closeEvent(self, event):
|
|
|
|
|
"""关闭事件处理"""
|
|
|
|
|
if self.is_modified:
|
|
|
|
|
@ -1375,6 +1964,77 @@ class WordStyleMainWindow(QMainWindow):
|
|
|
|
|
event.ignore()
|
|
|
|
|
else:
|
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
|
|
def extract_and_display_images(self, file_path):
|
|
|
|
|
"""提取并显示Word文档中的图片 - 修复图片位置计算"""
|
|
|
|
|
try:
|
|
|
|
|
# 提取图片
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 为每张图片创建位置信息 - 更合理的分布
|
|
|
|
|
if len(images) == 1:
|
|
|
|
|
# 只有一张图片,放在文档中间
|
|
|
|
|
start_pos = len(self.imported_content) // 2
|
|
|
|
|
else:
|
|
|
|
|
# 多张图片,均匀分布
|
|
|
|
|
start_pos = (len(self.imported_content) * (index + 1)) // (len(images) + 1)
|
|
|
|
|
|
|
|
|
|
end_pos = min(start_pos + 50, len(self.imported_content))
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 更新状态栏
|
|
|
|
|
self.status_bar.showMessage(f"已提取 {len(images)} 张图片,双击查看大图", 5000)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.status_bar.showMessage(f"提取图片失败: {str(e)}", 3000)
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
|
|