/* GTK - The GIMP Toolkit
 * Copyright (C) 2000 Red Hat Software
 * Copyright (C) 2003 Motonobu Ichimura
 *
 * 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 License, 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
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Motonobu Ichimura <famao@momonga-linux.org>
 *
 */

#include <config.h>
#include <locale.h>
#include <libintl.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <gtk/gtkimmodule.h>
#include <gtk/gtkinvisible.h>
#include <gdk/gdkproperty.h>
#include <gdk/gdkselection.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xatom.h>
#include "iiimcf.h"
#include "gtkimcontextiiim.h"
#include "imaux.h"
#include "imswitcher.h"
#include "IIIMGdkEventKey.h"

#ifdef DEBUG
#define DEBUG_DO(x) (x)
#else
#define DEBUG_DO(x)
#endif

typedef struct _CandidateWindow CandidateWindow;

/* Style for gtk input method preedit/status */
struct _GtkIIIMInfo
{
#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2)
  GdkScreen *screen;
#endif
  IIIMCF_handle iiim;

  /* locale is obtained from $IIIM_LOCALE, or LC_CTYPE seeting. */
  char *locale;

#ifdef DIRTY_HACK_FOR_TEST_PURPOSE
  /* le is obtained from $IIIM_LE */
  char *le_name;
#endif

  GSList *ics;

  SwitcherInfo *switcher_info;

  gchar *le_list;
  /*
    The following GtkSettings are used to configure appearance of
    input method status windows.

    status_style_setting:
    	GTK_IM_STATUS_NOTHING
	  IIIMGCF does not show the status window, but somebody else perhaps 
	  GIMLET shows it on their UI.
	GTK_IM_STATUS_CALLBACK
	  IIIMGCF shows the status window attached to application frame
	  window.
	GTK_IM_STATUS_NONE
	  no status window anywhere.
   */
  GtkSettings *settings;
  gulong status_set;
  GtkIMStatusStyle status_style_setting;
};

/* A candiate window */
struct _CandidateWindow
{
  GtkWidget *tree;
  GtkWidget *toplevel;
  GtkWidget *frame;

  /* Toplevel window to which the candiddate window corresponds */
  GtkWidget *app_toplevel;
  GtkListStore *store;
  gint choices_per_window;
  gint number_of_rows;
  gint number_of_columns;
  gint direction;

  gulong destroy_handler_id;
  gulong configure_handler_id;
  gulong button_press_handler_id;
};

/* A context status window; these are kept in the status_windows list. */
struct _StatusWindow
{
  GtkWidget *window;

  /* Toplevel window to which the status window corresponds */
  GtkWidget *toplevel;

  /* Currently focused GtkIMContextIIIM for the toplevel, if any */
  GtkIMContextIIIM *context;
};

typedef enum {
  ON_DESKTOP_PANEL,
  ATTACH_TO_APP_FRAME
} IMStatusPlacement;

typedef enum {
  CONV_OFF = 0,
  CONV_ON = 1
} ICConversionMode;

typedef enum {
  IM_OFF = 0,
  IM_ON
} IMEnabled;

typedef struct {
  IMEnabled im_enabled;
  IMEnabled status_enabled;
  IMEnabled lookup_enabled;
  IMStatusPlacement status_placement;
} IMSettings;

static IMSettings current_setting;
static gboolean current_setting_enabled;
static GdkAtom im_settings_atom = GDK_NONE;
static const char *_IM_SETTINGS = "_IM_SETTINGS";

static gboolean on_status_toplevel_configure (GtkWidget         *toplevel,
					      GdkEventConfigure *event,
					      StatusWindow      *status_window);
static void im_context_iiim_class_init (GtkIMContextIIIMClass * class);
static void im_context_iiim_init (GtkIMContextIIIM * im_context);
static void im_context_iiim_finalize (GObject * obj);
static void im_context_iiim_set_client_window (GtkIMContext * context,
					       GdkWindow * client_window);
static gboolean im_context_iiim_filter_keypress (GtkIMContext * context,
						 GdkEventKey * key);
static void im_context_iiim_reset (GtkIMContext * context);
static void im_context_iiim_focus_in (GtkIMContext * context);
static void im_context_iiim_focus_out (GtkIMContext * context);

static void im_context_iiim_set_cursor_location (GtkIMContext * context,
						 GdkRectangle * area);
static void im_context_iiim_set_use_preedit (GtkIMContext * context,
					     gboolean use_preedit);
static void im_context_iiim_get_preedit_string (GtkIMContext * context,
						gchar ** str,
						PangoAttrList ** attrs,
						gint * cursor_pos);


/* Session Context */
static IIIMCF_context iiim_get_session_context (GtkIMContextIIIM *
						context_iiim);

/* Candidate Window */
static IIIMCF_lookup_choice iiim_get_lookup_choice (GtkIMContextIIIM *
						    context_iiim);
static GtkWidget *iiim_get_candidate_window (GtkIMContextIIIM * context_iiim);
static void iiim_candidate_show (GtkIMContextIIIM * context_iiim);
static void iiim_destroy_candidate_window (GtkIMContextIIIM * context_iiim);
static gboolean iiim_candidate_window_configure (GtkWidget * toplevel,
						 GdkEventConfigure * event,
						 GtkIMContextIIIM *context_iiim);
static void iiim_candidate_window_button_press (GtkWidget *widget,
						GdkEventButton *event,
						GtkIMContextIIIM *context_iiim);

/* Key Event */
static void iiim_keylist_free (GtkIMContextIIIM * context_iiim);
static gint check_stored_keyevent (GtkIMContext * context,
				   GdkEventKey * event);

/* Event */
static gboolean iiim_event_dispatch (GtkIMContextIIIM * context_iiim);
static gboolean forward_event (GtkIMContextIIIM * context_iiim,
			       IIIMCF_event ev, IIIMF_status *st_ret);

static gchar *utf16_to_utf8 (IIIMCF_text text);

static void
set_sc_client_window (GtkIMContextIIIM * context_iiim,
		      GdkWindow * client_window, gboolean send_signal);

/* Status */
static void update_client_widget   (GtkIMContextIIIM *context_iiim);
static void update_status_window   (GtkIMContextIIIM*context_iiim);

static StatusWindow *status_window_get      (GtkWidget    *toplevel);
static void          status_window_free     (StatusWindow *status_window);
static void          status_window_set_text (StatusWindow *status_window,
					     const gchar  *text);

static void reinitialize_sc (GtkIMContextIIIM * context_iiim,
			     gboolean send_signal);


static GObjectClass *parent_class;

GType gtk_type_im_context_iiim = 0;

static GSList *open_iiims = NULL;

static gboolean iiim_is_initialized = FALSE;
static IIIMCF_handle iiim = NULL;

/* List of status windows for different toplevels */
static GSList *status_windows = NULL;

void
im_context_iiim_register_type (GTypeModule * type_module)
{
  static const GTypeInfo im_context_iiim_info = {
    sizeof (GtkIMContextIIIMClass),
    (GBaseInitFunc) NULL,
    (GBaseFinalizeFunc) NULL,
    (GClassInitFunc) im_context_iiim_class_init,
    NULL,
    NULL,
    sizeof (GtkIMContextIIIM),
    0,
    (GInstanceInitFunc) im_context_iiim_init,
  };

  gtk_type_im_context_iiim =
    g_type_module_register_type (type_module,
				 GTK_TYPE_IM_CONTEXT,
				 "GtkIMContextIIIM",
				 &im_context_iiim_info, 0);
}


static void
im_context_iiim_class_init (GtkIMContextIIIMClass * class)
{
  GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  parent_class = g_type_class_peek_parent (class);


  im_context_class->set_client_window = im_context_iiim_set_client_window;
  im_context_class->filter_keypress = im_context_iiim_filter_keypress;
  im_context_class->reset = im_context_iiim_reset;
  im_context_class->get_preedit_string = im_context_iiim_get_preedit_string;
  im_context_class->focus_in = im_context_iiim_focus_in;
  im_context_class->focus_out = im_context_iiim_focus_out;
  im_context_class->set_cursor_location = im_context_iiim_set_cursor_location;
  im_context_class->set_use_preedit = im_context_iiim_set_use_preedit;
  gobject_class->finalize = im_context_iiim_finalize;
  return;
}

/* 
 * Update Status Window Message 
 */
static void
status_callback (GtkIMContextIIIM * context_iiim)
{
  gchar *utf8;
  IIIMCF_context c = iiim_get_session_context (context_iiim);
  IIIMCF_text text;
  IIIMF_status st;

  if (!context_iiim->status_window)
    {
      if (!context_iiim->has_focus)
	{
	  context_iiim->has_focus = TRUE;
	  /* tell switcher that I'm the current client */
	  im_context_switcher_set_focus (context_iiim);
	}
      update_status_window (context_iiim);
    }
  if (!context_iiim->status_window)
    return;

  st = iiimcf_get_status_text (c, &text);
  if (st == IIIMF_STATUS_NO_STATUS_TEXT || st != IIIMF_STATUS_SUCCESS)
    {
      status_window_set_text (context_iiim->status_window, "");
      im_context_switcher_set_status_text (context_iiim, " ");
      return;
    }
  utf8 = utf16_to_utf8 (text);

  if (current_setting_enabled &&
      (current_setting.im_enabled == IM_OFF ||
       current_setting.status_enabled == IM_OFF))
    status_window_set_text (context_iiim->status_window, "");
  else if ((current_setting_enabled &&
	    current_setting.status_placement == ATTACH_TO_APP_FRAME) ||
	   !current_setting_enabled)
    {
      status_window_set_text (context_iiim->status_window, utf8);
      im_context_switcher_set_status_text (context_iiim, utf8);
    }
  else
    {
      status_window_set_text (context_iiim->status_window, "");
      im_context_switcher_set_status_text (context_iiim, utf8);
    }

  g_free (utf8);
}

/* get Lang List */
IIIMCF_language*
iiim_get_languages (GtkIIIMInfo *info, int *n_lang)
{
  IIIMF_status st;
  IIIMCF_handle iiim = info->iiim;
  IIIMCF_language *lang = NULL;

  if (!iiim)
    return NULL;

  st = iiimcf_get_supported_languages (iiim, n_lang, &lang);
  if (st != IIIMF_STATUS_SUCCESS)
    return NULL;

  return lang;
}

