323 lines
11 KiB
Python
323 lines
11 KiB
Python
"""
|
||
Page Setup Dialog for pyPhotoAlbum
|
||
|
||
Encapsulates all UI logic for page setup configuration,
|
||
separating presentation from business logic.
|
||
"""
|
||
|
||
import math
|
||
from typing import Optional, Dict, Any
|
||
from PyQt6.QtWidgets import (
|
||
QDialog,
|
||
QVBoxLayout,
|
||
QHBoxLayout,
|
||
QLabel,
|
||
QDoubleSpinBox,
|
||
QSpinBox,
|
||
QPushButton,
|
||
QGroupBox,
|
||
QComboBox,
|
||
QCheckBox,
|
||
)
|
||
from pyPhotoAlbum.project import Project
|
||
|
||
|
||
class PageSetupDialog(QDialog):
|
||
"""
|
||
Dialog for configuring page settings.
|
||
|
||
This dialog handles all UI presentation logic for page setup,
|
||
including page size, DPI settings, and cover configuration.
|
||
"""
|
||
|
||
def __init__(self, parent, project: Project, initial_page_index: int = 0):
|
||
"""
|
||
Initialize the page setup dialog.
|
||
|
||
Args:
|
||
parent: Parent widget
|
||
project: Project instance containing pages and settings
|
||
initial_page_index: Index of page to initially select
|
||
"""
|
||
super().__init__(parent)
|
||
self.project = project
|
||
self.initial_page_index = initial_page_index
|
||
|
||
self._setup_ui()
|
||
self._connect_signals()
|
||
self._initialize_values()
|
||
|
||
def _setup_ui(self):
|
||
"""Create and layout all UI components."""
|
||
self.setWindowTitle("Page Setup")
|
||
self.setMinimumWidth(450)
|
||
|
||
layout = QVBoxLayout()
|
||
|
||
# Page selection group
|
||
self._page_select_group = self._create_page_selection_group()
|
||
layout.addWidget(self._page_select_group)
|
||
|
||
# Cover settings group
|
||
self._cover_group = self._create_cover_settings_group()
|
||
layout.addWidget(self._cover_group)
|
||
|
||
# Page size group
|
||
self._size_group = self._create_page_size_group()
|
||
layout.addWidget(self._size_group)
|
||
|
||
# DPI settings group
|
||
self._dpi_group = self._create_dpi_settings_group()
|
||
layout.addWidget(self._dpi_group)
|
||
|
||
# Buttons
|
||
button_layout = self._create_button_layout()
|
||
layout.addLayout(button_layout)
|
||
|
||
self.setLayout(layout)
|
||
|
||
def _create_page_selection_group(self) -> QGroupBox:
|
||
"""Create the page selection group."""
|
||
group = QGroupBox("Select Page")
|
||
layout = QVBoxLayout()
|
||
|
||
# Page combo box
|
||
self.page_combo = QComboBox()
|
||
for i, page in enumerate(self.project.pages):
|
||
page_label = self.project.get_page_display_name(page)
|
||
if page.is_double_spread and not page.is_cover:
|
||
page_label += " (Double Spread)"
|
||
if page.manually_sized:
|
||
page_label += " *"
|
||
self.page_combo.addItem(page_label, i)
|
||
layout.addWidget(self.page_combo)
|
||
|
||
# Info label
|
||
info_label = QLabel("* = Manually sized page")
|
||
info_label.setStyleSheet("font-size: 9pt; color: gray;")
|
||
layout.addWidget(info_label)
|
||
|
||
group.setLayout(layout)
|
||
return group
|
||
|
||
def _create_cover_settings_group(self) -> QGroupBox:
|
||
"""Create the cover settings group."""
|
||
group = QGroupBox("Cover Settings")
|
||
layout = QVBoxLayout()
|
||
|
||
# Cover checkbox
|
||
self.cover_checkbox = QCheckBox("Designate as Cover")
|
||
self.cover_checkbox.setToolTip("Mark this page as the book cover with wrap-around front/spine/back")
|
||
layout.addWidget(self.cover_checkbox)
|
||
|
||
# Paper thickness
|
||
thickness_layout = QHBoxLayout()
|
||
thickness_layout.addWidget(QLabel("Paper Thickness:"))
|
||
self.thickness_spinbox = QDoubleSpinBox()
|
||
self.thickness_spinbox.setRange(0.05, 1.0)
|
||
self.thickness_spinbox.setSingleStep(0.05)
|
||
self.thickness_spinbox.setValue(self.project.paper_thickness_mm)
|
||
self.thickness_spinbox.setSuffix(" mm")
|
||
self.thickness_spinbox.setToolTip("Thickness of paper for spine calculation")
|
||
thickness_layout.addWidget(self.thickness_spinbox)
|
||
layout.addLayout(thickness_layout)
|
||
|
||
# Bleed margin
|
||
bleed_layout = QHBoxLayout()
|
||
bleed_layout.addWidget(QLabel("Bleed Margin:"))
|
||
self.bleed_spinbox = QDoubleSpinBox()
|
||
self.bleed_spinbox.setRange(0, 10)
|
||
self.bleed_spinbox.setSingleStep(0.5)
|
||
self.bleed_spinbox.setValue(self.project.cover_bleed_mm)
|
||
self.bleed_spinbox.setSuffix(" mm")
|
||
self.bleed_spinbox.setToolTip("Extra margin around cover for printing bleed")
|
||
bleed_layout.addWidget(self.bleed_spinbox)
|
||
layout.addLayout(bleed_layout)
|
||
|
||
# Calculated spine width display
|
||
self.spine_info_label = QLabel()
|
||
self.spine_info_label.setStyleSheet("font-size: 9pt; color: #0066cc; padding: 5px;")
|
||
self.spine_info_label.setWordWrap(True)
|
||
layout.addWidget(self.spine_info_label)
|
||
|
||
group.setLayout(layout)
|
||
return group
|
||
|
||
def _create_page_size_group(self) -> QGroupBox:
|
||
"""Create the page size group."""
|
||
group = QGroupBox("Page Size")
|
||
layout = QVBoxLayout()
|
||
|
||
# Width
|
||
width_layout = QHBoxLayout()
|
||
width_layout.addWidget(QLabel("Width:"))
|
||
self.width_spinbox = QDoubleSpinBox()
|
||
self.width_spinbox.setRange(10, 1000)
|
||
self.width_spinbox.setSuffix(" mm")
|
||
width_layout.addWidget(self.width_spinbox)
|
||
layout.addLayout(width_layout)
|
||
|
||
# Height
|
||
height_layout = QHBoxLayout()
|
||
height_layout.addWidget(QLabel("Height:"))
|
||
self.height_spinbox = QDoubleSpinBox()
|
||
self.height_spinbox.setRange(10, 1000)
|
||
self.height_spinbox.setSuffix(" mm")
|
||
height_layout.addWidget(self.height_spinbox)
|
||
layout.addLayout(height_layout)
|
||
|
||
# Set as default checkbox
|
||
self.set_default_checkbox = QCheckBox("Set as default for new pages")
|
||
self.set_default_checkbox.setToolTip("Update project default page size for future pages")
|
||
layout.addWidget(self.set_default_checkbox)
|
||
|
||
group.setLayout(layout)
|
||
return group
|
||
|
||
def _create_dpi_settings_group(self) -> QGroupBox:
|
||
"""Create the DPI settings group."""
|
||
group = QGroupBox("DPI Settings")
|
||
layout = QVBoxLayout()
|
||
|
||
# Working DPI
|
||
working_dpi_layout = QHBoxLayout()
|
||
working_dpi_layout.addWidget(QLabel("Working DPI:"))
|
||
self.working_dpi_spinbox = QSpinBox()
|
||
self.working_dpi_spinbox.setRange(72, 1200)
|
||
self.working_dpi_spinbox.setValue(self.project.working_dpi)
|
||
working_dpi_layout.addWidget(self.working_dpi_spinbox)
|
||
layout.addLayout(working_dpi_layout)
|
||
|
||
# Export DPI
|
||
export_dpi_layout = QHBoxLayout()
|
||
export_dpi_layout.addWidget(QLabel("Export DPI:"))
|
||
self.export_dpi_spinbox = QSpinBox()
|
||
self.export_dpi_spinbox.setRange(72, 1200)
|
||
self.export_dpi_spinbox.setValue(self.project.export_dpi)
|
||
export_dpi_layout.addWidget(self.export_dpi_spinbox)
|
||
layout.addLayout(export_dpi_layout)
|
||
|
||
group.setLayout(layout)
|
||
return group
|
||
|
||
def _create_button_layout(self) -> QHBoxLayout:
|
||
"""Create dialog button layout."""
|
||
layout = QHBoxLayout()
|
||
|
||
cancel_btn = QPushButton("Cancel")
|
||
cancel_btn.clicked.connect(self.reject)
|
||
|
||
ok_btn = QPushButton("OK")
|
||
ok_btn.clicked.connect(self.accept)
|
||
ok_btn.setDefault(True)
|
||
|
||
layout.addStretch()
|
||
layout.addWidget(cancel_btn)
|
||
layout.addWidget(ok_btn)
|
||
|
||
return layout
|
||
|
||
def _connect_signals(self):
|
||
"""Connect widget signals to handlers."""
|
||
self.page_combo.currentIndexChanged.connect(self._on_page_changed)
|
||
self.cover_checkbox.stateChanged.connect(self._update_spine_info)
|
||
self.thickness_spinbox.valueChanged.connect(self._update_spine_info)
|
||
self.bleed_spinbox.valueChanged.connect(self._update_spine_info)
|
||
|
||
def _initialize_values(self):
|
||
"""Initialize dialog values based on current page."""
|
||
# Set initial page selection
|
||
if 0 <= self.initial_page_index < len(self.project.pages):
|
||
self.page_combo.setCurrentIndex(self.initial_page_index)
|
||
|
||
# Trigger initial page change to populate values
|
||
self._on_page_changed(self.initial_page_index)
|
||
|
||
def _on_page_changed(self, index: int):
|
||
"""
|
||
Handle page selection change.
|
||
|
||
Args:
|
||
index: Index of selected page
|
||
"""
|
||
if index < 0 or index >= len(self.project.pages):
|
||
return
|
||
|
||
selected_page = self.project.pages[index]
|
||
is_first_page = index == 0
|
||
|
||
# Show/hide cover settings based on page selection
|
||
self._cover_group.setVisible(is_first_page)
|
||
|
||
# Update cover checkbox
|
||
if is_first_page:
|
||
self.cover_checkbox.setChecked(selected_page.is_cover)
|
||
self._update_spine_info()
|
||
|
||
# Get display width (accounting for double spreads and covers)
|
||
if selected_page.is_cover:
|
||
# For covers, show the full calculated width
|
||
display_width = selected_page.layout.size[0]
|
||
elif selected_page.is_double_spread:
|
||
display_width = (
|
||
selected_page.layout.base_width
|
||
if hasattr(selected_page.layout, "base_width")
|
||
else selected_page.layout.size[0] / 2
|
||
)
|
||
else:
|
||
display_width = selected_page.layout.size[0]
|
||
|
||
self.width_spinbox.setValue(display_width)
|
||
self.height_spinbox.setValue(selected_page.layout.size[1])
|
||
|
||
# Disable size editing for covers (auto-calculated)
|
||
is_cover = selected_page.is_cover
|
||
self.width_spinbox.setEnabled(not is_cover)
|
||
self.height_spinbox.setEnabled(not is_cover)
|
||
self.set_default_checkbox.setEnabled(not is_cover)
|
||
|
||
def _update_spine_info(self):
|
||
"""Update the spine information display."""
|
||
if self.cover_checkbox.isChecked():
|
||
# Calculate spine width with current settings
|
||
content_pages = sum(p.get_page_count() for p in self.project.pages if not p.is_cover)
|
||
sheets = math.ceil(content_pages / 4)
|
||
spine_width = sheets * self.thickness_spinbox.value() * 2
|
||
|
||
page_width = self.project.page_size_mm[0]
|
||
total_width = (page_width * 2) + spine_width + (self.bleed_spinbox.value() * 2)
|
||
|
||
self.spine_info_label.setText(
|
||
f"Cover Layout: Front ({page_width:.0f}mm) + "
|
||
f"Spine ({spine_width:.2f}mm) + "
|
||
f"Back ({page_width:.0f}mm) + "
|
||
f"Bleed ({self.bleed_spinbox.value():.1f}mm × 2)\n"
|
||
f"Total Width: {total_width:.1f}mm | "
|
||
f"Content Pages: {content_pages} | Sheets: {sheets}"
|
||
)
|
||
else:
|
||
self.spine_info_label.setText("")
|
||
|
||
def get_values(self) -> Dict[str, Any]:
|
||
"""
|
||
Get dialog values.
|
||
|
||
Returns:
|
||
Dictionary containing all dialog values
|
||
"""
|
||
selected_index = self.page_combo.currentData()
|
||
selected_page = self.project.pages[selected_index]
|
||
|
||
return {
|
||
"selected_index": selected_index,
|
||
"selected_page": selected_page,
|
||
"is_cover": self.cover_checkbox.isChecked(),
|
||
"paper_thickness_mm": self.thickness_spinbox.value(),
|
||
"cover_bleed_mm": self.bleed_spinbox.value(),
|
||
"width_mm": self.width_spinbox.value(),
|
||
"height_mm": self.height_spinbox.value(),
|
||
"working_dpi": self.working_dpi_spinbox.value(),
|
||
"export_dpi": self.export_dpi_spinbox.value(),
|
||
"set_as_default": self.set_default_checkbox.isChecked(),
|
||
}
|