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