static char*
format_iiimcf_string (const IIIMP_card16 *u16str)
{
  return (u16str != NULL ?
	  g_utf16_to_utf8 ((const gunichar2 *) u16str, -1, NULL, NULL, NULL) :
	  NULL);
}

/*
 * Get IM List
 */
static void
iiim_get_im_list (GtkIIIMInfo * info)
{
  const IIIMP_card16 *u16idname, *u16hrn, *u16domain;
  int input_methods_n;
  gint i;
  gsize len = 0;
  IIIMCF_handle handle = info->iiim;
  IIIMCF_input_method *input_methods;
  IIIMF_status st;
  const char *separator = ";";
  gsize separator_len;
  gchar *le_list, *ptr;

  separator_len = strlen (separator);

  if (!handle)
    return;

  st = iiimcf_get_supported_input_methods (handle,
					   &input_methods_n, &input_methods);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  /* First part, getting length */
  for (i = 0; i < input_methods_n; i++)
    {
      gint nlangs;
      gint j;
      IIIMCF_language *plangs;
      char *langid, *lename;

      st = iiimcf_get_input_method_desc (input_methods[i], &u16idname,
					 &u16hrn, &u16domain);
      if (st != IIIMF_STATUS_SUCCESS)
	return;

      lename = format_iiimcf_string (u16hrn);

      st = iiimcf_get_input_method_languages (input_methods[i],
					      &nlangs, &plangs);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
      for (j = 0; j < nlangs; j++)
	{
	  st = iiimcf_get_language_id (plangs[j],
				       (const char **)&langid);
	  len += strlen (lename) + 1 + strlen (langid);
	  len += separator_len;
	}
      g_free (lename);
    }

  /* Second part, building string */
  le_list = g_new (gchar, len + 1);

  ptr = le_list;
  for (i = 0; i < input_methods_n; i++)
    {
      gint nlangs;
      gint j;
      IIIMCF_language *plangs;
      char *langid, *lename;

      st = iiimcf_get_input_method_desc (input_methods[i], &u16idname,
					 &u16hrn, &u16domain);
      lename = format_iiimcf_string (u16hrn);

      st = iiimcf_get_input_method_languages (input_methods[i],
					      &nlangs, &plangs);
      if (st != IIIMF_STATUS_SUCCESS)
	return;

      st = iiimcf_get_language_id (plangs[0],
				   (const char **)&langid);
      for (j = 0; j < nlangs; j++)
	{
	  st = iiimcf_get_language_id (plangs[j],
				       (const char **)&langid);
	  ptr = g_stpcpy (ptr, langid);
	  ptr = g_stpcpy (ptr, ":");
	  ptr = g_stpcpy (ptr, lename);
	  ptr = g_stpcpy (ptr, separator);
	}
      g_free (lename);
    }
  le_list[len - 1] = 0;

  info->le_list = le_list;
  return;
}

/*
 * Event Dispatch
 */
static gboolean
iiim_event_dispatch (GtkIMContextIIIM * context_iiim)
{
  IIIMCF_context c;
  IIIMF_status st;
  IIIMCF_event ev;
  IIIMCF_event_type et;
  gboolean result = TRUE;

  c = iiim_get_session_context (context_iiim);
  if (!c)
    return FALSE;

  while ((st = iiimcf_get_next_event (c, &ev)) == IIIMF_STATUS_SUCCESS)
    {
      st = iiimcf_get_event_type (ev, &et);
      if (st != IIIMF_STATUS_SUCCESS)
	continue;
      DEBUG_DO (g_message ("event type %d", et));
      switch (et)
	{
	case IIIMCF_EVENT_TYPE_KEYEVENT:
	  {
	    /* We need to send GdkEventKey to a client */

	    GdkEventKey *event;
	    GdkEventKey release_event;
	    IIIMCF_keyevent kev;
	    st = iiimcf_get_keyevent_value (ev, &kev);
	    if (st != IIIMF_STATUS_SUCCESS)
	      {
		result = FALSE;
		break;
	      }
	    event = (GdkEventKey *) gdk_event_new (GDK_KEY_PRESS);
	    st = convert_IIIMCF_keyevent_to_GdkEventKey (&kev, event);
	    DEBUG_DO (g_message ("event created %s %d %d %d",
				 gdk_keyval_name (event->keyval),
				 event->hardware_keycode, event->length,
				 event->group));
	    if (st != IIIMF_STATUS_SUCCESS)
	      {
		gdk_event_free ((GdkEvent *) event);
		result = FALSE;
		break;
	      }
	    event->window = context_iiim->client_window;

	    /* 
	     * increment client_window's ref count.
	     * gdk_event_free decrements it.
	     */

	    g_object_ref (event->window);
	    gdk_event_put ((GdkEvent *) event);

	    /* Store GdkEventKey */
	    context_iiim->keylist =
	      g_slist_append (context_iiim->keylist, event);

	    /* create Fake Key Release Event */
	    memset (&release_event, 0, sizeof (GdkEventKey));
	    release_event.type = GDK_KEY_RELEASE;
	    release_event.window = context_iiim->client_window;
	    release_event.state = event->state;
	    release_event.keyval = event->keyval;
	    release_event.hardware_keycode = event->hardware_keycode;
	    gdk_event_put ((GdkEvent *) & release_event);
	  }
	  break;
	case IIIMCF_EVENT_TYPE_TRIGGER_NOTIFY:
	  im_context_switcher_set_conversion_mode (context_iiim);
	  break;
	case IIIMCF_EVENT_TYPE_UI_PREEDIT_START:
	  DEBUG_DO (g_message ("preedit start"));
	  if (!context_iiim->finalizing)
	    g_signal_emit_by_name (context_iiim, "preedit_start");
	  break;
	case IIIMCF_EVENT_TYPE_UI_PREEDIT_CHANGE:
	  DEBUG_DO (g_message ("preedit changed"));
	  if (!context_iiim->finalizing)
	    g_signal_emit_by_name (context_iiim, "preedit_changed");
	  break;
	case IIIMCF_EVENT_TYPE_UI_PREEDIT_DONE:
	  DEBUG_DO (g_message ("preedit end"));
	  /*
	    call preedit_changed here, otherwise
	    preedit text on gtkview and gtkentry remains when 
	    conversion is turned off.
	  */
	  if (!context_iiim->finalizing)
	    {
	      g_signal_emit_by_name (context_iiim, "preedit_changed");
	      g_signal_emit_by_name (context_iiim, "preedit_end");
	    }
	  break;
	case IIIMCF_EVENT_TYPE_UI_LOOKUP_CHOICE_START:
	  DEBUG_DO (g_message ("lookup_choice start"));
	  context_iiim->candidate_start = TRUE;
	  iiim_candidate_show (context_iiim);
	  break;
	case IIIMCF_EVENT_TYPE_UI_LOOKUP_CHOICE_CHANGE:
	  iiim_candidate_show (context_iiim);
	  DEBUG_DO (g_message ("lookup_choice change"));
	  break;
	case IIIMCF_EVENT_TYPE_UI_LOOKUP_CHOICE_DONE:
	  context_iiim->candidate_start = FALSE;
	  iiim_destroy_candidate_window (context_iiim);
	  DEBUG_DO (g_message ("lookup_choice_done"));
	  break;
	case IIIMCF_EVENT_TYPE_UI_STATUS_START:
	  DEBUG_DO (g_message ("ui_status_start"));
	  status_callback (context_iiim);
	  break;
	case IIIMCF_EVENT_TYPE_UI_STATUS_CHANGE:
	  DEBUG_DO (g_message ("ui_status_change"));
	  status_callback (context_iiim);
	  break;
	case IIIMCF_EVENT_TYPE_UI_STATUS_END:
	  DEBUG_DO (g_message ("ui_status_end"));
	  status_callback (context_iiim);
	  break;
	case IIIMCF_EVENT_TYPE_AUX_START:
	  DEBUG_DO (g_message ("aux_start"));
	  iiim_aux_start (context_iiim, ev);
	  break;
	case IIIMCF_EVENT_TYPE_AUX_DRAW:
	  DEBUG_DO (g_message ("aux_draw"));
	  iiim_aux_draw (context_iiim, ev);
	  break;
	case IIIMCF_EVENT_TYPE_AUX_DONE:
	  DEBUG_DO (g_message ("aux_done"));
	  iiim_aux_done (context_iiim, ev);
	  break;
	case IIIMCF_EVENT_TYPE_AUX_GETVALUES:
	  DEBUG_DO (g_message ("aux_getvalues_reply"));
	  iiim_aux_getvalues_reply (context_iiim, ev);
	  break;
	case IIIMCF_EVENT_TYPE_UI_COMMIT:
	  {
	    IIIMCF_text text;
	    gchar *utf8 = NULL;
	    st = iiimcf_get_committed_text (c, &text);
	    utf8 = utf16_to_utf8 (text);
	    g_signal_emit_by_name (context_iiim, "commit", utf8);
	    g_free (utf8);
	  }
	  break;
	default:
	  break;
	}
      iiimcf_dispatch_event (c, ev);
      iiimcf_ignore_event (ev);
    }

  return result;
}

#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >=2)
static void
iiim_info_display_closed (GdkDisplay * display,
			  gboolean is_error, GtkIIIMInfo * info)
{
  GSList *ics, *tmp_list;

  open_iiims = g_slist_remove (open_iiims, info);

  ics = info->ics;
  info->ics = NULL;

  for (tmp_list = ics; tmp_list; tmp_list = g_slist_next (tmp_list))
    {
      set_sc_client_window (tmp_list->data, NULL, TRUE);
    }

  g_slist_free (tmp_list);

  g_free (info->locale);

#ifdef DIRTY_HACK_FOR_TEST_PURPOSE
  if (info->le_name)
    g_free (info->le_name);
#endif

  g_free (info->le_list);
  /* TODO */
  g_free (info);
}
#endif

static void
status_style_change (GtkIIIMInfo *info)
{
  GtkIMStatusStyle status_style;
  
  g_object_get (info->settings,
		"gtk-im-status-style", &status_style,
		NULL);

  info->status_style_setting = status_style;
}

