From 05014feb40939307d6d95309fbd5128897f4b71c Mon Sep 17 00:00:00 2001 From: Mufeed Ali Date: Tue, 21 Jan 2025 23:56:01 +0530 Subject: [PATCH 1/3] feat: Add spell-checking support with GtkSource and libspelling integration Yes, it's needlessly complicated as is tradition :) Also fixes #399 --- build-aux/flatpak/app.drey.Dialect.Devel.json | 11 +++++ dialect/main.py | 2 + dialect/style.css | 6 +++ dialect/widgets/textview.py | 8 +-- dialect/window.py | 49 ++++++++++++++++++- 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/build-aux/flatpak/app.drey.Dialect.Devel.json b/build-aux/flatpak/app.drey.Dialect.Devel.json index b050a7c6..a6b51bb6 100644 --- a/build-aux/flatpak/app.drey.Dialect.Devel.json +++ b/build-aux/flatpak/app.drey.Dialect.Devel.json @@ -30,6 +30,17 @@ } ] }, + { + "name" : "libspelling", + "buildsystem" : "meson", + "sources" : [ + { + "type" : "git", + "url" : "https://gitlab.gnome.org/GNOME/libspelling.git", + "branch" : "main" + } + ] + }, { "name" : "dialect", "buildsystem" : "meson", diff --git a/dialect/main.py b/dialect/main.py index df660cdc..5b682d51 100755 --- a/dialect/main.py +++ b/dialect/main.py @@ -16,6 +16,8 @@ gi.require_version("Adw", "1") gi.require_version("Secret", "1") gi.require_version("Soup", "3.0") + gi.require_version("Spelling", "1") + gi.require_version("GtkSource", "5") from gi.repository import Adw, Gio, GLib, Gst except ImportError or ValueError: diff --git a/dialect/style.css b/dialect/style.css index cc29b714..ef9e00b7 100644 --- a/dialect/style.css +++ b/dialect/style.css @@ -117,3 +117,9 @@ actionbar.flat > revealer > box { .speech-button progressbar trough { min-width: 34px; } + +/* Set GtkSourceView Caret Color */ +.dialect-sourceview { + caret-color: var(--view-fg-color); +} + diff --git a/dialect/widgets/textview.py b/dialect/widgets/textview.py index 05272480..b067211c 100644 --- a/dialect/widgets/textview.py +++ b/dialect/widgets/textview.py @@ -3,12 +3,12 @@ # Copyright 2023 Libretto # SPDX-License-Identifier: GPL-3.0-or-later -from gi.repository import Gdk, GObject, Gtk +from gi.repository import Gdk, GObject, Gtk, GtkSource from dialect.settings import Settings -class TextView(Gtk.TextView): +class TextView(GtkSource.View): __gtype_name__ = "TextView" activate_mod: bool = GObject.Property(type=bool, default=True) # type: ignore @@ -35,7 +35,9 @@ def __init__(self, **kwargs): 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) + widget_style_context = self.get_style_context() + widget_style_context.add_provider(self._font_css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) + widget_style_context.add_class("dialect-sourceview") @GObject.Signal() def activate(self): ... diff --git a/dialect/window.py b/dialect/window.py index ae6011f4..32677d64 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -5,9 +5,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later import logging +import re from typing import Literal, TypedDict -from gi.repository import Adw, Gdk, Gio, GLib, GObject, Gst, Gtk +from gi.repository import Adw, Gdk, Gio, GLib, GObject, Gst, Gtk, Spelling from dialect.asyncio import background_task from dialect.define import APP_ID, PROFILE, RES_PATH, TRANS_NUMBER @@ -220,6 +221,7 @@ def setup(self): self.setup_selectors() self.setup_translation() + self.setup_spell_checking() self.set_help_overlay(DialectShortcutsWindow()) # Load translator @@ -245,6 +247,25 @@ def setup(self): lambda s, _k: self.src_text.set_property("activate_mod", not bool(s.translate_accel_value)), ) + def setup_spell_checking(self): + # Enable spell-checking + self.spell_checker: Spelling.Checker = Spelling.Checker.get_default() + spell_checker_adapter = Spelling.TextBufferAdapter.new(self.src_buffer, self.spell_checker) + spell_checker_menu = spell_checker_adapter.get_menu_model() + self.src_text.set_extra_menu(spell_checker_menu) + self.src_text.insert_action_group("spelling", spell_checker_adapter) + spell_checker_adapter.set_enabled(True) + + # Collect the spell checking provider's supported languages. + spell_checker_supported_languages_glist = self.spell_checker.get_provider().list_languages() + self.spell_checker_supported_languages = {} + for lang_object in spell_checker_supported_languages_glist: + lang_code = lang_object.get_code() + lang_base_code = re.split("_|-", lang_code)[0] + if lang_base_code not in self.spell_checker_supported_languages: + self.spell_checker_supported_languages[lang_base_code] = [] + self.spell_checker_supported_languages[lang_base_code].append(lang_code) + def setup_selectors(self): def lang_names_func(code: str): return self.provider["trans"].get_lang_name(code) # type: ignore @@ -673,6 +694,30 @@ def _check_switch_enabled(self): and self.dest_lang_selector.selected in self.provider["trans"].src_languages ) + def _pick_spell_checking_language(self, lang_code: str): + # Try and set the correct language if available. + lang_base_code = lang_code.split("-")[0] + default_user_lang_code = self.spell_checker.get_provider().get_default_code() + + spell_checker_lang_code = default_user_lang_code # Default to the user's preference + + if lang_base_code in self.spell_checker_supported_languages: + if lang_code in self.spell_checker_supported_languages[lang_base_code]: + # The language is matched exactly. + spell_checker_lang_code = lang_code + elif lang_code.replace("-", "_") in self.spell_checker_supported_languages[lang_base_code]: + # The language code needs underscores within the provider. + spell_checker_lang_code = lang_code.replace("-", "_") + elif default_user_lang_code.startswith(lang_base_code): + # Default to user preference if at least the base language code matches. + # Probably the most common scenario: en -> en_US. + spell_checker_lang_code = default_user_lang_code + else: + # Try to set the language even if the country code doesn't match, to the first available one. + spell_checker_lang_code = self.spell_checker_supported_languages[lang_base_code][0] + + self.spell_checker.set_language(spell_checker_lang_code) + """ User interface functions """ @@ -920,7 +965,7 @@ def _on_src_text_changed(self, buffer: Gtk.TextBuffer): if self.provider["trans"].chars_limit == -1: # -1 means unlimited self.char_counter.props.label = "" else: - self.char_counter.props.label = f'{str(char_count)}/{self.provider["trans"].chars_limit}' + self.char_counter.props.label = f"{str(char_count)}/{self.provider['trans'].chars_limit}" if char_count >= self.provider["trans"].chars_limit: self.send_notification(_("{} characters limit reached!").format(self.provider["trans"].chars_limit)) From 2dedf71d4d59e60c8e09f7468894dd9db10c356f Mon Sep 17 00:00:00 2001 From: Mufeed Ali Date: Wed, 22 Jan 2025 00:11:40 +0530 Subject: [PATCH 2/3] fix(flatpak): Update blueprint and add libspelling to non-devel manifest --- build-aux/flatpak/app.drey.Dialect.Devel.json | 41 +++++++++--------- build-aux/flatpak/app.drey.Dialect.json | 42 ++++++++++++------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/build-aux/flatpak/app.drey.Dialect.Devel.json b/build-aux/flatpak/app.drey.Dialect.Devel.json index a6b51bb6..c2cd17b9 100644 --- a/build-aux/flatpak/app.drey.Dialect.Devel.json +++ b/build-aux/flatpak/app.drey.Dialect.Devel.json @@ -1,10 +1,10 @@ { - "app-id" : "app.drey.Dialect.Devel", - "runtime" : "org.gnome.Platform", - "runtime-version" : "master", - "sdk" : "org.gnome.Sdk", - "command" : "dialect", - "finish-args" : [ + "id": "app.drey.Dialect.Devel", + "runtime": "org.gnome.Platform", + "runtime-version": "master", + "sdk": "org.gnome.Sdk", + "command": "dialect", + "finish-args": [ "--share=network", "--share=ipc", "--device=dri", @@ -13,10 +13,10 @@ "--socket=pulseaudio", "--own-name=app.drey.Dialect.Devel.SearchProvider" ], - "cleanup" : [ + "cleanup": [ "*blueprint*" ], - "modules" : [ + "modules": [ "pypi-dependencies.json", { "name": "blueprint", @@ -25,31 +25,30 @@ { "type": "git", "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler", - "tag": "v0.10.0", - "commit": "2a39a16391122af2f3d812e478c1c1398c98b972" + "branch": "main" } ] }, { - "name" : "libspelling", - "buildsystem" : "meson", - "sources" : [ + "name": "libspelling", + "buildsystem": "meson", + "sources": [ { - "type" : "git", - "url" : "https://gitlab.gnome.org/GNOME/libspelling.git", - "branch" : "main" + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/libspelling.git", + "branch": "main" } ] }, { - "name" : "dialect", - "buildsystem" : "meson", + "name": "dialect", + "buildsystem": "meson", "run-tests": true, "config-opts": ["-Dprofile=development"], - "sources" : [ + "sources": [ { - "type" : "dir", - "path" : "../../." + "type": "dir", + "path": "../../." } ] } diff --git a/build-aux/flatpak/app.drey.Dialect.json b/build-aux/flatpak/app.drey.Dialect.json index dc532164..f919c8ab 100644 --- a/build-aux/flatpak/app.drey.Dialect.json +++ b/build-aux/flatpak/app.drey.Dialect.json @@ -1,10 +1,10 @@ { - "app-id" : "app.drey.Dialect", - "runtime" : "org.gnome.Platform", - "runtime-version" : "47", - "sdk" : "org.gnome.Sdk", - "command" : "dialect", - "finish-args" : [ + "id": "app.drey.Dialect", + "runtime": "org.gnome.Platform", + "runtime-version": "47", + "sdk": "org.gnome.Sdk", + "command": "dialect", + "finish-args": [ "--share=network", "--share=ipc", "--device=dri", @@ -12,7 +12,7 @@ "--socket=wayland", "--socket=pulseaudio" ], - "modules" : [ + "modules": [ "pypi-dependencies.json", { "name": "blueprint", @@ -24,19 +24,31 @@ { "type": "git", "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler", - "tag": "v0.10.0", - "commit": "2a39a16391122af2f3d812e478c1c1398c98b972" + "tag": "v0.16.0", + "commit": "04ef0944db56ab01307a29aaa7303df6067cb3c0" } ] }, { - "name" : "dialect", - "builddir" : true, - "buildsystem" : "meson", - "sources" : [ + "name": "libspelling", + "buildsystem": "meson", + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/libspelling.git", + "tag": "0.4.5", + "commit": "ab64511edd5cf53c8a81a51e2883d6f69e585f64" + } + ] + }, + { + "name": "dialect", + "builddir": true, + "buildsystem": "meson", + "sources": [ { - "type" : "dir", - "path" : "../../." + "type": "dir", + "path": "../../." } ] } From 5a0eadd03c3f18fc555768d40438b6d690cc0cbd Mon Sep 17 00:00:00 2001 From: Mufeed Ali Date: Fri, 24 Jan 2025 00:46:38 +0530 Subject: [PATCH 3/3] fix(window): Fix missing language pick call --- dialect/window.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dialect/window.py b/dialect/window.py index 32677d64..c30debbe 100644 --- a/dialect/window.py +++ b/dialect/window.py @@ -257,9 +257,8 @@ def setup_spell_checking(self): spell_checker_adapter.set_enabled(True) # Collect the spell checking provider's supported languages. - spell_checker_supported_languages_glist = self.spell_checker.get_provider().list_languages() self.spell_checker_supported_languages = {} - for lang_object in spell_checker_supported_languages_glist: + for lang_object in self.spell_checker.get_provider().list_languages(): lang_code = lang_object.get_code() lang_base_code = re.split("_|-", lang_code)[0] if lang_base_code not in self.spell_checker_supported_languages: @@ -705,9 +704,9 @@ def _pick_spell_checking_language(self, lang_code: str): if lang_code in self.spell_checker_supported_languages[lang_base_code]: # The language is matched exactly. spell_checker_lang_code = lang_code - elif lang_code.replace("-", "_") in self.spell_checker_supported_languages[lang_base_code]: + elif (code := lang_code.replace("-", "_")) in self.spell_checker_supported_languages[lang_base_code]: # The language code needs underscores within the provider. - spell_checker_lang_code = lang_code.replace("-", "_") + spell_checker_lang_code = code elif default_user_lang_code.startswith(lang_base_code): # Default to user preference if at least the base language code matches. # Probably the most common scenario: en -> en_US. @@ -1040,6 +1039,9 @@ def _on_src_lang_changed(self, *_args): self.src_langs.pop() self.src_langs.insert(0, code) + # Try to set the language in the spell checker. + self._pick_spell_checking_language(code) + # Rewrite recent langs self.src_recent_lang_model.set_langs(self.src_langs, auto=True)