Merge branch 'master' into saveas
[ardour.git] / libs / gtkmm2ext / gtkapplication_quartz.mm
1 /* GTK+ application-level integration for the Mac OS X/Cocoa 
2  *
3  * Copyright (C) 2007 Pioneer Research Center USA, Inc.
4  * Copyright (C) 2007 Imendio AB
5  * Copyright (C) 2009 Paul Davis
6  *
7  * This is a reimplementation in Cocoa of the sync-menu.c concept
8  * from Imendio, although without the "set quit menu" API since
9  * a Cocoa app needs to handle termination anyway.
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; version 2.1
14  * of the License.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the
23  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24  * Boston, MA 02111-1307, USA.
25  */
26
27 #include <sigc++/signal.h>
28 #include <sigc++/slot.h>
29
30 #include <string.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtkmm2ext/gtkapplication.h>
34 #include <gtkmm2ext/gtkapplication-private.h>
35
36 #import <AppKit/NSMenu.h>
37 #import <AppKit/NSMenuItem.h>
38 #import <AppKit/NSCell.h>
39 #import <AppKit/NSEvent.h>
40 #import <AppKit/NSApplication.h>
41 #import <Foundation/NSString.h>
42 #import <Foundation/NSNotification.h>
43
44 #define UNUSED_PARAMETER(a) (void) (a)
45
46 // #define DEBUG(format, ...) g_printerr ("%s: " format, G_STRFUNC, ## __VA_ARGS__)
47 #define DEBUG(format, ...)
48
49 /* TODO
50  *
51  * - Sync adding/removing/reordering items
52  * - Create on demand? (can this be done with gtk+? ie fill in menu
53      items when the menu is opened)
54  * - Figure out what to do per app/window...
55  *
56  */
57
58 static gint _exiting = 0;
59
60 static guint
61 gdk_quartz_keyval_to_ns_keyval (guint keyval)
62 {
63         switch (keyval) {
64         case GDK_BackSpace:
65                 return NSBackspaceCharacter;
66         case GDK_Delete:
67                 return NSDeleteFunctionKey;
68         case GDK_Pause:
69                 return NSPauseFunctionKey;
70         case GDK_Scroll_Lock:
71                 return NSScrollLockFunctionKey;
72         case GDK_Sys_Req:
73                 return NSSysReqFunctionKey;
74         case GDK_Home:
75                 return NSHomeFunctionKey;
76         case GDK_Left:
77         case GDK_leftarrow:
78                 return NSLeftArrowFunctionKey;
79         case GDK_Up:
80         case GDK_uparrow:
81                 return NSUpArrowFunctionKey;
82         case GDK_Right:
83         case GDK_rightarrow:
84                 return NSRightArrowFunctionKey;
85         case GDK_Down:
86         case GDK_downarrow:
87                 return NSDownArrowFunctionKey;
88         case GDK_Page_Up:
89                 return NSPageUpFunctionKey;
90         case GDK_Page_Down:
91                 return NSPageDownFunctionKey;
92         case GDK_End:
93                 return NSEndFunctionKey;
94         case GDK_Begin:
95                 return NSBeginFunctionKey;
96         case GDK_Select:
97                 return NSSelectFunctionKey;
98         case GDK_Print:
99                 return NSPrintFunctionKey;
100         case GDK_Execute:
101                 return NSExecuteFunctionKey;
102         case GDK_Insert:
103                 return NSInsertFunctionKey;
104         case GDK_Undo:
105                 return NSUndoFunctionKey;
106         case GDK_Redo:
107                 return NSRedoFunctionKey;
108         case GDK_Menu:
109                 return NSMenuFunctionKey;
110         case GDK_Find:
111                 return NSFindFunctionKey;
112         case GDK_Help:
113                 return NSHelpFunctionKey;
114         case GDK_Break:
115                 return NSBreakFunctionKey;
116         case GDK_Mode_switch:
117                 return NSModeSwitchFunctionKey;
118         case GDK_F1:
119                 return NSF1FunctionKey;
120         case GDK_F2:
121                 return NSF2FunctionKey;
122         case GDK_F3:
123                 return NSF3FunctionKey;
124         case GDK_F4:
125                 return NSF4FunctionKey;
126         case GDK_F5:
127                 return NSF5FunctionKey;
128         case GDK_F6:
129                 return NSF6FunctionKey;
130         case GDK_F7:
131                 return NSF7FunctionKey;
132         case GDK_F8:
133                 return NSF8FunctionKey;
134         case GDK_F9:
135                 return NSF9FunctionKey;
136         case GDK_F10:
137                 return NSF10FunctionKey;
138         case GDK_F11:
139                 return NSF11FunctionKey;
140         case GDK_F12:
141                 return NSF12FunctionKey;
142         case GDK_F13:
143                 return NSF13FunctionKey;
144         case GDK_F14:
145                 return NSF14FunctionKey;
146         case GDK_F15:
147                 return NSF15FunctionKey;
148         case GDK_F16:
149                 return NSF16FunctionKey;
150         case GDK_F17:
151                 return NSF17FunctionKey;
152         case GDK_F18:
153                 return NSF18FunctionKey;
154         case GDK_F19:
155                 return NSF19FunctionKey;
156         case GDK_F20:
157                 return NSF20FunctionKey;
158         case GDK_F21:
159                 return NSF21FunctionKey;
160         case GDK_F22:
161                 return NSF22FunctionKey;
162         case GDK_F23:
163                 return NSF23FunctionKey;
164         case GDK_F24:
165                 return NSF24FunctionKey;
166         case GDK_F25:
167                 return NSF25FunctionKey;
168         case GDK_F26:
169                 return NSF26FunctionKey;
170         case GDK_F27:
171                 return NSF27FunctionKey;
172         case GDK_F28:
173                 return NSF28FunctionKey;
174         case GDK_F29:
175                 return NSF29FunctionKey;
176         case GDK_F30:
177                 return NSF30FunctionKey;
178         case GDK_F31:
179                 return NSF31FunctionKey;
180         case GDK_F32:
181                 return NSF32FunctionKey;
182         case GDK_F33:
183                 return NSF33FunctionKey;
184         case GDK_F34:
185                 return NSF34FunctionKey;
186         case GDK_F35:
187                 return NSF35FunctionKey;
188         default:
189                 break;
190         }
191
192         return 0;
193 }
194
195 static gboolean
196 keyval_is_keypad (guint keyval)
197 {
198         switch (keyval) {
199         case GDK_KP_F1:
200         case GDK_KP_F2:
201         case GDK_KP_F3:
202         case GDK_KP_F4:
203         case GDK_KP_Home:
204         case GDK_KP_Left:
205         case GDK_KP_Up:
206         case GDK_KP_Right:
207         case GDK_KP_Down:
208         case GDK_KP_Page_Up:
209         case GDK_KP_Page_Down:
210         case GDK_KP_End:
211         case GDK_KP_Begin:
212         case GDK_KP_Insert:
213         case GDK_KP_Delete:
214         case GDK_KP_Equal:
215         case GDK_KP_Multiply:
216         case GDK_KP_Add:
217         case GDK_KP_Separator:
218         case GDK_KP_Subtract:
219         case GDK_KP_Decimal:
220         case GDK_KP_Divide:
221         case GDK_KP_0:
222         case GDK_KP_1:
223         case GDK_KP_2:
224         case GDK_KP_3:
225         case GDK_KP_4:
226         case GDK_KP_5:
227         case GDK_KP_6:
228         case GDK_KP_7:
229         case GDK_KP_8:
230         case GDK_KP_9:
231                 return TRUE;
232                 break;
233         default:
234                 break;
235         }
236         return FALSE;
237 }
238
239 static guint
240 keyval_keypad_nonkeypad_equivalent (guint keyval)
241 {
242         switch (keyval) {
243         case GDK_KP_F1:
244                 return GDK_F1;
245         case GDK_KP_F2:
246                 return GDK_F2;
247         case GDK_KP_F3:
248                 return GDK_F3;
249         case GDK_KP_F4:
250                 return GDK_F4;
251         case GDK_KP_Home:
252                 return GDK_Home;
253         case GDK_KP_Left:
254                 return GDK_Left;
255         case GDK_KP_Up:
256                 return GDK_Up;
257         case GDK_KP_Right:
258                 return GDK_Right;
259         case GDK_KP_Down:
260                 return GDK_Down;
261         case GDK_KP_Page_Up:
262                 return GDK_Page_Up;
263         case GDK_KP_Page_Down:
264                 return GDK_Page_Down;
265         case GDK_KP_End:
266                 return GDK_End;
267         case GDK_KP_Begin:
268                 return GDK_Begin;
269         case GDK_KP_Insert:
270                 return GDK_Insert;
271         case GDK_KP_Delete:
272                 return GDK_Delete;
273         case GDK_KP_Equal:
274                 return GDK_equal;
275         case GDK_KP_Multiply:
276                 return GDK_asterisk;
277         case GDK_KP_Add:
278                 return GDK_plus;
279         case GDK_KP_Subtract:
280                 return GDK_minus;
281         case GDK_KP_Decimal:
282                 return GDK_period;
283         case GDK_KP_Divide:
284                 return GDK_slash;
285         case GDK_KP_0:
286                 return GDK_0;
287         case GDK_KP_1:
288                 return GDK_1;
289         case GDK_KP_2:
290                 return GDK_2;
291         case GDK_KP_3:
292                 return GDK_3;
293         case GDK_KP_4:
294                 return GDK_4;
295         case GDK_KP_5:
296                 return GDK_5;
297         case GDK_KP_6:
298                 return GDK_6;
299         case GDK_KP_7:
300                 return GDK_7;
301         case GDK_KP_8:
302                 return GDK_8;
303         case GDK_KP_9:
304                 return GDK_9;
305         default:
306                 break;
307         }
308
309         return GDK_VoidSymbol;
310 }
311
312 static const gchar* 
313 gdk_quartz_keyval_to_string (guint keyval)
314 {
315         switch (keyval) {
316         case GDK_space:
317                 return " ";
318         case GDK_exclam:
319                 return "!";
320         case GDK_quotedbl:
321                 return "\"";
322         case GDK_numbersign:
323                 return "#";
324         case GDK_dollar:
325                 return "$";
326         case GDK_percent:
327                 return "%";
328         case GDK_ampersand:
329                 return "&";
330         case GDK_apostrophe:
331                 return "'";
332         case GDK_parenleft:
333                 return "(";
334         case GDK_parenright:
335                 return ")";
336         case GDK_asterisk:
337                 return "*";
338         case GDK_plus:
339                 return "+";
340         case GDK_comma:
341                 return ",";
342         case GDK_minus:
343                 return "-";
344         case GDK_period:
345                 return ".";
346         case GDK_slash:
347                 return "/";
348         case GDK_0:
349                 return "0";
350         case GDK_1:
351                 return "1";
352         case GDK_2:
353                 return "2";
354         case GDK_3:
355                 return "3";
356         case GDK_4:
357                 return "4";
358         case GDK_5:
359                 return "5";
360         case GDK_6:
361                 return "6";
362         case GDK_7:
363                 return "7";
364         case GDK_8:
365                 return "8";
366         case GDK_9:
367                 return "9";
368         case GDK_colon:
369                 return ":";
370         case GDK_semicolon:
371                 return ";";
372         case GDK_less:
373                 return "<";
374         case GDK_equal:
375                 return "=";
376         case GDK_greater:
377                 return ">";
378         case GDK_question:
379                 return "?";
380         case GDK_at:
381                 return "@";
382         case GDK_A:
383         case GDK_a:
384                 return "a";
385         case GDK_B:
386         case GDK_b:
387                 return "b";
388         case GDK_C:
389         case GDK_c:
390                 return "c";
391         case GDK_D:
392         case GDK_d:
393                 return "d";
394         case GDK_E:
395         case GDK_e:
396                 return "e";
397         case GDK_F:
398         case GDK_f:
399                 return "f";
400         case GDK_G:
401         case GDK_g:
402                 return "g";
403         case GDK_H:
404         case GDK_h:
405                 return "h";
406         case GDK_I:
407         case GDK_i:
408                 return "i";
409         case GDK_J:
410         case GDK_j:
411                 return "j";
412         case GDK_K:
413         case GDK_k:
414                 return "k";
415         case GDK_L:
416         case GDK_l:
417                 return "l";
418         case GDK_M:
419         case GDK_m:
420                 return "m";
421         case GDK_N:
422         case GDK_n:
423                 return "n";
424         case GDK_O:
425         case GDK_o:
426                 return "o";
427         case GDK_P:
428         case GDK_p:
429                 return "p";
430         case GDK_Q:
431         case GDK_q:
432                 return "q";
433         case GDK_R:
434         case GDK_r:
435                 return "r";
436         case GDK_S:
437         case GDK_s:
438                 return "s";
439         case GDK_T:
440         case GDK_t:
441                 return "t";
442         case GDK_U:
443         case GDK_u:
444                 return "u";
445         case GDK_V:
446         case GDK_v:
447                 return "v";
448         case GDK_W:
449         case GDK_w:
450                 return "w";
451         case GDK_X:
452         case GDK_x:
453                 return "x";
454         case GDK_Y:
455         case GDK_y:
456                 return "y";
457         case GDK_Z:
458         case GDK_z:
459                 return "z";
460         case GDK_bracketleft:
461                 return "[";
462         case GDK_backslash:
463                 return "\\";
464         case GDK_bracketright:
465                 return "]";
466         case GDK_asciicircum:
467                 return "^";
468         case GDK_underscore:
469                 return "_";
470         case GDK_grave:
471                 return "`";
472         case GDK_braceleft:
473                 return "{";
474         case GDK_bar:
475                 return "|";
476         case GDK_braceright:
477                 return "}";
478         case GDK_asciitilde:
479                 return "~";
480         default:
481                 break;
482         }
483         return NULL;
484 };
485
486 static gboolean
487 keyval_is_uppercase (guint keyval)
488 {
489         switch (keyval) {
490         case GDK_A:
491         case GDK_B:
492         case GDK_C:
493         case GDK_D:
494         case GDK_E:
495         case GDK_F:
496         case GDK_G:
497         case GDK_H:
498         case GDK_I:
499         case GDK_J:
500         case GDK_K:
501         case GDK_L:
502         case GDK_M:
503         case GDK_N:
504         case GDK_O:
505         case GDK_P:
506         case GDK_Q:
507         case GDK_R:
508         case GDK_S:
509         case GDK_T:
510         case GDK_U:
511         case GDK_V:
512         case GDK_W:
513         case GDK_X:
514         case GDK_Y:
515         case GDK_Z:
516                 return TRUE;
517         default:
518                 return FALSE;
519         }
520         return FALSE;
521 }
522
523 /* gtk/osx has a problem in that mac main menu events
524    are handled using an "internal" event handling system that 
525    doesn't pass things back to the glib/gtk main loop. if we call
526    gtk_main_iteration() block while in a menu event handler, then
527    glib gets confused and thinks there are two threads running
528    g_main_poll_func(). apps call call gdk_quartz_in_menu_event_handler()
529    if they need to check this.
530  */
531
532 static int _in_menu_event_handler = 0;
533
534 int 
535 gdk_quartz_in_menu_event_handler ()
536 {
537         return _in_menu_event_handler;
538 }
539
540 static gboolean
541 idle_call_activate (gpointer data)
542 {
543         gtk_menu_item_activate ((GtkMenuItem*) data);
544         return FALSE;
545 }
546
547 @interface GNSMenuItem : NSMenuItem
548 {
549     @public
550         GtkMenuItem* gtk_menu_item;
551         GClosure      *accel_closure;
552 }
553 - (id) initWithTitle:(NSString*) title andGtkWidget:(GtkMenuItem*) w;
554 - (void) activate:(id) sender;
555 @end
556
557 @implementation GNSMenuItem
558 - (id) initWithTitle:(NSString*) title andGtkWidget:(GtkMenuItem*) w
559 {
560         /* All menu items have the action "activate", which will be handled by this child class
561          */
562
563         self = [ super initWithTitle:title action:@selector(activate:) keyEquivalent:@"" ];
564
565         if (self) {
566                 /* make this handle its own action */
567                 [ self setTarget:self ];
568                 gtk_menu_item = w;
569                 accel_closure = 0;
570         }
571         return self;
572 }
573 - (void) activate:(id) sender
574 {
575         UNUSED_PARAMETER(sender);
576         g_idle_add (idle_call_activate, gtk_menu_item);
577 }
578 @end
579
580 static void push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
581                                        NSMenu       *menu,
582                                        gboolean      toplevel,
583                                        gboolean      debug);
584
585 /*
586  * utility functions
587  */
588
589 static GtkWidget *
590 find_menu_label (GtkWidget *widget)
591 {
592   GtkWidget *label = NULL;
593
594   if (GTK_IS_LABEL (widget))
595     return widget;
596
597   if (GTK_IS_CONTAINER (widget))
598     {
599       GList *children;
600       GList *l;
601
602       children = gtk_container_get_children (GTK_CONTAINER (widget));
603
604       for (l = children; l; l = l->next)
605         {
606           label = find_menu_label ((GtkWidget*) l->data);
607           if (label)
608             break;
609         }
610
611       g_list_free (children);
612     }
613
614   return label;
615 }
616
617 static const gchar *
618 get_menu_label_text (GtkWidget  *menu_item,
619                      GtkWidget **label)
620 {
621   GtkWidget *my_label;
622
623   my_label = find_menu_label (menu_item);
624   if (label)
625     *label = my_label;
626
627   if (my_label)
628     return gtk_label_get_text (GTK_LABEL (my_label));
629
630   return NULL;
631 }
632
633 static gboolean
634 accel_find_func (GtkAccelKey * /*key*/,
635                  GClosure    *closure,
636                  gpointer     data)
637 {
638   return (GClosure *) data == closure;
639 }
640
641
642 /*
643  * CocoaMenu functions
644  */
645
646 static GQuark cocoa_menu_quark = 0;
647
648 static NSMenu *
649 cocoa_menu_get (GtkWidget *widget)
650 {
651   return (NSMenu*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_quark);
652 }
653
654 static void
655 cocoa_menu_free (gpointer *ptr)
656 {
657         NSMenu* menu = (NSMenu*) ptr;
658         [menu release];
659 }
660
661 static void
662 cocoa_menu_connect (GtkWidget *menu,
663                     NSMenu*    cocoa_menu)
664 {
665         [cocoa_menu retain];
666
667         if (cocoa_menu_quark == 0)
668                 cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
669         
670         g_object_set_qdata_full (G_OBJECT (menu), cocoa_menu_quark,
671                                  cocoa_menu,
672                                  (GDestroyNotify) cocoa_menu_free);
673 }
674
675 /*
676  * NSMenuItem functions
677  */
678
679 static GQuark cocoa_menu_item_quark = 0;
680 static void cocoa_menu_item_connect (GtkWidget*   menu_item,
681                                      GNSMenuItem* cocoa_menu_item,
682                                      GtkWidget     *label);
683
684 static void
685 cocoa_menu_item_free (gpointer *ptr)
686 {
687         GNSMenuItem* item = (GNSMenuItem*) ptr;
688         [item release];
689 }
690
691 static GNSMenuItem *
692 cocoa_menu_item_get (GtkWidget *widget)
693 {
694   return (GNSMenuItem*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_item_quark);
695 }
696
697 static void
698 cocoa_menu_item_update_state (NSMenuItem* cocoa_item,
699                               GtkWidget      *widget)
700 {
701   gboolean sensitive;
702   gboolean visible;
703
704   g_object_get (widget,
705                 "sensitive", &sensitive,
706                 "visible",   &visible,
707                 NULL);
708
709   if (!sensitive)
710           [cocoa_item setEnabled:NO];
711   else
712           [cocoa_item setEnabled:YES];
713
714 #if 0
715   // requires OS X 10.5 or later
716   if (!visible)
717           [cocoa_item setHidden:YES];
718   else
719           [cocoa_item setHidden:NO];
720 #endif
721 }
722
723 static void
724 cocoa_menu_item_update_active (NSMenuItem *cocoa_item,
725                                 GtkWidget  *widget)
726 {
727   gboolean active;
728
729   g_object_get (widget, "active", &active, NULL);
730
731   if (active) 
732     [cocoa_item setState:NSOnState];
733   else
734     [cocoa_item setState:NSOffState];
735 }
736
737 static void
738 cocoa_menu_item_update_submenu (NSMenuItem *cocoa_item,
739                                 GtkWidget      *widget)
740 {
741   GtkWidget *submenu;
742   
743   g_return_if_fail (cocoa_item != NULL);
744   g_return_if_fail (widget != NULL);
745
746   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
747
748   if (submenu)
749     {
750       GtkWidget* label = NULL;
751       const gchar *label_text;
752       NSMenu* cocoa_submenu;
753
754       label_text = get_menu_label_text (widget, &label);
755
756       /* create a new nsmenu to hold the GTK menu */
757
758       if (label_text) 
759               cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
760       else
761               cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:@""];
762
763       [cocoa_submenu setAutoenablesItems:NO];
764       cocoa_menu_connect (submenu, cocoa_submenu);
765
766       /* connect the new nsmenu to the passed-in item (which lives in
767          the parent nsmenu)
768          (Note: this will release any pre-existing version of this submenu)
769       */
770       [ cocoa_item setSubmenu:cocoa_submenu];
771
772       /* and push the GTK menu into the submenu */
773       push_menu_shell_to_nsmenu (GTK_MENU_SHELL (submenu), cocoa_submenu, FALSE, FALSE);
774
775       [ cocoa_submenu release ];
776     }
777 }
778
779 static void
780 cocoa_menu_item_update_label (NSMenuItem *cocoa_item,
781                                GtkWidget      *widget)
782 {
783   const gchar *label_text;
784
785   g_return_if_fail (cocoa_item != NULL);
786   g_return_if_fail (widget != NULL);
787
788   label_text = get_menu_label_text (widget, NULL);
789   if (label_text)
790           [cocoa_item setTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
791   else 
792           [cocoa_item setTitle:@""];
793 }
794
795 static void
796 cocoa_menu_item_update_accelerator (NSMenuItem *cocoa_item,
797                                      GtkWidget *widget)
798 {
799   GtkWidget *label;
800
801   g_return_if_fail (cocoa_item != NULL);
802   g_return_if_fail (widget != NULL);
803
804   /* important note: this function doesn't do anything to actually change
805      key handling. Its goal is to get Cocoa to display the correct
806      accelerator as part of a menu item. Actual accelerator handling
807      is still done by GTK, so this is more cosmetic than it may 
808      appear.
809   */
810
811   get_menu_label_text (widget, &label);  
812
813   if (GTK_IS_ACCEL_LABEL (label) &&
814             GTK_ACCEL_LABEL (label)->accel_closure)
815         {
816                 GtkAccelKey *key;
817                 
818                 key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
819                                             accel_find_func,
820                                   GTK_ACCEL_LABEL (label)->accel_closure);
821                 
822                 if (key            &&
823                     key->accel_key &&
824                     key->accel_flags & GTK_ACCEL_VISIBLE)
825                 {
826                         guint modifiers = 0; 
827                         const gchar* str = NULL;
828                         guint actual_key = key->accel_key; 
829                         
830                         if (keyval_is_keypad (actual_key)) {
831                                 if ((actual_key = keyval_keypad_nonkeypad_equivalent (actual_key)) == GDK_VoidSymbol) {
832                                         /* GDK_KP_Separator */
833                                         [cocoa_item setKeyEquivalent:@""];
834                                         return;
835                                 }
836                                 modifiers |= NSNumericPadKeyMask;
837                         }
838                         
839                         /* if we somehow got here with GDK_A ... GDK_Z rather than GDK_a ... GDK_z, then take note
840                            of that and make sure we use a shift modifier.
841                         */
842                         
843                         if (keyval_is_uppercase (actual_key)) {
844                                 modifiers |= NSShiftKeyMask;
845                         }
846                         
847                         str = gdk_quartz_keyval_to_string (actual_key);
848                         
849                         if (str) {
850                                 unichar ukey = str[0];
851                                 [cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
852                         } else {
853                                 unichar ukey = gdk_quartz_keyval_to_ns_keyval (actual_key);
854                                 if (ukey != 0) {
855                                         [cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
856                                 } else {
857                                         /* cannot map this key to Cocoa key equivalent */
858                                         [cocoa_item setKeyEquivalent:@""];
859                                         return;
860                                 }
861                         } 
862                         
863                         if (key->accel_mods || modifiers)
864                         {
865                                 if (key->accel_mods & GDK_SHIFT_MASK) {
866                                         modifiers |= NSShiftKeyMask;
867                                 }
868                                 
869                                 /* gdk/quartz maps Alt/Option to Mod1 */
870                                 
871                                 if (key->accel_mods & (GDK_MOD1_MASK)) {
872                                         modifiers |= NSAlternateKeyMask;
873                                 }
874                                 
875                                 if (key->accel_mods & GDK_CONTROL_MASK) {
876                                         modifiers |= NSControlKeyMask;
877                                 }
878                                 
879                                 /* gdk/quartz maps Command to Meta (XXX check this - it may move to SUPER at some point) */
880                                 
881                                 if (key->accel_mods & GDK_META_MASK) {
882                                         modifiers |= NSCommandKeyMask;
883                                 }
884                         }  
885                         
886                         [cocoa_item setKeyEquivalentModifierMask:modifiers];
887                         return;
888                 }
889         }
890
891         /*  otherwise, clear the menu shortcut  */
892         [cocoa_item setKeyEquivalent:@""];
893 }
894
895 static void
896 cocoa_menu_item_accel_changed (GtkAccelGroup*   /*accel_group*/,
897                                guint            /*keyval*/,
898                                 GdkModifierType /*modifier*/,
899                                 GClosure        *accel_closure,
900                                 GtkWidget       *widget)
901 {
902   GNSMenuItem *cocoa_item;
903   GtkWidget      *label;
904
905   if (_exiting) 
906     return;
907
908   cocoa_item = cocoa_menu_item_get (widget);
909   get_menu_label_text (widget, &label);
910
911   if (GTK_IS_ACCEL_LABEL (label) &&
912       GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
913     cocoa_menu_item_update_accelerator (cocoa_item, widget);
914 }
915
916 static void
917 cocoa_menu_item_update_accel_closure (GNSMenuItem *cocoa_item,
918                                       GtkWidget      *widget)
919 {
920   GtkAccelGroup *group;
921   GtkWidget     *label;
922
923   get_menu_label_text (widget, &label);
924
925   if (cocoa_item->accel_closure)
926     {
927       group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
928
929       g_signal_handlers_disconnect_by_func (group,
930                                             (void*) cocoa_menu_item_accel_changed,
931                                             widget);
932
933       g_closure_unref (cocoa_item->accel_closure);
934       cocoa_item->accel_closure = NULL;
935     }
936
937   if (GTK_IS_ACCEL_LABEL (label)) {
938     cocoa_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
939   }
940
941   if (cocoa_item->accel_closure)
942     {
943       g_closure_ref (cocoa_item->accel_closure);
944
945       group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
946
947       g_signal_connect_object (group, "accel-changed",
948                                G_CALLBACK (cocoa_menu_item_accel_changed),
949                                widget, (GConnectFlags) 0);
950     }
951
952   cocoa_menu_item_update_accelerator (cocoa_item, widget);
953 }
954
955 static void
956 cocoa_menu_item_notify_label (GObject    *object,
957                               GParamSpec *pspec,
958                               gpointer)
959 {
960   GNSMenuItem *cocoa_item;
961
962   if (_exiting) 
963     return;
964
965   cocoa_item = cocoa_menu_item_get (GTK_WIDGET (object));
966
967   if (!strcmp (pspec->name, "label"))
968     {
969       cocoa_menu_item_update_label (cocoa_item,
970                                      GTK_WIDGET (object));
971     }
972   else if (!strcmp (pspec->name, "accel-closure"))
973     {
974       cocoa_menu_item_update_accel_closure (cocoa_item,
975                                              GTK_WIDGET (object));
976     }
977 }
978
979 static void
980 cocoa_menu_item_notify (GObject        *object,
981                         GParamSpec     *pspec,
982                         NSMenuItem *cocoa_item)
983 {
984   if (_exiting)
985     return;
986
987   if (!strcmp (pspec->name, "sensitive") ||
988       !strcmp (pspec->name, "visible"))
989     {
990       cocoa_menu_item_update_state (cocoa_item, GTK_WIDGET (object));
991     }
992   else if (!strcmp (pspec->name, "active"))
993     {
994       cocoa_menu_item_update_active (cocoa_item, GTK_WIDGET (object));
995     }
996   else if (!strcmp (pspec->name, "submenu"))
997     {
998       cocoa_menu_item_update_submenu (cocoa_item, GTK_WIDGET (object));
999     }
1000 }
1001
1002 static void
1003 cocoa_menu_item_connect (GtkWidget*   menu_item,
1004                          GNSMenuItem* cocoa_item,
1005                          GtkWidget     *label)
1006 {
1007         GNSMenuItem* old_item = cocoa_menu_item_get (menu_item);
1008
1009         [cocoa_item retain];
1010
1011         if (cocoa_menu_item_quark == 0)
1012                 cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
1013
1014         g_object_set_qdata_full (G_OBJECT (menu_item), cocoa_menu_item_quark,
1015                                  cocoa_item,
1016                                  (GDestroyNotify) cocoa_menu_item_free);
1017         
1018         if (!old_item) {
1019
1020                 g_signal_connect (menu_item, "notify",
1021                                   G_CALLBACK (cocoa_menu_item_notify),
1022                                   cocoa_item);
1023                 
1024                 if (label)
1025                         g_signal_connect_swapped (label, "notify::label",
1026                                                   G_CALLBACK (cocoa_menu_item_notify_label),
1027                                                   menu_item);
1028         }
1029 }
1030
1031 static void
1032 add_menu_item (NSMenu* cocoa_menu, GtkWidget* menu_item, int index)
1033 {
1034         GtkWidget* label      = NULL;
1035         GNSMenuItem *cocoa_item;
1036         
1037         DEBUG ("add %s to menu %s separator ? %d\n", get_menu_label_text (menu_item, NULL), 
1038                [[cocoa_menu title] cStringUsingEncoding:NSUTF8StringEncoding],
1039                GTK_IS_SEPARATOR_MENU_ITEM(menu_item));
1040
1041         cocoa_item = cocoa_menu_item_get (menu_item);
1042
1043         if (cocoa_item) 
1044                 return;
1045
1046         if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item)) {
1047                 cocoa_item = [NSMenuItem separatorItem];
1048                 DEBUG ("\ta separator\n");
1049         } else {
1050
1051                 if (!GTK_WIDGET_VISIBLE (menu_item)) {
1052                         DEBUG ("\tnot visible\n");
1053                         return;
1054                 }
1055
1056                 const gchar* label_text = get_menu_label_text (menu_item, &label);
1057                 
1058                 if (label_text)
1059                         cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]
1060                                        andGtkWidget:(GtkMenuItem*)menu_item];
1061                 else
1062                         cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:@"" andGtkWidget:(GtkMenuItem*)menu_item];
1063                 DEBUG ("\tan item\n");
1064         }
1065         
1066         /* connect GtkMenuItem and NSMenuItem so that we can notice changes to accel/label/submenu etc. */
1067         cocoa_menu_item_connect (menu_item, (GNSMenuItem*) cocoa_item, label);
1068         
1069         [ cocoa_item setEnabled:YES];
1070         if (index >= 0) 
1071                 [ cocoa_menu insertItem:cocoa_item atIndex:index];
1072         else 
1073                 [ cocoa_menu addItem:cocoa_item];
1074         
1075         if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
1076                 [cocoa_item setState:NSOffState];
1077
1078 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4
1079         if (!GTK_WIDGET_VISIBLE (menu_item))
1080                 [cocoa_item setHidden:YES];
1081 #endif
1082         
1083         if (GTK_IS_CHECK_MENU_ITEM (menu_item))
1084                 cocoa_menu_item_update_active (cocoa_item, menu_item);
1085         
1086         if (!GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
1087                 cocoa_menu_item_update_accel_closure (cocoa_item, menu_item);
1088         
1089         if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item))) 
1090                 cocoa_menu_item_update_submenu (cocoa_item, menu_item);
1091
1092         [ cocoa_item release];
1093 }
1094         
1095 static void
1096 push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
1097                            NSMenu*       cocoa_menu,
1098                            gboolean      /*toplevel*/,
1099                            gboolean      /*debug*/)
1100 {
1101   GList         *children;
1102   GList         *l;
1103
1104   children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
1105
1106   for (l = children; l; l = l->next)
1107     {
1108       GtkWidget   *menu_item = (GtkWidget*) l->data;
1109
1110       if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
1111         continue;
1112
1113       if (g_object_get_data (G_OBJECT (menu_item), "gtk-empty-menu-item"))
1114         continue;
1115
1116       add_menu_item (cocoa_menu, menu_item, -1);
1117     }
1118   
1119   g_list_free (children);
1120 }
1121
1122
1123 static gulong emission_hook_id = 0;
1124
1125 static gboolean
1126 parent_set_emission_hook (GSignalInvocationHint* /*ihint*/,
1127                           guint                  /*n_param_values*/,
1128                           const GValue*          param_values,
1129                           gpointer               data)
1130 {
1131   GtkWidget *instance = (GtkWidget*) g_value_get_object (param_values);
1132
1133   if (GTK_IS_MENU_ITEM (instance))
1134     {
1135       GtkWidget *previous_parent = (GtkWidget*) g_value_get_object (param_values + 1);
1136       GtkWidget *menu_shell      = NULL;
1137
1138       if (GTK_IS_MENU_SHELL (previous_parent))
1139         {
1140           menu_shell = previous_parent;
1141         }
1142       else if (GTK_IS_MENU_SHELL (instance->parent))
1143         {
1144           menu_shell = instance->parent;
1145         }
1146
1147       if (menu_shell)
1148         {
1149           NSMenu *cocoa_menu = cocoa_menu_get (menu_shell);
1150
1151           if (cocoa_menu)
1152             {
1153               push_menu_shell_to_nsmenu (GTK_MENU_SHELL (menu_shell),
1154                                          cocoa_menu,
1155                                          cocoa_menu == (NSMenu*) data,
1156                                          FALSE);
1157             }
1158         }
1159     }
1160
1161   return TRUE;
1162 }
1163
1164 static void
1165 parent_set_emission_hook_remove (GtkWidget*, gpointer)
1166 {
1167         g_signal_remove_emission_hook (g_signal_lookup ("parent-set", GTK_TYPE_WIDGET),
1168                                        emission_hook_id);
1169 }
1170
1171 /* Building "standard" Cocoa/OS X menus */
1172
1173 #warning You can safely ignore the next warning about a duplicate interface definition
1174 @interface NSApplication(NSWindowsMenu)
1175         - (void)setAppleMenu:(NSMenu *)aMenu;
1176 @end
1177
1178 static NSMenu* _main_menubar = 0;
1179 static NSMenu* _window_menu = 0;
1180 static NSMenu* _app_menu = 0;
1181
1182 static int
1183 add_to_menubar (NSMenu *menu)
1184 {
1185         NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
1186                                  action:nil keyEquivalent:@""];
1187         [dummyItem setSubmenu:menu];
1188         [_main_menubar addItem:dummyItem];
1189         [dummyItem release];
1190         return 0;
1191 }
1192
1193 #if 0
1194 static int
1195 add_to_app_menu (NSMenu *menu)
1196 {
1197         NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
1198                                  action:nil keyEquivalent:@""];
1199         [dummyItem setSubmenu:menu];
1200         [_app_menu addItem:dummyItem];
1201         [dummyItem release];
1202         return 0;
1203 }
1204 #endif
1205
1206 static int
1207 create_apple_menu ()
1208 {
1209         NSMenuItem *menuitem;
1210         // Create the application (Apple) menu.
1211         _app_menu = [[NSMenu alloc] initWithTitle: @"Apple Menu"];
1212
1213         NSMenu *menuServices = [[NSMenu alloc] initWithTitle: @"Services"];
1214         [NSApp setServicesMenu:menuServices];
1215
1216         [_app_menu addItem: [NSMenuItem separatorItem]];
1217         menuitem = [[NSMenuItem alloc] initWithTitle: @"Services"
1218                     action:nil keyEquivalent:@""];
1219         [menuitem setSubmenu:menuServices];
1220         [_app_menu addItem: menuitem];
1221         [menuitem release];
1222         [_app_menu addItem: [NSMenuItem separatorItem]];
1223         menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide"
1224                     action:@selector(hide:) keyEquivalent:@""];
1225         [menuitem setTarget: NSApp];
1226         [_app_menu addItem: menuitem];
1227         [menuitem release];
1228         menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
1229                     action:@selector(hideOtherApplications:) keyEquivalent:@""];
1230         [menuitem setTarget: NSApp];
1231         [_app_menu addItem: menuitem];
1232         [menuitem release];
1233         menuitem = [[NSMenuItem alloc] initWithTitle:@"Show All"
1234                     action:@selector(unhideAllApplications:) keyEquivalent:@""];
1235         [menuitem setTarget: NSApp];
1236         [_app_menu addItem: menuitem];
1237         [menuitem release];
1238         [_app_menu addItem: [NSMenuItem separatorItem]];
1239         menuitem = [[NSMenuItem alloc] initWithTitle:@"Quit"
1240                     action:@selector(terminate:) keyEquivalent:@"q"];
1241         [menuitem setTarget: NSApp];
1242         [_app_menu addItem: menuitem];
1243         [menuitem release];
1244
1245         [NSApp setAppleMenu:_app_menu];
1246         add_to_menubar (_app_menu);
1247
1248         return 0;
1249 }
1250
1251 #if 0
1252 static int
1253 add_to_window_menu (NSMenu *menu)
1254 {
1255         NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
1256                                  action:nil keyEquivalent:@""];
1257         [dummyItem setSubmenu:menu];
1258         [_window_menu addItem:dummyItem];
1259         [dummyItem release];
1260         return 0;
1261 }
1262
1263 static int
1264 create_window_menu ()
1265 {   
1266         _window_menu = [[NSMenu alloc] initWithTitle: @"Window"];
1267
1268         [_window_menu addItemWithTitle:@"Minimize"
1269          action:@selector(performMiniaturize:) keyEquivalent:@""];
1270         [_window_menu addItem: [NSMenuItem separatorItem]];
1271         [_window_menu addItemWithTitle:@"Bring All to Front"
1272          action:@selector(arrangeInFront:) keyEquivalent:@""];
1273
1274         [NSApp setWindowsMenu:_window_menu];
1275         add_to_menubar(_window_menu);
1276
1277         return 0;
1278 }  
1279 #endif
1280
1281 /*
1282  * public functions
1283  */
1284
1285 extern "C" void
1286 gtk_application_set_menu_bar (GtkMenuShell *menu_shell)
1287 {
1288   NSMenu* cocoa_menubar;
1289
1290   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
1291
1292   if (cocoa_menu_quark == 0)
1293     cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
1294
1295   if (cocoa_menu_item_quark == 0)
1296     cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
1297
1298   cocoa_menubar = [ [ NSApplication sharedApplication] mainMenu];
1299
1300   /* turn off auto-enabling for the menu - its silly and slow and
1301      doesn't really make sense for a Gtk/Cocoa hybrid menu.
1302    */
1303
1304   [cocoa_menubar setAutoenablesItems:NO];
1305
1306   emission_hook_id =
1307     g_signal_add_emission_hook (g_signal_lookup ("parent-set",
1308                                                  GTK_TYPE_WIDGET),
1309                                 0,
1310                                 parent_set_emission_hook,
1311                                 cocoa_menubar, NULL);
1312
1313
1314   g_signal_connect (menu_shell, "destroy",
1315                     G_CALLBACK (parent_set_emission_hook_remove),
1316                     NULL);
1317
1318   push_menu_shell_to_nsmenu (menu_shell, cocoa_menubar, TRUE, FALSE);
1319 }
1320
1321 extern "C" void
1322 gtk_application_add_app_menu_item (GtkApplicationMenuGroup *group,
1323                                    GtkMenuItem     *menu_item)
1324 {
1325   // we know that the application menu is always the submenu of the first item in the main menu
1326   NSMenu* mainMenu;
1327   NSMenu *appMenu;
1328   NSMenuItem *firstItem;
1329   GList   *list;
1330   gint     index = 0;
1331
1332   mainMenu = [NSApp mainMenu];
1333   firstItem = [ mainMenu itemAtIndex:0];
1334   appMenu = [ firstItem submenu ];
1335
1336   g_return_if_fail (group != NULL);
1337   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
1338
1339   for (list = _gtk_application_menu_groups; list; list = g_list_next (list))
1340     {
1341       GtkApplicationMenuGroup *list_group = (GtkApplicationMenuGroup*) list->data;
1342
1343       index += g_list_length (list_group->items);
1344
1345       /*  adjust index for the separator between groups, but not
1346        *  before the first group
1347        */
1348       if (list_group->items && list->prev)
1349         index++;
1350
1351       if (group == list_group)
1352         {
1353                 /*  add a separator before adding the first item, but not
1354                  *  for the first group
1355                  */
1356                 
1357                 if (!group->items && list->prev)
1358                    {
1359                            [appMenu insertItem:[NSMenuItem separatorItem] atIndex:index+1];
1360                            index++;
1361                    }
1362                 DEBUG ("Add to APP menu bar %s\n", get_menu_label_text (GTK_WIDGET(menu_item), NULL));
1363                 add_menu_item (appMenu, GTK_WIDGET(menu_item), index+1);
1364
1365                 group->items = g_list_append (group->items, menu_item);
1366                 gtk_widget_hide (GTK_WIDGET (menu_item));
1367                 return;
1368         }
1369     }
1370
1371   if (!list)
1372     g_warning ("%s: app menu group %p does not exist",
1373                G_STRFUNC, group);
1374 }
1375
1376 /* application delegate, currently in C++ */
1377
1378 #include <gtkmm2ext/application.h>
1379 #include <glibmm/ustring.h>
1380
1381 namespace Gtk {
1382         namespace Application {
1383                 sigc::signal<void,bool> ActivationChanged;
1384                 sigc::signal<void,const Glib::ustring&> ShouldLoad;
1385                 sigc::signal<void> ShouldQuit;
1386         }
1387 }
1388
1389 @interface GtkApplicationNotificationObject : NSObject {}
1390 - (GtkApplicationNotificationObject*) init; 
1391 @end
1392
1393 @implementation GtkApplicationNotificationObject
1394 - (GtkApplicationNotificationObject*) init
1395 {
1396         self = [ super init ];
1397
1398         if (self) {
1399                 [[NSNotificationCenter defaultCenter] addObserver:self
1400                  selector:@selector(appDidBecomeActive:)
1401                  name:NSApplicationDidBecomeActiveNotification
1402                  object:[NSApplication sharedApplication]];
1403
1404                 [[NSNotificationCenter defaultCenter] addObserver:self
1405                  selector:@selector(appDidBecomeInactive:)
1406                  name:NSApplicationWillResignActiveNotification 
1407                  object:[NSApplication sharedApplication]];
1408         }
1409
1410         return self;
1411 }
1412
1413 - (void)appDidBecomeActive:(NSNotification *) notification
1414 {
1415         UNUSED_PARAMETER(notification);
1416         Gtkmm2ext::Application::instance()->ActivationChanged (true);
1417 }
1418
1419 - (void)appDidBecomeInactive:(NSNotification *) notification
1420 {
1421         UNUSED_PARAMETER(notification);
1422         Gtkmm2ext::Application::instance()->ActivationChanged (false);
1423 }
1424
1425 @end
1426
1427 @interface GtkApplicationDelegate : NSObject 
1428 -(BOOL) application:(NSApplication*) app openFile:(NSString*) file;
1429 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) app;
1430 @end
1431
1432 @implementation GtkApplicationDelegate
1433 -(BOOL) application:(NSApplication*) app openFile:(NSString*) file
1434 {
1435         UNUSED_PARAMETER(app);
1436         Glib::ustring utf8_path ([file UTF8String]);
1437         Gtkmm2ext::Application::instance()->ShouldLoad (utf8_path);
1438         return 1;
1439 }
1440 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) app
1441 {
1442         UNUSED_PARAMETER(app);
1443         Gtkmm2ext::Application::instance()->ShouldQuit ();
1444         return NSTerminateCancel;
1445 }
1446 @end
1447
1448
1449 /* Basic setup */
1450
1451 extern "C" int
1452 gtk_application_init ()
1453 {
1454         _main_menubar = [[NSMenu alloc] initWithTitle: @""];
1455
1456         if (!_main_menubar) 
1457                 return -1;
1458
1459         [NSApp setMainMenu: _main_menubar];
1460         create_apple_menu ();
1461         // create_window_menu ();
1462
1463         /* this will stick around for ever ... is that OK ? */
1464
1465         [ [GtkApplicationNotificationObject alloc] init];
1466         [ NSApp setDelegate: [GtkApplicationDelegate new]];
1467
1468         return 0;
1469 }
1470
1471 extern "C" void
1472 gtk_application_ready ()
1473 {
1474         [ NSApp finishLaunching ];
1475         [[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
1476 }
1477
1478 extern "C" void
1479 gtk_application_cleanup()
1480 {
1481         _exiting = 1;
1482
1483         if (_window_menu) {
1484                 [ _window_menu release ];
1485                 _window_menu = 0;
1486         }
1487         if (_app_menu) {
1488                 [ _app_menu release ];
1489                 _app_menu = 0;
1490         }
1491         if (_main_menubar) {
1492                 [ _main_menubar release ];
1493                 _main_menubar = 0;
1494         }
1495 }