static void
get_im_settings_property ()
{
  GdkWindow *root_window = gdk_get_default_root_window ();
  GdkAtom  type;
  guchar   *data = NULL;
  gint     format;
  gint     length = 0;
  if (im_settings_atom == GDK_NONE)
    im_settings_atom = gdk_atom_intern (_IM_SETTINGS, FALSE);
  if (im_settings_atom == GDK_NONE)
    return;

  gdk_property_get (root_window,
		    im_settings_atom, im_settings_atom,
		    0, INT_MAX, FALSE,
		    &type, &format, &length, &data);
  if (data)
    {
      g_memmove (&current_setting, data, sizeof (IMSettings));
      g_free (data);
      current_setting_enabled = TRUE;
    }
  else
    current_setting_enabled = FALSE;
}

static GtkIIIMInfo *
get_iiim (GdkWindow * client_window)
{
  GtkIIIMInfo *info = NULL;
  IIIMCF_attr attr;
  IIIMF_status st;
#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
  GSList *tmp_list;
  GdkScreen *screen = gdk_drawable_get_screen (client_window);
  GdkDisplay *display = gdk_screen_get_display (screen);
#endif

#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
  tmp_list = open_iiims;
  while (tmp_list)
    {
      info = tmp_list->data;
      if (info->screen == screen)
	return info;
      tmp_list = tmp_list->next;
    }
#else
  if (open_iiims)
    return open_iiims->data;
#endif
  info = NULL;

  if (!iiim_is_initialized)
    {
      Atom	iiimd;
      Window	iiimx;
      Display *	display_x11;

      st = iiimcf_initialize (IIIMCF_ATTR_NULL);
      if (st != IIIMF_STATUS_SUCCESS)
	goto Error;
      st = iiimcf_create_attr (&attr);
      if (st != IIIMF_STATUS_SUCCESS)
	goto Error;
      st = iiimcf_attr_put_string_value (attr,
					 IIIMCF_ATTR_CLIENT_TYPE,
					 "Gtk IIIMCF Module");
      if (st != IIIMF_STATUS_SUCCESS)
	goto Error;
      
      st = iiimcf_attr_put_string_value (attr,
					 IIIMCF_ATTR_CLIENT_X_DISPLAY_NAME,
					 gdk_screen_make_display_name(screen));
      if (st != IIIMF_STATUS_SUCCESS)
	goto Error;

      display_x11 = GDK_DISPLAY_XDISPLAY (display);
      iiimd = XInternAtom (display_x11, "IIIM_SERVER", True);
      if (None != iiimd) {
	iiimx = XGetSelectionOwner (display_x11, iiimd);
	if (None != iiimx) {
	  Atom		  type;
	  int		  format;
	  unsigned long	  length;
	  unsigned long	  nitem;
	  unsigned char * data;
	  data = NULL;
	  XGetWindowProperty(display_x11, iiimx, iiimd,
			     0, INT_MAX, False, XA_STRING,
			     &type, &format, &nitem, &length, &data);
	  if (NULL == data) {
	  } else if (0 == strncmp("uds:", (char *)data, 4)) {
	    st = iiimcf_attr_put_string_value (attr,
					       IIIMCF_ATTR_SERVER_ADDRESS,
					       data + 4);
	    XFree(data);
	    if (st != IIIMF_STATUS_SUCCESS)
	      goto Error;
	    st = iiimcf_attr_put_string_value (attr,
					       IIIMCF_ATTR_SERVER_SERVICE,
					       "");
	    if (st != IIIMF_STATUS_SUCCESS)
	      goto Error;
	  } else {
	    XFree (data);
	  }
	}
      }

      st = iiimcf_create_handle (attr, &iiim);
      if (st != IIIMF_STATUS_SUCCESS)
	goto Error;
      st = iiimcf_destroy_attr (attr);
      if (iiim)
	{
	  st = iiim_setup_aux_object (iiim);
	  if (st == IIIMF_STATUS_SUCCESS)
	    iiim_is_initialized = TRUE;
	}
    }

Error:

  if (!iiim)
    return NULL;

  info = g_new0 (GtkIIIMInfo, 1);
  open_iiims = g_slist_prepend (open_iiims, info);
#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
  info->screen = screen;
#endif

  info->iiim = iiim;
  info->ics = NULL;

#ifdef DIRTY_HACK_FOR_TEST_PURPOSE
  /* Dirty Hack for Test Purpose */
  {
    gchar *locale = NULL;
    gchar *le_name = NULL;

    locale = getenv ("IIIM_LOCALE");
    le_name = getenv ("IIIM_LE");

    if (locale && *locale)
      info->locale = g_strdup (locale);
    else
      info->locale = g_strdup (setlocale (LC_CTYPE, NULL));

    if (le_name && *le_name)
      info->le_name = g_strdup (le_name);
  }
#else
  info->locale = g_strdup (setlocale (LC_CTYPE, NULL));
#endif

  info->settings = gtk_settings_get_for_screen (info->screen);

  if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
				     "gtk-im-status-style"))
    gtk_settings_install_property (g_param_spec_enum ("gtk-im-status-style",
						      _("IM Status Style"),
						      _("Where to show the input method statusbar"),
						      GTK_TYPE_IM_STATUS_STYLE,
						      GTK_IM_STATUS_CALLBACK,
						      G_PARAM_READWRITE));

  info->status_set = g_signal_connect_swapped (info->settings,
					       "notify::gtk-im-status-style",
					       G_CALLBACK (status_style_change),
					       info);
  status_style_change (info);

  iiim_get_im_list (info);

  if (!info->iiim)
    g_warning ("Unable to Connect IIIM input method");

#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
  g_signal_connect (display, "closed",
		    G_CALLBACK (iiim_info_display_closed), info);
#endif
  return info;
}

/* 
 * Candidate Window Handling
 */

static void
iiim_candidate_move (GtkIMContextIIIM *context_iiim, GtkWidget *candwin)
{
  gint x, y;
  GdkRectangle rect;
  GtkRequisition requisition;
  gint width, height;

  height = gdk_screen_get_height (gtk_widget_get_screen (candwin));
  width = gdk_screen_get_width (gtk_widget_get_screen (candwin));
  gdk_window_get_frame_extents (candwin->window, &rect);
  gtk_widget_size_request (candwin, &requisition);

  gdk_window_get_origin (context_iiim->client_window, &x, &y);

  x = x + context_iiim->cursor.x;
  y = y + context_iiim->cursor.y + context_iiim->cursor.height;

  if (y + requisition.height > height)
    y = height - requisition.height;
  else if (y < 0)		/* may not happen */
    y = 0;

  if (x + requisition.width > width)
    x = width - requisition.width;
  else if (x < 0)
    x = 0;

  if (requisition.width < rect.width || requisition.height < rect.height)
    gtk_window_resize (GTK_WINDOW (candwin), requisition.width, requisition.height);
  gtk_window_move (GTK_WINDOW (candwin), x, y);
}

static void
iiim_candidate_show (GtkIMContextIIIM * context_iiim)
{
  GtkWidget *w = iiim_get_candidate_window (context_iiim);
  IIIMF_status st;
  IIIMCF_lookup_choice luc;
  CandidateWindow *cw;
  GtkTreeIter iter;
  GtkTreeIter riter;
  int size;
  int first_candidate;
  int last_candidate;
  int current_candidate;
  int i, j;
  IIIMCF_text title, candidate, label;
  gchar *title_u8 = NULL;
  int flag;

  DEBUG_DO (g_message ("candidate show"));
  if (!w)
    {
      DEBUG_DO (g_message ("w not found"));
      return;
    }
  if (!context_iiim->candidate_start)
    {
      DEBUG_DO (g_message ("candidate not start"));
      return;
    }

  if (current_setting_enabled &&
      current_setting.lookup_enabled == IM_OFF)
    return;

  luc = iiim_get_lookup_choice (context_iiim);
  if (!luc)
    {
      DEBUG_DO (g_message ("fail to obtain IIIMCF_lookup_choice"));
      return;
    }

  title = NULL;

  st = iiimcf_get_lookup_choice_title (luc, &title);
  if (st != IIIMF_STATUS_SUCCESS)
    {
      DEBUG_DO (g_message ("Failed to get lookup choice title"));
      return;
    }

  if (title)
    title_u8 = utf16_to_utf8 (title);

  cw = g_object_get_data (G_OBJECT (w), "iiim-candidate-win");
  if (!cw)
    {
      DEBUG_DO (g_message ("candidate window not found"));
      return;
    }

  gtk_frame_set_label(GTK_FRAME (cw->frame), title_u8);

  if (title_u8)
    g_free (title_u8);

  /* get candidates's amount from IIIMSF */
  size = 0;
  first_candidate = 0;
  last_candidate = 0;
  current_candidate = 0;
  st = iiimcf_get_lookup_choice_size (luc, &size, &first_candidate,
				      &last_candidate, &current_candidate);
  if (st != IIIMF_STATUS_SUCCESS)
    return;
  DEBUG_DO (g_message ("size %d first %d last %d current %d",
		       size, first_candidate, last_candidate,
		       current_candidate));
  /* some LE sends negative value :-< */
  if (current_candidate < 0) current_candidate = 0;

  /* clear */
  gtk_list_store_clear (cw->store);
  /* set iter */
  gtk_list_store_append (cw->store, &iter);
  /* adding candidate to treeview */
  for (i = first_candidate, j = 0;
       i < (last_candidate + 1); i++, j++)
    {
      gchar *candidate_u8, *label_u8 = NULL, *result = NULL;
      /* get candidates from IIIMSF */
      candidate = NULL;
      label = NULL;
      flag = 0;
      st = iiimcf_get_lookup_choice_item (luc, i, &candidate, &label, &flag);
      if (st != IIIMF_STATUS_SUCCESS)
	break;
      if (label)
	label_u8 = utf16_to_utf8 (label);
      candidate_u8 = utf16_to_utf8 (candidate);
      DEBUG_DO (g_message ("candidate %s", candidate_u8));
      if (label_u8)
	{
	  result = g_strconcat (label_u8, " ", candidate_u8, NULL);
	}
      /* max columns */
      if (j == cw->number_of_columns)
	{
	  /* set next row */
	  gtk_list_store_insert_after (cw->store, &riter, &iter);
	  iter = riter;
	  j = 0;
	}
      gtk_list_store_set (cw->store, &iter, j,
			  result ? result : candidate_u8, -1);
      /* current candidate */
      if (i == current_candidate)
	{
	  GtkTreeSelection *selection =
	    gtk_tree_view_get_selection (GTK_TREE_VIEW (cw->tree));
	  gtk_tree_selection_select_iter (selection, &iter);
	}
      if (result)
	{
	  g_free (result);
	  g_free (label_u8);
	}
      g_free (candidate_u8);
    }
  gtk_widget_realize (w);
  iiim_candidate_move (context_iiim, w);
  gtk_widget_show_all (w);
}

