267 lines
9.9 KiB
Python
267 lines
9.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
WorkspaceManager: Manages layout and lifecycle of dockable panels.
|
|
Uses tk.PanedWindow for resizable splits.
|
|
"""
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
import json
|
|
import os
|
|
from typing import Dict, Optional
|
|
from .dock_frame import DockFrame
|
|
from .title_panel import TitlePanel
|
|
|
|
|
|
class WorkspaceManager:
|
|
"""
|
|
Manages the layout and lifecycle of dock frames.
|
|
|
|
Features:
|
|
- Dynamic layout with resizable panes (tk.PanedWindow)
|
|
- Dock registration and retrieval
|
|
- Save/load workspace layouts to JSON
|
|
- Multiple dock positions (left, right, bottom, etc.)
|
|
"""
|
|
|
|
def __init__(self, root: tk.Tk, layouts_dir: str = "layouts"):
|
|
"""
|
|
Args:
|
|
root: Root Tk window
|
|
layouts_dir: Directory to store layout configurations
|
|
"""
|
|
self.root = root
|
|
self.layouts_dir = layouts_dir
|
|
# docks may be legacy DockFrame or new TitlePanel instances
|
|
self.docks: Dict[str, object] = {}
|
|
|
|
# Create layouts directory if needed
|
|
if not os.path.exists(self.layouts_dir):
|
|
os.makedirs(self.layouts_dir)
|
|
|
|
self._create_layout()
|
|
|
|
def _create_layout(self):
|
|
"""Create the main paned window layout structure."""
|
|
# Main container
|
|
self.main_container = ttk.Frame(self.root)
|
|
self.main_container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Main vertical split (top | bottom logger)
|
|
self.main_pane = tk.PanedWindow(
|
|
self.main_container,
|
|
orient=tk.VERTICAL,
|
|
sashrelief=tk.RAISED,
|
|
sashwidth=4,
|
|
bg='#cccccc'
|
|
)
|
|
self.main_pane.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Top horizontal split (MCS | right panels)
|
|
self.top_pane = tk.PanedWindow(
|
|
self.main_pane,
|
|
orient=tk.HORIZONTAL,
|
|
sashrelief=tk.RAISED,
|
|
sashwidth=4,
|
|
bg='#cccccc'
|
|
)
|
|
self.main_pane.add(self.top_pane, minsize=400)
|
|
|
|
# Left panel container (MCS)
|
|
self.left_container = ttk.Frame(self.top_pane)
|
|
self.top_pane.add(self.left_container, minsize=400, width=650)
|
|
|
|
# Right side vertical split (nav | irst)
|
|
self.right_pane = tk.PanedWindow(
|
|
self.top_pane,
|
|
orient=tk.VERTICAL,
|
|
sashrelief=tk.RAISED,
|
|
sashwidth=4,
|
|
bg='#cccccc'
|
|
)
|
|
self.top_pane.add(self.right_pane, minsize=300)
|
|
|
|
# Right top container (Navigation)
|
|
self.right_top_container = ttk.Frame(self.right_pane)
|
|
self.right_pane.add(self.right_top_container, minsize=150)
|
|
|
|
# Right bottom container (IRST)
|
|
self.right_bottom_container = ttk.Frame(self.right_pane)
|
|
self.right_pane.add(self.right_bottom_container, minsize=150)
|
|
|
|
# Bottom logger container (full width)
|
|
self.bottom_container = ttk.Frame(self.main_pane)
|
|
self.main_pane.add(self.bottom_container, minsize=150, height=200)
|
|
|
|
def add_dock(self, dock_id: str, dock,
|
|
position: str = 'left'):
|
|
"""
|
|
Register and place a dock in the workspace.
|
|
|
|
Args:
|
|
dock_id: Unique identifier for the dock
|
|
dock: DockFrame instance
|
|
position: Where to place the dock ('left', 'right_top', 'right_bottom', 'bottom')
|
|
|
|
Returns:
|
|
The dock instance
|
|
"""
|
|
# Accept either DockFrame or TitlePanel (or any widget with packable API)
|
|
self.docks[dock_id] = dock
|
|
|
|
if position == 'left':
|
|
dock.pack(in_=self.left_container, side=tk.TOP, fill=tk.BOTH, expand=True)
|
|
elif position == 'right_top':
|
|
dock.pack(in_=self.right_top_container, side=tk.TOP, fill=tk.BOTH, expand=True)
|
|
elif position == 'right_bottom':
|
|
dock.pack(in_=self.right_bottom_container, side=tk.TOP, fill=tk.BOTH, expand=True)
|
|
elif position == 'bottom':
|
|
dock.pack(in_=self.bottom_container, side=tk.TOP, fill=tk.BOTH, expand=True)
|
|
else:
|
|
raise ValueError(f"Unknown position: {position}")
|
|
|
|
# Call populate_content if not done yet
|
|
dock.populate_content()
|
|
|
|
return dock
|
|
|
|
def get_dock(self, dock_id: str) -> Optional[DockFrame]:
|
|
"""Retrieve a dock by its ID."""
|
|
return self.docks.get(dock_id)
|
|
|
|
def remove_dock(self, dock_id: str):
|
|
"""Remove a dock from the workspace."""
|
|
dock = self.docks.get(dock_id)
|
|
if dock:
|
|
dock.close()
|
|
del self.docks[dock_id]
|
|
|
|
def toggle_dock_visibility(self, dock_id: str):
|
|
"""Toggle the visibility of a dock."""
|
|
dock = self.docks.get(dock_id)
|
|
if dock:
|
|
if dock.is_visible:
|
|
dock.close()
|
|
else:
|
|
dock.show()
|
|
|
|
def save_layout(self, layout_name: str = "default"):
|
|
"""
|
|
Save the current workspace layout to a JSON file.
|
|
|
|
Args:
|
|
layout_name: Name of the layout configuration
|
|
"""
|
|
layout_file = os.path.join(self.layouts_dir, f"{layout_name}.json")
|
|
|
|
# Get current window geometry
|
|
window_geometry = self.root.geometry()
|
|
|
|
# Get current sash positions
|
|
top_sash_pos = None
|
|
right_sash_pos = None
|
|
main_sash_pos = None
|
|
|
|
try:
|
|
# Main vertical split (top | bottom)
|
|
if hasattr(self, 'main_pane') and len(self.main_pane.panes()) > 0:
|
|
main_sash_pos = self.main_pane.sash_coord(0)
|
|
|
|
# Top horizontal split (MCS | right)
|
|
if hasattr(self, 'top_pane') and len(self.top_pane.panes()) > 0:
|
|
top_sash_pos = self.top_pane.sash_coord(0)
|
|
|
|
# Right vertical split (nav | irst)
|
|
if hasattr(self, 'right_pane') and len(self.right_pane.panes()) > 0:
|
|
right_sash_pos = self.right_pane.sash_coord(0)
|
|
except tk.TclError:
|
|
pass # Ignore errors if panes not ready
|
|
|
|
layout_config = {
|
|
'window_geometry': window_geometry,
|
|
'main_sash_position': main_sash_pos,
|
|
'top_sash_position': top_sash_pos,
|
|
'right_sash_position': right_sash_pos,
|
|
'docks': {
|
|
dock_id: {
|
|
'visible': dock.is_visible,
|
|
'minimized': dock.is_minimized
|
|
}
|
|
for dock_id, dock in self.docks.items()
|
|
}
|
|
}
|
|
|
|
try:
|
|
with open(layout_file, 'w') as f:
|
|
json.dump(layout_config, f, indent=2)
|
|
print(f"Layout saved to {layout_file}")
|
|
except Exception as e:
|
|
print(f"Failed to save layout: {e}")
|
|
|
|
def load_layout(self, layout_name: str = "default"):
|
|
"""
|
|
Load a workspace layout from a JSON file.
|
|
|
|
Args:
|
|
layout_name: Name of the layout configuration to load
|
|
"""
|
|
layout_file = os.path.join(self.layouts_dir, f"{layout_name}.json")
|
|
|
|
if not os.path.exists(layout_file):
|
|
print(f"Layout file not found: {layout_file} - using default layout")
|
|
return
|
|
|
|
try:
|
|
with open(layout_file) as f:
|
|
layout_config = json.load(f)
|
|
|
|
# Restore window geometry immediately
|
|
if layout_config.get('window_geometry'):
|
|
try:
|
|
self.root.geometry(layout_config['window_geometry'])
|
|
print(f"Window geometry restored: {layout_config['window_geometry']}")
|
|
except Exception as e:
|
|
print(f"Error restoring window geometry: {e}")
|
|
|
|
# Restore sash positions after a delay to ensure widgets are rendered
|
|
def restore_sashes():
|
|
try:
|
|
# Restore main vertical split (top | bottom)
|
|
if layout_config.get('main_sash_position'):
|
|
x, y = layout_config['main_sash_position']
|
|
if hasattr(self, 'main_pane') and len(self.main_pane.panes()) > 0:
|
|
self.main_pane.sash_place(0, x, y)
|
|
print(f"Main sash restored: ({x}, {y})")
|
|
|
|
# Restore top horizontal split (MCS | right)
|
|
if layout_config.get('top_sash_position'):
|
|
x, y = layout_config['top_sash_position']
|
|
if hasattr(self, 'top_pane') and len(self.top_pane.panes()) > 0:
|
|
self.top_pane.sash_place(0, x, y)
|
|
print(f"Top sash restored: ({x}, {y})")
|
|
|
|
# Restore right vertical split (nav | irst)
|
|
if layout_config.get('right_sash_position'):
|
|
x, y = layout_config['right_sash_position']
|
|
if hasattr(self, 'right_pane') and len(self.right_pane.panes()) > 0:
|
|
self.right_pane.sash_place(0, x, y)
|
|
print(f"Right sash restored: ({x}, {y})")
|
|
|
|
print(f"✓ Layout fully loaded from {layout_file}")
|
|
except Exception as e:
|
|
print(f"Error restoring sash positions: {e}")
|
|
|
|
# Schedule sash restoration after widgets are fully rendered
|
|
self.root.after(300, restore_sashes)
|
|
|
|
# Restore dock states
|
|
for dock_id, state in layout_config.get('docks', {}).items():
|
|
dock = self.docks.get(dock_id)
|
|
if dock:
|
|
if not state['visible']:
|
|
dock.close()
|
|
elif state['minimized']:
|
|
dock.minimize()
|
|
|
|
except Exception as e:
|
|
print(f"Failed to load layout: {e}")
|