From bcc39400e7ff3509d9cd349430c84f2a04839737 Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Thu, 9 Jul 2026 11:00:49 +0200 Subject: [PATCH] feat(qtermwidget): add clipboard shortcuts for copy and paste functionality --- .../utility/bec_term/qtermwidget_wrapper.py | 37 ++++++++++++++++++- tests/unit_tests/test_bec_console.py | 16 ++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/bec_widgets/widgets/utility/bec_term/qtermwidget_wrapper.py b/bec_widgets/widgets/utility/bec_term/qtermwidget_wrapper.py index d3dafaee..5c5d3c1c 100644 --- a/bec_widgets/widgets/utility/bec_term/qtermwidget_wrapper.py +++ b/bec_widgets/widgets/utility/bec_term/qtermwidget_wrapper.py @@ -2,12 +2,13 @@ Simply displays a message in a QLabel if the dependency is not installed.""" import os +import sys from functools import wraps from typing import Sequence from qtpy.QtCore import QIODevice, QPoint, QSize, QUrl, Signal # type: ignore -from qtpy.QtGui import QAction, QFont, QKeyEvent, QResizeEvent, Qt # type: ignore -from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget +from qtpy.QtGui import QAction, QFont, QKeyEvent, QKeySequence, QResizeEvent, Qt # type: ignore +from qtpy.QtWidgets import QLabel, QShortcut, QVBoxLayout, QWidget try: from pyside6_qtermwidget import QTermWidget @@ -50,6 +51,7 @@ class BecQTerm(QWidget): super().__init__(parent) self._layout = QVBoxLayout() self.setLayout(self._layout) + self._clipboard_shortcuts: dict[str, QShortcut] = {} if QTermWidget: self._main_widget = QTermWidget(parent=self) self._main_widget.activity.connect(self.activity) @@ -71,6 +73,7 @@ class BecQTerm(QWidget): self._layout.setAlignment(Qt.AlignmentFlag.AlignCenter) self._main_widget = QLabel("pyside6_qterminal is not installed!") + self._install_clipboard_shortcuts() self._layout.addWidget(self._main_widget) def write(self, text: str, add_newline: bool = True): @@ -97,6 +100,36 @@ class BecQTerm(QWidget): """Send Ctrl+C to the terminal.""" self.write("\x03", add_newline=False) # Send Ctrl+C character to the terminal + def _install_clipboard_shortcuts(self) -> None: + copy_sequence: QKeySequence | str + paste_sequence: QKeySequence | str + if sys.platform == "darwin": + copy_sequence = QKeySequence(QKeySequence.StandardKey.Copy) + paste_sequence = QKeySequence(QKeySequence.StandardKey.Paste) + else: + # Let's not override the Ctrl+C shortcut + copy_sequence = "Ctrl+Shift+C" + paste_sequence = "Ctrl+Shift+V" + + self._clipboard_shortcuts["copy"] = self._make_shortcut( + copy_sequence, self._handle_copy_shortcut + ) + self._clipboard_shortcuts["paste"] = self._make_shortcut( + paste_sequence, self._handle_paste_shortcut + ) + + def _make_shortcut(self, sequence: QKeySequence | str, slot) -> QShortcut: + shortcut = QShortcut(QKeySequence(sequence), self) + shortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) + shortcut.activated.connect(slot) + return shortcut + + def _handle_copy_shortcut(self) -> None: + self._copyClipboard() + + def _handle_paste_shortcut(self) -> None: + self._pasteClipboard() + # automatically forwarded to the widget only if it exists @_forward def _addCustomColorSchemeDir(self, custom_dir: str, /) -> None: ... diff --git a/tests/unit_tests/test_bec_console.py b/tests/unit_tests/test_bec_console.py index 2ac9776c..abd199ce 100644 --- a/tests/unit_tests/test_bec_console.py +++ b/tests/unit_tests/test_bec_console.py @@ -14,6 +14,7 @@ from bec_widgets.widgets.editors.bec_console.bec_console import ( ConsoleMode, _bec_console_registry, ) +from bec_widgets.widgets.utility.bec_term.qtermwidget_wrapper import BecQTerm from .client_mocks import mocked_client @@ -354,3 +355,18 @@ def test_plain_console_terminal_removed_after_last_unregister(qtbot): _bec_console_registry.unregister(widget) assert "plain_terminal" not in _bec_console_registry._terminal_registry + + +def test_bec_qterm_clipboard_shortcuts_call_terminal_clipboard_methods(qtbot): + term = BecQTerm() + qtbot.addWidget(term) + + with ( + mock.patch.object(term, "_copyClipboard") as mock_copy, + mock.patch.object(term, "_pasteClipboard") as mock_paste, + ): + term._clipboard_shortcuts["copy"].activated.emit() + term._clipboard_shortcuts["paste"].activated.emit() + + mock_copy.assert_called_once_with() + mock_paste.assert_called_once_with()