1 """

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

3 """

4

5 from PyQt6.QtCore import Qt

6

7

8 class 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)