mirror of
https://github.com/1j01/textual-paint.git
synced 2024-09-11 15:56:03 +03:00
Show accelerators/shortcuts in menus
This commit is contained in:
parent
365eb6e4c7
commit
4cbafea3cb
@ -19,16 +19,20 @@
|
||||
"fudgedness",
|
||||
"Haha",
|
||||
"hkey",
|
||||
"humbnail",
|
||||
"𝗟𝙇",
|
||||
"llpaper",
|
||||
"modd",
|
||||
"Odhner",
|
||||
"Playscii",
|
||||
"pycache",
|
||||
"pypixelart",
|
||||
"Shft",
|
||||
"stransi",
|
||||
"STRINGTABLE",
|
||||
"ufeff",
|
||||
"undos",
|
||||
"ustom",
|
||||
"vkey"
|
||||
]
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
from typing import Optional
|
||||
import json
|
||||
import re
|
||||
|
||||
translations = {}
|
||||
|
||||
@ -28,14 +30,77 @@ try:
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
def get(base_language_str: str) -> str:
|
||||
def get(base_language_str: str, *interpolations: str) -> str:
|
||||
"""Get a localized string."""
|
||||
if base_language_str in translations:
|
||||
return translations[base_language_str]
|
||||
else:
|
||||
def find_localization(base_language_str: str):
|
||||
amp_index = index_of_hotkey(base_language_str)
|
||||
if amp_index > -1:
|
||||
without_hotkey = remove_hotkey(base_language_str)
|
||||
if without_hotkey in translations:
|
||||
hotkey_def = base_language_str[amp_index:amp_index + 2]
|
||||
if translations[without_hotkey].upper().find(hotkey_def.upper()) > -1:
|
||||
return translations[without_hotkey]
|
||||
else:
|
||||
if has_hotkey(translations[without_hotkey]):
|
||||
# window.console && console.warn(`Localization has differing accelerator (hotkey) hint: '${translations[without_hotkey]}' vs '${base_language_str}'`);
|
||||
# @TODO: detect differing accelerator more generally
|
||||
return f"{remove_hotkey(translations[without_hotkey])} ({hotkey_def})"
|
||||
return f"{translations[without_hotkey]} ({hotkey_def})"
|
||||
if base_language_str in translations:
|
||||
return translations[base_language_str]
|
||||
# special case for menu items, where we need to split the string into two parts to translate them separately
|
||||
# (maybe only because of how I preprocessed the localization files)
|
||||
parts = base_language_str.split("\t")
|
||||
if len(parts) == 2:
|
||||
parts[0] = find_localization(parts[0])
|
||||
return "\t".join(parts)
|
||||
|
||||
# special handling for ellipsis
|
||||
if base_language_str[-3:] == "...":
|
||||
return find_localization(base_language_str[:-3]) + "..."
|
||||
|
||||
if base_language_str not in untranslated:
|
||||
untranslated.add(base_language_str)
|
||||
# append to untranslated strings file
|
||||
with open("localization/untranslated.txt", "a") as f:
|
||||
f.write(base_language_str + "\n")
|
||||
return base_language_str
|
||||
|
||||
def interpolate(text: str, interpolations: list):
|
||||
for i in range(len(interpolations)):
|
||||
text = text.replace(f"%{i + 1}", interpolations[i])
|
||||
return text
|
||||
|
||||
return interpolate(find_localization(base_language_str), interpolations)
|
||||
|
||||
# & defines accelerators (hotkeys) in menus and buttons and things, which get underlined in the UI.
|
||||
# & can be escaped by doubling it, e.g. "&Taskbar && Start Menu"
|
||||
def index_of_hotkey(text: str) -> int:
|
||||
# Returns the index of the ampersand that defines a hotkey, or -1 if not present.
|
||||
# The space here handles beginning-of-string matching and counteracts the offset for the [^&] so it acts like a negative lookbehind
|
||||
m = re.search(r"[^&]&[^&\s]", f" {text}")
|
||||
return m.start() if m else -1
|
||||
|
||||
def has_hotkey(text: str) -> bool:
|
||||
return index_of_hotkey(text) != -1
|
||||
|
||||
def remove_hotkey(text: str) -> str:
|
||||
text = re.sub(r"\s?\(&.\)", "", text)
|
||||
text = re.sub(r"([^&]|^)&([^&\s])", r"\1\2", text)
|
||||
return text
|
||||
|
||||
def markup_hotkey(text: str) -> str:
|
||||
"""Returns Rich API-compatible markup underlining the hotkey if present."""
|
||||
index = index_of_hotkey(text)
|
||||
if index == -1:
|
||||
return text
|
||||
else:
|
||||
return text[:index] + f"[u]{text[index + 1]}[/u]" + text[index + 2:]
|
||||
|
||||
def get_hotkey(text: str) -> Optional[str]:
|
||||
"""Returns the hotkey if present."""
|
||||
index = index_of_hotkey(text)
|
||||
if index == -1:
|
||||
return None
|
||||
else:
|
||||
return text[index + 1]
|
||||
|
@ -23,21 +23,14 @@ def index_of_hotkey(text):
|
||||
def has_hotkey(text):
|
||||
return index_of_hotkey(text) != -1
|
||||
|
||||
|
||||
def remove_hotkey(text):
|
||||
text = re.sub(r"\s?\(&.\)", "", text)
|
||||
text = re.sub(r"([^&]|^)&([^&\s])", r"\1\2", text)
|
||||
return text
|
||||
|
||||
|
||||
def remove_ellipsis(string):
|
||||
return string.replace("...", "")
|
||||
|
||||
|
||||
def only_unique(value, index, self):
|
||||
return self.index(value) == index
|
||||
|
||||
|
||||
def get_strings(lang):
|
||||
rc_files = glob.glob(f"{os.path.dirname(__file__)}/{lang}/**/*.rc", recursive=True)
|
||||
for rc_file in rc_files:
|
||||
|
17
menus.py
17
menus.py
@ -9,6 +9,7 @@ from textual.geometry import Offset, Region, Size
|
||||
from textual.reactive import var, reactive
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Static
|
||||
from localization.i18n import markup_hotkey
|
||||
|
||||
def to_snake_case(name):
|
||||
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
@ -81,6 +82,20 @@ class Menu(Container):
|
||||
self.parent_menu_item = parent_menu_item
|
||||
self.add_class("menu_popup")
|
||||
self.styles.offset = (parent_menu_item.region.x, parent_menu_item.region.y + parent_menu_item.region.height)
|
||||
|
||||
# Find the widest label
|
||||
max_width = 0
|
||||
for item in self.items:
|
||||
if isinstance(item, MenuItem):
|
||||
if len(item.label.plain) > max_width:
|
||||
max_width = len(item.label.plain)
|
||||
# Split on tab character and align the shortcuts
|
||||
for item in self.items:
|
||||
if isinstance(item, MenuItem):
|
||||
markup_parts = item.label.markup.split("\t")
|
||||
plain_parts = item.label.plain.split("\t")
|
||||
if len(markup_parts) > 1:
|
||||
item.label = markup_parts[0] + " " * (max_width - len(plain_parts[0])) + markup_parts[1]
|
||||
|
||||
def close(self):
|
||||
for item in self.items:
|
||||
@ -103,7 +118,7 @@ class MenuItem(Button):
|
||||
|
||||
def __init__(self, name: str, action = None, id: str = None, submenu = None, grayed = False, **kwargs) -> None:
|
||||
"""Initialize a menu item."""
|
||||
super().__init__(name, **kwargs)
|
||||
super().__init__(markup_hotkey(name), **kwargs)
|
||||
self.add_class("menu_item")
|
||||
self.disabled = grayed
|
||||
self.action = action
|
||||
|
92
paint.py
92
paint.py
@ -1162,69 +1162,69 @@ class PaintApp(App):
|
||||
yield Header()
|
||||
with Container(id="paint"):
|
||||
yield MenuBar([
|
||||
MenuItem(_("File"), submenu=Menu([
|
||||
MenuItem(_("New"), self.action_new, 57600),
|
||||
MenuItem(_("Open"), self.action_open, 57601),
|
||||
MenuItem(_("Save"), self.action_save, 57603),
|
||||
MenuItem(_("Save As"), self.action_save_as, 57604),
|
||||
MenuItem(_("&File"), submenu=Menu([
|
||||
MenuItem(_("&New\tCtrl+N"), self.action_new, 57600),
|
||||
MenuItem(_("&Open...\tCtrl+O"), self.action_open, 57601),
|
||||
MenuItem(_("&Save\tCtrl+S"), self.action_save, 57603),
|
||||
MenuItem(_("Save &As..."), self.action_save_as, 57604),
|
||||
Separator(),
|
||||
MenuItem(_("Print Preview"), self.action_print_preview, 57609),
|
||||
MenuItem(_("Page Setup..."), self.action_page_setup, 57605),
|
||||
MenuItem(_("Print..."), self.action_print, 57607),
|
||||
MenuItem(_("Print Pre&view"), self.action_print_preview, 57609),
|
||||
MenuItem(_("Page Se&tup..."), self.action_page_setup, 57605),
|
||||
MenuItem(_("&Print...\tCtrl+P"), self.action_print, 57607),
|
||||
Separator(),
|
||||
MenuItem(_("Send..."), self.action_send, 37662),
|
||||
MenuItem(_("S&end..."), self.action_send, 37662),
|
||||
Separator(),
|
||||
MenuItem(_("Set As Wallpaper (Tiled)"), self.action_set_as_wallpaper_tiled, 57677),
|
||||
MenuItem(_("Set As Wallpaper (Centered)"), self.action_set_as_wallpaper_centered, 57675),
|
||||
MenuItem(_("Set As &Wallpaper (Tiled)"), self.action_set_as_wallpaper_tiled, 57677),
|
||||
MenuItem(_("Set As Wa&llpaper (Centered)"), self.action_set_as_wallpaper_centered, 57675),
|
||||
Separator(),
|
||||
MenuItem(_("Recent File"), self.action_recent_file, 57616, grayed=True),
|
||||
Separator(),
|
||||
MenuItem(_("Exit"), self.action_exit, 57665),
|
||||
MenuItem(_("E&xit\tAlt+F4"), self.action_exit, 57665),
|
||||
])),
|
||||
MenuItem(_("Edit"), submenu=Menu([
|
||||
MenuItem(_("Undo"), self.action_undo, 57643),
|
||||
MenuItem(_("Repeat"), self.action_redo, 57644),
|
||||
MenuItem(_("&Edit"), submenu=Menu([
|
||||
MenuItem(_("&Undo\tCtrl+Z"), self.action_undo, 57643),
|
||||
MenuItem(_("&Repeat\tF4"), self.action_redo, 57644),
|
||||
Separator(),
|
||||
MenuItem(_("Cut"), self.action_cut, 57635),
|
||||
MenuItem(_("Copy"), self.action_copy, 57634),
|
||||
MenuItem(_("Paste"), self.action_paste, 57637),
|
||||
MenuItem(_("Clear Selection"), self.action_clear_selection, 57632),
|
||||
MenuItem(_("Select All"), self.action_select_all, 57642),
|
||||
MenuItem(_("Cu&t\tCtrl+X"), self.action_cut, 57635),
|
||||
MenuItem(_("&Copy\tCtrl+C"), self.action_copy, 57634),
|
||||
MenuItem(_("&Paste\tCtrl+V"), self.action_paste, 57637),
|
||||
MenuItem(_("C&lear Selection\tDel"), self.action_clear_selection, 57632),
|
||||
MenuItem(_("Select &All\tCtrl+A"), self.action_select_all, 57642),
|
||||
Separator(),
|
||||
MenuItem(_("Copy To..."), self.action_copy_to, 37663),
|
||||
MenuItem(_("Paste From..."), self.action_paste_from, 37664),
|
||||
MenuItem(_("C&opy To..."), self.action_copy_to, 37663),
|
||||
MenuItem(_("Paste &From..."), self.action_paste_from, 37664),
|
||||
])),
|
||||
MenuItem(_("View"), submenu=Menu([
|
||||
MenuItem(_("Tool Box"), self.action_toggle_tools_box, 59415),
|
||||
MenuItem(_("Color Box"), self.action_toggle_colors_box, 59416),
|
||||
MenuItem(_("Status Bar"), self.action_toggle_status_bar, 59393),
|
||||
MenuItem(_("Text Toolbar"), self.action_text_toolbar, 37678),
|
||||
MenuItem(_("&View"), submenu=Menu([
|
||||
MenuItem(_("&Tool Box\tCtrl+T"), self.action_toggle_tools_box, 59415),
|
||||
MenuItem(_("&Color Box\tCtrl+L"), self.action_toggle_colors_box, 59416),
|
||||
MenuItem(_("&Status Bar"), self.action_toggle_status_bar, 59393),
|
||||
MenuItem(_("T&ext Toolbar"), self.action_text_toolbar, 37678),
|
||||
Separator(),
|
||||
MenuItem(_("Zoom"), submenu=Menu([
|
||||
MenuItem(_("Normal Size"), self.action_normal_size, 37670),
|
||||
MenuItem(_("Large Size"), self.action_large_size, 37671),
|
||||
MenuItem(_("Custom..."), self.action_custom_zoom, 37672),
|
||||
MenuItem(_("&Zoom"), submenu=Menu([
|
||||
MenuItem(_("&Normal Size\tCtrl+PgUp"), self.action_normal_size, 37670),
|
||||
MenuItem(_("&Large Size\tCtrl+PgDn"), self.action_large_size, 37671),
|
||||
MenuItem(_("C&ustom..."), self.action_custom_zoom, 37672),
|
||||
Separator(),
|
||||
MenuItem(_("Show Grid"), self.action_show_grid, 37677),
|
||||
MenuItem(_("Show Thumbnail"), self.action_show_thumbnail, 37676),
|
||||
MenuItem(_("Show &Grid\tCtrl+G"), self.action_show_grid, 37677),
|
||||
MenuItem(_("Show T&humbnail"), self.action_show_thumbnail, 37676),
|
||||
])),
|
||||
MenuItem(_("View Bitmap"), self.action_view_bitmap, 37673),
|
||||
MenuItem(_("&View Bitmap\tCtrl+F"), self.action_view_bitmap, 37673),
|
||||
])),
|
||||
MenuItem(_("Image"), submenu=Menu([
|
||||
MenuItem(_("Flip/Rotate..."), self.action_flip_rotate, 37680),
|
||||
MenuItem(_("Stretch/Skew..."), self.action_stretch_skew, 37681),
|
||||
MenuItem(_("Invert Colors"), self.action_invert_colors, 37682),
|
||||
MenuItem(_("Attributes..."), self.action_attributes, 37683),
|
||||
MenuItem(_("Clear Image"), self.action_clear_image, 37684),
|
||||
MenuItem(_("Draw Opaque"), self.action_draw_opaque, 6868),
|
||||
MenuItem(_("&Image"), submenu=Menu([
|
||||
MenuItem(_("&Flip/Rotate...\tCtrl+R"), self.action_flip_rotate, 37680),
|
||||
MenuItem(_("&Stretch/Skew...\tCtrl+W"), self.action_stretch_skew, 37681),
|
||||
MenuItem(_("&Invert Colors\tCtrl+I"), self.action_invert_colors, 37682),
|
||||
MenuItem(_("&Attributes...\tCtrl+E"), self.action_attributes, 37683),
|
||||
MenuItem(_("&Clear Image\tCtrl+Shft+N"), self.action_clear_image, 37684),
|
||||
MenuItem(_("&Draw Opaque"), self.action_draw_opaque, 6868),
|
||||
])),
|
||||
MenuItem(_("Colors"), submenu=Menu([
|
||||
MenuItem(_("Edit Colors..."), self.action_edit_colors, 6869),
|
||||
MenuItem(_("&Colors"), submenu=Menu([
|
||||
MenuItem(_("&Edit Colors..."), self.action_edit_colors, 6869),
|
||||
])),
|
||||
MenuItem(_("Help"), submenu=Menu([
|
||||
MenuItem(_("Help Topics"), self.action_help_topics, 57670),
|
||||
MenuItem(_("&Help"), submenu=Menu([
|
||||
MenuItem(_("&Help Topics"), self.action_help_topics, 57670),
|
||||
Separator(),
|
||||
MenuItem(_("About Paint"), self.action_about_paint, 57664),
|
||||
MenuItem(_("&About Paint"), self.action_about_paint, 57664),
|
||||
])),
|
||||
])
|
||||
yield Container(
|
||||
|
Loading…
Reference in New Issue
Block a user