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