]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkmodelmenu.c
modelmenu: listen for toplevel changes on the attach widget
[~andy/gtk] / gtk / gtkmodelmenu.c
index a9979cd03138554b57be9c0cce9957c8b7ffdb92..9dd9aa5e468f3a427149639b2306627d25b26076 100644 (file)
@@ -13,9 +13,7 @@
  * 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.
+ * 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 "gtkmenubar.h"
 #include "gtkseparatormenuitem.h"
 #include "gtkmodelmenuitem.h"
+#include "gtkapplicationprivate.h"
+
+#define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data"
 
 typedef struct {
   GActionObservable *actions;
   GMenuModel        *model;
+  GtkAccelGroup     *accels;
   GtkMenuShell      *shell;
   guint              update_idle;
   GSList            *connected;
@@ -64,8 +66,11 @@ gtk_model_menu_binding_free (gpointer data)
       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
     }
 
-  g_object_unref (binding->actions);
+  if (binding->actions)
+    g_object_unref (binding->actions);
   g_object_unref (binding->model);
+
+  g_slice_free (GtkModelMenuBinding, binding);
 }
 
 static void
@@ -78,14 +83,14 @@ gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
 
   if ((section = g_menu_model_get_item_link (model, item_index, "section")))
     {
-      g_menu_model_get_item_attribute (model, item_index, "label", "s", &heading);
+      g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
       gtk_model_menu_binding_append_model (binding, section, FALSE);
     }
   else
     {
       GtkMenuItem *item;
 
-      item = gtk_model_menu_item_new (model, item_index, binding->actions);
+      item = gtk_model_menu_item_new (model, item_index, binding->actions, binding->accels);
       gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
       gtk_widget_show (GTK_WIDGET (item));
       binding->n_items++;
@@ -221,47 +226,205 @@ gtk_model_menu_binding_items_changed (GMenuModel *model,
     }
 }
 
-void
+static void
 gtk_model_menu_bind (GtkMenuShell      *shell,
                      GMenuModel        *model,
-                     GActionObservable *actions,
                      gboolean           with_separators)
 {
   GtkModelMenuBinding *binding;
 
   binding = g_slice_new (GtkModelMenuBinding);
   binding->model = g_object_ref (model);
-  binding->actions = g_object_ref (actions);
+  binding->actions = NULL;
+  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,
+                         GActionObservable *actions,
+                         GtkAccelGroup     *accels)
+{
+  GtkModelMenuBinding *binding;
+
+  binding = (GtkModelMenuBinding*) g_object_get_data (G_OBJECT (shell), "gtk-model-menu-binding");
+
+  binding->actions = g_object_ref (actions);
+  binding->accels = accels;
+
   gtk_model_menu_binding_populate (binding);
 }
 
 GtkWidget *
 gtk_model_menu_create_menu (GMenuModel        *model,
-                            GActionObservable *actions)
+                            GActionObservable *actions,
+                            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), actions, accels);
+
+  return menu;
+}
+
+static void
+gtk_model_menu_connect_app_window (GtkMenu *menu,
+                                   GtkApplicationWindow *window)
+{
+  GActionObservable *actions;
+  GtkAccelGroup *accels;
+
+  actions = gtk_application_window_get_observable (window);
+  accels = gtk_application_window_get_accel_group (window);
+
+  gtk_menu_set_accel_group (menu, accels);
+  gtk_model_menu_populate (GTK_MENU_SHELL (menu), actions, accels);
+}
+
+static void
+attach_widget_hierarchy_changed (GtkWidget *attach_widget,
+                                 GtkWidget *previous_toplevel,
+                                 gpointer user_data)
+{
+  GtkWidget *toplevel;
+  GtkMenu *menu = user_data;
+
+  toplevel = gtk_widget_get_toplevel (attach_widget);
+  if (GTK_IS_APPLICATION_WINDOW (toplevel))
+    gtk_model_menu_connect_app_window (menu, GTK_APPLICATION_WINDOW (toplevel));
+}
+
+static void
+notify_attach (GtkMenu    *menu,
+               GParamSpec *pspec,
+               gpointer    data)
+{
+  GtkWidget *attach_widget, *toplevel;
+
+  attach_widget = g_object_get_data (G_OBJECT (menu), MODEL_MENU_WIDGET_DATA);
+  if (attach_widget != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (attach_widget, attach_widget_hierarchy_changed, menu);
+      g_object_set_data (G_OBJECT (menu), MODEL_MENU_WIDGET_DATA, NULL);
+    }
+
+  attach_widget = gtk_menu_get_attach_widget (menu);
+  if (!attach_widget)
+    return;
+
+  toplevel = gtk_widget_get_toplevel (attach_widget);
+  if (GTK_IS_APPLICATION_WINDOW (toplevel))
+    {
+      gtk_model_menu_connect_app_window (menu, GTK_APPLICATION_WINDOW (toplevel));
+    }
+  else
+    {
+      g_object_set_data (G_OBJECT (menu), MODEL_MENU_WIDGET_DATA, attach_widget);
+      g_signal_connect_object (attach_widget, "hierarchy-changed",
+                               G_CALLBACK (attach_widget_hierarchy_changed), menu, 0);
+    }
+}
+
+/**
+ * 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, actions, TRUE);
+  gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
+  g_signal_connect (menu, "notify::attach-widget",
+                    G_CALLBACK (notify_attach), NULL);
 
   return menu;
 }
 
 GtkWidget *
 gtk_model_menu_create_menu_bar (GMenuModel        *model,
-                                GActionObservable *actions)
+                                GActionObservable *actions,
+                                GtkAccelGroup     *accels)
 {
   GtkWidget *menubar;
 
   menubar = gtk_menu_bar_new ();
-  gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, actions, FALSE);
+
+  gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
+  gtk_model_menu_populate (GTK_MENU_SHELL (menubar), actions, accels);
 
   return menubar;
 }
 
+static void
+hierarchy_changed (GtkMenuShell *shell,
+                   GObject      *previous_toplevel,
+                   gpointer      data)
+{
+  GtkWidget *toplevel;
+  GActionObservable *actions;
+  GtkAccelGroup *accels;
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
+  if (GTK_IS_APPLICATION_WINDOW (toplevel))
+    {
+      actions = gtk_application_window_get_observable (GTK_APPLICATION_WINDOW (toplevel));
+      accels = gtk_application_window_get_accel_group (GTK_APPLICATION_WINDOW (toplevel));
+
+      gtk_model_menu_populate (shell, actions, accels);
+    }
+}
+
+/**
+ * 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);
+
+  g_signal_connect (menubar, "hierarchy-changed",
+                    G_CALLBACK (hierarchy_changed), NULL);
+
+  return menubar;
+}