From 48be824a50aaa6f6e9081dfae0ef5513ef334637 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Tue, 9 Jun 2026 13:09:42 +0200 Subject: [PATCH] settings: emoji search, Manual tab fix, readable infobox (v1.9.5) - Emoji picker: SearchEntry at top filters all categories via unicodedata.name() in real time; category view hides while searching, restores on clear - Manual tab: add pkg_dir/MANUAL.md to _app_paths() search list so it works in both venv and deb installs (MANUAL.md deployed alongside the package) - bt-infobox CSS: replace @theme_selected_bg_color (saturated blue) with a neutral 5% mix of fg/bg so banner text is always readable Co-Authored-By: Claude Sonnet 4.6 --- linux/CHANGELOG.md | 15 +++++++ linux/blitztext/__init__.py | 2 +- linux/blitztext/gtksettings.py | 72 +++++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/linux/CHANGELOG.md b/linux/CHANGELOG.md index c2b8f56..078c2b6 100644 --- a/linux/CHANGELOG.md +++ b/linux/CHANGELOG.md @@ -9,6 +9,21 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py). ## [Unreleased] +## [1.9.5] - 2026-06-09 + +### Added +- **Emoji picker search.** A search field at the top of the emoji picker filters + all categories in real time using Unicode character names (e.g. "fire", "dog", + "heart"). Typing hides the category bar and shows matching results; clearing + restores the category view. + +### Fixed +- **Manual tab now shows content.** `MANUAL.md` is copied next to the package + module so the Manual tab finds it in both venv and deb installs. +- **Info banner no longer bright blue.** The `.bt-infobox` background now uses + a neutral 5 % tint of the foreground colour instead of the theme accent + colour, so text stays readable on any theme. + ## [1.9.4] - 2026-06-09 ### Changed diff --git a/linux/blitztext/__init__.py b/linux/blitztext/__init__.py index bd3db2b..a9d619f 100644 --- a/linux/blitztext/__init__.py +++ b/linux/blitztext/__init__.py @@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host (not in a container) so it can type into any application via xdotool. """ -__version__ = "1.9.4" +__version__ = "1.9.5" diff --git a/linux/blitztext/gtksettings.py b/linux/blitztext/gtksettings.py index 8d7a84a..a4e451b 100644 --- a/linux/blitztext/gtksettings.py +++ b/linux/blitztext/gtksettings.py @@ -16,6 +16,7 @@ import os import sys import threading import time +import unicodedata from pathlib import Path import gi @@ -531,7 +532,7 @@ def _app_paths() -> dict[str, list[Path]]: return { "changelog": [linux_dir / "CHANGELOG.md", Path("/opt/blitztext/CHANGELOG.md")], "license": [repo_dir / "LICENSE", Path("/usr/share/doc/blitztext/copyright")], - "manual": [repo_dir / "MANUAL.md", Path("/opt/blitztext/MANUAL.md")], + "manual": [pkg_dir / "MANUAL.md", repo_dir / "MANUAL.md", Path("/opt/blitztext/MANUAL.md")], } @@ -588,8 +589,8 @@ class SettingsDialog: } .bt-infobox { border-radius: 6px; - border: 1px solid mix(@theme_selected_bg_color, @theme_bg_color, 0.55); - background-color: mix(@theme_selected_bg_color, @theme_base_color, 0.07); + border: 1px solid mix(@theme_fg_color, @theme_bg_color, 0.75); + background-color: mix(@theme_fg_color, @theme_bg_color, 0.05); padding: 4px; margin-bottom: 10px; } @@ -819,7 +820,26 @@ class SettingsDialog: pop = Gtk.Popover(relative_to=anchor) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - # -- category tab bar ------------------------------------------------ + # -- Search bar ------------------------------------------------------- + search = Gtk.SearchEntry() + search.set_placeholder_text("Search emoji…") + search.set_margin_top(6); search.set_margin_bottom(4) + search.set_margin_start(6); search.set_margin_end(6) + vbox.pack_start(search, False, False, 0) + vbox.pack_start(Gtk.Separator(), False, False, 0) + + # Flat emoji list (all categories) used by search + all_emojis: list[str] = [e for _, _, es in _EMOJI_CATEGORIES for e in es] + + def _keywords(e: str) -> str: + try: + return unicodedata.name(e[0], "").lower() + except Exception: + return "" + + # -- Category view ---------------------------------------------------- + cat_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + stack = Gtk.Stack() stack.set_transition_type(Gtk.StackTransitionType.NONE) stack.set_size_request(420, 230) @@ -829,7 +849,7 @@ class SettingsDialog: cat_bar.set_margin_start(4); cat_bar.set_margin_end(4) first_name: str | None = None - active_btn: list[Gtk.Button] = [None] # mutable cell + active_btn: list[Gtk.Button] = [None] def _select(name: str, btn: Gtk.Button) -> None: stack.set_visible_child_name(name) @@ -839,7 +859,6 @@ class SettingsDialog: active_btn[0] = btn for cat_emoji, cat_name, emojis in _EMOJI_CATEGORIES: - # Build scrollable emoji grid for this category flow = Gtk.FlowBox() flow.set_max_children_per_line(10) flow.set_selection_mode(Gtk.SelectionMode.NONE) @@ -855,7 +874,6 @@ class SettingsDialog: sw.add(flow) stack.add_named(sw, cat_name) - # Category tab button cb = Gtk.Button(label=cat_emoji) cb.set_relief(Gtk.ReliefStyle.NONE) cb.set_tooltip_text(cat_name) @@ -874,12 +892,46 @@ class SettingsDialog: cat_scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER) cat_scroll.set_min_content_height(44) cat_scroll.add(cat_bar) + cat_container.pack_start(cat_scroll, False, False, 0) + cat_container.pack_start(Gtk.Separator(), False, False, 0) + cat_container.pack_start(stack, True, True, 0) - vbox.pack_start(cat_scroll, False, False, 0) - vbox.pack_start(Gtk.Separator(), False, False, 0) - vbox.pack_start(stack, True, True, 0) + # -- Search results view ---------------------------------------------- + search_sw = Gtk.ScrolledWindow() + search_sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + search_sw.set_size_request(420, 274) + search_flow = Gtk.FlowBox() + search_flow.set_max_children_per_line(10) + search_flow.set_selection_mode(Gtk.SelectionMode.NONE) + search_flow.set_margin_top(4); search_flow.set_margin_bottom(4) + search_flow.set_margin_start(4); search_flow.set_margin_end(4) + search_sw.add(search_flow) + + def _on_search(_e: Gtk.SearchEntry) -> None: + query = search.get_text().strip().lower() + for child in search_flow.get_children(): + search_flow.remove(child) + if query: + cat_container.set_visible(False) + search_sw.set_visible(True) + for emoji in all_emojis: + if query in _keywords(emoji) or query in emoji: + btn = Gtk.Button(label=emoji) + btn.set_relief(Gtk.ReliefStyle.NONE) + btn.connect("clicked", lambda _b, e=emoji: (entry.set_text(e), pop.popdown())) + search_flow.add(btn) + search_flow.show_all() + else: + search_sw.set_visible(False) + cat_container.set_visible(True) + + search.connect("search-changed", _on_search) + + vbox.pack_start(cat_container, True, True, 0) + vbox.pack_start(search_sw, True, True, 0) pop.add(vbox) pop.show_all() + search_sw.set_visible(False) # hide search results until user types # ===== Engines ========================================================== def _build_engines(self, page: Gtk.Box) -> None: