Show menu item descriptions in the status bar

This commit is contained in:
Isaiah Odhner 2023-04-24 00:43:50 -04:00
parent d62b320ede
commit c12036880e
2 changed files with 67 additions and 41 deletions

View File

@ -4,6 +4,7 @@ from textual import events
from textual.containers import Container from textual.containers import Container
from textual.reactive import var from textual.reactive import var
from textual.widgets import Button, Static from textual.widgets import Button, Static
from textual.message import Message
from rich.text import Text from rich.text import Text
from localization.i18n import markup_hotkey, get_hotkey, get_direction from localization.i18n import markup_hotkey, get_hotkey, get_direction
@ -16,6 +17,10 @@ def to_snake_case(name: str) -> str:
class Menu(Container): class Menu(Container):
"""A menu widget. Note that menus can't be reused in multiple places.""" """A menu widget. Note that menus can't be reused in multiple places."""
class Closed(Message):
"""Sent when a menu is closed."""
pass
items = var([]) items = var([])
focus_index = var(0) focus_index = var(0)
@ -171,6 +176,7 @@ class Menu(Container):
item.submenu.close() item.submenu.close()
if not isinstance(self, MenuBar): if not isinstance(self, MenuBar):
self.display = False self.display = False
self.post_message(Menu.Closed())
class MenuBar(Menu): class MenuBar(Menu):
"""A menu bar widget.""" """A menu bar widget."""
@ -183,11 +189,19 @@ class MenuBar(Menu):
class MenuItem(Button): class MenuItem(Button):
"""A menu item widget.""" """A menu item widget."""
class Hovered(Message):
"""Message sent when the mouse hovers over a menu item."""
def __init__(self, menu_item: 'MenuItem') -> None:
"""Initialize the hover message."""
super().__init__()
self.menu_item = menu_item
def __init__(self, def __init__(self,
name: str, name: str,
action: Callable[[], None] | None = None, action: Callable[[], None] | None = None,
id: str | int | None = None, id: str | int | None = None,
submenu: Menu | None = None, submenu: Menu | None = None,
description: str | None = None,
grayed: bool = False, grayed: bool = False,
**kwargs: Any **kwargs: Any
) -> None: ) -> None:
@ -197,12 +211,16 @@ class MenuItem(Button):
self.disabled = grayed self.disabled = grayed
self.action = action self.action = action
self.submenu = submenu self.submenu = submenu
self.description = description
if isinstance(id, str): if isinstance(id, str):
self.id = id self.id = id
elif id: elif id:
self.id = "rc_" + str(id) self.id = "rc_" + str(id)
else: else:
self.id = "menu_item_" + to_snake_case(name) self.id = "menu_item_" + to_snake_case(name)
def on_enter(self, event: events.Enter) -> None:
self.post_message(self.Hovered(self))
mid_line = "" * 100 mid_line = "" * 100

View File

