+ if (!(self = menu_from_name(name))) return;
+ g_hash_table_remove(menu_hash, self->name);
+}
+
+void menu_show(gchar *name, gint x, gint y, ObClient *client)
+{
+ ObMenu *self;
+ ObMenuFrame *frame;
+
+ if (!(self = menu_from_name(name))) return;
+
+ frame = menu_frame_new(self, client);
+ menu_frame_move(frame, x, y);
+ menu_frame_show(frame, NULL);
+}
+
+static ObMenuEntry* menu_entry_new(ObMenu *menu, ObMenuEntryType type, gint id)
+{
+ ObMenuEntry *self;
+
+ g_assert(menu);
+
+ self = g_new0(ObMenuEntry, 1);
+ self->type = type;
+ self->menu = menu;
+ self->id = id;
+
+ switch (type) {
+ case OB_MENU_ENTRY_TYPE_NORMAL:
+ self->data.normal.enabled = TRUE;
+ break;
+ case OB_MENU_ENTRY_TYPE_SUBMENU:
+ case OB_MENU_ENTRY_TYPE_SEPARATOR:
+ break;
+ }
+
+ return self;
+}
+
+static void menu_entry_free(ObMenuEntry *self)
+{
+ if (self) {
+ switch (self->type) {
+ case OB_MENU_ENTRY_TYPE_NORMAL:
+ g_free(self->data.normal.label);
+ while (self->data.normal.actions) {
+ action_free(self->data.normal.actions->data);
+ self->data.normal.actions =
+ g_slist_delete_link(self->data.normal.actions,
+ self->data.normal.actions);
+ }
+ break;
+ case OB_MENU_ENTRY_TYPE_SUBMENU:
+ g_free(self->data.submenu.name);
+ break;
+ case OB_MENU_ENTRY_TYPE_SEPARATOR:
+ break;
+ }
+
+ g_free(self);
+ }
+}
+
+void menu_clear_entries(gchar *name)
+{
+ ObMenu *self;
+
+ if (!(self = menu_from_name(name))) return;
+
+ menu_clear_entries_internal(self);
+}
+
+static void menu_clear_entries_internal(ObMenu *self)
+{
+ /* XXX assert that the menu isn't visible */
+
+ while (self->entries) {
+ menu_entry_free(self->entries->data);
+ self->entries = g_list_delete_link(self->entries, self->entries);
+ }
+}
+
+void menu_add_normal(gchar *name, gint id, gchar *label, GSList *actions)
+{
+ ObMenu *self;
+ ObMenuEntry *e;
+
+ if (!(self = menu_from_name(name))) return;
+
+ e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_NORMAL, id);
+ e->data.normal.label = g_strdup(label);
+ e->data.normal.actions = actions;
+
+ self->entries = g_list_append(self->entries, e);
+}
+
+void menu_add_submenu(gchar *name, gint id, gchar *submenu)
+{
+ ObMenu *self;
+ ObMenuEntry *e;
+
+ if (!(self = menu_from_name(name))) return;
+
+ e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SUBMENU, id);
+ e->data.submenu.name = g_strdup(submenu);
+
+ self->entries = g_list_append(self->entries, e);
+}
+
+void menu_add_separator(gchar *name, gint id)
+{
+ ObMenu *self;
+ ObMenuEntry *e;
+
+ if (!(self = menu_from_name(name))) return;
+
+ e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SEPARATOR, id);
+
+ self->entries = g_list_append(self->entries, e);
+}
+
+void menu_set_update_func(gchar *name, ObMenuUpdateFunc func)
+{
+ ObMenu *self;
+
+ if (!(self = menu_from_name(name))) return;
+ self->update_func = func;
+}
+
+void menu_set_execute_func(gchar *name, ObMenuExecuteFunc func)
+{
+ ObMenu *self;
+
+ if (!(self = menu_from_name(name))) return;
+ self->execute_func = func;
+}
+
+void menu_set_destroy_func(gchar *name, ObMenuDestroyFunc func)
+{
+ ObMenu *self;
+
+ if (!(self = menu_from_name(name))) return;
+ self->destroy_func = func;
+}
+
+ObMenuEntry* menu_find_entry_id(ObMenu *self, gint id)
+{
+ ObMenuEntry *ret = NULL;
+ GList *it;
+
+ for (it = self->entries; it; it = g_list_next(it)) {
+ ObMenuEntry *e = it->data;