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;
104 }
106 static gboolean
107 accel_find_func (GtkAccelKey *key,
108 GClosure *closure,
109 gpointer data)
110 {
111 return (GClosure *) data == closure;
112 }
115 /*
116 * CarbonMenu functions
117 */
119 typedef struct
120 {
121 MenuRef menu;
122 } CarbonMenu;
124 static GQuark carbon_menu_quark = 0;
126 static CarbonMenu *
127 carbon_menu_new (void)
128 {
129 return g_slice_new0 (CarbonMenu);
130 }
132 static void
133 carbon_menu_free (CarbonMenu *menu)
134 {
135 g_slice_free (CarbonMenu, menu);
136 }
138 static CarbonMenu *
139 carbon_menu_get (GtkWidget *widget)
140 {
141 return g_object_get_qdata (G_OBJECT (widget), carbon_menu_quark);
142 }
144 static void
145 carbon_menu_connect (GtkWidget *menu,
146 MenuRef menuRef)
147 {
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;
160 }
163 /*
164 * CarbonMenuItem functions
165 */
167 typedef struct
168 {
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)
179 {
180 return g_slice_new0 (CarbonMenuItem);
181 }
183 static void
184 carbon_menu_item_free (CarbonMenuItem *menu_item)
185 {
186 if (menu_item->accel_closure)
187 g_closure_unref (menu_item->accel_closure);
189 g_slice_free (CarbonMenuItem, menu_item);
190 }
192 static CarbonMenuItem *
193 carbon_menu_item_get (GtkWidget *widget)
194 {
195 return g_object_get_qdata (G_OBJECT (widget), carbon_menu_item_quark);
196 }
198 static void
199 carbon_menu_item_update_state (CarbonMenuItem *carbon_item,
200 GtkWidget *widget)
201 {
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);
224 }
226 static void
227 carbon_menu_item_update_active (CarbonMenuItem *carbon_item,
228 GtkWidget *widget)
229 {
230 gboolean active;
232 g_object_get (widget,
233 "active", &active,
234 NULL);
236 CheckMenuItem (carbon_item->menu, carbon_item->index,
237 active);
238 }
240 static void
241 carbon_menu_item_update_submenu (CarbonMenuItem *carbon_item,
242 GtkWidget *widget)
243 {
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 }
274 }
276 static void
277 carbon_menu_item_update_label (CarbonMenuItem *carbon_item,
278 GtkWidget *widget)
279 {
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);
293 }
295 static void
296 carbon_menu_item_update_accelerator (CarbonMenuItem *carbon_item,
297 GtkWidget *widget)
298 {
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);
360 }
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)
368 {
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);
377 }
379 static void
380 carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
381 GtkWidget *widget)
382 {
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);
415 }
417 static void
418 carbon_menu_item_notify (GObject *object,
419 GParamSpec *pspec,
420 CarbonMenuItem *carbon_item)
421 {
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 }
435 }
437 static void
438 carbon_menu_item_notify_label (GObject *object,
439 GParamSpec *pspec,
440 gpointer data)
441 {
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 }
454 }
456 static CarbonMenuItem *
457 carbon_menu_item_connect (GtkWidget *menu_item,
458 GtkWidget *label,
459 MenuRef menu,
460 MenuItemIndex index)
461 {
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;
486 }
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)
497 {
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);
578 }
580 static void
581 setup_menu_event_handler (void)
582 {
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
604 }
606 static void
607 sync_menu_shell (GtkMenuShell *menu_shell,
608 MenuRef carbon_menu,
609 gboolean toplevel,
610 gboolean debug)
611 {
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);
704 }
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)
714 {
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;
754 }
756 static void
757 parent_set_emission_hook_remove (GtkWidget *widget,
758 gpointer data)
759 {
760 g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
761 GTK_TYPE_WIDGET),
762 emission_hook_id);
763 }
766 /*
767 * public functions
768 */
770 void
771 ige_mac_menu_set_menu_bar (GtkMenuShell *menu_shell)
772 {
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);
801 }
803 void
804 ige_mac_menu_set_quit_menu_item (GtkMenuItem *menu_item)
805 {
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 }
822 }
825 struct _IgeMacMenuGroup
826 {
827 GList *items;
828 };
830 static GList *app_menu_groups = NULL;
832 IgeMacMenuGroup *
833 ige_mac_menu_add_app_menu_group (void)
834 {
835 IgeMacMenuGroup *group = g_slice_new0 (IgeMacMenuGroup);
837 app_menu_groups = g_list_append (app_menu_groups, group);
839 return group;
840 }
842 void
843 ige_mac_menu_add_app_menu_item (IgeMacMenuGroup *group,
844 GtkMenuItem *menu_item,
845 const gchar *label)
846 {
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);
913 }
915 #endif /* GDK_WINDOWING_QUARTZ */