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 
34 /*
35  * uimのコールバック関数
36  */
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif
40 #if (!defined(DEBUG) && !defined(NDEBUG))
41 #define NDEBUG
42 #endif
43 #ifdef HAVE_STDLIB_H
44 #include <stdlib.h>
45 #endif
46 #ifdef HAVE_STRING_H
47 #include <string.h>
48 #endif
49 #ifdef HAVE_ASSERT_H
50 #include <assert.h>
51 #endif
52 #include "uim-fep.h"
53 #include "str.h"
54 #include "callbacks.h"
55 #include "helper.h"
56 #include <uim/uim-im-switcher.h>
57 #include <uim/uim-helper.h>
58 #include <uim/uim-util.h>
59 
60 /* ステータスラインの最大幅 */
61 static int s_max_width;
62 static char *s_commit_str;
63 static char *s_statusline_str;
64 static char *s_candidate_str;
65 static int s_candidate_col;
66 static char *s_index_str;
67 static struct preedit_tag *s_preedit;
68 static int s_mode;
69 static char *s_label_str;
70 static const char *s_im_str;
71 static char *s_nokori_str;
72 static int s_start_callbacks = FALSE;
73 
74 static void update_current_im_name(void);
75 static void configuration_changed_cb(void *ptr);
76 static void switch_app_global_im_cb(void *ptr, const char *name);
77 static void switch_system_global_im_cb(void *ptr, const char *name);
78 static void activate_cb(void *ptr, int nr, int display_limit);
79 static void select_cb(void *ptr, int index);
80 static void shift_page_cb(void *ptr, int direction);
81 static void deactivate_cb(void *ptr);
82 static void clear_cb(void *ptr);
83 static void pushback_cb(void *ptr, int attr, const char *str);
84 static void update_cb(void *ptr);
85 static void mode_update_cb(void *ptr, int mode);
86 static void prop_list_update_cb(void *ptr, const char *str);
87 static struct preedit_tag *dup_preedit(struct preedit_tag *p);
88 static void init_candidate(int nr, int display_limit);
89 static void make_page_strs(void);
90 static int numwidth(int n);
91 static int index2page(int index);
92 static void reset_candidate(void);
93 static void set_candidate(void);
94 
95 struct candidate_tag {
96   /* 候補の数 */
97   int nr;
98   /* 1度に表示する候補数 */
99   int limit;
100   /* 総page数 */
101   int nr_pages;
102   /* 現在の候補 0から始まる */
103   int index;
104   /* 現在のページ 0から始まる */
105   int page;
106   /* ページ文字列の配列 要素数nr_pages */
107   char **page_strs;
108   /* 候補の位置の配列 要素数nr */
109   int *cand_col;
110   /* ページの最初の候補のindexの配列 要素数nr_pages */
111   int *page2index;
112   /* 現在の候補のインデックスを描画するカラム */
113   int *index_col;
114 };
115 
116 static struct candidate_tag s_candidate = {
117   UNDEFINED, /* nr */
118   UNDEFINED, /* limit */
119   UNDEFINED, /* nr_pages */
120   UNDEFINED, /* index */
121   UNDEFINED, /* page */
122   NULL,      /* page_strs */
123   NULL,      /* cand_col */
124   NULL,      /* page2index */
125   NULL,      /* index_col */
126 };
127 
128 /*
129  * 初期化
130  */
init_callbacks(void)131 void init_callbacks(void)
132 {
133   s_max_width = g_win->ws_col;
134   if (g_opt.statusline_width != UNDEFINED && g_opt.statusline_width <= s_max_width) {
135     s_max_width = g_opt.statusline_width;
136   }
137   s_commit_str = uim_strdup("");
138   s_candidate_str = uim_strdup("");
139   s_statusline_str = uim_strdup("");
140   s_candidate_col = UNDEFINED;
141   s_index_str = uim_strdup("");
142   s_mode = uim_get_current_mode(g_context);
143   s_label_str = uim_strdup("");
144   s_preedit = create_preedit();
145   uim_set_preedit_cb(g_context, clear_cb, pushback_cb, update_cb);
146   uim_set_mode_cb(g_context, mode_update_cb);
147   uim_set_prop_list_update_cb(g_context, prop_list_update_cb);
148   uim_set_configuration_changed_cb(g_context, configuration_changed_cb);
149   uim_set_im_switch_request_cb(g_context, switch_app_global_im_cb, switch_system_global_im_cb);
150   configuration_changed_cb(NULL);
151   if (g_opt.status_type != NONE) {
152     uim_set_candidate_selector_cb(g_context, activate_cb, select_cb, shift_page_cb, deactivate_cb);
153   }
154 
155   if (g_opt.ddskk) {
156     const char *enc;
157 
158     if (uim_iconv->is_convertible(enc = get_enc(), "EUC-JP")) {
159       void *cd = uim_iconv->create(enc, "EUC-JP");
160       s_nokori_str = uim_iconv->convert(cd, "残り");
161       if (cd) {
162         uim_iconv->release(cd);
163       }
164     } else {
165       perror("error in iconv_open");
166       puts("-d option is not available");
167       done(EXIT_FAILURE);
168     }
169   }
170 }
171 
press_key(int key,int key_state)172 int press_key(int key, int key_state)
173 {
174   int raw;
175 #if defined DEBUG && DEBUG > 2
176   if (32 <= key && key <= 127) {
177     debug2(("press key = %c key_state = %d\n", key, key_state));
178   } else {
179     debug2(("press key = %d key_state = %d\n", key, key_state));
180   }
181 #endif
182   raw = uim_press_key(g_context, key, key_state);
183   uim_release_key(g_context, key, key_state);
184   return raw;
185 }
186 
187 
188 /*
189  * 名前が紛らわしいが、uim側から描画を要求されたら呼ぶ。
190  */
start_callbacks(void)191 void start_callbacks(void)
192 {
193   if (s_start_callbacks) {
194     return;
195   }
196   s_start_callbacks = TRUE;
197 
198   debug2(("\n\nstart_callbacks()\n"));
199 }
200 
201 /*
202  * コールバック関数が呼ばれていなければ、FALSEを返す
203  */
end_callbacks(void)204 int end_callbacks(void)
205 {
206   debug2(("end_callbacks()\n\n"));
207   if (!s_start_callbacks) {
208     return FALSE;
209   }
210 
211   s_start_callbacks = FALSE;
212 
213   /* cursorが指定されていないときはプリエディットの末尾にする */
214   if (s_preedit->cursor == UNDEFINED) {
215     s_preedit->cursor = s_preedit->width;
216   }
217 
218   free(s_statusline_str);
219   free(s_candidate_str);
220   free(s_index_str);
221 
222   if (s_candidate.nr != UNDEFINED) {
223     if (s_candidate.page_strs[s_candidate.page])
224       s_statusline_str = uim_strdup(s_candidate.page_strs[s_candidate.page]);
225     else
226       s_statusline_str = uim_strdup("");
227     if (s_candidate.index != UNDEFINED) {
228       set_candidate();
229     } else {
230       s_candidate_str = uim_strdup("");
231       s_candidate_col = UNDEFINED;
232       s_index_str = uim_strdup("");
233     }
234   } else {
235     s_statusline_str = uim_strdup("");
236     s_candidate_str = uim_strdup("");
237     s_candidate_col = UNDEFINED;
238     s_index_str = uim_strdup("");
239   }
240 
241   return TRUE;
242 }
243 
244 /*
245  * 確定文字列を返す
246  * 返り値はfreeする
247  */
get_commit_str(void)248 char *get_commit_str(void)
249 {
250   char *return_value = uim_strdup(s_commit_str);
251 
252   assert(!s_start_callbacks);
253 
254   free(s_commit_str);
255   s_commit_str = uim_strdup("");
256   return return_value;
257 }
258 
259 /*
260  * 候補一覧文字列を返す
261  * 返り値はfreeする
262  */
get_statusline_str(void)263 char *get_statusline_str(void)
264 {
265   assert(!s_start_callbacks);
266   return uim_strdup(s_statusline_str);
267 }
268 
269 /*
270  * 選択文字列を返す
271  * 返り値はfreeする
272  */
get_candidate_str(void)273 char *get_candidate_str(void)
274 {
275   assert(!s_start_callbacks);
276   return uim_strdup(s_candidate_str);
277 }
278 
279 /*
280  * 選択されている候補のcolumnを返す
281  * 選択されていないときはUNDEFINEDを返す
282  */
get_candidate_col(void)283 int get_candidate_col(void)
284 {
285   assert(!s_start_callbacks);
286   return s_candidate_col;
287 }
288 
289 /*
290  * 選択されている候補のインデックスの文字列を返す
291  * 返り値はfreeする
292  */
get_index_str(void)293 char *get_index_str(void)
294 {
295   assert(!s_start_callbacks);
296   return uim_strdup(s_index_str);
297 }
298 
299 /*
300  * s_index_strを描画するカラムを返す
301  * 選択されていないときはUNDEFINEDを返す
302  */
get_index_col(void)303 int get_index_col(void)
304 {
305   assert(!s_start_callbacks);
306 
307   if (s_candidate.index != UNDEFINED) {
308     return s_candidate.index_col[s_candidate.page];
309   }
310   return UNDEFINED;
311 }
312 
313 /*
314  * プリエディットを返す
315  * 返り値はfreeする
316  */
get_preedit(void)317 struct preedit_tag *get_preedit(void)
318 {
319   assert(!s_start_callbacks);
320   return dup_preedit(s_preedit);
321 }
322 
323 /*
324  * 現在のモードを返す
325  */
get_mode(void)326 int get_mode(void)
327 {
328   assert(!s_start_callbacks);
329   return s_mode;
330 }
331 
332 /*
333  * 現在のモード文字列を返す
334  * 返り値はNULLになることはなく、freeする必要がある
335  */
get_mode_str(void)336 char *get_mode_str(void)
337 {
338   char *str;
339 
340   assert(!s_start_callbacks);
341 
342   uim_asprintf(&str, "%s[%s]", s_im_str, s_label_str);
343   strhead(str, s_max_width);
344 
345   return str;
346 }
347 
update_current_im_name(void)348 static void update_current_im_name(void)
349 {
350   s_im_str = uim_get_current_im_name(g_context);
351   s_im_str = s_im_str != NULL ? s_im_str : "";
352 }
353 
configuration_changed_cb(void * ptr)354 static void configuration_changed_cb(void *ptr)
355 {
356   update_current_im_name();
357 }
358 
switch_app_global_im_cb(void * ptr,const char * name)359 static void switch_app_global_im_cb(void *ptr, const char *name)
360 {
361 }
362 
switch_system_global_im_cb(void * ptr,const char * name)363 static void switch_system_global_im_cb(void *ptr, const char *name)
364 {
365   char *buf;
366 
367   uim_asprintf(&buf, "im_change_whole_desktop\n%s\n", name ? name : "");
368   uim_helper_send_message(g_helper_fd, buf);
369   free(buf);
370 }
371 
372 /*
373  * 候補一覧を表示するときに呼ばれる。
374  * s_candidate.nr = nr(候補総数)
375  * s_candidate.limit = display_limit(表示する候補数)
376  * s_candidate.cand_colの領域を確保する。
377  * s_candidate.page = 0 (initial page)
378  */
activate_cb(void * ptr,int nr,int display_limit)379 static void activate_cb(void *ptr, int nr, int display_limit)
380 {
381   debug2(("activate_cb(nr = %d display_limit = %d)\n", nr, display_limit));
382   start_callbacks();
383   reset_candidate();
384   init_candidate(nr, display_limit);
385   make_page_strs(); /* setup first page */
386 }
387 
388 /*
389  * 候補が選択されたときに呼ばれる。
390  * s_candidate.index = index
391  */
select_cb(void * ptr,int index)392 static void select_cb(void *ptr, int index)
393 {
394   int current_index;
395   int current_page;
396 
397   debug2(("select_cb(index = %d)\n", index));
398   return_if_fail(s_candidate.nr != UNDEFINED);
399   return_if_fail(0 <= index && index < s_candidate.nr);
400 
401   current_index = s_candidate.index;
402   current_page = s_candidate.page;
403   if (current_index == index) {
404     return;
405   }
406   start_callbacks();
407   s_candidate.index = index;
408   s_candidate.page = index2page(index);
409   if (s_candidate.page != current_page &&
410       s_candidate.page_strs[s_candidate.page] == NULL) {
411     make_page_strs();
412   }
413 }
414 
415 /*
416  * ページを変えたときに呼ばれる。
417  * s_candidate.page = 1ページ前か後(directionによる)
418  * s_candidate.index = pageの最初か最後(directionによる)
419  */
shift_page_cb(void * ptr,int direction)420 static void shift_page_cb(void *ptr, int direction)
421 {
422   int page;
423   int index;
424   debug2(("shift_page_cb(direction = %d)\n", direction));
425   return_if_fail(s_candidate.nr != UNDEFINED);
426   start_callbacks();
427   if (direction == 0) {
428     direction = -1;
429   }
430   page = (s_candidate.page + direction + s_candidate.nr_pages) % s_candidate.nr_pages;
431   index = s_candidate.page2index[page];
432   return_if_fail(0 <= index && index < s_candidate.nr);
433   s_candidate.page = page;
434   s_candidate.index = index;
435   if (s_candidate.page_strs[page] == NULL)
436     make_page_strs();
437   uim_set_candidate_index(g_context, s_candidate.index);
438 }
439 
440 /*
441  * 候補一覧を消すときに呼ばれる。
442  * reset_candidateを呼ぶ。
443  */
deactivate_cb(void * ptr)444 static void deactivate_cb(void *ptr)
445 {
446   debug2(("deactivate_cb()\n"));
447   start_callbacks();
448   reset_candidate();
449 }
450 
451 
commit_cb(void * ptr,const char * commit_str)452 void commit_cb(void *ptr, const char *commit_str)
453 {
454   char *oldstr;
455 
456   debug2(("commit_cb(commit_str = \"%s\")\n", commit_str));
457   return_if_fail(commit_str != NULL);
458   if (strlen(commit_str) == 0) {
459     return;
460   }
461   start_callbacks();
462 
463   oldstr = s_commit_str;
464   uim_asprintf(&s_commit_str, "%s%s", oldstr, commit_str);
465   free(oldstr);
466 }
467 
clear_cb(void * ptr)468 static void clear_cb(void *ptr)
469 {
470   start_callbacks();
471   free_preedit(s_preedit);
472   s_preedit = create_preedit();
473   s_preedit->cursor = UNDEFINED;
474   debug2(("clear_cb()\n"));
475 }
476 
477 /*
478  * clear_cbの後に0回以上呼ばれる
479  * s_preeditを構成する
480  */
pushback_cb(void * ptr,int attr,const char * str)481 static void pushback_cb(void *ptr, int attr, const char *str)
482 {
483   int width;
484   static int cursor = FALSE;
485 
486   debug2(("pushback_cb(attr = %d str = \"%s\")\n", attr, str));
487 
488   return_if_fail(str && s_preedit != NULL);
489 
490   width = strwidth(str);
491 
492   if (width == 0 && (attr & UPreeditAttr_Cursor) == 0) {
493     return;
494   }
495 
496   start_callbacks();
497   /* UPreeditAttr_Cursorのときに空文字列とは限らない */
498   if (attr & UPreeditAttr_Cursor) {
499     /* skkの辞書登録はカーソルが2箇所ある */
500     /* assert(s_preedit->cursor == UNDEFINED); */
501     s_preedit->cursor = s_preedit->width;
502     attr -= UPreeditAttr_Cursor;
503     cursor = TRUE;
504   }
505   /* 空文字列は無視 */
506   if (width > 0) {
507     /* カーソル位置の文字を反転させない */
508     if (g_opt.cursor_no_reverse && cursor && attr & UPreeditAttr_Reverse && s_preedit->cursor != UNDEFINED) {
509       int *rval = width2byte2(str, 1);
510       int first_char_byte = rval[0];
511       int first_char_width = rval[1];
512       char *first_char = uim_malloc(first_char_byte + 1);
513       strlcpy(first_char, str, first_char_byte + 1);
514       cursor = FALSE;
515       pushback_cb(NULL, attr - UPreeditAttr_Reverse, first_char);
516       free(first_char);
517       str += first_char_byte;
518       width -= first_char_width;
519       if (width <= 0) {
520         return;
521       }
522     }
523     /* attrが前と同じ場合は前の文字列に付け足す */
524     if (s_preedit->nr_psegs > 0 && s_preedit->pseg[s_preedit->nr_psegs - 1].attr == attr) {
525       char *tmp_str = s_preedit->pseg[s_preedit->nr_psegs - 1].str;
526 
527       uim_asprintf(&s_preedit->pseg[s_preedit->nr_psegs - 1].str, "%s%s", tmp_str, str);
528       free(tmp_str);
529     } else {
530       s_preedit->pseg = uim_realloc(s_preedit->pseg,
531           sizeof(struct preedit_segment_tag) * (s_preedit->nr_psegs + 1));
532       s_preedit->pseg[s_preedit->nr_psegs].str = uim_strdup(str);
533       s_preedit->pseg[s_preedit->nr_psegs].attr = attr;
534       s_preedit->nr_psegs++;
535     }
536     s_preedit->width += width;
537   }
538 }
539 
update_cb(void * ptr)540 static void update_cb(void *ptr)
541 {
542   debug2(("update_cb()\n"));
543 }
544 
545 /*
546  * モードが変わったときに呼ばれる。
547  * 実際は変わっていないこともある。
548  */
mode_update_cb(void * ptr,int mode)549 static void mode_update_cb(void *ptr, int mode)
550 {
551   debug2(("mode_update_cb(mode = %d)\n", mode));
552 
553   if (s_mode == mode) {
554     return;
555   }
556 
557   start_callbacks();
558   s_mode = mode;
559 }
560 
prop_list_update_cb(void * ptr,const char * str)561 static void prop_list_update_cb(void *ptr, const char *str)
562 {
563   char *line;
564   int error = TRUE; /* str が "" のときはerrorにする*/
565   char *labels = uim_strdup("");
566   char *dup_str;
567 
568   const char *enc;
569   char *message_buf;
570 
571   debug(("prop_list_update_cb\n"));
572   debug2(("str = %s", str));
573 
574   dup_str = line = uim_strdup(str);
575 
576   while (line[0] != '\0') {
577     int i;
578     char *tab;
579     char *eol;
580     char *label;
581     int label_width;
582     int max_label_width = 0;
583     char *pad;
584     int padlen;
585     char *oldlabels;
586 
587     error = TRUE;
588 
589     /* branch = "branch\t" indication_id "\t" iconic_label "\t" label_string "\n" */
590     if (!str_has_prefix(line, "branch\t")) {
591       break;
592     }
593     label = line + strlen("branch\t");
594     if ((label = strchr(label, '\t')) == NULL) {
595       break;
596     }
597     label++;
598 
599     if ((tab = strchr(label, '\t')) == NULL) {
600       break;
601     }
602     *tab = '\0';
603 
604     if ((eol = strchr(tab + 1, '\n')) == NULL) {
605       break;
606     }
607     line = eol + 1;
608 
609     while (str_has_prefix(line, "leaf\t")) {
610       char *leaf_label = line + strlen("leaf\t");
611 
612       error = TRUE;
613 
614       if ((leaf_label = strchr(leaf_label, '\t')) == NULL) {
615 	goto loop_end;
616       }
617       leaf_label++;
618 
619       tab = leaf_label - 1;
620 
621       /* leaf = "leaf\t" indication_id "\t" iconic_label "\t" label_string "\t" short_desc "\t" action_id "\t" activity "\n" */
622       for (i = 0; i < 4; i++) {
623         if ((tab = strchr(tab + 1, '\t')) == NULL) {
624           goto loop_end;
625         }
626         *tab = '\0';
627       }
628       if ((eol = strchr(tab + 1, '\n')) == NULL) {
629         goto loop_end;
630       }
631       line = eol + 1;
632 
633       error = FALSE;
634 
635       label_width = strwidth(leaf_label);
636       if (label_width > max_label_width) {
637         max_label_width = label_width;
638       }
639     }
640 
641     if (error) {
642       break;
643     }
644 
645     label_width = strwidth(label);
646 
647     oldlabels = labels;
648     padlen = max_label_width - label_width;
649     pad = uim_malloc(padlen + 1);
650     memset(pad, ' ', padlen);
651     pad[padlen] = '\0';
652     uim_asprintf(&labels, "%s%s%s", oldlabels, label, pad);
653     free(oldlabels);
654     free(pad);
655   }
656 
657 loop_end:
658 
659   free(dup_str);
660 
661   if (error) {
662     free(labels);
663   } else {
664     if (strcmp(s_label_str, labels) != 0) {
665       start_callbacks();
666       free(s_label_str);
667       s_label_str = labels;
668     } else {
669       free(labels);
670     }
671     /* To make IM-name part of the status line updated. */
672     update_current_im_name();
673   }
674 
675   if (!g_focus_in) {
676     return;
677   }
678 
679   enc = get_enc();
680   uim_asprintf(&message_buf, "prop_list_update\ncharset=%s\n%s", enc, str);
681   uim_helper_send_message(g_helper_fd, message_buf);
682   free(message_buf);
683   debug(("prop_list_update_cb send message\n"));
684 }
685 
686 /*
687  * 新しいプリエディットを作り,ポインタを返す
688  */
create_preedit(void)689 struct preedit_tag *create_preedit(void)
690 {
691   struct preedit_tag *p = uim_malloc(sizeof(struct preedit_tag));
692   p->nr_psegs = 0;
693   p->width = 0;
694   p->cursor = 0;
695   p->pseg = NULL;
696   return p;
697 }
698 
699 /*
700  * pの領域を開放する
701  */
free_preedit(struct preedit_tag * p)702 void free_preedit(struct preedit_tag *p)
703 {
704   int i;
705   if (p == NULL) {
706     return;
707   }
708   for (i = 0; i < p->nr_psegs; i++) {
709     free(p->pseg[i].str);
710   }
711   free(p->pseg);
712   free(p);
713 }
714 
715 /*
716  * pの複製を作り,ポインタを返す
717  */
dup_preedit(struct preedit_tag * p)718 static struct preedit_tag *dup_preedit(struct preedit_tag *p)
719 {
720   int i;
721   struct preedit_tag *dup_p = create_preedit();
722   *dup_p = *p;
723   dup_p->pseg = uim_malloc(sizeof(struct preedit_segment_tag) * (p->nr_psegs));
724   for (i = 0; i < p->nr_psegs; i++) {
725     dup_p->pseg[i].attr = p->pseg[i].attr;
726     dup_p->pseg[i].str = uim_strdup(p->pseg[i].str);
727   }
728   return dup_p;
729 }
730 
731 /*
732  * initialize contents of s_candidate
733  *
734  * s_candidate.page_strs = array of page string
735  * s_candidate.page2index = head index of candidates at the page
736  * s_candidate.cand_col = place of a candidate
737  * s_candidate.nr_pages = total page number
738  * s_candidate.index_col = column of the currently selected candidate
739  */
init_candidate(int nr,int display_limit)740 static void init_candidate(int nr, int display_limit)
741 {
742   int nr_virtual_pages = 0;
743   int i;
744 
745   s_candidate.nr = nr;
746   s_candidate.limit = display_limit;
747   s_candidate.page = 0;
748   s_candidate.cand_col = uim_malloc(nr * sizeof(int));
749 
750   assert(s_candidate.nr != UNDEFINED);
751   assert(s_candidate.limit != UNDEFINED);
752   assert(s_candidate.cand_col != NULL);
753 
754   if (s_candidate.limit)
755     nr_virtual_pages = (s_candidate.nr - 1) / s_candidate.limit + 1;
756   else
757     nr_virtual_pages = 1;
758 
759   s_candidate.nr_pages = nr_virtual_pages;
760   s_candidate.page2index = uim_realloc(s_candidate.page2index, nr_virtual_pages * sizeof(int));
761   s_candidate.index_col = uim_realloc(s_candidate.index_col, nr_virtual_pages * sizeof(int));
762   s_candidate.page_strs = uim_realloc(s_candidate.page_strs, nr_virtual_pages * sizeof(char *));
763 
764   for (i = 0; i < nr_virtual_pages; i++) {
765     s_candidate.page2index[i] = i * s_candidate.limit;
766     s_candidate.index_col[i] = UNDEFINED;
767     s_candidate.page_strs[i] = NULL;
768   }
769 }
770 
771 /*
772  * s_candidate.page_strs = ページ文字列の配列
773  * 文字列の幅がs_max_widthを越えたときは、はみ出た候補を次のページに移す。
774  * 1つしか候補がなくてはみ出たときは、移さない。
775  * s_max_widthは端末の幅かオプションで指定された値
776  * s_candidate.page2index = ページの最初の候補のindex
777  * s_candidate.cand_col = 候補の位置
778  * s_candidate.nr_pages = ページの総数
779  * s_candidate.index_col = 候補のインデックスのカラム
780  */
make_page_strs(void)781 static void make_page_strs(void)
782 {
783   /* NULLをreallocしてゴミにならないように */
784   char *page_str = uim_strdup("");
785   int page_byte = 0;
786   int page_width = 0;
787   int index_in_page = 0;
788   char *old_str;
789 
790   int index, page, start, nr_in_virtual_page;
791 
792   assert(s_candidate.nr != UNDEFINED);
793   assert(s_candidate.limit != UNDEFINED);
794   assert(s_candidate.cand_col != NULL);
795 
796   page = s_candidate.page;
797   start = s_candidate.page2index[page];
798   if (s_candidate.limit && (s_candidate.nr - start) > s_candidate.limit)
799     nr_in_virtual_page = s_candidate.limit;
800   else
801     nr_in_virtual_page = s_candidate.nr - start;
802 
803   for (index = start; index < (start + nr_in_virtual_page); index++) {
804     /* A:工  S:広  D:向  F:考  J:構  K:敲  L:後  [残り 227] */
805     int next = FALSE; /* flag whether to finish page */
806     int add_extra_page = FALSE;
807     /* "[10/20]" の幅 */
808     int index_width;
809     uim_candidate cand = uim_get_candidate(g_context, index, index_in_page);
810     const char *cand_str_label = uim_candidate_get_heading_label(cand);
811     char *cand_str_cand = tab2space(uim_candidate_get_cand_str(cand));
812     int cand_label_width = strwidth(cand_str_label);
813     int cand_width = cand_label_width + strlen(":") + strwidth(cand_str_cand) + strlen(" ");
814     int cand_byte = strlen(cand_str_label) + strlen(":") + strlen(cand_str_cand) + strlen(" ");
815     char *cand_str;
816 
817     if (g_opt.ddskk) {
818       index_width = strlen("[xxxx ]") + numwidth(s_candidate.nr - index - 1);
819     } else {
820       index_width = strlen("[/]") + numwidth(index + 1) + numwidth(s_candidate.nr);
821     }
822 
823     uim_asprintf(&cand_str, "%s:%s ", cand_str_label, cand_str_cand);
824     uim_candidate_free(cand);
825     free(cand_str_cand);
826 
827     if (page_width + cand_width + index_width > s_max_width && index_in_page != 0) {
828       /* はみ出たので次のページに移す */
829       index--;
830       if (g_opt.ddskk) {
831         index_width = strlen("[xxxx ]") + numwidth(s_candidate.nr - index - 1);
832       } else {
833         index_width = strlen("[/]") + numwidth(index + 1) + numwidth(s_candidate.nr);
834       }
835       next = TRUE;
836       add_extra_page = TRUE;
837     } else {
838 
839       s_candidate.cand_col[index] = page_width + cand_label_width + strlen(":");
840 
841       if (cand_width + index_width > s_max_width && index_in_page == 0) {
842         /* はみ出たが、次に移さない */
843         assert(page_width == 0);
844         next = TRUE;
845                                                   /* 全角1文字の幅 */
846         if (s_max_width >= cand_label_width + (int)strlen(":") + 2 + (int)strlen(" ") + index_width) {
847           /* 候補 + インデックス */
848 
849           cand_width = s_max_width - index_width - strlen(" ");
850           cand_width = strhead(cand_str, cand_width);
851           assert(cand_width > cand_label_width);
852           cand_width += strwidth(" ");
853           cand_byte = strlen(cand_str);
854           cand_str[cand_byte++] = ' ';
855           cand_str[cand_byte] = '\0';
856         } else {
857           /* インデックスはなし */
858 
859           index_width = UNDEFINED;
860           if (cand_width > s_max_width) {
861             cand_width = s_max_width;
862           }
863           cand_width -= strlen(" ");
864           cand_width = strhead(cand_str, cand_width);
865           if (cand_width <= cand_label_width + (int)strlen(":")) {
866             cand_width = 1;
867             strlcpy(cand_str, " ", cand_byte + 1);
868             s_candidate.cand_col[index] = UNDEFINED;
869           } else {
870             cand_byte = strlen(cand_str);
871             cand_str[cand_byte++] = ' ';
872             cand_str[cand_byte] = '\0';
873           }
874         }
875       }
876 
877       page_width += cand_width;
878       page_byte += cand_byte;
879       old_str = page_str;
880       uim_asprintf(&page_str, "%s%s", old_str, cand_str);
881       free(old_str);
882 
883       index_in_page++;
884       if (index_in_page == s_candidate.limit || (index + 1 - start) == nr_in_virtual_page) {
885         next = TRUE;
886       }
887     }
888 
889     if (next) { /* do fix the page */
890       if (index_width == UNDEFINED) {
891         s_candidate.index_col[page] = UNDEFINED;
892       } else {
893         int index_byte = index_width + 2/* utf-8 */;
894         char *index_str;
895         int i;
896         if (g_opt.ddskk) {
897           uim_asprintf(&index_str, "[%s %d]", s_nokori_str, s_candidate.nr - index - 1);
898         } else {
899           uim_asprintf(&index_str, "[%d/%d]", index + 1, s_candidate.nr);
900           for (i = 0; i < numwidth(index + 1); i++) {
901             index_str[1 + i] = ' ';
902           }
903           index_str[i] = '-';
904         }
905         assert(page_width + index_width <= s_max_width);
906         s_candidate.index_col[page] = page_width + strlen("[");
907         page_byte += index_byte;
908         old_str = page_str;
909         uim_asprintf(&page_str, "%s%s", old_str, index_str);
910         free(old_str);
911         free(index_str);
912       }
913       if (page < s_candidate.nr_pages) {
914         free(s_candidate.page_strs[page]);
915       }
916       s_candidate.page_strs[page] = uim_strdup(page_str);
917       page++;
918 
919       page_byte = 0;
920       page_width = 0;
921       index_in_page = 0;
922       free(page_str);
923       page_str = uim_strdup("");
924     }
925     if (add_extra_page) {
926       int i;
927 
928       s_candidate.nr_pages++;
929       s_candidate.page2index = uim_realloc(s_candidate.page2index, s_candidate.nr_pages * sizeof(int));
930       s_candidate.index_col = uim_realloc(s_candidate.index_col, s_candidate.nr_pages * sizeof(int));
931       s_candidate.page_strs = uim_realloc(s_candidate.page_strs, s_candidate.nr_pages * sizeof(char *));
932 
933       for (i = s_candidate.nr_pages - 1; i >= page; i--) {
934         s_candidate.page2index[i] = s_candidate.page2index[i - 1];
935         s_candidate.index_col[i] = s_candidate.index_col[i - 1];
936         s_candidate.page_strs[i] = s_candidate.page_strs[i - 1];
937       }
938       s_candidate.page2index[page] = index + 1;
939       s_candidate.index_col[page] = UNDEFINED;
940       s_candidate.page_strs[page] = NULL;
941     }
942     free(cand_str);
943   }
944   free(page_str);
945 }
946 
947 /*
948  * nの文字列表現の幅を返す
949  * nは0以上でなければならない。
950  */
numwidth(int n)951 static int numwidth(int n)
952 {
953   int i;
954   assert(n >= 0);
955   for (i = 1;(n /= 10) > 0; i++);
956   return i;
957 }
958 
959 /*
960  * indexがあるページを返す。
961  */
index2page(int index)962 static int index2page(int index)
963 {
964   int i;
965   assert(s_candidate.nr_pages != UNDEFINED);
966   for (i = 0; i < s_candidate.nr_pages; i++) {
967     if (s_candidate.page2index[i] > index) {
968       break;
969     }
970   }
971   return i - 1;
972 }
973 
974 /*
975  * s_candidateを初期化する。
976  */
reset_candidate(void)977 static void reset_candidate(void)
978 {
979   if (s_candidate.nr == UNDEFINED) {
980     return;
981   }
982 
983   if (s_candidate.page_strs != NULL) {
984     int i;
985     for (i = 0; i < s_candidate.nr_pages; i++) {
986       free(s_candidate.page_strs[i]);
987     }
988     free(s_candidate.page_strs);
989   }
990   if (s_candidate.cand_col != NULL) {
991     free(s_candidate.cand_col);
992   }
993   if (s_candidate.page2index != NULL) {
994     free(s_candidate.page2index);
995   }
996   if (s_candidate.index_col != NULL) {
997     free(s_candidate.index_col);
998   }
999 
1000   s_candidate.nr = UNDEFINED;
1001   s_candidate.limit = UNDEFINED;
1002   s_candidate.nr_pages = UNDEFINED;
1003   s_candidate.index = UNDEFINED;
1004   s_candidate.page_strs = NULL;
1005   s_candidate.cand_col = NULL;
1006   s_candidate.page2index = NULL;
1007   s_candidate.index_col = NULL;
1008 }
1009 
1010 /*
1011  * s_candidate_colとs_candidate_strとs_index_strを設定する
1012  */
set_candidate(void)1013 static void set_candidate(void)
1014 {
1015   uim_candidate cand;
1016   int cand_width;
1017   /* "[10/20]"の幅 */
1018   int index_width;
1019 
1020   if (s_candidate.index_col[s_candidate.page] == UNDEFINED) {
1021     s_index_str = uim_strdup("");
1022     index_width = 0;
1023   } else {
1024     /* 右端の候補のインデックス */
1025     int right_edge_cand_index = s_candidate.page + 1 == s_candidate.nr_pages ? s_candidate.nr - 1 : s_candidate.page2index[s_candidate.page + 1] - 1;
1026     /* 右端の候補のインデックスの幅 */
1027     int right_edge_cand_index_width = numwidth(right_edge_cand_index + 1);
1028     /* 現在の候補のインデックスの幅 */
1029     int cand_index_width = numwidth(s_candidate.index + 1);
1030     int padlen = right_edge_cand_index_width - cand_index_width;
1031     char *pad;
1032 
1033     if (padlen < 0)
1034       padlen = 0;
1035     pad = uim_malloc(padlen + 1);
1036     memset(pad, ' ', padlen);
1037     pad[padlen] = '\0';
1038     uim_asprintf(&s_index_str, "%s%d", pad, s_candidate.index + 1);
1039     free(pad);
1040 
1041     if (g_opt.ddskk) {
1042       index_width = strlen("[xxxx ]") + numwidth(s_candidate.nr - s_candidate.index - 1);
1043     } else {
1044       index_width = strlen("[/]") + numwidth(s_candidate.index + 1) + numwidth(s_candidate.nr);
1045     }
1046   }
1047 
1048 
1049   s_candidate_col = s_candidate.cand_col[s_candidate.index];
1050   if (s_candidate_col == UNDEFINED) {
1051     s_candidate_str = uim_strdup("");
1052     return;
1053   }
1054   cand = uim_get_candidate(g_context, s_candidate.index, 0);
1055   if (uim_candidate_get_cand_str(cand) == NULL) {
1056     s_candidate_str = uim_strdup("");
1057     s_candidate_col = UNDEFINED;
1058     uim_candidate_free(cand);
1059     return;
1060   }
1061   s_candidate_str = tab2space(uim_candidate_get_cand_str(cand));
1062   cand_width = strwidth(s_candidate_str);
1063   if (s_candidate_col + cand_width + (int)strlen(" ") + index_width > s_max_width) {
1064     strhead(s_candidate_str, s_max_width - s_candidate_col - strlen(" ") - index_width);
1065   }
1066   uim_candidate_free(cand);
1067 }
1068 
callbacks_winch(void)1069 void callbacks_winch(void)
1070 {
1071   int current_index;
1072 
1073   start_callbacks();
1074 
1075   s_max_width = g_win->ws_col;
1076   if (g_opt.statusline_width != UNDEFINED && g_opt.statusline_width <= s_max_width) {
1077     s_max_width = g_opt.statusline_width;
1078   }
1079 
1080   if (s_candidate.nr == UNDEFINED) {
1081     return;
1082   }
1083 
1084   /* 候補一覧を表示中 */
1085   if (s_candidate.page_strs != NULL) {
1086     int i;
1087     for (i = 0; i < s_candidate.nr_pages; i++) {
1088       free(s_candidate.page_strs[i]);
1089     }
1090     free(s_candidate.page_strs);
1091     s_candidate.page_strs = NULL;
1092   }
1093   if (s_candidate.page2index != NULL) {
1094     free(s_candidate.page2index);
1095     s_candidate.page2index = NULL;
1096   }
1097   if (s_candidate.index_col != NULL) {
1098     free(s_candidate.index_col);
1099     s_candidate.index_col = NULL;
1100   }
1101   if (s_candidate.cand_col != NULL) {
1102     free(s_candidate.cand_col);
1103     s_candidate.cand_col = NULL;
1104   }
1105 
1106   current_index = s_candidate.index;
1107   init_candidate(s_candidate.nr, s_candidate.limit);
1108   s_candidate.index = current_index;
1109   make_page_strs();
1110 }
1111 
callbacks_set_mode(int mode)1112 void callbacks_set_mode(int mode)
1113 {
1114   s_mode = mode;
1115 }
1116