T-569 增加封面画廊任务切换
This commit is contained in:
+168
-15
@@ -86,12 +86,19 @@ class CoverGalleryDialog(QDialog):
|
||||
bulk_running_checker=None,
|
||||
regenerate_running_callback=None,
|
||||
gallery_updated_callback=None,
|
||||
visible_tasks=None,
|
||||
task_index=None,
|
||||
account_by_alias=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.task = task
|
||||
self.image_root = image_root
|
||||
self.db_path = db_path
|
||||
self.account = account
|
||||
self.visible_tasks = list(visible_tasks or [task])
|
||||
self.task_index = self._initial_task_index(task, task_index)
|
||||
self.task = self.visible_tasks[self.task_index] if self.visible_tasks else task
|
||||
self.account_by_alias = dict(account_by_alias or {})
|
||||
self.fallback_account = account
|
||||
self.account = self._account_for_task(self.task) or account
|
||||
self.config = config or {}
|
||||
self.prompt_values = dict(prompt_values or {})
|
||||
self.diagnostic_log_dir = diagnostic_log_dir
|
||||
@@ -106,8 +113,9 @@ class CoverGalleryDialog(QDialog):
|
||||
self.button_group.setExclusive(True)
|
||||
self.regenerate_worker = None
|
||||
self.regenerate_thread = None
|
||||
self.regenerating_task_id = None
|
||||
self._regenerate_last_error = ""
|
||||
self.candidates = image_paths.list_task_cover_candidates(image_root, task, account=account)
|
||||
self.candidates = image_paths.list_task_cover_candidates(image_root, self.task, account=self.account)
|
||||
|
||||
self.setWindowTitle(f"封面画廊:{getattr(task, 'item_id', '')}")
|
||||
layout = QVBoxLayout(self)
|
||||
@@ -117,6 +125,12 @@ class CoverGalleryDialog(QDialog):
|
||||
layout.addLayout(body_layout, 1)
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
self.previous_button = QPushButton("上一条")
|
||||
self.next_button = QPushButton("下一条")
|
||||
self.previous_button.clicked.connect(lambda checked=False: self.switch_task(-1))
|
||||
self.next_button.clicked.connect(lambda checked=False: self.switch_task(1))
|
||||
button_layout.addWidget(self.previous_button)
|
||||
button_layout.addWidget(self.next_button)
|
||||
self.regenerate_button = QPushButton("重新生成")
|
||||
self.regenerate_button.clicked.connect(self.regenerate_cover)
|
||||
button_layout.addWidget(self.regenerate_button)
|
||||
@@ -131,6 +145,7 @@ class CoverGalleryDialog(QDialog):
|
||||
|
||||
self._sync_initial_selection()
|
||||
self._update_status()
|
||||
self._update_navigation_buttons()
|
||||
self._update_regenerate_button_state()
|
||||
self._fit_to_screen()
|
||||
|
||||
@@ -138,17 +153,29 @@ class CoverGalleryDialog(QDialog):
|
||||
panel = QWidget()
|
||||
layout = QVBoxLayout(panel)
|
||||
layout.addWidget(QLabel("旧封面"))
|
||||
old_path = getattr(self.task, "old_cover_path", None)
|
||||
layout.addWidget(
|
||||
_build_image_label(
|
||||
old_path,
|
||||
self.OLD_THUMBNAIL_SIZE,
|
||||
empty_text="暂无旧封面",
|
||||
)
|
||||
self.old_cover_label = _build_image_label(
|
||||
getattr(self.task, "old_cover_path", None),
|
||||
self.OLD_THUMBNAIL_SIZE,
|
||||
empty_text="暂无旧封面",
|
||||
)
|
||||
layout.addWidget(self.old_cover_label)
|
||||
layout.addStretch(1)
|
||||
return panel
|
||||
|
||||
def _initial_task_index(self, task, task_index):
|
||||
if self.visible_tasks:
|
||||
if task_index is not None and 0 <= int(task_index) < len(self.visible_tasks):
|
||||
return int(task_index)
|
||||
task_id = getattr(task, "id", None)
|
||||
for index, visible_task in enumerate(self.visible_tasks):
|
||||
if getattr(visible_task, "id", None) == task_id:
|
||||
return index
|
||||
return 0
|
||||
|
||||
def _account_for_task(self, task):
|
||||
alias = str(getattr(task, "alias", "") or "").strip()
|
||||
return self.account_by_alias.get(alias) or self.fallback_account
|
||||
|
||||
def _candidate_panel(self):
|
||||
panel = QWidget()
|
||||
layout = QVBoxLayout(panel)
|
||||
@@ -207,6 +234,7 @@ class CoverGalleryDialog(QDialog):
|
||||
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
|
||||
radio = QRadioButton(_candidate_label(candidate_path, self.current_path))
|
||||
radio.setObjectName(f"coverCandidateRadio{index}")
|
||||
radio.setFocusPolicy(Qt.ClickFocus)
|
||||
radio.toggled.connect(
|
||||
lambda checked, path=candidate_path: self._select_candidate(path) if checked else None
|
||||
)
|
||||
@@ -235,6 +263,9 @@ class CoverGalleryDialog(QDialog):
|
||||
self._update_status()
|
||||
|
||||
def _update_status(self):
|
||||
if self._is_current_task_regenerating():
|
||||
self.status_label.setText("封面重新生成中,请稍候")
|
||||
return
|
||||
if not self.candidates:
|
||||
self.status_label.setText("暂无生成封面图片")
|
||||
self.save_button.setEnabled(False)
|
||||
@@ -254,12 +285,16 @@ class CoverGalleryDialog(QDialog):
|
||||
self.status_label.setText("已选择新的生成封面,保存后用于后续更新蝦皮")
|
||||
|
||||
def save_selection(self, checked=False):
|
||||
return self._save_selected_cover(close_on_success=True)
|
||||
|
||||
def _save_selected_cover(self, close_on_success=False):
|
||||
if not self.selected_path:
|
||||
QMessageBox.warning(self, "封面画廊", "请先选择一张生成封面")
|
||||
return False
|
||||
if _same_file(self.selected_path, self.current_path):
|
||||
self.changed = False
|
||||
self.accept()
|
||||
if close_on_success:
|
||||
self.accept()
|
||||
return True
|
||||
if self._needs_committed_confirmation() and not self._confirm_committed_save():
|
||||
return False
|
||||
@@ -268,9 +303,17 @@ class CoverGalleryDialog(QDialog):
|
||||
except Exception as exc:
|
||||
QMessageBox.warning(self, "封面画廊", f"保存当前封面失败:{exc}")
|
||||
return False
|
||||
fresh_task = db.get_task(self.task.id, path=self.db_path)
|
||||
if fresh_task is not None:
|
||||
self.task = fresh_task
|
||||
self.visible_tasks[self.task_index] = fresh_task
|
||||
self.current_path = _normalize_file_path(self.selected_path)
|
||||
self.changed = True
|
||||
self.accept()
|
||||
self._update_status()
|
||||
if self.gallery_updated_callback is not None:
|
||||
self.gallery_updated_callback(self.task)
|
||||
if close_on_success:
|
||||
self.accept()
|
||||
return True
|
||||
|
||||
def regenerate_cover(self, checked=False):
|
||||
@@ -301,6 +344,7 @@ class CoverGalleryDialog(QDialog):
|
||||
QMessageBox.warning(self, "重新生成封面", "任务不存在或已删除")
|
||||
self.status_label.setText("重新生成封面失败:任务不存在或已删除")
|
||||
return False
|
||||
self.regenerating_task_id = getattr(fresh_task, "id", None)
|
||||
self._reload_task_and_candidates(fresh_task)
|
||||
self.status_label.setText(f"开始重新生成商品 {getattr(fresh_task, 'item_id', '')} 的封面图片")
|
||||
worker = GenerateWorker(
|
||||
@@ -349,6 +393,96 @@ class CoverGalleryDialog(QDialog):
|
||||
box.exec()
|
||||
return box.clickedButton() is save_button
|
||||
|
||||
def switch_task(self, offset):
|
||||
target_index = self.task_index + int(offset)
|
||||
if target_index < 0 or target_index >= len(self.visible_tasks):
|
||||
return False
|
||||
if not self._confirm_unsaved_selection():
|
||||
return False
|
||||
self._load_task_at_index(target_index)
|
||||
return True
|
||||
|
||||
def _load_task_at_index(self, target_index):
|
||||
self.task_index = int(target_index)
|
||||
target_task = self.visible_tasks[self.task_index]
|
||||
fresh_task = db.get_task(getattr(target_task, "id", None), path=self.db_path) or target_task
|
||||
self.visible_tasks[self.task_index] = fresh_task
|
||||
self.task = fresh_task
|
||||
self.account = self._account_for_task(fresh_task)
|
||||
self.setWindowTitle(f"封面画廊:{getattr(fresh_task, 'item_id', '')}")
|
||||
_set_image_label(
|
||||
self.old_cover_label,
|
||||
getattr(fresh_task, "old_cover_path", None),
|
||||
self.OLD_THUMBNAIL_SIZE,
|
||||
"暂无旧封面",
|
||||
)
|
||||
self._reload_task_and_candidates(fresh_task)
|
||||
self._update_navigation_buttons()
|
||||
self._update_regenerate_button_state()
|
||||
|
||||
def _update_navigation_buttons(self):
|
||||
self.previous_button.setEnabled(self.task_index > 0)
|
||||
self.next_button.setEnabled(self.task_index < len(self.visible_tasks) - 1)
|
||||
|
||||
def _has_unsaved_selection(self):
|
||||
if not self.selected_path:
|
||||
return False
|
||||
return not _same_file(self.selected_path, self.current_path)
|
||||
|
||||
def _confirm_unsaved_selection(self):
|
||||
if not self._has_unsaved_selection():
|
||||
return True
|
||||
box = QMessageBox(self)
|
||||
box.setWindowTitle("未保存封面选择")
|
||||
box.setText("当前已选择另一张生成封面,但尚未保存。")
|
||||
save_button = box.addButton("保存", QMessageBox.AcceptRole)
|
||||
discard_button = box.addButton("不保存", QMessageBox.DestructiveRole)
|
||||
box.addButton("取消", QMessageBox.RejectRole)
|
||||
box.setDefaultButton(save_button)
|
||||
box.exec()
|
||||
clicked = box.clickedButton()
|
||||
if clicked is save_button:
|
||||
return self._save_selected_cover(close_on_success=False)
|
||||
if clicked is discard_button:
|
||||
return True
|
||||
return False
|
||||
|
||||
def reject(self):
|
||||
if self._confirm_unsaved_selection():
|
||||
super().reject()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self._confirm_unsaved_selection():
|
||||
event.accept()
|
||||
return
|
||||
event.ignore()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Up and self._should_consume_navigation_key():
|
||||
self.switch_task(-1)
|
||||
event.accept()
|
||||
return
|
||||
if event.key() == Qt.Key_Down and self._should_consume_navigation_key():
|
||||
self.switch_task(1)
|
||||
event.accept()
|
||||
return
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def _should_consume_navigation_key(self):
|
||||
focus = QApplication.focusWidget()
|
||||
if focus is None or focus is self:
|
||||
return True
|
||||
return not isinstance(
|
||||
focus,
|
||||
(
|
||||
QPushButton,
|
||||
QLineEdit,
|
||||
QPlainTextEdit,
|
||||
QComboBox,
|
||||
QSpinBox,
|
||||
),
|
||||
)
|
||||
|
||||
def open_original_image(self, image_path):
|
||||
dialog = OriginalImageDialog(image_path, self)
|
||||
dialog.exec()
|
||||
@@ -356,6 +490,13 @@ class CoverGalleryDialog(QDialog):
|
||||
def _bulk_generate_running(self):
|
||||
return bool(self.bulk_running_checker and self.bulk_running_checker())
|
||||
|
||||
def _is_current_task_regenerating(self):
|
||||
return (
|
||||
self.regenerate_thread is not None
|
||||
and self.regenerating_task_id is not None
|
||||
and getattr(self.task, "id", None) == self.regenerating_task_id
|
||||
)
|
||||
|
||||
def _set_regenerate_running(self, running):
|
||||
self.save_button.setEnabled(not running and self.selected_path is not None)
|
||||
self.regenerate_button.setEnabled(not running and not self._bulk_generate_running())
|
||||
@@ -379,6 +520,7 @@ class CoverGalleryDialog(QDialog):
|
||||
def _reload_task_and_candidates(self, task=None):
|
||||
if task is not None:
|
||||
self.task = task
|
||||
self.account = self._account_for_task(task)
|
||||
self.current_path = _normalize_file_path(getattr(self.task, "new_cover_path", None))
|
||||
self.selected_path = None
|
||||
self.candidates = image_paths.list_task_cover_candidates(self.image_root, self.task, account=self.account)
|
||||
@@ -431,6 +573,7 @@ class CoverGalleryDialog(QDialog):
|
||||
self._finish_regenerate(summary, cancelled=True)
|
||||
|
||||
def _finish_regenerate(self, summary, cancelled=False):
|
||||
self.regenerating_task_id = None
|
||||
fresh_task = db.get_task(self.task.id, path=self.db_path)
|
||||
if fresh_task is not None:
|
||||
self._reload_task_and_candidates(fresh_task)
|
||||
@@ -450,6 +593,7 @@ class CoverGalleryDialog(QDialog):
|
||||
if self.regenerate_thread is thread:
|
||||
self.regenerate_thread = None
|
||||
self.regenerate_worker = None
|
||||
self.regenerating_task_id = None
|
||||
self._set_regenerate_running(False)
|
||||
|
||||
def _fit_to_screen(self):
|
||||
@@ -488,10 +632,16 @@ def _build_image_label(image_path, size, empty_text):
|
||||
label = QLabel()
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
label.setMinimumSize(size, size)
|
||||
_set_image_label(label, image_path, size, empty_text)
|
||||
return label
|
||||
|
||||
|
||||
def _set_image_label(label, image_path, size, empty_text):
|
||||
label.clear()
|
||||
image = _load_image(image_path) if image_path else QImage()
|
||||
if image.isNull():
|
||||
label.setText(empty_text)
|
||||
return label
|
||||
return
|
||||
label.setPixmap(
|
||||
QPixmap.fromImage(
|
||||
image.scaled(
|
||||
@@ -502,7 +652,6 @@ def _build_image_label(image_path, size, empty_text):
|
||||
)
|
||||
)
|
||||
)
|
||||
return label
|
||||
|
||||
|
||||
def _candidate_label(candidate_path, current_path):
|
||||
@@ -1416,7 +1565,8 @@ class GenerateTab(QWidget):
|
||||
def show_task_images(self, index):
|
||||
if index.isValid() and index.column() == 3:
|
||||
return
|
||||
task = self.model.task_at(index.row()) if index.isValid() else self._selected_task()
|
||||
row = index.row() if index.isValid() else self.task_table.currentIndex().row()
|
||||
task = self.model.task_at(row) if row >= 0 else self._selected_task()
|
||||
if task is None:
|
||||
self._set_status("没有可预览的任务")
|
||||
return
|
||||
@@ -1436,6 +1586,9 @@ class GenerateTab(QWidget):
|
||||
bulk_running_checker=lambda: self.generate_thread is not None,
|
||||
regenerate_running_callback=self._set_cover_gallery_regenerate_running,
|
||||
gallery_updated_callback=self._on_cover_gallery_updated,
|
||||
visible_tasks=list(self.model.tasks),
|
||||
task_index=row if row >= 0 else 0,
|
||||
account_by_alias=self.model.account_by_alias,
|
||||
)
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
self.refresh_tasks()
|
||||
|
||||
+18
-2
@@ -3,7 +3,7 @@ id: T-569
|
||||
title: ②封面画廊上下条切换与未保存选择拦截
|
||||
phase: 7
|
||||
deps: [T-567]
|
||||
status: TODO
|
||||
status: DONE
|
||||
created: 2026-07-09
|
||||
---
|
||||
|
||||
@@ -66,4 +66,20 @@ T-567 的封面画廊只处理当前单条任务。运营批量检查时需要
|
||||
|
||||
## 执行记录
|
||||
|
||||
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)
|
||||
- 2026-07-09 完成 T-569。
|
||||
- 修改 `app/gui/tabs/generate.py`:
|
||||
- ②封面画廊新增「上一条」「下一条」按钮,目标任务来自打开画廊时②当前可见表格任务列表,按相邻行切换,首尾边界按钮自动置灰。
|
||||
- 切换任务时只刷新弹窗内部任务、旧封面和候选画廊内容,不重建整个窗口,窗口尺寸和位置保持不变;切换时会重新读取 DB 最新任务和候选文件。
|
||||
- 支持 `↑` / `↓` 键走同一套上一条/下一条切换逻辑;候选 `QRadioButton` 改为 `ClickFocus`,避免方向键在 radio 组内改候选。
|
||||
- 增加统一未保存选择拦截:按钮切换、方向键切换、取消/关闭弹窗前,如果当前选中候选不同于当前 DB 封面指针,会弹中文三态确认「保存 / 不保存 / 取消」。保存路径复用 `db.update_generated_cover()`,已提交线上任务仍先弹“本地换封面不会回滚蝦皮,重复更新会再次提交线上”确认;保存失败保留当前任务。
|
||||
- 与 T-568 兼容:单任务封面重生运行中切换回该任务时显示“封面重新生成中”,完成/失败后继续刷新 DB 和候选列表。
|
||||
- 修改 `tests/test_gui.py`:
|
||||
- 覆盖上一条/下一条按当前可见列表切换、边界按钮状态、切换不改变窗口尺寸/位置。
|
||||
- 覆盖 `↑`/`↓` 键切换任务且不改变候选 radio 选择。
|
||||
- 覆盖未保存选择在切换和关闭时的「保存 / 不保存 / 取消」三种路径,以及已提交任务保存前的二次确认。
|
||||
- 验证通过:
|
||||
- `py -3.10 -m unittest tests.test_gui`
|
||||
- `python -m ruff check app tests main.py`
|
||||
- `py -3.10 -m compileall app main.py`
|
||||
- `py -3.10 -m unittest discover -s tests`(302 tests;PySide6 字体目录警告不影响结果)
|
||||
- `git diff --check`
|
||||
|
||||
+261
-1
@@ -18,7 +18,7 @@ if gui.QT_IMPORT_ERROR is not None:
|
||||
raise unittest.SkipTest("PySide6 未安装")
|
||||
|
||||
from PySide6.QtCore import QItemSelectionModel, QRect
|
||||
from PySide6.QtGui import QImage, QTextCursor
|
||||
from PySide6.QtGui import QImage, QKeyEvent, QTextCursor
|
||||
from PySide6.QtWidgets import QApplication, QCheckBox, QLineEdit, QPlainTextEdit, QProgressBar, QTableView
|
||||
|
||||
from app.gui import (
|
||||
@@ -156,6 +156,40 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
db.set_generated(task.id, "新标题", canonical, path=cfg["db_path"])
|
||||
return cfg, account, db.get_task(task.id, path=cfg["db_path"]), canonical
|
||||
|
||||
def _cover_gallery_task_set(self, temp_dir, count=3):
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg.setdefault("ai", {})["generate_cover"] = True
|
||||
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
|
||||
db.init_db(cfg["db_path"])
|
||||
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
|
||||
rows = []
|
||||
for index in range(count):
|
||||
rows.append(
|
||||
{
|
||||
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
|
||||
"source_sheet": "商品",
|
||||
"source_row": index + 2,
|
||||
"account_name": "Excel主店",
|
||||
"alias": "alias-a",
|
||||
"item_id": f"5110063951{index}",
|
||||
}
|
||||
)
|
||||
db.insert_tasks(batch_id, rows, path=cfg["db_path"])
|
||||
tasks = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])
|
||||
canonicals = []
|
||||
archives = []
|
||||
for index, task in enumerate(tasks):
|
||||
old_cover = self.write_test_image(os.path.join(temp_dir, f"old-{index}.jpg"))
|
||||
canonical = image_paths.task_image_path(cfg["image_dir"], task, account, "new")
|
||||
archive = os.path.splitext(canonical)[0] + "_20260709101000.jpg"
|
||||
self.write_test_image(canonical)
|
||||
self.write_test_image(archive)
|
||||
db.set_collected(task.id, f"旧标题{index}", old_cover, path=cfg["db_path"])
|
||||
db.set_generated(task.id, f"新标题{index}", canonical, path=cfg["db_path"])
|
||||
canonicals.append(canonical)
|
||||
archives.append(archive)
|
||||
return cfg, account, db.list_tasks(batch_id=batch_id, path=cfg["db_path"]), canonicals, archives
|
||||
|
||||
def assert_foreground(self, model, row, column, color):
|
||||
value = model.data(model.index(row, column), gui.Qt.ForegroundRole)
|
||||
self.assertIsNotNone(value)
|
||||
@@ -217,6 +251,53 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
return FakeMessageBox, boxes
|
||||
|
||||
def make_sequence_message_box(self, selected_labels):
|
||||
boxes = []
|
||||
labels = list(selected_labels)
|
||||
|
||||
class FakeMessageBox:
|
||||
AcceptRole = object()
|
||||
DestructiveRole = object()
|
||||
RejectRole = object()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
self.title = ""
|
||||
self.text = ""
|
||||
self.buttons = {}
|
||||
self.default_button = None
|
||||
self.selected_label = labels.pop(0)
|
||||
boxes.append(self)
|
||||
|
||||
def setWindowTitle(self, title):
|
||||
self.title = title
|
||||
|
||||
def setText(self, text):
|
||||
self.text = text
|
||||
|
||||
def addButton(self, label, role):
|
||||
button = object()
|
||||
self.buttons[label] = button
|
||||
return button
|
||||
|
||||
def setDefaultButton(self, button):
|
||||
self.default_button = button
|
||||
|
||||
def exec(self):
|
||||
return 0
|
||||
|
||||
def clickedButton(self):
|
||||
return self.buttons[self.selected_label]
|
||||
|
||||
@staticmethod
|
||||
def warning(parent, title, text):
|
||||
box = FakeMessageBox(parent)
|
||||
box.setWindowTitle(title)
|
||||
box.setText(text)
|
||||
return 0
|
||||
|
||||
return FakeMessageBox, boxes
|
||||
|
||||
def test_collect_table_shows_product_unavailable_only_for_explicit_error(self):
|
||||
account = SimpleNamespace(alias="papa", account_name="papa 店铺")
|
||||
invalid_task = SimpleNamespace(
|
||||
@@ -2277,6 +2358,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.assertEqual(1, updated.committed)
|
||||
self.assertIn("重复更新会再次提交线上", boxes[0].text)
|
||||
self.assertFalse(dialog.changed)
|
||||
dialog.selected_path = dialog.current_path
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
@@ -2343,6 +2425,184 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_previous_next_switch_visible_tasks_without_resizing(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, account, tasks, canonicals, _archives = self._cover_gallery_task_set(temp_dir, count=3)
|
||||
dialog = CoverGalleryDialog(
|
||||
tasks[1],
|
||||
cfg["image_dir"],
|
||||
cfg["db_path"],
|
||||
account=account,
|
||||
visible_tasks=tasks,
|
||||
task_index=1,
|
||||
account_by_alias={"alias-a": account},
|
||||
)
|
||||
self.addCleanup(dialog.close)
|
||||
dialog.resize(820, 520)
|
||||
dialog.move(25, 35)
|
||||
size = dialog.size()
|
||||
position = dialog.pos()
|
||||
|
||||
self.assertTrue(dialog.previous_button.isEnabled())
|
||||
self.assertTrue(dialog.next_button.isEnabled())
|
||||
self.assertTrue(dialog.switch_task(1))
|
||||
|
||||
self.assertEqual(tasks[2].id, dialog.task.id)
|
||||
self.assertTrue(dialog.previous_button.isEnabled())
|
||||
self.assertFalse(dialog.next_button.isEnabled())
|
||||
self.assertEqual(canonicals[2], dialog.selected_path)
|
||||
self.assertEqual(size, dialog.size())
|
||||
self.assertEqual(position, dialog.pos())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_arrow_keys_switch_tasks_without_changing_radio_choice(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
|
||||
dialog = CoverGalleryDialog(
|
||||
tasks[0],
|
||||
cfg["image_dir"],
|
||||
cfg["db_path"],
|
||||
account=account,
|
||||
visible_tasks=tasks,
|
||||
task_index=0,
|
||||
account_by_alias={"alias-a": account},
|
||||
)
|
||||
self.addCleanup(dialog.close)
|
||||
dialog.candidate_buttons[canonicals[0]].setFocus()
|
||||
event = QKeyEvent(QKeyEvent.KeyPress, gui.Qt.Key_Down, gui.Qt.NoModifier)
|
||||
|
||||
dialog.keyPressEvent(event)
|
||||
|
||||
self.assertTrue(event.isAccepted())
|
||||
self.assertEqual(tasks[1].id, dialog.task.id)
|
||||
self.assertEqual(canonicals[1], dialog.selected_path)
|
||||
self.assertFalse(dialog.candidate_buttons[archives[1]].isChecked())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_unsaved_switch_save_discard_and_cancel_paths(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
|
||||
|
||||
save_dialog = CoverGalleryDialog(
|
||||
tasks[0],
|
||||
cfg["image_dir"],
|
||||
cfg["db_path"],
|
||||
account=account,
|
||||
visible_tasks=tasks,
|
||||
task_index=0,
|
||||
account_by_alias={"alias-a": account},
|
||||
)
|
||||
self.addCleanup(save_dialog.close)
|
||||
save_dialog.candidate_buttons[archives[0]].setChecked(True)
|
||||
message_box, _ = self.make_fake_message_box("保存")
|
||||
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
|
||||
self.assertTrue(save_dialog.switch_task(1))
|
||||
self.assertEqual(os.path.abspath(archives[0]), db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
|
||||
self.assertEqual(tasks[1].id, save_dialog.task.id)
|
||||
|
||||
db.update_generated_cover(tasks[0].id, canonicals[0], path=cfg["db_path"])
|
||||
tasks = [db.get_task(task.id, path=cfg["db_path"]) for task in tasks]
|
||||
discard_dialog = CoverGalleryDialog(
|
||||
tasks[0],
|
||||
cfg["image_dir"],
|
||||
cfg["db_path"],
|
||||
account=account,
|
||||
visible_tasks=tasks,
|
||||
task_index=0,
|
||||
account_by_alias={"alias-a": account},
|
||||
)
|
||||
self.addCleanup(discard_dialog.close)
|
||||
discard_dialog.candidate_buttons[archives[0]].setChecked(True)
|
||||
message_box, _ = self.make_fake_message_box("不保存")
|
||||
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
|
||||
self.assertTrue(discard_dialog.switch_task(1))
|
||||
self.assertEqual(canonicals[0], db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
|
||||
self.assertEqual(tasks[1].id, discard_dialog.task.id)
|
||||
|
||||
cancel_dialog = CoverGalleryDialog(
|
||||
tasks[0],
|
||||
cfg["image_dir"],
|
||||
cfg["db_path"],
|
||||
account=account,
|
||||
visible_tasks=tasks,
|
||||
task_index=0,
|
||||
account_by_alias={"alias-a": account},
|
||||
)
|
||||
self.addCleanup(cancel_dialog.close)
|
||||
cancel_dialog.candidate_buttons[archives[0]].setChecked(True)
|
||||
message_box, _ = self.make_fake_message_box("取消")
|
||||
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
|
||||
self.assertFalse(cancel_dialog.switch_task(1))
|
||||
self.assertEqual(canonicals[0], db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
|
||||
self.assertEqual(tasks[0].id, cancel_dialog.task.id)
|
||||
cancel_dialog.selected_path = cancel_dialog.current_path
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_unsaved_close_uses_same_three_choice_guard(self):
|
||||
class FakeCloseEvent:
|
||||
def __init__(self):
|
||||
self.accepted = False
|
||||
self.ignored = False
|
||||
|
||||
def accept(self):
|
||||
self.accepted = True
|
||||
|
||||
def ignore(self):
|
||||
self.ignored = True
|
||||
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=1)
|
||||
dialog = CoverGalleryDialog(tasks[0], cfg["image_dir"], cfg["db_path"], account=account)
|
||||
self.addCleanup(dialog.close)
|
||||
dialog.candidate_buttons[archives[0]].setChecked(True)
|
||||
event = FakeCloseEvent()
|
||||
message_box, _ = self.make_fake_message_box("取消")
|
||||
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
|
||||
dialog.closeEvent(event)
|
||||
|
||||
self.assertTrue(event.ignored)
|
||||
self.assertFalse(event.accepted)
|
||||
self.assertEqual(canonicals[0], db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
|
||||
|
||||
message_box, _ = self.make_fake_message_box("不保存")
|
||||
event = FakeCloseEvent()
|
||||
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
|
||||
dialog.closeEvent(event)
|
||||
self.assertTrue(event.accepted)
|
||||
dialog.selected_path = dialog.current_path
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_unsaved_save_for_committed_task_keeps_warning(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, account, tasks, _canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
|
||||
db.set_applied(tasks[0].id, True, path=cfg["db_path"])
|
||||
tasks = [db.get_task(task.id, path=cfg["db_path"]) for task in tasks]
|
||||
dialog = CoverGalleryDialog(
|
||||
tasks[0],
|
||||
cfg["image_dir"],
|
||||
cfg["db_path"],
|
||||
account=account,
|
||||
visible_tasks=tasks,
|
||||
task_index=0,
|
||||
account_by_alias={"alias-a": account},
|
||||
)
|
||||
self.addCleanup(dialog.close)
|
||||
dialog.candidate_buttons[archives[0]].setChecked(True)
|
||||
message_box, boxes = self.make_sequence_message_box(["保存", "确认保存"])
|
||||
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
|
||||
self.assertTrue(dialog.switch_task(1))
|
||||
|
||||
self.assertEqual(os.path.abspath(archives[0]), db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
|
||||
self.assertIn("未保存", boxes[0].title)
|
||||
self.assertIn("不会回滚蝦皮", boxes[1].text)
|
||||
self.assertEqual(tasks[1].id, dialog.task.id)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_regenerate_cancel_has_no_side_effect(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg, account, task, canonical = self._cover_gallery_task(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user