/* GTK+ Integration with platform-specific application-wide features
 * such as the OS X menubar and application delegate concepts.
 *
 * Copyright (C) 2009 Paul Davis
 * Copyright © 2010 John Ralls
 *
 * 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; version 2.1
 * of the License.
 *
 * 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
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <config.h>
#import <Cocoa/Cocoa.h>
#include <gtk/gtk.h>
#if GTK_MAJOR_VERSION == 3
#include <gdk/gdkkeysyms-compat.h>
#else
#include <gdk/gdkkeysyms.h>
#endif

#include "cocoa_menu_item.h"
#include "cocoa_menu.h"
#include "getlabel.h"
#include "gtkosx-image.h"
#import "GNSMenuBar.h"
#import "GNSMenuDelegate.h"

//#define DEBUG(format, ...) g_printerr ("%s: " format, G_STRFUNC, ## __VA_ARGS__)
#define DEBUG(format, ...)

/*
 * These functions are long, ugly, and monotonous, so forward declare
 * them here and define them at the end of the file.
 */
static guint gdk_quartz_keyval_to_ns_keyval (guint keyval);
static gboolean keyval_is_keypad (guint keyval);
static guint keyval_keypad_nonkeypad_equivalent (guint keyval);
static const gchar* gdk_quartz_keyval_to_string (guint keyval);
static gboolean keyval_is_uppercase (guint keyval);

static GQuark cocoa_menu_item_quark = 0;

/*
 * utility functions
 */

static gboolean
accel_find_func (GtkAccelKey *key,
                 GClosure    *closure,
                 gpointer     data)
{
  return (GClosure *) data == closure;
}

static GClosure *
_gtk_accel_label_get_closure (GtkAccelLabel *label)
{
  g_return_val_if_fail (GTK_IS_ACCEL_LABEL (label), NULL);

  GClosure *closure = NULL;
  g_object_get (G_OBJECT (label), "accel-closure", &closure, NULL);
  return closure;
}

static void
cocoa_menu_item_free (gpointer *ptr)
{
  _GNSMenuItem* item = (_GNSMenuItem*) ptr;
  [[item menu] removeItem: item];
  [item release];
}

_GNSMenuItem *
cocoa_menu_item_get (GtkWidget *widget)
{
  return (_GNSMenuItem*) g_object_get_qdata (G_OBJECT (widget),
					     cocoa_menu_item_quark);
}

static void
cocoa_menu_item_update_state (_GNSMenuItem* cocoa_item,
                              GtkWidget      *widget)
{
  gboolean sensitive;
  gboolean visible;

  g_object_get (widget,
                "sensitive", &sensitive,
                "visible",   &visible,
                NULL);

  if (!sensitive)
    [cocoa_item setEnabled: NO];
  else
    [cocoa_item setEnabled: YES];

  if (!visible)
    [cocoa_item setHidden: YES];
  else
    [cocoa_item setHidden: NO];
}

static void
cocoa_menu_item_update_checked (_GNSMenuItem *cocoa_item,
                                GtkWidget  *widget)
{
  gboolean active, inconsistent;

  g_object_get (widget,
                "active", &active,
                "inconsistent", &inconsistent,
                NULL);

  if (inconsistent)
    [cocoa_item setState: NSMixedState];
  else if (active)
    [cocoa_item setState: NSOnState];
  else
    [cocoa_item setState: NSOffState];
}

static void
cocoa_menu_item_update_submenu (_GNSMenuItem *cocoa_item,
                                GtkWidget      *widget)
{
  GtkWidget *submenu;

  g_return_if_fail (cocoa_item != NULL);
  g_return_if_fail (widget != NULL);

  submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));

  if (!submenu)
    {
      if ([cocoa_item hasSubmenu])
        /*If the cocoa_item has a submenu but the menu_item doesn't,
          lose the cocoa_item's submenu */
	[cocoa_item setSubmenu: nil];
      return;
    }
  GtkWidget* label = NULL;
  NSMenu* cocoa_submenu = cocoa_menu_get (submenu);

  if (cocoa_submenu != nil)
    if ([cocoa_item submenu] != cocoa_submenu)
      //covers no submenu or wrong submenu on cocoa_item)
      {
	[[[cocoa_item submenu] delegate] release];
	[[cocoa_item submenu] release];
	[cocoa_item setSubmenu: cocoa_submenu];
      }
    else ;
  //Nothing required -- the submenus are already set up correctly
  else if ([cocoa_item hasSubmenu])
    {
      cocoa_submenu = [cocoa_item submenu];
      cocoa_menu_connect (submenu, cocoa_submenu);
    }
  else   //no submenu anywhere, so create one
    {
      const gchar *text = get_menu_label_text (widget, &label);
      NSString *title = [NSString stringWithUTF8String: text ? text : ""];
      _GNSMenuDelegate *delegate = [[_GNSMenuDelegate alloc] init];

      cocoa_submenu = [[[NSMenu alloc] initWithTitle: title] autorelease];

      [cocoa_submenu setDelegate: delegate];
      cocoa_menu_connect (submenu, cocoa_submenu);
      /* connect the new nsmenu to the passed-in item (which lives in
         the parent nsmenu)
         (Note: this will release any pre-existing version of this submenu)
      */
      [ cocoa_item setSubmenu: cocoa_submenu];
    }
  /* and push the GTK menu into the submenu */
  cocoa_menu_item_add_submenu (GTK_MENU_SHELL (submenu), cocoa_submenu,
                               FALSE, FALSE);
}

