#ifndef lint
static char *rcsid = "$Id: imconv.c,v 1.25 2002/01/24 09:07:19 ishisone Exp $";
#endif
/*
 * Copyright (c) 1991, 1994  Software Research Associates, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Author:  Makoto Ishisone, Software Research Associates, Inc., Japan
 */

#define COMMIT_SYNC
#define STATUS_SYNC
#include "im.h"
#include "ConvMgr.h"
#include "OverConv.h"
#include "OffConv.h"
#include "OnConv.h"
#include "InputConv.h"

static void fillDefaultAttributesForStartup _Pt_((IMIC *icp));
static unsigned long makeConvAttributesForStartup _Pt_((IMIC *icp,
							ConversionAttributes *attrp));
static void commitString _Pt_((IMIC *icp, char *str, int len, int sync));
static void fixCallback _Pt_((Widget w, XtPointer client_data,
			      XtPointer call_data));
static void detachConverter _Pt_((IMIC *icp));
static void endCallback _Pt_((Widget w, XtPointer client_data,
			      XtPointer call_data));
static void unusedEventCallback _Pt_((Widget w, XtPointer client_data,
				      XtPointer call_data));
static void preeditStartCallback _Pt_((Widget w, XtPointer client_data,
				       XtPointer call_data));
static void preeditDoneCallback _Pt_((Widget w, XtPointer client_data,
				      XtPointer call_data));
static void preeditDrawCallback _Pt_((Widget w, XtPointer client_data,
				      XtPointer call_data));
static void preeditCaretCallback _Pt_((Widget w, XtPointer client_data,
				       XtPointer call_data));
static void statusStartCallback _Pt_((Widget w, XtPointer client_data,
				      XtPointer call_data));
static void statusDoneCallback _Pt_((Widget w, XtPointer client_data,
				     XtPointer call_data));
static void statusDrawCallback _Pt_((Widget w, XtPointer client_data,
				     XtPointer call_data));
static void preeditStart _Pt_((IMIC *icp));
static void preeditDone _Pt_((IMIC *icp));
static void preeditDraw _Pt_((IMIC *icp, OCCPreeditDrawArg *data));
static void preeditCaret _Pt_((IMIC *icp, int caret));
static void statusStart _Pt_((IMIC *icp));
static void statusDone _Pt_((IMIC *icp));
static void statusDraw _Pt_((IMIC *icp, OCCPreeditDrawArg *data));
static void setEventMask _Pt_((IMIC *icp, unsigned long forward_mask,
			       unsigned long synchronous_mask));


/*- fillDefaultAttributesForStartup: put default necessary for conv. start -*/
static void
fillDefaultAttributesForStartup(icp)
IMIC *icp;
{
    unsigned long cmask, pmask, smask;

    cmask = ATTR_MASK_FOCUS | ATTR_MASK_PREEDIT_STATE | ATTR_MASK_RESET_STATE;

    switch (icp->style) {
    case IMSTYLE_OVER_THE_SPOT:
	pmask = ATTR_MASK_FOREGROUND | ATTR_MASK_BACKGROUND |
	    ATTR_MASK_FONT_SET;
	smask = 0;
	break;
    case IMSTYLE_OFF_THE_SPOT:
	pmask = ATTR_MASK_FOREGROUND | ATTR_MASK_BACKGROUND |
	    ATTR_MASK_FONT_SET | ATTR_MASK_AREA;
	smask = ATTR_MASK_AREA;
	break;
    case IMSTYLE_ON_THE_SPOT:
	pmask = 0;
	smask = 0;
	break;
    default:
	pmask = 0;
	smask = 0;
    }
    IMFillDefault(icp, cmask, pmask, smask);
}

