Coverage for pyPhotoAlbum/dialogs/frame_picker_dialog.py: 0%

205 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-20 12:55 +0000

1""" 

2Frame picker dialog for pyPhotoAlbum 

3 

4Dialog for selecting decorative frames to apply to images. 

5""" 

6 

7from typing import Optional, Tuple 

8from PyQt6.QtWidgets import ( 

9 QDialog, 

10 QVBoxLayout, 

11 QHBoxLayout, 

12 QLabel, 

13 QPushButton, 

14 QTabWidget, 

15 QWidget, 

16 QGridLayout, 

17 QScrollArea, 

18 QFrame, 

19 QGroupBox, 

20 QCheckBox, 

21) 

22from PyQt6.QtCore import Qt, pyqtSignal 

23from PyQt6.QtGui import QPainter, QColor, QPen 

24 

25from pyPhotoAlbum.frame_manager import get_frame_manager, FrameCategory, FrameDefinition, FrameType 

26 

27 

28class FramePreviewWidget(QFrame): 

29 """Widget that shows a preview of a frame""" 

30 

31 clicked = pyqtSignal(str) # Emits frame name when clicked 

32 

33 def __init__(self, frame: FrameDefinition, parent=None): 

34 super().__init__(parent) 

35 self.frame = frame 

36 self.selected = False 

37 self.setFixedSize(100, 100) 

38 self.setFrameStyle(QFrame.Shape.Box) 

39 self.setCursor(Qt.CursorShape.PointingHandCursor) 

40 

41 def paintEvent(self, event): 

42 super().paintEvent(event) 

43 painter = QPainter(self) 

44 painter.setRenderHint(QPainter.RenderHint.Antialiasing) 

45 

46 # Background 

47 if self.selected: 

48 painter.fillRect(self.rect(), QColor(200, 220, 255)) 

49 else: 

50 painter.fillRect(self.rect(), QColor(245, 245, 245)) 

51 

52 # Draw a simple preview of the frame style 

53 margin = 15 

54 rect = self.rect().adjusted(margin, margin, -margin, -margin) 

55 

56 # Draw "photo" placeholder 

57 painter.fillRect(rect, QColor(180, 200, 220)) 

58 

59 # Draw frame preview based on type 

60 pen = QPen(QColor(80, 80, 80)) 

61 pen.setWidth(2) 

62 painter.setPen(pen) 

63 

64 if self.frame.frame_type.value == "corners": 

65 # Draw corner decorations 

66 corner_size = 12 

67 x, y, w, h = rect.x(), rect.y(), rect.width(), rect.height() 

68 

69 # Top-left 

70 painter.drawLine(x, y + corner_size, x, y) 

71 painter.drawLine(x, y, x + corner_size, y) 

72 

73 # Top-right 

74 painter.drawLine(x + w - corner_size, y, x + w, y) 

75 painter.drawLine(x + w, y, x + w, y + corner_size) 

76 

77 # Bottom-right 

78 painter.drawLine(x + w, y + h - corner_size, x + w, y + h) 

79 painter.drawLine(x + w, y + h, x + w - corner_size, y + h) 

80 

81 # Bottom-left 

82 painter.drawLine(x + corner_size, y + h, x, y + h) 

83 painter.drawLine(x, y + h, x, y + h - corner_size) 

84 

85 else: 

86 # Draw full border 

87 painter.drawRect(rect.adjusted(-3, -3, 3, 3)) 

88 painter.drawRect(rect) 

89 

90 # Draw frame name 

91 painter.setPen(QColor(0, 0, 0)) 

92 text_rect = self.rect().adjusted(0, 0, 0, 0) 

93 text_rect.setTop(self.rect().bottom() - 20) 

94 painter.drawText(text_rect, Qt.AlignmentFlag.AlignCenter, self.frame.display_name) 

95 

96 def mousePressEvent(self, event): 

97 self.clicked.emit(self.frame.name) 

98 

99 def set_selected(self, selected: bool): 

100 self.selected = selected 

101 self.update() 

102 

103 

104class FramePickerDialog(QDialog): 

105 """Dialog for selecting a decorative frame""" 

106 

107 def __init__( 

108 self, 

109 parent, 

110 current_frame: Optional[str] = None, 

111 current_color: Tuple[int, int, int] = (0, 0, 0), 

112 current_corners: Tuple[bool, bool, bool, bool] = (True, True, True, True), 

113 ): 

114 super().__init__(parent) 

115 self.setWindowTitle("Select Frame") 

116 self.setMinimumSize(500, 500) 

117 