static void
cocoa_menu_item_update_label (_GNSMenuItem *cocoa_item,
                              GtkWidget      *widget)
{
  g_return_if_fail (cocoa_item != NULL);
  g_return_if_fail (widget != NULL);

  {
    const gchar *text = get_menu_label_text (widget, NULL);
    NSString *title = [NSString stringWithUTF8String: text ? text : ""];
    [cocoa_item setTitle: title];
  }
}

static void
cocoa_menu_item_update_accelerator (_GNSMenuItem *cocoa_item,
                                    GtkWidget *widget)
{
  GtkWidget *label;

  g_return_if_fail (cocoa_item != NULL);
  g_return_if_fail (widget != NULL);

  /* Important note: this function doesn't do anything to actually
   * change key handling. Its goal is to get Cocoa to display the
   * correct accelerator as part of a menu item. Actual accelerator
   * handling depends on gtkosx_application_use_quartz_accelerators,
   * so this is more cosmetic than it may appear.
  */
  /* Return if there's no label; it's probably a separator which isn't
   * going to get an accelerator anyway. */
  get_menu_label_text (widget, &label);
  if (label == NULL) return;

  GClosure *closure = NULL;
  g_object_get (label, "accel-closure", &closure, NULL);
  if (GTK_IS_ACCEL_LABEL (label) && closure)
    {
      GtkAccelKey *key;

      key = gtk_accel_group_find (gtk_accel_group_from_accel_closure (closure),
                                  accel_find_func,
                                  closure);

      if (key            &&
          key->accel_key &&
          key->accel_flags & GTK_ACCEL_VISIBLE)
        {
          guint modifiers = 0;
          const gchar* str = NULL;
          guint actual_key = key->accel_key;

          if (keyval_is_keypad (actual_key))
            {
		 actual_key = keyval_keypad_nonkeypad_equivalent (actual_key);
              if (actual_key == GDK_VoidSymbol)
                {
                  /* GDK_KP_Separator */
		  [cocoa_item setKeyEquivalent: @""];
                  return;
                }
              modifiers |= NSNumericPadKeyMask;
            }

          /* if we somehow got here with GDK_A ... GDK_Z rather than GDK_a ... GDK_z, then take note
             of that and make sure we use a shift modifier.
          */

          if (keyval_is_uppercase (actual_key))
            {
              modifiers |= NSShiftKeyMask;
            }

          str = gdk_quartz_keyval_to_string (actual_key);
          if (str)
            {
              unichar ukey = str[0];
	      [cocoa_item setKeyEquivalent: [NSString stringWithCharacters: &ukey length: 1]];
            }
          else
            {
              unichar ukey = gdk_quartz_keyval_to_ns_keyval (actual_key);
              if (ukey != 0)
                {
		  [cocoa_item setKeyEquivalent: [NSString stringWithCharacters: &ukey length: 1]];
                }
              else
                {
                  /* cannot map this key to Cocoa key equivalent */
		  [cocoa_item setKeyEquivalent: @""];
                  return;
                }
            }

          if (key->accel_mods || modifiers)
            {
              if (key->accel_mods & GDK_SHIFT_MASK)
                {
                  modifiers |= NSShiftKeyMask;
                }

              if (key->accel_mods & GDK_CONTROL_MASK)
                {
                  modifiers |= NSControlKeyMask;
                }

              /* gdk/quartz maps Alt/Option to Mod5 */
              if (key->accel_mods & (GDK_MOD1_MASK))
                {
                  modifiers |= NSAlternateKeyMask;
                }

              /* gdk/quartz maps Command to MOD1 */
              if (key->accel_mods & GDK_META_MASK)
                {
                  modifiers |= NSCommandKeyMask;
                }

            }

	  [cocoa_item setKeyEquivalentModifierMask: modifiers];
          return;
        }
    }

  /*  otherwise, clear the menu shortcut  */
  [cocoa_item setKeyEquivalent: @""];
}