/*- makeConvAttributesForStartup: get conv. attrs needed for startup -*/
static unsigned long
makeConvAttributesForStartup(icp, attrp)
IMIC *icp;
ConversionAttributes *attrp;
{
    icp->common_attr.change_mask = icp->common_attr.set_mask;
    icp->preedit_attr.change_mask = icp->preedit_attr.set_mask;
    icp->status_attr.change_mask = icp->status_attr.set_mask;
    return IMMakeConvAttributes(icp, attrp);
}

/*- commitString: commmit converted string to client -*/
static void
commitString(icp, str, len, sync)
IMIC *icp;
char *str;
int len;
int sync;
{
    int offset;
    IMConnection *conn = icp->im->connection;
    unsigned int flag;

    TRACE(("imlib:commitString()\n"));

    if (DDEBUG_CONDITION(5)) {
	unsigned char *p = (unsigned char *)str;
	int i;

	/*
	 * Dump commiting string.
	 */
	printf("* commit string:\n\t");
	for (i = 0; i < len; i++, p++) {
	    if (*p == '\033') {
		printf("ESC ");
	    } else if (*p < ' ') {
		printf("^%c ", *p + '@');
	    } else if (*p == ' ') {
		printf("sp ");
	    } else if (*p >= 0x7f) {
		printf("%x ", *p);
	    } else {
		printf("%c ", *p);
	    }
	}
	printf("\n");
    }

    flag = XIM_FLAG_X_LOOKUP_CHARS;
    if (sync) flag |= XIM_FLAG_SYNCHRONOUS;

    offset = IMPutHeader(conn, XIM_COMMIT, 0, 0);
    IMPutC16(conn, icp->im->id);
    IMPutC16(conn, icp->id);
    IMPutC16(conn, flag);
    IMPutC16(conn, (unsigned int)len);
    IMPutString(conn, str, len);
    IMFinishRequest(conn, offset);
}

/*- fixCallback: fix callback -*/
/* ARGSUSED */
static void
fixCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;
    IMConnection *conn = icp->im->connection;
    Widget proto = conn->proto_widget;
    Atom ctext = IMCtextAtom(proto);
    CCTextCallbackArg *arg = (CCTextCallbackArg *)call_data;

    TRACE(("imlib:fixCallback()\n"));

    /* check encoding and format */
    if (arg->encoding != ctext || arg->format != 8) {
	/*
	 * since every conversion object must support COMPOUND_TEXT,
	 * it is a serious error.
	 */
	String params[2];
	Cardinal num_params;
	WidgetClass ioc = icp->im->converter->input_object_class;

	params[0] = XtClass(proto)->core_class.class_name;
	params[1] = ioc->core_class.class_name;
	num_params = 2;

	XtAppErrorMsg(XtWidgetToApplicationContext(proto),
		      "encodingError", "convertedString", "WidgetError",
		      "%s: encoding of the converted string is not COMPOUND_STRING. check inputObject %s",
		      params, &num_params);
    }

    /*
     * Send fixed string via XIM_COMMIT message.
     * Since kinput2 uses full-synchronous mode,
     * synchronous flag must be turned off.
     */
    commitString(icp, arg->text, arg->length, 0);

#ifdef COMMIT_SYNC
    /*
     * Send XIM_SYNC_REPLY so that synchronize with clients here.
     */
    if (icp->state & IC_FORWARDING) {
	icp->state &= ~IC_FORWARDING;
	IMSendRequestWithIC(conn, XIM_SYNC_REPLY, 0, icp);
    }
#endif /* COMMIT_SYNC */
}

