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