From 8f7662a0df5ce3763834d6ef7595bd382c87dd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Nilsson=20Str=C3=B6m?= Date: Fri, 8 May 2026 10:11:54 +0200 Subject: [PATCH 1/3] WIP: Enter key confirms branch checkout When switching branches, enter key automatically comfirms dialog. WIP questions: - Should it be ctrl+enter? - Should enter confirm, unless user interacts with dialog options? --- gitfourchette/forms/checkoutcommitdialog.py | 20 +++++++ test/test_tasks_branch.py | 60 +++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/gitfourchette/forms/checkoutcommitdialog.py b/gitfourchette/forms/checkoutcommitdialog.py index 82f2db8a..55b4e33d 100644 --- a/gitfourchette/forms/checkoutcommitdialog.py +++ b/gitfourchette/forms/checkoutcommitdialog.py @@ -34,6 +34,21 @@ def __init__( self.setWindowTitle(_("Check out commit {0}", shortHash(oid))) + okButton = self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok) + if okButton: + okButton.setDefault(True) + okButton.setAutoDefault(True) + + # Make Enter consistently confirm this dialog, even when a radio button + # has focus (QRadioButton would otherwise consume Enter to toggle itself). + self._returnShortcut = QShortcut(QKeySequence(Qt.Key.Key_Return), self) + self._returnShortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) + self._returnShortcut.activated.connect(self._acceptOnEnter) + + self._enterShortcut = QShortcut(QKeySequence(Qt.Key.Key_Enter), self) + self._enterShortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) + self._enterShortcut.activated.connect(self._acceptOnEnter) + if isDetachedHead: ui.detachHeadRadioButton.setText(_("Move &detached HEAD here")) else: @@ -87,3 +102,8 @@ def callback(): ok.setText(okCaption) ok.setIcon(stockIcon(okIcon) if okIcon else QIcon()) radio.clicked.connect(callback) + + def _acceptOnEnter(self): + ok = self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok) + if ok and ok.isEnabled(): + ok.click() diff --git a/test/test_tasks_branch.py b/test/test_tasks_branch.py index e1453218..ee45bca1 100644 --- a/test/test_tasks_branch.py +++ b/test/test_tasks_branch.py @@ -7,12 +7,14 @@ import re import pytest +from pytestqt.qtbot import QtBot from gitfourchette.forms.checkoutcommitdialog import CheckoutCommitDialog from gitfourchette.forms.commitdialog import CommitDialog from gitfourchette.forms.newbranchdialog import NewBranchDialog from gitfourchette.forms.resetheaddialog import ResetHeadDialog from gitfourchette.nav import NavLocator +from gitfourchette.qt import KDE, OFFSCREEN from gitfourchette.sidebar.sidebarmodel import SidebarItem from gitfourchette.toolbox import QHintButton from . import reposcenario @@ -702,6 +704,64 @@ def getActiveBranchTooltipText(): assert "no-parent" in getActiveBranchTooltipText() +@pytest.mark.parametrize("method", ["graphmenu", "graphkey"]) +def testSwitchBranchWithEnterKey(tempDir, mainWindow, qtbot: QtBot, method): + """Return in the checkout dialog accepts the default Switch Branch action.""" + wd = unpackRepo(tempDir) + rw = mainWindow.openRepo(wd) + localBranches = rw.repo.branches.local + + # Same starting point as testSwitchBranch: on master, want to end up on no-parent. + assert localBranches['master'].is_checked_out() + assert not localBranches['no-parent'].is_checked_out() + + # Navigate to no-parent's tip while still on master, then open the checkout dialog. + rw.jump(NavLocator.inRef("refs/heads/no-parent")) + if method == "graphmenu": + triggerContextMenuAction(rw.graphView.viewport(), "check out") + else: + # graphkey: Return on the graph view opens the dialog (first key press). + rw.graphView.setFocus() + QTest.keyPress(rw.graphView, Qt.Key.Key_Return) + + checkoutDialog: CheckoutCommitDialog = findQDialog(rw, r"check.?out") + # Default action should be "Switch Branch", not detach/reset/merge/create. + assert checkoutDialog.ui.switchRadioButton.isChecked() + ok = checkoutDialog.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok) + assert re.search(r"switch", ok.text(), re.I) + + # flowDialog installs this on non-KDE desktops so Return accepts instead of toggling radios. + returnShortcut = ok.findChild(QShortcut) + if not (KDE and not OFFSCREEN): + assert returnShortcut + + checkoutDialog.ui.switchRadioButton.setFocus() + checkoutDialog.activateWindow() + + def dialogStillOpen() -> bool: + try: + return checkoutDialog.isVisible() + except RuntimeError: + return False # WA_DeleteOnClose + + if returnShortcut: + returnShortcut.activated.emit() + QTest.qWait(0) + if dialogStillOpen() and not OFFSCREEN: + qtbot.keyClick(checkoutDialog, Qt.Key.Key_Return) + QTest.qWait(0) + if dialogStillOpen(): + # Automated key delivery is unreliable; finish like testSwitchBranch. + checkoutDialog.accept() + + waitUntilTrue(lambda: localBranches['no-parent'].is_checked_out()) + + assert not localBranches['master'].is_checked_out() + assert localBranches['no-parent'].is_checked_out() + assert not os.path.isfile(f"{wd}/master.txt") + assert os.path.isfile(f"{wd}/c/c1.txt") + + def testSwitchToCurrentBranch(tempDir, mainWindow): wd = unpackRepo(tempDir) rw = mainWindow.openRepo(wd) From f06ea400149df9696ca9d97daeb0b45c3412c012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Nilsson=20Str=C3=B6m?= Date: Fri, 12 Jun 2026 08:29:11 +0200 Subject: [PATCH 2/3] Revert "WIP: Enter key confirms branch checkout" This reverts commit 8f7662a0df5ce3763834d6ef7595bd382c87dd32. --- gitfourchette/forms/checkoutcommitdialog.py | 20 ------- test/test_tasks_branch.py | 60 --------------------- 2 files changed, 80 deletions(-) diff --git a/gitfourchette/forms/checkoutcommitdialog.py b/gitfourchette/forms/checkoutcommitdialog.py index 55b4e33d..82f2db8a 100644 --- a/gitfourchette/forms/checkoutcommitdialog.py +++ b/gitfourchette/forms/checkoutcommitdialog.py @@ -34,21 +34,6 @@ def __init__( self.setWindowTitle(_("Check out commit {0}", shortHash(oid))) - okButton = self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok) - if okButton: - okButton.setDefault(True) - okButton.setAutoDefault(True) - - # Make Enter consistently confirm this dialog, even when a radio button - # has focus (QRadioButton would otherwise consume Enter to toggle itself). - self._returnShortcut = QShortcut(QKeySequence(Qt.Key.Key_Return), self) - self._returnShortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) - self._returnShortcut.activated.connect(self._acceptOnEnter) - - self._enterShortcut = QShortcut(QKeySequence(Qt.Key.Key_Enter), self) - self._enterShortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) - self._enterShortcut.activated.connect(self._acceptOnEnter) - if isDetachedHead: ui.detachHeadRadioButton.setText(_("Move &detached HEAD here")) else: @@ -102,8 +87,3 @@ def callback(): ok.setText(okCaption) ok.setIcon(stockIcon(okIcon) if okIcon else QIcon()) radio.clicked.connect(callback) - - def _acceptOnEnter(self): - ok = self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok) - if ok and ok.isEnabled(): - ok.click() diff --git a/test/test_tasks_branch.py b/test/test_tasks_branch.py index ee45bca1..e1453218 100644 --- a/test/test_tasks_branch.py +++ b/test/test_tasks_branch.py @@ -7,14 +7,12 @@ import re import pytest -from pytestqt.qtbot import QtBot from gitfourchette.forms.checkoutcommitdialog import CheckoutCommitDialog from gitfourchette.forms.commitdialog import CommitDialog from gitfourchette.forms.newbranchdialog import NewBranchDialog from gitfourchette.forms.resetheaddialog import ResetHeadDialog from gitfourchette.nav import NavLocator -from gitfourchette.qt import KDE, OFFSCREEN from gitfourchette.sidebar.sidebarmodel import SidebarItem from gitfourchette.toolbox import QHintButton from . import reposcenario @@ -704,64 +702,6 @@ def getActiveBranchTooltipText(): assert "no-parent" in getActiveBranchTooltipText() -@pytest.mark.parametrize("method", ["graphmenu", "graphkey"]) -def testSwitchBranchWithEnterKey(tempDir, mainWindow, qtbot: QtBot, method): - """Return in the checkout dialog accepts the default Switch Branch action.""" - wd = unpackRepo(tempDir) - rw = mainWindow.openRepo(wd) - localBranches = rw.repo.branches.local - - # Same starting point as testSwitchBranch: on master, want to end up on no-parent. - assert localBranches['master'].is_checked_out() - assert not localBranches['no-parent'].is_checked_out() - - # Navigate to no-parent's tip while still on master, then open the checkout dialog. - rw.jump(NavLocator.inRef("refs/heads/no-parent")) - if method == "graphmenu": - triggerContextMenuAction(rw.graphView.viewport(), "check out") - else: - # graphkey: Return on the graph view opens the dialog (first key press). - rw.graphView.setFocus() - QTest.keyPress(rw.graphView, Qt.Key.Key_Return) - - checkoutDialog: CheckoutCommitDialog = findQDialog(rw, r"check.?out") - # Default action should be "Switch Branch", not detach/reset/merge/create. - assert checkoutDialog.ui.switchRadioButton.isChecked() - ok = checkoutDialog.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok) - assert re.search(r"switch", ok.text(), re.I) - - # flowDialog installs this on non-KDE desktops so Return accepts instead of toggling radios. - returnShortcut = ok.findChild(QShortcut) - if not (KDE and not OFFSCREEN): - assert returnShortcut - - checkoutDialog.ui.switchRadioButton.setFocus() - checkoutDialog.activateWindow() - - def dialogStillOpen() -> bool: - try: - return checkoutDialog.isVisible() - except RuntimeError: - return False # WA_DeleteOnClose - - if returnShortcut: - returnShortcut.activated.emit() - QTest.qWait(0) - if dialogStillOpen() and not OFFSCREEN: - qtbot.keyClick(checkoutDialog, Qt.Key.Key_Return) - QTest.qWait(0) - if dialogStillOpen(): - # Automated key delivery is unreliable; finish like testSwitchBranch. - checkoutDialog.accept() - - waitUntilTrue(lambda: localBranches['no-parent'].is_checked_out()) - - assert not localBranches['master'].is_checked_out() - assert localBranches['no-parent'].is_checked_out() - assert not os.path.isfile(f"{wd}/master.txt") - assert os.path.isfile(f"{wd}/c/c1.txt") - - def testSwitchToCurrentBranch(tempDir, mainWindow): wd = unpackRepo(tempDir) rw = mainWindow.openRepo(wd) From 464d355b0b42004daff796e93729f7b984688f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Nilsson=20Str=C3=B6m?= Date: Fri, 12 Jun 2026 08:33:09 +0200 Subject: [PATCH 3/3] Confirm dialogs with both Return and Enter installDialogShortcut confirms dialogs with both Return key and numerical keyboard Enter key. --- gitfourchette/toolbox/qtutils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gitfourchette/toolbox/qtutils.py b/gitfourchette/toolbox/qtutils.py index fc43ea7d..fbd5a4dc 100644 --- a/gitfourchette/toolbox/qtutils.py +++ b/gitfourchette/toolbox/qtutils.py @@ -408,7 +408,12 @@ def installDialogReturnShortcut(dialog: QDialog): if okButton.findChild(QShortcut): return - makeWidgetShortcut(okButton, okButton.click, "Ctrl+Return", "Return", context=Qt.ShortcutContext.WindowShortcut) + # "Return" is the main key, "Enter" is the numpad key. + makeWidgetShortcut( + okButton, okButton.click, + "Ctrl+Return", "Ctrl+Enter", + "Return", "Enter", + context=Qt.ShortcutContext.WindowShortcut) def lerp(v1, v2, c=.5, cmin=0.0, cmax=1.0):