From b4a095ef3a194cf172e328bb1543664f05a04b7e Mon Sep 17 00:00:00 2001 From: Chidi Williams Date: Sun, 23 Apr 2023 08:38:30 +0100 Subject: [PATCH] Add keyboard shortcuts (#409) --- buzz/assets.py | 8 ++ buzz/gui.py | 121 +++++++++++------- buzz/locale.py | 18 +++ buzz/settings/__init__.py | 0 buzz/{ => settings}/settings.py | 2 + buzz/settings/shortcut.py | 26 ++++ buzz/settings/shortcut_settings.py | 19 +++ buzz/widgets/__init__.py | 0 buzz/widgets/preferences_dialog.py | 35 +++++ buzz/widgets/shortcuts_editor_widget.py | 71 ++++++++++ tests/gui_test.py | 17 ++- tests/widgets/__init__.py | 0 tests/widgets/preferences_dialog_test.py | 16 +++ tests/widgets/shortcuts_editor_widget_test.py | 31 +++++ 14 files changed, 319 insertions(+), 45 deletions(-) create mode 100644 buzz/assets.py create mode 100644 buzz/locale.py create mode 100644 buzz/settings/__init__.py rename buzz/{ => settings}/settings.py (97%) create mode 100644 buzz/settings/shortcut.py create mode 100644 buzz/settings/shortcut_settings.py create mode 100644 buzz/widgets/__init__.py create mode 100644 buzz/widgets/preferences_dialog.py create mode 100644 buzz/widgets/shortcuts_editor_widget.py create mode 100644 tests/widgets/__init__.py create mode 100644 tests/widgets/preferences_dialog_test.py create mode 100644 tests/widgets/shortcuts_editor_widget_test.py diff --git a/buzz/assets.py b/buzz/assets.py new file mode 100644 index 00000000..bba32242 --- /dev/null +++ b/buzz/assets.py @@ -0,0 +1,8 @@ +import os +import sys + + +def get_asset_path(path: str): + if getattr(sys, 'frozen', False): + return os.path.join(os.path.dirname(sys.executable), path) + return os.path.join(os.path.dirname(__file__), '..', path) diff --git a/buzz/gui.py b/buzz/gui.py index 1795e4ac..12926b32 100644 --- a/buzz/gui.py +++ b/buzz/gui.py @@ -1,5 +1,4 @@ import enum -import gettext import json import logging import os @@ -14,7 +13,7 @@ import sounddevice from PyQt6 import QtGui from PyQt6.QtCore import (QObject, Qt, QThread, QTimer, QUrl, pyqtSignal, QModelIndex, QSize, QPoint, - QUrlQuery, QMetaObject, QEvent, QLocale) + QUrlQuery, QMetaObject, QEvent) from PyQt6.QtGui import (QAction, QCloseEvent, QDesktopServices, QIcon, QKeySequence, QPixmap, QTextCursor, QValidator, QKeyEvent, QPainter, QColor) from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest @@ -28,32 +27,19 @@ from whisper import tokenizer from buzz.cache import TasksCache from .__version__ import VERSION +from .assets import get_asset_path +from .locale import _ from .model_loader import ModelLoader, WhisperModelSize, ModelType, TranscriptionModel from .recording import RecordingAmplitudeListener -from .settings import Settings, APP_NAME +from .settings.settings import Settings, APP_NAME +from .settings.shortcut import Shortcut +from .settings.shortcut_settings import ShortcutSettings from .transcriber import (SUPPORTED_OUTPUT_FORMATS, FileTranscriptionOptions, OutputFormat, Task, get_default_output_file_path, segments_to_text, write_output, TranscriptionOptions, FileTranscriberQueueWorker, FileTranscriptionTask, RecordingTranscriber, LOADED_WHISPER_DLL, DEFAULT_WHISPER_TEMPERATURE) - - -def get_asset_path(path: str): - if getattr(sys, 'frozen', False): - return os.path.join(os.path.dirname(sys.executable), path) - return os.path.join(os.path.dirname(__file__), '..', path) - - -if 'LANG' not in os.environ: - language = str(QLocale().uiLanguages()[0]).replace("-", "_") - os.environ['LANG'] = language - -locale_dir = get_asset_path('locale') -gettext.bindtextdomain('buzz', locale_dir) - -translate = gettext.translation(APP_NAME, locale_dir, fallback=True) - -_ = translate.gettext +from .widgets.preferences_dialog import PreferencesDialog def get_platform_styles(all_platform_styles: Dict[str, str]): @@ -762,8 +748,6 @@ class AboutDialog(QDialog): parent: Optional[QWidget] = None) -> None: super().__init__(parent) - self.setFixedSize(200, 250) - self.setWindowIcon(QIcon(BUZZ_ICON_PATH)) self.setWindowTitle(f'{_("About")} {APP_NAME}') @@ -835,6 +819,8 @@ class TranscriptionTasksTableWidget(QTableWidget): FILE_NAME_COLUMN_INDEX = 1 STATUS_COLUMN_INDEX = 2 + return_clicked = pyqtSignal() + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) @@ -906,6 +892,11 @@ class TranscriptionTasksTableWidget(QTableWidget): sibling_index = index.siblingAtColumn(TranscriptionTasksTableWidget.TASK_ID_COLUMN_INDEX).data() return int(sibling_index) if sibling_index is not None else None + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: + if event.key() == Qt.Key.Key_Return: + self.return_clicked.emit() + super().keyPressEvent(event) + class MainWindowToolbar(QToolBar): new_transcription_action_triggered: pyqtSignal @@ -914,15 +905,14 @@ class MainWindowToolbar(QToolBar): ICON_LIGHT_THEME_BACKGROUND = '#555' ICON_DARK_THEME_BACKGROUND = '#AAA' - def __init__(self, parent: Optional[QWidget]): + def __init__(self, shortcuts: Dict[str, str], parent: Optional[QWidget]): super().__init__(parent) - record_action = QAction(self.load_icon(RECORD_ICON_PATH), _('Record'), self) - record_action.triggered.connect(self.on_record_action_triggered) + self.record_action = QAction(self.load_icon(RECORD_ICON_PATH), _('Record'), self) + self.record_action.triggered.connect(self.on_record_action_triggered) - new_transcription_action = QAction( - self.load_icon(ADD_ICON_PATH), _('New Transcription'), self) - self.new_transcription_action_triggered = new_transcription_action.triggered + self.new_transcription_action = QAction(self.load_icon(ADD_ICON_PATH), _('New Transcription'), self) + self.new_transcription_action_triggered = self.new_transcription_action.triggered self.open_transcript_action = QAction(self.load_icon(EXPAND_ICON_PATH), _('Open Transcript'), self) @@ -937,9 +927,11 @@ class MainWindowToolbar(QToolBar): self.clear_history_action_triggered = self.clear_history_action.triggered self.clear_history_action.setDisabled(True) - self.addAction(record_action) + self.set_shortcuts(shortcuts) + + self.addAction(self.record_action) self.addSeparator() - self.addAction(new_transcription_action) + self.addAction(self.new_transcription_action) self.addAction(self.open_transcript_action) self.addAction(self.stop_transcription_action) self.addAction(self.clear_history_action) @@ -957,6 +949,14 @@ class MainWindowToolbar(QToolBar): self.widgetForAction(self.actions()[0]).setStyleSheet( 'QToolButton { margin-left: 9px; margin-right: 1px; }') + def set_shortcuts(self, shortcuts: Dict[str, str]): + self.record_action.setShortcut(QKeySequence.fromString(shortcuts[Shortcut.OPEN_RECORD_WINDOW.name])) + self.new_transcription_action.setShortcut(QKeySequence.fromString(shortcuts[Shortcut.OPEN_IMPORT_WINDOW.name])) + self.open_transcript_action.setShortcut( + QKeySequence.fromString(shortcuts[Shortcut.OPEN_TRANSCRIPT_EDITOR.name])) + self.stop_transcription_action.setShortcut(QKeySequence.fromString(shortcuts[Shortcut.STOP_TRANSCRIPTION.name])) + self.clear_history_action.setShortcut(QKeySequence.fromString(shortcuts[Shortcut.CLEAR_HISTORY.name])) + def load_icon(self, file_path: str): is_dark_theme = self.palette().window().color().black() > 127 return self.load_icon_with_color(file_path, @@ -1003,24 +1003,31 @@ class MainWindow(QMainWindow): self.tasks_cache = tasks_cache + self.settings = Settings() + + self.shortcut_settings = ShortcutSettings(settings=self.settings) + self.shortcuts = self.shortcut_settings.load() + self.tasks = {} self.tasks_changed.connect(self.on_tasks_changed) - self.toolbar = MainWindowToolbar(self) + self.toolbar = MainWindowToolbar(shortcuts=self.shortcuts, parent=self) self.toolbar.new_transcription_action_triggered.connect(self.on_new_transcription_action_triggered) - self.toolbar.open_transcript_action_triggered.connect(self.on_open_transcript_action_triggered) + self.toolbar.open_transcript_action_triggered.connect(self.open_transcript_viewer) self.toolbar.clear_history_action_triggered.connect(self.on_clear_history_action_triggered) self.toolbar.stop_transcription_action_triggered.connect(self.on_stop_transcription_action_triggered) self.addToolBar(self.toolbar) self.setUnifiedTitleAndToolBarOnMac(True) - menu_bar = MenuBar(self) - menu_bar.import_action_triggered.connect( + self.menu_bar = MenuBar(shortcuts=self.shortcuts, parent=self) + self.menu_bar.import_action_triggered.connect( self.on_new_transcription_action_triggered) - self.setMenuBar(menu_bar) + self.menu_bar.shortcuts_changed.connect(self.on_shortcuts_changed) + self.setMenuBar(self.menu_bar) self.table_widget = TranscriptionTasksTableWidget(self) self.table_widget.doubleClicked.connect(self.on_table_double_clicked) + self.table_widget.return_clicked.connect(self.open_transcript_viewer) self.table_widget.itemSelectionChanged.connect( self.on_table_selection_changed) @@ -1119,7 +1126,7 @@ class MainWindow(QMainWindow): def on_openai_access_token_changed(self, access_token: str): self.openai_access_token = access_token - def on_open_transcript_action_triggered(self): + def open_transcript_viewer(self): selected_rows = self.table_widget.selectionModel().selectedRows() for selected_row in selected_rows: task_id = TranscriptionTasksTableWidget.find_task_id(selected_row) @@ -1182,11 +1189,18 @@ class MainWindow(QMainWindow): self.toolbar.set_clear_history_action_enabled(self.should_enable_clear_history_action()) self.save_tasks_to_cache() + def on_shortcuts_changed(self, shortcuts: dict): + self.shortcuts = shortcuts + self.menu_bar.set_shortcuts(shortcuts=self.shortcuts) + self.toolbar.set_shortcuts(shortcuts=self.shortcuts) + self.shortcut_settings.save(shortcuts=self.shortcuts) + def closeEvent(self, event: QtGui.QCloseEvent) -> None: self.transcriber_worker.stop() self.transcriber_thread.quit() self.transcriber_thread.wait() self.save_tasks_to_cache() + self.shortcut_settings.save(shortcuts=self.shortcuts) super().closeEvent(event) @@ -1444,23 +1458,31 @@ class TranscriptionOptionsGroupBox(QGroupBox): class MenuBar(QMenuBar): import_action_triggered = pyqtSignal() + shortcuts_changed = pyqtSignal(dict) - def __init__(self, parent: QWidget): + def __init__(self, shortcuts: Dict[str, str], parent: QWidget): super().__init__(parent) - import_action = QAction(_("Import Media File..."), self) - import_action.triggered.connect( - self.on_import_action_triggered) - import_action.setShortcut(QKeySequence.fromString('Ctrl+O')) + self.shortcuts = shortcuts - about_action = QAction(f'{_("About")} {APP_NAME}', self) + self.import_action = QAction(_("Import Media File..."), self) + self.import_action.triggered.connect( + self.on_import_action_triggered) + + about_action = QAction(f'{_("About...")} {APP_NAME}', self) about_action.triggered.connect(self.on_about_action_triggered) + self.preferences_action = QAction(_("Preferences..."), self) + self.preferences_action.triggered.connect(self.on_preferences_action_triggered) + + self.set_shortcuts(shortcuts) + file_menu = self.addMenu(_("File")) - file_menu.addAction(import_action) + file_menu.addAction(self.import_action) help_menu = self.addMenu(_("Help")) help_menu.addAction(about_action) + help_menu.addAction(self.preferences_action) def on_import_action_triggered(self): self.import_action_triggered.emit() @@ -1469,6 +1491,17 @@ class MenuBar(QMenuBar): about_dialog = AboutDialog(parent=self) about_dialog.open() + def on_preferences_action_triggered(self): + preferences_dialog = PreferencesDialog(shortcuts=self.shortcuts, parent=self) + preferences_dialog.shortcuts_changed.connect(self.shortcuts_changed) + preferences_dialog.open() + + def set_shortcuts(self, shortcuts: Dict[str, str]): + self.shortcuts = shortcuts + + self.import_action.setShortcut(QKeySequence.fromString(shortcuts[Shortcut.OPEN_IMPORT_WINDOW.name])) + self.preferences_action.setShortcut(QKeySequence.fromString(shortcuts[Shortcut.OPEN_PREFERENCES_WINDOW.name])) + class Application(QApplication): window: MainWindow diff --git a/buzz/locale.py b/buzz/locale.py new file mode 100644 index 00000000..e47f83f8 --- /dev/null +++ b/buzz/locale.py @@ -0,0 +1,18 @@ +import gettext +import os + +from PyQt6.QtCore import QLocale + +from buzz.assets import get_asset_path +from buzz.settings.settings import APP_NAME + +if 'LANG' not in os.environ: + language = str(QLocale().uiLanguages()[0]).replace("-", "_") + os.environ['LANG'] = language + +locale_dir = get_asset_path('locale') +gettext.bindtextdomain('buzz', locale_dir) + +translate = gettext.translation(APP_NAME, locale_dir, fallback=True) + +_ = translate.gettext diff --git a/buzz/settings/__init__.py b/buzz/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/buzz/settings.py b/buzz/settings/settings.py similarity index 97% rename from buzz/settings.py rename to buzz/settings/settings.py index dca525bd..f8d72c9e 100644 --- a/buzz/settings.py +++ b/buzz/settings/settings.py @@ -24,6 +24,8 @@ class Settings: FILE_TRANSCRIBER_WORD_LEVEL_TIMINGS = 'file-transcriber/word-level-timings' FILE_TRANSCRIBER_EXPORT_FORMATS = 'file-transcriber/export-formats' + SHORTCUTS = 'shortcuts' + def set_value(self, key: Key, value: typing.Any) -> None: self.settings.setValue(key.value, value) diff --git a/buzz/settings/shortcut.py b/buzz/settings/shortcut.py new file mode 100644 index 00000000..719aba7b --- /dev/null +++ b/buzz/settings/shortcut.py @@ -0,0 +1,26 @@ +import enum +import typing + + +class Shortcut(str, enum.Enum): + sequence: str + description: str + + def __new__(cls, sequence: str, description: str): + obj = str.__new__(cls, sequence) + obj._value_ = sequence + obj.sequence = sequence + obj.description = description + return obj + + OPEN_RECORD_WINDOW = ('Ctrl+R', "Open Record Window") + OPEN_IMPORT_WINDOW = ('Ctrl+O', "Import File") + OPEN_PREFERENCES_WINDOW = ('Ctrl+,', 'Open Preferences Window') + + OPEN_TRANSCRIPT_EDITOR = ('Ctrl+E', "Open Transcript Viewer") + CLEAR_HISTORY = ('Ctrl+S', "Clear History") + STOP_TRANSCRIPTION = ('Ctrl+X', "Cancel Transcription") + + @staticmethod + def get_default_shortcuts() -> typing.Dict[str, str]: + return {shortcut.name: shortcut.sequence for shortcut in Shortcut} diff --git a/buzz/settings/shortcut_settings.py b/buzz/settings/shortcut_settings.py new file mode 100644 index 00000000..3e200d8f --- /dev/null +++ b/buzz/settings/shortcut_settings.py @@ -0,0 +1,19 @@ +import typing + +from buzz.settings.settings import Settings +from buzz.settings.shortcut import Shortcut + + +class ShortcutSettings: + def __init__(self, settings: Settings): + self.settings = settings + + def load(self) -> typing.Dict[str, str]: + shortcuts = Shortcut.get_default_shortcuts() + custom_shortcuts: typing.Dict[str, str] = self.settings.value(Settings.Key.SHORTCUTS, {}) + for shortcut_name in custom_shortcuts: + shortcuts[shortcut_name] = custom_shortcuts[shortcut_name] + return shortcuts + + def save(self, shortcuts: typing.Dict[str, str]) -> None: + self.settings.set_value(Settings.Key.SHORTCUTS, shortcuts) diff --git a/buzz/widgets/__init__.py b/buzz/widgets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/buzz/widgets/preferences_dialog.py b/buzz/widgets/preferences_dialog.py new file mode 100644 index 00000000..d7d5be66 --- /dev/null +++ b/buzz/widgets/preferences_dialog.py @@ -0,0 +1,35 @@ +from typing import Dict, Optional + +from PyQt6.QtCore import pyqtSignal +from PyQt6.QtWidgets import QDialog, QWidget, QVBoxLayout, QTabWidget, QDialogButtonBox + +from buzz.locale import _ +from buzz.widgets.shortcuts_editor_widget import ShortcutsEditorWidget + + +class PreferencesDialog(QDialog): + shortcuts_changed = pyqtSignal(dict) + + def __init__(self, shortcuts: Dict[str, str], parent: Optional[QWidget] = None) -> None: + super().__init__(parent) + + self.setWindowTitle('Preferences') + + layout = QVBoxLayout(self) + tab_widget = QTabWidget(self) + + shortcuts_table_widget = ShortcutsEditorWidget(shortcuts, self) + shortcuts_table_widget.shortcuts_changed.connect(self.shortcuts_changed) + tab_widget.addTab(shortcuts_table_widget, _('Shortcuts')) + + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton( + QDialogButtonBox.StandardButton.Ok), self) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + layout.addWidget(tab_widget) + layout.addWidget(button_box) + + self.setLayout(layout) + + self.setFixedSize(self.sizeHint()) diff --git a/buzz/widgets/shortcuts_editor_widget.py b/buzz/widgets/shortcuts_editor_widget.py new file mode 100644 index 00000000..4e09b301 --- /dev/null +++ b/buzz/widgets/shortcuts_editor_widget.py @@ -0,0 +1,71 @@ +import platform +from typing import Optional, Dict + +from PyQt6 import QtGui +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QKeySequence +from PyQt6.QtWidgets import QKeySequenceEdit, QWidget, QFormLayout, QPushButton + +from buzz.settings.shortcut import Shortcut + + +class ShortcutsEditorWidget(QWidget): + shortcuts_changed = pyqtSignal(dict) + + def __init__(self, shortcuts: Dict[str, str], parent: Optional[QWidget] = None): + super().__init__(parent) + + self.shortcuts = shortcuts + + self.layout = QFormLayout(self) + for shortcut in Shortcut: + sequence_edit = SequenceEdit(shortcuts.get(shortcut.name, ''), self) + sequence_edit.keySequenceChanged.connect(self.get_key_sequence_changed(shortcut.name)) + self.layout.addRow(shortcut.description, sequence_edit) + + reset_to_defaults_button = QPushButton('Reset to Defaults', self) + reset_to_defaults_button.setDefault(False) + reset_to_defaults_button.setAutoDefault(False) + reset_to_defaults_button.clicked.connect(self.reset_to_defaults) + + self.layout.addWidget(reset_to_defaults_button) + + def get_key_sequence_changed(self, shortcut_name: str): + def key_sequence_changed(sequence: QKeySequence): + self.shortcuts[shortcut_name] = sequence.toString() + self.shortcuts_changed.emit(self.shortcuts) + + return key_sequence_changed + + def reset_to_defaults(self): + self.shortcuts = Shortcut.get_default_shortcuts() + + for i, shortcut in enumerate(Shortcut): + sequence_edit = self.layout.itemAt(i, QFormLayout.ItemRole.FieldRole).widget() + assert isinstance(sequence_edit, SequenceEdit) + sequence_edit.setKeySequence(QKeySequence(self.shortcuts[shortcut.name])) + + self.shortcuts_changed.emit(self.shortcuts) + + +class SequenceEdit(QKeySequenceEdit): + def __init__(self, sequence: str, parent: Optional[QWidget] = None): + super().__init__(sequence, parent) + self.setClearButtonEnabled(True) + if platform.system() == 'Darwin': + self.setStyleSheet('QLineEdit:focus { border: 2px solid #4d90fe; }') + + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: + key = event.key() + # The shortcut editor always focuses on the sequence edit widgets, so we need to + # manually capture Esc key presses to close the dialog. The downside being that + # the user can't set a shortcut that contains the Esc key. + if key == Qt.Key.Key_Escape: + self.parent().keyPressEvent(event) + return + + # Ignore pressing *only* modifier keys + if key == Qt.Key.Key_Control or key == Qt.Key.Key_Shift or key == Qt.Key.Key_Alt or key == Qt.Key.Key_Meta: + return + + super().keyPressEvent(event) diff --git a/tests/gui_test.py b/tests/gui_test.py index 41933f65..7351cabb 100644 --- a/tests/gui_test.py +++ b/tests/gui_test.py @@ -235,7 +235,7 @@ class TestMainWindow: window.close() @pytest.mark.parametrize('tasks_cache', [mock_tasks], indirect=True) - def test_should_open_transcription_viewer(self, qtbot, tasks_cache): + def test_should_open_transcription_viewer_when_menu_action_is_clicked(self, qtbot, tasks_cache): window = MainWindow(tasks_cache=tasks_cache) qtbot.add_widget(window) @@ -250,6 +250,21 @@ class TestMainWindow: window.close() + @pytest.mark.parametrize('tasks_cache', [mock_tasks], indirect=True) + def test_should_open_transcription_viewer_when_return_clicked(self, qtbot, tasks_cache): + window = MainWindow(tasks_cache=tasks_cache) + qtbot.add_widget(window) + + table_widget: QTableWidget = window.findChild(QTableWidget) + table_widget.selectRow(0) + table_widget.keyPressEvent( + QKeyEvent(QKeyEvent.Type.KeyPress, Qt.Key.Key_Return, Qt.KeyboardModifier.NoModifier, '\r')) + + transcription_viewer = window.findChild(TranscriptionViewerWidget) + assert transcription_viewer is not None + + window.close() + @pytest.mark.parametrize('tasks_cache', [mock_tasks], indirect=True) def test_should_have_open_transcript_action_disabled_with_no_rows_selected(self, qtbot, tasks_cache): window = MainWindow(tasks_cache=tasks_cache) diff --git a/tests/widgets/__init__.py b/tests/widgets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/widgets/preferences_dialog_test.py b/tests/widgets/preferences_dialog_test.py new file mode 100644 index 00000000..6c4fd5eb --- /dev/null +++ b/tests/widgets/preferences_dialog_test.py @@ -0,0 +1,16 @@ +from PyQt6.QtWidgets import QPushButton, QTabWidget +from pytestqt.qtbot import QtBot + +from buzz.widgets.preferences_dialog import PreferencesDialog + + +class TestPreferencesDialog: + def test_create(self, qtbot: QtBot): + dialog = PreferencesDialog(shortcuts={}) + qtbot.add_widget(dialog) + + assert dialog.windowTitle() == 'Preferences' + + tab_widget = dialog.findChild(QTabWidget) + assert isinstance(tab_widget, QTabWidget) + assert tab_widget.tabText(0) == 'Shortcuts' diff --git a/tests/widgets/shortcuts_editor_widget_test.py b/tests/widgets/shortcuts_editor_widget_test.py new file mode 100644 index 00000000..b52220b5 --- /dev/null +++ b/tests/widgets/shortcuts_editor_widget_test.py @@ -0,0 +1,31 @@ +from PyQt6.QtWidgets import QPushButton, QLabel + +from buzz.settings.shortcut import Shortcut +from buzz.widgets.shortcuts_editor_widget import ShortcutsEditorWidget, SequenceEdit + + +class TestShortcutsEditorWidget: + def test_should_reset_to_defaults(self, qtbot): + widget = ShortcutsEditorWidget(shortcuts=Shortcut.get_default_shortcuts()) + qtbot.add_widget(widget) + + reset_button = widget.findChild(QPushButton) + assert isinstance(reset_button, QPushButton) + reset_button.click() + + labels = widget.findChildren(QLabel) + sequence_edits = widget.findChildren(SequenceEdit) + + expected = ( + ('Open Record Window', 'Ctrl+R'), + ('Import File', 'Ctrl+O'), + ('Open Preferences Window', 'Ctrl+,'), + ('Open Transcript Viewer', 'Ctrl+E'), + ('Clear History', 'Ctrl+S'), + ('Cancel Transcription', 'Ctrl+X')) + + for i, (label, sequence_edit) in enumerate(zip(labels, sequence_edits)): + assert isinstance(label, QLabel) + assert isinstance(sequence_edit, SequenceEdit) + assert label.text() == expected[i][0] + assert sequence_edit.keySequence().toString() == expected[i][1]