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