feat: ajouter le split terminal (plusieurs panneaux par onglet)

Layouts : 1 (normal), 2 verticaux (cote a cote), 2 horizontaux (empiles), 4
panneaux (grille 2x2), via des QSplitter imbriques (app/ui/split_pane.py).
Chaque panneau reutilise tel quel un widget d'onglet existant
(SessionTabWidget, LocalTerminalTabWidget) : aucune modification de ces
classes, seule une fine barre (menu ☰ + fermeture) les enveloppe.

- Menu contextuel d'un onglet (SSH ou terminal local, pas SFTP, pas
  d'imbrication de splits) : "Diviser" -> choix du layout. Le contenu actuel
  occupe le premier panneau, les autres recoivent un terminal local par
  defaut.
- Menu ☰ d'un panneau : changer son contenu (autre terminal local), ou
  "Deplacer un onglet ici" pour y faire migrer un onglet de premier niveau
  existant sans fermer sa connexion.
- Fermer un panneau le retire de son QSplitter (qui redistribue l'espace tout
  seul) ; s'il n'en reste qu'un, l'onglet redevient un onglet normal
  (redescente automatique, pas de chrome de split residuel).
- Fermer l'onglet divise (bouton x) ferme toutes les connexions des panneaux.
- BroadcastManager (Multi-Send) atteint desormais aussi les panneaux a
  l'interieur d'un split (MainWindow._iter_tabs les deplie).

Bug intercepte par les tests avant integration : reparenter le widget d'origine
dans son panneau retire automatiquement son onglet du QTabWidget (un widget ne
peut avoir qu'un seul parent), ce qui decale les onglets suivants ; un appel
explicite a removeTab() ensuite visait donc le mauvais onglet. Corrige en
laissant Qt gerer ce retrait implicitement plutot que de le dupliquer.

tests/test_split_terminal.py : SSHConnection.start()/LocalTerminalConnection.start()
neutralisees (teste l'agencement des panneaux et le cycle de vie des onglets,
pas une connexion reelle).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Florian
2026-09-05 02:35:39 +02:00
parent 9c8017cebd
commit 5917e1ca04
4 changed files with 399 additions and 6 deletions

View File

@@ -22,6 +22,7 @@ from app.theme import THEME_LABELS, apply_theme, current_theme, save_theme
from app.controllers.broadcast_manager import BroadcastManager
from app.controllers.session_controller import SessionController
from app.controllers.connection_controller import ConnectionController
from app.ui.split_pane import SplitContainer
# re-exporte pour compatibilite (utilise par tests/test_detect.py et du code existant)
from app.services.ssh_target import detect_ssh_target # noqa: F401
@@ -59,8 +60,17 @@ class MainWindow(QMainWindow):
return self.connection_controller.tabs
def _iter_tabs(self):
"""Widgets de terminal joignables par le Multi-Send : les onglets de
premier niveau, et les panneaux a l'interieur des onglets divises."""
tabs = self.connection_controller.tabs
return [tabs.widget(i) for i in range(tabs.count())]
result = []
for i in range(tabs.count()):
widget = tabs.widget(i)
if isinstance(widget, SplitContainer):
result.extend(pane.content for pane in widget.panes)
else:
result.append(widget)
return result
# ---------- cablage entre controleurs ----------
def _wire_controllers(self) -> None: