diff --git a/src/gui.py b/src/gui.py index 06c4427..ad1893a 100644 --- a/src/gui.py +++ b/src/gui.py @@ -24,9 +24,11 @@ if ffmpeg_path: from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QTextEdit, QLabel, QFileDialog, - QMessageBox, QGroupBox, QFormLayout, QProgressBar + QMessageBox, QGroupBox, QFormLayout, QProgressBar, QListWidget, + QListWidgetItem, QInputDialog, QMenu ) from PySide6.QtCore import Qt, Signal, QObject +from PySide6.QtGui import QColor # 底层函数(与 CLI 共用) from core import parse_ppt_to_config, Pipeline @@ -193,14 +195,153 @@ class GUI(QMainWindow): layout.addLayout(btn_layout) def _setup_edit_ui(self): - """ - 编辑界面(Task 9 实现) - 目前显示 TODO 提示 - """ - QMessageBox.information( - self, "提示", - "编辑模式将在 Task 9 中实现\n目前可以手动编辑 generated_config.yaml 后重新运行" + """编辑界面:左侧 clip 列表 + 右侧字幕预览""" + central = QWidget() + self.setCentralWidget(central) + main_layout = QVBoxLayout(central) # Changed to QVBoxLayout for bottom bar + + # Top: Split left and right + top_layout = QHBoxLayout() + + # === 左侧:clip 列表 === + left_widget = QWidget() + left_layout = QVBoxLayout(left_widget) + + left_header = QLabel("知识点") + left_header.setStyleSheet("font-weight: bold; font-size: 14px;") + left_layout.addWidget(left_header) + + # clip 列表 + self.clip_list = QListWidget() + self.clip_list.itemDoubleClicked.connect(self._on_clip_double_clicked) + self.clip_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.clip_list.customContextMenuRequested.connect(self._on_clip_context_menu) + left_layout.addWidget(self.clip_list) + + # 底部按钮 + left_btn_layout = QHBoxLayout() + self.add_clip_btn = QPushButton("+ 新增知识点") + self.add_clip_btn.clicked.connect(self._on_add_clip) + self.add_clip_btn.setMaximumWidth(120) + left_btn_layout.addWidget(self.add_clip_btn) + left_btn_layout.addStretch() + left_layout.addLayout(left_btn_layout) + + top_layout.addWidget(left_widget, 1) + + # === 右侧:字幕预览 === + right_widget = QWidget() + right_layout = QVBoxLayout(right_widget) + + right_header = QLabel("字幕预览(可直接编辑)") + right_header.setStyleSheet("font-weight: bold; font-size: 14px;") + right_layout.addWidget(right_header) + + self.subtitle_edit = QTextEdit() + self.subtitle_edit.setReadOnly(False) # 用户可编辑 + right_layout.addWidget(self.subtitle_edit) + + right_btn_layout = QHBoxLayout() + self.load_subtitle_btn = QPushButton("加载字幕") + self.load_subtitle_btn.clicked.connect(self._load_subtitle_file) + right_btn_layout.addWidget(self.load_subtitle_btn) + right_layout.addLayout(right_btn_layout) + + top_layout.addWidget(right_widget, 2) + main_layout.addLayout(top_layout, 1) # Stretch factor 1 for top area + + # === 底部状态栏 === + bottom_layout = QHBoxLayout() + self.status_label = QLabel("就绪") + bottom_layout.addWidget(self.status_label) + bottom_layout.addStretch() + + self.apply_btn = QPushButton("应用") + self.apply_btn.setStyleSheet("font-weight: bold; background-color: #4CAF50; color: white;") + self.apply_btn.clicked.connect(self._on_apply) # Task 10 实现 + bottom_layout.addWidget(self.apply_btn) + + self.back_btn = QPushButton("返回") + self.back_btn.clicked.connect(self._show_project_info) + bottom_layout.addWidget(self.back_btn) + + main_layout.addLayout(bottom_layout) + + # 填充 clip 列表 + self._refresh_clip_list() + + # 加载字幕 + self._load_subtitle_file() + + def _refresh_clip_list(self): + """刷新 clip 列表""" + self.clip_list.clear() + for i, clip in enumerate(self.pipeline.clips): + title = clip.get('title', '未知') + matched = clip.get('matched', True) + + item_text = f"Clip {i+1}: {title}" + if not matched: + item_text += " ⚠️ 未匹配" + + item = QListWidgetItem(item_text) + if not matched: + item.setBackground(QColor(255, 200, 200)) # 红色背景 + item.setData(Qt.ItemDataRole.UserRole, i) # 存索引 + self.clip_list.addItem(item) + + def _on_clip_double_clicked(self, item): + """双击 clip 进入编辑模式""" + clip_index = item.data(Qt.ItemDataRole.UserRole) + # 简单的实现:弹出 QInputDialog 让用户输入新标题 + clip = self.pipeline.clips[clip_index] + new_title, ok = QInputDialog.getText( + self, "修改标题", f"Clip {clip_index+1} 标题:", + text=clip.get('title', '') ) + if ok and new_title.strip(): + self.pipeline.reextract_clip(clip_index, new_title.strip()) + self._refresh_clip_list() + self.status_label.setText(f"已修改 Clip {clip_index+1} 标题为: {new_title}") + + def _on_clip_context_menu(self, pos): + """右键菜单:删除""" + item = self.clip_list.itemAt(pos) + if not item: + return + clip_index = item.data(Qt.ItemDataRole.UserRole) + + menu = QMenu() + delete_action = menu.addAction("删除此 Clip") + action = menu.exec_(self.clip_list.mapToGlobal(pos)) + if action == delete_action: + self.pipeline.delete_clip(clip_index) + self._refresh_clip_list() + self.status_label.setText(f"已删除 Clip {clip_index+1}") + + def _on_add_clip(self): + """新增知识点""" + new_title, ok = QInputDialog.getText(self, "新增知识点", "请输入知识点标题:") + if ok and new_title.strip(): + idx, matched = self.pipeline.add_clip_by_title(new_title.strip()) + self._refresh_clip_list() + if matched: + self.status_label.setText(f"已新增: {new_title} (Clip {idx+1})") + else: + self.status_label.setText(f"已新增: {new_title},但未匹配到转录内容") + + def _load_subtitle_file(self): + """加载 v1_content.srt 到字幕编辑框""" + srt_path = os.path.join(self.pipeline.subs_dir, 'v1_content.srt') + if os.path.exists(srt_path): + with open(srt_path, 'r', encoding='utf-8') as f: + self.subtitle_edit.setPlainText(f.read()) + else: + self.subtitle_edit.setPlainText("# 字幕文件不存在") + + def _on_apply(self): + """Task 10 预留:应用修改""" + pass def _setup_new_project_ui(self): """新建项目的文件选择界面"""