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
;
316 UInt8 modifiers
= 0; /* implies Command key */
318 if (gdk_keymap_get_entries_for_keyval (keymap
, key
->accel_key
,
319 &keys
, &n_keys
) == 0)
323 switch (key
->accel_key
) {
326 realkey
= kRightArrowCharCode
;
330 realkey
= kLeftArrowCharCode
;
334 realkey
= kUpArrowCharCode
;
338 realkey
= kDownArrowCharCode
;
345 SetMenuItemCommandKey (carbon_item
->menu
, carbon_item
->index
,
347 add_modifiers
= TRUE
;
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
;
358 add_modifiers
= TRUE
;
363 UInt8 modifiers
= 0; /* implies Command key */
369 if (key
->accel_mods
& GDK_SHIFT_MASK
) {
370 modifiers
|= kMenuShiftModifier
;
373 /* gdk/quartz maps Alt/Option to Mod1 */
375 if (key
->accel_mods
& (GDK_MOD1_MASK
)) {
376 modifiers
|= kMenuOptionModifier
;
379 if (key
->accel_mods
& GDK_CONTROL_MASK
) {
380 modifiers
|= kMenuControlModifier
;
383 /* gdk/quartz maps Command to Meta */
385 if (key
->accel_mods
& GDK_META_MASK
) {
391 modifiers
|= kMenuNoCommandModifier
;
393 SetMenuItemModifiers (carbon_item
->menu
, carbon_item
->index
,
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
,
411 carbon_menu_item_accel_changed (GtkAccelGroup
*accel_group
,
413 GdkModifierType modifier
,
414 GClosure
*accel_closure
,
417 CarbonMenuItem
*carbon_item
= carbon_menu_item_get (widget
);
420 get_menu_label_text (widget
, &label
);
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
);
428 carbon_menu_item_update_accel_closure (CarbonMenuItem
*carbon_item
,
431 GtkAccelGroup
*group
;
434 get_menu_label_text (widget
, &label
);
436 if (carbon_item
->accel_closure
)
438 group
= gtk_accel_group_from_accel_closure (carbon_item
->accel_closure
);
440 g_signal_handlers_disconnect_by_func (group
,
441 carbon_menu_item_accel_changed
,
444 g_closure_unref (carbon_item
->accel_closure
);
445 carbon_item
->accel_closure
= NULL
;
448 if (GTK_IS_ACCEL_LABEL (label
))
449 carbon_item
->accel_closure
= GTK_ACCEL_LABEL (label
)->accel_closure
;
451 if (carbon_item
->accel_closure
)
453 g_closure_ref (carbon_item
->accel_closure
);
455 group
= gtk_accel_group_from_accel_closure (carbon_item
->accel_closure
);
457 g_signal_connect_object (group
, "accel-changed",
458 G_CALLBACK (carbon_menu_item_accel_changed
),
462 carbon_menu_item_update_accelerator (carbon_item
, widget
);
466 carbon_menu_item_notify (GObject
*object
,
468 CarbonMenuItem
*carbon_item
)
470 if (!strcmp (pspec
->name
, "sensitive") ||
471 !strcmp (pspec
->name
, "visible"))
473 carbon_menu_item_update_state (carbon_item
, GTK_WIDGET (object
));
475 else if (!strcmp (pspec
->name
, "active"))
477 carbon_menu_item_update_active (carbon_item
, GTK_WIDGET (object
));
479 else if (!strcmp (pspec
->name
, "submenu"))
481 carbon_menu_item_update_submenu (carbon_item
, GTK_WIDGET (object
));
486 carbon_menu_item_notify_label (GObject
*object
,
490 CarbonMenuItem
*carbon_item
= carbon_menu_item_get (GTK_WIDGET (object
));
492 if (!strcmp (pspec
->name
, "label"))
494 carbon_menu_item_update_label (carbon_item
,
495 GTK_WIDGET (object
));
497 else if (!strcmp (pspec
->name
, "accel-closure"))
499 carbon_menu_item_update_accel_closure (carbon_item
,
500 GTK_WIDGET (object
));
504 static CarbonMenuItem
*
505 carbon_menu_item_connect (GtkWidget
*menu_item
,
510 CarbonMenuItem
*carbon_item
= carbon_menu_item_get (menu_item
);
514 carbon_item
= carbon_menu_item_new ();
516 g_object_set_qdata_full (G_OBJECT (menu_item
), carbon_menu_item_quark
,
518 (GDestroyNotify
) carbon_menu_item_free
);
520 g_signal_connect (menu_item
, "notify",
521 G_CALLBACK (carbon_menu_item_notify
),
525 g_signal_connect_swapped (label
, "notify::label",
526 G_CALLBACK (carbon_menu_item_notify_label
),
530 carbon_item
->menu
= menu
;
531 carbon_item
->index
= index
;
538 * carbon event handler
541 static int _in_carbon_menu_event_handler
= 0;
544 gdk_quartz_in_carbon_menu_event_handler ()
546 return _in_carbon_menu_event_handler
;
550 dummy_gtk_menu_item_activate (gpointer
*arg
)
552 gtk_menu_item_activate (GTK_MENU_ITEM(arg
));
557 menu_event_handler_func (EventHandlerCallRef event_handler_call_ref
,
561 UInt32 event_class
= GetEventClass (event_ref
);
562 UInt32 event_kind
= GetEventKind (event_ref
);
566 _in_carbon_menu_event_handler
= 1;
570 case kEventClassCommand
:
571 /* This is called when activating (is that the right GTK+ term?)
574 if (event_kind
== kEventCommandProcess
)
579 /*g_printerr ("Menu: kEventClassCommand/kEventCommandProcess\n");*/
581 err
= GetEventParameter (event_ref
, kEventParamDirectObject
,
583 sizeof (command
), 0, &command
);
587 GtkWidget
*widget
= NULL
;
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
))
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;
606 case kEventClassMenu
:
607 GetEventParameter (event_ref
,
608 kEventParamDirectObject
,
617 case kEventMenuTargetItem
:
618 /* This is called when an item is selected (what is the
619 * GTK+ term? prelight?)
621 /*g_printerr ("kEventClassMenu/kEventMenuTargetItem\n");*/
624 case kEventMenuOpening
:
625 /* Is it possible to dynamically build the menu here? We
626 * can at least set visibility/sensitivity.
628 /*g_printerr ("kEventClassMenu/kEventMenuOpening\n");*/
631 case kEventMenuClosed
:
632 /*g_printerr ("kEventClassMenu/kEventMenuClosed\n");*/
645 ret
= CallNextEventHandler (event_handler_call_ref
, event_ref
);
646 _in_carbon_menu_event_handler
= 0;
651 setup_menu_event_handler (void)
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
}
662 /* FIXME: We might have to install one per window? */
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
);
670 /* FIXME: Remove the handler with: */
671 RemoveEventHandler(menu_event_handler_ref
);
672 DisposeEventHandlerUPP(menu_event_handler_upp
);
677 sync_menu_shell (GtkMenuShell
*menu_shell
,
684 MenuItemIndex carbon_index
= 1;
687 g_printerr ("%s: syncing shell %p\n", G_STRFUNC
, menu_shell
);
689 carbon_menu_connect (GTK_WIDGET (menu_shell
), carbon_menu
);
691 children
= gtk_container_get_children (GTK_CONTAINER (menu_shell
));
693 for (l
= children
; l
; l
= l
->next
)
695 GtkWidget
*menu_item
= l
->data
;
696 CarbonMenuItem
*carbon_item
;
698 if (GTK_IS_TEAROFF_MENU_ITEM (menu_item
))
701 if (toplevel
&& g_object_get_data (G_OBJECT (menu_item
),
702 "gtk-empty-menu-item"))
705 carbon_item
= carbon_menu_item_get (menu_item
);
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
)));
713 if (carbon_item
&& carbon_item
->index
!= carbon_index
)
716 g_printerr ("%s: -> not matching, deleting\n", G_STRFUNC
);
718 DeleteMenuItem (carbon_item
->menu
, carbon_index
);
724 GtkWidget
*label
= NULL
;
725 const gchar
*label_text
;
726 CFStringRef cfstr
= NULL
;
727 MenuItemAttributes attributes
= 0;
730 g_printerr ("%s: -> creating new\n", G_STRFUNC
);
732 label_text
= get_menu_label_text (menu_item
, &label
);
734 cfstr
= CFStringCreateWithCString (NULL
, label_text
,
735 kCFStringEncodingUTF8
);
737 if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item
))
738 attributes
|= kMenuItemAttrSeparator
;
740 if (!GTK_WIDGET_IS_SENSITIVE (menu_item
))
741 attributes
|= kMenuItemAttrDisabled
;
743 if (!GTK_WIDGET_VISIBLE (menu_item
))
744 attributes
|= kMenuItemAttrHidden
;
746 InsertMenuItemTextWithCFString (carbon_menu
, cfstr
,
749 SetMenuItemProperty (carbon_menu
, carbon_index
,
750 IGE_QUARTZ_MENU_CREATOR
,
751 IGE_QUARTZ_ITEM_WIDGET
,
752 sizeof (menu_item
), &menu_item
);
757 carbon_item
= carbon_menu_item_connect (menu_item
, label
,
761 if (GTK_IS_CHECK_MENU_ITEM (menu_item
))
762 carbon_menu_item_update_active (carbon_item
, menu_item
);
764 carbon_menu_item_update_accel_closure (carbon_item
, menu_item
);
766 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item
)))
767 carbon_menu_item_update_submenu (carbon_item
, menu_item
);
773 g_list_free (children
);
777 static gulong emission_hook_id
= 0;
780 parent_set_emission_hook (GSignalInvocationHint
*ihint
,
781 guint n_param_values
,
782 const GValue
*param_values
,
785 GtkWidget
*instance
= g_value_get_object (param_values
);
787 if (GTK_IS_MENU_ITEM (instance
))
789 GtkWidget
*previous_parent
= g_value_get_object (param_values
+ 1);
790 GtkWidget
*menu_shell
= NULL
;
792 if (GTK_IS_MENU_SHELL (previous_parent
))
794 menu_shell
= previous_parent
;
796 else if (GTK_IS_MENU_SHELL (instance
->parent
))
798 menu_shell
= instance
->parent
;
803 CarbonMenu
*carbon_menu
= carbon_menu_get (menu_shell
);
808 g_printerr ("%s: item %s %p (%s, %s)\n", G_STRFUNC
,
809 previous_parent
? "removed from" : "added to",
811 get_menu_label_text (instance
, NULL
),
812 g_type_name (G_TYPE_FROM_INSTANCE (instance
)));
815 sync_menu_shell (GTK_MENU_SHELL (menu_shell
),
817 carbon_menu
->menu
== (MenuRef
) data
,
827 parent_set_emission_hook_remove (GtkWidget
*widget
,
830 g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
841 ige_mac_menu_set_menu_bar (GtkMenuShell
*menu_shell
)
843 MenuRef carbon_menubar
;
845 g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell
));
847 if (carbon_menu_quark
== 0)
848 carbon_menu_quark
= g_quark_from_static_string ("CarbonMenu");
850 if (carbon_menu_item_quark
== 0)
851 carbon_menu_item_quark
= g_quark_from_static_string ("CarbonMenuItem");
853 CreateNewMenu (0 /*id*/, 0 /*options*/, &carbon_menubar
);
854 SetRootMenu (carbon_menubar
);
856 setup_menu_event_handler ();
859 g_signal_add_emission_hook (g_signal_lookup ("parent-set",
862 parent_set_emission_hook
,
863 carbon_menubar
, NULL
);
865 g_signal_connect (menu_shell
, "destroy",
866 G_CALLBACK (parent_set_emission_hook_remove
),
869 sync_menu_shell (menu_shell
, carbon_menubar
, TRUE
, FALSE
);
873 ige_mac_menu_set_quit_menu_item (GtkMenuItem
*menu_item
)
878 g_return_if_fail (GTK_IS_MENU_ITEM (menu_item
));
880 if (GetIndMenuItemWithCommandID (NULL
, kHICommandQuit
, 1,
881 &appmenu
, &index
) == noErr
)
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
);
889 gtk_widget_hide (GTK_WIDGET (menu_item
));
894 struct _IgeMacMenuGroup
899 static GList
*app_menu_groups
= NULL
;
902 ige_mac_menu_add_app_menu_group (void)
904 IgeMacMenuGroup
*group
= g_slice_new0 (IgeMacMenuGroup
);
906 app_menu_groups
= g_list_append (app_menu_groups
, group
);
912 ige_mac_menu_add_app_menu_item (IgeMacMenuGroup
*group
,
913 GtkMenuItem
*menu_item
,
920 g_return_if_fail (group
!= NULL
);
921 g_return_if_fail (GTK_IS_MENU_ITEM (menu_item
));
923 if (GetIndMenuItemWithCommandID (NULL
, kHICommandHide
, 1,
924 &appmenu
, NULL
) != noErr
)
926 g_warning ("%s: retrieving app menu failed",
931 for (list
= app_menu_groups
; list
; list
= g_list_next (list
))
933 IgeMacMenuGroup
*list_group
= list
->data
;
935 index
+= g_list_length (list_group
->items
);
937 /* adjust index for the separator between groups, but not
938 * before the first group
940 if (list_group
->items
&& list
->prev
)
943 if (group
== list_group
)
947 /* add a separator before adding the first item, but not
948 * for the first group
950 if (!group
->items
&& list
->prev
)
952 InsertMenuItemTextWithCFString (appmenu
, NULL
, index
,
953 kMenuItemAttrSeparator
, 0);
958 label
= get_menu_label_text (GTK_WIDGET (menu_item
), NULL
);
960 cfstr
= CFStringCreateWithCString (NULL
, label
,
961 kCFStringEncodingUTF8
);
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
);
971 gtk_widget_hide (GTK_WIDGET (menu_item
));
973 group
->items
= g_list_append (group
->items
, menu_item
);
980 g_warning ("%s: app menu group %p does not exist",