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