1 /*
2 
3   copyright (c) 2012-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 "candidatewindowproxy.h"
36 
37 #include <QtCore/QPoint>
38 #include <QtCore/QProcess>
39 #include <QtCore/QTimer>
40 #include <QtGui/QMoveEvent>
41 #if QT_VERSION < 0x050000
42 # include <QtGui/QApplication>
43 # include <QtGui/QDesktopWidget>
44 # include <QtGui/QLabel>
45 #else
46 # include <QtWidgets/QApplication>
47 # include <QtWidgets/QDesktopWidget>
48 # include <QtWidgets/QLabel>
49 #endif
50 
51 #include <uim-scm.h>
52 
53 #if QT_VERSION < 0x050000
54 # include "quiminputcontext.h"
55 #else
56 # include <quimplatforminputcontext.h>
57 #endif
58 
CandidateWindowProxy()59 CandidateWindowProxy::CandidateWindowProxy()
60 : ic(0), nrCandidates(0), displayLimit(0), candidateIndex(-1), pageIndex(-1),
61   window(0), isAlwaysLeft(false)
62 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
63 , m_isVisible(false)
64 #endif
65 {
66 #ifdef UIM_QT_USE_DELAY
67     m_delayTimer = new QTimer(this);
68     m_delayTimer->setSingleShot(true);
69     connect(m_delayTimer, SIGNAL(timeout()), this, SLOT(timerDone()));
70 #endif /* !UIM_QT_USE_DELAY */
71 
72     process = new QProcess;
73     initializeProcess();
74     connect(process, SIGNAL(readyReadStandardOutput()),
75         this, SLOT(slotReadyStandardOutput()));
76 }
77 
~CandidateWindowProxy()78 CandidateWindowProxy::~CandidateWindowProxy()
79 {
80     // clear stored candidate data
81     while (!stores.isEmpty()) {
82         uim_candidate cand = stores.takeFirst();
83         if (cand)
84             uim_candidate_free(cand);
85     }
86     process->close();
87 }
88 
deactivateCandwin()89 void CandidateWindowProxy::deactivateCandwin()
90 {
91 #ifdef UIM_QT_USE_DELAY
92     m_delayTimer->stop();
93 #endif /* !UIM_QT_USE_DELAY */
94 
95     execute("hide");
96     clearCandidates();
97 }
98 
clearCandidates()99 void CandidateWindowProxy::clearCandidates()
100 {
101 #ifdef ENABLE_DEBUG
102     qDebug("clear Candidates");
103 #endif
104 
105     candidateIndex = -1;
106     displayLimit = 0;
107     nrCandidates = 0;
108 
109     // clear stored candidate data
110     while (!stores.isEmpty()) {
111         uim_candidate cand = stores.takeFirst();
112         if (cand)
113             uim_candidate_free(cand);
114     }
115 }
116 
popup()117 void CandidateWindowProxy::popup()
118 {
119     execute("popup");
120 }
121 
hide()122 void CandidateWindowProxy::hide()
123 {
124     execute("hide");
125 }
126 
127 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
isVisible()128 bool CandidateWindowProxy::isVisible()
129 {
130     return m_isVisible;
131 }
132 #endif
133 
layoutWindow(int x,int y,int height)134 void CandidateWindowProxy::layoutWindow(int x, int y, int height)
135 {
136     execute("layout_window\f" + QString::number(x) + '\f'
137         + QString::number(y) + '\f' + QString::number(height));
138 }
139 
candidateActivate(int nr,int displayLimit)140 void CandidateWindowProxy::candidateActivate(int nr, int displayLimit)
141 {
142 #ifdef UIM_QT_USE_DELAY
143     m_delayTimer->stop();
144 #endif /* !UIM_QT_USE_DELAY */
145 
146    QList<uim_candidate> list;
147 
148 #if !UIM_QT_USE_NEW_PAGE_HANDLING
149     activateCandwin(displayLimit);
150 
151     // set candidates
152     for (int i = 0; i < nr; i++) {
153         cand = uim_get_candidate(ic->uimContext(), i,
154                 displayLimit ? i % displayLimit : i);
155         list.append(cand);
156     }
157     setCandidates(displayLimit, list);
158 
159 #else /* !UIM_QT_USE_NEW_PAGE_HANDLING */
160     nrPages = displayLimit ? (nr - 1) / displayLimit + 1 : 1;
161     pageFilled.clear();
162     for (int i = 0; i < nrPages; i++)
163         pageFilled.append(false);
164 
165     setNrCandidates(nr, displayLimit);
166 
167     // set page candidates
168     preparePageCandidates(0);
169     setPage(0);
170 #endif /* !UIM_QT_USE_NEW_PAGE_HANDLING */
171 
172     execute("candidate_activate");
173 }
174 
175 #ifdef UIM_QT_USE_DELAY
candidateActivateWithDelay(int delay)176 void CandidateWindowProxy::candidateActivateWithDelay(int delay)
177 {
178     m_delayTimer->stop();
179     (delay > 0) ?  m_delayTimer->start(delay * 1000) : timerDone();
180 }
181 #endif /* !UIM_QT_USE_DELAY */
182 
candidateSelect(int index)183 void CandidateWindowProxy::candidateSelect(int index)
184 {
185 #if UIM_QT_USE_NEW_PAGE_HANDLING
186     if (index >= nrCandidates)
187         index = 0;
188 
189     int new_page;
190     if (index >= 0 && displayLimit)
191         new_page = index / displayLimit;
192     else
193         new_page = pageIndex;
194 
195     preparePageCandidates(new_page);
196 #endif /* UIM_QT_USE_NEW_PAGE_HANDLING */
197     setIndex(index);
198 }
199 
candidateShiftPage(bool forward)200 void CandidateWindowProxy::candidateShiftPage(bool forward)
201 {
202 #if UIM_QT_USE_NEW_PAGE_HANDLING
203     int new_page, index;
204 
205     index = forward ? pageIndex + 1 : pageIndex - 1;
206     if (index < 0)
207         new_page = nrPages - 1;
208     else if (index >= nrPages)
209         new_page = 0;
210     else
211         new_page = index;
212 
213     preparePageCandidates(new_page);
214 #endif /* UIM_QT_USE_NEW_PAGE_HANDLING */
215     shiftPage(forward);
216 }
217 
218 // -v -> vertical
219 // -h -> horizontal
220 // -t -> table
candidateWindowStyle()221 QString CandidateWindowProxy::candidateWindowStyle()
222 {
223     QString windowStyle;
224     // uim-candwin-prog is deprecated
225     char *candwinprog = uim_scm_symbol_value_str("uim-candwin-prog");
226     if (candwinprog) {
227         if (!strncmp(candwinprog, "uim-candwin-tbl", 15))
228             windowStyle = "-t";
229         else if (!strncmp(candwinprog, "uim-candwin-horizontal", 22))
230             windowStyle = "-h";
231     } else {
232         char *style = uim_scm_symbol_value_str("candidate-window-style");
233         if (style) {
234             if (!strcmp(style, "table"))
235                 windowStyle = "-t";
236             else if (!strcmp(style, "horizontal"))
237                 windowStyle = "-h";
238         }
239         free(style);
240     }
241     free(candwinprog);
242 
243     if (windowStyle.isEmpty())
244         return "-v";
245     return windowStyle;
246 }
247 
slotReadyStandardOutput()248 void CandidateWindowProxy::slotReadyStandardOutput()
249 {
250     QByteArray output = process->readAllStandardOutput();
251     QList<QStringList> messageList = parse_messages(QString(output));
252     for (int i = 0, j = messageList.count(); i < j; i++) {
253         QStringList message = messageList[i];
254         QString command = message[0];
255         if (command == "set_candidate_index") {
256             uim_set_candidate_index(ic->uimContext(), message[1].toInt());
257         } else if (command == "set_candidate_index_2") {
258             candidateIndex = pageIndex * displayLimit + message[1].toInt();
259             uim_set_candidate_index(ic->uimContext(), candidateIndex);
260         } else if (command == "set_candwin_active") {
261             ic->setCandwinActive();
262         } else if (command == "set_focus_widget") {
263             setFocusWidget();
264         } else if (command == "update_label") {
265             updateLabel();
266         }
267 #ifdef WORKAROUND_BROKEN_RESET_IN_QT4
268         else if (command == "shown") {
269             m_isVisible = true;
270         } else if (command == "hidden") {
271             m_isVisible = false;
272         }
273 #endif
274     }
275 }
276 
277 #ifdef UIM_QT_USE_DELAY
timerDone()278 void CandidateWindowProxy::timerDone()
279 {
280     int nr = -1;
281     int display_limit = -1;
282     int selected_index = -1;
283     uim_delay_activating(ic->uimContext(), &nr, &display_limit,
284         &selected_index);
285     if (nr <= 0) {
286         return;
287     }
288     candidateActivate(nr, display_limit);
289     if (selected_index >= 0) {
290         candidateSelect(selected_index);
291     }
292 }
293 #endif /* !UIM_QT_USE_DELAY */
294 
initializeProcess()295 void CandidateWindowProxy::initializeProcess()
296 {
297     if (process->state() != QProcess::NotRunning) {
298         return;
299     }
300     QString style = candidateWindowStyle();
301     qputenv("__UIM_CANDWIN_CALLED", QByteArray("STARTED"));
302 #if QT_VERSION < 0x050000
303     process->start(UIM_LIBEXECDIR "/uim-candwin-qt4", QStringList() << style);
304 #else
305     process->start(UIM_LIBEXECDIR "/uim-candwin-qt5", QStringList() << style);
306 #endif
307     qputenv("__UIM_CANDWIN_CALLED", QByteArray("DONE"));
308     process->waitForStarted();
309 }
310 
execute(const QString & command)311 void CandidateWindowProxy::execute(const QString &command)
312 {
313     initializeProcess();
314     process->write((command + "\f\f").toUtf8());
315 }
316 
activateCandwin(int dLimit)317 void CandidateWindowProxy::activateCandwin(int dLimit)
318 {
319     candidateIndex = -1;
320     displayLimit = dLimit;
321     pageIndex = 0;
322     execute("setup_sub_window");
323 }
324 
shiftPage(bool forward)325 void CandidateWindowProxy::shiftPage(bool forward)
326 {
327 #ifdef ENABLE_DEBUG
328     qDebug("candidateIndex = %d", candidateIndex);
329 #endif
330 
331     if (forward) {
332         if (candidateIndex != -1)
333             candidateIndex += displayLimit;
334         setPage(pageIndex + 1);
335     } else {
336         if (candidateIndex != -1) {
337             if (candidateIndex < displayLimit)
338                 candidateIndex = displayLimit * (nrCandidates / displayLimit)
339                     + candidateIndex;
340             else
341                 candidateIndex -= displayLimit;
342         }
343         setPage(pageIndex - 1);
344     }
345     if (ic && ic->uimContext() && candidateIndex != -1)
346         uim_set_candidate_index(ic->uimContext(), candidateIndex);
347     // for CandidateWindow
348     if (candidateIndex != -1) {
349         int idx = displayLimit ? candidateIndex % displayLimit : candidateIndex;
350         execute("shift_page\f" + QString::number(idx));
351     }
352 }
353 
setIndex(int totalindex)354 void CandidateWindowProxy::setIndex(int totalindex)
355 {
356 #ifdef ENABLE_DEBUG
357     qDebug("setIndex : totalindex = %d", totalindex);
358 #endif
359 
360     // validity check
361     if (totalindex < 0)
362         candidateIndex = nrCandidates - 1;
363     else if (totalindex >= nrCandidates)
364         candidateIndex = 0;
365     else
366         candidateIndex = totalindex;
367 
368     // set page
369     int newpage = 0;
370     if (displayLimit)
371         newpage = candidateIndex / displayLimit;
372     if (pageIndex != newpage)
373         setPage(newpage);
374     execute("set_index\f" + QString::number(totalindex)
375         + '\f' + QString::number(displayLimit)
376         + '\f' + QString::number(candidateIndex));
377 }
378 
379 #if UIM_QT_USE_NEW_PAGE_HANDLING
setNrCandidates(int nrCands,int dLimit)380 void CandidateWindowProxy::setNrCandidates(int nrCands, int dLimit)
381 {
382 #ifdef ENABLE_DEBUG
383     qDebug("setNrCandidates");
384 #endif
385 
386     // remove old data
387     if (!stores.isEmpty())
388         clearCandidates();
389 
390     candidateIndex = -1;
391     displayLimit = dLimit;
392     nrCandidates = nrCands;
393     pageIndex = 0;
394 
395     // setup dummy candidate
396     for (int i = 0; i < nrCandidates; i++)
397         stores.append(0);
398 
399     execute("setup_sub_window");
400 }
401 #endif /* UIM_QT_USE_NEW_PAGE_HANDLING */
402 
updateLabel()403 void CandidateWindowProxy::updateLabel()
404 {
405     QString indexString;
406     if (candidateIndex >= 0)
407         indexString = QString::number(candidateIndex + 1) + " / "
408             + QString::number(nrCandidates);
409     else
410         indexString = "- / " + QString::number(nrCandidates);
411 
412     execute("update_label\f" + indexString);
413 }
414 
setCandidates(int dl,const QList<uim_candidate> & candidates)415 void CandidateWindowProxy::setCandidates(int dl,
416         const QList<uim_candidate> &candidates)
417 {
418 #ifdef ENABLE_DEBUG
419     qDebug("setCandidates");
420 #endif
421 
422     // remove old data
423     if (!stores.isEmpty())
424         clearCandidates();
425 
426     // set defalt value
427     candidateIndex = -1;
428     nrCandidates = candidates.count();
429     displayLimit = dl;
430 
431     if (candidates.isEmpty())
432         return;
433 
434     // set candidates
435     stores = candidates;
436 
437     // shift to default page
438     setPage(0);
439 }
440 
setPage(int page)441 void CandidateWindowProxy::setPage(int page)
442 {
443 #ifdef ENABLE_DEBUG
444     qDebug("setPage : page = %d", page);
445 #endif
446 
447     // calculate page
448     int lastpage = displayLimit ? nrCandidates / displayLimit : 0;
449 
450     int newpage;
451     if (page < 0)
452         newpage = lastpage;
453     else if (page > lastpage)
454         newpage = 0;
455     else
456         newpage = page;
457 
458     pageIndex = newpage;
459 
460     // calculate index
461     int newindex;
462     if (displayLimit) {
463         newindex = (candidateIndex >= 0)
464             ? (newpage * displayLimit) + (candidateIndex % displayLimit) : -1;
465     } else {
466         newindex = candidateIndex;
467     }
468 
469     if (newindex >= nrCandidates)
470         newindex = nrCandidates - 1;
471 
472     // set cand items
473     //
474     // If we switch to last page, the number of items to be added
475     // is lower than displayLimit.
476     //
477     // ex. if nrCandidate == 14 and displayLimit == 10, the number of
478     //     last page's item == 4
479     int ncandidates = displayLimit;
480     if (newpage == lastpage)
481         ncandidates = nrCandidates - displayLimit * lastpage;
482 
483     QString candidateMessage;
484     for (int i = 0; i < ncandidates; i++) {
485         uim_candidate cand = stores.at(displayLimit * newpage + i);
486         candidateMessage +=
487             QString::fromUtf8(uim_candidate_get_heading_label(cand)) + '\a'
488             + QString::fromUtf8(uim_candidate_get_cand_str(cand)) + '\a'
489             + QString::fromUtf8(uim_candidate_get_annotation_str(cand)) + '\f';
490     }
491 
492     execute("update_view\f" + QString::number(ncandidates) + "\f"
493         + candidateMessage);
494 
495     // set index
496     if (newindex != candidateIndex)
497         setIndex(newindex);
498     else
499         updateLabel();
500 
501     execute("update_size");
502 }
503 
504 #if UIM_QT_USE_NEW_PAGE_HANDLING
setPageCandidates(int page,const QList<uim_candidate> & candidates)505 void CandidateWindowProxy::setPageCandidates(int page,
506         const QList<uim_candidate> &candidates)
507 {
508 #ifdef ENABLE_DEBUG
509     qDebug("setPageCandidates");
510 #endif
511 
512     if (candidates.isEmpty())
513         return;
514 
515     // set candidates
516     int start, pageNr;
517     start = page * displayLimit;
518 
519     if (displayLimit && (nrCandidates - start) > displayLimit)
520         pageNr = displayLimit;
521     else
522         pageNr = nrCandidates - start;
523 
524     for (int i = 0; i < pageNr; i++)
525         stores[start + i] = candidates[i];
526 }
527 
preparePageCandidates(int page)528 void CandidateWindowProxy::preparePageCandidates(int page)
529 {
530     QList<uim_candidate> list;
531 
532     if (page < 0)
533         return;
534 
535     if (pageFilled[page])
536         return;
537 
538     // set page candidates
539     int start = page * displayLimit;
540 
541     int pageNr;
542     if (displayLimit && (nrCandidates - start) > displayLimit)
543         pageNr = displayLimit;
544     else
545         pageNr = nrCandidates - start;
546 
547     for (int i = start; i < pageNr + start; i++) {
548         // set page candidates
549         uim_candidate cand = uim_get_candidate(ic->uimContext(), i,
550                 displayLimit ? i % displayLimit : i);
551         list.append(cand);
552     }
553     pageFilled[page] = true;
554     setPageCandidates(page, list);
555 }
556 #endif /* UIM_QT_USE_NEW_PAGE_HANDLING */
557 
setFocusWidget()558 void CandidateWindowProxy::setFocusWidget()
559 {
560     if (QApplication::focusWidget() == NULL)
561 	return;
562     window = QApplication::focusWidget()->window();
563     window->installEventFilter(this);
564 }
565 
eventFilter(QObject * obj,QEvent * event)566 bool CandidateWindowProxy::eventFilter(QObject *obj, QEvent *event)
567 {
568     if (obj == window) {
569         if (event->type() == QEvent::Move) {
570             QWidget *widget = QApplication::focusWidget();
571             if (widget) {
572                 QRect rect
573                     = widget->inputMethodQuery(Qt::ImMicroFocus).toRect();
574                 QPoint p = widget->mapToGlobal(rect.topLeft());
575                 layoutWindow(p.x(), p.y(), rect.height());
576             } else {
577                 QMoveEvent *moveEvent = static_cast<QMoveEvent *>(event);
578                 QPoint p = moveEvent->pos() - moveEvent->oldPos();
579                 execute("move_candwin\f" + QString::number(p.x()) + '\f'
580                     + QString::number(p.y()));
581             }
582         }
583         return false;
584     }
585     return QObject::eventFilter(obj, event);
586 }
587