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