606 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			606 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | ||
|  * 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, see <http://www.gnu.org/licenses/>.
 | ||
|  *
 | ||
|  * Author: Ryan Lortie <desrt@desrt.ca>
 | ||
|  */
 | ||
| 
 | ||
| #include "config.h"
 | ||
| 
 | ||
| #include "gtkmenutracker.h"
 | ||
| 
 | ||
| /*< private >
 | ||
|  * SECTION:gtkmenutracker
 | ||
|  * @Title: GtkMenuTracker
 | ||
|  * @Short_description: A helper class for interpreting #GMenuModel
 | ||
|  *
 | ||
|  * #GtkMenuTracker is a simple object to ease implementations of #GMenuModel.
 | ||
|  * Given a #GtkActionObservable (usually a #GActionMuxer) along with a
 | ||
|  * #GMenuModel, it will tell you which menu items to create and where to place
 | ||
|  * them. If a menu item is removed, it will tell you the position of the menu
 | ||
|  * item to remove.
 | ||
|  *
 | ||
|  * Using #GtkMenuTracker is fairly simple. The only guarantee you must make
 | ||
|  * to #GtkMenuTracker is that you must obey all insert signals and track the
 | ||
|  * position of items that #GtkMenuTracker gives you. That is, #GtkMenuTracker
 | ||
|  * expects positions of all the latter items to change when it calls your
 | ||
|  * insertion callback with an early position, as it may ask you to remove
 | ||
|  * an item with a readjusted position later.
 | ||
|  *
 | ||
|  * #GtkMenuTracker will give you a #GtkMenuTrackerItem in your callback. You
 | ||
|  * must hold onto this object until a remove signal is emitted. This item
 | ||
|  * represents a single menu item, which can be one of three classes: normal item,
 | ||
|  * separator, or submenu.
 | ||
|  *
 | ||
|  * Certain properties on the #GtkMenuTrackerItem are mutable, and you must
 | ||
|  * listen for changes in the item. For more details, see the documentation
 | ||
|  * for #GtkMenuTrackerItem along with https://live.gnome.org/GApplication/GMenuModel.
 | ||
|  *
 | ||
|  * The idea of @with_separators is for special cases where menu models may
 | ||
|  * be tracked in places where separators are not available, like in toplevel
 | ||
|  * "File", “Edit” menu bars. Ignoring separator items is wrong, as #GtkMenuTracker
 | ||
|  * expects the position to change, so we must tell #GtkMenuTracker to ignore
 | ||
|  * separators itself.
 | ||
|  */
 | ||
| 
 | ||
| typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
 | ||
| 
 | ||
| struct _GtkMenuTracker
 | ||
| {
 | ||
|   GtkActionObservable      *observable;
 | ||
|   gboolean                  merge_sections;
 | ||
|   GtkMenuTrackerInsertFunc  insert_func;
 | ||
|   GtkMenuTrackerRemoveFunc  remove_func;
 | ||
|   gpointer                  user_data;
 | ||
| 
 | ||
|   GtkMenuTrackerSection    *toplevel;
 | ||
| };
 | ||
| 
 | ||
| struct _GtkMenuTrackerSection
 | ||
| {
 | ||
|   gpointer    model;   /* may be a GtkMenuTrackerItem or a GMenuModel */
 | ||
|   GSList     *items;
 | ||
|   gchar      *action_namespace;
 | ||
| 
 | ||
|   guint       separator_label : 1;
 | ||
|   guint       with_separators : 1;
 | ||
|   guint       has_separator   : 1;
 | ||
|   guint       is_fake         : 1;
 | ||
| 
 | ||
|   gulong      handler;
 | ||
| };
 | ||
| 
 | ||
| static GtkMenuTrackerSection *  gtk_menu_tracker_section_new    (GtkMenuTracker        *tracker,
 | ||
|                                                                  GMenuModel            *model,
 | ||
|                                                                  gboolean               with_separators,
 | ||
|                                                                  gboolean               separator_label,
 | ||
|                                                                  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,
 | ||
|                                      gpointer               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 NULL;
 | ||
| }
 | ||
| 
 | ||
| /* 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 there are items
 | ||
|  *    before us (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 && n_items > 0) || subsection->separator_label;
 | ||
| 
 | ||
|           /* Only pass the parent_model and parent_index in case they may be used to create the separator. */
 | ||
