1
/* GTK+ Integration for the Mac OS X Menubar.
3
* Copyright (C) 2007 Pioneer Research Center USA, Inc.
5
* For further information, see:
6
* http://developer.imendio.com/projects/gtk-macosx/menubar
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.
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.
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.
30
#ifdef GDK_WINDOWING_QUARTZ
32
#include <gdk/gdkkeysyms.h>
34
#include <Carbon/Carbon.h>
36
#include "ige-mac-menu.h"
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...
48
#define IGE_QUARTZ_MENU_CREATOR 'IGEC'
49
#define IGE_QUARTZ_ITEM_WIDGET 'IWID'
52
static void sync_menu_shell (GtkMenuShell *menu_shell,
63
find_menu_label (GtkWidget *widget)
65
GtkWidget *label = NULL;
67
if (GTK_IS_LABEL (widget))
70
if (GTK_IS_CONTAINER (widget))
75
children = gtk_container_get_children (GTK_CONTAINER (widget));
77
for (l = children; l; l = l->next)
79
label = find_menu_label (l->data);
84
g_list_free (children);
91
get_menu_label_text (GtkWidget *menu_item,
96
my_label = find_menu_label (menu_item);
101
return gtk_label_get_text (GTK_LABEL (my_label));
107
accel_find_func (GtkAccelKey *key,
111
return (GClosure *) data == closure;
116
* CarbonMenu functions
124
static GQuark carbon_menu_quark = 0;
127
carbon_menu_new (void)
129
return g_slice_new0 (CarbonMenu);
133
carbon_menu_free (CarbonMenu *menu)
135
g_slice_free (CarbonMenu, menu);
139
carbon_menu_get (GtkWidget *widget)
141
return g_object_get_qdata (G_OBJECT (widget), carbon_menu_quark);
145
carbon_menu_connect (GtkWidget *menu,
148
CarbonMenu *carbon_menu = carbon_menu_get (menu);
152
carbon_menu = carbon_menu_new ();
154
g_object_set_qdata_full (G_OBJECT (menu), carbon_menu_quark,
156
(GDestroyNotify) carbon_menu_free);
159
carbon_menu->menu = menuRef;
164
* CarbonMenuItem functions
172
GClosure *accel_closure;
175
static GQuark carbon_menu_item_quark = 0;
177
static CarbonMenuItem *
178
carbon_menu_item_new (void)
180
return g_slice_new0 (CarbonMenuItem);
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);
199
carbon_menu_item_update_state (CarbonMenuItem *carbon_item,
204
UInt32 set_attrs = 0;
205
UInt32 clear_attrs = 0;
207
g_object_get (widget,
208
"sensitive", &sensitive,
213
set_attrs |= kMenuItemAttrDisabled;
215
clear_attrs |= kMenuItemAttrDisabled;
218
set_attrs |= kMenuItemAttrHidden;
220
clear_attrs |= kMenuItemAttrHidden;
222
ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
223
set_attrs, clear_attrs);
227
carbon_menu_item_update_active (CarbonMenuItem *carbon_item,
232
g_object_get (widget,
236
CheckMenuItem (carbon_item->menu, carbon_item->index,
241
carbon_menu_item_update_submenu (CarbonMenuItem *carbon_item,
246
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
250
const gchar *label_text;
251
CFStringRef cfstr = NULL;
253
label_text = get_menu_label_text (widget, NULL);
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);
270
SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
272
carbon_item->submenu = NULL;
277
carbon_menu_item_update_label (CarbonMenuItem *carbon_item,
280
const gchar *label_text;
281
CFStringRef cfstr = NULL;
283
label_text = get_menu_label_text (widget, NULL);
285
cfstr = CFStringCreateWithCString (NULL, label_text,
286
kCFStringEncodingUTF8);
288
SetMenuItemTextWithCFString (carbon_item->menu, carbon_item->index,
296
carbon_menu_item_update_accelerator (CarbonMenuItem *carbon_item,
301
get_menu_label_text (widget, &label);
303
if (GTK_IS_ACCEL_LABEL (label) &&
304
GTK_ACCEL_LABEL (label)->accel_closure)
308
key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
310
GTK_ACCEL_LABEL (label)->accel_closure);
314
key->accel_flags & GTK_ACCEL_VISIBLE)
316
GdkDisplay *display = gtk_widget_get_display (widget);
317
GdkKeymap *keymap = gdk_keymap_get_for_display (display);
321
if (gdk_keymap_get_entries_for_keyval (keymap, key->accel_key,
326
SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
327
true, keys[0].keycode);
333
if (key->accel_mods & GDK_SHIFT_MASK)
334
modifiers |= kMenuShiftModifier;
336
if (key->accel_mods & GDK_MOD1_MASK)
337
modifiers |= kMenuOptionModifier;
340
if (!(key->accel_mods & GDK_CONTROL_MASK))
342
modifiers |= kMenuNoCommandModifier;
345
SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
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,
363
carbon_menu_item_accel_changed (GtkAccelGroup *accel_group,
365
GdkModifierType modifier,
366
GClosure *accel_closure,
369
CarbonMenuItem *carbon_item = carbon_menu_item_get (widget);
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);
380
carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
383
GtkAccelGroup *group;
386
get_menu_label_text (widget, &label);
388
if (carbon_item->accel_closure)
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,
396
g_closure_unref (carbon_item->accel_closure);
397
carbon_item->accel_closure = NULL;
400
if (GTK_IS_ACCEL_LABEL (label))
401
carbon_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
403
if (carbon_item->accel_closure)
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),
414
carbon_menu_item_update_accelerator (carbon_item, widget);
418
carbon_menu_item_notify (GObject *object,
420
CarbonMenuItem *carbon_item)
422
if (!strcmp (pspec->name, "sensitive") ||
423
!strcmp (pspec->name, "visible"))
425
carbon_menu_item_update_state (carbon_item, GTK_WIDGET (object));
427
else if (!strcmp (pspec->name, "active"))
429
carbon_menu_item_update_active (carbon_item, GTK_WIDGET (object));
431
else if (!strcmp (pspec->name, "submenu"))
433
carbon_menu_item_update_submenu (carbon_item, GTK_WIDGET (object));
438
carbon_menu_item_notify_label (GObject *object,
442
CarbonMenuItem *carbon_item = carbon_menu_item_get (GTK_WIDGET (object));
444
if (!strcmp (pspec->name, "label"))
446
carbon_menu_item_update_label (carbon_item,
447
GTK_WIDGET (object));
449
else if (!strcmp (pspec->name, "accel-closure"))
451
carbon_menu_item_update_accel_closure (carbon_item,
452
GTK_WIDGET (object));
456
static CarbonMenuItem *
457
carbon_menu_item_connect (GtkWidget *menu_item,
462
CarbonMenuItem *carbon_item = carbon_menu_item_get (menu_item);
466
carbon_item = carbon_menu_item_new ();
468
g_object_set_qdata_full (G_OBJECT (menu_item), carbon_menu_item_quark,
470
(GDestroyNotify) carbon_menu_item_free);
472
g_signal_connect (menu_item, "notify",
473
G_CALLBACK (carbon_menu_item_notify),
477
g_signal_connect_swapped (label, "notify::label",
478
G_CALLBACK (carbon_menu_item_notify_label),
482
carbon_item->menu = menu;
483
carbon_item->index = index;
490
* carbon event handler
494
menu_event_handler_func (EventHandlerCallRef event_handler_call_ref,
498
UInt32 event_class = GetEventClass (event_ref);
499
UInt32 event_kind = GetEventKind (event_ref);
504
case kEventClassCommand:
505
/* This is called when activating (is that the right GTK+ term?)
508
if (event_kind == kEventCommandProcess)
513
/*g_printerr ("Menu: kEventClassCommand/kEventCommandProcess\n");*/
515
err = GetEventParameter (event_ref, kEventParamDirectObject,
517
sizeof (command), 0, &command);
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))
531
gtk_menu_item_activate (GTK_MENU_ITEM (widget));
538
case kEventClassMenu:
539
GetEventParameter (event_ref,
540
kEventParamDirectObject,
549
case kEventMenuTargetItem:
550
/* This is called when an item is selected (what is the
551
* GTK+ term? prelight?)
553
/*g_printerr ("kEventClassMenu/kEventMenuTargetItem\n");*/
556
case kEventMenuOpening:
557
/* Is it possible to dynamically build the menu here? We
558
* can at least set visibility/sensitivity.
560
/*g_printerr ("kEventClassMenu/kEventMenuOpening\n");*/
563
case kEventMenuClosed:
564
/*g_printerr ("kEventClassMenu/kEventMenuClosed\n");*/
577
return CallNextEventHandler (event_handler_call_ref, event_ref);
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 }
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);
600
/* FIXME: Remove the handler with: */
601
RemoveEventHandler(menu_event_handler_ref);
602
DisposeEventHandlerUPP(menu_event_handler_upp);
607
sync_menu_shell (GtkMenuShell *menu_shell,
614
MenuItemIndex carbon_index = 1;
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)
625
GtkWidget *menu_item = l->data;
626
CarbonMenuItem *carbon_item;
628
if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
631
if (toplevel && g_object_get_data (G_OBJECT (menu_item),
632
"gtk-empty-menu-item"))
635
carbon_item = carbon_menu_item_get (menu_item);
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)
646
g_printerr ("%s: -> not matching, deleting\n", G_STRFUNC);
648
DeleteMenuItem (carbon_item->menu, carbon_index);
654
GtkWidget *label = NULL;
655
const gchar *label_text;
656
CFStringRef cfstr = NULL;
657
MenuItemAttributes attributes = 0;
660
g_printerr ("%s: -> creating new\n", G_STRFUNC);
662
label_text = get_menu_label_text (menu_item, &label);
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,
679
SetMenuItemProperty (carbon_menu, carbon_index,
680
IGE_QUARTZ_MENU_CREATOR,
681
IGE_QUARTZ_ITEM_WIDGET,
682
sizeof (menu_item), &menu_item);
687
carbon_item = carbon_menu_item_connect (menu_item, label,
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);
703
g_list_free (children);
707
static gulong emission_hook_id = 0;
710
parent_set_emission_hook (GSignalInvocationHint *ihint,
711
guint n_param_values,
712
const GValue *param_values,
715
GtkWidget *instance = g_value_get_object (param_values);
717
if (GTK_IS_MENU_ITEM (instance))
719
GtkWidget *previous_parent = g_value_get_object (param_values + 1);
720
GtkWidget *menu_shell = NULL;
722
if (GTK_IS_MENU_SHELL (previous_parent))
724
menu_shell = previous_parent;
726
else if (GTK_IS_MENU_SHELL (instance->parent))
728
menu_shell = instance->parent;
733
CarbonMenu *carbon_menu = carbon_menu_get (menu_shell);
738
g_printerr ("%s: item %s %p (%s, %s)\n", G_STRFUNC,
739
previous_parent ? "removed from" : "added to",
741
get_menu_label_text (instance, NULL),
742
g_type_name (G_TYPE_FROM_INSTANCE (instance)));
745
sync_menu_shell (GTK_MENU_SHELL (menu_shell),
747
carbon_menu->menu == (MenuRef) data,
757
parent_set_emission_hook_remove (GtkWidget *widget,
760
g_signal_remove_emission_hook (g_signal_lookup ("parent-set",
771
ige_mac_menu_set_menu_bar (GtkMenuShell *menu_shell)
773
MenuRef carbon_menubar;
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 ();
790
g_signal_add_emission_hook (g_signal_lookup ("parent-set",
793
parent_set_emission_hook,
794
carbon_menubar, NULL);
796
g_signal_connect (menu_shell, "destroy",
797
G_CALLBACK (parent_set_emission_hook_remove),
800
sync_menu_shell (menu_shell, carbon_menubar, TRUE, FALSE);
804
ige_mac_menu_set_quit_menu_item (GtkMenuItem *menu_item)
809
g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
811
if (GetIndMenuItemWithCommandID (NULL, kHICommandQuit, 1,
812
&appmenu, &index) == noErr)
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));
825
struct _IgeMacMenuGroup
830
static GList *app_menu_groups = NULL;
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);
843
ige_mac_menu_add_app_menu_item (IgeMacMenuGroup *group,
844
GtkMenuItem *menu_item,
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)
857
g_warning ("%s: retrieving app menu failed",
862
for (list = app_menu_groups; list; list = g_list_next (list))
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
871
if (list_group->items && list->prev)
874
if (group == list_group)
878
/* add a separator before adding the first item, but not
879
* for the first group
881
if (!group->items && list->prev)
883
InsertMenuItemTextWithCFString (appmenu, NULL, index,
884
kMenuItemAttrSeparator, 0);
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);
902
gtk_widget_hide (GTK_WIDGET (menu_item));
904
group->items = g_list_append (group->items, menu_item);
911
g_warning ("%s: app menu group %p does not exist",
915
#endif /* GDK_WINDOWING_QUARTZ */