gtk2/gtk/gtkmodelmenu.c
Ryan Lortie dd45862a06 gtkmodelmenu: move to new action regime
Drop the explicit passing of GActionGroup into the GtkMenu(Bar)
constructors and operate from the action context instead.

With GtkMenuItem implementing GtkActionable, this turns out to be pretty
easy (and most of the code can be removed from GtkModelMenuItem,
including the GActionObserver implementation).
2012-08-20 13:13:49 -04:00

344 lines
10 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 "gtkmodelmenu.h"
#include "gtkmenu.h"
#include "gtkmenubar.h"
#include "gtkseparatormenuitem.h"
#include "gtkmodelmenuitem.h"
#include "gtkapplicationprivate.h"
#define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data"
typedef struct {
GMenuModel *model;
GtkAccelGroup *accels;
GtkMenuShell *shell;
guint update_idle;
GSList *connected;
gboolean with_separators;
gint n_items;
} 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,
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_slice_free (GtkModelMenuBinding, binding);
}
static void
gtk_model_menu_binding_append_item (GtkModelMenuBinding *binding,
GMenuModel *model,
gint item_index,
gchar **heading)
{
GMenuModel *section;
if ((section = g_menu_model_get_item_link (model, item_index, "section")))
{
g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
gtk_model_menu_binding_append_model (binding, section, FALSE);
g_object_unref (section);
}
else
{
GtkMenuItem *item;
item = gtk_model_menu_item_new (model, item_index, binding->accels);
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,
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, 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->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);
}
}
static void
gtk_model_menu_bind (GtkMenuShell *shell,
GMenuModel *model,
gboolean with_separators)
{
GtkModelMenuBinding *binding;
binding = g_slice_new (GtkModelMenuBinding);
binding->model = g_object_ref (model);
binding->accels = NULL;
binding->shell = shell;
binding->update_idle = 0;
binding->connected = NULL;
binding->with_separators = with_separators;
g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
}
static void
gtk_model_menu_populate (GtkMenuShell *shell,
GtkAccelGroup *accels)
{
GtkModelMenuBinding *binding;
binding = (GtkModelMenuBinding*) g_object_get_data (G_OBJECT (shell), "gtk-model-menu-binding");
binding->accels = accels;
gtk_model_menu_binding_populate (binding);
}
GtkWidget *
gtk_model_menu_create_menu (GMenuModel *model,
GtkAccelGroup *accels)
{
GtkWidget *menu;
menu = gtk_menu_new ();
gtk_menu_set_accel_group (GTK_MENU (menu), accels);
gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
gtk_model_menu_populate (GTK_MENU_SHELL (menu), accels);
return menu;
}
/**
* gtk_menu_new_from_model:
* @model: a #GMenuModel
*
* Creates a #GtkMenu and populates it with menu items and
* submenus according to @model.
*
* The created menu items are connected to actions found in the
* #GtkApplicationWindow to which the menu belongs - typically
* by means of being attached to a widget (see gtk_menu_attach_to_widget())
* that is contained within the #GtkApplicationWindows widget hierarchy.
*
* Returns: a new #GtkMenu
*
* Since: 3.4
*/
GtkWidget *
gtk_menu_new_from_model (GMenuModel *model)
{
GtkWidget *menu;
menu = gtk_menu_new ();
gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
gtk_model_menu_populate (GTK_MENU_SHELL (menu), NULL);
return menu;
}
GtkWidget *
gtk_model_menu_create_menu_bar (GMenuModel *model,
GtkAccelGroup *accels)
{
GtkWidget *menubar;
menubar = gtk_menu_bar_new ();
gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
gtk_model_menu_populate (GTK_MENU_SHELL (menubar), accels);
return menubar;
}
/**
* gtk_menu_bar_new_from_model:
* @model: a #GMenuModel
*
* Creates a new #GtkMenuBar and populates it with menu items
* and submenus according to @model.
*
* The created menu items are connected to actions found in the
* #GtkApplicationWindow to which the menu bar belongs - typically
* by means of being contained within the #GtkApplicationWindows
* widget hierarchy.
*
* Returns: a new #GtkMenuBar
*
* Since: 3.4
*/
GtkWidget *
gtk_menu_bar_new_from_model (GMenuModel *model)
{
GtkWidget *menubar;
menubar = gtk_menu_bar_new ();
gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
gtk_model_menu_populate (GTK_MENU_SHELL (menubar), NULL);
return menubar;
}