static GtkListStore *
iiim_create_candidate_model (int number_of_columns)
{
  GtkListStore *ret;
  GType *types;
  int i;

  DEBUG_DO (g_message ("create_candidate_model"));
  types = g_new0 (GType, number_of_columns);
  for (i = 0; i < number_of_columns; i++)
    {
      types[i] = G_TYPE_STRING;
    }
  ret = gtk_list_store_newv (number_of_columns, types);
  g_free (types);
  return ret;
}

static void
iiim_candidate_window_button_press (GtkWidget *widget, GdkEventButton *event,
				    GtkIMContextIIIM *context_iiim)
{
  GtkTreeView *tree_view;
  GtkTreePath *path;
  GtkTreeIter iter;
  GtkTreeModel *model;
  GtkTreeSelection *selection;

  tree_view = GTK_TREE_VIEW (widget);

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
  gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
 				 &path, NULL, NULL, NULL);
  if (!path)  /* click event occured on no candidate */
    return;
  gtk_tree_selection_select_path (selection, path);

  if (gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      GdkEventKey *e;
      IIIMF_status st;
      IIIMCF_keyevent kev;
      IIIMCF_event ev;
      gchar *buffer = NULL;

      gtk_tree_model_get (model, &iter, 0, &buffer, -1);

      /* assemble the GdkEventKey */
      e = (GdkEventKey *) gdk_event_new (GDK_KEY_PRESS);
  
      e->time = gdk_event_get_time ((GdkEvent *)event);
      gdk_event_get_state ((GdkEvent *)event, &e->state);

      /* buffer[0] always holds a candidate index character. */
      e->keyval = buffer[0];

      g_free (buffer);
    
      st = convert_GdkEventKey_to_IIIMCF_keyevent (e, &kev);
      gdk_event_free ((GdkEvent *)e);
  
      if (st != IIIMF_STATUS_SUCCESS)
	return;

      st = iiimcf_create_keyevent (&kev, &ev);
      if (st != IIIMF_STATUS_SUCCESS)
	return;

      /* Send Message to IIIMSF */
      if (forward_event (context_iiim, ev, &st))
	iiim_event_dispatch (context_iiim);
    }
  return;
}

static GtkWidget *
iiim_create_candidate_window (GtkIMContextIIIM * context_iiim)
{
  CandidateWindow *w;
  GtkListStore *store;
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  IIIMCF_lookup_choice luc = iiim_get_lookup_choice (context_iiim);
  IIIMF_status st;
  IIIMCF_text title;
  GdkWindow *toplevel_gdk;
  GtkWidget *toplevel;
  gpointer ptoplevel;
  gchar *title_u8 = NULL;
  int choices_per_window;
  int number_of_rows;
  int number_of_columns;
  int direction;
  int i;

#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
  GdkScreen *screen;
  GdkWindow *root_window;
#endif

  if (!context_iiim->client_window)
    return NULL;

  toplevel_gdk = context_iiim->client_window;

#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
  screen = gdk_drawable_get_screen (toplevel_gdk);
  root_window = gdk_screen_get_root_window (screen);
#endif

  while (TRUE)
    {
      GdkWindow *parent = gdk_window_get_parent (toplevel_gdk);
#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
      if (parent == root_window)
#else
      if (parent == gdk_get_default_root_window ())
#endif
	break;
      else
	toplevel_gdk = parent;
    }

  gdk_window_get_user_data (toplevel_gdk, &ptoplevel);
  toplevel = GTK_WIDGET(ptoplevel);
  if (!toplevel)
    return NULL;

  DEBUG_DO (g_message ("create candidate window"));

  if (!luc)
    {
      DEBUG_DO (g_message ("lookup choice not found"));
      return NULL;
    }

  choices_per_window = 0;
  number_of_rows = 0;
  number_of_columns = 0;
  direction = IIIMCF_LOOKUP_CHOICE_HORIZONTAL_DIRECTION; /* default */
  st = iiimcf_get_lookup_choice_configuration (luc,
					       &choices_per_window,
					       &number_of_rows,
					       &number_of_columns,
					       &direction);
  if (st != IIIMF_STATUS_SUCCESS)
    {
      DEBUG_DO (g_message ("config failed"));
      return NULL;
    }

  title = NULL;
  st = iiimcf_get_lookup_choice_title (luc, &title);
  if (st != IIIMF_STATUS_SUCCESS)
    {
      DEBUG_DO (g_message ("Failed to get lookup choice title"));
      return NULL;
    }

  if (title)
    title_u8 = utf16_to_utf8 (title);

  if ((number_of_columns < 0) || (number_of_rows < 0))
    {
      DEBUG_DO (g_message
		("column %d %d", number_of_columns, number_of_rows));
      return NULL;
    }
  store = iiim_create_candidate_model (number_of_columns);
  if (!store)
    {
      DEBUG_DO (g_message ("create model failed"));
      return NULL;
    }
  w = g_new0 (CandidateWindow, 1);
  w->toplevel = gtk_window_new (GTK_WINDOW_POPUP);
  gtk_container_set_border_width (GTK_CONTAINER (w->toplevel), 2);

  w->frame = gtk_frame_new (title_u8);
  gtk_frame_set_shadow_type (GTK_FRAME (w->frame), GTK_SHADOW_ETCHED_OUT);

  w->tree = gtk_tree_view_new ();
  gtk_tree_view_set_model (GTK_TREE_VIEW (w->tree), GTK_TREE_MODEL (store));
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (w->tree), FALSE);
  gtk_tree_view_columns_autosize (GTK_TREE_VIEW (w->tree));

  for (i = 0; i < number_of_columns; i++)
    {
      renderer = gtk_cell_renderer_text_new ();
      column = gtk_tree_view_column_new_with_attributes ("",
							 renderer, "text", i,
							 NULL);
      gtk_tree_view_column_set_resizable (column, TRUE);
      gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
      gtk_tree_view_append_column (GTK_TREE_VIEW (w->tree), column);
    }
  gtk_tree_selection_set_mode (gtk_tree_view_get_selection
			       (GTK_TREE_VIEW (w->tree)),
			       GTK_SELECTION_SINGLE);
  w->store = store;
  w->app_toplevel = toplevel;
  w->choices_per_window = choices_per_window;
  w->number_of_rows = number_of_rows;
  w->number_of_columns = number_of_columns;
  w->direction = direction;
  
  w->button_press_handler_id = g_signal_connect (G_OBJECT (w->tree),
						 "button-press-event",
						 G_CALLBACK (iiim_candidate_window_button_press),
						 context_iiim);

  gtk_container_add (GTK_CONTAINER (w->toplevel), w->frame);
  gtk_container_add (GTK_CONTAINER (w->frame), w->tree);

  g_object_set_data (G_OBJECT (w->toplevel), "iiim-candidate-win",
		     (gpointer) w);
  DEBUG_DO (g_message ("create_candidate_window"));

  if (title_u8)
    g_free (title_u8);


  w->configure_handler_id = g_signal_connect (toplevel, "configure_event",
					      G_CALLBACK
					      (iiim_candidate_window_configure),
					      context_iiim);
  w->destroy_handler_id =
    g_signal_connect_swapped (toplevel, "destroy",
			      G_CALLBACK (iiim_destroy_candidate_window),
			      context_iiim);
  return w->toplevel;
}

static void
iiim_destroy_candidate_window (GtkIMContextIIIM * context_iiim)
{
  GtkWidget *w = context_iiim->candidate;
  CandidateWindow *cw;
  if (!w)
    return;
  cw = gtk_object_get_data (GTK_OBJECT (w), "iiim-candidate-win");
  g_signal_handler_disconnect (cw->app_toplevel, cw->destroy_handler_id);
  g_signal_handler_disconnect (cw->app_toplevel, cw->configure_handler_id);
  g_signal_handler_disconnect (cw->tree, cw->button_press_handler_id);

  gtk_widget_destroy (cw->toplevel);
  g_free (cw);
  context_iiim->candidate = NULL;
  return;
}

static gboolean
iiim_candidate_window_configure (GtkWidget * toplevel,
				 GdkEventConfigure * event,
				 GtkIMContextIIIM * context_iiim)
{
  GtkWidget *candwin = iiim_get_candidate_window (context_iiim);

  if (!candwin)
    return FALSE;

  iiim_candidate_move (context_iiim, candwin);
  return FALSE;
}

static GtkWidget *
iiim_get_candidate_window (GtkIMContextIIIM * context_iiim)
{
  GtkWidget *candidate = NULL;

  if (!context_iiim->candidate_start)
    {
      DEBUG_DO (g_message ("candidate not started yet"));
      return NULL;
    }
  if (context_iiim->candidate)
    {
      return context_iiim->candidate;
    }
  else
    {
      candidate = iiim_create_candidate_window (context_iiim);
      DEBUG_DO (g_message ("candidate %p", candidate));
      context_iiim->candidate = candidate;
    }
  return candidate;
}

static IIIMCF_lookup_choice
iiim_get_lookup_choice (GtkIMContextIIIM * context_iiim)
{
  IIIMCF_context context = iiim_get_session_context (context_iiim);
  IIIMCF_lookup_choice luc;
  IIIMF_status st;

  if (context && context_iiim->candidate_start)
    {
      if (context_iiim->lookup_choice)
	return context_iiim->lookup_choice;
      luc = NULL;
      st = iiimcf_get_lookup_choice (context, &luc);
      if (st != IIIMF_STATUS_SUCCESS)
	return NULL;
      context_iiim->lookup_choice = luc;
      return context_iiim->lookup_choice;
    }

  return NULL;
}

