1 /*
2
3 Copyright (c) 2003-2013 uim Project https://github.com/uim/uim
4
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10
11 1. Redistributions of source code must retain the above copyright
12 notice, this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the
15 documentation and/or other materials provided with the distribution.
16 3. Neither the name of authors nor the names of its contributors
17 may be used to endorse or promote products derived from this software
18 without specific prior written permission.
19
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 SUCH DAMAGE.
31
32 */
33 #include <config.h>
34
35 #include <qapplication.h>
36 #include <qlabel.h>
37 #include <qwidget.h>
38 #include <qheader.h>
39 #include <qsocketnotifier.h>
40 #include <qstringlist.h>
41 #include <qtextcodec.h>
42 #include <qrect.h>
43
44 #include <locale.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <stdlib.h>
49
50
51 #include "qtgettext.h"
52 #include "qt.h"
53
54 static const int NR_CANDIDATES = 10;
55 static const int MIN_CAND_WIDTH = 80;
56
57 const Qt::WFlags candidateFlag = ( Qt::WType_TopLevel
58 | Qt::WStyle_Customize
59 | Qt::WStyle_StaysOnTop
60 | Qt::WStyle_NoBorder
61 | Qt::WStyle_Tool
62 #if defined(Q_WS_X11)
63 | Qt::WX11BypassWM
64 #endif
65 );
66 static QSocketNotifier *notifier = NULL;
67
CandidateWindow(QWidget * parent,const char * name)68 CandidateWindow::CandidateWindow( QWidget *parent, const char * name )
69 : QVBox( parent, name, candidateFlag )
70 {
71 setFrameStyle( Raised | NoFrame );
72 setFocusPolicy( QWidget::NoFocus );
73
74 //setup CandidateList
75 cList = new CandidateListView( this, "candidateListView" );
76 cList->setSorting( -1 );
77 cList->setSelectionMode( QListView::Single );
78 cList->addColumn( "1" );
79 cList->setColumnWidthMode( 0, QListView::Maximum );
80 cList->addColumn( "2" );
81 cList->setColumnWidthMode( 1, QListView::Maximum );
82 cList->header() ->hide();
83 cList->setVScrollBarMode( QScrollView::AlwaysOff );
84 cList->setHScrollBarMode( QScrollView::AlwaysOff );
85 cList->setAllColumnsShowFocus( true );
86 QObject::connect( cList, SIGNAL( clicked( QListViewItem * ) ),
87 this , SLOT( slotCandidateSelected( QListViewItem * ) ) );
88
89 //setup NumberLabel
90 numLabel = new QLabel( this, "candidateLabel" );
91 numLabel->setFocusPolicy( QWidget::NoFocus );
92
93 nrCandidates = 0;
94 candidateIndex = 0;
95 displayLimit = NR_CANDIDATES;
96 pageIndex = -1;
97
98 isActive = false;
99
100 notifier = new QSocketNotifier( 0, QSocketNotifier::Read );
101 QObject::connect( notifier, SIGNAL( activated( int ) ),
102 this, SLOT( slotStdinActivated( int ) ) );
103 hide();
104 }
105
~CandidateWindow()106 CandidateWindow::~CandidateWindow()
107 {
108 if ( !stores.isEmpty() )
109 stores.clear();
110 }
111
activateCand(const QStringList & list)112 void CandidateWindow::activateCand( const QStringList &list )
113 {
114 #if defined(ENABLE_DEBUG)
115 qDebug( "uim-candwin-qt: activateCand()" );
116 #endif
117 /**
118 * format: activate\fcharset=$charset\fdisplay_limit=$value\fhead1\acand1\aannot1\fhead2\acand2\aannot2\fhead3\acand3\aannot3\f
119 */
120
121 // remove old data
122 cList->clear();
123 stores.clear();
124
125 // get charset and create codec
126 QTextCodec *codec = NULL;
127 if ( !list[ 1 ].isEmpty() && list[ 1 ].startsWith( "charset" ) )
128 {
129 const QStringList l = QStringList::split( "=", list[ 1 ] );
130 codec = QTextCodec::codecForName( l[ 1 ] );
131 }
132
133 // get display_limit
134 if ( !list[ 2 ].isEmpty() && list[ 2 ].startsWith( "display_limit" ) )
135 {
136 const QStringList l = QStringList::split( "=", list[ 2 ] );
137 displayLimit = l[ 1 ].toInt();
138 }
139
140 for ( int i = 3; !list[ i ].isNull(); i++ )
141 {
142 // case list[i] = ""
143 if ( list[ i ].isEmpty() )
144 break;
145
146 // split heading_label and cand_str
147 QStringList l = QStringList::split( "\a", list [ i ], true );
148
149 // store data
150 CandData d;
151 QString headString;
152 if ( codec )
153 headString = codec->toUnicode( l [ 0 ] );
154 else
155 headString = l [ 0 ];
156
157 d.label = headString;
158
159 l.pop_front();
160 QString candString = l [ 0 ];
161
162 if ( codec )
163 d.str = codec->toUnicode( candString );
164 else
165 d.str = candString;
166
167 l.pop_front();
168 QString annotString = l [ 0 ];
169
170 stores.append( d );
171 }
172
173 // set default value
174 candidateIndex = -1;
175 nrCandidates = stores.count();
176
177 // shift to default page
178 setPage( 0 );
179
180 adjustCandidateWindowSize();
181 show();
182
183 isActive = true;
184 }
selectCand(const QStringList & list)185 void CandidateWindow::selectCand( const QStringList &list )
186 {
187 #if defined(ENABLE_DEBUG)
188 qDebug( "uim-candwin-qt: selectCand()" );
189 #endif
190 const int index = list[ 1 ].toInt();
191 needHilite = (list[ 2 ].toInt() == 1) ? TRUE : FALSE;
192 setIndex( index );
193
194 updateLabel();
195 }
196
moveCand(const QStringList & list)197 void CandidateWindow::moveCand( const QStringList &list )
198 {
199 #if defined(ENABLE_DEBUG)
200 qDebug( "uim-candwin-qt: moveCand()" );
201 #endif
202 if ( list[ 1 ].isEmpty() || list[ 2 ].isEmpty() )
203 return ;
204
205 const int topwin_x = list[ 1 ].toInt();
206 const int topwin_y = list[ 2 ].toInt();
207 const int cw_wi = width();
208 const int cw_he = height();
209 const int sc_wi = QApplication::desktop() ->screenGeometry().width();
210 const int sc_he = QApplication::desktop() ->screenGeometry().height();
211
212 int x, y;
213 if ( sc_wi < topwin_x + cw_wi )
214 {
215 x = topwin_x - cw_wi;
216 }
217 else
218 {
219 x = topwin_x;
220 }
221
222 if ( sc_he < topwin_y + cw_he )
223 {
224 /* FIXME : How can I determine the preedit height? */
225 y = topwin_y - cw_he - 20;
226 }
227 else
228 {
229 y = topwin_y;
230 }
231
232 move( x, y );
233 }
234
showCand()235 void CandidateWindow::showCand()
236 {
237 #if defined(ENABLE_DEBUG)
238 qDebug( "uim-candwin-qt: showCand()" );
239 #endif
240 if ( isActive )
241 show();
242 }
deactivateCand()243 void CandidateWindow::deactivateCand()
244 {
245 #if defined(ENABLE_DEBUG)
246 qDebug( "uim-candwin-qt: deactivateCand()" );
247 #endif
248 hide();
249 isActive = false;
250 }
setNrCandidates(const QStringList & list)251 void CandidateWindow::setNrCandidates( const QStringList &list )
252 {
253 #if defined(ENABLE_DEBUG)
254 qDebug( "uim-candwin-qt: setNrCandidates()" );
255 #endif
256 if ( list[ 1 ].isEmpty() || list[ 2 ].isEmpty() )
257 return ;
258
259 // remove old data
260 cList->clear();
261 stores.clear();
262
263 // set default value
264 candidateIndex = -1;
265 nrCandidates = list[ 1 ].toInt();
266 displayLimit = list[ 2 ].toInt();
267 needHilite = false;
268 isActive = true;
269
270 // setup dummy stores
271 for ( int i = 0; i < nrCandidates; i++ ) {
272 CandData d;
273 stores.append( d );
274 }
275 }
setPageCandidates(const QStringList & list)276 void CandidateWindow::setPageCandidates( const QStringList &list )
277 {
278 #if defined(ENABLE_DEBUG)
279 qDebug( "uim-candwin-qt: setPageCandidates()" );
280 #endif
281 /**
282 * format: set_page_candidates\fcharset=$charset\fpage=$value\fhead1\acand1\aannot1\fhead2\acand2\aannot2\fhead3\acand3\aannot3\f
283 */
284
285 int page = 0;
286
287 // get charset and create codec
288 QTextCodec *codec = NULL;
289 if ( !list[ 1 ].isEmpty() && list[ 1 ].startsWith( "charset" ) )
290 {
291 const QStringList l = QStringList::split( "=", list[ 1 ] );
292 codec = QTextCodec::codecForName( l[ 1 ] );
293 }
294
295 // get page
296 if ( !list[ 2 ].isEmpty() && list[ 2 ].startsWith( "page" ) )
297 {
298 const QStringList l = QStringList::split( "=", list[ 2 ] );
299 page = l[ 1 ].toInt();
300 }
301
302 for ( int i = 3; !list[ i ].isNull(); i++ )
303 {
304 // case list[i] = ""
305 if ( list[ i ].isEmpty() )
306 break;
307
308 // split heading_label and cand_str
309 QStringList l = QStringList::split( "\a", list [ i ], true );
310
311 // store data
312 CandData &d = stores[page * displayLimit + i - 3];
313 QString headString;
314 if ( codec )
315 headString = codec->toUnicode( l [ 0 ] );
316 else
317 headString = l [ 0 ];
318
319 d.label = headString;
320
321 l.pop_front();
322 QString candString = l [ 0 ];
323
324 if ( codec )
325 d.str = codec->toUnicode( candString );
326 else
327 d.str = candString;
328
329 l.pop_front();
330 QString annotString = l [ 0 ];
331 }
332 }
showPage(const QStringList & list)333 void CandidateWindow::showPage( const QStringList &list )
334 {
335 #if defined(ENABLE_DEBUG)
336 qDebug( "uim-candwin-qt: showPage()" );
337 #endif
338 const int page = list[ 1 ].toInt();
339
340 setPage( page );
341 adjustCandidateWindowSize();
342 show();
343 }
slotStdinActivated(int fd)344 void CandidateWindow::slotStdinActivated( int fd )
345 {
346 char buf[ 4096 ];
347 char *read_buf = strdup( "" );
348 int n;
349
350 while (uim_helper_fd_readable( fd ) > 0) {
351 n = read( fd, buf, 4096 - 1 );
352 if ( n == 0 )
353 {
354 close( fd );
355 exit( 1 );
356 }
357 if ( n == -1 )
358 return ;
359 buf[ n ] = '\0';
360 read_buf = (char *)realloc( read_buf, strlen( read_buf ) + n + 1 );
361 strcat( read_buf, buf );
362 }
363
364 QStringList msgList = QStringList::split( "\f\f", QString( read_buf ) );
365
366 QStringList::Iterator it = msgList.begin();
367 const QStringList::Iterator end = msgList.end();
368 for ( ; it != end; ++it )
369 strParse( ( *it ) );
370 free( read_buf );
371 }
372
strParse(const QString & str)373 void CandidateWindow::strParse( const QString& str )
374 {
375 #if defined(ENABLE_DEBUG)
376 qDebug( "str = %s", ( const char* ) str.local8Bit() );
377 #endif
378 QStringList list = QStringList::split( "\f", str );
379
380 QStringList::Iterator it = list.begin();
381 const QStringList::Iterator end = list.end();
382 for ( ; it != end; ++it )
383 {
384 if ( QString::compare( "activate", ( *it ) ) == 0 )
385 activateCand( list );
386 else if ( QString::compare( "select", ( *it ) ) == 0 )
387 selectCand( list );
388 else if ( QString::compare( "show", ( *it ) ) == 0 )
389 showCand();
390 else if ( QString::compare( "hide", ( *it ) ) == 0 )
391 hide();
392 else if ( QString::compare( "move", ( *it ) ) == 0 )
393 moveCand( list );
394 else if ( QString::compare( "deactivate", ( *it ) ) == 0 )
395 deactivateCand();
396 else if ( QString::compare( "set_nr_candidates", ( *it ) ) == 0 )
397 setNrCandidates( list );
398 else if ( QString::compare( "set_page_candidates", ( *it ) ) == 0 )
399 setPageCandidates( list );
400 else if ( QString::compare( "show_page", ( *it ) ) == 0 )
401 showPage( list );
402 }
403 }
404
slotCandidateSelected(QListViewItem * item)405 void CandidateWindow::slotCandidateSelected( QListViewItem * item )
406 {
407 candidateIndex = ( pageIndex * displayLimit ) + cList->itemIndex( item );
408
409 // write message
410 fprintf( stdout, "index\n" );
411 fprintf( stdout, "%d\n\n", candidateIndex );
412 fflush( stdout );
413
414 updateLabel();
415 }
416
adjustCandidateWindowSize()417 void CandidateWindow::adjustCandidateWindowSize()
418 {
419 #if defined(ENABLE_DEBUG)
420 qDebug( "adjustCandidateWindowSize()" );
421 #endif
422 int width = 0;
423 int height = 0;
424 QListViewItem *item = cList->firstChild();
425 if ( item )
426 height = item->height() * ( cList->childCount() + 1 );
427
428 // 2004-08-02 Kazuki Ohta <mover@hct.zaq.ne.jp>
429 // FIXME!:
430 // There may be more proper way. Now width is adjusted by indeterminal 3 spaces.
431 // Using QWidget::adjustSize() seems not to work properly...
432 unsigned int maxCharIndex = 0, maxCharCount = 0;
433 for ( int i = 0; i < cList->childCount(); i++ )
434 {
435 if ( maxCharCount < cList->itemAtIndex( i ) ->text( 1 ).length() )
436 {
437 maxCharIndex = i;
438 maxCharCount = cList->itemAtIndex( i ) ->text( 1 ).length();
439 }
440 }
441 QFontMetrics fm( cList->font() );
442 width = fm.width( cList->itemAtIndex( maxCharIndex ) ->text( 0 ) + " " + cList->itemAtIndex( maxCharIndex ) ->text( 1 ) );
443 if ( width < MIN_CAND_WIDTH )
444 width = MIN_CAND_WIDTH;
445
446 resize( width, height );
447 }
448
setPage(int page)449 void CandidateWindow::setPage( int page )
450 {
451 // clear items
452 cList->clear();
453
454 // calculate page
455 int newpage, lastpage;
456 if ( displayLimit )
457 lastpage = nrCandidates / displayLimit;
458 else
459 lastpage = 0;
460
461 if ( page < 0 )
462 {
463 newpage = lastpage;
464 }
465 else if ( page > lastpage )
466 {
467 newpage = 0;
468 }
469 else
470 {
471 newpage = page;
472 }
473
474 pageIndex = newpage;
475
476 // calculate index
477 int newindex;
478 if ( displayLimit )
479 {
480 if ( candidateIndex >= 0 )
481 newindex = ( newpage * displayLimit ) + ( candidateIndex % displayLimit );
482 else
483 newindex = -1;
484 }
485 else
486 {
487 newindex = candidateIndex;
488 }
489
490 if ( newindex >= nrCandidates )
491 newindex = nrCandidates - 1;
492
493 // set cand items
494 //
495 // If we switch to last page, the number of items to be added
496 // is lower than displayLimit.
497 //
498 // ex. if nrCandidate==14 and displayLimit==10, the number of
499 // last page's item==4
500 int ncandidates = displayLimit;
501 if ( newpage == lastpage )
502 ncandidates = nrCandidates - displayLimit * lastpage;
503 for ( int i = ncandidates - 1; i >=0 ; i-- )
504 {
505 QString headString = stores[ displayLimit * newpage + i ].label;
506 QString candString = stores[ displayLimit * newpage + i ].str;
507
508 // insert new item to the candidate list
509 new QListViewItem( cList, headString, candString );
510 }
511
512 // set index
513 if ( newindex != candidateIndex )
514 setIndex( newindex );
515 else
516 updateLabel();
517
518 // set candwin size
519 adjustCandidateWindowSize();
520 }
521
setIndex(int index)522 void CandidateWindow::setIndex( int index )
523 {
524 #if defined(ENABLE_DEBUG)
525 qDebug( "setIndex : index = %d", index );
526 #endif
527 // validity check
528 if ( index < 0 )
529 candidateIndex = nrCandidates - 1;
530 else if ( index >= nrCandidates )
531 candidateIndex = 0;
532 else
533 candidateIndex = index;
534
535 // set page
536 int newpage = 0;
537 if ( displayLimit )
538 newpage = ( int ) candidateIndex / displayLimit;
539 if ( pageIndex != newpage )
540 setPage( newpage );
541
542 // select item
543 if ( candidateIndex >= 0 && needHilite )
544 {
545 int pos = index;
546 if ( displayLimit )
547 pos = candidateIndex % displayLimit;
548
549 if ( cList->itemAtIndex( pos ) && ! ( cList->itemAtIndex( pos ) ->isSelected() ) )
550 cList->setSelected( cList->itemAtIndex( pos ), true );
551 }
552 else
553 {
554 cList->clearSelection();
555 }
556
557 updateLabel();
558 }
559
updateLabel()560 void CandidateWindow::updateLabel()
561 {
562 QString indexString = QString::null;
563 if ( candidateIndex >= 0 && needHilite )
564 indexString = QString::number( candidateIndex + 1 ) + " / " + QString::number( nrCandidates );
565 else
566 indexString = "- / " + QString::number( nrCandidates );
567
568 numLabel->setText( indexString );
569 }
570
main(int argc,char * argv[])571 int main( int argc, char *argv[] )
572 {
573 setlocale(LC_ALL, "");
574 bindtextdomain(PACKAGE, LOCALEDIR);
575 textdomain(PACKAGE);
576 bind_textdomain_codeset(PACKAGE, "UTF-8"); // ensure code encoding is UTF8-
577
578 QApplication a( argc, argv );
579
580 CandidateWindow b;
581 a.setMainWidget( &b );
582
583 return a.exec();
584 }
585
586 #include "qt.moc"
587