Coverage for paintmaskanimator/normalize_dialog.py: 99%
99 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
1"""番号の正規化(タイムラインの順番で振り直す)の確認画面。"""
2from PySide6.QtGui import QColor
3from PySide6.QtWidgets import (
4 QButtonGroup,
5 QDialog,
6 QDialogButtonBox,
7 QFrame,
8 QHBoxLayout,
9 QLabel,
10 QPushButton,
11 QRadioButton,
12 QVBoxLayout,
13 QWidget,
14)
16from . import theme
17from .i18n import tr
20def number_changes(mapping):
21 """{旧番号: 新番号} から、実際に番号が変わるものだけを旧番号順で返す。"""
22 return sorted(
23 (int(old), int(new))
24 for old, new in dict(mapping).items()
25 if int(old) != int(new)
26 )
29class NormalizeNumbersDialog(QDialog):
30 """振り直す番号の一覧と、撮影済みカットへの注意を見せてから実行する。
32 ``layers`` は上から表示する順の ``(layer_index, name, mapping)`` の並び。
33 ``selected`` は選択中のレイヤー番号の集合。
34 """
36 def __init__(self, layers, selected, parent=None):
37 super().__init__(parent)
38 self.setWindowTitle(tr("タイムラインの順番で正規化"))
39 self.setModal(True)
40 self.setMinimumWidth(460)
41 self._layers = [
42 (int(index), str(name), number_changes(mapping))
43 for index, name, mapping in layers
44 ]
45 self._selected = {int(index) for index in selected}
46 c = theme.palette()
48 layout = QVBoxLayout(self)
49 layout.setSpacing(10)
51 summary = QLabel(
52 tr("左から最初に出てくる順に 1, 2, 3… と振り直します。"
53 "同じ絵を使い回しているセルは同じ番号のままです。")
54 )
55 summary.setWordWrap(True)
56 layout.addWidget(summary)
58 scope_row = QHBoxLayout()
59 self.scope_selected = QRadioButton(tr("選択レイヤー"))
60 self.scope_all = QRadioButton(tr("すべてのレイヤー"))
61 self._scope_group = QButtonGroup(self)
62 self._scope_group.addButton(self.scope_selected)
63 self._scope_group.addButton(self.scope_all)
64 scope_row.addWidget(self.scope_selected)
65 scope_row.addWidget(self.scope_all)
66 scope_row.addStretch()
67 layout.addLayout(scope_row)
69 self._changes_box = QWidget()
70 self._changes_layout = QVBoxLayout(self._changes_box)
71 self._changes_layout.setContentsMargins(0, 0, 0, 0)
72 self._changes_layout.setSpacing(4)
73 layout.addWidget(self._changes_box)
75 # リテイクで中割りを足したカットを正規化すると、AE のタイムリマップと
76 # 番号が食い違う。確認画面では必ずこの注意を出す。
77 warning = QColor(c["warning"])
78 fill = QColor(warning)
79 fill.setAlpha(34)
80 caution = QFrame()
81 caution.setObjectName("normalizeCaution")
82 caution.setStyleSheet(
83 "QFrame#normalizeCaution{background:rgba(%d,%d,%d,%d);"
84 "border:1px solid %s;border-radius:6px;}"
85 "QFrame#normalizeCaution QLabel{background:transparent;border:0;color:%s;}"
86 % (
87 fill.red(), fill.green(), fill.blue(), fill.alpha(),
88 warning.name(), c["text"],
89 )
90 )
91 caution_layout = QVBoxLayout(caution)
92 caution_layout.setContentsMargins(10, 8, 10, 8)
93 caution_layout.setSpacing(4)
94 caution_title = QLabel(
95 tr("撮影済みのカットをリテイクで直しているときは、正規化しないでください。")
96 )
97 caution_title.setWordWrap(True)
98 title_font = caution_title.font()
99 title_font.setBold(True)
100 caution_title.setFont(title_font)
101 caution_title.setStyleSheet("color:%s;" % warning.name())
102 caution_body = QLabel(
103 tr("AE ですでにタイムリマップを打っているため、中割りを足したところ以外の"
104 "番号が変わると、同じ番号が別の絵を指すようになります。"
105 "たとえば A4 は、リテイクの前後で同じ絵であるべきです。"
106 "足した絵は末尾の番号のままにしておき、タイムリマップ側で追加してください。")
107 )
108 caution_body.setWordWrap(True)
109 caution_layout.addWidget(caution_title)
110 caution_layout.addWidget(caution_body)
111 self.caution = caution
112 layout.addWidget(caution)
114 buttons = QDialogButtonBox()
115 self.cancel_button = buttons.addButton(QDialogButtonBox.StandardButton.Cancel)
116 self.apply_button = QPushButton()
117 self.apply_button.setDefault(False)
118 self.apply_button.setAutoDefault(False)
119 self.cancel_button.setDefault(True)
120 buttons.addButton(self.apply_button, QDialogButtonBox.ButtonRole.AcceptRole)
121 buttons.accepted.connect(self.accept)
122 buttons.rejected.connect(self.reject)
123 layout.addWidget(buttons)
125 has_selected_changes = any(
126 changes for index, _name, changes in self._layers
127 if index in self._selected
128 )
129 (self.scope_selected if has_selected_changes else self.scope_all).setChecked(True)
130 self.scope_selected.toggled.connect(self._refresh)
131 self._refresh()
133 def target_layer_indices(self):
134 """実行したときに振り直すレイヤー(番号が変わるものだけ)。"""
135 use_all = self.scope_all.isChecked()
136 return [
137 index for index, _name, changes in self._layers
138 if changes and (use_all or index in self._selected)
139 ]
141 def change_count(self):
142 targets = set(self.target_layer_indices())
143 return sum(
144 len(changes) for index, _name, changes in self._layers
145 if index in targets
146 )
148 def _refresh(self):
149 while self._changes_layout.count():
150 item = self._changes_layout.takeAt(0)
151 widget = item.widget() if item is not None else None
152 if widget is not None: 152 ↛ 149line 152 didn't jump to line 149 because the condition on line 152 was always true
153 widget.deleteLater()
154 targets = set(self.target_layer_indices())
155 c = theme.palette()
156 for index, name, changes in self._layers:
157 if index not in targets:
158 continue
159 pairs = " ".join(f"{old} → {new}" for old, new in changes)
160 label = QLabel(
161 "<b>%s</b> <span style='color:%s'>%s</span>"
162 % (name.replace("&", "&").replace("<", "<"), c["accent"], pairs)
163 )
164 label.setWordWrap(True)
165 self._changes_layout.addWidget(label)
166 count = self.change_count()
167 if count:
168 self.apply_button.setText(
169 tr("{count}個の番号を振り直す").format(count=count)
170 )
171 else:
172 empty = QLabel(tr("このレイヤーの番号は、すでにタイムラインの順番どおりです。"))
173 empty.setStyleSheet("color:%s;" % c["text_muted"])
174 self._changes_layout.addWidget(empty)
175 self.apply_button.setText(tr("振り直す"))
176 self.apply_button.setEnabled(bool(count))