Code

Node tool: correctly save node skewing to undo history
[inkscape.git] / src / ige-mac-menu.c
1 /* GTK+ Integration for the Mac OS X Menubar.
2  *
3  * Copyright (C) 2007 Pioneer Research Center USA, Inc.
4  *
5  * For further information, see:
6  * http://developer.imendio.com/projects/gtk-macosx/menubar
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
28 #include <gtk/gtk.h>
30 #ifdef GDK_WINDOWING_QUARTZ
32 #include <gdk/gdkkeysyms.h>
34 #include <Carbon/Carbon.h>
36 #include "ige-mac-menu.h"
39 /* TODO
40  *
41  * - Sync adding/removing/reordering items
42  * - Create on demand? (can this be done with gtk+? ie fill in menu
43      items when the menu is opened)
44  * - Figure out what to do per app/window...
45  *
46  */
48 #define IGE_QUARTZ_MENU_CREATOR 'IGEC'
49 #define IGE_QUARTZ_ITEM_WIDGET  'IWID'
52 static void   sync_menu_shell (GtkMenuShell *menu_shell,
53                                MenuRef       carbon_menu,
54                                gboolean      toplevel,
55                                gboolean      debug);
58 /*
59  * utility functions
60  */
62 static GtkWidget *
63 find_menu_label (GtkWidget *widget)
64 {
65   GtkWidget *label = NULL;
67   if (GTK_IS_LABEL (widget))
68     return widget;
70   if (GTK_IS_CONTAINER (widget))
71     {
72       GList *children;
73       GList *l;
75       children = gtk_container_get_children (GTK_CONTAINER (widget));
77       for (l = children; l; l = l->next)
78         {
79           label = find_menu_label (l->data);
80           if (label)
81             break;
82         }
84       g_list_free (children);
85     }
87   return label;
88 }
90 static const gchar *
91 get_menu_label_text (GtkWidget  *menu_item,
92                      GtkWidget **label)
93 {
94   GtkWidget *my_label;
96   my_label = find_menu_label (menu_item);
97   if (label)
98     *label = my_label;
100   if (my_label)
101     return gtk_label_get_text (GTK_LABEL (my_label));
103   return NULL;
106 static gboolean
107 accel_find_func (GtkAccelKey *key,
108                  GClosure    *closure,
109                  gpointer     data)
111   return (GClosure *) data == closure;
115 /*
116  * CarbonMenu functions
117  */
119 typedef struct
121   MenuRef menu;
122 } CarbonMenu;
124 static GQuark carbon_menu_quark = 0;
126 static CarbonMenu *
127 carbon_menu_new (void)
129   return g_slice_new0 (CarbonMenu);
132 static void
133 carbon_menu_free (CarbonMenu *menu)
135   g_slice_free (CarbonMenu, menu);
138 static CarbonMenu *
139 carbon_menu_get (GtkWidget *widget)
141   return g_object_get_qdata (G_OBJECT (widget), carbon_menu_quark);
144 static void
145 carbon_menu_connect (GtkWidget *menu,
146                      MenuRef    menuRef)
148   CarbonMenu *carbon_menu = carbon_menu_get (menu);
150   if (!carbon_menu)
151     {
152       carbon_menu = carbon_menu_new ();
154       g_object_set_qdata_full (G_OBJECT (menu), carbon_menu_quark,
155                                carbon_menu,
156                                (GDestroyNotify) carbon_menu_free);
157     }
159   carbon_menu->menu = menuRef;
163 /*
164  * CarbonMenuItem functions
165  */
167 typedef struct
169   MenuRef        menu;
170   MenuItemIndex  index;
171   MenuRef        submenu;
172   GClosure      *accel_closure;
173 } CarbonMenuItem;
175 static GQuark carbon_menu_item_quark = 0;
177 static CarbonMenuItem *
178 carbon_menu_item_new (void)
180   return g_slice_new0 (CarbonMenuItem);
183 static void
184 carbon_menu_item_free (CarbonMenuItem *menu_item)
186   if (menu_item->accel_closure)
187     g_closure_unref (menu_item->accel_closure);
189   g_slice_free (CarbonMenuItem, menu_item);
192 static CarbonMenuItem *
193 carbon_menu_item_get (GtkWidget *widget)
195   return g_object_get_qdata (G_OBJECT (widget), carbon_menu_item_quark);
198 static void
199 carbon_menu_item_update_state (CarbonMenuItem *carbon_item,
200                                GtkWidget      *widget)
202   gboolean sensitive;
203   gboolean visible;
204   UInt32   set_attrs = 0;
205   UInt32   clear_attrs = 0;
207   g_object_get (widget,
208                 "sensitive", &sensitive,
209                 "visible",   &visible,
210                 NULL);
212   if (!sensitive)
213     set_attrs |= kMenuItemAttrDisabled;
214   else
215     clear_attrs |= kMenuItemAttrDisabled;
217   if (!visible)
218     set_attrs |= kMenuItemAttrHidden;
219   else
220     clear_attrs |= kMenuItemAttrHidden;
222   ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
223                             set_attrs, clear_attrs);
226 static void
227 carbon_menu_item_update_active (CarbonMenuItem *carbon_item,
228                                 GtkWidget      *widget)
230   gboolean active;
232   g_object_get (widget,
233                 "active", &active,
234                 NULL);
236   CheckMenuItem (carbon_item->menu, carbon_item->index,
237                  active);
240 static void
241 carbon_menu_item_update_submenu (CarbonMenuItem *carbon_item,
242                                  GtkWidget      *widget)
244   GtkWidget *submenu;
246   submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
248   if (submenu)
249     {
250       const gchar *label_text;
251       CFStringRef  cfstr = NULL;
253       label_text = get_menu_label_text (widget, NULL);
254       if (label_text)
255         cfstr = CFStringCreateWithCString (NULL, label_text,
256                                            kCFStringEncodingUTF8);
258       CreateNewMenu (0, 0, &carbon_item->submenu);
259       SetMenuTitleWithCFString (carbon_item->submenu, cfstr);
260       SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
261                                    carbon_item->submenu);
263       sync_menu_shell (GTK_MENU_SHELL (submenu), carbon_item->submenu, FALSE, FALSE);
265       if (cfstr)
266         CFRelease (cfstr);
267     }
268   else
269     {
270       SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
271                                    NULL);
272       carbon_item->submenu = NULL;
273     }
276 static void
277 carbon_menu_item_update_label (CarbonMenuItem *carbon_item,
278                                GtkWidget      *widget)
280   const gchar *label_text;
281   CFStringRef  cfstr = NULL;
283   label_text = get_menu_label_text (widget, NULL);
284   if (label_text)
285     cfstr = CFStringCreateWithCString (NULL, label_text,
286                                        kCFStringEncodingUTF8);
288   SetMenuItemTextWithCFString (carbon_item->menu, carbon_item->index,
289                                cfstr);
291   if (cfstr)
292     CFRelease (cfstr);
295 static void
296 carbon_menu_item_update_accelerator (CarbonMenuItem *carbon_item,
297                                      GtkWidget      *widget)
299   GtkWidget *label;
301   get_menu_label_text (widget, &label);
303   if (GTK_IS_ACCEL_LABEL (label) &&
304       GTK_ACCEL_LABEL (label)->accel_closure)
305     {
306       GtkAccelKey *key;
308       key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
309                                   accel_find_func,
310                                   GTK_ACCEL_LABEL (label)->accel_closure);
312       if (key            &&
313           key->accel_key &&
314           key->accel_flags & GTK_ACCEL_VISIBLE)
315         {
316           GdkDisplay      *display = gtk_widget_get_display (widget);
317           GdkKeymap       *keymap  = gdk_keymap_get_for_display (display);
318           GdkKeymapKey    *keys;
319           gint             n_keys;
321           if (gdk_keymap_get_entries_for_keyval (keymap, key->accel_key,
322                                                  &keys, &n_keys))
323             {
324               UInt8 modifiers = 0;
326               SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
327                                      true, keys[0].keycode);
329               g_free (keys);
331               if (key->accel_mods)
332                 {
333                   if (key->accel_mods & GDK_SHIFT_MASK)
334                     modifiers |= kMenuShiftModifier;
336                   if (key->accel_mods & GDK_MOD1_MASK)
337                     modifiers |= kMenuOptionModifier;
338                 }
340               if (!(key->accel_mods & GDK_CONTROL_MASK))
341                 {
342                   modifiers |= kMenuNoCommandModifier;
343                 }
345               SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
346                                     modifiers);
348               return;
349             }
350         }
351     }
353   /*  otherwise, clear the menu shortcut  */
354   SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
355                         kMenuNoModifiers | kMenuNoCommandModifier);
356   ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
357                             0, kMenuItemAttrUseVirtualKey);
358   SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
359                          false, 0);
362 static void
363 carbon_menu_item_accel_changed (GtkAccelGroup   *accel_group,
364                                 guint            keyval,
365                                 GdkModifierType  modifier,
366                                 GClosure        *accel_closure,
367                                 GtkWidget       *widget)
369   CarbonMenuItem *carbon_item = carbon_menu_item_get (widget);
370   GtkWidget      *label;
372   get_menu_label_text (widget, &label);
374   if (GTK_IS_ACCEL_LABEL (label) &&
375       GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
376     carbon_menu_item_update_accelerator (carbon_item, widget);
379 static void
380 carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
381                                        GtkWidget      *widget)
383   GtkAccelGroup *group;
384   GtkWidget     *label;
386   get_menu_label_text (widget, &label);
388   if (carbon_item->accel_closure)
389     {
390       group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
392       g_signal_handlers_disconnect_by_func (group,
393                                             carbon_menu_item_accel_changed,
394                                             widget);
396       g_closure_unref (carbon_item->accel_closure);
397       carbon_item->accel_closure = NULL;
398     }
400   if (GTK_IS_ACCEL_LABEL (label))
401     carbon_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
403   if (carbon_item->accel_closure)
404     {
405       g_closure_ref (carbon_item->accel_closure);
407       group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
409       g_signal_connect_object (group, "accel-changed",
410                                G_CALLBACK (carbon_menu_item_accel_changed),
411                                widget, 0);
412     }
414   carbon_menu_item_update_accelerator (carbon_item, widget);
417 static void
418 carbon_menu_item_notify (GObject        *object,
419                          GParamSpec     *pspec,
420                          CarbonMenuItem *carbon_item)
422   if (!strcmp (pspec->name, "sensitive") ||
423       !strcmp (pspec->name, "visible"))
424     {
425       carbon_menu_item_update_state (carbon_item, GTK_WIDGET (object));
426     }
427   else if (!strcmp (pspec->name, "active"))
428     {
429       carbon_menu_item_update_active (carbon_item, GTK_WIDGET (object));
430     }
431   else if (!strcmp (pspec->name, "submenu"))
432     {
433       carbon_menu_item_update_submenu (carbon_item, GTK_WIDGET (object));
434     }
437 static void
438 carbon_menu_item_notify_label (GObject    *object,
439                                GParamSpec *pspec,
440                                gpointer    data)
442   CarbonMenuItem *carbon_item = carbon_menu_item_get (GTK_WIDGET (object));
444   if (!strcmp (pspec->name, "label"))
445     {
446       carbon_menu_item_update_label (carbon_item,
447                                      GTK_WIDGET (object));
448     }
449   else if (!strcmp (pspec->name, "accel-closure"))
450     {
451       carbon_menu_item_update_accel_closure (carbon_item,
452                                              GTK_WIDGET (object));
453     }
456 static CarbonMenuItem *
457 carbon_menu_item_connect (GtkWidget     *menu_item,
458                           GtkWidget     *label,
459                           MenuRef        menu,
460                           MenuItemIndex  index)
462   CarbonMenuItem *carbon_item = carbon_menu_item_get (menu_item);
464   if (!carbon_item)
465     {
466       carbon_item = carbon_menu_item_new ();
468       g_object_set_qdata_full (G_OBJECT (menu_item), carbon_menu_item_quark,
469                                carbon_item,
470                                (GDestroyNotify) carbon_menu_item_free);
472       g_signal_connect (menu_item, "notify",
473                         G_CALLBACK (carbon_menu_item_notify),
474                         carbon_item);
476       if (label)
477         g_signal_connect_swapped (label, "notify::label",
478                                   G_CALLBACK (carbon_menu_item_notify_label),
479                                   menu_item);
480     }
482   carbon_item->menu  = menu;
483   carbon_item->index = index;
485   return carbon_item;
489 /*
490  * carbon event handler
491  */
493 static OSStatus
494 menu_event_handler_func (EventHandlerCallRef  event_handler_call_ref,
495                          EventRef             event_ref,
496                          void                *data)
498   UInt32  event_class = GetEventClass (event_ref);
499   UInt32  event_kind = GetEventKind (event_ref);
500   MenuRef menu_ref;
502   switch (event_class)
503     {
504     case kEventClassCommand:
505       /* This is called when activating (is that the right GTK+ term?)
506        * a menu item.
507        */
508       if (event_kind == kEventCommandProcess)
509         {
510           HICommand command;
511           OSStatus  err;
513           /*g_printerr ("Menu: kEventClassCommand/kEventCommandProcess\n");*/
515           err = GetEventParameter (event_ref, kEventParamDirectObject,
516                                    typeHICommand, 0,
517                                    sizeof (command), 0, &command);
519           if (err == noErr)
520             {
521               GtkWidget *widget = NULL;
523               /* Get any GtkWidget associated with the item. */
524               err = GetMenuItemProperty (command.menu.menuRef,
525                                          command.menu.menuItemIndex,
526                                          IGE_QUARTZ_MENU_CREATOR,
527                                          IGE_QUARTZ_ITEM_WIDGET,
528                                          sizeof (widget), 0, &widget);
529               if (err == noErr && GTK_IS_WIDGET (widget))
530                 {
531                   gtk_menu_item_activate (GTK_MENU_ITEM (widget));
532                   return noErr;
533                 }
534             }
535         }
536       break;
538     case kEventClassMenu:
539       GetEventParameter (event_ref,
540                          kEventParamDirectObject,
541                          typeMenuRef,
542                          NULL,
543                          sizeof (menu_ref),
544                          NULL,
545                          &menu_ref);
547       switch (event_kind)
548         {
549         case kEventMenuTargetItem:
550           /* This is called when an item is selected (what is the
551            * GTK+ term? prelight?)
552            */
553           /*g_printerr ("kEventClassMenu/kEventMenuTargetItem\n");*/
554           break;
556         case kEventMenuOpening:
557           /* Is it possible to dynamically build the menu here? We
558            * can at least set visibility/sensitivity.
559            */
560           /*g_printerr ("kEventClassMenu/kEventMenuOpening\n");*/
561           break;
563         case kEventMenuClosed:
564           /*g_printerr ("kEventClassMenu/kEventMenuClosed\n");*/
565           break;
567         default:
568           break;
569         }
571       break;
573     default:
574       break;
575     }
577   return CallNextEventHandler (event_handler_call_ref, event_ref);
580 static void
581 setup_menu_event_handler (void)
583   EventHandlerUPP menu_event_handler_upp;
584   EventHandlerRef menu_event_handler_ref;
585   const EventTypeSpec menu_events[] = {
586     { kEventClassCommand, kEventCommandProcess },
587     { kEventClassMenu, kEventMenuTargetItem },
588     { kEventClassMenu, kEventMenuOpening },
589     { kEventClassMenu, kEventMenuClosed }
590   };
592   /* FIXME: We might have to install one per window? */
594   menu_event_handler_upp = NewEventHandlerUPP (menu_event_handler_func);
595   InstallEventHandler (GetApplicationEventTarget (), menu_event_handler_upp,
596                        GetEventTypeCount (menu_events), menu_events, 0,
597                        &menu_event_handler_ref);
599 #if 0
600   /* FIXME: Remove the handler with: */
601   RemoveEventHandler(menu_event_handler_ref);
602   DisposeEventHandlerUPP(menu_event_handler_upp);
603 #endif
606 static void
607 sync_menu_shell (GtkMenuShell *menu_shell,
608                  MenuRef       carbon_menu,
609                  gboolean      toplevel,
610                  gboolean      debug)
612   GList         *children;
613   GList         *l;
614   MenuItemIndex  carbon_index = 1;
616   if (debug)
617     g_printerr ("%s: syncing shell %p\n", G_STRFUNC, menu_shell);
619   carbon_menu_connect (GTK_WIDGET (menu_shell), carbon_menu);
621   children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
623   for (l = children; l; l = l->next)
624     {
625       GtkWidget      *menu_item = l->data;
626       CarbonMenuItem *carbon_item;
628       if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
629         continue;
631       if (toplevel && g_object_get_data (G_OBJECT (menu_item),
632                                          "gtk-empty-menu-item"))
633         continue;
635       carbon_item = carbon_menu_item_get (menu_item);
637       if (debug)
638         g_printerr ("%s: carbon_item %d for menu_item %d (%s, %s)\n",
639                     G_STRFUNC, carbon_item ? carbon_item->index : -1,
640                     carbon_index, get_menu_label_text (menu_item, NULL),
641                     g_type_name (G_TYPE_FROM_INSTANCE (menu_item)));
643       if (carbon_item && carbon_item->index != carbon_index)
644         {
645           if (debug)
646             g_printerr ("%s:   -> not matching, deleting\n", G_STRFUNC);
648           DeleteMenuItem (carbon_item->menu, carbon_index);
649           carbon_item = NULL;
650         }
652       if (!carbon_item)
653         {
654           GtkWidget          *label      = NULL;
655           const gchar        *label_text;
656           CFStringRef         cfstr      = NULL;
657           MenuItemAttributes  attributes = 0;
659           if (debug)
660             g_printerr ("%s:   -> creating new\n", G_STRFUNC);
662           label_text = get_menu_label_text (menu_item, &label);
663           if (label_text)
664             cfstr = CFStringCreateWithCString (NULL, label_text,
665                                                kCFStringEncodingUTF8);
667           if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
668             attributes |= kMenuItemAttrSeparator;
670           if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
671             attributes |= kMenuItemAttrDisabled;
673           if (!GTK_WIDGET_VISIBLE (menu_item))
674             attributes |= kMenuItemAttrHidden;
676           InsertMenuItemTextWithCFString (carbon_menu, cfstr,
677                                           carbon_index - 1,
678                                           attributes, 0);
679           SetMenuItemProperty (carbon_menu, carbon_index,
680                                IGE_QUARTZ_MENU_CREATOR,
681                                IGE_QUARTZ_ITEM_WIDGET,
682                                sizeof (menu_item), &menu_item);
684           if (cfstr)
685             CFRelease (cfstr);
687           carbon_item = carbon_menu_item_connect (menu_item, label,
688                                                   carbon_menu,
689                                                   carbon_index);
691           if (GTK_IS_CHECK_MENU_ITEM (menu_item))
692             carbon_menu_item_update_active (carbon_item, menu_item);
694           carbon_menu_item_update_accel_closure (carbon_item, menu_item);
696           if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
697             carbon_menu_item_update_submenu (carbon_item, menu_item);
698         }
700       carbon_index++;
701     }
703   g_list_free (children);
707 static gulong emission_hook_id = 0;
709 static gboolean
710 parent_set_emission_hook (GSignalInvocationHint *ihint,
711                           guint                  n_param_values,
712                           const GValue          *param_values,
713                           gpointer               data)
715   GtkWidget *instance = g_value_get_object (param_values);
717   if (GTK_IS_MENU_ITEM (instance))
718     {
719       GtkWidget *previous_parent = g_value_get_object (param_values + 1);
720       GtkWidget *menu_shell      = NULL;
722       if (GTK_IS_MENU_SHELL (previous_parent))
723         {
724           menu_shell = previous_parent;
725         }
726       else if (GTK_IS_MENU_SHELL (instance->parent))
727         {
728           menu_shell = instance->parent;
729         }
731       if (menu_shell)
732         {
733           CarbonMenu *carbon_menu = carbon_menu_get (menu_shell);
735           if (carbon_menu)
736             {
737 #if 0
738               g_printerr ("%s: item %s %p (%s, %s)\n", G_STRFUNC,
739                           previous_parent ? "removed from" : "added to",
740                           menu_shell,
741                           get_menu_label_text (instance, NULL),
742                           g_type_name (G_TYPE_FROM_INSTANCE (instance)));
743 #endif
745               sync_menu_shell (GTK_MENU_SHELL (menu_shell),
746                                carbon_menu->menu,
747                                carbon_menu->menu == (MenuRef) data,
748                                FALSE);
749             }
750         }
751     }
753   return TRUE;
756 static void
757 parent_set_emission_hook_remove (GtkWidget *widget,
758                                  gpointer   data)
760   g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
761                                                   GTK_TYPE_WIDGET),
762                                  emission_hook_id);
766 /*
767  * public functions
768  */
770 void
771 ige_mac_menu_set_menu_bar (GtkMenuShell *menu_shell)
773   MenuRef carbon_menubar;
774   guint   hook_id;
776   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
778   if (carbon_menu_quark == 0)
779     carbon_menu_quark = g_quark_from_static_string ("CarbonMenu");
781   if (carbon_menu_item_quark == 0)
782     carbon_menu_item_quark = g_quark_from_static_string ("CarbonMenuItem");
784   CreateNewMenu (0 /*id*/, 0 /*options*/, &carbon_menubar);
785   SetRootMenu (carbon_menubar);
787   setup_menu_event_handler ();
789   emission_hook_id =
790     g_signal_add_emission_hook (g_signal_lookup ("parent-set",
791                                                  GTK_TYPE_WIDGET),
792                                 0,
793                                 parent_set_emission_hook,
794                                 carbon_menubar, NULL);
796   g_signal_connect (menu_shell, "destroy",
797                     G_CALLBACK (parent_set_emission_hook_remove),
798                     NULL);
800   sync_menu_shell (menu_shell, carbon_menubar, TRUE, FALSE);
803 void
804 ige_mac_menu_set_quit_menu_item (GtkMenuItem *menu_item)
806   MenuRef       appmenu;
807   MenuItemIndex index;
809   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
811   if (GetIndMenuItemWithCommandID (NULL, kHICommandQuit, 1,
812                                    &appmenu, &index) == noErr)
813     {
814       SetMenuItemCommandID (appmenu, index, 0);
815       SetMenuItemProperty (appmenu, index,
816                            IGE_QUARTZ_MENU_CREATOR,
817                            IGE_QUARTZ_ITEM_WIDGET,
818                            sizeof (menu_item), &menu_item);
820       gtk_widget_hide (GTK_WIDGET (menu_item));
821     }
825 struct _IgeMacMenuGroup
827   GList *items;
828 };
830 static GList *app_menu_groups = NULL;
832 IgeMacMenuGroup *
833 ige_mac_menu_add_app_menu_group (void)
835   IgeMacMenuGroup *group = g_slice_new0 (IgeMacMenuGroup);
837   app_menu_groups = g_list_append (app_menu_groups, group);
839   return group;
842 void
843 ige_mac_menu_add_app_menu_item (IgeMacMenuGroup *group,
844                                 GtkMenuItem     *menu_item,
845                                 const gchar     *label)
847   MenuRef  appmenu;
848   GList   *list;
849   gint     index = 0;
851   g_return_if_fail (group != NULL);
852   g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
854   if (GetIndMenuItemWithCommandID (NULL, kHICommandHide, 1,
855                                    &appmenu, NULL) != noErr)
856     {
857       g_warning ("%s: retrieving app menu failed",
858                  G_STRFUNC);
859       return;
860     }
862   for (list = app_menu_groups; list; list = g_list_next (list))
863     {
864       IgeMacMenuGroup *list_group = list->data;
866       index += g_list_length (list_group->items);
868       /*  adjust index for the separator between groups, but not
869        *  before the first group
870        */
871       if (list_group->items && list->prev)
872         index++;
874       if (group == list_group)
875         {
876           CFStringRef cfstr;
878           /*  add a separator before adding the first item, but not
879            *  for the first group
880            */
881           if (!group->items && list->prev)
882             {
883               InsertMenuItemTextWithCFString (appmenu, NULL, index,
884                                               kMenuItemAttrSeparator, 0);
885               index++;
886             }
888           if (!label)
889             label = get_menu_label_text (GTK_WIDGET (menu_item), NULL);
891           cfstr = CFStringCreateWithCString (NULL, label,
892                                              kCFStringEncodingUTF8);
894           InsertMenuItemTextWithCFString (appmenu, cfstr, index, 0, 0);
895           SetMenuItemProperty (appmenu, index + 1,
896                                IGE_QUARTZ_MENU_CREATOR,
897                                IGE_QUARTZ_ITEM_WIDGET,
898                                sizeof (menu_item), &menu_item);
900           CFRelease (cfstr);
902           gtk_widget_hide (GTK_WIDGET (menu_item));
904           group->items = g_list_append (group->items, menu_item);
906           return;
907         }
908     }
910   if (!list)
911     g_warning ("%s: app menu group %p does not exist",
912                G_STRFUNC, group);
915 #endif /* GDK_WINDOWING_QUARTZ */