diff --git a/build-aux/flatpak/app.drey.Dialect.Devel.json b/build-aux/flatpak/app.drey.Dialect.Devel.json index b050a7c6..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,20 +25,30 @@ { "type": "git", "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler", - "tag": "v0.10.0", - "commit": "2a39a16391122af2f3d812e478c1c1398c98b972" + "branch": "main" } ] }, { - "name" : "dialect", - "buildsystem" : "meson", + "name": "libspelling", + "buildsystem": "meson", + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/libspelling.git", + "branch": "main" + } + ] + }, + { + "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": "../../." } ] } 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..c30debbe 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,24 @@ 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. + self.spell_checker_supported_languages = {} + 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: + 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 +693,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 (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 = 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. + 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 +964,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)) @@ -995,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)