deep, somewhat subtle changes for transport control. Everything should use Session...
[ardour.git] / libs / gtkmm2ext / sync-menu.c
1 /* GTK+ Integration for the Mac OS X Menubar.
2  *
3  * Copyright (C) 2007 Pioneer Research Center USA, Inc.
4  * Copyright (C) 2007 Imendio AB
5  *
6  * For further information, see:
7  * http://developer.imendio.com/projects/gtk-macosx/menubar
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; version 2.1
12  * of the License.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27
28 #include <Carbon/Carbon.h>
29
30 #include <gtkmm2ext/sync-menu.h>
31
32
33 /* TODO
34  *
35  * - Sync adding/removing/reordering items
36  * - Create on demand? (can this be done with gtk+? ie fill in menu
37      items when the menu is opened)
38  * - Figure out what to do per app/window...
39  *
40  */
41
42 #define IGE_QUARTZ_MENU_CREATOR 'IGEC'
43 #define IGE_QUARTZ_ITEM_WIDGET  'IWID'
44
45
46 static void   sync_menu_shell (GtkMenuShell *menu_shell,
47                                MenuRef       carbon_menu,
48                                gboolean      toplevel,
49                                gboolean      debug);
50
51
52 /*
53  * utility functions
54  */
55
56 static GtkWidget *
57 find_menu_label (GtkWidget *widget)
58 {
59   GtkWidget *label = NULL;
60
61   if (GTK_IS_LABEL (widget))
62     return widget;
63
64   if (GTK_IS_CONTAINER (widget))
65     {
66       GList *children;
67       GList *l;
68
69       children = gtk_container_get_children (GTK_CONTAINER (widget));
70
71       for (l = children; l; l = l->next)
72         {
73           label = find_menu_label (l->data);
74           if (label)
75             break;
76         }
77
78       g_list_free (children);
79     }
80
81   return label;
82 }
83
84 static const gchar *
85 get_menu_label_text (GtkWidget  *menu_item,
86                      GtkWidget **label)
87 {
88   GtkWidget *my_label;
89
90   my_label = find_menu_label (menu_item);
91   if (label)
92     *label = my_label;
93
94   if (my_label)
95     return gtk_label_get_text (GTK_LABEL (my_label));
96
97   return NULL;
98 }
99
100 static gboolean
101 accel_find_func (GtkAccelKey *key,
102                  GClosure    *closure,
103                  gpointer     data)
104 {
105   return (GClosure *) data == closure;
106 }
107
108
109 /*
110  * CarbonMenu functions
111  */
112
113 typedef struct
114 {
115   MenuRef menu;
116 } CarbonMenu;
117
118 static GQuark carbon_menu_quark = 0;
119
120 static CarbonMenu *
121 carbon_menu_new (void)
122 {
123   return g_slice_new0 (CarbonMenu);
124 }
125
126 static void
127 carbon_menu_free (CarbonMenu *menu)
128 {
129   g_slice_free (CarbonMenu, menu);
130 }
131
132 static CarbonMenu *
133 carbon_menu_get (GtkWidget *widget)
134 {
135   return g_object_get_qdata (G_OBJECT (widget), carbon_menu_quark);
136 }
137
138 static void
139 carbon_menu_connect (GtkWidget *menu,
140                      MenuRef    menuRef)
141 {
142   CarbonMenu *carbon_menu = carbon_menu_get (menu);
143
144   if (!carbon_menu)
145     {
146       carbon_menu = carbon_menu_new ();
147
148       g_object_set_qdata_full (G_OBJECT (menu), carbon_menu_quark,
149                                carbon_menu,
150                                (GDestroyNotify) carbon_menu_free);
151     }
152
153   carbon_menu->menu = menuRef;
154 }
155
156
157 /*
158  * CarbonMenuItem functions
159  */
160
161 typedef struct
162 {
163   MenuRef        menu;
164   MenuItemIndex  index;
165   MenuRef        submenu;
166   GClosure      *accel_closure;
167 } CarbonMenuItem;
168
169 static GQuark carbon_menu_item_quark = 0;
170
171 static CarbonMenuItem *
172 carbon_menu_item_new (void)
173 {
174   return g_slice_new0 (CarbonMenuItem);
175 }
176
177 static void
178 carbon_menu_item_free (CarbonMenuItem *menu_item)
179 {
180   if (menu_item->accel_closure)
181     g_closure_unref (menu_item->accel_closure);
182
183   g_slice_free (CarbonMenuItem, menu_item);
184 }
185
186 static CarbonMenuItem *
187 carbon_menu_item_get (GtkWidget *widget)
188 {
189   return g_object_get_qdata (G_OBJECT (widget), carbon_menu_item_quark);
190 }
191
192 static void
193 carbon_menu_item_update_state (CarbonMenuItem *carbon_item,
194                                GtkWidget      *widget)
195 {
196   gboolean sensitive;
197   gboolean visible;
198   UInt32   set_attrs = 0;
199   UInt32   clear_attrs = 0;
200
201   g_object_get (widget,
202                 "sensitive", &sensitive,
203                 "visible",   &visible,
204                 NULL);
205
206   if (!sensitive)
207     set_attrs |= kMenuItemAttrDisabled;
208   else
209     clear_attrs |= kMenuItemAttrDisabled;
210
211   if (!visible)
212     set_attrs |= kMenuItemAttrHidden;
213   else
214     clear_attrs |= kMenuItemAttrHidden;
215
216   ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
217                             set_attrs, clear_attrs);
218 }
219
220 static void
221 carbon_menu_item_update_active (CarbonMenuItem *carbon_item,
222                                 GtkWidget      *widget)
223 {
224   gboolean active;
225
226   g_object_get (widget,
227                 "active", &active,
228                 NULL);
229
230   CheckMenuItem (carbon_item->menu, carbon_item->index,
231                  active);
232 }
233
234 static void
235 carbon_menu_item_update_submenu (CarbonMenuItem *carbon_item,
236                                  GtkWidget      *widget)
237 {
238   GtkWidget *submenu;
239
240   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
241
242   if (submenu)
243     {
244       const gchar *label_text;
245       CFStringRef  cfstr = NULL;
246
247       label_text = get_menu_label_text (widget, NULL);
248       if (label_text)
249         cfstr = CFStringCreateWithCString (NULL, label_text,
250                                            kCFStringEncodingUTF8);
251
252       CreateNewMenu (0, 0, &carbon_item->submenu);
253       SetMenuTitleWithCFString (carbon_item->submenu, cfstr);
254       SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
255                                    carbon_item->submenu);
256
257       sync_menu_shell (GTK_MENU_SHELL (submenu), carbon_item->submenu, FALSE, FALSE);
258
259       if (cfstr)
260         CFRelease (cfstr);
261     }
262   else
263     {
264       SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
265                                    NULL);
266       carbon_item->submenu = NULL;
267     }
268 }
269
270 static void
271 carbon_menu_item_update_label (CarbonMenuItem *carbon_item,
272                                GtkWidget      *widget)
273 {
274   const gchar *label_text;
275   CFStringRef  cfstr = NULL;
276
277   label_text = get_menu_label_text (widget, NULL);
278   if (label_text)
279     cfstr = CFStringCreateWithCString (NULL, label_text,
280                                        kCFStringEncodingUTF8);
281
282   SetMenuItemTextWithCFString (carbon_item->menu, carbon_item->index,
283                                cfstr);
284
285   if (cfstr)
286     CFRelease (cfstr);
287 }
288
289 static void
290 carbon_menu_item_update_accelerator (CarbonMenuItem *carbon_item,
291                                      GtkWidget      *widget)
292 {
293   GtkWidget *label;
294
295   get_menu_label_text (widget, &label);
296
297   if (GTK_IS_ACCEL_LABEL (label) &&
298       GTK_ACCEL_LABEL (label)->accel_closure)
299     {
300       GtkAccelKey *key;
301
302       key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
303                                   accel_find_func,
304                                   GTK_ACCEL_LABEL (label)->accel_closure);
305
306       if (key            &&
307           key->accel_key &&
308           key->accel_flags & GTK_ACCEL_VISIBLE)
309         {
310           GdkDisplay      *display = gtk_widget_get_display (widget);
311           GdkKeymap       *keymap  = gdk_keymap_get_for_display (display);
312           GdkKeymapKey    *keys;
313           gint             n_keys;
314           gint             use_command;
315           gboolean         add_modifiers = FALSE;
316           UInt8 modifiers = 0; /* implies Command key */
317
318           if (gdk_keymap_get_entries_for_keyval (keymap, key->accel_key,
319                                                  &keys, &n_keys) == 0)
320             {
321                   gint realkey = -1;
322
323                   switch (key->accel_key) {
324                   case GDK_rightarrow:
325                   case GDK_Right:
326                           realkey = kRightArrowCharCode;
327                           break;
328                   case GDK_leftarrow:
329                   case GDK_Left:
330                           realkey = kLeftArrowCharCode;
331                           break;
332                   case GDK_uparrow:
333                   case GDK_Up:
334                           realkey = kUpArrowCharCode;
335                           break;
336                   case GDK_downarrow:
337                   case GDK_Down:
338                           realkey = kDownArrowCharCode;
339                           break;
340                   default:
341                           break;
342                   }
343           
344                   if (realkey != -1) {
345                           SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
346                                                  false, realkey);
347                           add_modifiers = TRUE;
348                   }
349
350             } else {
351
352                   SetMenuItemCommandKey (carbon_item->menu, carbon_item->index, true, keys[0].keycode);
353                   if (keys[0].level == 1)
354                     { 
355                        /* regular key, but it needs shift to make it work */
356                        modifiers |= kMenuShiftModifier;
357                     }
358
359                   g_free (keys);
360                   add_modifiers = TRUE;
361             }
362
363           if (add_modifiers)
364             {
365               use_command = 0;
366
367               if (key->accel_mods)
368                 {
369                   if (key->accel_mods & GDK_SHIFT_MASK) {
370                     modifiers |= kMenuShiftModifier;
371                   }
372
373                   /* gdk/quartz maps Alt/Option to Mod1 */
374
375                   if (key->accel_mods & (GDK_MOD1_MASK)) {
376                     modifiers |= kMenuOptionModifier;
377                   }
378
379                   if (key->accel_mods & GDK_CONTROL_MASK) {
380                     modifiers |= kMenuControlModifier;
381                   }
382
383                   /* gdk/quartz maps Command to Meta */
384                   
385                   if (key->accel_mods & GDK_META_MASK) {
386                           use_command = 1;
387                   }
388                 }  
389
390               if (!use_command)
391                 modifiers |= kMenuNoCommandModifier;
392
393               SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
394                                     modifiers);
395               return;
396             }
397         }
398     }
399
400   /*  otherwise, clear the menu shortcut  */
401   SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
402                         kMenuNoModifiers | kMenuNoCommandModifier);
403   ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
404                             0, kMenuItemAttrUseVirtualKey);
405   SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
406                          false, 0);
407 }
408
409 static void
410 carbon_menu_item_accel_changed (GtkAccelGroup   *accel_group,
411                                 guint            keyval,
412                                 GdkModifierType  modifier,
413                                 GClosure        *accel_closure,
414                                 GtkWidget       *widget)
415 {
416   CarbonMenuItem *carbon_item = carbon_menu_item_get (widget);
417   GtkWidget      *label;
418
419   get_menu_label_text (widget, &label);
420
421   if (GTK_IS_ACCEL_LABEL (label) &&
422       GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
423     carbon_menu_item_update_accelerator (carbon_item, widget);
424 }
425
426 static void
427 carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
428                                        GtkWidget      *widget)
429 {
430   GtkAccelGroup *group;
431   GtkWidget     *label;
432
433   get_menu_label_text (widget, &label);
434
435   if (carbon_item->accel_closure)
436     {
437       group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
438
439       g_signal_handlers_disconnect_by_func (group,
440                                             carbon_menu_item_accel_changed,
441                                             widget);
442
443       g_closure_unref (carbon_item->accel_closure);
444       carbon_item->accel_closure = NULL;
445     }
446
447   if (GTK_IS_ACCEL_LABEL (label))
448     carbon_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
449
450   if (carbon_item->accel_closure)
451     {
452       g_closure_ref (carbon_item->accel_closure);
453
454       group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
455
456       g_signal_connect_object (group, "accel-changed",
457                                G_CALLBACK (carbon_menu_item_accel_changed),
458                                widget, 0);
459     }
460
461   carbon_menu_item_update_accelerator (carbon_item, widget);
462 }
463
464 static void
465 carbon_menu_item_notify (GObject        *object,
466                          GParamSpec     *pspec,
467                          CarbonMenuItem *carbon_item)
468 {
469   if (!strcmp (pspec->name, "sensitive") ||
470       !strcmp (pspec->name, "visible"))
471     {
472       carbon_menu_item_update_state (carbon_item, GTK_WIDGET (object));
473     }
474   else if (!strcmp (pspec->name, "active"))
475     {
476       carbon_menu_item_update_active (carbon_item, GTK_WIDGET (object));
477     }
478   else if (!strcmp (pspec->name, "submenu"))
479     {
480       carbon_menu_item_update_submenu (carbon_item, GTK_WIDGET (object));
481     }
482 }
483
484 static void
485 carbon_menu_item_notify_label (GObject    *object,
486                                GParamSpec *pspec,
487                                gpointer    data)
488 {
489   CarbonMenuItem *carbon_item = carbon_menu_item_get (GTK_WIDGET (object));
490
491   if (!strcmp (pspec->name, "label"))
492     {
493       carbon_menu_item_update_label (carbon_item,
494                                      GTK_WIDGET (object));
495     }
496   else if (!strcmp (pspec->name, "accel-closure"))
497     {
498       carbon_menu_item_update_accel_closure (carbon_item,
499                                              GTK_WIDGET (object));
500     }
501 }
502
503 static CarbonMenuItem *
504 carbon_menu_item_connect (GtkWidget     *menu_item,
505                           GtkWidget     *label,
506                           MenuRef        menu,
507                           MenuItemIndex  index)
508 {
509   CarbonMenuItem *carbon_item = carbon_menu_item_get (menu_item);
510
511   if (!carbon_item)
512     {
513       carbon_item = carbon_menu_item_new ();
514
515       g_object_set_qdata_full (G_OBJECT (menu_item), carbon_menu_item_quark,
516                                carbon_item,
517                                (GDestroyNotify) carbon_menu_item_free);
518
519       g_signal_connect (menu_item, "notify",
520                         G_CALLBACK (carbon_menu_item_notify),
521                         carbon_item);
522
523       if (label)
524         g_signal_connect_swapped (label, "notify::label",
525                                   G_CALLBACK (carbon_menu_item_notify_label),
526                                   menu_item);
527     }
528
529   carbon_item->menu  = menu;
530   carbon_item->index = index;
531
532   return carbon_item;
533 }
534
535
536 /*
537  * carbon event handler
538  */
539
540 static int _in_carbon_menu_event_handler = 0;
541
542 int 
543 gdk_quartz_in_carbon_menu_event_handler ()
544 {
545         return _in_carbon_menu_event_handler;
546 }
547
548 static gboolean
549 dummy_gtk_menu_item_activate (gpointer *arg)
550 {
551         gtk_menu_item_activate (GTK_MENU_ITEM(arg));
552         return FALSE;
553 }
554
555 static OSStatus
556 menu_event_handler_func (EventHandlerCallRef  event_handler_call_ref,
557                          EventRef             event_ref,
558                          void                *data)
559 {
560   UInt32  event_class = GetEventClass (event_ref);
561   UInt32  event_kind = GetEventKind (event_ref);
562   MenuRef menu_ref;
563   OSStatus ret;
564
565   _in_carbon_menu_event_handler = 1;
566
567   switch (event_class)
568     {
569     case kEventClassCommand:
570       /* This is called when activating (is that the right GTK+ term?)
571        * a menu item.
572        */
573       if (event_kind == kEventCommandProcess)
574         {
575           HICommand command;
576           OSStatus  err;
577
578           /*g_printerr ("Menu: kEventClassCommand/kEventCommandProcess\n");*/
579
580           err = GetEventParameter (event_ref, kEventParamDirectObject,
581                                    typeHICommand, 0,
582                                    sizeof (command), 0, &command);
583
584           if (err == noErr)
585             {
586               GtkWidget *widget = NULL;
587
588               /* Get any GtkWidget associated with the item. */
589               err = GetMenuItemProperty (command.menu.menuRef,
590                                          command.menu.menuItemIndex,
591                                          IGE_QUARTZ_MENU_CREATOR,
592                                          IGE_QUARTZ_ITEM_WIDGET,
593                                          sizeof (widget), 0, &widget);
594               if (err == noErr && GTK_IS_WIDGET (widget))
595                 {
596                         g_idle_add ((GSourceFunc) dummy_gtk_menu_item_activate, widget);
597                   // gtk_menu_item_activate (GTK_MENU_ITEM (widget));
598                   _in_carbon_menu_event_handler = 0;
599                   return noErr;
600                 }
601             }
602         }
603       break;
604
605     case kEventClassMenu:
606       GetEventParameter (event_ref,
607                          kEventParamDirectObject,
608                          typeMenuRef,
609                          NULL,
610                          sizeof (menu_ref),
611                          NULL,
612                          &menu_ref);
613
614       switch (event_kind)
615         {
616         case kEventMenuTargetItem:
617           /* This is called when an item is selected (what is the
618            * GTK+ term? prelight?)
619            */
620           /*g_printerr ("kEventClassMenu/kEventMenuTargetItem\n");*/
621           break;
622
623         case kEventMenuOpening:
624           /* Is it possible to dynamically build the menu here? We
625            * can at least set visibility/sensitivity.
626            */
627           /*g_printerr ("kEventClassMenu/kEventMenuOpening\n");*/
628           break;
629
630         case kEventMenuClosed:
631           /*g_printerr ("kEventClassMenu/kEventMenuClosed\n");*/
632           break;
633
634         default:
635           break;
636         }
637
638       break;
639
640     default:
641       break;
642     }
643
644   ret = CallNextEventHandler (event_handler_call_ref, event_ref);
645   _in_carbon_menu_event_handler = 0;
646   return ret;
647 }
648
649 static void
650 setup_menu_event_handler (void)
651 {
652   EventHandlerUPP menu_event_handler_upp;
653   EventHandlerRef menu_event_handler_ref;
654   const EventTypeSpec menu_events[] = {
655     { kEventClassCommand, kEventCommandProcess },
656     { kEventClassMenu, kEventMenuTargetItem },
657     { kEventClassMenu, kEventMenuOpening },
658     { kEventClassMenu, kEventMenuClosed }
659   };
660
661   /* FIXME: We might have to install one per window? */
662
663   menu_event_handler_upp = NewEventHandlerUPP (menu_event_handler_func);
664   InstallEventHandler (GetApplicationEventTarget (), menu_event_handler_upp,
665                        GetEventTypeCount (menu_events), menu_events, 0,
666                        &menu_event_handler_ref);
667
668 #if 0
669   /* FIXME: Remove the handler with: */
670   RemoveEventHandler(menu_event_handler_ref);
671   DisposeEventHandlerUPP(menu_event_handler_upp);
672 #endif
673 }
674
675 static void
676 sync_menu_shell (GtkMenuShell *menu_shell,
677                  MenuRef       carbon_menu,
678                  gboolean      toplevel,
679                  gboolean      debug)
680 {
681   GList         *children;
682   GList         *l;
683   MenuItemIndex  carbon_index = 1;
684
685   if (debug)
686     g_printerr ("%s: syncing shell %p\n", G_STRFUNC, menu_shell);
687
688   carbon_menu_connect (GTK_WIDGET (menu_shell), carbon_menu);
689
690   children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
691
692   for (l = children; l; l = l->next)
693     {
694       GtkWidget      *menu_item = l->data;
695       CarbonMenuItem *carbon_item;
696
697       if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
698         continue;
699
700       if (toplevel && g_object_get_data (G_OBJECT (menu_item),
701                                          "gtk-empty-menu-item"))
702         continue;
703
704       carbon_item = carbon_menu_item_get (menu_item);
705
706       if (debug)
707         g_printerr ("%s: carbon_item %d for menu_item %d (%s, %s)\n",
708                     G_STRFUNC, carbon_item ? carbon_item->index : -1,
709                     carbon_index, get_menu_label_text (menu_item, NULL),
710                     g_type_name (G_TYPE_FROM_INSTANCE (menu_item)));
711
712       if (carbon_item && carbon_item->index != carbon_index)
713         {
714           if (debug)
715             g_printerr ("%s:   -> not matching, deleting\n", G_STRFUNC);
716
717           DeleteMenuItem (carbon_item->menu, carbon_index);
718           carbon_item = NULL;
719         }
720
721       if (!carbon_item)
722         {
723           GtkWidget          *label      = NULL;
724           const gchar        *label_text;
725           CFStringRef         cfstr      = NULL;
726           MenuItemAttributes  attributes = 0;
727
728           if (debug)
729             g_printerr ("%s:   -> creating new\n", G_STRFUNC);
730
731           label_text = get_menu_label_text (menu_item, &label);
732           if (label_text)
733             cfstr = CFStringCreateWithCString (NULL, label_text,
734                                                kCFStringEncodingUTF8);
735
736           if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
737             attributes |= kMenuItemAttrSeparator;
738
739           if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
740             attributes |= kMenuItemAttrDisabled;
741
742           if (!GTK_WIDGET_VISIBLE (menu_item))
743             attributes |= kMenuItemAttrHidden;
744
745           InsertMenuItemTextWithCFString (carbon_menu, cfstr,
746                                           carbon_index - 1,
747                                           attributes, 0);
748           SetMenuItemProperty (carbon_menu, carbon_index,
749                                IGE_QUARTZ_MENU_CREATOR,
750                                IGE_QUARTZ_ITEM_WIDGET,
751                                sizeof (menu_item), &menu_item);
752
753           if (cfstr)
754             CFRelease (cfstr);
755
756           carbon_item = carbon_menu_item_connect (menu_item, label,
757                                                   carbon_menu,
758                                                   carbon_index);
759
760           if (GTK_IS_CHECK_MENU_ITEM (menu_item))
761             carbon_menu_item_update_active (carbon_item, menu_item);
762
763           carbon_menu_item_update_accel_closure (carbon_item, menu_item);
764
765           if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
766             carbon_menu_item_update_submenu (carbon_item, menu_item);
767         }
768
769       carbon_index++;
770     }
771
772   g_list_free (children);
773 }
774
775
776 static gulong emission_hook_id = 0;
777
778 static gboolean
779 parent_set_emission_hook (GSignalInvocationHint *ihint,
780                           guint                  n_param_values,
781                           const GValue          *param_values,
782                           gpointer               data)
783 {
784   GtkWidget *instance = g_value_get_object (param_values);
785
786   if (GTK_IS_MENU_ITEM (instance))
787     {
788       GtkWidget *previous_parent = g_value_get_object (param_values + 1);
789       GtkWidget *menu_shell      = NULL;
790
791       if (GTK_IS_MENU_SHELL (previous_parent))
792         {
793           menu_shell = previous_parent;
794         }
795       else if (GTK_IS_MENU_SHELL (instance->parent))
796         {
797           menu_shell = instance->parent;
798         }
799
800       if (menu_shell)
801         {
802           CarbonMenu *carbon_menu = carbon_menu_get (menu_shell);
803
804           if (carbon_menu)
805             {
806 #if 0
807               g_printerr ("%s: item %s %p (%s, %s)\n", G_STRFUNC,
808                           previous_parent ? "removed from" : "added to",
809                           menu_shell,
810                           get_menu_label_text (instance, NULL),
811                           g_type_name (G_TYPE_FROM_INSTANCE (instance)));
812 #endif
813
814               sync_menu_shell (GTK_MENU_SHELL (menu_shell),
815                                carbon_menu->menu,
816                                carbon_menu->menu == (MenuRef) data,
817                                FALSE);
818             }
819         }
820     }
821
822   return TRUE;
823 }
824
825 static void
826 parent_set_emission_hook_remove (GtkWidget *widget,
827                                  gpointer   data)
828 {
829   g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
830                                                   GTK_TYPE_WIDGET),
831                                  emission_hook_id);
832 }
833
834
835 /*
836  * public functions
837  */
838
839 void
840 ige_mac_menu_set_menu_bar (GtkMenuShell *menu_shell)
841 {
842   MenuRef carbon_menubar;
843
844   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
845
846   if (carbon_menu_quark == 0)
847     carbon_menu_quark = g_quark_from_static_string ("CarbonMenu");
848
849   if (carbon_menu_item_quark == 0)
850     carbon_menu_item_quark = g_quark_from_static_string ("CarbonMenuItem");
851
852   CreateNewMenu (0 /*id*/, 0 /*options*/, &carbon_menubar);
853   SetRootMenu (carbon_menubar);
854
855   setup_menu_event_handler ();
856
857   emission_hook_id =
858     g_signal_add_emission_hook (g_signal_lookup ("parent-set",
859                                                  GTK_TYPE_WIDGET),
860                                 0,
861                                 parent_set_emission_hook,
862                                 carbon_menubar, NULL);
863
864   g_signal_connect (menu_shell, "destroy",
865                     G_CALLBACK (parent_set_emission_hook_remove),
866                     NULL);
867
868   sync_menu_shell (menu_shell, carbon_menubar, TRUE, FALSE);
869 }
870
871 void
872 ige_mac_menu_set_quit_menu_item (GtkMenuItem *menu_item)
873 {
874   MenuRef       appmenu;
875   MenuItemIndex index;
876
877   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
878
879   if (GetIndMenuItemWithCommandID (NULL, kHICommandQuit, 1,
880                                    &appmenu, &index) == noErr)
881     {
882       SetMenuItemCommandID (appmenu, index, 0);
883       SetMenuItemProperty (appmenu, index,
884                            IGE_QUARTZ_MENU_CREATOR,
885                            IGE_QUARTZ_ITEM_WIDGET,
886                            sizeof (menu_item), &menu_item);
887
888       gtk_widget_hide (GTK_WIDGET (menu_item));
889     }
890 }
891
892
893 struct _IgeMacMenuGroup
894 {
895   GList *items;
896 };
897
898 static GList *app_menu_groups = NULL;
899
900 IgeMacMenuGroup *
901 ige_mac_menu_add_app_menu_group (void)
902 {
903   IgeMacMenuGroup *group = g_slice_new0 (IgeMacMenuGroup);
904
905   app_menu_groups = g_list_append (app_menu_groups, group);
906
907   return group;
908 }
909
910 void
911 ige_mac_menu_add_app_menu_item (IgeMacMenuGroup *group,
912                                 GtkMenuItem     *menu_item,
913                                 const gchar     *label)
914 {
915   MenuRef  appmenu;
916   GList   *list;
917   gint     index = 0;
918
919   g_return_if_fail (group != NULL);
920   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
921
922   if (GetIndMenuItemWithCommandID (NULL, kHICommandHide, 1,
923                                    &appmenu, NULL) != noErr)
924     {
925       g_warning ("%s: retrieving app menu failed",
926                  G_STRFUNC);
927       return;
928     }
929
930   for (list = app_menu_groups; list; list = g_list_next (list))
931     {
932       IgeMacMenuGroup *list_group = list->data;
933
934       index += g_list_length (list_group->items);
935
936       /*  adjust index for the separator between groups, but not
937        *  before the first group
938        */
939       if (list_group->items && list->prev)
940         index++;
941
942       if (group == list_group)
943         {
944           CFStringRef cfstr;
945
946           /*  add a separator before adding the first item, but not
947            *  for the first group
948            */
949           if (!group->items && list->prev)
950             {
951               InsertMenuItemTextWithCFString (appmenu, NULL, index,
952                                               kMenuItemAttrSeparator, 0);
953               index++;
954             }
955
956           if (!label)
957             label = get_menu_label_text (GTK_WIDGET (menu_item), NULL);
958
959           cfstr = CFStringCreateWithCString (NULL, label,
960                                              kCFStringEncodingUTF8);
961
962           InsertMenuItemTextWithCFString (appmenu, cfstr, index, 0, 0);
963           SetMenuItemProperty (appmenu, index + 1,
964                                IGE_QUARTZ_MENU_CREATOR,
965                                IGE_QUARTZ_ITEM_WIDGET,
966                                sizeof (menu_item), &menu_item);
967
968           CFRelease (cfstr);
969
970           gtk_widget_hide (GTK_WIDGET (menu_item));
971
972           group->items = g_list_append (group->items, menu_item);
973
974           return;
975         }
976     }
977
978   if (!list)
979     g_warning ("%s: app menu group %p does not exist",
980                G_STRFUNC, group);
981 }