/**************************************************************************** ** $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 #include #if !defined(QT_NO_XIM) #define XK_MISCELLANY #define XK_LATIN1 #include // #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 *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; 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 contexts( *ximContextList ); QPtrList::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