Skip to content

Commit

Permalink
Create custom TextView widget (#363)
Browse files Browse the repository at this point in the history
  • Loading branch information
mufeedali authored Dec 11, 2023
2 parents ac86491 + 4ff9afc commit c79a6c4
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 57 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 3 additions & 7 deletions data/resources/window.blp
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions dialect/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
96 changes: 96 additions & 0 deletions dialect/widgets/textview.py
Original file line number Diff line number Diff line change
@@ -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
73 changes: 23 additions & 50 deletions dialect/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit c79a6c4

Please sign in to comment.