Coverage for pyPhotoAlbum/mixins/operations/view_ops.py: 83%
185 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"""
2View operations mixin for pyPhotoAlbum
3"""
5from pyPhotoAlbum.decorators import ribbon_action
6from pyPhotoAlbum.dialogs import PrintSettingsDialog
9class ViewOperationsMixin:
10 """Mixin providing view-related operations"""
12 @ribbon_action(label="Zoom In", tooltip="Zoom in", tab="View", group="Zoom", shortcut="Ctrl++")
13 def zoom_in(self):
14 """Zoom in"""
15 self.gl_widget.zoom_level *= 1.2
16 if self.gl_widget.zoom_level > 5.0:
17 self.gl_widget.zoom_level = 5.0
18 self.update_view()
19 self.show_status(f"Zoom: {int(self.gl_widget.zoom_level * 100)}%", 2000)
21 @ribbon_action(label="Zoom Out", tooltip="Zoom out", tab="View", group="Zoom", shortcut="Ctrl+-")
22 def zoom_out(self):
23 """Zoom out"""
24 self.gl_widget.zoom_level /= 1.2
25 if self.gl_widget.zoom_level < 0.1:
26 self.gl_widget.zoom_level = 0.1
27 self.update_view()
28 self.show_status(f"Zoom: {int(self.gl_widget.zoom_level * 100)}%", 2000)
30 @ribbon_action(label="Fit to Window", tooltip="Fit page to window", tab="View", group="Zoom", shortcut="Ctrl+0")
31 def zoom_fit(self):
32 """Fit page to window"""
33 if not self.project.pages:
34 return
36 current_page = self.project.pages[self.gl_widget.current_page_index]
37 page_width_mm = current_page.layout.size[0]
38 page_height_mm = current_page.layout.size[1]
40 # Convert to pixels
41 dpi = self.project.working_dpi
42 page_width_px = page_width_mm * dpi / 25.4
43 page_height_px = page_height_mm * dpi / 25.4
45 # Get widget size
46 widget_width = self.gl_widget.width() - 100 # Margins
47 widget_height = self.gl_widget.height() - 100
49 # Calculate zoom to fit
50 zoom_w = widget_width / page_width_px
51 zoom_h = widget_height / page_height_px
53 self.gl_widget.zoom_level = min(zoom_w, zoom_h)
54 self.gl_widget.zoom_level = max(0.1, min(5.0, self.gl_widget.zoom_level))
55 self.update_view()
56 self.show_status(f"Zoom: {int(self.gl_widget.zoom_level * 100)}%", 2000)
58 @ribbon_action(
59 label="Grid Snap",
60 tooltip="Enable/disable snapping to grid (Ctrl+G)",
61 tab="Insert",
62 group="Snapping",
63 shortcut="Ctrl+G",
64 )
65 def toggle_grid_snap(self):
66 """Toggle grid snapping"""
67 if not self.project:
68 return
70 self.project.snap_to_grid = not self.project.snap_to_grid
72 status = "enabled" if self.project.snap_to_grid else "disabled"
73 self.update_view()
74 self.show_status(f"Grid snapping {status}", 2000)
75 print(f"Grid snapping {status}")
77 @ribbon_action(
78 label="Edge Snap",
79 tooltip="Enable/disable snapping to page edges (Ctrl+E)",
80 tab="Insert",
81 group="Snapping",
82 shortcut="Ctrl+E",
83 )
84 def toggle_edge_snap(self):
85 """Toggle edge snapping"""
86 if not self.project:
87 return
89 self.project.snap_to_edges = not self.project.snap_to_edges
91 status = "enabled" if self.project.snap_to_edges else "disabled"
92 self.update_view()
93 self.show_status(f"Edge snapping {status}", 2000)
94 print(f"Edge snapping {status}")
96 @ribbon_action(label="Guide Snap", tooltip="Enable/disable snapping to guides", tab="Insert", group="Snapping")
97 def toggle_guide_snap(self):
98 """Toggle guide snapping"""
99 if not self.project:
100 return
102 self.project.snap_to_guides = not self.project.snap_to_guides
104 status = "enabled" if self.project.snap_to_guides else "disabled"
105 self.update_view()
106 self.show_status(f"Guide snapping {status}", 2000)
107 print(f"Guide snapping {status}")
109 @ribbon_action(label="Show Grid", tooltip="Toggle visibility of grid lines", tab="Insert", group="Snapping")
110 def toggle_show_grid(self):
111 """Toggle grid visibility"""
112 if not self.project:
113 return
115 self.project.show_grid = not self.project.show_grid
117 status = "visible" if self.project.show_grid else "hidden"
118 self.update_view()
119 self.show_status(f"Grid {status}", 2000)
120 print(f"Grid {status}")
122 @ribbon_action(
123 label="Print Settings...", tooltip="Configure bleed and safe area for all pages", tab="View", group="Guides"
124 )
125 def open_print_settings(self):
126 """Open the print settings dialog (bleed and safe area)"""
127 if not self.project:
128 return
130 dialog = PrintSettingsDialog(self, self.project)
131 if dialog.exec():
132 values = dialog.get_values()
133 self.project.page_bleed_mm = values["page_bleed_mm"]
134 self.project.page_safe_area_mm = values["page_safe_area_mm"]
135 self.update_view()
136 self.show_status(
137 f"Bleed: {values['page_bleed_mm']:.1f}mm, Safe area: {values['page_safe_area_mm']:.1f}mm", 2000
138 )
140 @ribbon_action(
141 label="Print Guides", tooltip="Toggle bleed/cut/safe-area guide lines in the editor", tab="View", group="Guides"
142 )
143 def toggle_print_guides(self):
144 """Toggle print guide lines (bleed/cut/safe area)"""
145 if not self.project:
146 return
148 self.project.show_print_guides = not self.project.show_print_guides
150 status = "visible" if self.project.show_print_guides else "hidden"
151 self.update_view()
152 self.show_status(f"Print guides {status}", 2000)
154 @ribbon_action(label="Show Guides", tooltip="Toggle visibility of guide lines", tab="Insert", group="Snapping")
155 def toggle_snap_lines(self):
156 """Toggle guide lines visibility"""
157 if not self.project:
158 return
160 self.project.show_snap_lines = not self.project.show_snap_lines
162 status = "visible" if self.project.show_snap_lines else "hidden"
163 self.update_view()
164 self.show_status(f"Guides {status}", 2000)
165 print(f"Guides {status}")
167 @ribbon_action(label="Add H Guide", tooltip="Add horizontal guide at page center", tab="View", group="Guides")
168 def add_horizontal_guide(self):
169 """Add a horizontal guide at page center"""
170 current_page = self.get_current_page()
171 if not current_page:
172 return
174 # Add guide at vertical center (in mm)
175 center_y = current_page.layout.size[1] / 2.0
176 current_page.layout.snapping_system.add_guide(center_y, "horizontal")
178 self.update_view()
179 self.show_status(f"Added horizontal guide at {center_y:.1f} mm", 2000)
180 print(f"Added horizontal guide at {center_y:.1f} mm")
182 @ribbon_action(label="Add V Guide", tooltip="Add vertical guide at page center", tab="View", group="Guides")
183 def add_vertical_guide(self):
184 """Add a vertical guide at page center"""
185 current_page = self.get_current_page()
186 if not current_page:
187 return
189 # Add guide at horizontal center (in mm)
190 center_x = current_page.layout.size[0] / 2.0
191 current_page.layout.snapping_system.add_guide(center_x, "vertical")
193 self.update_view()
194 self.show_status(f"Added vertical guide at {center_x:.1f} mm", 2000)
195 print(f"Added vertical guide at {center_x:.1f} mm")
197 @ribbon_action(label="Clear Guides", tooltip="Clear all guides from current page", tab="View", group="Guides")
198 def clear_guides(self):
199 """Clear all guides from current page"""
200 current_page = self.get_current_page()
201 if not current_page:
202 return
204 guide_count = len(current_page.layout.snapping_system.guides)
205 current_page.layout.snapping_system.clear_guides()
207 self.update_view()
208 self.show_status(f"Cleared {guide_count} guides", 2000)
209 print(f"Cleared {guide_count} guides")
211 @ribbon_action(
212 label="Image Browser",
213 tooltip="Show/hide the image browser panel",
214 tab="View",
215 group="Panels",
216 shortcut="Ctrl+B",
217 )
218 def toggle_image_browser(self):
219 """Toggle the thumbnail browser visibility"""
220 if hasattr(self, "_thumbnail_browser"):
221 if self._thumbnail_browser.isVisible():
222 self._thumbnail_browser.hide()
223 self.show_status("Image browser hidden", 2000)
224 else:
225 self._thumbnail_browser.show()
226 self.show_status("Image browser shown", 2000)
228 @ribbon_action(
229 label="Grid Settings...", tooltip="Configure grid size and snap threshold", tab="Insert", group="Snapping"
230 )
231 def set_grid_size(self):
232 """Open dialog to set grid size"""
233 from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QPushButton
235 if not self.project:
236 return
238 # Create dialog
239 dialog = QDialog(self)
240 dialog.setWindowTitle("Grid Settings")
241 dialog.setMinimumWidth(300)
243 layout = QVBoxLayout()
245 # Grid size setting
246 size_layout = QHBoxLayout()
247 size_layout.addWidget(QLabel("Grid Size:"))
249 size_spinbox = QDoubleSpinBox()
250 size_spinbox.setRange(1.0, 100.0)
251 size_spinbox.setValue(self.project.grid_size_mm)
252 size_spinbox.setSuffix(" mm")
253 size_spinbox.setDecimals(1)
254 size_spinbox.setSingleStep(1.0)
255 size_layout.addWidget(size_spinbox)
257 layout.addLayout(size_layout)
259 # Snap threshold setting
260 threshold_layout = QHBoxLayout()
261 threshold_layout.addWidget(QLabel("Snap Threshold:"))
263 threshold_spinbox = QDoubleSpinBox()
264 threshold_spinbox.setRange(0.5, 20.0)
265 threshold_spinbox.setValue(self.project.snap_threshold_mm)
266 threshold_spinbox.setSuffix(" mm")
267 threshold_spinbox.setDecimals(1)
268 threshold_spinbox.setSingleStep(0.5)
269 threshold_layout.addWidget(threshold_spinbox)
271 layout.addLayout(threshold_layout)
273 # Buttons
274 button_layout = QHBoxLayout()
275 cancel_btn = QPushButton("Cancel")
276 cancel_btn.clicked.connect(dialog.reject)
277 ok_btn = QPushButton("OK")
278 ok_btn.clicked.connect(dialog.accept)
279 ok_btn.setDefault(True)
281 button_layout.addStretch()
282 button_layout.addWidget(cancel_btn)
283 button_layout.addWidget(ok_btn)
284 layout.addLayout(button_layout)
286 dialog.setLayout(layout)
288 # Show dialog and apply if accepted
289 if dialog.exec() == QDialog.DialogCode.Accepted:
290 new_grid_size = size_spinbox.value()
291 new_threshold = threshold_spinbox.value()
293 self.project.grid_size_mm = new_grid_size
294 self.project.snap_threshold_mm = new_threshold
296 self.update_view()
297 self.show_status(f"Grid size: {new_grid_size:.1f}mm, Threshold: {new_threshold:.1f}mm", 2000)
298 print(f"Updated grid settings - Size: {new_grid_size:.1f}mm, Threshold: {new_threshold:.1f}mm")