]> Pileus Git - ~andy/gtk/blob - gtk/gtkquartz-menu.c
836d2e5c62b8a6a2a3e37f742186857d9b19b220
[~andy/gtk] / gtk / gtkquartz-menu.c
1 /*
2  * Copyright © 2011 William Hua, Ryan Lortie
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: William Hua <william@attente.ca>
20  *         Ryan Lortie <desrt@desrt.ca>
21  */
22
23 #include "gtkquartz-menu.h"
24
25 #include <gdk/gdkkeysyms.h>
26 #include "gtkaccelmapprivate.h"
27
28 #import <Cocoa/Cocoa.h>
29
30 /*
31  * Code for key code conversion
32  *
33  * Copyright (C) 2009 Paul Davis
34  */
35 static unichar
36 gtk_quartz_menu_get_unichar (gint key)
37 {
38   if (key >= GDK_KEY_A && key <= GDK_KEY_Z)
39     return key + (GDK_KEY_a - GDK_KEY_A);
40
41   if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde)
42     return key;
43
44   switch (key)
45     {
46       case GDK_KEY_BackSpace:
47         return NSBackspaceCharacter;
48       case GDK_KEY_Delete:
49         return NSDeleteFunctionKey;
50       case GDK_KEY_Pause:
51         return NSPauseFunctionKey;
52       case GDK_KEY_Scroll_Lock:
53         return NSScrollLockFunctionKey;
54       case GDK_KEY_Sys_Req:
55         return NSSysReqFunctionKey;
56       case GDK_KEY_Home:
57         return NSHomeFunctionKey;
58       case GDK_KEY_Left:
59       case GDK_KEY_leftarrow:
60         return NSLeftArrowFunctionKey;
61       case GDK_KEY_Up:
62       case GDK_KEY_uparrow:
63         return NSUpArrowFunctionKey;
64       case GDK_KEY_Right:
65       case GDK_KEY_rightarrow:
66         return NSRightArrowFunctionKey;
67       case GDK_KEY_Down:
68       case GDK_KEY_downarrow:
69         return NSDownArrowFunctionKey;
70       case GDK_KEY_Page_Up:
71         return NSPageUpFunctionKey;
72       case GDK_KEY_Page_Down:
73         return NSPageDownFunctionKey;
74       case GDK_KEY_End:
75         return NSEndFunctionKey;
76       case GDK_KEY_Begin:
77         return NSBeginFunctionKey;
78       case GDK_KEY_Select:
79         return NSSelectFunctionKey;
80       case GDK_KEY_Print:
81         return NSPrintFunctionKey;
82       case GDK_KEY_Execute:
83         return NSExecuteFunctionKey;
84       case GDK_KEY_Insert:
85         return NSInsertFunctionKey;
86       case GDK_KEY_Undo:
87         return NSUndoFunctionKey;
88       case GDK_KEY_Redo:
89         return NSRedoFunctionKey;
90       case GDK_KEY_Menu:
91         return NSMenuFunctionKey;
92       case GDK_KEY_Find:
93         return NSFindFunctionKey;
94       case GDK_KEY_Help:
95         return NSHelpFunctionKey;
96       case GDK_KEY_Break:
97         return NSBreakFunctionKey;
98       case GDK_KEY_Mode_switch:
99         return NSModeSwitchFunctionKey;
100       case GDK_KEY_F1:
101         return NSF1FunctionKey;
102       case GDK_KEY_F2:
103         return NSF2FunctionKey;
104       case GDK_KEY_F3:
105         return NSF3FunctionKey;
106       case GDK_KEY_F4:
107         return NSF4FunctionKey;
108       case GDK_KEY_F5:
109         return NSF5FunctionKey;
110       case GDK_KEY_F6:
111         return NSF6FunctionKey;
112       case GDK_KEY_F7:
113         return NSF7FunctionKey;
114       case GDK_KEY_F8:
115         return NSF8FunctionKey;
116       case GDK_KEY_F9:
117         return NSF9FunctionKey;
118       case GDK_KEY_F10:
119         return NSF10FunctionKey;
120       case GDK_KEY_F11:
121         return NSF11FunctionKey;
122       case GDK_KEY_F12:
123         return NSF12FunctionKey;
124       case GDK_KEY_F13:
125         return NSF13FunctionKey;
126       case GDK_KEY_F14:
127         return NSF14FunctionKey;
128       case GDK_KEY_F15:
129         return NSF15FunctionKey;
130       case GDK_KEY_F16:
131         return NSF16FunctionKey;
132       case GDK_KEY_F17:
133         return NSF17FunctionKey;
134       case GDK_KEY_F18:
135         return NSF18FunctionKey;
136       case GDK_KEY_F19:
137         return NSF19FunctionKey;
138       case GDK_KEY_F20:
139         return NSF20FunctionKey;
140       case GDK_KEY_F21:
141         return NSF21FunctionKey;
142       case GDK_KEY_F22:
143         return NSF22FunctionKey;
144       case GDK_KEY_F23:
145         return NSF23FunctionKey;
146       case GDK_KEY_F24:
147         return NSF24FunctionKey;
148       case GDK_KEY_F25:
149         return NSF25FunctionKey;
150       case GDK_KEY_F26:
151         return NSF26FunctionKey;
152       case GDK_KEY_F27:
153         return NSF27FunctionKey;
154       case GDK_KEY_F28:
155         return NSF28FunctionKey;
156       case GDK_KEY_F29:
157         return NSF29FunctionKey;
158       case GDK_KEY_F30:
159         return NSF30FunctionKey;
160       case GDK_KEY_F31:
161         return NSF31FunctionKey;
162       case GDK_KEY_F32:
163         return NSF32FunctionKey;
164       case GDK_KEY_F33:
165         return NSF33FunctionKey;
166       case GDK_KEY_F34:
167         return NSF34FunctionKey;
168       case GDK_KEY_F35:
169         return NSF35FunctionKey;
170       default:
171         break;
172     }
173
174   return '\0';
175 }
176
177
178
179 typedef struct _GtkQuartzActionObserver GtkQuartzActionObserver;
180
181
182
183 @interface GNSMenu : NSMenu
184 {
185   GActionObservable *actions;
186   GMenuModel        *model;
187   guint              update_idle;
188   GSList            *connected;
189   gboolean           with_separators;
190 }
191
192 - (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel actions:(GActionObservable *)someActions hasSeparators:(BOOL)hasSeparators;
193
194 - (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added;
195
196 - (gboolean)handleChanges;
197
198 @end
199
200
201
202 @interface GNSMenuItem : NSMenuItem
203 {
204   gchar                   *action;
205   GVariant                *target;
206   BOOL                     canActivate;
207   GActionGroup            *actions;
208   GtkQuartzActionObserver *observer;
209 }
210
211 - (id)initWithModel:(GMenuModel *)model index:(NSInteger)index observable:(GActionObservable *)observable;
212
213 - (void)observableActionAddedWithParameterType:(const GVariantType *)parameterType enabled:(BOOL)enabled state:(GVariant *)state;
214 - (void)observableActionEnabledChangedTo:(BOOL)enabled;
215 - (void)observableActionStateChangedTo:(GVariant *)state;
216 - (void)observableActionRemoved;
217
218 - (void)didSelectItem:(id)sender;
219
220 @end
221
222
223
224 struct _GtkQuartzActionObserver
225 {
226   GObject parent_instance;
227
228   GNSMenuItem *item;
229 };
230
231
232
233 typedef GObjectClass GtkQuartzActionObserverClass;
234
235 static void gtk_quartz_action_observer_observer_iface_init (GActionObserverInterface *iface);
236 G_DEFINE_TYPE_WITH_CODE (GtkQuartzActionObserver, gtk_quartz_action_observer, G_TYPE_OBJECT,
237                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_quartz_action_observer_observer_iface_init))
238
239 static void
240 gtk_quartz_action_observer_action_added (GActionObserver    *observer,
241                                          GActionObservable  *observable,
242                                          const gchar        *action_name,
243                                          const GVariantType *parameter_type,
244                                          gboolean            enabled,
245                                          GVariant           *state)
246 {
247   GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
248
249   [qao->item observableActionAddedWithParameterType:parameter_type enabled:enabled state:state];
250 }
251
252 static void
253 gtk_quartz_action_observer_action_enabled_changed (GActionObserver   *observer,
254                                                    GActionObservable *observable,
255                                                    const gchar       *action_name,
256                                                    gboolean           enabled)
257 {
258   GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
259
260   [qao->item observableActionEnabledChangedTo:enabled];
261 }
262
263 static void
264 gtk_quartz_action_observer_action_state_changed (GActionObserver   *observer,
265                                                  GActionObservable *observable,
266                                                  const gchar       *action_name,
267                                                  GVariant          *state)
268 {
269   GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
270
271   [qao->item observableActionStateChangedTo:state];
272 }
273
274 static void
275 gtk_quartz_action_observer_action_removed (GActionObserver   *observer,
276                                            GActionObservable *observable,
277                                            const gchar       *action_name)
278 {
279   GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
280
281   [qao->item observableActionRemoved];
282 }
283
284 static void
285 gtk_quartz_action_observer_init (GtkQuartzActionObserver *item)
286 {
287 }
288
289 static void
290 gtk_quartz_action_observer_observer_iface_init (GActionObserverInterface *iface)
291 {
292   iface->action_added = gtk_quartz_action_observer_action_added;
293   iface->action_enabled_changed = gtk_quartz_action_observer_action_enabled_changed;
294   iface->action_state_changed = gtk_quartz_action_observer_action_state_changed;
295   iface->action_removed = gtk_quartz_action_observer_action_removed;
296 }
297
298 static void
299 gtk_quartz_action_observer_class_init (GtkQuartzActionObserverClass *class)
300 {
301 }
302
303 static GtkQuartzActionObserver *
304 gtk_quartz_action_observer_new (GNSMenuItem *item)
305 {
306   GtkQuartzActionObserver *observer;
307
308   observer = g_object_new (gtk_quartz_action_observer_get_type (), NULL);
309   observer->item = item;
310
311   return observer;
312 }
313
314 static gboolean
315 gtk_quartz_menu_handle_changes (gpointer user_data)
316 {
317   GNSMenu *menu = user_data;
318
319   return [menu handleChanges];
320 }
321
322 static void
323 gtk_quartz_menu_items_changed (GMenuModel *model,
324                                gint        position,
325                                gint        removed,
326                                gint        added,
327                                gpointer    user_data)
328 {
329   GNSMenu *menu = user_data;
330
331   [menu model:model didChangeAtPosition:position removed:removed added:added];
332 }
333
334 void
335 gtk_quartz_set_main_menu (GMenuModel        *model,
336                           GActionObservable *observable)
337 {
338   [NSApp setMainMenu:[[[GNSMenu alloc] initWithTitle:@"Main Menu" model:model actions:observable hasSeparators:NO] autorelease]];
339 }
340
341 @interface GNSMenu ()
342
343 - (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators;
344
345 @end
346
347
348
349 @implementation GNSMenu
350
351 - (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added
352 {
353   if (update_idle == 0)
354     update_idle = gdk_threads_add_idle (gtk_quartz_menu_handle_changes, self);
355 }
356
357 - (void)appendItemFromModel:(GMenuModel *)aModel atIndex:(gint)index withHeading:(gchar **)heading
358 {
359   GMenuModel *section;
360
361   if ((section = g_menu_model_get_item_link (aModel, index, G_MENU_LINK_SECTION)))
362     {
363       g_menu_model_get_item_attribute (aModel, index, G_MENU_ATTRIBUTE_LABEL, "s", heading);
364       [self appendFromModel:section withSeparators:NO];
365       g_object_unref (section);
366     }
367   else
368     [self addItem:[[[GNSMenuItem alloc] initWithModel:aModel index:index observable:actions] autorelease]];
369 }
370
371 - (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators
372 {
373   gint n, i;
374
375   g_signal_connect (aModel, "items-changed", G_CALLBACK (gtk_quartz_menu_items_changed), self);
376   connected = g_slist_prepend (connected, g_object_ref (aModel));
377
378   n = g_menu_model_get_n_items (aModel);
379
380   for (i = 0; i < n; i++)
381     {
382       NSInteger ourPosition = [self numberOfItems];
383       gchar *heading = NULL;
384
385       [self appendItemFromModel:aModel atIndex:i withHeading:&heading];
386
387       if (withSeparators && ourPosition < [self numberOfItems])
388         {
389           NSMenuItem *separator = nil;
390
391           if (heading)
392             {
393               separator = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:heading] action:NULL keyEquivalent:@""] autorelease];
394
395               [separator setEnabled:NO];
396             }
397           else if (ourPosition > 0)
398             separator = [NSMenuItem separatorItem];
399
400           if (separator != nil)
401             [self insertItem:separator atIndex:ourPosition];
402         }
403
404       g_free (heading);
405     }
406 }
407
408 - (void)populate
409 {
410   [self removeAllItems];
411
412   [self appendFromModel:model withSeparators:with_separators];
413 }
414
415 - (gboolean)handleChanges
416 {
417   while (connected)
418     {
419       g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_menu_items_changed, self);
420       g_object_unref (connected->data);
421
422       connected = g_slist_delete_link (connected, connected);
423     }
424
425   [self populate];
426
427   update_idle = 0;
428
429   return G_SOURCE_REMOVE;
430 }
431
432 - (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel actions:(GActionObservable *)someActions hasSeparators:(BOOL)hasSeparators
433 {
434   if((self = [super initWithTitle:title]) != nil)
435     {
436       [self setAutoenablesItems:NO];
437
438       model = g_object_ref (aModel);
439       actions = g_object_ref (someActions);
440       with_separators = hasSeparators;
441
442       [self populate];
443     }
444
445   return self;
446 }
447
448 - (void)dealloc
449 {
450   while (connected)
451     {
452       g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_menu_items_changed, self);
453       g_object_unref (connected->data);
454
455       connected = g_slist_delete_link (connected, connected);
456     }
457
458   g_object_unref (actions);
459   g_object_unref (model);
460
461   [super dealloc];
462 }
463
464 @end
465
466
467
468 @implementation GNSMenuItem
469
470 - (id)initWithModel:(GMenuModel *)model index:(NSInteger)index observable:(GActionObservable *)observable
471 {
472   gchar *title = NULL;
473
474   if (g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_LABEL, "s", &title))
475     {
476       gchar *from, *to;
477
478       to = from = title;
479
480       while (*from)
481         {
482           if (*from == '_' && from[1])
483             from++;
484
485           *to++ = *from++;
486         }
487
488       *to = '\0';
489     }
490
491   if ((self = [super initWithTitle:[NSString stringWithUTF8String:title ? : ""] action:@selector(didSelectItem:) keyEquivalent:@""]) != nil)
492     {
493       GMenuModel *submenu;
494
495       g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_ACTION, "s", &action);
496       target = g_menu_model_get_item_attribute_value (model, index, G_MENU_ATTRIBUTE_TARGET, NULL);
497       actions = g_object_ref (observable);
498       observer = gtk_quartz_action_observer_new (self);
499
500       if ((submenu = g_menu_model_get_item_link (model, index, G_MENU_LINK_SUBMENU)))
501         {
502           [self setSubmenu:[[[GNSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title] model:submenu actions:observable hasSeparators:YES] autorelease]];
503           g_object_unref (submenu);
504         }
505
506       else if (action != NULL)
507         {
508           GtkAccelKey key;
509           gchar *path;
510
511           g_action_observable_register_observer (observable, action, G_ACTION_OBSERVER (observer));
512
513           path = _gtk_accel_path_for_action (action, target);
514           if (gtk_accel_map_lookup_entry (path, &key))
515             {
516               unichar character = gtk_quartz_menu_get_unichar (key.accel_key);
517
518               if (character)
519                 {
520                   NSUInteger modifiers = 0;
521
522                   if (key.accel_mods & GDK_SHIFT_MASK)
523                     modifiers |= NSShiftKeyMask;
524
525                   if (key.accel_mods & GDK_MOD1_MASK)
526                     modifiers |= NSAlternateKeyMask;
527
528                   if (key.accel_mods & GDK_CONTROL_MASK)
529                     modifiers |= NSControlKeyMask;
530
531                   if (key.accel_mods & GDK_META_MASK)
532                     modifiers |= NSCommandKeyMask;
533
534                   [self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]];
535                   [self setKeyEquivalentModifierMask:modifiers];
536                 }
537             }
538
539           g_free (path);
540
541           [self setTarget:self];
542
543           gboolean            enabled;
544           const GVariantType *parameterType;
545           GVariant           *state;
546
547           if (g_action_group_query_action (actions, action, &enabled, &parameterType, NULL, NULL, &state))
548             [self observableActionAddedWithParameterType:parameterType enabled:enabled state:state];
549           else
550             [self setEnabled:NO];
551         }
552     }
553
554   g_free (title);
555
556   return self;
557 }
558
559 - (void)dealloc
560 {
561   if (observer != NULL)
562     g_object_unref (observer);
563
564   if (actions != NULL)
565     g_object_unref (actions);
566
567   if (target != NULL)
568     g_variant_unref (target);
569
570   g_free (action);
571
572   [super dealloc];
573 }
574
575 - (void)observableActionAddedWithParameterType:(const GVariantType *)parameterType enabled:(BOOL)enabled state:(GVariant *)state
576 {
577   canActivate = (target == NULL && parameterType == NULL) ||
578                 (target != NULL && parameterType != NULL &&
579                  g_variant_is_of_type (target, parameterType));
580
581   if (canActivate)
582     {
583       if (target != NULL && state != NULL)
584         {
585           [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
586           [self setState:g_variant_equal (state, target) ? NSOnState : NSOffState];
587         }
588       else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
589         {
590           [self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]];
591           [self setState:g_variant_get_boolean (state) ? NSOnState : NSOffState];
592         }
593       else
594         [self setState:NSOffState];
595
596       [self setEnabled:enabled];
597     }
598   else
599     [self setEnabled:NO];
600 }
601
602 - (void)observableActionEnabledChangedTo:(BOOL)enabled
603 {
604   if (canActivate)
605     [self setEnabled:enabled];
606 }
607
608 - (void)observableActionStateChangedTo:(GVariant *)state
609 {
610   if (canActivate)
611     {
612       if (target != NULL)
613         [self setState:g_variant_equal (state, target) ? NSOnState : NSOffState];
614       else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
615         [self setState:g_variant_get_boolean (state) ? NSOnState : NSOffState];
616     }
617 }
618
619 - (void)observableActionRemoved
620 {
621   if (canActivate)
622     [self setEnabled:NO];
623 }
624
625 - (void)didSelectItem:(id)sender
626 {
627   if (canActivate)
628     g_action_group_activate_action (actions, action, target);
629 }
630
631 @end