|           n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items,
 | ||
|                                                                could_have_separator,
 | ||
|                                                                could_have_separator ? section->model : NULL,
 | ||
|                                                                could_have_separator ? i : 0);
 | ||
|         }
 | ||
|       else
 | ||
|         n_items++;
 | ||
| 
 | ||
|       i++;
 | ||
|     }
 | ||
| 
 | ||
|   should_have_separator = !section->is_fake && could_have_separator && n_items != 0;
 | ||
| 
 | ||
|   if (should_have_separator > section->has_separator)
 | ||
|     {
 | ||
|       /* Add a separator */
 | ||
|       GtkMenuTrackerItem *item;
 | ||
| 
 | ||
|       item = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE);
 | ||
|       (* tracker->insert_func) (item, offset, tracker->user_data);
 | ||
|       g_object_unref (item);
 | ||
| 
 | ||
|       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 void
 | ||
| gtk_menu_tracker_item_visibility_changed (GtkMenuTrackerItem *item,
 | ||
|                                           GParamSpec         *pspec,
 | ||
|                                           gpointer            user_data)
 | ||
| {
 | ||
|   GtkMenuTracker *tracker = user_data;
 | ||
|   GtkMenuTrackerSection *section;
 | ||
|   gboolean is_now_visible;
 | ||
|   gboolean was_visible;
 | ||
|   gint offset = 0;
 | ||
| 
 | ||
|   is_now_visible = gtk_menu_tracker_item_get_is_visible (item);
 | ||
| 
 | ||
|   /* remember: the item is our model */
 | ||
|   section = gtk_menu_tracker_section_find_model (tracker->toplevel, item, &offset);
 | ||
| 
 | ||
|   was_visible = section->items != NULL;
 | ||
| 
 | ||
|   if (is_now_visible == was_visible)
 | ||
|     return;
 | ||
| 
 | ||
|   if (is_now_visible)
 | ||
|     {
 | ||
|       section->items = g_slist_prepend (NULL, NULL);
 | ||
|       (* tracker->insert_func) (section->model, offset, tracker->user_data);
 | ||
|     }
 | ||
|   else
 | ||
|     {
 | ||
|       section->items = g_slist_delete_link (section->items, section->items);
 | ||
|       (* tracker->remove_func) (offset, tracker->user_data);
 | ||
|     }
 | ||
| 
 | ||
|   gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
 | ||
| }
 | ||
| 
 | ||
| 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 && tracker->merge_sections)
 | ||
|         {
 | ||
|           GtkMenuTrackerSection *subsection;
 | ||
|           gchar *action_namespace = NULL;
 | ||
|           gboolean has_label;
 | ||
| 
 | ||
|           has_label = g_menu_model_get_item_attribute (model, position + n_items,
 | ||
|                                                        G_MENU_ATTRIBUTE_LABEL, "s", 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, has_label, offset, namespace);
 | ||
|               g_free (namespace);
 | ||
|             }
 | ||
|           else
 | ||
|             subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace);
 | ||
| 
 | ||
|           *change_point = g_slist_prepend (*change_point, subsection);
 | ||
|           g_free (action_namespace);
 | ||
|           g_object_unref (submenu);
 | ||
|         }
 | ||
|       else
 | ||
|         {
 | ||
|           GtkMenuTrackerItem *item;
 | ||
| 
 | ||
|           item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items,
 | ||
|                                              section->action_namespace, submenu != NULL);
 | ||
| 
 | ||
|           /* In the case that the item may disappear we handle that by
 | ||
|            * treating the item that we just created as being its own
 | ||
|            * subsection.  This happens as so:
 | ||
|            *
 | ||
|            *  - the subsection is created without the possibility of
 | ||
|            *    showing a separator
 | ||
|            *
 | ||
|            *  - the subsection will have either 0 or 1 item in it at all
 | ||
|            *    times: either the shown item or not (in the case it is
 | ||
|            *    hidden)
 | ||
|            *
 | ||
|            *  - the created item acts as the "model" for this section
 | ||
|            *    and we use its "visiblity-changed" signal in the same
 | ||
|            *    way that we use the "items-changed" signal from a real
 | ||
|            *    GMenuModel
 | ||
|            *
 | ||
|            * We almost never use the '->model' stored in the section for
 | ||
|            * anything other than lookups and for dropped the ref and
 | ||
|            * disconnecting the signal when we destroy the menu, and we
 | ||
|            * need to do exactly those things in this case as well.
 | ||
|            *
 | ||
|            * The only other thing that '->model' is used for is in the
 | ||
|            * case that we want to show a separator, but we will never do
 | ||
|            * that because separators are not shown for this fake section.
 | ||
|            */
 | ||
