1 /*
2
3 Copyright (c) 2004-2005 Kazuki Ohta <mover@hct.zaq.ne.jp>
4 Copyright (c) 2005-2013 uim Project https://github.com/uim/uim
5
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11
12 1. Redistributions of source code must retain the above copyright
13 notice, this list of conditions and the following disclaimer.
14 2. Redistributions in binary form must reproduce the above copyright
15 notice, this list of conditions and the following disclaimer in the
16 documentation and/or other materials provided with the distribution.
17 3. Neither the name of authors nor the names of its contributors
18 may be used to endorse or promote products derived from this software
19 without specific prior written permission.
20
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
22 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 SUCH DAMAGE.
32
33 */
34 #include "quiminputcontext.h"
35
36 #include <cctype>
37 #include <cstring>
38
39 #include <QtCore/QPoint>
40 #include <QtGui/QApplication>
41 #include <QtGui/QInputMethodEvent>
42 #include <QtGui/QLabel>
43 #include <QtGui/QTextCharFormat>
44
45 #include <uim/uim-helper.h>
46 #include <uim/uim-im-switcher.h>
47 #include <uim/uim-scm.h>
48
49 #include "candidatewindowproxy.h"
50 #include "caretstateindicator.h"
51 #include "plugin.h"
52 #include "qhelpermanager.h"
53 #include "qtextutil.h"
54 #include "quiminfomanager.h"
55 #include "quiminputcontext_compose.h"
56
57 #if UIM_QT_USE_JAPANESE_KANA_KEYBOARD_HACK
58 #include <X11/Xlib.h>
59
60 #include "uim/uim-x-util.h"
61 #endif
62
63 static const char DEFAULT_SEPARATOR_STR[] = "|";
64
65 QUimInputContext *focusedInputContext = 0;
66 bool disableFocusedContext = false;
67
68 QList<QUimInputContext*> contextList;
69
70 QUimHelperManager * QUimInputContext::m_HelperManager = 0;
71 #ifdef Q_WS_X11
72 DefTree *QUimInputContext::mTreeTop = 0;
73 #endif
74
75 static int unicodeToUKey(ushort c);
76
77 // I think that current index-based query API of uim for language and
78 // input method name is useless and should be redesigned. I will
79 // suggest the change in future. -- YamaKen 2004-07-28
80
QUimInputContext(const char * imname)81 QUimInputContext::QUimInputContext( const char *imname )
82 : candwinIsActive( false ), m_isComposing( false ), m_uc( 0 )
83 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
84 , focusedWidget( 0 )
85 #endif
86 {
87 #ifdef ENABLE_DEBUG
88 qDebug( "QUimInputContext()" );
89 #endif
90
91 contextList.append( this );
92
93 // must be initialized before createUimContext() call
94 if ( !m_HelperManager )
95 m_HelperManager = new QUimHelperManager;
96
97 if ( imname )
98 m_uc = createUimContext( imname );
99
100 createCandidateWindow();
101
102 #ifdef Q_WS_X11
103 if ( !mTreeTop )
104 create_compose_tree();
105 mCompose = new Compose( mTreeTop, this );
106 #endif
107 mTextUtil = new QUimTextUtil( this );
108
109 // read configuration
110 updatePosition();
111
112 m_indicator = new CaretStateIndicator;
113 }
114
~QUimInputContext()115 QUimInputContext::~QUimInputContext()
116 {
117 #ifdef ENABLE_DEBUG
118 qDebug( "~QUimInputContext()" );
119 #endif
120
121 contextList.removeAll( this );
122
123 if ( m_uc )
124 uim_release_context( m_uc );
125 delete proxy;
126 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
127 foreach ( const uim_context uc, m_ucHash )
128 if ( uc )
129 uim_release_context( uc );
130 foreach ( const CandidateWindowProxy* window, proxyHash )
131 delete window;
132 #endif
133
134 if ( this == focusedInputContext )
135 {
136 focusedInputContext = 0;
137 disableFocusedContext = true;
138 }
139
140 #ifdef Q_WS_X11
141 delete mCompose;
142 #endif
143 }
144
createUimContext(const char * imname)145 uim_context QUimInputContext::createUimContext( const char *imname )
146 {
147 uim_context uc = uim_create_context( this, "UTF-8",
148 0, imname,
149 0,
150 QUimInputContext::commit_cb );
151
152 m_HelperManager->checkHelperConnection(uc);
153 /**/
154
155 uim_set_preedit_cb( uc, QUimInputContext::clear_cb,
156 QUimInputContext::pushback_cb,
157 QUimInputContext::update_cb );
158
159 uim_set_candidate_selector_cb( uc,
160 QUimInputContext::cand_activate_cb,
161 QUimInputContext::cand_select_cb,
162 QUimInputContext::cand_shift_page_cb,
163 QUimInputContext::cand_deactivate_cb );
164
165
166 uim_set_prop_list_update_cb( uc, QUimHelperManager::update_prop_list_cb );
167 uim_set_prop_label_update_cb( uc, QUimHelperManager::update_prop_label_cb );
168
169 uim_set_im_switch_request_cb( uc,
170 QUimInputContext::switch_app_global_im_cb,
171 QUimInputContext::switch_system_global_im_cb);
172
173 uim_set_text_acquisition_cb( uc,
174 QUimTextUtil::acquire_text_cb,
175 QUimTextUtil::delete_text_cb);
176
177 #if UIM_QT_USE_DELAY
178 uim_set_delay_candidate_selector_cb( uc,
179 QUimInputContext::cand_activate_with_delay_cb );
180 #endif /* !UIM_QT_USE_DELAY */
181
182 uim_prop_list_update( uc );
183
184 return uc;
185 }
186
createCandidateWindow()187 void QUimInputContext::createCandidateWindow()
188 {
189 proxy = new CandidateWindowProxy;
190 proxy->setQUimInputContext( this );
191 proxy->hide();
192 }
193
194 #ifdef Q_WS_X11
x11FilterEvent(QWidget * keywidget,XEvent * event)195 bool QUimInputContext::x11FilterEvent( QWidget *keywidget, XEvent *event )
196 {
197 Q_UNUSED( keywidget )
198
199 #if UIM_QT_USE_JAPANESE_KANA_KEYBOARD_HACK
200 return uim_x_kana_input_hack_filter_event( m_uc, event );
201 #else
202 Q_UNUSED( event )
203 return false;
204 #endif
205 }
206 #endif // Q_WS_X11
207
filterEvent(const QEvent * event)208 bool QUimInputContext::filterEvent( const QEvent *event )
209 {
210 #ifdef ENABLE_DEBUG
211 qDebug( "filterEvent" );
212 #endif
213
214 int type = event->type();
215
216 if ( type != QEvent::KeyPress &&
217 type != QEvent::KeyRelease )
218 return false;
219
220 const QKeyEvent *keyevent = static_cast<const QKeyEvent *>( event );
221 int qkey = keyevent->key();
222
223 int modifier = 0;
224 if ( keyevent->modifiers() & Qt::ShiftModifier )
225 modifier |= UMod_Shift;
226 if ( keyevent->modifiers() & Qt::ControlModifier )
227 modifier |= UMod_Control;
228 if ( keyevent->modifiers() & Qt::AltModifier )
229 modifier |= UMod_Alt;
230 #if defined(Q_WS_X11)
231 if ( keyevent->modifiers() & Qt::MetaModifier )
232 modifier |= UMod_Meta;
233 #endif
234
235 int key = 0;
236 if ( isascii( qkey ) && isprint( qkey ) )
237 {
238 int ascii = keyevent->text()[ 0 ].toAscii();
239 if ( isalpha( ascii ) )
240 {
241 key = ascii; // uim needs lower/upper encoded key
242 }
243 else
244 {
245 if ( keyevent->modifiers() & Qt::ControlModifier &&
246 ( ascii >= 0x01 && ascii <= 0x1a ) )
247 if ( keyevent->modifiers() & Qt::ShiftModifier )
248 key = ascii + 0x40;
249 else
250 key = ascii + 0x60;
251 else
252 key = qkey;
253 }
254 }
255 else if ( qkey >= Qt::Key_nobreakspace && qkey <= Qt::Key_ydiaeresis )
256 key = qkey;
257 else if ( qkey == Qt::Key_unknown )
258 {
259 QString text = keyevent->text();
260 if ( !text.isNull() )
261 {
262 QChar s = text.at(0);
263 key = unicodeToUKey ( s.unicode() );
264 }
265 else
266 {
267 key = UKey_Other;
268 }
269 }
270 else
271 {
272 if ( qkey >= Qt::Key_F1 && qkey <= Qt::Key_F35 )
273 {
274 key = qkey - Qt::Key_F1 + UKey_F1;
275 }
276 else if ( qkey >= Qt::Key_Dead_Grave && qkey <= Qt::Key_Dead_Horn )
277 {
278 key = qkey - Qt::Key_Dead_Grave + UKey_Dead_Grave;
279 }
280 else if ( qkey >= Qt::Key_Kanji && qkey <= Qt::Key_Eisu_toggle )
281 {
282 key = qkey - Qt::Key_Kanji + UKey_Kanji;
283 }
284 else if ( qkey >= Qt::Key_Hangul && qkey <= Qt::Key_Hangul_Special )
285 {
286 key = qkey - Qt::Key_Hangul + UKey_Hangul;
287 }
288 else
289 {
290 switch ( qkey )
291 {
292 case Qt::Key_Tab: key = UKey_Tab; break;
293 case Qt::Key_Backspace: key = UKey_Backspace; break;
294 case Qt::Key_Escape: key = UKey_Escape; break;
295 case Qt::Key_Delete: key = UKey_Delete; break;
296 case Qt::Key_Return: key = UKey_Return; break;
297 case Qt::Key_Left: key = UKey_Left; break;
298 case Qt::Key_Up: key = UKey_Up; break;
299 case Qt::Key_Right: key = UKey_Right; break;
300 case Qt::Key_Down: key = UKey_Down; break;
301 case Qt::Key_PageUp: key = UKey_Prior; break;
302 case Qt::Key_PageDown: key = UKey_Next; break;
303 case Qt::Key_Home: key = UKey_Home; break;
304 case Qt::Key_End: key = UKey_End; break;
305 case Qt::Key_Multi_key: key = UKey_Multi_key; break;
306 case Qt::Key_Mode_switch: key = UKey_Mode_switch; break;
307 case Qt::Key_Codeinput: key = UKey_Codeinput; break;
308 case Qt::Key_SingleCandidate: key = UKey_SingleCandidate; break;
309 case Qt::Key_MultipleCandidate: key = UKey_MultipleCandidate; break;
310 case Qt::Key_PreviousCandidate: key = UKey_PreviousCandidate; break;
311 // Qt4 seems to add its own modifier even the event is
312 // KeyPress, which differs from ordinary model. So remove
313 // them for uim.
314 case Qt::Key_Shift: key = UKey_Shift_key;
315 if ( type == QEvent::KeyPress )
316 modifier &= ~UMod_Shift;
317 break;
318 case Qt::Key_Control: key = UKey_Control_key;
319 if ( type == QEvent::KeyPress )
320 modifier &= ~UMod_Control;
321 break;
322 case Qt::Key_Alt: key = UKey_Alt_key;
323 if ( type == QEvent::KeyPress )
324 modifier &= ~UMod_Alt;
325 break;
326 case Qt::Key_Meta: key = UKey_Meta_key;
327 #ifdef Q_WS_X11
328 if ( type == QEvent::KeyPress )
329 modifier &= ~UMod_Meta;
330 #endif
331 break;
332 case Qt::Key_CapsLock: key = UKey_Caps_Lock; break;
333 case Qt::Key_NumLock: key = UKey_Num_Lock; break;
334 case Qt::Key_ScrollLock: key = UKey_Scroll_Lock; break;
335 default: key = UKey_Other;
336 }
337 }
338 }
339
340 int notFiltered;
341 if ( type == QEvent::KeyPress )
342 {
343 notFiltered = uim_press_key( m_uc, key, modifier );
344 #ifdef Q_WS_X11
345 if ( notFiltered )
346 return mCompose->handle_qkey( keyevent );
347 #else
348 if ( notFiltered )
349 return false;
350 #endif
351 }
352 else if ( type == QEvent::KeyRelease )
353 {
354 notFiltered = uim_release_key( m_uc, key, modifier );
355 #ifdef Q_WS_X11
356 if ( notFiltered )
357 return mCompose->handle_qkey( keyevent );
358 #else
359 if ( notFiltered )
360 return false;
361 #endif
362 }
363
364 return true;
365 }
366
setFocusWidget(QWidget * w)367 void QUimInputContext::setFocusWidget( QWidget *w )
368 {
369 #ifdef ENABLE_DEBUG
370 qDebug( "QUimInputContext::setFocusWidget() w = %p", w );
371 #endif
372
373 QInputContext::setFocusWidget( w );
374
375 if ( w )
376 setFocus();
377 else
378 unsetFocus();
379 }
380
381 // Qt4 does not have QInputContext::setFocus()
setFocus()382 void QUimInputContext::setFocus()
383 {
384 #ifdef ENABLE_DEBUG
385 qDebug( "QUimInputContext: %p->setFocus(), focusWidget()=%p",
386 this, QApplication::focusWidget() );
387 #endif
388
389 focusedInputContext = this;
390 disableFocusedContext = false;
391
392 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
393 focusedWidget = QApplication::focusWidget();
394 if ( isPreeditPreservationEnabled()
395 && m_ucHash.contains( focusedWidget ) )
396 restorePreedit();
397 else
398 #endif
399 if ( candwinIsActive )
400 proxy->popup();
401
402 m_HelperManager->checkHelperConnection(m_uc);
403
404 uim_helper_client_focus_in( m_uc );
405 uim_prop_list_update( m_uc );
406
407 uim_focus_in_context( m_uc );
408 }
409
410 // Qt4 does not have QInputContext::unsetFocus()
unsetFocus()411 void QUimInputContext::unsetFocus()
412 {
413 #ifdef ENABLE_DEBUG
414 qDebug( "QUimInputContext: %p->unsetFocus(), focusWidget()=%p",
415 this, QApplication::focusWidget() );
416 #endif
417
418 uim_focus_out_context( m_uc );
419
420 proxy->hide();
421 m_indicator->hide();
422
423 m_HelperManager->checkHelperConnection(m_uc);
424
425 uim_helper_client_focus_out( m_uc );
426 }
427
focusedIC()428 QUimInputContext * QUimInputContext::focusedIC()
429 {
430 return focusedInputContext;
431 }
432
mouseHandler(int x,QMouseEvent * e)433 void QUimInputContext::mouseHandler( int x, QMouseEvent *e )
434 {
435 switch ( e->type() )
436 {
437 case QEvent::MouseButtonPress:
438 case QEvent::MouseButtonRelease:
439 case QEvent::MouseButtonDblClick:
440 case QEvent::MouseMove:
441 #ifdef ENABLE_DEBUG
442 qDebug( "QUimInputContext::mouseHandler: "
443 "x=%d, type=%d, button=%d ", x, e->type(), e->button() );
444 #else
445 Q_UNUSED( x )
446 #endif
447 break;
448 default:
449 break;
450 }
451 }
452
reset()453 void QUimInputContext::reset()
454 {
455 #ifdef ENABLE_DEBUG
456 qDebug( "QUimInputContext::reset()" );
457 #endif
458
459 candwinIsActive = false;
460
461 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
462 // Japanese input context sometimes contains a whole paragraph
463 // and has minutes of lifetime different to ephemeral one
464 // in other languages. The input context should be survived
465 // until focused again.
466 if ( isPreeditPreservationEnabled()
467 && !m_ucHash.contains( focusedWidget ) ) {
468 psegs.isEmpty() ? proxy->hide() : savePreedit();
469 return;
470 }
471 #endif
472 proxy->hide();
473 uim_reset_context( m_uc );
474 #ifdef Q_WS_X11
475 mCompose->reset();
476 #endif
477 clearPreedit();
478 updatePreedit();
479 }
480
update()481 void QUimInputContext::update()
482 {
483 QWidget *w = QApplication::focusWidget();
484
485 #ifdef ENABLE_DEBUG
486 qDebug( "QUimInputContext::update() w = %p", w );
487 #endif
488
489 if ( w ) {
490 QRect mf = w->inputMethodQuery( Qt::ImMicroFocus ).toRect();
491 QPoint p = w->mapToGlobal( mf.topLeft() );
492 proxy->layoutWindow( p.x(), p.y(), mf.height() );
493 m_indicator->move( w->mapToGlobal( mf.bottomLeft() )
494 + QPoint( 0, CaretStateIndicator::SPACING ) );
495 }
496 }
497
identifierName()498 QString QUimInputContext::identifierName()
499 {
500 return QString( "uim" );
501 }
502
language()503 QString QUimInputContext::language()
504 {
505 const QUimInfoManager *manager
506 = UimInputContextPlugin::getQUimInfoManager();
507 const QString name = QString::fromUtf8( uim_get_current_im_name( m_uc ) );
508 return manager->imLang( name );
509 }
510
511 // callbacks for uim
commit_cb(void * ptr,const char * str)512 void QUimInputContext::commit_cb( void *ptr, const char *str )
513 {
514 QString qs = QString::fromUtf8( str );
515 #ifdef ENABLE_DEBUG
516 qDebug( "commit_cb : str = |%s|", qs.toLocal8Bit().data() );
517 #endif
518
519 QUimInputContext *ic = static_cast<QUimInputContext *>( ptr );
520 ic->commitString( qs );
521 }
522
clear_cb(void * ptr)523 void QUimInputContext::clear_cb( void *ptr )
524 {
525 #ifdef ENABLE_DEBUG
526 qDebug( "clear_cb" );
527 #endif
528
529 QUimInputContext* ic = static_cast<QUimInputContext*>( ptr );
530 ic->clearPreedit();
531 }
532
pushback_cb(void * ptr,int attr,const char * str)533 void QUimInputContext::pushback_cb( void *ptr, int attr, const char *str )
534 {
535 QString qs = QString::fromUtf8( str );
536 #ifdef ENABLE_DEBUG
537 qDebug( "pushback_cb : str = |%s|", qs.toLocal8Bit().data() );
538 #endif
539
540 if ( !str )
541 return ;
542 // Reject invalid empty string. UPreeditAttr_Cursor or
543 // UPreeditAttr_Separator with empty string is *valid* and
544 // required to work properly.
545 if ( !strcmp( str, "" ) && !( attr & ( UPreeditAttr_Cursor | UPreeditAttr_Separator ) ) )
546 return ;
547
548 QUimInputContext* ic = static_cast<QUimInputContext*>( ptr );
549 ic->pushbackPreeditString( attr, qs );
550 }
551
update_cb(void * ptr)552 void QUimInputContext::update_cb( void *ptr )
553 {
554 #ifdef ENABLE_DEBUG
555 qDebug( "update_cb" );
556 #endif
557
558 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
559 ic->updatePreedit();
560 }
561
cand_activate_cb(void * ptr,int nr,int displayLimit)562 void QUimInputContext::cand_activate_cb( void *ptr, int nr, int displayLimit )
563 {
564 #ifdef ENABLE_DEBUG
565 qDebug( "cand_activate_cb" );
566 #endif
567
568 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
569 ic->proxy->candidateActivate( nr, displayLimit );
570 }
571
cand_select_cb(void * ptr,int index)572 void QUimInputContext::cand_select_cb( void *ptr, int index )
573 {
574 #ifdef ENABLE_DEBUG
575 qDebug( "cand_select_cb" );
576 #endif
577
578 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
579 ic->proxy->candidateSelect( index );
580 }
581
cand_shift_page_cb(void * ptr,int forward)582 void QUimInputContext::cand_shift_page_cb( void *ptr, int forward )
583 {
584 #ifdef ENABLE_DEBUG
585 qDebug( "cand_shift_page_cb" );
586 #endif
587
588 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
589 ic->proxy->candidateShiftPage( forward );
590 }
591
cand_deactivate_cb(void * ptr)592 void QUimInputContext::cand_deactivate_cb( void *ptr )
593 {
594 #ifdef ENABLE_DEBUG
595 qDebug( "cand_deactivate_cb" );
596 #endif
597
598 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
599 ic->proxy->deactivateCandwin();
600 ic->candwinIsActive = false;
601 }
602
switch_app_global_im_cb(void * ptr,const char * name)603 void QUimInputContext::switch_app_global_im_cb( void *ptr, const char *name )
604 {
605 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
606 ic->switch_app_global_im( name );
607 }
608
switch_system_global_im_cb(void * ptr,const char * name)609 void QUimInputContext::switch_system_global_im_cb( void *ptr, const char *name )
610 {
611 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
612 ic->switch_system_global_im( name );
613 }
614
615 #if UIM_QT_USE_DELAY
cand_activate_with_delay_cb(void * ptr,int delay)616 void QUimInputContext::cand_activate_with_delay_cb( void *ptr, int delay )
617 {
618 QUimInputContext *ic = static_cast<QUimInputContext*>( ptr );
619 ic->proxy->candidateActivateWithDelay( delay );
620 }
621 #endif /* !UIM_QT_USE_DELAY */
622
commitString(const QString & str)623 void QUimInputContext::commitString( const QString& str )
624 {
625 QInputMethodEvent e;
626 e.setCommitString( str );
627 sendEvent( e );
628
629 m_isComposing = false;
630 }
631
clearPreedit()632 void QUimInputContext::clearPreedit()
633 {
634 psegs.clear();
635 }
636
pushbackPreeditString(int attr,const QString & str)637 void QUimInputContext::pushbackPreeditString( int attr, const QString& str )
638 {
639 PreeditSegment ps( attr, str );
640 psegs.append( ps );
641 }
642
updatePreedit()643 void QUimInputContext::updatePreedit()
644 {
645 QString newString = getPreeditString();
646
647 if ( !isComposing() ) {
648 if ( newString.isEmpty() )
649 return;
650
651 // Start conversion
652 m_isComposing = true;
653 }
654
655 if ( !newString.isEmpty() ) {
656 QInputMethodEvent e( newString, getPreeditAttrs() );
657 sendEvent( e );
658 // Qt4.3.1 does not call back update() here
659 update();
660 } else {
661 // Complete conversion implicitly since the preedit is empty
662 commitString( "" );
663 }
664 }
665
666 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
savePreedit()667 void QUimInputContext::savePreedit()
668 {
669 m_ucHash.insert( focusedWidget, m_uc );
670 psegsHash.insert( focusedWidget, psegs );
671 proxyHash.insert( focusedWidget, proxy );
672 visibleHash.insert( focusedWidget, proxy->isVisible() );
673 proxy->hide();
674
675 const char *imname = uim_get_current_im_name( m_uc );
676 if ( imname )
677 m_uc = createUimContext( imname );
678 psegs.clear();
679 createCandidateWindow();
680 }
681
restorePreedit()682 void QUimInputContext::restorePreedit()
683 {
684 CandidateWindowProxy *window = proxyHash.take( focusedWidget );
685 // if window is 0, updateStyle() was called.
686 if ( !window ) {
687 psegs = psegsHash.take( focusedWidget );
688 QString preedit;
689 while ( !psegs.isEmpty() ) {
690 preedit += psegs.takeFirst().str;
691 }
692 commitString( preedit );
693
694 uim_context uc = m_ucHash.take( focusedWidget );
695 if ( uc )
696 uim_release_context( uc );
697 visibleHash.remove( focusedWidget );
698 return;
699 }
700 if ( m_uc )
701 uim_release_context( m_uc );
702 delete proxy;
703 m_uc = m_ucHash.take( focusedWidget );
704 psegs = psegsHash.take( focusedWidget );
705 proxy = window;
706 if ( visibleHash.take( focusedWidget ) )
707 proxy->popup();
708 }
709 #endif
710
saveContext()711 void QUimInputContext::saveContext()
712 {
713 // just send QInputMethodEvent and keep preedit string
714 if ( isComposing() )
715 commitString( "" );
716 }
717
restoreContext()718 void QUimInputContext::restoreContext()
719 {
720 updatePreedit();
721 }
722
isPreeditPreservationEnabled()723 bool QUimInputContext::isPreeditPreservationEnabled()
724 {
725 return ( language() == "ja" );
726 }
727
getPreeditString()728 QString QUimInputContext::getPreeditString()
729 {
730 QString pstr;
731
732 QList<PreeditSegment>::ConstIterator seg = psegs.begin();
733 const QList<PreeditSegment>::ConstIterator end = psegs.end();
734 for ( ; seg != end; ++seg )
735 {
736 if ( ( *seg ).attr & UPreeditAttr_Separator && ( *seg ).str.isEmpty() )
737 {
738 pstr += DEFAULT_SEPARATOR_STR;
739 }
740 else
741 {
742 pstr += ( *seg ).str;
743 }
744 }
745
746 return pstr;
747 }
748
getPreeditCursorPosition()749 int QUimInputContext::getPreeditCursorPosition()
750 {
751 if ( proxy->isAlwaysLeftPosition() )
752 return 0;
753
754 int cursorPos = 0;
755 QList<PreeditSegment>::ConstIterator seg = psegs.begin();
756 const QList<PreeditSegment>::ConstIterator end = psegs.end();
757 for ( ; seg != end; ++seg )
758 {
759 if ( ( *seg ).attr & UPreeditAttr_Cursor )
760 {
761 return cursorPos;
762 }
763 else if ( ( *seg ).attr & UPreeditAttr_Separator
764 && ( *seg ).str.isEmpty() )
765 {
766 cursorPos += QString( DEFAULT_SEPARATOR_STR ).length();
767 }
768 else
769 {
770 cursorPos += ( *seg ).str.length();
771 }
772 }
773
774 return cursorPos;
775 }
776
getPreeditSelectionLength()777 int QUimInputContext::getPreeditSelectionLength()
778 {
779 int selectionLength = 0;
780
781 QList<PreeditSegment>::ConstIterator seg = psegs.begin();
782 const QList<PreeditSegment>::ConstIterator end = psegs.end();
783 for ( ; seg != end; ++seg )
784 {
785 // In converting state, uim encodes UPreeditAttr_Cursor into
786 // selected segment rather than separated empty cursor
787 // segment. So we can get selection length by length of this
788 // 'selected segment'. Don't use visual attributes such as
789 // UPreeditAttr_Underline or UPreeditAttr_Reverse to detect
790 // logical selection length. They are sometimes disabled by
791 // user preference.
792 if ( ( *seg ).attr & UPreeditAttr_Cursor )
793 {
794 selectionLength = ( *seg ).str.length();
795 return selectionLength;
796 }
797 }
798
799 return 0;
800 }
801
getUserDefinedColor(const char * symbol)802 static QColor getUserDefinedColor( const char *symbol )
803 {
804 char *literal = uim_scm_symbol_value_str( symbol );
805 QColor color( QString::fromAscii( literal ) );
806 free( literal );
807 return color;
808 }
809
getPreeditAttrs()810 QList<QInputMethodEvent::Attribute> QUimInputContext::getPreeditAttrs()
811 {
812 const int HIDE_CARET = 0;
813 const int SHOW_CARET = 1;
814 const int DUMMY = 0;
815 QList<QInputMethodEvent::Attribute> attrs;
816
817 QList<PreeditSegment>::ConstIterator seg = psegs.begin();
818 const QList<PreeditSegment>::ConstIterator end = psegs.end();
819 int segPos = 0;
820 for ( ; seg != end; ++seg ) {
821 int uimAttr = ( *seg ).attr;
822 int segStrLen = ( *seg ).str.length();
823 QTextCharFormat segFmt;
824
825 if ( uimAttr & UPreeditAttr_Cursor ) {
826 // Transparent cursor is required to set microfocus even
827 // if the caret is invisible to user.
828 //
829 // FIXME: Segment string may be outframed if the cursor is
830 // located near the right frame.
831 int visibility = ( segStrLen ) ? HIDE_CARET : SHOW_CARET;
832 QInputMethodEvent::Attribute cursor( QInputMethodEvent::Cursor,
833 segPos, visibility, DUMMY );
834 attrs << cursor;
835 } else if ( uimAttr & UPreeditAttr_Separator ) {
836 if ( !segStrLen )
837 segStrLen = QString( DEFAULT_SEPARATOR_STR ).length();
838 if ( !( uimAttr & UPreeditAttr_Reverse ) ) {
839 QColor color = getUserDefinedColor( "separator-foreground" );
840 if ( color.isValid() )
841 segFmt.setForeground( color );
842 color = getUserDefinedColor( "separator-background" );
843 if ( color.isValid() )
844 segFmt.setBackground( color );
845
846 }
847 }
848
849 if ( segStrLen ) {
850 if ( uimAttr & UPreeditAttr_Reverse ) {
851 #if 0
852 // FIXME: fmt.foreground() is white (expecting black)
853 QTextFormat fmt = standardFormat( PreeditFormat );
854 segFmt.setForeground( fmt.background() );
855 segFmt.setBackground( fmt.foreground() );
856 #else
857 // FIXME: Retrieve customized colors from the text widget
858 // foreground symbol
859 const char *fgSymbol;
860 // background symbol
861 const char *bgSymbol;
862 if ( uimAttr & UPreeditAttr_Separator ) {
863 fgSymbol = "reversed-separator-foreground";
864 bgSymbol = "reversed-separator-background";
865 } else {
866 fgSymbol = "reversed-preedit-foreground";
867 bgSymbol = "reversed-preedit-background";
868 }
869 QColor color = getUserDefinedColor( fgSymbol );
870 segFmt.setForeground( color.isValid() ? color : Qt::white );
871 color = getUserDefinedColor( bgSymbol );
872 segFmt.setBackground( color.isValid() ? color : Qt::black );
873 #endif
874 }
875 if ( uimAttr & UPreeditAttr_UnderLine ) {
876 segFmt.setFontUnderline( true );
877 }
878 QInputMethodEvent::Attribute segAttr( QInputMethodEvent::TextFormat,
879 segPos, segStrLen, segFmt );
880 attrs << segAttr;
881 segPos += segStrLen;
882 }
883 }
884
885 return attrs;
886 }
887
switch_app_global_im(const char * name)888 void QUimInputContext::switch_app_global_im( const char *name )
889 {
890 QList<QUimInputContext*>::iterator it;
891 QString im_name_sym( "'" );
892
893 im_name_sym += name;
894
895 for ( it = contextList.begin(); it != contextList.end(); ++it )
896 {
897 if ( ( *it ) != this) {
898 uim_switch_im( ( *it )->uimContext(), name );
899 ( *it )->updatePosition();
900 }
901 }
902 uim_prop_update_custom(this->uimContext(), "custom-preserved-default-im-name", im_name_sym.toUtf8().data() );
903 }
904
switch_system_global_im(const char * name)905 void QUimInputContext::switch_system_global_im( const char *name )
906 {
907 switch_app_global_im( name );
908 QUimHelperManager::send_im_change_whole_desktop( name );
909 }
910
updatePosition()911 void QUimInputContext::updatePosition()
912 {
913 char * leftp = uim_scm_symbol_value_str( "candidate-window-position" );
914 proxy->setAlwaysLeftPosition( leftp && !strcmp( leftp, "left" ) );
915 free( leftp );
916 }
917
updateStyle()918 void QUimInputContext::updateStyle()
919 {
920 // don't update window style if deprecated uim-candwin-prog is set
921 char *candwinprog = uim_scm_symbol_value_str( "uim-candwin-prog" );
922 if ( candwinprog ) {
923 free( candwinprog );
924 return;
925 }
926 delete proxy;
927 createCandidateWindow();
928 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
929 // invalidate all the candidate windows stored in proxyHash
930 QHashIterator<QWidget*, CandidateWindowProxy*> i( proxyHash );
931 while ( i.hasNext() ) {
932 i.next();
933 QWidget *widget = i.key();
934 delete proxyHash[ widget ];
935 proxyHash[ widget ] = 0;
936 }
937 #endif
938 }
939
updateIndicator(const QString & str)940 void QUimInputContext::updateIndicator( const QString &str )
941 {
942 m_indicator->update( str );
943 }
944
unicodeToUKey(ushort c)945 static int unicodeToUKey (ushort c) {
946 int sym;
947
948 switch (c) {
949 case 0x00A5: sym = UKey_Yen; break;
950 case 0x3002: sym = UKey_Kana_Fullstop; break;
951 case 0x300C: sym = UKey_Kana_OpeningBracket; break;
952 case 0x300D: sym = UKey_Kana_ClosingBracket; break;
953 case 0x3001: sym = UKey_Kana_Comma; break;
954 case 0x30FB: sym = UKey_Kana_Conjunctive; break;
955 case 0x30F2: sym = UKey_Kana_WO; break;
956 case 0x30A1: sym = UKey_Kana_a; break;
957 case 0x30A3: sym = UKey_Kana_i; break;
958 case 0x30A5: sym = UKey_Kana_u; break;
959 case 0x30A7: sym = UKey_Kana_e; break;
960 case 0x30A9: sym = UKey_Kana_o; break;
961 case 0x30E3: sym = UKey_Kana_ya; break;
962 case 0x30E5: sym = UKey_Kana_yu; break;
963 case 0x30E7: sym = UKey_Kana_yo; break;
964 case 0x30C3: sym = UKey_Kana_tsu; break;
965 case 0x30FC: sym = UKey_Kana_ProlongedSound; break;
966 case 0x30A2: sym = UKey_Kana_A; break;
967 case 0x30A4: sym = UKey_Kana_I; break;
968 case 0x30A6: sym = UKey_Kana_U; break;
969 case 0x30A8: sym = UKey_Kana_E; break;
970 case 0x30AA: sym = UKey_Kana_O; break;
971 case 0x30AB: sym = UKey_Kana_KA; break;
972 case 0x30AD: sym = UKey_Kana_KI; break;
973 case 0x30AF: sym = UKey_Kana_KU; break;
974 case 0x30B1: sym = UKey_Kana_KE; break;
975 case 0x30B3: sym = UKey_Kana_KO; break;
976 case 0x30B5: sym = UKey_Kana_SA; break;
977 case 0x30B7: sym = UKey_Kana_SHI; break;
978 case 0x30B9: sym = UKey_Kana_SU; break;
979 case 0x30BB: sym = UKey_Kana_SE; break;
980 case 0x30BD: sym = UKey_Kana_SO; break;
981 case 0x30BF: sym = UKey_Kana_TA; break;
982 case 0x30C1: sym = UKey_Kana_CHI; break;
983 case 0x30C4: sym = UKey_Kana_TSU; break;
984 case 0x30C6: sym = UKey_Kana_TE; break;
985 case 0x30C8: sym = UKey_Kana_TO; break;
986 case 0x30CA: sym = UKey_Kana_NA; break;
987 case 0x30CB: sym = UKey_Kana_NI; break;
988 case 0x30CC: sym = UKey_Kana_NU; break;
989 case 0x30CD: sym = UKey_Kana_NE; break;
990 case 0x30CE: sym = UKey_Kana_NO; break;
991 case 0x30CF: sym = UKey_Kana_HA; break;
992 case 0x30D2: sym = UKey_Kana_HI; break;
993 case 0x30D5: sym = UKey_Kana_FU; break;
994 case 0x30D8: sym = UKey_Kana_HE; break;
995 case 0x30DB: sym = UKey_Kana_HO; break;
996 case 0x30DE: sym = UKey_Kana_MA; break;
997 case 0x30DF: sym = UKey_Kana_MI; break;
998 case 0x30E0: sym = UKey_Kana_MU; break;
999 case 0x30E1: sym = UKey_Kana_ME; break;
1000 case 0x30E2: sym = UKey_Kana_MO; break;
1001 case 0x30E4: sym = UKey_Kana_YA; break;
1002 case 0x30E6: sym = UKey_Kana_YU; break;
1003 case 0x30E8: sym = UKey_Kana_YO; break;
1004 case 0x30E9: sym = UKey_Kana_RA; break;
1005 case 0x30EA: sym = UKey_Kana_RI; break;
1006 case 0x30EB: sym = UKey_Kana_RU; break;
1007 case 0x30EC: sym = UKey_Kana_RE; break;
1008 case 0x30ED: sym = UKey_Kana_RO; break;
1009 case 0x30EF: sym = UKey_Kana_WA; break;
1010 case 0x30F3: sym = UKey_Kana_N; break;
1011 case 0x309B: sym = UKey_Kana_VoicedSound; break;
1012 case 0x309C: sym = UKey_Kana_SemivoicedSound; break;
1013 default:
1014 sym = UKey_Other;
1015 break;
1016 }
1017
1018 return sym;
1019 }
1020