gtkimcontextxim.c 50.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* GTK - The GIMP Toolkit
 * Copyright (C) 2000 Red Hat, Inc.
 *
 * 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.
 */

20
#include <config.h>
21 22
#include "locale.h"
#include <string.h>
23
#include <stdlib.h>
24

25
#include "gtk/gtkintl.h"
26
#include "gtk/gtklabel.h"
27
#include "gtk/gtksignal.h"
28
#include "gtk/gtkwindow.h"
29 30
#include "gtkimcontextxim.h"

31
typedef struct _StatusWindow StatusWindow;
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
typedef struct _GtkXIMInfo GtkXIMInfo;

struct _GtkIMContextXIM
{
  GtkIMContext object;

  GtkXIMInfo *im_info;

  gchar *locale;
  gchar *mb_charset;

  GdkWindow *client_window;
  GtkWidget *client_widget;

  /* The status window for this input context; we claim the
   * status window when we are focused and have created an XIC
   */
  StatusWindow *status_window;

  gint preedit_size;
  gint preedit_length;
  gunichar *preedit_chars;
  XIMFeedback *feedbacks;

  gint preedit_cursor;
  
  XIMCallback preedit_start_callback;
  XIMCallback preedit_done_callback;
  XIMCallback preedit_draw_callback;
  XIMCallback preedit_caret_callback;

  XIMCallback status_start_callback;
  XIMCallback status_done_callback;
  XIMCallback status_draw_callback;

67
  XIMCallback string_conversion_callback;
68

69 70 71 72 73 74 75 76
  XIC ic;

  guint filter_key_release : 1;
  guint use_preedit : 1;
  guint finalizing : 1;
  guint in_toplevel : 1;
  guint has_focus : 1;
};
77

78 79
struct _GtkXIMInfo
{
80
  GdkScreen *screen;
81 82
  XIM im;
  char *locale;
83 84
  XIMStyle preedit_style_setting;
  XIMStyle status_style_setting;
85
  XIMStyle style;
86 87 88 89
  GtkSettings *settings;
  gulong status_set;
  gulong preedit_set;
  XIMStyles *xim_styles;
90
  GSList *ics;
91 92

  guint reconnecting :1;
93
  guint supports_string_conversion;
94 95
};

96 97 98 99 100 101 102
/* 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;
103 104 105

  /* Currently focused GtkIMContextXIM for the toplevel, if any */
  GtkIMContextXIM *context;
106 107
};

108 109 110 111 112 113 114 115
static void     gtk_im_context_xim_class_init         (GtkIMContextXIMClass  *class);
static void     gtk_im_context_xim_init               (GtkIMContextXIM       *im_context_xim);
static void     gtk_im_context_xim_finalize           (GObject               *obj);
static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *context,
						       GdkWindow             *client_window);
static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
						       GdkEventKey           *key);
static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
116 117
static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
118
static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
119
						       GdkRectangle		*area);
120 121
static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
						       gboolean		      use_preedit);
122 123 124 125 126
static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
						       gchar                **str,
						       PangoAttrList        **attrs,
						       gint                  *cursor_pos);

127
static void reinitialize_ic      (GtkIMContextXIM *context_xim);
128
static void set_ic_client_window (GtkIMContextXIM *context_xim,
129
				  GdkWindow       *client_window);
130

131 132
static void setup_styles (GtkXIMInfo *info);

133 134 135 136 137 138 139
static void update_client_widget   (GtkIMContextXIM *context_xim);
static void update_status_window   (GtkIMContextXIM *context_xim);

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);
140

141 142 143 144
static void xim_destroy_callback   (XIM      xim,
				    XPointer client_data,
				    XPointer call_data);

145 146 147 148 149
static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
static GObjectClass *parent_class;

GType gtk_type_im_context_xim = 0;

150
GSList *open_ims = NULL;
151

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

