Refactor camera control logic in `MainController` to use the State design pattern. - Create a new `core/camera/states.py` module with state classes (`NoCamerasState`, `DetectingState`, `ReadyToStreamState`, `StreamingState`). - `MainController` now acts as a context, delegating actions to the current state object. - This replaces complex conditional logic with a robust, scalable, and maintainable state machine, making it easier to manage camera behavior and add new states in the future.
167 lines
6.4 KiB
Python
167 lines
6.4 KiB
Python
from pathlib import Path
|
|
from PySide6.QtCore import Slot
|
|
from PySide6.QtGui import QPixmap
|
|
from PySide6.QtWidgets import QPushButton
|
|
|
|
from core.database import db_manager
|
|
from core.media import MediaRepository
|
|
from core.camera.camera_manager import CameraManager
|
|
from ui.widgets.color_list_widget import ColorListWidget
|
|
from ui.widgets.thumbnail_list_widget import ThumbnailListWidget
|
|
from ui.widgets.split_view_widget import SplitView, CameraPlaceholder, ViewWithOverlay
|
|
from core.camera.states import CameraState, NoCamerasState, ReadyToStreamState, StreamingState, DetectingState
|
|
|
|
|
|
class MainController:
|
|
def __init__(self, view):
|
|
self.view = view
|
|
self.db = db_manager
|
|
self.media_repo = MediaRepository()
|
|
self.camera_manager = CameraManager()
|
|
|
|
# --- UI Widgets ---
|
|
self.color_list: ColorListWidget = view.color_list_widget
|
|
self.thumbnail_list: ThumbnailListWidget = view.thumbnail_widget
|
|
self.split_view: SplitView = view.preview_widget
|
|
self.welcome_view: CameraPlaceholder = self.split_view.widget_start
|
|
self.live_view: ViewWithOverlay = self.split_view.widget_live
|
|
self.photo_button: QPushButton = view.photo_button
|
|
self.record_button: QPushButton = view.record_button
|
|
|
|
self._connect_signals()
|
|
|
|
self.db.connect()
|
|
self.media_repo.sync_media()
|
|
|
|
# Initialize state machine
|
|
self.state: CameraState = NoCamerasState()
|
|
self.state.enter_state(self)
|
|
|
|
def transition_to(self, new_state: CameraState):
|
|
"""Transitions the controller to a new state."""
|
|
print(f"Transitioning to state: {new_state.__class__.__name__}")
|
|
self.state = new_state
|
|
self.state.enter_state(self)
|
|
|
|
def _connect_signals(self):
|
|
"""Connects all signals to slots."""
|
|
# Database and media signals
|
|
self.color_list.colorSelected.connect(self.on_color_selected)
|
|
self.color_list.editColor.connect(self.on_edit_color)
|
|
self.thumbnail_list.selectedThumbnail.connect(self.on_thumbnail_selected)
|
|
|
|
# Camera signals
|
|
self.camera_manager.cameras_detected.connect(self.on_cameras_detected)
|
|
self.camera_manager.frame_ready.connect(self.on_frame_ready)
|
|
self.camera_manager.error_occurred.connect(self.on_camera_error)
|
|
self.camera_manager.camera_started.connect(self.on_camera_started)
|
|
self.camera_manager.camera_stopped.connect(self.on_camera_stopped)
|
|
|
|
# UI control signals
|
|
self.photo_button.clicked.connect(self.take_photo)
|
|
self.welcome_view.camera_start_btn.clicked.connect(self._on_start_button_clicked)
|
|
self.live_view.rotateCW.connect(self.camera_manager.rotate_right)
|
|
self.live_view.rotateCCW.connect(self.camera_manager.rotate_left)
|
|
|
|
def load_colors(self) -> None:
|
|
"""Loads colors from the database and populates the list."""
|
|
colors = self.db.get_all_colors()
|
|
self.color_list.set_colors(colors)
|
|
|
|
def shutdown(self):
|
|
"""Cleans up resources before application exit."""
|
|
self.camera_manager.shutdown()
|
|
self.db.disconnect()
|
|
|
|
# --- Slots for Database/Media ---
|
|
|
|
@Slot(str)
|
|
def on_color_selected(self, color_name: str):
|
|
color_id = self.db.get_color_id(color_name)
|
|
if color_id is not None:
|
|
media_items = self.db.get_media_for_color(color_id)
|
|
self.thumbnail_list.list_widget.clear()
|
|
for media in media_items:
|
|
if media['file_type'] == 'photo':
|
|
file_name = Path(media['media_path']).name
|
|
self.thumbnail_list.add_thumbnail(media['media_path'], file_name, media['id'])
|
|
|
|
@Slot(str)
|
|
def on_edit_color(self, color_name: str):
|
|
print(f"Edycja koloru: {color_name}") # Placeholder
|
|
|
|
@Slot(int)
|
|
def on_thumbnail_selected(self, media_id: int):
|
|
media = self.db.get_media(media_id)
|
|
if media:
|
|
self.split_view.set_reference_image(media['media_path'])
|
|
|
|
# --- Slots for CameraManager ---
|
|
|
|
@Slot(list)
|
|
def on_cameras_detected(self, cameras: list[dict]):
|
|
"""Handles the list of detected cameras and transitions state."""
|
|
print("Detected cameras:", cameras)
|
|
if cameras:
|
|
self.transition_to(ReadyToStreamState())
|
|
else:
|
|
self.transition_to(NoCamerasState())
|
|
|
|
@Slot(QPixmap)
|
|
def on_frame_ready(self, pixmap: QPixmap):
|
|
"""Displays a new frame from the camera."""
|
|
self.split_view.set_live_image(pixmap)
|
|
|
|
@Slot(str)
|
|
def on_camera_error(self, error_message: str):
|
|
"""Shows an error message from the camera and transitions to a safe state."""
|
|
print(f"Camera Error: {error_message}")
|
|
self.welcome_view.set_error_text(error_message)
|
|
self.transition_to(NoCamerasState())
|
|
|
|
@Slot()
|
|
def on_camera_started(self):
|
|
"""Transitions to StreamingState when the camera starts."""
|
|
self.transition_to(StreamingState())
|
|
|
|
@Slot()
|
|
def on_camera_stopped(self):
|
|
"""Transitions to a post-streaming state."""
|
|
self.split_view.toggle_live_view()
|
|
if self.camera_manager.get_detected_cameras():
|
|
self.transition_to(ReadyToStreamState())
|
|
else:
|
|
self.transition_to(NoCamerasState())
|
|
|
|
# --- UI Actions ---
|
|
|
|
def _on_start_button_clicked(self):
|
|
"""Delegates the button click to the current state."""
|
|
self.state.handle_start_button(self)
|
|
|
|
def camera_detect(self):
|
|
"""Initiates camera detection and transitions to DetectingState."""
|
|
self.transition_to(DetectingState())
|
|
self.camera_manager.detect_cameras()
|
|
|
|
def start_liveview(self):
|
|
"""Starts the camera feed."""
|
|
detected_cameras = self.camera_manager.get_detected_cameras()
|
|
if not detected_cameras:
|
|
self.on_camera_error("No cameras detected.")
|
|
return
|
|
|
|
# For now, just start the first detected camera.
|
|
camera_id = detected_cameras[0]['id']
|
|
self.camera_manager.start_camera(camera_id)
|
|
|
|
def stop_liveview(self):
|
|
"""Stops the camera feed."""
|
|
self.camera_manager.stop_camera()
|
|
|
|
def take_photo(self):
|
|
"""Takes a photo with the active camera."""
|
|
print("Taking photo...") # Placeholder
|
|
# This needs to be implemented in CameraManager and called here.
|
|
# e.g., self.camera_manager.take_photo()
|
|
self.split_view.toggle_live_view() # This seems like a UI toggle, maybe rename? |