/*- detachConverter: detach conversion widget from specified IC -*/
static void
detachConverter(icp)
IMIC *icp;
{
    Widget conv;

    TRACE(("imlib:detachConverter()\n"));

    conv = icp->conversion;
    XtRemoveCallback(conv, XtNtextCallback, fixCallback, (XtPointer)icp);
    XtRemoveCallback(conv, XtNendCallback, endCallback, (XtPointer)icp);
    XtRemoveCallback(conv, XtNunusedEventCallback, unusedEventCallback, (XtPointer)icp);
    if (icp->style == IMSTYLE_ON_THE_SPOT) {
	XtRemoveCallback(conv, XtNpreeditStartCallback, preeditStartCallback,
			 (XtPointer)icp);
	XtRemoveCallback(conv, XtNpreeditDoneCallback, preeditDoneCallback,
			 (XtPointer)icp);
	XtRemoveCallback(conv, XtNpreeditDrawCallback, preeditDrawCallback,
			 (XtPointer)icp);
	XtRemoveCallback(conv, XtNpreeditCaretCallback, preeditCaretCallback,
			 (XtPointer)icp);
	XtRemoveCallback(conv, XtNstatusStartCallback, statusStartCallback,
			 (XtPointer)icp);
	XtRemoveCallback(conv, XtNstatusDoneCallback, statusDoneCallback,
			 (XtPointer)icp);
	XtRemoveCallback(conv, XtNstatusDrawCallback, statusDrawCallback,
			 (XtPointer)icp);
    }

    CMReleaseConverter(XtParent(icp->im->connection->proto_widget), conv);
    icp->conversion = NULL;
}

/*- endCallback: conversion end callback -*/
/* ARGSUSED */
static void
endCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;

    TRACE(("imlib:endCallback()\n"));

    if (icp->state & IC_CONVERTING) {
	detachConverter(icp);
	icp->state &= ~IC_CONVERTING;
    }
}

/*- unusedEventCallback: unused key event callback -*/
/* ARGSUSED */
static void
unusedEventCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;
    IMConnection *conn = icp->im->connection;
    XKeyEvent *ev = (XKeyEvent *)call_data;
    int offset;

    TRACE(("imlib:unusedEventCallback()\n"));

    if (icp->im->mask & XIM_EXT_FORWARD_KEYEVENT_MASK) {
	offset = IMPutHeader(conn, XIM_EXT_FORWARD_KEYEVENT, 0, 0);
	IMPutC16(conn, icp->im->id);
	IMPutC16(conn, icp->id);
	IMPutC16(conn, 0);
	IMPutC16(conn, (unsigned int)(ev->serial & 0xffff));
	IMPutC8(conn, ev->type);
	IMPutC8(conn, (int)ev->keycode);
	IMPutC16(conn, (unsigned int)ev->state);
	IMPutC32(conn, ev->time);
	IMPutC32(conn, ev->window);
	IMFinishRequest(conn, offset);
    } else {
	offset = IMPutHeader(conn, XIM_FORWARD_EVENT, 0, 0);
	IMPutC16(conn, icp->im->id);
	IMPutC16(conn, icp->id);
	IMPutC16(conn, 0);	/* ?? */
	IMPutC16(conn, (unsigned int)((ev->serial >> 16) & 0xffff));

	IMPutC8(conn, ev->type);
	IMPutC8(conn, (int)ev->keycode);
	IMPutC16(conn, (unsigned int)(ev->serial & 0xffff));
	IMPutC32(conn, ev->time);
	IMPutC32(conn, ev->root);
	IMPutC32(conn, ev->window);
	IMPutC32(conn, ev->subwindow);
	IMPutI16(conn, ev->x_root);
	IMPutI16(conn, ev->y_root);
	IMPutI16(conn, ev->x);
	IMPutI16(conn, ev->y);
	IMPutC16(conn, ev->state);
	IMPutC8(conn, ev->same_screen);

	IMFinishRequest(conn, offset);
    }
}

/*- preeditStartCallback: preedit start -*/
/* ARGSUSED */
static void
preeditStartCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;

    TRACE(("preeditStartCallback(icp=0x%lx)\n", icp));

    if (!(icp->common_attr.input_style & XIMPreeditCallbacks))
	return;

    preeditStart(icp);
}

/*- preeditDoneCallback: preedit done -*/
/* ARGSUSED */
static void
preeditDoneCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;

    TRACE(("preeditDoneCallback(icp=0x%lx)\n", icp));

    if (!(icp->common_attr.input_style & XIMPreeditCallbacks))
	return;

    preeditDone(icp);
}

