diff --git a/README.md b/README.md index de0deb6..c36220c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Client SSH/SFTP personnel (type MobaXterm/Termius/Bitvise), 100% local, sans com - Confort d'affichage : zoom `Ctrl+molette` ou `Ctrl+ +/-` (`Ctrl+0` remet a zero, la taille est memorisee), barre de defilement de l'historique, `Maj+PagePrec/Suiv`, `Ctrl+Fin` pour revenir en direct, et recherche dans l'historique avec `Ctrl+F` +- Onglets renommables (double-clic sur l'onglet, ou clic droit) et deplaçables ; + le clic droit permet aussi de fermer les autres onglets - Panneau des sessions redimensionnable en attrapant le separateur, et masquable entierement (`F9`, menu Affichage, ou la croix du panneau) pour donner tout l'ecran au terminal - **Tunnels SSH** (redirection de ports) : bouton "Tunnels" dans la barre d'un onglet SSH. diff --git a/app/main_window.py b/app/main_window.py index 12f465a..ae0d0fa 100644 --- a/app/main_window.py +++ b/app/main_window.py @@ -12,7 +12,7 @@ from PySide6.QtGui import QAction, QActionGroup, QKeySequence, QShortcut from PySide6.QtWidgets import ( QMainWindow, QDockWidget, QTreeWidget, QTreeWidgetItem, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QLabel, QMenu, QMessageBox, QSplitter, - QStatusBar, QFrame, QApplication, QLineEdit, QFileDialog, + QStatusBar, QFrame, QApplication, QLineEdit, QFileDialog, QInputDialog, ) from app.session_manager import SessionStore, Session, AUTH_KEY, AUTH_PASSWORD @@ -661,6 +661,10 @@ class MainWindow(QMainWindow): self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.setMovable(True) + # double-clic sur un onglet pour le renommer, clic droit pour le menu + self.tabs.tabBarDoubleClicked.connect(self.rename_tab) + self.tabs.tabBar().setContextMenuPolicy(Qt.CustomContextMenu) + self.tabs.tabBar().customContextMenuRequested.connect(self._on_tab_context_menu) self.tabs.tabCloseRequested.connect(self._on_tab_close) self.setCentralWidget(self.tabs) @@ -896,6 +900,41 @@ class MainWindow(QMainWindow): def _register_broadcast(self, tab) -> None: tab.terminal.data_to_send.connect(lambda data, t=tab: self._broadcast(data, t)) + # ---------- renommage des onglets ---------- + def rename_tab(self, index: int) -> None: + if index < 0: + return + actuel = self.tabs.tabText(index) + nouveau, ok = QInputDialog.getText( + self, "Renommer l'onglet", "Nom de l'onglet :", text=actuel + ) + if ok and nouveau.strip(): + self.tabs.setTabText(index, nouveau.strip()) + + def _on_tab_context_menu(self, pos) -> None: + bar = self.tabs.tabBar() + index = bar.tabAt(pos) + if index < 0: + return + menu = QMenu(self) + act_rename = menu.addAction("Renommer... (double-clic)") + menu.addSeparator() + act_close = menu.addAction("Fermer cet onglet") + act_close_others = menu.addAction("Fermer les autres onglets") + act_close_others.setEnabled(self.tabs.count() > 1) + + action = menu.exec(bar.mapToGlobal(pos)) + if action == act_rename: + self.rename_tab(index) + elif action == act_close: + self._on_tab_close(index) + elif action == act_close_others: + # on ferme par la fin : retirer un onglet decale les suivants + cible = self.tabs.widget(index) + for other in range(self.tabs.count() - 1, -1, -1): + if self.tabs.widget(other) is not cible: + self._on_tab_close(other) + def _on_tab_close(self, index: int) -> None: widget = self.tabs.widget(index) if isinstance(widget, (BaseTerminalTab, SFTPTabWidget)):