refactor(rpgmaker): simplify button labels and enhance asset scanning

- Updated button labels for clarity: "Open folder" to "Open", "Decrypt highlighted" to "Decrypt", "Remove highlighted" to "Remove", and "Encrypt all + patch" to "Patch all".
- Improved asset scanning logic to better handle editable images and ensure workspace images remain scannable until removed.
- Added tests for new button functionalities and ensured proper behavior when no items are highlighted.
This commit is contained in:
DazedAnon 2026-07-25 16:49:31 -05:00
parent d82895cc4d
commit 7dee0b7f20
4 changed files with 100 additions and 24 deletions

View file

@ -291,23 +291,23 @@ class RPGMakerImageManager(QWidget):
action_row = QHBoxLayout()
action_row.setSpacing(8)
self.open_workspace_button = QPushButton("Open folder")
self.open_workspace_button = QPushButton("Open")
self.open_workspace_button.setToolTip(
"Open the highlighted image's editable folder, the chosen folder filter, or the "
"editable img root."
)
self.open_workspace_button.clicked.connect(self._open_editable_folder)
self.decrypt_selected_button = QPushButton("Decrypt highlighted")
self.decrypt_selected_button = QPushButton("Decrypt")
self.decrypt_selected_button.clicked.connect(self._decrypt_checked)
self.decrypt_all_button = QPushButton("Decrypt all")
self.decrypt_all_button.clicked.connect(self._decrypt_all)
self.remove_button = QPushButton("Remove highlighted")
self.remove_button = QPushButton("Remove")
self.remove_button.setToolTip(
"Delete highlighted PNG copies from the editable folder. Runtime images remain "
"untouched and can be decrypted again. The Delete key does the same thing."
)
self.remove_button.clicked.connect(self._remove_highlighted)
self.prepare_button = QPushButton("Encrypt all + patch")
self.prepare_button = QPushButton("Patch all")
self.prepare_button.setStyleSheet(
"QPushButton{border:1px solid #4ec9b0;color:#4ec9b0;font-weight:bold;padding:6px 14px;}"
"QPushButton:hover{background:#18352f;}"
@ -494,9 +494,14 @@ class RPGMakerImageManager(QWidget):
label = asset.relative_png.name
item = QListWidgetItem(placeholder_icon, label)
item.setData(_ASSET_ID_ROLE, asset.asset_id)
kind = "encrypted + editable" if asset.has_encrypted and asset.has_plain else (
"encrypted" if asset.has_encrypted else "runtime PNG"
)
if asset.has_encrypted and asset.has_plain:
kind = "encrypted + editable"
elif asset.has_encrypted:
kind = "encrypted"
elif asset.has_runtime_plain:
kind = "runtime PNG"
else:
kind = "editable PNG"
item.setToolTip(f"{asset.asset_id}\n{kind}")
self.image_list.addItem(item)
item.setSelected(asset.asset_id in self.selected_ids)
@ -562,9 +567,9 @@ class RPGMakerImageManager(QWidget):
def _update_prepare_scope(self) -> None:
if self.selected_ids:
self.prepare_button.setText("Encrypt highlighted + patch")
self.prepare_button.setText("Patch selected")
else:
self.prepare_button.setText("Encrypt all + patch")
self.prepare_button.setText("Patch all")
def _show_preview(self, current: QListWidgetItem | None, _previous=None) -> None:
if current is None:
@ -606,14 +611,11 @@ class RPGMakerImageManager(QWidget):
return
try:
workspace = ensure_editable_workspace(self.game_root)
current = self.image_list.currentItem()
asset = (
self.assets_by_id.get(current.data(_ASSET_ID_ROLE))
if current is not None
else None
)
if asset is not None:
target = asset.plain_path.parent
highlighted_parents = {
asset.plain_path.parent for asset in self._selected_assets()
}
if len(highlighted_parents) == 1:
target = highlighted_parents.pop()
else:
root = Path(self.game_root).expanduser().resolve()
content_relative = resolve_content_root(root).relative_to(root)

View file

@ -193,7 +193,7 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
self.assertFalse(asset.plain_path.exists())
self.assertTrue(asset.runtime_plain_path.exists())
self.assertEqual(self.manager.image_list.count(), 0)
self.assertEqual(self.manager.remove_button.text(), "Remove highlighted")
self.assertEqual(self.manager.remove_button.text(), "Remove")
def test_prepare_uses_only_highlighted_editable_images(self):
highlighted = self.manager.assets[0]
@ -214,9 +214,7 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
self.assertEqual(assets, [highlighted])
self.assertTrue(highlighted.plain_path.exists())
self.assertTrue(not_highlighted.plain_path.exists())
self.assertEqual(
self.manager.prepare_button.text(), "Encrypt highlighted + patch"
)
self.assertEqual(self.manager.prepare_button.text(), "Patch selected")
def test_prepare_uses_all_editable_images_without_highlights(self):
editable = self.manager.assets[:2]
@ -233,7 +231,7 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
action, assets = start_action.call_args.args
self.assertEqual(action, "prepare")
self.assertEqual(assets, editable)
self.assertEqual(self.manager.prepare_button.text(), "Encrypt all + patch")
self.assertEqual(self.manager.prepare_button.text(), "Patch all")
def test_bottom_controls_share_one_row_and_action_width(self):
action_buttons = (
@ -256,6 +254,22 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
self._click(0)
asset_id = self.manager.image_list.currentItem().data(Qt.UserRole + 1)
expected = self.manager.assets_by_id[asset_id].plain_path.parent
self.manager.image_list.setCurrentItem(None)
with patch(
"gui.rpgmaker_image_manager.QDesktopServices.openUrl",
return_value=True,
) as open_url:
self.manager._open_editable_folder()
opened_url = open_url.call_args.args[0]
self.assertEqual(Path(opened_url.toLocalFile()), expected)
self.assertTrue(expected.is_dir())
def test_open_folder_ignores_current_item_without_highlights(self):
self.manager.image_list.setCurrentRow(0)
self.manager.selected_ids.clear()
expected = self.game_root / ".dazedtl" / "images" / "img"
with patch(
"gui.rpgmaker_image_manager.QDesktopServices.openUrl",

View file

@ -88,6 +88,46 @@ class RPGMakerImageTests(unittest.TestCase):
self.assertEqual(result.skipped, 1)
self.assertEqual(plain.read_bytes(), translated)
def test_separate_decrypt_batches_accumulate_without_overwriting(self):
self._encrypted_asset("001.rpgmvp", "red")
self._encrypted_asset("002.rpgmvp", "green")
first, second = scan_image_assets(self.root)
first_result = decrypt_assets([first], KEY, game_root=self.root)
edited = png_bytes("blue")
first.plain_path.write_bytes(edited)
refreshed = {asset.asset_id: asset for asset in scan_image_assets(self.root)}
second_result = decrypt_assets(
[refreshed[second.asset_id]], KEY, game_root=self.root
)
editable = {
asset.asset_id: asset
for asset in scan_image_assets(self.root)
if asset.has_plain
}
self.assertEqual(first_result.completed, 1)
self.assertEqual(second_result.completed, 1)
self.assertEqual(set(editable), {first.asset_id, second.asset_id})
self.assertEqual(editable[first.asset_id].plain_path.read_bytes(), edited)
self.assertEqual(
editable[second.asset_id].plain_path.read_bytes(), png_bytes("green")
)
def test_workspace_image_remains_scannable_until_removed(self):
encrypted = self._encrypted_asset()
asset = scan_image_assets(self.root)[0]
decrypt_assets([asset], KEY, game_root=self.root)
encrypted.unlink()
editable_only = scan_image_assets(self.root)
self.assertEqual([item.asset_id for item in editable_only], [asset.asset_id])
self.assertTrue(editable_only[0].has_plain)
self.assertFalse(editable_only[0].has_encrypted)
remove_editable_assets(self.root, editable_only)
self.assertEqual(scan_image_assets(self.root), [])
def test_remove_editable_image_deletes_only_workspace_copy(self):
self._encrypted_asset()
asset = scan_image_assets(self.root)[0]

View file

@ -122,11 +122,13 @@ def _logical_png(path: Path) -> Path:
def scan_image_assets(game_root: str | Path) -> list[ImageAsset]:
"""Scan ``img/`` and combine matching PNG/encrypted files into records."""
"""Scan runtime and editable image trees and combine matching files."""
root = Path(game_root).expanduser().resolve()
content_root = resolve_content_root(root)
image_root = content_root / "img"
workspace_root = editable_workspace_root(root)
workspace_content_root = workspace_root / content_root.relative_to(root)
by_id: dict[str, dict[str, Path]] = {}
for path in image_root.rglob("*"):
if not path.is_file():
@ -149,11 +151,29 @@ def scan_image_assets(game_root: str | Path) -> list[ImageAsset]:
else:
entry["runtime_plain"] = path
editable_image_root = workspace_content_root / "img"
if editable_image_root.is_dir():
for path in editable_image_root.rglob("*"):
if not path.is_file() or path.suffix.casefold() != ".png":
continue
relative = path.relative_to(workspace_content_root)
asset_id = relative.as_posix()
entry = by_id.setdefault(
asset_id,
{
"relative": relative,
"workspace_relative": path.relative_to(workspace_root),
},
)
entry["plain"] = path
assets = [
ImageAsset(
asset_id=asset_id,
relative_png=entry["relative"],
plain_path=editable_workspace_root(root) / entry["workspace_relative"],
plain_path=entry.get(
"plain", workspace_root / entry["workspace_relative"]
),
encrypted_path=entry.get("encrypted"),
runtime_plain_path=entry.get("runtime_plain"),
)