diff options
author | William Hua <william@attente.ca> | 2013-12-16 09:33:38 -0500 |
---|---|---|
committer | Ryan Lortie <desrt@desrt.ca> | 2013-12-17 17:17:09 -0500 |
commit | 0c03793b6384280099a33fce948497135672ff57 (patch) | |
tree | 22cadf857ef92637dceed7537632c3a5eb19a1f6 | |
parent | 3396c6634696fefb2a7f86db04a4c84d3f2ad488 (diff) | |
download | gtk+-0c03793b6384280099a33fce948497135672ff57.tar.gz |
Use GtkMenuTracker for Quartz backend
-rw-r--r-- | gtk/Makefile.am | 3 | ||||
-rw-r--r-- | gtk/gtkapplication-quartz-menu.c | 700 | ||||
-rw-r--r-- | gtk/gtkapplication-quartz.c | 86 | ||||
-rw-r--r-- | gtk/gtkapplicationprivate.h | 4 | ||||
-rw-r--r-- | gtk/gtkmodelmenu-quartz.c | 519 | ||||
-rw-r--r-- | gtk/gtkmodelmenu-quartz.h | 30 |
6 files changed, 771 insertions, 571 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 31c700eb3d..63b97aceec 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -979,8 +979,8 @@ gtk_use_win32_c_sources = \ gtk_use_quartz_c_sources = \ gtksearchenginequartz.c \ gtkmountoperation-stub.c \ - gtkmodelmenu-quartz.c \ gtkapplication-quartz.c \ + gtkapplication-quartz-menu.c \ gtkquartz.c gtk_use_stub_c_sources = \ gtkmountoperation-stub.c @@ -1018,7 +1018,6 @@ endif gtk_use_quartz_private_h_sources = \ gtksearchenginequartz.h \ - gtkmodelmenu-quartz.h \ gtkquartz.h if USE_QUARTZ gtk_c_sources += $(gtk_use_quartz_c_sources) diff --git a/gtk/gtkapplication-quartz-menu.c b/gtk/gtkapplication-quartz-menu.c new file mode 100644 index 0000000000..bf88ded2a1 --- /dev/null +++ b/gtk/gtkapplication-quartz-menu.c @@ -0,0 +1,700 @@ +/* + * Copyright © 2011 William Hua, Ryan Lortie + * + * 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: William Hua <william@attente.ca> + * Ryan Lortie <desrt@desrt.ca> + */ + +#include "config.h" + +#include "gtkapplicationprivate.h" + +#include <gdk/quartz/gdkquartz.h> +#include <gdk/gdkkeysyms.h> +#include "gtkmenutracker.h" +#include "gtkicontheme.h" +#import <Cocoa/Cocoa.h> + +#define BUFFER_SIZE 1024 +#define ICON_SIZE 16 + +@interface GNSMenu : NSMenu +{ + GtkMenuTracker *tracker; +} + +- (id)initWithTitle:(NSString *)title model:(GMenuModel *)model observable:(GtkActionObservable *)observable; + +- (id)initWithTitle:(NSString *)title trackerItem:(GtkMenuTrackerItem *)trackerItem; + +@end + +@interface NSMenuItem (GtkMenuTrackerItem) + ++ (id)menuItemForTrackerItem:(GtkMenuTrackerItem *)trackerItem; + +@end + +@interface GNSMenuItem : NSMenuItem +{ + GtkMenuTrackerItem *trackerItem; + gulong trackerItemChangedHandler; + NSMutableData *iconData; + gpointer iconBuffer; + GCancellable *cancellable; +} + +- (id)initWithTrackerItem:(GtkMenuTrackerItem *)aTrackerItem; + +- (void)didChangeLabel; +- (void)didChangeIcon; +- (void)didChangeSensitive; +- (void)didChangeVisible; +- (void)didChangeToggled; +- (void)didChangeAccel; + +- (void)didSelectItem:(id)sender; + +@end + +@interface GNSMenuItem () + +- (void)setImageFromLoadableIcon:(GLoadableIcon *)icon; +- (void)readDataFromStream:(GInputStream *)stream; +- (void)readDataFromStream:(GInputStream *)stream count:(gssize)count; +- (void)reset; + +@end + +/* + * Code for key code conversion + * + * Copyright (C) 2009 Paul Davis + */ +static unichar +get_key_equivalent (guint key) +{ + if (key >= GDK_KEY_A && key <= GDK_KEY_Z) + return key + (GDK_KEY_a - GDK_KEY_A); + + if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde) + return key; + + switch (key) + { + case GDK_KEY_BackSpace: + return NSBackspaceCharacter; + case GDK_KEY_Delete: + return NSDeleteFunctionKey; + case GDK_KEY_Pause: + return NSPauseFunctionKey; + case GDK_KEY_Scroll_Lock: + return NSScrollLockFunctionKey; + case GDK_KEY_Sys_Req: + return NSSysReqFunctionKey; + case GDK_KEY_Home: + return NSHomeFunctionKey; + case GDK_KEY_Left: + case GDK_KEY_leftarrow: + return NSLeftArrowFunctionKey; + case GDK_KEY_Up: + case GDK_KEY_uparrow: + return NSUpArrowFunctionKey; + case GDK_KEY_Right: + case GDK_KEY_rightarrow: + return NSRightArrowFunctionKey; + case GDK_KEY_Down: + case GDK_KEY_downarrow: + return NSDownArrowFunctionKey; + case GDK_KEY_Page_Up: + return NSPageUpFunctionKey; + case GDK_KEY_Page_Down: + return NSPageDownFunctionKey; + case GDK_KEY_End: + return NSEndFunctionKey; + case GDK_KEY_Begin: + return NSBeginFunctionKey; + case GDK_KEY_Select: + return NSSelectFunctionKey; + case GDK_KEY_Print: + return NSPrintFunctionKey; + case GDK_KEY_Execute: + return NSExecuteFunctionKey; + case GDK_KEY_Insert: + return NSInsertFunctionKey; + case GDK_KEY_Undo: + return NSUndoFunctionKey; + case GDK_KEY_Redo: + return NSRedoFunctionKey; + case GDK_KEY_Menu: + return NSMenuFunctionKey; + case GDK_KEY_Find: + return NSFindFunctionKey; + case GDK_KEY_Help: + return NSHelpFunctionKey; + case GDK_KEY_Break: + return NSBreakFunctionKey; + case GDK_KEY_Mode_switch: + return NSModeSwitchFunctionKey; + case GDK_KEY_F1: + return NSF1FunctionKey; + case GDK_KEY_F2: + return NSF2FunctionKey; + case GDK_KEY_F3: + return NSF3FunctionKey; + case GDK_KEY_F4: + return NSF4FunctionKey; + case GDK_KEY_F5: + return NSF5FunctionKey; + case GDK_KEY_F6: + return NSF6FunctionKey; + case GDK_KEY_F7: + return NSF7FunctionKey; + case GDK_KEY_F8: + return NSF8FunctionKey; + case GDK_KEY_F9: + return NSF9FunctionKey; + case GDK_KEY_F10: + return NSF10FunctionKey; + case GDK_KEY_F11: + return NSF11FunctionKey; + case GDK_KEY_F12: + return NSF12FunctionKey; + case GDK_KEY_F13: + return NSF13FunctionKey; + case GDK_KEY_F14: + return NSF14FunctionKey; + case GDK_KEY_F15: + return NSF15FunctionKey; + case GDK_KEY_F16: + return NSF16FunctionKey; + case GDK_KEY_F17: + return NSF17FunctionKey; + case GDK_KEY_F18: + return NSF18FunctionKey; + case GDK_KEY_F19: + return NSF19FunctionKey; + case GDK_KEY_F20: + return NSF20FunctionKey; + case GDK_KEY_F21: + return NSF21FunctionKey; + case GDK_KEY_F22: + return NSF22FunctionKey; + case GDK_KEY_F23: + return NSF23FunctionKey; + case GDK_KEY_F24: + return NSF24FunctionKey; + case GDK_KEY_F25: + return NSF25FunctionKey; + case GDK_KEY_F26: + return NSF26FunctionKey; + case GDK_KEY_F27: + return NSF27FunctionKey; + case GDK_KEY_F28: + return NSF28FunctionKey; + case GDK_KEY_F29: + return NSF29FunctionKey; + case GDK_KEY_F30: + return NSF30FunctionKey; + case GDK_KEY_F31: + return NSF31FunctionKey; + case GDK_KEY_F32: + return NSF32FunctionKey; + case GDK_KEY_F33: + return NSF33FunctionKey; + case GDK_KEY_F34: + return NSF34FunctionKey; + case GDK_KEY_F35: + return NSF35FunctionKey; + default: + break; + } + + return '\0'; +} + +static void +pixbuf_loaded (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GtkIconInfo *info = GTK_ICON_INFO (object); + GNSMenuItem *item = user_data; + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_symbolic_finish (info, result, NULL, &error); + + if (pixbuf != NULL) + { + if (G_IS_LOADABLE_ICON (pixbuf)) + [item setImageFromLoadableIcon:G_LOADABLE_ICON (pixbuf)]; + + g_object_unref (pixbuf); + } + + if (error != NULL) + g_error_free (error); +} + +static void +input_stream_created (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GLoadableIcon *icon = G_LOADABLE_ICON (object); + GNSMenuItem *item = user_data; + GError *error = NULL; + GInputStream *stream = g_loadable_icon_load_finish (icon, result, NULL, &error); + + if (stream != NULL) + { + [item readDataFromStream:stream]; + g_object_unref (stream); + } + else + [item reset]; + + if (error != NULL) + g_error_free (error); +} + +static void +input_stream_read (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (object); + GNSMenuItem *item = user_data; + GError *error = NULL; + gssize count = g_input_stream_read_finish (stream, result, &error); + + if (count >= 0) + [item readDataFromStream:stream count:count]; + else + [item reset]; + + if (error != NULL) + g_error_free (error); +} + +static void +tracker_item_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + static const gchar *label = NULL; + static const gchar *icon = NULL; + static const gchar *sensitive = NULL; + static const gchar *visible = NULL; + static const gchar *toggled = NULL; + static const gchar *accel = NULL; + + GNSMenuItem *item = user_data; + const gchar *name = g_param_spec_get_name (pspec); + + if (G_UNLIKELY (label == NULL)) + label = g_intern_static_string ("label"); + if (G_UNLIKELY (icon == NULL)) + icon = g_intern_static_string ("icon"); + if (G_UNLIKELY (sensitive == NULL)) + sensitive = g_intern_static_string ("sensitive"); + if (G_UNLIKELY (visible == NULL)) + visible = g_intern_static_string ("visible"); + if (G_UNLIKELY (toggled == NULL)) + toggled = g_intern_static_string ("toggled"); + if (G_UNLIKELY (accel == NULL)) + accel = g_intern_static_string ("accel"); + + if (name == label) + [item didChangeLabel]; + else if (name == icon) + [item didChangeIcon]; + else if (name == sensitive) + [item didChangeSensitive]; + else if (name == visible) + [item didChangeVisible]; + else if (name == toggled) + [item didChangeToggled]; + else if (name == accel) + [item didChangeAccel]; +} + +@implementation GNSMenuItem + +- (id)initWithTrackerItem:(GtkMenuTrackerItem *)aTrackerItem +{ + if ((self = [super initWithTitle:@"" + action:@selector(didSelectItem:) + keyEquivalent:@""]) != nil) + { + [self setTarget:self]; + + trackerItem = g_object_ref (aTrackerItem); + trackerItemChangedHandler = g_signal_connect (trackerItem, "notify", G_CALLBACK (tracker_item_changed), self); + + [self didChangeLabel]; + [self didChangeIcon]; + [self didChangeSensitive]; + [self didChangeVisible]; + [self didChangeToggled]; + [self didChangeAccel]; + + if (gtk_menu_tracker_item_get_has_submenu (trackerItem)) + [self setSubmenu:[[[GNSMenu alloc] initWithTitle:[self title] trackerItem:trackerItem] autorelease]]; + } + + return self; +} + +- (void)dealloc +{ + if (cancellable != NULL) + { + g_cancellable_cancel (cancellable); + [self reset]; + } + + g_signal_handler_disconnect (trackerItem, trackerItemChangedHandler); + g_object_unref (trackerItem); + + [super dealloc]; +} + +- (void)reset +{ + g_clear_object (&cancellable); + g_free (iconBuffer); + iconBuffer = NULL; + [iconData release]; + iconData = nil; +} + +- (void)readDataFromStream:(GInputStream *)stream +{ + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + + g_input_stream_read_async (stream, + iconBuffer, + BUFFER_SIZE, + G_PRIORITY_LOW, + cancellable, + input_stream_read, + self); +} + +- (void)readDataFromStream:(GInputStream *)stream count:(gssize)count +{ + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + g_return_if_fail (count >= 0); + + if (count > 0) + { + [iconData appendBytes:iconBuffer length:count]; + [self readDataFromStream:stream]; + } + else if (count == 0) + { + NSImage *image = [[[NSImage alloc] initWithData:iconData] autorelease]; + [image setSize:NSMakeSize(16, 16)]; + [self setImage:image]; + [self reset]; + } +} + +- (void)setImageFromLoadableIcon:(GLoadableIcon *)icon +{ + if (GDK_IS_PIXBUF (icon)) + { + NSImage *image = gdk_quartz_pixbuf_to_ns_image_libgtk_only (GDK_PIXBUF (icon)); + [image setSize:NSMakeSize(16, 16)]; + [self setImage:image]; + } + else + { + iconData = [[NSMutableData alloc] init]; + iconBuffer = g_malloc (BUFFER_SIZE); + cancellable = g_cancellable_new (); + g_loadable_icon_load_async (icon, ICON_SIZE, cancellable, input_stream_created, self); + } +} + +- (void)didChangeLabel +{ + NSString *label = [NSString stringWithUTF8String:gtk_menu_tracker_item_get_label (trackerItem) ? : ""]; + NSMutableString *title = [NSMutableString stringWithCapacity:[label length]]; + int i; + + for (i = 0; i < [label length]; i++) + { + unichar c = [label characterAtIndex:i]; + + if (c == '_') + { + i++; + + if (i >= [label length]) + break; + + c = [label characterAtIndex:i]; + } + + [title appendString:[NSString stringWithCharacters:&c length:1]]; + } + + [self setTitle:title]; +} + +- (void)didChangeIcon +{ + GIcon *icon = gtk_menu_tracker_item_get_icon (trackerItem); + + if (cancellable != NULL) + { + g_cancellable_cancel (cancellable); + [self reset]; + } + + while (G_IS_EMBLEM (icon) || G_IS_EMBLEMED_ICON (icon)) + { + if (G_IS_EMBLEM (icon)) + icon = g_emblem_get_icon (G_EMBLEM (icon)); + else + icon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon)); + } + + if (G_IS_FILE_ICON (icon)) + { + NSImage *image = nil; + GFile *file = g_file_icon_get_file (G_FILE_ICON (icon)); + gchar *path = g_file_get_path (file); + + if (path != NULL) + { + image = [[[NSImage alloc] initByReferencingFile:[NSString stringWithUTF8String:path]] autorelease]; + g_free (path); + } + else + { + path = g_file_get_uri (file); + + if (path != NULL) + { + image = [[[NSImage alloc] initByReferencingURL:[NSURL URLWithString:[NSString stringWithUTF8String:path]]] autorelease]; + g_free (path); + } + } + + [image setSize:NSMakeSize(16, 16)]; + [self setImage:image]; + } + else if (G_IS_THEMED_ICON (icon)) + { + GtkIconTheme *theme = gtk_icon_theme_get_default (); + + if (theme != NULL) + { + GtkIconInfo *info = gtk_icon_theme_lookup_by_gicon (theme, icon, ICON_SIZE, GTK_ICON_LOOKUP_USE_BUILTIN); + + if (info != NULL) + { + GdkRGBA foreground = { 0.0, 0.0, 0.0, 1.0 }; + GdkRGBA success = { 0.0, 1.0, 0.0, 1.0 }; + GdkRGBA warning = { 1.0, 1.0, 0.0, 1.0 }; + GdkRGBA error = { 1.0, 0.0, 0.0, 1.0 }; + + if (gtk_icon_info_is_symbolic (info)) + { + cancellable = g_cancellable_new (); + gtk_icon_info_load_symbolic_async (info, &foreground, &success, &warning, &error, cancellable, pixbuf_loaded, self); + } + else + { + const gchar *path = gtk_icon_info_get_filename (info); + + if (path != NULL) + { + NSImage *image = [[[NSImage alloc] initByReferencingFile:[NSString stringWithUTF8String:path]] autorelease]; + [image setSize:NSMakeSize(16, 16)]; + [self setImage:image]; + } + else + { + GdkPixbuf *pixbuf = gtk_icon_info_get_builtin_pixbuf (info); + + if (G_IS_LOADABLE_ICON (pixbuf)) + [self setImageFromLoadableIcon:G_LOADABLE_ICON (pixbuf)]; + else + { + cancellable = g_cancellable_new (); + gtk_icon_info_load_symbolic_async (info, &foreground, &success, &warning, &error, cancellable, pixbuf_loaded, self); + } + } + } + + g_object_unref (info); + } + } + } + else if (G_IS_LOADABLE_ICON (icon)) + [self setImageFromLoadableIcon:G_LOADABLE_ICON (icon)]; + else + [self setImage:nil]; +} + +- (void)didChangeSensitive +{ + [self setEnabled:gtk_menu_tracker_item_get_sensitive (trackerItem) ? YES : NO]; +} + +- (void)didChangeVisible +{ + [self setHidden:gtk_menu_tracker_item_get_visible (trackerItem) ? NO : YES]; +} + +- (void)didChangeToggled +{ + [self setState:gtk_menu_tracker_item_get_toggled (trackerItem) ? NSOnState : NSOffState]; +} + +- (void)didChangeAccel +{ + const gchar *accel = gtk_menu_tracker_item_get_accel (trackerItem); + + if (accel != NULL) + { + guint key; + GdkModifierType mask; + unichar character; + NSUInteger modifiers; + + gtk_accelerator_parse (accel, &key, &mask); + + character = get_key_equivalent (key); + [self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]]; + + modifiers = 0; + if (mask & GDK_SHIFT_MASK) + modifiers |= NSShiftKeyMask; + if (mask & GDK_CONTROL_MASK) + modifiers |= NSControlKeyMask; + if (mask & GDK_MOD1_MASK) + modifiers |= NSAlternateKeyMask; + if (mask & GDK_META_MASK) + modifiers |= NSCommandKeyMask; + [self setKeyEquivalentModifierMask:modifiers]; + } + else + { + [self setKeyEquivalent:@""]; + [self setKeyEquivalentModifierMask:0]; + } +} + +- (void)didSelectItem:(id)sender +{ + gtk_menu_tracker_item_activated (trackerItem); +} + +@end + +@implementation NSMenuItem (GtkMenuTrackerItem) + ++ (id)menuItemForTrackerItem:(GtkMenuTrackerItem *)trackerItem +{ + if (gtk_menu_tracker_item_get_is_separator (trackerItem)) + return [NSMenuItem separatorItem]; + + return [[[GNSMenuItem alloc] initWithTrackerItem:trackerItem] autorelease]; +} + +@end + +static void +menu_item_inserted (GtkMenuTrackerItem *item, + gint position, + gpointer user_data) +{ + GNSMenu *menu = user_data; + + [menu insertItem:[NSMenuItem menuItemForTrackerItem:item] atIndex:position]; +} + +static void +menu_item_removed (gint position, + gpointer user_data) +{ + GNSMenu *menu = user_data; + + [menu removeItemAtIndex:position]; +} + +@implementation GNSMenu + +- (id)initWithTitle:(NSString *)title model:(GMenuModel *)model observable:(GtkActionObservable *)observable +{ + if ((self = [super initWithTitle:title]) != nil) + { + [self setAutoenablesItems:NO]; + + tracker = gtk_menu_tracker_new (observable, + model, + NO, + NULL, + menu_item_inserted, + menu_item_removed, + self); + } + + return self; +} + +- (id)initWithTitle:(NSString *)title trackerItem:(GtkMenuTrackerItem *)trackerItem +{ + if ((self = [super initWithTitle:title]) != nil) + { + [self setAutoenablesItems:NO]; + + tracker = gtk_menu_tracker_new_for_item_submenu (trackerItem, + menu_item_inserted, + menu_item_removed, + self); + } + + return self; +} + +- (void)dealloc +{ + gtk_menu_tracker_free (tracker); + + [super dealloc]; +} + +@end + +void +gtk_application_impl_quartz_setup_menu (GMenuModel *model, + GtkActionMuxer *muxer) +{ + NSMenu *menu; + + if (model != NULL) + menu = [[GNSMenu alloc] initWithTitle:@"Main Menu" model:model observable:GTK_ACTION_OBSERVABLE (muxer)]; + else + menu = [[NSMenu alloc] init]; + + [NSApp setMainMenu:menu]; + [menu release]; +} diff --git a/gtk/gtkapplication-quartz.c b/gtk/gtkapplication-quartz.c index 1d6f14dc03..8d2bea97e0 100644 --- a/gtk/gtkapplication-quartz.c +++ b/gtk/gtkapplication-quartz.c @@ -21,7 +21,6 @@ #include "config.h" #include "gtkapplicationprivate.h" -#include "gtkmodelmenu-quartz.h" #import <Cocoa/Cocoa.h> typedef struct @@ -46,6 +45,9 @@ typedef struct { GtkApplicationImpl impl; + GtkActionMuxer *muxer; + GMenu *combined; + GSList *inhibitors; gint quit_inhibit; guint next_cookie; @@ -82,20 +84,6 @@ G_DEFINE_TYPE (GtkApplicationImplQuartz, gtk_application_impl_quartz, GTK_TYPE_A @end static void -gtk_application_impl_quartz_menu_changed (GtkApplicationImplQuartz *quartz) -{ - GMenu *combined; - - combined = g_menu_new (); - g_menu_append_submenu (combined, "Application", gtk_application_get_app_menu (quartz->impl.application)); - g_menu_append_section (combined, NULL, gtk_application_get_menubar (quartz->impl.application)); - - gtk_quartz_set_main_menu (G_MENU_MODEL (combined), quartz->impl.application); - - g_object_unref (combined); -} - -static void gtk_application_impl_quartz_startup (GtkApplicationImpl *impl, gboolean register_session) { @@ -104,7 +92,17 @@ gtk_application_impl_quartz_startup (GtkApplicationImpl *impl, if (register_session) [NSApp setDelegate: [[GtkApplicationQuartzDelegate alloc] initWithImpl:quartz]]; - gtk_application_impl_quartz_menu_changed (quartz); + quartz->muxer = gtk_action_muxer_new (); + gtk_action_muxer_set_parent (quartz->muxer, gtk_application_get_action_muxer (impl->application)); + + /* app menu must come first so that we always see index '0' in + * 'combined' as being the app menu. + */ + gtk_application_impl_set_app_menu (impl, gtk_application_get_app_menu (impl->application)); + gtk_application_impl_set_menubar (impl, gtk_application_get_menubar (impl->application)); + + /* OK. Now put it in the menu. */ + gtk_application_impl_quartz_setup_menu (G_MENU_MODEL (quartz->combined), quartz->muxer); [NSApp finishLaunching]; } @@ -114,19 +112,58 @@ gtk_application_impl_quartz_shutdown (GtkApplicationImpl *impl) { GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl; - gtk_quartz_clear_main_menu (); + /* destroy our custom menubar */ + [NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]]; g_slist_free_full (quartz->inhibitors, (GDestroyNotify) gtk_application_quartz_inhibitor_free); quartz->inhibitors = NULL; } static void +gtk_application_impl_quartz_window_added (GtkApplicationImpl *impl, + GtkWindow *window) +{ +} + +static void +gtk_application_impl_quartz_window_removed (GtkApplicationImpl *impl, + GtkWindow *window) +{ +} + +static void +gtk_application_impl_quartz_active_window_changed (GtkApplicationImpl *impl, + GtkWindow *window) +{ + GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl; + + gtk_action_muxer_remove (quartz->muxer, "win"); + + if (G_IS_ACTION_GROUP (window)) + gtk_action_muxer_insert (quartz->muxer, "win", G_ACTION_GROUP (window)); +} + +static void gtk_application_impl_quartz_set_app_menu (GtkApplicationImpl *impl, GMenuModel *app_menu) { GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl; - gtk_application_impl_quartz_menu_changed (quartz); + /* If there are any items at all, then the first one is the app menu */ + if (g_menu_model_get_n_items (G_MENU_MODEL (quartz->combined))) + g_menu_remove (quartz->combined, 0); + + if (app_menu) + g_menu_prepend_submenu (quartz->combined, "`gtk-private-appname`", app_menu); + else + { + GMenu *empty; + + /* We must preserve the rule that index 0 is the app menu */ + empty = g_menu_new (); + g_menu_prepend_submenu (quartz->combined, "`gtk-private-appname`", G_MENU_MODEL (empty)); + g_object_unref (empty); + } } static void @@ -135,7 +172,12 @@ gtk_application_impl_quartz_set_menubar (GtkApplicationImpl *impl, { GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) impl; - gtk_application_impl_quartz_menu_changed (quartz); + /* If we have the menubar, it is a section at index '1' */ + if (g_menu_model_get_n_items (G_MENU_MODEL (quartz->combined)) > 1) + g_menu_remove (quartz->combined, 1); + + if (menubar) + g_menu_append_section (quartz->combined, NULL, menubar); } static guint @@ -200,6 +242,7 @@ gtk_application_impl_quartz_is_inhibited (GtkApplicationImpl *impl, static void gtk_application_impl_quartz_init (GtkApplicationImplQuartz *quartz) { + quartz->combined = g_menu_new (); } static void @@ -207,7 +250,7 @@ gtk_application_impl_quartz_finalize (GObject *object) { GtkApplicationImplQuartz *quartz = (GtkApplicationImplQuartz *) object; - g_slist_free_full (quartz->inhibitors, (GDestroyNotify) gtk_application_quartz_inhibitor_free); + g_clear_object (&quartz->combined); G_OBJECT_CLASS (gtk_application_impl_quartz_parent_class)->finalize (object); } @@ -219,6 +262,9 @@ gtk_application_impl_quartz_class_init (GtkApplicationImplClass *class) class->startup = gtk_application_impl_quartz_startup; class->shutdown = gtk_application_impl_quartz_shutdown; + class->window_added = gtk_application_impl_quartz_window_added; + class->window_removed = gtk_application_impl_quartz_window_removed; + class->active_window_changed = gtk_application_impl_quartz_active_window_changed; class->set_app_menu = gtk_application_impl_quartz_set_app_menu; class->set_menubar = gtk_application_impl_quartz_set_menubar; class->inhibit = gtk_application_impl_quartz_inhibit; diff --git a/gtk/gtkapplicationprivate.h b/gtk/gtkapplicationprivate.h index db2fa0086c..7847fcfb22 100644 --- a/gtk/gtkapplicationprivate.h +++ b/gtk/gtkapplicationprivate.h @@ -208,4 +208,8 @@ G_GNUC_INTERNAL gchar * gtk_application_impl_dbus_get_window_path (GtkApplicationImplDBus *dbus, GtkWindow *window); +G_GNUC_INTERNAL +void gtk_application_impl_quartz_setup_menu (GMenuModel *model, + GtkActionMuxer *muxer); + #endif /* __GTK_APPLICATION_PRIVATE_H__ */ diff --git a/gtk/gtkmodelmenu-quartz.c b/gtk/gtkmodelmenu-quartz.c deleted file mode 100644 index dae2376ee1..0000000000 --- a/gtk/gtkmodelmenu-quartz.c +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright © 2011 William Hua, Ryan Lortie - * - * 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: William Hua <william@attente.ca> - * Ryan Lortie <desrt@desrt.ca> - */ - -#include "gtkmodelmenu-quartz.h" - -#include <gdk/gdkkeysyms.h> -#include "gtkaccelmapprivate.h" -#include "gtkactionhelper.h" -#include "../gdk/quartz/gdkquartz.h" - -#import <Cocoa/Cocoa.h> - -/* - * Code for key code conversion - * - * Copyright (C) 2009 Paul Davis - */ -static unichar -gtk_quartz_model_menu_get_unichar (gint key) -{ - if (key >= GDK_KEY_A && key <= GDK_KEY_Z) - return key + (GDK_KEY_a - GDK_KEY_A); - - if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde) - return key; - - switch (key) - { - case GDK_KEY_BackSpace: - return NSBackspaceCharacter; - case GDK_KEY_Delete: - return NSDeleteFunctionKey; - case GDK_KEY_Pause: - return NSPauseFunctionKey; - case GDK_KEY_Scroll_Lock: - return NSScrollLockFunctionKey; - case GDK_KEY_Sys_Req: - return NSSysReqFunctionKey; - case GDK_KEY_Home: - return NSHomeFunctionKey; - case GDK_KEY_Left: - case GDK_KEY_leftarrow: - return NSLeftArrowFunctionKey; - case GDK_KEY_Up: - case GDK_KEY_uparrow: - return NSUpArrowFunctionKey; - case GDK_KEY_Right: - case GDK_KEY_rightarrow: - return NSRightArrowFunctionKey; - case GDK_KEY_Down: - case GDK_KEY_downarrow: - return NSDownArrowFunctionKey; - case GDK_KEY_Page_Up: - return NSPageUpFunctionKey; - case GDK_KEY_Page_Down: - return NSPageDownFunctionKey; - case GDK_KEY_End: - return NSEndFunctionKey; - case GDK_KEY_Begin: - return NSBeginFunctionKey; - case GDK_KEY_Select: - return NSSelectFunctionKey; - case GDK_KEY_Print: - return NSPrintFunctionKey; - case GDK_KEY_Execute: - return NSExecuteFunctionKey; - case GDK_KEY_Insert: - return NSInsertFunctionKey; - case GDK_KEY_Undo: - return NSUndoFunctionKey; - case GDK_KEY_Redo: - return NSRedoFunctionKey; - case GDK_KEY_Menu: - return NSMenuFunctionKey; - case GDK_KEY_Find: - return NSFindFunctionKey; - case GDK_KEY_Help: - return NSHelpFunctionKey; - case GDK_KEY_Break: - return NSBreakFunctionKey; - case GDK_KEY_Mode_switch: - return NSModeSwitchFunctionKey; - case GDK_KEY_F1: - return NSF1FunctionKey; - case GDK_KEY_F2: - return NSF2FunctionKey; - case GDK_KEY_F3: - return NSF3FunctionKey; - case GDK_KEY_F4: - return NSF4FunctionKey; - case GDK_KEY_F5: - return NSF5FunctionKey; - case GDK_KEY_F6: - return NSF6FunctionKey; - case GDK_KEY_F7: - return NSF7FunctionKey; - case GDK_KEY_F8: - return NSF8FunctionKey; - case GDK_KEY_F9: - return NSF9FunctionKey; - case GDK_KEY_F10: - return NSF10FunctionKey; - case GDK_KEY_F11: - return NSF11FunctionKey; - case GDK_KEY_F12: - return NSF12FunctionKey; - case GDK_KEY_F13: - return NSF13FunctionKey; - case GDK_KEY_F14: - return NSF14FunctionKey; - case GDK_KEY_F15: - return NSF15FunctionKey; - case GDK_KEY_F16: - return NSF16FunctionKey; - case GDK_KEY_F17: - return NSF17FunctionKey; - case GDK_KEY_F18: - return NSF18FunctionKey; - case GDK_KEY_F19: - return NSF19FunctionKey; - case GDK_KEY_F20: - return NSF20FunctionKey; - case GDK_KEY_F21: - return NSF21FunctionKey; - case GDK_KEY_F22: - return NSF22FunctionKey; - case GDK_KEY_F23: - return NSF23FunctionKey; - case GDK_KEY_F24: - return NSF24FunctionKey; - case GDK_KEY_F25: - return NSF25FunctionKey; - case GDK_KEY_F26: - return NSF26FunctionKey; - case GDK_KEY_F27: - return NSF27FunctionKey; - case GDK_KEY_F28: - return NSF28FunctionKey; - case GDK_KEY_F29: - return NSF29FunctionKey; - case GDK_KEY_F30: - return NSF30FunctionKey; - case GDK_KEY_F31: - return NSF31FunctionKey; - case GDK_KEY_F32: - return NSF32FunctionKey; - case GDK_KEY_F33: - return NSF33FunctionKey; - case GDK_KEY_F34: - return NSF34FunctionKey; - case GDK_KEY_F35: - return NSF35FunctionKey; - default: - break; - } - - return '\0'; -} - - - -@interface GNSMenu : NSMenu -{ - GtkApplication *application; - GMenuModel *model; - guint update_idle; - GSList *connected; - gboolean with_separators; -} - -- (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)application hasSeparators:(BOOL)hasSeparators; - -- (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added; - -- (gboolean)handleChanges; - -@end - - - -@interface GNSMenuItem : NSMenuItem -{ - GtkActionHelper *helper; -} - -- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application; - -- (void)didSelectItem:(id)sender; - -- (void)helperChanged; - -@end - - - -static gboolean -gtk_quartz_model_menu_handle_changes (gpointer user_data) -{ - GNSMenu *menu = user_data; - - return [menu handleChanges]; -} - -static void -gtk_quartz_model_menu_items_changed (GMenuModel *model, - gint position, - gint removed, - gint added, - gpointer user_data) -{ - GNSMenu *menu = user_data; - - [menu model:model didChangeAtPosition:position removed:removed added:added]; -} - -void -gtk_quartz_set_main_menu (GMenuModel *model, - GtkApplication *application) -{ - [NSApp setMainMenu:[[[GNSMenu alloc] initWithTitle:@"Main Menu" model:model application:application hasSeparators:NO] autorelease]]; -} - -void -gtk_quartz_clear_main_menu (void) -{ - // ensure that we drop all GNSMenuItem (to ensure 'application' has no extra references) - [NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]]; -} - -@interface GNSMenu () - -- (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators; - -@end - - - -@implementation GNSMenu - -- (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added -{ - if (update_idle == 0) - update_idle = gdk_threads_add_idle (gtk_quartz_model_menu_handle_changes, self); -} - -- (void)appendItemFromModel:(GMenuModel *)aModel atIndex:(gint)index withHeading:(gchar **)heading -{ - GMenuModel *section; - - if ((section = g_menu_model_get_item_link (aModel, index, G_MENU_LINK_SECTION))) - { - g_menu_model_get_item_attribute (aModel, index, G_MENU_ATTRIBUTE_LABEL, "s", heading); - [self appendFromModel:section withSeparators:NO]; - g_object_unref (section); - } - else - [self addItem:[[[GNSMenuItem alloc] initWithModel:aModel index:index application:application] autorelease]]; -} - -- (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators -{ - gint n, i; - - g_signal_connect (aModel, "items-changed", G_CALLBACK (gtk_quartz_model_menu_items_changed), self); - connected = g_slist_prepend (connected, g_object_ref (aModel)); - - n = g_menu_model_get_n_items (aModel); - - for (i = 0; i < n; i++) - { - NSInteger ourPosition = [self numberOfItems]; - gchar *heading = NULL; - - [self appendItemFromModel:aModel atIndex:i withHeading:&heading]; - - if (withSeparators && ourPosition < [self numberOfItems]) - { - NSMenuItem *separator = nil; - - if (heading) - { - separator = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:heading] action:NULL keyEquivalent:@""] autorelease]; - - [separator setEnabled:NO]; - } - else if (ourPosition > 0) - separator = [NSMenuItem separatorItem]; - - if (separator != nil) - [self insertItem:separator atIndex:ourPosition]; - } - - g_free (heading); - } -} - -- (void)populate -{ - /* removeAllItems is available only in 10.6 and later, but it's more - efficient than iterating over the array of - NSMenuItems. performSelector: suppresses a compiler warning when - building on earlier OSX versions. */ - if ([self respondsToSelector: @selector (removeAllItems)]) - [self performSelector: @selector (removeAllItems)]; - else - { - /* Iterate from the bottom up to save reindexing the NSArray. */ - int i; - for (i = [self numberOfItems]; i > 0; i--) - [self removeItemAtIndex: i]; - } - - [self appendFromModel:model withSeparators:with_separators]; -} - -- (gboolean)handleChanges -{ - while (connected) - { - g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self); - g_object_unref (connected->data); - - connected = g_slist_delete_link (connected, connected); - } - - [self populate]; - - update_idle = 0; - - return G_SOURCE_REMOVE; -} - -- (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)anApplication hasSeparators:(BOOL)hasSeparators -{ - if((self = [super initWithTitle:title]) != nil) - { - [self setAutoenablesItems:NO]; - - model = g_object_ref (aModel); - application = g_object_ref (anApplication); - with_separators = hasSeparators; - - [self populate]; - } - - return self; -} - -- (void)dealloc -{ - while (connected) - { - g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self); - g_object_unref (connected->data); - - connected = g_slist_delete_link (connected, connected); - } - - g_object_unref (application); - g_object_unref (model); - - [super dealloc]; -} - -@end - - - -static void -gtk_quartz_action_helper_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - GNSMenuItem *item = user_data; - - [item helperChanged]; -} - -@implementation GNSMenuItem - -- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application -{ - 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) - { - GMenuModel *submenu; - gchar *action; - GVariant *target; - - action = NULL; - 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); - - if ((submenu = g_menu_model_get_item_link (model, index, G_MENU_LINK_SUBMENU))) - { - [self setSubmenu:[[[GNSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title] model:submenu application:application hasSeparators:YES] autorelease]]; - g_object_unref (submenu); - } - - else if (action != NULL) - { - GtkAccelKey key; - gchar *path; - - helper = gtk_action_helper_new_with_application (application); - gtk_action_helper_set_action_name (helper, action); - gtk_action_helper_set_action_target_value (helper, target); - - g_signal_connect (helper, "notify", G_CALLBACK (gtk_quartz_action_helper_changed), self); - - [self helperChanged]; - - path = _gtk_accel_path_for_action (action, target); - if (gtk_accel_map_lookup_entry (path, &key)) - { - unichar character = gtk_quartz_model_menu_get_unichar (key.accel_key); - - if (character) - { - NSUInteger modifiers = 0; - - if (key.accel_mods & GDK_SHIFT_MASK) - modifiers |= NSShiftKeyMask; - - if (key.accel_mods & GDK_MOD1_MASK) - modifiers |= NSAlternateKeyMask; - - if (key.accel_mods & GDK_CONTROL_MASK) - modifiers |= NSControlKeyMask; - - if (key.accel_mods & GDK_META_MASK) - modifiers |= NSCommandKeyMask; - - [self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]]; - [self setKeyEquivalentModifierMask:modifiers]; - } - } - - g_free (path); - - [self setTarget:self]; - } - } - - g_free (title); - - return self; -} - -- (void)dealloc -{ - if (helper != NULL) - g_object_unref (helper); - - [super dealloc]; -} - -- (void)didSelectItem:(id)sender -{ - gtk_action_helper_activate (helper); -} - -- (void)helperChanged -{ - [self setEnabled:gtk_action_helper_get_enabled (helper)]; - [self setState:gtk_action_helper_get_active (helper)]; - - switch (gtk_action_helper_get_role (helper)) - { - case GTK_ACTION_HELPER_ROLE_NORMAL: - [self setOnStateImage:nil]; - break; - case GTK_ACTION_HELPER_ROLE_TOGGLE: - [self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]]; - break; - case GTK_ACTION_HELPER_ROLE_RADIO: - [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]]; - break; - default: - g_assert_not_reached (); - } -} - -@end diff --git a/gtk/gtkmodelmenu-quartz.h b/gtk/gtkmodelmenu-quartz.h deleted file mode 100644 index 1be9220363..0000000000 --- a/gtk/gtkmodelmenu-quartz.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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, see <http://www.gnu.org/licenses/>. - * - * Author: William Hua <william@attente.ca> - */ - -#ifndef __GTK_MODELMENU_QUARTZ_H__ -#define __GTK_MODELMENU_QUARTZ_H__ - -#include "gtkapplication.h" - -void gtk_quartz_set_main_menu (GMenuModel *model, - GtkApplication *application); - -void gtk_quartz_clear_main_menu (void); - -#endif /* __GTK_MODELMENU_QUARTZ_H__ */ |