/****************************************************************************
** $Id: qximinputcontext_x11.cpp,v 1.10 2004/06/22 06:47:27 daisuke Exp $
**
** Implementation of QXIMInputContext class
**
** Copyright (C) 2000-2003 Trolltech AS.  All rights reserved.
**
** This file is part of the input method module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
** licenses for Unix/X11 may use this file in accordance with the Qt Commercial
** License Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/


#include "qximinputcontext.h"

const int XKeyPress = KeyPress;
const int XKeyRelease = KeyRelease;
#undef KeyPress
#undef KeyRelease

#if !defined(QT_NO_IM)

#include "qplatformdefs.h"

#include "qapplication.h"
#include "qwidget.h"
#include "qstring.h"
#include "qptrlist.h"
#include "qintdict.h"
#include "qtextcodec.h"

#include <stdlib.h>
#include <limits.h>

#if !defined(QT_NO_XIM)

#define XK_MISCELLANY
#define XK_LATIN1
#include <X11/keysymdef.h>

// #define QT_XIM_DEBUG

// from qapplication_x11.cpp
static XIM	qt_xim = 0;
extern XIMStyle	qt_xim_style;
extern XIMStyle	qt_xim_preferred_style;
extern char    *qt_ximServer;
static bool isInitXIM = FALSE;
static QPtrList<QXIMInputContext> *ximContextList = 0;
#endif
extern int qt_ximComposingKeycode;
extern QTextCodec * qt_input_mapper;


#if !defined(QT_NO_XIM)

#if defined(Q_C_CALLBACKS)
extern "C" {
#endif // Q_C_CALLBACKS

#ifdef USE_X11R6_XIM
    static void xim_create_callback(XIM /*im*/,
				    XPointer /*client_data*/,
				    XPointer /*call_data*/)
    {
	// qDebug("xim_create_callback");
	QXIMInputContext::create_xim();
    }

    static void xim_destroy_callback(XIM /*im*/,
				     XPointer /*client_data*/,
				     XPointer /*call_data*/)
    {
	// qDebug("xim_destroy_callback");
	QXIMInputContext::close_xim();
	Display *dpy = QPaintDevice::x11AppDisplay();
	XRegisterIMInstantiateCallback(dpy, 0, 0, 0,
				       (XIMProc) xim_create_callback, 0);
    }

#endif // USE_X11R6_XIM

#if defined(Q_C_CALLBACKS)
}
#endif // Q_C_CALLBACKS

#endif // QT_NO_XIM

#ifndef QT_NO_XIM

