pyPhotoAlbum/pyPhotoAlbum/dialogs/page_setup_dialog.py
2026-01-01 17:47:58 +00:00

323 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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(),
}