From 7e78f763f5bdb3cc1fcdbc55951f84e843f28670 Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Mon, 26 Aug 2024 07:52:27 -0500 Subject: [PATCH 1/8] window: Use Adw.Spinner --- dialect/widgets/provider_preferences.blp | 4 ++-- dialect/widgets/provider_preferences.py | 6 ----- dialect/window.blp | 28 +++++------------------- dialect/window.py | 4 +--- 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/dialect/widgets/provider_preferences.blp b/dialect/widgets/provider_preferences.blp index 7dcfe97e..59b12da2 100644 --- a/dialect/widgets/provider_preferences.blp +++ b/dialect/widgets/provider_preferences.blp @@ -41,7 +41,7 @@ template $ProviderPreferences : Adw.NavigationPage { StackPage { name: "spinner"; - child: Spinner instance_spinner { + child: Adw.Spinner { valign: center; }; } @@ -71,7 +71,7 @@ template $ProviderPreferences : Adw.NavigationPage { StackPage { name: "spinner"; - child: Spinner api_key_spinner { + child: Adw.Spinner { valign: center; }; } diff --git a/dialect/widgets/provider_preferences.py b/dialect/widgets/provider_preferences.py index 6aa066c7..e498b886 100644 --- a/dialect/widgets/provider_preferences.py +++ b/dialect/widgets/provider_preferences.py @@ -31,11 +31,9 @@ class ProviderPreferences(Adw.NavigationPage): instance_entry: Adw.EntryRow = Gtk.Template.Child() # type: ignore instance_stack: Gtk.Stack = Gtk.Template.Child() # type: ignore instance_reset: Gtk.Button = Gtk.Template.Child() # type: ignore - instance_spinner: Gtk.Spinner = Gtk.Template.Child() # type: ignore api_key_entry: Adw.PasswordEntryRow = Gtk.Template.Child() # type: ignore api_key_stack: Gtk.Stack = Gtk.Template.Child() # type: ignore api_key_reset: Gtk.Button = Gtk.Template.Child() # type: ignore - api_key_spinner: Gtk.Spinner = Gtk.Template.Child() # type: ignore api_usage_group: Adw.PreferencesGroup = Gtk.Template.Child() # type: ignore api_usage: Gtk.LevelBar = Gtk.Template.Child() # type: ignore api_usage_label: Gtk.Label = Gtk.Template.Child() # type: ignore @@ -109,7 +107,6 @@ def on_done(valid): self.instance_entry.props.sensitive = True self.api_key_entry.props.sensitive = True self.instance_stack.props.visible_child_name = "reset" - self.instance_spinner.stop() if not self.provider: return @@ -126,7 +123,6 @@ def on_done(valid): self.instance_entry.props.sensitive = False self.api_key_entry.props.sensitive = False self.instance_stack.props.visible_child_name = "spinner" - self.instance_spinner.start() # TODO: Use on_fail to notify network error self.provider.validate_instance(self.new_instance_url, on_done, lambda _: on_done(False)) @@ -177,7 +173,6 @@ def on_done(valid): self.instance_entry.props.sensitive = True self.api_key_entry.props.sensitive = True self.api_key_stack.props.visible_child_name = "reset" - self.api_key_spinner.stop() if not self.provider: return @@ -191,7 +186,6 @@ def on_done(valid): self.instance_entry.props.sensitive = False self.api_key_entry.props.sensitive = False self.api_key_stack.props.visible_child_name = "spinner" - self.api_key_spinner.start() # TODO: Use on_fail to notify network error self.provider.validate_api_key(self.new_api_key, on_done, lambda _: on_done(False)) diff --git a/dialect/window.blp b/dialect/window.blp index b84f5888..3f8a49c0 100644 --- a/dialect/window.blp +++ b/dialect/window.blp @@ -59,29 +59,13 @@ template $DialectWindow : Adw.ApplicationWindow { StackPage { name: "loading"; child: WindowHandle { - Box { - orientation: vertical; - spacing: 12; - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - halign: center; - valign: center; - - Spinner { - spinning: true; - width-request: 32; - height-request: 32; - } + Adw.StatusPage loading_page { + paintable: Adw.SpinnerPaintable { + widget: loading_page; + }; - Label { - wrap: true; + accessibility { label: _("Loading…"); - - styles [ - "title-1", - ] } } }; @@ -501,7 +485,7 @@ template $DialectWindow : Adw.ApplicationWindow { StackPage { name: "default"; child: Box { - Spinner trans_spinner { + Adw.Spinner trans_spinner { tooltip-text: _("Translating…"); margin-start: 8; } diff --git a/dialect/window.py b/dialect/window.py index fbfb2a4e..8fc9bd05 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -66,7 +66,7 @@ class DialectWindow(Adw.ApplicationWindow): dest_pron_label: Gtk.Label = Gtk.Template.Child() # type: ignore dest_text: TextView = Gtk.Template.Child() # type: ignore dest_toolbar_stack: Gtk.Stack = Gtk.Template.Child() # type: ignore - trans_spinner: Gtk.Spinner = Gtk.Template.Child() # type: ignore + trans_spinner: Adw.Spinner = Gtk.Template.Child() # type: ignore trans_warning: Gtk.Image = Gtk.Template.Child() # type: ignore edit_btn: Gtk.Button = Gtk.Template.Child() # type: ignore copy_btn: Gtk.Button = Gtk.Template.Child() # type: ignore @@ -1171,12 +1171,10 @@ def on_translation_fail(self, error: ProviderError): def translation_loading(self): self.trans_spinner.show() - self.trans_spinner.start() self.dest_box.props.sensitive = False self.langs_button_box.props.sensitive = False def translation_finish(self): - self.trans_spinner.stop() self.trans_spinner.hide() self.dest_box.props.sensitive = True self.langs_button_box.props.sensitive = True From b8cc0cc8aa84c1ffe30911dc811f1398272d16dd Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Mon, 26 Aug 2024 07:55:38 -0500 Subject: [PATCH 2/8] Introduce `VoiceButton` widget to manage TTS This new custom button also shows playback progress. We also now allow to cancel the playback if the button is pressed while playing. Closes #370 --- dialect/dialect.gresource.xml | 1 + dialect/meson.build | 1 + dialect/style.css | 12 +++++ dialect/widgets/__init__.py | 1 + dialect/widgets/voice_button.blp | 46 +++++++++++++++++ dialect/widgets/voice_button.py | 41 +++++++++++++++ dialect/window.blp | 4 +- dialect/window.py | 86 ++++++++++++++++++-------------- 8 files changed, 153 insertions(+), 39 deletions(-) create mode 100644 dialect/widgets/voice_button.blp create mode 100644 dialect/widgets/voice_button.py diff --git a/dialect/dialect.gresource.xml b/dialect/dialect.gresource.xml index 7227e461..30af8e83 100644 --- a/dialect/dialect.gresource.xml +++ b/dialect/dialect.gresource.xml @@ -11,6 +11,7 @@ widgets/lang_selector.ui widgets/provider_preferences.ui widgets/theme_switcher.ui + widgets/voice_button.ui @appstream-path@ diff --git a/dialect/meson.build b/dialect/meson.build index 4a3b9631..b5510cb2 100644 --- a/dialect/meson.build +++ b/dialect/meson.build @@ -11,6 +11,7 @@ blueprints = custom_target('blueprints', 'widgets/lang_row.blp', 'widgets/provider_preferences.blp', 'widgets/theme_switcher.blp', + 'widgets/voice_button.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], diff --git a/dialect/style.css b/dialect/style.css index dff47501..775e461d 100644 --- a/dialect/style.css +++ b/dialect/style.css @@ -129,3 +129,15 @@ actionbar.flat > revealer > box { color: #ae7b03; background: alpha(@yellow_5, .25); } + +/* Voice Button */ +.voice-button { + padding: 0; +} +.voice-button image { + padding: 5px 9px; +} +.voice-button progressbar trough { + min-width: 34px; + max-width: 34px; +} diff --git a/dialect/widgets/__init__.py b/dialect/widgets/__init__.py index 2e904f50..83a585aa 100644 --- a/dialect/widgets/__init__.py +++ b/dialect/widgets/__init__.py @@ -6,3 +6,4 @@ from dialect.widgets.provider_preferences import ProviderPreferences # noqa from dialect.widgets.textview import TextView # noqa from dialect.widgets.theme_switcher import ThemeSwitcher # noqa +from dialect.widgets.voice_button import VoiceButton # noqa diff --git a/dialect/widgets/voice_button.blp b/dialect/widgets/voice_button.blp new file mode 100644 index 00000000..651b823e --- /dev/null +++ b/dialect/widgets/voice_button.blp @@ -0,0 +1,46 @@ +using Gtk 4.0; +using Adw 1; + +template $VoiceButton : Button { + tooltip-text: _("Listen"); + + styles ["voice-button"] + + child: Overlay { + [overlay] + ProgressBar progress_bar { + visible: false; + valign: end; + + styles ["osd"] + } + + child: Stack stack { + StackPage { + name: "ready"; + child: Image { + icon-name: "audio-speakers-symbolic"; + }; + } + + StackPage { + name: "progress"; + child: Image { + icon-name: "media-playback-stop-symbolic"; + }; + } + + StackPage { + name: "error"; + child: Image { + icon-name: "dialog-warning-symbolic"; + }; + } + + StackPage { + name: "loading"; + child: Adw.Spinner {}; + } + }; + }; +} diff --git a/dialect/widgets/voice_button.py b/dialect/widgets/voice_button.py new file mode 100644 index 00000000..57b3dabc --- /dev/null +++ b/dialect/widgets/voice_button.py @@ -0,0 +1,41 @@ +# Copyright 2024 Mufeed Ali +# Copyright 2024 Rafael Mardojai CM +# SPDX-License-Identifier: GPL-3.0-or-later + +from gi.repository import Gtk + +from dialect.define import RES_PATH + + +@Gtk.Template(resource_path=f"{RES_PATH}/widgets/voice_button.ui") +class VoiceButton(Gtk.Button): + __gtype_name__ = "VoiceButton" + + stack: Gtk.Stack = Gtk.Template.Child() # type: ignore + progress_bar: Gtk.ProgressBar = Gtk.Template.Child() # type: ignore + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def ready(self): + self.stack.props.visible_child_name = "ready" + self.props.tooltip_text = _("Listen") + self.progress_bar.props.visible = False + + def progress(self, fraction: float): + if self.stack.props.visible_child_name != "progress": + self.stack.props.visible_child_name = "progress" + self.props.tooltip_text = _("Cancel Audio") + self.progress_bar.props.visible = True + + self.progress_bar.props.fraction = fraction + + def error(self, message: str = _("A network issue has occurred. Retry?")): + self.stack.props.visible_child_name = "error" + self.props.tooltip_text = message + self.progress_bar.props.visible = False + + def loading(self): + self.stack.props.visible_child_name = "loading" + self.props.tooltip_text = _("Loading…") + self.progress_bar.props.visible = False diff --git a/dialect/window.blp b/dialect/window.blp index 3f8a49c0..d78f6310 100644 --- a/dialect/window.blp +++ b/dialect/window.blp @@ -392,7 +392,7 @@ template $DialectWindow : Adw.ApplicationWindow { icon-name: "edit-paste-symbolic"; } - Button src_voice_btn { + $VoiceButton src_voice_btn { action-name: "win.listen-src"; tooltip-text: _("Listen"); @@ -510,7 +510,7 @@ template $DialectWindow : Adw.ApplicationWindow { icon-name: "document-edit-symbolic"; } - Button dest_voice_btn { + $VoiceButton dest_voice_btn { action-name: "win.listen-dest"; tooltip-text: _("Listen"); diff --git a/dialect/window.py b/dialect/window.py index 8fc9bd05..df40e0f1 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -22,7 +22,7 @@ from dialect.settings import Settings from dialect.shortcuts import DialectShortcutsWindow from dialect.utils import find_item_match, first_exclude -from dialect.widgets import LangSelector, TextView, ThemeSwitcher +from dialect.widgets import LangSelector, TextView, ThemeSwitcher, VoiceButton @Gtk.Template(resource_path=f"{RES_PATH}/window.ui") @@ -58,7 +58,7 @@ class DialectWindow(Adw.ApplicationWindow): src_text: TextView = Gtk.Template.Child() # type: ignore clear_btn: Gtk.Button = Gtk.Template.Child() # type: ignore paste_btn: Gtk.Button = Gtk.Template.Child() # type: ignore - src_voice_btn: Gtk.Button = Gtk.Template.Child() # type: ignore + src_voice_btn: VoiceButton = Gtk.Template.Child() # type: ignore translate_btn: Gtk.Button = Gtk.Template.Child() # type: ignore dest_box: Gtk.Box = Gtk.Template.Child() # type: ignore @@ -70,7 +70,7 @@ class DialectWindow(Adw.ApplicationWindow): trans_warning: Gtk.Image = Gtk.Template.Child() # type: ignore edit_btn: Gtk.Button = Gtk.Template.Child() # type: ignore copy_btn: Gtk.Button = Gtk.Template.Child() # type: ignore - dest_voice_btn: Gtk.Button = Gtk.Template.Child() # type: ignore + dest_voice_btn: VoiceButton = Gtk.Template.Child() # type: ignore actionbar: Gtk.ActionBar = Gtk.Template.Child() # type: ignore src_lang_selector_m: LangSelector = Gtk.Template.Child() # type: ignore @@ -116,7 +116,7 @@ def __init__(self, **kwargs): if self.player: if bus := self.player.get_bus(): bus.add_signal_watch() - bus.connect("message", self.on_gst_message) + bus.connect("message", self._on_gst_message) # Setup window self.setup_actions() @@ -270,15 +270,6 @@ def setup_translation(self): self.trans_spinner.hide() self.trans_warning.hide() - # Voice buttons prep-work - self.src_voice_warning = Gtk.Image.new_from_icon_name("dialog-warning-symbolic") - self.src_voice_image = Gtk.Image.new_from_icon_name("audio-speakers-symbolic") - self.src_voice_spinner = Gtk.Spinner() # For use while audio is running or still loading. - - self.dest_voice_warning = Gtk.Image.new_from_icon_name("dialog-warning-symbolic") - self.dest_voice_image = Gtk.Image.new_from_icon_name("audio-speakers-symbolic") - self.dest_voice_spinner = Gtk.Spinner() - self.toggle_voice_spinner(True) def load_translator(self): @@ -493,15 +484,8 @@ def on_listen_failed(self): if not self.provider["tts"]: return - self.src_voice_btn.props.child = self.src_voice_warning - self.src_voice_spinner.stop() - - self.dest_voice_btn.props.child = self.dest_voice_warning - self.dest_voice_spinner.stop() - - tooltip_text = _("A network issue has occurred. Retry?") - self.src_voice_btn.props.tooltip_text = tooltip_text - self.dest_voice_btn.props.tooltip_text = tooltip_text + self.src_voice_btn.error() + self.dest_voice_btn.error() if self.current_speech: called_from = self.current_speech["called_from"] @@ -604,19 +588,16 @@ def toggle_voice_spinner(self, active=True): if active: self.lookup_action("listen-src").props.enabled = False # type: ignore - self.src_voice_btn.props.child = self.src_voice_spinner - self.src_voice_spinner.start() + self.src_voice_btn.loading() self.lookup_action("listen-dest").props.enabled = False # type: ignore - self.dest_voice_btn.props.child = self.dest_voice_spinner - self.dest_voice_spinner.start() + self.dest_voice_btn.loading() else: src_text = self.src_buffer.get_text(self.src_buffer.get_start_iter(), self.src_buffer.get_end_iter(), True) self.lookup_action("listen-src").set_enabled( # type: ignore self.src_lang_selector.selected in self.provider["tts"].tts_languages and src_text != "" ) - self.src_voice_btn.props.child = self.src_voice_image - self.src_voice_spinner.stop() + self.src_voice_btn.ready() dest_text = self.dest_buffer.get_text( self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True @@ -624,8 +605,7 @@ def toggle_voice_spinner(self, active=True): self.lookup_action("listen-dest").set_enabled( # type: ignore self.dest_lang_selector.selected in self.provider["tts"].tts_languages and dest_text != "" ) - self.dest_voice_btn.props.child = self.dest_voice_image - self.dest_voice_spinner.stop() + self.dest_voice_btn.ready() @Gtk.Template.Callback() def _on_src_lang_changed(self, _obj, _param): @@ -856,11 +836,19 @@ def ui_suggest_cancel(self, _action, _param): self.dest_text.props.editable = False def ui_src_voice(self, _action, _param): + if self.current_speech: + self._voice_reset() + return + src_text = self.src_buffer.get_text(self.src_buffer.get_start_iter(), self.src_buffer.get_end_iter(), True) src_language = self.src_lang_selector.selected self._pre_speech(src_text, src_language, "src") def ui_dest_voice(self, _action, _param): + if self.current_speech: + self._voice_reset() + return + dest_text = self.dest_buffer.get_text(self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True) dest_language = self.dest_lang_selector.selected self._pre_speech(dest_text, dest_language, "dest") @@ -874,15 +862,39 @@ def _pre_speech(self, text: str, lang: str, called_from: Literal["src", "dest"]) self.download_speech() - def on_gst_message(self, _bus, message: Gst.Message): + def _voice_reset(self): if not self.player: return - if message.type == Gst.MessageType.EOS: - self.player.set_state(Gst.State.NULL) - elif message.type == Gst.MessageType.ERROR: - self.player.set_state(Gst.State.NULL) - logging.error("Some error occurred while trying to play.") + self.player.set_state(Gst.State.NULL) + self.current_speech = {} + self.src_voice_btn.ready() + self.dest_voice_btn.ready() + + def _on_gst_message(self, _bus, message: Gst.Message): + if message.type == Gst.MessageType.EOS or message.type == Gst.MessageType.ERROR: + if message.type == Gst.MessageType.ERROR: + logging.error("Some error occurred while trying to play.") + + self._voice_reset() + + def _gst_progress_timeout(self): + if not self.player: + return False + + if self.current_speech and self.player.get_state(Gst.CLOCK_TIME_NONE) != Gst.State.NULL: + have_pos, pos = self.player.query_position(Gst.Format.TIME) + have_dur, dur = self.player.query_duration(Gst.Format.TIME) + + if have_pos and have_dur: + if self.current_speech["called_from"] == "src": + self.src_voice_btn.progress(pos / dur) + else: + self.dest_voice_btn.progress(pos / dur) + + return True + + return False def download_speech(self): def on_done(file: IO): @@ -896,7 +908,6 @@ def on_done(file: IO): self.toggle_voice_spinner(False) finally: self.voice_loading = False - self.current_speech = {} def on_fail(_error: ProviderError): self.on_listen_failed() @@ -919,6 +930,7 @@ def _play_audio(self, path: str): uri = "file://" + path self.player.set_property("uri", uri) self.player.set_state(Gst.State.PLAYING) + GLib.timeout_add(50, self._gst_progress_timeout) @Gtk.Template.Callback() def _on_key_event(self, _ctrl, keyval: int, _keycode: int, state: Gdk.ModifierType): From e228e5ed4dc606ba0fb24dbdc5c8c619c665b22e Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Mon, 26 Aug 2024 11:34:32 -0500 Subject: [PATCH 3/8] Update styles Remove unused styles and use new CSS variables and functions --- dialect/style.css | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/dialect/style.css b/dialect/style.css index 775e461d..ae179e0e 100644 --- a/dialect/style.css +++ b/dialect/style.css @@ -29,7 +29,7 @@ padding-left: 12px; padding-top: 9px; padding-bottom: 9px; - background-color: mix(@accent_bg_color, @window_bg_color, 0.7); + background-color: color-mix(in srgb, var(--accent-bg-color), var(--window-bg-color) 70%); } .pronunciation { @@ -38,8 +38,8 @@ /* Lang Selector */ .search_box { - background: @popover_bg_color; - border-bottom: 1px solid @borders; + background: var(--popover-bg-color); + border-bottom: 1px solid var(--border-color); padding: 6px; } @@ -75,12 +75,12 @@ actionbar.flat > revealer > box { padding: 1px; background-clip: content-box; border-radius: 9999px; - box-shadow: inset 0 0 0 1px @borders; + box-shadow: inset 0 0 0 1px var(--border-color); } .themeswitcher checkbutton.system:checked, .themeswitcher checkbutton.light:checked, .themeswitcher checkbutton.dark:checked { - box-shadow: inset 0 0 0 2px @theme_selected_bg_color; + box-shadow: inset 0 0 0 2px var(--accent-bg-color); } .themeswitcher checkbutton.system { background-image: linear-gradient(to bottom right, #fff 49.99%, #202020 50.01%); @@ -103,31 +103,8 @@ actionbar.flat > revealer > box { } .themeswitcher checkbutton.theme-selector radio:checked { -gtk-icon-source: -gtk-icontheme("object-select-symbolic"); - background-color: @theme_selected_bg_color; - color: @theme_selected_fg_color; -} - -/* Provider settings */ -.provider-feature { - padding: 3px 9px; - border-radius: 12px; - font-weight: bold; -} -.provider-feature:disabled { - opacity: 1; - filter: none; -} -.provider-feature-tts { - color: @green_5; - background-color: alpha(@green_3, .25); -} -.provider-feature-trans { - color: @blue_4; - background-color: alpha(@blue_3, .25); -} -.provider-feature-dic { - color: #ae7b03; - background: alpha(@yellow_5, .25); + background-color: var(--accent-bg-color); + color: var(--accent-fg-color); } /* Voice Button */ From 005cb0b218c7c3ad621af9c33872afc25e427304 Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Mon, 26 Aug 2024 11:47:16 -0500 Subject: [PATCH 4/8] README: Update deps info --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f2155e5..45b2c80f 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ sudo apt-get install dialect - Python 3 `python` - PyGObject `python-gobject` -- GTK4 `gtk4` -- libadwaita (>= 1.4.0) `libadwaita` +- GTK4 (>= 4.16.0) `gtk4` +- libadwaita (>= 1.6.0) `libadwaita` - libsoup (>= 3.0) `libsoup` - libsecret - GStreamer 1.0 `gstreamer` From 058167bbe6258dc554a69d0012e3a6f37027a53e Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Mon, 26 Aug 2024 11:53:08 -0500 Subject: [PATCH 5/8] window: Send toast after copying to clipboard Closes #386 --- dialect/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dialect/window.py b/dialect/window.py index df40e0f1..08b05567 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -777,6 +777,7 @@ def ui_copy(self, _action, _param): dest_text = self.dest_buffer.get_text(self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True) if display := Gdk.Display.get_default(): display.get_clipboard().set(dest_text) + self.send_notification(_("Copied to clipboard"), timeout=1) def ui_paste(self, _action, _param): def on_paste(clipboard: Gdk.Clipboard, result: Gio.AsyncResult): From ba252be6bc45b1417a664d152f30054e524ee634 Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Mon, 26 Aug 2024 11:55:16 -0500 Subject: [PATCH 6/8] window: Disable listen actions by default --- dialect/window.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dialect/window.py b/dialect/window.py index 08b05567..eb8f70aa 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -169,6 +169,7 @@ def setup_actions(self): listen_dest_action = Gio.SimpleAction(name="listen-dest") listen_dest_action.connect("activate", self.ui_dest_voice) + listen_dest_action.props.enabled = False self.add_action(listen_dest_action) suggest_action = Gio.SimpleAction(name="suggest") @@ -186,6 +187,7 @@ def setup_actions(self): listen_src_action = Gio.SimpleAction(name="listen-src") listen_src_action.connect("activate", self.ui_src_voice) + listen_src_action.props.enabled = False self.add_action(listen_src_action) translation_action = Gio.SimpleAction(name="translation") From 5c40ce428872e2f6a86902d6c31cf18fcc975bac Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Fri, 30 Aug 2024 16:02:19 -0500 Subject: [PATCH 7/8] window: Refactor TTS logic - Use `speech` and `listen` as terms instead of `voice`. - Add a dedicated icon for TTS failure --- dialect/dialect.gresource.xml | 3 +- dialect/icons/speakers-broken-symbolic.svg | 24 ++ dialect/meson.build | 2 +- dialect/style.css | 9 +- dialect/widgets/__init__.py | 2 +- .../{voice_button.blp => speech_button.blp} | 6 +- .../{voice_button.py => speech_button.py} | 8 +- dialect/window.blp | 4 +- dialect/window.py | 296 +++++++++--------- 9 files changed, 190 insertions(+), 164 deletions(-) create mode 100644 dialect/icons/speakers-broken-symbolic.svg rename dialect/widgets/{voice_button.blp => speech_button.blp} (85%) rename dialect/widgets/{voice_button.py => speech_button.py} (84%) diff --git a/dialect/dialect.gresource.xml b/dialect/dialect.gresource.xml index 30af8e83..803f73a5 100644 --- a/dialect/dialect.gresource.xml +++ b/dialect/dialect.gresource.xml @@ -10,13 +10,14 @@ widgets/lang_row.ui widgets/lang_selector.ui widgets/provider_preferences.ui + widgets/speech_button.ui widgets/theme_switcher.ui - widgets/voice_button.ui @appstream-path@ icons/settings-symbolic.svg + icons/speakers-broken-symbolic.svg diff --git a/dialect/icons/speakers-broken-symbolic.svg b/dialect/icons/speakers-broken-symbolic.svg new file mode 100644 index 00000000..fcb59a42 --- /dev/null +++ b/dialect/icons/speakers-broken-symbolic.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/dialect/meson.build b/dialect/meson.build index b5510cb2..11c7c209 100644 --- a/dialect/meson.build +++ b/dialect/meson.build @@ -10,8 +10,8 @@ blueprints = custom_target('blueprints', 'widgets/lang_selector.blp', 'widgets/lang_row.blp', 'widgets/provider_preferences.blp', + 'widgets/speech_button.blp', 'widgets/theme_switcher.blp', - 'widgets/voice_button.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], diff --git a/dialect/style.css b/dialect/style.css index ae179e0e..cd3a4f4d 100644 --- a/dialect/style.css +++ b/dialect/style.css @@ -107,14 +107,13 @@ actionbar.flat > revealer > box { color: var(--accent-fg-color); } -/* Voice Button */ -.voice-button { +/* Speech Button */ +.speech-button { padding: 0; } -.voice-button image { +.speech-button image { padding: 5px 9px; } -.voice-button progressbar trough { +.speech-button progressbar trough { min-width: 34px; - max-width: 34px; } diff --git a/dialect/widgets/__init__.py b/dialect/widgets/__init__.py index 83a585aa..565cc12f 100644 --- a/dialect/widgets/__init__.py +++ b/dialect/widgets/__init__.py @@ -4,6 +4,6 @@ from dialect.widgets.lang_selector import LangSelector # noqa from dialect.widgets.provider_preferences import ProviderPreferences # noqa +from dialect.widgets.speech_button import SpeechButton # noqa from dialect.widgets.textview import TextView # noqa from dialect.widgets.theme_switcher import ThemeSwitcher # noqa -from dialect.widgets.voice_button import VoiceButton # noqa diff --git a/dialect/widgets/voice_button.blp b/dialect/widgets/speech_button.blp similarity index 85% rename from dialect/widgets/voice_button.blp rename to dialect/widgets/speech_button.blp index 651b823e..63ad1237 100644 --- a/dialect/widgets/voice_button.blp +++ b/dialect/widgets/speech_button.blp @@ -1,10 +1,10 @@ using Gtk 4.0; using Adw 1; -template $VoiceButton : Button { +template $SpeechButton : Button { tooltip-text: _("Listen"); - styles ["voice-button"] + styles ["speech-button"] child: Overlay { [overlay] @@ -33,7 +33,7 @@ template $VoiceButton : Button { StackPage { name: "error"; child: Image { - icon-name: "dialog-warning-symbolic"; + icon-name: "dialect-speakers-broken-symbolic"; }; } diff --git a/dialect/widgets/voice_button.py b/dialect/widgets/speech_button.py similarity index 84% rename from dialect/widgets/voice_button.py rename to dialect/widgets/speech_button.py index 57b3dabc..a9d5bc91 100644 --- a/dialect/widgets/voice_button.py +++ b/dialect/widgets/speech_button.py @@ -7,9 +7,9 @@ from dialect.define import RES_PATH -@Gtk.Template(resource_path=f"{RES_PATH}/widgets/voice_button.ui") -class VoiceButton(Gtk.Button): - __gtype_name__ = "VoiceButton" +@Gtk.Template(resource_path=f"{RES_PATH}/widgets/speech_button.ui") +class SpeechButton(Gtk.Button): + __gtype_name__ = "SpeechButton" stack: Gtk.Stack = Gtk.Template.Child() # type: ignore progress_bar: Gtk.ProgressBar = Gtk.Template.Child() # type: ignore @@ -30,7 +30,7 @@ def progress(self, fraction: float): self.progress_bar.props.fraction = fraction - def error(self, message: str = _("A network issue has occurred. Retry?")): + def error(self, message: str): self.stack.props.visible_child_name = "error" self.props.tooltip_text = message self.progress_bar.props.visible = False diff --git a/dialect/window.blp b/dialect/window.blp index d78f6310..20129dec 100644 --- a/dialect/window.blp +++ b/dialect/window.blp @@ -392,7 +392,7 @@ template $DialectWindow : Adw.ApplicationWindow { icon-name: "edit-paste-symbolic"; } - $VoiceButton src_voice_btn { + $SpeechButton src_speech_btn { action-name: "win.listen-src"; tooltip-text: _("Listen"); @@ -510,7 +510,7 @@ template $DialectWindow : Adw.ApplicationWindow { icon-name: "document-edit-symbolic"; } - $VoiceButton dest_voice_btn { + $SpeechButton dest_speech_btn { action-name: "win.listen-dest"; tooltip-text: _("Listen"); diff --git a/dialect/window.py b/dialect/window.py index eb8f70aa..dd2158e4 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -5,7 +5,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import logging -from typing import IO, Literal +from typing import IO, Literal, TypedDict from gi.repository import Adw, Gdk, Gio, GLib, GObject, Gst, Gtk @@ -22,7 +22,18 @@ from dialect.settings import Settings from dialect.shortcuts import DialectShortcutsWindow from dialect.utils import find_item_match, first_exclude -from dialect.widgets import LangSelector, TextView, ThemeSwitcher, VoiceButton +from dialect.widgets import LangSelector, SpeechButton, TextView, ThemeSwitcher + + +class _OngoingSpeech(TypedDict): + text: str + lang: str + called_from: Literal["src", "dest"] + + +class _NotificationAction(TypedDict): + label: str + name: str @Gtk.Template(resource_path=f"{RES_PATH}/window.ui") @@ -58,7 +69,7 @@ class DialectWindow(Adw.ApplicationWindow): src_text: TextView = Gtk.Template.Child() # type: ignore clear_btn: Gtk.Button = Gtk.Template.Child() # type: ignore paste_btn: Gtk.Button = Gtk.Template.Child() # type: ignore - src_voice_btn: VoiceButton = Gtk.Template.Child() # type: ignore + src_speech_btn: SpeechButton = Gtk.Template.Child() # type: ignore translate_btn: Gtk.Button = Gtk.Template.Child() # type: ignore dest_box: Gtk.Box = Gtk.Template.Child() # type: ignore @@ -70,7 +81,7 @@ class DialectWindow(Adw.ApplicationWindow): trans_warning: Gtk.Image = Gtk.Template.Child() # type: ignore edit_btn: Gtk.Button = Gtk.Template.Child() # type: ignore copy_btn: Gtk.Button = Gtk.Template.Child() # type: ignore - dest_voice_btn: VoiceButton = Gtk.Template.Child() # type: ignore + dest_speech_btn: SpeechButton = Gtk.Template.Child() # type: ignore actionbar: Gtk.ActionBar = Gtk.Template.Child() # type: ignore src_lang_selector_m: LangSelector = Gtk.Template.Child() # type: ignore @@ -85,8 +96,9 @@ class DialectWindow(Adw.ApplicationWindow): provider: dict[str, BaseProvider | None] = {"trans": None, "tts": None} # Text to speech - current_speech: dict[str, str] = {} - voice_loading = False # tts loading status + speech_provider_failed = False # tts provider loading failed + current_speech: _OngoingSpeech | None = None + speech_loading = False # tts loading status # Preset language values src_langs: list[str] = [] @@ -97,7 +109,6 @@ class DialectWindow(Adw.ApplicationWindow): # Translation-related variables next_trans = {} # for ongoing translation ongoing_trans = False # for ongoing translation - trans_failed = False # for monitoring connectivity issues trans_mistakes: tuple[str | None, str | None] = (None, None) # "mistakes" suggestions # Pronunciations trans_src_pron = None @@ -168,7 +179,7 @@ def setup_actions(self): self.add_action(copy_action) listen_dest_action = Gio.SimpleAction(name="listen-dest") - listen_dest_action.connect("activate", self.ui_dest_voice) + listen_dest_action.connect("activate", self.ui_dest_listen) listen_dest_action.props.enabled = False self.add_action(listen_dest_action) @@ -186,7 +197,7 @@ def setup_actions(self): self.add_action(suggest_cancel_action) listen_src_action = Gio.SimpleAction(name="listen-src") - listen_src_action.connect("activate", self.ui_src_voice) + listen_src_action.connect("activate", self.ui_src_listen) listen_src_action.props.enabled = False self.add_action(listen_src_action) @@ -272,8 +283,6 @@ def setup_translation(self): self.trans_spinner.hide() self.trans_warning.hide() - self.toggle_voice_spinner(True) - def load_translator(self): def on_done(): if not self.provider["trans"]: @@ -453,18 +462,33 @@ def remove_key_and_reload(self, _button): def load_tts(self): def on_done(): - self.download_speech() + self.speech_provider_failed = False + self.src_speech_btn.ready() + self.dest_speech_btn.ready() + self._check_speech_enabled() + + def on_fail(error: ProviderError): + button_text = _("Failed loading the text-to-speech service. Retry?") + toast_text = _("Failed loading the text-to-speech service") + if error.code == ProviderErrorCode.NETWORK: + toast_text = _("Failed loading the text-to-speech service, check for network issues") + + self.speech_provider_failed = True + self.src_speech_btn.error(button_text) + self.dest_speech_btn.error(button_text) + self.send_notification(toast_text) + self._check_speech_enabled() - def on_fail(_error: ProviderError): - self.on_listen_failed() + self.src_speech_btn.loading() + self.dest_speech_btn.loading() # TTS name provider = Settings.get().active_tts # Check if TTS is disabled if provider != "": - self.src_voice_btn.props.visible = True - self.dest_voice_btn.props.visible = True + self.src_speech_btn.props.visible = True + self.dest_speech_btn.props.visible = True # TTS Object self.provider["tts"] = TTS[provider]() @@ -479,40 +503,8 @@ def on_fail(_error: ProviderError): ) else: self.provider["tts"] = None - self.src_voice_btn.props.visible = False - self.dest_voice_btn.props.visible = False - - def on_listen_failed(self): - if not self.provider["tts"]: - return - - self.src_voice_btn.error() - self.dest_voice_btn.error() - - if self.current_speech: - called_from = self.current_speech["called_from"] - action = { - "label": _("Retry"), - "name": "win.listen-src" if called_from == "src" else "win.listen-dest", - } - else: - action = None - - self.send_notification(_("A network issue has occurred. Please try again."), action=action) - - src_text = self.src_buffer.get_text(self.src_buffer.get_start_iter(), self.src_buffer.get_end_iter(), True) - dest_text = self.dest_buffer.get_text(self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True) - - if self.provider["tts"].tts_languages: - self.lookup_action("listen-src").set_enabled( # type: ignore - self.src_lang_selector.selected in self.provider["tts"].tts_languages and src_text != "" - ) - self.lookup_action("listen-dest").set_enabled( # type: ignore - self.dest_lang_selector.selected in self.provider["tts"].tts_languages and dest_text != "" - ) - else: - self.lookup_action("listen-src").props.enabled = src_text != "" # type: ignore - self.lookup_action("listen-dest").props.enabled = dest_text != "" # type: ignore + self.src_speech_btn.props.visible = False + self.dest_speech_btn.props.visible = False def translate(self, text: str, src_lang: str | None, dest_lang: str | None): """ @@ -555,7 +547,7 @@ def send_notification( self, text: str, queue: bool | None = False, - action: dict[str, str] | None = None, + action: _NotificationAction | None = None, timeout=5, priority=Adw.ToastPriority.NORMAL, ): @@ -584,30 +576,32 @@ def toast_dismissed(_toast: Adw.Toast): self.toast.props.priority = priority self.toast_overlay.add_toast(self.toast) - def toggle_voice_spinner(self, active=True): + def _check_speech_enabled(self): if not self.provider["tts"]: return - if active: - self.lookup_action("listen-src").props.enabled = False # type: ignore - self.src_voice_btn.loading() - - self.lookup_action("listen-dest").props.enabled = False # type: ignore - self.dest_voice_btn.loading() - else: - src_text = self.src_buffer.get_text(self.src_buffer.get_start_iter(), self.src_buffer.get_end_iter(), True) - self.lookup_action("listen-src").set_enabled( # type: ignore - self.src_lang_selector.selected in self.provider["tts"].tts_languages and src_text != "" - ) - self.src_voice_btn.ready() + src_playing = dest_playing = False + if self.current_speech: + src_playing = self.current_speech["called_from"] == "src" + dest_playing = self.current_speech["called_from"] == "dest" + + # Check src listen button + self.lookup_action("listen-src").set_enabled( # type: ignore + self.speech_provider_failed + or self.src_lang_selector.selected in self.provider["tts"].tts_languages + and self.src_buffer.get_char_count() != 0 + and not self.speech_loading + and not dest_playing + ) - dest_text = self.dest_buffer.get_text( - self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True - ) - self.lookup_action("listen-dest").set_enabled( # type: ignore - self.dest_lang_selector.selected in self.provider["tts"].tts_languages and dest_text != "" - ) - self.dest_voice_btn.ready() + # Check dest listen button + self.lookup_action("listen-dest").set_enabled( # type: ignore + self.speech_provider_failed + or self.dest_lang_selector.selected in self.provider["tts"].tts_languages + and self.dest_buffer.get_char_count() != 0 + and not self.speech_loading + and not src_playing + ) @Gtk.Template.Callback() def _on_src_lang_changed(self, _obj, _param): @@ -617,7 +611,6 @@ def _on_src_lang_changed(self, _obj, _param): code = self.src_lang_selector.selected dest_code = self.dest_lang_selector.selected - src_text = self.src_buffer.get_text(self.src_buffer.get_start_iter(), self.src_buffer.get_end_iter(), True) if self.provider["trans"].cmp_langs(code, dest_code): # Get first lang from saved src langs that is not current dest @@ -629,10 +622,6 @@ def _on_src_lang_changed(self, _obj, _param): self.dest_lang_selector.selected = valid or "" - # Disable or enable listen function. - if self.provider["tts"] and Settings.get().active_tts != "": - self.lookup_action("listen-src").set_enabled(code in self.provider["tts"].tts_languages and src_text != "") # type: ignore - if code in self.provider["trans"].src_languages: # Update saved src langs list if code in self.src_langs: @@ -649,6 +638,7 @@ def _on_src_lang_changed(self, _obj, _param): self.src_recent_lang_model.set_langs(self.src_langs, auto=True) self._check_switch_enabled() + self._check_speech_enabled() @Gtk.Template.Callback() def _on_dest_lang_changed(self, _obj, _param): @@ -658,7 +648,6 @@ def _on_dest_lang_changed(self, _obj, _param): code = self.dest_lang_selector.selected src_code = self.src_lang_selector.selected - dest_text = self.dest_buffer.get_text(self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True) if self.provider["trans"].cmp_langs(code, src_code): # Get first lang from saved dest langs that is not current src @@ -670,12 +659,6 @@ def _on_dest_lang_changed(self, _obj, _param): self.src_lang_selector.selected = valid or "" - # Disable or enable listen function. - if self.provider["tts"] and Settings.get().active_tts != "": - self.lookup_action("listen-dest").set_enabled( # type: ignore - code in self.provider["tts"].tts_languages and dest_text != "" - ) - # Update saved dest langs list if code in self.dest_langs: # Bring lang to the top @@ -691,6 +674,7 @@ def _on_dest_lang_changed(self, _obj, _param): self.dest_recent_lang_model.set_langs(self.dest_langs) self._check_switch_enabled() + self._check_speech_enabled() def _check_switch_enabled(self): if not self.provider["trans"]: @@ -838,18 +822,18 @@ def ui_suggest_cancel(self, _action, _param): self.before_suggest = None self.dest_text.props.editable = False - def ui_src_voice(self, _action, _param): + def ui_src_listen(self, _action, _param): if self.current_speech: - self._voice_reset() + self._speech_reset() return src_text = self.src_buffer.get_text(self.src_buffer.get_start_iter(), self.src_buffer.get_end_iter(), True) src_language = self.src_lang_selector.selected self._pre_speech(src_text, src_language, "src") - def ui_dest_voice(self, _action, _param): + def ui_dest_listen(self, _action, _param): if self.current_speech: - self._voice_reset() + self._speech_reset() return dest_text = self.dest_buffer.get_text(self.dest_buffer.get_start_iter(), self.dest_buffer.get_end_iter(), True) @@ -858,63 +842,46 @@ def ui_dest_voice(self, _action, _param): def _pre_speech(self, text: str, lang: str, called_from: Literal["src", "dest"]): if text != "": - self.voice_loading = True - self.toggle_voice_spinner(True) - + self.speech_loading = True self.current_speech = {"text": text, "lang": lang, "called_from": called_from} + self._check_speech_enabled() - self.download_speech() + if self.speech_provider_failed: + self.load_tts() + else: + self._download_speech() - def _voice_reset(self): + if called_from == "src": # Show spinner on button + self.src_speech_btn.loading() + else: + self.dest_speech_btn.loading() + elif self.speech_provider_failed: + self.load_tts() + + def _speech_reset(self, set_ready: bool = True): if not self.player: return self.player.set_state(Gst.State.NULL) - self.current_speech = {} - self.src_voice_btn.ready() - self.dest_voice_btn.ready() - - def _on_gst_message(self, _bus, message: Gst.Message): - if message.type == Gst.MessageType.EOS or message.type == Gst.MessageType.ERROR: - if message.type == Gst.MessageType.ERROR: - logging.error("Some error occurred while trying to play.") - - self._voice_reset() - - def _gst_progress_timeout(self): - if not self.player: - return False - - if self.current_speech and self.player.get_state(Gst.CLOCK_TIME_NONE) != Gst.State.NULL: - have_pos, pos = self.player.query_position(Gst.Format.TIME) - have_dur, dur = self.player.query_duration(Gst.Format.TIME) + self.current_speech = None + self.speech_loading = False + self._check_speech_enabled() - if have_pos and have_dur: - if self.current_speech["called_from"] == "src": - self.src_voice_btn.progress(pos / dur) - else: - self.dest_voice_btn.progress(pos / dur) - - return True - - return False + if set_ready: + self.src_speech_btn.ready() + self.dest_speech_btn.ready() - def download_speech(self): + def _download_speech(self): def on_done(file: IO): try: self._play_audio(file.name) file.close() except Exception as exc: logging.error(exc) - self.on_listen_failed() - else: - self.toggle_voice_spinner(False) - finally: - self.voice_loading = False + self._on_speech_failed() - def on_fail(_error: ProviderError): - self.on_listen_failed() - self.toggle_voice_spinner(False) + def on_fail(error: ProviderError): + self._on_speech_failed(error) if not self.provider["tts"]: return @@ -922,9 +889,29 @@ def on_fail(_error: ProviderError): if self.current_speech: lang: str = self.provider["tts"].denormalize_lang(self.current_speech["lang"]) # type: ignore self.provider["tts"].speech(self.current_speech["text"], lang, on_done, on_fail) - else: - self.toggle_voice_spinner(False) - self.voice_loading = False + + def _on_speech_failed(self, error: ProviderError | None = None): + text = _("Text-to-Speech failed") + action: _NotificationAction | None = None + + if error and error.code == ProviderErrorCode.NETWORK: + text = _("Text-to-Speech failed, check for network issues") + + if self.current_speech: + called_from = self.current_speech["called_from"] + action = { + "label": _("Retry"), + "name": "win.listen-src" if called_from == "src" else "win.listen-dest", + } + + button_text = _("Text-to-Speech failed. Retry?") + if called_from == "src": + self.src_speech_btn.error(button_text) + else: + self.dest_speech_btn.error(button_text) + + self.send_notification(text, action=action) + self._speech_reset(False) def _play_audio(self, path: str): if not self.player: @@ -935,6 +922,34 @@ def _play_audio(self, path: str): self.player.set_state(Gst.State.PLAYING) GLib.timeout_add(50, self._gst_progress_timeout) + def _on_gst_message(self, _bus, message: Gst.Message): + if message.type == Gst.MessageType.EOS or message.type == Gst.MessageType.ERROR: + if message.type == Gst.MessageType.ERROR: + logging.error("Some error occurred while trying to play.") + self._speech_reset() + + def _gst_progress_timeout(self): + if not self.player: + return False + + if self.current_speech and self.player.get_state(Gst.CLOCK_TIME_NONE) != Gst.State.NULL: + have_pos, pos = self.player.query_position(Gst.Format.TIME) + have_dur, dur = self.player.query_duration(Gst.Format.TIME) + + if have_pos and have_dur: + if self.current_speech["called_from"] == "src": + self.src_speech_btn.progress(pos / dur) + else: + self.dest_speech_btn.progress(pos / dur) + + if self.speech_loading: + self.speech_loading = False + self._check_speech_enabled() + + return True + + return False + @Gtk.Template.Callback() def _on_key_event(self, _ctrl, keyval: int, _keycode: int, state: Gdk.ModifierType): """Called on self.win_key_ctrlr::key-pressed signal""" @@ -990,12 +1005,7 @@ def on_src_text_changed(self, buffer: Gtk.TextBuffer): sensitive = char_count != 0 self.lookup_action("translation").props.enabled = sensitive # type: ignore self.lookup_action("clear").props.enabled = sensitive # type: ignore - if not self.voice_loading and self.provider["tts"]: - self.lookup_action("listen-src").set_enabled( # type: ignore - self.src_lang_selector.selected in self.provider["tts"].tts_languages and sensitive - ) - elif not self.voice_loading and not self.provider["tts"]: - self.lookup_action("listen-src").props.enabled = sensitive # type: ignore + self._check_speech_enabled() def on_dest_text_changed(self, buffer: Gtk.TextBuffer): if not self.provider["trans"]: @@ -1006,12 +1016,7 @@ def on_dest_text_changed(self, buffer: Gtk.TextBuffer): self.lookup_action("suggest").set_enabled( # type: ignore ProviderFeature.SUGGESTIONS in self.provider["trans"].features and sensitive ) - if not self.voice_loading and self.provider["tts"]: - self.lookup_action("listen-dest").set_enabled( # type: ignore - self.dest_lang_selector.selected in self.provider["tts"].tts_languages and sensitive - ) - elif not self.voice_loading and self.provider["tts"] is not None and not self.provider["tts"].tts_languages: - self.lookup_action("listen-dest").props.enabled = sensitive # type: ignore + self._check_speech_enabled() def user_action_ended(self, _buffer): if Settings.get().live_translation: @@ -1144,11 +1149,8 @@ def on_translation_success(self, translation: Translation): def on_translation_fail(self, error: ProviderError): if not self.next_trans: self.translation_finish() - self.trans_warning.props.visible = True - self.lookup_action("copy").props.enabled = False # type: ignore - self.lookup_action("listen-src").props.enabled = False # type: ignore - self.lookup_action("listen-dest").props.enabled = False # type: ignore + self.ongoing_trans = False match error.code: case ProviderErrorCode.NETWORK: From dd012c688af0f69b4e2f65ddc904cb9e7a649642 Mon Sep 17 00:00:00 2001 From: Rafael Mardojai CM Date: Sun, 1 Sep 2024 18:37:45 -0500 Subject: [PATCH 8/8] window: Use `Gtk.Widget.add_tick_callback` for speech button progress This way the progressbar animation should be smoother --- dialect/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dialect/window.py b/dialect/window.py index dd2158e4..b3dbedde 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -920,7 +920,7 @@ def _play_audio(self, path: str): uri = "file://" + path self.player.set_property("uri", uri) self.player.set_state(Gst.State.PLAYING) - GLib.timeout_add(50, self._gst_progress_timeout) + self.add_tick_callback(self._gst_progress_timeout) def _on_gst_message(self, _bus, message: Gst.Message): if message.type == Gst.MessageType.EOS or message.type == Gst.MessageType.ERROR: @@ -928,7 +928,7 @@ def _on_gst_message(self, _bus, message: Gst.Message): logging.error("Some error occurred while trying to play.") self._speech_reset() - def _gst_progress_timeout(self): + def _gst_progress_timeout(self, _widget, _clock): if not self.player: return False