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