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 <signal.h>
21 
22 #include "hime.h"
23 
24 #include "gst.h"
25 #include "gtab.h"
26 #include "pho.h"
27 
28 #if !GTK_CHECK_VERSION(2, 90, 6)
29 extern GdkPixbuf *gdk_pixbuf_get_from_surface (cairo_surface_t *surface, gint src_x, gint src_y, gint width, gint height);
30 #endif
31 
32 extern void destroy_other_tray ();
33 
34 GtkStatusIcon *tray_icon;
35 static GdkPixbuf *pixbuf, *pixbuf_ch;
36 static PangoLayout *pango;
37 static cairo_t *cr;
38 static GtkWidget *tray_menu = NULL;
39 
40 static int iw, ih;
41 
42 static GdkColor red_color_fg;
43 static GdkColor blue_color_fg;
44 
45 #define HIME_TRAY_PNG "hime-tray.png"
46 static char pixbuf_ch_fname[512];
47 
48 void toggle_gb_output ();
49 extern gboolean gb_output;
50 
51 static char full[] = N_ ("全"), engst[] = N_ ("ABC"), sim[] = N_ ("简");
52 extern int current_shape_mode ();
53 
destroy_tray_icon()54 void destroy_tray_icon () {
55     if (tray_icon != NULL) {
56         // Workaround: to release the space on notification area
57         gtk_status_icon_set_visible (tray_icon, FALSE);
58         g_object_unref (tray_icon);
59         tray_icon = NULL;
60     }
61     if (tray_menu) {
62         gtk_widget_destroy (tray_menu);
63         tray_menu = NULL;
64     }
65     if (pixbuf) {
66         g_object_unref (pixbuf);
67         pixbuf = NULL;
68     }
69     if (pixbuf_ch) {
70         g_object_unref (pixbuf_ch);
71         pixbuf_ch = NULL;
72     }
73 }
74 
get_text_w_h(char * s,int * w,int * h)75 static void get_text_w_h (char *s, int *w, int *h) {
76     pango_layout_set_text (pango, s, strlen (s));
77     pango_layout_get_pixel_size (pango, w, h);
78 }
79 
draw_icon()80 static void draw_icon () {
81     gboolean tsin_pho_mode ();
82 
83     if (!tray_icon)
84         return;
85 
86     GdkPixbuf *pix = ((!current_CS) ||
87                       (current_CS->im_state != HIME_STATE_CHINESE))
88                          ? pixbuf
89                          : pixbuf_ch;
90 
91     int w = 0, h = 0;
92     iw = gtk_status_icon_get_size (tray_icon), ih = gtk_status_icon_get_size (tray_icon);
93 
94     cairo_surface_t *cst = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, iw, ih);
95     cr = cairo_create (cst);
96     gdk_cairo_set_source_color (cr, &red_color_fg);
97 
98     if (pix) {
99         gdk_cairo_set_source_pixbuf (cr, pix, 0, 0);
100         cairo_paint (cr);
101     } else {
102         get_text_w_h (inmd[current_CS->in_method].cname, &w, &h);
103         cairo_move_to (cr, 0, 0);
104         pango_cairo_show_layout (cr, pango);
105     }
106 
107     if (current_CS) {
108         gdk_cairo_set_source_color (cr, &red_color_fg);
109         if (current_shape_mode ()) {
110             get_text_w_h (full, &w, &h);
111             cairo_move_to (cr, iw - w, ih - h);
112             pango_cairo_show_layout (cr, pango);
113         }
114         if (current_CS->im_state == HIME_STATE_CHINESE && !tsin_pho_mode ()) {
115             gdk_cairo_set_source_color (cr, &blue_color_fg);
116             get_text_w_h (engst, &w, &h);
117             cairo_move_to (cr, 0, 0);
118             pango_cairo_show_layout (cr, pango);
119         }
120     }
121 
122     if (gb_output) {
123         gdk_cairo_set_source_color (cr, &red_color_fg);
124         get_text_w_h (sim, &w, &h);
125         cairo_move_to (cr, 0, ih - h);
126         pango_cairo_show_layout (cr, pango);
127     }
128     cairo_destroy (cr);
129     cr = NULL;
130     GdkPixbuf *icon_pixbuf_output = gdk_pixbuf_get_from_surface (cst, 0, 0, iw, ih);
131     cairo_surface_destroy (cst);
132     cst = NULL;
133     gtk_status_icon_set_from_pixbuf (tray_icon, icon_pixbuf_output);
134     g_object_unref (icon_pixbuf_output);
135     icon_pixbuf_output = NULL;
136     pix = NULL;
137 }
138 
139 void get_icon_path (char *iconame, char fname[]);
140 gboolean create_tray (gpointer data);
141 
load_tray_icon()142 void load_tray_icon () {
143     if (!hime_status_tray)
144         return;
145     if (!tray_icon) {
146         create_tray (NULL);
147         return;
148     }
149     // wrong width & height if it is not embedded-ready
150     if (!gtk_status_icon_is_embedded (tray_icon))
151         return;
152     iw = gtk_status_icon_get_size (tray_icon), ih = gtk_status_icon_get_size (tray_icon);
153     if (!pixbuf) {
154         char icon_fname[128];
155         get_icon_path (HIME_TRAY_PNG, icon_fname);
156         pixbuf = gdk_pixbuf_new_from_file_at_size (icon_fname, iw, ih, NULL);
157     }
158     char *iconame = HIME_TRAY_PNG;
159     //  if (current_CS && current_CS->in_method && inmd)
160     // Workaround due to issue #161
161     if (current_CS && current_CS->im_state != HIME_STATE_DISABLED && current_CS->im_state != HIME_STATE_ENG_FULL)
162         iconame = inmd[current_CS->in_method].icon;
163     char fname[512];
164     if (iconame)
165         get_icon_path (iconame, fname);
166     if (strcmp (pixbuf_ch_fname, fname) && pixbuf_ch) {
167         g_object_unref (pixbuf_ch);
168         pixbuf_ch = NULL;
169     }
170     if (!pixbuf_ch) {
171         strcpy (pixbuf_ch_fname, fname);
172         pixbuf_ch = gdk_pixbuf_new_from_file_at_size (fname, iw, ih, NULL);
173     }
174     draw_icon ();
175     iconame = NULL;
176 }
177 
178 void exec_hime_setup_ (GtkCheckMenuItem *checkmenuitem, gpointer dat);
179 
180 void cb_trad_sim_toggle ();
181 void cb_trad_sim_toggle_ (GtkCheckMenuItem *checkmenuitem, gpointer dat);
182 
183 void cb_sim2trad (GtkCheckMenuItem *checkmenuitem, gpointer dat);
184 void cb_trad2sim (GtkCheckMenuItem *checkmenuitem, gpointer dat);
185 
186 void quit_hime (GtkCheckMenuItem *checkmenuitem, gpointer dat);
187 
188 void cb_tog_phospeak (GtkCheckMenuItem *checkmenuitem, gpointer dat);
189 
190 void kbm_toggle_ (GtkCheckMenuItem *checkmenuitem, gpointer dat);
191 extern gboolean win_kbm_on;
192 
193 void cb_inmd_menu (GtkCheckMenuItem *checkmenuitem, gpointer dat);
194 
195 #include "mitem.h"
196 
197 static MITEM mitems[] = {
198     {N_ ("Configuration"), GTK_STOCK_PREFERENCES, exec_hime_setup_, NULL},
199     {N_ ("Exit"), GTK_STOCK_QUIT, quit_hime, NULL},
200     {N_ ("Text-to-speech"), NULL, cb_tog_phospeak, &phonetic_speak},
201     {N_ ("Trad. to Simp. conversion tool"), NULL, cb_trad2sim, NULL},
202     {N_ ("Simp. to Trad. conversion tool"), NULL, cb_sim2trad, NULL},
203     {N_ ("Select input methods"), NULL, cb_inmd_menu, NULL},
204     {N_ ("Virtual keyboard"), NULL, kbm_toggle_, &hime_show_win_kbm},
205     {N_ ("Simplified Chinese output"), NULL, cb_trad_sim_toggle_, &gb_output},
206     {NULL, NULL, NULL, NULL}};
207 
208 GtkWidget *create_tray_menu (MITEM *mitems);
209 void update_item_active_all ();
210 
211 gint inmd_switch_popup_handler (GtkWidget *widget, GdkEvent *event);
212 
reload_tray_icon()213 void reload_tray_icon () {
214     if (pixbuf) {
215         g_object_unref (pixbuf);
216         pixbuf = NULL;
217     }
218     if (pixbuf_ch) {
219         g_object_unref (pixbuf_ch);
220         pixbuf_ch = NULL;
221     }
222     load_tray_icon ();
223 }
224 
tray_size_changed_cb(GtkStatusIcon * status_icon,gint * size,gpointer user_data)225 gboolean tray_size_changed_cb (GtkStatusIcon *status_icon, gint *size, gpointer user_data) {
226     reload_tray_icon ();
227     return FALSE;
228 }
229 
tray_embedded_cb(GtkStatusIcon * status_icon,GParamSpec * pspec,gpointer user_data)230 gboolean tray_embedded_cb (GtkStatusIcon *status_icon, GParamSpec *pspec, gpointer user_data) {
231     if (gtk_status_icon_is_embedded (tray_icon)) {
232         reload_tray_icon ();
233     }
234     return TRUE;
235 }
236 
237 void toggle_im_enabled (), kbm_toggle ();
tray_button_press_event_cb(GtkStatusIcon * status_icon,GdkEventButton * event,gpointer userdata)238 gboolean tray_button_press_event_cb (GtkStatusIcon *status_icon, GdkEventButton *event, gpointer userdata) {
239     switch (event->button) {
240     case 1:
241         if (event->state & GDK_SHIFT_MASK)
242             inmd_switch_popup_handler (NULL, (GdkEvent *) event);
243         else
244             toggle_im_enabled ();
245         break;
246     case 2:
247 #if 0
248       inmd_switch_popup_handler(NULL, (GdkEvent *)event);
249 #else
250         kbm_toggle ();
251         dbg ("win_kbm_on %d\n", win_kbm_on);
252         update_item_active_all ();
253 #endif
254         break;
255     case 3:
256         if (!tray_menu)
257             tray_menu = create_tray_menu (mitems);
258         gtk_menu_popup (GTK_MENU (tray_menu), NULL, NULL, gtk_status_icon_position_menu, tray_icon, event->button, event->time);
259         break;
260     }
261 
262     return TRUE;
263 }
264 
265 void update_item_active (MITEM *mitems);
266 
update_item_active_single()267 void update_item_active_single () {
268     update_item_active (mitems);
269 }
270 
create_tray(gpointer data)271 gboolean create_tray (gpointer data) {
272     if (tray_icon)
273         return FALSE;
274 
275     destroy_other_tray ();
276 
277     tray_icon = gtk_status_icon_new ();
278 
279     g_signal_connect (G_OBJECT (tray_icon), "button-press-event",
280                       G_CALLBACK (tray_button_press_event_cb), NULL);
281 
282     g_signal_connect (G_OBJECT (tray_icon), "size-changed",
283                       G_CALLBACK (tray_size_changed_cb), NULL);
284 
285     g_signal_connect (G_OBJECT (tray_icon), "notify::embedded",
286                       G_CALLBACK (tray_embedded_cb), NULL);
287 
288 #if GTK_CHECK_VERSION(2, 12, 0)
289     gtk_status_icon_set_tooltip_text (tray_icon, _ ("Left:Toggle alphabet-numeric mode  Middle:Virtual Keyboard  Right:Preferences"));
290 #else
291     GtkTooltips *tips = gtk_tooltips_new ();
292     gtk_status_icon_set_tooltip (GTK_TOOLTIPS (tips), tray_icon, _ ("Left:Toggle alphabet-numeric mode  Middle:Virtual Keyboard  Right:Preferences"), NULL);
293 #endif
294 
295     // Initiate Pango for drawing texts from default setting
296     GtkWidget *wi = gtk_label_new (NULL);  // for reference
297     PangoContext *context = gtk_widget_get_pango_context (wi);
298     PangoFontDescription *desc = pango_font_description_copy (pango_context_get_font_description (context));  // Copy one from wi for pango
299     pango_font_description_set_size (desc, 9 * PANGO_SCALE);
300     pango = gtk_widget_create_pango_layout (wi, NULL);
301     pango_layout_set_font_description (pango, desc);
302 
303     gdk_color_parse ("red", &red_color_fg);
304     gdk_color_parse ("blue", &blue_color_fg);
305 
306     gtk_widget_destroy (wi);
307 
308     load_tray_icon ();
309     return FALSE;
310 }
311 
is_exist_tray()312 gboolean is_exist_tray () {
313     return tray_icon != NULL;
314 }
315 
init_tray()316 void init_tray () {
317     g_timeout_add (200, create_tray, NULL);  // Old setting is 5000 here.
318 }
319