add keyboard shortcuts to the menus. you can specify the shortcut key with & even in root menu and stuff

This commit is contained in:
Dana Jansens 2007-04-25 01:33:20 +00:00
parent 138d3e38d8
commit 5d5be2ba2a
8 changed files with 246 additions and 41 deletions

View file

@ -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;

View file

@ -39,6 +39,7 @@
#include "group.h"
#include "stacking.h"
#include "extensions.h"
#include "translate.h"
#include <X11/Xlib.h>
#include <X11/keysym.h>
@ -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;
}
}

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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