Make the main (and only) entry-point to gtkmodelmenu.c the now-public gtk_menu_shell_bind_model(). Move the convenience constructors (gtk_menu_new_from_model() and gtk_menu_bar_new_from_model()) to their proper files. Remove the private header file. Simplify the code a bit by making the initial populate part of the bind() call. https://bugzilla.gnome.org/show_bug.cgi?id=682831
		
			
				
	
	
		
			323 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright © 2011 Red Hat, Inc.
 | 
						|
 * Copyright © 2011 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: Matthias Clasen <mclasen@redhat.com>
 | 
						|
 *         Ryan Lortie <desrt@desrt.ca>
 | 
						|
 */
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
 | 
						|
#include "gtkmenushell.h"
 | 
						|
#include "gtkmenubar.h"
 | 
						|
#include "gtkmenu.h"
 | 
						|
 | 
						|
#include "gtkseparatormenuitem.h"
 | 
						|
#include "gtkmodelmenuitem.h"
 | 
						|
#include "gtkapplicationprivate.h"
 | 
						|
 | 
						|
#define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data"
 | 
						|
 | 
						|
typedef struct {
 | 
						|
  GMenuModel        *model;
 | 
						|
  GtkMenuShell      *shell;
 | 
						|
  guint              update_idle;
 | 
						|
  GSList            *connected;
 | 
						|
  gboolean           with_separators;
 | 
						|
  gint               n_items;
 | 
						|
  gchar             *action_namespace;
 | 
						|
} GtkModelMenuBinding;
 | 
						|
 | 
						|
static void
 | 
						|
gtk_model_menu_binding_items_changed (GMenuModel *model,
 | 
						|
                                      gint        position,
 | 
						|
                                      gint        removed,
 | 
						|
                                      gint        added,
 | 
						|
                                      gpointer    user_data);
 | 
						|
static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
 | 
						|
                                                 GMenuModel *model,
 | 
						|
                                                 const gchar *action_namespace,
 | 
						|
                                                 gboolean with_separators);
 | 
						|
 | 
						|
static void
 | 
						|