static void
cocoa_menu_item_accel_changed (GtkAccelGroup   *accel_group,
                               guint            keyval,
                               GdkModifierType  modifier,
                               GClosure        *accel_closure,
                               GtkWidget       *widget)
{
  _GNSMenuItem *cocoa_item = cocoa_menu_item_get (widget);
  GtkWidget      *label;

  get_menu_label_text (widget, &label);
  if (gtk_accel_group_from_accel_closure (accel_closure) != accel_group)
    return;
  if (GTK_IS_ACCEL_LABEL (label) &&
      _gtk_accel_label_get_closure ((GtkAccelLabel *) label) == accel_closure)
    cocoa_menu_item_update_accelerator (cocoa_item, widget);
}

static void
cocoa_menu_item_update_accel_closure (_GNSMenuItem *cocoa_item,
                                      GtkWidget      *widget)
{
  GtkAccelGroup *group;
  GtkWidget     *label;

  get_menu_label_text (widget, &label);

  if (cocoa_item->accel_closure)
    {
      group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
      if (group)
	g_signal_handlers_disconnect_by_func (group,
					      (void*) cocoa_menu_item_accel_changed,
					      widget);

      g_closure_unref (cocoa_item->accel_closure);
      cocoa_item->accel_closure = NULL;
    }

  if (GTK_IS_ACCEL_LABEL (label))
    {
      cocoa_item->accel_closure = _gtk_accel_label_get_closure ((GtkAccelLabel *) label);
    }

  if (cocoa_item->accel_closure)
    {
      g_closure_ref (cocoa_item->accel_closure);

      group = gtk_accel_group_from_accel_closure (cocoa_item->accel_closure);
      if (group)
	g_signal_connect_object (group, "accel-changed",
				 G_CALLBACK (cocoa_menu_item_accel_changed),
				 widget, (GConnectFlags) 0);
    }

  cocoa_menu_item_update_accelerator (cocoa_item, widget);
}

static void
cocoa_menu_item_notify_label (GObject    *object,
                              GParamSpec *pspec,
                              gpointer    data)
{
  _GNSMenuItem *cocoa_item = cocoa_menu_item_get (GTK_WIDGET (object));

  if (!strcmp (pspec->name, "label"))
    {
      cocoa_menu_item_update_label (cocoa_item,
                                    GTK_WIDGET (object));
    }
  else if (!strcmp (pspec->name, "accel-closure"))
    {
      cocoa_menu_item_update_accel_closure (cocoa_item,
                                            GTK_WIDGET (object));
    }
}

static void
cocoa_menu_item_notify (GObject        *object,
                        GParamSpec     *pspec,
                        _GNSMenuItem *cocoa_item)
{
  if (!strcmp (pspec->name, "sensitive") ||
      !strcmp (pspec->name, "visible"))
    {
      cocoa_menu_item_update_state (cocoa_item, GTK_WIDGET (object));
    }
  else if (!strcmp (pspec->name, "active") ||
           !strcmp (pspec->name, "inconsistent"))
    {
      cocoa_menu_item_update_checked (cocoa_item, GTK_WIDGET (object));
    }
  else if (!strcmp (pspec->name, "submenu"))
    {
      cocoa_menu_item_update_submenu (cocoa_item, GTK_WIDGET (object));
    }
}

static void
cocoa_menu_item_connect (GtkWidget*   menu_item,
                         _GNSMenuItem* cocoa_item,
                         GtkWidget     *label)
{
  _GNSMenuItem* old_item = cocoa_menu_item_get (menu_item);

  if (old_item == cocoa_item)
    return;

  [cocoa_item retain];

  if (cocoa_menu_item_quark == 0)
    cocoa_menu_item_quark = g_quark_from_static_string ("_GNSMenuItem");

  g_object_set_qdata_full (G_OBJECT (menu_item), cocoa_menu_item_quark,
                           cocoa_item,
                           (GDestroyNotify) cocoa_menu_item_free);

  if (old_item)
    {
      GSignalMatchType mask = G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_DATA;
      guint notify_sig = g_signal_lookup ("notify", GTK_TYPE_MENU_ITEM);
      if (! g_signal_handlers_disconnect_matched (G_OBJECT (menu_item),
          mask, notify_sig,
          (GQuark)0, NULL, NULL,
          old_item))
        g_print ("Failed to disconnect old notify signal for %s\\n",
                 gtk_widget_get_name (menu_item));
    }
  g_signal_connect (menu_item, "notify",
                    G_CALLBACK (cocoa_menu_item_notify),
                    cocoa_item);

  if (label)
    g_signal_connect_swapped (label, "notify::label",
                              G_CALLBACK (cocoa_menu_item_notify_label),
                              menu_item);
}

