moses-palmer / pystray

GNU General Public License v3.0
463 stars 57 forks source link

Support for keyboard shortcuts in menu #75

Open suurjaak opened 3 years ago

suurjaak commented 3 years ago

First of all, thank you for this library, a very handy cross-platform solution to a very common need.

What do you think about adding support for keyboard shortcuts, i.e. underlined characters in menu item texts? Currently, pystray passes the menu texts straight to the underlying backend, so that specifying "E&xit" produces a nice underlined x-shortcut on Windows, but on Linux the text is displayed exactly as given. On Linux, the standard shortcut character has been underscore, e.g. using "E_xit" (except for the brave new Gnome Shell which does not support shortcut accelerators at all).

So, perhaps pystray.Menu could have an additional method set_accelerator(char), where char would specify the symbol the invoker has used in their menu texts? Then the library would replace the character in menu texts with the backend-specific accelerator symbol - or strip it altogether if the backend does not support shortcuts. And in texts where the user wants this symbol to actually be displayed, they should give it in double, e.g. "Save && e&xit" would produce "Save & exit" with x underlined as the shortcut.

It would make sense for a cross-platform library to encapsulate such platform-specific logic.

MaxBQb commented 1 year ago

In other menus keyboard selection has underscore indication: 6523 image_thumb_668BD4CB

Sadly, system tray menu doesn't. As workaround, you may use unicode underscore symbols as example:

def insert_underscore(text: str, at: int):
    """
    Make chosen letter underscored

    :param text: String to modify
    :param at: Position of letter to underscore
    :return: String with unicode underscore inserted
    """
    underscore = "\u0332"
    insert_pos = at + 1  # this underscore symbol applies to previous symbol
    return f'{text[:insert_pos]}{underscore}{text[insert_pos:]}'

As for me, I'd like to use automatic '&' + underscore insertion, at least for now:

def make_tray_shortcut(text: str, shortcut_symbol='&', look_for='&'):
    """
    Search for letter right after `look_for` subsequence or uses first letter instead.
    Underscore this letter and mark it as shortcut for system tray,
    so you may press this letter on keyboard to select corresponding menu item

    :param text: Menu item title
    :param shortcut_symbol: Letter used to mark keyboard shortcut,                   
                            platform-specific char (Windows: & | Linux: _)
    :param look_for: Symbol before letter, chosen as keyboard shortcut
    :return: Menu item title with char-markers
    """
    try:
        shortcut_pos = text.index(look_for) + 1
        text = text.replace(look_for, shortcut_symbol, 1)
    except ValueError:
        text = shortcut_symbol + text
        shortcut_pos = 1
    text = insert_underscore(text, shortcut_pos)
    return text

Usage example:

from pystray import Menu, MenuItem
from example import make_tray_shortcut as ref, some_escape_function as escape

menu = Menu(
    MenuItem(ref("Show console"), ...),
    MenuItem(ref("Run a&t system startup"), ...),
    MenuItem(ref("Mode"), ...),
    Menu.SEPARATOR,
    MenuItem(ref('Open'), Menu(
        MenuItem(ref('Work directory'), ...),
        MenuItem(ref('Settings file'), ...),
    )),
    MenuItem(ref('Re&load from disk'), Menu(...)),
    MenuItem(ref('Check for &updates'), ...),
    Menu.SEPARATOR,
    MenuItem(ref(escape('Save & *Exit'), look_for='*')),  # Escape & with && for Windows
)