Coverage for pyPhotoAlbum/mixins/operations/edit_ops.py: 92%

75 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-20 12:55 +0000

1""" 

2Edit operations mixin for pyPhotoAlbum 

3""" 

4 

5from pyPhotoAlbum.decorators import ribbon_action 

6from pyPhotoAlbum.commands import DeleteElementCommand, RotateElementCommand 

7 

8 

9class EditOperationsMixin: 

10 """Mixin providing edit-related operations""" 

11 

12 @ribbon_action(label="Undo", tooltip="Undo last action (Ctrl+Z)", tab="Home", group="Edit", shortcut="Ctrl+Z") 

13 def undo(self): 

14 """Undo last action""" 

15 if self.project.history.undo(): 

16 self.update_view() 

17 self.show_status("Undo successful", 2000) 

18 print("Undo successful") 

19 else: 

20 self.show_status("Nothing to undo", 2000) 

21 print("Nothing to undo") 

22 

23 @ribbon_action( 

24 label="Redo", tooltip="Redo last action (Ctrl+Y or Ctrl+Shift+Z)", tab="Home", group="Edit", shortcut="Ctrl+Y" 

25 ) 

26 def redo(self): 

27 """Redo last action""" 

28 if self.project.history.redo(): 

29 self.update_view() 

30 self.show_status("Redo successful", 2000) 

31 print("Redo successful") 

32 else: 

33 self.show_status("Nothing to redo", 2000) 

34 print("Nothing to redo") 

35 

36 @ribbon_action( 

37 label="Delete", 

38 tooltip="Delete selected element (Delete key)", 

39 tab="Home", 

40 group="Edit", 

41 shortcut="Delete", 

42 requires_selection=True, 

43 ) 

44 def delete_selected_element(self): 

45 """Delete the currently selected element""" 

46 if not self.require_selection(min_count=1): 

47 return 

48 

49 current_page = self.get_current_page() 

50 if not current_page: 

51 return 

52 

53 # Delete the first selected element (for backward compatibility) 

54 # In the future, we could delete all selected elements 

55 selected_element = next(iter(self.gl_widget.selected_elements)) 

56 

57 try: 

58 cmd = DeleteElementCommand(current_page.layout, selected_element, asset_manager=self.project.asset_manager) 

59 self.project.history.execute(cmd) 

60 

61 # Clear selection 

62 self.gl_widget.selected_elements.clear() 

63 

64 # Update display 

65 self.update_view() 

66 

67 self.show_status("Element deleted (Ctrl+Z to undo)", 2000) 

68 print("Deleted selected element") 

69 

70 except Exception as e: 

71 self.show_error("Error", f"Failed to delete element: {str(e)}") 

72 print(f"Error deleting element: {e}") 

73 

74 @ribbon_action( 

75 label="Rotate Left", 

76 tooltip="Rotate selected element 90° counter-clockwise", 

77 tab="Arrange", 

78 group="Transform", 

79 requires_selection=True, 

80 ) 

81 def rotate_left(self): 

82 """Rotate selected element 90 degrees counter-clockwise""" 

83 if not self.require_selection(min_count=1): 

84 return 

85 

86 selected_element = next(iter(self.gl_widget.selected_elements)) 

87 old_rotation = selected_element.rotation 

88 new_rotation = (old_rotation - 90) % 360 

89 

90 cmd = RotateElementCommand(selected_element, old_rotation, new_rotation) 

91 self.project.history.execute(cmd) 

92 

93 self.update_view() 

94 self.show_status(f"Rotated left (Ctrl+Z to undo)", 2000) 

95 print(f"Rotated element left: {old_rotation}° → {new_rotation}°") 

96 

97 @ribbon_action( 

98 label="Rotate Right", 

99 tooltip="Rotate selected element 90° clockwise", 

100 tab="Arrange", 

101 group="Transform", 

102 requires_selection=True, 

103 ) 

104 def rotate_right(self): 

105 """Rotate selected element 90 degrees clockwise""" 

106 if not self.require_selection(min_count=1): 

107 return 

108 

109 selected_element = next(iter(self.gl_widget.selected_elements)) 

110 old_rotation = selected_element.rotation 

111 new_rotation = (old_rotation + 90) % 360 

112 

113 cmd = RotateElementCommand(selected_element, old_rotation, new_rotation) 

114 self.project.history.execute(cmd) 

115 

116 self.update_view() 

117 self.show_status(f"Rotated right (Ctrl+Z to undo)", 2000) 

118 print(f"Rotated element right: {old_rotation}° → {new_rotation}°") 

119 

120 @ribbon_action( 

121 label="Reset Rotation", 

122 tooltip="Reset selected element rotation to 0°", 

123 tab="Arrange", 

124 group="Transform", 

125 requires_selection=True, 

126 ) 

127 def reset_rotation(self): 

128 """Reset selected element rotation to 0 degrees""" 

129 if not self.require_selection(min_count=1): 

130 return 

131 

132 selected_element = next(iter(self.gl_widget.selected_elements)) 

133 old_rotation = selected_element.rotation 

134 

135 if old_rotation == 0: 

136 self.show_status("Element already at 0°", 2000) 

137 return 

138 

139 cmd = RotateElementCommand(selected_element, old_rotation, 0) 

140 self.project.history.execute(cmd) 

141 

142 self.update_view() 

143 self.show_status("Reset rotation to 0° (Ctrl+Z to undo)", 2000) 

144 print(f"Reset element rotation: {old_rotation}° → 0°")