SXXXXXXX_PyMsc/pymsc/gui/main_docking_window.py

340 lines
12 KiB
Python

# -*- coding: utf-8 -*-
"""
ARTOS Main Docking Window
Modular GUI with dockable panels for different system modules.
"""
import tkinter as tk
from tkinter import ttk, messagebox
import logging
from pymsc.core.bus_1553_module import Bus1553Module
from pymsc.gui.docking.workspace_manager import WorkspaceManager
from pymsc.gui.docking.mcs_dock import MCSDock
from pymsc.gui.docking.placeholder_dock import PlaceholderDock
from pymsc.gui.docking.logger_dock import LoggerDock, TextWidgetHandler
from pymsc.gui.components.command_widgets import (
CommandFrameCheckBox, CommandFrameComboBox,
CommandFrameSpinBox, CommandFrameLabels,
CommandFrameControls
)
class MainDockingWindow:
"""
ARTOS main window with modular docking system.
Architecture:
- Left panel: MCS (Mission Control System)
- Right top: Navigation, IRST, Algorithms (placeholders)
- Right bottom: Logger, Status (placeholders)
"""
def __init__(self, bus_module: Bus1553Module):
"""
Args:
bus_module: Bus1553Module instance for message access
"""
self.bus_module = bus_module
# Create main window
self.root = tk.Tk()
self.root.title("ARTOS - Advanced Radar Test & Orchestration System")
self.root.geometry("1600x900")
# Configure light theme
style = ttk.Style()
style.theme_use('clam')
# Light theme configuration
bg_light = '#f0f0f0'
bg_lighter = '#ffffff'
fg_dark = '#000000'
accent = '#0078d7'
self.root.configure(bg=bg_light)
style.configure('TFrame', background=bg_light)
style.configure('TLabel', background=bg_light, foreground=fg_dark)
style.configure('TButton', background=accent, foreground='white')
style.map('TButton', background=[('active', '#005a9e')])
style.configure('TLabelframe', background=bg_light, foreground=fg_dark)
style.configure('TLabelframe.Label', background=bg_light, foreground=fg_dark)
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
# Create a temporary text widget for logging (will be moved to LoggerDock later)
# This allows us to capture ALL logs from the very beginning
import tkinter.scrolledtext as scrolledtext
self._temp_log_widget = scrolledtext.ScrolledText(
self.root,
wrap=tk.WORD,
font=('Consolas', 9),
bg='#ffffff',
fg='#000000',
state='disabled'
)
# Configure tags for different log levels
self._temp_log_widget.tag_config('INFO', foreground='#0066cc')
self._temp_log_widget.tag_config('WARNING', foreground='#ff8800')
self._temp_log_widget.tag_config('ERROR', foreground='#cc0000')
self._temp_log_widget.tag_config('DEBUG', foreground='#666666')
# Create and attach the log handler immediately
self._log_handler = TextWidgetHandler(self._temp_log_widget)
self._log_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
self._log_handler.setFormatter(formatter)
logging.getLogger().addHandler(self._log_handler)
# Log that GUI logger is now active
logger = logging.getLogger('ARTOS')
logger.info("GUI logger initialized - capturing all subsequent logs")
# Create menu bar
self._create_menu()
# Create workspace manager
self.workspace = WorkspaceManager(self.root, layouts_dir="layouts")
# Create and register docks
self._create_docks()
# Load saved layout automatically after widgets are fully rendered
self.root.after(200, lambda: self.workspace.load_layout("user_layout"))
# Start refresh loops
self._start_refresh()
def _create_menu(self):
"""Create the main menu bar."""
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# File menu
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Save Layout", command=self._save_layout)
file_menu.add_command(label="Load Layout", command=self._load_layout)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self._on_close)
# View menu - toggle dock visibility
view_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="View", menu=view_menu)
view_menu.add_command(label="Toggle MCS",
command=lambda: self.workspace.toggle_dock_visibility('mcs'))
view_menu.add_command(label="Toggle Navigation",
command=lambda: self.workspace.toggle_dock_visibility('nav'))
view_menu.add_command(label="Toggle IRST",
command=lambda: self.workspace.toggle_dock_visibility('irst'))
view_menu.add_separator()
view_menu.add_command(label="Restore All", command=self._restore_all_docks)
# System menu
system_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="System", menu=system_menu)
system_menu.add_command(label="Start System", command=self._start_system)
system_menu.add_command(label="Stop System", command=self._stop_system)
system_menu.add_separator()
system_menu.add_command(label="System Info", command=self._show_system_info)
# Help menu
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="About", command=self._show_about)
help_menu.add_command(label="Documentation", command=self._show_docs)
def _create_docks(self):
"""Create and register all dock panels."""
# Left panel: MCS
mcs_dock = MCSDock(
self.workspace.left_container,
bus_module=self.bus_module
)
self.workspace.add_dock('mcs', mcs_dock, position='left')
# Right top: Navigation placeholder
nav_dock = PlaceholderDock(
self.workspace.right_top_container,
title="Navigation System",
description="Navigation algorithms and data processing\n\n(Coming in Phase 3)"
)
self.workspace.add_dock('nav', nav_dock, position='right_top')
# Right bottom: IRST placeholder
irst_dock = PlaceholderDock(
self.workspace.right_bottom_container,
title="IRST Module",
description="Infrared Search & Track\n\n(Coming in Phase 4)"
)
self.workspace.add_dock('irst', irst_dock, position='right_bottom')
# Bottom: Logger with scrollable text
# Transfer the temporary log widget to the LoggerDock
logger_dock = LoggerDock(
self.workspace.bottom_container,
existing_widget=self._temp_log_widget,
existing_handler=self._log_handler
)
self.workspace.add_dock('logger', logger_dock, position='bottom')
def _start_refresh(self):
"""Start the GUI refresh loop to update all widgets."""
self.gui_refresh_loop()
def _stop_refresh(self):
"""Stop refresh loops (cleanup on exit)."""
# The refresh loop will stop automatically when window closes
pass
def gui_refresh_loop(self):
"""
Periodically refreshes all custom widgets in all visible docks.
This updates telemetry displays from received 1553 messages.
"""
# First, sync all messages from the 1553 bus (read latest telemetry)
if self.bus_module and self.bus_module.is_running:
self.bus_module.sync_all_messages()
# Then refresh widgets in all visible docks
for dock_id, dock in self.workspace.docks.items():
if dock.is_visible and not dock.is_minimized:
self._refresh_widgets_recursive(dock.content_frame)
# Schedule next refresh (100ms = 10 Hz update rate)
self.root.after(100, self.gui_refresh_loop)
def _refresh_widgets_recursive(self, container: tk.Widget):
"""
Traverses the widget tree to find and update PyMsc custom components.
Calls check_updated_value() on each command widget found.
"""
for child in container.winfo_children():
# Check if it's one of our custom command widgets
is_custom = isinstance(child, (
CommandFrameCheckBox, CommandFrameComboBox,
CommandFrameSpinBox, CommandFrameLabels,
CommandFrameControls
))
if is_custom and hasattr(child, 'check_updated_value'):
child.check_updated_value()
# Continue searching if the child has its own children (like sub-frames)
if child.winfo_children():
self._refresh_widgets_recursive(child)
def _save_layout(self):
"""Save current workspace layout."""
self.workspace.save_layout("user_layout")
messagebox.showinfo("Layout Saved", "Workspace layout saved successfully.")
def _load_layout(self):
"""Load saved workspace layout."""
self.workspace.load_layout("user_layout")
messagebox.showinfo("Layout Loaded", "Workspace layout loaded successfully.")
def _restore_all_docks(self):
"""Restore all docks to visible state."""
for dock_id, dock in self.workspace.docks.items():
if not dock.is_visible:
dock.show()
if dock.is_minimized:
dock.restore()
def _start_system(self):
"""Start the 1553 bus system."""
if hasattr(self.bus_module, 'start'):
self.bus_module.start()
messagebox.showinfo("System", "Bus system started.")
def _stop_system(self):
"""Stop the 1553 bus system."""
if hasattr(self.bus_module, 'stop'):
self.bus_module.stop()
messagebox.showinfo("System", "Bus system stopped.")
def _show_system_info(self):
"""Display system information."""
info = f"""
ARTOS System Information
Architecture: Layered Modular Design
- L0: Hardware Abstraction (1553 Bus)
- L1: Test Orchestration (TestContext)
- L2: Algorithm Library
- L3: Test Execution
Active Modules:
- MCS: Mission Control System ✓
- Navigation: Placeholder
- IRST: Placeholder
- Logger: Placeholder
Framework: Tkinter + Custom Docking
Bus Protocol: MIL-STD-1553B over UDP
"""
messagebox.showinfo("System Information", info.strip())
def _show_about(self):
"""Show about dialog."""
about_text = """
ARTOS v2.0
Advanced Radar Test & Orchestration System
Modular GUI with docking panels
Built with Tkinter and ttkbootstrap
© 2025
"""
messagebox.showinfo("About ARTOS", about_text.strip())
def _show_docs(self):
"""Open documentation."""
messagebox.showinfo(
"Documentation",
"Documentation is available in the 'doc/' folder.\n\n"
"Key files:\n"
"- ARTOS_Architecture_Decisions.md\n"
"- ARTOS_design.md\n"
"- English-manual.md"
)
def _on_close(self):
"""Handle window close event."""
# Save layout automatically before closing (includes window geometry)
self.workspace.save_layout("user_layout")
if messagebox.askokcancel("Quit", "Do you want to quit ARTOS?"):
self._stop_refresh()
self.root.quit()
self.root.destroy()
def run(self):
"""Start the main event loop."""
self.root.mainloop()
def main():
"""Main entry point for the ARTOS GUI application."""
# Initialize the bus module
from pymsc.core.bus_1553_module import Bus1553Module
bus_module = Bus1553Module()
# Initialize with configuration
config = {
'udp_send_ip': '127.0.0.1',
'udp_send_port': 51553,
'udp_recv_ip': '0.0.0.0',
'udp_recv_port': 61553
}
bus_module.initialize(config)
# Create and run the main window
app = MainDockingWindow(bus_module)
app.run()
if __name__ == '__main__':
main()