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
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
1"""
2Frame picker dialog for pyPhotoAlbum
4Dialog for selecting decorative frames to apply to images.
5"""
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
25from pyPhotoAlbum.frame_manager import get_frame_manager, FrameCategory, FrameDefinition, FrameType
28class FramePreviewWidget(QFrame):
29 """Widget that shows a preview of a frame"""
31 clicked = pyqtSignal(str) # Emits frame name when clicked
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)
41 def paintEvent(self, event):
42 super().paintEvent(event)
43 painter = QPainter(self)
44 painter.setRenderHint(QPainter.RenderHint.Antialiasing)
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))
52 # Draw a simple preview of the frame style
53 margin = 15
54 rect = self.rect().adjusted(margin, margin, -margin, -margin)
56 # Draw "photo" placeholder
57 painter.fillRect(rect, QColor(180, 200, 220))
59 # Draw frame preview based on type
60 pen = QPen(QColor(80, 80, 80))
61 pen.setWidth(2)
62 painter.setPen(pen)
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()
69 # Top-left
70 painter.drawLine(x, y + corner_size, x, y)
71 painter.drawLine(x, y, x + corner_size, y)
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)
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)
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)
85 else:
86 # Draw full border
87 painter.drawRect(rect.adjusted(-3, -3, 3, 3))
88 painter.drawRect(rect)
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)
96 def mousePressEvent(self, event):
97 self.clicked.emit(self.frame.name)
99 def set_selected(self, selected: bool):
100 self.selected = selected
101 self.update()
104class FramePickerDialog(QDialog):
105 """Dialog for selecting a decorative frame"""
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)
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] = {}
123 self._setup_ui()
125 def _setup_ui(self):
126 layout = QVBoxLayout(self)
128 # Tab widget for categories
129 self.tab_widget = QTabWidget()
131 # All frames tab
132 all_tab = self._create_category_tab(None)
133 self.tab_widget.addTab(all_tab, "All")
135 # Category tabs
136 for category in FrameCategory:
137 tab = self._create_category_tab(category)
138 self.tab_widget.addTab(tab, category.value.title())
140 layout.addWidget(self.tab_widget)
142 # Selected frame info
143 info_group = QGroupBox("Selected Frame")
144 info_layout = QVBoxLayout(info_group)
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)
151 # Color button
152 from pyPhotoAlbum.dialogs.style_dialogs import ColorButton
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)
160 # Corner selection (for corner-type frames)
161 self.corners_group = QGroupBox("Corner Decorations")
162 corners_layout = QGridLayout(self.corners_group)
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)
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)
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)
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)
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)
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)
200 info_layout.addWidget(self.corners_group)
202 layout.addWidget(info_group)
204 # Update corners group visibility based on frame type
205 self._update_corners_visibility()
207 # Buttons
208 button_layout = QHBoxLayout()
210 clear_btn = QPushButton("No Frame")
211 clear_btn.clicked.connect(self._clear_selection)
212 button_layout.addWidget(clear_btn)
214 button_layout.addStretch()
216 ok_btn = QPushButton("OK")
217 ok_btn.clicked.connect(self.accept)
218 button_layout.addWidget(ok_btn)
220 cancel_btn = QPushButton("Cancel")
221 cancel_btn.clicked.connect(self.reject)
222 button_layout.addWidget(cancel_btn)
224 layout.addLayout(button_layout)
226 # Update selection display
227 self._update_selection_display()
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)
234 scroll = QScrollArea()
235 scroll.setWidgetResizable(True)
236 scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
238 content = QWidget()
239 grid = QGridLayout(content)
240 grid.setSpacing(10)
242 frame_manager = get_frame_manager()
244 if category:
245 frames = frame_manager.get_frames_by_category(category)
246 else:
247 frames = frame_manager.get_all_frames()
249 row, col = 0, 0
250 max_cols = 4
252 for frame in frames:
253 preview = FramePreviewWidget(frame)
254 preview.clicked.connect(self._on_frame_clicked)
256 if frame.name == self.selected_frame:
257 preview.set_selected(True)
259 grid.addWidget(preview, row, col)
260 self.frame_widgets[frame.name] = preview
262 col += 1
263 if col >= max_cols:
264 col = 0
265 row += 1
267 # Add stretch at the bottom
268 grid.setRowStretch(row + 1, 1)
270 scroll.setWidget(content)
271 layout.addWidget(scroll)
273 return widget
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)
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)
286 self._update_selection_display()
287 self._update_corners_visibility()
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()
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")
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 )
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)
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()
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()
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()
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