static void
cocoa_menu_item_sync (GtkWidget* menu_item)
{
  _GNSMenuItem *cocoa_item = cocoa_menu_item_get (menu_item);
  cocoa_menu_item_update_state (cocoa_item, menu_item);

  if (GTK_IS_CHECK_MENU_ITEM (menu_item))
    cocoa_menu_item_update_checked (cocoa_item, menu_item);

  if (!GTK_IS_SEPARATOR_MENU_ITEM (menu_item))
    cocoa_menu_item_update_accel_closure (cocoa_item, menu_item);

  if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_item)))
    cocoa_menu_item_update_submenu (cocoa_item, menu_item);

}


/*
 * If the menu item does contain an image Widget, depending of what
 * this widget is, get a PixBuf with the following method: - menu_item
 * is GtkImage of type GTK_IMAGE_PIXBUF: use gtk_image_get_pixbuf() -
 * menu_item is GtkImage of type GTK_IMAGE_STOCK: use
 * gtk_widget_render_icon() - menu_item is GtkWidget: use
 * gtk_offscreen_window_get_pixbuf() on GtkOffscreenWindow
 */
#ifdef USE_MENU_IMAGES
static void
cocoa_menu_item_add_item_image (_GNSMenuItem* cocoa_item, GtkWidget* menu_item)
{
  GtkImageMenuItem *image_menu_item = GTK_MENU_ITEM (menu_item);
  GtkWidget *menu_widget = gtk_image_menu_item_get_image (image_menu_item);
  GdkPixbuf *pixbuf = NULL;
  NSImage *image = nil;

  if (menu_widget == NULL)
    return;

  if (GTK_IS_IMAGE (menu_widget))
    {
      GtkImage *menu_image = menu_widget;
      switch (gtk_image_get_storage_type (menu_image))
        {
        case GTK_IMAGE_PIXBUF:
          pixbuf = gtk_image_get_pixbuf (menu_image);
          image = nsimage_from_pixbuf (pixbuf);
          break;
        case GTK_IMAGE_STOCK:
        {
          DEBUG ("Menu image is GTK_IMAGE_STOCK\n");
          gchar *stock_id;
          GtkIconSize size;
          gtk_image_get_stock (menu_image, &stock_id, &size);
          pixbuf = gtk_widget_render_icon (menu_image, stock_id, size, "");
          image = nsimage_from_pixbuf (pixbuf);
          g_object_unref (pixbuf);
          break;
        }
        default:
          return;
        }
    }
  if (image)
    [cocoa_item setImage: image];
}
#endif

/*
 * Public Functions
 */