/*- preeditDrawCallback: preedit draw -*/
/* ARGSUSED */
static void
preeditDrawCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;
    OCCPreeditDrawArg *arg = (OCCPreeditDrawArg *)call_data;
    IMConnection *conn = icp->im->connection;
    Widget proto = conn->proto_widget;
    Atom ctext = IMCtextAtom(proto);

    TRACE(("preeditDrawCallback(icp=0x%lx, length=%d)\n",icp,arg->text_length));

    if (!(icp->common_attr.input_style & XIMPreeditCallbacks))
	return;

    /* check encoding and format */
    if (arg->encoding != ctext || arg->format != 8) {
	/*
	 * since every conversion object must support COMPOUND_TEXT,
	 * it is a serious error.
	 */
	String params[2];
	Cardinal num_params;
	WidgetClass ioc = icp->im->converter->input_object_class;

	params[0] = XtClass(proto)->core_class.class_name;
	params[1] = ioc->core_class.class_name;
	num_params = 2;

	XtAppErrorMsg(XtWidgetToApplicationContext(proto),
		      "encodingError", "preeditString", "WidgetError",
		      "%s: encoding of the preedit string is not COMPOUND_STRING. check inputObject %s",
		      params, &num_params);
    }

    preeditDraw(icp, arg);
}

/*- preeditCaretCallback: preedit caret -*/
/* ARGSUSED */
static void
preeditCaretCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;
    int caret = (int)call_data;

    TRACE(("preeditCaretCallback(icp=0x%lx, caret=%d)\n", icp, caret));

    if (!(icp->common_attr.input_style & XIMPreeditCallbacks))
	return;

    preeditCaret(icp, caret);
}

/*- statusStartCallback: status start -*/
/* ARGSUSED */
static void
statusStartCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;

    TRACE(("statusStartCallback(icp=0x%lx)\n", icp));

    if (!(icp->common_attr.input_style & XIMStatusCallbacks))
	return;

    statusStart(icp);
}

/*- statusDoneCallback: status done -*/
/* ARGSUSED */
static void
statusDoneCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;

    TRACE(("statusDoneCallback(icp=0x%lx)\n", icp));

    if (!(icp->common_attr.input_style & XIMStatusCallbacks))
	return;

    statusDone(icp);
}

/*- statusDrawCallback: status draw -*/
/* ARGSUSED */
static void
statusDrawCallback(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    IMIC *icp = (IMIC *)client_data;
    OCCPreeditDrawArg *arg = (OCCPreeditDrawArg *)call_data;
    IMConnection *conn = icp->im->connection;
    Widget proto = conn->proto_widget;
    Atom ctext = IMCtextAtom(proto);

    TRACE(("statusDrawCallback(icp=0x%lx, length=%d)\n", icp,arg->text_length));

    if (!(icp->common_attr.input_style & XIMStatusCallbacks))
	return;

    /* check encoding and format */
    if (arg->encoding != ctext || arg->format != 8) {
	/*
	 * since every conversion object must support COMPOUND_TEXT,
	 * it is a serious error.
	 */
	String params[2];
	Cardinal num_params;
	WidgetClass ioc = icp->im->converter->input_object_class;

	params[0] = XtClass(proto)->core_class.class_name;
	params[1] = ioc->core_class.class_name;
	num_params = 2;

	XtAppErrorMsg(XtWidgetToApplicationContext(proto),
		      "encodingError", "statusString", "WidgetError",
		      "%s: encoding of the status string is not COMPOUND_STRING. check inputObject %s",
		      params, &num_params);
    }

    statusDraw(icp, arg);
}

