[gtk+] begin GtkApplication menu support for Mac OS



commit 8bc7513a7b7f9a487cfb0b84cecdc9b973dd1b07
Author: William Hua <william attente ca>
Date:   Sat Dec 10 18:51:30 2011 -0500

    begin GtkApplication menu support for Mac OS

 gtk/Makefile.am      |    3 +
 gtk/gtkapplication.c |   72 +++++++++++
 gtk/gtkquartz-menu.c |  336 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkquartz-menu.h |   30 +++++
 4 files changed, 441 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index c5cd39a..a5fb322 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -805,6 +805,8 @@ gtk_use_win32_c_sources = 	\
 gtk_use_quartz_c_sources = 	\
 	gtksearchenginequartz.c	\
 	gtkmountoperation-stub.c \
+  gtkquartz-menu.h \
+  gtkquartz-menu.c \
 	gtkquartz.c
 gtk_use_stub_c_sources = 	\
 	gtkmountoperation-stub.c
@@ -825,6 +827,7 @@ else
 if USE_QUARTZ
 gtk_private_h_sources += 	\
 	gtksearchenginequartz.h	\
+	gtkmenuquartz.h \
 	gtkquartz.h
 gtk_c_sources += $(gtk_use_quartz_c_sources)
 libgtk_3_la_CFLAGS = "-xobjective-c"
diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c
index 097009a..8c0deba 100644
--- a/gtk/gtkapplication.c
+++ b/gtk/gtkapplication.c
@@ -32,6 +32,12 @@
 #include "gtkmain.h"
 #include "gtkapplicationwindow.h"
 #include "gtkaccelmapprivate.h"
+#include "gactionmuxer.h"
+
+#ifdef GDK_WINDOWING_QUARTZ
+#include "gtkquartz-menu.h"
+#import <Cocoa/Cocoa.h>
+#endif
 
 #include <gdk/gdk.h>
 #ifdef GDK_WINDOWING_X11
@@ -83,6 +89,11 @@ struct _GtkApplicationPrivate
   gchar *window_prefix;
   guint next_id;
 #endif
+
+#ifdef GDK_WINDOWING_QUARTZ
+  GActionMuxer *muxer;
+  GMenu *combined;
+#endif
 };
 
 #ifdef GDK_WINDOWING_X11
@@ -213,6 +224,55 @@ gtk_application_shutdown_x11 (GtkApplication *application)
 }
 #endif
 
+#ifdef GDK_WINDOWING_QUARTZ
+static void
+gtk_application_menu_changed_quartz (GObject    *object,
+                                     GParamSpec *pspec,
+                                     gpointer    user_data)
+{
+  GtkApplication *application = GTK_APPLICATION (object);
+  GMenu *combined;
+
+  combined = g_menu_new ();
+  g_menu_append_submenu (combined, "Application", g_application_get_app_menu (G_APPLICATION (object)));
+  g_menu_append_section (combined, NULL, g_application_get_menubar (G_APPLICATION (object)));
+
+  gtk_quartz_set_main_menu (G_MENU_MODEL (combined), G_ACTION_OBSERVABLE (application->priv->muxer));
+}
+
+static void
+gtk_application_startup_quartz (GtkApplication *application)
+{
+  [NSApp finishLaunching];
+
+  application->priv->muxer = g_action_muxer_new ();
+  g_action_muxer_insert (application->priv->muxer, "app", G_ACTION_GROUP (application));
+
+  g_signal_connect (application, "notify::app-menu", G_CALLBACK (gtk_application_menu_changed_quartz), NULL);
+  g_signal_connect (application, "notify::menubar", G_CALLBACK (gtk_application_menu_changed_quartz), NULL);
+  gtk_application_menu_changed_quartz (G_OBJECT (application), NULL, NULL);
+}
+
+static void
+gtk_application_shutdown_quartz (GtkApplication *application)
+{
+  g_signal_handlers_disconnect_by_func (application, gtk_application_menu_changed_quartz, NULL);
+
+  g_object_unref (application->priv->muxer);
+  application->priv->muxer = NULL;
+}
+
+static void
+gtk_application_focus_changed (GtkApplication *application,
+                               GtkWindow      *window)
+{
+  if (G_IS_ACTION_GROUP (window))
+    g_action_muxer_insert (application->priv->muxer, "win", G_ACTION_GROUP (window));
+  else
+    g_action_muxer_remove (application->priv->muxer, "win");
+}
+#endif
+
 static gboolean
 gtk_application_focus_in_event_cb (GtkWindow      *window,
                                    GdkEventFocus  *event,
@@ -229,6 +289,10 @@ gtk_application_focus_in_event_cb (GtkWindow      *window,
       priv->windows = g_list_concat (link, priv->windows);
     }
 
+#ifdef GDK_WINDOWING_QUARTZ
+  gtk_application_focus_changed (application, window);
+#endif
+
   return FALSE;
 }
 
