1 """

2 Template operations mixin for pyPhotoAlbum

3 """

4

5 from PyQt6.QtWidgets import (

6 QInputDialog,

7 QDialog,

8 QVBoxLayout,

9 QLabel,

10 QComboBox,

11 QRadioButton,

12 QButtonGroup,

13 QPushButton,

14 QHBoxLayout,

15 QDoubleSpinBox,

16 )

17 from pyPhotoAlbum.decorators import ribbon_action, undoable_operation

18

19

20 class TemplateOperationsMixin:

21 """Mixin providing template-related operations"""

22

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

35

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

40

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 )

48

49 if not ok or not name:

50 return

51

52 # Ask for optional description

53 description, ok = QInputDialog.getText(self, "Template Description", "Enter description (optional):")

54

55 if not ok:

56 description = ""

57

58 try:

59 # Create template from page

60 template = self.template_manager.create_template_from_page(current_page, name, description)

61

62 # Save template

63 self.template_manager.save_template(template)

64

65 self.show_info("Template Saved", f"Template '{name}' has been saved successfully.")

66

67 print(f"Saved template: {name}")

68

69 except Exception as e:

70 self.show_error("Error", f"Failed to save template: {str(e)}")

71 print(f"Error saving template: {e}")

72

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()

80

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

86

87 # Create dialog for template selection and options

88 dialog = QDialog(self)

89 dialog.setWindowTitle("New Page from Template")

90 dialog.setMinimumWidth(400)

91

92 layout = QVBoxLayout()

93

94 # Template selection

95 layout.addWidget(QLabel("Select Template:"))

96 template_combo = QComboBox()

97 template_combo.addItems(templates)

98 layout.addWidget(template_combo)

99

100 layout.addSpacing(10)

101

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)

115

116 layout.addSpacing(10)

117

118 # Scaling selection

119 layout.addWidget(QLabel("Scaling:"))

120 scale_group = QButtonGroup(dialog)

121

122 proportional_radio = QRadioButton("Proportional (maintain aspect ratio)")

123 scale_group.addButton(proportional_radio, 0)

124 layout.addWidget(proportional_radio)

125

126 stretch_radio = QRadioButton("Stretch to fit")

127 stretch_radio.setChecked(True)

128 scale_group.addButton(stretch_radio, 1)

129 layout.addWidget(stretch_radio)

130

131 center_radio = QRadioButton("Center (no scaling)")

132 scale_group.addButton(center_radio, 2)

133 layout.addWidget(center_radio)

134

135 layout.addSpacing(20)

136

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)

144

145 button_layout.addStretch()

146 button_layout.addWidget(cancel_btn)

147 button_layout.addWidget(create_btn)

148 layout.addLayout(button_layout)

149

150 dialog.setLayout(layout)

151

152 # Show dialog

153 if dialog.exec() != QDialog.DialogCode.Accepted:

154 return

155

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]

161

162 try:

163 # Load template

164 template = self.template_manager.load_template(template_name)

165

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 )

175

176 # Add to project

177 self.project.add_page(new_page)

178

179 # Switch to new page

180 self.gl_widget.current_page_index = len(self.project.pages) - 1

181 self.update_view()

182

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}%")

185

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}")

189

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

203

204 # Get available templates

205 templates = self.template_manager.list_templates()

206

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

212

213 # Create dialog for template application options

214 dialog = QDialog(self)

215 dialog.setWindowTitle("Apply Template")

216 dialog.setMinimumWidth(400)

217

218 layout = QVBoxLayout()

219

220 # Template selection

221 layout.addWidget(QLabel("Select Template:"))

222 template_combo = QComboBox()

223 template_combo.addItems(templates)

224 layout.addWidget(template_combo)

225

226 layout.addSpacing(10)

227

228 # Mode selection

229 layout.addWidget(QLabel("Mode:"))

230 mode_group = QButtonGroup(dialog)

231

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)

237

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)

242

243 layout.addSpacing(10)

244

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)

258

259 layout.addSpacing(10)

260

261 # Scaling selection

262 layout.addWidget(QLabel("Scaling:"))

263 scale_group = QButtonGroup(dialog)

264

265 proportional_radio = QRadioButton("Proportional (maintain aspect ratio)")

266 scale_group.addButton(proportional_radio, 0)

267 layout.addWidget(proportional_radio)

268

269 stretch_radio = QRadioButton("Stretch to fit")

270 stretch_radio.setChecked(True)

271 scale_group.addButton(stretch_radio, 1)

272 layout.addWidget(stretch_radio)

273

274 center_radio = QRadioButton("Center (no scaling)")

275 scale_group.addButton(center_radio, 2)

276 layout.addWidget(center_radio)

277

278 layout.addSpacing(20)

279

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)

287

288 button_layout.addStretch()

289 button_layout.addWidget(cancel_btn)

290 button_layout.addWidget(apply_btn)

291 layout.addLayout(button_layout)

292

293 dialog.setLayout(layout)

294

295 # Show dialog

296 if dialog.exec() != QDialog.DialogCode.Accepted:

297 return

298

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()

304

305 mode = "replace" if mode_id == 0 else "reflow"

306 scale_mode = ["proportional", "stretch", "center"][scale_id]

307

308 try:

309 # Load template

310 template = self.template_manager.load_template(template_name)

311

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 )

316

317 # Update display

318 self.update_view()

319

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}")

322

323 except Exception as e:

324 self.show_error("Error", f"Failed to apply template: {str(e)}")

325 print(f"Error applying template: {e}")