155 156 157 158 159 160 161 162 163 164 165 166 167
void
gtk_im_context_xim_register_type (GTypeModule *type_module)
{
  static const GTypeInfo im_context_xim_info =
  {
    sizeof (GtkIMContextXIMClass),
    (GBaseInitFunc) NULL,
    (GBaseFinalizeFunc) NULL,
    (GClassInitFunc) gtk_im_context_xim_class_init,
    NULL,           /* class_finalize */    
    NULL,           /* class_data */
    sizeof (GtkIMContextXIM),
    0,
Manish Singh's avatar
Manish Singh committed
168
    (GInstanceInitFunc) gtk_im_context_xim_init,
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
  };

  gtk_type_im_context_xim = 
    g_type_module_register_type (type_module,
				 GTK_TYPE_IM_CONTEXT,
				 "GtkIMContextXIM",
				 &im_context_xim_info, 0);
}

#define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
		      XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
#define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
		      XIMStatusNothing | XIMStatusNone)
#define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
		      XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)

static XIMStyle 
choose_better_style (XIMStyle style1, XIMStyle style2) 
{
  XIMStyle s1, s2, u; 
  
  if (style1 == 0) return style2;
  if (style2 == 0) return style1;
  if ((style1 & (PREEDIT_MASK | STATUS_MASK))
    	== (style2 & (PREEDIT_MASK | STATUS_MASK)))
    return style1;

  s1 = style1 & PREEDIT_MASK;
  s2 = style2 & PREEDIT_MASK;
  u = s1 | s2;
  if (s1 != s2) {
    if (u & XIMPreeditCallbacks)
      return (s1 == XIMPreeditCallbacks) ? style1 : style2;
    else if (u & XIMPreeditPosition)
      return (s1 == XIMPreeditPosition) ? style1 :style2;
    else if (u & XIMPreeditArea)
      return (s1 == XIMPreeditArea) ? style1 : style2;
    else if (u & XIMPreeditNothing)
      return (s1 == XIMPreeditNothing) ? style1 : style2;
208 209
    else if (u & XIMPreeditNone)
      return (s1 == XIMPreeditNone) ? style1 : style2;
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
  } else {
    s1 = style1 & STATUS_MASK;
    s2 = style2 & STATUS_MASK;
    u = s1 | s2;
    if (u & XIMStatusCallbacks)
      return (s1 == XIMStatusCallbacks) ? style1 : style2;
    else if (u & XIMStatusArea)
      return (s1 == XIMStatusArea) ? style1 : style2;
    else if (u & XIMStatusNothing)
      return (s1 == XIMStatusNothing) ? style1 : style2;
    else if (u & XIMStatusNone)
      return (s1 == XIMStatusNone) ? style1 : style2;
  }
  return 0; /* Get rid of stupid warning */
}

226 227 228 229 230 231
static void
reinitialize_all_ics (GtkXIMInfo *info)
{
  GSList *tmp_list;

  for (tmp_list = info->ics; tmp_list; tmp_list = tmp_list->next)
232
    reinitialize_ic (tmp_list->data);
233 234
}

235
static void
236
status_style_change (GtkXIMInfo *info)
237
{
238
  GtkIMStatusStyle status_style;
239
  
240 241 242 243 244 245 246
  g_object_get (info->settings,
		"gtk-im-status-style", &status_style,
		NULL);
  if (status_style == GTK_IM_STATUS_CALLBACK)
    info->status_style_setting = XIMStatusCallbacks;
  else if (status_style == GTK_IM_STATUS_NOTHING)
    info->status_style_setting = XIMStatusNothing;
247 248
  else if (status_style == GTK_IM_STATUS_NONE)
    info->status_style_setting = XIMStatusNone;
249 250
  else
    return;
251

252
  setup_styles (info);
253 254
  
  reinitialize_all_ics (info);
255
}
256

257 258 259 260 261 262
static void
preedit_style_change (GtkXIMInfo *info)
{
  GtkIMPreeditStyle preedit_style;
  g_object_get (info->settings,
		"gtk-im-preedit-style", &preedit_style,
263
		NULL);
264 265 266 267
  if (preedit_style == GTK_IM_PREEDIT_CALLBACK)
    info->preedit_style_setting = XIMPreeditCallbacks;
  else if (preedit_style == GTK_IM_PREEDIT_NOTHING)
    info->preedit_style_setting = XIMPreeditNothing;
268 269
  else if (preedit_style == GTK_IM_PREEDIT_NONE)
    info->preedit_style_setting = XIMPreeditNone;
270 271 272 273
  else
    return;

  setup_styles (info);
274 275
  
  reinitialize_all_ics (info);
276
}
277

