diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 11c3ab513..77196e3c7 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -1799,8 +1799,8 @@ const RemoteMenu = new Lang.Class({ this.model = model; this.actionGroup = actionGroup; - this._actions = { }; - this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this); + this._actions = {}; + this._trackMenu(model, this); this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged)); this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged)); @@ -1858,7 +1858,7 @@ const RemoteMenu = new Lang.Class({ break; default: log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string())); - return [null, false]; + return; } } else { target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null); @@ -1878,8 +1878,18 @@ const RemoteMenu = new Lang.Class({ if (pos != -1) action.items.splice(pos, 1); })); + }, - return [item, false]; + _trackMenu: function(model, item) { + item._tracker = Shell.MenuTracker.new(model, + null, /* action namespace */ + Lang.bind(this, this._insertItem, item), + Lang.bind(this, this._removeItem, item)); + + item.connect('destroy', function() { + item._tracker.destroy(); + item._tracker = null; + }); }, _createMenuItem: function(model, index) { @@ -1888,26 +1898,11 @@ const RemoteMenu = new Lang.Class({ // remove all underscores that are not followed by another underscore label = label.replace(/_([^_])/, '$1'); - let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION); - if (section_link) { - let item = new PopupMenuSection(); - if (label) { - let title = new PopupMenuItem(label, { reactive: false, - style_class: 'popup-subtitle-menu-item' }); - item._titleMenuItem = title; - title._ignored = true; - item.addMenuItem(title); - } - this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item); - return [item, true]; - } - - let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU); - - if (submenu_link) { + let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU); + if (submenuModel) { let item = new PopupSubMenuMenuItem(label); - this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu); - return [item, false]; + this._trackMenu(submenuModel, item.menu); + return item; } let item = new PopupMenuItem(label); @@ -1918,7 +1913,7 @@ const RemoteMenu = new Lang.Class({ if (this.actionGroup.has_action(action_id)) { this._actionAdded(model, item, index); - return [item, false]; + return item; } let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, actionName) { @@ -1931,85 +1926,6 @@ const RemoteMenu = new Lang.Class({ return [item, false]; }, - _modelChanged: function(model, position, removed, added, target) { - let j, k; - let j0, k0; - - let currentItems = target._getMenuItems(); - - k0 = 0; - // skip ignored items at the beginning - while (k0 < currentItems.length && currentItems[k0]._ignored) - k0++; - // find the right menu item matching the model item - for (j0 = 0; k0 < currentItems.length && j0 < position; j0++, k0++) { - if (currentItems[k0]._ignored) - k0++; - } - - if (removed == -1) { - // special flag to indicate we should destroy everything - for (k = k0; k < currentItems.length; k++) - currentItems[k].destroy(); - } else { - for (j = j0, k = k0; k < currentItems.length && j < j0 + removed; j++, k++) { - currentItems[k].destroy(); - - if (currentItems[k]._ignored) - j--; - } - } - - for (j = j0, k = k0; j < j0 + added; j++, k++) { - let [item, addSeparator] = this._createMenuItem(model, j); - - if (!item) - continue; - - // separators must be added in the parent to make autohiding work - if (addSeparator) { - let separator = new PopupSeparatorMenuItem(); - item.separators.push(separator); - separator._ignored = true; - target.addMenuItem(separator, k+1); - k++; - } - - target.addMenuItem(item, k); - - if (addSeparator) { - let separator = new PopupSeparatorMenuItem(); - item.separators.push(separator); - separator._ignored = true; - target.addMenuItem(separator, k+1); - k++; - } - } - - if (!model._changedId) { - model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target)); - model._destroyId = target.connect('destroy', function() { - if (model._changedId) - model.disconnect(model._changedId); - if (model._destroyId) - target.disconnect(model._destroyId); - model._changedId = 0; - model._destroyId = 0; - }); - } - - if (target instanceof PopupMenuSection) { - if (target._titleMenuItem) - target.actor.visible = target.numMenuItems != 1; - else - target.actor.visible = target.numMenuItems != 0; - } else { - let sourceItem = target.sourceActor._delegate; - if (sourceItem instanceof PopupSubMenuMenuItem) - sourceItem.actor.visible = target.numMenuItems != 0; - } - }, - _actionStateChanged: function(actionGroup, action_id) { let action = this._actions[action_id]; if (!action) @@ -2047,7 +1963,23 @@ const RemoteMenu = new Lang.Class({ item.actor.reactive = item.actor.can_focus = action.enabled; } } - } + }, + + _insertItem: function(position, model, item_index, action_namespace, is_separator, target) { + let item; + + if (is_separator) + item = new PopupSeparatorMenuItem(); + else + item = this._createMenuItem(model, item_index); + + target.addMenuItem(item, position); + }, + + _removeItem: function(position, target) { + let items = target._getMenuItems(); + items[position].destroy(); + }, }); /* Basic implementation of a menu manager. diff --git a/src/Makefile.am b/src/Makefile.am index 5afff4069..58e92bd35 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -135,7 +135,10 @@ shell_private_sources = \ gactionobservable.h \ gactionobservable.c \ gactionobserver.h \ - gactionobserver.c + gactionobserver.c \ + gtkmenutracker.c \ + gtkmenutracker.h \ + $(NULL) libgnome_shell_la_SOURCES = \ $(shell_built_sources) \ @@ -158,6 +161,8 @@ libgnome_shell_la_SOURCES = \ shell-invert-lightness-effect.c \ shell-keyring-prompt.h \ shell-keyring-prompt.c \ + shell-menu-tracker.c \ + shell-menu-tracker.h \ shell-mount-operation.c \ shell-network-agent.c \ shell-perf-log.c \ diff --git a/src/gtkmenutracker.c b/src/gtkmenutracker.c new file mode 100644 index 000000000..f49457f11 --- /dev/null +++ b/src/gtkmenutracker.c @@ -0,0 +1,434 @@ +/* + * Copyright © 2013 Canonical Limited + * + * This library 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; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gtkmenutracker.h" + +typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection; + +struct _GtkMenuTracker +{ + GtkMenuTrackerInsertFunc insert_func; + GtkMenuTrackerRemoveFunc remove_func; + gpointer user_data; + + GtkMenuTrackerSection *toplevel; +}; + +struct _GtkMenuTrackerSection +{ + GMenuModel *model; + GSList *items; + gchar *action_namespace; + + guint with_separators : 1; + guint has_separator : 1; + + gulong handler; +}; + +static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, + GMenuModel *model, + gboolean with_separators, + gint offset, + const gchar *action_namespace); +static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section); + +static GtkMenuTrackerSection * +gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, + GMenuModel *model, + gint *offset) +{ + GSList *item; + + if (section->has_separator) + (*offset)++; + + if (section->model == model) + return section; + + for (item = section->items; item; item = item->next) + { + GtkMenuTrackerSection *subsection = item->data; + + if (subsection) + { + GtkMenuTrackerSection *found_section; + + found_section = gtk_menu_tracker_section_find_model (subsection, model, offset); + + if (found_section) + return found_section; + } + else + (*offset)++; + } + + return FALSE; +} + +/* this is responsible for syncing the showing of a separator for a + * single subsection (and its children). + * + * we only ever show separators if we have _actual_ children (ie: we do + * not show a separator if the section contains only empty child + * sections). it's difficult to determine this on-the-fly, so we have + * this separate function to come back later and figure it out. + * + * 'section' is that section. + * + * 'tracker' is passed in so that we can emit callbacks when we decide + * to add/remove separators. + * + * 'offset' is passed in so we know which position to emit in our + * callbacks. ie: if we add a separator right at the top of this + * section then we would emit it with this offset. deeper inside, we + * adjust accordingly. + * + * could_have_separator is true in two situations: + * + * - our parent section had with_separators defined and we are not the + * first section (ie: we should add a separator if we have content in + * order to divide us from the items above) + * + * - if we had a 'label' attribute set for this section + * + * parent_model and parent_index are passed in so that we can give them + * to the insertion callback so that it can see the label (and anything + * else that happens to be defined on the section). + * + * we iterate each item in ourselves. for subsections, we recursively + * run ourselves to sync separators. after we are done, we notice if we + * have any items in us or if we are completely empty and sync if our + * separator is shown or not. + */ +static gint +gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, + GtkMenuTracker *tracker, + gint offset, + gboolean could_have_separator, + GMenuModel *parent_model, + gint parent_index) +{ + gboolean should_have_separator; + gint n_items = 0; + GSList *item; + gint i = 0; + + for (item = section->items; item; item = item->next) + { + GtkMenuTrackerSection *subsection = item->data; + + if (subsection) + { + gboolean could_have_separator; + + could_have_separator = (section->with_separators && i > 0) || + g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL); + + n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items, + could_have_separator, section->model, i); + } + else + n_items++; + + i++; + } + + should_have_separator = could_have_separator && n_items != 0; + + if (should_have_separator > section->has_separator) + { + /* Add a separator */ + (* tracker->insert_func) (offset, parent_model, parent_index, NULL, TRUE, tracker->user_data); + section->has_separator = TRUE; + } + else if (should_have_separator < section->has_separator) + { + /* Remove a separator */ + (* tracker->remove_func) (offset, tracker->user_data); + section->has_separator = FALSE; + } + + n_items += section->has_separator; + + return n_items; +} + +static gint +gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section) +{ + GSList *item; + gint n_items; + + if (section == NULL) + return 1; + + n_items = 0; + + if (section->has_separator) + n_items++; + + for (item = section->items; item; item = item->next) + n_items += gtk_menu_tracker_section_measure (item->data); + + return n_items; +} + +static void +gtk_menu_tracker_remove_items (GtkMenuTracker *tracker, + GSList **change_point, + gint offset, + gint n_items) +{ + gint i; + + for (i = 0; i < n_items; i++) + { + GtkMenuTrackerSection *subsection; + gint n; + + subsection = (*change_point)->data; + *change_point = g_slist_delete_link (*change_point, *change_point); + + n = gtk_menu_tracker_section_measure (subsection); + gtk_menu_tracker_section_free (subsection); + + while (n--) + (* tracker->remove_func) (offset, tracker->user_data); + } +} + +static void +gtk_menu_tracker_add_items (GtkMenuTracker *tracker, + GtkMenuTrackerSection *section, + GSList **change_point, + gint offset, + GMenuModel *model, + gint position, + gint n_items) +{ + while (n_items--) + { + GMenuModel *submenu; + + submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION); + g_assert (submenu != model); + if (submenu != NULL) + { + GtkMenuTrackerSection *subsection; + gchar *action_namespace = NULL; + + g_menu_model_get_item_attribute (model, position + n_items, + G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace); + + if (section->action_namespace) + { + gchar *namespace; + + namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace); + g_free (namespace); + } + else + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace); + + *change_point = g_slist_prepend (*change_point, subsection); + g_free (action_namespace); + g_object_unref (submenu); + } + else + { + (* tracker->insert_func) (offset, model, position + n_items, + section->action_namespace, FALSE, tracker->user_data); + *change_point = g_slist_prepend (*change_point, NULL); + } + } +} + +static void +gtk_menu_tracker_model_changed (GMenuModel *model, + gint position, + gint removed, + gint added, + gpointer user_data) +{ + GtkMenuTracker *tracker = user_data; + GtkMenuTrackerSection *section; + GSList **change_point; + gint offset = 0; + gint i; + + /* First find which section the changed model corresponds to, and the + * position of that section within the overall menu. + */ + section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset); + + /* Next, seek through that section to the change point. This gives us + * the correct GSList** to make the change to and also finds the final + * offset at which we will make the changes (by measuring the number + * of items within each item of the section before the change point). + */ + change_point = §ion->items; + for (i = 0; i < position; i++) + { + offset += gtk_menu_tracker_section_measure ((*change_point)->data); + change_point = &(*change_point)->next; + } + + /* We remove items in order and add items in reverse order. This + * means that the offset used for all inserts and removes caused by a + * single change will be the same. + * + * This also has a performance advantage: GtkMenuShell stores the + * menu items in a linked list. In the case where we are creating a + * menu for the first time, adding the items in reverse order means + * that we only ever insert at index zero, prepending the list. This + * means that we can populate in O(n) time instead of O(n^2) that we + * would do by appending. + */ + gtk_menu_tracker_remove_items (tracker, change_point, offset, removed); + gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added); + + /* The offsets for insertion/removal of separators will be all over + * the place, however... + */ + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); +} + +static void +gtk_menu_tracker_section_free (GtkMenuTrackerSection *section) +{ + if (section == NULL) + return; + + g_signal_handler_disconnect (section->model, section->handler); + g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free); + g_free (section->action_namespace); + g_object_unref (section->model); + g_slice_free (GtkMenuTrackerSection, section); +} + +static GtkMenuTrackerSection * +gtk_menu_tracker_section_new (GtkMenuTracker *tracker, + GMenuModel *model, + gboolean with_separators, + gint offset, + const gchar *action_namespace) +{ + GtkMenuTrackerSection *section; + + section = g_slice_new0 (GtkMenuTrackerSection); + section->model = g_object_ref (model); + section->with_separators = with_separators; + section->action_namespace = g_strdup (action_namespace); + + gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model)); + section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker); + + return section; +} + +/*< private > + * gtk_menu_tracker_new: + * @model: the model to flatten + * @with_separators: if the toplevel should have separators (ie: TRUE + * for menus, FALSE for menubars) + * @action_namespace: the passed-in action namespace + * @insert_func: insert callback + * @remove_func: remove callback + * @user_data user data for callbacks + * + * Creates a GtkMenuTracker for @model, holding a ref on @model for as + * long as the tracker is alive. + * + * This flattens out the model, merging sections and inserting + * separators where appropriate. It monitors for changes and performs + * updates on the fly. It also handles action_namespace for subsections + * (but you will need to handle it yourself for submenus). + * + * When the tracker is first created, @insert_func will be called many + * times to populate the menu with the initial contents of @model + * (unless it is empty), before gtk_menu_tracker_new() returns. For + * this reason, the menu that is using the tracker ought to be empty + * when it creates the tracker. + * + * Future changes to @model will result in more calls to @insert_func + * and @remove_func. + * + * The position argument to both functions is the linear 0-based + * position in the menu at which the item in question should be inserted + * or removed. + * + * For @insert_func, @model and @item_index are used to get the + * information about the menu item to insert. @action_namespace is the + * action namespace that actions referred to from that item should place + * themselves in. Note that if the item is a submenu and the + * "action-namespace" attribute is defined on the item, it will _not_ be + * applied to the @action_namespace argument as it is meant for the + * items inside of the submenu, not the submenu item itself. + * + * @is_separator is set to %TRUE in case the item being added is a + * separator. @model and @item_index will still be meaningfully set in + * this case -- to the section menu item corresponding to the separator. + * This is useful if the section specifies a label, for example. If + * there is an "action-namespace" attribute on this menu item then it + * should be ignored by the consumer because #GtkMenuTracker has already + * handled it. + * + * When using #GtkMenuTracker there is no need to hold onto @model or + * monitor it for changes. The model will be unreffed when + * gtk_menu_tracker_free() is called. + */ +GtkMenuTracker * +gtk_menu_tracker_new (GMenuModel *model, + gboolean with_separators, + const gchar *action_namespace, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data) +{ + GtkMenuTracker *tracker; + + tracker = g_slice_new (GtkMenuTracker); + tracker->insert_func = insert_func; + tracker->remove_func = remove_func; + tracker->user_data = user_data; + + tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace); + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); + + return tracker; +} + +/*< private > + * gtk_menu_tracker_free: + * @tracker: a #GtkMenuTracker + * + * Frees the tracker, ... + */ +void +gtk_menu_tracker_free (GtkMenuTracker *tracker) +{ + gtk_menu_tracker_section_free (tracker->toplevel); + g_slice_free (GtkMenuTracker, tracker); +} diff --git a/src/gtkmenutracker.h b/src/gtkmenutracker.h new file mode 100644 index 000000000..51810a104 --- /dev/null +++ b/src/gtkmenutracker.h @@ -0,0 +1,51 @@ +/* + * Copyright © 2013 Canonical Limited + * + * This library 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; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#ifndef __GTK_MENU_TRACKER_H__ +#define __GTK_MENU_TRACKER_H__ + +#include + +typedef struct _GtkMenuTracker GtkMenuTracker; + +typedef void (* GtkMenuTrackerInsertFunc) (gint position, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator, + gpointer user_data); + +typedef void (* GtkMenuTrackerRemoveFunc) (gint position, + gpointer user_data); + + +G_GNUC_INTERNAL +GtkMenuTracker * gtk_menu_tracker_new (GMenuModel *model, + gboolean with_separators, + const gchar *action_namespace, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data); + +G_GNUC_INTERNAL +void gtk_menu_tracker_free (GtkMenuTracker *tracker); + +#endif /* __GTK_MENU_TRACKER_H__ */ diff --git a/src/shell-menu-tracker.c b/src/shell-menu-tracker.c new file mode 100644 index 000000000..788605ec4 --- /dev/null +++ b/src/shell-menu-tracker.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2013 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: + * Jasper St. Pierre + */ + +#include "config.h" + +#include "shell-menu-tracker.h" +#include "gtkmenutracker.h" + +struct _ShellMenuTracker +{ + guint ref_count; + + GtkMenuTracker *tracker; + + ShellMenuTrackerInsertFunc insert_func; + gpointer insert_user_data; + GDestroyNotify insert_notify; + ShellMenuTrackerRemoveFunc remove_func; + gpointer remove_user_data; + GDestroyNotify remove_notify; +}; + +static void +shell_menu_tracker_insert_func (gint position, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator, + gpointer user_data) +{ + ShellMenuTracker *tracker = (ShellMenuTracker *) user_data; + tracker->insert_func (position, model, item_index, + action_namespace, is_separator, + tracker->insert_user_data); +} + +static void +shell_menu_tracker_remove_func (gint position, + gpointer user_data) +{ + ShellMenuTracker *tracker = (ShellMenuTracker *) user_data; + tracker->remove_func (position, tracker->remove_user_data); +} + +/** + * shell_menu_tracker_new: + * @model: + * @action_namespace: (allow-none): + * @insert_func: + * @insert_user_data: + * @insert_notify: + * @remove_func: + * @remove_user_data: + * @remove_notify: + */ +ShellMenuTracker * +shell_menu_tracker_new (GMenuModel *model, + const gchar *action_namespace, + ShellMenuTrackerInsertFunc insert_func, + gpointer insert_user_data, + GDestroyNotify insert_notify, + ShellMenuTrackerRemoveFunc remove_func, + gpointer remove_user_data, + GDestroyNotify remove_notify) +{ + ShellMenuTracker *tracker = g_slice_new0 (ShellMenuTracker); + + tracker->ref_count = 1; + tracker->insert_func = insert_func; + tracker->insert_user_data = insert_user_data; + tracker->insert_notify = insert_notify; + tracker->remove_func = remove_func; + tracker->remove_user_data = remove_user_data; + tracker->remove_notify = remove_notify; + + tracker->tracker = gtk_menu_tracker_new (model, + TRUE, /* with separators */ + action_namespace, + shell_menu_tracker_insert_func, + shell_menu_tracker_remove_func, + tracker); + + return tracker; +} + +ShellMenuTracker * +shell_menu_tracker_ref (ShellMenuTracker *tracker) +{ + tracker->ref_count++; + return tracker; +} + +void +shell_menu_tracker_unref (ShellMenuTracker *tracker) +{ + if (tracker->ref_count-- <= 0) + { + shell_menu_tracker_destroy (tracker); + g_slice_free (ShellMenuTracker, tracker); + } +} + +void +shell_menu_tracker_destroy (ShellMenuTracker *tracker) +{ + if (tracker->tracker != NULL) + { + gtk_menu_tracker_free (tracker->tracker); + tracker->tracker = NULL; + tracker->insert_notify (tracker->insert_user_data); + tracker->remove_notify (tracker->remove_user_data); + } +} + +G_DEFINE_BOXED_TYPE(ShellMenuTracker, + shell_menu_tracker, + shell_menu_tracker_ref, + shell_menu_tracker_unref) diff --git a/src/shell-menu-tracker.h b/src/shell-menu-tracker.h new file mode 100644 index 000000000..9af602e00 --- /dev/null +++ b/src/shell-menu-tracker.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2013 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: + * Jasper St. Pierre + */ + +#ifndef __SHELL_MENU_TRACKER_H__ +#define __SHELL_MENU_TRACKER_H__ + +#include + +typedef struct _ShellMenuTracker ShellMenuTracker; + +GType shell_menu_tracker_get_type (void) G_GNUC_CONST; + +typedef void (* ShellMenuTrackerInsertFunc) (gint position, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator, + gpointer user_data); + +typedef void (* ShellMenuTrackerRemoveFunc) (gint position, + gpointer user_data); + +ShellMenuTracker * shell_menu_tracker_new (GMenuModel *model, + const gchar *action_namespace, + ShellMenuTrackerInsertFunc insert_func, + gpointer insert_user_data, + GDestroyNotify insert_notify, + ShellMenuTrackerRemoveFunc remove_func, + gpointer remove_user_data, + GDestroyNotify remove_notify); +ShellMenuTracker * shell_menu_tracker_ref (ShellMenuTracker *tracker); +void shell_menu_tracker_unref (ShellMenuTracker *tracker); +void shell_menu_tracker_destroy (ShellMenuTracker *tracker); + +#endif /* __SHELL_MENU_TRACKER_H__ */