118 self.selected_frame: Optional[str] = current_frame 

119 self.frame_color = current_color 

120 self.frame_corners = current_corners # (TL, TR, BR, BL) 

121 self.frame_widgets: dict[str, FramePreviewWidget] = {} 

122 

123 self._setup_ui() 

124 

125 def _setup_ui(self): 

126 layout = QVBoxLayout(self) 

127 

128 # Tab widget for categories 

129 self.tab_widget = QTabWidget() 

130 

131 # All frames tab 

132 all_tab = self._create_category_tab(None) 

133 self.tab_widget.addTab(all_tab, "All") 

134 

135 # Category tabs 

136 for category in FrameCategory: 

137 tab = self._create_category_tab(category) 

138 self.tab_widget.addTab(tab, category.value.title()) 

139 

140 layout.addWidget(self.tab_widget) 

141 

142 # Selected frame info 

143 info_group = QGroupBox("Selected Frame") 

144 info_layout = QVBoxLayout(info_group) 

145 

146 # Frame name and color row 

147 name_color_layout = QHBoxLayout() 

148 self.selected_label = QLabel("None") 

149 name_color_layout.addWidget(self.selected_label) 

150 

151 # Color button 

152 from pyPhotoAlbum.dialogs.style_dialogs import ColorButton 

153 

154 name_color_layout.addWidget(QLabel("Color:")) 

155 self.color_btn = ColorButton(self.frame_color) 

156 name_color_layout.addWidget(self.color_btn) 

157 name_color_layout.addStretch() 

158 info_layout.addLayout(name_color_layout) 

159 

160 # Corner selection (for corner-type frames) 

161 self.corners_group = QGroupBox("Corner Decorations") 

162 corners_layout = QGridLayout(self.corners_group) 

163 

164 # Create a visual grid for corner checkboxes 

165 self.corner_tl = QCheckBox("Top-Left") 

166 self.corner_tl.setChecked(self.frame_corners[0]) 

167 self.corner_tl.stateChanged.connect(self._update_corners) 

168 

169 self.corner_tr = QCheckBox("Top-Right") 

170 self.corner_tr.setChecked(self.frame_corners[1]) 

171 self.corner_tr.stateChanged.connect(self._update_corners) 

172 

173 self.corner_br = QCheckBox("Bottom-Right") 

174 self.corner_br.setChecked(self.frame_corners[2]) 

175 self.corner_br.stateChanged.connect(self._update_corners) 

176 

177 self.corner_bl = QCheckBox("Bottom-Left") 

178 self.corner_bl.setChecked(self.frame_corners[3]) 

179 self.corner_bl.stateChanged.connect(self._update_corners) 

180 

181 corners_layout.addWidget(self.corner_tl, 0, 0) 

182 corners_layout.addWidget(self.corner_tr, 0, 1) 

183 corners_layout.addWidget(self.corner_bl, 1, 0) 

184 corners_layout.addWidget(self.corner_br, 1, 1) 

185 

186 # Quick selection buttons 

187 quick_btns_layout = QHBoxLayout() 

188 all_btn = QPushButton("All") 

189 all_btn.clicked.connect(self._select_all_corners) 

190 none_btn = QPushButton("None") 

191 none_btn.clicked.connect(self._select_no_corners) 

192 diag_btn = QPushButton("Diagonal") 

193 diag_btn.clicked.connect(self._select_diagonal_corners) 

194 quick_btns_layout.addWidget(all_btn) 

195 quick_btns_layout.addWidget(none_btn) 

196 quick_btns_layout.addWidget(diag_btn) 

197 quick_btns_layout.addStretch() 

198 corners_layout.addLayout(quick_btns_layout, 2, 0, 1, 2) 

199 

200 info_layout.addWidget(self.corners_group) 

201 

202 layout.addWidget(info_group) 

203 

204 # Update corners group visibility based on frame type 

205 self._update_corners_visibility() 

206 

207 # Buttons 

208 button_layout = QHBoxLayout() 

209 

210 clear_btn = QPushButton("No Frame") 

211 clear_btn.clicked.connect(self._clear_selection) 

212 button_layout.addWidget(clear_btn) 

213 

214 button_layout.addStretch() 

215 

216 ok_btn = QPushButton("OK") 

217 ok_btn.clicked.connect(self.accept) 

218 button_layout.addWidget(ok_btn) 

219 

220 cancel_btn = QPushButton("Cancel") 

221 cancel_btn.clicked.connect(self.reject) 

222 button_layout.addWidget(cancel_btn) 

223 

224 layout.addLayout(button_layout) 

225 

226 # Update selection display 

