Coverage for pyPhotoAlbum/mixins/interaction_undo.py: 100%
44 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
1"""
2Mixin for automatic undo/redo handling in interactive mouse operations
3"""
5from typing import Optional
6from pyPhotoAlbum.models import BaseLayoutElement
7from .interaction_command_factory import InteractionCommandFactory, InteractionState
10class UndoableInteractionMixin:
11 """
12 Mixin providing automatic undo/redo for interactive mouse operations.
14 This mixin tracks the state of elements before interactive operations
15 (move, resize, rotate) and automatically creates appropriate Command
16 objects when the interaction completes.
17 """
19 def __init__(self, *args, **kwargs):
20 super().__init__(*args, **kwargs)
22 # Command factory for creating undo/redo commands
23 self._command_factory = InteractionCommandFactory()
25 # Interaction state tracking
26 self._interaction_state = InteractionState()
28 def _begin_move(self, element: BaseLayoutElement):
29 """
30 Begin tracking a move operation.
32 Args:
33 element: The element being moved
34 """
35 self._interaction_state.element = element
36 self._interaction_state.interaction_type = "move"
37 self._interaction_state.position = element.position
39 def _begin_resize(self, element: BaseLayoutElement):
40 """
41 Begin tracking a resize operation.
43 Args:
44 element: The element being resized
45 """
46 self._interaction_state.element = element
47 self._interaction_state.interaction_type = "resize"
48 self._interaction_state.position = element.position
49 self._interaction_state.size = element.size
51 def _begin_rotate(self, element: BaseLayoutElement):
52 """
53 Begin tracking a rotate operation.
55 Args:
56 element: The element being rotated
57 """
58 self._interaction_state.element = element
59 self._interaction_state.interaction_type = "rotate"
60 self._interaction_state.rotation = element.rotation
62 def _begin_image_pan(self, element):
63 """
64 Begin tracking an image pan operation.
66 Args:
67 element: The ImageData element being panned
68 """
69 from pyPhotoAlbum.models import ImageData
71 if not isinstance(element, ImageData):
72 return
74 self._interaction_state.element = element
75 self._interaction_state.interaction_type = "image_pan"
76 self._interaction_state.crop_info = element.crop_info
78 def _end_interaction(self):
79 """
80 End the current interaction and create appropriate undo/redo command.
82 This method uses the command factory to create the appropriate
83 Command object based on what changed during the interaction.
84 """
85 # Validate interaction state
86 if not self._interaction_state.is_valid():
87 self._clear_interaction_state()
88 return
90 # Get main window to access project history
91 main_window = self.window()
92 if not hasattr(main_window, "project"):
93 self._clear_interaction_state()
94 return
96 # Use factory to create command based on interaction type and changes
97 command = self._command_factory.create_command(
98 interaction_type=self._interaction_state.interaction_type,
99 element=self._interaction_state.element,
100 start_state=self._interaction_state.to_dict(),
101 )
103 # Execute the command through history if one was created
104 if command:
105 main_window.project.history.execute(command)
107 # Clear interaction state
108 self._clear_interaction_state()
110 def _clear_interaction_state(self):
111 """Clear all interaction tracking state"""
112 self._interaction_state.clear()
114 def _cancel_interaction(self):
115 """Cancel the current interaction without creating a command"""
116 self._clear_interaction_state()