@@ -243,6 +307,10 @@ gtk_application_startup (GApplication *application)
 #ifdef GDK_WINDOWING_X11
   gtk_application_startup_x11 (GTK_APPLICATION (application));
 #endif
+
+#ifdef GDK_WINDOWING_QUARTZ
+  gtk_application_startup_quartz (GTK_APPLICATION (application));
+#endif
 }
 
 static void
@@ -252,6 +320,10 @@ gtk_application_shutdown (GApplication *application)
   gtk_application_shutdown_x11 (GTK_APPLICATION (application));
 #endif
 
+#ifdef GDK_WINDOWING_QUARTZ
+  gtk_application_shutdown_quartz (GTK_APPLICATION (application));
+#endif
+
   G_APPLICATION_CLASS (gtk_application_parent_class)
     ->shutdown (application);
 }
diff --git a/gtk/gtkquartz-menu.c b/gtk/gtkquartz-menu.c
new file mode 100644
index 0000000..3a6e900
--- /dev/null
+++ b/gtk/gtkquartz-menu.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright  2011 William Hua
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: William Hua <william attente ca>
+ */
+
+#include "gtkquartz-menu.h"
+
+#import <Cocoa/Cocoa.h>
+
+typedef struct _GtkQuartzActionObserver GtkQuartzActionObserver;
+
+ interface GNSMenuItem : NSMenuItem
+{
+  gchar                   *action;
+  GVariant                *target;
+  BOOL                     canActivate;
+  GActionGroup            *actions;
+  GtkQuartzActionObserver *observer;
+}
+
+- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index observable:(GActionObservable *)observable;
+
+
+
+- (void)observableActionAddedWithParameterType:(const GVariantType *)parameterType enabled:(BOOL)enabled state:(GVariant *)state;
+- (void)observableActionEnabledChangedTo:(BOOL)enabled;
+- (void)observableActionStateChangedTo:(GVariant *)state;
+- (void)observableActionRemoved;
+
+- (void)didSelectItem:(id)sender;
+
+ end
+
+
+
+struct _GtkQuartzActionObserver
+{
+  GObject parent_instance;
+
+  GNSMenuItem *item;
+};
+
+
+typedef GObjectClass GtkQuartzActionObserverClass;
+
+static void gtk_quartz_action_observer_observer_iface_init (GActionObserverInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (GtkQuartzActionObserver, gtk_quartz_action_observer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_quartz_action_observer_observer_iface_init))
+
+static void
+gtk_quartz_action_observer_action_added (GActionObserver    *observer,
+                                         GActionObservable  *observable,
+                                         const gchar        *action_name,
+                                         const GVariantType *parameter_type,
+                                         gboolean            enabled,
+                                         GVariant           *state)
+{
+  GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
+
+  [qao->item observableActionAddedWithParameterType:parameter_type enabled:enabled state:state];
+}
+
+static void
+gtk_quartz_action_observer_action_enabled_changed (GActionObserver   *observer,
+                                                   GActionObservable *observable,
+                                                   const gchar       *action_name,
+                                                   gboolean           enabled)
+{
+  GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
+
+  [qao->item observableActionEnabledChangedTo:enabled];
+}
+
+static void
+gtk_quartz_action_observer_action_state_changed (GActionObserver   *observer,
+                                                 GActionObservable *observable,
+                                                 const gchar       *action_name,
+                                                 GVariant          *state)
+{
+  GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
+
+  [qao->item observableActionStateChangedTo:state];
+}
+
+static void
+gtk_quartz_action_observer_action_removed (GActionObserver   *observer,
+                                           GActionObservable *observable,
+                                           const gchar       *action_name)
+{
+  GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
+
+  [qao->item observableActionRemoved];
+}
+
+static void
+gtk_quartz_action_observer_init (GtkQuartzActionObserver *item)
+{
+}
+
+static void
+gtk_quartz_action_observer_observer_iface_init (GActionObserverInterface *iface)
+{
+  iface->action_added = gtk_quartz_action_observer_action_added;
+  iface->action_enabled_changed = gtk_quartz_action_observer_action_enabled_changed;
+  iface->action_state_changed = gtk_quartz_action_observer_action_state_changed;
+  iface->action_removed = gtk_quartz_action_observer_action_removed;
+}
+
+static void
+gtk_quartz_action_observer_class_init (GtkQuartzActionObserverClass *class)
+{
+}
+
+static GtkQuartzActionObserver *
+gtk_quartz_action_observer_new (GNSMenuItem *item)
+{
+  GtkQuartzActionObserver *observer;
+
+  observer = g_object_new (gtk_quartz_action_observer_get_type (), NULL);
+  observer->item = item;
+
+  return observer;
+}
+
+NSMenu *
+gtk_menu_quartz_create_menu (const gchar       *title,
+                             GMenuModel        *model,
+                             GActionObservable *observable)
+{
+  if (model == NULL)
+    return nil;
+
+  NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title ? : ""]] autorelease];
+
+  [menu setAutoenablesItems:NO];
+
+  gint n = g_menu_model_get_n_items (model);
+  gint i;
+
+  for (i = 0; i < n; i++)
+    {
+      gchar *label = NULL;
+
+      if (g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label))
+        {
+          gchar *from, *to;
+
+          to = from = label;
+
+          while (*from)
+            {
+              if (*from == '_' && from[1])
+                from++;
+
+              *to++ = *from++;
+            }
+
+          *to = '\0';
+        }
+
+      NSString *text = [NSString stringWithUTF8String:label ? : ""];
+
+      NSMenu *section = gtk_menu_quartz_create_menu (label, g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION), observable);
+      NSMenu *submenu = gtk_menu_quartz_create_menu (label, g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU), observable);
+
+      if (section != nil)
+        {
+          if ([menu numberOfItems] > 0)
+            [menu addItem:[NSMenuItem separatorItem]];
+
+          if ([text length] > 0)
+          {
+            NSMenuItem *header = [[[NSMenuItem alloc] initWithTitle:text action:NULL keyEquivalent:@""] autorelease];
+
+            [header setEnabled:NO];
+
+            [menu addItem:header];
+          }
+
+          for (NSMenuItem *item in [section itemArray])
+            {
+              [item retain];
+              [[item menu] removeItem:item];
+              [menu addItem:item];
+              [item release];
+            }
+        }
+      else if (submenu != nil)
+        {
+          NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:text action:NULL keyEquivalent:@""] autorelease];
+
+          [item setSubmenu:submenu];
+
+          [menu addItem:item];
+        }
+      else
+        [menu addItem:[[[GNSMenuItem alloc] initWithModel:model index:i observable:observable] autorelease]];
+    }
+
+  return menu;
+}
+
+void
+gtk_quartz_set_main_menu (GMenuModel        *model,
+                          GActionObservable *observable)
+{
+  [NSApp setMainMenu:gtk_menu_quartz_create_menu ("Main Menu", model, observable)];
+}
+
+
+
+ implementation GNSMenuItem
+
+- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index observable:(GActionObservable *)observable
+{
+  gchar *title = NULL;
+
+  if (g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_LABEL, "s", &title))
+    {
+      gchar *from, *to;
+
+      to = from = title;
+
+      while (*from)
+        {
+          if (*from == '_' && from[1])
+            from++;
+
+          *to++ = *from++;
+        }
+
+      *to = '\0';
+    }
+
+  if ((self = [super initWithTitle:[NSString stringWithUTF8String:title ? : ""] action:@selector(didSelectItem:) keyEquivalent:@""]) != nil)
+    {
+      g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_ACTION, "s", &action);
+      target = g_menu_model_get_item_attribute_value (model, index, G_MENU_ATTRIBUTE_TARGET, NULL);
+      actions = g_object_ref (observable);
+      observer = gtk_quartz_action_observer_new (self);
+
+      if (action != NULL)
+        {
+          g_action_observable_register_observer (observable, action, G_ACTION_OBSERVER (observer));
+
+          [self setTarget:self];
+
+          gboolean            enabled;
+          const GVariantType *parameterType;
+          GVariant           *state;
+
+          if (g_action_group_query_action (G_ACTION_GROUP (actions), action, &enabled, &parameterType, NULL, NULL, &state))
+            [self observableActionAddedWithParameterType:parameterType enabled:enabled state:state];
+          else
+            [self setEnabled:NO];
+        }
+    }
+
+  g_free (title);
+
+  return self;
+}
+
+- (void)observableActionAddedWithParameterType:(const GVariantType *)parameterType enabled:(BOOL)enabled state:(GVariant *)state
+{
+  canActivate = (target == NULL && parameterType == NULL) ||
+                (target != NULL && parameterType != NULL &&
+                 g_variant_is_of_type (target, parameterType));
+
+  if (canActivate)
+    {
+      if (target != NULL && state != NULL)
+        {
+          [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
+          [self setState:g_variant_equal (state, target) ? NSOnState : NSOffState];
+        }
+      else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
+        {
+          [self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]];
+          [self setState:g_variant_get_boolean (state) ? NSOnState : NSOffState];
+        }
+      else
+        [self setState:NSOffState];
+
+      [self setEnabled:enabled];
+    }
+  else
+    [self setEnabled:NO];
+}
+
+- (void)observableActionEnabledChangedTo:(BOOL)enabled
+{
+  if (canActivate)
+    [self setEnabled:enabled];
+}
+
+- (void)observableActionStateChangedTo:(GVariant *)state
+{
+  if (canActivate)
+    {
+      if (target != NULL)
+        [self setState:g_variant_equal (state, target) ? NSOnState : NSOffState];
+      else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
+        [self setState:g_variant_get_boolean (state) ? NSOnState : NSOffState];
+    }
+}
+
+- (void)observableActionRemoved
+{
+  if (canActivate)
+    [self setEnabled:NO];
+}
+
+- (void)didSelectItem:(id)sender
+{
+  if (canActivate)
+    g_action_group_activate_action (actions, action, target);
+}
+
+ end
diff --git a/gtk/gtkquartz-menu.h b/gtk/gtkquartz-menu.h
new file mode 100644
index 0000000..5e1c2d7
--- /dev/null
+++ b/gtk/gtkquartz-menu.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright  2011 William Hua
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: William Hua <william attente ca>
+ */
+
+#ifndef __GTK_QUARTZ_MENU_H__
+#define __GTK_QUARTZ_MENU_H__
+
+#include "gactionobservable.h"
+
+void gtk_quartz_set_main_menu (GMenuModel        *model,
+                               GActionObservable *observable);
+
+#endif /* __GTK_QUARTZ_MENU_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]