static IIIMCF_language
get_input_language (GtkIMContextIIIM *context_iiim, gchar *input_language,
		    gboolean exact_match)
{
  int i;
  GtkIIIMInfo *info = context_iiim->iiim_info;
  IIIMF_status st;
  char *langid, *lang, *p;
  int n_lang;
  IIIMCF_language *lang_list;

  if (input_language == NULL)
    return NULL;
  else
    lang = g_strdup(input_language);

  lang_list = iiim_get_languages (info, &n_lang);
  if (lang_list)
    {
      for (i = 0; i < n_lang; i++)
	{
	  st = iiimcf_get_language_id(lang_list[i], (const char **)&langid);
	  if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	  if (!strcmp(lang, langid))
	    goto found_language;
	}
      /* try to find the supported language again without charset locale */
      p = strchr(lang, '@');
      if (p)
	*p = 0;
      p = strchr(lang, '.');
      if (p)
	*p = 0;

      for (i = 0; i < n_lang; i++)
	{
	  st = iiimcf_get_language_id(lang_list[i], (const char **)&langid);
	  if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	  if (!strcmp(lang, langid))
	    goto found_language;
	}
      /* finally try to take care of the locale, such as en */
      p = strchr(lang, '_');
      if (p)
	*p = 0;

      for (i = 0; i < n_lang; i++)
	{
	  st = iiimcf_get_language_id(lang_list[i], (const char **)&langid);
	  if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	  if (!strcmp(lang, langid))
	    goto found_language;
	}
      g_free(lang);

      /*
	still cannot find a match. Just take one for "en" as
	a default.
      */
      for (i = 0; i < n_lang; i++)
	{
	  st = iiimcf_get_language_id (lang_list[i],
				       (const char **)&langid);
	  if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	  if (!strncmp (langid, "en", 2))
	    goto found_language;
	}
    }

  return NULL;

 found_language:;
  g_free(context_iiim->current_language);
  context_iiim->current_language = g_strdup(langid);

  return lang_list[i];
}

#ifdef DEBUG
static void
next_input_language (GtkIMContextIIIM *context_iiim)
{
  int i;
  GtkIIIMInfo *info = context_iiim->iiim_info;
  IIIMF_status st;
  char *langid;
  int n_lang;
  IIIMCF_language *lang_list;

  lang_list = iiim_get_languages (info, &n_lang);
  if (lang_list)
    {
      for (i = 0; i < n_lang; i++)
	{
	  st = iiimcf_get_language_id (lang_list[i],
				       (const char **)&langid);
	  if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	  if (!strncmp (langid, context_iiim->current_language,
			strlen (langid)))
	    break;
	}
      if (i == n_lang - 1)
	i = 0;
      else
	i++;

      st = iiimcf_get_language_id (lang_list[i],
				   (const char **)&langid);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
      g_free (context_iiim->current_language);
      context_iiim->current_language = g_strdup (langid);
    }
  return;
}
static void
prev_input_language (GtkIMContextIIIM *context_iiim)
{
  int i;
  GtkIIIMInfo *info = context_iiim->iiim_info;
  IIIMF_status st;
  char *langid;
  int n_lang;
  IIIMCF_language *lang_list;

  lang_list = iiim_get_languages (info, &n_lang);
  if (lang_list)
    {
      for (i = n_lang - 1; i > 0; i--)
	{
	  st = iiimcf_get_language_id (lang_list[i],
				       (const char **)&langid);
	  if (st != IIIMF_STATUS_SUCCESS)
	    continue;
	  if (!strncmp (langid, context_iiim->current_language,
			strlen (langid)))
	    break;
	}
      if (i == 0)
	i = n_lang - 1;
      else
	i--;

      st = iiimcf_get_language_id (lang_list[i],
				   (const char **)&langid);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
      g_free (context_iiim->current_language);
      context_iiim->current_language = g_strdup (langid);
    }
  return;
}
#endif

static IIIMCF_context
iiim_get_session_context (GtkIMContextIIIM * context_iiim)
{
  IIIMF_status st;
  IIIMCF_attr attr;

  if (!context_iiim->iiim_info)
    {
      if (context_iiim->client_window)
	context_iiim->iiim_info = get_iiim (context_iiim->client_window);
      if (context_iiim->iiim_info)
	context_iiim->iiim_info->ics =
	  g_slist_prepend (context_iiim->iiim_info->ics, context_iiim);
    }
  if (!context_iiim->iiim_info)
    {
      DEBUG_DO (g_message ("iiim_info->iiim not found"));
      return NULL;
    }
  if (!context_iiim->context && context_iiim->iiim_info)
    {
      IIIMCF_language iiim_lang = NULL;

      iiim_lang = get_input_language (context_iiim,
				      context_iiim->iiim_info->locale,
				      FALSE);
      st = iiimcf_create_attr (&attr);
      if (st != IIIMF_STATUS_SUCCESS)
	return NULL;

      if (iiim_lang)
	iiimcf_attr_put_ptr_value (attr, IIIMCF_ATTR_INPUT_LANGUAGE,
				   iiim_lang);
      st = iiimcf_create_context (context_iiim->iiim_info->iiim,
				  attr, &(context_iiim->context));
      iiimcf_destroy_attr (attr);
      if (st != IIIMF_STATUS_SUCCESS)
	return NULL;
    }

  DEBUG_DO (g_message ("%p", context_iiim->context));
  return context_iiim->context;
}

static void
im_context_iiim_init (GtkIMContextIIIM * im_context_iiim)
{
  im_context_iiim->context = NULL;
  im_context_iiim->candidate = NULL;
  im_context_iiim->keylist = NULL;
  im_context_iiim->candidate_start = FALSE;
  im_context_iiim->use_preedit = FALSE;
  im_context_iiim->finalizing = FALSE;
  im_context_iiim->has_focus = FALSE;
  im_context_iiim->in_toplevel = FALSE;

  bindtextdomain (GETTEXT_PACKAGE, IIIMGCFLOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
}

static gchar *
utf16_to_utf8 (IIIMCF_text text)
{
  IIIMF_status st;
  IIIMP_card16 *u16str = NULL;
  st = iiimcf_get_text_utf16string (text, (const IIIMP_card16 **) &u16str);
  return (u16str != NULL ?
	  g_utf16_to_utf8 ((const gunichar2 *) u16str, -1, NULL, NULL, NULL) :
	  NULL);
}

static void
iiim_keylist_free (GtkIMContextIIIM * context_iiim)
{
  while (context_iiim->keylist != NULL)
    {
      GdkEventKey *ev = (GdkEventKey *)context_iiim->keylist->data;

      context_iiim->keylist = g_slist_remove (context_iiim->keylist, ev);
      gdk_event_free ((GdkEvent *) ev);
    }
}

static gint
check_stored_keyevent (GtkIMContext * context, GdkEventKey * event)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);
  GSList *tmp;
  for (tmp = context_iiim->keylist; tmp; tmp = g_slist_next (tmp))
    {
      GdkEventKey *ev = (GdkEventKey *) tmp->data;
      if (ev->time == event->time && ev->keyval == event->keyval)
	{
	  /* found */
	  guint32 unicode;
	  context_iiim->keylist = g_slist_remove (context_iiim->keylist, ev);
	  unicode = gdk_keyval_to_unicode (event->keyval);
	  DEBUG_DO (g_message
		    ("keyevent found %s %d %d", gdk_keyval_name (ev->keyval),
		     g_unichar_isprint (unicode), unicode));
	  if (g_unichar_isprint (unicode)
	      && (event->state == 0 || event->state == GDK_SHIFT_MASK))
	    {
	      gchar utf8[7];
	      gint len;
	      len = g_unichar_to_utf8 (unicode, utf8);
	      utf8[len] = 0;
	      g_signal_emit_by_name (context, "commit", utf8);
	      gdk_event_free ((GdkEvent *) ev);
	      return 1;
	    }
	  gdk_event_free ((GdkEvent *) ev);
	  return 0;
	}
    }
  return -1;
}

static void
set_error_message (GtkIMContextIIIM * context_iiim)
{
#ifdef DEBUG
  /* set error messages to status window */
  char *error_message = _("Can't communicate with IIIM server");

  if (!context_iiim->status_window)
    {
      if (!context_iiim->has_focus)
	{
	  context_iiim->has_focus = TRUE;
	  /* tell switcher that I'm the current client */
	  im_context_switcher_set_focus (context_iiim);
	}
      update_status_window (context_iiim);
    }
  status_window_set_text (context_iiim->status_window, error_message);
  im_context_switcher_set_status_text (context_iiim, error_message);
#endif
}

static gboolean
forward_event (GtkIMContextIIIM * context_iiim, IIIMCF_event ev,
	       IIIMF_status *st_ret)
{
  IIIMCF_context c;
  IIIMF_status st;

  c = iiim_get_session_context (context_iiim);
  if (!c)
    {
      set_error_message (context_iiim);
      if (st_ret)
	*st_ret = IIIMF_STATUS_EVENT_NOT_FORWARDED;
      return FALSE;
    }
  /* Send Message to IIIMSF */
  st = iiimcf_forward_event (c, ev);
  if (st_ret)
    *st_ret = st;

  DEBUG_DO (g_message ("iiimcf_forward_event %d", st));
  switch (st)
    {
    case IIIMF_STATUS_SUCCESS:
      break;
    case IIIMF_STATUS_IC_INVALID:
    case IIIMF_STATUS_EVENT_NOT_FORWARDED:
      break;
    case IIIMF_STATUS_STREAM_SEND:
    case IIIMF_STATUS_STREAM_RECEIVE:
    case IIIMF_STATUS_CONNECTION_CLOSED:
      set_error_message (context_iiim);
      break;
    default:
      status_window_set_text (context_iiim->status_window, "");
      break;
    }
  return (st == IIIMF_STATUS_SUCCESS);
}

