1 /* GTK+ Integration for the Mac OS X Menubar.
3 * Copyright (C) 2007 Pioneer Research Center USA, Inc.
4 * Copyright (C) 2007 Imendio AB
6 * For further information, see:
7 * http://developer.imendio.com/projects/gtk-macosx/menubar
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
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.
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.
26 #include <gdk/gdkkeysyms.h>
28 #include <Carbon/Carbon.h>
30 #include <gtkmm2ext/sync-menu.h>
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...
42 #define IGE_QUARTZ_MENU_CREATOR 'IGEC'
43 #define IGE_QUARTZ_ITEM_WIDGET 'IWID'
46 static void sync_menu_shell (GtkMenuShell *menu_shell,
57 find_menu_label (GtkWidget *widget)
59 GtkWidget *label = NULL;
61 if (GTK_IS_LABEL (widget))
64 if (GTK_IS_CONTAINER (widget))
69 children = gtk_container_get_children (GTK_CONTAINER (widget));
71 for (l = children; l; l = l->next)
73 label = find_menu_label (l->data);
78 g_list_free (children);
85 get_menu_label_text (GtkWidget *menu_item,
90 my_label = find_menu_label (menu_item);
95 return gtk_label_get_text (GTK_LABEL (my_label));
101 accel_find_func (GtkAccelKey *key,
105 return (GClosure *) data == closure;
110 * CarbonMenu functions
118 static GQuark carbon_menu_quark = 0;
121 carbon_menu_new (void)
123 return g_slice_new0 (CarbonMenu);
127 carbon_menu_free (CarbonMenu *menu)
129 g_slice_free (CarbonMenu, menu);
133 carbon_menu_get (GtkWidget *widget)
135 return g_object_get_qdata (G_OBJECT (widget), carbon_menu_quark);
139 carbon_menu_connect (GtkWidget *menu,
142 CarbonMenu *carbon_menu = carbon_menu_get (menu);
146 carbon_menu = carbon_menu_new ();
148 g_object_set_qdata_full (G_OBJECT (menu), carbon_menu_quark,
150 (GDestroyNotify) carbon_menu_free);
153 carbon_menu->menu = menuRef;
158 * CarbonMenuItem functions
166 GClosure *accel_closure;
169 static GQuark carbon_menu_item_quark = 0;
171 static CarbonMenuItem *
172 carbon_menu_item_new (void)
174 return g_slice_new0 (CarbonMenuItem);
178 carbon_menu_item_free (CarbonMenuItem *menu_item)
180 if (menu_item->accel_closure)
181 g_closure_unref (menu_item->accel_closure);
183 g_slice_free (CarbonMenuItem, menu_item);
186 static CarbonMenuItem *
187 carbon_menu_item_get (GtkWidget *widget)
189 return g_object_get_qdata (G_OBJECT (widget), carbon_menu_item_quark);
193 carbon_menu_item_update_state (CarbonMenuItem *carbon_item,
198 UInt32 set_attrs = 0;
199 UInt32 clear_attrs = 0;
201 g_object_get (widget,
202 "sensitive", &sensitive,
207 set_attrs |= kMenuItemAttrDisabled;
209 clear_attrs |= kMenuItemAttrDisabled;
212 set_attrs |= kMenuItemAttrHidden;
214 clear_attrs |= kMenuItemAttrHidden;
216 ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
217 set_attrs, clear_attrs);
221 carbon_menu_item_update_active (CarbonMenuItem *carbon_item,
226 g_object_get (widget,
230 CheckMenuItem (carbon_item->menu, carbon_item->index,
235 carbon_menu_item_update_submenu (CarbonMenuItem *carbon_item,
240 submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
244 const gchar *label_text;
245 CFStringRef cfstr = NULL;
247 label_text = get_menu_label_text (widget, NULL);
249 cfstr = CFStringCreateWithCString (NULL, label_text,
250 kCFStringEncodingUTF8);
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);
257 sync_menu_shell (GTK_MENU_SHELL (submenu), carbon_item->submenu, FALSE, FALSE);
264 SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
266 carbon_item->submenu = NULL;
271 carbon_menu_item_update_label (CarbonMenuItem *carbon_item,
274 const gchar *label_text;
275 CFStringRef cfstr = NULL;
277 label_text = get_menu_label_text (widget, NULL);
279 cfstr = CFStringCreateWithCString (NULL, label_text,
280 kCFStringEncodingUTF8);
282 SetMenuItemTextWithCFString (carbon_item->menu, carbon_item->index,
290 carbon_menu_item_update_accelerator (CarbonMenuItem *carbon_item,
295 get_menu_label_text (widget, &label);
297 if (GTK_IS_ACCEL_LABEL (label) &&
298 GTK_ACCEL_LABEL (label)->accel_closure)
302 key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
304 GTK_ACCEL_LABEL (label)->accel_closure);
308 key->accel_flags & GTK_ACCEL_VISIBLE)
310 GdkDisplay *display = gtk_widget_get_display (widget);
311 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
315 gboolean add_modifiers = FALSE;
317 if (gdk_keymap_get_entries_for_keyval (keymap, key->accel_key,
318 &keys, &n_keys) == 0)
322 switch (key->accel_key) {
325 realkey = kRightArrowCharCode;
329 realkey = kLeftArrowCharCode;
333 realkey = kUpArrowCharCode;
337 realkey = kDownArrowCharCode;
344 SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
346 add_modifiers = TRUE;
350 SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
351 true, keys[0].keycode);
353 add_modifiers = TRUE;
358 UInt8 modifiers = 0; /* implies Command key */
364 if (key->accel_mods & GDK_SHIFT_MASK) {
365 modifiers |= kMenuShiftModifier;
368 /* gdk/quartz maps Alt/Option to Mod1 */
370 if (key->accel_mods & (GDK_MOD1_MASK)) {
371 modifiers |= kMenuOptionModifier;
374 if (key->accel_mods & GDK_CONTROL_MASK) {
375 modifiers |= kMenuControlModifier;
378 /* gdk/quartz maps Command to Meta */
380 if (key->accel_mods & GDK_META_MASK) {
386 modifiers |= kMenuNoCommandModifier;
388 SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
396 /* otherwise, clear the menu shortcut */
397 SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
398 kMenuNoModifiers | kMenuNoCommandModifier);
399 ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
400 0, kMenuItemAttrUseVirtualKey);
401 SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
406 carbon_menu_item_accel_changed (GtkAccelGroup *accel_group,
408 GdkModifierType modifier,
409 GClosure *accel_closure,
412 CarbonMenuItem *carbon_item = carbon_menu_item_get (widget);
415 get_menu_label_text (widget, &label);
417 if (GTK_IS_ACCEL_LABEL (label) &&
418 GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
419 carbon_menu_item_update_accelerator (carbon_item, widget);
423 carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
426 GtkAccelGroup *group;
429 get_menu_label_text (widget, &label);
431 if (carbon_item->accel_closure)
433 group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
435 g_signal_handlers_disconnect_by_func (group,
436 carbon_menu_item_accel_changed,
439 g_closure_unref (carbon_item->accel_closure);
440 carbon_item->accel_closure = NULL;
443 if (GTK_IS_ACCEL_LABEL (label))
444 carbon_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
446 if (carbon_item->accel_closure)
448 g_closure_ref (carbon_item->accel_closure);
450 group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
452 g_signal_connect_object (group, "accel-changed",
453 G_CALLBACK (carbon_menu_item_accel_changed),
457 carbon_menu_item_update_accelerator (carbon_item, widget);
461 carbon_menu_item_notify (GObject *object,
463 CarbonMenuItem *carbon_item)
465 if (!strcmp (pspec->name, "sensitive") ||
466 !strcmp (pspec->name, "visible"))
468 carbon_menu_item_update_state (carbon_item, GTK_WIDGET (object));
470 else if (!strcmp (pspec->name, "active"))
472 carbon_menu_item_update_active (carbon_item, GTK_WIDGET (object));
474 else if (!strcmp (pspec->name, "submenu"))
476 carbon_menu_item_update_submenu (carbon_item, GTK_WIDGET (object));
481 carbon_menu_item_notify_label (GObject *object,
485 CarbonMenuItem *carbon_item = carbon_menu_item_get (GTK_WIDGET (object));
487 if (!strcmp (pspec->name, "label"))
489 carbon_menu_item_update_label (carbon_item,
490 GTK_WIDGET (object));
492 else if (!strcmp (pspec->name, "accel-closure"))
494 carbon_menu_item_update_accel_closure (carbon_item,
495 GTK_WIDGET (object));
499 static CarbonMenuItem *
500 carbon_menu_item_connect (GtkWidget *menu_item,
505 CarbonMenuItem *carbon_item = carbon_menu_item_get (menu_item);
509 carbon_item = carbon_menu_item_new ();
511 g_object_set_qdata_full (G_OBJECT (menu_item), carbon_menu_item_quark,
513 (GDestroyNotify) carbon_menu_item_free);
515 g_signal_connect (menu_item, "notify",
516 G_CALLBACK (carbon_menu_item_notify),
520 g_signal_connect_swapped (label, "notify::label",
521 G_CALLBACK (carbon_menu_item_notify_label),
525 carbon_item->menu = menu;
526 carbon_item->index = index;
533 * carbon event handler
536 static int _in_carbon_menu_event_handler = 0;
539 gdk_quartz_in_carbon_menu_event_handler ()
541 return _in_carbon_menu_event_handler;
545 dummy_gtk_menu_item_activate (gpointer *arg)
547 gtk_menu_item_activate (GTK_MENU_ITEM(arg));
552 menu_event_handler_func (EventHandlerCallRef event_handler_call_ref,
556 UInt32 event_class = GetEventClass (event_ref);
557 UInt32 event_kind = GetEventKind (event_ref);
561 _in_carbon_menu_event_handler = 1;
565 case kEventClassCommand:
566 /* This is called when activating (is that the right GTK+ term?)
569 if (event_kind == kEventCommandProcess)
574 /*g_printerr ("Menu: kEventClassCommand/kEventCommandProcess\n");*/
576 err = GetEventParameter (event_ref, kEventParamDirectObject,
578 sizeof (command), 0, &command);
582 GtkWidget *widget = NULL;
584 /* Get any GtkWidget associated with the item. */
585 err = GetMenuItemProperty (command.menu.menuRef,
586 command.menu.menuItemIndex,
587 IGE_QUARTZ_MENU_CREATOR,
588 IGE_QUARTZ_ITEM_WIDGET,
589 sizeof (widget), 0, &widget);
590 if (err == noErr && GTK_IS_WIDGET (widget))
592 g_idle_add (dummy_gtk_menu_item_activate, widget);
593 // gtk_menu_item_activate (GTK_MENU_ITEM (widget));
594 _in_carbon_menu_event_handler = 0;
601 case kEventClassMenu:
602 GetEventParameter (event_ref,
603 kEventParamDirectObject,
612 case kEventMenuTargetItem:
613 /* This is called when an item is selected (what is the
614 * GTK+ term? prelight?)
616 /*g_printerr ("kEventClassMenu/kEventMenuTargetItem\n");*/
619 case kEventMenuOpening:
620 /* Is it possible to dynamically build the menu here? We
621 * can at least set visibility/sensitivity.
623 /*g_printerr ("kEventClassMenu/kEventMenuOpening\n");*/
626 case kEventMenuClosed:
627 /*g_printerr ("kEventClassMenu/kEventMenuClosed\n");*/
640 ret = CallNextEventHandler (event_handler_call_ref, event_ref);
641 _in_carbon_menu_event_handler = 0;
646 setup_menu_event_handler (void)
648 EventHandlerUPP menu_event_handler_upp;
649 EventHandlerRef menu_event_handler_ref;
650 const EventTypeSpec menu_events[] = {
651 { kEventClassCommand, kEventCommandProcess },
652 { kEventClassMenu, kEventMenuTargetItem },
653 { kEventClassMenu, kEventMenuOpening },
654 { kEventClassMenu, kEventMenuClosed }
657 /* FIXME: We might have to install one per window? */
659 menu_event_handler_upp = NewEventHandlerUPP (menu_event_handler_func);
660 InstallEventHandler (GetApplicationEventTarget (), menu_event_handler_upp,
661 GetEventTypeCount (menu_events), menu_events, 0,
662 &menu_event_handler_ref);
665 /* FIXME: Remove the handler with: */
666 RemoveEventHandler(menu_event_handler_ref);
667 DisposeEventHandlerUPP(menu_event_handler_upp);
672 sync_menu_shell (GtkMenuShell *menu_shell,
679 MenuItemIndex carbon_index = 1;
682 g_printerr ("%s: syncing shell %p\n", G_STRFUNC, menu_shell);
684 carbon_menu_connect (GTK_WIDGET (menu_shell), carbon_menu);
686 children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
688 for (l = children; l; l = l->next)
690 GtkWidget *menu_item = l->data;
691 CarbonMenuItem *carbon_item;
693 if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
696 if (toplevel && g_object_get_data (G_OBJECT (menu_item),
697 "gtk-empty-menu-item"))
700 carbon_item = carbon_menu_item_get (menu_item);
703 g_printerr ("%s: carbon_item %d for menu_item %d (%s, %s)\n",
704 G_STRFUNC, carbon_item ? carbon_item->index : -1,
705 carbon_index, get_menu_label_text (menu_item, NULL),
706 g_type_name (G_TYPE_FROM_INSTANCE (menu_item)));
708 if (carbon_item && carbon_item->index != carbon_index)
711 g_printerr ("%s: -> not matching, deleting\n", G_STRFUNC);
713 DeleteMenuItem (carbon_item->menu, carbon_index);
719 GtkWidget *label = NULL;
720 const gchar *label_text;
721 CFStringRef cfstr = NULL;
722 MenuItemAttributes attributes = 0;
725 g_printerr ("%s: -> creating new\n", G_STRFUNC);
727 label_text = get_menu_label_text (menu_item, &label);
729 cfstr = CFStringCreateWithCString (NULL, label_text,
730 kCFStringEncodingUTF8);
732 if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
733 attributes |= kMenuItemAttrSeparator;
735 if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
736 attributes |= kMenuItemAttrDisabled;
738 if (!GTK_WIDGET_VISIBLE (menu_item))
739 attributes |= kMenuItemAttrHidden;
741 InsertMenuItemTextWithCFString (carbon_menu, cfstr,
744 SetMenuItemProperty (carbon_menu, carbon_index,
745 IGE_QUARTZ_MENU_CREATOR,
746 IGE_QUARTZ_ITEM_WIDGET,
747 sizeof (menu_item), &menu_item);
752 carbon_item = carbon_menu_item_connect (menu_item, label,
756 if (GTK_IS_CHECK_MENU_ITEM (menu_item))
757 carbon_menu_item_update_active (carbon_item, menu_item);
759 carbon_menu_item_update_accel_closure (carbon_item, menu_item);
761 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
762 carbon_menu_item_update_submenu (carbon_item, menu_item);
768 g_list_free (children);
772 static gulong emission_hook_id = 0;
775 parent_set_emission_hook (GSignalInvocationHint *ihint,
776 guint n_param_values,
777 const GValue *param_values,
780 GtkWidget *instance = g_value_get_object (param_values);
782 if (GTK_IS_MENU_ITEM (instance))
784 GtkWidget *previous_parent = g_value_get_object (param_values + 1);
785 GtkWidget *menu_shell = NULL;
787 if (GTK_IS_MENU_SHELL (previous_parent))
789 menu_shell = previous_parent;
791 else if (GTK_IS_MENU_SHELL (instance->parent))
793 menu_shell = instance->parent;
798 CarbonMenu *carbon_menu = carbon_menu_get (menu_shell);
803 g_printerr ("%s: item %s %p (%s, %s)\n", G_STRFUNC,
804 previous_parent ? "removed from" : "added to",
806 get_menu_label_text (instance, NULL),
807 g_type_name (G_TYPE_FROM_INSTANCE (instance)));
810 sync_menu_shell (GTK_MENU_SHELL (menu_shell),
812 carbon_menu->menu == (MenuRef) data,
822 parent_set_emission_hook_remove (GtkWidget *widget,
825 g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
836 ige_mac_menu_set_menu_bar (GtkMenuShell *menu_shell)
838 MenuRef carbon_menubar;
840 g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
842 if (carbon_menu_quark == 0)
843 carbon_menu_quark = g_quark_from_static_string ("CarbonMenu");
845 if (carbon_menu_item_quark == 0)
846 carbon_menu_item_quark = g_quark_from_static_string ("CarbonMenuItem");
848 CreateNewMenu (0 /*id*/, 0 /*options*/, &carbon_menubar);
849 SetRootMenu (carbon_menubar);
851 setup_menu_event_handler ();
854 g_signal_add_emission_hook (g_signal_lookup ("parent-set",
857 parent_set_emission_hook,
858 carbon_menubar, NULL);
860 g_signal_connect (menu_shell, "destroy",
861 G_CALLBACK (parent_set_emission_hook_remove),
864 sync_menu_shell (menu_shell, carbon_menubar, TRUE, FALSE);
868 ige_mac_menu_set_quit_menu_item (GtkMenuItem *menu_item)
873 g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
875 if (GetIndMenuItemWithCommandID (NULL, kHICommandQuit, 1,
876 &appmenu, &index) == noErr)
878 SetMenuItemCommandID (appmenu, index, 0);
879 SetMenuItemProperty (appmenu, index,
880 IGE_QUARTZ_MENU_CREATOR,
881 IGE_QUARTZ_ITEM_WIDGET,
882 sizeof (menu_item), &menu_item);
884 gtk_widget_hide (GTK_WIDGET (menu_item));
889 struct _IgeMacMenuGroup
894 static GList *app_menu_groups = NULL;
897 ige_mac_menu_add_app_menu_group (void)
899 IgeMacMenuGroup *group = g_slice_new0 (IgeMacMenuGroup);
901 app_menu_groups = g_list_append (app_menu_groups, group);
907 ige_mac_menu_add_app_menu_item (IgeMacMenuGroup *group,
908 GtkMenuItem *menu_item,
915 g_return_if_fail (group != NULL);
916 g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
918 if (GetIndMenuItemWithCommandID (NULL, kHICommandHide, 1,
919 &appmenu, NULL) != noErr)
921 g_warning ("%s: retrieving app menu failed",
926 for (list = app_menu_groups; list; list = g_list_next (list))
928 IgeMacMenuGroup *list_group = list->data;
930 index += g_list_length (list_group->items);
932 /* adjust index for the separator between groups, but not
933 * before the first group
935 if (list_group->items && list->prev)
938 if (group == list_group)
942 /* add a separator before adding the first item, but not
943 * for the first group
945 if (!group->items && list->prev)
947 InsertMenuItemTextWithCFString (appmenu, NULL, index,
948 kMenuItemAttrSeparator, 0);
953 label = get_menu_label_text (GTK_WIDGET (menu_item), NULL);
955 cfstr = CFStringCreateWithCString (NULL, label,
956 kCFStringEncodingUTF8);
958 InsertMenuItemTextWithCFString (appmenu, cfstr, index, 0, 0);
959 SetMenuItemProperty (appmenu, index + 1,
960 IGE_QUARTZ_MENU_CREATOR,
961 IGE_QUARTZ_ITEM_WIDGET,
962 sizeof (menu_item), &menu_item);
966 gtk_widget_hide (GTK_WIDGET (menu_item));
968 group->items = g_list_append (group->items, menu_item);
975 g_warning ("%s: app menu group %p does not exist",