Lemontropia-Suite/ui/main_window.py

1175 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Lemontropia Suite - Main Application Window
PyQt6 GUI for managing game automation projects and sessions.
"""
import sys
from datetime import datetime
from enum import Enum, auto
from typing import Optional, List, Callable
from dataclasses import dataclass
from PyQt6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QSplitter, QPushButton, QListWidget, QListWidgetItem,
QTextEdit, QLabel, QStatusBar, QMenuBar, QMenu,
QDialog, QLineEdit, QFormLayout, QDialogButtonBox,
QMessageBox, QGroupBox, QFrame, QApplication,
QTreeWidget, QTreeWidgetItem, QHeaderView
)
from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QSize
from PyQt6.QtGui import QAction, QFont, QColor, QPalette, QIcon
# ============================================================================
# Data Models
# ============================================================================
class SessionState(Enum):
"""Session state enumeration."""
IDLE = "Idle"
RUNNING = "Running"
PAUSED = "Paused"
ERROR = "Error"
STOPPING = "Stopping"
@dataclass
class Project:
"""Project data model."""
id: int
name: str
description: str = ""
created_at: Optional[datetime] = None
session_count: int = 0
last_session: Optional[datetime] = None
@dataclass
class LogEvent:
"""Log event data model."""
timestamp: datetime
level: str # DEBUG, INFO, WARNING, ERROR, CRITICAL
source: str
message: str
def __str__(self) -> str:
time_str = self.timestamp.strftime("%H:%M:%S.%f")[:-3]
return f"[{time_str}] [{self.level}] [{self.source}] {self.message}"
# ============================================================================
# HUD Overlay (Placeholder for integration)
# ============================================================================
class HUDOverlay:
"""
HUD Overlay controller placeholder.
This will be replaced with the actual HUD implementation.
"""
def __init__(self):
self.visible = False
def show(self):
"""Show the HUD overlay."""
self.visible = True
print("[HUD] Overlay shown")
def hide(self):
"""Hide the HUD overlay."""
self.visible = False
print("[HUD] Overlay hidden")
def is_visible(self) -> bool:
"""Check if HUD is visible."""
return self.visible
# ============================================================================
# Project Manager (Placeholder for integration)
# ============================================================================
class ProjectManager:
"""
Project manager placeholder.
This will be replaced with the actual ProjectManager implementation.
"""
def __init__(self):
self._projects: List[Project] = [
Project(1, "Default Project", "Default automation project", session_count=5),
Project(2, "Farming Bot", "Resource farming automation", session_count=12),
Project(3, "Quest Helper", "Quest automation helper", session_count=3),
]
self._next_id = 4
def get_all_projects(self) -> List[Project]:
"""Get all projects."""
return self._projects.copy()
def get_project(self, project_id: int) -> Optional[Project]:
"""Get project by ID."""
for proj in self._projects:
if proj.id == project_id:
return proj
return None
def create_project(self, name: str, description: str = "") -> Project:
"""Create a new project."""
project = Project(
id=self._next_id,
name=name,
description=description,
created_at=datetime.now(),
session_count=0
)
self._projects.append(project)
self._next_id += 1
return project
# ============================================================================
# Log Watcher (Placeholder for integration)
# ============================================================================
class LogWatcher:
"""
Log watcher placeholder.
This will be replaced with the actual LogWatcher implementation.
"""
def __init__(self):
self._callbacks: List[Callable[[LogEvent], None]] = []
self._running = False
def register_callback(self, callback: Callable[[LogEvent], None]):
"""Register a callback for log events."""
self._callbacks.append(callback)
def unregister_callback(self, callback: Callable[[LogEvent], None]):
"""Unregister a callback."""
if callback in self._callbacks:
self._callbacks.remove(callback)
def emit(self, event: LogEvent):
"""Emit a log event to all registered callbacks."""
for callback in self._callbacks:
callback(event)
# ============================================================================
# Custom Dialogs
# ============================================================================
class NewProjectDialog(QDialog):
"""Dialog for creating a new project."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("New Project")
self.setMinimumWidth(400)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
# Form layout for inputs
form_layout = QFormLayout()
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("Enter project name...")
form_layout.addRow("Name:", self.name_input)
self.desc_input = QLineEdit()
self.desc_input.setPlaceholderText("Enter description (optional)...")
form_layout.addRow("Description:", self.desc_input)
layout.addLayout(form_layout)
layout.addSpacing(10)
# Button box
button_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def get_project_data(self) -> tuple:
"""Get the entered project data."""
return self.name_input.text().strip(), self.desc_input.text().strip()
def accept(self):
"""Validate before accepting."""
name = self.name_input.text().strip()
if not name:
QMessageBox.warning(self, "Validation Error", "Project name is required.")
return
super().accept()
class ProjectStatsDialog(QDialog):
"""Dialog for displaying project statistics."""
def __init__(self, project: Project, parent=None):
super().__init__(parent)
self.project = project
self.setWindowTitle(f"Project Statistics - {project.name}")
self.setMinimumWidth(350)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
# Stats display
stats_group = QGroupBox("Project Information")
stats_layout = QFormLayout(stats_group)
stats_layout.addRow("ID:", QLabel(str(self.project.id)))
stats_layout.addRow("Name:", QLabel(self.project.name))
stats_layout.addRow("Description:", QLabel(self.project.description or "N/A"))
created = self.project.created_at.strftime("%Y-%m-%d %H:%M") if self.project.created_at else "N/A"
stats_layout.addRow("Created:", QLabel(created))
stats_layout.addRow("Total Sessions:", QLabel(str(self.project.session_count)))
last = self.project.last_session.strftime("%Y-%m-%d %H:%M") if self.project.last_session else "Never"
stats_layout.addRow("Last Session:", QLabel(last))
layout.addWidget(stats_group)
# Close button
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
class SettingsDialog(QDialog):
"""Dialog for application settings."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Settings")
self.setMinimumWidth(400)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
info_label = QLabel("Settings configuration would go here.")
info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(info_label)
layout.addStretch()
button_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
# ============================================================================
# Main Window
# ============================================================================
class MainWindow(QMainWindow):
"""
Main application window for Lemontropia Suite.
Provides project management, session control, and log viewing capabilities.
"""
# Signals
session_started = pyqtSignal(int) # project_id
session_stopped = pyqtSignal()
session_paused = pyqtSignal()
session_resumed = pyqtSignal()
def __init__(self):
super().__init__()
# Window configuration
self.setWindowTitle("Lemontropia Suite")
self.setMinimumSize(1200, 800)
self.resize(1400, 900)
# Initialize components
self.hud = HUDOverlay()
self.project_manager = ProjectManager()
self.log_watcher = LogWatcher()
# State
self.current_project: Optional[Project] = None
self.session_state = SessionState.IDLE
self.current_session_id: Optional[int] = None
# Setup UI
self.setup_ui()
self.apply_dark_theme()
self.create_menu_bar()
self.create_status_bar()
# Connect log watcher
self.log_watcher.register_callback(self.on_log_event)
# Load initial data
self.refresh_project_list()
# Welcome message
self.log_info("Application", "Lemontropia Suite initialized")
# ========================================================================
# UI Setup
# ========================================================================
def setup_ui(self):
"""Setup the main UI layout."""
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Main layout
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(10, 10, 10, 10)
main_layout.setSpacing(10)
# Main splitter (horizontal: left panels | log panel)
self.main_splitter = QSplitter(Qt.Orientation.Horizontal)
main_layout.addWidget(self.main_splitter)
# Left side container
left_container = QWidget()
left_layout = QVBoxLayout(left_container)
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(10)
# Left splitter (vertical: projects | session control)
left_splitter = QSplitter(Qt.Orientation.Vertical)
left_layout.addWidget(left_splitter)
# Project panel
self.project_panel = self.create_project_panel()
left_splitter.addWidget(self.project_panel)
# Session control panel
self.session_panel = self.create_session_panel()
left_splitter.addWidget(self.session_panel)
# Set splitter proportions
left_splitter.setSizes([400, 300])
# Add left container to main splitter
self.main_splitter.addWidget(left_container)
# Log output panel
self.log_panel = self.create_log_panel()
self.main_splitter.addWidget(self.log_panel)
# Set main splitter proportions (30% left, 70% log)
self.main_splitter.setSizes([400, 900])
def create_project_panel(self) -> QGroupBox:
"""Create the project management panel."""
panel = QGroupBox("Project Management")
layout = QVBoxLayout(panel)
layout.setSpacing(8)
# Project list
self.project_list = QTreeWidget()
self.project_list.setHeaderLabels(["ID", "Name", "Sessions"])
self.project_list.setAlternatingRowColors(True)
self.project_list.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection)
self.project_list.setRootIsDecorated(False)
self.project_list.itemSelectionChanged.connect(self.on_project_selected)
self.project_list.itemDoubleClicked.connect(self.on_project_double_clicked)
# Adjust column widths
header = self.project_list.header()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed)
header.resizeSection(0, 50)
header.resizeSection(2, 70)
layout.addWidget(self.project_list)
# Button row
button_layout = QHBoxLayout()
self.new_project_btn = QPushButton(" New Project")
self.new_project_btn.setToolTip("Create a new project")
self.new_project_btn.clicked.connect(self.on_new_project)
button_layout.addWidget(self.new_project_btn)
self.view_stats_btn = QPushButton("📊 View Stats")
self.view_stats_btn.setToolTip("View selected project statistics")
self.view_stats_btn.clicked.connect(self.on_view_stats)
self.view_stats_btn.setEnabled(False)
button_layout.addWidget(self.view_stats_btn)
self.refresh_btn = QPushButton("🔄 Refresh")
self.refresh_btn.setToolTip("Refresh project list")
self.refresh_btn.clicked.connect(self.refresh_project_list)
button_layout.addWidget(self.refresh_btn)
layout.addLayout(button_layout)
return panel
def create_session_panel(self) -> QGroupBox:
"""Create the session control panel."""
panel = QGroupBox("Session Control")
layout = QVBoxLayout(panel)
layout.setSpacing(10)
# Current project display
project_info_layout = QFormLayout()
self.current_project_label = QLabel("No project selected")
self.current_project_label.setStyleSheet("font-weight: bold; color: #888;")
project_info_layout.addRow("Selected Project:", self.current_project_label)
layout.addLayout(project_info_layout)
# Separator line
separator = QFrame()
separator.setFrameShape(QFrame.Shape.HLine)
separator.setStyleSheet("background-color: #444;")
layout.addWidget(separator)
# Session status
status_layout = QHBoxLayout()
status_layout.addWidget(QLabel("Status:"))
self.session_status_label = QLabel("Idle")
self.session_status_label.setStyleSheet("""
QLabel {
font-weight: bold;
color: #888;
padding: 5px 15px;
background-color: #2a2a2a;
border-radius: 4px;
border: 1px solid #444;
}
""")
status_layout.addWidget(self.session_status_label)
status_layout.addStretch()
layout.addLayout(status_layout)
# Control buttons
button_layout = QHBoxLayout()
self.start_session_btn = QPushButton("▶️ Start Session")
self.start_session_btn.setToolTip("Start a new session with selected project")
self.start_session_btn.clicked.connect(self.on_start_session)
self.start_session_btn.setEnabled(False)
button_layout.addWidget(self.start_session_btn)
self.stop_session_btn = QPushButton("⏹️ Stop")
self.stop_session_btn.setToolTip("Stop current session")
self.stop_session_btn.clicked.connect(self.on_stop_session)
self.stop_session_btn.setEnabled(False)
button_layout.addWidget(self.stop_session_btn)
self.pause_session_btn = QPushButton("⏸️ Pause")
self.pause_session_btn.setToolTip("Pause/Resume current session")
self.pause_session_btn.clicked.connect(self.on_pause_session)
self.pause_session_btn.setEnabled(False)
button_layout.addWidget(self.pause_session_btn)
layout.addLayout(button_layout)
# Session info
self.session_info_label = QLabel("Ready to start")
self.session_info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.session_info_label.setStyleSheet("color: #666; padding: 10px;")
layout.addWidget(self.session_info_label)
layout.addStretch()
return panel
def create_log_panel(self) -> QGroupBox:
"""Create the log output panel."""
panel = QGroupBox("Event Log")
layout = QVBoxLayout(panel)
layout.setSpacing(8)
# Log text edit
self.log_output = QTextEdit()
self.log_output.setReadOnly(True)
self.log_output.setFont(QFont("Consolas", 10))
self.log_output.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
layout.addWidget(self.log_output)
# Log controls
controls_layout = QHBoxLayout()
self.clear_log_btn = QPushButton("🗑️ Clear")
self.clear_log_btn.setToolTip("Clear log output")
self.clear_log_btn.clicked.connect(self.log_output.clear)
controls_layout.addWidget(self.clear_log_btn)
controls_layout.addStretch()
self.auto_scroll_check = QLabel("✓ Auto-scroll")
self.auto_scroll_check.setStyleSheet("color: #888;")
controls_layout.addWidget(self.auto_scroll_check)
layout.addLayout(controls_layout)
return panel
def create_menu_bar(self):
"""Create the application menu bar."""
menubar = self.menuBar()
# File menu
file_menu = menubar.addMenu("&File")
new_project_action = QAction("&New Project", self)
new_project_action.setShortcut("Ctrl+N")
new_project_action.triggered.connect(self.on_new_project)
file_menu.addAction(new_project_action)
open_project_action = QAction("&Open Project", self)
open_project_action.setShortcut("Ctrl+O")
open_project_action.triggered.connect(self.on_open_project)
file_menu.addAction(open_project_action)
file_menu.addSeparator()
exit_action = QAction("E&xit", self)
exit_action.setShortcut("Alt+F4")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# Session menu
session_menu = menubar.addMenu("&Session")
start_action = QAction("&Start", self)
start_action.setShortcut("F5")
start_action.triggered.connect(self.on_start_session)
session_menu.addAction(start_action)
self.start_action = start_action
stop_action = QAction("St&op", self)
stop_action.setShortcut("Shift+F5")
stop_action.triggered.connect(self.on_stop_session)
session_menu.addAction(stop_action)
self.stop_action = stop_action
pause_action = QAction("&Pause", self)
pause_action.setShortcut("F6")
pause_action.triggered.connect(self.on_pause_session)
session_menu.addAction(pause_action)
self.pause_action = pause_action
# View menu
view_menu = menubar.addMenu("&View")
show_hud_action = QAction("Show &HUD", self)
show_hud_action.setShortcut("F9")
show_hud_action.triggered.connect(self.on_show_hud)
view_menu.addAction(show_hud_action)
hide_hud_action = QAction("&Hide HUD", self)
hide_hud_action.setShortcut("F10")
hide_hud_action.triggered.connect(self.on_hide_hud)
view_menu.addAction(hide_hud_action)
view_menu.addSeparator()
settings_action = QAction("&Settings", self)
settings_action.setShortcut("Ctrl+,")
settings_action.triggered.connect(self.on_settings)
view_menu.addAction(settings_action)
# Help menu
help_menu = menubar.addMenu("&Help")
about_action = QAction("&About", self)
about_action.triggered.connect(self.on_about)
help_menu.addAction(about_action)
def create_status_bar(self):
"""Create the status bar."""
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
# Permanent widgets
self.status_state_label = QLabel("● Idle")
self.status_state_label.setStyleSheet("color: #888; padding: 0 10px;")
self.status_bar.addPermanentWidget(self.status_state_label)
self.status_project_label = QLabel("No project")
self.status_project_label.setStyleSheet("color: #888; padding: 0 10px;")
self.status_bar.addPermanentWidget(self.status_project_label)
# Message area
self.status_bar.showMessage("Ready")
# ========================================================================
# Theme
# ========================================================================
def apply_dark_theme(self):
"""Apply dark theme styling."""
dark_stylesheet = """
QMainWindow {
background-color: #1e1e1e;
}
QWidget {
background-color: #1e1e1e;
color: #e0e0e0;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 10pt;
}
QGroupBox {
font-weight: bold;
border: 1px solid #444;
border-radius: 6px;
margin-top: 10px;
padding-top: 10px;
padding: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
color: #888;
}
QPushButton {
background-color: #2d2d2d;
border: 1px solid #444;
border-radius: 4px;
padding: 8px 16px;
color: #e0e0e0;
}
QPushButton:hover {
background-color: #3d3d3d;
border-color: #555;
}
QPushButton:pressed {
background-color: #4d4d4d;
}
QPushButton:disabled {
background-color: #252525;
color: #666;
border-color: #333;
}
QPushButton#start_button {
background-color: #1b5e20;
border-color: #2e7d32;
}
QPushButton#start_button:hover {
background-color: #2e7d32;
}
QPushButton#stop_button {
background-color: #b71c1c;
border-color: #c62828;
}
QPushButton#stop_button:hover {
background-color: #c62828;
}
QTreeWidget {
background-color: #252525;
border: 1px solid #444;
border-radius: 4px;
outline: none;
}
QTreeWidget::item {
padding: 6px;
border-bottom: 1px solid #333;
}
QTreeWidget::item:selected {
background-color: #0d47a1;
color: white;
}
QTreeWidget::item:alternate {
background-color: #2a2a2a;
}
QHeaderView::section {
background-color: #2d2d2d;
padding: 6px;
border: none;
border-right: 1px solid #444;
font-weight: bold;
}
QTextEdit {
background-color: #151515;
border: 1px solid #444;
border-radius: 4px;
padding: 8px;
color: #d0d0d0;
}
QLineEdit {
background-color: #252525;
border: 1px solid #444;
border-radius: 4px;
padding: 6px;
color: #e0e0e0;
}
QLineEdit:focus {
border-color: #0d47a1;
}
QMenuBar {
background-color: #1e1e1e;
border-bottom: 1px solid #444;
}
QMenuBar::item {
background-color: transparent;
padding: 6px 12px;
}
QMenuBar::item:selected {
background-color: #2d2d2d;
}
QMenu {
background-color: #2d2d2d;
border: 1px solid #444;
padding: 4px;
}
QMenu::item {
padding: 6px 24px;
border-radius: 2px;
}
QMenu::item:selected {
background-color: #0d47a1;
}
QMenu::separator {
height: 1px;
background-color: #444;
margin: 4px 8px;
}
QStatusBar {
background-color: #1e1e1e;
border-top: 1px solid #444;
}
QSplitter::handle {
background-color: #444;
}
QSplitter::handle:horizontal {
width: 2px;
}
QSplitter::handle:vertical {
height: 2px;
}
QDialog {
background-color: #1e1e1e;
}
QLabel {
color: #e0e0e0;
}
QFormLayout QLabel {
color: #888;
}
"""
self.setStyleSheet(dark_stylesheet)
# ========================================================================
# Project Management
# ========================================================================
def refresh_project_list(self):
"""Refresh the project list display."""
self.project_list.clear()
projects = self.project_manager.get_all_projects()
for project in projects:
item = QTreeWidgetItem([
str(project.id),
project.name,
str(project.session_count)
])
item.setData(0, Qt.ItemDataRole.UserRole, project.id)
self.project_list.addTopLevelItem(item)
self.log_debug("ProjectManager", f"Loaded {len(projects)} projects")
def on_project_selected(self):
"""Handle project selection change."""
selected = self.project_list.selectedItems()
if selected:
project_id = selected[0].data(0, Qt.ItemDataRole.UserRole)
self.current_project = self.project_manager.get_project(project_id)
if self.current_project:
self.current_project_label.setText(self.current_project.name)
self.current_project_label.setStyleSheet("font-weight: bold; color: #4caf50;")
self.view_stats_btn.setEnabled(True)
self.start_session_btn.setEnabled(self.session_state == SessionState.IDLE)
self.status_project_label.setText(f"Project: {self.current_project.name}")
self.log_debug("ProjectManager", f"Selected project: {self.current_project.name}")
else:
self.current_project = None
self.current_project_label.setText("No project selected")
self.current_project_label.setStyleSheet("font-weight: bold; color: #888;")
self.view_stats_btn.setEnabled(False)
self.start_session_btn.setEnabled(False)
self.status_project_label.setText("No project")
def on_project_double_clicked(self, item: QTreeWidgetItem, column: int):
"""Handle double-click on project."""
project_id = item.data(0, Qt.ItemDataRole.UserRole)
project = self.project_manager.get_project(project_id)
if project:
self.show_project_stats(project)
def on_new_project(self):
"""Handle new project creation."""
dialog = NewProjectDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted:
name, description = dialog.get_project_data()
project = self.project_manager.create_project(name, description)
self.refresh_project_list()
self.log_info("ProjectManager", f"Created project: {project.name}")
self.status_bar.showMessage(f"Project '{name}' created", 3000)
def on_open_project(self):
"""Handle open project action."""
# For now, just focus the project list
self.project_list.setFocus()
self.status_bar.showMessage("Select a project from the list", 3000)
def on_view_stats(self):
"""Handle view stats button."""
if self.current_project:
self.show_project_stats(self.current_project)
def show_project_stats(self, project: Project):
"""Show project statistics dialog."""
dialog = ProjectStatsDialog(project, self)
dialog.exec()
# ========================================================================
# Session Control
# ========================================================================
def start_session(self, project_id: int):
"""
Start a new session with the given project.
Args:
project_id: The ID of the project to start session for
"""
project = self.project_manager.get_project(project_id)
if not project:
self.log_error("Session", f"Project {project_id} not found")
return
if self.session_state != SessionState.IDLE:
self.log_warning("Session", "Cannot start: session already active")
return
# Update state
self.set_session_state(SessionState.RUNNING)
self.current_session_id = project_id
# Emit signal
self.session_started.emit(project_id)
# Log
self.log_info("Session", f"Started session for project: {project.name}")
self.session_info_label.setText(f"Session active: {project.name}")
# Show HUD
self.hud.show()
def on_start_session(self):
"""Handle start session button."""
if self.current_project and self.session_state == SessionState.IDLE:
self.start_session(self.current_project.id)
def on_stop_session(self):
"""Handle stop session button."""
if self.session_state in (SessionState.RUNNING, SessionState.PAUSED):
self.set_session_state(SessionState.IDLE)
self.current_session_id = None
self.session_stopped.emit()
self.log_info("Session", "Session stopped")
self.session_info_label.setText("Session stopped")
# Hide HUD
self.hud.hide()
def on_pause_session(self):
"""Handle pause/resume session button."""
if self.session_state == SessionState.RUNNING:
self.set_session_state(SessionState.PAUSED)
self.session_paused.emit()
self.log_info("Session", "Session paused")
self.session_info_label.setText("Session paused")
self.pause_session_btn.setText("▶️ Resume")
elif self.session_state == SessionState.PAUSED:
self.set_session_state(SessionState.RUNNING)
self.session_resumed.emit()
self.log_info("Session", "Session resumed")
self.session_info_label.setText("Session resumed")
self.pause_session_btn.setText("⏸️ Pause")
def set_session_state(self, state: SessionState):
"""
Update the session state and UI.
Args:
state: New session state
"""
self.session_state = state
# Update status label
colors = {
SessionState.IDLE: "#888",
SessionState.RUNNING: "#4caf50",
SessionState.PAUSED: "#ff9800",
SessionState.ERROR: "#f44336",
SessionState.STOPPING: "#ff5722"
}
self.session_status_label.setText(state.value)
self.session_status_label.setStyleSheet(f"""
QLabel {{
font-weight: bold;
color: {colors.get(state, '#888')};
padding: 5px 15px;
background-color: #2a2a2a;
border-radius: 4px;
border: 1px solid #444;
}}
""")
# Update status bar
self.status_state_label.setText(f"{state.value}")
self.status_state_label.setStyleSheet(f"color: {colors.get(state, '#888')}; padding: 0 10px;")
# Update buttons
self.start_session_btn.setEnabled(
state == SessionState.IDLE and self.current_project is not None
)
self.stop_session_btn.setEnabled(state in (SessionState.RUNNING, SessionState.PAUSED))
self.pause_session_btn.setEnabled(state in (SessionState.RUNNING, SessionState.PAUSED))
# Update menu actions
self.start_action.setEnabled(self.start_session_btn.isEnabled())
self.stop_action.setEnabled(self.stop_session_btn.isEnabled())
self.pause_action.setEnabled(self.pause_session_btn.isEnabled())
if state == SessionState.IDLE:
self.pause_session_btn.setText("⏸️ Pause")
# ========================================================================
# Log Handling
# ========================================================================
def on_log_event(self, event: LogEvent):
"""
Handle incoming log events.
Args:
event: The log event to display
"""
# Color mapping
colors = {
"DEBUG": "#888",
"INFO": "#4fc3f7",
"WARNING": "#ff9800",
"ERROR": "#f44336",
"CRITICAL": "#e91e63"
}
color = colors.get(event.level, "#e0e0e0")
html = f'<span style="color: {color}">{self.escape_html(str(event))}</span>'
self.log_output.append(html)
# Auto-scroll to bottom
scrollbar = self.log_output.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def log_debug(self, source: str, message: str):
"""Log a debug message."""
self.log_watcher.emit(LogEvent(
timestamp=datetime.now(),
level="DEBUG",
source=source,
message=message
))
def log_info(self, source: str, message: str):
"""Log an info message."""
self.log_watcher.emit(LogEvent(
timestamp=datetime.now(),
level="INFO",
source=source,
message=message
))
def log_warning(self, source: str, message: str):
"""Log a warning message."""
self.log_watcher.emit(LogEvent(
timestamp=datetime.now(),
level="WARNING",
source=source,
message=message
))
def log_error(self, source: str, message: str):
"""Log an error message."""
self.log_watcher.emit(LogEvent(
timestamp=datetime.now(),
level="ERROR",
source=source,
message=message
))
def escape_html(self, text: str) -> str:
"""Escape HTML special characters."""
return (text
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;"))
# ========================================================================
# Menu Actions
# ========================================================================
def on_show_hud(self):
"""Show the HUD overlay."""
self.hud.show()
self.log_info("HUD", "HUD overlay shown")
def on_hide_hud(self):
"""Hide the HUD overlay."""
self.hud.hide()
self.log_info("HUD", "HUD overlay hidden")
def on_settings(self):
"""Open settings dialog."""
dialog = SettingsDialog(self)
dialog.exec()
def on_about(self):
"""Show about dialog."""
QMessageBox.about(
self,
"About Lemontropia Suite",
"""<h2>Lemontropia Suite</h2>
<p>Version 1.0.0</p>
<p>A PyQt6-based GUI for game automation and session management.</p>
<p>Features:</p>
<ul>
<li>Project management</li>
<li>Session control</li>
<li>Real-time logging</li>
<li>HUD overlay</li>
</ul>
"""
)
# ========================================================================
# Event Overrides
# ========================================================================
def closeEvent(self, event):
"""Handle window close event."""
if self.session_state == SessionState.RUNNING:
reply = QMessageBox.question(
self,
"Confirm Exit",
"A session is currently running. Are you sure you want to exit?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.on_stop_session()
event.accept()
else:
event.ignore()
else:
event.accept()
# ============================================================================
# Test Entry Point
# ============================================================================
def main():
"""Main entry point for testing."""
app = QApplication(sys.argv)
# Set application-wide font
font = QFont("Segoe UI", 10)
app.setFont(font)
# Create and show main window
window = MainWindow()
window.show()
# Simulate some log activity for demonstration
def simulate_logs():
import random
sources = ["Engine", "Input", "Vision", "Network", "Session"]
levels = ["DEBUG", "INFO", "INFO", "INFO", "WARNING"]
messages = [
"Initializing component...",
"Connection established",
"Processing frame #1234",
"Waiting for input",
"Buffer cleared",
"Sync complete"
]
if window.session_state == SessionState.RUNNING:
if random.random() < 0.3: # 30% chance each tick
event = LogEvent(
timestamp=datetime.now(),
level=random.choice(levels),
source=random.choice(sources),
message=random.choice(messages)
)
window.log_watcher.emit(event)
# Timer to simulate log activity
timer = QTimer()
timer.timeout.connect(simulate_logs)
timer.start(1000) # Every second
sys.exit(app.exec())
if __name__ == '__main__':
main()