T-573 修复封面画廊候选图宽度
This commit is contained in:
+45
-10
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PySide6.QtCore import QEvent
|
from PySide6.QtCore import QEvent, QSize
|
||||||
|
|
||||||
from ..models import GenerateTaskTableModel
|
from ..models import GenerateTaskTableModel
|
||||||
from ..widgets import *
|
from ..widgets import *
|
||||||
@@ -188,13 +188,14 @@ class CoverGalleryDialog(QDialog):
|
|||||||
self.status_label.setWordWrap(True)
|
self.status_label.setWordWrap(True)
|
||||||
layout.addWidget(self.status_label)
|
layout.addWidget(self.status_label)
|
||||||
|
|
||||||
scroll = QScrollArea()
|
self.candidate_scroll = QScrollArea()
|
||||||
scroll.setObjectName("coverGalleryScrollArea")
|
self.candidate_scroll.setObjectName("coverGalleryScrollArea")
|
||||||
scroll.setWidgetResizable(False)
|
self.candidate_scroll.setWidgetResizable(False)
|
||||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.candidate_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.candidate_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
content = QWidget()
|
self.candidate_content = QWidget()
|
||||||
self.candidate_layout = QHBoxLayout(content)
|
self.candidate_content.setObjectName("coverGalleryCandidateContent")
|
||||||
|
self.candidate_layout = QHBoxLayout(self.candidate_content)
|
||||||
self.candidate_layout.setContentsMargins(0, 0, 0, 0)
|
self.candidate_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.candidate_layout.setSpacing(12)
|
self.candidate_layout.setSpacing(12)
|
||||||
if self.candidates:
|
if self.candidates:
|
||||||
@@ -207,8 +208,9 @@ class CoverGalleryDialog(QDialog):
|
|||||||
empty_label.setMinimumSize(320, self.THUMBNAIL_SIZE)
|
empty_label.setMinimumSize(320, self.THUMBNAIL_SIZE)
|
||||||
empty_label.setAlignment(Qt.AlignCenter)
|
empty_label.setAlignment(Qt.AlignCenter)
|
||||||
self.candidate_layout.addWidget(empty_label)
|
self.candidate_layout.addWidget(empty_label)
|
||||||
scroll.setWidget(content)
|
self._sync_candidate_content_size()
|
||||||
layout.addWidget(scroll, 1)
|
self.candidate_scroll.setWidget(self.candidate_content)
|
||||||
|
layout.addWidget(self.candidate_scroll, 1)
|
||||||
return panel
|
return panel
|
||||||
|
|
||||||
def _candidate_item(self, candidate_path, index):
|
def _candidate_item(self, candidate_path, index):
|
||||||
@@ -252,6 +254,38 @@ class CoverGalleryDialog(QDialog):
|
|||||||
layout.addStretch(1)
|
layout.addStretch(1)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
def _sync_candidate_content_size(self):
|
||||||
|
layout = getattr(self, "candidate_layout", None)
|
||||||
|
content = getattr(self, "candidate_content", None)
|
||||||
|
if layout is None or content is None:
|
||||||
|
return
|
||||||
|
layout.activate()
|
||||||
|
margins = layout.contentsMargins()
|
||||||
|
spacing = max(0, layout.spacing())
|
||||||
|
hint = layout.sizeHint()
|
||||||
|
if self.candidates:
|
||||||
|
candidate_count = len(self.candidates)
|
||||||
|
min_width = (
|
||||||
|
margins.left()
|
||||||
|
+ margins.right()
|
||||||
|
+ candidate_count * (self.THUMBNAIL_SIZE + 16)
|
||||||
|
+ max(0, candidate_count - 1) * spacing
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
min_width = margins.left() + margins.right() + 320
|
||||||
|
min_height = max(
|
||||||
|
self.THUMBNAIL_SIZE,
|
||||||
|
hint.height(),
|
||||||
|
content.minimumHeight(),
|
||||||
|
)
|
||||||
|
size = QSize(max(min_width, hint.width()), min_height)
|
||||||
|
content.setMinimumSize(size)
|
||||||
|
content.resize(size)
|
||||||
|
content.updateGeometry()
|
||||||
|
scroll = getattr(self, "candidate_scroll", None)
|
||||||
|
if scroll is not None:
|
||||||
|
scroll.updateGeometry()
|
||||||
|
|
||||||
def _sync_initial_selection(self):
|
def _sync_initial_selection(self):
|
||||||
for candidate_path, radio in self.candidate_buttons.items():
|
for candidate_path, radio in self.candidate_buttons.items():
|
||||||
if _same_file(candidate_path, self.current_path):
|
if _same_file(candidate_path, self.current_path):
|
||||||
@@ -560,6 +594,7 @@ class CoverGalleryDialog(QDialog):
|
|||||||
empty_label.setMinimumSize(320, self.THUMBNAIL_SIZE)
|
empty_label.setMinimumSize(320, self.THUMBNAIL_SIZE)
|
||||||
empty_label.setAlignment(Qt.AlignCenter)
|
empty_label.setAlignment(Qt.AlignCenter)
|
||||||
self.candidate_layout.addWidget(empty_label)
|
self.candidate_layout.addWidget(empty_label)
|
||||||
|
self._sync_candidate_content_size()
|
||||||
self._install_navigation_event_filters()
|
self._install_navigation_event_filters()
|
||||||
|
|
||||||
def _on_regenerate_progress(self, payload):
|
def _on_regenerate_progress(self, payload):
|
||||||
|
|||||||
+10
-2
@@ -3,7 +3,7 @@ id: T-573
|
|||||||
title: ②封面画廊候选图横向滚动内容宽度修复
|
title: ②封面画廊候选图横向滚动内容宽度修复
|
||||||
phase: 7
|
phase: 7
|
||||||
deps: [T-567, T-569]
|
deps: [T-567, T-569]
|
||||||
status: TODO
|
status: DONE
|
||||||
created: 2026-07-09
|
created: 2026-07-09
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -65,4 +65,12 @@ T-568/T-569 后封面画廊支持切换任务和重新生成,候选图列表
|
|||||||
|
|
||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
(做完在这里写:改了什么文件、跑了什么验证命令及结果、关键决策。)
|
- 2026-07-09:已完成。
|
||||||
|
- `app/gui/tabs/generate.py`:`CoverGalleryDialog` 保存 `candidate_scroll` / `candidate_content` 引用,新增 `_sync_candidate_content_size()`;候选图初始化和 `_rebuild_candidate_items()` 后按候选数量、item 宽度、spacing 与 layout sizeHint 同步 content 最小尺寸和实际尺寸,保留横向滚动行为。
|
||||||
|
- `tests/test_gui.py`:新增多候选图内容宽度测试,以及从少候选任务切换到多候选任务后的宽度重新同步测试。
|
||||||
|
- 验证通过:
|
||||||
|
- `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`
|
||||||
|
- `git diff --check`
|
||||||
|
|||||||
@@ -2277,6 +2277,60 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_cover_gallery_candidate_content_width_fits_multiple_candidates(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg, account, tasks, canonicals, _archives = self._cover_gallery_task_set(temp_dir, count=1)
|
||||||
|
prefix = os.path.splitext(canonicals[0])[0]
|
||||||
|
self.write_test_image(f"{prefix}_20260709101100.jpg")
|
||||||
|
self.write_test_image(f"{prefix}_20260709101200.jpg")
|
||||||
|
|
||||||
|
dialog = CoverGalleryDialog(tasks[0], cfg["image_dir"], cfg["db_path"], account=account)
|
||||||
|
self.addCleanup(dialog.close)
|
||||||
|
|
||||||
|
self.assertGreaterEqual(len(dialog.candidates), 4)
|
||||||
|
expected_width = (
|
||||||
|
len(dialog.candidates) * (dialog.THUMBNAIL_SIZE + 16)
|
||||||
|
+ (len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
||||||
|
)
|
||||||
|
self.assertIs(dialog.candidate_scroll.widget(), dialog.candidate_content)
|
||||||
|
self.assertGreaterEqual(dialog.candidate_content.minimumWidth(), expected_width)
|
||||||
|
self.assertGreaterEqual(dialog.candidate_content.width(), expected_width)
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_cover_gallery_switch_task_resyncs_candidate_content_width(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
|
||||||
|
os.remove(archives[0])
|
||||||
|
prefix = os.path.splitext(canonicals[1])[0]
|
||||||
|
self.write_test_image(f"{prefix}_20260709101100.jpg")
|
||||||
|
self.write_test_image(f"{prefix}_20260709101200.jpg")
|
||||||
|
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)
|
||||||
|
initial_width = dialog.candidate_content.minimumWidth()
|
||||||
|
|
||||||
|
self.assertTrue(dialog.switch_task(1))
|
||||||
|
|
||||||
|
expected_width = (
|
||||||
|
len(dialog.candidates) * (dialog.THUMBNAIL_SIZE + 16)
|
||||||
|
+ (len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
||||||
|
)
|
||||||
|
self.assertEqual(tasks[1].id, dialog.task.id)
|
||||||
|
self.assertGreater(len(dialog.candidates), 2)
|
||||||
|
self.assertGreater(dialog.candidate_content.minimumWidth(), initial_width)
|
||||||
|
self.assertGreaterEqual(dialog.candidate_content.minimumWidth(), expected_width)
|
||||||
|
self.assertGreaterEqual(dialog.candidate_content.width(), expected_width)
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
def test_cover_gallery_save_switches_current_cover(self):
|
def test_cover_gallery_save_switches_current_cover(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg = self.make_config(temp_dir)
|
cfg = self.make_config(temp_dir)
|
||||||
|
|||||||
Reference in New Issue
Block a user