diff --git a/README.md b/README.md index c36220c..67bdb22 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ Client SSH/SFTP personnel (type MobaXterm/Termius/Bitvise), 100% local, sans com ## Fonctionnalites - Sessions SSH multiples dans des onglets, avec terminal ANSI colore et trois themes -- Copier-coller dans le terminal : selection a la souris, double-clic pour selectionner un mot, - `Ctrl+Shift+C` / `Ctrl+Shift+V`, `Maj+Inser`, clic molette, et menu au clic droit. - `Ctrl+C` reste l'interruption, comme dans tout terminal +- Copier-coller **a la souris, comme dans un terminal Unix** : selectionner copie aussitot + (double-clic pour un mot), et le clic droit colle directement — ni menu, ni raccourci. + Le clic molette colle aussi. `Ctrl+Shift+C` / `Ctrl+Shift+V` / `Maj+Inser` restent + disponibles au clavier, et `Ctrl+C` reste l'interruption comme dans tout terminal - 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` diff --git a/app/terminal_widget.py b/app/terminal_widget.py index 5d3375c..229a7c5 100644 --- a/app/terminal_widget.py +++ b/app/terminal_widget.py @@ -11,9 +11,9 @@ from __future__ import annotations import pyte from PySide6.QtCore import QRect, Qt, Signal from PySide6.QtGui import ( - QColor, QFont, QFontMetrics, QPainter, QKeyEvent, QWheelEvent, QKeySequence, + QColor, QFont, QFontMetrics, QPainter, QKeyEvent, QWheelEvent, ) -from PySide6.QtWidgets import QWidget, QMenu, QApplication, QScrollBar +from PySide6.QtWidgets import QWidget, QApplication, QScrollBar from app import theme as app_theme @@ -478,6 +478,10 @@ class TerminalWidget(QWidget): if key == Qt.Key_V: self.paste_clipboard() return + if key == Qt.Key_A: + # Ctrl+A seul reste "debut de ligne" pour le shell + self.select_all() + return if key == Qt.Key_Insert and (modifiers & Qt.ShiftModifier): self.paste_clipboard() return @@ -551,8 +555,10 @@ class TerminalWidget(QWidget): self._sel_anchor = self._sel_focus = self._cell_at(event.position().toPoint()) self._selecting = True self.update() - elif event.button() == Qt.MiddleButton: - self.paste_clipboard() # habitude Unix : clic molette = coller + elif event.button() in (Qt.RightButton, Qt.MiddleButton): + # Comportement des terminaux Unix (et de PuTTY) : le clic droit colle + # directement, sans passer par un menu. + self.paste_clipboard() super().mousePressEvent(event) def mouseMoveEvent(self, event) -> None: @@ -564,9 +570,14 @@ class TerminalWidget(QWidget): def mouseReleaseEvent(self, event) -> None: if event.button() == Qt.LeftButton: self._selecting = False - # un simple clic (sans deplacement) annule la selection if self._sel_anchor == self._sel_focus: + # simple clic sans deplacement : on annule la selection sans toucher + # au presse-papiers, qui contient peut-etre ce qu'on s'apprete a coller self.clear_selection() + else: + # Comportement des terminaux Unix : relacher la souris apres une + # selection copie immediatement, sans raccourci ni menu. + self.copy_selection() super().mouseReleaseEvent(event) def mouseDoubleClickEvent(self, event) -> None: @@ -584,22 +595,9 @@ class TerminalWidget(QWidget): end += 1 self._sel_anchor, self._sel_focus = (start, row), (end, row) self.update() + self.copy_selection() # comme la selection a la souris : copie immediate def contextMenuEvent(self, event) -> None: - menu = QMenu(self) - copy_action = menu.addAction("Copier") - copy_action.setShortcut(QKeySequence("Ctrl+Shift+C")) - copy_action.setEnabled(self.has_selection()) - paste_action = menu.addAction("Coller") - paste_action.setShortcut(QKeySequence("Ctrl+Shift+V")) - paste_action.setEnabled(bool(QApplication.clipboard().text())) - menu.addSeparator() - select_all_action = menu.addAction("Tout selectionner") - - action = menu.exec(event.globalPos()) - if action == copy_action: - self.copy_selection() - elif action == paste_action: - self.paste_clipboard() - elif action == select_all_action: - self.select_all() + # Pas de menu contextuel : dans un terminal Unix le clic droit colle, + # et il est traite dans mousePressEvent. + event.ignore() diff --git a/tests/test_clipboard.py b/tests/test_clipboard.py index e37988b..6771d28 100644 --- a/tests/test_clipboard.py +++ b/tests/test_clipboard.py @@ -88,5 +88,39 @@ t.clear_selection() assert not t.has_selection() print(" OK") +print() +print("=== 9. Souris : selection = copie, clic droit = collage (comme sous Unix) ===") +from PySide6.QtGui import QMouseEvent, QContextMenuEvent +from PySide6.QtCore import QPointF + + +def clic(bouton, col, row, type_evt=QEvent.MouseButtonPress): + pos = QPointF(col * t.char_w + 1, row * t.char_h + 1) + return QMouseEvent(type_evt, pos, pos, bouton, bouton, Qt.NoModifier) + + +QApplication.clipboard().setText("") +t.mousePressEvent(clic(Qt.LeftButton, 0, 1)) +t._sel_focus = (14, 1) +t.mouseReleaseEvent(clic(Qt.LeftButton, 14, 1, QEvent.MouseButtonRelease)) +print(" selection relachee -> presse-papiers :", repr(QApplication.clipboard().text())) +assert QApplication.clipboard().text() == "serveur-test-01", "la selection doit copier sans raccourci" + +envoye.clear() +t.mousePressEvent(clic(Qt.RightButton, 5, 0)) +print(" clic droit -> envoye au shell :", envoye) +assert envoye == [b"serveur-test-01"], "le clic droit doit coller directement" + +QApplication.clipboard().setText("a-conserver") +t.mousePressEvent(clic(Qt.LeftButton, 3, 0)) +t.mouseReleaseEvent(clic(Qt.LeftButton, 3, 0, QEvent.MouseButtonRelease)) +assert QApplication.clipboard().text() == "a-conserver", "un simple clic ne doit rien ecraser" +print(" simple clic -> presse-papiers intact") + +ev = QContextMenuEvent(QContextMenuEvent.Mouse, QPoint(10, 10), QPoint(10, 10)) +t.contextMenuEvent(ev) +assert not ev.isAccepted(), "aucun menu contextuel ne doit s'ouvrir" +print(" clic droit -> aucun menu contextuel") + print() print("COPIER/COLLER : TOUS LES TESTS OK")