static gboolean
im_context_iiim_filter_keypress (GtkIMContext * context, GdkEventKey * event)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);
  IIIMF_status st;
  IIIMCF_keyevent kev;
  IIIMCF_event ev;
  gint store_key = 0;

  /* IIIMF doesn't recognize */
  if (event->type == GDK_KEY_RELEASE)
    return FALSE;

  if (current_setting_enabled &&
      current_setting.im_enabled == IM_OFF)
    goto commit_this_event;

  DEBUG_DO (g_message
	    ("event in %s %d %d %d", gdk_keyval_name (event->keyval),
	     event->hardware_keycode, event->length, event->group));

  store_key = check_stored_keyevent (context, event);
  if (!store_key)
    return FALSE;
  else if (store_key > 0)
    return TRUE;

#ifdef DEBUG
  if (event->state == GDK_CONTROL_MASK) {
    if (event->keyval == GDK_Down)
      {
	next_input_language (context_iiim);
	im_context_initialize_with_input_language (context_iiim, NULL);
	im_context_switcher_set_input_language (context_iiim, NULL);
	return FALSE;
      }
    else if (event->keyval == GDK_Up)
      {
	prev_input_language (context_iiim);
	im_context_initialize_with_input_language (context_iiim, NULL);
	im_context_switcher_set_input_language (context_iiim, NULL);
	return FALSE;
      }
  }
#endif

  st = convert_GdkEventKey_to_IIIMCF_keyevent (event, &kev);
  if (st != IIIMF_STATUS_SUCCESS)
    goto commit_this_event;

  st = iiimcf_create_keyevent (&kev, &ev);
  if (st != IIIMF_STATUS_SUCCESS)
    goto commit_this_event;

  /* Send Message to IIIMSF */
  if (forward_event (context_iiim, ev, &st))
    return iiim_event_dispatch (context_iiim);

  if (st != IIIMF_STATUS_EVENT_NOT_FORWARDED &&
      st != IIIMF_STATUS_IC_INVALID)
    return FALSE;

 commit_this_event:
  if (event->state &
      (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK))
    return FALSE;
  else
    {
      /* commit this event */
      guint32 unicode;
      unicode = gdk_keyval_to_unicode (event->keyval);
      if (g_unichar_isprint (unicode))
	{
	  gchar utf8[7];
	  gint len;
	  len = g_unichar_to_utf8 (unicode, utf8);
	  utf8[len] = 0;
	  g_signal_emit_by_name (context, "commit", utf8);
	  return TRUE;
	}
    }
  return FALSE;
}

static void
set_sc_client_window (GtkIMContextIIIM * context_iiim,
		      GdkWindow * client_window, gboolean send_signal)
{
  DEBUG_DO (g_message ("set_sc_client_window"));
  reinitialize_sc (context_iiim, TRUE);
  if (context_iiim->client_window)
    {
      DEBUG_DO (g_message ("set_sc_client_window 1"));
      if (context_iiim->iiim_info)
	context_iiim->iiim_info->ics =
	  g_slist_remove (context_iiim->iiim_info->ics, context_iiim);
      context_iiim->iiim_info = NULL;
    }
  context_iiim->client_window = client_window;

  if (context_iiim->client_window)
    {
      DEBUG_DO (g_message ("set_sc_client_window 2"));
      context_iiim->iiim_info =
	get_iiim (context_iiim->client_window);
      if (context_iiim->iiim_info)
	context_iiim->iiim_info->ics =
	  g_slist_prepend (context_iiim->iiim_info->ics, context_iiim);
    }

  update_client_widget (context_iiim);
}

static void
im_context_iiim_set_client_window (GtkIMContext * context,
				   GdkWindow * client_window)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);

  DEBUG_DO (g_message ("iiim_set_client_window"));
  set_sc_client_window (context_iiim, client_window, TRUE);
  if (!context_iiim->context)
    return;

  DEBUG_DO (g_message ("set client window"));
}

static void
im_context_iiim_finalize (GObject * obj)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (obj);

  context_iiim->finalizing = TRUE;

  IIim_aux_destrory_ic (context_iiim);

  set_sc_client_window (context_iiim, NULL, FALSE);

  if (context_iiim->candidate)
    iiim_destroy_candidate_window (context_iiim);

  im_context_switcher_finalize (context_iiim);

  g_free (context_iiim->current_language);

  iiim_keylist_free (context_iiim);
  DEBUG_DO (g_message ("im_context_iiim_finalize"));

}

static void
reinitialize_sc (GtkIMContextIIIM * context_iiim, gboolean send_signal)
{
  IIIMF_status st;
  if (context_iiim->context)
    {
      st = iiimcf_destroy_context (context_iiim->context);
      context_iiim->context = NULL;
      update_status_window (context_iiim);
      context_iiim->lookup_choice = NULL;
      g_free (context_iiim->current_language);
      context_iiim->current_language = NULL;
    }
}

static void
im_context_iiim_focus_in (GtkIMContext * context)
{
  IIIMF_status st;
  IIIMCF_event ev;
  IIIMCF_context c;
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);
  GtkWidget *cand_win = iiim_get_candidate_window (context_iiim);
  guint has_focus;

  DEBUG_DO (g_message ("im_context_iiim_focus_in"));

  get_im_settings_property ();

  has_focus = context_iiim->has_focus;

  if (!has_focus)
    {
      c = iiim_get_session_context (context_iiim);
      if (!c)
	return;

      context_iiim->has_focus = TRUE;
      update_status_window (context_iiim);
      if (context_iiim->status_window != NULL)
	{
	  /* enforce to update the status window position */
	  on_status_toplevel_configure(context_iiim->status_window->toplevel,
				       NULL,
				       context_iiim->status_window);
	}
      im_context_switcher_set_focus (context_iiim);
      /* tell switcher that I'm the current client */
      im_context_switcher_set_focus (context_iiim);
    }

  st = iiimcf_create_seticfocus_event (&ev);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  if (forward_event (context_iiim, ev, NULL))
    iiim_event_dispatch (context_iiim);

  if (cand_win && !GTK_WIDGET_VISIBLE (cand_win))
    {
      gtk_widget_show (cand_win);
    }

  if (!has_focus)
    {
      if (im_info_switcher_active (context_iiim->iiim_info))
	{
	  IIIMCF_language *lang_list;
	  int n_lang;

	  lang_list = iiim_get_languages (context_iiim->iiim_info, &n_lang);
	  im_context_switcher_set_language_list (context_iiim, lang_list,
						 n_lang);
	  im_context_switcher_set_language_engine_list (context_iiim,
							context_iiim->iiim_info->le_list);
	  im_context_switcher_set_input_language (context_iiim, NULL);
	}
    }
  return;
}

static void
im_context_iiim_focus_out (GtkIMContext * context)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);
  IIIMF_status st;
  IIIMCF_event ev;
  IIIMCF_context c;
  GtkWidget *cand_win = iiim_get_candidate_window (context_iiim);

  DEBUG_DO (g_message ("im_context_iiim_focus_out"));

  if (context_iiim->has_focus)
    {
      c = iiim_get_session_context (context_iiim);
      if (!c)
	return;

      context_iiim->has_focus = FALSE;
      update_status_window (context_iiim);
      IIim_aux_unset_icfocus (context_iiim);

      DEBUG_DO (g_message ("candidate_window %p", cand_win));
      if (cand_win && GTK_WIDGET_VISIBLE (cand_win))
	{
	  gtk_widget_hide (cand_win);
	}
    }

  st = iiimcf_create_unseticfocus_event (&ev);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  if (forward_event (context_iiim, ev, NULL))
    iiim_event_dispatch (context_iiim);
  return;
}

static void
im_context_iiim_set_cursor_location (GtkIMContext * context,
				     GdkRectangle * area)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);
  DEBUG_DO (g_message ("im_context_iiim_set_cursor_location"));
  if (!context_iiim)
    return;
  context_iiim->cursor.x = area->x;
  context_iiim->cursor.y = area->y;
  context_iiim->cursor.height = area->height;
  context_iiim->cursor.width = area->width;
  return;
}

static void
im_context_iiim_set_use_preedit (GtkIMContext * context, gboolean use_preedit)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context);
  DEBUG_DO (g_message ("im_context_iiim_set_use_preedit"));

  use_preedit = use_preedit != FALSE;

  if (context_iiim->use_preedit != use_preedit)
    {
      context_iiim->use_preedit = use_preedit;
      reinitialize_sc (context_iiim, TRUE);
    }
  return;
}

static void
im_context_iiim_reset (GtkIMContext * context)
{
  GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM(context);
  IIIMCF_context ctxt;
  IIIMF_status st;
  IIIMCF_text text;
  IIIMCF_event event;
  gint caret_position, preedit_len = 0;
  gchar *utf8;

  DEBUG_DO (g_message ("im_context_iiim_reset"));

  ctxt = iiim_get_session_context(context_iiim);
  if (!ctxt)
    return;

  /* commit the preedit strings first */
  st = iiimcf_get_preedit_text(ctxt, &text, &caret_position);
  if (st != IIIMF_STATUS_SUCCESS)
    return;
  st = iiimcf_get_text_length(text, &preedit_len);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  if (preedit_len == 0)
    return;

  utf8 = utf16_to_utf8(text);
  if (utf8 != NULL)
    {
      /* reset IC */
      st = iiimcf_reset_context(ctxt);
      if (st != IIIMF_STATUS_SUCCESS)
	return;

      /* FIXME: workaround to get it working after calling iiimcf_reset_context */
      st = iiimcf_create_trigger_notify_event(0, &event);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
      st = iiimcf_forward_event(ctxt, event);
      st = iiimcf_create_trigger_notify_event(1, &event);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
      st = iiimcf_forward_event(ctxt, event);

      /* emit 'commit' signal after resetting IC */
      g_signal_emit_by_name(context_iiim, "commit", utf8);
      g_free(utf8);

      if (preedit_len > 0)
	g_signal_emit_by_name(context_iiim, "preedit_changed");
    }
}

