212 lines
6.6 KiB
Python
212 lines
6.6 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()
|