void
cocoa_menu_item_add_item (NSMenu* cocoa_menu, GtkWidget* menu_item, int index)
{
  GtkWidget* label      = NULL;
  _GNSMenuItem *cocoa_item;

  DEBUG ("add %s to menu %s separator ? %d\n",
         get_menu_label_text (menu_item, NULL),
       [[cocoa_menu title] cStringUsingEncoding: NSUTF8StringEncoding],
         GTK_IS_SEPARATOR_MENU_ITEM (menu_item));

  cocoa_item = cocoa_menu_item_get (menu_item);

  if (cocoa_item)
    {
      NSMenu *cocoa_menu = [cocoa_item menu];
      NSInteger index = [cocoa_menu indexOfItem: cocoa_item];
      DEBUG ("\tItem exists\n");
      [cocoa_item retain];
      [cocoa_menu removeItem: cocoa_item];
      [cocoa_item release];
      /* Clean up adjacent separators: */
      if ([cocoa_menu numberOfItems] > 0)
	{
	  if (index == 0)
	    {
	      if ([[cocoa_menu itemAtIndex: index] isSeparatorItem])
		[cocoa_menu removeItemAtIndex: index];
	    }
	  else if (([cocoa_menu numberOfItems] == index ||
		    [[cocoa_menu itemAtIndex: index] isSeparatorItem]) &&
		   [[cocoa_menu itemAtIndex: index - 1] isSeparatorItem])
	    [cocoa_menu removeItemAtIndex: index - 1];

	}
    }

  if (GTK_IS_SEPARATOR_MENU_ITEM (GTK_MENU_ITEM (menu_item)))
    {
      cocoa_item = (_GNSMenuItem*)[_GNSMenuItem separatorItem];
      DEBUG ("\ta separator\n");
    }
  else
    {
      const gchar* text = get_menu_label_text (menu_item, &label);
      NSString *title = [NSString stringWithUTF8String: (text ? text : "")];

      GClosure *menu_action =
        g_cclosure_new_object_swap (G_CALLBACK (gtk_menu_item_activate),
                                    G_OBJECT (menu_item));
      g_closure_set_marshal (menu_action, g_cclosure_marshal_VOID__VOID);

      cocoa_item = [[_GNSMenuItem alloc]
		    initWithTitle: title
		    aGClosure: menu_action andPointer: menu_item];

      DEBUG ("\tan item\n");
    }
  cocoa_menu_item_connect (menu_item, (_GNSMenuItem*) cocoa_item, label);

#ifdef USE_MENU_IMAGES
  if (GTK_IS_IMAGE_MENU_ITEM (menu_item))
    cocoa_menu_item_add_item_image (cocoa_item, menu_item);
#endif

[cocoa_item setEnabled: YES];

  /* connect GtkMenuItem and _GNSMenuItem so that we can notice changes
   * to accel/label/submenu etc. */

  if (index >= 0 && index < [cocoa_menu numberOfItems])
    [ cocoa_menu insertItem: cocoa_item atIndex: index];
  else
    [ cocoa_menu addItem: cocoa_item];

  cocoa_menu_item_sync (menu_item);
}

void
cocoa_menu_item_add_submenu (GtkMenuShell *menu_shell,
                             NSMenu*       cocoa_menu,
                             gboolean      toplevel,
                             gboolean      debug)
{
  GList         *children;
  GList         *l;
  guint index = 0, count, loc;
  GtkWidget *last_item = NULL;
  NSMenuItem *last_cocoa_item = nil;

  count = [cocoa_menu numberOfItems];
  /* First go through the cocoa menu and mark all of the items unused. */
  for (index = 0; index < count; index++)
    {
      _GNSMenuItem *indexedItem = (_GNSMenuItem*)[cocoa_menu itemAtIndex: index];
      if (GTK_IS_MENU_BAR (menu_shell) &&
          (indexedItem == [ (_GNSMenuBar*)cocoa_menu windowsMenu] ||
           indexedItem == [ (_GNSMenuBar*)cocoa_menu helpMenu] ||
           indexedItem == [ (_GNSMenuBar*)cocoa_menu appMenu]))
        continue;
      if ([[cocoa_menu itemAtIndex: index] respondsToSelector: @selector (mark)])
	[ (_GNSMenuItem*)[cocoa_menu itemAtIndex: index] mark];
    }
  index = toplevel ? 1 : 0; //Skip the 0th menu item on the menu bar
  /* Now iterate over the menu shell and check it against the cocoa menu */
  children = gtk_container_get_children (GTK_CONTAINER (menu_shell));
  for (l = children; l; l = l->next)
    {
      GtkWidget   *menu_item = (GtkWidget*) l->data;
      _GNSMenuItem *cocoa_item =  cocoa_menu_item_get (menu_item);
      if ([cocoa_item menu] && [cocoa_item menu] != cocoa_menu)
        /* This item has been moved to another menu; skip it */
        continue;
      if ([cocoa_item respondsToSelector: @selector (isMarked)] &&
	  [cocoa_menu numberOfItems] > index &&
	  [cocoa_menu itemAtIndex: index] == cocoa_item)
        {
          /* This item is where it belongs, so unmark and update it */
          [cocoa_item unmark];
          cocoa_menu_item_sync (menu_item);
          ++index;
	  last_item = menu_item;
          continue;
        }
      if (cocoa_item && (loc = [cocoa_menu indexOfItem: cocoa_item]) > -1)
        {
          /*It's in there, just in the wrong place. Put it where it goes
          and update it*/
          [cocoa_item retain];
	  [cocoa_menu removeItem: cocoa_item];
	  [cocoa_menu insertItem: cocoa_item atIndex: index++];
	  if ([cocoa_item respondsToSelector: @selector (isMarked)])
            [cocoa_item unmark];
          [cocoa_item release];
          cocoa_menu_item_sync (menu_item);
	  last_item = menu_item;
          continue;
        }
      if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item) &&
	  GTK_IS_MENU_BAR (menu_shell))
        /* Don't want separators on the menubar */
        continue;

      if (GTK_IS_SEPARATOR_MENU_ITEM (menu_item) &&
	  (last_item == NULL || GTK_IS_SEPARATOR_MENU_ITEM (last_item)))
	/* Don't put a separator at the top, nor make separators with
	 * nothing between them.
	 */
	continue;

