1 /* -*- mode: c; style: linux -*- */
2
3 /* mate-keyboard-properties-xkbot.c
4 * Copyright (C) 2003-2007 Sergey V. Udaltsov
5 *
6 * Written by: Sergey V. Udaltsov <svu@gnome.org>
7 * John Spray <spray_john@users.sourceforge.net>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2, or (at your option)
12 * any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28
29 #include <glib/gi18n.h>
30 #include <string.h>
31 #include <gio/gio.h>
32
33 #include "capplet-util.h"
34
35 #include "mate-keyboard-properties-xkb.h"
36
37 static GtkBuilder *chooser_dialog = NULL;
38 static const char *current1st_level_id = NULL;
39 static GSList *option_checks_list = NULL;
40 static GtkWidget *current_none_radio = NULL;
41 static GtkWidget *current_expander = NULL;
42 static gboolean current_multi_select = FALSE;
43 static GSList *current_radio_group = NULL;
44
45 #define OPTION_ID_PROP "optionID"
46 #define SELCOUNTER_PROP "selectionCounter"
47 #define EXPANDERS_PROP "expandersList"
48
49 GSList *
xkb_options_get_selected_list(void)50 xkb_options_get_selected_list (void)
51 {
52 gchar **array;
53 GSList *retval = NULL;
54 gint i;
55 array = g_settings_get_strv (xkb_kbd_settings, "options");
56 if (array != NULL) {
57 for (i = 0; array[i]; i++) {
58 retval = g_slist_append (retval, g_strdup (array[i]));
59 }
60 }
61 g_strfreev (array);
62
63 if (retval == NULL) {
64 if (initial_config.options != NULL) {
65 for (i = 0; initial_config.options[i] != NULL; i++)
66 retval =
67 g_slist_prepend (retval, g_strdup (initial_config.options[i]));
68 }
69 retval = g_slist_reverse (retval);
70 }
71
72 return retval;
73 }
74
75 /* Returns the selection counter of the expander (static current_expander) */
76 static int
xkb_options_expander_selcounter_get(void)77 xkb_options_expander_selcounter_get (void)
78 {
79 return
80 GPOINTER_TO_INT (g_object_get_data
81 (G_OBJECT (current_expander),
82 SELCOUNTER_PROP));
83 }
84
85 /* Increments the selection counter in the expander (static current_expander)
86 using the value (can be 0)*/
87 static void
xkb_options_expander_selcounter_add(int value)88 xkb_options_expander_selcounter_add (int value)
89 {
90 g_object_set_data (G_OBJECT (current_expander), SELCOUNTER_PROP,
91 GINT_TO_POINTER
92 (xkb_options_expander_selcounter_get ()
93 + value));
94 }
95
96 /* Resets the seletion counter in the expander (static current_expander) */
97 static void
xkb_options_expander_selcounter_reset(void)98 xkb_options_expander_selcounter_reset (void)
99 {
100 g_object_set_data (G_OBJECT (current_expander), SELCOUNTER_PROP,
101 GINT_TO_POINTER (0));
102 }
103
104 /* Formats the expander (static current_expander), based on the selection counter */
105 static void
xkb_options_expander_highlight(void)106 xkb_options_expander_highlight (void)
107 {
108 char *utf_group_name =
109 g_object_get_data (G_OBJECT (current_expander),
110 "utfGroupName");
111 int counter = xkb_options_expander_selcounter_get ();
112 if (utf_group_name != NULL) {
113 gchar *titlemarkup =
114 g_strconcat (counter >
115 0 ? "<span weight=\"bold\">" : "<span>",
116 utf_group_name, "</span>", NULL);
117 gtk_expander_set_label (GTK_EXPANDER (current_expander),
118 titlemarkup);
119 g_free (titlemarkup);
120 }
121 }
122
123 /* Add optionname from the backend's selection list if it's not
124 already in there. */
125 static void
xkb_options_select(gchar * optionname)126 xkb_options_select (gchar * optionname)
127 {
128 gboolean already_selected = FALSE;
129 GSList *options_list = xkb_options_get_selected_list ();
130 GSList *option;
131 for (option = options_list; option != NULL; option = option->next)
132 if (!strcmp ((gchar *) option->data, optionname))
133 already_selected = TRUE;
134
135 if (!already_selected)
136 options_list =
137 g_slist_append (options_list, g_strdup (optionname));
138 xkb_options_set_selected_list (options_list);
139
140 clear_xkb_elements_list (options_list);
141 }
142
143 /* Remove all occurences of optionname from the backend's selection list */
144 static void
xkb_options_deselect(gchar * optionname)145 xkb_options_deselect (gchar * optionname)
146 {
147 GSList *options_list = xkb_options_get_selected_list ();
148 GSList *nodetmp;
149 GSList *option = options_list;
150 while (option != NULL) {
151 gchar *id = (char *) option->data;
152 if (!strcmp (id, optionname)) {
153 nodetmp = option->next;
154 g_free (id);
155 options_list =
156 g_slist_remove_link (options_list, option);
157 g_slist_free_1 (option);
158 option = nodetmp;
159 } else
160 option = option->next;
161 }
162 xkb_options_set_selected_list (options_list);
163 clear_xkb_elements_list (options_list);
164 }
165
166 /* Return true if optionname describes a string already in the backend's
167 list of selected options */
168 static gboolean
xkb_options_is_selected(gchar * optionname)169 xkb_options_is_selected (gchar * optionname)
170 {
171 gboolean retval = FALSE;
172 GSList *options_list = xkb_options_get_selected_list ();
173 GSList *option;
174 for (option = options_list; option != NULL; option = option->next) {
175 if (!strcmp ((gchar *) option->data, optionname))
176 retval = TRUE;
177 }
178 clear_xkb_elements_list (options_list);
179 return retval;
180 }
181
182 /* Make sure selected options stay visible when navigating with the keyboard */
183 static gboolean
option_focused_cb(GtkWidget * widget,GdkEventFocus * event,gpointer data)184 option_focused_cb (GtkWidget * widget, GdkEventFocus * event,
185 gpointer data)
186 {
187 GtkScrolledWindow *win = GTK_SCROLLED_WINDOW (data);
188 GtkAllocation alloc;
189 GtkAdjustment *adj;
190
191 gtk_widget_get_allocation (widget, &alloc);
192 adj = gtk_scrolled_window_get_vadjustment (win);
193 gtk_adjustment_clamp_page (adj, alloc.y,
194 alloc.y + alloc.height);
195
196 return FALSE;
197 }
198
199 /* Update xkb backend to reflect the new UI state */
200 static void
option_toggled_cb(GtkWidget * checkbutton,gpointer data)201 option_toggled_cb (GtkWidget * checkbutton, gpointer data)
202 {
203 gpointer optionID =
204 g_object_get_data (G_OBJECT (checkbutton), OPTION_ID_PROP);
205 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbutton)))
206 xkb_options_select (optionID);
207 else
208 xkb_options_deselect (optionID);
209 }
210
211 /* Add a check_button or radio_button to control a particular option
212 This function makes particular use of the current... variables at
213 the top of this file. */
214 static void
xkb_options_add_option(XklConfigRegistry * config_registry,XklConfigItem * config_item,GtkBuilder * dialog)215 xkb_options_add_option (XklConfigRegistry * config_registry,
216 XklConfigItem * config_item, GtkBuilder * dialog)
217 {
218 GtkWidget *option_check;
219 gchar *utf_option_name = xci_desc_to_utf8 (config_item);
220 /* Copy this out because we'll load it into the widget with set_data */
221 gchar *full_option_name =
222 g_strdup (matekbd_keyboard_config_merge_items
223 (current1st_level_id, config_item->name));
224 gboolean initial_state;
225
226 if (current_multi_select)
227 option_check =
228 gtk_check_button_new_with_label (utf_option_name);
229 else {
230 if (current_radio_group == NULL) {
231 /* The first radio in a group is to be "Default", meaning none of
232 the below options are to be included in the selected list.
233 This is a HIG-compliant alternative to allowing no
234 selection in the group. */
235 option_check =
236 gtk_radio_button_new_with_label
237 (current_radio_group, _("Default"));
238 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
239 (option_check),
240 TRUE);
241 /* Make option name underscore -
242 to enforce its first position in the list */
243 g_object_set_data_full (G_OBJECT (option_check),
244 "utfOptionName",
245 g_strdup (" "), g_free);
246 option_checks_list =
247 g_slist_append (option_checks_list,
248 option_check);
249 current_radio_group =
250 gtk_radio_button_get_group (GTK_RADIO_BUTTON
251 (option_check));
252 current_none_radio = option_check;
253
254 g_signal_connect (option_check, "focus-in-event",
255 G_CALLBACK (option_focused_cb),
256 WID ("options_scroll"));
257 }
258 option_check =
259 gtk_radio_button_new_with_label (current_radio_group,
260 utf_option_name);
261 current_radio_group =
262 gtk_radio_button_get_group (GTK_RADIO_BUTTON
263 (option_check));
264 g_object_set_data (G_OBJECT (option_check), "NoneRadio",
265 current_none_radio);
266 }
267
268 initial_state = xkb_options_is_selected (full_option_name);
269
270 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (option_check),
271 initial_state);
272
273 g_object_set_data_full (G_OBJECT (option_check), OPTION_ID_PROP,
274 full_option_name, g_free);
275 g_object_set_data_full (G_OBJECT (option_check), "utfOptionName",
276 utf_option_name, g_free);
277
278 g_signal_connect (option_check, "toggled",
279 G_CALLBACK (option_toggled_cb), NULL);
280
281 option_checks_list =
282 g_slist_append (option_checks_list, option_check);
283
284 g_signal_connect (option_check, "focus-in-event",
285 G_CALLBACK (option_focused_cb),
286 WID ("options_scroll"));
287
288 xkb_options_expander_selcounter_add (initial_state);
289 }
290
291 static gint
xkb_option_checks_compare(GtkWidget * chk1,GtkWidget * chk2)292 xkb_option_checks_compare (GtkWidget * chk1, GtkWidget * chk2)
293 {
294 const gchar *t1 =
295 g_object_get_data (G_OBJECT (chk1), "utfOptionName");
296 const gchar *t2 =
297 g_object_get_data (G_OBJECT (chk2), "utfOptionName");
298 return g_utf8_collate (t1, t2);
299 }
300
301 /* Add a group of options: create title and layout widgets and then
302 add widgets for all the options in the group. */
303 static void
xkb_options_add_group(XklConfigRegistry * config_registry,XklConfigItem * config_item,GtkBuilder * dialog)304 xkb_options_add_group (XklConfigRegistry * config_registry,
305 XklConfigItem * config_item, GtkBuilder * dialog)
306 {
307 GtkWidget *vbox, *option_check;
308 gboolean allow_multiple_selection =
309 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (config_item),
310 XCI_PROP_ALLOW_MULTIPLE_SELECTION));
311
312 GSList *expanders_list =
313 g_object_get_data (G_OBJECT (dialog), EXPANDERS_PROP);
314
315 gchar *utf_group_name = xci_desc_to_utf8 (config_item);
316 gchar *titlemarkup =
317 g_strconcat ("<span>", utf_group_name, "</span>", NULL);
318
319 current_expander = gtk_expander_new (titlemarkup);
320 gtk_expander_set_use_markup (GTK_EXPANDER (current_expander),
321 TRUE);
322 g_object_set_data_full (G_OBJECT (current_expander),
323 "utfGroupName", utf_group_name, g_free);
324 g_object_set_data_full (G_OBJECT (current_expander), "groupId",
325 g_strdup (config_item->name), g_free);
326
327 g_free (titlemarkup);
328 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
329 gtk_widget_set_halign (vbox, GTK_ALIGN_FILL);
330 gtk_widget_set_valign (vbox, GTK_ALIGN_START);
331 gtk_widget_set_hexpand (vbox, TRUE);
332 gtk_widget_set_vexpand (vbox, TRUE);
333 gtk_widget_set_margin_top (vbox, 6);
334 gtk_widget_set_margin_bottom (vbox, 12);
335 gtk_widget_set_margin_start (vbox, 12);
336 gtk_container_add (GTK_CONTAINER (current_expander), vbox);
337
338 current_multi_select = (gboolean) allow_multiple_selection;
339 current_radio_group = NULL;
340 current1st_level_id = config_item->name;
341
342 option_checks_list = NULL;
343
344 xkl_config_registry_foreach_option (config_registry,
345 config_item->name,
346 (ConfigItemProcessFunc)
347 xkb_options_add_option,
348 dialog);
349 /* sort it */
350 option_checks_list =
351 g_slist_sort (option_checks_list,
352 (GCompareFunc) xkb_option_checks_compare);
353 while (option_checks_list) {
354 option_check = GTK_WIDGET (option_checks_list->data);
355 gtk_box_pack_start (GTK_BOX (vbox), option_check, TRUE, TRUE, 0);
356 option_checks_list = option_checks_list->next;
357 }
358 /* free it */
359 g_slist_free (option_checks_list);
360 option_checks_list = NULL;
361
362 xkb_options_expander_highlight ();
363
364 expanders_list = g_slist_append (expanders_list, current_expander);
365 g_object_set_data (G_OBJECT (dialog), EXPANDERS_PROP,
366 expanders_list);
367
368 g_signal_connect (current_expander, "focus-in-event",
369 G_CALLBACK (option_focused_cb),
370 WID ("options_scroll"));
371 }
372
373 static gint
xkb_options_expanders_compare(GtkWidget * expander1,GtkWidget * expander2)374 xkb_options_expanders_compare (GtkWidget * expander1,
375 GtkWidget * expander2)
376 {
377 const gchar *t1 =
378 g_object_get_data (G_OBJECT (expander1), "utfGroupName");
379 const gchar *t2 =
380 g_object_get_data (G_OBJECT (expander2), "utfGroupName");
381 return g_utf8_collate (t1, t2);
382 }
383
384 /* Create widgets to represent the options made available by the backend */
385 void
xkb_options_load_options(GtkBuilder * dialog)386 xkb_options_load_options (GtkBuilder * dialog)
387 {
388 GtkWidget *opts_vbox = WID ("options_vbox");
389 GSList *expanders_list;
390 GtkWidget *expander;
391
392 current1st_level_id = NULL;
393 current_none_radio = NULL;
394 current_multi_select = FALSE;
395 current_radio_group = NULL;
396
397 /* fill the list */
398 xkl_config_registry_foreach_option_group (config_registry,
399 (ConfigItemProcessFunc)
400 xkb_options_add_group,
401 dialog);
402 /* sort it */
403 expanders_list =
404 g_object_get_data (G_OBJECT (dialog), EXPANDERS_PROP);
405 expanders_list =
406 g_slist_sort (expanders_list,
407 (GCompareFunc) xkb_options_expanders_compare);
408 g_object_set_data (G_OBJECT (dialog), EXPANDERS_PROP,
409 expanders_list);
410 while (expanders_list) {
411 expander = GTK_WIDGET (expanders_list->data);
412 gtk_box_pack_start (GTK_BOX (opts_vbox), expander, FALSE,
413 FALSE, 0);
414 expanders_list = expanders_list->next;
415 }
416
417 gtk_widget_show_all (opts_vbox);
418 }
419
420 static void
chooser_response_cb(GtkDialog * dialog,gint response,gpointer data)421 chooser_response_cb (GtkDialog * dialog, gint response, gpointer data)
422 {
423 switch (response) {
424 case GTK_RESPONSE_HELP:
425 capplet_help (GTK_WINDOW (dialog),
426 "prefs-keyboard#prefs-keyboard-layoutoptions");
427 break;
428 case GTK_RESPONSE_CLOSE:{
429 /* just cleanup */
430 GSList *expanders_list =
431 g_object_get_data (G_OBJECT (dialog),
432 EXPANDERS_PROP);
433 g_object_set_data (G_OBJECT (dialog),
434 EXPANDERS_PROP, NULL);
435 g_slist_free (expanders_list);
436
437 gtk_widget_destroy (GTK_WIDGET (dialog));
438 chooser_dialog = NULL;
439 }
440 break;
441 }
442 }
443
444 /* Create popup dialog */
445 void
xkb_options_popup_dialog(GtkBuilder * dialog)446 xkb_options_popup_dialog (GtkBuilder * dialog)
447 {
448 GtkWidget *chooser;
449
450 chooser_dialog = gtk_builder_new ();
451 gtk_builder_add_from_resource (chooser_dialog,
452 "/org/mate/mcc/keyboard/mate-keyboard-properties-options-dialog.ui",
453 NULL);
454
455 chooser = CWID ("xkb_options_dialog");
456 gtk_window_set_transient_for (GTK_WINDOW (chooser),
457 GTK_WINDOW (WID
458 ("keyboard_dialog")));
459 xkb_options_load_options (chooser_dialog);
460
461 g_signal_connect (chooser, "response",
462 G_CALLBACK (chooser_response_cb), dialog);
463
464 gtk_dialog_run (GTK_DIALOG (chooser));
465 }
466
467 /* Update selected option counters for a group-bound expander */
468 static void
xkb_options_update_option_counters(XklConfigRegistry * config_registry,XklConfigItem * config_item)469 xkb_options_update_option_counters (XklConfigRegistry * config_registry,
470 XklConfigItem * config_item)
471 {
472 gchar *full_option_name =
473 g_strdup (matekbd_keyboard_config_merge_items
474 (current1st_level_id, config_item->name));
475 gboolean current_state =
476 xkb_options_is_selected (full_option_name);
477 xkb_options_expander_selcounter_add (current_state);
478 }
479
480 /* Respond to a change in the xkb gsettings settings */
481 static void
xkb_options_update(GSettings * settings,gchar * key,GtkBuilder * dialog)482 xkb_options_update (GSettings * settings, gchar * key, GtkBuilder * dialog)
483 {
484 /* Updating options is handled by gsettings notifies for each widget
485 This is here to avoid calling it N_OPTIONS times for each gsettings
486 change. */
487 enable_disable_restoring (dialog);
488
489 if (chooser_dialog != NULL) {
490 GSList *expanders_list =
491 g_object_get_data (G_OBJECT (chooser_dialog),
492 EXPANDERS_PROP);
493 while (expanders_list) {
494 current_expander =
495 GTK_WIDGET (expanders_list->data);
496 gchar *group_id =
497 g_object_get_data (G_OBJECT (current_expander),
498 "groupId");
499 current1st_level_id = group_id;
500 xkb_options_expander_selcounter_reset ();
501 xkl_config_registry_foreach_option
502 (config_registry, group_id,
503 (ConfigItemProcessFunc)
504 xkb_options_update_option_counters,
505 current_expander);
506 xkb_options_expander_highlight ();
507 expanders_list = expanders_list->next;
508 }
509 }
510 }
511
512 void
xkb_options_register_gsettings_listener(GtkBuilder * dialog)513 xkb_options_register_gsettings_listener (GtkBuilder * dialog)
514 {
515 g_signal_connect (xkb_kbd_settings,
516 "changed::options",
517 G_CALLBACK (xkb_options_update),
518 dialog);
519 }
520