fix(gui): make cover gallery previews responsive
This commit is contained in:
+154
-23
@@ -34,31 +34,88 @@ class OriginalImageDialog(QDialog):
|
|||||||
def __init__(self, image_path, parent=None):
|
def __init__(self, image_path, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.image_path = image_path
|
self.image_path = image_path
|
||||||
|
self.image = QImage(str(image_path))
|
||||||
|
self.fit_to_window = True
|
||||||
|
self._display_size = QSize()
|
||||||
|
self._resize_timer = QTimer(self)
|
||||||
|
self._resize_timer.setSingleShot(True)
|
||||||
|
self._resize_timer.setInterval(50)
|
||||||
|
self._resize_timer.timeout.connect(self._update_image_display)
|
||||||
self.setWindowTitle(os.path.basename(str(image_path or "")) or "原图")
|
self.setWindowTitle(os.path.basename(str(image_path or "")) or "原图")
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
image = QImage(str(image_path))
|
self.scroll = QScrollArea()
|
||||||
scroll = QScrollArea()
|
self.scroll.setObjectName("originalImageScrollArea")
|
||||||
scroll.setWidgetResizable(False)
|
self.scroll.setWidgetResizable(False)
|
||||||
image_label = QLabel()
|
self.image_label = QLabel()
|
||||||
image_label.setAlignment(Qt.AlignCenter)
|
self.image_label.setObjectName("originalImageLabel")
|
||||||
if image.isNull():
|
self.image_label.setAlignment(Qt.AlignCenter)
|
||||||
image_label.setText("图片读取失败")
|
if self.image.isNull():
|
||||||
|
self.image_label.setText("图片读取失败")
|
||||||
else:
|
else:
|
||||||
self.setWindowTitle(
|
self.setWindowTitle(
|
||||||
f"{os.path.basename(str(image_path))} · {image.width()}x{image.height()}"
|
f"{os.path.basename(str(image_path))} · {self.image.width()}x{self.image.height()}"
|
||||||
)
|
)
|
||||||
pixmap = QPixmap.fromImage(image)
|
self.scroll.setWidget(self.image_label)
|
||||||
image_label.setPixmap(pixmap)
|
layout.addWidget(self.scroll, 1)
|
||||||
image_label.resize(pixmap.size())
|
|
||||||
scroll.setWidget(image_label)
|
|
||||||
layout.addWidget(scroll, 1)
|
|
||||||
button_layout = QHBoxLayout()
|
button_layout = QHBoxLayout()
|
||||||
button_layout.addStretch(1)
|
button_layout.addStretch(1)
|
||||||
|
self.display_mode_button = QPushButton("原始尺寸")
|
||||||
|
self.display_mode_button.setObjectName("originalImageDisplayModeButton")
|
||||||
|
self.display_mode_button.setToolTip("按图片原始像素查看")
|
||||||
|
self.display_mode_button.setEnabled(not self.image.isNull())
|
||||||
|
self.display_mode_button.clicked.connect(self.toggle_display_mode)
|
||||||
|
button_layout.addWidget(self.display_mode_button)
|
||||||
close_button = QPushButton("关闭")
|
close_button = QPushButton("关闭")
|
||||||
close_button.clicked.connect(self.reject)
|
close_button.clicked.connect(self.reject)
|
||||||
button_layout.addWidget(close_button)
|
button_layout.addWidget(close_button)
|
||||||
layout.addLayout(button_layout)
|
layout.addLayout(button_layout)
|
||||||
self._fit_to_screen(image)
|
self._fit_to_screen(self.image)
|
||||||
|
self._update_image_display()
|
||||||
|
QTimer.singleShot(0, self._update_image_display)
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
super().resizeEvent(event)
|
||||||
|
self._resize_timer.start()
|
||||||
|
|
||||||
|
def toggle_display_mode(self, checked=False):
|
||||||
|
self.fit_to_window = not self.fit_to_window
|
||||||
|
if self.fit_to_window:
|
||||||
|
self.display_mode_button.setText("原始尺寸")
|
||||||
|
self.display_mode_button.setToolTip("按图片原始像素查看")
|
||||||
|
else:
|
||||||
|
self.display_mode_button.setText("适应窗口")
|
||||||
|
self.display_mode_button.setToolTip("缩放图片以完整显示在窗口内")
|
||||||
|
self._display_size = QSize()
|
||||||
|
self._update_image_display()
|
||||||
|
QTimer.singleShot(0, self._update_image_display)
|
||||||
|
|
||||||
|
def _update_image_display(self):
|
||||||
|
viewport_size = self.scroll.viewport().size()
|
||||||
|
viewport_width = max(1, viewport_size.width())
|
||||||
|
viewport_height = max(1, viewport_size.height())
|
||||||
|
if self.image.isNull():
|
||||||
|
self.image_label.clear()
|
||||||
|
self.image_label.setText("图片读取失败")
|
||||||
|
self.image_label.resize(viewport_width, viewport_height)
|
||||||
|
return
|
||||||
|
if self.fit_to_window:
|
||||||
|
target_size = QSize(viewport_width, viewport_height)
|
||||||
|
pixmap = QPixmap.fromImage(
|
||||||
|
self.image.scaled(
|
||||||
|
target_size,
|
||||||
|
Qt.KeepAspectRatio,
|
||||||
|
Qt.SmoothTransformation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
label_size = target_size
|
||||||
|
else:
|
||||||
|
pixmap = QPixmap.fromImage(self.image)
|
||||||
|
label_size = pixmap.size()
|
||||||
|
if self._display_size == label_size and self.image_label.pixmap() is not None:
|
||||||
|
return
|
||||||
|
self.image_label.setPixmap(pixmap)
|
||||||
|
self.image_label.resize(label_size)
|
||||||
|
self._display_size = QSize(label_size)
|
||||||
|
|
||||||
def _fit_to_screen(self, image):
|
def _fit_to_screen(self, image):
|
||||||
available = _available_geometry()
|
available = _available_geometry()
|
||||||
@@ -132,6 +189,8 @@ class _SegmentedProgressBar(QProgressBar):
|
|||||||
|
|
||||||
class CoverGalleryDialog(QDialog):
|
class CoverGalleryDialog(QDialog):
|
||||||
THUMBNAIL_SIZE = 180
|
THUMBNAIL_SIZE = 180
|
||||||
|
MAX_THUMBNAIL_SIZE = 360
|
||||||
|
CANDIDATE_TEXT_HEIGHT = 72
|
||||||
OLD_THUMBNAIL_SIZE = 300
|
OLD_THUMBNAIL_SIZE = 300
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -163,6 +222,14 @@ class CoverGalleryDialog(QDialog):
|
|||||||
self.current_path = _normalize_file_path(getattr(task, "new_cover_path", None))
|
self.current_path = _normalize_file_path(getattr(task, "new_cover_path", None))
|
||||||
self.selected_path = None
|
self.selected_path = None
|
||||||
self.changed = False
|
self.changed = False
|
||||||
|
self.candidate_thumbnail_size = self.THUMBNAIL_SIZE
|
||||||
|
self.candidate_images = {}
|
||||||
|
self.candidate_thumbnails = {}
|
||||||
|
self.candidate_items = {}
|
||||||
|
self._candidate_resize_timer = QTimer(self)
|
||||||
|
self._candidate_resize_timer.setSingleShot(True)
|
||||||
|
self._candidate_resize_timer.setInterval(60)
|
||||||
|
self._candidate_resize_timer.timeout.connect(self._update_candidate_thumbnail_layout)
|
||||||
self.candidate_buttons = {}
|
self.candidate_buttons = {}
|
||||||
self.button_group = QButtonGroup(self)
|
self.button_group = QButtonGroup(self)
|
||||||
self.button_group.setExclusive(True)
|
self.button_group.setExclusive(True)
|
||||||
@@ -203,6 +270,7 @@ class CoverGalleryDialog(QDialog):
|
|||||||
self._update_reset_cover_button_state()
|
self._update_reset_cover_button_state()
|
||||||
self._fit_to_screen()
|
self._fit_to_screen()
|
||||||
self._install_navigation_event_filters()
|
self._install_navigation_event_filters()
|
||||||
|
QTimer.singleShot(0, self._update_candidate_thumbnail_layout)
|
||||||
|
|
||||||
def _old_cover_panel(self):
|
def _old_cover_panel(self):
|
||||||
panel = QWidget()
|
panel = QWidget()
|
||||||
@@ -267,28 +335,30 @@ class CoverGalleryDialog(QDialog):
|
|||||||
|
|
||||||
def _candidate_item(self, candidate_path, index):
|
def _candidate_item(self, candidate_path, index):
|
||||||
item = QWidget()
|
item = QWidget()
|
||||||
item.setFixedWidth(self.THUMBNAIL_SIZE + 16)
|
item.setFixedWidth(self.candidate_thumbnail_size + 16)
|
||||||
layout = QVBoxLayout(item)
|
layout = QVBoxLayout(item)
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
thumbnail = _CoverThumbnailLabel(candidate_path, self.open_original_image)
|
thumbnail = _CoverThumbnailLabel(candidate_path, self.open_original_image)
|
||||||
thumbnail.setObjectName("coverCandidateThumbnail")
|
thumbnail.setObjectName("coverCandidateThumbnail")
|
||||||
thumbnail.setAlignment(Qt.AlignCenter)
|
thumbnail.setAlignment(Qt.AlignCenter)
|
||||||
image = _load_image(candidate_path)
|
image = _load_image(candidate_path)
|
||||||
|
self.candidate_images[candidate_path] = image
|
||||||
|
self.candidate_thumbnails[candidate_path] = thumbnail
|
||||||
|
self.candidate_items[candidate_path] = item
|
||||||
if image.isNull():
|
if image.isNull():
|
||||||
thumbnail.setText("图片读取失败")
|
thumbnail.setText("图片读取失败")
|
||||||
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
|
|
||||||
else:
|
else:
|
||||||
thumbnail.setPixmap(
|
thumbnail.setPixmap(
|
||||||
QPixmap.fromImage(
|
QPixmap.fromImage(
|
||||||
image.scaled(
|
image.scaled(
|
||||||
self.THUMBNAIL_SIZE,
|
self.candidate_thumbnail_size,
|
||||||
self.THUMBNAIL_SIZE,
|
self.candidate_thumbnail_size,
|
||||||
Qt.KeepAspectRatio,
|
Qt.KeepAspectRatio,
|
||||||
Qt.SmoothTransformation,
|
Qt.SmoothTransformation,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
|
thumbnail.setFixedSize(self.candidate_thumbnail_size, self.candidate_thumbnail_size)
|
||||||
radio = QRadioButton(_candidate_label(candidate_path, self.current_path))
|
radio = QRadioButton(_candidate_label(candidate_path, self.current_path))
|
||||||
radio.setObjectName(f"coverCandidateRadio{index}")
|
radio.setObjectName(f"coverCandidateRadio{index}")
|
||||||
radio.setFocusPolicy(Qt.ClickFocus)
|
radio.setFocusPolicy(Qt.ClickFocus)
|
||||||
@@ -320,15 +390,14 @@ class CoverGalleryDialog(QDialog):
|
|||||||
min_width = (
|
min_width = (
|
||||||
margins.left()
|
margins.left()
|
||||||
+ margins.right()
|
+ margins.right()
|
||||||
+ candidate_count * (self.THUMBNAIL_SIZE + 16)
|
+ candidate_count * (self.candidate_thumbnail_size + 16)
|
||||||
+ max(0, candidate_count - 1) * spacing
|
+ max(0, candidate_count - 1) * spacing
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
min_width = margins.left() + margins.right() + 320
|
min_width = margins.left() + margins.right() + 320
|
||||||
min_height = max(
|
min_height = max(
|
||||||
self.THUMBNAIL_SIZE,
|
self.candidate_thumbnail_size,
|
||||||
hint.height(),
|
hint.height(),
|
||||||
content.minimumHeight(),
|
|
||||||
)
|
)
|
||||||
size = QSize(max(min_width, hint.width()), min_height)
|
size = QSize(max(min_width, hint.width()), min_height)
|
||||||
content.setMinimumSize(size)
|
content.setMinimumSize(size)
|
||||||
@@ -338,6 +407,64 @@ class CoverGalleryDialog(QDialog):
|
|||||||
if scroll is not None:
|
if scroll is not None:
|
||||||
scroll.updateGeometry()
|
scroll.updateGeometry()
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
super().resizeEvent(event)
|
||||||
|
self._candidate_resize_timer.start()
|
||||||
|
|
||||||
|
def _candidate_thumbnail_target_size(self):
|
||||||
|
scroll = getattr(self, "candidate_scroll", None)
|
||||||
|
if scroll is None or not self.candidates:
|
||||||
|
return self.THUMBNAIL_SIZE
|
||||||
|
viewport = scroll.viewport()
|
||||||
|
viewport_width = viewport.width()
|
||||||
|
viewport_height = viewport.height()
|
||||||
|
if viewport_width <= 0 or viewport_height <= 0:
|
||||||
|
return self.candidate_thumbnail_size
|
||||||
|
visible_count = min(3, max(1, len(self.candidates)))
|
||||||
|
spacing = max(0, self.candidate_layout.spacing())
|
||||||
|
width_budget = (
|
||||||
|
viewport_width
|
||||||
|
- max(0, visible_count - 1) * spacing
|
||||||
|
- visible_count * 16
|
||||||
|
) // visible_count
|
||||||
|
height_budget = viewport_height - self.CANDIDATE_TEXT_HEIGHT
|
||||||
|
target = min(width_budget, height_budget, self.MAX_THUMBNAIL_SIZE)
|
||||||
|
return max(self.THUMBNAIL_SIZE, int(target))
|
||||||
|
|
||||||
|
def _update_candidate_thumbnail_layout(self):
|
||||||
|
if not self.candidates:
|
||||||
|
self._sync_candidate_content_size()
|
||||||
|
return
|
||||||
|
target_size = self._candidate_thumbnail_target_size()
|
||||||
|
if target_size == self.candidate_thumbnail_size:
|
||||||
|
self._sync_candidate_content_size()
|
||||||
|
return
|
||||||
|
self.candidate_thumbnail_size = target_size
|
||||||
|
for candidate_path in self.candidates:
|
||||||
|
item = self.candidate_items.get(candidate_path)
|
||||||
|
thumbnail = self.candidate_thumbnails.get(candidate_path)
|
||||||
|
image = self.candidate_images.get(candidate_path)
|
||||||
|
if item is not None:
|
||||||
|
item.setFixedWidth(target_size + 16)
|
||||||
|
if thumbnail is None:
|
||||||
|
continue
|
||||||
|
thumbnail.setFixedSize(target_size, target_size)
|
||||||
|
thumbnail.clear()
|
||||||
|
if image is None or image.isNull():
|
||||||
|
thumbnail.setText("图片读取失败")
|
||||||
|
continue
|
||||||
|
thumbnail.setPixmap(
|
||||||
|
QPixmap.fromImage(
|
||||||
|
image.scaled(
|
||||||
|
target_size,
|
||||||
|
target_size,
|
||||||
|
Qt.KeepAspectRatio,
|
||||||
|
Qt.SmoothTransformation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._sync_candidate_content_size()
|
||||||
|
|
||||||
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):
|
||||||
@@ -609,6 +736,9 @@ class CoverGalleryDialog(QDialog):
|
|||||||
def _rebuild_candidate_items(self):
|
def _rebuild_candidate_items(self):
|
||||||
_clear_layout(self.candidate_layout)
|
_clear_layout(self.candidate_layout)
|
||||||
self.candidate_buttons = {}
|
self.candidate_buttons = {}
|
||||||
|
self.candidate_images = {}
|
||||||
|
self.candidate_thumbnails = {}
|
||||||
|
self.candidate_items = {}
|
||||||
self.button_group = QButtonGroup(self)
|
self.button_group = QButtonGroup(self)
|
||||||
self.button_group.setExclusive(True)
|
self.button_group.setExclusive(True)
|
||||||
if self.candidates:
|
if self.candidates:
|
||||||
@@ -622,6 +752,7 @@ class CoverGalleryDialog(QDialog):
|
|||||||
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._sync_candidate_content_size()
|
||||||
|
self._candidate_resize_timer.start()
|
||||||
self._install_navigation_event_filters()
|
self._install_navigation_event_filters()
|
||||||
|
|
||||||
def _fit_to_screen(self):
|
def _fit_to_screen(self):
|
||||||
@@ -631,7 +762,7 @@ class CoverGalleryDialog(QDialog):
|
|||||||
return
|
return
|
||||||
max_width = max(520, int(available.width() * 0.9))
|
max_width = max(520, int(available.width() * 0.9))
|
||||||
max_height = max(420, int(available.height() * 0.9))
|
max_height = max(420, int(available.height() * 0.9))
|
||||||
self.resize(min(960, max_width), min(580, max_height))
|
self.resize(min(1180, max_width), min(720, max_height))
|
||||||
_center_dialog(self, available)
|
_center_dialog(self, available)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -3,7 +3,7 @@ id: T-626
|
|||||||
title: ②封面画廊生成图随窗口自适应显示
|
title: ②封面画廊生成图随窗口自适应显示
|
||||||
phase: 7
|
phase: 7
|
||||||
deps: [T-574]
|
deps: [T-574]
|
||||||
status: TODO
|
status: DONE
|
||||||
created: 2026-07-13
|
created: 2026-07-13
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -75,4 +75,10 @@ T-573 只修复了候选图横向容器没有按数量撑开导致的遮挡问
|
|||||||
|
|
||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
- 待实现。
|
- 2026-07-14:完成 T-626。
|
||||||
|
- `app/gui/tabs/generate.py`:封面画廊候选图改为按候选区 viewport、候选数量和可用高度在 `180~360px` 内动态计算;候选 item、缩略图和横向内容宽度使用同一尺寸,窗口 resize、任务切换和候选重建后自动同步。
|
||||||
|
- 候选原始 `QImage` 在当前任务画廊内缓存,连续 resize 使用单次计时器合并重绘;预览继续使用 `Qt.KeepAspectRatio` 和平滑缩放,读取失败时保持中文占位。
|
||||||
|
- `OriginalImageDialog` 默认按窗口完整显示图片,新增「原始尺寸 / 适应窗口」切换;原始尺寸保留滚动查看,窗口尺寸变化后自动更新预览。
|
||||||
|
- `tests/test_gui.py`:新增候选图随窗口变化、非方图比例、损坏图片占位、任务切换后响应式尺寸,以及原图双模式和失败占位测试;既有候选内容宽度断言改为使用当前动态尺寸。
|
||||||
|
- 主工作区存在与本任务无关的默认封面模板未提交改名,导致两项旧模板断言失败;未回退用户改动,在基于 `HEAD` 的干净 worktree 中仅套用 T-626 代码和测试差异完成验证。
|
||||||
|
- 验证通过:`py -3.10 -m unittest tests.test_gui`(179 tests)、`py -3.10 -m unittest discover -s tests`(444 tests)、`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check`。
|
||||||
|
|||||||
+146
-2
@@ -4069,7 +4069,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assertGreaterEqual(len(dialog.candidates), 4)
|
self.assertGreaterEqual(len(dialog.candidates), 4)
|
||||||
expected_width = (
|
expected_width = (
|
||||||
len(dialog.candidates) * (dialog.THUMBNAIL_SIZE + 16)
|
len(dialog.candidates) * (dialog.candidate_thumbnail_size + 16)
|
||||||
+ (len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
+ (len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
||||||
)
|
)
|
||||||
self.assertIs(dialog.candidate_scroll.widget(), dialog.candidate_content)
|
self.assertIs(dialog.candidate_scroll.widget(), dialog.candidate_content)
|
||||||
@@ -4078,6 +4078,63 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_cover_gallery_candidate_images_resize_with_viewport_and_keep_ratio(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg, account, task, canonical = self._cover_gallery_task(temp_dir)
|
||||||
|
self.write_test_image(canonical, width=240, height=120)
|
||||||
|
dialog = CoverGalleryDialog(task, cfg["image_dir"], cfg["db_path"], account=account)
|
||||||
|
self.addCleanup(dialog.close)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
dialog.resize(650, 460)
|
||||||
|
QApplication.processEvents()
|
||||||
|
dialog._candidate_resize_timer.stop()
|
||||||
|
dialog._update_candidate_thumbnail_layout()
|
||||||
|
small_size = dialog.candidate_thumbnail_size
|
||||||
|
thumbnail = dialog.candidate_thumbnails[canonical]
|
||||||
|
small_pixmap = thumbnail.pixmap()
|
||||||
|
|
||||||
|
self.assertEqual(QSize(small_size, small_size), thumbnail.size())
|
||||||
|
self.assertIsNotNone(small_pixmap)
|
||||||
|
self.assertAlmostEqual(2.0, small_pixmap.width() / small_pixmap.height(), places=1)
|
||||||
|
|
||||||
|
dialog.resize(1100, 700)
|
||||||
|
QApplication.processEvents()
|
||||||
|
dialog._candidate_resize_timer.stop()
|
||||||
|
dialog._update_candidate_thumbnail_layout()
|
||||||
|
large_size = dialog.candidate_thumbnail_size
|
||||||
|
large_pixmap = thumbnail.pixmap()
|
||||||
|
|
||||||
|
self.assertGreater(large_size, small_size)
|
||||||
|
self.assertLessEqual(large_size, dialog.MAX_THUMBNAIL_SIZE)
|
||||||
|
self.assertEqual(QSize(large_size, large_size), thumbnail.size())
|
||||||
|
self.assertAlmostEqual(2.0, large_pixmap.width() / large_pixmap.height(), places=1)
|
||||||
|
expected_width = len(dialog.candidates) * (large_size + 16)
|
||||||
|
expected_width += max(0, len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
||||||
|
self.assertGreaterEqual(dialog.candidate_content.minimumWidth(), expected_width)
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_cover_gallery_broken_candidate_keeps_failure_placeholder_after_resize(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg, account, task, canonical = self._cover_gallery_task(temp_dir)
|
||||||
|
with open(canonical, "wb") as fh:
|
||||||
|
fh.write(b"not-an-image")
|
||||||
|
dialog = CoverGalleryDialog(task, cfg["image_dir"], cfg["db_path"], account=account)
|
||||||
|
self.addCleanup(dialog.close)
|
||||||
|
dialog.resize(1000, 680)
|
||||||
|
dialog._update_candidate_thumbnail_layout()
|
||||||
|
|
||||||
|
thumbnail = dialog.candidate_thumbnails[canonical]
|
||||||
|
self.assertTrue(dialog.candidate_images[canonical].isNull())
|
||||||
|
self.assertEqual("图片读取失败", thumbnail.text())
|
||||||
|
self.assertEqual(
|
||||||
|
QSize(dialog.candidate_thumbnail_size, dialog.candidate_thumbnail_size),
|
||||||
|
thumbnail.size(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
def test_cover_gallery_switch_task_resyncs_candidate_content_width(self):
|
def test_cover_gallery_switch_task_resyncs_candidate_content_width(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
|
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
|
||||||
@@ -4100,7 +4157,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertTrue(dialog.switch_task(1))
|
self.assertTrue(dialog.switch_task(1))
|
||||||
|
|
||||||
expected_width = (
|
expected_width = (
|
||||||
len(dialog.candidates) * (dialog.THUMBNAIL_SIZE + 16)
|
len(dialog.candidates) * (dialog.candidate_thumbnail_size + 16)
|
||||||
+ (len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
+ (len(dialog.candidates) - 1) * dialog.candidate_layout.spacing()
|
||||||
)
|
)
|
||||||
self.assertEqual(tasks[1].id, dialog.task.id)
|
self.assertEqual(tasks[1].id, dialog.task.id)
|
||||||
@@ -4111,6 +4168,40 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_cover_gallery_switch_task_keeps_responsive_thumbnail_size(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.resize(1100, 700)
|
||||||
|
dialog.show()
|
||||||
|
QApplication.processEvents()
|
||||||
|
dialog._candidate_resize_timer.stop()
|
||||||
|
dialog._update_candidate_thumbnail_layout()
|
||||||
|
responsive_size = dialog.candidate_thumbnail_size
|
||||||
|
|
||||||
|
self.assertGreater(responsive_size, dialog.THUMBNAIL_SIZE)
|
||||||
|
self.assertTrue(dialog.switch_task(1))
|
||||||
|
dialog._candidate_resize_timer.stop()
|
||||||
|
dialog._update_candidate_thumbnail_layout()
|
||||||
|
|
||||||
|
self.assertEqual(responsive_size, dialog.candidate_thumbnail_size)
|
||||||
|
for thumbnail in dialog.candidate_thumbnails.values():
|
||||||
|
self.assertEqual(QSize(responsive_size, responsive_size), thumbnail.size())
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -4265,6 +4356,59 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_original_image_dialog_switches_between_fit_and_original_size(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
image_path = self.write_test_image(
|
||||||
|
os.path.join(temp_dir, "large-cover.jpg"),
|
||||||
|
width=1200,
|
||||||
|
height=800,
|
||||||
|
)
|
||||||
|
dialog = OriginalImageDialog(image_path)
|
||||||
|
self.addCleanup(dialog.close)
|
||||||
|
dialog.resize(600, 450)
|
||||||
|
dialog.show()
|
||||||
|
QApplication.processEvents()
|
||||||
|
dialog._resize_timer.stop()
|
||||||
|
dialog._update_image_display()
|
||||||
|
|
||||||
|
fit_pixmap = dialog.image_label.pixmap()
|
||||||
|
viewport_size = dialog.scroll.viewport().size()
|
||||||
|
self.assertTrue(dialog.fit_to_window)
|
||||||
|
self.assertEqual("原始尺寸", dialog.display_mode_button.text())
|
||||||
|
self.assertLessEqual(fit_pixmap.width(), viewport_size.width())
|
||||||
|
self.assertLessEqual(fit_pixmap.height(), viewport_size.height())
|
||||||
|
self.assertAlmostEqual(1.5, fit_pixmap.width() / fit_pixmap.height(), places=1)
|
||||||
|
|
||||||
|
dialog.toggle_display_mode()
|
||||||
|
|
||||||
|
original_pixmap = dialog.image_label.pixmap()
|
||||||
|
self.assertFalse(dialog.fit_to_window)
|
||||||
|
self.assertEqual("适应窗口", dialog.display_mode_button.text())
|
||||||
|
self.assertEqual(QSize(1200, 800), original_pixmap.size())
|
||||||
|
self.assertEqual(QSize(1200, 800), dialog.image_label.size())
|
||||||
|
|
||||||
|
dialog.toggle_display_mode()
|
||||||
|
|
||||||
|
self.assertTrue(dialog.fit_to_window)
|
||||||
|
self.assertEqual("原始尺寸", dialog.display_mode_button.text())
|
||||||
|
self.assertLessEqual(dialog.image_label.pixmap().width(), dialog.scroll.viewport().width())
|
||||||
|
self.assertLessEqual(dialog.image_label.pixmap().height(), dialog.scroll.viewport().height())
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_original_image_dialog_keeps_chinese_failure_placeholder(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
dialog = OriginalImageDialog(os.path.join(temp_dir, "missing.jpg"))
|
||||||
|
self.addCleanup(dialog.close)
|
||||||
|
dialog.resize(520, 400)
|
||||||
|
dialog._update_image_display()
|
||||||
|
|
||||||
|
self.assertTrue(dialog.image.isNull())
|
||||||
|
self.assertEqual("图片读取失败", dialog.image_label.text())
|
||||||
|
self.assertFalse(dialog.display_mode_button.isEnabled())
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
def test_cover_gallery_previous_next_switch_visible_tasks_without_resizing(self):
|
def test_cover_gallery_previous_next_switch_visible_tasks_without_resizing(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg, account, tasks, canonicals, _archives = self._cover_gallery_task_set(temp_dir, count=3)
|
cfg, account, tasks, canonicals, _archives = self._cover_gallery_task_set(temp_dir, count=3)
|
||||||
|
|||||||
Reference in New Issue
Block a user