#if ! GTK_CHECK_VERSION (3, 4, 0)
      if (GTK_IS_TEAROFF_MENU_ITEM (menu_item))
        /*Don't want tearoff items at all */
        continue;
#endif
      if (g_object_get_data (G_OBJECT (menu_item), "gtk-empty-menu-item"))
        /* Nor blank items. */
        continue;
      /*OK, this must be a new one. Add it. */
      cocoa_menu_item_add_item (cocoa_menu, menu_item, index++);
      last_item = menu_item;
    }
  /* Iterate over the cocoa menu again removing anything that's still
     marked, checking again for adjacent separators. */
  for (index = 0; index < [cocoa_menu numberOfItems]; index++)
    {
      _GNSMenuItem *item = (_GNSMenuItem*)[cocoa_menu itemAtIndex: index];
      if ([item isSeparatorItem] &&
	  (last_cocoa_item == nil || [last_cocoa_item isSeparatorItem]))
	{
	  [cocoa_menu removeItem: item];
	  continue;
	}
      if (([item respondsToSelector: @selector (isMarked)]) && [item isMarked])
	[cocoa_menu removeItem: item];
      else if (![item isHidden])
	last_cocoa_item = item;
    }
  /* Finally make sure that the last item isn't a separator. */
  if (last_cocoa_item != nil && [last_cocoa_item isSeparatorItem])
    [cocoa_menu removeItem: last_cocoa_item];

  g_list_free (children);
}

/*
 * The Keyval functions, forward declared at the top of the file.
 */

static guint
gdk_quartz_keyval_to_ns_keyval (guint keyval)
{
  switch (keyval)
    {
    case GDK_BackSpace:
      return NSBackspaceCharacter;
    case GDK_Delete:
      return NSDeleteFunctionKey;
    case GDK_Pause:
      return NSPauseFunctionKey;
    case GDK_Scroll_Lock:
      return NSScrollLockFunctionKey;
    case GDK_Sys_Req:
      return NSSysReqFunctionKey;
    case GDK_Home:
      return NSHomeFunctionKey;
    case GDK_Left:
    case GDK_leftarrow:
      return NSLeftArrowFunctionKey;
    case GDK_Up:
    case GDK_uparrow:
      return NSUpArrowFunctionKey;
    case GDK_Right:
    case GDK_rightarrow:
      return NSRightArrowFunctionKey;
    case GDK_Down:
    case GDK_downarrow:
      return NSDownArrowFunctionKey;
    case GDK_Page_Up:
      return NSPageUpFunctionKey;
    case GDK_Page_Down:
      return NSPageDownFunctionKey;
    case GDK_End:
      return NSEndFunctionKey;
    case GDK_Begin:
      return NSBeginFunctionKey;
    case GDK_Select:
      return NSSelectFunctionKey;
    case GDK_Print:
      return NSPrintFunctionKey;
    case GDK_Execute:
      return NSExecuteFunctionKey;
    case GDK_Insert:
      return NSInsertFunctionKey;
    case GDK_Undo:
      return NSUndoFunctionKey;
    case GDK_Redo:
      return NSRedoFunctionKey;
    case GDK_Menu:
      return NSMenuFunctionKey;
    case GDK_Find:
      return NSFindFunctionKey;
    case GDK_Help:
      return NSHelpFunctionKey;
    case GDK_Break:
      return NSBreakFunctionKey;
    case GDK_Mode_switch:
      return NSModeSwitchFunctionKey;
    case GDK_F1:
      return NSF1FunctionKey;
    case GDK_F2:
      return NSF2FunctionKey;
    case GDK_F3:
      return NSF3FunctionKey;
    case GDK_F4:
      return NSF4FunctionKey;
    case GDK_F5:
      return NSF5FunctionKey;
    case GDK_F6:
      return NSF6FunctionKey;
    case GDK_F7:
      return NSF7FunctionKey;
    case GDK_F8:
      return NSF8FunctionKey;
    case GDK_F9:
      return NSF9FunctionKey;
    case GDK_F10:
      return NSF10FunctionKey;
    case GDK_F11:
      return NSF11FunctionKey;
    case GDK_F12:
      return NSF12FunctionKey;
    case GDK_F13:
      return NSF13FunctionKey;
    case GDK_F14:
      return NSF14FunctionKey;
    case GDK_F15:
      return NSF15FunctionKey;
    case GDK_F16:
      return NSF16FunctionKey;
    case GDK_F17:
      return NSF17FunctionKey;
    case GDK_F18:
      return NSF18FunctionKey;
    case GDK_F19:
      return NSF19FunctionKey;
    case GDK_F20:
      return NSF20FunctionKey;
    case GDK_F21:
      return NSF21FunctionKey;
    case GDK_F22:
      return NSF22FunctionKey;
    case GDK_F23:
      return NSF23FunctionKey;
    case GDK_F24:
      return NSF24FunctionKey;
    case GDK_F25:
      return NSF25FunctionKey;
    case GDK_F26:
      return NSF26FunctionKey;
    case GDK_F27:
      return NSF27FunctionKey;
    case GDK_F28:
      return NSF28FunctionKey;
    case GDK_F29:
      return NSF29FunctionKey;
    case GDK_F30:
      return NSF30FunctionKey;
    case GDK_F31:
      return NSF31FunctionKey;
    case GDK_F32:
      return NSF32FunctionKey;
    case GDK_F33:
      return NSF33FunctionKey;
    case GDK_F34:
      return NSF34FunctionKey;
    case GDK_F35:
      return NSF35FunctionKey;
    default:
      break;
    }

  return 0;
}

