feat: ajouter les Workspaces (enregistrer/restaurer l'etat des onglets)
Un workspace capture les onglets ouverts (sessions SSH, terminaux locaux, splits avec leurs tailles) et les restaure plus tard. Ne reference les sessions que par id : aucun mot de passe stocke, redemande a la restauration exactement comme une ouverture normale. - app/workspace.py : Workspace/WorkspaceTab/WorkspaceItem + WorkspaceStore (meme pattern que SessionStore/KeyManager : JSON atomique, schema_version). - app/controllers/workspace_manager.py : capture() lit l'etat reel des onglets de ConnectionController (types de widgets, layout et tailles d'un SplitContainer) ; restore() rouvre chaque onglet via ConnectionController.build_session_tab()/build_sftp_tab()/new_local_terminal() (factorises hors de open_session()/open_sftp_session() pour etre reutilisables ici) puis reconstruit les splits avec split_tab(). Un element non restaurable (session supprimee depuis) est signale (compteur "ignores") plutot que de faire echouer toute la restauration. - WorkspaceDialog : creer / enregistrer (ecraser) / ouvrir / renommer / supprimer, plus la case "Restaurer automatiquement le dernier workspace ouvert au demarrage" (desactivee par defaut). - app/app_settings.py : preferences globales (theme, taille de police, dernier workspace) consolidees dans un seul module au lieu d'un mecanisme de lecture/ecriture duplique par prefernce dans theme.py. - SplitContainer expose layout_kind et get_layout_sizes()/set_layout_sizes() (parcours recursif des QSplitter imbriques) pour la capture/restauration fidele d'une grille 2x2. tests/test_workspace.py : capture (simple + split), sauvegarde/rechargement JSON, restauration complete et partielle, renommer/supprimer/re-enregistrer, restauration automatique - avec app_settings.SETTINGS_FILE redirige vers un fichier temporaire pour ne jamais toucher les vrais reglages de l'utilisateur. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,10 +18,13 @@ from app.local_terminal import SHELL_LABELS, DEFAULT_SHELL
|
||||
from app.terminal_widget import TerminalWidget
|
||||
from app.dialogs.key_dialog import KeyManagerDialog
|
||||
from app.dialogs.known_hosts_dialog import KnownHostsDialog
|
||||
from app.dialogs.workspace_dialog import WorkspaceDialog
|
||||
from app.theme import THEME_LABELS, apply_theme, current_theme, save_theme
|
||||
from app.workspace import WorkspaceStore
|
||||
from app.controllers.broadcast_manager import BroadcastManager
|
||||
from app.controllers.session_controller import SessionController
|
||||
from app.controllers.connection_controller import ConnectionController
|
||||
from app.controllers.workspace_manager import WorkspaceManager
|
||||
from app.ui.split_pane import SplitContainer
|
||||
|
||||
# re-exporte pour compatibilite (utilise par tests/test_detect.py et du code existant)
|
||||
@@ -36,6 +39,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.session_store = SessionStore()
|
||||
self.key_manager = KeyManager()
|
||||
self.workspace_store = WorkspaceStore()
|
||||
|
||||
self.broadcast = BroadcastManager(self._iter_tabs, self)
|
||||
self.connection_controller = ConnectionController(
|
||||
@@ -44,6 +48,9 @@ class MainWindow(QMainWindow):
|
||||
self.session_controller = SessionController(
|
||||
self.session_store, self.key_manager, self.broadcast.button, parent_widget=self,
|
||||
)
|
||||
self.workspace_manager = WorkspaceManager(
|
||||
self.workspace_store, self.session_store, self.connection_controller, parent_widget=self,
|
||||
)
|
||||
self._wire_controllers()
|
||||
|
||||
self._build_menu()
|
||||
@@ -51,8 +58,10 @@ class MainWindow(QMainWindow):
|
||||
self.setCentralWidget(self.connection_controller.tabs)
|
||||
self.setStatusBar(QStatusBar())
|
||||
|
||||
# demarre directement dans un terminal utilisable, sans avoir a en choisir un
|
||||
self.connection_controller.open_local_terminal(DEFAULT_SHELL)
|
||||
# demarre directement dans un terminal utilisable, sans avoir a en choisir un -
|
||||
# sauf si un workspace vient d'etre restaure automatiquement (il a deja le sien)
|
||||
if not self.workspace_manager.try_restore_last_on_startup():
|
||||
self.connection_controller.open_local_terminal(DEFAULT_SHELL)
|
||||
|
||||
# ---------- acces retro-compatible (tests, code externe) ----------
|
||||
@property
|
||||
@@ -151,6 +160,12 @@ class MainWindow(QMainWindow):
|
||||
manage_hosts_action.triggered.connect(self.open_known_hosts_manager)
|
||||
hosts_menu.addAction(manage_hosts_action)
|
||||
|
||||
workspaces_menu = self.menuBar().addMenu("&Workspaces")
|
||||
manage_workspaces_action = QAction("Gestionnaire de workspaces...", self)
|
||||
manage_workspaces_action.setShortcut(QKeySequence("Ctrl+Shift+W"))
|
||||
manage_workspaces_action.triggered.connect(self.open_workspace_manager)
|
||||
workspaces_menu.addAction(manage_workspaces_action)
|
||||
|
||||
help_menu = self.menuBar().addMenu("&Aide")
|
||||
about_action = QAction("A propos", self)
|
||||
about_action.triggered.connect(self.show_about)
|
||||
@@ -200,6 +215,10 @@ class MainWindow(QMainWindow):
|
||||
dialog = KnownHostsDialog(parent=self)
|
||||
dialog.exec()
|
||||
|
||||
def open_workspace_manager(self) -> None:
|
||||
dialog = WorkspaceDialog(self.workspace_manager, parent=self)
|
||||
dialog.exec()
|
||||
|
||||
def show_about(self) -> None:
|
||||
QMessageBox.information(
|
||||
self, "A propos",
|
||||
|
||||
Reference in New Issue
Block a user