gtk_model_menu_binding_free (gpointer data)
 | 
						|
{
 | 
						|
  GtkModelMenuBinding *binding = data;
 | 
						|
 | 
						|
  /* disconnect all existing signal handlers */
 | 
						|
  while (binding->connected)
 | 
						|
    {
 | 
						|
      g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
 | 
						|
      g_object_unref (binding->connected->data);
 | 
						|
 | 
						|
      binding->connected = g_slist_delete_link (binding->connected, binding->connected);
 | 
						|
    }
 | 
						|
 | 
						|
  g_object_unref (binding->model);
 | 
						|
  g_free (binding->action_namespace);
 | 
						|
 | 
						|
  g_slice_free (GtkModelMenuBinding, binding);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
 | 
						|
                                    GMenuModel           *model,
 | 
						|
                                    const gchar          *action_namespace,
 | 
						|
                                    gint                  item_index,
 | 
						|
                                    gchar               **heading)
 | 
						|
{
 | 
						|
  GMenuModel *section;
 | 
						|
 | 
						|
  if ((section = g_menu_model_get_item_link (model, item_index, "section")))
 | 
						|
    {
 | 
						|
      gchar *section_namespace = NULL;
 | 
						|
 | 
						|
      g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
 | 
						|
      g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", §ion_namespace);
 | 
						|
 | 
						|
      if (action_namespace)
 | 
						|
        {
 | 
						|
          gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL);
 | 
						|
          gtk_model_menu_binding_append_model (binding, section, namespace, FALSE);
 | 
						|
          g_free (namespace);
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          gtk_model_menu_binding_append_model (binding, section, section_namespace, FALSE);
 | 
						|
        }
 | 
						|
 | 
						|
      g_free (section_namespace);
 | 
						|
      g_object_unref (section);
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      GtkMenuItem *item;
 | 
						|
 | 
						|
      item = gtk_model_menu_item_new (model, item_index, action_namespace);
 | 
						|
      gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
 | 
						|
      gtk_widget_show (GTK_WIDGET (item));
 | 
						|
      binding->n_items++;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
 | 
						|
                                     GMenuModel          *model,
 | 
						|
                                     const gchar         *action_namespace,
 | 
						|
                                     gboolean             with_separators)
 | 
						|
{
 | 
						|
  gint n, i;
 | 
						|
 | 
						|
  g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
 | 
						|
  binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
 | 
						|
 | 
						|
  /* Deciding if we should show a separator is a bit difficult.  There
 | 
						|
   * are two types of separators:
 | 
						|
   *
 | 
						|
   *  - section headings (when sections have 'label' property)
 | 
						|
   *
 | 
						|
   *  - normal separators automatically put between sections
 | 
						|
   *
 | 
						|
   * The easiest way to think about it is that a section usually has a
 | 
						|
   * separator (or heading) immediately before it.
 | 
						|
   *
 | 
						|
   * There are three exceptions to this general rule:
 | 
						|
   *
 | 
						|
   *  - empty sections don't get separators or headings
 | 
						|
   *
 | 
						|
   *  - sections only get separators and headings at the toplevel of a
 | 
						|
   *    menu (ie: no separators on nested sections or in menubars)
 | 
						|
   *
 | 
						|
   *  - the first section in the menu doesn't get a normal separator,
 | 
						|
   *    but it can get a header (if it's not empty)
 | 
						|
   *
 | 
						|
   * Unfortunately, we cannot simply check the size of the section in
 | 
						|
   * order to determine if we should place a header: the section may
 | 
						|
   * contain other sections that are themselves empty.  Instead, we need
 | 
						|
   * to append the section, and check if we ended up with any actual
 | 
						|
   * content.  If we did, then we need to insert before that content.
 | 
						|
   * We use 'our_position' to keep track of this.
 | 
						|
   */
 | 
						|
 | 
						|
  n = g_menu_model_get_n_items (model);
 | 
						|
 | 
						|
  for (i = 0; i < n; i++)
 | 
						|
    {
 | 
						|
      gint our_position = binding->n_items;
 | 
						|
      gchar *heading = NULL;
 | 
						|
 | 
						|
      gtk_model_menu_binding_append_item (binding, model, action_namespace, i, &heading);
 | 
						|
 | 
						|
      if (with_separators && our_position < binding->n_items)
 | 
						|
        {
 | 
						|
          GtkWidget *separator = NULL;
 | 
						|
 | 
						|
          if (heading)
 | 
						|
            {
 | 
						|
              separator = gtk_menu_item_new_with_label (heading);
 | 
						|
              gtk_widget_set_sensitive (separator, FALSE);
 | 
						|
            }
 | 
						|
          else if (our_position > 0)
 | 
						|
            separator = gtk_separator_menu_item_new ();
 | 
						|
 | 
						|
          if (separator)
 | 
						|
            {
 | 
						|
              gtk_menu_shell_insert (binding->shell, separator, our_position);
 | 
						|
              gtk_widget_show (separator);
 | 
						|
              binding->n_items++;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
      g_free (heading);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
 | 
						|
{
 | 
						|
  GList *children;
 | 
						|
 | 
						|
  /* remove current children */
 | 
						|
  children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
 | 
						|
  while (children)
 | 
						|
    {
 | 
						|
      gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
 | 
						|
      children = g_list_delete_link (children, children);
 | 
						|
    }
 | 
						|
 | 
						|
  binding->n_items = 0;
 | 
						|
 | 
						|
  /* add new items from the model */
 | 
						|
  gtk_model_menu_binding_append_model (binding, binding->model, binding->action_namespace, binding->with_separators);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
gtk_model_menu_binding_handle_changes (gpointer user_data)
 | 
						|
{
 | 
						|
  GtkModelMenuBinding *binding = user_data;
 | 
						|
 | 
						|
  /* disconnect all existing signal handlers */
 | 
						|
  while (binding->connected)
 | 
						|
    {
 | 
						|
      g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
 | 
						|
      g_object_unref (binding->connected->data);
 | 
						|
 | 
						|
      binding->connected = g_slist_delete_link (binding->connected, binding->connected);
 | 
						|
    }
 | 
						|
 | 
						|
  gtk_model_menu_binding_populate (binding);
 | 
						|
 | 
						|
  binding->update_idle = 0;
 | 
						|
 | 
						|
  g_object_unref (binding->shell);
 | 
						|
 | 
						|
  return G_SOURCE_REMOVE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
gtk_model_menu_binding_items_changed (GMenuModel *model,
 | 
						|
                                      gint        position,
 | 
						|
                                      gint        removed,
 | 
						|
                                      gint        added,
 | 
						|
                                      gpointer    user_data)
 | 
						|
{
 | 
						|
  GtkModelMenuBinding *binding = user_data;
 | 
						|
 | 
						|
  if (binding->update_idle == 0)
 | 
						|
    {
 | 
						|
      binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
 | 
						|
      g_object_ref (binding->shell);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * gtk_menu_shell_bind_model:
 | 
						|
 * @menu_shell: a #GtkMenuShell
 | 
						|
 * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove
 | 
						|
 *   binding
 | 
						|
 * @action_namespace: (allow-none): the namespace for actions in @model
 | 
						|
 * @with_separators: %TRUE if toplevel items in @shell should have
 | 
						|
 *   separators between them
 | 
						|
 *
 | 
						|
 * Establishes a binding between a #GtkMenuShell and a #GMenuModel.
 | 
						|
 *
 | 
						|
 * The contents of @shell are removed and then refilled with menu items
 | 
						|
 * according to @model.  When @model changes, @shell is updated.
 | 
						|
 * Calling this function twice on @shell with different @model will
 | 
						|
 * cause the first binding to be replaced with a binding to the new
 | 
						|
 * model. If @model is %NULL then any previous binding is undone and
 | 
						|
 * all children are removed.
 | 
						|
 *
 | 
						|
 * @with_separators determines if toplevel items (eg: sections) have
 | 
						|
 * separators inserted between them.  This is typically desired for
 | 
						|
 * menus but doesn't make sense for menubars.
 | 
						|
 *
 | 
						|
 * If @action_namespace is non-%NULL then the effect is as if all
 | 
						|
 * actions mentioned in the @model have their names prefixed with the
 | 
						|
 * namespace, plus a dot.  For example, if the action "quit" is
 | 
						|
 * mentioned and @action_namespace is "app" then the effective action
 | 
						|
 * name is "app.quit".
 | 
						|
 *
 | 
						|
 * For most cases you are probably better off using
 | 
						|
 * gtk_menu_new_from_model() or gtk_menu_bar_new_from_model() or just
 | 
						|
 * directly passing the #GMenuModel to gtk_application_set_app_menu() or
 | 
						|
 * gtk_application_set_menu_bar().
 | 
						|
 *
 | 
						|
 * Since: 3.6
 | 
						|
 */
 | 
						|
void
 | 
						|
gtk_menu_shell_bind_model (GtkMenuShell *shell,
 | 
						|
                           GMenuModel   *model,
 | 
						|
                           const gchar  *action_namespace,
 | 
						|
                           gboolean      with_separators)
 | 
						|
{
 | 
						|
  g_return_if_fail (GTK_IS_MENU_SHELL (shell));
 | 
						|
  g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
 | 
						|
 | 
						|
  if (model)
 | 
						|
    {
 | 
						|
      GtkModelMenuBinding *binding;
 | 
						|
 | 
						|
      binding = g_slice_new (GtkModelMenuBinding);
 | 
						|
      binding->model = g_object_ref (model);
 | 
						|
      binding->shell = shell;
 | 
						|
      binding->update_idle = 0;
 | 
						|
      binding->connected = NULL;
 | 
						|
      binding->with_separators = with_separators;
 | 
						|
      binding->action_namespace = g_strdup (action_namespace);
 | 
						|
 | 
						|
      g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
 | 
						|
 | 
						|
      gtk_model_menu_binding_populate (binding);
 | 
						|
    }
 | 
						|
 | 
						|
  else
 | 
						|
    {
 | 
						|
      GList *children;
 | 
						|
 | 
						|
      /* break existing binding */
 | 
						|
      g_object_set_data (G_OBJECT (shell), "gtk-model-menu-binding", NULL);
 | 
						|
 | 
						|
      /* remove all children */
 | 
						|
      children = gtk_container_get_children (GTK_CONTAINER (shell));
 | 
						|
      while (children)
 | 
						|
        {
 | 
						|
          gtk_container_remove (GTK_CONTAINER (shell), children->data);
 | 
						|
          children = g_list_delete_link (children, children);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |