Wrap group is based on scene for maps/events
This commit is contained in:
parent
5ff504e549
commit
9db8089076
3 changed files with 130 additions and 53 deletions
|
|
@ -3015,7 +3015,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
scope = (
|
scope = (
|
||||||
wolf_ws.scope_stats(doc, hit, width)
|
wolf_ws.scope_stats(doc, hit, width)
|
||||||
if doc is not None
|
if doc is not None
|
||||||
else wolf_ws.ScopeStats(label=wolf_ws.scope_label(hit), total=0, overflow=0)
|
else wolf_ws.ScopeStats(label=wolf_ws.scope_label(hit, doc), total=0, overflow=0)
|
||||||
)
|
)
|
||||||
overflow_note = (
|
overflow_note = (
|
||||||
f" · {scope.overflow} of {scope.total} lines overflow at width {width}"
|
f" · {scope.overflow} of {scope.total} lines overflow at width {width}"
|
||||||
|
|
@ -3380,7 +3380,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
width=width,
|
width=width,
|
||||||
font=font,
|
font=font,
|
||||||
)
|
)
|
||||||
scope = wolf_ws.scope_label(hit)
|
scope = wolf_ws.scope_label(hit, self._current_wrap_doc)
|
||||||
font_note = (
|
font_note = (
|
||||||
"fonts unchanged"
|
"fonts unchanged"
|
||||||
if font is None
|
if font is None
|
||||||
|
|
@ -3427,7 +3427,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
self._remember_sheet_width(
|
self._remember_sheet_width(
|
||||||
hit.sheet_name, width, hit.json_file, max_lines=max_lines or None
|
hit.sheet_name, width, hit.json_file, max_lines=max_lines or None
|
||||||
)
|
)
|
||||||
scope = wolf_ws.scope_label(hit)
|
scope = wolf_ws.scope_label(hit, self._current_wrap_doc)
|
||||||
if max_lines > 0:
|
if max_lines > 0:
|
||||||
msg = (
|
msg = (
|
||||||
f"Relayout {count} line(s) in {scope} "
|
f"Relayout {count} line(s) in {scope} "
|
||||||
|
|
|
||||||
|
|
@ -243,25 +243,34 @@ class TestManualFontAndWrap(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestEventScopeWrap(unittest.TestCase):
|
class TestEventScopeWrap(unittest.TestCase):
|
||||||
def test_wrap_all_in_map_file(self):
|
def test_wrap_all_in_map_scene_only(self):
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
path = Path(tmp) / "Map001.mps.json"
|
path = Path(tmp) / "Map001.mps.json"
|
||||||
|
long_a = (
|
||||||
|
"This is a very long line of dialogue that "
|
||||||
|
"should wrap at thirty four visible chars."
|
||||||
|
)
|
||||||
|
long_b = (
|
||||||
|
"Another very long line of dialogue that also "
|
||||||
|
"should wrap at thirty four visible chars."
|
||||||
|
)
|
||||||
doc = {
|
doc = {
|
||||||
"kind": "map",
|
"kind": "map",
|
||||||
"file": "Map001.mps",
|
"file": "Map001.mps",
|
||||||
"scenes": [
|
"scenes": [
|
||||||
{
|
{
|
||||||
"event": 1,
|
"event": 1,
|
||||||
|
"name": "Intro",
|
||||||
"lines": [
|
"lines": [
|
||||||
{
|
{"text": long_a},
|
||||||
"text": (
|
|
||||||
"This is a very long line of dialogue that "
|
|
||||||
"should wrap at thirty four visible chars."
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{"text": "Short line."},
|
{"text": "Short line."},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"event": 2,
|
||||||
|
"name": "Other",
|
||||||
|
"lines": [{"text": long_b}],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
path.write_text(json.dumps(doc, ensure_ascii=False), encoding="utf-8")
|
path.write_text(json.dumps(doc, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
@ -270,15 +279,22 @@ class TestEventScopeWrap(unittest.TestCase):
|
||||||
kind="map",
|
kind="map",
|
||||||
sheet_name="Map001.mps",
|
sheet_name="Map001.mps",
|
||||||
row=1,
|
row=1,
|
||||||
field_name="scene 0 line 0",
|
field_name="UI",
|
||||||
text="very long line",
|
text="very long line",
|
||||||
max_line_len=80,
|
max_line_len=80,
|
||||||
scene_index=0,
|
scene_index=0,
|
||||||
line_index=0,
|
line_index=0,
|
||||||
map_file="Map001.mps",
|
map_file="Map001.mps",
|
||||||
)
|
)
|
||||||
self.assertEqual(ws.scope_stats(doc, hit, 34).overflow, 1)
|
stats = ws.scope_stats(doc, hit, 34)
|
||||||
|
self.assertEqual(stats.total, 2)
|
||||||
|
self.assertEqual(stats.overflow, 1)
|
||||||
|
self.assertIn("event 1", stats.label)
|
||||||
|
self.assertIn("Intro", stats.label)
|
||||||
self.assertEqual(ws.wrap_overflow_in_scope(path, doc, hit, 34), 1)
|
self.assertEqual(ws.wrap_overflow_in_scope(path, doc, hit, 34), 1)
|
||||||
|
# Scene 2 must stay untouched.
|
||||||
|
self.assertEqual(doc["scenes"][1]["lines"][0]["text"], long_b)
|
||||||
|
self.assertNotEqual(doc["scenes"][0]["lines"][0]["text"], long_a)
|
||||||
|
|
||||||
|
|
||||||
class TestFitTextToBox(unittest.TestCase):
|
class TestFitTextToBox(unittest.TestCase):
|
||||||
|
|
|
||||||
|
|
@ -820,18 +820,16 @@ def wrap_overflow_manual_in_scope(
|
||||||
font: int | None = None,
|
font: int | None = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Manual wrap (+ optional proportional font) for every line in *hit*'s group."""
|
"""Manual wrap (+ optional proportional font) for every line in *hit*'s group."""
|
||||||
if isinstance(hit, WrapHit):
|
kind, sheet_name = _hit_kind_and_sheet(hit, doc)
|
||||||
kind, sheet_name = hit.kind, hit.sheet_name
|
|
||||||
else:
|
|
||||||
kind = str(hit.get("kind") or doc.get("kind") or "")
|
|
||||||
sheet_name = str(hit.get("sheet_name") or "")
|
|
||||||
|
|
||||||
if kind == "names":
|
if kind == "names":
|
||||||
return _wrap_manual_in_names_category(path, doc, sheet_name, width, font=font)
|
return _wrap_manual_in_names_category(path, doc, sheet_name, width, font=font)
|
||||||
if kind == "db":
|
if kind == "db":
|
||||||
return _wrap_manual_in_db_sheet(path, doc, sheet_name, width, font=font)
|
return _wrap_manual_in_db_sheet(path, doc, sheet_name, width, font=font)
|
||||||
if kind in ("map", "common"):
|
if kind in ("map", "common"):
|
||||||
return _wrap_manual_in_event_file(path, doc, width, font=font)
|
return _wrap_manual_in_event_scene(
|
||||||
|
path, doc, _hit_scene_index(hit), width, font=font
|
||||||
|
)
|
||||||
if kind == "gamedat":
|
if kind == "gamedat":
|
||||||
return _wrap_manual_in_gamedat(path, doc, width, font=font)
|
return _wrap_manual_in_gamedat(path, doc, width, font=font)
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -900,20 +898,24 @@ def _wrap_manual_in_db_sheet(
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|
||||||
def _wrap_manual_in_event_file(
|
def _wrap_manual_in_event_scene(
|
||||||
path: Path,
|
path: Path,
|
||||||
doc: dict[str, Any],
|
doc: dict[str, Any],
|
||||||
|
scene_index: int | None,
|
||||||
width: int,
|
width: int,
|
||||||
*,
|
*,
|
||||||
font: int | None = None,
|
font: int | None = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
|
"""Manual-wrap overflowing lines in one map/CommonEvent scene."""
|
||||||
if doc.get("kind") not in ("map", "common"):
|
if doc.get("kind") not in ("map", "common"):
|
||||||
return 0
|
return 0
|
||||||
|
scenes = doc.get("scenes") or []
|
||||||
|
if scene_index is None or not (0 <= scene_index < len(scenes)):
|
||||||
|
return 0
|
||||||
changed = 0
|
changed = 0
|
||||||
for scene in doc.get("scenes") or []:
|
for line in scenes[scene_index].get("lines") or []:
|
||||||
for line in scene.get("lines") or []:
|
if _apply_manual_to_line_dict(line, width, font=font):
|
||||||
if _apply_manual_to_line_dict(line, width, font=font):
|
changed += 1
|
||||||
changed += 1
|
|
||||||
if changed:
|
if changed:
|
||||||
save_document(path, doc)
|
save_document(path, doc)
|
||||||
return changed
|
return changed
|
||||||
|
|
@ -946,13 +948,66 @@ class ScopeStats:
|
||||||
overflow: int
|
overflow: int
|
||||||
|
|
||||||
|
|
||||||
def scope_label(hit: WrapHit | dict[str, Any]) -> str:
|
def _hit_kind_and_sheet(hit: WrapHit | dict[str, Any], doc: dict[str, Any] | None = None) -> tuple[str, str]:
|
||||||
"""Human-readable group name for status messages."""
|
|
||||||
if isinstance(hit, WrapHit):
|
if isinstance(hit, WrapHit):
|
||||||
kind, sheet, jf = hit.kind, hit.sheet_name, hit.json_file
|
return hit.kind, hit.sheet_name
|
||||||
|
kind = str(hit.get("kind") or (doc or {}).get("kind") or "")
|
||||||
|
sheet = str(hit.get("sheet_name") or "")
|
||||||
|
return kind, sheet
|
||||||
|
|
||||||
|
|
||||||
|
def _hit_scene_index(hit: WrapHit | dict[str, Any]) -> int | None:
|
||||||
|
if isinstance(hit, WrapHit):
|
||||||
|
return hit.scene_index
|
||||||
|
raw = hit.get("scene_index")
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(raw)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _scene_group_label(doc: dict[str, Any] | None, hit: WrapHit | dict[str, Any]) -> str:
|
||||||
|
"""Label for one map/CommonEvent scene (the wrap-group unit)."""
|
||||||
|
kind, sheet = _hit_kind_and_sheet(hit, doc)
|
||||||
|
si = _hit_scene_index(hit)
|
||||||
|
scene: dict[str, Any] | None = None
|
||||||
|
if doc is not None and si is not None:
|
||||||
|
scenes = doc.get("scenes") or []
|
||||||
|
if 0 <= si < len(scenes) and isinstance(scenes[si], dict):
|
||||||
|
scene = scenes[si]
|
||||||
|
event_id = None
|
||||||
|
name = ""
|
||||||
|
if scene is not None:
|
||||||
|
event_id = scene.get("event")
|
||||||
|
name = str(scene.get("name") or "").strip()
|
||||||
|
elif isinstance(hit, WrapHit) and hit.row is not None:
|
||||||
|
event_id = hit.row
|
||||||
|
elif isinstance(hit, dict) and hit.get("row") is not None:
|
||||||
|
event_id = hit.get("row")
|
||||||
|
|
||||||
|
parts: list[str] = []
|
||||||
|
if kind == "common":
|
||||||
|
parts.append("CommonEvent")
|
||||||
|
elif sheet:
|
||||||
|
parts.append(sheet)
|
||||||
|
if event_id is not None:
|
||||||
|
parts.append(f"event {event_id}")
|
||||||
|
if name:
|
||||||
|
parts.append(name)
|
||||||
|
elif si is not None and event_id is None:
|
||||||
|
parts.append(f"scene {si}")
|
||||||
|
return " · ".join(parts) if parts else (sheet or "event")
|
||||||
|
|
||||||
|
|
||||||
|
def scope_label(hit: WrapHit | dict[str, Any], doc: dict[str, Any] | None = None) -> str:
|
||||||
|
"""Human-readable group name for status messages."""
|
||||||
|
kind, sheet = _hit_kind_and_sheet(hit, doc)
|
||||||
|
jf = ""
|
||||||
|
if isinstance(hit, WrapHit):
|
||||||
|
jf = hit.json_file
|
||||||
else:
|
else:
|
||||||
kind = str(hit.get("kind") or "")
|
|
||||||
sheet = str(hit.get("sheet_name") or "")
|
|
||||||
jf = str(hit.get("json_file") or "")
|
jf = str(hit.get("json_file") or "")
|
||||||
if kind == "db":
|
if kind == "db":
|
||||||
return f"sheet {sheet}"
|
return f"sheet {sheet}"
|
||||||
|
|
@ -960,10 +1015,8 @@ def scope_label(hit: WrapHit | dict[str, Any]) -> str:
|
||||||
return f"category {sheet}"
|
return f"category {sheet}"
|
||||||
if kind == "gamedat":
|
if kind == "gamedat":
|
||||||
return "Game.dat"
|
return "Game.dat"
|
||||||
if kind == "common":
|
if kind in ("map", "common"):
|
||||||
return "CommonEvent"
|
return _scene_group_label(doc, hit)
|
||||||
if kind == "map":
|
|
||||||
return sheet or jf
|
|
||||||
return sheet or jf
|
return sheet or jf
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -973,11 +1026,7 @@ def scope_stats(
|
||||||
width: int,
|
width: int,
|
||||||
) -> ScopeStats:
|
) -> ScopeStats:
|
||||||
"""Count lines and overflows in the same group as *hit*."""
|
"""Count lines and overflows in the same group as *hit*."""
|
||||||
if isinstance(hit, WrapHit):
|
kind, sheet_name = _hit_kind_and_sheet(hit, doc)
|
||||||
kind, sheet_name = hit.kind, hit.sheet_name
|
|
||||||
else:
|
|
||||||
kind = str(hit.get("kind") or doc.get("kind") or "")
|
|
||||||
sheet_name = str(hit.get("sheet_name") or "")
|
|
||||||
|
|
||||||
total = 0
|
total = 0
|
||||||
overflow = 0
|
overflow = 0
|
||||||
|
|
@ -1007,8 +1056,15 @@ def scope_stats(
|
||||||
if line_needs_wrap(text, width):
|
if line_needs_wrap(text, width):
|
||||||
overflow += 1
|
overflow += 1
|
||||||
elif kind in ("map", "common"):
|
elif kind in ("map", "common"):
|
||||||
for scene in doc.get("scenes") or []:
|
si = _hit_scene_index(hit)
|
||||||
for line in scene.get("lines") or []:
|
scenes = doc.get("scenes") or []
|
||||||
|
if si is not None and 0 <= si < len(scenes):
|
||||||
|
scene_iter = [scenes[si]]
|
||||||
|
else:
|
||||||
|
# No scene on the hit: fall back to whole file (legacy).
|
||||||
|
scene_iter = scenes
|
||||||
|
for scene in scene_iter:
|
||||||
|
for line in (scene.get("lines") or []):
|
||||||
text = line.get("text")
|
text = line.get("text")
|
||||||
if not isinstance(text, str) or not text.strip():
|
if not isinstance(text, str) or not text.strip():
|
||||||
continue
|
continue
|
||||||
|
|
@ -1026,7 +1082,7 @@ def scope_stats(
|
||||||
if line_needs_wrap(text, width):
|
if line_needs_wrap(text, width):
|
||||||
overflow += 1
|
overflow += 1
|
||||||
|
|
||||||
return ScopeStats(label=scope_label(hit), total=total, overflow=overflow)
|
return ScopeStats(label=scope_label(hit, doc), total=total, overflow=overflow)
|
||||||
|
|
||||||
|
|
||||||
def _line_needs_relayout(text: str, width: int, max_lines: int | None) -> bool:
|
def _line_needs_relayout(text: str, width: int, max_lines: int | None) -> bool:
|
||||||
|
|
@ -1040,26 +1096,29 @@ def _line_needs_relayout(text: str, width: int, max_lines: int | None) -> bool:
|
||||||
return count_soft_lines(wrapped) > limit or wrapped != text
|
return count_soft_lines(wrapped) > limit or wrapped != text
|
||||||
|
|
||||||
|
|
||||||
def _wrap_overflow_in_event_file(
|
def _wrap_overflow_in_event_scene(
|
||||||
path: Path,
|
path: Path,
|
||||||
doc: dict[str, Any],
|
doc: dict[str, Any],
|
||||||
|
scene_index: int | None,
|
||||||
width: int,
|
width: int,
|
||||||
*,
|
*,
|
||||||
max_lines: int | None = None,
|
max_lines: int | None = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Wrap overflowing dialogue lines in one map or CommonEvent JSON file."""
|
"""Wrap overflowing dialogue lines in one map or CommonEvent scene."""
|
||||||
if doc.get("kind") not in ("map", "common"):
|
if doc.get("kind") not in ("map", "common"):
|
||||||
return 0
|
return 0
|
||||||
|
scenes = doc.get("scenes") or []
|
||||||
|
if scene_index is None or not (0 <= scene_index < len(scenes)):
|
||||||
|
return 0
|
||||||
changed = 0
|
changed = 0
|
||||||
for scene in doc.get("scenes") or []:
|
for line in scenes[scene_index].get("lines") or []:
|
||||||
for line in scene.get("lines") or []:
|
text = line.get("text")
|
||||||
text = line.get("text")
|
if not isinstance(text, str) or not text.strip():
|
||||||
if not isinstance(text, str) or not text.strip():
|
continue
|
||||||
continue
|
if not _line_needs_relayout(text, width, max_lines):
|
||||||
if not _line_needs_relayout(text, width, max_lines):
|
continue
|
||||||
continue
|
if apply_relayout_to_line_dict(line, width, max_lines=max_lines):
|
||||||
if apply_relayout_to_line_dict(line, width, max_lines=max_lines):
|
changed += 1
|
||||||
changed += 1
|
|
||||||
if changed:
|
if changed:
|
||||||
save_document(path, doc)
|
save_document(path, doc)
|
||||||
return changed
|
return changed
|
||||||
|
|
@ -1117,7 +1176,9 @@ def wrap_overflow_in_scope(
|
||||||
path, doc, sheet_name, width, kind="db", max_lines=max_lines
|
path, doc, sheet_name, width, kind="db", max_lines=max_lines
|
||||||
)
|
)
|
||||||
if kind in ("map", "common"):
|
if kind in ("map", "common"):
|
||||||
return _wrap_overflow_in_event_file(path, doc, width, max_lines=max_lines)
|
return _wrap_overflow_in_event_scene(
|
||||||
|
path, doc, _hit_scene_index(hit), width, max_lines=max_lines
|
||||||
|
)
|
||||||
if kind == "gamedat":
|
if kind == "gamedat":
|
||||||
return _wrap_overflow_in_gamedat(path, doc, width, max_lines=max_lines)
|
return _wrap_overflow_in_gamedat(path, doc, width, max_lines=max_lines)
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue