sistemato log
This commit is contained in:
parent
837a9dc274
commit
18f3aba5c6
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"window_geometry": "1600x996+52+52",
|
"window_geometry": "1600x1099+310+130",
|
||||||
"main_sash_position": [
|
"main_sash_position": [
|
||||||
1,
|
1,
|
||||||
793
|
793
|
||||||
@ -13,6 +13,10 @@
|
|||||||
243
|
243
|
||||||
],
|
],
|
||||||
"docks": {
|
"docks": {
|
||||||
|
"logger": {
|
||||||
|
"visible": true,
|
||||||
|
"minimized": false
|
||||||
|
},
|
||||||
"mcs": {
|
"mcs": {
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"minimized": false
|
"minimized": false
|
||||||
@ -24,10 +28,6 @@
|
|||||||
"irst": {
|
"irst": {
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"minimized": false
|
"minimized": false
|
||||||
},
|
|
||||||
"logger": {
|
|
||||||
"visible": true,
|
|
||||||
"minimized": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,6 +10,9 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Add external logger module to path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '_external', 'externals', 'python-tkinter-logger'))
|
||||||
|
|
||||||
# Ensure local imports work correctly
|
# Ensure local imports work correctly
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
@ -21,6 +24,7 @@ from pymsc.utils.profiler import print_stats, save_stats_to_csv
|
|||||||
# Global flag to control the time update thread
|
# Global flag to control the time update thread
|
||||||
_time_update_running = False
|
_time_update_running = False
|
||||||
_time_update_thread = None
|
_time_update_thread = None
|
||||||
|
_logger_system = None
|
||||||
|
|
||||||
|
|
||||||
def update_radar_datetime_periodically(bus_module):
|
def update_radar_datetime_periodically(bus_module):
|
||||||
@ -104,15 +108,23 @@ def main():
|
|||||||
# Create logs directory first
|
# Create logs directory first
|
||||||
os.makedirs('logs', exist_ok=True)
|
os.makedirs('logs', exist_ok=True)
|
||||||
|
|
||||||
# Setup basic logging
|
# Initialize TkinterLogger IMMEDIATELY (before any logging)
|
||||||
logging.basicConfig(
|
# This ensures consistent formatting from the start
|
||||||
level=logging.INFO,
|
from tkinter_logger import TkinterLogger
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
||||||
handlers=[
|
# Create logger system without Tkinter widget initially (console + file only)
|
||||||
logging.FileHandler('logs/artos.log'),
|
global _logger_system
|
||||||
logging.StreamHandler()
|
_logger_system = TkinterLogger(tk_root=None)
|
||||||
]
|
_logger_system.setup(
|
||||||
|
enable_console=True,
|
||||||
|
enable_file=True,
|
||||||
|
file_path='logs/artos.log',
|
||||||
|
file_max_bytes=10 * 1024 * 1024, # 10MB
|
||||||
|
file_backup_count=5,
|
||||||
|
enable_tkinter=False, # Will add Tkinter handler later
|
||||||
|
root_level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger('ARTOS')
|
logger = logging.getLogger('ARTOS')
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
logger.info("Starting ARTOS - Advanced Radar Test & Orchestration System")
|
logger.info("Starting ARTOS - Advanced Radar Test & Orchestration System")
|
||||||
@ -196,7 +208,7 @@ def main():
|
|||||||
|
|
||||||
# Create and launch the modular docking GUI
|
# Create and launch the modular docking GUI
|
||||||
logger.info("Launching ARTOS Docking GUI...")
|
logger.info("Launching ARTOS Docking GUI...")
|
||||||
app = MainDockingWindow(bus_module)
|
app = MainDockingWindow(bus_module, logger_system=_logger_system)
|
||||||
|
|
||||||
# Start widget refresh loops to display live data
|
# Start widget refresh loops to display live data
|
||||||
logger.info("Starting GUI refresh loops...")
|
logger.info("Starting GUI refresh loops...")
|
||||||
|
|||||||
@ -79,6 +79,12 @@ class CommandFrameCheckBox(BaseCommandFrame):
|
|||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
val = 1 if self.command_var.get() else 0
|
val = 1 if self.command_var.get() else 0
|
||||||
self.info['message'].set_value_for_field(self.info['field'], val)
|
self.info['message'].set_value_for_field(self.info['field'], val)
|
||||||
|
|
||||||
|
# Log the change
|
||||||
|
logger = logging.getLogger('ARTOS.MCS')
|
||||||
|
state = "ON" if val == 1 else "OFF"
|
||||||
|
logger.info(f"User changed '{self.info['label']}' → {state}")
|
||||||
|
|
||||||
if self.script_manager.is_recording:
|
if self.script_manager.is_recording:
|
||||||
action_val = "on" if val == 1 else "off"
|
action_val = "on" if val == 1 else "off"
|
||||||
self.script_manager.write_command(self.info['label'], "toggle", action_val)
|
self.script_manager.write_command(self.info['label'], "toggle", action_val)
|
||||||
@ -144,6 +150,11 @@ class CommandFrameComboBox(BaseCommandFrame):
|
|||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
idx = self.combo.current()
|
idx = self.combo.current()
|
||||||
self.info['message'].set_value_for_field(self.info['field'], idx)
|
self.info['message'].set_value_for_field(self.info['field'], idx)
|
||||||
|
|
||||||
|
# Log the change
|
||||||
|
logger = logging.getLogger('ARTOS.MCS')
|
||||||
|
logger.info(f"User changed '{self.info['label']}' → {self.combo.get()}")
|
||||||
|
|
||||||
if self.script_manager.is_recording:
|
if self.script_manager.is_recording:
|
||||||
self.script_manager.write_command(self.info['label'], "set_value", self.combo.get())
|
self.script_manager.write_command(self.info['label'], "set_value", self.combo.get())
|
||||||
if self.info.get('message_tb'):
|
if self.info.get('message_tb'):
|
||||||
@ -277,6 +288,13 @@ class CommandFrameSpinBox(BaseCommandFrame):
|
|||||||
val = float(self.cmd_var.get())
|
val = float(self.cmd_var.get())
|
||||||
raw_val = set_correct_value(self.info, -1, val)
|
raw_val = set_correct_value(self.info, -1, val)
|
||||||
self.info['message'].set_value_for_field(self.info['field'], raw_val)
|
self.info['message'].set_value_for_field(self.info['field'], raw_val)
|
||||||
|
|
||||||
|
# Log the change
|
||||||
|
logger = logging.getLogger('ARTOS.MCS')
|
||||||
|
unit = self.info.get('unit', '')
|
||||||
|
unit_str = f" {unit}" if unit else ""
|
||||||
|
logger.info(f"User changed '{self.info['label']}' → {val}{unit_str}")
|
||||||
|
|
||||||
# Clear editing flag after sending
|
# Clear editing flag after sending
|
||||||
self.user_editing = False
|
self.user_editing = False
|
||||||
except (tk.TclError, ValueError):
|
except (tk.TclError, ValueError):
|
||||||
@ -298,6 +316,13 @@ class CommandFrameSpinBox(BaseCommandFrame):
|
|||||||
return
|
return
|
||||||
raw_val = set_correct_value(self.info, -1, val)
|
raw_val = set_correct_value(self.info, -1, val)
|
||||||
self.info['message'].set_value_for_field(self.info['field'], raw_val)
|
self.info['message'].set_value_for_field(self.info['field'], raw_val)
|
||||||
|
|
||||||
|
# Log the change
|
||||||
|
logger = logging.getLogger('ARTOS.MCS')
|
||||||
|
unit = self.info.get('unit', '')
|
||||||
|
unit_str = f" {unit}" if unit else ""
|
||||||
|
logger.info(f"User changed '{self.info['label']}' → {val}{unit_str}")
|
||||||
|
|
||||||
if self.script_manager.is_recording:
|
if self.script_manager.is_recording:
|
||||||
self.script_manager.write_command(self.info['label'], "set_value", val)
|
self.script_manager.write_command(self.info['label'], "set_value", val)
|
||||||
# Clear editing flag after successful change
|
# Clear editing flag after successful change
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Logger Dock: Real-time system logs display.
|
Logger Dock: Real-time system logs display using external TkinterLogger module.
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, scrolledtext
|
from tkinter import ttk, scrolledtext
|
||||||
@ -11,203 +11,35 @@ from pymsc.gui.docking.title_panel import TitlePanel
|
|||||||
class LoggerDock(TitlePanel):
|
class LoggerDock(TitlePanel):
|
||||||
"""
|
"""
|
||||||
Logger dockable panel with scrollable text display.
|
Logger dockable panel with scrollable text display.
|
||||||
Shows real-time logs from the ARTOS system.
|
Shows real-time logs from the ARTOS system using external TkinterLogger.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent: tk.Widget, existing_widget=None, existing_handler=None, **kwargs):
|
def __init__(self, parent: tk.Widget, **kwargs):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
parent: Parent widget
|
parent: Parent widget
|
||||||
existing_widget: Optional pre-created ScrolledText widget to use
|
|
||||||
existing_handler: Optional pre-created TextWidgetHandler to use
|
|
||||||
"""
|
"""
|
||||||
self.existing_widget = existing_widget
|
|
||||||
self.existing_handler = existing_handler
|
|
||||||
|
|
||||||
super().__init__(parent, title="System Logger", closable=False, **kwargs)
|
super().__init__(parent, title="System Logger", closable=False, **kwargs)
|
||||||
self.log_handler = existing_handler
|
|
||||||
|
|
||||||
def populate_content(self):
|
def populate_content(self):
|
||||||
"""Create or reuse the scrollable log text widget."""
|
"""Create the scrollable log text widget."""
|
||||||
# Remove outer padding from the content frame so the text widget
|
# Remove outer padding from the content frame
|
||||||
# can occupy the full dock area without an unwanted gap.
|
|
||||||
try:
|
try:
|
||||||
self.content_frame.configure(padding=0)
|
self.content_frame.configure(padding=0)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if self.existing_widget:
|
|
||||||
# Try to reparent the existing widget. If that doesn't render
|
|
||||||
# correctly (some platforms don't fully support reparenting),
|
|
||||||
# create a new ScrolledText inside our content_frame and copy
|
|
||||||
# the text/tags across, then update the handler to point to it.
|
|
||||||
old_widget = self.existing_widget
|
|
||||||
|
|
||||||
# Read existing content (if any) and current view
|
# Always create a new widget in this panel
|
||||||
try:
|
self.log_text = scrolledtext.ScrolledText(
|
||||||
old_text = old_widget.get('1.0', tk.END)
|
self.content_frame,
|
||||||
except Exception:
|
wrap=tk.WORD,
|
||||||
old_text = ''
|
height=10,
|
||||||
|
font=('Consolas', 9),
|
||||||
|
bg='#ffffff',
|
||||||
|
fg='#000000'
|
||||||
|
)
|
||||||
|
self.log_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
|
||||||
|
|
||||||
# Remove old widget from geometry managers
|
def get_log_widget(self):
|
||||||
try:
|
"""Return the log widget for external handlers to use."""
|
||||||
old_widget.pack_forget()
|
return self.log_text
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Create a fresh ScrolledText in our content frame
|
|
||||||
self.log_text = scrolledtext.ScrolledText(
|
|
||||||
self.content_frame,
|
|
||||||
wrap=tk.WORD,
|
|
||||||
height=10,
|
|
||||||
font=('Consolas', 9),
|
|
||||||
bg='#ffffff',
|
|
||||||
fg='#000000',
|
|
||||||
state='disabled'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Configure tags for different log levels on the new widget
|
|
||||||
self.log_text.tag_config('INFO', foreground='#0066cc')
|
|
||||||
self.log_text.tag_config('WARNING', foreground='#ff8800')
|
|
||||||
self.log_text.tag_config('ERROR', foreground='#cc0000')
|
|
||||||
self.log_text.tag_config('DEBUG', foreground='#666666')
|
|
||||||
|
|
||||||
# Insert previous content into the new widget
|
|
||||||
try:
|
|
||||||
self.log_text.config(state='normal')
|
|
||||||
if old_text:
|
|
||||||
self.log_text.insert(tk.END, old_text)
|
|
||||||
self.log_text.see(tk.END)
|
|
||||||
self.log_text.config(state='disabled')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Pack the new widget to fill area
|
|
||||||
self.log_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
|
|
||||||
|
|
||||||
# Update or create the handler so logs go to the new widget
|
|
||||||
if self.existing_handler:
|
|
||||||
try:
|
|
||||||
self.existing_handler.text_widget = self.log_text
|
|
||||||
self.log_handler = self.existing_handler
|
|
||||||
except Exception:
|
|
||||||
self.log_handler = TextWidgetHandler(self.log_text)
|
|
||||||
logging.getLogger().addHandler(self.log_handler)
|
|
||||||
else:
|
|
||||||
self.log_handler = TextWidgetHandler(self.log_text)
|
|
||||||
logging.getLogger().addHandler(self.log_handler)
|
|
||||||
|
|
||||||
# Load previous logs from file to show startup messages
|
|
||||||
self._load_previous_logs()
|
|
||||||
|
|
||||||
# Log that we've transferred
|
|
||||||
logger = logging.getLogger('ARTOS')
|
|
||||||
logger.info("System Logger dock created - continuing to capture logs")
|
|
||||||
else:
|
|
||||||
# Create new widget (fallback if called without existing widget)
|
|
||||||
self.log_text = scrolledtext.ScrolledText(
|
|
||||||
self.content_frame,
|
|
||||||
wrap=tk.WORD,
|
|
||||||
height=10,
|
|
||||||
font=('Consolas', 9),
|
|
||||||
bg='#ffffff',
|
|
||||||
fg='#000000',
|
|
||||||
state='disabled' # Read-only
|
|
||||||
)
|
|
||||||
# Pack without extra padding so it fills the dock content fully
|
|
||||||
self.log_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
|
|
||||||
|
|
||||||
# Configure tags for different log levels
|
|
||||||
self.log_text.tag_config('INFO', foreground='#0066cc')
|
|
||||||
self.log_text.tag_config('WARNING', foreground='#ff8800')
|
|
||||||
self.log_text.tag_config('ERROR', foreground='#cc0000')
|
|
||||||
self.log_text.tag_config('DEBUG', foreground='#666666')
|
|
||||||
|
|
||||||
# Create custom log handler that writes to this widget
|
|
||||||
self.log_handler = TextWidgetHandler(self.log_text)
|
|
||||||
self.log_handler.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
self.log_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Add handler to root logger
|
|
||||||
logging.getLogger().addHandler(self.log_handler)
|
|
||||||
|
|
||||||
# Add initial message
|
|
||||||
self.append_log("System Logger initialized. Ready to display logs.", 'INFO')
|
|
||||||
|
|
||||||
def _load_previous_logs(self):
|
|
||||||
"""Load and display logs from the log file that were written before GUI started."""
|
|
||||||
import os
|
|
||||||
log_file = 'logs/artos.log'
|
|
||||||
|
|
||||||
if not os.path.exists(log_file):
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(log_file, 'r', encoding='utf-8') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
# Load last 100 lines to avoid overwhelming the widget
|
|
||||||
recent_lines = lines[-100:] if len(lines) > 100 else lines
|
|
||||||
|
|
||||||
self.log_text.config(state='normal')
|
|
||||||
for line in recent_lines:
|
|
||||||
line = line.rstrip('\n')
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Detect log level from line content
|
|
||||||
level = 'INFO'
|
|
||||||
if ' - ERROR - ' in line:
|
|
||||||
level = 'ERROR'
|
|
||||||
elif ' - WARNING - ' in line:
|
|
||||||
level = 'WARNING'
|
|
||||||
elif ' - DEBUG - ' in line:
|
|
||||||
level = 'DEBUG'
|
|
||||||
|
|
||||||
self.log_text.insert(tk.END, line + '\n', level)
|
|
||||||
|
|
||||||
self.log_text.see(tk.END)
|
|
||||||
self.log_text.config(state='disabled')
|
|
||||||
except Exception as e:
|
|
||||||
# Silently ignore if we can't read the file
|
|
||||||
pass
|
|
||||||
|
|
||||||
def append_log(self, message: str, level: str = 'INFO'):
|
|
||||||
"""
|
|
||||||
Append a log message to the text widget.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message: Log message text
|
|
||||||
level: Log level (INFO, WARNING, ERROR, DEBUG)
|
|
||||||
"""
|
|
||||||
self.log_text.config(state='normal')
|
|
||||||
self.log_text.insert(tk.END, message + '\n', level)
|
|
||||||
self.log_text.see(tk.END) # Auto-scroll to bottom
|
|
||||||
self.log_text.config(state='disabled')
|
|
||||||
|
|
||||||
|
|
||||||
class TextWidgetHandler(logging.Handler):
|
|
||||||
"""
|
|
||||||
Custom logging handler that outputs to a Tkinter Text widget.
|
|
||||||
"""
|
|
||||||
def __init__(self, text_widget):
|
|
||||||
super().__init__()
|
|
||||||
self.text_widget = text_widget
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
"""Emit a log record to the text widget."""
|
|
||||||
try:
|
|
||||||
msg = self.format(record)
|
|
||||||
level = record.levelname
|
|
||||||
|
|
||||||
# Thread-safe update
|
|
||||||
self.text_widget.after(0, self._append_log, msg, level)
|
|
||||||
except Exception:
|
|
||||||
self.handleError(record)
|
|
||||||
|
|
||||||
def _append_log(self, message, level):
|
|
||||||
"""Append message to text widget (must be called from main thread)."""
|
|
||||||
self.text_widget.config(state='normal')
|
|
||||||
self.text_widget.insert(tk.END, message + '\n', level)
|
|
||||||
self.text_widget.see(tk.END)
|
|
||||||
self.text_widget.config(state='disabled')
|
|
||||||
|
|||||||
@ -10,11 +10,20 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
# Add external modules to path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '_external', 'externals', 'python-tkinter-logger'))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '_external', 'externals', 'python-resource-monitor'))
|
||||||
|
|
||||||
|
from tkinter_logger import TkinterLogger
|
||||||
|
from resource_monitor import ResourceMonitor
|
||||||
|
|
||||||
|
from tkinter.scrolledtext import ScrolledText
|
||||||
|
|
||||||
from pymsc.core.bus_1553_module import Bus1553Module
|
from pymsc.core.bus_1553_module import Bus1553Module
|
||||||
from pymsc.gui.docking.workspace_manager import WorkspaceManager
|
from pymsc.gui.docking.workspace_manager import WorkspaceManager
|
||||||
from pymsc.gui.docking.mcs_dock import MCSDock
|
from pymsc.gui.docking.mcs_dock import MCSDock
|
||||||
from pymsc.gui.docking.placeholder_dock import PlaceholderDock
|
from pymsc.gui.docking.placeholder_dock import PlaceholderDock
|
||||||
from pymsc.gui.docking.logger_dock import LoggerDock, TextWidgetHandler
|
from pymsc.gui.docking.logger_dock import LoggerDock
|
||||||
from pymsc.gui.components.command_widgets import (
|
from pymsc.gui.components.command_widgets import (
|
||||||
CommandFrameCheckBox, CommandFrameComboBox,
|
CommandFrameCheckBox, CommandFrameComboBox,
|
||||||
CommandFrameSpinBox, CommandFrameLabels,
|
CommandFrameSpinBox, CommandFrameLabels,
|
||||||
@ -32,10 +41,11 @@ class MainDockingWindow:
|
|||||||
- Right bottom: Logger, Status (placeholders)
|
- Right bottom: Logger, Status (placeholders)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bus_module: Bus1553Module):
|
def __init__(self, bus_module: Bus1553Module, logger_system=None):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
bus_module: Bus1553Module instance for message access
|
bus_module: Bus1553Module instance for message access
|
||||||
|
logger_system: Pre-configured TkinterLogger instance (optional)
|
||||||
"""
|
"""
|
||||||
self.bus_module = bus_module
|
self.bus_module = bus_module
|
||||||
self._refresh_active = False # Flag to control GUI refresh loop
|
self._refresh_active = False # Flag to control GUI refresh loop
|
||||||
@ -66,41 +76,53 @@ class MainDockingWindow:
|
|||||||
|
|
||||||
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
|
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
|
||||||
|
|
||||||
# Create a temporary text widget for logging (will be moved to LoggerDock later)
|
# Use existing TkinterLogger or create new one
|
||||||
# This allows us to capture ALL logs from the very beginning
|
if logger_system:
|
||||||
import tkinter.scrolledtext as scrolledtext
|
self.logger_system = logger_system
|
||||||
self._temp_log_widget = scrolledtext.ScrolledText(
|
# Update root for after() scheduling
|
||||||
self.root,
|
self.logger_system.tk_root = self.root
|
||||||
wrap=tk.WORD,
|
else:
|
||||||
font=('Consolas', 9),
|
# Fallback: create new logger system
|
||||||
bg='#ffffff',
|
self.logger_system = TkinterLogger(self.root)
|
||||||
fg='#000000',
|
self.logger_system.setup(
|
||||||
state='disabled'
|
enable_console=True,
|
||||||
)
|
enable_file=True,
|
||||||
# Configure tags for different log levels
|
file_path='logs/artos.log',
|
||||||
self._temp_log_widget.tag_config('INFO', foreground='#0066cc')
|
enable_tkinter=False, # Will add widget after LoggerDock is created
|
||||||
self._temp_log_widget.tag_config('WARNING', foreground='#ff8800')
|
root_level=logging.INFO
|
||||||
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
|
# Create menu bar
|
||||||
self._create_menu()
|
self._create_menu()
|
||||||
|
|
||||||
# Create workspace manager
|
# Create status bar at bottom (before workspace)
|
||||||
|
self._create_status_bar()
|
||||||
|
|
||||||
|
# Create workspace manager FIRST (so containers exist)
|
||||||
self.workspace = WorkspaceManager(self.root, layouts_dir="layouts")
|
self.workspace = WorkspaceManager(self.root, layouts_dir="layouts")
|
||||||
|
|
||||||
# Create and register docks
|
# Create LoggerDock FIRST so it creates its widget in the correct container
|
||||||
|
self.logger_dock = LoggerDock(self.workspace.bottom_container)
|
||||||
|
self.workspace.add_dock('logger', self.logger_dock, position='bottom')
|
||||||
|
|
||||||
|
# Get the widget from LoggerDock and connect it to TkinterLogger
|
||||||
|
log_widget = self.logger_dock.get_log_widget()
|
||||||
|
self.logger_system.add_tkinter_handler(
|
||||||
|
log_widget,
|
||||||
|
level_colors={
|
||||||
|
logging.DEBUG: '#666666',
|
||||||
|
logging.INFO: '#0066cc',
|
||||||
|
logging.WARNING: '#ff8800',
|
||||||
|
logging.ERROR: '#cc0000',
|
||||||
|
logging.CRITICAL: '#8B0000'
|
||||||
|
},
|
||||||
|
max_lines=1000
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger('ARTOS')
|
||||||
|
logger.info("GUI logger initialized - capturing all subsequent logs")
|
||||||
|
|
||||||
|
# Create and register other docks
|
||||||
self._create_docks()
|
self._create_docks()
|
||||||
|
|
||||||
# Load saved layout automatically after widgets are fully rendered
|
# Load saved layout automatically after widgets are fully rendered
|
||||||
@ -109,6 +131,42 @@ class MainDockingWindow:
|
|||||||
# Start refresh loops
|
# Start refresh loops
|
||||||
self._start_refresh()
|
self._start_refresh()
|
||||||
|
|
||||||
|
def _create_status_bar(self):
|
||||||
|
"""Create status bar at bottom with resource monitor on the right."""
|
||||||
|
self.status_bar = ttk.Frame(self.root, relief=tk.SUNKEN, borderwidth=1)
|
||||||
|
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
|
||||||
|
# Left side: general status message
|
||||||
|
self.status_label = ttk.Label(
|
||||||
|
self.status_bar,
|
||||||
|
text="ARTOS Ready",
|
||||||
|
anchor=tk.W,
|
||||||
|
padding=(5, 2)
|
||||||
|
)
|
||||||
|
self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||||
|
|
||||||
|
# Right side: resource monitor
|
||||||
|
self.resource_label = ttk.Label(
|
||||||
|
self.status_bar,
|
||||||
|
text="",
|
||||||
|
anchor=tk.E,
|
||||||
|
padding=(5, 2)
|
||||||
|
)
|
||||||
|
self.resource_label.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
# Initialize ResourceMonitor
|
||||||
|
def update_resource_stats(stats_str):
|
||||||
|
try:
|
||||||
|
self.resource_label.config(text=stats_str)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.resource_monitor = ResourceMonitor(
|
||||||
|
update_callback=update_resource_stats,
|
||||||
|
poll_interval=1.0
|
||||||
|
)
|
||||||
|
self.resource_monitor.start()
|
||||||
|
|
||||||
def _create_menu(self):
|
def _create_menu(self):
|
||||||
"""Create the main menu bar."""
|
"""Create the main menu bar."""
|
||||||
menubar = tk.Menu(self.root)
|
menubar = tk.Menu(self.root)
|
||||||
@ -149,7 +207,7 @@ class MainDockingWindow:
|
|||||||
help_menu.add_command(label="Documentation", command=self._show_docs)
|
help_menu.add_command(label="Documentation", command=self._show_docs)
|
||||||
|
|
||||||
def _create_docks(self):
|
def _create_docks(self):
|
||||||
"""Create and register all dock panels."""
|
"""Create and register remaining dock panels (logger was created first)."""
|
||||||
# Left panel: MCS
|
# Left panel: MCS
|
||||||
mcs_dock = MCSDock(
|
mcs_dock = MCSDock(
|
||||||
self.workspace.left_container,
|
self.workspace.left_container,
|
||||||
@ -173,14 +231,7 @@ class MainDockingWindow:
|
|||||||
)
|
)
|
||||||
self.workspace.add_dock('irst', irst_dock, position='right_bottom')
|
self.workspace.add_dock('irst', irst_dock, position='right_bottom')
|
||||||
|
|
||||||
# Bottom: Logger with scrollable text
|
# Logger was already created first in __init__
|
||||||
# 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):
|
def _start_refresh(self):
|
||||||
"""Start the GUI refresh loop to update all widgets."""
|
"""Start the GUI refresh loop to update all widgets."""
|
||||||
@ -332,10 +383,17 @@ Built with Tkinter and ttkbootstrap
|
|||||||
# Stop GUI refresh loop FIRST to prevent any further after() calls
|
# Stop GUI refresh loop FIRST to prevent any further after() calls
|
||||||
self._refresh_active = False
|
self._refresh_active = False
|
||||||
|
|
||||||
# Remove GUI log handler to avoid callbacks after widgets destroyed
|
# Stop resource monitor
|
||||||
try:
|
try:
|
||||||
if hasattr(self, '_log_handler'):
|
if hasattr(self, 'resource_monitor'):
|
||||||
logging.getLogger().removeHandler(self._log_handler)
|
self.resource_monitor.stop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Shutdown TkinterLogger system
|
||||||
|
try:
|
||||||
|
if hasattr(self, 'logger_system'):
|
||||||
|
self.logger_system.shutdown()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user