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
« 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"""
5from PyQt6.QtCore import Qt
8class KeyboardNavigationMixin:
9 """
10 Mixin providing keyboard navigation functionality.
12 This mixin handles Page Up/Down navigation between pages,
13 arrow key viewport movement, and arrow key element movement.
14 """
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
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)
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)
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
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)
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)
46 def _scroll_to_page(self, page, page_index):
47 """
48 Scroll the viewport to center a specific page.
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
58 dpi = main_window.project.working_dpi
59 PAGE_MARGIN = 50
60 PAGE_SPACING = 50
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
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
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
79 self.pan_offset[1] = target_pan_y
81 # Clamp pan offset to content bounds
82 if hasattr(self, "clamp_pan_offset"):
83 self.clamp_pan_offset()
85 self.update()
87 # Update scrollbars if available
88 main_window = self.window()
89 if hasattr(main_window, "update_scrollbars"):
90 main_window.update_scrollbars()
92 def _move_viewport_with_arrow_keys(self, key):
93 """
94 Move the viewport using arrow keys when no objects are selected.
96 Args:
97 key: The Qt key code (Up, Down, Left, Right)
98 """
99 # Movement amount in pixels
100 move_amount = 50
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
111 # Clamp pan offset to content bounds
112 if hasattr(self, "clamp_pan_offset"):
113 self.clamp_pan_offset()
115 self.update()
117 # Update scrollbars if available
118 main_window = self.window()
119 if hasattr(main_window, "update_scrollbars"):
120 main_window.update_scrollbars()
122 def _move_selected_elements_with_arrow_keys(self, key):
123 """
124 Move selected elements using arrow keys.
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
133 # Movement amount in mm
134 move_amount_mm = 1.0 # 1mm per keypress
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
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
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
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)
171 self.update()
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)