1 """

2 Mixin for automatic undo/redo handling in interactive mouse operations

3 """

4

5 from typing import Optional

6 from pyPhotoAlbum.models import BaseLayoutElement

7 from .interaction_command_factory import InteractionCommandFactory, InteractionState

8

9

10 class UndoableInteractionMixin:

11 """

12 Mixin providing automatic undo/redo for interactive mouse operations.

13

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 """

18

19 def __init__(self, *args, **kwargs):

20 super().__init__(*args, **kwargs)

21

22 # Command factory for creating undo/redo commands

23 self._command_factory = InteractionCommandFactory()

24

25 # Interaction state tracking

26 self._interaction_state = InteractionState()

27

28 def _begin_move(self, element: BaseLayoutElement):

29 """

30 Begin tracking a move operation.

31

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

38

39 def _begin_resize(self, element: BaseLayoutElement):

40 """

41 Begin tracking a resize operation.

42

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

50

51 def _begin_rotate(self, element: BaseLayoutElement):

52 """

53 Begin tracking a rotate operation.

54

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

61

62 def _begin_image_pan(self, element):

63 """

64 Begin tracking an image pan operation.

65

66 Args:

67 element: The ImageData element being panned

68 """

69 from pyPhotoAlbum.models import ImageData

70

71 if not isinstance(element, ImageData):

72 return

73

74 self._interaction_state.element = element

75 self._interaction_state.interaction_type = "image_pan"

76 self._interaction_state.crop_info = element.crop_info

77

78 def _end_interaction(self):

79 """

80 End the current interaction and create appropriate undo/redo command.

81

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

89

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

95

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 )

102

103 # Execute the command through history if one was created

104 if command:

105 main_window.project.history.execute(command)

106

107 # Clear interaction state

108 self._clear_interaction_state()

109

110 def _clear_interaction_state(self):

111 """Clear all interaction tracking state"""

112 self._interaction_state.clear()

113

114 def _cancel_interaction(self):

115 """Cancel the current interaction without creating a command"""

116 self._clear_interaction_state()