340 lines
12 KiB
Python
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()
|