Files
MayoStainHelper/controllers/main_controller.py
bartool 02edb186bb feat: Implement photo capture functionality
Implement the logic for capturing and saving photos from the live preview stream.

- Add `save_photo` method to `MediaRepository` to handle file saving and database updates.
- `MainController` now tracks the selected color and the latest camera frame.
- The "Take Photo" button is enabled only when a color is selected.
- Pressing the button saves the current preview frame to the correct media folder and refreshes the thumbnail list.
- Fixes indentation issues in `main_controller.py` caused by previous faulty replacements.
2025-10-14 10:04:01 +02:00

187 lines
7.0 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()
# --- State ---
self.selected_color_name: str | None = None
self._latest_pixmap: QPixmap | None = None
# --- 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()
# Disable button by default
self.photo_button.setEnabled(False)
# 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):
self.selected_color_name = color_name
self.photo_button.setEnabled(True)
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 and stores it."""
self._latest_pixmap = pixmap
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
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."""
if self.selected_color_name is None:
print("Cannot take photo: No color selected.")
# Optionally: show a message to the user in the UI
return
if self._latest_pixmap is None or self._latest_pixmap.isNull():
print("Cannot take photo: No frame available.")
return
print(f"Taking photo for color: {self.selected_color_name}")
self.media_repo.save_photo(self._latest_pixmap, self.selected_color_name)
# Refresh thumbnail list
self.on_color_selected(self.selected_color_name)