@ -1742,69 +1742,69 @@ class PaintApp(App[None]):
with Container(id="paint"): with Container(id="paint"):
yield MenuBar([ yield MenuBar([
MenuItem(_("&File"), submenu=Menu([ MenuItem(_("&File"), submenu=Menu([
MenuItem(_("&New\tCtrl+N"), self.action_new, 57600), MenuItem(_("&New\tCtrl+N"), self.action_new, 57600, description=_("Creates a new document.")),
MenuItem(_("&Open...\tCtrl+O"), self.action_open, 57601), MenuItem(_("&Open...\tCtrl+O"), self.action_open, 57601, description=_("Opens an existing document.")),
MenuItem(_("&Save\tCtrl+S"), self.action_save, 57603), MenuItem(_("&Save\tCtrl+S"), self.action_save, 57603, description=_("Saves the active document.")),
MenuItem(_("Save &As..."), self.action_save_as, 57604), MenuItem(_("Save &As..."), self.action_save_as, 57604, description=_("Saves the active document with a new name.")),
Separator(), Separator(),
MenuItem(_("Print Pre&view"), self.action_print_preview, 57609, grayed=True), MenuItem(_("Print Pre&view"), self.action_print_preview, 57609, grayed=True, description=_("Displays full pages.")),
MenuItem(_("Page Se&tup..."), self.action_page_setup, 57605, grayed=True), MenuItem(_("Page Se&tup..."), self.action_page_setup, 57605, grayed=True, description=_("Changes the page layout.")),
MenuItem(_("&Print...\tCtrl+P"), self.action_print, 57607, grayed=True), MenuItem(_("&Print...\tCtrl+P"), self.action_print, 57607, grayed=True, description=_("Prints the active document and sets printing options.")),
Separator(), Separator(),
MenuItem(_("S&end..."), self.action_send, 37662, grayed=True), MenuItem(_("S&end..."), self.action_send, 37662, grayed=True, description=_("Sends a picture by using mail or fax.")),
Separator(), Separator(),
MenuItem(_("Set As &Wallpaper (Tiled)"), self.action_set_as_wallpaper_tiled, 57677, grayed=True), MenuItem(_("Set As &Wallpaper (Tiled)"), self.action_set_as_wallpaper_tiled, 57677, grayed=True, description=_("Tiles this bitmap as the desktop wallpaper.")),
MenuItem(_("Set As Wa&llpaper (Centered)"), self.action_set_as_wallpaper_centered, 57675, grayed=True), MenuItem(_("Set As Wa&llpaper (Centered)"), self.action_set_as_wallpaper_centered, 57675, grayed=True, description=_("Centers this bitmap as the desktop wallpaper.")),
Separator(), Separator(),
MenuItem(_("Recent File"), self.action_recent_file, 57616, grayed=True), MenuItem(_("Recent File"), self.action_recent_file, 57616, grayed=True, description=_("Opens this document.")),
Separator(), Separator(),
# MenuItem(_("E&xit\tAlt+F4"), self.action_exit, 57665), # MenuItem(_("E&xit\tAlt+F4"), self.action_exit, 57665, description=_("Quits Paint.")),
MenuItem(_("E&xit\tCtrl+Q"), self.action_exit, 57665), MenuItem(_("E&xit\tCtrl+Q"), self.action_exit, 57665, description=_("Quits Paint.")),
])), ])),
MenuItem(_("&Edit"), submenu=Menu([ MenuItem(_("&Edit"), submenu=Menu([
MenuItem(_("&Undo\tCtrl+Z"), self.action_undo, 57643), MenuItem(_("&Undo\tCtrl+Z"), self.action_undo, 57643, description=_("Undoes the last action.")),
MenuItem(_("&Repeat\tF4"), self.action_redo, 57644), MenuItem(_("&Repeat\tF4"), self.action_redo, 57644, description=_("Redoes the previously undone action.")),
Separator(), Separator(),
MenuItem(_("Cu&t\tCtrl+X"), self.action_cut, 57635, grayed=True), MenuItem(_("Cu&t\tCtrl+X"), self.action_cut, 57635, grayed=True, description=_("Cuts the selection and puts it on the Clipboard.")),
MenuItem(_("&Copy\tCtrl+C"), self.action_copy, 57634, grayed=True), MenuItem(_("&Copy\tCtrl+C"), self.action_copy, 57634, grayed=True, description=_("Copies the selection and puts it on the Clipboard.")),
MenuItem(_("&Paste\tCtrl+V"), self.action_paste, 57637, grayed=True), MenuItem(_("&Paste\tCtrl+V"), self.action_paste, 57637, grayed=True, description=_("Inserts the contents of the Clipboard.")),
MenuItem(_("C&lear Selection\tDel"), self.action_clear_selection, 57632), MenuItem(_("C&lear Selection\tDel"), self.action_clear_selection, 57632, description=_("Deletes the selection.")),
MenuItem(_("Select &All\tCtrl+A"), self.action_select_all, 57642), MenuItem(_("Select &All\tCtrl+A"), self.action_select_all, 57642, description=_("Selects everything.")),
Separator(), Separator(),
MenuItem(_("C&opy To..."), self.action_copy_to, 37663, grayed=True), MenuItem(_("C&opy To..."), self.action_copy_to, 37663, grayed=True, description=_("Copies the selection to a file.")),
MenuItem(_("Paste &From..."), self.action_paste_from, 37664, grayed=True), MenuItem(_("Paste &From..."), self.action_paste_from, 37664, grayed=True, description=_("Pastes a file into the selection.")),
])), ])),
MenuItem(_("&View"), submenu=Menu([ MenuItem(_("&View"), submenu=Menu([
MenuItem(_("&Tool Box\tCtrl+T"), self.action_toggle_tools_box, 59415), MenuItem(_("&Tool Box\tCtrl+T"), self.action_toggle_tools_box, 59415, description=_("Shows or hides the tool box.")),
MenuItem(_("&Color Box\tCtrl+L"), self.action_toggle_colors_box, 59416), MenuItem(_("&Color Box\tCtrl+L"), self.action_toggle_colors_box, 59416, description=_("Shows or hides the color box.")),
MenuItem(_("&Status Bar"), self.action_toggle_status_bar, 59393), MenuItem(_("&Status Bar"), self.action_toggle_status_bar, 59393, description=_("Shows or hides the status bar.")),
MenuItem(_("T&ext Toolbar"), self.action_text_toolbar, 37678, grayed=True), MenuItem(_("T&ext Toolbar"), self.action_text_toolbar, 37678, grayed=True, description=_("Shows or hides the text toolbar.")),
Separator(), Separator(),
MenuItem(_("&Zoom"), submenu=Menu([ MenuItem(_("&Zoom"), submenu=Menu([
MenuItem(_("&Normal Size\tCtrl+PgUp"), self.action_normal_size, 37670), MenuItem(_("&Normal Size\tCtrl+PgUp"), self.action_normal_size, 37670, description=_("Zooms the picture to 100%.")),
MenuItem(_("&Large Size\tCtrl+PgDn"), self.action_large_size, 37671), MenuItem(_("&Large Size\tCtrl+PgDn"), self.action_large_size, 37671, description=_("Zooms the picture to 400%.")),
MenuItem(_("C&ustom..."), self.action_custom_zoom, 37672), MenuItem(_("C&ustom..."), self.action_custom_zoom, 37672, description=_("Zooms the picture.")),
Separator(), Separator(),
MenuItem(_("Show &Grid\tCtrl+G"), self.action_show_grid, 37677, grayed=True), MenuItem(_("Show &Grid\tCtrl+G"), self.action_show_grid, 37677, grayed=True, description=_("Shows or hides the grid.")),
MenuItem(_("Show T&humbnail"), self.action_show_thumbnail, 37676, grayed=True), MenuItem(_("Show T&humbnail"), self.action_show_thumbnail, 37676, grayed=True, description=_("Shows or hides the thumbnail view of the picture.")),
])), ])),
MenuItem(_("&View Bitmap\tCtrl+F"), self.action_view_bitmap, 37673, grayed=True), MenuItem(_("&View Bitmap\tCtrl+F"), self.action_view_bitmap, 37673, grayed=True, description=_("Displays the entire picture.")),
])), ])),
MenuItem(_("&Image"), submenu=Menu([ MenuItem(_("&Image"), submenu=Menu([
MenuItem(_("&Flip/Rotate...\tCtrl+R"), self.action_flip_rotate, 37680, grayed=True), MenuItem(_("&Flip/Rotate...\tCtrl+R"), self.action_flip_rotate, 37680, grayed=True, description=_("Flips or rotates the picture or a selection.")),
MenuItem(_("&Stretch/Skew...\tCtrl+W"), self.action_stretch_skew, 37681, grayed=True), MenuItem(_("&Stretch/Skew...\tCtrl+W"), self.action_stretch_skew, 37681, grayed=True, description=_("Stretches or skews the picture or a selection.")),
MenuItem(_("&Invert Colors\tCtrl+I"), self.action_invert_colors, 37682, grayed=True), MenuItem(_("&Invert Colors\tCtrl+I"), self.action_invert_colors, 37682, grayed=True, description=_("Inverts the colors of the picture or a selection.")),
MenuItem(_("&Attributes...\tCtrl+E"), self.action_attributes, 37683, grayed=True), MenuItem(_("&Attributes...\tCtrl+E"), self.action_attributes, 37683, grayed=True, description=_("Changes the attributes of the picture.")),
MenuItem(_("&Clear Image\tCtrl+Shft+N"), self.action_clear_image, 37684, grayed=True), MenuItem(_("&Clear Image\tCtrl+Shft+N"), self.action_clear_image, 37684, grayed=True, description=_("Clears the picture or selection.")),
MenuItem(_("&Draw Opaque"), self.action_draw_opaque, 6868, grayed=True), MenuItem(_("&Draw Opaque"), self.action_draw_opaque, 6868, grayed=True, description=_("Makes the current selection either opaque or transparent.")),
])), ])),
MenuItem(_("&Colors"), submenu=Menu([ MenuItem(_("&Colors"), submenu=Menu([
MenuItem(_("&Edit Colors..."), self.action_edit_colors, 6869), MenuItem(_("&Edit Colors..."), self.action_edit_colors, 6869, description=_("Creates a new color.")),
])), ])),
MenuItem(_("&Help"), submenu=Menu([ MenuItem(_("&Help"), submenu=Menu([
MenuItem(_("&Help Topics"), self.action_help_topics, 57670), MenuItem(_("&Help Topics"), self.action_help_topics, 57670, description=_("Displays Help for the current task or command.")),
Separator(), Separator(),
MenuItem(_("&About Paint"), self.action_about_paint, 57664), MenuItem(_("&About Paint"), self.action_about_paint, 57664, description=_("Displays program information, version number, and copyright.")),
])), ])),
]) ])
yield Container( yield Container(
@ -2468,6 +2468,14 @@ class PaintApp(App[None]):
else: else:
self.directory_tree_selected_path = None self.directory_tree_selected_path = None
def on_menu_item_hovered(self, event: MenuItem.Hovered) -> None:
"""Called when a menu item is hovered."""
self.get_widget_by_id("status_text", Static).update(event.menu_item.description or "")
def on_menu_closed(self, event: Menu.Closed) -> None:
"""Called when a menu is closed."""
self.get_widget_by_id("status_text", Static).update(_("For Help, click Help Topics on the Help Menu."))
def within_menus(self, node: DOMNode) -> bool: def within_menus(self, node: DOMNode) -> bool:
"""Returns True if the node is within the menus.""" """Returns True if the node is within the menus."""
# root node will never be a menu, so it doesn't need to be `while node:` # root node will never be a menu, so it doesn't need to be `while node:`