1""" 2Undo/Redo Commands 3 4""" 5import typing 6from typing import Callable, Optional, Tuple, List, Any 7 8from AnyQt.QtWidgets import QUndoCommand 9 10if typing.TYPE_CHECKING: 11 from ..scheme import ( 12 Scheme, SchemeNode, SchemeLink, BaseSchemeAnnotation, 13 SchemeTextAnnotation, SchemeArrowAnnotation 14 ) 15 Pos = Tuple[float, float] 16 Rect = Tuple[float, float, float, float] 17 Line = Tuple[Pos, Pos] 18 19 20class UndoCommand(QUndoCommand): 21 """ 22 For pickling 23 """ 24 def __init__(self, text, parent=None): 25 QUndoCommand.__init__(self, text, parent) 26 self.__parent = parent 27 self.__initialized = True 28 29 # defined and initialized in __setstate__ 30 # self.__child_states = {} 31 # self.__children = [] 32 33 def __getstate__(self): 34 return { 35 **{k: v for k, v in self.__dict__.items()}, 36 '_UndoCommand__initialized': False, 37 '_UndoCommand__text': self.text(), 38 '_UndoCommand__children': 39 [self.child(i) for i in range(self.childCount())] 40 } 41 42 def __setstate__(self, state): 43 if hasattr(self, '_UndoCommand__initialized') and \ 44 self.__initialized: 45 return 46 47 text = state['_UndoCommand__text'] 48 parent = state['_UndoCommand__parent'] # type: UndoCommand 49 50 if parent is not None and \ 51 (not hasattr(parent, '_UndoCommand__initialized') or 52 not parent.__initialized): 53 # will be initialized in parent's __setstate__ 54 if not hasattr(parent, '_UndoCommand__child_states'): 55 setattr(parent, '_UndoCommand__child_states', {}) 56 parent.__child_states[self] = state 57 return 58 59 # init must be called on unpickle-time to recreate Qt object 60 UndoCommand.__init__(self, text, parent) 61 if hasattr(self, '_UndoCommand__child_states'): 62 for child, s in self.__child_states.items(): 63 child.__setstate__(s) 64 65 self.__dict__ = {k: v for k, v in state.items()} 66 self.__initialized = True 67 68 @staticmethod 69 def from_QUndoCommand(qc: QUndoCommand, parent=None): 70 if type(qc) == QUndoCommand: 71 qc.__class__ = UndoCommand 72 73 qc.__parent = parent 74 75 children = [qc.child(i) for i in range(qc.childCount())] 76 for child in children: 77 UndoCommand.from_QUndoCommand(child, parent=qc) 78 79 return qc 80 81 82class AddNodeCommand(UndoCommand): 83 def __init__(self, scheme, node, parent=None): 84 # type: (Scheme, SchemeNode, Optional[UndoCommand]) -> None 85 super().__init__("Add %s" % node.title, parent) 86 self.scheme = scheme 87 self.node = node 88 89 def redo(self): 90 self.scheme.add_node(self.node) 91 92 def undo(self): 93 self.scheme.remove_node(self.node) 94 95 96class RemoveNodeCommand(UndoCommand): 97 def __init__(self, scheme, node, parent=None): 98 # type: (Scheme, SchemeNode, Optional[UndoCommand]) -> None 99 super().__init__("Remove %s" % node.title, parent) 100 self.scheme = scheme 101 self.node = node 102 self._index = -1 103 links = scheme.input_links(self.node) + \ 104 scheme.output_links(self.node) 105 106 for link in links: 107 RemoveLinkCommand(scheme, link, parent=self) 108 109 def redo(self): 110 # redo child commands 111 super().redo() 112 self._index = self.scheme.nodes.index(self.node) 113 self.scheme.remove_node(self.node) 114 115 def undo(self): 116 assert self._index != -1 117 self.scheme.insert_node(self._index, self.node) 118 # Undo child commands 119 super().undo() 120 121 122class AddLinkCommand(UndoCommand): 123 def __init__(self, scheme, link, parent=None): 124 # type: (Scheme, SchemeLink, Optional[UndoCommand]) -> None 125 super().__init__("Add link", parent) 126 self.scheme = scheme 127 self.link = link 128 129 def redo(self): 130 self.scheme.add_link(self.link) 131 132 def undo(self): 133 self.scheme.remove_link(self.link) 134 135 136class RemoveLinkCommand(UndoCommand): 137 def __init__(self, scheme, link, parent=None): 138 # type: (Scheme, SchemeLink, Optional[UndoCommand]) -> None 139 super().__init__("Remove link", parent) 140 self.scheme = scheme 141 self.link = link 142 self._index = -1 143 144 def redo(self): 145 self._index = self.scheme.links.index(self.link) 146 self.scheme.remove_link(self.link) 147 148 def undo(self): 149 assert self._index != -1 150 self.scheme.insert_link(self._index, self.link) 151 self._index = -1 152 153 154class InsertNodeCommand(UndoCommand): 155 def __init__( 156 self, 157 scheme, # type: Scheme 158 new_node, # type: SchemeNode 159 old_link, # type: SchemeLink 160 new_links, # type: Tuple[SchemeLink, SchemeLink] 161 parent=None # type: Optional[UndoCommand] 162 ): # type: (...) -> None 163 super().__init__("Insert widget into link", parent) 164 165 AddNodeCommand(scheme, new_node, parent=self) 166 RemoveLinkCommand(scheme, old_link, parent=self) 167 for link in new_links: 168 AddLinkCommand(scheme, link, parent=self) 169 170 171class AddAnnotationCommand(UndoCommand): 172 def __init__(self, scheme, annotation, parent=None): 173 # type: (Scheme, BaseSchemeAnnotation, Optional[UndoCommand]) -> None 174 super().__init__("Add annotation", parent) 175 self.scheme = scheme 176 self.annotation = annotation 177 178 def redo(self): 179 self.scheme.add_annotation(self.annotation) 180 181 def undo(self): 182 self.scheme.remove_annotation(self.annotation) 183 184 185class RemoveAnnotationCommand(UndoCommand): 186 def __init__(self, scheme, annotation, parent=None): 187 # type: (Scheme, BaseSchemeAnnotation, Optional[UndoCommand]) -> None 188 super().__init__("Remove annotation", parent) 189 self.scheme = scheme 190 self.annotation = annotation 191 self._index = -1 192 193 def redo(self): 194 self._index = self.scheme.annotations.index(self.annotation) 195 self.scheme.remove_annotation(self.annotation) 196 197 def undo(self): 198 assert self._index != -1 199 self.scheme.insert_annotation(self._index, self.annotation) 200 self._index = -1 201 202 203class MoveNodeCommand(UndoCommand): 204 def __init__(self, scheme, node, old, new, parent=None): 205 # type: (Scheme, SchemeNode, Pos, Pos, Optional[UndoCommand]) -> None 206 super().__init__("Move", parent) 207 self.scheme = scheme 208 self.node = node 209 self.old = old 210 self.new = new 211 212 def redo(self): 213 self.node.position = self.new 214 215 def undo(self): 216 self.node.position = self.old 217 218 219class ResizeCommand(UndoCommand): 220 def __init__(self, scheme, item, new_geom, parent=None): 221 # type: (Scheme, SchemeTextAnnotation, Rect, Optional[UndoCommand]) -> None 222 super().__init__("Resize", parent) 223 self.scheme = scheme 224 self.item = item 225 self.new_geom = new_geom 226 self.old_geom = item.rect 227 228 def redo(self): 229 self.item.rect = self.new_geom 230 231 def undo(self): 232 self.item.rect = self.old_geom 233 234 235class ArrowChangeCommand(UndoCommand): 236 def __init__(self, scheme, item, new_line, parent=None): 237 # type: (Scheme, SchemeArrowAnnotation, Line, Optional[UndoCommand]) -> None 238 super().__init__("Move arrow", parent) 239 self.scheme = scheme 240 self.item = item 241 self.new_line = new_line 242 self.old_line = (item.start_pos, item.end_pos) 243 244 def redo(self): 245 self.item.set_line(*self.new_line) 246 247 def undo(self): 248 self.item.set_line(*self.old_line) 249 250 251class AnnotationGeometryChange(UndoCommand): 252 def __init__( 253 self, 254 scheme, # type: Scheme 255 annotation, # type: BaseSchemeAnnotation 256 old, # type: Any 257 new, # type: Any 258 parent=None # type: Optional[UndoCommand] 259 ): # type: (...) -> None 260 super().__init__("Change Annotation Geometry", parent) 261 self.scheme = scheme 262 self.annotation = annotation 263 self.old = old 264 self.new = new 265 266 def redo(self): 267 self.annotation.geometry = self.new # type: ignore 268 269 def undo(self): 270 self.annotation.geometry = self.old # type: ignore 271 272 273class RenameNodeCommand(UndoCommand): 274 def __init__(self, scheme, node, old_name, new_name, parent=None): 275 # type: (Scheme, SchemeNode, str, str, Optional[UndoCommand]) -> None 276 super().__init__("Rename", parent) 277 self.scheme = scheme 278 self.node = node 279 self.old_name = old_name 280 self.new_name = new_name 281 282 def redo(self): 283 self.node.set_title(self.new_name) 284 285 def undo(self): 286 self.node.set_title(self.old_name) 287 288 289class TextChangeCommand(UndoCommand): 290 def __init__( 291 self, 292 scheme, # type: Scheme 293 annotation, # type: SchemeTextAnnotation 294 old_content, # type: str 295 old_content_type, # type: str 296 new_content, # type: str 297 new_content_type, # type: str 298 parent=None # type: Optional[UndoCommand] 299 ): # type: (...) -> None 300 super().__init__("Change text", parent) 301 self.scheme = scheme 302 self.annotation = annotation 303 self.old_content = old_content 304 self.old_content_type = old_content_type 305 self.new_content = new_content 306 self.new_content_type = new_content_type 307 308 def redo(self): 309 self.annotation.set_content(self.new_content, self.new_content_type) 310 311 def undo(self): 312 self.annotation.set_content(self.old_content, self.old_content_type) 313 314 315class SetAttrCommand(UndoCommand): 316 def __init__( 317 self, 318 obj, # type: Any 319 attrname, # type: str 320 newvalue, # type: Any 321 name=None, # type: Optional[str] 322 parent=None # type: Optional[UndoCommand] 323 ): # type: (...) -> None 324 if name is None: 325 name = "Set %r" % attrname 326 super().__init__(name, parent) 327 self.obj = obj 328 self.attrname = attrname 329 self.newvalue = newvalue 330 self.oldvalue = getattr(obj, attrname) 331 332 def redo(self): 333 setattr(self.obj, self.attrname, self.newvalue) 334 335 def undo(self): 336 setattr(self.obj, self.attrname, self.oldvalue) 337 338 339class SetWindowGroupPresets(UndoCommand): 340 def __init__( 341 self, 342 scheme: 'Scheme', 343 presets: List['Scheme.WindowGroup'], 344 parent: Optional[UndoCommand] = None, 345 **kwargs 346 ) -> None: 347 text = kwargs.pop("text", "Set Window Presets") 348 super().__init__(text, parent, **kwargs) 349 self.scheme = scheme 350 self.presets = presets 351 self.__undo_presets = None 352 353 def redo(self): 354 presets = self.scheme.window_group_presets() 355 self.scheme.set_window_group_presets(self.presets) 356 self.__undo_presets = presets 357 358 def undo(self): 359 self.scheme.set_window_group_presets(self.__undo_presets) 360 self.__undo_presets = None 361 362 363class SimpleUndoCommand(UndoCommand): 364 """ 365 Simple undo/redo command specified by callable function pair. 366 Parameters 367 ---------- 368 redo: Callable[[], None] 369 A function expressing a redo action. 370 undo : Callable[[], None] 371 A function expressing a undo action. 372 text : str 373 The command's text (see `UndoCommand.setText`) 374 parent : Optional[UndoCommand] 375 """ 376 377 def __init__( 378 self, 379 redo, # type: Callable[[], None] 380 undo, # type: Callable[[], None] 381 text, # type: str 382 parent=None # type: Optional[UndoCommand] 383 ): # type: (...) -> None 384 super().__init__(text, parent) 385 self._redo = redo 386 self._undo = undo 387 388 def undo(self): 389 # type: () -> None 390 """Reimplemented.""" 391 self._undo() 392 393 def redo(self): 394 # type: () -> None 395 """Reimplemented.""" 396 self._redo() 397