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);
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);
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;
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));
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;
#include "group.h"
#include "stacking.h"
#include "extensions.h"
+#include "translate.h"
#include <X11/Xlib.h>
#include <X11/keysym.h>
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);
}
}
-ObMenuFrame* find_active_menu()
+static ObMenuFrame* find_active_menu()
{
GList *it;
ObMenuFrame *ret = NULL;
return ret;
}
-ObMenuFrame* find_active_or_last_menu()
+static ObMenuFrame* find_active_or_last_menu()
{
ObMenuFrame *ret = NULL;
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;
if (e->ignore_enters)
--e->ignore_enters;
else
- menu_frame_select(e->frame, e);
+ menu_frame_select(e->frame, e, FALSE);
}
break;
case LeaveNotify:
(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;
ObMenuFrame *f;
if ((f = find_active_or_last_menu()))
menu_frame_select_next(f);
- }
+ } else
+ event_handle_menu_shortcut(ev);
break;
}
}
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)
{
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)
{
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;
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);
}
}
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;
}
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;
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;
+}
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;
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;
/* 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);
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 */
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 ?
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)
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;
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,
}
}
}
- menu_frame_select(self, it ? it->data : NULL);
+ menu_frame_select(self, it ? it->data : NULL, TRUE);
}
void menu_frame_select_next(ObMenuFrame *self)
}
}
}
- menu_frame_select(self, it ? it->data : NULL);
+ menu_frame_select(self, it ? it->data : NULL, TRUE);
}
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);
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);
+}
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