Coverage for graphica/core/commands.py: 100.0%
156 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1import logging
2from PySide6.QtGui import QUndoCommand
3from typing import TYPE_CHECKING, Any, Callable
4if TYPE_CHECKING:
5 from graphica.core.dataset import Dataset
6 from graphica.models.project import ProjectModel
8logger = logging.getLogger(__name__)
11# コマンドは Dataset の公開メソッドだけを呼び、GUI を知らない。表の描き直しと通知は
12# DataEditorDialog が QUndoStack.indexChanged で行う。
14class EditCellCommand(QUndoCommand):
15 def __init__(self, dataset: "Dataset", row_idx: Any, col_name: str, old_value: Any, new_value: Any,
16 description: str = "セル編集") -> None:
17 """row_idx は dataset.df の index ラベル。"""
18 super().__init__(description)
19 self.dataset = dataset
20 self.row_idx = row_idx
21 self.col_name = col_name
22 self.old_value = old_value
23 self.new_value = new_value
25 def redo(self) -> None:
26 self.dataset.set_cell(self.row_idx, self.col_name, self.new_value)
28 def undo(self) -> None:
29 self.dataset.set_cell(self.row_idx, self.col_name, self.old_value)
32class AddRowCommand(QUndoCommand):
33 def __init__(self, dataset: "Dataset", description: str = "行追加") -> None:
34 super().__init__(description)
35 self.dataset = dataset
37 def redo(self) -> None:
38 self.dataset.add_row()
40 def undo(self) -> None:
41 self.dataset.delete_last_row()
44class DeleteRowsCommand(QUndoCommand):
45 def __init__(self, dataset: "Dataset", row_indices: list[Any], deleted_data: Any, description: str = "行削除") -> None:
46 """row_indices は df の index ラベル、deleted_data は Undo 用の消した行。"""
47 super().__init__(description)
48 self.dataset = dataset
49 self.row_indices = row_indices
50 self.deleted_data = deleted_data
52 def redo(self) -> None:
53 self.dataset.delete_rows(self.row_indices)
55 def undo(self) -> None:
56 self.dataset.restore_rows(self.deleted_data)
59class AddColumnCommand(QUndoCommand):
60 def __init__(self, dataset: "Dataset", col_name: str, description: str = "列追加") -> None:
61 super().__init__(description)
62 self.dataset = dataset
63 self.col_name = col_name
65 def redo(self) -> None:
66 self.dataset.add_column(self.col_name)
68 def undo(self) -> None:
69 # 足した後にその列を X/Y にされていたら、消すと描画が壊れるので取り消さない
70 if self.dataset.is_column_in_use(self.col_name):
71 logger.warning("Undo不可: 列 '%s' はプロットに使用されています。", self.col_name)
72 self.setObsolete(True)
73 return
74 self.dataset.remove_column(self.col_name)
77class DeleteColumnCommand(QUndoCommand):
78 def __init__(self, dataset: "Dataset", col_name: str, deleted_column_data: Any, description: str = "列削除") -> None:
79 super().__init__(description)
80 self.dataset = dataset
81 self.col_name = col_name
82 self.deleted_column_data = deleted_column_data
84 def redo(self) -> None:
85 # 取り消し → 列を X/Y に設定 → やり直し、の順でも壊れないよう、ここでも確かめる
86 if self.dataset.is_column_in_use(self.col_name):
87 logger.warning("Redo不可: 列 '%s' はプロットに使用されています。", self.col_name)
88 self.setObsolete(True)
89 return
90 self.dataset.remove_column(self.col_name)
92 def undo(self) -> None:
93 self.dataset.restore_column(self.col_name, self.deleted_column_data)
96class RenameColumnCommand(QUndoCommand):
97 """X/Y などの参照は Dataset.rename_column が新しい名前に付け替える。"""
98 def __init__(self, dataset: "Dataset", old_name: str, new_name: str, description: str = "列名の変更") -> None:
99 super().__init__(description)
100 self.dataset = dataset
101 self.old_name = old_name
102 self.new_name = new_name
104 def redo(self) -> None:
105 self.dataset.rename_column(self.old_name, self.new_name)
107 def undo(self) -> None:
108 self.dataset.rename_column(self.new_name, self.old_name)
111class SetDatasetPropertiesCommand(QUndoCommand):
112 """Dataset の属性(色、線種、名前、描画先など)をまとめて変える。"""
113 def __init__(self, dataset: "Dataset", old_values: dict[str, Any], new_values: dict[str, Any],
114 on_applied: Callable[[], Any], description: str = "プロパティ変更") -> None:
115 """old_values / new_values は {属性名: 値}。on_applied は適用後の再描画など(GUI 側の仕事)。"""
116 super().__init__(description)
117 self.dataset = dataset
118 self.old_values = old_values
119 self.new_values = new_values
120 self.on_applied = on_applied
122 def redo(self) -> None:
123 for attr, value in self.new_values.items():
124 setattr(self.dataset, attr, value)
125 self.on_applied()
127 def undo(self) -> None:
128 for attr, value in self.old_values.items():
129 setattr(self.dataset, attr, value)
130 self.on_applied()
133class SetMaskedRowsCommand(QUndoCommand):
134 """行のマスクをまとめて切り替える。"""
135 def __init__(self, dataset: "Dataset", old_masked_indices: list[Any], new_masked_indices: list[Any],
136 description: str = "行の除外/解除", on_applied: Callable[[], Any] | None = None) -> None:
137 """マスクする行は df の index ラベルのリスト。on_applied はタブの Undo で積むときの描き直し
138 (データエディタは自分の Undo の通知で描き直すので要らない)。"""
139 super().__init__(description)
140 self.dataset = dataset
141 self.old_masked_indices = list(old_masked_indices)
142 self.new_masked_indices = list(new_masked_indices)
143 self.on_applied = on_applied
145 def redo(self) -> None:
146 self.dataset.masked_row_indices = list(self.new_masked_indices)
147 if self.on_applied is not None:
148 self.on_applied()
150 def undo(self) -> None:
151 self.dataset.masked_row_indices = list(self.old_masked_indices)
152 if self.on_applied is not None:
153 self.on_applied()
156class SetAnnotationsCommand(QUndoCommand):
157 """1つの軸の注釈のリストを丸ごと置き換える(追加も削除もこれ)。"""
158 def __init__(self, project: "ProjectModel", axis_index: int, old_annotations: list[dict[str, Any]],
159 new_annotations: list[dict[str, Any]], on_applied: Callable[[], Any],
160 description: str = "注釈の変更") -> None:
161 super().__init__(description)
162 self.project = project
163 self.axis_index = axis_index
164 self.old_annotations = list(old_annotations)
165 self.new_annotations = list(new_annotations)
166 self.on_applied = on_applied
168 def redo(self) -> None:
169 self.project.all_plot_settings[self.axis_index]['annotations'] = list(self.new_annotations)
170 self.on_applied()
172 def undo(self) -> None:
173 self.project.all_plot_settings[self.axis_index]['annotations'] = list(self.old_annotations)
174 self.on_applied()
177class AddDatasetCommand(QUndoCommand):
178 """データセットを1つ追加する(プラグインの処理と解析の結果用)。
180 追加にはツリーの項目(フォルダの中の位置)が伴うので、追加と削除の処理そのものを GUI から受け取る。
181 """
182 def __init__(self, add_callback: Callable[[], Any], remove_callback: Callable[[], Any],
183 description: str = "データセットの追加") -> None:
184 """remove_callback は、直前に add_callback が足したものを取り除く。"""
185 super().__init__(description)
186 self.add_callback = add_callback
187 self.remove_callback = remove_callback
189 def redo(self) -> None:
190 self.add_callback()
192 def undo(self) -> None:
193 self.remove_callback()
196class DeferRedrawCommand(QUndoCommand):
197 """マクロの最初(opening=True)と最後に置き、間の描き直しを最後の1回にまとめる。
199 Qt はマクロを Undo するとき子を逆順に流すので、Undo では最後のものが遅らせ始め、最初のものが描く。
200 """
201 def __init__(self, begin_deferring: Callable[[], Any], end_deferring: Callable[[], Any],
202 opening: bool) -> None:
203 super().__init__("描き直しをまとめる")
204 self.begin_deferring = begin_deferring
205 self.end_deferring = end_deferring
206 self.opening = opening
208 def redo(self) -> None:
209 (self.begin_deferring if self.opening else self.end_deferring)()
211 def undo(self) -> None:
212 (self.end_deferring if self.opening else self.begin_deferring)()
215class RemoveDatasetCommand(QUndoCommand):
216 """データセットやフォルダの削除。
218 元の親フォルダの元の位置へ戻す必要があるので、削除と復元の処理そのものを GUI から受け取る。
219 """
220 def __init__(self, remove_callback: Callable[[], Any], restore_callback: Callable[[], Any],
221 description: str = "データセットの削除") -> None:
222 """restore_callback は、datasets の順とツリーの親・位置の両方を元に戻す。"""
223 super().__init__(description)
224 self.remove_callback = remove_callback
225 self.restore_callback = restore_callback
227 def redo(self) -> None:
228 self.remove_callback()
230 def undo(self) -> None:
231 self.restore_callback()
234class ReorderDatasetsCommand(QUndoCommand):
235 """project.datasets の並び(描画の順と重なり)を変える。"""
236 def __init__(self, project: "ProjectModel", old_order: "list[Dataset]", new_order: "list[Dataset]",
237 on_applied: Callable[[], Any], description: str = "データセットの並べ替え") -> None:
238 """on_applied は一覧の同期と再描画。"""
239 super().__init__(description)
240 self.project = project
241 self.old_order = list(old_order)
242 self.new_order = list(new_order)
243 self.on_applied = on_applied
245 def redo(self) -> None:
246 self.project.datasets = list(self.new_order)
247 self.on_applied()
249 def undo(self) -> None:
250 self.project.datasets = list(self.old_order)
251 self.on_applied()