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 #include <config.h>
35 
36 #include <gtk/gtk.h>
37 #include <ctype.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <sys/stat.h>
41 
42 #include <uim/uim.h>
43 #include "uim/uim-helper.h"
44 #include "uim/uim-scm.h"
45 #include "uim/uim-custom.h"
46 #include "uim/gettext.h"
47 
48 #define OBJECT_DATA_PROP_BUTTONS "PROP_BUTTONS"
49 #define OBJECT_DATA_TOOL_BUTTONS "TOOL_BUTTONS"
50 #define OBJECT_DATA_SIZE_GROUP "SIZE_GROUP"
51 #define OBJECT_DATA_TOOLBAR_TYPE "TOOLBAR_TYPE"
52 #define OBJECT_DATA_BUTTON_TYPE "BUTTON_TYPE"
53 #define OBJECT_DATA_COMMAND "COMMAND"
54 
55 /* exported functions */
56 GtkWidget *uim_toolbar_standalone_new(void);
57 GtkWidget *uim_toolbar_trayicon_new(void);
58 GtkWidget *uim_toolbar_applet_new(void);
59 void uim_toolbar_check_helper_connection(GtkWidget *widget);
60 void uim_toolbar_get_im_list(void);
61 void uim_toolbar_launch_helper_application(const char *command);
62 
63 
64 enum {
65   TYPE_STANDALONE,
66   TYPE_APPLET,
67   TYPE_ICON
68 };
69 
70 enum {
71   BUTTON_PROP,
72   BUTTON_TOOL
73 };
74 
75 struct _CommandEntry {
76   const gchar *desc;
77   const gchar *label;
78   const gchar *icon;
79   const gchar *command;
80   const gchar *custom_button_show_symbol;
81   uim_bool show_button;
82 };
83 
84 /* FIXME! command menu and buttons should be customizable. */
85 static struct _CommandEntry command_entry[] = {
86   {
87     N_("Switch input method"),
88     NULL,
89     "im_switcher",
90 #if GTK_CHECK_VERSION(2, 90, 0)
91     "uim-im-switcher-gtk3",
92 #else
93     "uim-im-switcher-gtk",
94 #endif
95     "toolbar-show-switcher-button?",
96     UIM_FALSE
97   },
98 
99   {
100     N_("Preference"),
101     NULL,
102     GTK_STOCK_PREFERENCES,
103 #if GTK_CHECK_VERSION(2, 90, 0)
104     "uim-pref-gtk3",
105 #else
106     "uim-pref-gtk",
107 #endif
108     "toolbar-show-pref-button?",
109     UIM_FALSE
110   },
111 
112   {
113     N_("Japanese dictionary editor"),
114     NULL,
115     "uim-dict",
116 #if GTK_CHECK_VERSION(2, 90, 0)
117     "uim-dict-gtk3",
118 #else
119     "uim-dict-gtk",
120 #endif
121     "toolbar-show-dict-button?",
122     UIM_FALSE
123   },
124 
125   {
126     N_("Input pad"),
127     NULL,
128     GTK_STOCK_BOLD,
129 #if GTK_CHECK_VERSION(2, 90, 0)
130     "uim-input-pad-ja-gtk3",
131 #else
132     "uim-input-pad-ja",
133 #endif
134     "toolbar-show-input-pad-button?",
135     UIM_FALSE
136   },
137 
138   {
139     N_("Handwriting input pad"),
140     "H",
141 #if GTK_CHECK_VERSION(2, 6, 0)
142     GTK_STOCK_EDIT,
143 #else
144     NULL,
145 #endif
146     "uim-tomoe-gtk",
147     "toolbar-show-handwriting-input-pad-button?",
148     UIM_FALSE
149   },
150 
151   {
152     N_("Help"),
153     NULL,
154     GTK_STOCK_HELP,
155     "uim-help",
156     "toolbar-show-help-button?",
157     UIM_FALSE
158   }
159 };
160 
161 static guint command_entry_len = sizeof(command_entry) / sizeof(struct _CommandEntry);
162 
163 static GtkWidget *im_menu;
164 static GtkWidget *prop_menu;
165 static GtkWidget *right_click_menu;
166 static unsigned int read_tag;
167 static int uim_fd;
168 static GtkIconFactory *uim_factory;
169 static GList *uim_icon_list;
170 static gboolean prop_menu_showing = FALSE;
171 static gboolean custom_enabled;
172 static gboolean with_dark_bg;
173 
174 static void set_button_style(GtkWidget *button, gint type);
175 static const char *safe_gettext(const char *msgid);
176 static gboolean has_n_strs(gchar **str_list, guint n);
177 static gboolean register_icon(const gchar *name);
178 static void reset_icon(void);
179 
180 static void
set_button_style(GtkWidget * button,gint type)181 set_button_style(GtkWidget *button, gint type)
182 {
183 #if GTK_CHECK_VERSION(2, 90, 0)
184     GtkStyleContext *context = gtk_widget_get_style_context(button);
185     GtkCssProvider *provider = gtk_css_provider_new();
186     switch (type) {
187     case TYPE_ICON:
188         gtk_css_provider_load_from_data(provider,
189                                         "#uim-systray-button {\n"
190 #if GTK_CHECK_VERSION(3, 14, 0)
191                                         " outline-width: 0;\n"
192 #else
193                                         " -GtkWidget-focus-line-width: 0;\n"
194                                         " -GtkWidget-focus-padding: 0;\n"
195 #endif
196                                         " padding-top: 0;\n"
197                                         " padding-bottom: 0;\n"
198                                         " padding-left: 2px;\n"
199                                         " padding-right: 2px;\n"
200                                         "}\n", -1, NULL);
201 	break;
202     case TYPE_STANDALONE:
203         gtk_css_provider_load_from_data(provider,
204                                         "#uim-toolbar-button {\n"
205                                         " padding-left: 5px;\n"
206                                         " padding-right: 5px;\n"
207                                         "}\n", -1, NULL);
208 	break;
209     case TYPE_APPLET:
210          gtk_css_provider_load_from_data(provider,
211                                         "#uim-applet-button {\n"
212                                         " padding-left: 2px;\n"
213                                         " padding-right: 2px;\n"
214                                         "}\n", -1, NULL);
215 	break;
216    }
217     gtk_style_context_add_provider(context,
218                                    GTK_STYLE_PROVIDER(provider),
219                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
220     g_object_unref(provider);
221 #endif
222     switch (type) {
223     case TYPE_ICON:
224         gtk_widget_set_name(button, "uim-systray-button");
225 	break;
226     case TYPE_STANDALONE:
227         gtk_widget_set_name(button, "uim-toolbar-button");
228 	break;
229     case TYPE_APPLET:
230         gtk_widget_set_name(button, "uim-applet-button");
231 	break;
232     }
233 }
234 
235 static const char *
safe_gettext(const char * msgid)236 safe_gettext(const char *msgid)
237 {
238   const char *p;
239 
240   for (p = msgid; *p && isascii(*p); p++)
241     continue;
242 
243   return (*p) ? msgid : gettext(msgid);
244 }
245 
246 static gboolean
has_n_strs(gchar ** str_list,guint n)247 has_n_strs(gchar **str_list, guint n)
248 {
249   guint i;
250 
251   if (!str_list)
252     return FALSE;
253 
254   for (i = 0; i < n; i++) {
255     if (!str_list[i])
256       return FALSE;
257   }
258 
259   return TRUE;
260 }
261 
262 static void
calc_menu_position(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,GtkWidget * button)263 calc_menu_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in,
264 		   GtkWidget *button)
265 {
266   gint sc_height, sc_width, menu_width, menu_height, button_height;
267   GtkRequisition requisition;
268 
269   g_return_if_fail(x && y);
270   g_return_if_fail(GTK_IS_BUTTON(button));
271 
272   gdk_window_get_origin(gtk_widget_get_window(button), x, y);
273 #if GTK_CHECK_VERSION(2, 90, 0)
274   button_height = gdk_window_get_height(gtk_widget_get_window(button));
275 #else
276   gdk_drawable_get_size(gtk_widget_get_window(button), NULL, &button_height);
277 #endif
278 
279 #if GTK_CHECK_VERSION(2, 18, 0)
280   if (!gtk_widget_get_has_window(button)) {
281     GtkAllocation allocation;
282     gtk_widget_get_allocation(button, &allocation);
283     *x += allocation.x;
284   }
285 #else
286   if (GTK_WIDGET_NO_WINDOW(button))
287     *x += button->allocation.x;
288 #endif
289 
290   sc_height = gdk_screen_get_height(gdk_screen_get_default());
291   sc_width = gdk_screen_get_width(gdk_screen_get_default());
292 
293 #if GTK_CHECK_VERSION(3, 0, 0)
294   gtk_widget_get_preferred_size(GTK_WIDGET(menu), &requisition, NULL);
295 #else
296   gtk_widget_size_request(GTK_WIDGET(menu), &requisition);
297 #endif
298 
299   menu_width = requisition.width;
300   menu_height = requisition.height;
301 
302   if (*y + button_height + menu_height < sc_height)
303     *y = *y + button_height;
304   else {
305     if (*y + button_height < sc_height / 2)
306       *y = *y + button_height;
307     else
308       *y = *y - menu_height;
309   }
310 
311   if (*x + menu_width > sc_width)
312     *x = sc_width - menu_width;
313 }
314 
315 static void
right_click_menu_quit_activated(GtkMenu * menu_item,gpointer data)316 right_click_menu_quit_activated(GtkMenu *menu_item, gpointer data)
317 {
318   gtk_main_quit();
319   uim_quit();
320 }
321 
322 void
uim_toolbar_launch_helper_application(const char * command)323 uim_toolbar_launch_helper_application(const char *command)
324 {
325   if (command) {
326     if (!g_spawn_command_line_async(command, NULL)) {
327       GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
328           GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
329           _("Cannot launch '%s'."), command);
330       gtk_dialog_run(GTK_DIALOG(dialog));
331       gtk_widget_destroy(GTK_WIDGET(dialog));
332     }
333   }
334 }
335 
336 static void
right_click_menu_activated(GtkMenu * menu_item,gpointer data)337 right_click_menu_activated(GtkMenu *menu_item, gpointer data)
338 {
339   const char *command = data;
340   uim_toolbar_launch_helper_application(command);
341 }
342 
343 static gboolean
right_button_pressed(GtkButton * button,GdkEventButton * event,gpointer data)344 right_button_pressed(GtkButton *button, GdkEventButton *event, gpointer data)
345 {
346   gtk_menu_popup(GTK_MENU(right_click_menu), NULL, NULL,
347 		 (GtkMenuPositionFunc)calc_menu_position,
348 		 (gpointer)button, event->button,
349 		 gtk_get_current_event_time());
350 
351   return FALSE;
352 }
353 
354 static void
save_default_im_internal(const char * im)355 save_default_im_internal(const char *im)
356 {
357   uim_scm_callf("custom-set-value!",
358 		"yy",
359 		"custom-preserved-default-im-name",
360 		im);
361   uim_custom_save_custom("custom-preserved-default-im-name");
362 }
363 
364 static void
save_default_im(const char * im)365 save_default_im(const char *im)
366 {
367   if (custom_enabled)
368     uim_scm_call_with_gc_ready_stack((uim_gc_gate_func_ptr)save_default_im_internal, (void *)im);
369 }
370 
371 static gboolean
is_msg_imsw(const gchar * str)372 is_msg_imsw(const gchar *str)
373 {
374   return g_str_has_prefix(str, "action_imsw_");
375 }
376 
377 static gboolean
is_imsw_coverage_system_global()378 is_imsw_coverage_system_global()
379 {
380   char *coverage;
381   gboolean ret;
382 
383   coverage = uim_scm_symbol_value_str("imsw-coverage");
384 
385   ret =  (gboolean)!strcmp(coverage, "system-global");
386   free(coverage);
387 
388   return ret;
389 }
390 
391 static const char*
get_imsw_im(const gchar * str)392 get_imsw_im(const gchar *str)
393 {
394   /* skip "action_imsw_" */
395   return str + strlen("action_imsw_");
396 }
397 
398 static void
prop_menu_activate(GtkMenu * menu_item,gpointer data)399 prop_menu_activate(GtkMenu *menu_item, gpointer data)
400 {
401   GString *msg;
402   const gchar *str;
403 
404   str = g_object_get_data(G_OBJECT(menu_item), "prop_action");
405   msg = g_string_new(str);
406   g_string_prepend(msg, "prop_activate\n");
407   g_string_append(msg, "\n");
408   uim_helper_send_message(uim_fd, msg->str);
409   if (is_msg_imsw(str) && is_imsw_coverage_system_global()) {
410     const char *im = get_imsw_im(str);
411     save_default_im(im);
412   }
413 
414   g_string_free(msg, TRUE);
415 }
416 
417 static gboolean
prop_menu_shell_deactivate(GtkMenuShell * menu_shell,gpointer data)418 prop_menu_shell_deactivate(GtkMenuShell *menu_shell, gpointer data)
419 {
420   prop_menu_showing = FALSE;
421 
422   return FALSE;
423 }
424 
425 static void
popup_prop_menu(GtkButton * prop_button,GdkEventButton * event,GtkWidget * widget)426 popup_prop_menu(GtkButton *prop_button, GdkEventButton *event,
427 		GtkWidget *widget)
428 {
429   GtkWidget *menu_item, *hbox, *label, *img;
430   GList *menu_item_list, *icon_list, *label_list, *tooltip_list, *action_list,
431 	*state_list, *list;
432   int i, selected = -1;
433 
434   uim_toolbar_check_helper_connection(widget);
435 
436   menu_item_list = gtk_container_get_children(GTK_CONTAINER(prop_menu));
437   icon_list = g_object_get_data(G_OBJECT(prop_button), "prop_icon");
438   label_list = g_object_get_data(G_OBJECT(prop_button), "prop_label");
439   tooltip_list = g_object_get_data(G_OBJECT(prop_button), "prop_tooltip");
440   action_list = g_object_get_data(G_OBJECT(prop_button), "prop_action");
441   state_list = g_object_get_data(G_OBJECT(prop_button), "prop_state");
442 
443   list = menu_item_list;
444   while (list) {
445     gtk_widget_destroy(list->data);
446     list = list->next;
447   }
448   g_list_free(menu_item_list);
449 
450   gtk_widget_destroy(prop_menu);
451   prop_menu = gtk_menu_new();
452 
453   /* check selected item */
454   i = 0;
455   while (state_list) {
456     if (!strcmp("*", state_list->data)) {
457       selected = i;
458       break;
459     }
460     state_list = state_list->next;
461     i++;
462   }
463 
464   i = 0;
465   while (label_list) {
466     if (selected != -1) {
467       menu_item = gtk_check_menu_item_new();
468       label = gtk_label_new(label_list->data);
469 #if GTK_CHECK_VERSION(3, 2, 0)
470       hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
471 #else
472       hbox = gtk_hbox_new(FALSE, 0);
473 #endif
474 #if GTK_CHECK_VERSION(2, 4, 0)
475       gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(menu_item),
476 					    TRUE);
477 #endif
478       if (register_icon(icon_list->data))
479 	img = gtk_image_new_from_stock(icon_list->data, GTK_ICON_SIZE_MENU);
480       else
481 	img = gtk_image_new_from_stock("null", GTK_ICON_SIZE_MENU);
482       if (img) {
483 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 3);
484 	gtk_widget_show(img);
485       }
486       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 3);
487       gtk_container_add(GTK_CONTAINER(menu_item), hbox);
488       gtk_widget_show(label);
489       gtk_widget_show(hbox);
490       if (i == selected)
491 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), TRUE);
492     } else {
493       menu_item = gtk_image_menu_item_new_with_label(label_list->data);
494       if (register_icon(icon_list->data)) {
495 	img = gtk_image_new_from_stock(icon_list->data, GTK_ICON_SIZE_MENU);
496 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), img);
497 #if GTK_CHECK_VERSION(2, 16, 0)
498 	gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(menu_item), TRUE);
499 #endif
500       }
501     }
502 
503     /* tooltips */
504     gtk_widget_set_tooltip_text(menu_item,
505 			 tooltip_list ? tooltip_list->data : NULL);
506 
507     /* add to the menu */
508     gtk_menu_shell_append(GTK_MENU_SHELL(prop_menu), menu_item);
509 
510     gtk_widget_show(menu_item);
511     g_signal_connect(G_OBJECT(menu_item), "activate",
512 		     G_CALLBACK(prop_menu_activate), prop_menu);
513     g_object_set_data(G_OBJECT(menu_item), "prop_action",
514 		      action_list? action_list->data : NULL);
515     label_list = label_list->next;
516     if (icon_list)
517       icon_list = icon_list->next;
518     if (action_list)
519       action_list = action_list->next;
520     if (tooltip_list)
521       tooltip_list = tooltip_list->next;
522     i++;
523   }
524 
525   g_signal_connect(G_OBJECT(GTK_MENU_SHELL(prop_menu)), "deactivate",
526 		   G_CALLBACK(prop_menu_shell_deactivate), NULL);
527 
528   gtk_menu_popup(GTK_MENU(prop_menu), NULL, NULL,
529 		 (GtkMenuPositionFunc)calc_menu_position,
530 		 (gpointer)prop_button, event->button,
531 		 gtk_get_current_event_time());
532   prop_menu_showing = TRUE;
533 }
534 
535 static gboolean
button_pressed(GtkButton * button,GdkEventButton * event,GtkWidget * widget)536 button_pressed(GtkButton *button, GdkEventButton *event, GtkWidget *widget)
537 {
538   gint toolbar_type, button_type;
539 
540   switch (event->button) {
541   case 3:
542     toolbar_type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget),
543 			    OBJECT_DATA_TOOLBAR_TYPE));
544     if (toolbar_type == TYPE_APPLET)
545       gtk_propagate_event(gtk_widget_get_parent(GTK_WIDGET(button)),
546 		      				(GdkEvent *)event);
547     else
548       right_button_pressed(button, event, widget);
549     break;
550   case 2:
551     gtk_propagate_event(gtk_widget_get_parent(GTK_WIDGET(button)),
552 			(GdkEvent *)event);
553     break;
554   case 1:
555   default:
556     button_type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button),
557 			    OBJECT_DATA_BUTTON_TYPE));
558     if (button_type == BUTTON_PROP)
559       popup_prop_menu(button, event, widget);
560     break;
561   }
562 
563   return FALSE;
564 }
565 
566 static gboolean
prop_button_released(GtkButton * button,GdkEventButton * event,GtkWidget * widget)567 prop_button_released(GtkButton *button, GdkEventButton *event,
568 		     GtkWidget *widget)
569 {
570   switch (event->button) {
571   case 2:
572   case 3:
573     gtk_propagate_event(gtk_widget_get_parent(GTK_WIDGET(button)),
574 			(GdkEvent *)event);
575     break;
576   default:
577     break;
578   }
579 
580   return FALSE;
581 }
582 
583 static void
tool_button_clicked_cb(GtkButton * tool_button,GtkWidget * widget)584 tool_button_clicked_cb(GtkButton *tool_button, GtkWidget *widget)
585 {
586   const gchar *command;
587 
588   command = g_object_get_data(G_OBJECT(tool_button), OBJECT_DATA_COMMAND);
589   if (command)
590     if (!g_spawn_command_line_async(command, NULL)) {
591       GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
592           GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
593           _("Cannot launch '%s'."), command);
594       gtk_dialog_run(GTK_DIALOG(dialog));
595       gtk_widget_destroy(GTK_WIDGET(dialog));
596     }
597 }
598 
599 
600 static void
list_data_free(GList * list)601 list_data_free(GList *list)
602 {
603   g_list_foreach(list, (GFunc)g_free, NULL);
604   g_list_free(list);
605 }
606 
607 static void
prop_data_flush(gpointer data)608 prop_data_flush(gpointer data)
609 {
610   GList *list;
611   list = g_object_get_data(data, "prop_icon");
612   list_data_free(list);
613   list = g_object_get_data(data, "prop_label");
614   list_data_free(list);
615   list = g_object_get_data(data, "prop_tooltip");
616   list_data_free(list);
617   list = g_object_get_data(data, "prop_action");
618   list_data_free(list);
619   list = g_object_get_data(data, "prop_state");
620   list_data_free(list);
621 
622   g_object_set_data(G_OBJECT(data), "prop_icon", NULL);
623   g_object_set_data(G_OBJECT(data), "prop_label", NULL);
624   g_object_set_data(G_OBJECT(data), "prop_tooltip", NULL);
625   g_object_set_data(G_OBJECT(data), "prop_action", NULL);
626   g_object_set_data(G_OBJECT(data), "prop_state", NULL);
627 }
628 
629 static void
prop_button_destroy(gpointer data,gpointer user_data)630 prop_button_destroy(gpointer data, gpointer user_data)
631 {
632   prop_data_flush(data);
633   gtk_widget_destroy(GTK_WIDGET(data));
634 }
635 
636 static void
tool_button_destroy(gpointer data,gpointer user_data)637 tool_button_destroy(gpointer data, gpointer user_data)
638 {
639   gtk_widget_destroy(GTK_WIDGET(data));
640 }
641 
642 static GtkWidget*
button_create(GtkWidget * widget,GtkSizeGroup * sg,const gchar * icon_name,const gchar * label,gint type)643 button_create(GtkWidget *widget, GtkSizeGroup *sg, const gchar *icon_name,
644               const gchar *label, gint type)
645 {
646   GtkWidget *button;
647 
648   if (register_icon(icon_name)) {
649     GtkWidget *img = gtk_image_new_from_stock(icon_name, GTK_ICON_SIZE_MENU);
650     button = gtk_button_new();
651     gtk_container_add(GTK_CONTAINER(button), img);
652   } else {
653     button = gtk_button_new_with_label(label);
654   }
655 
656   set_button_style(button, type);
657 
658   gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
659   gtk_size_group_add_widget(sg, button);
660   g_object_set_data(G_OBJECT(button), OBJECT_DATA_BUTTON_TYPE,
661 		    GINT_TO_POINTER(BUTTON_PROP));
662 
663   g_signal_connect(G_OBJECT(button), "button-press-event",
664 		   G_CALLBACK(button_pressed), widget);
665 
666   return button;
667 }
668 
669 static GtkWidget *
prop_button_create(GtkWidget * widget,const gchar * icon_name,const gchar * label,const gchar * tip_text)670 prop_button_create(GtkWidget *widget, const gchar *icon_name,
671 		   const gchar *label, const gchar *tip_text)
672 {
673   GtkWidget *button;
674   GtkSizeGroup *sg;
675   gint type;
676 
677   sg = g_object_get_data(G_OBJECT(widget), OBJECT_DATA_SIZE_GROUP);
678   type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget),
679 		         OBJECT_DATA_TOOLBAR_TYPE));
680 
681   button = button_create(widget, sg, icon_name, label, type);
682 
683   gtk_widget_set_tooltip_text(button, tip_text);
684 
685   g_signal_connect(G_OBJECT(button), "button-release-event",
686 		   G_CALLBACK(prop_button_released), widget);
687 
688   return button;
689 }
690 
691 static void
prop_button_append_menu(GtkWidget * button,const gchar * icon_name,const gchar * label,const gchar * tooltip,const gchar * action,const gchar * state)692 prop_button_append_menu(GtkWidget *button,
693 			const gchar *icon_name,
694 			const gchar *label, const gchar *tooltip,
695 			const gchar *action, const gchar *state)
696 {
697   GList *icon_list, *label_list, *tooltip_list, *action_list, *state_list;
698 
699   icon_list = g_object_get_data(G_OBJECT(button), "prop_icon");
700   label_list = g_object_get_data(G_OBJECT(button), "prop_label");
701   tooltip_list = g_object_get_data(G_OBJECT(button), "prop_tooltip");
702   action_list = g_object_get_data(G_OBJECT(button), "prop_action");
703   state_list = g_object_get_data(G_OBJECT(button), "prop_state");
704 
705   icon_list = g_list_append(icon_list, g_strdup(icon_name));
706   label_list = g_list_append(label_list, g_strdup(label));
707   tooltip_list = g_list_append(tooltip_list, g_strdup(tooltip));
708   action_list = g_list_append(action_list, g_strdup(action));
709   state_list = g_list_append(state_list, g_strdup(state));
710 
711   g_object_set_data(G_OBJECT(button), "prop_icon", icon_list);
712   g_object_set_data(G_OBJECT(button), "prop_label", label_list);
713   g_object_set_data(G_OBJECT(button), "prop_tooltip", tooltip_list);
714   g_object_set_data(G_OBJECT(button), "prop_action", action_list);
715   g_object_set_data(G_OBJECT(button), "prop_state", state_list);
716 }
717 
718 static void
append_prop_button(GtkWidget * hbox,GtkWidget * button)719 append_prop_button(GtkWidget *hbox, GtkWidget *button)
720 {
721   GList *prop_buttons;
722 
723   if (button) {
724     gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
725 
726     prop_buttons = g_object_get_data(G_OBJECT(hbox), OBJECT_DATA_PROP_BUTTONS);
727     prop_buttons = g_list_append(prop_buttons, button);
728     g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_PROP_BUTTONS, prop_buttons);
729   }
730 }
731 
732 static void
append_tool_button(GtkWidget * hbox,GtkWidget * button)733 append_tool_button(GtkWidget *hbox, GtkWidget *button)
734 {
735   GList *tool_buttons;
736 
737   if (button) {
738     gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
739 
740     tool_buttons = g_object_get_data(G_OBJECT(hbox), OBJECT_DATA_TOOL_BUTTONS);
741     tool_buttons = g_list_append(tool_buttons, button);
742     g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_TOOL_BUTTONS, tool_buttons);
743   }
744 }
745 
746 
747 static gchar *
get_charset(gchar * line)748 get_charset(gchar *line)
749 {
750   gchar **tokens;
751   gchar *charset = NULL;
752 
753   tokens = g_strsplit(line, "=", 0);
754   if (tokens && tokens[0] && tokens[1] && !strcmp("charset", tokens[0]))
755     charset = g_strdup(tokens[1]);
756   g_strfreev(tokens);
757 
758   return charset;
759 }
760 
761 static gchar *
convert_charset(const gchar * charset,const gchar * str)762 convert_charset(const gchar *charset, const gchar *str)
763 {
764   if (!charset)
765     return NULL;
766 
767   return g_convert(str, strlen(str),
768 		   "UTF-8", charset,
769 		   NULL, /* gsize *bytes_read */
770 		   NULL, /* size *bytes_written */
771 		   NULL); /* GError **error */
772 }
773 
774 static void
helper_toolbar_prop_list_update(GtkWidget * widget,gchar ** lines)775 helper_toolbar_prop_list_update(GtkWidget *widget, gchar **lines)
776 {
777   GtkWidget *button = NULL;
778   guint i;
779   gchar **cols;
780   gchar *charset;
781   const gchar *indication_id, *iconic_label, *label, *tooltip_str;
782   const gchar *action_id, *is_selected;
783   GList *prop_buttons, *tool_buttons;
784   GtkSizeGroup *sg;
785   char *display_time;
786   gboolean is_hidden;
787   GtkWidget *toplevel;
788 
789   if (prop_menu_showing)
790     return;
791 
792   charset = get_charset(lines[1]);
793 
794   prop_buttons = g_object_get_data(G_OBJECT(widget), OBJECT_DATA_PROP_BUTTONS);
795   tool_buttons = g_object_get_data(G_OBJECT(widget), OBJECT_DATA_TOOL_BUTTONS);
796   sg  = g_object_get_data(G_OBJECT(widget), OBJECT_DATA_SIZE_GROUP);
797 
798   if (prop_buttons) {
799     g_list_foreach(prop_buttons, prop_button_destroy, NULL);
800     g_list_free(prop_buttons);
801     g_object_set_data(G_OBJECT(widget), OBJECT_DATA_PROP_BUTTONS, NULL);
802   }
803 
804   if (tool_buttons) {
805     g_list_foreach(tool_buttons, tool_button_destroy, NULL);
806     g_list_free(tool_buttons);
807     g_object_set_data(G_OBJECT(widget), OBJECT_DATA_TOOL_BUTTONS, NULL);
808   }
809 
810   display_time
811         = uim_scm_c_symbol( uim_scm_symbol_value( "toolbar-display-time" ) );
812   is_hidden = strcmp(display_time, "mode");
813   for (i = 0; lines[i] && strcmp("", lines[i]); i++) {
814     gchar *utf8_str = convert_charset(charset, lines[i]);
815 
816     if (utf8_str != NULL) {
817       cols = g_strsplit(utf8_str, "\t", 0);
818       g_free(utf8_str);
819     } else {
820       cols = g_strsplit(lines[i], "\t", 0);
821     }
822 
823     if (cols && cols[0]) {
824       if (!strcmp("branch", cols[0]) && has_n_strs(cols, 4)) {
825 	indication_id = cols[1];
826 	iconic_label  = safe_gettext(cols[2]);
827 	tooltip_str   = safe_gettext(cols[3]);
828 	button = prop_button_create(widget,
829 				    indication_id, iconic_label, tooltip_str);
830 	append_prop_button(widget, button);
831 
832         if (!is_hidden && (!strcmp(indication_id, "direct")
833             || g_str_has_suffix(indication_id, "_direct"))) {
834           is_hidden = TRUE;
835         }
836       } else if (!strcmp("leaf", cols[0]) && has_n_strs(cols, 7)) {
837 	indication_id = cols[1];
838 	iconic_label  = safe_gettext(cols[2]);
839 	label         = safe_gettext(cols[3]);
840 	tooltip_str   = safe_gettext(cols[4]);
841 	action_id     = cols[5];
842 	is_selected   = cols[6];
843 	prop_button_append_menu(button,
844 				indication_id, label, tooltip_str, action_id,
845 				is_selected);
846       }
847       g_strfreev(cols);
848     }
849   }
850   toplevel = gtk_widget_get_toplevel(widget);
851   is_hidden = (is_hidden && strcmp(display_time, "always"));
852 #if GTK_CHECK_VERSION(2, 18, 0)
853   if (gtk_widget_get_visible(toplevel) == is_hidden) {
854 #else
855   if (GTK_WIDGET_VISIBLE(toplevel) == is_hidden) {
856 #endif
857     if (is_hidden) {
858       gtk_widget_hide(toplevel);
859     } else {
860       gint x = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toplevel),
861                                                  "position_x"));
862       gint y = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toplevel),
863                                                  "position_y"));
864       gtk_window_move(GTK_WINDOW(toplevel), x, y);
865       gtk_widget_show(toplevel);
866     }
867   }
868 
869   /* create tool buttons */
870   /* FIXME! command menu and buttons should be customizable. */
871   for (i = 0; i < command_entry_len; i++) {
872     GtkWidget *tool_button;
873     GtkWidget *img;
874 
875     if (!command_entry[i].show_button)
876       continue;
877 
878     tool_button = gtk_button_new();
879 
880     set_button_style(tool_button,
881                      GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget),
882                                      OBJECT_DATA_TOOLBAR_TYPE)));
883 
884     g_object_set_data(G_OBJECT(tool_button), OBJECT_DATA_BUTTON_TYPE,
885 		      GINT_TO_POINTER(BUTTON_TOOL));
886     g_object_set_data(G_OBJECT(tool_button), OBJECT_DATA_COMMAND,
887 		      (gpointer)command_entry[i].command);
888     if (command_entry[i].icon)
889       img = gtk_image_new_from_stock(command_entry[i].icon,
890 				     GTK_ICON_SIZE_MENU);
891     else {
892       img = gtk_label_new("");
893       gtk_label_set_markup(GTK_LABEL(img), command_entry[i].label);
894     }
895     if (img)
896       gtk_container_add(GTK_CONTAINER(tool_button), img);
897     gtk_button_set_relief(GTK_BUTTON(tool_button), GTK_RELIEF_NONE);
898     gtk_size_group_add_widget(sg, tool_button);
899     g_signal_connect(G_OBJECT(tool_button), "button-press-event",
900 		     G_CALLBACK(button_pressed), widget);
901     g_signal_connect(G_OBJECT(tool_button), "clicked",
902 		     G_CALLBACK(tool_button_clicked_cb), widget);
903 
904     /* tooltip */
905     gtk_widget_set_tooltip_text(tool_button, _(command_entry[i].desc));
906 
907     append_tool_button(widget, tool_button);
908   }
909 
910   gtk_widget_show_all(widget);
911   g_free(charset);
912 }
913 
914 static void
915 helper_toolbar_check_custom()
916 {
917   guint i;
918 
919   for (i = 0; i < command_entry_len; i++)
920     command_entry[i].show_button =
921       uim_scm_symbol_value_bool(command_entry[i].custom_button_show_symbol);
922 
923   with_dark_bg =
924     uim_scm_symbol_value_bool("toolbar-icon-for-dark-background?");
925 }
926 
927 static void
928 helper_toolbar_parse_helper_str(GtkWidget *widget, gchar *str)
929 {
930   gchar **lines;
931   lines = g_strsplit(str, "\n", 0);
932 
933   if (lines && lines[0]) {
934     if (!strcmp("prop_list_update", lines[0]))
935       helper_toolbar_prop_list_update(widget, lines);
936     else if (!strcmp("custom_reload_notify", lines[0])) {
937       uim_prop_reload_configs();
938       helper_toolbar_check_custom();
939       reset_icon();
940     }
941     g_strfreev(lines);
942   }
943 }
944 
945 static gboolean
946 fd_read_cb(GIOChannel *channel, GIOCondition c, gpointer p)
947 {
948   gchar *msg;
949   int fd = g_io_channel_unix_get_fd(channel);
950   GtkWidget *widget = GTK_WIDGET(p);
951 
952   uim_helper_read_proc(fd);
953 
954   while ((msg = uim_helper_get_message())) {
955     helper_toolbar_parse_helper_str(widget, msg);
956     free(msg);
957   }
958 
959   return TRUE;
960 }
961 
962 static void
963 helper_disconnect_cb(void)
964 {
965   uim_fd = -1;
966   g_source_remove(read_tag);
967 }
968 
969 void
970 uim_toolbar_get_im_list(void)
971 {
972   uim_helper_send_message(uim_fd, "im_list_get\n");
973 }
974 
975 void
976 uim_toolbar_check_helper_connection(GtkWidget *widget)
977 {
978   if (uim_fd < 0) {
979     uim_fd = uim_helper_init_client_fd(helper_disconnect_cb);
980     if (uim_fd > 0) {
981       GIOChannel *channel;
982       channel = g_io_channel_unix_new(uim_fd);
983       read_tag = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
984 				fd_read_cb, (gpointer)widget);
985       g_io_channel_unref(channel);
986     }
987   }
988 }
989 
990 static GtkWidget *
991 right_click_menu_create(void)
992 {
993   GtkWidget *menu;
994   GtkWidget *menu_item;
995   GtkWidget *img;
996   guint i;
997 
998   menu = gtk_menu_new();
999 
1000   /* FIXME! command menu and buttons should be customizable. */
1001   for (i = 0; i < command_entry_len; i++) {
1002     menu_item = gtk_image_menu_item_new_with_label(_(command_entry[i].desc));
1003 
1004     if (command_entry[i].icon) {
1005       img = gtk_image_new_from_stock(command_entry[i].icon,
1006 		      		     GTK_ICON_SIZE_MENU);
1007       if (img)
1008 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), img);
1009     }
1010 
1011     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1012     g_signal_connect(G_OBJECT(menu_item), "activate",
1013 		     G_CALLBACK(right_click_menu_activated),
1014 		     (gpointer)command_entry[i].command);
1015   }
1016 
1017   /* Add quit item */
1018   img = gtk_image_new_from_stock(GTK_STOCK_QUIT, GTK_ICON_SIZE_MENU);
1019   menu_item = gtk_image_menu_item_new_with_label(_("Quit this toolbar"));
1020   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), img);
1021   gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1022   g_signal_connect(G_OBJECT(menu_item), "activate",
1023 		   G_CALLBACK(right_click_menu_quit_activated), NULL);
1024 
1025   gtk_widget_show_all(menu);
1026 
1027   return menu;
1028 }
1029 
1030 static gboolean
1031 is_icon_registered(const gchar *name)
1032 {
1033   GList *list;
1034 
1035   list = uim_icon_list;
1036   while (list) {
1037    if (!strcmp(list->data, name))
1038      return TRUE;
1039    list = list->next;
1040   }
1041 
1042   return FALSE;
1043 }
1044 
1045 static gboolean
1046 register_icon(const gchar *name)
1047 {
1048   GtkIconSet *icon_set;
1049   GdkPixbuf *pixbuf;
1050   GString *filename;
1051   struct stat st;
1052 
1053   g_return_val_if_fail(uim_factory, FALSE);
1054 
1055   if (is_icon_registered(name))
1056     return TRUE;
1057 
1058   filename = g_string_new(UIM_PIXMAPSDIR "/");
1059   g_string_append(filename, name);
1060   if (with_dark_bg) {
1061     g_string_append(filename, "_dark_background");
1062   }
1063   g_string_append(filename, ".png");
1064 
1065   if (with_dark_bg && stat(filename->str, &st) == -1) {
1066     g_string_free(filename, TRUE);
1067     filename = g_string_new(UIM_PIXMAPSDIR "/");
1068     g_string_append(filename, name);
1069     g_string_append(filename, ".png");
1070   }
1071 
1072   pixbuf = gdk_pixbuf_new_from_file(filename->str, NULL);
1073   if (!pixbuf) {
1074     g_string_free(filename, TRUE);
1075     return FALSE;
1076   }
1077 
1078   icon_set = gtk_icon_set_new_from_pixbuf(pixbuf);
1079   gtk_icon_factory_add(uim_factory, name, icon_set);
1080 
1081   uim_icon_list = g_list_append(uim_icon_list, g_strdup(name));
1082 
1083   g_string_free(filename, TRUE);
1084   gtk_icon_set_unref(icon_set);
1085   g_object_unref(G_OBJECT(pixbuf));
1086 
1087   return TRUE;
1088 }
1089 
1090 static void
1091 init_icon(void)
1092 {
1093   if (uim_factory)
1094     return;
1095 
1096   uim_factory = gtk_icon_factory_new();
1097   gtk_icon_factory_add_default(uim_factory);
1098 
1099   register_icon("im_switcher");
1100   register_icon("uim-icon");
1101   register_icon("uim-dict");
1102   register_icon("null");
1103 }
1104 
1105 static void
1106 reset_icon(void)
1107 {
1108   g_list_foreach(uim_icon_list, (GFunc)g_free, NULL);
1109   g_list_free(uim_icon_list);
1110   uim_icon_list = NULL;
1111 
1112   if (GTK_IS_ICON_FACTORY(uim_factory)) {
1113     gtk_icon_factory_remove_default(uim_factory);
1114     uim_factory = NULL;
1115     init_icon();
1116   }
1117 }
1118 
1119 
1120 static GtkWidget *
1121 toolbar_new(gint type)
1122 {
1123   GtkWidget *button;
1124   GtkWidget *hbox;
1125   GList *prop_buttons = NULL;
1126   GtkSizeGroup *sg;
1127 
1128   /*
1129    * Set uim-toolbar-save-default-im? #t in ~/.uim enable this if you'd like to
1130    * save default IM into ~/.uim.d/custom/custom-global.scm upon system global
1131    * IM switch.  However, using uim-custom consumes quite amount of memory, and
1132    * requires additional startup time.
1133    */
1134   if (uim_scm_symbol_value_bool("uim-toolbar-save-default-im?"))
1135     custom_enabled = (gboolean)uim_custom_enable();
1136 
1137   helper_toolbar_check_custom();
1138   init_icon();
1139 
1140   /* create widgets */
1141 #if GTK_CHECK_VERSION(3, 2, 0)
1142   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1143 #else
1144   hbox = gtk_hbox_new(FALSE, 0);
1145 #endif
1146 
1147   im_menu = gtk_menu_new();
1148   prop_menu = gtk_menu_new();
1149   right_click_menu = right_click_menu_create();
1150   sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1151 
1152   /* prop menu button */
1153   button = button_create(hbox, sg, "uim-icon", " x", type);
1154 
1155   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
1156 
1157   prop_buttons = g_list_append(prop_buttons, button);
1158 
1159   g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_PROP_BUTTONS, prop_buttons);
1160   g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_SIZE_GROUP, sg);
1161   g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_TOOLBAR_TYPE,
1162 		    GINT_TO_POINTER(type));
1163 
1164   uim_fd = -1;
1165 
1166   if (type != TYPE_ICON) {
1167     /* delay initialization until getting "embedded" signal */
1168     uim_toolbar_check_helper_connection(hbox);
1169     uim_helper_client_get_prop_list();
1170     uim_toolbar_get_im_list();
1171   }
1172 
1173   return hbox;
1174 }
1175 
1176 GtkWidget *
1177 uim_toolbar_standalone_new(void)
1178 {
1179   return toolbar_new(TYPE_STANDALONE);
1180 }
1181 
1182 GtkWidget *
1183 uim_toolbar_applet_new(void)
1184 {
1185   return toolbar_new(TYPE_APPLET);
1186 }
1187 
1188 GtkWidget *
1189 uim_toolbar_trayicon_new(void)
1190 {
1191 #if !GTK_CHECK_VERSION(2, 90, 0)
1192   gtk_rc_parse_string("\n"
1193 		      "   style \"uim-systray-button-style\"\n"
1194 		      "   {\n"
1195 		      "      GtkWidget::focus-line-width=0\n"
1196 		      "      GtkWidget::focus-padding=0\n"
1197 		      "      ythickness=0\n"
1198 		      "   }\n" "\n"
1199 		      "    widget \"*.uim-systray-button\" style \"uim-systray-button-style\"\n"
1200 		      "\n");
1201 #endif
1202 
1203   return toolbar_new(TYPE_ICON);
1204 }
1205