From 5d5be2ba2a6e0b3886e0076475ed9d7a2d4ac9ab Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Wed, 25 Apr 2007 01:33:20 +0000 Subject: [PATCH] add keyboard shortcuts to the menus. you can specify the shortcut key with & even in root menu and stuff --- openbox/client_menu.c | 39 +++++++-------- openbox/event.c | 107 +++++++++++++++++++++++++++++++++++++----- openbox/menu.c | 88 ++++++++++++++++++++++++++++++++-- openbox/menu.h | 17 +++++++ openbox/menuframe.c | 21 +++++++-- openbox/menuframe.h | 3 +- openbox/translate.c | 10 ++++ openbox/translate.h | 2 + 8 files changed, 246 insertions(+), 41 deletions(-) diff --git a/openbox/client_menu.c b/openbox/client_menu.c index f67ea906..fc88025c 100644 --- a/openbox/client_menu.c +++ b/openbox/client_menu.c @@ -70,16 +70,14 @@ static void client_update(ObMenuFrame *frame, gpointer data) e->data.normal.enabled = frame->client->functions & OB_CLIENT_FUNC_ICONIFY; e = menu_find_entry_id(menu, CLIENT_MAXIMIZE); - g_free(e->data.normal.label); - e->data.normal.label = - g_strdup(frame->client->max_vert || frame->client->max_horz ? - _("Restore") : _("Maximize")); + menu_entry_set_label(e, + (frame->client->max_vert || frame->client->max_horz ? + _("Restor&e") : _("Maximiz&e"))); e->data.normal.enabled =frame->client->functions & OB_CLIENT_FUNC_MAXIMIZE; e = menu_find_entry_id(menu, CLIENT_SHADE); - g_free(e->data.normal.label); - e->data.normal.label = g_strdup(frame->client->shaded ? - _("Roll down") : _("Roll up")); + menu_entry_set_label(e, (frame->client->shaded ? + _("&Roll down") : _("&Roll up"))); e->data.normal.enabled = frame->client->functions & OB_CLIENT_FUNC_SHADE; e = menu_find_entry_id(menu, CLIENT_MOVE); @@ -165,29 +163,32 @@ void client_menu_startup() ObMenu *menu; ObMenuEntry *e; - menu = menu_new(LAYER_MENU_NAME, _("Layer"), NULL); + menu = menu_new(LAYER_MENU_NAME, _("&Layer"), NULL); + menu_show_all_shortcuts(menu, TRUE); menu_set_update_func(menu, layer_update); acts = g_slist_prepend(NULL, action_from_string ("SendToTopLayer", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, LAYER_TOP, _("Always on top"), acts); + menu_add_normal(menu, LAYER_TOP, _("Always on &top"), acts); acts = g_slist_prepend(NULL, action_from_string ("SendToNormalLayer", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, LAYER_NORMAL, _("Normal"), acts); + menu_add_normal(menu, LAYER_NORMAL, _("&Normal"), acts); acts = g_slist_prepend(NULL, action_from_string ("SendToBottomLayer", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, LAYER_BOTTOM, _("Always on bottom"),acts); + menu_add_normal(menu, LAYER_BOTTOM, _("Always on &bottom"),acts); - menu = menu_new(SEND_TO_MENU_NAME, _("Send to desktop"), NULL); + menu = menu_new(SEND_TO_MENU_NAME, _("&Send to desktop"), NULL); + menu_show_all_shortcuts(menu, TRUE); menu_set_update_func(menu, send_to_update); menu = menu_new(CLIENT_MENU_NAME, _("Client menu"), NULL); + menu_show_all_shortcuts(menu, TRUE); menu_set_update_func(menu, client_update); menu_add_submenu(menu, CLIENT_SEND_TO, SEND_TO_MENU_NAME); @@ -196,7 +197,7 @@ void client_menu_startup() acts = g_slist_prepend(NULL, action_from_string ("Iconify", OB_USER_ACTION_MENU_SELECTION)); - e = menu_add_normal(menu, CLIENT_ICONIFY, _("Iconify"), acts); + e = menu_add_normal(menu, CLIENT_ICONIFY, _("Ico&nify"), acts); e->data.normal.mask = ob_rr_theme->iconify_mask; e->data.normal.mask_normal_color = ob_rr_theme->menu_color; e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color; @@ -213,11 +214,11 @@ void client_menu_startup() acts = g_slist_prepend(NULL, action_from_string ("Raise", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_RAISE, _("Raise to top"), acts); + menu_add_normal(menu, CLIENT_RAISE, _("Raise to &top"), acts); acts = g_slist_prepend(NULL, action_from_string ("Lower", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_LOWER, _("Lower to bottom"),acts); + menu_add_normal(menu, CLIENT_LOWER, _("Lower to &bottom"),acts); acts = g_slist_prepend(NULL, action_from_string ("ToggleShade", OB_USER_ACTION_MENU_SELECTION)); @@ -230,23 +231,23 @@ void client_menu_startup() acts = g_slist_prepend(NULL, action_from_string ("ToggleDecorations", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_DECORATE, _("Decorate"), acts); + menu_add_normal(menu, CLIENT_DECORATE, _("&Decorate"), acts); menu_add_separator(menu, -1, NULL); acts = g_slist_prepend(NULL, action_from_string ("Move", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_MOVE, _("Move"), acts); + menu_add_normal(menu, CLIENT_MOVE, _("&Move"), acts); acts = g_slist_prepend(NULL, action_from_string ("Resize", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_RESIZE, _("Resize"), acts); + menu_add_normal(menu, CLIENT_RESIZE, _("&Resize"), acts); menu_add_separator(menu, -1, NULL); acts = g_slist_prepend(NULL, action_from_string ("Close", OB_USER_ACTION_MENU_SELECTION)); - e = menu_add_normal(menu, CLIENT_CLOSE, _("Close"), acts); + e = menu_add_normal(menu, CLIENT_CLOSE, _("&Close"), acts); e->data.normal.mask = ob_rr_theme->close_mask; e->data.normal.mask_normal_color = ob_rr_theme->menu_color; e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color; diff --git a/openbox/event.c b/openbox/event.c index 788fd4bc..9d6ff5fd 100644 --- a/openbox/event.c +++ b/openbox/event.c @@ -39,6 +39,7 @@ #include "group.h" #include "stacking.h" #include "extensions.h" +#include "translate.h" #include #include @@ -72,6 +73,7 @@ typedef struct static void event_process(const XEvent *e, gpointer data); static void event_handle_root(XEvent *e); +static void event_handle_menu_shortcut(XEvent *e); static void event_handle_menu(XEvent *e); static void event_handle_dock(ObDock *s, XEvent *e); static void event_handle_dockapp(ObDockApp *app, XEvent *e); @@ -1262,7 +1264,7 @@ static void event_handle_dockapp(ObDockApp *app, XEvent *e) } } -ObMenuFrame* find_active_menu() +static ObMenuFrame* find_active_menu() { GList *it; ObMenuFrame *ret = NULL; @@ -1276,7 +1278,7 @@ ObMenuFrame* find_active_menu() return ret; } -ObMenuFrame* find_active_or_last_menu() +static ObMenuFrame* find_active_or_last_menu() { ObMenuFrame *ret = NULL; @@ -1286,6 +1288,77 @@ ObMenuFrame* find_active_or_last_menu() return ret; } +static void event_handle_menu_shortcut(XEvent *ev) +{ + gunichar unikey = 0; + ObMenuFrame *frame; + GList *start; + GList *it; + ObMenuEntryFrame *found = NULL; + guint num_found = 0; + + { + const char *key; + if ((key = translate_keycode(ev->xkey.keycode)) == NULL) + return; + unikey = g_utf8_get_char_validated(key, -1); + if (unikey == (gunichar)-1 || unikey == (gunichar)-2 || unikey == 0) + return; + } + + if ((frame = find_active_or_last_menu()) == NULL) + return; + + + if (!frame->entries) + return; /* nothing in the menu anyways */ + + /* start after the selected one */ + start = frame->entries; + if (frame->selected) { + for (it = start; frame->selected != it->data; it = g_list_next(it)) + g_assert(it != NULL); /* nothing was selected? */ + /* next with wraparound */ + start = g_list_next(it); + if (start == NULL) start = frame->entries; + } + + it = start; + do { + ObMenuEntryFrame *e = it->data; + gunichar entrykey = 0; + + if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) + entrykey = e->entry->data.normal.shortcut; + else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) + entrykey = e->entry->data.submenu.submenu->shortcut; + + if (unikey == entrykey) { + if (found == NULL) found = e; + ++num_found; + } + + /* next with wraparound */ + it = g_list_next(it); + if (it == NULL) it = frame->entries; + } while (it != start); + + if (found) { + if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL && + num_found == 1) + { + menu_frame_select(frame, found, TRUE); + usleep(50000); + menu_entry_frame_execute(found, ev->xkey.state, + ev->xkey.time); + } else { + menu_frame_select(frame, found, TRUE); + if (num_found == 1) + menu_frame_select_next(frame->child); + } + } +} + static void event_handle_menu(XEvent *ev) { ObMenuFrame *f; @@ -1307,7 +1380,7 @@ static void event_handle_menu(XEvent *ev) if (e->ignore_enters) --e->ignore_enters; else - menu_frame_select(e->frame, e); + menu_frame_select(e->frame, e, FALSE); } break; case LeaveNotify: @@ -1315,28 +1388,35 @@ static void event_handle_menu(XEvent *ev) (f = find_active_menu()) && f->selected == e && e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU) { - menu_frame_select(e->frame, NULL); + menu_frame_select(e->frame, NULL, FALSE); } case MotionNotify: if ((e = menu_entry_frame_under(ev->xmotion.x_root, ev->xmotion.y_root))) - menu_frame_select(e->frame, e); + menu_frame_select(e->frame, e, FALSE); break; case KeyPress: if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) - menu_frame_hide_all(); + if ((f = find_active_or_last_menu()) && f->parent) + menu_frame_select(f, NULL, TRUE); + else + menu_frame_hide_all(); else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) { ObMenuFrame *f; - if ((f = find_active_menu())) - menu_entry_frame_execute(f->selected, ev->xkey.state, - ev->xkey.time); + if ((f = find_active_menu())) { + if (f->child) + menu_frame_select_next(f->child); + else + menu_entry_frame_execute(f->selected, ev->xkey.state, + ev->xkey.time); + } } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) { ObMenuFrame *f; - if ((f = find_active_or_last_menu()) && f->parent) - menu_frame_select(f, NULL); + if ((f = find_active_or_last_menu())) + menu_frame_select(f, NULL, TRUE); } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) { ObMenuFrame *f; - if ((f = find_active_or_last_menu()) && f->child) + if ((f = find_active_menu()) && f->child) menu_frame_select_next(f->child); } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) { ObMenuFrame *f; @@ -1346,7 +1426,8 @@ static void event_handle_menu(XEvent *ev) ObMenuFrame *f; if ((f = find_active_or_last_menu())) menu_frame_select_next(f); - } + } else + event_handle_menu_shortcut(ev); break; } } diff --git a/openbox/menu.c b/openbox/menu.c index 311701a7..1f335c78 100644 --- a/openbox/menu.c +++ b/openbox/menu.c @@ -54,6 +54,9 @@ static void parse_menu_separator(ObParseInst *i, gpointer data); static void parse_menu(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node, gpointer data); +static gunichar parse_shortcut(const gchar *label, gchar **strippedlabel, + guint *position); + static void client_dest(ObClient *client, gpointer data) { @@ -178,6 +181,56 @@ static ObMenu* menu_from_name(gchar *name) return self; } +#define VALID_SHORTCUT(c) (((c) >= '0' && (c) <= '9') || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z')) + +static gunichar parse_shortcut(const gchar *label, gchar **strippedlabel, + guint *position) +{ + gunichar shortcut = 0; + + *position = 0; + + g_assert(strippedlabel != NULL); + + if (label == NULL) { + *strippedlabel = NULL; + } else { + gchar *i; + + *strippedlabel = g_strdup(label); + + i = strchr(*strippedlabel, '&'); + if (i != NULL) { + /* there is an ampersand in the string */ + + /* you have to use a printable ascii character for shortcuts + don't allow space either, so you can have like "a & b" + */ + if (VALID_SHORTCUT(*(i+1))) { + shortcut = g_unichar_tolower(g_utf8_get_char(i+1)); + *position = i - *strippedlabel; + + /* remove the & from the string */ + for (; *i != '\0'; ++i) + *i = *(i+1); + } + } else { + /* there is no ampersand, so find the first valid character to use + instead */ + + for (i = *strippedlabel; *i != '\0'; ++i) + if (VALID_SHORTCUT(*i)) { + *position = i - *strippedlabel; + shortcut = g_unichar_tolower(g_utf8_get_char(i)); + break; + } + } + } + return shortcut; +} + static void parse_menu_item(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node, gpointer data) { @@ -262,9 +315,11 @@ ObMenu* menu_new(const gchar *name, const gchar *title, gpointer data) self = g_new0(ObMenu, 1); self->name = g_strdup(name); - self->title = g_strdup(title); self->data = data; + self->shortcut = parse_shortcut(title, &self->title, + &self->shortcut_position); + g_hash_table_replace(menu_hash, self->name, self); return self; @@ -325,7 +380,7 @@ void menu_show(gchar *name, gint x, gint y, ObClient *client) ObMenuEntryFrame *e = frame->entries->data; if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL && e->entry->data.normal.enabled) - menu_frame_select(frame, e); + menu_frame_select(frame, e, FALSE); } } @@ -409,9 +464,10 @@ ObMenuEntry* menu_add_normal(ObMenu *self, gint id, const gchar *label, ObMenuEntry *e; e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_NORMAL, id); - e->data.normal.label = g_strdup(label); e->data.normal.actions = actions; + menu_entry_set_label(e, label); + self->entries = g_list_append(self->entries, e); return e; } @@ -432,7 +488,8 @@ ObMenuEntry* menu_add_separator(ObMenu *self, gint id, const gchar *label) ObMenuEntry *e; e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SEPARATOR, id); - e->data.separator.label = g_strdup(label); + + menu_entry_set_label(e, label); self->entries = g_list_append(self->entries, e); return e; @@ -480,3 +537,26 @@ void menu_find_submenus(ObMenu *self) e->data.submenu.submenu = menu_from_name(e->data.submenu.name); } } + +void menu_entry_set_label(ObMenuEntry *self, const gchar *label) +{ + switch (self->type) { + case OB_MENU_ENTRY_TYPE_SEPARATOR: + g_free(self->data.separator.label); + self->data.separator.label = g_strdup(label); + break; + case OB_MENU_ENTRY_TYPE_NORMAL: + g_free(self->data.normal.label); + self->data.normal.shortcut = + parse_shortcut(label, &self->data.normal.label, + &self->data.normal.shortcut_position); + break; + default: + g_assert_not_reached(); + } +} + +void menu_show_all_shortcuts(ObMenu *self, gboolean show) +{ + self->show_all_shortcuts = show; +} diff --git a/openbox/menu.h b/openbox/menu.h index 5b8810fd..bdc05266 100644 --- a/openbox/menu.h +++ b/openbox/menu.h @@ -48,6 +48,15 @@ struct _ObMenu gchar *name; /* Displayed title */ gchar *title; + /*! The shortcut key that would be used to activate this menu if it was + displayed as a submenu */ + gunichar shortcut; + /*! The shortcut's position in the string */ + guint shortcut_position; + + /*! If the shortcut key should be shown in menu entries even when it + is the first character in the string */ + gboolean show_all_shortcuts; /* Command to execute to rebuild the menu */ gchar *execute; @@ -75,6 +84,10 @@ typedef enum struct _ObNormalMenuEntry { gchar *label; + /*! The shortcut key that would be used to activate this menu entry */ + gunichar shortcut; + /*! The shortcut's position in the string */ + guint shortcut_position; /* state */ gboolean enabled; @@ -126,6 +139,8 @@ void menu_free(ObMenu *menu); /* Repopulate a pipe-menu by running its command */ void menu_pipe_execute(ObMenu *self); +void menu_show_all_shortcuts(ObMenu *self, gboolean show); + void menu_show(gchar *name, gint x, gint y, struct _ObClient *client); void menu_set_update_func(ObMenu *menu, ObMenuUpdateFunc func); @@ -141,6 +156,8 @@ ObMenuEntry* menu_add_separator(ObMenu *menu, gint id, const gchar *label); void menu_clear_entries(ObMenu *menu); void menu_entry_remove(ObMenuEntry *self); +void menu_entry_set_label(ObMenuEntry *self, const gchar *label); + ObMenuEntry* menu_find_entry_id(ObMenu *self, gint id); /* fills in the submenus, for use when a menu is being shown */ diff --git a/openbox/menuframe.c b/openbox/menuframe.c index 4cb2083d..d92f47df 100644 --- a/openbox/menuframe.c +++ b/openbox/menuframe.c @@ -318,6 +318,13 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self) self->a_text_selected : self->a_text_normal)); text_a->texture[0].data.text.string = self->entry->data.normal.label; + if (self->frame->menu->show_all_shortcuts || + self->entry->data.normal.shortcut_position > 0) + { + text_a->texture[0].data.text.shortcut = + self->entry->data.normal.shortcut; + } else + text_a->texture[0].data.text.shortcut = 0; break; case OB_MENU_ENTRY_TYPE_SUBMENU: text_a = (self == self->frame->selected ? @@ -325,6 +332,11 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self) self->a_text_normal); sub = self->entry->data.submenu.submenu; text_a->texture[0].data.text.string = sub ? sub->title : ""; + if (self->frame->menu->show_all_shortcuts || + sub->shortcut_position > 0) { + text_a->texture[0].data.text.shortcut = sub->shortcut; + } else + text_a->texture[0].data.text.shortcut = 0; break; case OB_MENU_ENTRY_TYPE_SEPARATOR: if (self->entry->data.separator.label != NULL) @@ -886,7 +898,8 @@ static gboolean menu_entry_frame_submenu_timeout(gpointer data) return FALSE; } -void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry) +void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry, + gboolean immediate) { ObMenuEntryFrame *old = self->selected; ObMenuFrame *oldchild = self->child; @@ -913,7 +926,7 @@ void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry) menu_entry_frame_render(self->selected); if (self->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) { - if (config_submenu_show_delay) { + if (config_submenu_show_delay && !immediate) { /* initiate a new submenu open request */ ob_main_loop_timeout_add(ob_main_loop, config_submenu_show_delay * 1000, @@ -988,7 +1001,7 @@ void menu_frame_select_previous(ObMenuFrame *self) } } } - menu_frame_select(self, it ? it->data : NULL); + menu_frame_select(self, it ? it->data : NULL, TRUE); } void menu_frame_select_next(ObMenuFrame *self) @@ -1014,5 +1027,5 @@ void menu_frame_select_next(ObMenuFrame *self) } } } - menu_frame_select(self, it ? it->data : NULL); + menu_frame_select(self, it ? it->data : NULL, TRUE); } diff --git a/openbox/menuframe.h b/openbox/menuframe.h index 016700a7..4cd27d37 100644 --- a/openbox/menuframe.h +++ b/openbox/menuframe.h @@ -124,7 +124,8 @@ void menu_frame_hide(ObMenuFrame *self); void menu_frame_hide_all(); void menu_frame_hide_all_client(struct _ObClient *client); -void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry); +void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry, + gboolean immediate); void menu_frame_select_previous(ObMenuFrame *self); void menu_frame_select_next(ObMenuFrame *self); diff --git a/openbox/translate.c b/openbox/translate.c index a26017e5..97066519 100644 --- a/openbox/translate.c +++ b/openbox/translate.c @@ -139,3 +139,13 @@ translation_fail: g_strfreev(parsed); return ret; } + +const gchar *translate_keycode(guint keycode) +{ + KeySym sym; + const gchar *ret = NULL; + + if ((sym = XKeycodeToKeysym(ob_display, keycode, 0)) != NoSymbol) + ret = XKeysymToString(sym); + return g_locale_to_utf8(ret, -1, NULL, NULL, NULL); +} diff --git a/openbox/translate.h b/openbox/translate.h index 8249514e..14efe73d 100644 --- a/openbox/translate.h +++ b/openbox/translate.h @@ -24,4 +24,6 @@ gboolean translate_button(const gchar *str, guint *state, guint *keycode); gboolean translate_key(const gchar *str, guint *state, guint *keycode); +/*! Give the string form of a keycode */ +const gchar *translate_keycode(guint keycode); #endif -- 2.45.2