Update classkeys to match new total LuaSignal count (windows only)
[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                  SetMenuItemCommandKey (carbon_item->menu, carbon_item->index, true, keys[0].keycode);
352                  if (keys[0].level == 1) {
353                          /* regular key, but it needs shift to make it work */
354                          modifiers |= kMenuShiftModifier;
355                  }
356
357                  g_free (keys);
358                  add_modifiers = TRUE;
359             }
360
361           if (add_modifiers)
362             {
363              UInt8 modifiers = 0; /* implies Command key */
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
396               return;
397             }
398         }
399     }
400
401   /*  otherwise, clear the menu shortcut  */
402   SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
403                         kMenuNoModifiers | kMenuNoCommandModifier);
404   ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
405                             0, kMenuItemAttrUseVirtualKey);
406   SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
407                          false, 0);
408 }
409
410 static void
411 carbon_menu_item_accel_changed (GtkAccelGroup   *accel_group,
412                                 guint            keyval,
413                                 GdkModifierType  modifier,
414                                 GClosure        *accel_closure,
415                                 GtkWidget       *widget)
416 {
417   CarbonMenuItem *carbon_item = carbon_menu_item_get (widget);
418   GtkWidget      *label;
419
420   get_menu_label_text (widget, &label);
421
422   if (GTK_IS_ACCEL_LABEL (label) &&
423       GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
424     carbon_menu_item_update_accelerator (carbon_item, widget);
425 }
426
427 static void
428 carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
429                                        GtkWidget      *widget)
430 {
431   GtkAccelGroup *group;
432   GtkWidget     *label;
433
434   get_menu_label_text (widget, &label);
435
436   if (carbon_item->accel_closure)
437     {
438       group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
439
440       g_signal_handlers_disconnect_by_func (group,
441                                             carbon_menu_item_accel_changed,
442                                             widget);
443
444       g_closure_unref (carbon_item->accel_closure);
445       carbon_item->accel_closure = NULL;
446     }
447
448   if (GTK_IS_ACCEL_LABEL (label))
449     carbon_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
450
451   if (carbon_item->accel_closure)
452     {
453       g_closure_ref (carbon_item->accel_closure);
454
455       group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
456
457       g_signal_connect_object (group, "accel-changed",
458                                G_CALLBACK (carbon_menu_item_accel_changed),
459                                widget, 0);
460     }
461
462   carbon_menu_item_update_accelerator (carbon_item, widget);
463 }
464
465 static void
466 carbon_menu_item_notify (GObject        *object,
467                          GParamSpec     *pspec,
468                          CarbonMenuItem *carbon_item)
469 {
470   if (!strcmp (pspec->name, "sensitive") ||
471       !strcmp (pspec->name, "visible"))
472     {
473       carbon_menu_item_update_state (carbon_item, GTK_WIDGET (object));
474     }
475   else if (!strcmp (pspec->name, "active"))
476     {
477       carbon_menu_item_update_active (carbon_item, GTK_WIDGET (object));
478     }
479   else if (!strcmp (pspec->name, "submenu"))
480     {
481       carbon_menu_item_update_submenu (carbon_item, GTK_WIDGET (object));
482     }
483 }
484
485 static void
486 carbon_menu_item_notify_label (GObject    *object,
487                                GParamSpec *pspec,
488                                gpointer    data)
489 {
490   CarbonMenuItem *carbon_item = carbon_menu_item_get (GTK_WIDGET (object));
491
492   if (!strcmp (pspec->name, "label"))
493     {
494       carbon_menu_item_update_label (carbon_item,
495                                      GTK_WIDGET (object));
496     }
497   else if (!strcmp (pspec->name, "accel-closure"))
498     {
499       carbon_menu_item_update_accel_closure (carbon_item,
500                                              GTK_WIDGET (object));
501     }
502 }
503
504 static CarbonMenuItem *
505 carbon_menu_item_connect (GtkWidget     *menu_item,
506                           GtkWidget     *label,
507                           MenuRef        menu,
508                           MenuItemIndex  index)
509 {
510   CarbonMenuItem *carbon_item = carbon_menu_item_get (menu_item);
511
512   if (!carbon_item)
513     {
514       carbon_item = carbon_menu_item_new ();
515
516       g_object_set_qdata_full (G_OBJECT (menu_item), carbon_menu_item_quark,
517                                carbon_item,
518                                (GDestroyNotify) carbon_menu_item_free);
519
520       g_signal_connect (menu_item, "notify",
521                         G_CALLBACK (carbon_menu_item_notify),
522                         carbon_item);
523
524       if (label)
525         g_signal_connect_swapped (label, "notify::label",
526                                   G_CALLBACK (carbon_menu_item_notify_label),
527                                   menu_item);
528     }
529
530   carbon_item->menu  = menu;
531   carbon_item->index = index;
532
533   return carbon_item;
534 }
535
536
537 /*
538  * carbon event handler
539  */
540
541 static int _in_carbon_menu_event_handler = 0;
542
543 int
544 gdk_quartz_in_carbon_menu_event_handler ()
545 {
546         return _in_carbon_menu_event_handler;
547 }
548
549 static gboolean
550 dummy_gtk_menu_item_activate (gpointer *arg)
551 {
552         gtk_menu_item_activate (GTK_MENU_ITEM(arg));
553         return FALSE;
554 }
555
556 static OSStatus
557 menu_event_handler_func (EventHandlerCallRef  event_handler_call_ref,
558                          EventRef             event_ref,
559                          void                *data)
560 {
561   UInt32  event_class = GetEventClass (event_ref);
562   UInt32  event_kind = GetEventKind (event_ref);
563   MenuRef menu_ref;
564   OSStatus ret;
565
566   _in_carbon_menu_event_handler = 1;
567
568   switch (event_class)
569     {
570     case kEventClassCommand:
571       /* This is called when activating (is that the right GTK+ term?)
572        * a menu item.
573        */
574       if (event_kind == kEventCommandProcess)
575         {
576           HICommand command;
577           OSStatus  err;
578
579           /*g_printerr ("Menu: kEventClassCommand/kEventCommandProcess\n");*/
580
581           err = GetEventParameter (event_ref, kEventParamDirectObject,
582                                    typeHICommand, 0,
583                                    sizeof (command), 0, &command);
584
585           if (err == noErr)
586             {
587               GtkWidget *widget = NULL;
588
589               /* Get any GtkWidget associated with the item. */
590               err = GetMenuItemProperty (command.menu.menuRef,
591                                          command.menu.menuItemIndex,
592                                          IGE_QUARTZ_MENU_CREATOR,
593                                          IGE_QUARTZ_ITEM_WIDGET,
594                                          sizeof (widget), 0, &widget);
595               if (err == noErr && GTK_IS_WIDGET (widget))
596                 {
597                   g_idle_add ((GSourceFunc) dummy_gtk_menu_item_activate, widget);
598                   // gtk_menu_item_activate (GTK_MENU_ITEM (widget));
599                   _in_carbon_menu_event_handler = 0;
600                   return noErr;
601                 }
602             }
603         }
604       break;
605
606     case kEventClassMenu:
607       GetEventParameter (event_ref,
608                          kEventParamDirectObject,
609                          typeMenuRef,
610                          NULL,
611                          sizeof (menu_ref),
612                          NULL,
613                          &menu_ref);
614
615       switch (event_kind)
616         {
617         case kEventMenuTargetItem:
618           /* This is called when an item is selected (what is the
619            * GTK+ term? prelight?)
620            */
621           /*g_printerr ("kEventClassMenu/kEventMenuTargetItem\n");*/
622           break;
623
624         case kEventMenuOpening:
625           /* Is it possible to dynamically build the menu here? We
626            * can at least set visibility/sensitivity.
627            */
628           /*g_printerr ("kEventClassMenu/kEventMenuOpening\n");*/
629           break;
630
631         case kEventMenuClosed:
632           /*g_printerr ("kEventClassMenu/kEventMenuClosed\n");*/
633           break;
634
635         default:
636           break;
637         }
638
639       break;
640
641     default:
642       break;
643     }
644
645   ret = CallNextEventHandler (event_handler_call_ref, event_ref);
646   _in_carbon_menu_event_handler = 0;
647   return ret;
648 }
649
650 static void
651 setup_menu_event_handler (void)
652 {
653   EventHandlerUPP menu_event_handler_upp;
654   EventHandlerRef menu_event_handler_ref;
655   const EventTypeSpec menu_events[] = {
656     { kEventClassCommand, kEventCommandProcess },
657     { kEventClassMenu, kEventMenuTargetItem },
658     { kEventClassMenu, kEventMenuOpening },
659     { kEventClassMenu, kEventMenuClosed }
660   };
661
662   /* FIXME: We might have to install one per window? */
663
664   menu_event_handler_upp = NewEventHandlerUPP (menu_event_handler_func);
665   InstallEventHandler (GetApplicationEventTarget (), menu_event_handler_upp,
666                        GetEventTypeCount (menu_events), menu_events, 0,
667                        &menu_event_handler_ref);
668
669 #if 0
670   /* FIXME: Remove the handler with: */
671   RemoveEventHandler(menu_event_handler_ref);
672   DisposeEventHandlerUPP(menu_event_handler_upp);
673 #endif
674 }
675
676 static void
677 sync_menu_shell (GtkMenuShell *menu_shell,
678                  MenuRef       carbon_menu,
679                  gboolean      toplevel,
680                  gboolean      debug)
681 {
682   GList         *children;
683   GList         *l;
684   MenuItemIndex  carbon_index = 1;
685
686   if (debug)
687     g_printerr ("%s: syncing shell %p\n", G_STRFUNC, menu_shell);
688
689   carbon_menu_connect (GTK_WIDGET (menu_shell), carbon_menu);
690
691   children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
692
693   for (l = children; l; l = l->next)
694     {
695       GtkWidget      *menu_item = l->data;
696       CarbonMenuItem *carbon_item;
697
698       if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
699         continue;
700
701       if (toplevel && g_object_get_data (G_OBJECT (menu_item),
702                                          "gtk-empty-menu-item"))
703         continue;
704
705       carbon_item = carbon_menu_item_get (menu_item);
706
707       if (debug)
708         g_printerr ("%s: carbon_item %d for menu_item %d (%s, %s)\n",
709                     G_STRFUNC, carbon_item ? carbon_item->index : -1,
710                     carbon_index, get_menu_label_text (menu_item, NULL),
711                     g_type_name (G_TYPE_FROM_INSTANCE (menu_item)));
712
713       if (carbon_item && carbon_item->index != carbon_index)
714         {
715           if (debug)
716             g_printerr ("%s:   -> not matching, deleting\n", G_STRFUNC);
717
718           DeleteMenuItem (carbon_item->menu, carbon_index);
719           carbon_item = NULL;
720         }
721
722       if (!carbon_item)
723         {
724           GtkWidget          *label      = NULL;
725           const gchar        *label_text;
726           CFStringRef         cfstr      = NULL;
727           MenuItemAttributes  attributes = 0;
728
729           if (debug)
730             g_printerr ("%s:   -> creating new\n", G_STRFUNC);
731
732           label_text = get_menu_label_text (menu_item, &label);
733           if (label_text)
734             cfstr = CFStringCreateWithCString (NULL, label_text,
735                                                kCFStringEncodingUTF8);
736
737           if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
738             attributes |= kMenuItemAttrSeparator;
739
740           if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
741             attributes |= kMenuItemAttrDisabled;
742
743           if (!GTK_WIDGET_VISIBLE (menu_item))
744             attributes |= kMenuItemAttrHidden;
745
746           InsertMenuItemTextWithCFString (carbon_menu, cfstr,
747                                           carbon_index - 1,
748                                           attributes, 0);
749           SetMenuItemProperty (carbon_menu, carbon_index,
750                                IGE_QUARTZ_MENU_CREATOR,
751                                IGE_QUARTZ_ITEM_WIDGET,
752                                sizeof (menu_item), &menu_item);
753
754           if (cfstr)
755             CFRelease (cfstr);
756
757           carbon_item = carbon_menu_item_connect (menu_item, label,
758                                                   carbon_menu,
759                                                   carbon_index);
760
761           if (GTK_IS_CHECK_MENU_ITEM (menu_item))
762             carbon_menu_item_update_active (carbon_item, menu_item);
763
764           carbon_menu_item_update_accel_closure (carbon_item, menu_item);
765
766           if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
767             carbon_menu_item_update_submenu (carbon_item, menu_item);
768         }
769
770       carbon_index++;
771     }
772
773   g_list_free (children);
774 }
775
776
777 static gulong emission_hook_id = 0;
778
779 static gboolean
780 parent_set_emission_hook (GSignalInvocationHint *ihint,
781                           guint                  n_param_values,
782                           const GValue          *param_values,
783                           gpointer               data)
784 {
785   GtkWidget *instance = g_value_get_object (param_values);
786
787   if (GTK_IS_MENU_ITEM (instance))
788     {
789       GtkWidget *previous_parent = g_value_get_object (param_values + 1);
790       GtkWidget *menu_shell      = NULL;
791
792       if (GTK_IS_MENU_SHELL (previous_parent))
793         {
794           menu_shell = previous_parent;
795         }
796       else if (GTK_IS_MENU_SHELL (instance->parent))
797         {
798           menu_shell = instance->parent;
799         }
800
801       if (menu_shell)
802         {
803           CarbonMenu *carbon_menu = carbon_menu_get (menu_shell);
804
805           if (carbon_menu)
806             {
807 #if 0
808               g_printerr ("%s: item %s %p (%s, %s)\n", G_STRFUNC,
809                           previous_parent ? "removed from" : "added to",
810                           menu_shell,
811                           get_menu_label_text (instance, NULL),
812                           g_type_name (G_TYPE_FROM_INSTANCE (instance)));
813 #endif
814
815               sync_menu_shell (GTK_MENU_SHELL (menu_shell),
816                                carbon_menu->menu,
817                                carbon_menu->menu == (MenuRef) data,
818                                FALSE);
819             }
820         }
821     }
822
823   return TRUE;
824 }
825
826 static void
827 parent_set_emission_hook_remove (GtkWidget *widget,
828                                  gpointer   data)
829 {
830   g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
831                                                   GTK_TYPE_WIDGET),
832                                  emission_hook_id);
833 }
834
835
836 /*
837  * public functions
838  */
839
840 void
841 ige_mac_menu_set_menu_bar (GtkMenuShell *menu_shell)
842 {
843   MenuRef carbon_menubar;
844
845   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
846
847   if (carbon_menu_quark == 0)
848     carbon_menu_quark = g_quark_from_static_string ("CarbonMenu");
849
850   if (carbon_menu_item_quark == 0)
851     carbon_menu_item_quark = g_quark_from_static_string ("CarbonMenuItem");
852
853   CreateNewMenu (0 /*id*/, 0 /*options*/, &carbon_menubar);
854   SetRootMenu (carbon_menubar);
855
856   setup_menu_event_handler ();
857
858   emission_hook_id =
859     g_signal_add_emission_hook (g_signal_lookup ("parent-set",
860                                                  GTK_TYPE_WIDGET),
861                                 0,
862                                 parent_set_emission_hook,
863                                 carbon_menubar, NULL);
864
865   g_signal_connect (menu_shell, "destroy",
866                     G_CALLBACK (parent_set_emission_hook_remove),
867                     NULL);
868
869   sync_menu_shell (menu_shell, carbon_menubar, TRUE, FALSE);
870 }
871
872 void
873 ige_mac_menu_set_quit_menu_item (GtkMenuItem *menu_item)
874 {
875   MenuRef       appmenu;
876   MenuItemIndex index;
877
878   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
879
880   if (GetIndMenuItemWithCommandID (NULL, kHICommandQuit, 1,
881                                    &appmenu, &index) == noErr)
882     {
883       SetMenuItemCommandID (appmenu, index, 0);
884       SetMenuItemProperty (appmenu, index,
885                            IGE_QUARTZ_MENU_CREATOR,
886                            IGE_QUARTZ_ITEM_WIDGET,
887                            sizeof (menu_item), &menu_item);
888
889       gtk_widget_hide (GTK_WIDGET (menu_item));
890     }
891 }
892
893
894 struct _IgeMacMenuGroup
895 {
896   GList *items;
897 };
898
899 static GList *app_menu_groups = NULL;
900
901 IgeMacMenuGroup *
902 ige_mac_menu_add_app_menu_group (void)
903 {
904   IgeMacMenuGroup *group = g_slice_new0 (IgeMacMenuGroup);
905
906   app_menu_groups = g_list_append (app_menu_groups, group);
907
908   return group;
909 }
910
911 void
912 ige_mac_menu_add_app_menu_item (IgeMacMenuGroup *group,
913                                 GtkMenuItem     *menu_item,
914                                 const gchar     *label)
915 {
916   MenuRef  appmenu;
917   GList   *list;
918   gint     index = 0;
919
920   g_return_if_fail (group != NULL);
921   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
922
923   if (GetIndMenuItemWithCommandID (NULL, kHICommandHide, 1,
924                                    &appmenu, NULL) != noErr)
925     {
926       g_warning ("%s: retrieving app menu failed",
927                  G_STRFUNC);
928       return;
929     }
930
931   for (list = app_menu_groups; list; list = g_list_next (list))
932     {
933       IgeMacMenuGroup *list_group = list->data;
934
935       index += g_list_length (list_group->items);
936
937       /*  adjust index for the separator between groups, but not
938        *  before the first group
939        */
940       if (list_group->items && list->prev)
941         index++;
942
943       if (group == list_group)
944         {
945           CFStringRef cfstr;
946
947           /*  add a separator before adding the first item, but not
948            *  for the first group
949            */
950           if (!group->items && list->prev)
951             {
952               InsertMenuItemTextWithCFString (appmenu, NULL, index,
953                                               kMenuItemAttrSeparator, 0);
954               index++;
955             }
956
957           if (!label)
958             label = get_menu_label_text (GTK_WIDGET (menu_item), NULL);
959
960           cfstr = CFStringCreateWithCString (NULL, label,
961                                              kCFStringEncodingUTF8);
962
963           InsertMenuItemTextWithCFString (appmenu, cfstr, index, 0, 0);
964           SetMenuItemProperty (appmenu, index + 1,
965                                IGE_QUARTZ_MENU_CREATOR,
966                                IGE_QUARTZ_ITEM_WIDGET,
967                                sizeof (menu_item), &menu_item);
968
969           CFRelease (cfstr);
970
971           gtk_widget_hide (GTK_WIDGET (menu_item));
972
973           group->items = g_list_append (group->items, menu_item);
974
975           return;
976         }
977     }
978
979   if (!list)
980     g_warning ("%s: app menu group %p does not exist",
981                G_STRFUNC, group);
982 }