/*- preeditStart: do preedit start -*/
static void
preeditStart(icp)
IMIC *icp;
{
    if (!(icp->state & IC_IN_PREEDIT)) {
	int offset;
	IMConnection *conn = icp->im->connection;

	TRACE(("imlib:preeditStart()\n"));

	offset = IMPutHeader(conn, XIM_PREEDIT_START, 0, 0);
	IMPutC16(conn, icp->im->id);
	IMPutC16(conn, icp->id);
	IMFinishRequest(conn, offset);
	icp->state |= IC_IN_PREEDIT;
    }
}

/*- preeditDone: do preedit done -*/
static void
preeditDone(icp)
IMIC *icp;
{
    if (icp->state & IC_IN_PREEDIT) {
	int offset;
	IMConnection *conn = icp->im->connection;

	TRACE(("imlib:preeditDone()\n"));

	offset = IMPutHeader(conn, XIM_PREEDIT_DONE, 0, 0);
	IMPutC16(conn, icp->im->id);
	IMPutC16(conn, icp->id);
	IMFinishRequest(conn, offset);
	icp->state &= ~IC_IN_PREEDIT;
    }
}

/*- preeditDraw: do actual preedit draw -*/
static void
preeditDraw(icp, data)
IMIC *icp;
OCCPreeditDrawArg *data;
{
    IMConnection *conn = icp->im->connection;
    int offset;
    unsigned int status;
    XIMFeedback feedback;
    int i;

    if (icp->state & IC_RESETTING) return;

    preeditStart(icp);

    TRACE(("imlib:preeditDraw()\n"));

    if (DDEBUG_CONDITION(5)) {
	unsigned char *p = (unsigned char *)data->text;

	/*
	 * Dump preedit string.
	 */
	printf("* preedit string:\n\t");
	for (i = 0; i < data->text_length; i++, p++) {
	    if (*p == '\033') {
		printf("ESC ");
	    } else if (*p < ' ') {
		printf("^%c ", *p + '@');
	    } else if (*p == ' ') {
		printf("sp ");
	    } else if (*p >= 0x7f) {
		printf("%x ", *p);
	    } else {
		printf("%c ", *p);
	    }
	}
	printf("\n");
    }

    offset = IMPutHeader(conn, XIM_PREEDIT_DRAW, 0, 0);
    IMPutC16(conn, icp->im->id);
    IMPutC16(conn, icp->id);
    IMPutC32(conn, data->caret);
    IMPutC32(conn, data->chg_first);
    IMPutC32(conn, data->chg_length);
    status = 0;
    if (data->text_length == 0)  status |= 0x1; /* no string */
    if (data->attrs_length == 0) status |= 0x2; /* no feedback */
    IMPutC32(conn, status);
    IMPutC16(conn, (unsigned int)data->text_length);
    if (data->text_length > 0) {
	IMPutString(conn, data->text, data->text_length);
    }
    IMPutPad(conn);
    IMPutC16(conn, (unsigned int)(data->attrs_length * 4));
    IMPutC16(conn, 0L); /* unused */
    if (data->attrs_length > 0) {
	for (i = 0; i < data->attrs_length; i++) {
	    IMPutC32(conn, data->attrs[i]);
	}
    }
    IMFinishRequest(conn, offset);
}

/*- preeditCaret: do actual preedit caret -*/
static void
preeditCaret(icp, caret)
IMIC *icp;
int caret;
{
    IMConnection *conn = icp->im->connection;
    int offset;

    if (icp->state & IC_RESETTING) return;

    preeditStart(icp);

    TRACE(("imlib:preeditCaret()\n"));

    offset = IMPutHeader(conn, XIM_PREEDIT_CARET, 0, 0);
    IMPutC16(conn, icp->im->id);
    IMPutC16(conn, icp->id);
    IMPutC32(conn, caret);
    IMPutC32(conn, (long)XIMAbsolutePosition);
    IMPutC32(conn, (long)XIMPrimary);
    IMFinishRequest(conn, offset);
}

