summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Hua <william@attente.ca>2013-12-16 09:33:38 -0500
committerRyan Lortie <desrt@desrt.ca>2013-12-17 17:17:09 -0500
commit0c03793b6384280099a33fce948497135672ff57 (patch)
tree22cadf857ef92637dceed7537632c3a5eb19a1f6
parent3396c6634696fefb2a7f86db04a4c84d3f2ad488 (diff)
downloadgtk+-0c03793b6384280099a33fce948497135672ff57.tar.gz
Use GtkMenuTracker for Quartz backend
-rw-r--r--gtk/Makefile.am3
-rw-r--r--gtk/gtkapplication-quartz-menu.c700
-rw-r--r--gtk/gtkapplication-quartz.c86
-rw-r--r--gtk/gtkapplicationprivate.h4
-rw-r--r--gtk/gtkmodelmenu-quartz.c519
-rw-r--r--gtk/gtkmodelmenu-quartz.h30
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__ */