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