blob: 100a6c0f52d77335350e64c2f5a9c6389ca34d77 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#include <stdio.h>
#include <sal/alloca.h>
#include <osl/thread.h>
#include <tools/prex.h>
#include <X11/Xlocale.h>
#include <X11/Xlib.h>
#include <tools/postx.h>
#include <unx/salunx.h>
#include <unx/XIM.h>
#include <unx/i18n_ic.hxx>
#include <unx/i18n_im.hxx>
#include <unx/i18n_status.hxx>
#include <unx/salframe.h>
#include <unx/saldata.hxx>
#include <unx/saldisp.hxx>
using namespace vcl;
static void sendEmptyCommit( SalFrame* pFrame )
{
vcl::DeletionListener aDel( pFrame );
SalExtTextInputEvent aEmptyEv;
aEmptyEv.mnTime = 0;
aEmptyEv.mpTextAttr = 0;
aEmptyEv.maText = String();
aEmptyEv.mnCursorPos = 0;
aEmptyEv.mnCursorFlags = 0;
aEmptyEv.mnDeltaStart = 0;
aEmptyEv.mbOnlyCursor = False;
pFrame->CallCallback( SALEVENT_EXTTEXTINPUT, (void*)&aEmptyEv );
if( ! aDel.isDeleted() )
pFrame->CallCallback( SALEVENT_ENDEXTTEXTINPUT, NULL );
}
// ---------------------------------------------------------------------------
//
// Constructor / Destructor, the InputContext is bound to the SalFrame, as it
// needs the shell window as a focus window
//
// ----------------------------------------------------------------------------
SalI18N_InputContext::~SalI18N_InputContext()
{
if ( maContext != NULL )
XDestroyIC( maContext );
if ( mpAttributes != NULL )
XFree( mpAttributes );
if ( mpStatusAttributes != NULL )
XFree( mpStatusAttributes );
if ( mpPreeditAttributes != NULL )
XFree( mpPreeditAttributes );
if (maClientData.aText.pUnicodeBuffer != NULL)
free(maClientData.aText.pUnicodeBuffer);
if (maClientData.aText.pCharStyle != NULL)
free(maClientData.aText.pCharStyle);
}
// ----------------------------------------------------------------------------
// convenience routine to add items to a XVaNestedList
// ----------------------------------------------------------------------------
static XVaNestedList
XVaAddToNestedList( XVaNestedList a_srclist, char* name, XPointer value )
{
XVaNestedList a_dstlist;
// if ( value == NULL )
// return a_srclist;
if ( a_srclist == NULL )
{
a_dstlist = XVaCreateNestedList(
0,
name, value,
NULL );
}
else
{
a_dstlist = XVaCreateNestedList(
0,
XNVaNestedList, a_srclist,
name, value,
NULL );
}
return a_dstlist != NULL ? a_dstlist : a_srclist ;
}
// ----------------------------------------------------------------------------
// convenience routine to create a fontset
// ----------------------------------------------------------------------------
static XFontSet
get_font_set( Display *p_display )
{
static XFontSet p_font_set = NULL;
if (p_font_set == NULL)
{
char **pp_missing_list;
int n_missing_count;
char *p_default_string;
p_font_set = XCreateFontSet(p_display, "-*",
&pp_missing_list, &n_missing_count, &p_default_string);
}
return p_font_set;
}
// ---------------------------------------------------------------------------
//
// Constructor for a InputContext (IC)
//
// ----------------------------------------------------------------------------
SalI18N_InputContext::SalI18N_InputContext ( SalFrame *pFrame ) :
mbUseable( True ),
maContext( (XIC)NULL ),
mnSupportedStatusStyle(
XIMStatusCallbacks |
XIMStatusNothing |
XIMStatusNone
),
mnSupportedPreeditStyle(
XIMPreeditCallbacks |
XIMPreeditNothing |
XIMPreeditNone
),
mnStatusStyle( 0 ),
mnPreeditStyle( 0 ),
mpAttributes( NULL ),
mpStatusAttributes( NULL ),
mpPreeditAttributes( NULL )
{
#ifdef SOLARIS
static const char* pIIIMPEnable = getenv( "SAL_DISABLE_OWN_IM_STATUS" );
if( pIIIMPEnable && *pIIIMPEnable )
mnSupportedStatusStyle &= ~XIMStatusCallbacks;
#endif
maClientData.aText.pUnicodeBuffer = NULL;
maClientData.aText.pCharStyle = NULL;
maClientData.aInputEv.mnTime = 0;
maClientData.aInputEv.mpTextAttr = NULL;
maClientData.aInputEv.mnCursorPos = 0;
maClientData.aInputEv.mnDeltaStart = 0;
maClientData.aInputEv.mnCursorFlags = 0;
maClientData.aInputEv.mbOnlyCursor = sal_False;
SalI18N_InputMethod *pInputMethod;
pInputMethod = GetX11SalData()->GetDisplay()->GetInputMethod();
mbMultiLingual = pInputMethod->IsMultiLingual();
mnSupportedPreeditStyle = XIMPreeditCallbacks | XIMPreeditPosition
| XIMPreeditNothing | XIMPreeditNone;
if (pInputMethod->UseMethod()
&& SupportInputMethodStyle( pInputMethod->GetSupportedStyles() ) )
{
const SystemEnvData* pEnv = pFrame->GetSystemData();
XLIB_Window aClientWindow = pEnv->aShellWindow;
XLIB_Window aFocusWindow = pEnv->aWindow;
// for status callbacks and commit string callbacks
#define PREEDIT_BUFSZ 16
maClientData.bIsMultilingual = mbMultiLingual;
maClientData.eState = ePreeditStatusStartPending;
maClientData.pFrame = pFrame;
maClientData.aText.pUnicodeBuffer =
(sal_Unicode*)malloc(PREEDIT_BUFSZ * sizeof(sal_Unicode));
maClientData.aText.pCharStyle =
(XIMFeedback*)malloc(PREEDIT_BUFSZ * sizeof(XIMFeedback));;
maClientData.aText.nSize = PREEDIT_BUFSZ;
maClientData.aText.nCursorPos = 0;
maClientData.aText.nLength = 0;
//
// Status attributes
//
switch ( mnStatusStyle )
{
case XIMStatusCallbacks:
{
static XIMCallback aStatusStartCallback;
static XIMCallback aStatusDoneCallback;
static XIMCallback aStatusDrawCallback;
aStatusStartCallback.callback = (XIMProc)StatusStartCallback;
aStatusStartCallback.client_data = (XPointer)&maClientData;
aStatusDoneCallback.callback = (XIMProc)StatusDoneCallback;
aStatusDoneCallback.client_data = (XPointer)&maClientData;
aStatusDrawCallback.callback = (XIMProc)StatusDrawCallback;
aStatusDrawCallback.client_data = (XPointer)&maClientData;
mpStatusAttributes = XVaCreateNestedList (
0,
XNStatusStartCallback, &aStatusStartCallback,
XNStatusDoneCallback, &aStatusDoneCallback,
XNStatusDrawCallback, &aStatusDrawCallback,
NULL );
break;
}
case XIMStatusArea:
/* not supported */
break;
case XIMStatusNone:
case XIMStatusNothing:
default:
/* no arguments needed */
break;
}
//
// set preedit attributes
//
switch ( mnPreeditStyle )
{
case XIMPreeditCallbacks:
maPreeditCaretCallback.callback = (XIMProc)PreeditCaretCallback;
maPreeditStartCallback.callback = (XIMProc)PreeditStartCallback;
maPreeditDoneCallback.callback = (XIMProc)PreeditDoneCallback;
maPreeditDrawCallback.callback = (XIMProc)PreeditDrawCallback;
maPreeditCaretCallback.client_data = (XPointer)&maClientData;
maPreeditStartCallback.client_data = (XPointer)&maClientData;
maPreeditDoneCallback.client_data = (XPointer)&maClientData;
maPreeditDrawCallback.client_data = (XPointer)&maClientData;
mpPreeditAttributes = XVaCreateNestedList (
0,
XNPreeditStartCallback, &maPreeditStartCallback,
XNPreeditDoneCallback, &maPreeditDoneCallback,
XNPreeditDrawCallback, &maPreeditDrawCallback,
XNPreeditCaretCallback, &maPreeditCaretCallback,
NULL );
break;
case XIMPreeditArea:
/* not supported */
break;
case XIMPreeditPosition:
{
// spot location
SalExtTextInputPosEvent aPosEvent;
pFrame->CallCallback(SALEVENT_EXTTEXTINPUTPOS, (void*)&aPosEvent);
static XPoint aSpot;
aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
// create attributes for preedit position style
mpPreeditAttributes = XVaCreateNestedList (
0,
XNSpotLocation, &aSpot,
NULL );
// XCreateIC() fails on Redflag Linux 2.0 if there is no
// fontset though the data itself is not evaluated nor is
// it required according to the X specs.
Display* pDisplay = GetX11SalData()->GetDisplay()->GetDisplay();
XFontSet pFontSet = get_font_set(pDisplay);
if (pFontSet != NULL)
{
mpPreeditAttributes = XVaAddToNestedList( mpPreeditAttributes,
const_cast<char*>(XNFontSet), (XPointer)pFontSet);
}
break;
}
case XIMPreeditNone:
case XIMPreeditNothing:
default:
/* no arguments needed */
break;
}
// Create the InputContext by giving it exactly the information it
// deserves, because inappropriate attributes
// let XCreateIC fail on Solaris (eg. for C locale)
mpAttributes = XVaCreateNestedList(
0,
XNFocusWindow, aFocusWindow,
XNClientWindow, aClientWindow,
XNInputStyle, mnPreeditStyle | mnStatusStyle,
NULL );
if ( mnPreeditStyle != XIMPreeditNone )
{
#if defined LINUX || defined FREEBSD || defined NETBSD
if ( mpPreeditAttributes != NULL )
#endif
mpAttributes = XVaAddToNestedList( mpAttributes,
const_cast<char*>(XNPreeditAttributes), (XPointer)mpPreeditAttributes );
}
if ( mnStatusStyle != XIMStatusNone )
{
#if defined LINUX || defined FREEBSD || defined NETBSD
if ( mpStatusAttributes != NULL )
#endif
mpAttributes = XVaAddToNestedList( mpAttributes,
const_cast<char*>(XNStatusAttributes), (XPointer)mpStatusAttributes );
}
maContext = XCreateIC( pInputMethod->GetMethod(),
XNVaNestedList, mpAttributes,
NULL );
}
if ( maContext == NULL )
{
#if OSL_DEBUG_LEVEL > 1
fprintf(stderr, "input context creation failed\n");
#endif
mbUseable = False;
mbMultiLingual = False;
if ( mpAttributes != NULL )
XFree( mpAttributes );
if ( mpStatusAttributes != NULL )
XFree( mpStatusAttributes );
if ( mpPreeditAttributes != NULL )
XFree( mpPreeditAttributes );
if ( maClientData.aText.pUnicodeBuffer != NULL )
free ( maClientData.aText.pUnicodeBuffer );
if ( maClientData.aText.pCharStyle != NULL )
free ( maClientData.aText.pCharStyle );
mpAttributes = NULL;
mpStatusAttributes = NULL;
mpPreeditAttributes = NULL;
maClientData.aText.pUnicodeBuffer = NULL;
maClientData.aText.pCharStyle = NULL;
}
if ( maContext != NULL && mbMultiLingual )
{
maCommitStringCallback.callback = (XIMProc)::CommitStringCallback;
maCommitStringCallback.client_data = (XPointer)&maClientData;
maSwitchIMCallback.callback = (XIMProc)::SwitchIMCallback;
maSwitchIMCallback.client_data = (XPointer)&maClientData;
XSetICValues( maContext,
XNCommitStringCallback, &maCommitStringCallback,
XNSwitchIMNotifyCallback, &maSwitchIMCallback,
NULL );
}
if ( maContext != NULL)
{
maDestroyCallback.callback = (XIMProc)IC_IMDestroyCallback;
maDestroyCallback.client_data = (XPointer)this;
XSetICValues( maContext,
XNDestroyCallback, &maDestroyCallback,
NULL );
}
if( mbMultiLingual )
{
// set initial IM status
XIMUnicodeCharacterSubset* pSubset = NULL;
if( ! XGetICValues( maContext,
XNUnicodeCharacterSubset, & pSubset,
NULL )
&& pSubset )
{
String aCurrent( ByteString( pSubset->name ), RTL_TEXTENCODING_UTF8 );
::vcl::I18NStatus::get().changeIM( aCurrent );
::vcl::I18NStatus::get().setStatusText( aCurrent );
}
}
}
// ---------------------------------------------------------------------------
//
// In Solaris 8 the status window does not unmap if the frame unmapps, so
// unmap it the hard way
//
// ---------------------------------------------------------------------------
void
SalI18N_InputContext::Unmap( SalFrame* pFrame )
{
if ( maContext != NULL )
{
I18NStatus& rStatus( I18NStatus::get() );
if( rStatus.getParent() == pFrame )
rStatus.show( false, I18NStatus::contextmap );
}
UnsetICFocus( pFrame );
maClientData.pFrame = NULL;
}
void
SalI18N_InputContext::Map( SalFrame *pFrame )
{
if( mbUseable )
{
I18NStatus& rStatus(I18NStatus::get() );
rStatus.setParent( pFrame );
if( pFrame )
{
rStatus.show( true, I18NStatus::contextmap );
if ( maContext == NULL )
{
SalI18N_InputMethod *pInputMethod;
pInputMethod = GetX11SalData()->GetDisplay()->GetInputMethod();
maContext = XCreateIC( pInputMethod->GetMethod(),
XNVaNestedList, mpAttributes,
NULL );
if ( maContext != NULL && mbMultiLingual )
XSetICValues( maContext,
XNCommitStringCallback, &maCommitStringCallback,
XNSwitchIMNotifyCallback, &maSwitchIMCallback,
NULL );
}
if( maClientData.pFrame != pFrame )
SetICFocus( pFrame );
}
}
}
// --------------------------------------------------------------------------
//
// Handle DestroyCallbacks
// in fact this is a callback called from the XNDestroyCallback
//
// --------------------------------------------------------------------------
void
SalI18N_InputContext::HandleDestroyIM()
{
maContext = 0; // noli me tangere
mbUseable = False;
}
// ---------------------------------------------------------------------------
//
// make sure, the input method gets all the X-Events it needs, this is only
// called once on each frame, it relys on a valid maContext
//
// ---------------------------------------------------------------------------
void
SalI18N_InputContext::ExtendEventMask( XLIB_Window aFocusWindow )
{
unsigned long nIMEventMask;
XWindowAttributes aWindowAttributes;
if ( mbUseable )
{
Display *pDisplay = XDisplayOfIM( XIMOfIC(maContext) );
XGetWindowAttributes( pDisplay, aFocusWindow,
&aWindowAttributes );
XGetICValues ( maContext,
XNFilterEvents, &nIMEventMask,
NULL);
nIMEventMask |= aWindowAttributes.your_event_mask;
XSelectInput ( pDisplay, aFocusWindow, nIMEventMask );
}
}
// ---------------------------------------------------------------------------
//
// tune the styles provided by the input method with the supported one
//
// ---------------------------------------------------------------------------
unsigned int
SalI18N_InputContext::GetWeightingOfIMStyle( XIMStyle nStyle ) const
{
struct StyleWeightingT {
const XIMStyle nStyle;
const unsigned int nWeight;
};
StyleWeightingT const *pWeightPtr;
const StyleWeightingT pWeight[] = {
{ XIMPreeditCallbacks, 0x10000000 },
{ XIMPreeditPosition, 0x02000000 },
{ XIMPreeditArea, 0x01000000 },
{ XIMPreeditNothing, 0x00100000 },
{ XIMPreeditNone, 0x00010000 },
{ XIMStatusCallbacks, 0x1000 },
{ XIMStatusArea, 0x0100 },
{ XIMStatusNothing, 0x0010 },
{ XIMStatusNone, 0x0001 },
{ 0, 0x0 }
};
int nWeight = 0;
for ( pWeightPtr = pWeight; pWeightPtr->nStyle != 0; pWeightPtr++ )
{
if ( (pWeightPtr->nStyle & nStyle) != 0 )
nWeight += pWeightPtr->nWeight;
}
return nWeight;
}
Bool
SalI18N_InputContext::IsSupportedIMStyle( XIMStyle nStyle ) const
{
if ( (nStyle & mnSupportedPreeditStyle)
&& (nStyle & mnSupportedStatusStyle) )
{
return True;
}
return False;
}
Bool
SalI18N_InputContext::SupportInputMethodStyle( XIMStyles *pIMStyles )
{
int nBestScore = 0;
int nActualScore = 0;
mnPreeditStyle = 0;
mnStatusStyle = 0;
if ( pIMStyles != NULL )
{
// check whether the XIM supports one of the desired styles
// only a single preedit and a single status style must occure
// in a inpuut method style. Hideki said so, so i trust him
for ( int nStyle = 0; nStyle < pIMStyles->count_styles; nStyle++ )
{
XIMStyle nProvidedStyle = pIMStyles->supported_styles[ nStyle ];
if ( IsSupportedIMStyle(nProvidedStyle) )
{
nActualScore = GetWeightingOfIMStyle( nProvidedStyle );
if ( nActualScore >= nBestScore )
{
nBestScore = nActualScore;
mnPreeditStyle = nProvidedStyle & mnSupportedPreeditStyle;
mnStatusStyle = nProvidedStyle & mnSupportedStatusStyle;
}
}
}
}
#if OSL_DEBUG_LEVEL > 1
char pBuf[ 128 ];
fprintf( stderr, "selected inputmethod style = %s\n",
GetMethodName(mnPreeditStyle | mnStatusStyle, pBuf, sizeof(pBuf)) );
#endif
return (mnPreeditStyle != 0) && (mnStatusStyle != 0) ;
}
// ---------------------------------------------------------------------------
//
// handle extended and normal key input
//
// ---------------------------------------------------------------------------
int
SalI18N_InputContext::CommitStringCallback (sal_Unicode* pText, sal_Size nLength)
{
XIMUnicodeText call_data;
call_data.string.utf16_char = pText;
call_data.length = nLength;
call_data.annotations = NULL;
call_data.count_annotations = 0;
call_data.feedback = NULL;
return ::CommitStringCallback( maContext,
(XPointer)&maClientData, (XPointer)&call_data );
}
int
SalI18N_InputContext::CommitKeyEvent(sal_Unicode* pText, sal_Size nLength)
{
if (nLength == 1 && IsControlCode(pText[0]))
return 0;
if( maClientData.pFrame )
{
SalExtTextInputEvent aTextEvent;
aTextEvent.mnTime = 0;
aTextEvent.mpTextAttr = 0;
aTextEvent.mnCursorPos = nLength;
aTextEvent.maText = UniString(pText, nLength);
aTextEvent.mnCursorFlags = 0;
aTextEvent.mnDeltaStart = 0;
aTextEvent.mbOnlyCursor = False;
maClientData.pFrame->CallCallback(SALEVENT_EXTTEXTINPUT, (void*)&aTextEvent);
maClientData.pFrame->CallCallback(SALEVENT_ENDEXTTEXTINPUT, (void*)NULL);
}
#if OSL_DEBUG_LEVEL > 1
else
fprintf(stderr, "CommitKeyEvent without frame\n" );
#endif
return 0;
}
int
SalI18N_InputContext::UpdateSpotLocation()
{
if (maContext == 0 || maClientData.pFrame == NULL)
return -1;
SalExtTextInputPosEvent aPosEvent;
maClientData.pFrame->CallCallback(SALEVENT_EXTTEXTINPUTPOS, (void*)&aPosEvent);
XPoint aSpot;
aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &aSpot, NULL);
XSetICValues(maContext, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
I18NStatus::get().show( true, I18NStatus::contextmap );
return 0;
}
// ---------------------------------------------------------------------------
//
// set and unset the focus for the Input Context
// the context may be NULL despite it is useable if the framewindow is
// in unmapped state
//
// ---------------------------------------------------------------------------
void
SalI18N_InputContext::SetICFocus( SalFrame* pFocusFrame )
{
I18NStatus::get().setParent( pFocusFrame );
if ( mbUseable && (maContext != NULL) )
{
maClientData.pFrame = pFocusFrame;
const SystemEnvData* pEnv = pFocusFrame->GetSystemData();
XLIB_Window aClientWindow = pEnv->aShellWindow;
XLIB_Window aFocusWindow = pEnv->aWindow;
XSetICValues( maContext,
XNFocusWindow, aFocusWindow,
XNClientWindow, aClientWindow,
NULL );
if( maClientData.aInputEv.mpTextAttr )
{
sendEmptyCommit(pFocusFrame);
// begin preedit again
GetX11SalData()->GetDisplay()->SendInternalEvent( pFocusFrame, &maClientData.aInputEv, SALEVENT_EXTTEXTINPUT );
}
XSetICFocus( maContext );
}
}
void
SalI18N_InputContext::UnsetICFocus( SalFrame* pFrame )
{
I18NStatus& rStatus( I18NStatus::get() );
if( rStatus.getParent() == pFrame )
rStatus.setParent( NULL );
if ( mbUseable && (maContext != NULL) )
{
// cancel an eventual event posted to begin preedit again
GetX11SalData()->GetDisplay()->CancelInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SALEVENT_EXTTEXTINPUT );
maClientData.pFrame = NULL;
XUnsetICFocus( maContext );
}
}
// ---------------------------------------------------------------------------
//
// multi byte input method only
//
// ---------------------------------------------------------------------------
void
SalI18N_InputContext::SetPreeditState(Bool aPreeditState)
{
XIMPreeditState preedit_state = XIMPreeditUnKnown;
XVaNestedList preedit_attr;
preedit_attr = XVaCreateNestedList(
0,
XNPreeditState, &preedit_state,
NULL);
if (!XGetICValues(maContext, XNPreeditAttributes, preedit_attr, NULL))
{
XFree(preedit_attr);
preedit_state = aPreeditState? XIMPreeditEnable : XIMPreeditDisable;
preedit_attr = XVaCreateNestedList(
0,
XNPreeditState, preedit_state,
NULL);
XSetICValues(maContext, XNPreeditAttributes, preedit_attr, NULL);
}
XFree(preedit_attr);
return;
}
void
SalI18N_InputContext::SetLanguage(LanguageType)
{
// not yet implemented
return;
}
void
SalI18N_InputContext::EndExtTextInput( sal_uInt16 /*nFlags*/ )
{
if ( mbUseable && (maContext != NULL) && maClientData.pFrame )
{
vcl::DeletionListener aDel( maClientData.pFrame );
// delete preedit in sal (commit an empty string)
sendEmptyCommit( maClientData.pFrame );
if( ! aDel.isDeleted() )
{
// mark previous preedit state again (will e.g. be sent at focus gain)
maClientData.aInputEv.mpTextAttr = &maClientData.aInputFlags[0];
if( static_cast<X11SalFrame*>(maClientData.pFrame)->hasFocus() )
{
// begin preedit again
GetX11SalData()->GetDisplay()->SendInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SALEVENT_EXTTEXTINPUT );
}
}
}
}