gtkmodelmenu-quartz.c 13.3 KB
Newer Older
1
/*
2
 * Copyright © 2011 William Hua, Ryan Lortie
3 4 5 6 7 8 9 10 11 12 13 14
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the licence, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
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 24
#include <gdk/gdkkeysyms.h>
#include "gtkaccelmapprivate.h"
25
#include "gtkactionhelper.h"
26

27 28
#import <Cocoa/Cocoa.h>

29 30 31 32 33 34
/*
 * Code for key code conversion
 *
 * Copyright (C) 2009 Paul Davis
 */
static unichar
35
gtk_quartz_model_menu_get_unichar (gint key)
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
{
  if (key >= GDK_KEY_A && key <= GDK_KEY_Z)
    return key + (GDK_KEY_a - GDK_KEY_A);

  if (key >= GDK_KEY_space && key <= GDK_KEY_asciitilde)
    return key;

  switch (key)
    {
      case GDK_KEY_BackSpace:
        return NSBackspaceCharacter;
      case GDK_KEY_Delete:
        return NSDeleteFunctionKey;
      case GDK_KEY_Pause:
        return NSPauseFunctionKey;
      case GDK_KEY_Scroll_Lock:
        return NSScrollLockFunctionKey;
      case GDK_KEY_Sys_Req:
        return NSSysReqFunctionKey;
      case GDK_KEY_Home:
        return NSHomeFunctionKey;
      case GDK_KEY_Left:
      case GDK_KEY_leftarrow:
        return NSLeftArrowFunctionKey;
      case GDK_KEY_Up:
      case GDK_KEY_uparrow:
        return NSUpArrowFunctionKey;
      case GDK_KEY_Right:
      case GDK_KEY_rightarrow:
        return NSRightArrowFunctionKey;
      case GDK_KEY_Down:
      case GDK_KEY_downarrow:
        return NSDownArrowFunctionKey;
      case GDK_KEY_Page_Up:
        return NSPageUpFunctionKey;
      case GDK_KEY_Page_Down:
        return NSPageDownFunctionKey;
      case GDK_KEY_End:
        return NSEndFunctionKey;
      case GDK_KEY_Begin:
        return NSBeginFunctionKey;
      case GDK_KEY_Select:
        return NSSelectFunctionKey;
      case GDK_KEY_Print:
        return NSPrintFunctionKey;
      case GDK_KEY_Execute:
        return NSExecuteFunctionKey;
      case GDK_KEY_Insert:
        return NSInsertFunctionKey;
      case GDK_KEY_Undo:
        return NSUndoFunctionKey;
      case GDK_KEY_Redo:
        return NSRedoFunctionKey;
      case GDK_KEY_Menu:
        return NSMenuFunctionKey;
      case GDK_KEY_Find:
        return NSFindFunctionKey;
      case GDK_KEY_Help:
        return NSHelpFunctionKey;
      case GDK_KEY_Break:
        return NSBreakFunctionKey;
      case GDK_KEY_Mode_switch:
        return NSModeSwitchFunctionKey;
      case GDK_KEY_F1:
        return NSF1FunctionKey;
      case GDK_KEY_F2:
        return NSF2FunctionKey;
      case GDK_KEY_F3:
        return NSF3FunctionKey;
      case GDK_KEY_F4:
        return NSF4FunctionKey;
      case GDK_KEY_F5:
        return NSF5FunctionKey;
      case GDK_KEY_F6:
        return NSF6FunctionKey;
      case GDK_KEY_F7:
        return NSF7FunctionKey;
      case GDK_KEY_F8:
        return NSF8FunctionKey;
      case GDK_KEY_F9:
        return NSF9FunctionKey;
      case GDK_KEY_F10:
        return NSF10FunctionKey;
      case GDK_KEY_F11:
        return NSF11FunctionKey;
      case GDK_KEY_F12:
        return NSF12FunctionKey;
      case GDK_KEY_F13:
        return NSF13FunctionKey;
      case GDK_KEY_F14:
        return NSF14FunctionKey;
      case GDK_KEY_F15:
        return NSF15FunctionKey;
      case GDK_KEY_F16:
        return NSF16FunctionKey;
      case GDK_KEY_F17:
        return NSF17FunctionKey;
      case GDK_KEY_F18:
        return NSF18FunctionKey;
      case GDK_KEY_F19:
        return NSF19FunctionKey;
      case GDK_KEY_F20:
        return NSF20FunctionKey;
      case GDK_KEY_F21:
        return NSF21FunctionKey;
      case GDK_KEY_F22:
        return NSF22FunctionKey;
      case GDK_KEY_F23:
        return NSF23FunctionKey;
      case GDK_KEY_F24:
        return NSF24FunctionKey;
      case GDK_KEY_F25:
        return NSF25FunctionKey;
      case GDK_KEY_F26:
        return NSF26FunctionKey;
      case GDK_KEY_F27:
        return NSF27FunctionKey;
      case GDK_KEY_F28:
        return NSF28FunctionKey;
      case GDK_KEY_F29:
        return NSF29FunctionKey;
      case GDK_KEY_F30:
        return NSF30FunctionKey;
      case GDK_KEY_F31:
        return NSF31FunctionKey;
      case GDK_KEY_F32:
        return NSF32FunctionKey;
      case GDK_KEY_F33:
        return NSF33FunctionKey;
      case GDK_KEY_F34:
        return NSF34FunctionKey;
      case GDK_KEY_F35:
        return NSF35FunctionKey;
      default:
        break;
    }

  return '\0';
}



