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.")