Back to index

indicator-appmenu  12.10.0
gtkmodelmenu.c
Go to the documentation of this file.
00001 /*
00002  * Copyright © 2011 Canonical Limited
00003  *
00004  * This library is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU Lesser General Public
00006  * License as published by the Free Software Foundation; either
00007  * version 2 of the licence, or (at your option) any later version.
00008  *
00009  * This library is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * Lesser General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU Lesser General Public
00015  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Author: Ryan Lortie <desrt@desrt.ca>
00018  */
00019 
00020 #include "config.h"
00021 
00022 #include "gtkmodelmenu.h"
00023 
00024 #include "gtkmodelmenuitem.h"
00025 
00026 typedef struct {
00027   GActionObservable *actions;
00028   GMenuModel        *model;
00029   GtkAccelGroup     *accels;
00030   GtkMenuShell      *shell;
00031   guint              update_idle;
00032   GSList            *connected;
00033   gboolean           with_separators;
00034   gint               n_items;
00035 } GtkModelMenuBinding;
00036 
00037 static void
00038 gtk_model_menu_binding_items_changed (GMenuModel *model,
00039                                       gint        position,
00040                                       gint        removed,
00041                                       gint        added,
00042                                       gpointer    user_data);
00043 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
00044                                                  GMenuModel *model,
00045                                                  gboolean with_separators);
00046 
00047 static void
00048 gtk_model_menu_binding_free (gpointer data)
00049 {
00050   GtkModelMenuBinding *binding = data;
00051 
00052   /* disconnect all existing signal handlers */
00053   while (binding->connected)
00054     {
00055       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
00056       g_object_unref (binding->connected->data);
00057 
00058       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
00059     }
00060 
00061   if (binding->actions)
00062     g_object_unref (binding->actions);
00063   g_object_unref (binding->model);
00064 
00065   g_slice_free (GtkModelMenuBinding, binding);
00066 }
00067 
00068 static void
00069 gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
00070                                     GMenuModel           *model,
00071                                     gint                  item_index,
00072                                     gchar               **heading)
00073 {
00074   GMenuModel *section;
00075 
00076   if ((section = g_menu_model_get_item_link (model, item_index, "section")))
00077     {
00078       g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
00079       gtk_model_menu_binding_append_model (binding, section, FALSE);
00080     }
00081   else
00082     {
00083       GtkMenuItem *item;
00084 
00085       item = gtk_model_menu_item_new (model, item_index, binding->actions, binding->accels);
00086       gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
00087       gtk_widget_show (GTK_WIDGET (item));
00088       binding->n_items++;
00089     }
00090 }
00091 
00092 static void
00093 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
00094                                      GMenuModel          *model,
00095                                      gboolean             with_separators)
00096 {
00097   gint n, i;
00098 
00099   g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
00100   binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
00101 
00102   /* Deciding if we should show a separator is a bit difficult.  There
00103    * are two types of separators:
00104    *
00105    *  - section headings (when sections have 'label' property)
00106    *
00107    *  - normal separators automatically put between sections
00108    *
00109    * The easiest way to think about it is that a section usually has a
00110    * separator (or heading) immediately before it.
00111    *
00112    * There are three exceptions to this general rule:
00113    *
00114    *  - empty sections don't get separators or headings
00115    *
00116    *  - sections only get separators and headings at the toplevel of a
00117    *    menu (ie: no separators on nested sections or in menubars)
00118    *
00119    *  - the first section in the menu doesn't get a normal separator,
00120    *    but it can get a header (if it's not empty)
00121    *
00122    * Unfortunately, we cannot simply check the size of the section in
00123    * order to determine if we should place a header: the section may
00124    * contain other sections that are themselves empty.  Instead, we need
00125    * to append the section, and check if we ended up with any actual
00126    * content.  If we did, then we need to insert before that content.
00127    * We use 'our_position' to keep track of this.
00128    */
00129 
00130   n = g_menu_model_get_n_items (model);
00131 
00132   for (i = 0; i < n; i++)
00133     {
00134       gint our_position = binding->n_items;
00135       gchar *heading = NULL;
00136 
00137       gtk_model_menu_binding_append_item (binding, model, i, &heading);
00138 
00139       if (with_separators && our_position < binding->n_items)
00140         {
00141           GtkWidget *separator = NULL;
00142 
00143           if (heading)
00144             {
00145               separator = gtk_menu_item_new_with_label (heading);
00146               gtk_widget_set_sensitive (separator, FALSE);
00147             }
00148           else if (our_position > 0)
00149             separator = gtk_separator_menu_item_new ();
00150 
00151           if (separator)
00152             {
00153               gtk_menu_shell_insert (binding->shell, separator, our_position);
00154               gtk_widget_show (separator);
00155               binding->n_items++;
00156             }
00157         }
00158 
00159       g_free (heading);
00160     }
00161 }
00162 
00163 static void
00164 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
00165 {
00166   GList *children;
00167 
00168   /* remove current children */
00169   children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
00170   while (children)
00171     {
00172       gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
00173       children = g_list_delete_link (children, children);
00174     }
00175 
00176   binding->n_items = 0;
00177 
00178   /* add new items from the model */
00179   gtk_model_menu_binding_append_model (binding, binding->model, binding->with_separators);
00180 }
00181 
00182 static gboolean
00183 gtk_model_menu_binding_handle_changes (gpointer user_data)
00184 {
00185   GtkModelMenuBinding *binding = user_data;
00186 
00187   /* disconnect all existing signal handlers */
00188   while (binding->connected)
00189     {
00190       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
00191       g_object_unref (binding->connected->data);
00192 
00193       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
00194     }
00195 
00196   gtk_model_menu_binding_populate (binding);
00197 
00198   binding->update_idle = 0;
00199 
00200   g_object_unref (binding->shell);
00201 
00202   return G_SOURCE_REMOVE;
00203 }
00204 
00205 static void
00206 gtk_model_menu_binding_items_changed (GMenuModel *model,
00207                                       gint        position,
00208                                       gint        removed,
00209                                       gint        added,
00210                                       gpointer    user_data)
00211 {
00212   GtkModelMenuBinding *binding = user_data;
00213 
00214   if (binding->update_idle == 0)
00215     {
00216       binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
00217       g_object_ref (binding->shell);
00218     }
00219 }
00220 
00221 static void
00222 gtk_model_menu_bind (GtkMenuShell      *shell,
00223                      GMenuModel        *model,
00224                      gboolean           with_separators)
00225 {
00226   GtkModelMenuBinding *binding;
00227 
00228   binding = g_slice_new (GtkModelMenuBinding);
00229   binding->model = g_object_ref (model);
00230   binding->actions = NULL;
00231   binding->accels = NULL;
00232   binding->shell = shell;
00233   binding->update_idle = 0;
00234   binding->connected = NULL;
00235   binding->with_separators = with_separators;
00236 
00237   g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
00238 }
00239 
00240 
00241 static void
00242 gtk_model_menu_populate (GtkMenuShell      *shell,
00243                          GActionObservable *actions,
00244                          GtkAccelGroup     *accels)
00245 {
00246   GtkModelMenuBinding *binding;
00247 
00248   binding = (GtkModelMenuBinding*) g_object_get_data (G_OBJECT (shell), "gtk-model-menu-binding");
00249 
00250   binding->actions = g_object_ref (actions);
00251   binding->accels = accels;
00252 
00253   gtk_model_menu_binding_populate (binding);
00254 }
00255 
00256 GtkWidget *
00257 gtk_model_menu_create_menu (GMenuModel        *model,
00258                             GActionObservable *actions,
00259                             GtkAccelGroup     *accels)
00260 {
00261   GtkWidget *menu;
00262 
00263   menu = gtk_menu_new ();
00264 
00265   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
00266   gtk_model_menu_populate (GTK_MENU_SHELL (menu), actions, accels);
00267 
00268   return menu;
00269 }
00270