/*- statusStart: do status start -*/
static void
statusStart(icp)
IMIC *icp;
{
    if (!(icp->state & IC_IN_STATUS)) {
	int offset;
	IMConnection *conn = icp->im->connection;

	TRACE(("imlib:statusStart()\n"));

	offset = IMPutHeader(conn, XIM_STATUS_START, 0, 0);
	IMPutC16(conn, icp->im->id);
	IMPutC16(conn, icp->id);
	IMFinishRequest(conn, offset);
	icp->state |= IC_IN_STATUS;
#ifdef STATUS_SYNC
        IMFlush(conn);
#endif /* STATUS_SYNC */
    }
}

/*- statusDone: do status done -*/
static void
statusDone(icp)
IMIC *icp;
{
    if (icp->state & IC_IN_STATUS) {
	int offset;
	IMConnection *conn = icp->im->connection;

	TRACE(("imlib:statusDone()\n"));

	offset = IMPutHeader(conn, XIM_STATUS_DONE, 0, 0);
	IMPutC16(conn, icp->im->id);
	IMPutC16(conn, icp->id);
	IMFinishRequest(conn, offset);
	icp->state &= ~IC_IN_STATUS;
#ifdef STATUS_SYNC
        IMFlush(conn);
#endif /* STATUS_SYNC */
    }
}

/*- statusDraw: do actual status draw -*/
static void
statusDraw(icp, data)
IMIC *icp;
OCCPreeditDrawArg *data;
{
    IMConnection *conn = icp->im->connection;
    int offset;
    unsigned int status;

    if (icp->state & IC_RESETTING) return;

    statusStart(icp);

    TRACE(("imlib:statusDraw()\n"));

    offset = IMPutHeader(conn, XIM_STATUS_DRAW, 0, 0);
    IMPutC16(conn, icp->im->id);
    IMPutC16(conn, icp->id);
    IMPutC32(conn, 0L); /* text type */
    status = 0;
    if (data->text_length == 0)  status |= 0x1; /* no string */
    if (data->attrs_length == 0) status |= 0x2; /* no feedback */
    IMPutC32(conn, status);
    IMPutC16(conn, (unsigned int)data->text_length);
    if (data->text_length > 0) {
	IMPutString(conn, data->text, data->text_length);
    }
    IMPutPad(conn);
    IMPutC16(conn, (unsigned int)(data->attrs_length * 32));
    IMPutC16(conn, 0L); /* unused */
    if (data->attrs_length > 0) {
	int i;
	for (i = 0; i < data->attrs_length; i++) {
	    IMPutC32(conn, 0L);
	}
    }
    IMFinishRequest(conn, offset);
#ifdef STATUS_SYNC
    IMFlush(conn);
#endif /* STATUS_SYNC */
}

/*- setEventMask: put XIM_SET_EVENT_MASK request on the output stream -*/
static void
setEventMask(icp, forward_mask, synchronous_mask)
IMIC *icp;
unsigned long forward_mask;
unsigned long synchronous_mask;
{
    IMConnection *conn = icp->im->connection;

    (void)IMPutHeader(conn, XIM_SET_EVENT_MASK, 0, 12);
    IMPutC16(conn, icp->im->id);
    IMPutC16(conn, icp->id);
    IMPutC32(conn, forward_mask);
    IMPutC32(conn, synchronous_mask);
    IMSchedule(conn, SCHED_WRITE);
}


/*
 * Public functions
 */

