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 .
23 #include <o3tl/safeint.hxx>
24 #include <osl/thread.h>
25 #include <sal/log.hxx>
29 #include <vcl/commandevent.hxx>
30 #include <unx/i18n_cb.hxx>
31 #include <unx/i18n_ic.hxx>
32 #include <unx/i18n_im.hxx>
33 #include <salframe.hxx>
35 // i. preedit start callback
38 PreeditStartCallback ( XIC
, XPointer client_data
, XPointer
)
40 preedit_data_t
* pPreeditData
= reinterpret_cast<preedit_data_t
*>(client_data
);
41 if ( pPreeditData
->eState
== PreeditStatus::ActivationRequired
)
43 pPreeditData
->eState
= PreeditStatus::Active
;
44 pPreeditData
->aText
.nLength
= 0;
50 // ii. preedit done callback
53 PreeditDoneCallback ( XIC
, XPointer client_data
, XPointer
)
55 preedit_data_t
* pPreeditData
= reinterpret_cast<preedit_data_t
*>(client_data
);
56 if (pPreeditData
->eState
== PreeditStatus::Active
)
58 if( pPreeditData
->pFrame
)
59 pPreeditData
->pFrame
->CallCallback( SalEvent::EndExtTextInput
, nullptr );
61 pPreeditData
->eState
= PreeditStatus::StartPending
;
64 // iii. preedit draw callback
66 // Handle deletion of text in a preedit_draw_callback
67 // from and howmuch are guaranteed to be nonnegative
70 Preedit_DeleteText(preedit_text_t
*ptext
, int from
, int howmuch
)
72 // If we've been asked to delete no text then just set
73 // nLength correctly and return
74 if (ptext
->nLength
== 0)
76 ptext
->nLength
= from
;
80 int to
= from
+ howmuch
;
82 if (to
== static_cast<int>(ptext
->nLength
))
84 // delete from the end of the text
85 ptext
->nLength
= from
;
87 else if (to
< static_cast<int>(ptext
->nLength
))
89 // cut out of the middle of the text
90 memmove( static_cast<void*>(ptext
->pUnicodeBuffer
+ from
),
91 static_cast<void*>(ptext
->pUnicodeBuffer
+ to
),
92 (ptext
->nLength
- to
) * sizeof(sal_Unicode
));
93 memmove( static_cast<void*>(ptext
->pCharStyle
+ from
),
94 static_cast<void*>(ptext
->pCharStyle
+ to
),
95 (ptext
->nLength
- to
) * sizeof(XIMFeedback
));
96 ptext
->nLength
-= howmuch
;
100 // XXX this indicates an error, are we out of sync ?
101 SAL_INFO("vcl.app", "Preedit_DeleteText( from=" << from
103 << " length=" << ptext
->nLength
105 fprintf (stderr
, "\t XXX internal error, out of sync XXX\n");
107 ptext
->nLength
= from
;
110 // NULL-terminate the string
111 ptext
->pUnicodeBuffer
[ptext
->nLength
] = u
'\0';
114 // reallocate the textbuffer with sufficiently large size 2^x
115 // nnewlimit is presupposed to be larger than ptext->size
117 enlarge_buffer ( preedit_text_t
*ptext
, int nnewlimit
)
119 size_t nnewsize
= ptext
->nSize
;
121 while ( nnewsize
<= o3tl::make_unsigned(nnewlimit
) )
124 ptext
->nSize
= nnewsize
;
125 ptext
->pUnicodeBuffer
= static_cast<sal_Unicode
*>(realloc(static_cast<void*>(ptext
->pUnicodeBuffer
),
126 nnewsize
* sizeof(sal_Unicode
)));
127 ptext
->pCharStyle
= static_cast<XIMFeedback
*>(realloc(static_cast<void*>(ptext
->pCharStyle
),
128 nnewsize
* sizeof(XIMFeedback
)));
131 // Handle insertion of text in a preedit_draw_callback
132 // string field of XIMText struct is guaranteed to be != NULL
135 Preedit_InsertText(preedit_text_t
*pText
, XIMText
*pInsertText
, int where
)
137 sal_Unicode
*pInsertTextString
;
138 int nInsertTextLength
= 0;
139 XIMFeedback
*pInsertTextCharStyle
= pInsertText
->feedback
;
141 nInsertTextLength
= pInsertText
->length
;
143 // can't handle wchar_t strings, so convert to multibyte chars first
146 if (pInsertText
->encoding_is_wchar
)
148 wchar_t *pWCString
= pInsertText
->string
.wide_char
;
149 size_t nBytes
= wcstombs ( nullptr, pWCString
, 0 /* don't care */);
150 pMBString
= static_cast<char*>(alloca( nBytes
+ 1 ));
151 nMBLength
= wcstombs ( pMBString
, pWCString
, nBytes
+ 1);
155 pMBString
= pInsertText
->string
.multi_byte
;
156 nMBLength
= strlen(pMBString
); // xxx
159 // convert multibyte chars to unicode
160 rtl_TextEncoding nEncoding
= osl_getThreadTextEncoding();
162 if (nEncoding
!= RTL_TEXTENCODING_UNICODE
)
164 rtl_TextToUnicodeConverter aConverter
=
165 rtl_createTextToUnicodeConverter( nEncoding
);
166 rtl_TextToUnicodeContext aContext
=
167 rtl_createTextToUnicodeContext(aConverter
);
169 sal_Size nBufferSize
= nInsertTextLength
* 2;
171 pInsertTextString
= static_cast<sal_Unicode
*>(alloca(nBufferSize
));
173 sal_uInt32 nConversionInfo
;
174 sal_Size nConvertedChars
;
176 rtl_convertTextToUnicode( aConverter
, aContext
,
177 pMBString
, nMBLength
,
178 pInsertTextString
, nBufferSize
,
179 RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE
180 | RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE
,
181 &nConversionInfo
, &nConvertedChars
);
183 rtl_destroyTextToUnicodeContext(aConverter
, aContext
);
184 rtl_destroyTextToUnicodeConverter(aConverter
);
189 pInsertTextString
= reinterpret_cast<sal_Unicode
*>(pMBString
);
192 // enlarge target text-buffer if necessary
193 if (pText
->nSize
<= (pText
->nLength
+ nInsertTextLength
))
194 enlarge_buffer(pText
, pText
->nLength
+ nInsertTextLength
);
196 // insert text: displace old mem and put new bytes in
198 int to
= where
+ nInsertTextLength
;
199 int howmany
= pText
->nLength
- where
;
201 memmove(static_cast<void*>(pText
->pUnicodeBuffer
+ to
),
202 static_cast<void*>(pText
->pUnicodeBuffer
+ from
),
203 howmany
* sizeof(sal_Unicode
));
204 memmove(static_cast<void*>(pText
->pCharStyle
+ to
),
205 static_cast<void*>(pText
->pCharStyle
+ from
),
206 howmany
* sizeof(XIMFeedback
));
209 howmany
= nInsertTextLength
;
211 memcpy(static_cast<void*>(pText
->pUnicodeBuffer
+ to
), static_cast<void*>(pInsertTextString
),
212 howmany
* sizeof(sal_Unicode
));
213 memcpy(static_cast<void*>(pText
->pCharStyle
+ to
), static_cast<void*>(pInsertTextCharStyle
),
214 howmany
* sizeof(XIMFeedback
));
216 pText
->nLength
+= howmany
;
218 // NULL-terminate the string
219 pText
->pUnicodeBuffer
[pText
->nLength
] = u
'\0';
222 // Handle the change of attributes in a preedit_draw_callback
225 Preedit_UpdateAttributes ( preedit_text_t
* ptext
, XIMFeedback
const * feedback
,
226 int from
, int amount
)
228 if ( (from
+ amount
) > static_cast<int>(ptext
->nLength
) )
230 // XXX this indicates an error, are we out of sync ?
231 SAL_INFO("vcl.app", "Preedit_UpdateAttributes( "
232 << from
<< " + " << amount
<< " > " << ptext
->nLength
234 fprintf (stderr
, "\t XXX internal error, out of sync XXX\n");
239 memcpy ( ptext
->pCharStyle
+ from
,
240 feedback
, amount
* sizeof(XIMFeedback
) );
243 // Convert the XIM feedback values into appropriate VCL
244 // EXTTEXTINPUT_ATTR values
245 // returns an allocate list of attributes, which must be freed by caller
246 static ExtTextInputAttr
*
247 Preedit_FeedbackToSAL ( const XIMFeedback
* pfeedback
, int nlength
, std::vector
<ExtTextInputAttr
>& rSalAttr
)
249 ExtTextInputAttr
*psalattr
;
250 ExtTextInputAttr nval
;
251 ExtTextInputAttr noldval
= ExtTextInputAttr::NONE
;
252 XIMFeedback nfeedback
;
254 // only work with reasonable length
255 if (nlength
> 0 && nlength
> sal::static_int_cast
<int>(rSalAttr
.size()) )
257 rSalAttr
.reserve( nlength
);
258 psalattr
= rSalAttr
.data();
263 for (int npos
= 0; npos
< nlength
; npos
++)
265 nval
= ExtTextInputAttr::NONE
;
266 nfeedback
= pfeedback
[npos
];
268 // means to use the feedback of the previous char
273 // convert feedback to attributes
276 if (nfeedback
& XIMReverse
)
277 nval
|= ExtTextInputAttr::Highlight
;
278 if (nfeedback
& XIMUnderline
)
279 nval
|= ExtTextInputAttr::Underline
;
280 if (nfeedback
& XIMHighlight
)
281 nval
|= ExtTextInputAttr::Highlight
;
282 if (nfeedback
& XIMPrimary
)
283 nval
|= ExtTextInputAttr::DottedUnderline
;
284 if (nfeedback
& XIMSecondary
)
285 nval
|= ExtTextInputAttr::DashDotUnderline
;
286 if (nfeedback
& XIMTertiary
) // same as 2ery
287 nval
|= ExtTextInputAttr::DashDotUnderline
;
291 psalattr
[npos
] = nval
;
294 // return list of sal attributes
299 PreeditDrawCallback(XIC ic
, XPointer client_data
,
300 XIMPreeditDrawCallbackStruct
*call_data
)
302 preedit_data_t
* pPreeditData
= reinterpret_cast<preedit_data_t
*>(client_data
);
304 // if there's nothing to change then change nothing
305 if ( ( (call_data
->text
== nullptr) && (call_data
->chg_length
== 0) )
306 || pPreeditData
->pFrame
== nullptr )
309 // Solaris 7 deletes the preedit buffer after commit
310 // since the next call to preeditstart will have the same effect just skip this.
311 // if (pPreeditData->eState == ePreeditStatusStartPending && call_data->text == NULL)
314 if ( pPreeditData
->eState
== PreeditStatus::StartPending
)
315 pPreeditData
->eState
= PreeditStatus::ActivationRequired
;
316 PreeditStartCallback( ic
, client_data
, nullptr );
318 // Edit the internal textbuffer as indicated by the call_data,
319 // chg_first and chg_length are guaranteed to be nonnegative
321 // handle text deletion
322 if (call_data
->text
== nullptr)
324 Preedit_DeleteText(&(pPreeditData
->aText
),
325 call_data
->chg_first
, call_data
->chg_length
);
329 // handle text insertion
330 if ( (call_data
->chg_length
== 0)
331 && (call_data
->text
->string
.wide_char
!= nullptr))
333 Preedit_InsertText(&(pPreeditData
->aText
), call_data
->text
,
334 call_data
->chg_first
);
336 else if ( (call_data
->chg_length
!= 0)
337 && (call_data
->text
->string
.wide_char
!= nullptr))
339 // handle text replacement by deletion and insertion of text,
340 // not smart, just good enough
342 Preedit_DeleteText(&(pPreeditData
->aText
),
343 call_data
->chg_first
, call_data
->chg_length
);
344 Preedit_InsertText(&(pPreeditData
->aText
), call_data
->text
,
345 call_data
->chg_first
);
347 else if ( (call_data
->chg_length
!= 0)
348 && (call_data
->text
->string
.wide_char
== nullptr))
350 // not really a text update, only attributes are concerned
351 Preedit_UpdateAttributes(&(pPreeditData
->aText
),
352 call_data
->text
->feedback
,
353 call_data
->chg_first
, call_data
->chg_length
);
357 // build the SalExtTextInputEvent and send it up
359 pPreeditData
->aInputEv
.mpTextAttr
= Preedit_FeedbackToSAL(
360 pPreeditData
->aText
.pCharStyle
, pPreeditData
->aText
.nLength
, pPreeditData
->aInputFlags
);
361 pPreeditData
->aInputEv
.mnCursorPos
= call_data
->caret
;
362 pPreeditData
->aInputEv
.maText
= OUString(pPreeditData
->aText
.pUnicodeBuffer
,
363 pPreeditData
->aText
.nLength
);
364 pPreeditData
->aInputEv
.mnCursorFlags
= 0; // default: make cursor visible
366 if ( pPreeditData
->eState
== PreeditStatus::Active
&& pPreeditData
->pFrame
)
367 pPreeditData
->pFrame
->CallCallback(SalEvent::ExtTextInput
, static_cast<void*>(&pPreeditData
->aInputEv
));
368 if (pPreeditData
->aText
.nLength
== 0 && pPreeditData
->pFrame
)
369 pPreeditData
->pFrame
->CallCallback( SalEvent::EndExtTextInput
, nullptr );
371 if (pPreeditData
->aText
.nLength
== 0)
372 pPreeditData
->eState
= PreeditStatus::StartPending
;
374 GetPreeditSpotLocation(ic
, reinterpret_cast<XPointer
>(pPreeditData
));
378 GetPreeditSpotLocation(XIC ic
, XPointer client_data
)
381 // Send SalEventExtTextInputPos event to get spotlocation
383 SalExtTextInputPosEvent aPosEvent
;
384 preedit_data_t
* pPreeditData
= reinterpret_cast<preedit_data_t
*>(client_data
);
386 if( pPreeditData
->pFrame
)
387 pPreeditData
->pFrame
->CallCallback(SalEvent::ExtTextInputPos
, static_cast<void*>(&aPosEvent
));
390 point
.x
= aPosEvent
.mnX
+ aPosEvent
.mnWidth
;
391 point
.y
= aPosEvent
.mnY
+ aPosEvent
.mnHeight
;
393 XVaNestedList preedit_attr
;
394 preedit_attr
= XVaCreateNestedList(0, XNSpotLocation
, &point
, nullptr);
395 XSetICValues(ic
, XNPreeditAttributes
, preedit_attr
, nullptr);
399 // iv. preedit caret callback
401 #if OSL_DEBUG_LEVEL > 1
403 PreeditCaretCallback ( XIC ic
, XPointer client_data
,
404 XIMPreeditCaretCallbackStruct
*call_data
)
406 // XXX PreeditCaretCallback is pure debug code for now
407 const char *direction
= "?";
408 const char *style
= "?";
410 switch ( call_data
->style
)
412 case XIMIsInvisible
: style
= "Invisible"; break;
413 case XIMIsPrimary
: style
= "Primary"; break;
414 case XIMIsSecondary
: style
= "Secondary"; break;
416 switch ( call_data
->direction
)
418 case XIMForwardChar
: direction
= "Forward char"; break;
419 case XIMBackwardChar
: direction
= "Backward char"; break;
420 case XIMForwardWord
: direction
= "Forward word"; break;
421 case XIMBackwardWord
: direction
= "Backward word"; break;
422 case XIMCaretUp
: direction
= "Caret up"; break;
423 case XIMCaretDown
: direction
= "Caret down"; break;
424 case XIMNextLine
: direction
= "Next line"; break;
425 case XIMPreviousLine
: direction
= "Previous line"; break;
426 case XIMLineStart
: direction
= "Line start"; break;
427 case XIMLineEnd
: direction
= "Line end"; break;
428 case XIMAbsolutePosition
: direction
= "Absolute"; break;
429 case XIMDontChange
: direction
= "Don't change"; break;
432 SAL_INFO("vcl.app", "PreeditCaretCallback( ic=" << ic
433 << ", client=" << client_data
435 SAL_INFO("vcl.app", "\t position=" << call_data
->position
436 << ", direction=\"" << direction
437 << "\", style=\"" << style
442 PreeditCaretCallback ( XIC
, XPointer
, XIMPreeditCaretCallbackStruct
* )
447 // v. commit string callback: convert an extended text input (iiimp ... )
448 // into an ordinary key-event
451 IsControlCode(sal_Unicode nChar
)
453 if ( nChar
<= 0x1F /* C0 controls */ )
459 // vi. status callbacks: for now these are empty, they are just needed for turbo linux
462 StatusStartCallback (XIC
, XPointer
, XPointer
)
467 StatusDoneCallback (XIC
, XPointer
, XPointer
)
472 StatusDrawCallback (XIC
, XPointer
, XIMStatusDrawCallbackStruct
*)
476 // vii. destroy callbacks: internally disable all IC/IM calls
479 IC_IMDestroyCallback (XIM
, XPointer client_data
, XPointer
)
481 SalI18N_InputContext
*pContext
= reinterpret_cast<SalI18N_InputContext
*>(client_data
);
482 if (pContext
!= nullptr)
483 pContext
->HandleDestroyIM();
487 IM_IMDestroyCallback (XIM
, XPointer client_data
, XPointer
)
489 SalI18N_InputMethod
*pMethod
= reinterpret_cast<SalI18N_InputMethod
*>(client_data
);
490 if (pMethod
!= nullptr)
491 pMethod
->HandleDestroyIM();
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */