summaryrefslogtreecommitdiff
path: root/libs/gtkmm2ext/gtkapplication_quartz.mm
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2010-03-16 19:18:37 +0000
committerPaul Davis <paul@linuxaudiosystems.com>2010-03-16 19:18:37 +0000
commit7286ca7c458228e60adf6c4b9ba0c4214c26c11a (patch)
tree5c7f7e1b69cafb2a1c53e54490c8431bae791985 /libs/gtkmm2ext/gtkapplication_quartz.mm
parent4e9ad8da4501848af08cb99d377b36bcf60e5d10 (diff)
more new files
git-svn-id: svn://localhost/ardour2/branches/3.0@6763 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/gtkmm2ext/gtkapplication_quartz.mm')
-rw-r--r--libs/gtkmm2ext/gtkapplication_quartz.mm1458
1 files changed, 1458 insertions, 0 deletions
diff --git a/libs/gtkmm2ext/gtkapplication_quartz.mm b/libs/gtkmm2ext/gtkapplication_quartz.mm
new file mode 100644
index 0000000000..b6d6b71d5d
--- /dev/null
+++ b/libs/gtkmm2ext/gtkapplication_quartz.mm
@@ -0,0 +1,1458 @@
+/* GTK+ application-level integration for the Mac OS X/Cocoa
+ *
+ * Copyright (C) 2007 Pioneer Research Center USA, Inc.
+ * Copyright (C) 2007 Imendio AB
+ * Copyright (C) 2009 Paul Davis
+ *
+ * This is a reimplementation in Cocoa of the sync-menu.c concept
+ * from Imendio, although without the "set quit menu" API since
+ * a Cocoa app needs to handle termination anyway.
+ *
+ * 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; version 2.1
+ * of the License.
+ *
+ * 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.
+ */
+
+#import <AppKit/NSMenu.h>
+#import <AppKit/NSMenuItem.h>
+#import <AppKit/NSCell.h>
+#import <AppKit/NSEvent.h>
+#import <AppKit/NSApplication.h>
+#import <Foundation/NSString.h>
+#import <Foundation/NSNotification.h>
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtkmm2ext/gtkapplication.h>
+#include <gtkmm2ext/gtkapplication-private.h>
+
+// #define DEBUG(format, ...) g_printerr ("%s: " format, G_STRFUNC, ## __VA_ARGS__)
+#define DEBUG(format, ...)
+
+/* TODO
+ *
+ * - Sync adding/removing/reordering items
+ * - Create on demand? (can this be done with gtk+? ie fill in menu
+ items when the menu is opened)
+ * - Figure out what to do per app/window...
+ *
+ */
+
+static guint
+gdk_quartz_keyval_to_ns_keyval (guint keyval)
+{
+ switch (keyval) {
+ case GDK_BackSpace:
+ return NSBackspaceCharacter;
+ case GDK_Delete:
+ return NSDeleteFunctionKey;
+ case GDK_Pause:
+ return NSPauseFunctionKey;
+ case GDK_Scroll_Lock:
+ return NSScrollLockFunctionKey;
+ case GDK_Sys_Req:
+ return NSSysReqFunctionKey;
+ case GDK_Home:
+ return NSHomeFunctionKey;
+ case GDK_Left:
+ case GDK_leftarrow:
+ return NSLeftArrowFunctionKey;
+ case GDK_Up:
+ case GDK_uparrow:
+ return NSUpArrowFunctionKey;
+ case GDK_Right:
+ case GDK_rightarrow:
+ return NSRightArrowFunctionKey;
+ case GDK_Down:
+ case GDK_downarrow:
+ return NSDownArrowFunctionKey;
+ case GDK_Page_Up:
+ return NSPageUpFunctionKey;
+ case GDK_Page_Down:
+ return NSPageDownFunctionKey;
+ case GDK_End:
+ return NSEndFunctionKey;
+ case GDK_Begin:
+ return NSBeginFunctionKey;
+ case GDK_Select:
+ return NSSelectFunctionKey;
+ case GDK_Print:
+ return NSPrintFunctionKey;
+ case GDK_Execute:
+ return NSExecuteFunctionKey;
+ case GDK_Insert:
+ return NSInsertFunctionKey;
+ case GDK_Undo:
+ return NSUndoFunctionKey;
+ case GDK_Redo:
+ return NSRedoFunctionKey;
+ case GDK_Menu:
+ return NSMenuFunctionKey;
+ case GDK_Find:
+ return NSFindFunctionKey;
+ case GDK_Help:
+ return NSHelpFunctionKey;
+ case GDK_Break:
+ return NSBreakFunctionKey;
+ case GDK_Mode_switch:
+ return NSModeSwitchFunctionKey;
+ case GDK_F1:
+ return NSF1FunctionKey;
+ case GDK_F2:
+ return NSF2FunctionKey;
+ case GDK_F3:
+ return NSF3FunctionKey;
+ case GDK_F4:
+ return NSF4FunctionKey;
+ case GDK_F5:
+ return NSF5FunctionKey;
+ case GDK_F6:
+ return NSF6FunctionKey;
+ case GDK_F7:
+ return NSF7FunctionKey;
+ case GDK_F8:
+ return NSF8FunctionKey;
+ case GDK_F9:
+ return NSF9FunctionKey;
+ case GDK_F10:
+ return NSF10FunctionKey;
+ case GDK_F11:
+ return NSF11FunctionKey;
+ case GDK_F12:
+ return NSF12FunctionKey;
+ case GDK_F13:
+ return NSF13FunctionKey;
+ case GDK_F14:
+ return NSF14FunctionKey;
+ case GDK_F15:
+ return NSF15FunctionKey;
+ case GDK_F16:
+ return NSF16FunctionKey;
+ case GDK_F17:
+ return NSF17FunctionKey;
+ case GDK_F18:
+ return NSF18FunctionKey;
+ case GDK_F19:
+ return NSF19FunctionKey;
+ case GDK_F20:
+ return NSF20FunctionKey;
+ case GDK_F21:
+ return NSF21FunctionKey;
+ case GDK_F22:
+ return NSF22FunctionKey;
+ case GDK_F23:
+ return NSF23FunctionKey;
+ case GDK_F24:
+ return NSF24FunctionKey;
+ case GDK_F25:
+ return NSF25FunctionKey;
+ case GDK_F26:
+ return NSF26FunctionKey;
+ case GDK_F27:
+ return NSF27FunctionKey;
+ case GDK_F28:
+ return NSF28FunctionKey;
+ case GDK_F29:
+ return NSF29FunctionKey;
+ case GDK_F30:
+ return NSF30FunctionKey;
+ case GDK_F31:
+ return NSF31FunctionKey;
+ case GDK_F32:
+ return NSF32FunctionKey;
+ case GDK_F33:
+ return NSF33FunctionKey;
+ case GDK_F34:
+ return NSF34FunctionKey;
+ case GDK_F35:
+ return NSF35FunctionKey;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static gboolean
+keyval_is_keypad (guint keyval)
+{
+ switch (keyval) {
+ case GDK_KP_F1:
+ case GDK_KP_F2:
+ case GDK_KP_F3:
+ case GDK_KP_F4:
+ case GDK_KP_Home:
+ case GDK_KP_Left:
+ case GDK_KP_Up:
+ case GDK_KP_Right:
+ case GDK_KP_Down:
+ case GDK_KP_Page_Up:
+ case GDK_KP_Page_Down:
+ case GDK_KP_End:
+ case GDK_KP_Begin:
+ case GDK_KP_Insert:
+ case GDK_KP_Delete:
+ case GDK_KP_Equal:
+ case GDK_KP_Multiply:
+ case GDK_KP_Add:
+ case GDK_KP_Separator:
+ case GDK_KP_Subtract:
+ case GDK_KP_Decimal:
+ case GDK_KP_Divide:
+ case GDK_KP_0:
+ case GDK_KP_1:
+ case GDK_KP_2:
+ case GDK_KP_3:
+ case GDK_KP_4:
+ case GDK_KP_5:
+ case GDK_KP_6:
+ case GDK_KP_7:
+ case GDK_KP_8:
+ case GDK_KP_9:
+ return TRUE;
+ break;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+static guint
+keyval_keypad_nonkeypad_equivalent (guint keyval)
+{
+ switch (keyval) {
+ case GDK_KP_F1:
+ return GDK_F1;
+ case GDK_KP_F2:
+ return GDK_F2;
+ case GDK_KP_F3:
+ return GDK_F3;
+ case GDK_KP_F4:
+ return GDK_F4;
+ case GDK_KP_Home:
+ return GDK_Home;
+ case GDK_KP_Left:
+ return GDK_Left;
+ case GDK_KP_Up:
+ return GDK_Up;
+ case GDK_KP_Right:
+ return GDK_Right;
+ case GDK_KP_Down:
+ return GDK_Down;
+ case GDK_KP_Page_Up:
+ return GDK_Page_Up;
+ case GDK_KP_Page_Down:
+ return GDK_Page_Down;
+ case GDK_KP_End:
+ return GDK_End;
+ case GDK_KP_Begin:
+ return GDK_Begin;
+ case GDK_KP_Insert:
+ return GDK_Insert;
+ case GDK_KP_Delete:
+ return GDK_Delete;
+ case GDK_KP_Equal:
+ return GDK_equal;
+ case GDK_KP_Multiply:
+ return GDK_asterisk;
+ case GDK_KP_Add:
+ return GDK_plus;
+ case GDK_KP_Subtract:
+ return GDK_minus;
+ case GDK_KP_Decimal:
+ return GDK_period;
+ case GDK_KP_Divide:
+ return GDK_slash;
+ case GDK_KP_0:
+ return GDK_0;
+ case GDK_KP_1:
+ return GDK_1;
+ case GDK_KP_2:
+ return GDK_2;
+ case GDK_KP_3:
+ return GDK_3;
+ case GDK_KP_4:
+ return GDK_4;
+ case GDK_KP_5:
+ return GDK_5;
+ case GDK_KP_6:
+ return GDK_6;
+ case GDK_KP_7:
+ return GDK_7;
+ case GDK_KP_8:
+ return GDK_8;
+ case GDK_KP_9:
+ return GDK_9;
+ default:
+ break;
+ }
+
+ return GDK_VoidSymbol;
+}
+
+static const gchar*
+gdk_quartz_keyval_to_string (guint keyval)
+{
+ switch (keyval) {
+ case GDK_space:
+ return " ";
+ case GDK_exclam:
+ return "!";
+ case GDK_quotedbl:
+ return "\"";
+ case GDK_numbersign:
+ return "#";
+ case GDK_dollar:
+ return "$";
+ case GDK_percent:
+ return "%";
+ case GDK_ampersand:
+ return "&";
+ case GDK_apostrophe:
+ return "'";
+ case GDK_parenleft:
+ return "(";
+ case GDK_parenright:
+ return ")";
+ case GDK_asterisk:
+ return "*";
+ case GDK_plus:
+ return "+";
+ case GDK_comma:
+ return ",";
+ case GDK_minus:
+ return "-";
+ case GDK_period:
+ return ".";
+ case GDK_slash:
+ return "/";
+ case GDK_0:
+ return "0";
+ case GDK_1:
+ return "1";
+ case GDK_2:
+ return "2";
+ case GDK_3:
+ return "3";
+ case GDK_4:
+ return "4";
+ case GDK_5:
+ return "5";
+ case GDK_6:
+ return "6";
+ case GDK_7:
+ return "7";
+ case GDK_8:
+ return "8";
+ case GDK_9:
+ return "9";
+ case GDK_colon:
+ return ":";
+ case GDK_semicolon:
+ return ";";
+ case GDK_less:
+ return "<";
+ case GDK_equal:
+ return "=";
+ case GDK_greater:
+ return ">";
+ case GDK_question:
+ return "?";
+ case GDK_at:
+ return "@";
+ case GDK_A:
+ case GDK_a:
+ return "a";
+ case GDK_B:
+ case GDK_b:
+ return "b";
+ case GDK_C:
+ case GDK_c:
+ return "c";
+ case GDK_D:
+ case GDK_d:
+ return "d";
+ case GDK_E:
+ case GDK_e:
+ return "e";
+ case GDK_F:
+ case GDK_f:
+ return "f";
+ case GDK_G:
+ case GDK_g:
+ return "g";
+ case GDK_H:
+ case GDK_h:
+ return "h";
+ case GDK_I:
+ case GDK_i:
+ return "i";
+ case GDK_J:
+ case GDK_j:
+ return "j";
+ case GDK_K:
+ case GDK_k:
+ return "k";
+ case GDK_L:
+ case GDK_l:
+ return "l";
+ case GDK_M:
+ case GDK_m:
+ return "m";
+ case GDK_N:
+ case GDK_n:
+ return "n";
+ case GDK_O:
+ case GDK_o:
+ return "o";
+ case GDK_P:
+ case GDK_p:
+ return "p";
+ case GDK_Q:
+ case GDK_q:
+ return "q";
+ case GDK_R:
+ case GDK_r:
+ return "r";
+ case GDK_S:
+ case GDK_s:
+ return "s";
+ case GDK_T:
+ case GDK_t:
+ return "t";
+ case GDK_U:
+ case GDK_u:
+ return "u";
+ case GDK_V:
+ case GDK_v:
+ return "v";
+ case GDK_W:
+ case GDK_w:
+ return "w";
+ case GDK_X:
+ case GDK_x:
+ return "x";
+ case GDK_Y:
+ case GDK_y:
+ return "y";
+ case GDK_Z:
+ case GDK_z:
+ return "z";
+ case GDK_bracketleft:
+ return "[";
+ case GDK_backslash:
+ return "\\";
+ case GDK_bracketright:
+ return "]";
+ case GDK_asciicircum:
+ return "^";
+ case GDK_underscore:
+ return "_";
+ case GDK_grave:
+ return "`";
+ case GDK_braceleft:
+ return "{";
+ case GDK_bar:
+ return "|";
+ case GDK_braceright:
+ return "}";
+ case GDK_asciitilde:
+ return "~";
+ default:
+ break;
+ }
+ return NULL;
+};
+
+static gboolean
+keyval_is_uppercase (guint keyval)
+{
+ switch (keyval) {
+ case GDK_A:
+ case GDK_B:
+ case GDK_C:
+ case GDK_D:
+ case GDK_E:
+ case GDK_F:
+ case GDK_G:
+ case GDK_H:
+ case GDK_I:
+ case GDK_J:
+ case GDK_K:
+ case GDK_L:
+ case GDK_M:
+ case GDK_N:
+ case GDK_O:
+ case GDK_P:
+ case GDK_Q:
+ case GDK_R:
+ case GDK_S:
+ case GDK_T:
+ case GDK_U:
+ case GDK_V:
+ case GDK_W:
+ case GDK_X:
+ case GDK_Y:
+ case GDK_Z:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+ return FALSE;
+}
+
+/* gtk/osx has a problem in that mac main menu events
+ are handled using an "internal" event handling system that
+ doesn't pass things back to the glib/gtk main loop. if we call
+ gtk_main_iteration() block while in a menu event handler, then
+ glib gets confused and thinks there are two threads running
+ g_main_poll_func(). apps call call gdk_quartz_in_menu_event_handler()
+ if they need to check this.
+ */
+
+static int _in_menu_event_handler = 0;
+
+int
+gdk_quartz_in_menu_event_handler ()
+{
+ return _in_menu_event_handler;
+}
+
+static gboolean
+idle_call_activate (gpointer data)
+{
+ gtk_menu_item_activate ((GtkMenuItem*) data);
+ return FALSE;
+}
+
+@interface GNSMenuItem : NSMenuItem
+{
+ @public
+ GtkMenuItem* gtk_menu_item;
+ GClosure *accel_closure;
+}
+- (id) initWithTitle:(NSString*) title andGtkWidget:(GtkMenuItem*) w;
+- (void) activate:(id) sender;
+@end
+
+@implementation GNSMenuItem
+- (id) initWithTitle:(NSString*) title andGtkWidget:(GtkMenuItem*) w
+{
+ /* All menu items have the action "activate", which will be handled by this child class
+ */
+
+ self = [ super initWithTitle:title action:@selector(activate:) keyEquivalent:@"" ];
+
+ if (self) {
+ /* make this handle its own action */
+ [ self setTarget:self ];
+ gtk_menu_item = w;
+ accel_closure = 0;
+ }
+ return self;
+}
+- (void) activate:(id) sender
+{
+ g_idle_add (idle_call_activate, gtk_menu_item);
+}
+@end
+
+static void push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
+ NSMenu *menu,
+ gboolean toplevel,
+ gboolean debug);
+
+/*
+ * utility functions
+ */
+
+static GtkWidget *
+find_menu_label (GtkWidget *widget)
+{
+ GtkWidget *label = NULL;
+
+ if (GTK_IS_LABEL (widget))
+ return widget;
+
+ if (GTK_IS_CONTAINER (widget))
+ {
+ GList *children;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (widget));
+
+ for (l = children; l; l = l->next)
+ {
+ label = find_menu_label ((GtkWidget*) l->data);
+ if (label)
+ break;
+ }
+
+ g_list_free (children);
+ }
+
+ return label;
+}
+
+static const gchar *
+get_menu_label_text (GtkWidget *menu_item,
+ GtkWidget **label)
+{
+ GtkWidget *my_label;
+
+ my_label = find_menu_label (menu_item);
+ if (label)
+ *label = my_label;
+
+ if (my_label)
+ return gtk_label_get_text (GTK_LABEL (my_label));
+
+ return NULL;
+}
+
+static gboolean
+accel_find_func (GtkAccelKey *key,
+ GClosure *closure,
+ gpointer data)
+{
+ return (GClosure *) data == closure;
+}
+
+
+/*
+ * CocoaMenu functions
+ */
+
+static GQuark cocoa_menu_quark = 0;
+
+static NSMenu *
+cocoa_menu_get (GtkWidget *widget)
+{
+ return (NSMenu*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_quark);
+}
+
+static void
+cocoa_menu_free (gpointer *ptr)
+{
+ NSMenu* menu = (NSMenu*) ptr;
+ [menu release];
+}
+
+static void
+cocoa_menu_connect (GtkWidget *menu,
+ NSMenu* cocoa_menu)
+{
+ [cocoa_menu retain];
+
+ if (cocoa_menu_quark == 0)
+ cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
+
+ g_object_set_qdata_full (G_OBJECT (menu), cocoa_menu_quark,
+ cocoa_menu,
+ (GDestroyNotify) cocoa_menu_free);
+}
+
+/*
+ * NSMenuItem functions
+ */
+
+static GQuark cocoa_menu_item_quark = 0;
+static void cocoa_menu_item_connect (GtkWidget* menu_item,
+ GNSMenuItem* cocoa_menu_item,
+ GtkWidget *label);
+
+static void
+cocoa_menu_item_free (gpointer *ptr)
+{
+ GNSMenuItem* item = (GNSMenuItem*) ptr;
+ [item release];
+}
+
+static GNSMenuItem *
+cocoa_menu_item_get (GtkWidget *widget)
+{
+ return (GNSMenuItem*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_item_quark);
+}
+
+static void
+cocoa_menu_item_update_state (NSMenuItem* cocoa_item,
+ GtkWidget *widget)
+{
+ gboolean sensitive;
+ gboolean visible;
+
+ g_object_get (widget,
+ "sensitive", &sensitive,
+ "visible", &visible,
+ NULL);
+
+ if (!sensitive)
+ [cocoa_item setEnabled:NO];
+ else
+ [cocoa_item setEnabled:YES];
+
+#if 0
+ // requires OS X 10.5 or later
+ if (!visible)
+ [cocoa_item setHidden:YES];
+ else
+ [cocoa_item setHidden:NO];
+#endif
+}
+
+static void
+cocoa_menu_item_update_active (NSMenuItem *cocoa_item,
+ GtkWidget *widget)
+{
+ gboolean active;
+
+ g_object_get (widget, "active", &active, NULL);
+
+ if (active)
+ [cocoa_item setState:NSOnState];
+ else
+ [cocoa_item setState:NSOffState];
+}
+
+static void
+cocoa_menu_item_update_submenu (NSMenuItem *cocoa_item,
+ GtkWidget *widget)
+{
+ GtkWidget *submenu;
+
+ g_return_if_fail (cocoa_item != NULL);
+ g_return_if_fail (widget != NULL);
+
+ submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
+
+ if (submenu)
+ {
+ GtkWidget* label = NULL;
+ const gchar *label_text;
+ NSMenu* cocoa_submenu;
+
+ label_text = get_menu_label_text (widget, &label);
+
+ /* create a new nsmenu to hold the GTK menu */
+
+ if (label_text)
+ cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
+ else
+ cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:@""];
+
+ [cocoa_submenu setAutoenablesItems:NO];
+ cocoa_menu_connect (submenu, cocoa_submenu);
+
+ /* connect the new nsmenu to the passed-in item (which lives in
+ the parent nsmenu)
+ (Note: this will release any pre-existing version of this submenu)
+ */
+ [ cocoa_item setSubmenu:cocoa_submenu];
+
+ /* and push the GTK menu into the submenu */
+ push_menu_shell_to_nsmenu (GTK_MENU_SHELL (submenu), cocoa_submenu, FALSE, FALSE);
+
+ [ cocoa_submenu release ];
+ }
+}
+
+static void
+cocoa_menu_item_update_label (NSMenuItem *cocoa_item,
+ GtkWidget *widget)
+{
+ const gchar *label_text;
+
+ g_return_if_fail (cocoa_item != NULL);
+ g_return_if_fail (widget != NULL);
+
+ label_text = get_menu_label_text (widget, NULL);
+ if (label_text)
+ [cocoa_item setTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
+ else
+ [cocoa_item setTitle:@""];
+}
+
+static void
+cocoa_menu_item_update_accelerator (NSMenuItem *cocoa_item,
+ GtkWidget *widget)
+{
+ GtkWidget *label;
+
+ g_return_if_fail (cocoa_item != NULL);
+ g_return_if_fail (widget != NULL);
+
+ /* important note: this function doesn't do anything to actually change
+ key handling. Its goal is to get Cocoa to display the correct
+ accelerator as part of a menu item. Actual accelerator handling
+ is still done by GTK, so this is more cosmetic than it may
+ appear.
+ */
+
+ const gchar* ltxt = get_menu_label_text (widget, &label);
+
+ if (GTK_IS_ACCEL_LABEL (label) &&
+ GTK_ACCEL_LABEL (label)->accel_closure)
+ {
+ GtkAccelKey *key;
+
+ key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
+ accel_find_func,
+ GTK_ACCEL_LABEL (label)->accel_closure);
+
+ if (key &&
+ key->accel_key &&
+ key->accel_flags & GTK_ACCEL_VISIBLE)
+ {
+ guint modifiers = 0;
+ const gchar* str = NULL;
+ guint actual_key = key->accel_key;
+
+ if (keyval_is_keypad (actual_key)) {
+ if ((actual_key = keyval_keypad_nonkeypad_equivalent (actual_key)) == GDK_VoidSymbol) {
+ /* GDK_KP_Separator */
+ [cocoa_item setKeyEquivalent:@""];
+ return;
+ }
+ modifiers |= NSNumericPadKeyMask;
+ }
+
+ /* if we somehow got here with GDK_A ... GDK_Z rather than GDK_a ... GDK_z, then take note
+ of that and make sure we use a shift modifier.
+ */
+
+ if (keyval_is_uppercase (actual_key)) {
+ modifiers |= NSShiftKeyMask;
+ }
+
+ str = gdk_quartz_keyval_to_string (actual_key);
+
+ if (str) {
+ unichar ukey = str[0];
+ [cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
+ } else {
+ unichar ukey = gdk_quartz_keyval_to_ns_keyval (actual_key);
+ if (ukey != 0) {
+ [cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
+ } else {
+ /* cannot map this key to Cocoa key equivalent */
+ [cocoa_item setKeyEquivalent:@""];
+ return;
+ }
+ }
+
+ if (key->accel_mods || modifiers)
+ {
+ if (key->accel_mods & GDK_SHIFT_MASK) {
+ modifiers |= NSShiftKeyMask;
+ }
+
+ /* gdk/quartz maps Alt/Option to Mod1 */
+
+ if (key->accel_mods & (GDK_MOD1_MASK)) {
+ modifiers |= NSAlternateKeyMask;
+ }
+
+ if (key->accel_mods & GDK_CONTROL_MASK) {
+ modifiers |= NSControlKeyMask;
+ }
+
+ /* gdk/quartz maps Command to Meta (XXX check this - it may move to SUPER at some point) */
+
+ if (key->accel_mods & GDK_META_MASK) {
+ modifiers |= NSCommandKeyMask;
+ }
+ }
+
+ [cocoa_item setKeyEquivalentModifierMask:modifiers];
+ return;
+ }
+ }
+
+ /* otherwise, clear the menu shortcut */
+ [cocoa_item setKeyEquivalent:@""];
+}
+
+static void
+cocoa_menu_item_accel_changed (GtkAccelGroup *accel_group,
+ guint keyval,
+ GdkModifierType modifier,
+ GClosure *accel_closure,
+ GtkWidget *widget)
+{
+ GNSMenuItem *cocoa_item = cocoa_menu_item_get (widget);
+ GtkWidget *label;
+
+ get_menu_label_text (widget, &label);
+
+ if (GTK_IS_ACCEL_LABEL (label) &&
+ GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
+ cocoa_menu_item_update_accelerator (cocoa_item, widget);
+}
+
+static void
+cocoa_menu_item_update_accel_closure (GNSMenuItem *cocoa_item,
+ GtkWidget *widget)
+{
+ GtkAccelGroup *group;
+ GtkWidget *label;
+
+ get_menu_label_text (widget, &label);
+
+ if (cocoa_item->accel_closure)
+ {
+ group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
+
+ g_signal_handlers_disconnect_by_func (group,
+ (void*) cocoa_menu_item_accel_changed,
+ widget);
+
+ g_closure_unref (cocoa_item->accel_closure);
+ cocoa_item->accel_closure = NULL;
+ }
+
+ if (GTK_IS_ACCEL_LABEL (label)) {
+ cocoa_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
+ }
+
+ if (cocoa_item->accel_closure)
+ {
+ g_closure_ref (cocoa_item->accel_closure);
+
+ group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
+
+ g_signal_connect_object (group, "accel-changed",
+ G_CALLBACK (cocoa_menu_item_accel_changed),
+ widget, (GConnectFlags) 0);
+ }
+
+ cocoa_menu_item_update_accelerator (cocoa_item, widget);
+}
+
+static void
+cocoa_menu_item_notify_label (GObject *object,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ GNSMenuItem *cocoa_item = cocoa_menu_item_get (GTK_WIDGET (object));
+
+ if (!strcmp (pspec->name, "label"))
+ {
+ cocoa_menu_item_update_label (cocoa_item,
+ GTK_WIDGET (object));
+ }
+ else if (!strcmp (pspec->name, "accel-closure"))
+ {
+ cocoa_menu_item_update_accel_closure (cocoa_item,
+ GTK_WIDGET (object));
+ }
+}
+
+static void
+cocoa_menu_item_notify (GObject *object,
+ GParamSpec *pspec,
+ NSMenuItem *cocoa_item)
+{
+ if (!strcmp (pspec->name, "sensitive") ||
+ !strcmp (pspec->name, "visible"))
+ {
+ cocoa_menu_item_update_state (cocoa_item, GTK_WIDGET (object));
+ }
+ else if (!strcmp (pspec->name, "active"))
+ {
+ cocoa_menu_item_update_active (cocoa_item, GTK_WIDGET (object));
+ }
+ else if (!strcmp (pspec->name, "submenu"))
+ {
+ cocoa_menu_item_update_submenu (cocoa_item, GTK_WIDGET (object));
+ }
+}
+
+static void
+cocoa_menu_item_connect (GtkWidget* menu_item,
+ GNSMenuItem* cocoa_item,
+ GtkWidget *label)
+{
+ GNSMenuItem* old_item = cocoa_menu_item_get (menu_item);
+
+ [cocoa_item retain];
+
+ if (cocoa_menu_item_quark == 0)
+ cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
+
+ g_object_set_qdata_full (G_OBJECT (menu_item), cocoa_menu_item_quark,
+ cocoa_item,
+ (GDestroyNotify) cocoa_menu_item_free);
+
+ if (!old_item) {
+
+ g_signal_connect (menu_item, "notify",
+ G_CALLBACK (cocoa_menu_item_notify),
+ cocoa_item);
+
+ if (label)
+ g_signal_connect_swapped (label, "notify::label",
+ G_CALLBACK (cocoa_menu_item_notify_label),
+ menu_item);
+ }
+}
+
+static void
+add_menu_item (NSMenu* cocoa_menu, GtkWidget* menu_item, int index)
+{
+ GtkWidget* label = NULL;
+ GNSMenuItem *cocoa_item;
+
+ DEBUG ("add %s to menu %s separator ? %d\n", get_menu_label_text (menu_item, NULL),
+ [[cocoa_menu title] cStringUsingEncoding:NSUTF8StringEncoding],
+ GTK_IS_SEPARATOR_MENU_ITEM(menu_item));
+
+ cocoa_item = cocoa_menu_item_get (menu_item);
+
+ if (cocoa_item)
+ return;
+
+ if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item)) {
+ cocoa_item = [NSMenuItem separatorItem];
+ DEBUG ("\ta separator\n");
+ } else {
+
+ if (!GTK_WIDGET_VISIBLE (menu_item)) {
+ DEBUG ("\tnot visible\n");
+ return;
+ }
+
+ const gchar* label_text = get_menu_label_text (menu_item, &label);
+
+ if (label_text)
+ cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]
+ andGtkWidget:(GtkMenuItem*)menu_item];
+ else
+ cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:@"" andGtkWidget:(GtkMenuItem*)menu_item];
+ DEBUG ("\tan item\n");
+ }
+
+ /* connect GtkMenuItem and NSMenuItem so that we can notice changes to accel/label/submenu etc. */
+ cocoa_menu_item_connect (menu_item, (GNSMenuItem*) cocoa_item, label);
+
+ [ cocoa_item setEnabled:YES];
+ if (index >= 0)
+ [ cocoa_menu insertItem:cocoa_item atIndex:index];
+ else
+ [ cocoa_menu addItem:cocoa_item];
+
+ if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
+ [cocoa_item setState:NSOffState];
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4
+ if (!GTK_WIDGET_VISIBLE (menu_item))
+ [cocoa_item setHidden:YES];
+#endif
+
+ if (GTK_IS_CHECK_MENU_ITEM (menu_item))
+ cocoa_menu_item_update_active (cocoa_item, menu_item);
+
+ if (!GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
+ cocoa_menu_item_update_accel_closure (cocoa_item, menu_item);
+
+ if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
+ cocoa_menu_item_update_submenu (cocoa_item, menu_item);
+
+ [ cocoa_item release];
+}
+
+static void
+push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
+ NSMenu* cocoa_menu,
+ gboolean toplevel,
+ gboolean debug)
+{
+ GList *children;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
+
+ for (l = children; l; l = l->next)
+ {
+ GtkWidget *menu_item = (GtkWidget*) l->data;
+
+ if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
+ continue;
+
+ if (g_object_get_data (G_OBJECT (menu_item), "gtk-empty-menu-item"))
+ continue;
+
+ add_menu_item (cocoa_menu, menu_item, -1);
+ }
+
+ g_list_free (children);
+}
+
+
+static gulong emission_hook_id = 0;
+
+static gboolean
+parent_set_emission_hook (GSignalInvocationHint *ihint,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer data)
+{
+ GtkWidget *instance = (GtkWidget*) g_value_get_object (param_values);
+
+ if (GTK_IS_MENU_ITEM (instance))
+ {
+ GtkWidget *previous_parent = (GtkWidget*) g_value_get_object (param_values + 1);
+ GtkWidget *menu_shell = NULL;
+
+ if (GTK_IS_MENU_SHELL (previous_parent))
+ {
+ menu_shell = previous_parent;
+ }
+ else if (GTK_IS_MENU_SHELL (instance->parent))
+ {
+ menu_shell = instance->parent;
+ }
+
+ if (menu_shell)
+ {
+ NSMenu *cocoa_menu = cocoa_menu_get (menu_shell);
+
+ if (cocoa_menu)
+ {
+ push_menu_shell_to_nsmenu (GTK_MENU_SHELL (menu_shell),
+ cocoa_menu,
+ cocoa_menu == (NSMenu*) data,
+ FALSE);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+parent_set_emission_hook_remove (GtkWidget *widget,
+ gpointer data)
+{
+ g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
+ GTK_TYPE_WIDGET),
+ emission_hook_id);
+}
+
+/* Building "standard" Cocoa/OS X menus */
+
+#warning You can safely ignore the next warning about a duplicate interface definition
+@interface NSApplication(NSWindowsMenu)
+ - (void)setAppleMenu:(NSMenu *)aMenu;
+@end
+
+static NSMenu* _main_menubar = 0;
+static NSMenu* _window_menu = 0;
+static NSMenu* _app_menu = 0;
+
+static int
+add_to_menubar (NSMenu *menu)
+{
+ NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
+ action:nil keyEquivalent:@""];
+ [dummyItem setSubmenu:menu];
+ [_main_menubar addItem:dummyItem];
+ [dummyItem release];
+ return 0;
+}
+
+static int
+add_to_app_menu (NSMenu *menu)
+{
+ NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
+ action:nil keyEquivalent:@""];
+ [dummyItem setSubmenu:menu];
+ [_app_menu addItem:dummyItem];
+ [dummyItem release];
+ return 0;
+}
+
+static int
+add_to_window_menu (NSMenu *menu)
+{
+ NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
+ action:nil keyEquivalent:@""];
+ [dummyItem setSubmenu:menu];
+ [_window_menu addItem:dummyItem];
+ [dummyItem release];
+ return 0;
+}
+
+static int
+create_apple_menu ()
+{
+ NSMenuItem *menuitem;
+ // Create the application (Apple) menu.
+ _app_menu = [[NSMenu alloc] initWithTitle: @"Apple Menu"];
+
+ NSMenu *menuServices = [[NSMenu alloc] initWithTitle: @"Services"];
+ [NSApp setServicesMenu:menuServices];
+
+ [_app_menu addItem: [NSMenuItem separatorItem]];
+ menuitem = [[NSMenuItem alloc] initWithTitle: @"Services"
+ action:nil keyEquivalent:@""];
+ [menuitem setSubmenu:menuServices];
+ [_app_menu addItem: menuitem];
+ [menuitem release];
+ [_app_menu addItem: [NSMenuItem separatorItem]];
+ menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide"
+ action:@selector(hide:) keyEquivalent:@""];
+ [menuitem setTarget: NSApp];
+ [_app_menu addItem: menuitem];
+ [menuitem release];
+ menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
+ action:@selector(hideOtherApplications:) keyEquivalent:@""];
+ [menuitem setTarget: NSApp];
+ [_app_menu addItem: menuitem];
+ [menuitem release];
+ menuitem = [[NSMenuItem alloc] initWithTitle:@"Show All"
+ action:@selector(unhideAllApplications:) keyEquivalent:@""];
+ [menuitem setTarget: NSApp];
+ [_app_menu addItem: menuitem];
+ [menuitem release];
+ [_app_menu addItem: [NSMenuItem separatorItem]];
+ menuitem = [[NSMenuItem alloc] initWithTitle:@"Quit"
+ action:@selector(terminate:) keyEquivalent:@"q"];
+ [menuitem setTarget: NSApp];
+ [_app_menu addItem: menuitem];
+ [menuitem release];
+
+ [NSApp setAppleMenu:_app_menu];
+ add_to_menubar (_app_menu);
+
+ return 0;
+}
+
+static int
+create_window_menu ()
+{
+ _window_menu = [[NSMenu alloc] initWithTitle: @"Window"];
+
+ [_window_menu addItemWithTitle:@"Minimize"
+ action:@selector(performMiniaturize:) keyEquivalent:@""];
+ [_window_menu addItem: [NSMenuItem separatorItem]];
+ [_window_menu addItemWithTitle:@"Bring All to Front"
+ action:@selector(arrangeInFront:) keyEquivalent:@""];
+
+ [NSApp setWindowsMenu:_window_menu];
+ add_to_menubar(_window_menu);
+
+ return 0;
+}
+
+/*
+ * public functions
+ */
+
+extern "C" void
+gtk_application_set_menu_bar (GtkMenuShell *menu_shell)
+{
+ NSMenu* cocoa_menubar;
+
+ g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+
+ if (cocoa_menu_quark == 0)
+ cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
+
+ if (cocoa_menu_item_quark == 0)
+ cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
+
+ cocoa_menubar = [ [ NSApplication sharedApplication] mainMenu];
+
+ /* turn off auto-enabling for the menu - its silly and slow and
+ doesn't really make sense for a Gtk/Cocoa hybrid menu.
+ */
+
+ [cocoa_menubar setAutoenablesItems:NO];
+
+ emission_hook_id =
+ g_signal_add_emission_hook (g_signal_lookup ("parent-set",
+ GTK_TYPE_WIDGET),
+ 0,
+ parent_set_emission_hook,
+ cocoa_menubar, NULL);
+
+
+ g_signal_connect (menu_shell, "destroy",
+ G_CALLBACK (parent_set_emission_hook_remove),
+ NULL);
+
+ push_menu_shell_to_nsmenu (menu_shell, cocoa_menubar, TRUE, FALSE);
+}
+
+extern "C" void
+gtk_application_add_app_menu_item (GtkApplicationMenuGroup *group,
+ GtkMenuItem *menu_item)
+{
+ // we know that the application menu is always the submenu of the first item in the main menu
+ NSMenu* mainMenu;
+ NSMenu *appMenu;
+ NSMenuItem *firstItem;
+ GList *list;
+ gint index = 0;
+
+ mainMenu = [NSApp mainMenu];
+ firstItem = [ mainMenu itemAtIndex:0];
+ appMenu = [ firstItem submenu ];
+
+ g_return_if_fail (group != NULL);
+ g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
+
+ for (list = _gtk_application_menu_groups; list; list = g_list_next (list))
+ {
+ GtkApplicationMenuGroup *list_group = (GtkApplicationMenuGroup*) list->data;
+
+ index += g_list_length (list_group->items);
+
+ /* adjust index for the separator between groups, but not
+ * before the first group
+ */
+ if (list_group->items && list->prev)
+ index++;
+
+ if (group == list_group)
+ {
+ /* add a separator before adding the first item, but not
+ * for the first group
+ */
+
+ if (!group->items && list->prev)
+ {
+ [appMenu insertItem:[NSMenuItem separatorItem] atIndex:index+1];
+ index++;
+ }
+ DEBUG ("Add to APP menu bar %s\n", get_menu_label_text (GTK_WIDGET(menu_item), NULL));
+ add_menu_item (appMenu, GTK_WIDGET(menu_item), index+1);
+
+ group->items = g_list_append (group->items, menu_item);
+ gtk_widget_hide (GTK_WIDGET (menu_item));
+ return;
+ }
+ }
+
+ if (!list)
+ g_warning ("%s: app menu group %p does not exist",
+ G_STRFUNC, group);
+}
+
+/* application delegate, currently in C++ */
+
+#include <gtkmm2ext/application.h>
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ namespace Application {
+ sigc::signal<void,bool> ActivationChanged;
+ sigc::signal<void,const Glib::ustring&> ShouldLoad;
+ sigc::signal<void> ShouldQuit;
+ }
+}
+
+@interface GtkApplicationNotificationObject : NSObject {}
+- (GtkApplicationNotificationObject*) init;
+@end
+
+@implementation GtkApplicationNotificationObject
+- (GtkApplicationNotificationObject*) init
+{
+ self = [ super init ];
+
+ if (self) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(appDidBecomeActive:)
+ name:NSApplicationDidBecomeActiveNotification
+ object:[NSApplication sharedApplication]];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(appDidBecomeInactive:)
+ name:NSApplicationWillResignActiveNotification
+ object:[NSApplication sharedApplication]];
+ }
+
+ return self;
+}
+
+- (void)appDidBecomeActive:(NSNotification *)notification
+{
+ Gtkmm2ext::Application::instance()->ActivationChanged (true);
+}
+
+- (void)appDidBecomeInactive:(NSNotification *)notification
+{
+ Gtkmm2ext::Application::instance()->ActivationChanged (false);
+}
+
+@end
+
+@interface GtkApplicationDelegate : NSObject {}
+@end
+
+@implementation GtkApplicationDelegate
+-(BOOL) application:(NSApplication*) theApplication openFile:(NSString*) file
+{
+ Glib::ustring utf8_path ([file UTF8String]);
+ Gtkmm2ext::Application::instance()->ShouldLoad (utf8_path);
+ return 1;
+}
+- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
+{
+ Gtkmm2ext::Application::instance()->ShouldQuit ();
+ return NSTerminateCancel;
+}
+@end
+
+
+/* Basic setup */
+
+extern "C" int
+gtk_application_init ()
+{
+ _main_menubar = [[NSMenu alloc] initWithTitle: @""];
+
+ if (!_main_menubar)
+ return -1;
+
+ [NSApp setMainMenu: _main_menubar];
+ create_apple_menu ();
+ // create_window_menu ();
+
+ /* this will stick around for ever ... is that OK ? */
+
+ [ [GtkApplicationNotificationObject alloc] init];
+ [ NSApp setDelegate: [GtkApplicationDelegate new]];
+
+ return 0;
+}
+
+extern "C" void
+gtk_application_ready ()
+{
+ [ NSApp finishLaunching ];
+}
+
+extern "C" void
+gtk_application_cleanup()
+{
+ if (_window_menu)
+ [ _window_menu release ];
+ if (_app_menu)
+ [ _app_menu release ];
+ if (_main_menubar)
+ [ _main_menubar release ];
+}