278 279 280 281 282 283 284 285
static void
setup_styles (GtkXIMInfo *info)
{
  int i;
  unsigned long settings_preference;
  XIMStyles *xim_styles = info->xim_styles;

  settings_preference = info->status_style_setting|info->preedit_style_setting;
286
  info->style = 0;
287 288 289 290
  if (xim_styles)
    {
      for (i = 0; i < xim_styles->count_styles; i++)
	if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
291
	  {
292
	    if (settings_preference == xim_styles->supported_styles[i])
293
	      {
294
		info->style = settings_preference;
295 296 297 298 299
		break;
	      }
	    info->style = choose_better_style (info->style,
					       xim_styles->supported_styles[i]);
	  }
300
    }
301 302
  if (info->style == 0)
    info->style = XIMPreeditNothing | XIMStatusNothing;
303 304 305
}

static void
306
setup_im (GtkXIMInfo *info)
307 308
{
  XIMValuesList *ic_values = NULL;
309
  XIMCallback im_destroy_callback;
310

311 312 313
  if (info->im == NULL)
    return;

314 315 316 317 318 319
  im_destroy_callback.client_data = (XPointer)info;
  im_destroy_callback.callback = (XIMProc)xim_destroy_callback;
  XSetIMValues (info->im,
		XNDestroyCallback, &im_destroy_callback,
		NULL);

320 321 322 323
  XGetIMValues (info->im,
		XNQueryInputStyle, &info->xim_styles,
		XNQueryICValuesList, &ic_values,
		NULL);
324

325
  info->settings = gtk_settings_get_for_screen (info->screen);
326 327 328 329

  if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
				     "gtk-im-preedit-style"))
    gtk_settings_install_property (g_param_spec_enum ("gtk-im-preedit-style",
330 331
						      P_("IM Preedit style"),
						      P_("How to draw the input method preedit string"),
332 333 334 335 336 337 338
						      GTK_TYPE_IM_PREEDIT_STYLE,
						      GTK_IM_PREEDIT_CALLBACK,
						      G_PARAM_READWRITE));

  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",
339 340
						      P_("IM Status style"),
						      P_("How to draw the input method statusbar"),
341 342 343
						      GTK_TYPE_IM_STATUS_STYLE,
						      GTK_IM_STATUS_CALLBACK,
						      G_PARAM_READWRITE));
344 345 346 347 348 349 350 351 352 353

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

354
  info->supports_string_conversion = FALSE;
355 356
  if (ic_values)
    {
357 358 359 360 361 362 363 364 365 366 367
      int i;
      
      for (i = 0; i < ic_values->count_values; i++)
	if (strcmp (ic_values->supported_values[i],
		    XNStringConversionCallback) == 0)
	  {
	    info->supports_string_conversion = TRUE;
	    break;
	  }

#if 0
368 369 370 371
      for (i = 0; i < ic_values->count_values; i++)
	g_print ("%s\n", ic_values->supported_values[i]);
      for (i = 0; i < xim_styles->count_styles; i++)
	g_print ("%#x\n", xim_styles->supported_styles[i]);
372
#endif
373 374 375
      
      XFree (ic_values);
    }
376

377 378
  status_style_change (info);
  preedit_style_change (info);
379 380
}

381 382 383 384 385 386 387 388 389 390 391 392 393
static void
xim_info_display_closed (GdkDisplay *display,
			 gboolean    is_error,
			 GtkXIMInfo *info)
{
  GSList *ics, *tmp_list;

  open_ims = g_slist_remove (open_ims, info);

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

  for (tmp_list = ics; tmp_list; tmp_list = tmp_list->next)
394
    set_ic_client_window (tmp_list->data, NULL);
395

396
  g_slist_free (ics);
397 398 399 400 401 402 403
  
  g_signal_handler_disconnect (info->settings, info->status_set);
  g_signal_handler_disconnect (info->settings, info->preedit_set);
  
  XFree (info->xim_styles->supported_styles);
  XFree (info->xim_styles);
  g_free (info->locale);
404 405 406

  if (info->im)
    XCloseIM (info->im);
407 408 409 410

  g_free (info);
}