static gboolean
keyval_is_keypad (guint keyval)
{
  switch (keyval)
    {
    case GDK_KP_F1:
    case GDK_KP_F2:
    case GDK_KP_F3:
    case GDK_KP_F4:
    case GDK_KP_Home:
    case GDK_KP_Left:
    case GDK_KP_Up:
    case GDK_KP_Right:
    case GDK_KP_Down:
    case GDK_KP_Page_Up:
    case GDK_KP_Page_Down:
    case GDK_KP_End:
    case GDK_KP_Begin:
    case GDK_KP_Insert:
    case GDK_KP_Delete:
    case GDK_KP_Equal:
    case GDK_KP_Multiply:
    case GDK_KP_Add:
    case GDK_KP_Separator:
    case GDK_KP_Subtract:
    case GDK_KP_Decimal:
    case GDK_KP_Divide:
    case GDK_KP_0:
    case GDK_KP_1:
    case GDK_KP_2:
    case GDK_KP_3:
    case GDK_KP_4:
    case GDK_KP_5:
    case GDK_KP_6:
    case GDK_KP_7:
    case GDK_KP_8:
    case GDK_KP_9:
      return TRUE;
      break;
    default:
      break;
    }
  return FALSE;
}

static guint
keyval_keypad_nonkeypad_equivalent (guint keyval)
{
  switch (keyval)
    {
    case GDK_KP_F1:
      return GDK_F1;
    case GDK_KP_F2:
      return GDK_F2;
    case GDK_KP_F3:
      return GDK_F3;
    case GDK_KP_F4:
      return GDK_F4;
    case GDK_KP_Home:
      return GDK_Home;
    case GDK_KP_Left:
      return GDK_Left;
    case GDK_KP_Up:
      return GDK_Up;
    case GDK_KP_Right:
      return GDK_Right;
    case GDK_KP_Down:
      return GDK_Down;
    case GDK_KP_Page_Up:
      return GDK_Page_Up;
    case GDK_KP_Page_Down:
      return GDK_Page_Down;
    case GDK_KP_End:
      return GDK_End;
    case GDK_KP_Begin:
      return GDK_Begin;
    case GDK_KP_Insert:
      return GDK_Insert;
    case GDK_KP_Delete:
      return GDK_Delete;
    case GDK_KP_Equal:
      return GDK_equal;
    case GDK_KP_Multiply:
      return GDK_asterisk;
    case GDK_KP_Add:
      return GDK_plus;
    case GDK_KP_Subtract:
      return GDK_minus;
    case GDK_KP_Decimal:
      return GDK_period;
    case GDK_KP_Divide:
      return GDK_slash;
    case GDK_KP_0:
      return GDK_0;
    case GDK_KP_1:
      return GDK_1;
    case GDK_KP_2:
      return GDK_2;
    case GDK_KP_3:
      return GDK_3;
    case GDK_KP_4:
      return GDK_4;
    case GDK_KP_5:
      return GDK_5;
    case GDK_KP_6:
      return GDK_6;
    case GDK_KP_7:
      return GDK_7;
    case GDK_KP_8:
      return GDK_8;
    case GDK_KP_9:
      return GDK_9;
    default:
      break;
    }

  return GDK_VoidSymbol;
}