178 179
@interface GNSMenu : NSMenu
{
180 181 182 183 184
  GtkApplication *application;
  GMenuModel     *model;
  guint           update_idle;
  GSList         *connected;
  gboolean        with_separators;
185 186
}

187
- (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)application hasSeparators:(BOOL)hasSeparators;
188 189 190

- (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added;

William Hua's avatar
William Hua committed
191 192
- (gboolean)handleChanges;

193 194 195 196
@end



197 198
@interface GNSMenuItem : NSMenuItem
{
199
  GtkActionHelper *helper;
200 201
}

202
- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application;
203 204 205

- (void)didSelectItem:(id)sender;

206
- (void)helperChanged;
207

208
@end
209 210 211



William Hua's avatar
William Hua committed
212
static gboolean
213
gtk_quartz_model_menu_handle_changes (gpointer user_data)
William Hua's avatar
William Hua committed
214 215 216 217 218 219
{
  GNSMenu *menu = user_data;

  return [menu handleChanges];
}

220
static void
221
gtk_quartz_model_menu_items_changed (GMenuModel *model,
222 223 224 225
                               gint        position,
                               gint        removed,
                               gint        added,
                               gpointer    user_data)
226
{
227
  GNSMenu *menu = user_data;
228

229 230
  [menu model:model didChangeAtPosition:position removed:removed added:added];
}
231

232
void
233 234
gtk_quartz_set_main_menu (GMenuModel     *model,
                          GtkApplication *application)
235
{
236 237 238 239 240 241 242 243
  [NSApp setMainMenu:[[[GNSMenu alloc] initWithTitle:@"Main Menu" model:model application:application hasSeparators:NO] autorelease]];
}

void
gtk_quartz_clear_main_menu (void)
{
  // ensure that we drop all GNSMenuItem (to ensure 'application' has no extra references)
  [NSApp setMainMenu:[[[NSMenu alloc] init] autorelease]];
244
}
245

246
@interface GNSMenu ()
247

248
- (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators;
249

250
@end
251 252 253



254
@implementation GNSMenu
255

256 257
- (void)model:(GMenuModel *)model didChangeAtPosition:(NSInteger)position removed:(NSInteger)removed added:(NSInteger)added
{
William Hua's avatar
William Hua committed
258
  if (update_idle == 0)
259
    update_idle = gdk_threads_add_idle (gtk_quartz_model_menu_handle_changes, self);
260
}
261

262 263 264
- (void)appendItemFromModel:(GMenuModel *)aModel atIndex:(gint)index withHeading:(gchar **)heading
{
  GMenuModel *section;
265

266 267 268 269 270 271 272
  if ((section = g_menu_model_get_item_link (aModel, index, G_MENU_LINK_SECTION)))
    {
      g_menu_model_get_item_attribute (aModel, index, G_MENU_ATTRIBUTE_LABEL, "s", heading);
      [self appendFromModel:section withSeparators:NO];
      g_object_unref (section);
    }
  else
273
    [self addItem:[[[GNSMenuItem alloc] initWithModel:aModel index:index application:application] autorelease]];
274
}
275

276 277 278
- (void)appendFromModel:(GMenuModel *)aModel withSeparators:(BOOL)withSeparators
{
  gint n, i;
279

280
  g_signal_connect (aModel, "items-changed", G_CALLBACK (gtk_quartz_model_menu_items_changed), self);
281
  connected = g_slist_prepend (connected, g_object_ref (aModel));
282

283
  n = g_menu_model_get_n_items (aModel);
284

285 286 287 288
  for (i = 0; i < n; i++)
    {
      NSInteger ourPosition = [self numberOfItems];
      gchar *heading = NULL;
289

290
      [self appendItemFromModel:aModel atIndex:i withHeading:&heading];
291

292 293 294
      if (withSeparators && ourPosition < [self numberOfItems])
        {
          NSMenuItem *separator = nil;
295

296
          if (heading)
297
            {
298 299 300
              separator = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:heading] action:NULL keyEquivalent:@""] autorelease];

              [separator setEnabled:NO];
301
            }
302 303 304 305 306
          else if (ourPosition > 0)
            separator = [NSMenuItem separatorItem];

          if (separator != nil)
            [self insertItem:separator atIndex:ourPosition];
307 308
        }

309 310 311
      g_free (heading);
    }
}
312

313 314 315 316 317 318 319
- (void)populate
{
  [self removeAllItems];

  [self appendFromModel:model withSeparators:with_separators];
}

William Hua's avatar
William Hua committed
320 321 322 323
- (gboolean)handleChanges
{
  while (connected)
    {
324
      g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self);
William Hua's avatar
William Hua committed
325 326 327 328 329 330 331 332 333 334 335 336
      g_object_unref (connected->data);

      connected = g_slist_delete_link (connected, connected);
    }

  [self populate];

  update_idle = 0;

  return G_SOURCE_REMOVE;
}

