comparison src/audlegacy/sync-menu.c @ 4811:7bf7f83a217e

rename src/audacious src/audlegacy so that both audlegacy and audacious can coexist.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 26 Nov 2008 00:44:56 +0900
parents src/audacious/sync-menu.c@db83b4a786ed
children
comparison
equal deleted inserted replaced
4810:c10e53092037 4811:7bf7f83a217e
1 /* GTK+ Integration for the Mac OS X Menubar.
2 *
3 * Copyright (C) 2007 Pioneer Research Center USA, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21 #include <gtk/gtk.h>
22
23 #ifdef GDK_WINDOWING_QUARTZ
24
25 #include <gdk/gdkkeysyms.h>
26
27 #include <Carbon/Carbon.h>
28
29 #include "sync-menu.h"
30
31
32 /* TODO
33 *
34 * - Setup shortcuts, possibly transforming ctrl->cmd
35 * - Sync menus
36 * - Create on demand? (can this be done with gtk+? ie fill in menu items when the menu is opened)
37 * - Figure out what to do per app/window...
38 * - Toggle/radio items
39 *
40 */
41
42 #define GTK_QUARTZ_MENU_CREATOR 'GTKC'
43 #define GTK_QUARTZ_ITEM_WIDGET 'GWID'
44
45
46 static void sync_menu_shell (GtkMenuShell *menu_shell,
47 MenuRef carbon_menu,
48 gboolean toplevel);
49
50
51 /*
52 * utility functions
53 */
54
55 static GtkWidget *
56 find_menu_label (GtkWidget *widget)
57 {
58 GtkWidget *label = NULL;
59
60 if (GTK_IS_LABEL (widget))
61 return widget;
62
63 if (GTK_IS_CONTAINER (widget))
64 {
65 GList *children;
66 GList *l;
67
68 children = gtk_container_get_children (GTK_CONTAINER (widget));
69
70 for (l = children; l; l = l->next)
71 {
72 label = find_menu_label (l->data);
73 if (label)
74 break;
75 }
76
77 g_list_free (children);
78 }
79
80 return label;
81 }
82
83 static const gchar *
84 get_menu_label_text (GtkWidget *menu_item,
85 GtkWidget **label)
86 {
87 *label = find_menu_label (menu_item);
88 if (!*label)
89 return NULL;
90
91 return gtk_label_get_text (GTK_LABEL (*label));
92 }
93
94 static gboolean
95 accel_find_func (GtkAccelKey *key,
96 GClosure *closure,
97 gpointer data)
98 {
99 return (GClosure *) data == closure;
100 }
101
102
103 /*
104 * CarbonMenu functions
105 */
106
107 typedef struct
108 {
109 MenuRef menu;
110 } CarbonMenu;
111
112 static GQuark carbon_menu_quark = 0;
113
114 static CarbonMenu *
115 carbon_menu_new (void)
116 {
117 return g_slice_new0 (CarbonMenu);
118 }
119
120 static void
121 carbon_menu_free (CarbonMenu *menu)
122 {
123 g_slice_free (CarbonMenu, menu);
124 }
125
126 static CarbonMenu *
127 carbon_menu_get (GtkWidget *widget)
128 {
129 return g_object_get_qdata (G_OBJECT (widget), carbon_menu_quark);
130 }
131
132 static void
133 carbon_menu_connect (GtkWidget *menu,
134 MenuRef menuRef)
135 {
136 CarbonMenu *carbon_menu = carbon_menu_get (menu);
137
138 if (!carbon_menu)
139 {
140 carbon_menu = carbon_menu_new ();
141
142 g_object_set_qdata_full (G_OBJECT (menu), carbon_menu_quark,
143 carbon_menu,
144 (GDestroyNotify) carbon_menu_free);
145 }
146
147 carbon_menu->menu = menuRef;
148 }
149
150
151 /*
152 * CarbonMenuItem functions
153 */
154
155 typedef struct
156 {
157 MenuRef menu;
158 MenuItemIndex index;
159 MenuRef submenu;
160 GClosure *accel_closure;
161 } CarbonMenuItem;
162
163 static GQuark carbon_menu_item_quark = 0;
164
165 static CarbonMenuItem *
166 carbon_menu_item_new (void)
167 {
168 return g_slice_new0 (CarbonMenuItem);
169 }
170
171 static void
172 carbon_menu_item_free (CarbonMenuItem *menu_item)
173 {
174 if (menu_item->accel_closure)
175 g_closure_unref (menu_item->accel_closure);
176
177 g_slice_free (CarbonMenuItem, menu_item);
178 }
179
180 static CarbonMenuItem *
181 carbon_menu_item_get (GtkWidget *widget)
182 {
183 return g_object_get_qdata (G_OBJECT (widget), carbon_menu_item_quark);
184 }
185
186 static void
187 carbon_menu_item_update_state (CarbonMenuItem *carbon_item,
188 GtkWidget *widget)
189 {
190 gboolean sensitive;
191 gboolean visible;
192 UInt32 set_attrs = 0;
193 UInt32 clear_attrs = 0;
194
195 g_object_get (widget,
196 "sensitive", &sensitive,
197 "visible", &visible,
198 NULL);
199
200 if (!sensitive)
201 set_attrs |= kMenuItemAttrDisabled;
202 else
203 clear_attrs |= kMenuItemAttrDisabled;
204
205 if (!visible)
206 set_attrs |= kMenuItemAttrHidden;
207 else
208 clear_attrs |= kMenuItemAttrHidden;
209
210 ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
211 set_attrs, clear_attrs);
212 }
213
214 static void
215 carbon_menu_item_update_active (CarbonMenuItem *carbon_item,
216 GtkWidget *widget)
217 {
218 gboolean active;
219
220 g_object_get (widget,
221 "active", &active,
222 NULL);
223
224 CheckMenuItem (carbon_item->menu, carbon_item->index,
225 active);
226 }
227
228 static void
229 carbon_menu_item_update_submenu (CarbonMenuItem *carbon_item,
230 GtkWidget *widget)
231 {
232 GtkWidget *submenu;
233
234 submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
235
236 if (submenu)
237 {
238 GtkWidget *label = NULL;
239 const gchar *label_text;
240 CFStringRef cfstr = NULL;
241
242 label_text = get_menu_label_text (widget, &label);
243 if (label_text)
244 cfstr = CFStringCreateWithCString (NULL, label_text,
245 kCFStringEncodingUTF8);
246
247 CreateNewMenu (0, 0, &carbon_item->submenu);
248 SetMenuTitleWithCFString (carbon_item->submenu, cfstr);
249 SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
250 carbon_item->submenu);
251
252 sync_menu_shell (GTK_MENU_SHELL (submenu), carbon_item->submenu, FALSE);
253
254 if (cfstr)
255 CFRelease (cfstr);
256 }
257 else
258 {
259 SetMenuItemHierarchicalMenu (carbon_item->menu, carbon_item->index,
260 NULL);
261 carbon_item->submenu = NULL;
262 }
263 }
264
265 static void
266 carbon_menu_item_update_label (CarbonMenuItem *carbon_item,
267 GtkWidget *widget)
268 {
269 GtkWidget *label;
270 const gchar *label_text;
271 CFStringRef cfstr = NULL;
272
273 label_text = get_menu_label_text (widget, &label);
274 if (label_text)
275 cfstr = CFStringCreateWithCString (NULL, label_text,
276 kCFStringEncodingUTF8);
277
278 SetMenuItemTextWithCFString (carbon_item->menu, carbon_item->index,
279 cfstr);
280
281 if (cfstr)
282 CFRelease (cfstr);
283 }
284
285 static void
286 carbon_menu_item_update_accelerator (CarbonMenuItem *carbon_item,
287 GtkWidget *widget)
288 {
289 GtkWidget *label;
290
291 get_menu_label_text (widget, &label);
292
293 if (GTK_IS_ACCEL_LABEL (label) &&
294 GTK_ACCEL_LABEL (label)->accel_closure)
295 {
296 GtkAccelKey *key;
297
298 key = gtk_accel_group_find (GTK_ACCEL_LABEL (label)->accel_group,
299 accel_find_func,
300 GTK_ACCEL_LABEL (label)->accel_closure);
301
302 if (key &&
303 key->accel_key &&
304 key->accel_flags & GTK_ACCEL_VISIBLE)
305 {
306 GdkDisplay *display = gtk_widget_get_display (widget);
307 GdkKeymap *keymap = gdk_keymap_get_for_display (display);
308 GdkKeymapKey *keys;
309 gint n_keys;
310
311 if (gdk_keymap_get_entries_for_keyval (keymap, key->accel_key,
312 &keys, &n_keys))
313 {
314 UInt8 modifiers = 0;
315
316 SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
317 true, keys[0].keycode);
318
319 g_free (keys);
320
321 if (key->accel_mods)
322 {
323 if (key->accel_mods & GDK_SHIFT_MASK)
324 modifiers |= kMenuShiftModifier;
325
326 if (key->accel_mods & GDK_MOD1_MASK)
327 modifiers |= kMenuOptionModifier;
328 }
329
330 if (!(key->accel_mods & GDK_CONTROL_MASK))
331 {
332 modifiers |= kMenuNoCommandModifier;
333 }
334
335 SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
336 modifiers);
337
338 return;
339 }
340 }
341 }
342
343 /* otherwise, clear the menu shortcut */
344 SetMenuItemModifiers (carbon_item->menu, carbon_item->index,
345 kMenuNoModifiers | kMenuNoCommandModifier);
346 ChangeMenuItemAttributes (carbon_item->menu, carbon_item->index,
347 0, kMenuItemAttrUseVirtualKey);
348 SetMenuItemCommandKey (carbon_item->menu, carbon_item->index,
349 false, 0);
350 }
351
352 static void
353 carbon_menu_item_accel_changed (GtkAccelGroup *accel_group,
354 guint keyval,
355 GdkModifierType modifier,
356 GClosure *accel_closure,
357 GtkWidget *widget)
358 {
359 CarbonMenuItem *carbon_item = carbon_menu_item_get (widget);
360 GtkWidget *label;
361
362 get_menu_label_text (widget, &label);
363
364 if (GTK_IS_ACCEL_LABEL (label) &&
365 GTK_ACCEL_LABEL (label)->accel_closure == accel_closure)
366 carbon_menu_item_update_accelerator (carbon_item, widget);
367 }
368
369 static void
370 carbon_menu_item_update_accel_closure (CarbonMenuItem *carbon_item,
371 GtkWidget *widget)
372 {
373 GtkAccelGroup *group;
374 GtkWidget *label;
375
376 get_menu_label_text (widget, &label);
377
378 if (carbon_item->accel_closure)
379 {
380 group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
381
382 g_signal_handlers_disconnect_by_func (group,
383 carbon_menu_item_accel_changed,
384 widget);
385
386 g_closure_unref (carbon_item->accel_closure);
387 carbon_item->accel_closure = NULL;
388 }
389
390 if (GTK_IS_ACCEL_LABEL (label))
391 carbon_item->accel_closure = GTK_ACCEL_LABEL (label)->accel_closure;
392
393 if (carbon_item->accel_closure)
394 {
395 g_closure_ref (carbon_item->accel_closure);
396
397 group = gtk_accel_group_from_accel_closure (carbon_item->accel_closure);
398
399 g_signal_connect_object (group, "accel-changed",
400 G_CALLBACK (carbon_menu_item_accel_changed),
401 widget, 0);
402 }
403
404 carbon_menu_item_update_accelerator (carbon_item, widget);
405 }
406
407 static void
408 carbon_menu_item_notify (GObject *object,
409 GParamSpec *pspec,
410 CarbonMenuItem *carbon_item)
411 {
412 if (!strcmp (pspec->name, "sensitive") ||
413 !strcmp (pspec->name, "visible"))
414 {
415 carbon_menu_item_update_state (carbon_item, GTK_WIDGET (object));
416 }
417 else if (!strcmp (pspec->name, "active"))
418 {
419 carbon_menu_item_update_active (carbon_item, GTK_WIDGET (object));
420 }
421 else if (!strcmp (pspec->name, "submenu"))
422 {
423 carbon_menu_item_update_submenu (carbon_item, GTK_WIDGET (object));
424 }
425 }
426
427 static void
428 carbon_menu_item_notify_label (GObject *object,
429 GParamSpec *pspec,
430 gpointer data)
431 {
432 CarbonMenuItem *carbon_item = carbon_menu_item_get (GTK_WIDGET (object));
433
434 if (!strcmp (pspec->name, "label"))
435 {
436 carbon_menu_item_update_label (carbon_item,
437 GTK_WIDGET (object));
438 }
439 else if (!strcmp (pspec->name, "accel-closure"))
440 {
441 carbon_menu_item_update_accel_closure (carbon_item,
442 GTK_WIDGET (object));
443 }
444 }
445
446 static CarbonMenuItem *
447 carbon_menu_item_connect (GtkWidget *menu_item,
448 GtkWidget *label,
449 MenuRef menu,
450 MenuItemIndex index)
451 {
452 CarbonMenuItem *carbon_item = carbon_menu_item_get (menu_item);
453
454 if (!carbon_item)
455 {
456 carbon_item = carbon_menu_item_new ();
457
458 g_object_set_qdata_full (G_OBJECT (menu_item), carbon_menu_item_quark,
459 carbon_item,
460 (GDestroyNotify) carbon_menu_item_free);
461
462 g_signal_connect (menu_item, "notify",
463 G_CALLBACK (carbon_menu_item_notify),
464 carbon_item);
465
466 if (label)
467 g_signal_connect_swapped (label, "notify::label",
468 G_CALLBACK (carbon_menu_item_notify_label),
469 menu_item);
470 }
471
472 carbon_item->menu = menu;
473 carbon_item->index = index;
474
475 return carbon_item;
476 }
477
478
479 /*
480 * carbon event handler
481 */
482
483 static OSStatus
484 menu_event_handler_func (EventHandlerCallRef event_handler_call_ref,
485 EventRef event_ref,
486 void *data)
487 {
488 UInt32 event_class = GetEventClass (event_ref);
489 UInt32 event_kind = GetEventKind (event_ref);
490 MenuRef menu_ref;
491
492 switch (event_class)
493 {
494 case kEventClassCommand:
495 /* This is called when activating (is that the right GTK+ term?)
496 * a menu item.
497 */
498 if (event_kind == kEventCommandProcess)
499 {
500 HICommand command;
501 OSStatus err;
502
503 //g_print ("Menu: kEventClassCommand/kEventCommandProcess\n");
504
505 err = GetEventParameter (event_ref, kEventParamDirectObject,
506 typeHICommand, 0,
507 sizeof (command), 0, &command);
508
509 if (err == noErr)
510 {
511 GtkWidget *widget = NULL;
512
513 if (command.commandID == kHICommandQuit)
514 {
515 gtk_main_quit (); /* Just testing... */
516 return noErr;
517 }
518
519 /* Get any GtkWidget associated with the item. */
520 err = GetMenuItemProperty (command.menu.menuRef,
521 command.menu.menuItemIndex,
522 GTK_QUARTZ_MENU_CREATOR,
523 GTK_QUARTZ_ITEM_WIDGET,
524 sizeof (widget), 0, &widget);
525 if (err == noErr && widget)
526 {
527 gtk_menu_item_activate (GTK_MENU_ITEM (widget));
528 return noErr;
529 }
530 }
531 }
532 break;
533
534 case kEventClassMenu:
535 GetEventParameter (event_ref,
536 kEventParamDirectObject,
537 typeMenuRef,
538 NULL,
539 sizeof (menu_ref),
540 NULL,
541 &menu_ref);
542
543 switch (event_kind)
544 {
545 case kEventMenuTargetItem:
546 /* This is called when an item is selected (what is the
547 * GTK+ term? prelight?)
548 */
549 //g_print ("kEventClassMenu/kEventMenuTargetItem\n");
550 break;
551
552 case kEventMenuOpening:
553 /* Is it possible to dynamically build the menu here? We
554 * can at least set visibility/sensitivity.
555 */
556 //g_print ("kEventClassMenu/kEventMenuOpening\n");
557 break;
558
559 case kEventMenuClosed:
560 //g_print ("kEventClassMenu/kEventMenuClosed\n");
561 break;
562
563 default:
564 break;
565 }
566
567 break;
568
569 default:
570 break;
571 }
572
573 return CallNextEventHandler (event_handler_call_ref, event_ref);
574 }
575
576 static void
577 setup_menu_event_handler (void)
578 {
579 EventHandlerUPP menu_event_handler_upp;
580 EventHandlerRef menu_event_handler_ref;
581 const EventTypeSpec menu_events[] = {
582 { kEventClassCommand, kEventCommandProcess },
583 { kEventClassMenu, kEventMenuTargetItem },
584 { kEventClassMenu, kEventMenuOpening },
585 { kEventClassMenu, kEventMenuClosed }
586 };
587
588 /* FIXME: We might have to install one per window? */
589
590 menu_event_handler_upp = NewEventHandlerUPP (menu_event_handler_func);
591 InstallEventHandler (GetApplicationEventTarget (), menu_event_handler_upp,
592 GetEventTypeCount (menu_events), menu_events, 0,
593 &menu_event_handler_ref);
594
595 #if 0
596 /* FIXME: Remove the handler with: */
597 RemoveEventHandler(menu_event_handler_ref);
598 DisposeEventHandlerUPP(menu_event_handler_upp);
599 #endif
600 }
601
602 static void
603 sync_menu_shell (GtkMenuShell *menu_shell,
604 MenuRef carbon_menu,
605 gboolean toplevel)
606 {
607 GList *children;
608 GList *l;
609 MenuItemIndex carbon_index = 1;
610
611 carbon_menu_connect (GTK_WIDGET (menu_shell), carbon_menu);
612
613 children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
614
615 for (l = children; l; l = l->next)
616 {
617 GtkWidget *menu_item = l->data;
618 CarbonMenuItem *carbon_item;
619
620 if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
621 continue;
622
623 if (toplevel && g_object_get_data (G_OBJECT (menu_item), "gtk-empty-menu-item"))
624 continue;
625
626 carbon_item = carbon_menu_item_get (menu_item);
627
628 if (carbon_item && carbon_item->index != carbon_index)
629 {
630 DeleteMenuItem (carbon_item->menu,
631 carbon_item->index);
632 carbon_item = NULL;
633 }
634
635 if (!carbon_item)
636 {
637 GtkWidget *label = NULL;
638 const gchar *label_text;
639 CFStringRef cfstr = NULL;
640 MenuItemAttributes attributes = 0;
641
642 label_text = get_menu_label_text (menu_item, &label);
643 if (label_text)
644 cfstr = CFStringCreateWithCString (NULL, label_text,
645 kCFStringEncodingUTF8);
646
647 if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
648 attributes |= kMenuItemAttrSeparator;
649
650 if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
651 attributes |= kMenuItemAttrDisabled;
652
653 if (!GTK_WIDGET_VISIBLE (menu_item))
654 attributes |= kMenuItemAttrHidden;
655
656 InsertMenuItemTextWithCFString (carbon_menu, cfstr,
657 carbon_index,
658 attributes, 0);
659 SetMenuItemProperty (carbon_menu, carbon_index,
660 GTK_QUARTZ_MENU_CREATOR,
661 GTK_QUARTZ_ITEM_WIDGET,
662 sizeof (menu_item), &menu_item);
663
664 if (cfstr)
665 CFRelease (cfstr);
666
667 carbon_item = carbon_menu_item_connect (menu_item, label,
668 carbon_menu,
669 carbon_index);
670
671 if (GTK_IS_CHECK_MENU_ITEM (menu_item))
672 carbon_menu_item_update_active (carbon_item, menu_item);
673
674 carbon_menu_item_update_accel_closure (carbon_item, menu_item);
675
676 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
677 carbon_menu_item_update_submenu (carbon_item, menu_item);
678 }
679
680 carbon_index++;
681 }
682
683 g_list_free (children);
684 }
685
686 void
687 sync_menu_takeover_menu (GtkMenuShell *menu_shell)
688 {
689 MenuRef carbon_menubar;
690
691 g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
692
693 if (carbon_menu_quark == 0)
694 carbon_menu_quark = g_quark_from_static_string ("CarbonMenu");
695
696 if (carbon_menu_item_quark == 0)
697 carbon_menu_item_quark = g_quark_from_static_string ("CarbonMenuItem");
698
699 CreateNewMenu (0 /*id*/, 0 /*options*/, &carbon_menubar);
700 SetRootMenu (carbon_menubar);
701
702 setup_menu_event_handler ();
703
704 sync_menu_shell (menu_shell, carbon_menubar, TRUE);
705 }
706
707 #else
708
709 void
710 sync_menu_takeover_menu (GtkMenuShell *menu_shell)
711 {
712
713 }
714
715 #endif