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

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) 

15 

16from . import theme 

17from .i18n import tr 

18 

19 

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 ) 

27 

28 

29class NormalizeNumbersDialog(QDialog): 

30 """振り直す番号の一覧と、撮影済みカットへの注意を見せてから実行する。 

31 

32 ``layers`` は上から表示する順の ``(layer_index, name, mapping)`` の並び。 

33 ``selected`` は選択中のレイヤー番号の集合。 

34 """ 

35 

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() 

47 

48 layout = QVBoxLayout(self) 

49 layout.setSpacing(10) 

50 

51 summary = QLabel( 

52 tr("左から最初に出てくる順に 1, 2, 3… と振り直します。" 

53 "同じ絵を使い回しているセルは同じ番号のままです。") 

54 ) 

55 summary.setWordWrap(True) 

56 layout.addWidget(summary) 

57 

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) 

68 

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) 

74 

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) 

113 

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) 

124 

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() 

132 

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 ] 

140 

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 ) 

147 

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("&", "&amp;").replace("<", "&lt;"), 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))