development-hub/src/development_hub/main_window.py

277 lines
9.4 KiB
Python

"""Main window for Development Hub."""
from pathlib import Path
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction, QKeySequence
from PyQt6.QtWidgets import (
QLabel,
QMainWindow,
QSplitter,
QStatusBar,
QVBoxLayout,
QWidget,
)
from development_hub.project_discovery import Project
from development_hub.project_list import ProjectListWidget
from development_hub.workspace import WorkspaceManager
from development_hub.dialogs import NewProjectDialog, SettingsDialog
from development_hub.settings import Settings
class MainWindow(QMainWindow):
"""Main application window with project list and workspace."""
def __init__(self):
super().__init__()
self.setWindowTitle("Development Hub")
self.resize(1200, 800)
self.settings = Settings()
self._setup_ui()
self._setup_menus()
self._setup_status_bar()
self._restore_session()
def _setup_ui(self):
"""Set up the main UI layout."""
# Central widget with splitter
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout(central)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Main splitter: project list | workspace
self.main_splitter = QSplitter(Qt.Orientation.Horizontal)
layout.addWidget(self.main_splitter)
# Left: Project list
self.project_list = ProjectListWidget()
self.project_list.setFixedWidth(200)
self.project_list.open_terminal_requested.connect(self._open_terminal)
self.main_splitter.addWidget(self.project_list)
# Right: Workspace manager with splittable panes
self.workspace = WorkspaceManager()
self.workspace.pane_count_changed.connect(self._update_status)
self.main_splitter.addWidget(self.workspace)
# Set splitter proportions
self.main_splitter.setSizes([200, 1000])
def _setup_menus(self):
"""Set up the menu bar."""
menubar = self.menuBar()
# File menu
file_menu = menubar.addMenu("&File")
new_project = QAction("&New Project...", self)
new_project.setShortcut(QKeySequence("Ctrl+N"))
new_project.triggered.connect(self._new_project)
file_menu.addAction(new_project)
file_menu.addSeparator()
settings_action = QAction("&Settings...", self)
settings_action.setShortcut(QKeySequence("Ctrl+,"))
settings_action.triggered.connect(self._show_settings)
file_menu.addAction(settings_action)
file_menu.addSeparator()
exit_action = QAction("E&xit", self)
exit_action.setShortcut(QKeySequence("Ctrl+Q"))
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# Project menu
project_menu = menubar.addMenu("&Project")
refresh = QAction("&Refresh List", self)
refresh.setShortcut(QKeySequence("F5"))
refresh.triggered.connect(self.project_list.refresh)
project_menu.addAction(refresh)
# View menu
view_menu = menubar.addMenu("&View")
toggle_projects = QAction("Toggle &Project Panel", self)
toggle_projects.setShortcut(QKeySequence("Ctrl+B"))
toggle_projects.triggered.connect(self._toggle_project_panel)
view_menu.addAction(toggle_projects)
view_menu.addSeparator()
split_h = QAction("Split &Horizontal", self)
split_h.setShortcut(QKeySequence("Ctrl+Shift+D"))
split_h.triggered.connect(self._split_horizontal)
view_menu.addAction(split_h)
split_v = QAction("Split &Vertical", self)
split_v.setShortcut(QKeySequence("Ctrl+Shift+E"))
split_v.triggered.connect(self._split_vertical)
view_menu.addAction(split_v)
view_menu.addSeparator()
close_pane = QAction("Close &Pane", self)
close_pane.setShortcut(QKeySequence("Ctrl+Shift+P"))
close_pane.triggered.connect(self._close_active_pane)
view_menu.addAction(close_pane)
view_menu.addSeparator()
next_pane = QAction("&Next Pane", self)
next_pane.setShortcut(QKeySequence("Ctrl+Alt+Right"))
next_pane.triggered.connect(self.workspace.focus_next_pane)
view_menu.addAction(next_pane)
prev_pane = QAction("P&revious Pane", self)
prev_pane.setShortcut(QKeySequence("Ctrl+Alt+Left"))
prev_pane.triggered.connect(self.workspace.focus_previous_pane)
view_menu.addAction(prev_pane)
# Terminal menu
terminal_menu = menubar.addMenu("&Terminal")
new_tab = QAction("New &Tab", self)
new_tab.setShortcut(QKeySequence("Ctrl+Shift+T"))
new_tab.triggered.connect(self._new_terminal_tab)
terminal_menu.addAction(new_tab)
close_tab = QAction("&Close Tab", self)
close_tab.setShortcut(QKeySequence("Ctrl+Shift+W"))
close_tab.triggered.connect(self._close_current_tab)
terminal_menu.addAction(close_tab)
# Help menu
help_menu = menubar.addMenu("&Help")
about = QAction("&About", self)
about.triggered.connect(self._show_about)
help_menu.addAction(about)
def _setup_status_bar(self):
"""Set up the status bar."""
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self._update_status()
def _update_status(self, *args):
"""Update status bar text."""
project_count = self.project_list.list_widget.count()
pane_count = len(self.workspace.find_panes())
tab_count = self.workspace.total_tab_count()
pane_str = f"{pane_count} pane{'s' if pane_count != 1 else ''}"
tab_str = f"{tab_count} tab{'s' if tab_count != 1 else ''}"
self.status_bar.showMessage(
f"{project_count} projects | {pane_str} | {tab_str}"
)
def _open_terminal(self, project: Project):
"""Open a terminal tab for a project in the active pane."""
self.workspace.add_terminal(project.path, project.title)
self._update_status()
def _new_terminal_tab(self):
"""Create a new terminal tab at home directory in the active pane."""
# Count existing terminal tabs for naming
panes = self.workspace.find_panes()
terminal_count = 0
for pane in panes:
for i in range(pane.tab_widget.count()):
if pane.tab_widget.tabText(i).startswith("Terminal"):
terminal_count += 1
tab_name = f"Terminal {terminal_count + 1}" if terminal_count > 0 else "Terminal"
self.workspace.add_terminal(Path.home(), tab_name)
self._update_status()
def _close_current_tab(self):
"""Close the current tab in the active pane."""
self.workspace.close_current_tab()
self._update_status()
def _toggle_project_panel(self):
"""Toggle visibility of the project list panel."""
if self.project_list.isVisible():
self.project_list.hide()
else:
self.project_list.show()
def _split_horizontal(self):
"""Split the active pane horizontally (creates left/right panes)."""
self.workspace.split_horizontal()
def _split_vertical(self):
"""Split the active pane vertically (creates top/bottom panes)."""
self.workspace.split_vertical()
def _close_active_pane(self):
"""Close the currently active pane."""
self.workspace.close_active_pane()
self._update_status()
def _new_project(self):
"""Show new project dialog."""
dialog = NewProjectDialog(self)
dialog.project_created.connect(self._on_project_created)
dialog.exec()
def _on_project_created(self, project_name: str):
"""Handle new project creation."""
# Refresh the project list to show the new project
self.project_list.refresh()
self._update_status()
def _show_settings(self):
"""Show settings dialog."""
dialog = SettingsDialog(self)
dialog.exec()
def _show_about(self):
"""Show about dialog."""
from PyQt6.QtWidgets import QMessageBox
QMessageBox.about(
self,
"About Development Hub",
"<h3>Development Hub</h3>"
"<p>Version 0.1.0</p>"
"<p>Central project orchestration for multi-project development.</p>"
"<p>Part of Rob's development ecosystem.</p>"
"<h3>Keyboard Shortcuts</h3>"
"<ul>"
"<li><b>Ctrl+Shift+T</b> - New terminal tab</li>"
"<li><b>Ctrl+Shift+W</b> - Close current tab</li>"
"<li><b>Ctrl+Shift+D</b> - Split pane horizontal</li>"
"<li><b>Ctrl+Shift+E</b> - Split pane vertical</li>"
"<li><b>Ctrl+Alt+Left/Right</b> - Switch panes</li>"
"<li><b>Ctrl+B</b> - Toggle project panel</li>"
"<li><b>F5</b> - Refresh project list</li>"
"</ul>"
)
def _restore_session(self):
"""Restore the previous session."""
session = self.settings.load_session()
if session:
self.workspace.restore_session(session)
self._update_status()
def closeEvent(self, event):
"""Handle window close - save session and terminate all terminals."""
# Save session state
session = self.workspace.get_session_state()
self.settings.save_session(session)
# Terminate all terminals
self.workspace.terminate_all()
super().closeEvent(event)