411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
static void
xim_instantiate_callback (Display *display, XPointer client_data,
			  XPointer call_data)
{
  GtkXIMInfo *info = (GtkXIMInfo*)client_data;
  XIM im = NULL;

  im = XOpenIM (display, NULL, NULL, NULL);

  if (!im)
    return;

  info->im = im;
  setup_im (info);

  XUnregisterIMInstantiateCallback (display, NULL, NULL, NULL,
				    xim_instantiate_callback,
				    (XPointer)info);
  info->reconnecting = FALSE;
}

/* initialize info->im */
static void
xim_info_try_im (GtkXIMInfo *info)
{
  GdkScreen *screen = info->screen;
  GdkDisplay *display = gdk_screen_get_display (screen);

  g_assert (info->im == NULL);
  if (info->reconnecting)
    return;

  if (XSupportsLocale ())
    {
      if (!XSetLocaleModifiers (""))
	g_warning ("Unable to set locale modifiers with XSetLocaleModifiers()");
      info->im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
      if (!info->im)
	{
	  XRegisterIMInstantiateCallback (GDK_DISPLAY_XDISPLAY(display),
					  NULL, NULL, NULL,
					  xim_instantiate_callback,
					  (XPointer)info);
	  info->reconnecting = TRUE;
	  return;
	}
      setup_im (info);

      g_signal_connect (display, "closed",
			G_CALLBACK (xim_info_display_closed), info);
    }
}

static void
xim_destroy_callback (XIM      xim,
		      XPointer client_data,
		      XPointer call_data)
{
  GtkXIMInfo *info = (GtkXIMInfo*)client_data;

  info->im = NULL;

  g_signal_handler_disconnect (info->settings, info->status_set);
  g_signal_handler_disconnect (info->settings, info->preedit_set);

  reinitialize_all_ics (info);
  xim_info_try_im (info);
  return;
} 

481
static GtkXIMInfo *
482
get_im (GdkWindow *client_window,
483
	const char *locale)
484
{
485
  GSList *tmp_list;
486
  GtkXIMInfo *info;
487
  GdkScreen *screen = gdk_drawable_get_screen (client_window);
488

489
  info = NULL;
490
  tmp_list = open_ims;
491 492
  while (tmp_list)
    {
493 494 495
      GtkXIMInfo *tmp_info = tmp_list->data;
      if (tmp_info->screen == screen &&
	  strcmp (tmp_info->locale, locale) == 0)
496
	{
497 498 499 500
	  if (tmp_info->im)
	    {
	      return tmp_info;
	    }
501
	  else
502 503 504 505
	    {
	      tmp_info = tmp_info;
	      break;
	    }
506
	}
507 508 509
      tmp_list = tmp_list->next;
    }

510
  if (info == NULL)
511
    {
512 513 514 515 516 517 518 519 520 521 522 523
      info = g_new (GtkXIMInfo, 1);
      open_ims = g_slist_prepend (open_ims, info);

      info->screen = screen;
      info->locale = g_strdup (locale);
      info->xim_styles = NULL;
      info->preedit_style_setting = 0;
      info->status_style_setting = 0;
      info->settings = NULL;
      info->preedit_set = 0;
      info->status_set = 0;
      info->ics = NULL;
524 525
      info->reconnecting = FALSE;
      info->im = NULL;
526 527
    }

528
  xim_info_try_im (info);
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
  return info;
}

static void
gtk_im_context_xim_class_init (GtkIMContextXIMClass *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 = gtk_im_context_xim_set_client_window;
  im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
  im_context_class->reset = gtk_im_context_xim_reset;
  im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
544 545
  im_context_class->focus_in = gtk_im_context_xim_focus_in;
  im_context_class->focus_out = gtk_im_context_xim_focus_out;
546
  im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
547
  im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
548 549 550 551 552 553
  gobject_class->finalize = gtk_im_context_xim_finalize;
}

