diff --git a/NEWS.md b/NEWS.md index 1ab03181..6bc3df00 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ ### In Development +- Make text views font size configurable and temporally modify it with shortcuts or scroll +- Fix search provider always running in the background +- Allow opening language selector with keyboard shortcuts ### 2.2.0 - Refactored providers implementation to ease future features diff --git a/data/resources/window.blp b/data/resources/window.blp index d0dbd263..8e08221e 100644 --- a/data/resources/window.blp +++ b/data/resources/window.blp @@ -328,16 +328,13 @@ template $DialectWindow : Adw.ApplicationWindow { ScrolledWindow { vexpand: true; - TextView src_text { - wrap-mode: word_char; + $TextView src_text { left-margin: 9; right-margin: 9; top-margin: 9; bottom-margin: 9; - EventControllerKey src_key_ctrlr { - key-pressed => $_update_trans_button(); - } + activate => $_on_src_activated(); } styles [ @@ -461,9 +458,8 @@ template $DialectWindow : Adw.ApplicationWindow { ScrolledWindow { vexpand: true; - TextView dest_text { + $TextView dest_text { editable: false; - wrap-mode: word_char; left-margin: 9; right-margin: 9; top-margin: 9; diff --git a/dialect/widgets/__init__.py b/dialect/widgets/__init__.py index d2f9663a..fba982cb 100644 --- a/dialect/widgets/__init__.py +++ b/dialect/widgets/__init__.py @@ -4,4 +4,5 @@ from dialect.widgets.lang_selector import LangSelector # noqa from dialect.widgets.provider_preferences import ProviderPreferences # noqa +from dialect.widgets.textview import TextView # noqa from dialect.widgets.theme_switcher import ThemeSwitcher # noqa diff --git a/dialect/widgets/textview.py b/dialect/widgets/textview.py new file mode 100644 index 00000000..55abbb45 --- /dev/null +++ b/dialect/widgets/textview.py @@ -0,0 +1,96 @@ +# Copyright 2023 Mufeed Ali +# Copyright 2023 Rafael Mardojai CM +# Copyright 2023 Libretto +# SPDX-License-Identifier: GPL-3.0-or-later + +from gi.repository import Gdk, GObject, Gtk + + +class TextView(Gtk.TextView): + __gtype_name__ = 'TextView' + __gsignals__ = {'activate': (GObject.SIGNAL_RUN_FIRST, None, ())} + + activate_mod: bool = GObject.Property(type=bool, default=True) + """If activation requieres the mod key""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Set word/char text wrapping + self.props.wrap_mode = Gtk.WrapMode.WORD_CHAR + + # Key press controller + key_ctrlr = Gtk.EventControllerKey() + key_ctrlr.connect('key-pressed', self._on_key_pressed) + self.add_controller(key_ctrlr) + + # Scroll controller + scroll_ctrlr = Gtk.EventControllerScroll.new(Gtk.EventControllerScrollFlags.VERTICAL) + scroll_ctrlr.connect('scroll', self._on_scroll) + self.add_controller(scroll_ctrlr) + + # Custom font + self._font_size = int( + Gtk.Settings.get_default().get_property('gtk-font-name').split()[1] + ) + self._font_css_provider = Gtk.CssProvider() + + # Add font CSS provider + self.get_style_context().add_provider( + self._font_css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER + ) + + @GObject.Property(type=int) + def font_size(self) -> int: + return self._font_size + + @font_size.setter + def font_size(self, value: int): + # Save value + self._font_size = value + # Update CSS + self._font_css_provider.load_from_data(f'textview {{ font-size: { str(value) }pt; }}') + + def font_size_inc(self): + self.font_size += 5 + + def font_size_dec(self): + new_size = self.font_size - 5 + if new_size >= 6: + self.font_size = new_size + + def _on_key_pressed(self, _button, keyval, _keycode, state): + modifiers = state & Gtk.accelerator_get_default_mod_mask() + control_mask = Gdk.ModifierType.CONTROL_MASK + enter_keys = (Gdk.KEY_Return, Gdk.KEY_KP_Enter) + + # Activate with mod key pressed + if control_mask == modifiers: + if keyval in enter_keys: + if self.activate_mod: + self.emit('activate') + return Gdk.EVENT_STOP + + # Activate without mod key pressed + elif keyval in enter_keys: + if not self.activate_mod: + self.emit('activate') + return Gdk.EVENT_STOP + + return Gdk.EVENT_PROPAGATE + + def _on_scroll(self, ctrl: Gtk.EventControllerScroll, _dx: float, dy: float): + state = ctrl.get_current_event_state() + + # If Control modifier is pressed + if state == Gdk.ModifierType.CONTROL_MASK: + if dy > 0: + self.font_size_dec() + else: + self.font_size_inc() + + # Stop propagation + return Gdk.EVENT_STOP + + # Propagate event (scrolled window, etc) + return Gdk.EVENT_PROPAGATE diff --git a/dialect/window.py b/dialect/window.py index af038623..a27e7e94 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -18,7 +18,7 @@ from dialect.session import Session, ResponseError from dialect.settings import Settings from dialect.shortcuts import DialectShortcutsWindow -from dialect.widgets import LangSelector, ThemeSwitcher +from dialect.widgets import LangSelector, TextView, ThemeSwitcher @Gtk.Template(resource_path=f'{RES_PATH}/window.ui') @@ -51,7 +51,7 @@ class DialectWindow(Adw.ApplicationWindow): mistakes: Gtk.Revealer = Gtk.Template.Child() mistakes_label: Gtk.Label = Gtk.Template.Child() char_counter: Gtk.Label = Gtk.Template.Child() - src_text: Gtk.TextView = Gtk.Template.Child() + src_text: TextView = Gtk.Template.Child() clear_btn: Gtk.Button = Gtk.Template.Child() paste_btn: Gtk.Button = Gtk.Template.Child() src_voice_btn: Gtk.Button = Gtk.Template.Child() @@ -60,7 +60,7 @@ class DialectWindow(Adw.ApplicationWindow): dest_box: Gtk.Box = Gtk.Template.Child() dest_pron_revealer: Gtk.Revealer = Gtk.Template.Child() dest_pron_label: Gtk.Label = Gtk.Template.Child() - dest_text: Gtk.TextView = Gtk.Template.Child() + dest_text: TextView = Gtk.Template.Child() dest_toolbar_stack: Gtk.Stack = Gtk.Template.Child() trans_spinner: Gtk.Spinner = Gtk.Template.Child() trans_warning: Gtk.Image = Gtk.Template.Child() @@ -75,7 +75,6 @@ class DialectWindow(Adw.ApplicationWindow): toast: Adw.Toast | None = None # for notification management toast_overlay: Adw.ToastOverlay = Gtk.Template.Child() - src_key_ctrlr: Gtk.EventControllerKey = Gtk.Template.Child() win_key_ctrlr: Gtk.EventControllerKey = Gtk.Template.Child() # Window Launch Tracking @@ -123,14 +122,7 @@ def __init__(self, text, langs, **kwargs): bus = self.player.get_bus() bus.add_signal_watch() bus.connect('message', self.on_gst_message) - self.player_event = threading.Event() # An event for letting us know when Gst is done playing - - # Text buffers font size - self.font_css_provider = Gtk.CssProvider() - if Settings.get().custom_default_font_size: - self.font_size = Settings.get().default_font_size - else: - self.font_size = int(Gtk.Settings.get_default().get_property('gtk-font-name').split()[1]) + self.player_event = threading.Event() # An event for letting us know when Gst is done playing # Setup window self.setup_actions() @@ -234,17 +226,20 @@ def setup(self): Settings.get().connect('translator-changed', self._on_active_provider_changed, 'trans') Settings.get().connect('tts-changed', self._on_active_provider_changed, 'tts') - # Connect text buffers to font css provider - self.src_text.get_style_context().add_provider( - self.font_css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_USER - ) - self.dest_text.get_style_context().add_provider( - self.font_css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_USER + # Bind text views font size + self.src_text.bind_property('font-size', self.dest_text, 'font-size', GObject.BindingFlags.BIDIRECTIONAL) + + # Set initial saved text view font size + if Settings.get().custom_default_font_size: + font_size = Settings.get().default_font_size + self.set_font_size(font_size) + + # Set src textview mod key requirement + self.src_text.activate_mod = not bool(Settings.get().translate_accel_value) + Settings.get().connect( + 'changed::translate-accel', + lambda s, _k: self.src_text.set_property('activate_mod', not bool(s.translate_accel_value)) ) - # Set text view font size - self.set_font_size(self.font_size) def setup_selectors(self): # Languages models @@ -810,16 +805,13 @@ def ui_clear(self, _action, _param): self.src_buffer.emit('end-user-action') def set_font_size(self, size): - self.font_size = size - self.font_css_provider.load_from_data(f'textview {{ font-size: { str(size) }pt; }}') + self.src_text.font_size = size def ui_font_size_inc(self, _action, _param): - self.set_font_size(self.font_size + 5) + self.src_text.font_size_inc() def ui_font_size_dec(self, _action, _param): - new_size = self.font_size - 5 - if new_size >= 6: - self.set_font_size(new_size) + self.src_text.font_size_dec() def ui_copy(self, _action, _param): dest_text = self.dest_buffer.get_text( @@ -1017,29 +1009,10 @@ def _on_key_event(self, _button, keyval, _keycode, state): return Gdk.EVENT_PROPAGATE @Gtk.Template.Callback() - def _update_trans_button(self, _button, keyval, _keycode, state): - """ Called on self.src_key_ctrlr::key-pressed signal - Starts translation when user presses the translate keyboard shorcut - """ - modifiers = state & Gtk.accelerator_get_default_mod_mask() - - control_mask = Gdk.ModifierType.CONTROL_MASK - enter_keys = (Gdk.KEY_Return, Gdk.KEY_KP_Enter) - + def _on_src_activated(self, _texview): + """ Called on self.src_text::active signal """ if not Settings.get().live_translation: - if control_mask == modifiers: - if keyval in enter_keys: - if not Settings.get().translate_accel_value: - self.translation() - return Gdk.EVENT_STOP - return Gdk.EVENT_PROPAGATE - elif keyval in enter_keys: - if Settings.get().translate_accel_value: - self.translation() - return Gdk.EVENT_STOP - return Gdk.EVENT_PROPAGATE - - return Gdk.EVENT_PROPAGATE + self.translation() @Gtk.Template.Callback() def _on_mistakes_clicked(self, _button, _data):