2 * Copyright © 2011 William Hua, Ryan Lortie
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.
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.
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/>.
17 * Author: William Hua <william@attente.ca>
18 * Ryan Lortie <desrt@desrt.ca>
21 #include "gtkquartz-menu.h"
23 #include <gdk/gdkkeysyms.h>
24 #include "gtkaccelmapprivate.h"
26 #import <Cocoa/Cocoa.h>
29 * Code for key code conversion
31 * Copyright (C) 2009 Paul Davis
34 gtk_quartz_menu_get_unichar (gint key)
36 if (key >= GDK_KEY_A && key <= GDK_KEY_Z)
37 return key + (GDK_KEY_a - GDK_KEY_A);
39 if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde)
44 case GDK_KEY_BackSpace:
45 return NSBackspaceCharacter;
47 return NSDeleteFunctionKey;
49 return NSPauseFunctionKey;
50 case GDK_KEY_Scroll_Lock:
51 return NSScrollLockFunctionKey;
53 return NSSysReqFunctionKey;
55 return NSHomeFunctionKey;
57 case GDK_KEY_leftarrow:
58 return NSLeftArrowFunctionKey;
61 return NSUpArrowFunctionKey;
63 case GDK_KEY_rightarrow:
64 return NSRightArrowFunctionKey;
66 case GDK_KEY_downarrow:
67 return NSDownArrowFunctionKey;
69 return NSPageUpFunctionKey;
70 case GDK_KEY_Page_Down:
71 return NSPageDownFunctionKey;
73 return NSEndFunctionKey;
75 return NSBeginFunctionKey;
77 return NSSelectFunctionKey;
79 return NSPrintFunctionKey;
81 return NSExecuteFunctionKey;
83 return NSInsertFunctionKey;
85 return NSUndoFunctionKey;
87 return NSRedoFunctionKey;
89 return NSMenuFunctionKey;
91 return NSFindFunctionKey;
93 return NSHelpFunctionKey;
95 return NSBreakFunctionKey;
96 case GDK_KEY_Mode_switch:
97 return NSModeSwitchFunctionKey;
99 return NSF1FunctionKey;
101 return NSF2FunctionKey;
103 return NSF3FunctionKey;
105 return NSF4FunctionKey;
107 return NSF5FunctionKey;
109 return NSF6FunctionKey;
111 return NSF7FunctionKey;
113 return NSF8FunctionKey;
115 return NSF9FunctionKey;
117 return NSF10FunctionKey;
119 return NSF11FunctionKey;
121 return NSF12FunctionKey;
123 return NSF13FunctionKey;
125 return NSF14FunctionKey;
127 return NSF15FunctionKey;
129 return NSF16FunctionKey;
131 return NSF17FunctionKey;
133 return NSF18FunctionKey;
135 return NSF19FunctionKey;
137 return NSF20FunctionKey;
139 return NSF21FunctionKey;
141 return NSF22FunctionKey;
143 return NSF23FunctionKey;
145 return NSF24FunctionKey;
147 return NSF25FunctionKey;
149 return NSF26FunctionKey;
151 return NSF27FunctionKey;
153 return NSF28FunctionKey;
155 return NSF29FunctionKey;
157 return NSF30FunctionKey;
159 return NSF31FunctionKey;
161 return NSF32FunctionKey;
163 return NSF33FunctionKey;
165 return NSF34FunctionKey;
167 return NSF35FunctionKey;
177 typedef struct _GtkQuartzActionObserver GtkQuartzActionObserver;
181 @interface GNSMenu : NSMenu
183 GActionObservable *actions;
187 gboolean with_separators;
190 - (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel actions:(GActionObservable *)someActions hasSeparators:(BOOL)hasSeparators;
192 - (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added;
194 - (gboolean)handleChanges;
200 @interface GNSMenuItem : NSMenuItem
205 GActionGroup *actions;
206 GtkQuartzActionObserver *observer;
209 - (id)initWithModel:(GMenuModel *)model index:(NSInteger)index observable:(GActionObservable *)observable;
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;
216 - (void)didSelectItem:(id)sender;
222 struct _GtkQuartzActionObserver
224 GObject parent_instance;
231 typedef GObjectClass GtkQuartzActionObserverClass;
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))
238 gtk_quartz_action_observer_action_added (GActionObserver *observer,
239 GActionObservable *observable,
240 const gchar *action_name,
241 const GVariantType *parameter_type,
245 GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
247 [qao->item observableActionAddedWithParameterType:parameter_type enabled:enabled state:state];
251 gtk_quartz_action_observer_action_enabled_changed (GActionObserver *observer,
252 GActionObservable *observable,
253 const gchar *action_name,
256 GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
258 [qao->item observableActionEnabledChangedTo:enabled];
262 gtk_quartz_action_observer_action_state_changed (GActionObserver *observer,
263 GActionObservable *observable,
264 const gchar *action_name,
267 GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
269 [qao->item observableActionStateChangedTo:state];
273 gtk_quartz_action_observer_action_removed (GActionObserver *observer,
274 GActionObservable *observable,
275 const gchar *action_name)
277 GtkQuartzActionObserver *qao = (GtkQuartzActionObserver *) observer;
279 [qao->item observableActionRemoved];
283 gtk_quartz_action_observer_init (GtkQuartzActionObserver *item)
288 gtk_quartz_action_observer_observer_iface_init (GActionObserverInterface *iface)
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;
297 gtk_quartz_action_observer_class_init (GtkQuartzActionObserverClass *class)
301 static GtkQuartzActionObserver *
302 gtk_quartz_action_observer_new (GNSMenuItem *item)
304 GtkQuartzActionObserver *observer;
306 observer = g_object_new (gtk_quartz_action_observer_get_type (), NULL);
307 observer->item = item;
313 gtk_quartz_menu_handle_changes (gpointer user_data)
315 GNSMenu *menu = user_data;
317 return [menu handleChanges];
321 gtk_quartz_menu_items_changed (GMenuModel *model,
327 GNSMenu *menu = user_data;
329 [menu model:model didChangeAtPosition:position removed:removed added:added];
333 gtk_quartz_set_main_menu (GMenuModel *model,
334 GActionObservable *observable)
336 [NSApp setMainMenu:[[[GNSMenu alloc] initWithTitle:@"Main Menu" model:model actions:observable hasSeparators:NO] autorelease]];
339 @interface GNSMenu ()
341 - (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators;
347 @implementation GNSMenu
349 - (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added
351 if (update_idle == 0)
352 update_idle = gdk_threads_add_idle (gtk_quartz_menu_handle_changes, self);
355 - (void)appendItemFromModel:(GMenuModel *)aModel atIndex:(gint)index withHeading:(gchar **)heading
359 if ((section = g_menu_model_get_item_link (aModel, index, G_MENU_LINK_SECTION)))
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);
366 [self addItem:[[[GNSMenuItem alloc] initWithModel:aModel index:index observable:actions] autorelease]];
369 - (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators
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));
376 n = g_menu_model_get_n_items (aModel);
378 for (i = 0; i < n; i++)
380 NSInteger ourPosition = [self numberOfItems];
381 gchar *heading = NULL;
383 [self appendItemFromModel:aModel atIndex:i withHeading:&heading];
385 if (withSeparators && ourPosition < [self numberOfItems])
387 NSMenuItem *separator = nil;
391 separator = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:heading] action:NULL keyEquivalent:@""] autorelease];
393 [separator setEnabled:NO];
395 else if (ourPosition > 0)
396 separator = [NSMenuItem separatorItem];
398 if (separator != nil)
399 [self insertItem:separator atIndex:ourPosition];
408 [self removeAllItems];
410 [self appendFromModel:model withSeparators:with_separators];
413 - (gboolean)handleChanges
417 g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_menu_items_changed, self);
418 g_object_unref (connected->data);
420 connected = g_slist_delete_link (connected, connected);
427 return G_SOURCE_REMOVE;
430 - (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel actions:(GActionObservable *)someActions hasSeparators:(BOOL)hasSeparators
432 if((self = [super initWithTitle:title]) != nil)
434 [self setAutoenablesItems:NO];
436 model = g_object_ref (aModel);
437 actions = g_object_ref (someActions);
438 with_separators = hasSeparators;
450 g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_menu_items_changed, self);
451 g_object_unref (connected->data);
453 connected = g_slist_delete_link (connected, connected);
456 g_object_unref (actions);
457 g_object_unref (model);
466 @implementation GNSMenuItem
468 - (id)initWithModel:(GMenuModel *)model index:(NSInteger)index observable:(GActionObservable *)observable
472 if (g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_LABEL, "s", &title))
480 if (*from == '_' && from[1])
489 if ((self = [super initWithTitle:[NSString stringWithUTF8String:title ? : ""] action:@selector(didSelectItem:) keyEquivalent:@""]) != nil)
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);
498 if ((submenu = g_menu_model_get_item_link (model, index, G_MENU_LINK_SUBMENU)))
500 [self setSubmenu:[[[GNSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title] model:submenu actions:observable hasSeparators:YES] autorelease]];
501 g_object_unref (submenu);
504 else if (action != NULL)
509 g_action_observable_register_observer (observable, action, G_ACTION_OBSERVER (observer));
511 path = _gtk_accel_path_for_action (action, target);
512 if (gtk_accel_map_lookup_entry (path, &key))
514 unichar character = gtk_quartz_menu_get_unichar (key.accel_key);
518 NSUInteger modifiers = 0;
520 if (key.accel_mods & GDK_SHIFT_MASK)
521 modifiers |= NSShiftKeyMask;
523 if (key.accel_mods & GDK_MOD1_MASK)
524 modifiers |= NSAlternateKeyMask;
526 if (key.accel_mods & GDK_CONTROL_MASK)
527 modifiers |= NSControlKeyMask;
529 if (key.accel_mods & GDK_META_MASK)
530 modifiers |= NSCommandKeyMask;
532 [self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]];
533 [self setKeyEquivalentModifierMask:modifiers];
539 [self setTarget:self];
542 const GVariantType *parameterType;
545 if (g_action_group_query_action (actions, action, &enabled, ¶meterType, NULL, NULL, &state))
546 [self observableActionAddedWithParameterType:parameterType enabled:enabled state:state];
548 [self setEnabled:NO];
559 if (observer != NULL)
560 g_object_unref (observer);
563 g_object_unref (actions);
566 g_variant_unref (target);
573 - (void)observableActionAddedWithParameterType:(const GVariantType *)parameterType enabled:(BOOL)enabled state:(GVariant *)state
575 canActivate = (target == NULL && parameterType == NULL) ||
576 (target != NULL && parameterType != NULL &&
577 g_variant_is_of_type (target, parameterType));
581 if (target != NULL && state != NULL)
583 [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
584 [self setState:g_variant_equal (state, target) ? NSOnState : NSOffState];
586 else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
588 [self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]];
589 [self setState:g_variant_get_boolean (state) ? NSOnState : NSOffState];
592 [self setState:NSOffState];
594 [self setEnabled:enabled];
597 [self setEnabled:NO];
600 - (void)observableActionEnabledChangedTo:(BOOL)enabled
603 [self setEnabled:enabled];
606 - (void)observableActionStateChangedTo:(GVariant *)state
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];
617 - (void)observableActionRemoved
620 [self setEnabled:NO];
623 - (void)didSelectItem:(id)sender
626 g_action_group_activate_action (actions, action, target);