/* * 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-menu-shell-private.h" #include "unity-gtk-menu-section-private.h" #include "unity-gtk-action-group-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); item->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 gchar *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 (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 (0 <= index && 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 gchar *property) { static const gchar *visible_name; static const gchar *sensitive_name; static const gchar *label_name; static const gchar *use_underline_name; static const gchar *accel_path_name; static const gchar *active_name; static const gchar *parent_name; static const gchar *submenu_name; const gchar *name; g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (shell)); g_return_if_fail (UNITY_GTK_IS_MENU_ITEM (item)); if (visible_name == NULL) visible_name = g_intern_static_string ("visible"); if (sensitive_name == NULL) sensitive_name = g_intern_static_string ("sensitive"); if (label_name == NULL) label_name = g_intern_static_string ("label"); if (use_underline_name == NULL) use_underline_name = g_intern_static_string ("use-underline"); if (accel_path_name == NULL) accel_path_name = g_intern_static_string ("accel-path"); if (active_name == NULL) active_name = g_intern_static_string ("active"); if (parent_name == NULL) parent_name = g_intern_static_string ("parent"); if (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) { gchar *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; }