337
- (id)initWithTitle:(NSString *)title model:(GMenuModel *)aModel application:(GtkApplication *)anApplication hasSeparators:(BOOL)hasSeparators
338 339 340 341 342 343
{
  if((self = [super initWithTitle:title]) != nil)
    {
      [self setAutoenablesItems:NO];

      model = g_object_ref (aModel);
344
      application = g_object_ref (anApplication);
345 346 347
      with_separators = hasSeparators;

      [self populate];
348 349
    }

350
  return self;
351 352
}

353
- (void)dealloc
354
{
355 356
  while (connected)
    {
357
      g_signal_handlers_disconnect_by_func (connected->data, gtk_quartz_model_menu_items_changed, self);
358 359 360 361 362
      g_object_unref (connected->data);

      connected = g_slist_delete_link (connected, connected);
    }

363
  g_object_unref (application);
364 365 366
  g_object_unref (model);

  [super dealloc];
367 368
}

369 370
@end

371 372


373 374 375 376 377 378 379 380 381 382
static void
gtk_quartz_action_helper_changed (GObject    *object,
                                  GParamSpec *pspec,
                                  gpointer    user_data)
{
  GNSMenuItem *item = user_data;

  [item helperChanged];
}

383 384
@implementation GNSMenuItem

385
- (id)initWithModel:(GMenuModel *)model index:(NSInteger)index application:(GtkApplication *)application
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
{
  gchar *title = NULL;

  if (g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_LABEL, "s", &title))
    {
      gchar *from, *to;

      to = from = title;

      while (*from)
        {
          if (*from == '_' && from[1])
            from++;

          *to++ = *from++;
        }

      *to = '\0';
    }

  if ((self = [super initWithTitle:[NSString stringWithUTF8String:title ? : ""] action:@selector(didSelectItem:) keyEquivalent:@""]) != nil)
    {
408
      GMenuModel *submenu;
409 410
      gchar      *action;
      GVariant   *target;
411

412
      action = NULL;
413 414 415
      g_menu_model_get_item_attribute (model, index, G_MENU_ATTRIBUTE_ACTION, "s", &action);
      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
          [self setSubmenu:[[[GNSMenu alloc] initWithTitle:[NSString stringWithUTF8String:title] model:submenu application:application hasSeparators:YES] autorelease]];
419 420 421 422
          g_object_unref (submenu);
        }

      else if (action != NULL)
423
        {
424 425 426
          GtkAccelKey key;
          gchar *path;

427 428 429 430 431 432 433
          helper = gtk_action_helper_new_with_application (application);
          gtk_action_helper_set_action_name         (helper, action);
          gtk_action_helper_set_action_target_value (helper, target);

          g_signal_connect (helper, "notify", G_CALLBACK (gtk_quartz_action_helper_changed), self);

          [self helperChanged];
434

435 436 437
          path = _gtk_accel_path_for_action (action, target);
          if (gtk_accel_map_lookup_entry (path, &key))
            {
438
              unichar character = gtk_quartz_model_menu_get_unichar (key.accel_key);
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462

              if (character)
                {
                  NSUInteger modifiers = 0;

                  if (key.accel_mods & GDK_SHIFT_MASK)
                    modifiers |= NSShiftKeyMask;

                  if (key.accel_mods & GDK_MOD1_MASK)
                    modifiers |= NSAlternateKeyMask;

                  if (key.accel_mods & GDK_CONTROL_MASK)
                    modifiers |= NSControlKeyMask;

                  if (key.accel_mods & GDK_META_MASK)
                    modifiers |= NSCommandKeyMask;

                  [self setKeyEquivalent:[NSString stringWithCharacters:&character length:1]];
                  [self setKeyEquivalentModifierMask:modifiers];
                }
            }

          g_free (path);

463 464 465 466 467 468 469 470 471
          [self setTarget:self];
        }
    }

  g_free (title);

  return self;
}

472 473
- (void)dealloc
{
474 475
  if (helper != NULL)
    g_object_unref (helper);
476 477 478 479

  [super dealloc];
}

480
- (void)didSelectItem:(id)sender
481
{
482
  gtk_action_helper_activate (helper);
483 484
}

485
- (void)helperChanged
486
{
487 488
  [self setEnabled:gtk_action_helper_get_enabled (helper)];
  [self setState:gtk_action_helper_get_active (helper)];
489

490
  switch (gtk_action_helper_get_role (helper))
491
    {
492 493 494 495 496 497 498 499 500 501 502
      case GTK_ACTION_HELPER_ROLE_NORMAL:
        [self setOnStateImage:nil];
        break;
      case GTK_ACTION_HELPER_ROLE_TOGGLE:
        [self setOnStateImage:[NSImage imageNamed:@"NSMenuCheckmark"]];
        break;
      case GTK_ACTION_HELPER_ROLE_RADIO:
        [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
        break;
      default:
        g_assert_not_reached ();
503 504 505 506
    }
}

@end