Files
pyPhotoAlbum/pyPhotoAlbum/dialogs/frame_picker_dialog.py
T

353 lines
12 KiB
Python

"""
Frame picker dialog for pyPhotoAlbum
Dialog for selecting decorative frames to apply to images.
"""
from typing import Optional, Tuple
from PyQt6.QtWidgets import (
QDialog,
QVBoxLayout,
QHBoxLayout,
QLabel,
QPushButton,
QTabWidget,
QWidget,
QGridLayout,
QScrollArea,
QFrame,
QGroupBox,
QCheckBox,
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QPainter, QColor, QPen
from pyPhotoAlbum.frame_manager import get_frame_manager, FrameCategory, FrameDefinition, FrameType
class FramePreviewWidget(QFrame):
"""Widget that shows a preview of a frame"""
clicked = pyqtSignal(str) # Emits frame name when clicked
def __init__(self, frame: FrameDefinition, parent=None):
super().__init__(parent)
self.frame = frame
self.selected = False
self.setFixedSize(100, 100)
self.setFrameStyle(QFrame.Shape.Box)
self.setCursor(Qt.CursorShape.PointingHandCursor)
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# Background
if self.selected:
painter.fillRect(self.rect(), QColor(200, 220, 255))
else:
painter.fillRect(self.rect(), QColor(245, 245, 245))
# Draw a simple preview of the frame style
margin = 15
rect = self.rect().adjusted(margin, margin, -margin, -margin)
# Draw "photo" placeholder
painter.fillRect(rect, QColor(180, 200, 220))
# Draw frame preview based on type
pen = QPen(QColor(80, 80, 80))
pen.setWidth(2)
painter.setPen(pen)
if self.frame.frame_type.value == "corners":
# Draw corner decorations
corner_size = 12
x, y, w, h = rect.x(), rect.y(), rect.width(), rect.height()
# Top-left
painter.drawLine(x, y + corner_size, x, y)
painter.drawLine(x, y, x + corner_size, y)
# Top-right
painter.drawLine(x + w - corner_size, y, x + w, y)
painter.drawLine(x + w, y, x + w, y + corner_size)
# Bottom-right
painter.drawLine(x + w, y + h - corner_size, x + w, y + h)
painter.drawLine(x + w, y + h, x + w - corner_size, y + h)
# Bottom-left
painter.drawLine(x + corner_size, y + h, x, y + h)
painter.drawLine(x, y + h, x, y + h - corner_size)
else:
# Draw full border
painter.drawRect(rect.adjusted(-3, -3, 3, 3))
painter.drawRect(rect)
# Draw frame name
painter.setPen(QColor(0, 0, 0))
text_rect = self.rect().adjusted(0, 0, 0, 0)
text_rect.setTop(self.rect().bottom() - 20)
painter.drawText(text_rect, Qt.AlignmentFlag.AlignCenter, self.frame.display_name)
def mousePressEvent(self, event):
self.clicked.emit(self.frame.name)
def set_selected(self, selected: bool):
self.selected = selected
self.update()
class FramePickerDialog(QDialog):
"""Dialog for selecting a decorative frame"""
def __init__(
self,
parent,
current_frame: Optional[str] = None,
current_color: Tuple[int, int, int] = (0, 0, 0),
current_corners: Tuple[bool, bool, bool, bool] = (True, True, True, True),
):
super().__init__(parent)
self.setWindowTitle("Select Frame")
self.setMinimumSize(500, 500)
self.selected_frame: Optional[str] = current_frame
self.frame_color = current_color
self.frame_corners = current_corners # (TL, TR, BR, BL)
self.frame_widgets: dict[str, FramePreviewWidget] = {}
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
# Tab widget for categories
self.tab_widget = QTabWidget()
# All frames tab
all_tab = self._create_category_tab(None)
self.tab_widget.addTab(all_tab, "All")
# Category tabs
for category in FrameCategory:
tab = self._create_category_tab(category)
self.tab_widget.addTab(tab, category.value.title())
layout.addWidget(self.tab_widget)
# Selected frame info
info_group = QGroupBox("Selected Frame")
info_layout = QVBoxLayout(info_group)
# Frame name and color row
name_color_layout = QHBoxLayout()
self.selected_label = QLabel("None")
name_color_layout.addWidget(self.selected_label)
# Color button
from pyPhotoAlbum.dialogs.style_dialogs import ColorButton
name_color_layout.addWidget(QLabel("Color:"))
self.color_btn = ColorButton(self.frame_color)
name_color_layout.addWidget(self.color_btn)
name_color_layout.addStretch()
info_layout.addLayout(name_color_layout)
# Corner selection (for corner-type frames)
self.corners_group = QGroupBox("Corner Decorations")
corners_layout = QGridLayout(self.corners_group)
# Create a visual grid for corner checkboxes
self.corner_tl = QCheckBox("Top-Left")
self.corner_tl.setChecked(self.frame_corners[0])
self.corner_tl.stateChanged.connect(self._update_corners)
self.corner_tr = QCheckBox("Top-Right")
self.corner_tr.setChecked(self.frame_corners[1])
self.corner_tr.stateChanged.connect(self._update_corners)
self.corner_br = QCheckBox("Bottom-Right")
self.corner_br.setChecked(self.frame_corners[2])
self.corner_br.stateChanged.connect(self._update_corners)
self.corner_bl = QCheckBox("Bottom-Left")
self.corner_bl.setChecked(self.frame_corners[3])
self.corner_bl.stateChanged.connect(self._update_corners)
corners_layout.addWidget(self.corner_tl, 0, 0)
corners_layout.addWidget(self.corner_tr, 0, 1)
corners_layout.addWidget(self.corner_bl, 1, 0)
corners_layout.addWidget(self.corner_br, 1, 1)
# Quick selection buttons
quick_btns_layout = QHBoxLayout()
all_btn = QPushButton("All")
all_btn.clicked.connect(self._select_all_corners)
none_btn = QPushButton("None")
none_btn.clicked.connect(self._select_no_corners)
diag_btn = QPushButton("Diagonal")
diag_btn.clicked.connect(self._select_diagonal_corners)
quick_btns_layout.addWidget(all_btn)
quick_btns_layout.addWidget(none_btn)
quick_btns_layout.addWidget(diag_btn)
quick_btns_layout.addStretch()
corners_layout.addLayout(quick_btns_layout, 2, 0, 1, 2)
info_layout.addWidget(self.corners_group)
layout.addWidget(info_group)
# Update corners group visibility based on frame type
self._update_corners_visibility()
# Buttons
button_layout = QHBoxLayout()
clear_btn = QPushButton("No Frame")
clear_btn.clicked.connect(self._clear_selection)
button_layout.addWidget(clear_btn)
button_layout.addStretch()
ok_btn = QPushButton("OK")
ok_btn.clicked.connect(self.accept)
button_layout.addWidget(ok_btn)
cancel_btn = QPushButton("Cancel")
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
# Update selection display
self._update_selection_display()
def _create_category_tab(self, category: Optional[FrameCategory]) -> QWidget:
"""Create a tab for a frame category"""
widget = QWidget()
layout = QVBoxLayout(widget)
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
content = QWidget()
grid = QGridLayout(content)
grid.setSpacing(10)
frame_manager = get_frame_manager()
if category:
frames = frame_manager.get_frames_by_category(category)
else:
frames = frame_manager.get_all_frames()
row, col = 0, 0
max_cols = 4
for frame in frames:
preview = FramePreviewWidget(frame)
preview.clicked.connect(self._on_frame_clicked)
if frame.name == self.selected_frame:
preview.set_selected(True)
grid.addWidget(preview, row, col)
self.frame_widgets[frame.name] = preview
col += 1
if col >= max_cols:
col = 0
row += 1
# Add stretch at the bottom
grid.setRowStretch(row + 1, 1)
scroll.setWidget(content)
layout.addWidget(scroll)
return widget
def _on_frame_clicked(self, frame_name: str):
"""Handle frame selection"""
# Deselect previous
if self.selected_frame and self.selected_frame in self.frame_widgets:
self.frame_widgets[self.selected_frame].set_selected(False)
# Select new
self.selected_frame = frame_name
if frame_name in self.frame_widgets:
self.frame_widgets[frame_name].set_selected(True)
self._update_selection_display()
self._update_corners_visibility()
def _clear_selection(self):
"""Clear frame selection"""
if self.selected_frame and self.selected_frame in self.frame_widgets:
self.frame_widgets[self.selected_frame].set_selected(False)
self.selected_frame = None
self._update_selection_display()
self._update_corners_visibility()
def _update_selection_display(self):
"""Update the selected frame label"""
if self.selected_frame:
frame = get_frame_manager().get_frame(self.selected_frame)
if frame:
self.selected_label.setText(f"{frame.display_name} - {frame.description}")
else:
self.selected_label.setText(self.selected_frame)
else:
self.selected_label.setText("None")
def _update_corners(self):
"""Update corner selection from checkboxes"""
self.frame_corners = (
self.corner_tl.isChecked(),
self.corner_tr.isChecked(),
self.corner_br.isChecked(),
self.corner_bl.isChecked(),
)
def _update_corners_visibility(self):
"""Show/hide corners group based on selected frame type"""
if self.selected_frame:
frame = get_frame_manager().get_frame(self.selected_frame)
if frame and frame.frame_type == FrameType.CORNERS:
self.corners_group.setVisible(True)
return
self.corners_group.setVisible(False)
def _select_all_corners(self):
"""Select all corners"""
self.corner_tl.setChecked(True)
self.corner_tr.setChecked(True)
self.corner_br.setChecked(True)
self.corner_bl.setChecked(True)
self._update_corners()
def _select_no_corners(self):
"""Deselect all corners"""
self.corner_tl.setChecked(False)
self.corner_tr.setChecked(False)
self.corner_br.setChecked(False)
self.corner_bl.setChecked(False)
self._update_corners()
def _select_diagonal_corners(self):
"""Select diagonal corners (TL and BR)"""
self.corner_tl.setChecked(True)
self.corner_tr.setChecked(False)
self.corner_br.setChecked(True)
self.corner_bl.setChecked(False)
self._update_corners()
def get_values(self) -> Tuple[Optional[str], Tuple[int, int, int], Tuple[bool, bool, bool, bool]]:
"""Get selected frame name, color, and corner configuration"""
return self.selected_frame, self.color_btn.get_color(), self.frame_corners