keypresser/controller/controller.py

137 lines
7.5 KiB
Python
Raw Normal View History

2025-06-19 18:38:55 +00:00
import tkinter as tk
from tkinter import messagebox
import logging
from model.model import KeyPressModel
from view.view import KeyPressView
# NEW: Import UIController
from controller.ui_controller import UIController
logger = logging.getLogger(__name__)
class KeyPressController:
def __init__(self, model, view):
self.model = model
self.view = view # This will be set by main.py after View is instantiated.
self.ui_controller = None # Will be instantiated and linked after view is set.
logger.info("Controller: Initialized (view and UIController might be None for now).")
# Register this controller's callback with the Model for AHK stopping events
self.model.register_ahk_stopped_callback(self._handle_ahk_external_stop)
def initial_setup_after_view_is_ready(self):
"""
Performs controller setup that relies on the view being fully initialized.
Called from main.py after controller.view is set.
"""
if self.view is None:
logger.error("Controller: initial_setup_after_view_is_ready called but view is None!")
return
# Instantiate UIController and pass itself (main controller) and the view
self.ui_controller = UIController(self, self.view)
self.ui_controller.initial_ui_setup_after_view_is_ready() # Trigger UIController's setup
logger.info("Controller: Performing post-view-initialization setup.")
# Initial set for key based on UI default
self.model.set_key_to_press(self.view.get_key_input())
# Initial window refresh and selection handled by ui_controller.refresh_windows_ui()
# --- Methods called by UIController to update Model/Controller state ---
def set_key_input_from_ui(self, key_str):
"""Receives key input from UI, updates model."""
self.model.set_key_to_press(key_str)
def set_window_selection_from_ui(self, selected_title):
"""Receives window selection from UI, updates model based on AHK logic."""
if selected_title == "--- Global Input (No Specific Window) ---":
# As per user's explicit request: "it is always a specific window", no "GLOBAL" AHK branch.
# This selection means AHK script will likely not find a window, thus releasing the key.
self.model.set_target_window("INVALID_GLOBAL_TARGET_FOR_AHK") # Use a distinct string for internal model logic
self.ui_controller.show_ui_error_message("Warning: 'Global Input' selected. The AutoHotkey script is designed for specific windows and will likely NOT press the key automatically with this selection.")
logger.warning("Controller: 'Global Input' selected. AHK logic is non-global; key will not be pressed.")
elif selected_title == "--- Select a Window ---":
self.model.set_target_window("")
logger.info("Controller: No specific window selected. Target is empty.")
else:
self.model.set_target_window(selected_title)
logger.info(f"Controller: Target window set to: '{selected_title}'")
# --- Methods for UIController to query Model ---
def get_available_window_titles(self):
"""Provides available window titles (from model) to UIController."""
return self.model.get_window_titles()
# --- Core Application Logic / Event Handlers (Called by UIController/View directly, or internally) ---
def _check_start_button_state(self):
"""
Internal method to check application state and update UI button state.
This is called by UIController after relevant inputs change.
"""
if self.ui_controller: # Ensure UIController is instantiated before calling its methods
self.ui_controller._check_start_button_state_ui() # Delegate to UIController for UI update
def start_key_press(self):
"""Handles the 'Start Holding Key' button click event (from View)."""
logger.info("Controller: Start key press requested.")
# Perform validation using UIController's methods to get current UI state
key_value = self.view.get_key_input() # Get current key from view
selected_window = self.view.get_selected_window_title() # Get current window from view
if not key_value:
self.ui_controller.show_ui_error_message("The 'Key to Press' field cannot be empty. What exactly do you expect me to press?")
self.view.set_key_validation_visibility(True)
logger.error("Controller: Aborting: Key to press field is empty.")
return
if selected_window == "--- Select a Window ---":
self.ui_controller.show_ui_error_message("Please select a specific target window or 'Global Input'.")
self.view.set_window_validation_visibility(True)
logger.error("Controller: Aborting: No target window selected.")
return
if selected_window == "--- Global Input (No Specific Window) ---":
# Show the warning again if the user is attempting to start with 'Global Input'
self.ui_controller.show_ui_error_message("Warning: 'Global Input' selected. The current AutoHotkey script is designed to activate a specific window, not provide global key presses. This option will prevent the key from being pressed automatically.")
logger.warning("Controller: Attempting to start with 'Global Input'. AHK logic is non-global. Key will not be pressed.")
# Tell Model to start AHK script. Model handles pynput listener internally.
if self.model.start_autohotkey_script():
self.ui_controller.set_ui_running_state() # Update UI via UIController
logger.info("Controller: Key press operation successfully initiated.")
else:
self.ui_controller.show_ui_error_message("Failed to start AutoHotkey script. Check application logs for details (e.g., AutoHotkey.exe not found).")
self.ui_controller.set_ui_idle_state() # Reset UI via UIController
logger.error("Controller: Failed to start AHK script. Aborting.")
def stop_key_press(self):
"""Handles the 'Stop Key Press' button click event (from View)."""
logger.info("Controller: Stop key press requested.")
self.model.stop_autohotkey_script() # Tell Model to stop AHK
self.ui_controller.set_ui_idle_state() # Set UI to idle state via UIController
logger.info("Controller: Key press operation stopped.")
def _handle_ahk_external_stop(self):
"""
Callback method invoked by the Model when the AHK script stops due to
an external event (e.g., AHK error, F6 press, window not found).
Resets the UI state via UIController.
"""
logger.info("Controller: AHK script stopped externally. Requesting UI reset.")
# Ensure this is scheduled on the main Tkinter thread if called from a background thread
if self.view and self.view.master:
self.view.master.after_idle(self.ui_controller.set_ui_idle_state)
else:
logger.warning("Controller: Cannot schedule UI reset, view or master is unavailable.")
def on_app_close(self):
"""Handles application window close event, ensures AHK cleanup."""
logger.info("Controller: Application close requested. Initiating cleanup.")
self.model.stop_autohotkey_script()
if self.view and self.view.master: # Check if master still exists before destroying
self.view.master.destroy()
logger.info("Controller: Application closed.")