249 lines
10 KiB
Python
249 lines
10 KiB
Python
"""
|
||
Page operations mixin for pyPhotoAlbum
|
||
"""
|
||
|
||
from pyPhotoAlbum.decorators import ribbon_action, dialog_action
|
||
from pyPhotoAlbum.dialogs import PageSetupDialog
|
||
from pyPhotoAlbum.project import Page
|
||
from pyPhotoAlbum.page_layout import PageLayout
|
||
|
||
|
||
class PageOperationsMixin:
|
||
"""Mixin providing page management operations"""
|
||
|
||
# Note: Previous/Next page navigation removed - now using scrollable multi-page view
|
||
# User can scroll through all pages vertically
|
||
|
||
@ribbon_action(label="Add Page", tooltip="Add a new page to the project", tab="Layout", group="Page")
|
||
def add_page(self):
|
||
"""Add a new page to the project after the current page"""
|
||
# Get the most visible page in viewport to determine insertion point
|
||
current_page_index = self._get_most_visible_page_index()
|
||
|
||
# Ensure index is valid, default to end if not
|
||
if current_page_index < 0 or current_page_index >= len(self.project.pages):
|
||
insert_index = len(self.project.pages)
|
||
else:
|
||
# Insert after the current page
|
||
insert_index = current_page_index + 1
|
||
|
||
# Create layout with project default size
|
||
width_mm, height_mm = self.project.page_size_mm
|
||
new_layout = PageLayout(width=width_mm, height=height_mm)
|
||
|
||
# Calculate proper page number for the new page
|
||
# The page_number represents the logical page number in the book
|
||
if insert_index == 0:
|
||
# Inserting at the beginning
|
||
new_page_number = 1
|
||
elif insert_index >= len(self.project.pages):
|
||
# Inserting at the end - calculate based on last page
|
||
if self.project.pages:
|
||
last_page = self.project.pages[-1]
|
||
# Add the count of pages the last page represents
|
||
new_page_number = last_page.page_number + last_page.get_page_count()
|
||
else:
|
||
new_page_number = 1
|
||
else:
|
||
# Inserting in the middle - take the page number of the page that will come after
|
||
new_page_number = self.project.pages[insert_index].page_number
|
||
|
||
new_page = Page(layout=new_layout, page_number=new_page_number)
|
||
# New pages are not manually sized - they use project defaults
|
||
new_page.manually_sized = False
|
||
|
||
# Insert the page at the calculated position
|
||
self.project.add_page(new_page, index=insert_index)
|
||
|
||
# Renumber all pages to ensure consistent numbering
|
||
# Page numbers represent logical page numbers in the book
|
||
current_page_num = 1
|
||
for page in self.project.pages:
|
||
page.page_number = current_page_num
|
||
current_page_num += page.get_page_count()
|
||
|
||
self.update_view()
|
||
|
||
# Get display name for status message
|
||
new_page_name = self.project.get_page_display_name(new_page)
|
||
print(f"Added {new_page_name} at position {insert_index + 1} with size {width_mm}×{height_mm} mm")
|
||
|
||
@ribbon_action(label="Page Setup", tooltip="Configure page size and settings", tab="Layout", group="Page")
|
||
@dialog_action(dialog_class=PageSetupDialog, requires_pages=True)
|
||
def page_setup(self, values):
|
||
"""
|
||
Apply page setup configuration.
|
||
|
||
This method contains only business logic. UI presentation
|
||
is handled by PageSetupDialog and the dialog_action decorator.
|
||
|
||
Args:
|
||
values: Dictionary of values from the dialog
|
||
"""
|
||
selected_page = values["selected_page"]
|
||
selected_index = values["selected_index"]
|
||
|
||
# Update project cover settings
|
||
self.project.paper_thickness_mm = values["paper_thickness_mm"]
|
||
self.project.cover_bleed_mm = values["cover_bleed_mm"]
|
||
|
||
# Handle cover designation (only for first page)
|
||
if selected_index == 0:
|
||
was_cover = selected_page.is_cover
|
||
is_cover = values["is_cover"]
|
||
|
||
if was_cover != is_cover:
|
||
selected_page.is_cover = is_cover
|
||
self.project.has_cover = is_cover
|
||
|
||
if is_cover:
|
||
# Calculate and set cover dimensions
|
||
self.project.update_cover_dimensions()
|
||
print(f"Page 1 designated as cover")
|
||
else:
|
||
# Restore normal page size
|
||
selected_page.layout.size = self.project.page_size_mm
|
||
print(f"Cover removed from page 1")
|
||
|
||
# Get new values
|
||
width_mm = values["width_mm"]
|
||
height_mm = values["height_mm"]
|
||
|
||
# Don't allow manual size changes for covers
|
||
if not selected_page.is_cover:
|
||
# Check if size actually changed
|
||
# For double spreads, compare with base width
|
||
if selected_page.is_double_spread:
|
||
old_base_width = (
|
||
selected_page.layout.base_width
|
||
if hasattr(selected_page.layout, "base_width")
|
||
else selected_page.layout.size[0] / 2
|
||
)
|
||
old_height = selected_page.layout.size[1]
|
||
size_changed = old_base_width != width_mm or old_height != height_mm
|
||
|
||
if size_changed:
|
||
# Update double spread
|
||
selected_page.layout.base_width = width_mm
|
||
selected_page.layout.size = (width_mm * 2, height_mm)
|
||
selected_page.manually_sized = True
|
||
print(
|
||
f"{self.project.get_page_display_name(selected_page)} "
|
||
f"(double spread) updated to {width_mm}×{height_mm} mm per page"
|
||
)
|
||
else:
|
||
old_size = selected_page.layout.size
|
||
size_changed = old_size != (width_mm, height_mm)
|
||
|
||
if size_changed:
|
||
# Update single page
|
||
selected_page.layout.size = (width_mm, height_mm)
|
||
selected_page.layout.base_width = width_mm
|
||
selected_page.manually_sized = True
|
||
print(
|
||
f"{self.project.get_page_display_name(selected_page)} " f"updated to {width_mm}×{height_mm} mm"
|
||
)
|
||
|
||
# Update DPI settings
|
||
self.project.working_dpi = values["working_dpi"]
|
||
self.project.export_dpi = values["export_dpi"]
|
||
|
||
# Set as default if checkbox is checked
|
||
if values["set_as_default"]:
|
||
self.project.page_size_mm = (width_mm, height_mm)
|
||
print(f"Project default page size set to {width_mm}×{height_mm} mm")
|
||
|
||
self.update_view()
|
||
|
||
# Build status message
|
||
page_name = self.project.get_page_display_name(selected_page)
|
||
if selected_page.is_cover:
|
||
status_msg = f"{page_name} updated"
|
||
else:
|
||
status_msg = f"{page_name} size: {width_mm}×{height_mm} mm"
|
||
if values["set_as_default"]:
|
||
status_msg += " (set as default)"
|
||
self.show_status(status_msg, 2000)
|
||
|
||
@ribbon_action(
|
||
label="Toggle Spread", tooltip="Toggle double page spread for current page", tab="Layout", group="Page"
|
||
)
|
||
def toggle_double_spread(self):
|
||
"""Toggle double spread for the current page"""
|
||
if not self.project.pages:
|
||
return
|
||
|
||
# Try to get the most visible page in viewport, fallback to current_page_index
|
||
page_index = self._get_most_visible_page_index()
|
||
|
||
# Ensure index is valid
|
||
if page_index < 0 or page_index >= len(self.project.pages):
|
||
page_index = 0
|
||
|
||
current_page = self.project.pages[page_index]
|
||
|
||
# Toggle the state
|
||
is_double = not current_page.is_double_spread
|
||
current_page.is_double_spread = is_double
|
||
|
||
# Mark as manually sized when toggling spread
|
||
current_page.manually_sized = True
|
||
|
||
# Update the page layout width
|
||
current_width = current_page.layout.size[0]
|
||
current_height = current_page.layout.size[1]
|
||
|
||
# Get base width (might already be doubled)
|
||
if hasattr(current_page.layout, "base_width"):
|
||
base_width = current_page.layout.base_width
|
||
else:
|
||
# Assume current width is single if not marked as facing
|
||
base_width = current_width / 2 if current_page.layout.is_facing_page else current_width
|
||
|
||
# Set new width based on double spread state
|
||
new_width = base_width * 2 if is_double else base_width
|
||
current_page.layout.base_width = base_width
|
||
current_page.layout.is_facing_page = is_double
|
||
current_page.layout.size = (new_width, current_height)
|
||
|
||
# Update display
|
||
self.update_view()
|
||
|
||
status = "enabled" if is_double else "disabled"
|
||
page_name = self.project.get_page_display_name(current_page)
|
||
self.show_status(f"{page_name}: Double spread {status}, width = {new_width:.0f}mm", 2000)
|
||
print(f"{page_name}: Double spread {status}, width = {new_width}mm")
|
||
|
||
@ribbon_action(label="Remove Page", tooltip="Remove the currently selected page", tab="Layout", group="Page")
|
||
def remove_page(self):
|
||
"""Remove the currently selected page"""
|
||
if len(self.project.pages) <= 1:
|
||
self.show_warning("Cannot Remove", "Must have at least one page")
|
||
print("Cannot remove page - must have at least one page")
|
||
return
|
||
|
||
# Get the most visible page in viewport
|
||
page_index = self._get_most_visible_page_index()
|
||
|
||
# Ensure index is valid
|
||
if page_index < 0 or page_index >= len(self.project.pages):
|
||
page_index = len(self.project.pages) - 1
|
||
|
||
page_to_remove = self.project.pages[page_index]
|
||
page_name = self.project.get_page_display_name(page_to_remove)
|
||
|
||
# Remove the selected page
|
||
self.project.remove_page(page_to_remove)
|
||
|
||
# Renumber remaining pages to ensure consistent numbering
|
||
# Page numbers represent logical page numbers in the book
|
||
current_page_num = 1
|
||
for page in self.project.pages:
|
||
page.page_number = current_page_num
|
||
current_page_num += page.get_page_count()
|
||
|
||
# Update display
|
||
self.update_view()
|
||
|
||
print(f"Removed {page_name}, now have {len(self.project.pages)} pages")
|