Coverage for pyPhotoAlbum/mixins/operations/template_ops.py: 100%
180 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"""
2Template operations mixin for pyPhotoAlbum
3"""
5from PyQt6.QtWidgets import (
6 QInputDialog,
7 QDialog,
8 QVBoxLayout,
9 QLabel,
10 QComboBox,
11 QRadioButton,
12 QButtonGroup,
13 QPushButton,
14 QHBoxLayout,
15 QDoubleSpinBox,
16)
17from pyPhotoAlbum.decorators import ribbon_action, undoable_operation
20class TemplateOperationsMixin:
21 """Mixin providing template-related operations"""
23 @ribbon_action(
24 label="Save as Template",
25 tooltip="Save current page as a reusable template",
26 tab="Layout",
27 group="Templates",
28 requires_page=True,
29 )
30 def save_page_as_template(self):
31 """Save current page as a template"""
32 current_page = self.get_current_page()
33 if not current_page:
34 return
36 # Check if page has any elements
37 if not current_page.layout.elements:
38 self.show_warning("Empty Page", "Cannot save an empty page as a template.")
39 return
41 # Ask for template name
42 name, ok = QInputDialog.getText(
43 self,
44 "Save Template",
45 "Enter template name:",
46 text=f"Template_{len(self.template_manager.list_templates()) + 1}",
47 )
49 if not ok or not name:
50 return
52 # Ask for optional description
53 description, ok = QInputDialog.getText(self, "Template Description", "Enter description (optional):")
55 if not ok:
56 description = ""
58 try:
59 # Create template from page
60 template = self.template_manager.create_template_from_page(current_page, name, description)
62 # Save template
63 self.template_manager.save_template(template)
65 self.show_info("Template Saved", f"Template '{name}' has been saved successfully.")
67 print(f"Saved template: {name}")
69 except Exception as e:
70 self.show_error("Error", f"Failed to save template: {str(e)}")
71 print(f"Error saving template: {e}")
73 @ribbon_action(
74 label="New from Template", tooltip="Create a new page from a template", tab="Layout", group="Templates"
75 )
76 def new_page_from_template(self):
77 """Create a new page from a template"""
78 # Get available templates
79 templates = self.template_manager.list_templates()
81 if not templates:
82 self.show_info(
83 "No Templates", "No templates available. Create a template first by using 'Save as Template'."
84 )
85 return
87 # Create dialog for template selection and options
88 dialog = QDialog(self)
89 dialog.setWindowTitle("New Page from Template")
90 dialog.setMinimumWidth(400)
92 layout = QVBoxLayout()
94 # Template selection
95 layout.addWidget(QLabel("Select Template:"))
96 template_combo = QComboBox()
97 template_combo.addItems(templates)
98 layout.addWidget(template_combo)
100 layout.addSpacing(10)
102 # Margin/Spacing percentage
103 layout.addWidget(QLabel("Margin/Spacing:"))
104 margin_layout = QHBoxLayout()
105 margin_spinbox = QDoubleSpinBox()
106 margin_spinbox.setRange(0.0, 10.0)
107 margin_spinbox.setValue(2.5)
108 margin_spinbox.setSuffix("%")
109 margin_spinbox.setDecimals(1)
110 margin_spinbox.setSingleStep(0.5)
111 margin_spinbox.setToolTip("Percentage of page size to use for margins and spacing")
112 margin_layout.addWidget(margin_spinbox)
113 margin_layout.addStretch()
114 layout.addLayout(margin_layout)
116 layout.addSpacing(10)
118 # Scaling selection
119 layout.addWidget(QLabel("Scaling:"))
120 scale_group = QButtonGroup(dialog)
122 proportional_radio = QRadioButton("Proportional (maintain aspect ratio)")
123 scale_group.addButton(proportional_radio, 0)
124 layout.addWidget(proportional_radio)
126 stretch_radio = QRadioButton("Stretch to fit")
127 stretch_radio.setChecked(True)
128 scale_group.addButton(stretch_radio, 1)
129 layout.addWidget(stretch_radio)
131 center_radio = QRadioButton("Center (no scaling)")
132 scale_group.addButton(center_radio, 2)
133 layout.addWidget(center_radio)
135 layout.addSpacing(20)
137 # Buttons
138 button_layout = QHBoxLayout()
139 cancel_btn = QPushButton("Cancel")
140 cancel_btn.clicked.connect(dialog.reject)
141 create_btn = QPushButton("Create")
142 create_btn.clicked.connect(dialog.accept)
143 create_btn.setDefault(True)
145 button_layout.addStretch()
146 button_layout.addWidget(cancel_btn)
147 button_layout.addWidget(create_btn)
148 layout.addLayout(button_layout)
150 dialog.setLayout(layout)
152 # Show dialog
153 if dialog.exec() != QDialog.DialogCode.Accepted:
154 return
156 # Get selections
157 template_name = template_combo.currentText()
158 scale_id = scale_group.checkedId()
159 margin_percent = margin_spinbox.value()
160 scale_mode = ["proportional", "stretch", "center"][scale_id]
162 try:
163 # Load template
164 template = self.template_manager.load_template(template_name)
166 # Create new page from template
167 new_page_number = len(self.project.pages) + 1
168 new_page = self.template_manager.create_page_from_template(
169 template,
170 page_number=new_page_number,
171 target_size_mm=self.project.page_size_mm,
172 scale_mode=scale_mode,
173 margin_percent=margin_percent,
174 )
176 # Add to project
177 self.project.add_page(new_page)
179 # Switch to new page
180 self.gl_widget.current_page_index = len(self.project.pages) - 1
181 self.update_view()
183 self.show_status(f"Created page {new_page_number} from template '{template_name}'", 3000)
184 print(f"Created page from template: {template_name} with scale_mode={scale_mode}, margin={margin_percent}%")
186 except Exception as e:
187 self.show_error("Error", f"Failed to create page from template: {str(e)}")
188 print(f"Error creating page from template: {e}")
190 @ribbon_action(
191 label="Apply Template",
192 tooltip="Apply a template layout to current page",
193 tab="Layout",
194 group="Templates",
195 requires_page=True,
196 )
197 @undoable_operation(capture="page_elements", description="Apply Template")
198 def apply_template_to_page(self):
199 """Apply a template to the current page"""
200 current_page = self.get_current_page()
201 if not current_page:
202 return
204 # Get available templates
205 templates = self.template_manager.list_templates()
207 if not templates:
208 self.show_info(
209 "No Templates", "No templates available. Create a template first by using 'Save as Template'."
210 )
211 return
213 # Create dialog for template application options
214 dialog = QDialog(self)
215 dialog.setWindowTitle("Apply Template")
216 dialog.setMinimumWidth(400)
218 layout = QVBoxLayout()
220 # Template selection
221 layout.addWidget(QLabel("Select Template:"))
222 template_combo = QComboBox()
223 template_combo.addItems(templates)
224 layout.addWidget(template_combo)
226 layout.addSpacing(10)
228 # Mode selection
229 layout.addWidget(QLabel("Mode:"))
230 mode_group = QButtonGroup(dialog)
232 replace_radio = QRadioButton("Replace with placeholders")
233 replace_radio.setChecked(True)
234 replace_radio.setToolTip("Clear page and add template placeholders")
235 mode_group.addButton(replace_radio, 0)
236 layout.addWidget(replace_radio)
238 reflow_radio = QRadioButton("Reflow existing content")
239 reflow_radio.setToolTip("Keep existing images and reposition to template slots")
240 mode_group.addButton(reflow_radio, 1)
241 layout.addWidget(reflow_radio)
243 layout.addSpacing(10)
245 # Margin/Spacing percentage
246 layout.addWidget(QLabel("Margin/Spacing:"))
247 margin_layout = QHBoxLayout()
248 margin_spinbox = QDoubleSpinBox()
249 margin_spinbox.setRange(0.0, 10.0)
250 margin_spinbox.setValue(2.5)
251 margin_spinbox.setSuffix("%")
252 margin_spinbox.setDecimals(1)
253 margin_spinbox.setSingleStep(0.5)
254 margin_spinbox.setToolTip("Percentage of page size to use for margins and spacing")
255 margin_layout.addWidget(margin_spinbox)
256 margin_layout.addStretch()
257 layout.addLayout(margin_layout)
259 layout.addSpacing(10)
261 # Scaling selection
262 layout.addWidget(QLabel("Scaling:"))
263 scale_group = QButtonGroup(dialog)
265 proportional_radio = QRadioButton("Proportional (maintain aspect ratio)")
266 scale_group.addButton(proportional_radio, 0)
267 layout.addWidget(proportional_radio)
269 stretch_radio = QRadioButton("Stretch to fit")
270 stretch_radio.setChecked(True)
271 scale_group.addButton(stretch_radio, 1)
272 layout.addWidget(stretch_radio)
274 center_radio = QRadioButton("Center (no scaling)")
275 scale_group.addButton(center_radio, 2)
276 layout.addWidget(center_radio)
278 layout.addSpacing(20)
280 # Buttons
281 button_layout = QHBoxLayout()
282 cancel_btn = QPushButton("Cancel")
283 cancel_btn.clicked.connect(dialog.reject)
284 apply_btn = QPushButton("Apply")
285 apply_btn.clicked.connect(dialog.accept)
286 apply_btn.setDefault(True)
288 button_layout.addStretch()
289 button_layout.addWidget(cancel_btn)
290 button_layout.addWidget(apply_btn)
291 layout.addLayout(button_layout)
293 dialog.setLayout(layout)
295 # Show dialog
296 if dialog.exec() != QDialog.DialogCode.Accepted:
297 return
299 # Get selections
300 template_name = template_combo.currentText()
301 mode_id = mode_group.checkedId()
302 scale_id = scale_group.checkedId()
303 margin_percent = margin_spinbox.value()
305 mode = "replace" if mode_id == 0 else "reflow"
306 scale_mode = ["proportional", "stretch", "center"][scale_id]
308 try:
309 # Load template
310 template = self.template_manager.load_template(template_name)
312 # Apply template to page
313 self.template_manager.apply_template_to_page(
314 template, current_page, mode=mode, scale_mode=scale_mode, margin_percent=margin_percent
315 )
317 # Update display
318 self.update_view()
320 self.show_status(f"Applied template '{template_name}' to current page", 3000)
321 print(f"Applied template '{template_name}' with mode={mode}, scale_mode={scale_mode}")
323 except Exception as e:
324 self.show_error("Error", f"Failed to apply template: {str(e)}")
325 print(f"Error applying template: {e}")