int
IMStartConversion(icp)
IMIC *icp;
{
    IMIM *imp = icp->im;
    Widget proto = imp->connection->proto_widget;
    Widget converter;
    WidgetClass class;
    unsigned long attrmask;
    ConversionAttributes attrs;

    TRACE(("IMStartConversion()\n"));

    if (icp->state & IC_CONVERTING) return 0;

    /*
     * Check required attributes i.e. client window.
     */
    if (!(icp->common_attr.set_mask & ATTR_MASK_CLIENT)) {
	IMSendError(icp->im->connection, IMBadSomething, icp->im->id, icp->id,
		    "client window required");
	return -1;
    }

    /*
     * Fill in default values for unspecified attributes.
     */
    fillDefaultAttributesForStartup(icp);

    /*
     * Get appropriate converter class.
     */
    if (icp->style == IMSTYLE_OVER_THE_SPOT) {
	class = overTheSpotConversionWidgetClass;
    } else if (icp->style == IMSTYLE_OFF_THE_SPOT) {
	class = offTheSpotConversionWidgetClass;
    } else if (icp->style == IMSTYLE_ON_THE_SPOT) {
	class = onTheSpotConversionWidgetClass;
    } else {
	class = separateConversionWidgetClass;
    }

    /*
     * Compute conversion attributes to be passed to the converter.
     */
    attrmask = makeConvAttributesForStartup(icp, &attrs);

    icp->state &= ~IC_RESETTING;

    /*
     * Attach converter to this IC.
     */
    converter = CMGetConverter(XtParent(proto),
			       icp->common_attr.client, class,
			       imp->converter->input_object_class,
			       imp->converter->display_object_class);
    if (converter == NULL) {
	IMSendError(imp->connection, IMBadSomething, imp->id, icp->id,
		    "can't attach converter to this IC");
	return -1;
    }
    icp->conversion = converter;

    /*
     * Add callback functions.
     */
    XtAddCallback(converter, XtNtextCallback, fixCallback, (XtPointer)icp);
    XtAddCallback(converter, XtNendCallback, endCallback, (XtPointer)icp);
    XtAddCallback(converter, XtNunusedEventCallback, unusedEventCallback, (XtPointer)icp);
    if (icp->style == IMSTYLE_ON_THE_SPOT) {
	XtAddCallback(converter, XtNpreeditStartCallback, preeditStartCallback,
		      (XtPointer)icp);
	XtAddCallback(converter, XtNpreeditDoneCallback, preeditDoneCallback,
		      (XtPointer)icp);
	XtAddCallback(converter, XtNpreeditDrawCallback, preeditDrawCallback,
		      (XtPointer)icp);
	XtAddCallback(converter, XtNpreeditCaretCallback, preeditCaretCallback,
		      (XtPointer)icp);
	XtAddCallback(converter, XtNstatusStartCallback, statusStartCallback,
		      (XtPointer)icp);
	XtAddCallback(converter, XtNstatusDoneCallback, statusDoneCallback,
		      (XtPointer)icp);
	XtAddCallback(converter, XtNstatusDrawCallback, statusDrawCallback,
		      (XtPointer)icp);
    }

    /*
     * Start conversion
     */
    /* !!! if front-end method is used, ESMethodSelectFocus should be used */
    XtVaSetValues(converter, XtNeventSelectMethod, ESMethodNone, NULL);
    CControlStartConversion(converter, icp->common_attr.client,
			    attrmask, &attrs);

    icp->state |= IC_CONVERTING;

    if (icp->common_attr.input_style & XIMPreeditCallbacks)
	preeditStart(icp);

    /*
     * Send XIM_SET_EVENT_MASK to let the client forward the key events.
     */
    IMStartForwarding(icp);

    return 0;
}

void
IMStopConversion(icp)
IMIC *icp;
{
    TRACE(("IMStopConversion()\n"));

    if (!(icp->state & IC_CONVERTING)) return;

    /*
     * Terminate conversion.
     */
    CControlEndConversion(icp->conversion);

    if (icp->common_attr.input_style & XIMPreeditCallbacks)
	preeditDone(icp);
    IMStatusDone(icp);

    /*
     * Detach converter.
     */
    detachConverter(icp);

    /*
     * Stop forwarding key events unless this IC is being destroyed.
     */
    if (!(icp->state & IC_DESTROYING)) {
	IMStopForwarding(icp);
    }

    icp->state &= ~IC_CONVERTING;
}