|           if (gtk_menu_tracker_item_may_disappear (item))
 | ||
|             {
 | ||
|               GtkMenuTrackerSection *fake_section;
 | ||
| 
 | ||
|               fake_section = g_slice_new0 (GtkMenuTrackerSection);
 | ||
|               fake_section->is_fake = TRUE;
 | ||
|               fake_section->model = g_object_ref (item);
 | ||
|               fake_section->handler = g_signal_connect (item, "notify::is-visible",
 | ||
|                                                         G_CALLBACK (gtk_menu_tracker_item_visibility_changed),
 | ||
|                                                         tracker);
 | ||
|               *change_point = g_slist_prepend (*change_point, fake_section);
 | ||
| 
 | ||
|               if (gtk_menu_tracker_item_get_is_visible (item))
 | ||
|                 {
 | ||
|                   (* tracker->insert_func) (item, offset, tracker->user_data);
 | ||
|                   fake_section->items = g_slist_prepend (NULL, NULL);
 | ||
|                 }
 | ||
|             }
 | ||
|           else
 | ||
|             {
 | ||
|               /* In the normal case, we store NULL in the linked list.
 | ||
|                * The measurement and lookup code count NULL always as
 | ||
|                * exactly 1: an item that will always be there.
 | ||
|                */
 | ||
|               (* tracker->insert_func) (item, offset, tracker->user_data);
 | ||
|               *change_point = g_slist_prepend (*change_point, NULL);
 | ||
|             }
 | ||
| 
 | ||
|           g_object_unref (item);
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| 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,
 | ||
|                               gboolean        separator_label,
 | ||
|                               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);
 | ||
|   section->separator_label = separator_label;
 | ||
| 
 | ||
|   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 (GtkActionObservable      *observable,
 | ||
|                       GMenuModel               *model,
 | ||
|                       gboolean                  with_separators,
 | ||
|                       gboolean                  merge_sections,
 | ||
|                       const gchar              *action_namespace,
 | ||
|                       GtkMenuTrackerInsertFunc  insert_func,
 | ||
|                       GtkMenuTrackerRemoveFunc  remove_func,
 | ||
|                       gpointer                  user_data)
 | ||
| {
 | ||
|   GtkMenuTracker *tracker;
 | ||
| 
 | ||
|   tracker = g_slice_new (GtkMenuTracker);
 | ||
|   tracker->merge_sections = merge_sections;
 | ||
|   tracker->observable = g_object_ref (observable);
 | ||
|   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, FALSE, 0, action_namespace);
 | ||
|   gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
 | ||
| 
 | ||
|   return tracker;
 | ||
| }
 | ||
| 
 | ||
| GtkMenuTracker *
 | ||
| gtk_menu_tracker_new_for_item_link (GtkMenuTrackerItem       *item,
 | ||
|                                     const gchar              *link_name,
 | ||
|                                     gboolean                  merge_sections,
 | ||
|                                     GtkMenuTrackerInsertFunc  insert_func,
 | ||
|                                     GtkMenuTrackerRemoveFunc  remove_func,
 | ||
|                                     gpointer                  user_data)
 | ||
| {
 | ||
|   GtkMenuTracker *tracker;
 | ||
|   GMenuModel *submenu;
 | ||
|   gchar *namespace;
 | ||
| 
 | ||
|   submenu = _gtk_menu_tracker_item_get_link (item, link_name);
 | ||
|   namespace = _gtk_menu_tracker_item_get_link_namespace (item);
 | ||
| 
 | ||
|   tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu,
 | ||
|                                   TRUE, merge_sections, namespace, insert_func, remove_func, user_data);
 | ||
| 
 | ||
|   g_object_unref (submenu);
 | ||
|   g_free (namespace);
 | ||
| 
 | ||
|   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_object_unref (tracker->observable);
 | ||
|   g_slice_free (GtkMenuTracker, tracker);
 | ||
| }
 | 
