Coverage for pyPhotoAlbum/mixins/keyboard_navigation.py: 100%

96 statements  

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

1""" 

2Keyboard navigation mixin for GLWidget - handles keyboard-based navigation 

3""" 

4 

5from PyQt6.QtCore import Qt 

6 

7 

8class KeyboardNavigationMixin: 

9 """ 

10 Mixin providing keyboard navigation functionality. 

11 

12 This mixin handles Page Up/Down navigation between pages, 

13 arrow key viewport movement, and arrow key element movement. 

14 """ 

15 

16 def _navigate_to_next_page(self): 

17 """Navigate to the next page using Page Down key""" 

18 main_window = self.window() 

19 if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages: 

20 return 

21 

22 current_index = main_window._get_most_visible_page_index() 

23 if current_index < len(main_window.project.pages) - 1: 

24 next_page = main_window.project.pages[current_index + 1] 

25 self._scroll_to_page(next_page, current_index + 1) 

26 

27 if hasattr(main_window, "show_status"): 

28 page_name = main_window.project.get_page_display_name(next_page) 

29 main_window.show_status(f"Navigated to {page_name}", 2000) 

30 

31 def _navigate_to_previous_page(self): 

32 """Navigate to the previous page using Page Up key""" 

33 main_window = self.window() 

34 if not hasattr(main_window, "project") or not main_window.project or not main_window.project.pages: 

35 return 

36 

37 current_index = main_window._get_most_visible_page_index() 

38 if current_index > 0: 

39 prev_page = main_window.project.pages[current_index - 1] 

40 self._scroll_to_page(prev_page, current_index - 1) 

41 

42 if hasattr(main_window, "show_status"): 

43 page_name = main_window.project.get_page_display_name(prev_page) 

44 main_window.show_status(f"Navigated to {page_name}", 2000) 

45 

46 def _scroll_to_page(self, page, page_index): 

47 """ 

48 Scroll the viewport to center a specific page. 

49 

50 Args: 

51 page: The page to scroll to 

52 page_index: The index of the page in the project 

53 """ 

54 main_window = self.window() 

55 if not hasattr(main_window, "project"): 

56 return 

57 

58 dpi = main_window.project.working_dpi 

59 PAGE_MARGIN = 50 

60 PAGE_SPACING = 50 

61 

62 # Calculate the Y offset for this page 

63 y_offset = PAGE_MARGIN 

64 for i in range(page_index): 

65 prev_page = main_window.project.pages[i] 

66 prev_height_mm = prev_page.layout.size[1] 

67 prev_height_px = prev_height_mm * dpi / 25.4 

68 y_offset += prev_height_px * self.zoom_level + PAGE_SPACING 

69 

70 # Get page height 

71 page_height_mm = page.layout.size[1] 

72 page_height_px = page_height_mm * dpi / 25.4 

73 screen_page_height = page_height_px * self.zoom_level 

74 

75 # Center the page in the viewport 

76 viewport_height = self.height() 

77 target_pan_y = viewport_height / 2 - y_offset - screen_page_height / 2 

78 

79 self.pan_offset[1] = target_pan_y 

80 

81 # Clamp pan offset to content bounds 

82 if hasattr(self, "clamp_pan_offset"): 

83 self.clamp_pan_offset() 

84 

85 self.update() 

86 

87 # Update scrollbars if available 

88 main_window = self.window() 

89 if hasattr(main_window, "update_scrollbars"): 

90 main_window.update_scrollbars() 

91 

92 def _move_viewport_with_arrow_keys(self, key): 

93 """ 

94 Move the viewport using arrow keys when no objects are selected. 

95 

96 Args: 

97 key: The Qt key code (Up, Down, Left, Right) 

98 """ 

99 # Movement amount in pixels 

100 move_amount = 50 

101 

102 if key == Qt.Key.Key_Up: 

103 self.pan_offset[1] += move_amount 

104 elif key == Qt.Key.Key_Down: 

105 self.pan_offset[1] -= move_amount 

106 elif key == Qt.Key.Key_Left: 

107 self.pan_offset[0] += move_amount 

108 elif key == Qt.Key.Key_Right: 

109 self.pan_offset[0] -= move_amount 

110 

111 # Clamp pan offset to content bounds 

112 if hasattr(self, "clamp_pan_offset"): 

113 self.clamp_pan_offset() 

114 

115 self.update() 

116 

117 # Update scrollbars if available 

118 main_window = self.window() 

119 if hasattr(main_window, "update_scrollbars"): 

120 main_window.update_scrollbars() 

121 

122 def _move_selected_elements_with_arrow_keys(self, key): 

123 """ 

124 Move selected elements using arrow keys. 

125 

126 Args: 

127 key: The Qt key code (Up, Down, Left, Right) 

128 """ 

129 main_window = self.window() 

130 if not hasattr(main_window, "project"): 

131 return 

132 

133 # Movement amount in mm 

134 move_amount_mm = 1.0 # 1mm per keypress 

135 

136 # Calculate movement delta 

137 dx, dy = 0, 0 

138 if key == Qt.Key.Key_Up: 

139 dy = -move_amount_mm 

140 elif key == Qt.Key.Key_Down: 

141 dy = move_amount_mm 

142 elif key == Qt.Key.Key_Left: 

143 dx = -move_amount_mm 

144 elif key == Qt.Key.Key_Right: 

145 dx = move_amount_mm 

146 

147 # Move all selected elements 

148 for element in self.selected_elements: 

149 current_x, current_y = element.position 

150 new_x = current_x + dx 

151 new_y = current_y + dy 

152 

153 # Apply snapping if element has a parent page 

154 if hasattr(element, "_parent_page") and element._parent_page: 

155 page = element._parent_page 

156 snap_sys = page.layout.snapping_system 

157 page_size = page.layout.size 

158 dpi = main_window.project.working_dpi 

159 

160 snapped_pos = snap_sys.snap_position( 

161 position=(new_x, new_y), 

162 size=element.size, 

163 page_size=page_size, 

164 dpi=dpi, 

165 project=main_window.project, 

166 ) 

167 element.position = snapped_pos 

168 else: 

169 element.position = (new_x, new_y) 

170 

171 self.update() 

172 

173 if hasattr(main_window, "show_status"): 

174 count = len(self.selected_elements) 

175 elem_text = "element" if count == 1 else "elements" 

176 main_window.show_status(f"Moved {count} {elem_text}", 1000)