# -*- 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()