Duncan Tourolle f6ed11b0bc
All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
black formatting
2025-11-27 23:07:16 +01:00

213 lines
6.5 KiB
Python

"""
Base mixin providing shared application state access
"""
from typing import Optional
from PyQt6.QtWidgets import QStatusBar, QMessageBox
class ApplicationStateMixin:
"""
Base mixin providing access to shared application state.
This mixin provides properties and helper methods for accessing
core application objects that are shared across all operation mixins.
Required attributes (must be set by MainWindow):
_project: Project instance
_gl_widget: GLWidget instance
_status_bar: QStatusBar instance
_template_manager: TemplateManager instance
"""
@property
def project(self):
"""Access to current project"""
if not hasattr(self, "_project"):
raise AttributeError("MainWindow must set _project attribute")
return self._project
@project.setter
def project(self, value):
"""Set the current project"""
self._project = value
@property
def gl_widget(self):
"""Access to GL rendering widget"""
if not hasattr(self, "_gl_widget"):
raise AttributeError("MainWindow must set _gl_widget attribute")
return self._gl_widget
@property
def status_bar(self) -> QStatusBar:
"""Access to status bar"""
if not hasattr(self, "_status_bar"):
raise AttributeError("MainWindow must set _status_bar attribute")
return self._status_bar
@property
def template_manager(self):
"""Access to template manager"""
if not hasattr(self, "_template_manager"):
raise AttributeError("MainWindow must set _template_manager attribute")
return self._template_manager
# Common helper methods
def _get_most_visible_page_index(self):
"""
Determine which page is most visible in the current viewport.
Returns:
int: Index of the most visible page
"""
if not hasattr(self.gl_widget, "_page_renderers") or not self.gl_widget._page_renderers:
return self.gl_widget.current_page_index
# Get viewport dimensions
viewport_height = self.gl_widget.height()
viewport_center_y = viewport_height / 2
# Find which page's center is closest to viewport center
min_distance = float("inf")
best_page_index = self.gl_widget.current_page_index
for renderer, page in self.gl_widget._page_renderers:
# Get page center Y position in screen coordinates
page_height_mm = page.layout.size[1]
page_height_px = page_height_mm * self.project.working_dpi / 25.4
page_center_y_offset = renderer.screen_y + (page_height_px * self.gl_widget.zoom_level / 2)
# Calculate distance from viewport center
distance = abs(page_center_y_offset - viewport_center_y)
if distance < min_distance:
min_distance = distance
# Find the page index in project.pages
try:
best_page_index = self.project.pages.index(page)
except ValueError:
pass
return best_page_index
def get_current_page(self):
"""
Get currently visible page (most visible in viewport).
Returns:
Page instance or None if no page is selected
"""
if not self.project or not self.project.pages:
return None
index = self._get_most_visible_page_index()
if 0 <= index < len(self.project.pages):
return self.project.pages[index]
return None
def get_current_page_index(self) -> int:
"""
Get current page index.
Returns:
Current page index, or -1 if no page
"""
if not self.project or not self.project.pages:
return -1
return self.gl_widget.current_page_index
def show_status(self, message: str, timeout: int = 2000):
"""
Show message in status bar.
Args:
message: Message to display
timeout: Display duration in milliseconds
"""
if self.status_bar:
self.status_bar.showMessage(message, timeout)
def show_error(self, title: str, message: str):
"""
Show error dialog.
Args:
title: Dialog title
message: Error message
"""
QMessageBox.critical(self, title, message)
def show_warning(self, title: str, message: str):
"""
Show warning dialog.
Args:
title: Dialog title
message: Warning message
"""
QMessageBox.warning(self, title, message)
def show_info(self, title: str, message: str):
"""
Show information dialog.
Args:
title: Dialog title
message: Information message
"""
QMessageBox.information(self, title, message)
def require_page(self, show_warning: bool = True) -> bool:
"""
Check if a page is available and optionally show warning.
Args:
show_warning: Whether to show warning dialog if no page exists
Returns:
True if page exists, False otherwise
"""
current_page = self.get_current_page()
if current_page is None:
if show_warning:
self.show_warning("No Page", "Please create a page first.")
return False
return True
def require_selection(self, min_count: int = 1, show_warning: bool = True) -> bool:
"""
Check if required number of elements are selected.
Args:
min_count: Minimum number of selected elements required
show_warning: Whether to show warning dialog if requirement not met
Returns:
True if requirements met, False otherwise
"""
selected_count = len(self.gl_widget.selected_elements)
if selected_count < min_count:
if show_warning:
if min_count == 1:
self.show_info("No Selection", "Please select an element.")
else:
self.show_info("Selection Required", f"Please select at least {min_count} elements.")
return False
return True
def update_view(self):
"""Trigger GL widget update to refresh the view"""
if self.gl_widget:
self.gl_widget.update()
# Update scrollbars to reflect new content
if hasattr(self, "update_scrollbars"):
self.update_scrollbars()