]> Pileus Git - ~andy/gtk/blob - gtk/gtkmodelmenu-quartz.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkmodelmenu-quartz.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 "gtkmodelmenu-quartz.h"
22
23 #include <gdk/gdkkeysyms.h>
24 #include "gtkaccelmapprivate.h"
25 #include "gtkactionhelper.h"
26 #include "../gdk/quartz/gdkquartz.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_model_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 @interface GNSMenu : NSMenu
180 {
181   GtkApplication *application;
182   GMenuModel     *model;
183   guint           update_idle;
184   GSList         *connected;
185   gboolean        with_separators;
186 }
187
188 - (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)application hasSeparators:(BOOL)hasSeparators;
189
190 - (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added;
191
192 - (gboolean)handleChanges;
193
194 @end
195
196
197
198 @interface GNSMenuItem : NSMenuItem
199 {
200   GtkActionHelper *helper;
201 }
202
203 - (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application;
204
205 - (void)didSelectItem:(id)sender;
206
207 - (void)helperChanged;
208
209 @end
210
211
212
213 static gboolean
214 gtk_quartz_model_menu_handle_changes (gpointer user_data)
215 {
216   GNSMenu *menu = user_data;
217
218   return [menu handleChanges];
219 }
220
221 static void
222 gtk_quartz_model_menu_items_changed (GMenuModel *model,
223                                gint        position,
224                                gint        removed,
225                                gint        added,
226                                gpointer    user_data)
227 {
228   GNSMenu *menu = user_data;
229
230   [menu model:model didChangeAtPosition:position removed:removed added:added];
231 }
232
233 void
234 gtk_quartz_set_main_menu (GMenuModel     *model,
235                           GtkApplication *application)
236 {
237   [NSApp setMainMenu:[[[GNSMenu alloc] initWithTitle:@"Main Menu" model:model application:application hasSeparators:NO] autorelease]];
238 }
239
240 void
241 gtk_quartz_clear_main_menu (void)
242 {
243   // ensure that we drop all GNSMenuItem (to ensure 'application' has no extra references)
244   [NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]];
245 }
246
247 @interface GNSMenu ()
248
249 - (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators;
250
251 @end
252
253
254
255 @implementation GNSMenu
256
257 - (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added
258 {
259   if (update_idle == 0)
260     update_idle = gdk_threads_add_idle (gtk_quartz_model_menu_handle_changes, self);
261 }
262
263 - (void)appendItemFromModel:(GMenuModel *)aModel atIndex:(gint)index withHeading:(gchar **)heading
264 {
265   GMenuModel *section;
266
267   if ((section = g_menu_model_get_item_link (aModel, index, G_MENU_LINK_SECTION)))
268     {
269       g_menu_model_get_item_attribute (aModel, index, G_MENU_ATTRIBUTE_LABEL, "s", heading);
270       [self appendFromModel:section withSeparators:NO];
271       g_object_unref (section);
272     }
273   else
274     [self addItem:[[[GNSMenuItem alloc] initWithModel:aModel index:index application:application] autorelease]];
275 }
276
277 - (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators
278 {
279   gint n, i;
280
281   g_signal_connect (aModel, "items-changed", G_CALLBACK (gtk_quartz_model_menu_items_changed), self);
282   connected = g_slist_prepend (connected, g_object_ref (aModel));
283
284   n = g_menu_model_get_n_items (aModel);
285
286   for (i = 0; i < n; i++)
287     {
288       NSInteger ourPosition = [self numberOfItems];
289       gchar *heading = NULL;
290
291       [self appendItemFromModel:aModel atIndex:i withHeading:&heading];
292
293       if (withSeparators && ourPosition < [self numberOfItems])
294         {
295           NSMenuItem *separator = nil;
296
297           if (heading)
298             {
299               separator = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:heading] action:NULL keyEquivalent:@""] autorelease];
300
301               [separator setEnabled:NO];
302             }
303           else if (ourPosition > 0)
304             separator = [NSMenuItem separatorItem];
305
306           if (separator != nil)
307             [self insertItem:separator atIndex:ourPosition];
308         }
309
310       g_free (heading);
311     }
312 }
313
314 - (void)populate
315 {
316   [self removeAllItems];
317
318   [self appendFromModel:model withSeparators:with_separators];
319 }
320
321 - (gboolean)handleChanges
322 {
323   while (connected)
324     {
325       g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self);
326       g_object_unref (connected->data);
327
328       connected = g_slist_delete_link (connected, connected);
329     }
330
331   [self populate];
332
333   update_idle = 0;
334
335   return G_SOURCE_REMOVE;
336 }
337
338 - (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)anApplication hasSeparators:(BOOL)hasSeparators
339 {
340   if((self = [super initWithTitle:title]) != nil)
341     {
342       [self setAutoenablesItems:NO];
343
344       model = g_object_ref (aModel);
345       application = g_object_ref (anApplication);
346       with_separators = hasSeparators;
347
348       [self populate];
349     }
350
351   return self;
352 }
353
354 - (void)dealloc
355 {
356   while (connected)
357     {
358       g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self);
359       g_object_unref (connected->data);
360
361       connected = g_slist_delete_link (connected, connected);
362     }
363
364   g_object_unref (application);
365   g_object_unref (model);
366
367   [super dealloc];
368 }
369
370 @end
371
372
373
374 static void
375 gtk_quartz_action_helper_changed (GObject    *object,
376                                   GParamSpec *pspec,
377                                   gpointer    user_data)
378 {
379   GNSMenuItem *item = user_data;
380
381   [item helperChanged];
382 }
383
384 @implementation GNSMenuItem
385
386 - (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application
387 {
388   gchar *title = NULL;
389
390   if (g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_LABEL, "s", &title))
391     {
392       gchar *from, *to;
393
394       to = from = title;
395
396       while (*from)
397         {
398           if (*from == '_' && from[1])
399             from++;
400
401           *to++ = *from++;
402         }
403
404       *to = '\0';
405     }
406
407   if ((self = [super initWithTitle:[NSString stringWithUTF8String:title ? : ""] action:@selector(didSelectItem:) keyEquivalent:@""]) != nil)
408     {
409       GMenuModel *submenu;
410       gchar      *action;
411       GVariant   *target;
412
413       action = NULL;
414       g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_ACTION, "s", &action);
415       target = g_menu_model_get_item_attribute_value (model, index, G_MENU_ATTRIBUTE_TARGET, NULL);
416
417       if ((submenu = g_menu_model_get_item_link (model, index, G_MENU_LINK_SUBMENU)))
418         {
419           [self setSubmenu:[[[GNSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title] model:submenu application:application hasSeparators:YES] autorelease]];
420           g_object_unref (submenu);
421         }
422
423       else if (action != NULL)
424         {
425           GtkAccelKey key;
426           gchar *path;
427
428           helper = gtk_action_helper_new_with_application (application);
429           gtk_action_helper_set_action_name         (helper, action);
430           gtk_action_helper_set_action_target_value (helper, target);
431
432           g_signal_connect (helper, "notify", G_CALLBACK (gtk_quartz_action_helper_changed), self);
433
434           [self helperChanged];
435
436           path = _gtk_accel_path_for_action (action, target);
437           if (gtk_accel_map_lookup_entry (path, &key))
438             {
439               unichar character = gtk_quartz_model_menu_get_unichar (key.accel_key);
440
441               if (character)
442                 {
443                   NSUInteger modifiers = 0;
444
445                   if (key.accel_mods & GDK_SHIFT_MASK)
446                     modifiers |= NSShiftKeyMask;
447
448                   if (key.accel_mods & GDK_MOD1_MASK)
449                     modifiers |= NSAlternateKeyMask;
450
451                   if (key.accel_mods & GDK_CONTROL_MASK)
452                     modifiers |= NSControlKeyMask;
453
454                   if (key.accel_mods & GDK_META_MASK)
455                     modifiers |= NSCommandKeyMask;
456
457                   [self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]];
458                   [self setKeyEquivalentModifierMask:modifiers];
459                 }
460             }
461
462           g_free (path);
463
464           [self setTarget:self];
465         }
466     }
467
468   g_free (title);
469
470   return self;
471 }
472
473 - (void)dealloc
474 {
475   if (helper != NULL)
476     g_object_unref (helper);
477
478   [super dealloc];
479 }
480
481 - (void)didSelectItem:(id)sender
482 {
483   gtk_action_helper_activate (helper);
484 }
485
486 - (void)helperChanged
487 {
488   [self setEnabled:gtk_action_helper_get_enabled (helper)];
489   [self setState:gtk_action_helper_get_active (helper)];
490
491   switch (gtk_action_helper_get_role (helper))
492     {
493       case GTK_ACTION_HELPER_ROLE_NORMAL:
494         [self setOnStateImage:nil];
495         break;
496       case GTK_ACTION_HELPER_ROLE_TOGGLE:
497         [self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]];
498         break;
499       case GTK_ACTION_HELPER_ROLE_RADIO:
500         [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
501         break;
502       default:
503         g_assert_not_reached ();
504     }
505 }
506
507 @end