static void
gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
{
554
  im_context_xim->use_preedit = TRUE;
555
  im_context_xim->filter_key_release = FALSE;
556
  im_context_xim->finalizing = FALSE;
557 558
  im_context_xim->has_focus = FALSE;
  im_context_xim->in_toplevel = FALSE;
559 560 561 562 563 564 565
}

static void
gtk_im_context_xim_finalize (GObject *obj)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);

566 567 568
  context_xim->finalizing = TRUE;

  set_ic_client_window (context_xim, NULL);
569

570
  g_free (context_xim->locale);
571
  g_free (context_xim->mb_charset);
Matthias Clasen's avatar
Matthias Clasen committed
572 573

  G_OBJECT_CLASS (parent_class)->finalize (obj);
574 575 576
}

static void
577
reinitialize_ic (GtkIMContextXIM *context_xim)
578 579 580 581 582
{
  if (context_xim->ic)
    {
      XDestroyIC (context_xim->ic);
      context_xim->ic = NULL;
583
      update_status_window (context_xim);
584 585 586 587

      if (context_xim->preedit_length)
	{
	  context_xim->preedit_length = 0;
588
	  if (!context_xim->finalizing)
589
	    g_signal_emit_by_name (context_xim, "preedit_changed");
590
	}
591
    }
592 593 594 595 596
  /* 
     reset filter_key_release flag, otherwise keystrokes will be doubled
     until reconnecting to XIM.
  */
  context_xim->filter_key_release = FALSE;
597 598
}

599 600
static void
set_ic_client_window (GtkIMContextXIM *context_xim,
601
		      GdkWindow       *client_window)
602
{
603
  reinitialize_ic (context_xim);
604 605 606 607 608 609 610 611 612 613 614 615 616
  if (context_xim->client_window)
    {
      context_xim->im_info->ics = g_slist_remove (context_xim->im_info->ics, context_xim);
      context_xim->im_info = NULL;
    }
  
  context_xim->client_window = client_window;

  if (context_xim->client_window)
    {
      context_xim->im_info = get_im (context_xim->client_window, context_xim->locale);
      context_xim->im_info->ics = g_slist_prepend (context_xim->im_info->ics, context_xim);
    }
617 618
  
  update_client_widget (context_xim);
619 620
}

621 622 623 624 625
static void
gtk_im_context_xim_set_client_window (GtkIMContext          *context,
				      GdkWindow             *client_window)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
626

627
  set_ic_client_window (context_xim, client_window);
628 629 630 631 632 633
}

GtkIMContext *
gtk_im_context_xim_new (void)
{
  GtkIMContextXIM *result;
634
  const gchar *charset;
635

636
  result = g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL);
637

638
  result->locale = g_strdup (setlocale (LC_CTYPE, NULL));
639 640 641 642 643 644 645 646 647 648 649 650 651 652
  
  g_get_charset (&charset);
  result->mb_charset = g_strdup (charset);

  return GTK_IM_CONTEXT (result);
}

static char *
mb_to_utf8 (GtkIMContextXIM *context_xim,
	    const char      *str)
{
  GError *error = NULL;
  gchar *result;

653 654 655
  if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
    result = g_strdup (str);
  else
656
    {
657 658 659 660 661 662 663 664
      result = g_convert (str, -1,
			  "UTF-8", context_xim->mb_charset,
			  NULL, NULL, &error);
      if (!result)
	{
	  g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
	  g_error_free (error);
	}
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
    }
  
  return result;
}

static gboolean
gtk_im_context_xim_filter_keypress (GtkIMContext *context,
				    GdkEventKey  *event)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
  XIC ic = gtk_im_context_xim_get_ic (context_xim);
  gchar static_buffer[256];
  gchar *buffer = static_buffer;
  gint buffer_size = sizeof(static_buffer) - 1;
  gint num_bytes = 0;
  KeySym keysym;
  Status status;
  gboolean result = FALSE;
683
  GdkWindow *root_window = gdk_screen_get_root_window (gdk_drawable_get_screen (event->window));