static const gchar*
gdk_quartz_keyval_to_string (guint keyval)
{
  switch (keyval)
    {
    case GDK_space:
      return " ";
    case GDK_exclam:
      return "!";
    case GDK_quotedbl:
      return "\"";
    case GDK_numbersign:
      return "#";
    case GDK_dollar:
      return "$";
    case GDK_percent:
      return "%";
    case GDK_ampersand:
      return "&";
    case GDK_apostrophe:
      return "'";
    case GDK_parenleft:
      return "(";
    case GDK_parenright:
      return ")";
    case GDK_asterisk:
      return "*";
    case GDK_plus:
      return "+";
    case GDK_comma:
      return ",";
    case GDK_minus:
      return "-";
    case GDK_period:
      return ".";
    case GDK_slash:
      return "/";
    case GDK_0:
      return "0";
    case GDK_1:
      return "1";
    case GDK_2:
      return "2";
    case GDK_3:
      return "3";
    case GDK_4:
      return "4";
    case GDK_5:
      return "5";
    case GDK_6:
      return "6";
    case GDK_7:
      return "7";
    case GDK_8:
      return "8";
    case GDK_9:
      return "9";
    case GDK_colon:
      return ":";
    case GDK_semicolon:
      return ";";
    case GDK_less:
      return "<";
    case GDK_equal:
      return "=";
    case GDK_greater:
      return ">";
    case GDK_question:
      return "?";
    case GDK_at:
      return "@";
    case GDK_A:
    case GDK_a:
      return "a";
    case GDK_B:
    case GDK_b:
      return "b";
    case GDK_C:
    case GDK_c:
      return "c";
    case GDK_D:
    case GDK_d:
      return "d";
    case GDK_E:
    case GDK_e:
      return "e";
    case GDK_F:
    case GDK_f:
      return "f";
    case GDK_G:
    case GDK_g:
      return "g";
    case GDK_H:
    case GDK_h:
      return "h";
    case GDK_I:
    case GDK_i:
      return "i";
    case GDK_J:
    case GDK_j:
      return "j";
    case GDK_K:
    case GDK_k:
      return "k";
    case GDK_L:
    case GDK_l:
      return "l";
    case GDK_M:
    case GDK_m:
      return "m";
    case GDK_N:
    case GDK_n:
      return "n";
    case GDK_O:
    case GDK_o:
      return "o";
    case GDK_P:
    case GDK_p:
      return "p";
    case GDK_Q:
    case GDK_q:
      return "q";
    case GDK_R:
    case GDK_r:
      return "r";
    case GDK_S:
    case GDK_s:
      return "s";
    case GDK_T:
    case GDK_t:
      return "t";
    case GDK_U:
    case GDK_u:
      return "u";
    case GDK_V:
    case GDK_v:
      return "v";
    case GDK_W:
    case GDK_w:
      return "w";
    case GDK_X:
    case GDK_x:
      return "x";
    case GDK_Y:
    case GDK_y:
      return "y";
    case GDK_Z:
    case GDK_z:
      return "z";
    case GDK_bracketleft:
      return "[";
    case GDK_backslash:
      return "\\";
    case GDK_bracketright:
      return "]";
    case GDK_asciicircum:
      return "^";
    case GDK_underscore:
      return "_";
    case GDK_grave:
      return "`";
    case GDK_braceleft:
      return "{";
    case GDK_bar:
      return "|";
    case GDK_braceright:
      return "}";
    case GDK_asciitilde:
      return "~";
    default:
      break;
    }
  return NULL;
};
static gboolean
keyval_is_uppercase (guint keyval)
{
  switch (keyval)
    {
    case GDK_A:
    case GDK_B:
    case GDK_C:
    case GDK_D:
    case GDK_E:
    case GDK_F:
    case GDK_G:
    case GDK_H:
    case GDK_I:
    case GDK_J:
    case GDK_K:
    case GDK_L:
    case GDK_M:
    case GDK_N:
    case GDK_O:
    case GDK_P:
    case GDK_Q:
    case GDK_R:
    case GDK_S:
    case GDK_T:
    case GDK_U:
    case GDK_V:
    case GDK_W:
    case GDK_X:
    case GDK_Y:
    case GDK_Z:
      return TRUE;
    default:
      return FALSE;
    }
  return FALSE;
}
