From 8bc7513a7b7f9a487cfb0b84cecdc9b973dd1b07 Mon Sep 17 00:00:00 2001 From: William Hua Date: Sat, 10 Dec 2011 18:51:30 -0500 Subject: [PATCH] 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(+) create mode 100644 gtk/gtkquartz-menu.c create mode 100644 gtk/gtkquartz-menu.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index c5cd39a4a1..a5fb322237 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 097009a400..8c0debaecb 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 +#endif #include #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 0000000000..3a6e90019f --- /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 + */ + +#include "gtkquartz-menu.h" + +#import + +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, ¶meterType, 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 0000000000..5e1c2d752f --- /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 + */ + +#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__ */