/* The cache here is needed, as X11 leaks a few kb for every
   XFreeFontSet call, so we avoid creating and deletion of fontsets as
   much as possible
*/
static XFontSet fontsetCache[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
static int fontsetRefCount = 0;

static const char * const fontsetnames[] = {
    "-*-fixed-medium-r-*-*-16-*,-*-*-medium-r-*-*-16-*",
    "-*-fixed-medium-i-*-*-16-*,-*-*-medium-i-*-*-16-*",
    "-*-fixed-bold-r-*-*-16-*,-*-*-bold-r-*-*-16-*",
    "-*-fixed-bold-i-*-*-16-*,-*-*-bold-i-*-*-16-*",
    "-*-fixed-medium-r-*-*-24-*,-*-*-medium-r-*-*-24-*",
    "-*-fixed-medium-i-*-*-24-*,-*-*-medium-i-*-*-24-*",
    "-*-fixed-bold-r-*-*-24-*,-*-*-bold-r-*-*-24-*",
    "-*-fixed-bold-i-*-*-24-*,-*-*-bold-i-*-*-24-*"
};

static XFontSet getFontSet( const QFont &f )
{
    int i = 0;
    if (f.italic())
	i |= 1;
    if (f.bold())
	i |= 2;

    if ( f.pointSize() > 20 )
	i += 4;

    if ( !fontsetCache[i] ) {
	Display* dpy = QPaintDevice::x11AppDisplay();
	int missCount;
	char** missList;
	fontsetCache[i] = XCreateFontSet(dpy, fontsetnames[i], &missList, &missCount, 0);
	if(missCount > 0)
	    XFreeStringList(missList);
	if ( !fontsetCache[i] ) {
	    fontsetCache[i] = XCreateFontSet(dpy,  "-*-fixed-*-*-*-*-16-*", &missList, &missCount, 0);
	    if(missCount > 0)
		XFreeStringList(missList);
	    if ( !fontsetCache[i] )
		fontsetCache[i] = (XFontSet)-1;
	}
    }
    return (fontsetCache[i] == (XFontSet)-1) ? 0 : fontsetCache[i];
}


#ifdef Q_C_CALLBACKS
extern "C" {
#endif // Q_C_CALLBACKS

    // These static functions should be rewritten as member of
    // QXIMInputContext

    static int xic_start_callback(XIC, XPointer client_data, XPointer) {
	QXIMInputContext *qic = (QXIMInputContext *) client_data;
	if (! qic) {
#ifdef QT_XIM_DEBUG
	    qDebug("compose start: no qic");
#endif // QT_XIM_DEBUG

	    return 0;
	}

	qic->resetClientState();
	qic->sendIMEvent( QEvent::IMStart );

#ifdef QT_XIM_DEBUG
	qDebug("compose start");
#endif // QT_XIM_DEBUG

	return 0;
    }

    static int xic_draw_callback(XIC, XPointer client_data, XPointer call_data) {
	QXIMInputContext *qic = (QXIMInputContext *) client_data;
	if (! qic) {
#ifdef QT_XIM_DEBUG
	    qDebug("compose event: invalid compose event %p", qic);
#endif // QT_XIM_DEBUG

	    return 0;
	}

	bool send_imstart = FALSE;
	if( ! qic->isComposing() && qic->hasFocus() ) {
	    qic->resetClientState();
	    send_imstart = TRUE;
	} else if ( ! qic->isComposing() || ! qic->hasFocus() ) {
#ifdef QT_XIM_DEBUG
	    qDebug( "compose event: invalid compose event composing=%d hasFocus=%d",
		    qic->isComposing(), qic->hasFocus() );
#endif // QT_XIM_DEBUG

	    return 0;
	}

	if ( send_imstart )
	    qic->sendIMEvent( QEvent::IMStart );

	XIMPreeditDrawCallbackStruct *drawstruct =
	    (XIMPreeditDrawCallbackStruct *) call_data;
	XIMText *text = (XIMText *) drawstruct->text;
	int cursor = drawstruct->caret, sellen = 0;

	if ( ! drawstruct->caret && ! drawstruct->chg_first &&
	     ! drawstruct->chg_length && ! text ) {
	    if( qic->composingText.isEmpty() ) {
#ifdef QT_XIM_DEBUG
		qDebug( "compose emptied" );
#endif // QT_XIM_DEBUG
		// if the composition string has been emptied, we need
		// to send an IMEnd event
		qic->sendIMEvent( QEvent::IMEnd );
		qic->resetClientState();
		// if the commit string has coming after here, IMStart
		// will be sent dynamically
	    }
	    return 0;
	}

	if (text) {
	    char *str = 0;
	    if (text->encoding_is_wchar) {
		int l = wcstombs(NULL, text->string.wide_char, text->length);
		if (l != -1) {
		    str = new char[l + 1];
		    wcstombs(str, text->string.wide_char, l);
		    str[l] = 0;
		}
	    } else
		str = text->string.multi_byte;

	    if (! str)
		return 0;

	    QString s = QString::fromLocal8Bit(str);

	    if (text->encoding_is_wchar)
		delete [] str;

	    if (drawstruct->chg_length < 0)
		qic->composingText.replace(drawstruct->chg_first, UINT_MAX, s);
	    else
		qic->composingText.replace(drawstruct->chg_first, drawstruct->chg_length, s);

	    if ( qic->selectedChars.size() < qic->composingText.length() ) {
		// expand the selectedChars array if the compose string is longer
		uint from = qic->selectedChars.size();
		qic->selectedChars.resize( qic->composingText.length() );
		for ( uint x = from; from < qic->selectedChars.size(); ++x )
		    qic->selectedChars[x] = 0;
	    }

	    uint x;
	    bool *p = qic->selectedChars.data() + drawstruct->chg_first;
	    // determine if the changed chars are selected based on text->feedback
	    for ( x = 0; x < s.length(); ++x )
		*p++ = ( text->feedback ? ( text->feedback[x] & XIMReverse ) : 0 );

	    // figure out where the selection starts, and how long it is
	    p = qic->selectedChars.data();
	    bool started = FALSE;
	    for ( x = 0; x < QMIN(qic->composingText.length(), qic->selectedChars.size()); ++x ) {
		if ( started ) {
		    if ( *p ) ++sellen;
		    else break;
		} else {
		    if ( *p ) {
			cursor = x;
			started = TRUE;
			sellen = 1;
		    }
		}
		++p;
	    }
	} else {
	    if (drawstruct->chg_length == 0)
		drawstruct->chg_length = -1;

	    qic->composingText.remove(drawstruct->chg_first, drawstruct->chg_length);
	    bool qt_compose_emptied = qic->composingText.isEmpty();
	    if ( qt_compose_emptied ) {
#ifdef QT_XIM_DEBUG
		qDebug( "compose emptied" );
#endif // QT_XIM_DEBUG
		// if the composition string has been emptied, we need
		// to send an IMEnd event
		qic->sendIMEvent( QEvent::IMEnd );
		qic->resetClientState();
		// if the commit string has coming after here, IMStart
		// will be sent dynamically
		return 0;
	    }
	}

	qic->sendIMEvent( QEvent::IMCompose,
			  qic->composingText, cursor, sellen );

	return 0;
    }

    static int xic_done_callback(XIC, XPointer client_data, XPointer) {
	QXIMInputContext *qic = (QXIMInputContext *) client_data;
	if (! qic)
	    return 0;

	// Don't send IMEnd here. QXIMInputContext::x11FilterEvent()
	// handles IMEnd with commit string.
#if 0
	if ( qic->isComposing() )
	    qic->sendIMEvent( QEvent::IMEnd );
	qic->resetClientState();
#endif

	return 0;
    }

#ifdef Q_C_CALLBACKS
}
#endif // Q_C_CALLBACKS

#endif // !QT_NO_XIM



QXIMInputContext::QXIMInputContext()
    : QInputContext(), ic(0), fontset(0)
{
    if(!isInitXIM)
	QXIMInputContext::init_xim();
}


void QXIMInputContext::setHolderWidget( QWidget *widget )
{
    if ( ! widget )
	return;

    QInputContext::setHolderWidget( widget );

#if !defined(QT_NO_XIM)
    fontsetRefCount++;
    if (! qt_xim) {
	// qWarning("QInputContext: no input method context available");
	return;
    }

    if (! widget->isTopLevel()) {
	qWarning("QInputContext: cannot create input context for non-toplevel widgets");
	return;
    }

    XPoint spot;
    XRectangle rect;
    XVaNestedList preedit_attr = 0;
    XIMCallback startcallback, drawcallback, donecallback;

    font = widget->font();
    fontset = getFontSet( font );

    if (qt_xim_style & XIMPreeditArea) {
	rect.x = 0;
	rect.y = 0;
	rect.width = widget->width();
	rect.height = widget->height();

	preedit_attr = XVaCreateNestedList(0,
					   XNArea, &rect,
					   XNFontSet, fontset,
					   (char *) 0);
    } else if (qt_xim_style & XIMPreeditPosition) {
	spot.x = 1;
	spot.y = 1;

	preedit_attr = XVaCreateNestedList(0,
					   XNSpotLocation, &spot,
					   XNFontSet, fontset,
					   (char *) 0);
    } else if (qt_xim_style & XIMPreeditCallbacks) {
	startcallback.client_data = (XPointer) this;
	startcallback.callback = (XIMProc) xic_start_callback;
	drawcallback.client_data = (XPointer) this;
	drawcallback.callback = (XIMProc)xic_draw_callback;
	donecallback.client_data = (XPointer) this;
	donecallback.callback = (XIMProc) xic_done_callback;

	preedit_attr = XVaCreateNestedList(0,
					   XNPreeditStartCallback, &startcallback,
					   XNPreeditDrawCallback, &drawcallback,
					   XNPreeditDoneCallback, &donecallback,
					   (char *) 0);
    }

    if (preedit_attr) {
	ic = XCreateIC(qt_xim,
		       XNInputStyle, qt_xim_style,
		       XNClientWindow, widget->winId(),
		       XNPreeditAttributes, preedit_attr,
		       (char *) 0);
	XFree(preedit_attr);
    } else
	ic = XCreateIC(qt_xim,
		       XNInputStyle, qt_xim_style,
		       XNClientWindow, widget->winId(),
		       (char *) 0);

    if (! ic)
	qFatal("Failed to create XIM input context!");

    // when resetting the input context, preserve the input state
    (void) XSetICValues((XIC) ic, XNResetState, XIMPreserveState, (char *) 0);

    if( ! ximContextList )
	ximContextList = new QPtrList<QXIMInputContext>;
    ximContextList->append( this );
#endif // !QT_NO_XIM
}


QXIMInputContext::~QXIMInputContext()
{

#if !defined(QT_NO_XIM)
    if (ic)
	XDestroyIC((XIC) ic);

    if ( --fontsetRefCount == 0 ) {
	Display *dpy = QPaintDevice::x11AppDisplay();
	for ( int i = 0; i < 8; i++ ) {
	    if ( fontsetCache[i] && fontsetCache[i] != (XFontSet)-1 ) {
		XFreeFontSet(dpy, fontsetCache[i]);
		fontsetCache[i] = 0;
	    }
	}
    }

    if( ximContextList ) {
	ximContextList->remove( this );
	if(ximContextList->isEmpty()) {
	    // Calling XCloseIM gives a Purify FMR error
	    // XCloseIM( qt_xim );
	    // We prefer a less serious memory leak
	    if( qt_xim ) {
		qt_xim = 0;
		isInitXIM = FALSE;
	    }

	    delete ximContextList;
	    ximContextList = 0;
	}
    }
#endif // !QT_NO_XIM

    ic = 0;
}

void QXIMInputContext::init_xim()
{
#ifndef QT_NO_XIM
    if(!isInitXIM)
	isInitXIM = TRUE;

    qt_xim = 0;
    QString ximServerName(qt_ximServer);
    if (qt_ximServer)
	ximServerName.prepend("@im=");
    else
	ximServerName = "";

    if ( !XSupportsLocale() )
	qWarning("Qt: Locales not supported on X server");

#ifdef USE_X11R6_XIM
    else if ( XSetLocaleModifiers (ximServerName.ascii()) == 0 )
	qWarning( "Qt: Cannot set locale modifiers: %s",
		  ximServerName.ascii());
    else {
	Display *dpy = QPaintDevice::x11AppDisplay();
	XWindowAttributes attr; // XIM unselects all events on the root window
	XGetWindowAttributes( dpy, QPaintDevice::x11AppRootWindow(),&attr );
	XRegisterIMInstantiateCallback(dpy, 0, 0, 0,
				       (XIMProc) xim_create_callback, 0);
	XSelectInput( dpy, QPaintDevice::x11AppRootWindow(), attr.your_event_mask );
    }
#else // !USE_X11R6_XIM
    else if ( XSetLocaleModifiers ("") == 0 )
	qWarning("Qt: Cannot set locale modifiers");
    else
	QXIMInputContext::create_xim();
#endif // USE_X11R6_XIM
#endif // QT_NO_XIM
}


/*! \internal
  Creates the application input method.
 */
void QXIMInputContext::create_xim()
{
#ifndef QT_NO_XIM
    Display *appDpy = QPaintDevice::x11AppDisplay();
    qt_xim = XOpenIM( appDpy, 0, 0, 0 );
    if ( qt_xim ) {

#ifdef USE_X11R6_XIM
	XIMCallback destroy;
	destroy.callback = (XIMProc) xim_destroy_callback;
	destroy.client_data = 0;
	if ( XSetIMValues( qt_xim, XNDestroyCallback, &destroy, (char *) 0 ) != 0 )
	    qWarning( "Xlib doesn't support destroy callback");
#endif // USE_X11R6_XIM

	XIMStyles *styles = 0;
	XGetIMValues(qt_xim, XNQueryInputStyle, &styles, (char *) 0, (char *) 0);
	if ( styles ) {
	    int i;
	    for ( i = 0; !qt_xim_style && i < styles->count_styles; i++ ) {
		if ( styles->supported_styles[i] == qt_xim_preferred_style ) {
		    qt_xim_style = qt_xim_preferred_style;
		    break;
		}
	    }
	    // if the preferred input style couldn't be found, look for
	    // Nothing
	    for ( i = 0; !qt_xim_style && i < styles->count_styles; i++ ) {
		if ( styles->supported_styles[i] == (XIMPreeditNothing |
						     XIMStatusNothing) ) {
		    qt_xim_style = XIMPreeditNothing | XIMStatusNothing;
		    break;
		}
	    }
	    // ... and failing that, None.
	    for ( i = 0; !qt_xim_style && i < styles->count_styles; i++ ) {
		if ( styles->supported_styles[i] == (XIMPreeditNone |
						     XIMStatusNone) ) {
		    qt_xim_style = XIMPreeditNone | XIMStatusNone;
		    break;
		}
	    }

	    // qDebug("QApplication: using im style %lx", qt_xim_style);
	    XFree( (char *)styles );
	}

	if ( qt_xim_style ) {

#ifdef USE_X11R6_XIM
	    XUnregisterIMInstantiateCallback(appDpy, 0, 0, 0,
					     (XIMProc) xim_create_callback, 0);
#endif // USE_X11R6_XIM

	} else {
	    // Give up
	    qWarning( "No supported input style found."
		      "  See InputMethod documentation.");
	    QXIMInputContext::close_xim();
	}
    }
#endif // QT_NO_XIM
}


/*! \internal
  Closes the application input method.
*/
void QXIMInputContext::close_xim()
{
#ifndef QT_NO_XIM
    QString errMsg( "QXIMInputContext::close_xim() has been called" );

    // Calling XCloseIM gives a Purify FMR error
    // XCloseIM( qt_xim );
    // We prefer a less serious memory leak

    qt_xim = 0;
    if( ximContextList ) {
	QPtrList<QXIMInputContext> contexts( *ximContextList );
	QPtrList<QXIMInputContext>::Iterator it = contexts.begin();
	while( it != contexts.end() ) {
	    (*it)->close( errMsg );
	    ++it;
	}
	// ximContextList will be deleted in ~QXIMInputContext
    }
#endif // QT_NO_XIM
}


bool QXIMInputContext::x11FilterEvent( QWidget *keywidget, XEvent *event )
{
#ifndef QT_NO_XIM
    int xkey_keycode = event->xkey.keycode;
    if ( XFilterEvent( event, keywidget->topLevelWidget()->winId() ) ) {
	qt_ximComposingKeycode = xkey_keycode; // ### not documented in xlib

	// Cancel of the composition is realizable even if
	// follwing codes don't exist
#if 0
	if ( event->type != XKeyPress || ! (qt_xim_style & XIMPreeditCallbacks) )
	    return TRUE;

	/*
	 * The Solaris htt input method will transform a ClientMessage
	 * event into a filtered KeyPress event, in which case our
	 * keywidget is still zero.
	 */
	QETWidget *widget = (QETWidget*)QWidget::find( (WId)event->xany.window );
        if ( ! keywidget ) {
 	    keywidget = (QETWidget*)QWidget::keyboardGrabber();
	    if ( keywidget ) {
	        grabbed = TRUE;
	    } else {
	        if ( focus_widget )
		    keywidget = (QETWidget*)focus_widget;
	        if ( !keywidget ) {
		    if ( qApp->inPopupMode() ) // no focus widget, see if we have a popup
		        keywidget = (QETWidget*) qApp->activePopupWidget();
		    else if ( widget )
		        keywidget = (QETWidget*)widget->topLevelWidget();
	        }
	    }
        }

	/*
	  if the composition string has been emptied, we need to send
	  an IMEnd event.  however, we have no way to tell if the user
	  has cancelled input, or if the user has accepted the
	  composition.

	  so, we have to look for the next keypress and see if it is
	  the 'commit' key press (keycode == 0).  if it is, we deliver
	  an IMEnd event with the final text, otherwise we deliver an
	  IMEnd with empty text (meaning the user has cancelled the
	  input).
	*/
	if ( composing && focusWidget && qt_compose_emptied ) {
	    XEvent event2;
	    bool found = FALSE;
	    if ( XCheckTypedEvent( QPaintDevice::x11AppDisplay(),
				   XKeyPress, &event2 ) ) {
		if ( event2.xkey.keycode == 0 ) {
		    // found a key event with the 'commit' string
		    found = TRUE;
		    XPutBackEvent( QPaintDevice::x11AppDisplay(), &event2 );
		}
	    }

	    if ( !found ) {
		// no key event, so the user must have cancelled the composition
		QIMEvent endevent( QEvent::IMEnd, QString::null, -1 );
		QApplication::sendEvent( focusWidget, &endevent );

		focusWidget = 0;
	    }

	    qt_compose_emptied = FALSE;
	}
#endif
	return TRUE;
    } else if ( focusWidget() ) {
        if ( event->type == XKeyPress && event->xkey.keycode == 0 ) {
	    // input method has sent us a commit string
	    QCString data(513);
	    KeySym sym;    // unused
	    Status status; // unused
	    QString inputText;
	    int count = lookupString( &(event->xkey), data, &sym, &status );
	    if ( count > 0 )
	        inputText = qt_input_mapper->toUnicode( data, count );

	    if ( ! ( qt_xim_style & XIMPreeditCallbacks ) || ! isComposing() ) {
		// there is no composing state
		sendIMEvent( QEvent::IMStart );
	    }

	    sendIMEvent( QEvent::IMEnd, inputText );
	    resetClientState();

	    return TRUE;
	}
    }
#endif // !QT_NO_XIM

    return FALSE;
}


void QXIMInputContext::sendIMEvent( QEvent::Type type, const QString &text,
				    int cursorPosition, int selLength )
{
    QInputContext::sendIMEvent( type, text, cursorPosition, selLength );
    if ( type == QEvent::IMCompose )
	composingText = text;
}


void QXIMInputContext::reset()
{
#if !defined(QT_NO_XIM)
    if ( focusWidget() && isComposing() && ! composingText.isNull() ) {
#ifdef QT_XIM_DEBUG
	qDebug("QXIMInputContext::reset: composing - sending IMEnd (empty) to %p",
	       focusWidget() );
#endif // QT_XIM_DEBUG

	QInputContext::reset();
	resetClientState();

	char *mb = XmbResetIC((XIC) ic);
	if (mb)
	    XFree(mb);
    }
#endif // !QT_NO_XIM
}


void QXIMInputContext::resetClientState()
{
#if !defined(QT_NO_XIM)
    composingText = QString::null;
    if ( selectedChars.size() < 128 )
	selectedChars.resize( 128 );
    selectedChars.fill( 0 );
#endif // !QT_NO_XIM
}


void QXIMInputContext::close( const QString &errMsg )
{
    // qDebug( errMsg );
    emit deletionRequested();
}


bool QXIMInputContext::hasFocus() const
{
    return ( focusWidget() != 0 );
}


void QXIMInputContext::setMicroFocus(int x, int y, int, int h, QFont *f)
{
    QWidget *widget = focusWidget();
    if ( qt_xim && widget ) {
	QPoint p( x, y );
	QPoint p2 = widget->mapTo( widget->topLevelWidget(), QPoint( 0, 0 ) );
	p = widget->topLevelWidget()->mapFromGlobal( p );
	setXFontSet( f ? *f : widget->font() );
	setComposePosition(p.x(), p.y() + h);
	setComposeArea(p2.x(), p2.y(), widget->width(), widget->height());
    }

}

void QXIMInputContext::mouseHandler( int , QEvent::Type type,
				     Qt::ButtonState button,
				     Qt::ButtonState)
{
    if ( type == QEvent::MouseButtonPress ||
	 type == QEvent::MouseButtonDblClick ) {
	// Don't reset Japanese input context here. Japanese input
	// context sometimes contains a whole paragraph and has
	// minutes of lifetime different to ephemeral one in other
	// languages. The input context should be survived until
	// focused again.
	if ( ! isPreeditPreservationEnabled() )
	    reset();
    }
}

void QXIMInputContext::setComposePosition(int x, int y)
{
#if !defined(QT_NO_XIM)
    if (qt_xim && ic) {
	XPoint point;
	point.x = x;
	point.y = y;

	XVaNestedList preedit_attr =
	    XVaCreateNestedList(0,
				XNSpotLocation, &point,

				(char *) 0);
	XSetICValues((XIC) ic, XNPreeditAttributes, preedit_attr, (char *) 0);
	XFree(preedit_attr);
    }
#endif // !QT_NO_XIM
}


void QXIMInputContext::setComposeArea(int x, int y, int w, int h)
{
#if !defined(QT_NO_XIM)
    if (qt_xim && ic) {
	XRectangle rect;
	rect.x = x;
	rect.y = y;
	rect.width = w;
	rect.height = h;

	XVaNestedList preedit_attr = XVaCreateNestedList(0,
							 XNArea, &rect,

							 (char *) 0);
	XSetICValues((XIC) ic, XNPreeditAttributes, preedit_attr, (char *) 0);
	XFree(preedit_attr);
    }
#endif
}


void QXIMInputContext::setXFontSet(const QFont &f)
{
#if !defined(QT_NO_XIM)
    if (font == f) return; // nothing to do
    font = f;

    XFontSet fs = getFontSet(font);
    if (fontset == fs) return; // nothing to do
    fontset = fs;

    XVaNestedList preedit_attr = XVaCreateNestedList(0, XNFontSet, fontset, (char *) 0);
    XSetICValues((XIC) ic, XNPreeditAttributes, preedit_attr, (char *) 0);
    XFree(preedit_attr);
#else
    Q_UNUSED( f );
#endif
}


int QXIMInputContext::lookupString(XKeyEvent *event, QCString &chars,
				KeySym *key, Status *status) const
{
    int count = 0;

#if !defined(QT_NO_XIM)
    if (qt_xim && ic) {
	count = XmbLookupString((XIC) ic, event, chars.data(),
				chars.size(), key, status);

	if ((*status) == XBufferOverflow ) {
	    chars.resize(count + 1);
	    count = XmbLookupString((XIC) ic, event, chars.data(),
				    chars.size(), key, status);
	}
    }

#endif // QT_NO_XIM

    return count;
}

void QXIMInputContext::setFocus()
{
#if !defined(QT_NO_XIM)
    if ( qt_xim && ic )
	XSetICFocus((XIC) ic);
#endif // !QT_NO_XIM
}

void QXIMInputContext::unsetFocus()
{
#if !defined(QT_NO_XIM)
    if (qt_xim && ic)
	XUnsetICFocus((XIC) ic);
#endif // !QT_NO_XIM

    // Don't reset Japanese input context here. Japanese input context
    // sometimes contains a whole paragraph and has minutes of
    // lifetime different to ephemeral one in other languages. The
    // input context should be survived until focused again.
    if ( ! isPreeditPreservationEnabled() )
	reset();
}


bool QXIMInputContext::isPreeditRelocationEnabled()
{
    return ( language() == "ja" );
}


bool QXIMInputContext::isPreeditPreservationEnabled()
{
    return ( language() == "ja" );
}


QString QXIMInputContext::identifierName()
{
    // the name should be "xim" rather than "XIM" to be consistent
    // with corresponding immodule of GTK+
    return "xim";
}


QString QXIMInputContext::language()
{
#if !defined(QT_NO_XIM)
    if ( qt_xim ) {
	QString locale( XLocaleOfIM( qt_xim ) );

	if ( locale.startsWith( "zh" ) ) {
	    // Chinese language should be formed as "zh_CN", "zh_TW", "zh_HK"
	    _language = locale.left( 5 );
	} else {
	    // other languages should be two-letter ISO 639 language code
	    _language = locale.left( 2 );
	}
    }
#endif
    return _language;
}

#endif //QT_NO_IM