227 self._update_selection_display() 

228 

229 def _create_category_tab(self, category: Optional[FrameCategory]) -> QWidget: 

230 """Create a tab for a frame category""" 

231 widget = QWidget() 

232 layout = QVBoxLayout(widget) 

233 

234 scroll = QScrollArea() 

235 scroll.setWidgetResizable(True) 

236 scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) 

237 

238 content = QWidget() 

239 grid = QGridLayout(content) 

240 grid.setSpacing(10) 

241 

242 frame_manager = get_frame_manager() 

243 

244 if category: 

245 frames = frame_manager.get_frames_by_category(category) 

246 else: 

247 frames = frame_manager.get_all_frames() 

248 

249 row, col = 0, 0 

250 max_cols = 4 

251 

252 for frame in frames: 

253 preview = FramePreviewWidget(frame) 

254 preview.clicked.connect(self._on_frame_clicked) 

255 

256 if frame.name == self.selected_frame: 

257 preview.set_selected(True) 

258 

259 grid.addWidget(preview, row, col) 

260 self.frame_widgets[frame.name] = preview 

261 

262 col += 1 

263 if col >= max_cols: 

264 col = 0 

265 row += 1 

266 

267 # Add stretch at the bottom 

268 grid.setRowStretch(row + 1, 1) 

269 

270 scroll.setWidget(content) 

271 layout.addWidget(scroll) 

272 

273 return widget 

274 

275 def _on_frame_clicked(self, frame_name: str): 

276 """Handle frame selection""" 

277 # Deselect previous 

278 if self.selected_frame and self.selected_frame in self.frame_widgets: 

279 self.frame_widgets[self.selected_frame].set_selected(False) 

280 

281 # Select new 

282 self.selected_frame = frame_name 

283 if frame_name in self.frame_widgets: 

284 self.frame_widgets[frame_name].set_selected(True) 

285 

286 self._update_selection_display() 

287 self._update_corners_visibility() 

288 

289 def _clear_selection(self): 

290 """Clear frame selection""" 

291 if self.selected_frame and self.selected_frame in self.frame_widgets: 

292 self.frame_widgets[self.selected_frame].set_selected(False) 

293 self.selected_frame = None 

294 self._update_selection_display() 

295 self._update_corners_visibility() 

296 

297 def _update_selection_display(self): 

298 """Update the selected frame label""" 

299 if self.selected_frame: 

300 frame = get_frame_manager().get_frame(self.selected_frame) 

301 if frame: 

302 self.selected_label.setText(f"{frame.display_name} - {frame.description}") 

303 else: 

304 self.selected_label.setText(self.selected_frame) 

305 else: 

306 self.selected_label.setText("None") 

307 

308 def _update_corners(self): 

309 """Update corner selection from checkboxes""" 

310 self.frame_corners = ( 

311 self.corner_tl.isChecked(), 

312 self.corner_tr.isChecked(), 

313 self.corner_br.isChecked(), 

314 self.corner_bl.isChecked(), 

315 ) 

316 

317 def _update_corners_visibility(self): 

318 """Show/hide corners group based on selected frame type""" 

319 if self.selected_frame: 

320 frame = get_frame_manager().get_frame(self.selected_frame) 

321 if frame and frame.frame_type == FrameType.CORNERS: 

322 self.corners_group.setVisible(True) 

323 return 

324 self.corners_group.setVisible(False) 

325 

326 def _select_all_corners(self): 

327 """Select all corners""" 

328 self.corner_tl.setChecked(True) 

329 self.corner_tr.setChecked(True) 

330 self.corner_br.setChecked(True) 

331 self.corner_bl.setChecked(True) 

332 self._update_corners() 

333 

334 def _select_no_corners(self): 

335 """Deselect all corners""" 

336 self.corner_tl.setChecked(False) 

337 self.corner_tr.setChecked(False) 

338 self.corner_br.setChecked(False) 

339 self.corner_bl.setChecked(False) 

340 self._update_corners() 

341 

342 def _select_diagonal_corners(self): 

343 """Select diagonal corners (TL and BR)""" 

344 self.corner_tl.setChecked(True) 

345 self.corner_tr.setChecked(False) 

346 self.corner_br.setChecked(True) 

347 self.corner_bl.setChecked(False) 

348 self._update_corners() 

349 

350 def get_values(self) -> Tuple[Optional[str], Tuple[int, int, int], Tuple[bool, bool, bool, bool]]: 

351 """Get selected frame name, color, and corner configuration""" 

352 return self.selected_frame, self.color_btn.get_color(), self.frame_corners