LCXL: some more small tweaks
[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     // Hot Fix. Increase Priority.
577         g_idle_add_full (G_PRIORITY_HIGH_IDLE, idle_call_activate, gtk_menu_item, NULL);
578 //    g_idle_add (idle_call_activate, gtk_menu_item);
579 }
580 @end
581
582 static void push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
583                                        NSMenu       *menu,
584                                        gboolean      toplevel,
585                                        gboolean      debug);
586
587 /*
588  * utility functions
589  */
590
591 static GtkWidget *
592 find_menu_label (GtkWidget *widget)
593 {
594   GtkWidget *label = NULL;
595
596   if (GTK_IS_LABEL (widget))
597     return widget;
598
599   if (GTK_IS_CONTAINER (widget))
600     {
601       GList *children;
602       GList *l;
603
604       children = gtk_container_get_children (GTK_CONTAINER (widget));
605
606       for (l = children; l; l = l->next)
607         {
608           label = find_menu_label ((GtkWidget*) l->data);
609           if (label)
610             break;
611         }
612
613       g_list_free (children);
614     }
615
616   return label;
617 }
618
619 static const gchar *
620 get_menu_label_text (GtkWidget  *menu_item,
621                      GtkWidget **label)
622 {
623   GtkWidget *my_label;
624
625   my_label = find_menu_label (menu_item);
626   if (label)
627     *label = my_label;
628
629   if (my_label)
630     return gtk_label_get_text (GTK_LABEL (my_label));
631
632   return NULL;
633 }
634
635 static gboolean
636 accel_find_func (GtkAccelKey * /*key*/,
637                  GClosure    *closure,
638                  gpointer     data)
639 {
640   return (GClosure *) data == closure;
641 }
642
643
644 /*
645  * CocoaMenu functions
646  */
647
648 static GQuark cocoa_menu_quark = 0;
649
650 static NSMenu *
651 cocoa_menu_get (GtkWidget *widget)
652 {
653   return (NSMenu*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_quark);
654 }
655
656 static void
657 cocoa_menu_free (gpointer *ptr)
658 {
659         NSMenu* menu = (NSMenu*) ptr;
660         [menu release];
661 }
662
663 static void
664 cocoa_menu_connect (GtkWidget *menu,
665                     NSMenu*    cocoa_menu)
666 {
667         [cocoa_menu retain];
668
669         if (cocoa_menu_quark == 0)
670                 cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
671         
672         g_object_set_qdata_full (G_OBJECT (menu), cocoa_menu_quark,
673                                  cocoa_menu,
674                                  (GDestroyNotify) cocoa_menu_free);
675 }
676
677 /*
678  * NSMenuItem functions
679  */
680
681 static GQuark cocoa_menu_item_quark = 0;
682 static void cocoa_menu_item_connect (GtkWidget*   menu_item,
683                                      GNSMenuItem* cocoa_menu_item,
684                                      GtkWidget     *label);
685
686 static void
687 cocoa_menu_item_free (gpointer *ptr)
688 {
689         GNSMenuItem* item = (GNSMenuItem*) ptr;
690         [item release];
691 }
692
693 static GNSMenuItem *
694 cocoa_menu_item_get (GtkWidget *widget)
695 {
696   return (GNSMenuItem*) g_object_get_qdata (G_OBJECT (widget), cocoa_menu_item_quark);
697 }
698
699 static void
700 cocoa_menu_item_update_state (NSMenuItem* cocoa_item,
701                               GtkWidget      *widget)
702 {
703   gboolean sensitive;
704   gboolean visible;
705
706   g_object_get (widget,
707                 "sensitive", &sensitive,
708                 "visible",   &visible,
709                 NULL);
710
711   if (!sensitive)
712           [cocoa_item setEnabled:NO];
713   else
714           [cocoa_item setEnabled:YES];
715
716 #if 0
717   // requires OS X 10.5 or later
718   if (!visible)
719           [cocoa_item setHidden:YES];
720   else
721           [cocoa_item setHidden:NO];
722 #endif
723 }
724
725 static void
726 cocoa_menu_item_update_active (NSMenuItem *cocoa_item,
727                                 GtkWidget  *widget)
728 {
729   gboolean active;
730
731   g_object_get (widget, "active", &active, NULL);
732
733   if (active) 
734     [cocoa_item setState:NSOnState];
735   else
736     [cocoa_item setState:NSOffState];
737 }
738
739 static void
740 cocoa_menu_item_update_submenu (NSMenuItem *cocoa_item,
741                                 GtkWidget      *widget)
742 {
743   GtkWidget *submenu;
744   
745   g_return_if_fail (cocoa_item != NULL);
746   g_return_if_fail (widget != NULL);
747
748   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
749
750   if (submenu)
751     {
752       GtkWidget* label = NULL;
753       const gchar *label_text;
754       NSMenu* cocoa_submenu;
755
756       label_text = get_menu_label_text (widget, &label);
757
758       /* create a new nsmenu to hold the GTK menu */
759
760       if (label_text) 
761               cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
762       else
763               cocoa_submenu = [ [ NSMenu alloc ] initWithTitle:@""];
764
765       [cocoa_submenu setAutoenablesItems:NO];
766       cocoa_menu_connect (submenu, cocoa_submenu);
767
768       /* connect the new nsmenu to the passed-in item (which lives in
769          the parent nsmenu)
770          (Note: this will release any pre-existing version of this submenu)
771       */
772       [ cocoa_item setSubmenu:cocoa_submenu];
773
774       /* and push the GTK menu into the submenu */
775       push_menu_shell_to_nsmenu (GTK_MENU_SHELL (submenu), cocoa_submenu, FALSE, FALSE);
776
777       [ cocoa_submenu release ];
778     }
779 }
780
781 static void
782 cocoa_menu_item_update_label (NSMenuItem *cocoa_item,
783                                GtkWidget      *widget)
784 {
785   const gchar *label_text;
786
787   g_return_if_fail (cocoa_item != NULL);
788   g_return_if_fail (widget != NULL);
789
790   label_text = get_menu_label_text (widget, NULL);
791   if (label_text)
792           [cocoa_item setTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]];
793   else 
794           [cocoa_item setTitle:@""];
795 }
796
797 static void
798 cocoa_menu_item_update_accelerator (NSMenuItem *cocoa_item,
799                                      GtkWidget *widget)
800 {
801   GtkWidget *label;
802
803   g_return_if_fail (cocoa_item != NULL);
804   g_return_if_fail (widget != NULL);
805
806   /* important note: this function doesn't do anything to actually change
807      key handling. Its goal is to get Cocoa to display the correct
808      accelerator as part of a menu item. Actual accelerator handling
809      is still done by GTK, so this is more cosmetic than it may 
810      appear.
811   */
812
813   get_menu_label_text (widget, &label);  
814
815   if (GTK_IS_ACCEL_LABEL (label) &&
816             GTK_ACCEL_LABEL (label)->accel_closure)
817         {
818                 GtkAccelKey *key;
819                 
820                 key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
821                                             accel_find_func,
822                                   GTK_ACCEL_LABEL (label)->accel_closure);
823                 
824                 if (key            &&
825                     key->accel_key &&
826                     key->accel_flags & GTK_ACCEL_VISIBLE)
827                 {
828                         guint modifiers = 0; 
829                         const gchar* str = NULL;
830                         guint actual_key = key->accel_key; 
831                         
832                         if (keyval_is_keypad (actual_key)) {
833                                 if ((actual_key = keyval_keypad_nonkeypad_equivalent (actual_key)) == GDK_VoidSymbol) {
834                                         /* GDK_KP_Separator */
835                                         [cocoa_item setKeyEquivalent:@""];
836                                         return;
837                                 }
838                                 modifiers |= NSNumericPadKeyMask;
839                         }
840                         
841                         /* if we somehow got here with GDK_A ... GDK_Z rather than GDK_a ... GDK_z, then take note
842                            of that and make sure we use a shift modifier.
843                         */
844                         
845                         if (keyval_is_uppercase (actual_key)) {
846                                 modifiers |= NSShiftKeyMask;
847                         }
848                         
849                         str = gdk_quartz_keyval_to_string (actual_key);
850                         
851                         if (str) {
852                                 unichar ukey = str[0];
853                                 [cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
854                         } else {
855                                 unichar ukey = gdk_quartz_keyval_to_ns_keyval (actual_key);
856                                 if (ukey != 0) {
857                                         [cocoa_item setKeyEquivalent:[NSString stringWithCharacters:&ukey length:1]];
858                                 } else {
859                                         /* cannot map this key to Cocoa key equivalent */
860                                         [cocoa_item setKeyEquivalent:@""];
861                                         return;
862                                 }
863                         } 
864                         
865                         if (key->accel_mods || modifiers)
866                         {
867                                 if (key->accel_mods & GDK_SHIFT_MASK) {
868                                         modifiers |= NSShiftKeyMask;
869                                 }
870                                 
871                                 /* gdk/quartz maps Alt/Option to Mod1 */
872                                 
873                                 if (key->accel_mods & (GDK_MOD1_MASK)) {
874                                         modifiers |= NSAlternateKeyMask;
875                                 }
876                                 
877                                 if (key->accel_mods & GDK_CONTROL_MASK) {
878                                         modifiers |= NSControlKeyMask;
879                                 }
880                                 
881                                 /* our modified gdk/quartz maps Command to Mod2 */
882                                 
883                                 if (key->accel_mods & GDK_MOD2_MASK) {
884                                         modifiers |= NSCommandKeyMask;
885                                 }
886                         }  
887                         
888                         [cocoa_item setKeyEquivalentModifierMask:modifiers];
889                         return;
890                 }
891         }
892
893         /*  otherwise, clear the menu shortcut  */
894         [cocoa_item setKeyEquivalent:@""];
895 }
896
897 static void
898 cocoa_menu_item_accel_changed (GtkAccelGroup*   /*accel_group*/,
899                                guint            /*keyval*/,
900                                 GdkModifierType /*modifier*/,
901                                 GClosure        *accel_closure,
902                                 GtkWidget       *widget)
903 {
904   GNSMenuItem *cocoa_item;
905   GtkWidget      *label;
906
907   if (_exiting) 
908     return;
909
910   cocoa_item = cocoa_menu_item_get (widget);
911   get_menu_label_text (widget, &label);
912
913   if (GTK_IS_ACCEL_LABEL (label) &&
914       GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
915     cocoa_menu_item_update_accelerator (cocoa_item, widget);
916 }
917
918 static void
919 cocoa_menu_item_update_accel_closure (GNSMenuItem *cocoa_item,
920                                       GtkWidget      *widget)
921 {
922   GtkAccelGroup *group;
923   GtkWidget     *label;
924
925   get_menu_label_text (widget, &label);
926
927   if (cocoa_item->accel_closure)
928     {
929       group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
930
931       g_signal_handlers_disconnect_by_func (group,
932                                             (void*) cocoa_menu_item_accel_changed,
933                                             widget);
934
935       g_closure_unref (cocoa_item->accel_closure);
936       cocoa_item->accel_closure = NULL;
937     }
938
939   if (GTK_IS_ACCEL_LABEL (label)) {
940     cocoa_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
941   }
942
943   if (cocoa_item->accel_closure)
944     {
945       g_closure_ref (cocoa_item->accel_closure);
946
947       group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
948
949       g_signal_connect_object (group, "accel-changed",
950                                G_CALLBACK (cocoa_menu_item_accel_changed),
951                                widget, (GConnectFlags) 0);
952     }
953
954   cocoa_menu_item_update_accelerator (cocoa_item, widget);
955 }
956
957 static void
958 cocoa_menu_item_notify_label (GObject    *object,
959                               GParamSpec *pspec,
960                               gpointer)
961 {
962   GNSMenuItem *cocoa_item;
963
964   if (_exiting) 
965     return;
966
967   cocoa_item = cocoa_menu_item_get (GTK_WIDGET (object));
968
969   if (!strcmp (pspec->name, "label"))
970     {
971       cocoa_menu_item_update_label (cocoa_item,
972                                      GTK_WIDGET (object));
973     }
974   else if (!strcmp (pspec->name, "accel-closure"))
975     {
976       cocoa_menu_item_update_accel_closure (cocoa_item,
977                                              GTK_WIDGET (object));
978     }
979 }
980
981 static void
982 cocoa_menu_item_notify (GObject        *object,
983                         GParamSpec     *pspec,
984                         NSMenuItem *cocoa_item)
985 {
986   if (_exiting)
987     return;
988
989   if (!strcmp (pspec->name, "sensitive") ||
990       !strcmp (pspec->name, "visible"))
991     {
992       cocoa_menu_item_update_state (cocoa_item, GTK_WIDGET (object));
993     }
994   else if (!strcmp (pspec->name, "active"))
995     {
996       cocoa_menu_item_update_active (cocoa_item, GTK_WIDGET (object));
997     }
998   else if (!strcmp (pspec->name, "submenu"))
999     {
1000       cocoa_menu_item_update_submenu (cocoa_item, GTK_WIDGET (object));
1001     }
1002 }
1003
1004 static void
1005 cocoa_menu_item_connect (GtkWidget*   menu_item,
1006                          GNSMenuItem* cocoa_item,
1007                          GtkWidget     *label)
1008 {
1009         GNSMenuItem* old_item = cocoa_menu_item_get (menu_item);
1010
1011         [cocoa_item retain];
1012
1013         if (cocoa_menu_item_quark == 0)
1014                 cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
1015
1016         g_object_set_qdata_full (G_OBJECT (menu_item), cocoa_menu_item_quark,
1017                                  cocoa_item,
1018                                  (GDestroyNotify) cocoa_menu_item_free);
1019         
1020         if (!old_item) {
1021
1022                 g_signal_connect (menu_item, "notify",
1023                                   G_CALLBACK (cocoa_menu_item_notify),
1024                                   cocoa_item);
1025                 
1026                 if (label)
1027                         g_signal_connect_swapped (label, "notify::label",
1028                                                   G_CALLBACK (cocoa_menu_item_notify_label),
1029                                                   menu_item);
1030         }
1031 }
1032
1033 static void
1034 add_menu_item (NSMenu* cocoa_menu, GtkWidget* menu_item, int index)
1035 {
1036         GtkWidget* label      = NULL;
1037         GNSMenuItem *cocoa_item;
1038         
1039         DEBUG ("add %s to menu %s separator ? %d\n", get_menu_label_text (menu_item, NULL), 
1040                [[cocoa_menu title] cStringUsingEncoding:NSUTF8StringEncoding],
1041                GTK_IS_SEPARATOR_MENU_ITEM(menu_item));
1042
1043         cocoa_item = cocoa_menu_item_get (menu_item);
1044
1045         if (cocoa_item) 
1046                 return;
1047
1048         if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item)) {
1049                 cocoa_item = [NSMenuItem separatorItem];
1050                 DEBUG ("\ta separator\n");
1051         } else {
1052
1053                 if (!GTK_WIDGET_VISIBLE (menu_item)) {
1054                         DEBUG ("\tnot visible\n");
1055                         return;
1056                 }
1057
1058                 const gchar* label_text = get_menu_label_text (menu_item, &label);
1059                 
1060                 if (label_text)
1061                         cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:[ [ NSString alloc] initWithCString:label_text encoding:NSUTF8StringEncoding]
1062                                        andGtkWidget:(GtkMenuItem*)menu_item];
1063                 else
1064                         cocoa_item = [ [ GNSMenuItem alloc] initWithTitle:@"" andGtkWidget:(GtkMenuItem*)menu_item];
1065                 DEBUG ("\tan item\n");
1066         }
1067         
1068         /* connect GtkMenuItem and NSMenuItem so that we can notice changes to accel/label/submenu etc. */
1069         cocoa_menu_item_connect (menu_item, (GNSMenuItem*) cocoa_item, label);
1070         
1071         [ cocoa_item setEnabled:YES];
1072         if (index >= 0) 
1073                 [ cocoa_menu insertItem:cocoa_item atIndex:index];
1074         else 
1075                 [ cocoa_menu addItem:cocoa_item];
1076         
1077         if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
1078                 [cocoa_item setState:NSOffState];
1079
1080 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4
1081         if (!GTK_WIDGET_VISIBLE (menu_item))
1082                 [cocoa_item setHidden:YES];
1083 #endif
1084         
1085         if (GTK_IS_CHECK_MENU_ITEM (menu_item))
1086                 cocoa_menu_item_update_active (cocoa_item, menu_item);
1087         
1088         if (!GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
1089                 cocoa_menu_item_update_accel_closure (cocoa_item, menu_item);
1090         
1091         if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item))) 
1092                 cocoa_menu_item_update_submenu (cocoa_item, menu_item);
1093
1094         [ cocoa_item release];
1095 }
1096         
1097 static void
1098 push_menu_shell_to_nsmenu (GtkMenuShell *menu_shell,
1099                            NSMenu*       cocoa_menu,
1100                            gboolean      /*toplevel*/,
1101                            gboolean      /*debug*/)
1102 {
1103   GList         *children;
1104   GList         *l;
1105
1106   children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
1107
1108   for (l = children; l; l = l->next)
1109     {
1110       GtkWidget   *menu_item = (GtkWidget*) l->data;
1111
1112       if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
1113         continue;
1114
1115       if (g_object_get_data (G_OBJECT (menu_item), "gtk-empty-menu-item"))
1116         continue;
1117
1118       add_menu_item (cocoa_menu, menu_item, -1);
1119     }
1120   
1121   g_list_free (children);
1122 }
1123
1124
1125 static gulong emission_hook_id = 0;
1126
1127 static gboolean
1128 parent_set_emission_hook (GSignalInvocationHint* /*ihint*/,
1129                           guint                  /*n_param_values*/,
1130                           const GValue*          param_values,
1131                           gpointer               data)
1132 {
1133   GtkWidget *instance = (GtkWidget*) g_value_get_object (param_values);
1134
1135   if (GTK_IS_MENU_ITEM (instance))
1136     {
1137       GtkWidget *previous_parent = (GtkWidget*) g_value_get_object (param_values + 1);
1138       GtkWidget *menu_shell      = NULL;
1139
1140       if (GTK_IS_MENU_SHELL (previous_parent))
1141         {
1142           menu_shell = previous_parent;
1143         }
1144       else if (GTK_IS_MENU_SHELL (instance->parent))
1145         {
1146           menu_shell = instance->parent;
1147         }
1148
1149       if (menu_shell)
1150         {
1151           NSMenu *cocoa_menu = cocoa_menu_get (menu_shell);
1152
1153           if (cocoa_menu)
1154             {
1155               push_menu_shell_to_nsmenu (GTK_MENU_SHELL (menu_shell),
1156                                          cocoa_menu,
1157                                          cocoa_menu == (NSMenu*) data,
1158                                          FALSE);
1159             }
1160         }
1161     }
1162
1163   return TRUE;
1164 }
1165
1166 static void
1167 parent_set_emission_hook_remove (GtkWidget*, gpointer)
1168 {
1169         g_signal_remove_emission_hook (g_signal_lookup ("parent-set", GTK_TYPE_WIDGET),
1170                                        emission_hook_id);
1171 }
1172
1173 /* Building "standard" Cocoa/OS X menus */
1174
1175 #warning You can safely ignore the next warning about a duplicate interface definition
1176 @interface NSApplication(NSWindowsMenu)
1177         - (void)setAppleMenu:(NSMenu *)aMenu;
1178 @end
1179
1180 static NSMenu* _main_menubar = 0;
1181 static NSMenu* _window_menu = 0;
1182 static NSMenu* _app_menu = 0;
1183
1184 static int
1185 add_to_menubar (NSMenu *menu)
1186 {
1187         NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
1188                                  action:nil keyEquivalent:@""];
1189         [dummyItem setSubmenu:menu];
1190         [_main_menubar addItem:dummyItem];
1191         [dummyItem release];
1192         return 0;
1193 }
1194
1195 #if 0
1196 static int
1197 add_to_app_menu (NSMenu *menu)
1198 {
1199         NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
1200                                  action:nil keyEquivalent:@""];
1201         [dummyItem setSubmenu:menu];
1202         [_app_menu addItem:dummyItem];
1203         [dummyItem release];
1204         return 0;
1205 }
1206 #endif
1207
1208 static int
1209 create_apple_menu ()
1210 {
1211         NSMenuItem *menuitem;
1212         // Create the application (Apple) menu.
1213         _app_menu = [[NSMenu alloc] initWithTitle: @"Apple Menu"];
1214
1215         NSMenu *menuServices = [[NSMenu alloc] initWithTitle: @"Services"];
1216         [NSApp setServicesMenu:menuServices];
1217
1218         [_app_menu addItem: [NSMenuItem separatorItem]];
1219         menuitem = [[NSMenuItem alloc] initWithTitle: @"Services"
1220                     action:nil keyEquivalent:@""];
1221         [menuitem setSubmenu:menuServices];
1222         [_app_menu addItem: menuitem];
1223         [menuitem release];
1224         [_app_menu addItem: [NSMenuItem separatorItem]];
1225         menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide"
1226                     action:@selector(hide:) keyEquivalent:@"h"];
1227         [menuitem setTarget: NSApp];
1228         [_app_menu addItem: menuitem];
1229         [menuitem release];
1230         menuitem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
1231                     action:@selector(hideOtherApplications:) keyEquivalent:@""];
1232         [menuitem setTarget: NSApp];
1233         [_app_menu addItem: menuitem];
1234         [menuitem release];
1235         menuitem = [[NSMenuItem alloc] initWithTitle:@"Show All"
1236                     action:@selector(unhideAllApplications:) keyEquivalent:@""];
1237         [menuitem setTarget: NSApp];
1238         [_app_menu addItem: menuitem];
1239         [menuitem release];
1240         [_app_menu addItem: [NSMenuItem separatorItem]];
1241         menuitem = [[NSMenuItem alloc] initWithTitle:@"Quit"
1242                     action:@selector(terminate:) keyEquivalent:@"q"];
1243         [menuitem setTarget: NSApp];
1244         [_app_menu addItem: menuitem];
1245         [menuitem release];
1246
1247         [NSApp setAppleMenu:_app_menu];
1248         add_to_menubar (_app_menu);
1249
1250         return 0;
1251 }
1252
1253 #if 0
1254 static int
1255 add_to_window_menu (NSMenu *menu)
1256 {
1257         NSMenuItem *dummyItem = [[NSMenuItem alloc] initWithTitle:@""
1258                                  action:nil keyEquivalent:@""];
1259         [dummyItem setSubmenu:menu];
1260         [_window_menu addItem:dummyItem];
1261         [dummyItem release];
1262         return 0;
1263 }
1264
1265 static int
1266 create_window_menu ()
1267 {   
1268         _window_menu = [[NSMenu alloc] initWithTitle: @"Window"];
1269
1270         [_window_menu addItemWithTitle:@"Minimize"
1271          action:@selector(performMiniaturize:) keyEquivalent:@""];
1272         [_window_menu addItem: [NSMenuItem separatorItem]];
1273         [_window_menu addItemWithTitle:@"Bring All to Front"
1274          action:@selector(arrangeInFront:) keyEquivalent:@""];
1275
1276         [NSApp setWindowsMenu:_window_menu];
1277         add_to_menubar(_window_menu);
1278
1279         return 0;
1280 }  
1281 #endif
1282
1283 /*
1284  * public functions
1285  */
1286
1287 extern "C" void
1288 gtk_application_set_menu_bar (GtkMenuShell *menu_shell)
1289 {
1290   NSMenu* cocoa_menubar;
1291
1292   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
1293
1294   if (cocoa_menu_quark == 0)
1295     cocoa_menu_quark = g_quark_from_static_string ("NSMenu");
1296
1297   if (cocoa_menu_item_quark == 0)
1298     cocoa_menu_item_quark = g_quark_from_static_string ("NSMenuItem");
1299
1300   cocoa_menubar = [ [ NSApplication sharedApplication] mainMenu];
1301
1302   /* turn off auto-enabling for the menu - its silly and slow and
1303      doesn't really make sense for a Gtk/Cocoa hybrid menu.
1304    */
1305
1306   [cocoa_menubar setAutoenablesItems:NO];
1307
1308   emission_hook_id =
1309     g_signal_add_emission_hook (g_signal_lookup ("parent-set",
1310                                                  GTK_TYPE_WIDGET),
1311                                 0,
1312                                 parent_set_emission_hook,
1313                                 cocoa_menubar, NULL);
1314
1315
1316   g_signal_connect (menu_shell, "destroy",
1317                     G_CALLBACK (parent_set_emission_hook_remove),
1318                     NULL);
1319
1320   push_menu_shell_to_nsmenu (menu_shell, cocoa_menubar, TRUE, FALSE);
1321 }
1322
1323 extern "C" void
1324 gtk_application_add_app_menu_item (GtkApplicationMenuGroup *group,
1325                                    GtkMenuItem     *menu_item)
1326 {
1327   // we know that the application menu is always the submenu of the first item in the main menu
1328   NSMenu* mainMenu;
1329   NSMenu *appMenu;
1330   NSMenuItem *firstItem;
1331   GList   *list;
1332   gint     index = 0;
1333
1334   mainMenu = [NSApp mainMenu];
1335   firstItem = [ mainMenu itemAtIndex:0];
1336   appMenu = [ firstItem submenu ];
1337
1338   g_return_if_fail (group != NULL);
1339   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
1340
1341   for (list = _gtk_application_menu_groups; list; list = g_list_next (list))
1342     {
1343       GtkApplicationMenuGroup *list_group = (GtkApplicationMenuGroup*) list->data;
1344
1345       index += g_list_length (list_group->items);
1346
1347       /*  adjust index for the separator between groups, but not
1348        *  before the first group
1349        */
1350       if (list_group->items && list->prev)
1351         index++;
1352
1353       if (group == list_group)
1354         {
1355                 /*  add a separator before adding the first item, but not
1356                  *  for the first group
1357                  */
1358                 
1359                 if (!group->items && list->prev)
1360                    {
1361                            [appMenu insertItem:[NSMenuItem separatorItem] atIndex:index+1];
1362                            index++;
1363                    }
1364                 DEBUG ("Add to APP menu bar %s\n", get_menu_label_text (GTK_WIDGET(menu_item), NULL));
1365                 add_menu_item (appMenu, GTK_WIDGET(menu_item), index+1);
1366
1367                 group->items = g_list_append (group->items, menu_item);
1368                 gtk_widget_hide (GTK_WIDGET (menu_item));
1369                 return;
1370         }
1371     }
1372
1373   if (!list)
1374     g_warning ("%s: app menu group %p does not exist",
1375                G_STRFUNC, group);
1376 }
1377
1378 /* application delegate, currently in C++ */
1379
1380 #include <gtkmm2ext/application.h>
1381 #include <glibmm/ustring.h>
1382
1383 namespace Gtk {
1384         namespace Application {
1385                 sigc::signal<void,bool> ActivationChanged;
1386                 sigc::signal<void,const Glib::ustring&> ShouldLoad;
1387                 sigc::signal<void> ShouldQuit;
1388         }
1389 }
1390
1391 @interface GtkApplicationNotificationObject : NSObject {}
1392 - (GtkApplicationNotificationObject*) init; 
1393 @end
1394
1395 @implementation GtkApplicationNotificationObject
1396 - (GtkApplicationNotificationObject*) init
1397 {
1398         self = [ super init ];
1399
1400         if (self) {
1401                 [[NSNotificationCenter defaultCenter] addObserver:self
1402                  selector:@selector(appDidBecomeActive:)
1403                  name:NSApplicationDidBecomeActiveNotification
1404                  object:[NSApplication sharedApplication]];
1405
1406                 [[NSNotificationCenter defaultCenter] addObserver:self
1407                  selector:@selector(appDidBecomeInactive:)
1408                  name:NSApplicationWillResignActiveNotification 
1409                  object:[NSApplication sharedApplication]];
1410         }
1411
1412         return self;
1413 }
1414
1415 - (void)appDidBecomeActive:(NSNotification *) notification
1416 {
1417         UNUSED_PARAMETER(notification);
1418         Gtkmm2ext::Application::instance()->ActivationChanged (true);
1419 }
1420
1421 - (void)appDidBecomeInactive:(NSNotification *) notification
1422 {
1423         UNUSED_PARAMETER(notification);
1424         Gtkmm2ext::Application::instance()->ActivationChanged (false);
1425 }
1426
1427 @end
1428
1429 @interface GtkApplicationDelegate : NSObject
1430 -(BOOL) application:(NSApplication*) app openFile:(NSString*) file;
1431 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) app;
1432 - (void) startApp;
1433 @end
1434
1435 @implementation GtkApplicationDelegate
1436 -(BOOL) application:(NSApplication*) app openFile:(NSString*) file
1437 {
1438         UNUSED_PARAMETER(app);
1439         Glib::ustring utf8_path ([file UTF8String]);
1440         Gtkmm2ext::Application::instance()->ShouldLoad (utf8_path);
1441         return 1;
1442 }
1443 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) app
1444 {
1445         UNUSED_PARAMETER(app);
1446         Gtkmm2ext::Application::instance()->ShouldQuit ();
1447         return NSTerminateCancel;
1448 }
1449 @end
1450
1451
1452 /* Basic setup */
1453
1454 extern "C" int
1455 gtk_application_init ()
1456 {
1457         _main_menubar = [[NSMenu alloc] initWithTitle: @""];
1458
1459         if (!_main_menubar) 
1460                 return -1;
1461
1462         [NSApp setMainMenu: _main_menubar];
1463         create_apple_menu ();
1464         // create_window_menu ();
1465
1466         /* this will stick around for ever ... is that OK ? */
1467
1468         [ [GtkApplicationNotificationObject alloc] init];
1469         [ NSApp setDelegate: [GtkApplicationDelegate new]];
1470
1471         return 0;
1472 }
1473
1474 extern "C" void
1475 gtk_application_ready ()
1476 {
1477         [ NSApp finishLaunching ];
1478         [[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
1479 }
1480
1481 extern "C" void
1482 gtk_application_hide ()
1483 {
1484     [NSApp performSelector:@selector(hide:)];
1485 }
1486
1487 extern "C" void
1488 gtk_application_cleanup()
1489 {
1490         _exiting = 1;
1491
1492         if (_window_menu) {
1493                 [ _window_menu release ];
1494                 _window_menu = 0;
1495         }
1496         if (_app_menu) {
1497                 [ _app_menu release ];
1498                 _app_menu = 0;
1499         }
1500         if (_main_menubar) {
1501                 [ _main_menubar release ];
1502                 _main_menubar = 0;
1503         }
1504 }