684 685 686

  XKeyPressedEvent xevent;

687
  if (event->type == GDK_KEY_RELEASE && !context_xim->filter_key_release)
688 689
    return FALSE;

Owen Taylor's avatar
Owen Taylor committed
690
  xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
691 692 693 694
  xevent.serial = 0;		/* hope it doesn't matter */
  xevent.send_event = event->send_event;
  xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
  xevent.window = GDK_DRAWABLE_XID (event->window);
695
  xevent.root = GDK_DRAWABLE_XID (root_window);
696 697 698 699 700
  xevent.subwindow = xevent.window;
  xevent.time = event->time;
  xevent.x = xevent.x_root = 0;
  xevent.y = xevent.y_root = 0;
  xevent.state = event->state;
701
  xevent.keycode = event->hardware_keycode;
702 703 704 705 706 707
  xevent.same_screen = True;
  
  if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
    return TRUE;
  
 again:
708 709 710 711 712 713 714
  if (ic)
    num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
  else
    {
      num_bytes = XLookupString (&xevent, buffer, buffer_size, &keysym, NULL);
      status = XLookupBoth;
    }
715 716 717 718

  if (status == XBufferOverflow)
    {
      buffer_size = num_bytes;
719 720
      if (buffer != static_buffer) 
	g_free (buffer);
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
      buffer = g_malloc (num_bytes + 1);
      goto again;
    }

  /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
   * here ... do input methods actually change the keysym? we can't really
   * feed it back to accelerator processing at this point...
   */
  if (status == XLookupChars || status == XLookupBoth)
    {
      char *result_utf8;

      buffer[num_bytes] = '\0';

      result_utf8 = mb_to_utf8 (context_xim, buffer);
      if (result_utf8)
	{
738 739 740 741
	  if ((guchar)result_utf8[0] >= 0x20 &&
	      result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
				       * control characters into strings
				       */
742
	    {
743
	      g_signal_emit_by_name (context, "commit", result_utf8);
744 745 746 747 748 749 750
	      result = TRUE;
	    }
	  
	  g_free (result_utf8);
	}
    }

751 752 753
  if (buffer != static_buffer) 
    g_free (buffer);

754
  return result;
755 756
}

757 758 759 760 761
static void
gtk_im_context_xim_focus_in (GtkIMContext *context)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);

762 763 764
  if (!context_xim->has_focus)
    {
      XIC ic = gtk_im_context_xim_get_ic (context_xim);
765

766 767 768 769 770 771
      context_xim->has_focus = TRUE;
      update_status_window (context_xim);
      
      if (ic)
	XSetICFocus (ic);
    }
772

773 774 775 776 777 778 779 780
  return;
}

static void
gtk_im_context_xim_focus_out (GtkIMContext *context)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);

781 782 783 784 785 786 787 788 789 790
  if (context_xim->has_focus)
    {
      XIC ic = gtk_im_context_xim_get_ic (context_xim);
      
      context_xim->has_focus = FALSE;
      update_status_window (context_xim);
  
      if (ic)
	XUnsetICFocus (ic);
    }
791

792 793 794
  return;
}

795
static void
796 797
gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
					GdkRectangle *area)
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
  XIC ic = gtk_im_context_xim_get_ic (context_xim);

  XVaNestedList preedit_attr;
  XPoint          spot;

  if (!ic)
    return;

  spot.x = area->x;
  spot.y = area->y;

  preedit_attr = XVaCreateNestedList (0,
				      XNSpotLocation, &spot,
813
				      NULL);
814 815 816 817 818 819 820 821
  XSetICValues (ic,
		XNPreeditAttributes, preedit_attr,
		NULL);
  XFree(preedit_attr);

  return;
}

822 823 824 825 826 827 828 829 830 831 832
static void
gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
				    gboolean      use_preedit)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);

  use_preedit = use_preedit != FALSE;

  if (context_xim->use_preedit != use_preedit)
    {
      context_xim->use_preedit = use_preedit;
833
      reinitialize_ic (context_xim);
834 835 836 837 838
    }

  return;
}

