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 <noreply@anthropic.com>
This commit is contained in:
parent
2fe6632269
commit
48be824a50
@ -9,6 +9,21 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [1.9.4] - 2026-06-09
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@ -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.
|
(not in a container) so it can type into any application via xdotool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "1.9.4"
|
__version__ = "1.9.5"
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@ -531,7 +532,7 @@ def _app_paths() -> dict[str, list[Path]]:
|
|||||||
return {
|
return {
|
||||||
"changelog": [linux_dir / "CHANGELOG.md", Path("/opt/blitztext/CHANGELOG.md")],
|
"changelog": [linux_dir / "CHANGELOG.md", Path("/opt/blitztext/CHANGELOG.md")],
|
||||||
"license": [repo_dir / "LICENSE", Path("/usr/share/doc/blitztext/copyright")],
|
"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 {
|
.bt-infobox {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid mix(@theme_selected_bg_color, @theme_bg_color, 0.55);
|
border: 1px solid mix(@theme_fg_color, @theme_bg_color, 0.75);
|
||||||
background-color: mix(@theme_selected_bg_color, @theme_base_color, 0.07);
|
background-color: mix(@theme_fg_color, @theme_bg_color, 0.05);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
@ -819,7 +820,26 @@ class SettingsDialog:
|
|||||||
pop = Gtk.Popover(relative_to=anchor)
|
pop = Gtk.Popover(relative_to=anchor)
|
||||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
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 = Gtk.Stack()
|
||||||
stack.set_transition_type(Gtk.StackTransitionType.NONE)
|
stack.set_transition_type(Gtk.StackTransitionType.NONE)
|
||||||
stack.set_size_request(420, 230)
|
stack.set_size_request(420, 230)
|
||||||
@ -829,7 +849,7 @@ class SettingsDialog:
|
|||||||
cat_bar.set_margin_start(4); cat_bar.set_margin_end(4)
|
cat_bar.set_margin_start(4); cat_bar.set_margin_end(4)
|
||||||
|
|
||||||
first_name: str | None = None
|
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:
|
def _select(name: str, btn: Gtk.Button) -> None:
|
||||||
stack.set_visible_child_name(name)
|
stack.set_visible_child_name(name)
|
||||||
@ -839,7 +859,6 @@ class SettingsDialog:
|
|||||||
active_btn[0] = btn
|
active_btn[0] = btn
|
||||||
|
|
||||||
for cat_emoji, cat_name, emojis in _EMOJI_CATEGORIES:
|
for cat_emoji, cat_name, emojis in _EMOJI_CATEGORIES:
|
||||||
# Build scrollable emoji grid for this category
|
|
||||||
flow = Gtk.FlowBox()
|
flow = Gtk.FlowBox()
|
||||||
flow.set_max_children_per_line(10)
|
flow.set_max_children_per_line(10)
|
||||||
flow.set_selection_mode(Gtk.SelectionMode.NONE)
|
flow.set_selection_mode(Gtk.SelectionMode.NONE)
|
||||||
@ -855,7 +874,6 @@ class SettingsDialog:
|
|||||||
sw.add(flow)
|
sw.add(flow)
|
||||||
stack.add_named(sw, cat_name)
|
stack.add_named(sw, cat_name)
|
||||||
|
|
||||||
# Category tab button
|
|
||||||
cb = Gtk.Button(label=cat_emoji)
|
cb = Gtk.Button(label=cat_emoji)
|
||||||
cb.set_relief(Gtk.ReliefStyle.NONE)
|
cb.set_relief(Gtk.ReliefStyle.NONE)
|
||||||
cb.set_tooltip_text(cat_name)
|
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_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER)
|
||||||
cat_scroll.set_min_content_height(44)
|
cat_scroll.set_min_content_height(44)
|
||||||
cat_scroll.add(cat_bar)
|
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)
|
# -- Search results view ----------------------------------------------
|
||||||
vbox.pack_start(Gtk.Separator(), False, False, 0)
|
search_sw = Gtk.ScrolledWindow()
|
||||||
vbox.pack_start(stack, True, True, 0)
|
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.add(vbox)
|
||||||
pop.show_all()
|
pop.show_all()
|
||||||
|
search_sw.set_visible(False) # hide search results until user types
|
||||||
|
|
||||||
# ===== Engines ==========================================================
|
# ===== Engines ==========================================================
|
||||||
def _build_engines(self, page: Gtk.Box) -> None:
|
def _build_engines(self, page: Gtk.Box) -> None:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user