/* * Copyright 2012 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: Ryan Lortie * William Hua */ /** * SECTION:unity-gtk-menu-shell * @short_description: Menu shell proxy * @include: unity-gtk-parser.h * * A #UnityGtkMenuShell is a #GMenuModel that acts as a proxy for a * #GtkMenuShell. This can be used for purposes such as exporting menu * shells over DBus with g_dbus_connection_export_menu_model (). * * #UnityGtkMenuShells are most useful when used with * #UnityGtkActionGroups. */ #include "unity-gtk-action-group-private.h" #include "unity-gtk-menu-section-private.h" #include "unity-gtk-menu-shell-private.h" G_DEFINE_QUARK(menu_shell, menu_shell); G_DEFINE_TYPE(UnityGtkMenuShell, unity_gtk_menu_shell, G_TYPE_MENU_MODEL); static gboolean unity_gtk_menu_shell_debug; static gint g_uintcmp(gconstpointer a, gconstpointer b, gpointer user_data) { return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b); } static guint g_sequence_get_uint(GSequenceIter *iter) { return GPOINTER_TO_UINT(g_sequence_get(iter)); } static void g_sequence_set_uint(GSequenceIter *iter, guint i) { g_sequence_set(iter, GUINT_TO_POINTER(i)); } static GSequenceIter *g_sequence_insert_sorted_uint(GSequence *sequence, guint i) { return g_sequence_insert_sorted(sequence, GUINT_TO_POINTER(i), g_uintcmp, NULL); } static GSequenceIter *g_sequence_lookup_uint(GSequence *sequence, guint i) { return g_sequence_lookup(sequence, GUINT_TO_POINTER(i), g_uintcmp, NULL); } static GSequenceIter *g_sequence_search_uint(GSequence *sequence, guint i) { return g_sequence_search(sequence, GUINT_TO_POINTER(i), g_uintcmp, NULL); } static GSequenceIter *g_sequence_search_inf_uint(GSequence *sequence, guint i) { GSequenceIter *iter = g_sequence_iter_prev(g_sequence_search_uint(sequence, i)); return !g_sequence_iter_is_end(iter) && g_sequence_get_uint(iter) <= i ? iter : NULL; } static gboolean gtk_menu_item_handle_idle_activate(gpointer user_data) { g_return_val_if_fail(GTK_IS_MENU_ITEM(user_data), G_SOURCE_REMOVE); gtk_menu_item_activate(user_data); return G_SOURCE_REMOVE; } static GPtrArray *unity_gtk_menu_shell_get_items(UnityGtkMenuShell *shell) { g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL); if (shell->items == NULL) { GList *children; GList *iter; guint i; g_return_val_if_fail(shell->menu_shell != NULL, NULL); shell->items = g_ptr_array_new_with_free_func(g_object_unref); children = gtk_container_get_children(GTK_CONTAINER(shell->menu_shell)); for (iter = children, i = 0; iter != NULL; i++) { g_ptr_array_add(shell->items, unity_gtk_menu_item_new(iter->data, shell, i)); iter = g_list_next(iter); } g_list_free(children); } return shell->items; } static GPtrArray *unity_gtk_menu_shell_get_sections(UnityGtkMenuShell *shell) { g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL); if (shell->sections == NULL) { GSequence *separator_indices = unity_gtk_menu_shell_get_separator_indices(shell); guint n = g_sequence_get_length(separator_indices); guint i; shell->sections = g_ptr_array_new_full(n + 1, g_object_unref); for (i = 0; i <= n; i++) g_ptr_array_add(shell->sections, unity_gtk_menu_section_new(shell, i)); } return shell->sections; } static void unity_gtk_menu_shell_show_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GSequence *visible_indices; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); visible_indices = shell->visible_indices; if (visible_indices != NULL) { GSequence *separator_indices = shell->separator_indices; guint item_index = item->item_index; GSequenceIter *insert_iter = g_sequence_lookup_uint(visible_indices, item_index); gboolean already_visible = insert_iter != NULL; if (!already_visible) insert_iter = g_sequence_insert_sorted_uint(visible_indices, item_index); else g_warn_if_reached(); if (shell->action_group != NULL) { unity_gtk_action_group_connect_item(shell->action_group, item); if (item->child_shell != NULL) { if (item->child_shell_valid) unity_gtk_action_group_connect_shell(shell->action_group, item->child_shell); else g_warn_if_reached(); } } if (separator_indices != NULL) { GPtrArray *sections = shell->sections; GSequenceIter *separator_iter = g_sequence_search_inf_uint(separator_indices, item_index); guint section_index = separator_iter == NULL ? 0 : g_sequence_iter_get_position(separator_iter) + 1; gboolean separator_already_visible = separator_iter != NULL && g_sequence_get_uint(separator_iter) == item_index; if (!separator_already_visible) { if (unity_gtk_menu_item_is_separator(item)) { g_sequence_insert_sorted_uint(separator_indices, item_index); if (sections != NULL) { UnityGtkMenuSection *section = g_ptr_array_index(sections, section_index); GSequenceIter *section_iter = unity_gtk_menu_section_get_begin_iter(section); guint position = g_sequence_iter_get_position(insert_iter) - g_sequence_iter_get_position(section_iter); UnityGtkMenuSection *new_section = unity_gtk_menu_section_new(shell, section_index + 1); guint removed = g_menu_model_get_n_items( G_MENU_MODEL(new_section)); guint i; g_ptr_array_insert(sections, section_index + 1, new_section); for (i = section_index + 2; i < sections->len; i++) UNITY_GTK_MENU_SECTION( g_ptr_array_index(sections, i)) ->section_index = i; if (removed) g_menu_model_items_changed(G_MENU_MODEL( section), position, removed, 0); g_menu_model_items_changed(G_MENU_MODEL(shell), section_index + 1, 0, 1); } } else { if (sections != NULL) { UnityGtkMenuSection *section = g_ptr_array_index(sections, section_index); GSequenceIter *section_iter = unity_gtk_menu_section_get_begin_iter(section); guint position = g_sequence_iter_get_position(insert_iter) - g_sequence_iter_get_position(section_iter); g_menu_model_items_changed(G_MENU_MODEL(section), position, 0, 1); } } } else g_warn_if_reached(); } } } static void unity_gtk_menu_shell_hide_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GSequence *visible_indices; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); visible_indices = shell->visible_indices; if (visible_indices != NULL) { GSequence *separator_indices = shell->separator_indices; guint item_index = item->item_index; GSequenceIter *visible_iter = g_sequence_lookup_uint(visible_indices, item_index); if (shell->action_group != NULL) { if (item->child_shell != NULL) { if (item->child_shell_valid) unity_gtk_action_group_disconnect_shell(shell->action_group, item->child_shell); else g_warn_if_reached(); } unity_gtk_action_group_disconnect_item(shell->action_group, item); } if (separator_indices != NULL) { if (unity_gtk_menu_item_is_separator(item)) { GSequenceIter *separator_iter = g_sequence_lookup_uint(separator_indices, item_index); if (separator_iter != NULL) { GPtrArray *sections = shell->sections; guint section_index = g_sequence_iter_get_position(separator_iter); if (shell->sections != NULL) { UnityGtkMenuSection *section = g_ptr_array_index(sections, section_index); UnityGtkMenuSection *next_section = g_ptr_array_index(sections, section_index + 1); guint position = g_menu_model_get_n_items(G_MENU_MODEL(section)); guint added = g_menu_model_get_n_items( G_MENU_MODEL(next_section)); guint i; g_sequence_remove(separator_iter); if (visible_iter != NULL) g_sequence_remove(visible_iter); else g_warn_if_reached(); g_menu_model_items_changed(G_MENU_MODEL(shell), section_index + 1, 1, 0); if (added) g_menu_model_items_changed(G_MENU_MODEL( section), position, 0, added); g_ptr_array_remove_index(sections, section_index + 1); for (i = section_index + 1; i < sections->len; i++) UNITY_GTK_MENU_SECTION( g_ptr_array_index(sections, i)) ->section_index = i; } else { g_sequence_remove(separator_iter); if (visible_iter != NULL) g_sequence_remove(visible_iter); else g_warn_if_reached(); } } else { g_warn_if_reached(); if (visible_iter != NULL) g_sequence_remove(visible_iter); else g_warn_if_reached(); } } else { if (visible_iter != NULL) { GPtrArray *sections = shell->sections; GSequenceIter *separator_iter = g_sequence_search_inf_uint(separator_indices, item_index); guint section_index = separator_iter == NULL ? 0 : g_sequence_iter_get_position(separator_iter) + 1; if (shell->sections != NULL) { UnityGtkMenuSection *section = g_ptr_array_index(sections, section_index); GSequenceIter *section_iter = unity_gtk_menu_section_get_begin_iter(section); guint position = g_sequence_iter_get_position(visible_iter) - g_sequence_iter_get_position(section_iter); g_sequence_remove(visible_iter); g_menu_model_items_changed(G_MENU_MODEL(section), position, 1, 0); } } else g_warn_if_reached(); } } else { if (visible_iter != NULL) g_sequence_remove(visible_iter); else g_warn_if_reached(); } } } static void unity_gtk_menu_shell_update_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GSequence *visible_indices; GSequenceIter *visible_iter; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); visible_indices = unity_gtk_menu_shell_get_visible_indices(shell); visible_iter = g_sequence_lookup_uint(visible_indices, item->item_index); if (visible_iter != NULL) { GSequence *separator_indices; GSequenceIter *separator_iter; guint section_index; GPtrArray *sections; UnityGtkMenuSection *section; GSequenceIter *section_iter; guint position; separator_indices = unity_gtk_menu_shell_get_separator_indices(shell); separator_iter = g_sequence_search_inf_uint(separator_indices, item->item_index); section_index = separator_iter == NULL ? 0 : g_sequence_iter_get_position(separator_iter) + 1; sections = unity_gtk_menu_shell_get_sections(shell); section = g_ptr_array_index(sections, section_index); section_iter = unity_gtk_menu_section_get_begin_iter(section); position = g_sequence_iter_get_position(visible_iter) - g_sequence_iter_get_position(section_iter); g_menu_model_items_changed(G_MENU_MODEL(section), position, 1, 1); } } static void unity_gtk_menu_shell_handle_item_visible(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GSequence *visible_indices; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); visible_indices = shell->visible_indices; if (visible_indices != NULL) { GSequenceIter *visible_iter = g_sequence_lookup_uint(visible_indices, item->item_index); gboolean was_visible = visible_iter != NULL; gboolean is_visible = unity_gtk_menu_item_is_visible(item); if (!was_visible && is_visible) unity_gtk_menu_shell_show_item(shell, item); else if (was_visible && !is_visible) unity_gtk_menu_shell_hide_item(shell, item); } } static void unity_gtk_menu_shell_handle_item_sensitive(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GActionGroup *action_group; UnityGtkAction *action; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); action_group = G_ACTION_GROUP(shell->action_group); action = item->action; if (action_group != NULL && action != NULL) { gboolean enabled = unity_gtk_menu_item_is_sensitive(item); g_action_group_action_enabled_changed(action_group, action->name, enabled); } } static void unity_gtk_menu_shell_handle_item_label(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); g_free(item->label_label); item->label_label = NULL; unity_gtk_menu_shell_update_item(shell, item); } static void unity_gtk_menu_shell_handle_item_use_underline(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { unity_gtk_menu_shell_handle_item_label(shell, item); } static void unity_gtk_menu_shell_handle_item_accel_path(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { unity_gtk_menu_shell_update_item(shell, item); } static void unity_gtk_menu_shell_handle_item_active(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GActionGroup *action_group; UnityGtkAction *action; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); action_group = G_ACTION_GROUP(shell->action_group); action = item->action; if (action_group != NULL && action != NULL) { if (action->items_by_name != NULL) { const char *name = NULL; GHashTableIter iter; gpointer key; gpointer value; g_hash_table_iter_init(&iter, action->items_by_name); while (name == NULL && g_hash_table_iter_next(&iter, &key, &value)) if (unity_gtk_menu_item_is_active(value)) name = key; if (name != NULL) { GVariant *state = g_variant_new_string(name); g_action_group_action_state_changed(action_group, action->name, state); } else g_action_group_action_state_changed(action_group, action->name, NULL); } else if (unity_gtk_menu_item_is_check(item)) { gboolean active = unity_gtk_menu_item_is_active(item); GVariant *state = g_variant_new_boolean(active); g_action_group_action_state_changed(action_group, action->name, state); } } } static void unity_gtk_menu_shell_handle_item_parent(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { GtkMenuItem *menu_item; GtkWidget *parent; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); menu_item = item->menu_item; parent = gtk_widget_get_parent(GTK_WIDGET(menu_item)); if (parent == NULL) { GPtrArray *items = shell->items; if (unity_gtk_menu_item_is_visible(item)) unity_gtk_menu_shell_hide_item(shell, item); if (items != NULL) { GSequence *visible_indices = shell->visible_indices; GSequence *separator_indices = shell->separator_indices; guint item_index = item->item_index; guint i; g_ptr_array_remove_index(items, item_index); for (i = item_index; i < items->len; i++) UNITY_GTK_MENU_ITEM(g_ptr_array_index(items, i))->item_index = i; if (visible_indices != NULL) { GSequenceIter *iter = g_sequence_search_uint(visible_indices, item_index); while (!g_sequence_iter_is_end(iter)) { g_sequence_set_uint(iter, g_sequence_get_uint(iter) - 1); iter = g_sequence_iter_next(iter); } } if (separator_indices != NULL) { GSequenceIter *iter = g_sequence_search_uint(separator_indices, item_index); while (!g_sequence_iter_is_end(iter)) { g_sequence_set_uint(iter, g_sequence_get_uint(iter) - 1); iter = g_sequence_iter_next(iter); } } } } } static void unity_gtk_menu_shell_handle_item_submenu(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); g_warn_if_fail(item->parent_shell == shell); if (shell->action_group != NULL) { /* If a submenu was added or removed, we need to update the submenu action. */ unity_gtk_action_group_disconnect_item(shell->action_group, item); unity_gtk_action_group_connect_item(shell->action_group, item); } if (item->child_shell_valid) { GtkMenuShell *old_submenu = item->child_shell != NULL ? item->child_shell->menu_shell : NULL; GtkMenuShell *new_submenu = item->menu_item != NULL ? GTK_MENU_SHELL(gtk_menu_item_get_submenu(item->menu_item)) : NULL; if (new_submenu != old_submenu) { UnityGtkMenuShell *child_shell = item->child_shell; GSequence *visible_indices = unity_gtk_menu_shell_get_visible_indices(shell); GSequence *separator_indices = unity_gtk_menu_shell_get_separator_indices(shell); GSequenceIter *separator_iter = g_sequence_search_inf_uint(separator_indices, item->item_index); guint section_index = separator_iter == NULL ? 0 : g_sequence_iter_get_position(separator_iter) + 1; GPtrArray *sections = unity_gtk_menu_shell_get_sections(shell); UnityGtkMenuSection *section = g_ptr_array_index(sections, section_index); GSequenceIter *section_iter = unity_gtk_menu_section_get_begin_iter(section); GSequenceIter *visible_iter = g_sequence_lookup_uint(visible_indices, item->item_index); guint position = g_sequence_iter_get_position(visible_iter) - g_sequence_iter_get_position(section_iter); if (child_shell != NULL) { item->child_shell = NULL; g_object_unref(child_shell); } item->child_shell_valid = FALSE; g_menu_model_items_changed(G_MENU_MODEL(section), position, 1, 1); } } } static void unity_gtk_menu_shell_handle_shell_insert(GtkMenuShell *menu_shell, GtkWidget *child, gint position, gpointer user_data) { UnityGtkMenuShell *shell; GPtrArray *items; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(user_data)); if (unity_gtk_menu_shell_is_debug()) g_print("%s ((%s *) %p, (%s *) %p \"%s\", %d, (%s *) %p)\n", G_STRFUNC, G_OBJECT_TYPE_NAME(menu_shell), menu_shell, G_OBJECT_TYPE_NAME(child), child, gtk_menu_item_get_label(GTK_MENU_ITEM(child)), position, G_OBJECT_TYPE_NAME(user_data), user_data); shell = UNITY_GTK_MENU_SHELL(user_data); items = shell->items; if (items != NULL) { UnityGtkMenuItem *item; GtkMenuItem *menu_item; GSequence *visible_indices; GSequence *separator_indices; guint i; if (position < 0) position = items->len; menu_item = GTK_MENU_ITEM(child); item = unity_gtk_menu_item_new(menu_item, shell, position); g_ptr_array_insert(items, position, item); for (i = position + 1; i < items->len; i++) UNITY_GTK_MENU_ITEM(g_ptr_array_index(items, i))->item_index = i; visible_indices = shell->visible_indices; separator_indices = shell->separator_indices; if (visible_indices != NULL) { GSequenceIter *iter = g_sequence_search_uint(visible_indices, position - 1); while (!g_sequence_iter_is_end(iter)) { g_sequence_set_uint(iter, g_sequence_get_uint(iter) + 1); iter = g_sequence_iter_next(iter); } } if (separator_indices != NULL) { GSequenceIter *iter = g_sequence_search_uint(separator_indices, position - 1); while (!g_sequence_iter_is_end(iter)) { g_sequence_set_uint(iter, g_sequence_get_uint(iter) + 1); iter = g_sequence_iter_next(iter); } } if (unity_gtk_menu_item_is_visible(item)) unity_gtk_menu_shell_show_item(shell, item); } } static void unity_gtk_menu_shell_set_has_mnemonics(UnityGtkMenuShell *shell, gboolean has_mnemonics) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); if (has_mnemonics != shell->has_mnemonics) { shell->has_mnemonics = has_mnemonics; if (shell->items != NULL) { guint i; for (i = 0; i < shell->items->len; i++) unity_gtk_menu_shell_handle_item_label( shell, g_ptr_array_index(shell->items, i)); } } } static void unity_gtk_menu_shell_handle_settings_notify(GObject *object, GParamSpec *pspec, gpointer user_data) { gboolean has_mnemonics; g_return_if_fail(GTK_IS_SETTINGS(object)); g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(user_data)); g_object_get(GTK_SETTINGS(object), "gtk-enable-mnemonics", &has_mnemonics, NULL); unity_gtk_menu_shell_set_has_mnemonics(UNITY_GTK_MENU_SHELL(user_data), has_mnemonics); } static void unity_gtk_menu_shell_clear_menu_shell(UnityGtkMenuShell *shell); static void unity_gtk_menu_shell_set_menu_shell(UnityGtkMenuShell *shell, GtkMenuShell *menu_shell) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); if (menu_shell != shell->menu_shell) { GPtrArray *items = shell->items; GPtrArray *sections = shell->sections; GSequence *visible_indices = shell->visible_indices; GSequence *separator_indices = shell->separator_indices; if (shell->action_group != NULL) unity_gtk_action_group_disconnect_shell(shell->action_group, shell); if (shell->menu_shell != NULL) g_signal_handlers_disconnect_by_data(shell->menu_shell, shell); if (separator_indices != NULL) { shell->separator_indices = NULL; g_sequence_free(separator_indices); } if (visible_indices != NULL) { shell->visible_indices = NULL; g_sequence_free(visible_indices); } if (sections != NULL) { shell->sections = NULL; g_ptr_array_unref(sections); } if (items != NULL) { shell->items = NULL; g_ptr_array_unref(items); } if (shell->menu_shell != NULL) g_object_steal_qdata(G_OBJECT(shell->menu_shell), menu_shell_quark()); shell->menu_shell = menu_shell; if (menu_shell != NULL) { g_object_set_qdata_full(G_OBJECT(menu_shell), menu_shell_quark(), shell, (GDestroyNotify) unity_gtk_menu_shell_clear_menu_shell); g_signal_connect(menu_shell, "insert", G_CALLBACK(unity_gtk_menu_shell_handle_shell_insert), shell); } } } static void unity_gtk_menu_shell_clear_menu_shell(UnityGtkMenuShell *shell) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); unity_gtk_menu_shell_set_menu_shell(shell, NULL); } static void unity_gtk_menu_shell_dispose(GObject *object) { UnityGtkMenuShell *shell; GtkSettings *settings; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(object)); shell = UNITY_GTK_MENU_SHELL(object); settings = gtk_settings_get_default(); unity_gtk_menu_shell_set_menu_shell(shell, NULL); if (settings != NULL) g_signal_handlers_disconnect_by_data(settings, shell); G_OBJECT_CLASS(unity_gtk_menu_shell_parent_class)->dispose(object); } static gboolean unity_gtk_menu_shell_is_mutable(GMenuModel *model) { g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(model), TRUE); return TRUE; } static gint unity_gtk_menu_shell_get_n_items(GMenuModel *model) { g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(model), 0); return unity_gtk_menu_shell_get_sections(UNITY_GTK_MENU_SHELL(model))->len; } static void unity_gtk_menu_shell_get_item_attributes(GMenuModel *model, gint item_index, GHashTable **attributes) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(model)); g_return_if_fail(0 <= item_index && item_index < g_menu_model_get_n_items(model)); g_return_if_fail(attributes != NULL); *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_variant_unref); } static void unity_gtk_menu_shell_get_item_links(GMenuModel *model, gint item_index, GHashTable **links) { UnityGtkMenuShell *shell; GPtrArray *sections; UnityGtkMenuSection *section; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(model)); g_return_if_fail(0 <= item_index && item_index < g_menu_model_get_n_items(model)); g_return_if_fail(links != NULL); shell = UNITY_GTK_MENU_SHELL(model); sections = unity_gtk_menu_shell_get_sections(shell); section = g_ptr_array_index(sections, item_index); *links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); g_hash_table_insert(*links, G_MENU_LINK_SECTION, g_object_ref(section)); } static void unity_gtk_menu_shell_class_init(UnityGtkMenuShellClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GMenuModelClass *menu_model_class = G_MENU_MODEL_CLASS(klass); object_class->dispose = unity_gtk_menu_shell_dispose; menu_model_class->is_mutable = unity_gtk_menu_shell_is_mutable; menu_model_class->get_n_items = unity_gtk_menu_shell_get_n_items; menu_model_class->get_item_attributes = unity_gtk_menu_shell_get_item_attributes; menu_model_class->get_item_links = unity_gtk_menu_shell_get_item_links; } static void unity_gtk_menu_shell_init(UnityGtkMenuShell *self) { self->has_mnemonics = TRUE; } /** * unity_gtk_menu_shell_new: * @menu_shell: a #GtkMenuShell to watch. * * Creates a new #UnityGtkMenuShell based on the contents of the given * @menu_shell. Any subsequent changes to @menu_shell are reflected in * the returned #UnityGtkMenuShell. * * Returns: a new #UnityGtkMenuShell based on @menu_shell. */ UnityGtkMenuShell *unity_gtk_menu_shell_new(GtkMenuShell *menu_shell) { UnityGtkMenuShell *shell = g_object_new(UNITY_GTK_TYPE_MENU_SHELL, NULL); GtkSettings *settings = gtk_settings_get_default(); if (settings != NULL) { g_signal_connect(settings, "notify::gtk-enable-mnemonics", G_CALLBACK(unity_gtk_menu_shell_handle_settings_notify), shell); g_object_get(settings, "gtk-enable-mnemonics", &shell->has_mnemonics, NULL); } unity_gtk_menu_shell_set_menu_shell(shell, menu_shell); return shell; } UnityGtkMenuShell *unity_gtk_menu_shell_new_internal(GtkMenuShell *menu_shell) { UnityGtkMenuShell *shell = g_object_new(UNITY_GTK_TYPE_MENU_SHELL, NULL); unity_gtk_menu_shell_set_menu_shell(shell, menu_shell); return shell; } UnityGtkMenuItem *unity_gtk_menu_shell_get_item(UnityGtkMenuShell *shell, guint index) { GPtrArray *items; g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL); items = unity_gtk_menu_shell_get_items(shell); g_return_val_if_fail(index < items->len, NULL); return g_ptr_array_index(items, index); } GSequence *unity_gtk_menu_shell_get_visible_indices(UnityGtkMenuShell *shell) { g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL); if (shell->visible_indices == NULL) { GPtrArray *items = unity_gtk_menu_shell_get_items(shell); guint i; shell->visible_indices = g_sequence_new(NULL); for (i = 0; i < items->len; i++) { UnityGtkMenuItem *item = g_ptr_array_index(items, i); if (unity_gtk_menu_item_is_visible(item)) g_sequence_append(shell->visible_indices, GUINT_TO_POINTER(i)); } if (shell->action_group != NULL) unity_gtk_action_group_connect_shell(shell->action_group, shell); } return shell->visible_indices; } GSequence *unity_gtk_menu_shell_get_separator_indices(UnityGtkMenuShell *shell) { g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL); unity_gtk_menu_shell_get_visible_indices(shell); if (shell->separator_indices == NULL) { GPtrArray *items = unity_gtk_menu_shell_get_items(shell); guint i; shell->separator_indices = g_sequence_new(NULL); for (i = 0; i < items->len; i++) { UnityGtkMenuItem *item = g_ptr_array_index(items, i); if (unity_gtk_menu_item_is_visible(item) && unity_gtk_menu_item_is_separator(item)) g_sequence_append(shell->separator_indices, GUINT_TO_POINTER(i)); } } return shell->separator_indices; } void unity_gtk_menu_shell_handle_item_notify(UnityGtkMenuShell *shell, UnityGtkMenuItem *item, const char *property) { static const char *visible_name; static const char *sensitive_name; static const char *label_name; static const char *use_underline_name; static const char *accel_path_name; static const char *active_name; static const char *parent_name; static const char *submenu_name; const char *name; g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); if (G_UNLIKELY(visible_name == NULL)) visible_name = g_intern_static_string("visible"); if (G_UNLIKELY(sensitive_name == NULL)) sensitive_name = g_intern_static_string("sensitive"); if (G_UNLIKELY(label_name == NULL)) label_name = g_intern_static_string("label"); if (G_UNLIKELY(use_underline_name == NULL)) use_underline_name = g_intern_static_string("use-underline"); if (G_UNLIKELY(accel_path_name == NULL)) accel_path_name = g_intern_static_string("accel-path"); if (G_UNLIKELY(active_name == NULL)) active_name = g_intern_static_string("active"); if (G_UNLIKELY(parent_name == NULL)) parent_name = g_intern_static_string("parent"); if (G_UNLIKELY(submenu_name == NULL)) submenu_name = g_intern_static_string("submenu"); name = g_intern_string(property); if (unity_gtk_menu_shell_is_debug()) g_print("%s ((%s *) %p, (%s *) %p { \"%s\" }, %s)\n", G_STRFUNC, G_OBJECT_TYPE_NAME(shell), shell, G_OBJECT_TYPE_NAME(item), item, unity_gtk_menu_item_get_label(item), name); if (name == visible_name) unity_gtk_menu_shell_handle_item_visible(shell, item); else if (name == sensitive_name) unity_gtk_menu_shell_handle_item_sensitive(shell, item); else if (name == label_name) unity_gtk_menu_shell_handle_item_label(shell, item); else if (name == use_underline_name) unity_gtk_menu_shell_handle_item_use_underline(shell, item); else if (name == accel_path_name) unity_gtk_menu_shell_handle_item_accel_path(shell, item); else if (name == active_name) unity_gtk_menu_shell_handle_item_active(shell, item); else if (name == parent_name) unity_gtk_menu_shell_handle_item_parent(shell, item); else if (name == submenu_name) unity_gtk_menu_shell_handle_item_submenu(shell, item); } void unity_gtk_menu_shell_activate_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item) { g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell)); g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item)); if (item->menu_item != NULL) { if (GTK_IS_MENU(shell->menu_shell)) gtk_menu_set_active(GTK_MENU(shell->menu_shell), item->item_index); /* * We dispatch the menu item activation in an idle to fix LP: #1258669. * * We get a deadlock when the menu item is activated if something like * gtk_dialog_run () is called. gtk_dialog_run () releases the GDK lock * just before starting its own main loop, and tries to re-acquire it * once it terminates. For whatever reason, a direct call to * gtk_menu_item_activate () here causes the GDK lock to be acquired * before gtk_dialog_run () tries to acquire it, whereas dispatching it * using gdk_threads_add_idle_full () seems to cleanly acquire the lock * once only at the beginning, preventing the deadlock. * * Suspicion is that this was executing during the main context * iteration of gtk_main_iteration (), which grabs the GDK lock * immediately after. But it's still not clear how that's possible.... */ gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE, gtk_menu_item_handle_idle_activate, g_object_ref(item->menu_item), g_object_unref); } } void unity_gtk_menu_shell_print(UnityGtkMenuShell *shell, guint indent) { char *space; g_return_if_fail(shell == NULL || UNITY_GTK_IS_MENU_SHELL(shell)); space = g_strnfill(indent, ' '); if (shell != NULL) { g_print("%s(%s *) %p\n", space, G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(shell)), shell); if (shell->menu_shell != NULL) g_print("%s (%s *) %p\n", space, G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(shell->menu_shell)), shell->menu_shell); if (shell->items != NULL) { guint i; for (i = 0; i < shell->items->len; i++) unity_gtk_menu_item_print(g_ptr_array_index(shell->items, i), indent + 2); } if (shell->sections != NULL) { guint i; for (i = 0; i < shell->sections->len; i++) unity_gtk_menu_section_print(g_ptr_array_index(shell->sections, i), indent + 2); } if (shell->visible_indices != NULL) { GSequenceIter *iter = g_sequence_get_begin_iter(shell->visible_indices); g_print("%s ", space); while (!g_sequence_iter_is_end(iter)) { g_print(" %u", g_sequence_get_uint(iter)); iter = g_sequence_iter_next(iter); } g_print("\n"); } if (shell->separator_indices != NULL) { GSequenceIter *iter = g_sequence_get_begin_iter(shell->separator_indices); g_print("%s ", space); while (!g_sequence_iter_is_end(iter)) { g_print(" %u", g_sequence_get_uint(iter)); iter = g_sequence_iter_next(iter); } g_print("\n"); } if (shell->action_group != NULL) g_print("%s (%s *) %p\n", space, G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(shell->action_group)), shell->action_group); } else g_print("%sNULL\n", space); g_free(space); } gboolean unity_gtk_menu_shell_is_debug(void) { return unity_gtk_menu_shell_debug; } /** * unity_gtk_menu_shell_set_debug: * @debug: #TRUE to enable debugging output * * Sets if menu shell changes should be logged using g_print (). */ void unity_gtk_menu_shell_set_debug(gboolean debug) { unity_gtk_menu_shell_debug = debug; }