load_menu: return number of visible items and hide empty submenus
[lxde/lxpanel.git] / src / plugins / xkb / xkb-plugin.c
CommitLineData
22242ed4
HJYP
1/*
2//====================================================================
3// xfce4-xkb-plugin - XFCE4 Xkb Layout Indicator panel plugin
4// -------------------------------------------------------------------
5// Alexander Iliev <sasoiliev@mamul.org>
6// 20-Feb-04
7// -------------------------------------------------------------------
8// Parts of this code belong to Michael Glickman <wmalms@yahooo.com>
9// and his program wmxkb.
10// WARNING: DO NOT BOTHER Michael Glickman WITH QUESTIONS ABOUT THIS
11// PROGRAM!!! SEND INSTEAD EMAILS TO <sasoiliev@mamul.org>
12//====================================================================
13*/
14
15/* Modified by Hong Jen Yee (PCMan) <pcman.tw@gmail.com> on 2008-04-06 for lxpanel */
16
17#ifdef HAVE_CONFIG_H
18#include <config.h>
19#endif
20
21#include <glib/gi18n.h>
22
23#include <stdio.h>
45bd719e 24#include <stdlib.h>
22242ed4
HJYP
25#include <ctype.h>
26
27#include "xkb.h"
28
18ecfe2a 29void panel_config_save(Panel * panel); /* defined in configurator.c */
30
d862b520 31static void xkb_active_window_event(FbEv * ev, gpointer data);
32static gboolean xkb_scroll_event(GtkWidget * widget, GdkEventScroll * event, gpointer data);
33static gboolean xkb_button_press_event(GtkWidget * widget, GdkEventButton * event, gpointer data);
34static int xkb_constructor(Plugin * plugin, char ** fp);
35static void xkb_destructor(Plugin * plugin);
36static void xkb_display_type_changed(GtkComboBox * cb, gpointer * data);
37static void xkb_enable_per_application_changed(GtkToggleButton * tb, gpointer * data);
38static void xkb_default_language_changed(GtkComboBox * cb, gpointer * data);
39static void xkb_configuration_response(GtkDialog * dialog, gint arg1, gpointer data);
40static void xkb_configure(Plugin * p, GtkWindow * parent);
41static void xkb_save_configuration(Plugin * p, FILE * fp);
42static void xkb_panel_configuration_changed(Plugin * p);
43
44/* Redraw the graphics. */
45void xkb_redraw(XkbPlugin * xkb)
22242ed4 46{
d862b520 47 /* Set the image. */
48 gboolean valid_image = FALSE;
49 if (xkb->display_type == IMAGE)
22242ed4 50 {
d862b520 51 int size = xkb->plugin->panel->icon_size;
52 char * group_name = (char *) xkb_get_current_group_name_lowercase(xkb);
53 char * filename = g_strdup_printf("%s/%s.png", FLAGSDIR, group_name);
54 GdkPixbuf * unscaled_pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
55 g_free(filename);
56 g_free(group_name);
57
58 if (unscaled_pixbuf != NULL)
59 {
60 /* Loaded successfully. */
61 int width = gdk_pixbuf_get_width(unscaled_pixbuf);
62 int height = gdk_pixbuf_get_height(unscaled_pixbuf);
63 GdkPixbuf * pixbuf = gdk_pixbuf_scale_simple(unscaled_pixbuf, size * width / height, size, GDK_INTERP_BILINEAR);
64 if (pixbuf != NULL)
65 {
66 gtk_image_set_from_pixbuf(GTK_IMAGE(xkb->image), pixbuf);
67 g_object_unref(G_OBJECT(pixbuf));
68 gtk_widget_hide(xkb->label);
69 gtk_widget_show(xkb->image);
70 gtk_widget_set_tooltip_text(xkb->btn, xkb_get_current_group_name(xkb));
71 valid_image = TRUE;
22242ed4 72 }
d862b520 73 g_object_unref(unscaled_pixbuf);
22242ed4
HJYP
74 }
75 }
76
d862b520 77 /* Set the label. */
78 if ((xkb->display_type == TEXT) || ( ! valid_image))
79 {
6835a23e 80 panel_draw_label_text(xkb->plugin->panel, xkb->label, (char *) xkb_get_current_group_name(xkb), TRUE, TRUE);
d862b520 81 gtk_widget_hide(xkb->image);
82 gtk_widget_show(xkb->label);
83 gtk_widget_set_tooltip_text(xkb->btn, NULL);
84 }
22242ed4
HJYP
85}
86
d862b520 87/* Handler for "active_window" event on root window listener. */
88static void xkb_active_window_event(FbEv * ev, gpointer data)
22242ed4 89{
d862b520 90 XkbPlugin * xkb = (XkbPlugin *) data;
91 if (xkb->enable_perapp)
92 {
93 Window * win = fb_ev_active_window(ev);
94 if (*win != None)
95 {
96 xkb_active_window_changed(xkb, get_net_wm_pid(*win));
97 xkb_redraw(xkb);
98 }
99 }
22242ed4
HJYP
100}
101
d862b520 102/* Handler for "scroll-event" on drawing area. */
103static gboolean xkb_scroll_event(GtkWidget * widget, GdkEventScroll * event, gpointer data)
22242ed4 104{
d862b520 105 XkbPlugin * xkb = (XkbPlugin *) data;
22242ed4 106
d862b520 107 /* Change to next or previous group. */
108 xkb_change_group(xkb,
109 (((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_RIGHT)) ? 1 : -1));
110 return TRUE;
22242ed4
HJYP
111}
112
d862b520 113/* Handler for button-press-event on top level widget. */
114static gboolean xkb_button_press_event(GtkWidget * widget, GdkEventButton * event, gpointer data)
22242ed4 115{
d862b520 116 XkbPlugin * xkb = (XkbPlugin *) data;
22242ed4 117
d862b520 118 /* Standard right-click handling. */
119 if (plugin_button_press_event(widget, event, xkb->plugin))
120 return TRUE;
22242ed4 121
d862b520 122 /* Change to next group. */
123 xkb_change_group(xkb, 1);
18ecfe2a 124 return TRUE;
22242ed4
HJYP
125}
126
d862b520 127/* Plugin constructor. */
128static int xkb_constructor(Plugin * plugin, char ** fp)
22242ed4 129{
d862b520 130 /* Allocate plugin context and set into Plugin private data pointer. */
131 XkbPlugin * xkb = g_new0(XkbPlugin, 1);
132 xkb->plugin = plugin;
133 plugin->priv = xkb;
134
135 /* Initialize to defaults. */
136 xkb->display_type = IMAGE;
137 xkb->enable_perapp = TRUE;
138 xkb->default_group = 0;
139
140 /* Load parameters from the configuration file. */
141 line s;
142 s.len = 256;
143 if (fp != NULL)
144 {
145 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
146 {
147 if (s.type == LINE_NONE)
148 {
149 ERR( "xkb: illegal token %s\n", s.str);
150 return 0;
151 }
152 if (s.type == LINE_VAR)
153 {
154 if (g_ascii_strcasecmp(s.t[0], "DisplayType") == 0)
155 xkb->display_type = atoi(s.t[1]);
156 else if (g_ascii_strcasecmp(s.t[0], "PerAppLayout") == 0)
157 xkb->enable_perapp = str2num(bool_pair, s.t[1], 0);
158 else if (g_ascii_strcasecmp(s.t[0], "DefaultGroup") == 0)
159 xkb->default_group = atoi(s.t[1]);
160 else
161 ERR( "xkb: unknown var %s\n", s.t[0]);
162 }
163 else
164 {
165 ERR( "xkb: illegal in this context %s\n", s.str);
166 return 0;
167 }
168 }
169 }
2918994e 170
d862b520 171 /* Allocate top level widget and set into Plugin widget pointer. */
172 plugin->pwid = gtk_event_box_new();
173 gtk_widget_add_events(plugin->pwid, GDK_BUTTON_PRESS_MASK);
174
175 /* Create a button as the child of the event box. */
176 xkb->btn = gtk_button_new();
177 gtk_container_add(GTK_CONTAINER(plugin->pwid), xkb->btn);
178 gtk_button_set_relief(GTK_BUTTON(xkb->btn), GTK_RELIEF_NONE);
179 GTK_WIDGET_UNSET_FLAGS(xkb->btn, GTK_CAN_FOCUS);
180 GTK_WIDGET_UNSET_FLAGS(xkb->btn, GTK_CAN_DEFAULT);
181 gtk_widget_show(xkb->btn);
182
183 /* Create a horizontal box as the child of the button. */
184 GtkWidget * hbox = gtk_hbox_new(FALSE, 0);
185 gtk_container_add(GTK_CONTAINER(xkb->btn), hbox);
186 gtk_widget_show(hbox);
187
188 /* Create a label and an image as children of the horizontal box.
189 * Only one of these is visible at a time, controlled by user preference
190 * and the successful loading of the image. */
191 xkb->label = gtk_label_new("");
192 gtk_container_add(GTK_CONTAINER(hbox), xkb->label);
193 xkb->image = gtk_image_new();
194 gtk_container_add(GTK_CONTAINER(hbox), xkb->image);
195
196 /* Initialize the XKB interface. */
197 xkb_mechanism_constructor(xkb);
198
199 /* Initialize a channel to listen for XKB events. */
200 GIOChannel * channel = g_io_channel_unix_new(xkb_get_connection_number(xkb));
201 xkb->source_id = g_io_add_watch(channel, G_IO_IN | G_IO_PRI, (GIOFunc) xkb_gio_callback, (gpointer) xkb);
202
203 /* Connect signals. */
204 g_signal_connect(xkb->btn, "button-press-event", G_CALLBACK(xkb_button_press_event), xkb);
205 g_signal_connect(xkb->btn, "scroll-event", G_CALLBACK(xkb_scroll_event), xkb);
206 g_signal_connect(G_OBJECT(fbev), "active_window", G_CALLBACK(xkb_active_window_event), xkb);
207
208 /* Show the widget and return. */
209 xkb_redraw(xkb);
210 gtk_widget_show(plugin->pwid);
211 return 1;
22242ed4
HJYP
212}
213
d862b520 214/* Plugin destructor. */
215static void xkb_destructor(Plugin * plugin)
22242ed4 216{
d862b520 217 XkbPlugin * xkb = (XkbPlugin *) plugin->priv;
22242ed4 218
d862b520 219 /* Disconnect root window event handler. */
220 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), xkb_active_window_event, xkb);
22242ed4 221
d862b520 222 /* Disconnect from the XKB mechanism. */
223 g_source_remove(xkb->source_id);
224 xkb_mechanism_destructor(xkb);
22242ed4 225
d862b520 226 /* Ensure that the configuration dialog is dismissed. */
227 if (xkb->config_dlg != NULL)
228 gtk_widget_destroy(xkb->config_dlg);
22242ed4 229
d862b520 230 /* Deallocate all memory. */
231 g_free(xkb);
22242ed4
HJYP
232}
233
d862b520 234/* Handler for "changed" event on default language combo box of configuration dialog. */
235static void xkb_display_type_changed(GtkComboBox * cb, gpointer * data)
22242ed4 236{
d862b520 237 /* Fetch the new value and redraw. */
238 XkbPlugin * xkb = (XkbPlugin *) data;
239 xkb->display_type = gtk_combo_box_get_active(cb);
240 xkb_redraw(xkb);
22242ed4
HJYP
241}
242
d862b520 243/* Handler for "toggled" event on per-application check box of configuration dialog. */
244static void xkb_enable_per_application_changed(GtkToggleButton * tb, gpointer * data)
22242ed4 245{
d862b520 246 /* Fetch the new value and redraw. */
247 XkbPlugin * xkb = (XkbPlugin *) data;
248 xkb->enable_perapp = gtk_toggle_button_get_active(tb);
249 gtk_widget_set_sensitive(xkb->per_app_default_layout_menu, xkb->enable_perapp);
250 xkb_redraw(xkb);
22242ed4
HJYP
251}
252
d862b520 253/* Handler for "changed" event on default language combo box of configuration dialog. */
254static void xkb_default_language_changed(GtkComboBox * cb, gpointer * data)
22242ed4 255{
d862b520 256 /* Fetch the new value and redraw. */
257 XkbPlugin * xkb = (XkbPlugin *) data;
258 xkb->default_group = gtk_combo_box_get_active(cb);
259 xkb_redraw(xkb);
22242ed4
HJYP
260}
261
d862b520 262/* Handler for "response" event on configuration dialog. */
263static void xkb_configuration_response(GtkDialog * dialog, int response, gpointer data)
22242ed4 264{
d862b520 265 XkbPlugin * xkb = (XkbPlugin *) data;
22242ed4 266
d862b520 267 /* Save the new configuration and redraw the plugin. */
268 panel_config_save(xkb->plugin->panel);
269 xkb_redraw(xkb);
22242ed4 270
d862b520 271 /* Destroy the dialog. */
272 gtk_widget_destroy(xkb->config_dlg);
273 xkb->config_dlg = NULL;
22242ed4
HJYP
274}
275
d862b520 276/* Callback when the configuration dialog is to be shown. */
277static void xkb_configure(Plugin * p, GtkWindow * parent)
22242ed4 278{
d862b520 279 XkbPlugin * xkb = (XkbPlugin *) p->priv;
280
281 /* Create dialog window. */
282 GtkWidget * dlg = gtk_dialog_new_with_buttons(
283 _("Configure Keyboard Layout Switcher"),
284 NULL,
285 GTK_DIALOG_NO_SEPARATOR,
286 GTK_STOCK_CLOSE,
287 GTK_RESPONSE_OK,
288 NULL);
289 xkb->config_dlg = dlg;
290 panel_apply_icon(GTK_WINDOW(dlg));
291
292 /* Create a vertical box as the child of the dialog. */
293 GtkWidget * vbox = gtk_vbox_new(FALSE, 2);
294 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dlg)->vbox), vbox);
295
296 /* Create a frame as the child of the vertical box. */
297 GtkWidget * display_type_frame = gtk_frame_new(NULL);
298 gtk_frame_set_label(GTK_FRAME(display_type_frame), _("Show layout as"));
299 gtk_box_pack_start(GTK_BOX(vbox), display_type_frame, TRUE, TRUE, 2);
300 gtk_container_set_border_width(GTK_CONTAINER(display_type_frame), 5);
301
302 /* Create an alignment as the child of the frame. */
303 GtkWidget * alignment2 = gtk_alignment_new(0.5, 0.5, 1, 1);
304 gtk_container_add(GTK_CONTAINER(display_type_frame), alignment2);
305 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment2), 4, 4, 10, 10);
22242ed4 306
d862b520 307 /* Create a horizontal box as the child of the alignment. */
308 GtkWidget * hbox = gtk_hbox_new(FALSE, 2);
309 gtk_container_add(GTK_CONTAINER(alignment2), hbox);
310
311 /* Create a combo box as the child of the horizontal box. */
312 GtkWidget * display_type_optmenu = gtk_combo_box_new_text();
313 gtk_combo_box_append_text(GTK_COMBO_BOX(display_type_optmenu), _("image"));
314 gtk_combo_box_append_text(GTK_COMBO_BOX(display_type_optmenu), _("text"));
315 gtk_box_pack_start(GTK_BOX(hbox), display_type_optmenu, TRUE, TRUE, 2);
316 g_signal_connect(display_type_optmenu, "changed", G_CALLBACK(xkb_display_type_changed), xkb);
317 gtk_combo_box_set_active(GTK_COMBO_BOX(display_type_optmenu), xkb->display_type);
318
319 /* Create a frame as the child of the vertical box. */
320 GtkWidget * per_app_frame = gtk_frame_new(NULL);
321 gtk_frame_set_label(GTK_FRAME(per_app_frame), _("Per application settings"));
322 gtk_widget_show(per_app_frame);
323 gtk_box_pack_start(GTK_BOX(vbox), per_app_frame, TRUE, TRUE, 2);
324 gtk_container_set_border_width(GTK_CONTAINER(per_app_frame), 5);
325
326 /* Create an alignment as the child of the frame. */
327 GtkWidget * alignment1 = gtk_alignment_new(0.5, 0.5, 1, 1);
328 gtk_container_add(GTK_CONTAINER(per_app_frame), alignment1);
329 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment1), 4, 4, 10, 10);
330
331 /* Create a vertical box as the child of the alignment. */
332 GtkWidget * per_app_vbox = gtk_vbox_new(FALSE, 2);
333 gtk_container_add(GTK_CONTAINER(alignment1), per_app_vbox);
334
335 /* Create a check button as the child of the vertical box. */
336 GtkWidget * per_app_checkbutton = gtk_check_button_new_with_mnemonic(_("_Remember layout for each application"));
337 gtk_box_pack_start(GTK_BOX(per_app_vbox), per_app_checkbutton, FALSE, FALSE, 2);
338 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(per_app_checkbutton), xkb->enable_perapp);
339 g_signal_connect(per_app_checkbutton, "toggled", G_CALLBACK(xkb_enable_per_application_changed), xkb);
340
341 /* Create a horizontal box as the child of the vertical box. */
342 GtkWidget * hbox3 = gtk_hbox_new(FALSE, 2);
343 gtk_box_pack_start(GTK_BOX(per_app_vbox), hbox3, TRUE, TRUE, 2);
344
345 /* Create a label as the child of the horizontal box. */
346 GtkWidget * label4 = gtk_label_new(_("Default layout:"));
347 gtk_box_pack_start(GTK_BOX(hbox3), label4, FALSE, FALSE, 2);
348
349 /* Create a combo box as the child of the horizontal box. */
350 xkb->per_app_default_layout_menu = gtk_combo_box_new_text();
351 gtk_box_pack_start(GTK_BOX(hbox3), xkb->per_app_default_layout_menu, FALSE, TRUE, 2);
352 gtk_widget_set_sensitive(xkb->per_app_default_layout_menu, xkb->enable_perapp);
353
354 /* Populate the combo box with the available choices. */
355 int i;
356 for (i = 0; i < xkb_get_group_count(xkb); i++)
357 {
358 gtk_combo_box_append_text(
359 GTK_COMBO_BOX(xkb->per_app_default_layout_menu),
360 xkb_get_symbol_name_by_res_no(xkb, i));
361 }
362 gtk_combo_box_set_active(GTK_COMBO_BOX(xkb->per_app_default_layout_menu), xkb->default_group);
363 g_signal_connect(xkb->per_app_default_layout_menu, "changed", G_CALLBACK(xkb_default_language_changed), xkb);
22242ed4 364
d862b520 365 /* Connect signals. */
366 g_signal_connect(xkb->config_dlg, "response", G_CALLBACK(xkb_configuration_response), xkb);
22242ed4 367
d862b520 368 /* Display the dialog. */
369 gtk_widget_set_size_request(GTK_WIDGET(xkb->config_dlg), 400, -1); /* Improve geometry */
370 gtk_widget_show_all(xkb->config_dlg);
371 gtk_window_present(GTK_WINDOW(xkb->config_dlg));
22242ed4
HJYP
372}
373
8f9e6256 374/* Callback when the configuration is to be saved. */
375static void xkb_save_configuration(Plugin * p, FILE * fp)
376{
d862b520 377 XkbPlugin * xkb = (XkbPlugin *) p->priv;
8f9e6256 378 lxpanel_put_int(fp, "DisplayType", xkb->display_type);
379 lxpanel_put_int(fp, "PerAppLayout", xkb->enable_perapp);
380 lxpanel_put_int(fp, "DefaultGroup", xkb->default_group);
381}
382
383/* Callback when panel configuration changes. */
384static void xkb_panel_configuration_changed(Plugin * p)
385{
386 /* Do a full redraw. */
d862b520 387 XkbPlugin * xkb = (XkbPlugin *) p->priv;
388 xkb_redraw(xkb);
8f9e6256 389}
22242ed4 390
d862b520 391/* Plugin descriptor. */
22242ed4 392PluginClass xkb_plugin_class = {
2918994e 393
394 PLUGINCLASS_VERSIONING,
22242ed4
HJYP
395
396 type : "xkb",
2918994e 397 name : N_("Keyboard Layout Switcher"),
22242ed4
HJYP
398 version: "1.0",
399 description : N_("Switch between available keyboard layouts"),
400
401 constructor : xkb_constructor,
402 destructor : xkb_destructor,
8f9e6256 403 config : xkb_configure,
404 save : xkb_save_configuration,
405 panel_configuration_changed : xkb_panel_configuration_changed
d862b520 406
22242ed4 407};