1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
22 #include <unx/i18n_ic.hxx>
23 #include <unx/i18n_im.hxx>
25 #include <unx/salframe.h>
26 #include <unx/saldisp.hxx>
28 #include <sal/log.hxx>
32 static void sendEmptyCommit( SalFrame
* pFrame
)
34 vcl::DeletionListener
aDel( pFrame
);
36 SalExtTextInputEvent aEmptyEv
;
37 aEmptyEv
.mpTextAttr
= nullptr;
38 aEmptyEv
.maText
.clear();
39 aEmptyEv
.mnCursorPos
= 0;
40 aEmptyEv
.mnCursorFlags
= 0;
41 pFrame
->CallCallback( SalEvent::ExtTextInput
, static_cast<void*>(&aEmptyEv
) );
42 if( ! aDel
.isDeleted() )
43 pFrame
->CallCallback( SalEvent::EndExtTextInput
, nullptr );
46 // Constructor / Destructor, the InputContext is bound to the SalFrame, as it
47 // needs the shell window as a focus window
49 SalI18N_InputContext::~SalI18N_InputContext()
51 if ( maContext
!= nullptr )
52 XDestroyIC( maContext
);
53 if ( mpAttributes
!= nullptr )
54 XFree( mpAttributes
);
55 if ( mpStatusAttributes
!= nullptr )
56 XFree( mpStatusAttributes
);
57 if ( mpPreeditAttributes
!= nullptr )
58 XFree( mpPreeditAttributes
);
60 if (maClientData
.aText
.pUnicodeBuffer
!= nullptr)
61 free(maClientData
.aText
.pUnicodeBuffer
);
62 if (maClientData
.aText
.pCharStyle
!= nullptr)
63 free(maClientData
.aText
.pCharStyle
);
66 // convenience routine to add items to a XVaNestedList
69 XVaAddToNestedList( XVaNestedList a_srclist
, char* name
, XPointer value
)
71 XVaNestedList a_dstlist
;
73 // if ( value == NULL )
76 if ( a_srclist
== nullptr )
78 a_dstlist
= XVaCreateNestedList(
85 a_dstlist
= XVaCreateNestedList(
87 XNVaNestedList
, a_srclist
,
92 return a_dstlist
!= nullptr ? a_dstlist
: a_srclist
;
95 // convenience routine to create a fontset
98 get_font_set( Display
*p_display
)
100 static XFontSet p_font_set
= nullptr;
102 if (p_font_set
== nullptr)
104 char **pp_missing_list
;
106 char *p_default_string
;
108 p_font_set
= XCreateFontSet(p_display
, "-*",
109 &pp_missing_list
, &n_missing_count
, &p_default_string
);
115 const XIMStyle
g_nSupportedStatusStyle(
121 // Constructor for an InputContext (IC)
123 SalI18N_InputContext::SalI18N_InputContext ( SalFrame
*pFrame
) :
125 maContext( nullptr ),
126 mnSupportedPreeditStyle(
127 XIMPreeditCallbacks
|
133 mpAttributes( nullptr ),
134 mpStatusAttributes( nullptr ),
135 mpPreeditAttributes( nullptr )
138 static const char* pIIIMPEnable
= getenv( "SAL_DISABLE_OWN_IM_STATUS" );
139 if( pIIIMPEnable
&& *pIIIMPEnable
)
140 mnSupportedStatusStyle
&= ~XIMStatusCallbacks
;
143 memset(&maPreeditStartCallback
, 0, sizeof(maPreeditStartCallback
));
144 memset(&maPreeditDoneCallback
, 0, sizeof(maPreeditDoneCallback
));
145 memset(&maPreeditDrawCallback
, 0, sizeof(maPreeditDrawCallback
));
146 memset(&maPreeditCaretCallback
, 0, sizeof(maPreeditCaretCallback
));
147 memset(&maCommitStringCallback
, 0, sizeof(maCommitStringCallback
));
148 memset(&maSwitchIMCallback
, 0, sizeof(maSwitchIMCallback
));
149 memset(&maDestroyCallback
, 0, sizeof(maDestroyCallback
));
151 maClientData
.aText
.pUnicodeBuffer
= nullptr;
152 maClientData
.aText
.pCharStyle
= nullptr;
153 maClientData
.aInputEv
.mpTextAttr
= nullptr;
154 maClientData
.aInputEv
.mnCursorPos
= 0;
155 maClientData
.aInputEv
.mnCursorFlags
= 0;
157 SalI18N_InputMethod
*pInputMethod
;
158 pInputMethod
= vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
160 mnSupportedPreeditStyle
= XIMPreeditCallbacks
| XIMPreeditPosition
161 | XIMPreeditNothing
| XIMPreeditNone
;
162 if (pInputMethod
->UseMethod()
163 && SupportInputMethodStyle( pInputMethod
->GetSupportedStyles() ) )
165 const SystemEnvData
& rEnv
= pFrame
->GetSystemData();
166 ::Window aClientWindow
= rEnv
.aShellWindow
;
167 ::Window aFocusWindow
= rEnv
.GetWindowHandle(pFrame
);
169 // for status callbacks and commit string callbacks
170 #define PREEDIT_BUFSZ 16
171 maClientData
.eState
= PreeditStatus::StartPending
;
172 maClientData
.pFrame
= pFrame
;
173 maClientData
.aText
.pUnicodeBuffer
=
174 static_cast<sal_Unicode
*>(malloc(PREEDIT_BUFSZ
* sizeof(sal_Unicode
)));
175 maClientData
.aText
.pCharStyle
=
176 static_cast<XIMFeedback
*>(malloc(PREEDIT_BUFSZ
* sizeof(XIMFeedback
)));
177 maClientData
.aText
.nSize
= PREEDIT_BUFSZ
;
178 maClientData
.aText
.nLength
= 0;
182 switch ( mnStatusStyle
)
184 case XIMStatusCallbacks
:
186 static XIMCallback aStatusStartCallback
;
187 static XIMCallback aStatusDoneCallback
;
188 static XIMCallback aStatusDrawCallback
;
190 aStatusStartCallback
.callback
= reinterpret_cast<XIMProc
>(StatusStartCallback
);
191 aStatusStartCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
192 aStatusDoneCallback
.callback
= reinterpret_cast<XIMProc
>(StatusDoneCallback
);
193 aStatusDoneCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
194 aStatusDrawCallback
.callback
= reinterpret_cast<XIMProc
>(StatusDrawCallback
);
195 aStatusDrawCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
197 mpStatusAttributes
= XVaCreateNestedList (
199 XNStatusStartCallback
, &aStatusStartCallback
,
200 XNStatusDoneCallback
, &aStatusDoneCallback
,
201 XNStatusDrawCallback
, &aStatusDrawCallback
,
212 case XIMStatusNothing
:
214 /* no arguments needed */
218 // set preedit attributes
220 switch ( mnPreeditStyle
)
222 case XIMPreeditCallbacks
:
224 maPreeditCaretCallback
.callback
= reinterpret_cast<XIMProc
>(PreeditCaretCallback
);
225 maPreeditStartCallback
.callback
= reinterpret_cast<XIMProc
>(PreeditStartCallback
);
226 maPreeditDoneCallback
.callback
= reinterpret_cast<XIMProc
>(PreeditDoneCallback
);
227 maPreeditDrawCallback
.callback
= reinterpret_cast<XIMProc
>(PreeditDrawCallback
);
228 maPreeditCaretCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
229 maPreeditStartCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
230 maPreeditDoneCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
231 maPreeditDrawCallback
.client_data
= reinterpret_cast<XPointer
>(&maClientData
);
233 mpPreeditAttributes
= XVaCreateNestedList (
235 XNPreeditStartCallback
, &maPreeditStartCallback
,
236 XNPreeditDoneCallback
, &maPreeditDoneCallback
,
237 XNPreeditDrawCallback
, &maPreeditDrawCallback
,
238 XNPreeditCaretCallback
, &maPreeditCaretCallback
,
247 case XIMPreeditPosition
:
250 SalExtTextInputPosEvent aPosEvent
;
251 pFrame
->CallCallback(SalEvent::ExtTextInputPos
, static_cast<void*>(&aPosEvent
));
254 aSpot
.x
= aPosEvent
.mnX
+ aPosEvent
.mnWidth
;
255 aSpot
.y
= aPosEvent
.mnY
+ aPosEvent
.mnHeight
;
257 // create attributes for preedit position style
258 mpPreeditAttributes
= XVaCreateNestedList (
260 XNSpotLocation
, &aSpot
,
263 // XCreateIC() fails on Redflag Linux 2.0 if there is no
264 // fontset though the data itself is not evaluated nor is
265 // it required according to the X specs.
266 Display
* pDisplay
= vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay();
267 XFontSet pFontSet
= get_font_set(pDisplay
);
269 if (pFontSet
!= nullptr)
271 mpPreeditAttributes
= XVaAddToNestedList( mpPreeditAttributes
,
272 const_cast<char*>(XNFontSet
), reinterpret_cast<XPointer
>(pFontSet
));
279 case XIMPreeditNothing
:
281 /* no arguments needed */
285 // Create the InputContext by giving it exactly the information it
286 // deserves, because inappropriate attributes
287 // let XCreateIC fail on Solaris (eg. for C locale)
289 mpAttributes
= XVaCreateNestedList(
291 XNFocusWindow
, aFocusWindow
,
292 XNClientWindow
, aClientWindow
,
293 XNInputStyle
, mnPreeditStyle
| mnStatusStyle
,
296 if ( mnPreeditStyle
!= XIMPreeditNone
)
298 #if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
299 if ( mpPreeditAttributes
!= nullptr )
301 mpAttributes
= XVaAddToNestedList( mpAttributes
,
302 const_cast<char*>(XNPreeditAttributes
), static_cast<XPointer
>(mpPreeditAttributes
) );
304 if ( mnStatusStyle
!= XIMStatusNone
)
306 #if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
307 if ( mpStatusAttributes
!= nullptr )
309 mpAttributes
= XVaAddToNestedList( mpAttributes
,
310 const_cast<char*>(XNStatusAttributes
), static_cast<XPointer
>(mpStatusAttributes
) );
312 maContext
= XCreateIC( pInputMethod
->GetMethod(),
313 XNVaNestedList
, mpAttributes
,
317 if ( maContext
== nullptr )
319 #if OSL_DEBUG_LEVEL > 1
320 SAL_WARN("vcl.app", "input context creation failed.");
325 if ( mpAttributes
!= nullptr )
326 XFree( mpAttributes
);
327 if ( mpStatusAttributes
!= nullptr )
328 XFree( mpStatusAttributes
);
329 if ( mpPreeditAttributes
!= nullptr )
330 XFree( mpPreeditAttributes
);
331 if ( maClientData
.aText
.pUnicodeBuffer
!= nullptr )
332 free ( maClientData
.aText
.pUnicodeBuffer
);
333 if ( maClientData
.aText
.pCharStyle
!= nullptr )
334 free ( maClientData
.aText
.pCharStyle
);
336 mpAttributes
= nullptr;
337 mpStatusAttributes
= nullptr;
338 mpPreeditAttributes
= nullptr;
339 maClientData
.aText
.pUnicodeBuffer
= nullptr;
340 maClientData
.aText
.pCharStyle
= nullptr;
343 if ( maContext
!= nullptr)
345 maDestroyCallback
.callback
= IC_IMDestroyCallback
;
346 maDestroyCallback
.client_data
= reinterpret_cast<XPointer
>(this);
347 XSetICValues( maContext
,
348 XNDestroyCallback
, &maDestroyCallback
,
353 // In Solaris 8 the status window does not unmap if the frame unmapps, so
354 // unmap it the hard way
357 SalI18N_InputContext::Unmap()
360 maClientData
.pFrame
= nullptr;
364 SalI18N_InputContext::Map( SalFrame
*pFrame
)
372 if ( maContext
== nullptr )
374 SalI18N_InputMethod
*pInputMethod
;
375 pInputMethod
= vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
377 maContext
= XCreateIC( pInputMethod
->GetMethod(),
378 XNVaNestedList
, mpAttributes
,
381 if( maClientData
.pFrame
!= pFrame
)
382 SetICFocus( pFrame
);
385 // Handle DestroyCallbacks
386 // in fact this is a callback called from the XNDestroyCallback
389 SalI18N_InputContext::HandleDestroyIM()
391 maContext
= nullptr; // don't change
395 // make sure, the input method gets all the X-Events it needs, this is only
396 // called once on each frame, it relies on a valid maContext
399 SalI18N_InputContext::ExtendEventMask( ::Window aFocusWindow
)
401 unsigned long nIMEventMask
;
402 XWindowAttributes aWindowAttributes
;
406 Display
*pDisplay
= XDisplayOfIM( XIMOfIC(maContext
) );
408 XGetWindowAttributes( pDisplay
, aFocusWindow
,
409 &aWindowAttributes
);
410 XGetICValues ( maContext
,
411 XNFilterEvents
, &nIMEventMask
,
413 nIMEventMask
|= aWindowAttributes
.your_event_mask
;
414 XSelectInput ( pDisplay
, aFocusWindow
, nIMEventMask
);
418 // tune the styles provided by the input method with the supported one
421 SalI18N_InputContext::GetWeightingOfIMStyle( XIMStyle nStyle
)
423 struct StyleWeightingT
{
424 const XIMStyle nStyle
;
425 const unsigned int nWeight
;
428 StyleWeightingT
const *pWeightPtr
;
429 static const StyleWeightingT pWeight
[] = {
430 { XIMPreeditCallbacks
, 0x10000000 },
431 { XIMPreeditPosition
, 0x02000000 },
432 { XIMPreeditArea
, 0x01000000 },
433 { XIMPreeditNothing
, 0x00100000 },
434 { XIMPreeditNone
, 0x00010000 },
435 { XIMStatusCallbacks
, 0x1000 },
436 { XIMStatusArea
, 0x0100 },
437 { XIMStatusNothing
, 0x0010 },
438 { XIMStatusNone
, 0x0001 },
443 for ( pWeightPtr
= pWeight
; pWeightPtr
->nStyle
!= 0; pWeightPtr
++ )
445 if ( (pWeightPtr
->nStyle
& nStyle
) != 0 )
446 nWeight
+= pWeightPtr
->nWeight
;
452 SalI18N_InputContext::IsSupportedIMStyle( XIMStyle nStyle
) const
454 return (nStyle
& mnSupportedPreeditStyle
)
455 && (nStyle
& g_nSupportedStatusStyle
);
459 SalI18N_InputContext::SupportInputMethodStyle( XIMStyles
const *pIMStyles
)
464 if ( pIMStyles
!= nullptr )
467 int nActualScore
= 0;
469 // check whether the XIM supports one of the desired styles
470 // only a single preedit and a single status style must occur
471 // in an input method style. Hideki said so, so i trust him
472 for ( int nStyle
= 0; nStyle
< pIMStyles
->count_styles
; nStyle
++ )
474 XIMStyle nProvidedStyle
= pIMStyles
->supported_styles
[ nStyle
];
475 if ( IsSupportedIMStyle(nProvidedStyle
) )
477 nActualScore
= GetWeightingOfIMStyle( nProvidedStyle
);
478 if ( nActualScore
>= nBestScore
)
480 nBestScore
= nActualScore
;
481 mnPreeditStyle
= nProvidedStyle
& mnSupportedPreeditStyle
;
482 mnStatusStyle
= nProvidedStyle
& g_nSupportedStatusStyle
;
488 return (mnPreeditStyle
!= 0) && (mnStatusStyle
!= 0) ;
491 // handle extended and normal key input
494 SalI18N_InputContext::CommitKeyEvent(sal_Unicode
const * pText
, std::size_t nLength
)
496 if (nLength
== 1 && IsControlCode(pText
[0]))
499 if( maClientData
.pFrame
)
501 SalExtTextInputEvent aTextEvent
;
503 aTextEvent
.mpTextAttr
= nullptr;
504 aTextEvent
.mnCursorPos
= nLength
;
505 aTextEvent
.maText
= OUString(pText
, nLength
);
506 aTextEvent
.mnCursorFlags
= 0;
508 maClientData
.pFrame
->CallCallback(SalEvent::ExtTextInput
, static_cast<void*>(&aTextEvent
));
509 maClientData
.pFrame
->CallCallback(SalEvent::EndExtTextInput
, nullptr);
511 #if OSL_DEBUG_LEVEL > 1
513 SAL_WARN("vcl.app", "CommitKeyEvent without frame.");
518 SalI18N_InputContext::UpdateSpotLocation()
520 if (maContext
== nullptr || maClientData
.pFrame
== nullptr)
523 SalExtTextInputPosEvent aPosEvent
;
524 maClientData
.pFrame
->CallCallback(SalEvent::ExtTextInputPos
, static_cast<void*>(&aPosEvent
));
527 aSpot
.x
= aPosEvent
.mnX
+ aPosEvent
.mnWidth
;
528 aSpot
.y
= aPosEvent
.mnY
+ aPosEvent
.mnHeight
;
530 XVaNestedList preedit_attr
= XVaCreateNestedList(0, XNSpotLocation
, &aSpot
, nullptr);
531 XSetICValues(maContext
, XNPreeditAttributes
, preedit_attr
, nullptr);
537 // set and unset the focus for the Input Context
538 // the context may be NULL despite it is usable if the framewindow is
542 SalI18N_InputContext::SetICFocus( SalFrame
* pFocusFrame
)
544 if ( !(mbUseable
&& (maContext
!= nullptr)) )
547 maClientData
.pFrame
= pFocusFrame
;
549 const SystemEnvData
& rEnv
= pFocusFrame
->GetSystemData();
550 ::Window aClientWindow
= rEnv
.aShellWindow
;
551 ::Window aFocusWindow
= rEnv
.GetWindowHandle(pFocusFrame
);
553 XSetICValues( maContext
,
554 XNFocusWindow
, aFocusWindow
,
555 XNClientWindow
, aClientWindow
,
558 if( maClientData
.aInputEv
.mpTextAttr
)
560 sendEmptyCommit(pFocusFrame
);
561 // begin preedit again
562 vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( pFocusFrame
, &maClientData
.aInputEv
, SalEvent::ExtTextInput
);
565 XSetICFocus( maContext
);
569 SalI18N_InputContext::UnsetICFocus()
572 if ( mbUseable
&& (maContext
!= nullptr) )
574 // cancel an eventual event posted to begin preedit again
575 vcl_sal::getSalDisplay(GetGenericUnixSalData())->CancelInternalEvent( maClientData
.pFrame
, &maClientData
.aInputEv
, SalEvent::ExtTextInput
);
576 maClientData
.pFrame
= nullptr;
577 XUnsetICFocus( maContext
);
581 // multi byte input method only
584 SalI18N_InputContext::EndExtTextInput()
586 if ( !mbUseable
|| (maContext
== nullptr) || !maClientData
.pFrame
)
589 vcl::DeletionListener
aDel( maClientData
.pFrame
);
590 // delete preedit in sal (commit an empty string)
591 sendEmptyCommit( maClientData
.pFrame
);
592 if( ! aDel
.isDeleted() )
594 // mark previous preedit state again (will e.g. be sent at focus gain)
595 maClientData
.aInputEv
.mpTextAttr
= maClientData
.aInputFlags
.data();
596 if( static_cast<X11SalFrame
*>(maClientData
.pFrame
)->hasFocus() )
598 // begin preedit again
599 vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( maClientData
.pFrame
, &maClientData
.aInputEv
, SalEvent::ExtTextInput
);
604 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */