from PyQt5 import QtWidgets, QtCore, QtGui import logging import subprocess import os from logging_config import get_logger, get_log_directory_path from launch_agent import get_launch_agent_manager class OptionsDialog(QtWidgets.QDialog): """Options dialog for configuring HereIAm settings""" def __init__(self, current_wait_time=240, current_start_enabled=True, current_log_level="INFO", current_move_px=10, current_launch_at_startup=False, parent=None): super().__init__(parent) self.logger = get_logger(__name__) # Store current values self.wait_time = current_wait_time self.start_enabled = current_start_enabled self.log_level = current_log_level self.move_px = current_move_px self.launch_at_startup = current_launch_at_startup # Initialize launch agent manager self.launch_agent_manager = get_launch_agent_manager() self.setupUI() self.load_current_values() def setupUI(self): """Setup the dialog UI""" self.setWindowTitle("HereIAm Options") self.setModal(True) self.setFixedSize(400, 260) # Increased height to accommodate new button # Ensure dialog appears on top and is properly managed self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowStaysOnTopHint) # Remove WA_DeleteOnClose to prevent automatic deletion conflicts # Main layout layout = QtWidgets.QVBoxLayout(self) layout.setSpacing(10) # Reduce spacing between elements # Create form layout form_layout = QtWidgets.QFormLayout() form_layout.setVerticalSpacing(8) # Reduce vertical spacing between form rows form_layout.setHorizontalSpacing(10) # Set horizontal spacing # Wait Time setting self.wait_time_spinbox = QtWidgets.QSpinBox() self.wait_time_spinbox.setRange(10, 3600) # 10 seconds to 1 hour self.wait_time_spinbox.setSuffix(" seconds") self.wait_time_spinbox.setToolTip("Time to wait before moving mouse when idle (Range: 10-3600 seconds)") self.wait_time_spinbox.setKeyboardTracking(True) # Enable keyboard input self.wait_time_spinbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.UpDownArrows) # Ensure arrows are visible self.wait_time_spinbox.lineEdit().setReadOnly(False) # Allow typing in the field form_layout.addRow("Wait Time (10-3600s):", self.wait_time_spinbox) # Mouse Movement Distance setting self.move_px_spinbox = QtWidgets.QSpinBox() self.move_px_spinbox.setRange(1, 100) # 1 to 100 pixels self.move_px_spinbox.setSuffix(" pixels") self.move_px_spinbox.setToolTip("Distance to move mouse in pixels (Range: 1-100 pixels)") self.move_px_spinbox.setKeyboardTracking(True) # Enable keyboard input self.move_px_spinbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.UpDownArrows) # Ensure arrows are visible self.move_px_spinbox.lineEdit().setReadOnly(False) # Allow typing in the field form_layout.addRow("Movement Distance (1-100px):", self.move_px_spinbox) # Start Enabled setting self.start_enabled_checkbox = QtWidgets.QCheckBox() self.start_enabled_checkbox.setToolTip("Start HereIAm enabled when application launches") form_layout.addRow("Start Enabled:", self.start_enabled_checkbox) # Launch at Startup setting self.launch_at_startup_checkbox = QtWidgets.QCheckBox() self.launch_at_startup_checkbox.setToolTip("Automatically launch HereIAm when you log in to macOS") form_layout.addRow("Launch at Startup:", self.launch_at_startup_checkbox) # Log Level setting self.log_level_combo = QtWidgets.QComboBox() self.log_level_combo.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) self.log_level_combo.setToolTip("Set the logging level for the application") form_layout.addRow("Log Level:", self.log_level_combo) layout.addLayout(form_layout) # Open Logs button logs_button = QtWidgets.QPushButton("Open Logs Directory") logs_button.setToolTip("Open the directory containing HereIAm log files") logs_button.clicked.connect(self.open_logs_directory) layout.addWidget(logs_button) # Button box button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel ) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box) # Center the dialog on screen self.center_on_screen() def center_on_screen(self): """Center the dialog on the screen""" screen = QtWidgets.QApplication.primaryScreen() if screen: screen_geometry = screen.geometry() dialog_geometry = self.geometry() x = (screen_geometry.width() - dialog_geometry.width()) // 2 y = (screen_geometry.height() - dialog_geometry.height()) // 2 self.move(x, y) def showEvent(self, event): """Called when dialog is shown""" super().showEvent(event) self.raise_() self.activateWindow() def closeEvent(self, event): """Called when dialog is closed""" self.logger.debug("Options dialog closing") super().closeEvent(event) def load_current_values(self): """Load current values into the form""" self.wait_time_spinbox.setValue(self.wait_time) self.move_px_spinbox.setValue(self.move_px) self.start_enabled_checkbox.setChecked(self.start_enabled) # Check current startup status from the system current_startup_enabled = self.launch_agent_manager.is_startup_enabled() self.launch_at_startup_checkbox.setChecked(current_startup_enabled) # Set log level index = self.log_level_combo.findText(self.log_level) if index >= 0: self.log_level_combo.setCurrentIndex(index) def get_values(self): """Get the values from the form""" try: return { 'wait_time': self.wait_time_spinbox.value(), 'move_px': self.move_px_spinbox.value(), 'start_enabled': self.start_enabled_checkbox.isChecked(), 'launch_at_startup': self.launch_at_startup_checkbox.isChecked(), 'log_level': self.log_level_combo.currentText() } except AttributeError as e: # If widgets are already destroyed, return None self.logger.error(f"Error getting dialog values: {e}") return None def open_logs_directory(self): """Open the logs directory in Finder (macOS)""" try: log_dir = get_log_directory_path() self.logger.debug(f"Opening logs directory: {log_dir}") # Ensure the directory exists if not os.path.exists(log_dir): self.logger.warning(f"Logs directory does not exist: {log_dir}") QtWidgets.QMessageBox.warning( self, "Directory Not Found", f"The logs directory does not exist yet:\n{log_dir}\n\nLogs will be created when the application runs." ) return # Open directory in Finder on macOS subprocess.run(['open', log_dir], check=True) self.logger.info(f"Successfully opened logs directory: {log_dir}") except subprocess.CalledProcessError as e: self.logger.error(f"Failed to open logs directory: {e}") QtWidgets.QMessageBox.critical( self, "Error Opening Directory", f"Failed to open logs directory:\n{str(e)}" ) except Exception as e: self.logger.error(f"Unexpected error opening logs directory: {e}") QtWidgets.QMessageBox.critical( self, "Error", f"An unexpected error occurred:\n{str(e)}" ) @staticmethod def get_options(current_wait_time=240, current_start_enabled=True, current_log_level="INFO", current_move_px=10, current_launch_at_startup=False, parent=None): """Static method to show the dialog and return values""" dialog = OptionsDialog(current_wait_time, current_start_enabled, current_log_level, current_move_px, current_launch_at_startup, parent) if dialog.exec_() == QtWidgets.QDialog.Accepted: return dialog.get_values(), True return None, False