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