static void
add_feedback_attr (PangoAttrList * attrs,
		   const gchar * str,
		   const IIIMP_card32 feedback,
		   gint start_pos, gint end_pos)
{
  PangoAttribute *attr;
  gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
  gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;

  DEBUG_DO (g_message ("feedback %d", feedback));

  if (feedback == 2 /* Underline */ )
    {
      attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
      attr->start_index = start_index;
      attr->end_index = end_index;

      pango_attr_list_change (attrs, attr);
    }

  if (feedback == 1 /* Reverse */ )
    {
      attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
      attr->start_index = start_index;
      attr->end_index = end_index;

      pango_attr_list_change (attrs, attr);

      attr = pango_attr_background_new (0, 0, 0);
      attr->start_index = start_index;
      attr->end_index = end_index;

      pango_attr_list_change (attrs, attr);
    }
}

static gboolean
iiim_check_feedback (const IIIMP_card32 * feedback, gint nfb,
		     const IIIMP_card32 * feedback2, gint nfb2)
{
  gint i;
  gboolean result = TRUE;
  if (!feedback)
    return FALSE;
  if (nfb != nfb2)
    return FALSE;
  for (i = 0; i < nfb; i++)
    {
      if (feedback[i] != feedback2[i])
	result = FALSE;
    }
  return result;
}

static void
im_context_iiim_get_preedit_string (GtkIMContext * context,
				    gchar ** str,
				    PangoAttrList ** attrs, gint * cursor_pos)
{
  GtkIMContextIIIM *im_context_iiim = GTK_IM_CONTEXT_IIIM (context);
  IIIMF_status st;
  IIIMCF_text text;
  gint caret_position;
  gint preedit_len;
  gchar *utf8;

  if (attrs)
    *attrs = pango_attr_list_new ();

  if (!im_context_iiim->context)
    {
      DEBUG_DO (g_message ("preedit_string context is not initialized"));
      goto Error;
    }
  st =
    iiimcf_get_preedit_text (im_context_iiim->context, &text,
			     &caret_position);
  if (st != IIIMF_STATUS_SUCCESS)
    goto Error;
  utf8 = utf16_to_utf8 (text);

  if (attrs)
    {
      gint i;
      gint j;
      IIIMP_card32 last_visual_feedback = 0;
      gint start = -1;

      st = iiimcf_get_text_length (text, &preedit_len);
      if (st != IIIMF_STATUS_SUCCESS)
	goto Error;

      for (i = 0; i < preedit_len; i++)
	{
	  IIIMP_card16 ch;
	  const IIIMP_card32 *feedback_ids, *feedbacks;
	  int nfb;

	  st =
	    iiimcf_get_char_with_feedback (text, i, &ch, &nfb, &feedback_ids,
					   &feedbacks);
	  if (st != IIIMF_STATUS_SUCCESS)
	    goto Error;
	  for (j = 0; j < nfb; j++)
	    {
	      IIIMP_card32 new_visual_feedback;

	      switch (feedback_ids[j])
		{
		case 0:		/* VISUAL ATTRIBUTES */
		  new_visual_feedback = feedbacks[j];
		  if (new_visual_feedback != last_visual_feedback)
		    {
		      if (start >= 0)
			add_feedback_attr (*attrs, utf8, last_visual_feedback,
					   start, i);
		      last_visual_feedback = new_visual_feedback;
		      start = i;
		    }
		default:
		  break;
		}
	    }
	}
      if (start >= 0)
	add_feedback_attr (*attrs, utf8, last_visual_feedback, start, i);
    }

  if (str)
    *str = utf8;
  else
    g_free (utf8);

  DEBUG_DO (g_message ("cursor %d", caret_position));
  if (cursor_pos)
    *cursor_pos = caret_position;
  return;

Error:
  if (str)
    {
      *str = g_strdup ("");
    }
  if (cursor_pos)
    {
      *cursor_pos = 0;
    }

}

GtkIMContext *
im_context_iiim_new (void)
{
  GtkIMContextIIIM *result;

  result =
    GTK_IM_CONTEXT_IIIM (g_object_new (GTK_TYPE_IM_CONTEXT_IIIM, NULL));

  return GTK_IM_CONTEXT (result);
}

/*****************************************************************
 * Status Window handling
 *
 * A status window is a small window attached to the toplevel
 * that is used to display information to the user about the
 * current input operation.
 *
 * We claim the toplevel's status window for an input context if:
 *
 * A) The input context has a toplevel
 * B) The input context has the focus
 * C) The input context has an XIC associated with it
 *
 * Tracking A) and C) is pretty reliable since we
 * compute A) and create the XIC for C) ourselves.
 * For B) we basically have to depend on our callers
 * calling ::focus-in and ::focus-out at the right time.
 *
 * The toplevel is computed by walking up the GdkWindow
 * hierarchy from context->client_window until we find a
 * window that is owned by some widget, and then calling
 * gtk_widget_get_toplevel() on that widget. This should
 * handle both cases where we might have GdkWindows without widgets,
 * and cases where GtkWidgets have strange window hierarchies
 * (like a torn off GtkHandleBox.)
 *
 * The status window is visible if and only if there is text
 * for it; whenever a new GtkIMContextIIIM claims the status
 * window, we blank out any existing text. We actually only
 * create a GtkWindow for the status window the first time
 * it is shown; this is an important optimization when we are
 * using IIIM with something like a simple compose-key input
 * method that never needs a status window.
 *****************************************************************/

/* Called when we no longer need a status window
*/
static void
disclaim_status_window (GtkIMContextIIIM *context_iiim)
{
  if (context_iiim->status_window)
    {
      g_assert (context_iiim->status_window->context == context_iiim);

      status_window_set_text (context_iiim->status_window, "");
      
      context_iiim->status_window->context = NULL;
      context_iiim->status_window = NULL;
    }
}

/* Called when we need a status window
 */
static void
claim_status_window (GtkIMContextIIIM *context_iiim)
{
  if (!context_iiim->status_window && context_iiim->client_widget)
    {
      GtkWidget *toplevel = gtk_widget_get_toplevel (context_iiim->client_widget);
      if (toplevel && GTK_WIDGET_TOPLEVEL (toplevel))
	{
	  StatusWindow *status_window = status_window_get (toplevel);

	  if (status_window->context)
	    disclaim_status_window (status_window->context);

	  status_window->context = context_iiim;
	  context_iiim->status_window = status_window;
	}
    }
}

/* Basic call made whenever something changed that might cause
 * us to need, or not to need a status window.
 */
static void
update_status_window (GtkIMContextIIIM *context_iiim)
{
  if (context_iiim->in_toplevel && context_iiim->has_focus)
    claim_status_window (context_iiim);
  else
    disclaim_status_window (context_iiim);
}

/* Updates the in_toplevel flag for @context_iiim
 */
static void
update_in_toplevel (GtkIMContextIIIM *context_iiim)
{
  if (context_iiim->client_widget)
    {
      GtkWidget *toplevel = gtk_widget_get_toplevel (context_iiim->client_widget);
      
      context_iiim->in_toplevel = (toplevel && GTK_WIDGET_TOPLEVEL (toplevel));
    }
  else
    context_iiim->in_toplevel = FALSE;

  /* Some paranoia, in case we don't get a focus out */
  if (!context_iiim->in_toplevel)
    context_iiim->has_focus = FALSE;
  
  update_status_window (context_iiim);
}

/* Callback when @widget's toplevel changes. It will always
 * change from NULL to a window, or a window to NULL;
 * we use that intermediate NULL state to make sure
 * that we disclaim the toplevel status window for the old
 * window.
 */
static void
on_client_widget_hierarchy_changed (GtkWidget       *widget,
				    GtkWidget       *old_toplevel,
				    GtkIMContextIIIM *context_iiim)
{
  update_in_toplevel (context_iiim);
}

/* Finds the GtkWidget that owns the window, or if none, the
 * widget owning the nearest parent that has a widget.
 */
static GtkWidget *
widget_for_window (GdkWindow *window)
{
  while (window)
    {
      gpointer user_data;
      gdk_window_get_user_data (window, &user_data);
      if (user_data)
	return user_data;

      window = gdk_window_get_parent (window);
    }

  return NULL;
}

/* Called when context_iiim->client_window changes; takes care of
 * removing and/or setting up our watches for the toplevel
 */
static void
update_client_widget (GtkIMContextIIIM *context_iiim)
{
  GtkWidget *new_client_widget = widget_for_window (context_iiim->client_window);

  if (new_client_widget != context_iiim->client_widget)
    {
      if (context_iiim->client_widget)
	{
	  g_signal_handlers_disconnect_by_func (context_iiim->client_widget,
						G_CALLBACK (on_client_widget_hierarchy_changed),
						context_iiim);
	}
      context_iiim->client_widget = new_client_widget;
      if (context_iiim->client_widget)
	{
	  g_signal_connect (context_iiim->client_widget, "hierarchy-changed",
			    G_CALLBACK (on_client_widget_hierarchy_changed),
			    context_iiim);
	}

      update_in_toplevel (context_iiim);
    }
}

/* Called when the toplevel is destroyed; frees the status window
 */
static void
on_status_toplevel_destroy (GtkWidget    *toplevel,
			    StatusWindow *status_window)
{
  status_window_free (status_window);
}

/* Called when the screen for the toplevel changes; updates the
 * screen for the status window to match.
 */
static void
on_status_toplevel_notify_screen (GtkWindow    *toplevel,
				  GParamSpec   *pspec,
				  StatusWindow *status_window)
{
  if (status_window->window)
    gtk_window_set_screen (GTK_WINDOW (status_window->window),
			   gtk_widget_get_screen (GTK_WIDGET (toplevel)));
}

/* Called when the toplevel window is moved; updates the position of
 * the status window to follow it.
 */
static gboolean
on_status_toplevel_configure (GtkWidget         *toplevel,
			      GdkEventConfigure *event,
			      StatusWindow      *status_window)
{
  GdkRectangle rect;
  GtkRequisition requisition;
  gint y;
  gint height;

  if (status_window->window)
    {
      height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
  
      gdk_window_get_frame_extents (toplevel->window, &rect);
      gtk_widget_size_request (status_window->window, &requisition);
      
      if (rect.y + rect.height + requisition.height < height)
	y = rect.y + rect.height;
      else
	y = height - requisition.height;
      
      gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
    }

  return FALSE;
}

/* Frees a status window and removes its link from the status_windows list
 */