839 840 841 842 843 844 845
static void
gtk_im_context_xim_reset (GtkIMContext *context)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
  XIC ic = gtk_im_context_xim_get_ic (context_xim);
  gchar *result;

846 847 848 849 850
  /* restore conversion state after resetting ic later */
  XIMPreeditState preedit_state = XIMPreeditUnKnown;
  XVaNestedList preedit_attr;
  gboolean have_preedit_state = FALSE;

851 852 853
  if (!ic)
    return;
  
854

855 856 857
  if (context_xim->preedit_length == 0)
    return;

858 859
  preedit_attr = XVaCreateNestedList(0,
                                     XNPreeditState, &preedit_state,
860
                                     NULL);
861 862 863 864 865 866 867
  if (!XGetICValues(ic,
                    XNPreeditAttributes, preedit_attr,
                    NULL))
    have_preedit_state = TRUE;

  XFree(preedit_attr);

868 869
  result = XmbResetIC (ic);

870 871
  preedit_attr = XVaCreateNestedList(0,
                                     XNPreeditState, preedit_state,
872
                                     NULL);
873 874 875 876 877 878 879
  if (have_preedit_state)
    XSetICValues(ic,
		 XNPreeditAttributes, preedit_attr,
		 NULL);

  XFree(preedit_attr);

880 881 882 883 884
  if (result)
    {
      char *result_utf8 = mb_to_utf8 (context_xim, result);
      if (result_utf8)
	{
885
	  g_signal_emit_by_name (context, "commit", result_utf8);
886 887 888 889 890 891 892
	  g_free (result_utf8);
	}
    }

  if (context_xim->preedit_length)
    {
      context_xim->preedit_length = 0;
893
      g_signal_emit_by_name (context, "preedit_changed");
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949
    }

  XFree (result);
}

/* Mask of feedback bits that we render
 */
#define FEEDBACK_MASK (XIMReverse | XIMUnderline)

static void
add_feedback_attr (PangoAttrList *attrs,
		   const gchar   *str,
		   XIMFeedback    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;

  if (feedback & XIMUnderline)
    {
      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 & XIMReverse)
    {
      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);
    }

  if (feedback & ~FEEDBACK_MASK)
    g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
}

static void     
gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
				       gchar         **str,
				       PangoAttrList **attrs,
				       gint           *cursor_pos)
{
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
950
  gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985

  if (attrs)
    {
      int i;
      XIMFeedback last_feedback = 0;
      gint start = -1;
      
      *attrs = pango_attr_list_new ();

      for (i = 0; i < context_xim->preedit_length; i++)
	{
	  XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
	  if (new_feedback != last_feedback)
	    {
	      if (start >= 0)
		add_feedback_attr (*attrs, utf8, last_feedback, start, i);
	      
	      last_feedback = new_feedback;
	      start = i;
	    }
	}

      if (start >= 0)
	add_feedback_attr (*attrs, utf8, last_feedback, start, i);
    }

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

  if (cursor_pos)
    *cursor_pos = context_xim->preedit_cursor;
}

986
static int
987 988 989 990 991
preedit_start_callback (XIC      xic,
			XPointer client_data,
			XPointer call_data)
{
  GtkIMContext *context = GTK_IM_CONTEXT (client_data);
992
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
993
  
994 995
  if (!context_xim->finalizing)
    g_signal_emit_by_name (context, "preedit_start");
996 997

  return -1;			/* No length limit */
998 999 1000 1001 1002 1003 1004 1005
}		     

static void
preedit_done_callback (XIC      xic,
		     XPointer client_data,
		     XPointer call_data)
{
  GtkIMContext *context = GTK_IM_CONTEXT (client_data);
1006 1007
  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);

1008 1009 1010 1011 1012 1013 1014
  if (context_xim->preedit_length)
    {
      context_xim->preedit_length = 0;
      if (!context_xim->finalizing)
	g_signal_emit_by_name (context_xim, "preedit_changed");
    }

1015 1016
  if (!context_xim->finalizing)
    g_signal_emit_by_name (context, "preedit_end");  
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
}		     

