|
|
# learning_mode_window.py
|
|
|
import sys
|
|
|
import os
|
|
|
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
|
QTextEdit, QLabel, QFrame, QMenuBar,
|
|
|
QAction, QFileDialog, QMessageBox, QApplication,
|
|
|
QSplitter, QScrollArea, QStatusBar, QProgressBar, QTextBrowser, QSizePolicy,
|
|
|
QListWidget, QListWidgetItem, QDialog, QGraphicsScene, QGraphicsView)
|
|
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QRect
|
|
|
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QTextCharFormat, QTextCursor
|
|
|
|
|
|
# 修复导入路径
|
|
|
from ui.components import CustomTitleBar, TextDisplayWidget
|
|
|
from typing_logic import TypingLogic
|
|
|
from file_parser import FileParser
|
|
|
from ui.theme_manager import theme_manager
|
|
|
import tempfile
|
|
|
import hashlib
|
|
|
|
|
|
class LearningModeWindow(QMainWindow):
|
|
|
# 定义内容变化信号
|
|
|
content_changed = pyqtSignal(str, int) # 参数:内容,位置
|
|
|
# 定义关闭信号
|
|
|
closed = pyqtSignal()
|
|
|
def __init__(self, parent=None, imported_content="", current_position=0, image_data=None, image_positions=None):
|
|
|
"""
|
|
|
学习模式窗口
|
|
|
- 顶部显示UI.png图片
|
|
|
- 下方显示输入字符页面
|
|
|
- 输入字符显示导入的文件内容
|
|
|
|
|
|
参数:
|
|
|
parent: 父窗口
|
|
|
imported_content: 从主窗口传递的导入内容
|
|
|
current_position: 当前学习进度位置
|
|
|
image_data: 图片数据字典 {文件名: 二进制数据}
|
|
|
image_positions: 图片位置信息列表
|
|
|
"""
|
|
|
super().__init__(parent)
|
|
|
self.parent_window = parent
|
|
|
self.imported_content = imported_content
|
|
|
self.current_position = current_position
|
|
|
self.image_data = image_data or {}
|
|
|
self.image_positions = image_positions or []
|
|
|
self.typing_logic = None
|
|
|
self.is_loading_file = False
|
|
|
self.extracted_images = [] # 用于存储提取的图片数据
|
|
|
|
|
|
# 初始化UI
|
|
|
self.initUI()
|
|
|
|
|
|
# 初始化打字逻辑
|
|
|
self.init_typing_logic()
|
|
|
|
|
|
# 初始化同步位置跟踪
|
|
|
self.last_sync_position = current_position
|
|
|
|
|
|
# 如果有导入内容,初始化显示
|
|
|
if self.imported_content:
|
|
|
self.initialize_with_imported_content()
|
|
|
|
|
|
def initialize_with_imported_content(self):
|
|
|
"""
|
|
|
使用从主窗口传递的导入内容初始化学习模式窗口
|
|
|
"""
|
|
|
if not self.imported_content:
|
|
|
return
|
|
|
|
|
|
# 重置打字逻辑
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.reset(self.imported_content)
|
|
|
|
|
|
# 设置图片数据到打字逻辑
|
|
|
if self.image_data:
|
|
|
self.typing_logic.set_image_data(self.image_data)
|
|
|
if self.image_positions:
|
|
|
self.typing_logic.set_image_positions(self.image_positions)
|
|
|
|
|
|
# 显示已学习的内容
|
|
|
display_text = self.imported_content[:self.current_position]
|
|
|
self.text_display_widget.text_display.setPlainText(display_text)
|
|
|
|
|
|
# 更新状态
|
|
|
progress = (self.current_position / len(self.imported_content)) * 100
|
|
|
self.status_label.setText(f"继续学习 - 进度: {self.current_position}/{len(self.imported_content)} 字符")
|
|
|
self.progress_label.setText(f"进度: {progress:.1f}%")
|
|
|
|
|
|
# 设置光标位置到文本末尾
|
|
|
cursor = self.text_display_widget.text_display.textCursor()
|
|
|
cursor.movePosition(cursor.End)
|
|
|
self.text_display_widget.text_display.setTextCursor(cursor)
|
|
|
|
|
|
def initUI(self):
|
|
|
"""
|
|
|
初始化学习模式窗口UI
|
|
|
- 设置窗口标题和大小
|
|
|
- 创建顶部图片区域
|
|
|
- 创建下方输入区域
|
|
|
"""
|
|
|
# 设置窗口属性
|
|
|
self.setWindowTitle("学习模式 - MagicWord")
|
|
|
self.setGeometry(200, 200, 900, 700)
|
|
|
self.setWindowFlags(Qt.Window) # 独立窗口
|
|
|
|
|
|
# 创建中央widget
|
|
|
central_widget = QWidget()
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
# 创建主布局
|
|
|
main_layout = QVBoxLayout()
|
|
|
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
main_layout.setSpacing(0)
|
|
|
central_widget.setLayout(main_layout)
|
|
|
|
|
|
# 创建顶部图片区域
|
|
|
self.create_top_image_area(main_layout)
|
|
|
|
|
|
# 创建分隔线
|
|
|
separator = QFrame()
|
|
|
separator.setFrameShape(QFrame.HLine)
|
|
|
separator.setFrameShadow(QFrame.Sunken)
|
|
|
separator.setStyleSheet("background-color: #d0d0d0;")
|
|
|
main_layout.addWidget(separator)
|
|
|
|
|
|
# 创建输入区域
|
|
|
self.create_input_area(main_layout)
|
|
|
|
|
|
# 创建菜单栏
|
|
|
self.create_menu_bar()
|
|
|
|
|
|
# 创建状态栏
|
|
|
self.create_status_bar()
|
|
|
|
|
|
# 如果有导入内容,更新状态栏显示
|
|
|
if self.imported_content:
|
|
|
progress = (self.current_position / len(self.imported_content)) * 100
|
|
|
self.status_label.setText(f"继续学习 - 进度: {self.current_position}/{len(self.imported_content)} 字符")
|
|
|
self.progress_label.setText(f"进度: {progress:.1f}%")
|
|
|
|
|
|
def create_top_image_area(self, main_layout):
|
|
|
"""
|
|
|
创建顶部图片区域
|
|
|
- 加载并显示UI.png图片,完全铺满窗口上方
|
|
|
- 窗口大小自动适配图片大小
|
|
|
"""
|
|
|
# 创建图片标签
|
|
|
self.image_label = QLabel()
|
|
|
self.image_label.setAlignment(Qt.AlignCenter)
|
|
|
self.image_label.setStyleSheet("""
|
|
|
QLabel {
|
|
|
background-color: #f8f9fa;
|
|
|
border: none;
|
|
|
margin: 0px;
|
|
|
padding: 0px;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 加载UI.png图片
|
|
|
ui_image_path = os.path.join(os.path.dirname(__file__), 'ui', 'UI.png')
|
|
|
if os.path.exists(ui_image_path):
|
|
|
pixmap = QPixmap(ui_image_path)
|
|
|
if not pixmap.isNull():
|
|
|
# 保存原始图片用于缩放计算
|
|
|
self.original_pixmap = pixmap
|
|
|
|
|
|
# 设置图片到标签
|
|
|
self.image_label.setPixmap(pixmap)
|
|
|
self.image_label.setScaledContents(True) # 关键:让图片缩放填充整个标签
|
|
|
|
|
|
# 设置大小策略
|
|
|
self.image_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
|
|
|
|
# 计算合适的最小高度(保持图片比例)
|
|
|
window_width = 900 # 默认窗口宽度
|
|
|
original_width = pixmap.width()
|
|
|
original_height = pixmap.height()
|
|
|
if original_width > 0:
|
|
|
min_height = int(window_width * original_height / original_width)
|
|
|
self.image_label.setMinimumHeight(min_height)
|
|
|
else:
|
|
|
self.image_label.setMinimumHeight(200)
|
|
|
else:
|
|
|
self.image_label.setText("UI图片加载失败")
|
|
|
self.image_label.setStyleSheet("""
|
|
|
QLabel {
|
|
|
background-color: #f8f9fa;
|
|
|
color: #666666;
|
|
|
font-size: 14px;
|
|
|
qproperty-alignment: AlignCenter;
|
|
|
}
|
|
|
""")
|
|
|
else:
|
|
|
self.image_label.setText("UI图片未找到")
|
|
|
self.image_label.setStyleSheet("""
|
|
|
QLabel {
|
|
|
background-color: #f8f9fa;
|
|
|
color: #666666;
|
|
|
font-size: 14px;
|
|
|
qproperty-alignment: AlignCenter;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 添加到主布局
|
|
|
main_layout.addWidget(self.image_label)
|
|
|
|
|
|
def resizeEvent(self, event):
|
|
|
"""
|
|
|
窗口大小改变事件
|
|
|
- 动态调整图片显示区域
|
|
|
"""
|
|
|
super().resizeEvent(event)
|
|
|
|
|
|
# 获取窗口宽度
|
|
|
window_width = self.width()
|
|
|
|
|
|
# 如果图片标签存在,调整其大小以适配窗口
|
|
|
if hasattr(self, 'image_label') and self.image_label:
|
|
|
# 设置图片标签的固定宽度为窗口宽度
|
|
|
self.image_label.setFixedWidth(window_width)
|
|
|
|
|
|
# 根据窗口宽度计算合适的高度(保持图片比例)
|
|
|
if hasattr(self, 'original_pixmap') and self.original_pixmap:
|
|
|
original_width = self.original_pixmap.width()
|
|
|
original_height = self.original_pixmap.height()
|
|
|
|
|
|
# 计算保持比例的高度
|
|
|
new_height = int(window_width * original_height / original_width)
|
|
|
self.image_label.setFixedHeight(new_height)
|
|
|
else:
|
|
|
# 如果没有原始图片,使用默认高度
|
|
|
self.image_label.setFixedHeight(300)
|
|
|
|
|
|
def create_input_area(self, main_layout):
|
|
|
"""
|
|
|
创建输入区域
|
|
|
- 创建文本显示组件
|
|
|
- 设置与主系统相同的样式
|
|
|
- 创建图片列表区域
|
|
|
"""
|
|
|
# 创建文本显示组件(复用主系统的组件)
|
|
|
self.text_display_widget = TextDisplayWidget(self)
|
|
|
|
|
|
# 设置样式,使其与主系统保持一致
|
|
|
self.text_display_widget.setStyleSheet("""
|
|
|
TextDisplayWidget {
|
|
|
background-color: white;
|
|
|
border: 1px solid #d0d0d0;
|
|
|
border-radius: 0px;
|
|
|
}
|
|
|
""")
|
|
|
|
|
|
# 连接文本变化信号
|
|
|
self.text_display_widget.text_display.textChanged.connect(self.on_text_changed)
|
|
|
|
|
|
# 创建图片显示区域
|
|
|
self.image_list_widget = QListWidget()
|
|
|
self.image_list_widget.setMaximumHeight(150)
|
|
|
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)
|
|
|
|
|
|
# 创建布局容器
|
|
|
input_container = QWidget()
|
|
|
input_layout = QVBoxLayout()
|
|
|
input_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
input_layout.setSpacing(5)
|
|
|
|
|
|
input_layout.addWidget(self.text_display_widget, 1) # 文本显示区域占据剩余空间
|
|
|
input_layout.addWidget(self.image_list_widget) # 图片列表区域
|
|
|
|
|
|
input_container.setLayout(input_layout)
|
|
|
|
|
|
main_layout.addWidget(input_container, 1) # 占据剩余空间
|
|
|
|
|
|
def create_menu_bar(self):
|
|
|
"""
|
|
|
创建菜单栏
|
|
|
- 文件菜单:导入、退出
|
|
|
- 帮助菜单:关于
|
|
|
"""
|
|
|
menu_bar = self.menuBar()
|
|
|
|
|
|
# 文件菜单
|
|
|
file_menu = menu_bar.addMenu('文件')
|
|
|
|
|
|
# 导入动作
|
|
|
import_action = QAction('导入文件', self)
|
|
|
import_action.setShortcut('Ctrl+O')
|
|
|
import_action.triggered.connect(self.import_file)
|
|
|
file_menu.addAction(import_action)
|
|
|
|
|
|
file_menu.addSeparator()
|
|
|
|
|
|
# 退出动作
|
|
|
exit_action = QAction('退出学习模式', self)
|
|
|
exit_action.setShortcut('Ctrl+Q')
|
|
|
exit_action.triggered.connect(self.close)
|
|
|
file_menu.addAction(exit_action)
|
|
|
|
|
|
# 帮助菜单
|
|
|
help_menu = menu_bar.addMenu('帮助')
|
|
|
|
|
|
about_action = QAction('关于', self)
|
|
|
about_action.triggered.connect(self.show_about)
|
|
|
help_menu.addAction(about_action)
|
|
|
|
|
|
def create_status_bar(self):
|
|
|
"""
|
|
|
创建状态栏
|
|
|
- 显示当前状态和学习进度
|
|
|
"""
|
|
|
self.status_bar = self.statusBar()
|
|
|
|
|
|
# 创建状态标签
|
|
|
self.status_label = QLabel("就绪 - 请先导入文件开始学习")
|
|
|
self.progress_label = QLabel("进度: 0%")
|
|
|
|
|
|
# 添加到状态栏
|
|
|
self.status_bar.addWidget(self.status_label)
|
|
|
self.status_bar.addPermanentWidget(self.progress_label)
|
|
|
|
|
|
def init_typing_logic(self):
|
|
|
"""
|
|
|
初始化打字逻辑
|
|
|
- 创建打字逻辑实例
|
|
|
- 设置默认内容
|
|
|
"""
|
|
|
# 创建打字逻辑实例
|
|
|
self.typing_logic = TypingLogic("欢迎使用学习模式!\n\n请先导入文件开始打字学习。")
|
|
|
|
|
|
# 设置文本显示组件的打字逻辑
|
|
|
if hasattr(self.text_display_widget, 'set_typing_logic'):
|
|
|
self.text_display_widget.set_typing_logic(self.typing_logic)
|
|
|
|
|
|
def import_file(self):
|
|
|
"""
|
|
|
导入文件
|
|
|
- 打开文件对话框选择文件
|
|
|
- 解析文件内容
|
|
|
- 重置打字逻辑
|
|
|
"""
|
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
|
self, "导入学习文件", "",
|
|
|
"文档文件 (*.docx *.txt *.pdf);;所有文件 (*.*)"
|
|
|
)
|
|
|
|
|
|
if file_path:
|
|
|
self.is_loading_file = True
|
|
|
try:
|
|
|
# 获取文件扩展名
|
|
|
_, ext = os.path.splitext(file_path)
|
|
|
ext = ext.lower()
|
|
|
|
|
|
# 对于docx文件,直接解析而不转换为txt
|
|
|
if ext == '.docx':
|
|
|
# 直接解析docx文件内容
|
|
|
content = FileParser.parse_docx(file_path)
|
|
|
# 提取图片数据
|
|
|
images = FileParser.extract_images_from_docx(file_path)
|
|
|
else:
|
|
|
# 其他文件类型使用原来的转换方法
|
|
|
result = FileParser.parse_and_convert_to_txt(file_path)
|
|
|
content = result['content']
|
|
|
images = result.get('images', [])
|
|
|
|
|
|
if not content:
|
|
|
QMessageBox.warning(self, "导入失败", "文件内容为空或解析失败!")
|
|
|
return
|
|
|
|
|
|
# 保存导入的内容
|
|
|
self.imported_content = content
|
|
|
self.current_position = 0
|
|
|
|
|
|
# 保存提取的图片数据
|
|
|
self.extracted_images = images
|
|
|
|
|
|
# 设置打字逻辑
|
|
|
if self.typing_logic:
|
|
|
self.typing_logic.reset(content)
|
|
|
|
|
|
# 如果有图片,设置图片数据到打字逻辑
|
|
|
if images:
|
|
|
image_data_dict = {}
|
|
|
image_positions = []
|
|
|
|
|
|
# 为每张图片生成位置信息 - 改进位置计算逻辑
|
|
|
for i, (filename, image_data) in enumerate(images):
|
|
|
image_data_dict[filename] = image_data
|
|
|
|
|
|
# 改进图片位置计算,确保图片能在用户早期打字时显示
|
|
|
content_length = len(content)
|
|
|
if content_length == 0:
|
|
|
content_length = 1000 # 备用长度
|
|
|
|
|
|
if len(images) == 1:
|
|
|
# 只有一张图片,放在文档开始位置附近(前10%),确保用户能快速看到
|
|
|
image_pos = max(10, content_length // 10)
|
|
|
else:
|
|
|
# 多张图片:前几张放在较前位置,确保用户能看到
|
|
|
if i < 3:
|
|
|
# 前3张图片放在文档前30%
|
|
|
segment = content_length // 3
|
|
|
image_pos = max(10, segment * (i + 1) // 4)
|
|
|
else:
|
|
|
# 其余图片均匀分布
|
|
|
remaining_start = content_length // 2
|
|
|
remaining_index = i - 3
|
|
|
remaining_count = len(images) - 3
|
|
|
if remaining_count > 0:
|
|
|
segment = (content_length - remaining_start) // (remaining_count + 1)
|
|
|
image_pos = remaining_start + segment * (remaining_index + 1)
|
|
|
else:
|
|
|
image_pos = content_length // 2
|
|
|
|
|
|
image_positions.append({
|
|
|
'start_pos': image_pos,
|
|
|
'end_pos': image_pos + 50, # 图片占位符长度
|
|
|
'filename': filename
|
|
|
})
|
|
|
|
|
|
# 设置图片数据到打字逻辑
|
|
|
self.typing_logic.set_image_data(image_data_dict)
|
|
|
self.typing_logic.set_image_positions(image_positions)
|
|
|
|
|
|
# 显示图片列表
|
|
|
self.display_image_list(images)
|
|
|
|
|
|
# 显示初始内容(空)
|
|
|
self.text_display_widget.text_display.clear()
|
|
|
self.status_label.setText("已导入文件,请开始打字学习...")
|
|
|
self.progress_label.setText("进度: 0%")
|
|
|
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "导入错误", f"导入文件时发生错误:\n{str(e)}")
|
|
|
|
|
|
finally:
|
|
|
self.is_loading_file = False
|
|
|
|
|
|
def on_text_changed(self):
|
|
|
"""
|
|
|
文本变化处理
|
|
|
- 根据导入的内容逐步显示
|
|
|
- 更新学习进度
|
|
|
- 同步内容到打字模式
|
|
|
- 处理图片插入
|
|
|
"""
|
|
|
# 如果正在加载文件,跳过处理
|
|
|
if self.is_loading_file:
|
|
|
return
|
|
|
|
|
|
# 如果没有导入内容,清空文本
|
|
|
if not self.imported_content:
|
|
|
current_text = self.text_display_widget.text_display.toPlainText()
|
|
|
if current_text:
|
|
|
self.text_display_widget.text_display.clear()
|
|
|
self.status_label.setText("请先导入文件开始学习")
|
|
|
return
|
|
|
|
|
|
# 获取当前文本
|
|
|
current_text = self.text_display_widget.text_display.toPlainText()
|
|
|
|
|
|
# 始终确保显示的是导入的文件内容,而不是用户输入的文字
|
|
|
expected_text = self.imported_content[:len(current_text)]
|
|
|
|
|
|
# 如果当前文本与期望文本不一致,强制恢复到期望文本
|
|
|
if current_text != expected_text:
|
|
|
cursor = self.text_display_widget.text_display.textCursor()
|
|
|
self.text_display_widget.text_display.setPlainText(expected_text)
|
|
|
self.text_display_widget.text_display.setTextCursor(cursor)
|
|
|
|
|
|
# 显示错误信息
|
|
|
if len(current_text) > 0:
|
|
|
expected_char = self.imported_content[len(current_text)-1] if len(current_text) <= len(self.imported_content) else ''
|
|
|
self.status_label.setText(f"输入错误!期望字符: '{expected_char}'")
|
|
|
return
|
|
|
|
|
|
# 检查输入是否正确
|
|
|
if self.typing_logic and len(current_text) > 0:
|
|
|
result = self.typing_logic.check_input(current_text)
|
|
|
|
|
|
if not result['correct']:
|
|
|
# 输入错误,恢复到正确的状态
|
|
|
expected_text = self.imported_content[:len(current_text)]
|
|
|
if current_text != expected_text:
|
|
|
cursor = self.text_display_widget.text_display.textCursor()
|
|
|
self.text_display_widget.text_display.setPlainText(expected_text)
|
|
|
self.text_display_widget.text_display.setTextCursor(cursor)
|
|
|
|
|
|
# 显示错误信息
|
|
|
self.status_label.setText(f"输入错误!期望字符: '{result.get('expected', '')}'")
|
|
|
else:
|
|
|
# 输入正确,更新进度
|
|
|
old_position = self.current_position
|
|
|
self.current_position = len(current_text)
|
|
|
progress = (self.current_position / len(self.imported_content)) * 100
|
|
|
|
|
|
self.progress_label.setText(
|
|
|
f"进度: {progress:.1f}% ({self.current_position}/{len(self.imported_content)} 字符)"
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 只在用户新输入的字符上同步到打字模式
|
|
|
if self.parent_window:
|
|
|
# 获取用户这一轮新输入的字符(与上一轮相比的新内容)
|
|
|
if old_position < self.current_position:
|
|
|
new_input = expected_text[old_position:self.current_position]
|
|
|
if new_input: # 只有新输入内容时才同步
|
|
|
# 只同步新输入的内容,不传递整个文本
|
|
|
self.content_changed.emit(new_input, len(new_input))
|
|
|
|
|
|
# 检查是否完成
|
|
|
if result.get('completed', False):
|
|
|
self.status_label.setText("恭喜!学习完成!")
|
|
|
QMessageBox.information(self, "学习完成", "恭喜您完成了所有学习内容!")
|
|
|
else:
|
|
|
self.status_label.setText("继续输入以显示更多内容...")
|
|
|
|
|
|
|
|
|
|
|
|
def show_about(self):
|
|
|
"""
|
|
|
显示关于对话框
|
|
|
"""
|
|
|
QMessageBox.about(self, "关于学习模式",
|
|
|
"MagicWord 学习模式\n\n"
|
|
|
"功能特点:\n"
|
|
|
"• 顶部显示UI界面图片\n"
|
|
|
"• 下方为打字输入区域\n"
|
|
|
"• 导入文件后逐步显示内容\n"
|
|
|
"• 实时显示学习进度\n"
|
|
|
"• 支持图片显示\n\n"
|
|
|
"使用方法:\n"
|
|
|
"1. 点击'文件'->'导入文件'选择学习材料\n"
|
|
|
"2. 在下方文本区域开始打字\n"
|
|
|
"3. 系统会根据您的输入逐步显示内容")
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
"""
|
|
|
窗口关闭事件
|
|
|
- 通知父窗口学习模式已关闭
|
|
|
"""
|
|
|
# 发射关闭信号
|
|
|
self.closed.emit()
|
|
|
|
|
|
if self.parent_window and hasattr(self.parent_window, 'on_learning_mode_closed'):
|
|
|
self.parent_window.on_learning_mode_closed()
|
|
|
|
|
|
event.accept()
|
|
|
|
|
|
def keyPressEvent(self, event):
|
|
|
"""
|
|
|
按键事件处理
|
|
|
- 处理特殊按键
|
|
|
"""
|
|
|
# 处理Escape键关闭窗口
|
|
|
if event.key() == Qt.Key_Escape:
|
|
|
self.close()
|
|
|
else:
|
|
|
super().keyPressEvent(event)
|
|
|
|
|
|
def display_image_list(self, images):
|
|
|
"""
|
|
|
显示图片列表
|
|
|
"""
|
|
|
try:
|
|
|
# 清空之前的图片列表
|
|
|
self.image_list_widget.clear()
|
|
|
|
|
|
# 如果没有图片,隐藏图片列表区域
|
|
|
if not images:
|
|
|
self.image_list_widget.setVisible(False)
|
|
|
return
|
|
|
|
|
|
# 显示图片列表区域
|
|
|
self.image_list_widget.setVisible(True)
|
|
|
|
|
|
# 添加图片项到列表
|
|
|
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.setIcon(QIcon(thumbnail))
|
|
|
item.setText(f"{filename} ({pixmap.width()}x{pixmap.height()})")
|
|
|
item.setData(Qt.UserRole, index) # 保存图片索引
|
|
|
self.image_list_widget.addItem(item)
|
|
|
else:
|
|
|
# 如果无法加载图片,显示默认文本
|
|
|
item = QListWidgetItem(f"{filename} (无法预览)")
|
|
|
item.setData(Qt.UserRole, index)
|
|
|
self.image_list_widget.addItem(item)
|
|
|
|
|
|
# 更新状态栏
|
|
|
self.status_label.setText(f"已提取 {len(images)} 张图片,双击查看大图")
|
|
|
|
|
|
except Exception as e:
|
|
|
self.status_label.setText(f"显示图片列表失败: {str(e)}")
|
|
|
|
|
|
def on_image_item_double_clicked(self, item):
|
|
|
"""
|
|
|
双击图片项时显示大图
|
|
|
"""
|
|
|
try:
|
|
|
# 获取图片索引
|
|
|
index = item.data(Qt.UserRole)
|
|
|
if 0 <= index < len(self.extracted_images):
|
|
|
image_filename, image_data = self.extracted_images[index]
|
|
|
self.show_image_viewer(image_filename, image_data)
|
|
|
except Exception as e:
|
|
|
self.status_label.setText(f"显示图片失败: {str(e)}")
|
|
|
|
|
|
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_label.setText(f"加载图片失败: {filename}")
|
|
|
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_label.setText(f"创建图片查看器失败: {str(e)}")
|
|
|
import traceback
|
|
|
traceback.print_exc() |