1 /*
2  * Copyright (C) 2020 The HIME team, Taiwan
3  * Copyright (C) 2011 Edward Der-Hua Liu, Hsin-Chu, Taiwan
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation version 2.1
8  * of the License.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "hime.h"
21 
22 #include "gst.h"
23 #include "pho.h"
24 #include "win-sym.h"
25 
26 /* "destroy_window = FALSE" should be ok with both GTK+ 2.x and 3.x
27  * gcin use TRUE for GTK+ 3.x, but caleb- always patch it to FALSE
28  */
29 #if 0
30 int destroy_window = TRUE;
31 #else
32 int destroy_window = FALSE;
33 #endif
34 
35 GtkWidget *gwin0 = NULL;
36 extern GtkWidget *gwin1;
37 extern Display *dpy;
38 static GtkWidget *top_bin;
39 int current_hime_inner_frame;
40 
41 static GtkWidget *hbox_edit;
42 static PangoAttrList *attr_list, *attr_list_blank;
43 extern gboolean test_mode;
44 
45 void compact_win0 ();
46 void move_win0 (int x, int y);
47 void get_win0_geom ();
48 
49 static struct {
50     GtkWidget *vbox;
51     GtkWidget *label;
52     //  GtkWidget *line;
53     int x;
54 } chars[MAX_PH_BF_EXT];
55 
56 static GtkWidget *button_pho;
57 static GtkWidget *label_pho;
58 extern char text_pho[];
59 extern int text_pho_N;
60 static GtkWidget *button_eng_ph;
61 //static int max_yl;
62 
63 static void create_win0_gui ();
64 
recreate_win0()65 static void recreate_win0 () {
66     memset (chars, 0, sizeof (chars));
67     label_pho = NULL;
68 
69     create_win0_gui ();
70 }
71 
72 #if USE_TSIN
change_win0_style()73 void change_win0_style () {
74     if (!top_bin || current_hime_inner_frame == hime_inner_frame)
75         return;
76 
77     gtk_widget_destroy (top_bin);
78     top_bin = NULL;
79 
80     current_hime_inner_frame = hime_inner_frame;
81     recreate_win0 ();
82 }
83 #endif
84 
85 /* there is a bug in gtk, if the widget is created and hasn't been processed by
86    gtk_main(), the coodinate of the widget is sometimes invalid.
87    We use pre-create to overcome this bug.
88 */
89 
90 void drawcursor ();
91 void open_select_pho ();
92 void create_phrase_save_menu (GdkEventButton *event);
93 
mouse_char_callback(GtkWidget * widget,GdkEventButton * event,gpointer data)94 static void mouse_char_callback (GtkWidget *widget, GdkEventButton *event, gpointer data) {
95     tss.c_idx = GPOINTER_TO_INT (data);
96     drawcursor ();
97 
98     switch (event->button) {
99     case 1:
100     case 2:
101         open_select_pho ();
102         break;
103     case 3: {
104         create_phrase_save_menu (event);
105         break;
106     }
107     }
108 }
109 
create_char(int index)110 static void create_char (int index) {
111 
112     if (!hbox_edit)
113         return;
114 
115     GdkColor fg;
116     gdk_color_parse (hime_win_color_fg, &fg);
117     GdkColor color_bg;
118     gdk_color_parse (tsin_phrase_line_color, &color_bg);
119 
120     int i = index;
121     {
122         if (chars[i].vbox)
123             return;
124 
125         GtkWidget *event_box = gtk_event_box_new ();
126         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
127         chars[i].vbox = event_box;
128         g_signal_connect (
129             G_OBJECT (event_box), "button-press-event",
130             G_CALLBACK (mouse_char_callback), GINT_TO_POINTER (index));
131 
132         gtk_box_pack_start (GTK_BOX (hbox_edit), event_box, FALSE, FALSE, 0);
133         GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
134         gtk_orientable_set_orientation (GTK_ORIENTABLE (vbox), GTK_ORIENTATION_VERTICAL);
135         gtk_container_add (GTK_CONTAINER (event_box), vbox);
136 
137         GtkWidget *label = gtk_label_new (NULL);
138         gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
139 
140         set_label_font_size (label, hime_font_size);
141         chars[i].label = label;
142 
143         if (hime_win_color_use) {
144 #if !GTK_CHECK_VERSION(3, 0, 0)
145             gtk_widget_modify_fg (label, GTK_STATE_NORMAL, &fg);
146 #else
147             GdkRGBA rgbfg;
148             gdk_rgba_parse (&rgbfg, gdk_color_to_string (&fg));
149             gtk_widget_override_color (label, GTK_STATE_FLAG_NORMAL, &rgbfg);
150 #endif
151         }
152         gtk_widget_show_all (event_box);
153     }
154 }
155 
156 extern gboolean b_use_full_space;
157 
158 void show_win0 ();
159 
disp_char(int index,char * ch)160 void disp_char (int index, char *ch) {
161     if (hime_edit_display_ap_only ())
162         return;
163     if (!top_bin)
164         show_win0 ();
165 
166     //  dbg("disp_char %d %s\n", index, ch);
167     create_char (index);
168     GtkWidget *label = chars[index].label;
169 
170     if (label) {
171         if (ch[0] == ' ' && ch[1] == ' ')
172             set_label_space (label);
173         else {
174             gtk_label_set_text (GTK_LABEL (label), ch);
175         }
176     }
177 
178     get_win0_geom ();
179     if (win_x + win_xl >= dpy_xl)
180         move_win0 (dpy_xl - win_xl, win_y);
181 
182     gtk_widget_show_all (chars[index].vbox);
183 }
184 
hide_char(int index)185 void hide_char (int index) {
186     if (!chars[index].vbox)
187         return;
188     gtk_label_set_text (GTK_LABEL (chars[index].label), "");
189     gtk_widget_hide (chars[index].vbox);
190 }
191 
clear_chars_all()192 void clear_chars_all () {
193     int i;
194     for (i = 0; i < MAX_PH_BF_EXT; i++) {
195         hide_char (i);
196     }
197 
198     compact_win0 ();
199 }
200 
set_cursor_tsin(int index)201 void set_cursor_tsin (int index) {
202     GtkWidget *label = chars[index].label;
203 
204     if (!label)
205         return;
206 
207     if (hime_edit_display_ap_only ())
208         return;
209 
210     gtk_label_set_attributes (GTK_LABEL (label), attr_list);
211 }
212 
clr_tsin_cursor(int index)213 void clr_tsin_cursor (int index) {
214     GtkWidget *label = chars[index].label;
215 
216     if (!label)
217         return;
218     gtk_label_set_attributes (GTK_LABEL (label), attr_list_blank);
219 }
220 
221 void hide_win0 ();
222 
disp_tsin_pho(int index,char * pho)223 void disp_tsin_pho (int index, char *pho) {
224     if (hime_display_on_the_spot_key ()) {
225         if (gwin0 && gtk_widget_get_visible (gwin0))
226             hide_win0 ();
227         return;
228     }
229 
230     if (button_pho && !gtk_widget_get_visible (button_pho))
231         gtk_widget_show (button_pho);
232 
233     text_pho_N = pin_juyin ? 6 : 3;
234     disp_pho_sub (label_pho, index, pho);
235 }
236 
237 void disp_tsin_pho (int index, char *pho);
clr_in_area_pho_tsin()238 void clr_in_area_pho_tsin () {
239     int i;
240     for (i = 0; i < text_pho_N; i++)
241         disp_tsin_pho (i, " ");
242 }
243 
get_widget_xy(GtkWidget * win,GtkWidget * widget,int * rx,int * ry)244 int get_widget_xy (GtkWidget *win, GtkWidget *widget, int *rx, int *ry) {
245     if (!win && !widget)
246         p_err ("get_widget_xy err");
247 
248     //  gdk_flush();
249 
250     GtkRequisition sz;
251     gtk_widget_get_preferred_size (widget, NULL, &sz);
252     int wx, wy;
253 
254     wx = wy = 0;
255 
256     gtk_widget_translate_coordinates (widget, win,
257                                       0, sz.height, &wx, &wy);
258 
259     gtk_widget_translate_coordinates (widget, win,
260                                       0, sz.height, &wx, &wy);
261 
262     //  dbg("%d wx:%d\n", index,  wx);
263 
264     int win_x, win_y;
265 
266     gtk_window_get_position (GTK_WINDOW (win), &win_x, &win_y);
267     int win_xl, win_yl;
268     get_win_size (win, &win_xl, &win_yl);
269 
270     if (wx > win_xl)
271         wx = win_xl;
272 
273     *rx = win_x + wx;
274     *ry = win_y + wy;
275     return wx;
276 }
277 
278 void getRootXY (Window win, int wx, int wy, int *tx, int *ty);
279 void disp_selections (int x, int y);
disp_tsin_select(int index)280 void disp_tsin_select (int index) {
281     int x, y;
282 
283     if (index < 0)
284         return;
285 
286     //  dbg("hime_edit_display_ap_only() %d\n", hime_edit_display_ap_only());
287 
288     if (hime_edit_display_ap_only ()) {
289         getRootXY (current_CS->client_win, current_CS->spot_location.x, current_CS->spot_location.y, &x, &y);
290     } else {
291 #if 1
292         int i;
293         // bug in GTK, widget position is wrong, repeat util find one
294         for (i = index; i >= 0; i--) {
295             gtk_widget_show_now (chars[i].label);
296             gtk_widget_show (chars[i].vbox);
297             gtk_main_iteration_do (FALSE);
298 
299             int tx = get_widget_xy (gwin0, chars[i].vbox, &x, &y);
300 
301             if (tx >= 0)
302                 break;
303         }
304 #else
305         get_widget_xy (gwin0, chars[index].vbox, &x, &y);
306 #endif
307         get_win0_geom ();
308     }
309     disp_selections (x, y);
310 }
311 
312 #define MIN_X_SIZE 32
313 
314 static int best_win_x, best_win_y;
315 
raw_move(int x,int y)316 static void raw_move (int x, int y) {
317     int xl, yl;
318 
319     if (!gwin0)
320         return;
321 
322     get_win_size (gwin0, &xl, &yl);
323 
324     if (x + xl > dpy_xl)
325         x = dpy_xl - xl;
326     if (y + yl > dpy_yl)
327         y = dpy_yl - yl;
328 
329     gtk_window_move (GTK_WINDOW (gwin0), x, y);
330     //  dbg("gwin0:%x raw_move %d,%d\n", gwin0, x, y);
331 }
332 
compact_win0()333 void compact_win0 () {
334     if (!gwin0)
335         return;
336 
337     //  max_yl = 0;
338     raw_move (best_win_x, best_win_y);
339 }
340 
341 gboolean tsin_has_input ();
342 GtkWidget *gwin_sym;
343 
move_win0(int x,int y)344 void move_win0 (int x, int y) {
345     //  dbg("--- gwin0:%x move_win0 %d,%d\n", gwin0, x,y);
346     best_win_x = x;
347     best_win_y = y;
348 
349     if (gwin0)
350         gtk_window_get_size (GTK_WINDOW (gwin0), &win_xl, &win_yl);
351 
352     if (x + win_xl > dpy_xl)
353         x = dpy_xl - win_xl;
354     if (x < 0)
355         x = 0;
356 
357     if (y + win_yl > dpy_yl)
358         y = dpy_yl - win_yl;
359     if (y < 0)
360         y = 0;
361 
362     //  dbg("move_win0 %d,%d\n",x, y);
363 
364     if (gwin0)
365         gtk_window_move (GTK_WINDOW (gwin0), x, y);
366 
367     //  dbg("move_win0 %d %d\n",x,y);
368     win_x = x;
369     win_y = y;
370 
371     move_win_sym ();
372 }
373 
disp_tsin_eng_pho(int eng_pho)374 void disp_tsin_eng_pho (int eng_pho) {
375     static unich_t *eng_pho_strs[] = {N_ ("英"), N_ ("注")};
376 
377     if (!button_eng_ph)
378         return;
379 
380     gtk_button_set_label (GTK_BUTTON (button_eng_ph), _ (eng_pho_strs[eng_pho]));
381 }
382 
mouse_button_callback(GtkWidget * widget,GdkEventButton * event,gpointer data)383 static void mouse_button_callback (GtkWidget *widget, GdkEventButton *event, gpointer data) {
384     //  dbg("mouse_button_callback %d\n", event->button);
385     switch (event->button) {
386     case 1:
387         toggle_win_sym ();
388         break;
389     case 2:
390         inmd_switch_popup_handler (widget, (GdkEvent *) event);
391         break;
392     case 3:
393         exec_hime_setup ();
394         break;
395     }
396 }
397 
398 void tsin_toggle_eng_ch ();
399 
create_win0()400 void create_win0 () {
401     if (gwin0)
402         return;
403 #if _DEBUG && 0
404     dbg ("create_win0\n");
405 #endif
406     gwin0 = gtk_window_new (GTK_WINDOW_TOPLEVEL);
407     gtk_window_set_has_resize_grip (GTK_WINDOW (gwin0), FALSE);
408     gtk_container_set_border_width (GTK_CONTAINER (gwin0), 0);
409     gtk_widget_realize (gwin0);
410     set_no_focus (gwin0);
411 }
412 
413 void create_win1 ();
414 
create_cursor_attr()415 static void create_cursor_attr () {
416     if (attr_list)
417         pango_attr_list_unref (attr_list);
418 
419     GdkColor color_bg, color_fg;
420     if (hime_win_color_use)
421         gdk_color_parse (tsin_cursor_color, &color_bg);
422     else
423         gdk_color_parse (TSIN_CURSOR_COLOR_DEFAULT, &color_bg);
424     gdk_color_parse ("white", &color_fg);
425 
426     attr_list = pango_attr_list_new ();
427     attr_list_blank = pango_attr_list_new ();
428 
429     PangoAttribute *blue_bg = pango_attr_background_new (
430         color_bg.red, color_bg.green, color_bg.blue);
431     blue_bg->start_index = 0;
432     blue_bg->end_index = 128;
433     pango_attr_list_insert (attr_list, blue_bg);
434 
435     PangoAttribute *white_fg = pango_attr_foreground_new (
436         color_fg.red, color_fg.green, color_fg.blue);
437     white_fg->start_index = 0;
438     white_fg->end_index = 128;
439     pango_attr_list_insert (attr_list, white_fg);
440 }
441 
442 void init_tsin_selection_win ();
443 
set_win0_bg()444 static void set_win0_bg () {
445 #if 1
446     change_win_bg (gwin0);
447 #endif
448 }
449 
450 void change_win1_font ();
451 
create_win0_gui()452 static void create_win0_gui () {
453     if (top_bin)
454         return;
455 
456     GtkWidget *vbox_top = gtk_vbox_new (FALSE, 0);
457     gtk_orientable_set_orientation (GTK_ORIENTABLE (vbox_top), GTK_ORIENTATION_VERTICAL);
458     gtk_container_set_border_width (GTK_CONTAINER (gwin0), 0);
459 
460     if (hime_inner_frame) {
461         GtkWidget *frame;
462         top_bin = frame = gtk_frame_new (NULL);
463         gtk_container_set_border_width (GTK_CONTAINER (frame), 0);
464         gtk_container_add (GTK_CONTAINER (gwin0), frame);
465         gtk_container_add (GTK_CONTAINER (frame), vbox_top);
466     } else {
467         top_bin = vbox_top;
468         gtk_container_add (GTK_CONTAINER (gwin0), vbox_top);
469     }
470 
471     memset (chars, 0, sizeof (chars));
472 
473     GtkWidget *hbox_row1 = gtk_hbox_new (FALSE, 0);
474     /* This packs the button into the gwin0 (a gtk container). */
475     gtk_box_pack_start (GTK_BOX (vbox_top), hbox_row1, FALSE, FALSE, 0);
476 
477     hbox_edit = gtk_hbox_new (FALSE, 0);
478     gtk_container_set_border_width (GTK_CONTAINER (hbox_edit), 0);
479     /* This packs the button into the gwin0 (a gtk container). */
480     gtk_box_pack_start (GTK_BOX (hbox_row1), hbox_edit, FALSE, FALSE, 0);
481 
482     create_cursor_attr ();
483 
484     button_pho = gtk_button_new ();
485     gtk_container_set_border_width (GTK_CONTAINER (button_pho), 0);
486     gtk_box_pack_start (GTK_BOX (hbox_row1), button_pho, FALSE, FALSE, 0);
487 
488     g_signal_connect (G_OBJECT (button_pho), "button-press-event",
489                       G_CALLBACK (mouse_button_callback), NULL);
490 #if GTK_CHECK_VERSION(2, 18, 0)
491     gtk_widget_set_can_focus (button_pho, FALSE);
492     gtk_widget_set_can_default (button_pho, FALSE);
493 #else
494     GTK_WIDGET_UNSET_FLAGS (button_pho, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
495 #endif
496 
497 #if 0
498   if (left_right_button_tips) {
499 #if GTK_CHECK_VERSION(2, 12, 0)
500     gtk_widget_set_tooltip_text (button_pho, _("Left:Symbol selection tables  Right:Preferences"));
501 #else
502     GtkTooltips *button_pho_tips = gtk_tooltips_new ();
503     gtk_tooltips_set_tip (GTK_TOOLTIPS (button_pho_tips), button_pho, _("Left:Symbol selection tables  Right:Preferences"),NULL);
504 #endif
505   }
506 #endif
507 
508     label_pho = gtk_label_new ("");
509     set_label_font_size (label_pho, hime_font_size_tsin_pho_in);
510     gtk_container_add (GTK_CONTAINER (button_pho), label_pho);
511 
512     clr_in_area_pho_tsin ();
513 
514     gtk_widget_show_all (gwin0);
515     //  gdk_flush();
516     gtk_widget_hide (gwin0);
517 
518     init_tsin_selection_win ();
519 
520     set_win0_bg ();
521 
522     //  change_win1_font();
523 }
524 
destroy_top_bin()525 static void destroy_top_bin () {
526     if (!top_bin)
527         return;
528     gtk_widget_destroy (top_bin);
529     top_bin = NULL;
530     label_pho = NULL;
531     button_pho = NULL;
532     button_eng_ph = NULL;
533     hbox_edit = NULL;
534     memset (chars, 0, sizeof (chars));
535 }
536 
537 #if USE_TSIN
destroy_win0()538 void destroy_win0 () {
539     if (!gwin0)
540         return;
541     destroy_top_bin ();
542     gtk_widget_destroy (gwin0);
543     gwin0 = NULL;
544 }
545 #endif
546 
get_win0_geom()547 void get_win0_geom () {
548     if (!gwin0)
549         return;
550     gtk_window_get_position (GTK_WINDOW (gwin0), &win_x, &win_y);
551     get_win_size (gwin0, &win_xl, &win_yl);
552 }
553 
554 gboolean tsin_has_input ();
555 extern gboolean force_show;
556 void raise_tsin_selection_win ();
557 
show_win0()558 void show_win0 () {
559 #if _DEBUG && 1
560     dbg ("show_win0 pop:%d in:%d for:%d \n", hime_pop_up_win, tsin_has_input (), force_show);
561 #endif
562     create_win0 ();
563     create_win0_gui ();
564 
565     if (hime_pop_up_win && !tsin_has_input () && !force_show) {
566         //    dbg("show ret\n");
567         return;
568     }
569 
570 #if 0
571   if (!gtk_widget_get_visible(gwin0))
572 #endif
573     {
574         //    dbg("gtk_widget_show %x\n", gwin0);
575         move_win0 (win_x, win_y);
576         gtk_widget_show (gwin0);
577     }
578 
579     show_win_sym ();
580 
581     if (current_CS->b_raise_window) {
582         gtk_window_present (GTK_WINDOW (gwin0));
583         raise_tsin_selection_win ();
584     }
585 }
586 
587 void hide_selections_win ();
hide_win0()588 void hide_win0 () {
589     if (!gwin0)
590         return;
591 
592     gtk_widget_hide (gwin0);
593     if (destroy_window)
594         destroy_win0 ();
595     else
596         destroy_top_bin ();
597 
598     hide_selections_win ();
599     hide_win_sym ();
600 }
601 
602 void bell ();
603 
604 #if USE_TSIN
change_tsin_font_size()605 void change_tsin_font_size () {
606     if (!top_bin)
607         return;
608 
609     GdkColor fg;
610     gdk_color_parse (hime_win_color_fg, &fg);
611 
612     set_label_font_size (label_pho, hime_font_size_tsin_pho_in);
613 
614     int i;
615     for (i = 0; i < MAX_PH_BF_EXT; i++) {
616         GtkWidget *label = chars[i].label;
617         if (!label)
618             continue;
619 
620         set_label_font_size (label, hime_font_size);
621 
622         if (hime_win_color_use) {
623 #if !GTK_CHECK_VERSION(3, 0, 0)
624             gtk_widget_modify_fg (label, GTK_STATE_NORMAL, &fg);
625 #else
626             GdkRGBA rgbfg;
627             gdk_rgba_parse (&rgbfg, gdk_color_to_string (&fg));
628             gtk_widget_override_color (label, GTK_STATE_FLAG_NORMAL, &rgbfg);
629 #endif
630         }
631     }
632 
633     compact_win0 ();
634 
635     set_win0_bg ();
636 }
637 #endif  // USE_TSIN
638 
show_button_pho(gboolean bshow)639 void show_button_pho (gboolean bshow) {
640     if (!button_pho)
641         return;
642 
643     if (bshow)
644         gtk_widget_show (button_pho);
645     else {
646         gtk_widget_hide (button_pho);
647         compact_win0 ();
648     }
649 }
650 
651 char *get_full_str ();
652 
win_tsin_disp_half_full()653 void win_tsin_disp_half_full () {
654     if (label_pho == NULL)
655         show_win0 ();
656 
657     if (hime_win_color_use)
658         gtk_label_set_markup (GTK_LABEL (label_pho), get_full_str ());
659     else
660         gtk_label_set_text (GTK_LABEL (label_pho), get_full_str ());
661     compact_win0 ();
662 }
663 
664 void drawcursor ();
665 
666 #if USE_TSIN
change_tsin_color()667 void change_tsin_color () {
668     create_cursor_attr ();
669 
670     drawcursor ();
671 }
672 #endif
673