int
IMResetIC(icp, preedit_strp)
IMIC *icp;
char **preedit_strp;
{
    int num_bytes = 0;

    TRACE(("IMResetIC()\n"));

    *preedit_strp = NULL;

    if (icp->state & IC_CONVERTING) {
	/*
	 * get input object by asking conversion widget of XtNinputObject
	 * resource. however, it is not recommended since protocol widget
	 * should interact with input object only through conversion
	 * widget.
	 */
	CCTextCallbackArg arg;
	Widget input_obj;
	Widget w = icp->im->connection->proto_widget;

	XtVaGetValues(icp->conversion, XtNinputObject, &input_obj, NULL);
	arg.encoding = IMCtextAtom(w);
#ifdef notdef
	if (ICGetConvertedString(input_obj, &arg.encoding, &arg.format,
				 &arg.length, &arg.text) >= 0) {
	    num_bytes = arg.length;
	    *preedit_strp = (char *)arg.text;
	}
#else
	/*
	 * Canna seems to have some problem with ICGetConvertedString().
	 * Use ICGetPreeditString instead.
	 */
	if (ICGetPreeditString(input_obj, 0, 0, &arg.encoding, &arg.format,
			       &arg.length, &arg.text) >= 0) {
	    num_bytes = arg.length;
	    *preedit_strp = (char *)arg.text;
	}
#endif
	ICClearConversion(input_obj);
	TRACE(("\twas converting. %d bytes left\n", num_bytes));

	if (icp->common_attr.reset_state == XIMInitialState) {
	    /* Force to end the conversion. */
	    TRACE(("\tback to the initial state\n"));
	    IMStopConversion(icp);
	}
    }
    return num_bytes;
}

void
IMForwardEvent(icp, ev)
IMIC *icp;
XEvent *ev;
{
    TRACE(("IMForwardEvent()\n"));

    if (icp->conversion == NULL) return;
    XtCallActionProc(icp->conversion, "to-inputobj", ev,
		     (String *)NULL, (Cardinal)0);
}

/* ARGSUSED */
void
IMSetFocus(icp)
IMIC *icp;
{
    TRACE(("IMSetFocus(ic%d)\n", icp->id));
    if (icp->conversion != NULL) {
	CControlChangeFocus(icp->conversion, 1);
    }
}

/* ARGSUSED */
void
IMUnsetFocus(icp)
IMIC *icp;
{
    TRACE(("IMUnsetFocus(ic%d)\n", icp->id));
    if (icp->conversion != NULL) {
	CControlChangeFocus(icp->conversion, 0);
    }
}

/* ARGSUSED */
void
IMStatusStart(icp)
IMIC *icp;
{
    TRACE(("IMStatusStart(ic%d)\n", icp->id));
    if (!(icp->common_attr.input_style & XIMStatusCallbacks))
	return;
    statusStart(icp);
}

/* ARGSUSED */
void
IMStatusDone(icp)
IMIC *icp;
{
    TRACE(("IMStatusDone(ic%d)\n", icp->id));
    if (!(icp->common_attr.input_style & XIMStatusCallbacks))
	return;
    statusDone(icp);
}

void
IMStartForwarding(icp)
IMIC *icp;
{
    /*
     * Make the client forward key events to us.
     */
    TRACE(("IMStartForwarding(ic%d)\n", icp->id));

#define FORWARD_MASK (KeyPressMask|KeyReleaseMask)

#ifdef notdef
    if (synchronous) {
	setEventMask(icp, FORWARD_MASK, FORWARD_MASK);
    } else {
	setEventMask(icp, FORWARD_MASK, NoEventMask);
    }
#else
    /* using full-synchronous method */
    setEventMask(icp, FORWARD_MASK, FORWARD_MASK);
#endif

#undef FORWARD_MASK
}

void
IMStopForwarding(icp)
IMIC *icp;
{
    /*
     * Make the client stop sending key events.
     */
    TRACE(("IMStopForwarding(ic%d)\n", icp->id));
    setEventMask(icp, NoEventMask, NoEventMask);
}
