353 lines
12 KiB
Python
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
|