static void
status_window_free (StatusWindow *status_window)
{
  status_windows = g_slist_remove (status_windows, status_window);

  if (status_window->context)
    status_window->context->status_window = NULL;
 
  g_signal_handlers_disconnect_by_func (status_window->toplevel,
					G_CALLBACK (on_status_toplevel_destroy),
					status_window);
  g_signal_handlers_disconnect_by_func (status_window->toplevel,
					G_CALLBACK (on_status_toplevel_notify_screen),
					status_window);
  g_signal_handlers_disconnect_by_func (status_window->toplevel,
					G_CALLBACK (on_status_toplevel_configure),
					status_window);

  if (status_window->window)
    gtk_widget_destroy (status_window->window);
  
  g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-iiim-status-window", NULL);
 
  g_free (status_window);
}

/* Finds the status window object for a toplevel, creating it if necessary.
 */
static StatusWindow *
status_window_get (GtkWidget *toplevel)
{
  StatusWindow *status_window;

  status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-iiim-status-window");
  if (status_window)
    return status_window;
  
  status_window = g_new0 (StatusWindow, 1);
  status_window->toplevel = toplevel;

  status_windows = g_slist_prepend (status_windows, status_window);

  g_signal_connect (toplevel, "destroy",
		    G_CALLBACK (on_status_toplevel_destroy),
		    status_window);
  g_signal_connect (toplevel, "configure_event",
		    G_CALLBACK (on_status_toplevel_configure),
		    status_window);
  g_signal_connect (toplevel, "notify::screen",
		    G_CALLBACK (on_status_toplevel_notify_screen),
		    status_window);
  
  g_object_set_data (G_OBJECT (toplevel), "gtk-im-iiim-status-window", status_window);

  return status_window;
}

/* Draw the background (normally white) and border for the status window
 */
static gboolean
on_status_window_expose_event (GtkWidget      *widget,
			       GdkEventExpose *event)
{
  gdk_draw_rectangle (widget->window,
		      widget->style->base_gc [GTK_STATE_NORMAL],
		      TRUE,
		      0, 0,
		      widget->allocation.width, widget->allocation.height);
  gdk_draw_rectangle (widget->window,
		      widget->style->text_gc [GTK_STATE_NORMAL],
		      FALSE,
		      0, 0,
		      widget->allocation.width - 1, widget->allocation.height - 1);

  return FALSE;
}

/* We watch the ::style-set signal for our label widget
 * and use that to change it's foreground color to match
 * the 'text' color of the toplevel window. The text/base
 * pair of colors might be reversed from the fg/bg pair
 * that are normally used for labels.
 */
static void
on_status_window_style_set (GtkWidget *toplevel,
			    GtkStyle  *previous_style,
			    GtkWidget *label)
{
  gint i;
  
  for (i = 0; i < 5; i++)
    gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
}

/* Creates the widgets for the status window; called when we
 * first need to show text for the status window.
 */
static void
status_window_make_window (StatusWindow *status_window)
{
  GtkWidget *window;
  GtkWidget *status_label;
  
  status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
  window = status_window->window;

  gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
  gtk_widget_set_app_paintable (window, TRUE);

  status_label = gtk_label_new ("");
  gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
  gtk_widget_show (status_label);
  
  g_signal_connect (window, "style_set",
		    G_CALLBACK (on_status_window_style_set), status_label);
  gtk_container_add (GTK_CONTAINER (window), status_label);
  
  g_signal_connect (window, "expose_event",
		    G_CALLBACK (on_status_window_expose_event), NULL);
  
  gtk_window_set_screen (GTK_WINDOW (status_window->window),
			 gtk_widget_get_screen (status_window->toplevel));

  on_status_toplevel_configure (status_window->toplevel, NULL, status_window);
}

/* Updates the text in the status window, hiding or
 * showing the window as necessary.
 */
static void
status_window_set_text (StatusWindow *status_window,
			const gchar  *text)
{
  if (status_window == NULL)
    /* status window is disclaimed */
    return;

  if (text[0])
    {
      GtkWidget *label;
      
      if (!status_window->window)
	status_window_make_window (status_window);
      
      label = GTK_BIN (status_window->window)->child;
      gtk_label_set_text (GTK_LABEL (label), text);
  
      gtk_widget_show (status_window->window);
    }
  else
    {
      if (status_window->window)
	gtk_widget_hide (status_window->window);
    }
}

/**
 * im_context_iiim_shutdown:
 *
 * Destroys all the status windows that are kept by the IIIM contexts. This
 * function should only be called by the IIIM module exit routine.
 **/
void
im_context_iiim_shutdown (void)
{
  GSList *tmp_list;
  GtkIIIMInfo *info = NULL;

  DEBUG_DO (g_message ("shutdown\n"));
  if (iiim)
    iiimcf_destroy_handle (iiim);
  iiimcf_finalize ();
  iiim_is_initialized = FALSE;
  while (status_windows)
    status_window_free (status_windows->data);

  tmp_list = open_iiims;
  while (tmp_list)
    {
      info = tmp_list->data;
      g_signal_handler_disconnect (info->settings, info->status_set);
      im_info_switcher_shutdown (info);
      tmp_list = tmp_list->next;
    }
}

void
im_context_initialize_with_input_language (GtkIMContextIIIM *context_iiim,
					   gchar *new_lang)

{
  IIIMCF_language iiim_lang = NULL;
  IIIMF_status st;
  IIIMCF_attr attr;
  IIIMCF_event ev;
  gboolean conversion_mode = FALSE;
  gchar **names;
  gchar *le_name = NULL;
  gint i = 0, n;
  const IIIMP_card16 *u16idname, *u16hrn, *u16domain;
  gboolean found_le = FALSE;
  IIIMCF_input_method *pims;

  if (context_iiim == NULL || context_iiim->context == NULL)
    return;

  if (new_lang)
    {
      names = g_strsplit(new_lang, ":", -1);
      new_lang = names[0];
      le_name = names[1];

      iiim_lang = get_input_language (context_iiim,
				      new_lang,
				      TRUE);
      if (iiim_lang != NULL)
	{
	  g_free (context_iiim->current_language);
	  context_iiim->current_language = g_strdup (new_lang);
	}
      else
	/* invalid new lang */
	return;
    }
  else
    iiim_lang = get_input_language (context_iiim,
				    context_iiim->current_language,
				    FALSE);

  if (le_name != NULL)
    {
      char *idname;
      st = iiimcf_get_supported_input_methods (iiim, &n, &pims);
      if (st != IIIMF_STATUS_SUCCESS)
	return;

      for (i = 0; i < n; i++)
	{
	  st = iiimcf_get_input_method_desc (pims[i], &u16idname, &u16hrn, &u16domain);
	  if (st != IIIMF_STATUS_SUCCESS)
	    return;

	  idname = format_iiimcf_string (u16hrn);
          if (strcmp (le_name, idname) == 0)
	    {
	      found_le = TRUE;
	      break;
	    }
	}
    }
  /* save conversion mode to restore this after switching */
  st = iiimcf_get_current_conversion_mode (context_iiim->context,
					   &conversion_mode);

  /* to cancel any remaining preedit text */
  if (!context_iiim->finalizing)
    g_signal_emit_by_name (context_iiim, "preedit_changed");

  /* clear candidate if any */
  if (context_iiim->candidate_start == TRUE)
    {
      iiim_destroy_candidate_window (context_iiim);
      context_iiim->candidate_start = FALSE;
    }

  context_iiim->lookup_choice = NULL;

  st = iiimcf_create_attr (&attr);
  if (st != IIIMF_STATUS_SUCCESS)
    return;
  if (found_le)
      iiimcf_attr_put_ptr_value (attr, IIIMCF_ATTR_INPUT_METHOD,
				 pims[i]);
  if (iiim_lang)
    iiimcf_attr_put_ptr_value (attr, IIIMCF_ATTR_INPUT_LANGUAGE,
			       iiim_lang);
  st = iiimcf_context_set_attr(context_iiim->context, attr);
  iiimcf_destroy_attr (attr);
  if (st != IIIMF_STATUS_SUCCESS)
    return;

  st = iiimcf_create_seticfocus_event (&ev);

  if (st != IIIMF_STATUS_SUCCESS)
    return;

  forward_event(context_iiim, ev, NULL);
  if (conversion_mode)
    {
      st = iiimcf_create_trigger_notify_event (CONV_ON, &ev);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
      forward_event(context_iiim, ev, NULL);
    }
  iiim_event_dispatch (context_iiim);
}

void
im_context_change_conversion_mode (GtkIMContextIIIM *context_iiim,
				   gchar *conv_mode)
{
  IIIMCF_event event;

  if (conv_mode && !strcmp ((gchar*)conv_mode, "on"))
    {
      IIIMF_status st;
      st = iiimcf_create_trigger_notify_event (CONV_ON, &event);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
    }
  else if (conv_mode && !strcmp ((gchar*)conv_mode, "off"))
    {
      IIIMF_status st;
      st = iiimcf_create_trigger_notify_event (CONV_OFF, &event);
      if (st != IIIMF_STATUS_SUCCESS)
	return;
    }
  if (forward_event(context_iiim, event, NULL))
    iiim_event_dispatch (context_iiim);
}

GdkScreen*
im_info_get_screen (GtkIIIMInfo *info)
{
  if (info == NULL)
    return NULL;
  return info->screen;
}

IIIMCF_handle
im_info_get_handle (GtkIIIMInfo *info)
{
  if (info == NULL)
    return NULL;
  return info->iiim;
}

SwitcherInfo*
im_info_get_switcher_info (GtkIIIMInfo *info)
{
  if (info == NULL)
    return NULL;
  return info->switcher_info;
}

void
im_info_set_switcher_info (GtkIIIMInfo *info, SwitcherInfo *sw_info)
{
  info->switcher_info = sw_info;
}

/* Aux */
void
im_context_aux_set_values (GtkIMContextIIIM *context_iiim,
			   IIIMCF_event ev)
{
  forward_event(context_iiim, ev, NULL);
  iiim_event_dispatch (context_iiim);
}

void
im_context_aux_get_values (GtkIMContextIIIM *context_iiim,
			   IIIMCF_event ev)
{
  forward_event(context_iiim, ev, NULL);
  iiim_event_dispatch (context_iiim);
}

/* Local Variables: */
/* c-file-style: "gnu" */
/* End: */