static gint
xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
{
  gint text_length = 0;
  GError *error = NULL;
  gchar *result = NULL;

  if (xim_text && xim_text->string.multi_byte)
    {
      if (xim_text->encoding_is_wchar)
	{
	  g_warning ("Wide character return from Xlib not currently supported");
	  *text = NULL;
	  return 0;
	}

1035 1036 1037 1038 1039 1040 1041 1042
      if (strcmp (context->mb_charset, "UTF-8") == 0)
	result = g_strdup (xim_text->string.multi_byte);
      else
	result = g_convert (xim_text->string.multi_byte,
			    -1,
			    "UTF-8",
			    context->mb_charset,
			    NULL, NULL, &error);
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
      
      if (result)
	{
	  text_length = g_utf8_strlen (result, -1);
	  
	  if (text_length != xim_text->length)
	    {
	      g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
	    }
	}
      else
	{
	  g_warning ("Error converting text from IM to UCS-4: %s", error->message);
	  g_error_free (error);
1057 1058 1059

	  *text = NULL;
	  return 0;
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
	}

      *text = result;
      return text_length;
    }
  else
    {
      *text = NULL;
      return 0;
    }
}

static void
preedit_draw_callback (XIC                           xic, 
		       XPointer                      client_data,
		       XIMPreeditDrawCallbackStruct *call_data)
{
  GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);

  XIMText *new_xim_text = call_data->text;
  gint new_text_length;
  gunichar *new_text = NULL;
  gint i;
  gint diff;
  gint new_length;
  gchar *tmp;
  
  gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
  gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);

  context->preedit_cursor = call_data->caret;
  
  if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
    g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
	       call_data->chg_first, call_data->chg_length, context->preedit_length);

  new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
  if (tmp)
    {
1099
      new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
      g_free (tmp);
    }
  
  diff = new_text_length - chg_length;
  new_length = context->preedit_length + diff;

  if (new_length > context->preedit_size)
    {
      context->preedit_size = new_length;
      context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
      context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
    }

  if (diff < 0)
    {
      for (i = chg_first + chg_length ; i < context->preedit_length; i++)
	{
	  context->preedit_chars[i + diff] = context->preedit_chars[i];
	  context->feedbacks[i + diff] = context->feedbacks[i];
	}
    }
  else
    {
      for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
	{
	  context->preedit_chars[i + diff] = context->preedit_chars[i];
	  context->feedbacks[i + diff] = context->feedbacks[i];
	}
    }

  for (i = 0; i < new_text_length; i++)
    {
      context->preedit_chars[chg_first + i] = new_text[i];
      context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
    }

  context->preedit_length += diff;

  if (new_text)
    g_free (new_text);

1141 1142
  if (!context->finalizing)
    g_signal_emit_by_name (context, "preedit_changed");
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
}
    

static void
preedit_caret_callback (XIC                            xic,
			XPointer                       client_data,
			XIMPreeditCaretCallbackStruct *call_data)
{
  GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
  
  if (call_data->direction == XIMAbsolutePosition)
    {
      context->preedit_cursor = call_data->position;
1156 1157
      if (!context->finalizing)
	g_signal_emit_by_name (context, "preedit_changed");
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
    }
  else
    {
      g_warning ("Caret movement command: %d %d %d not supported",
		 call_data->position, call_data->direction, call_data->style);
    }
}	     

static void
status_start_callback (XIC      xic,
		       XPointer client_data,
		       XPointer call_data)
{
1171
  return;
1172 1173 1174 1175 1176 1177 1178
} 

static void
status_done_callback (XIC      xic,
		      XPointer client_data,
		      XPointer call_data)
{
1179
  return;
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
}

static void
status_draw_callback (XIC      xic,
		      XPointer client_data,
		      XIMStatusDrawCallbackStruct *call_data)
{
  GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);

  if (call_data->type == XIMTextType)
    {
      gchar *text;
      xim_text_to_utf8 (context, call_data->data.text, &text);

1194 1195
      if (context->status_window)
	status_window_set_text (context->status_window, text ? text : "");
1196 1197 1198
    }
  else				/* bitmap */
    {
1199
      g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
1200 1201 1202
    }
}

1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271