1 /* cinnamon-region-panel-xkbltadd.c
2  * Copyright (C) 2007 Sergey V. Udaltsov
3  *
4  * Written by: Sergey V. Udaltsov <svu@gnome.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
19  * 02110-1335, USA.
20  */
21 
22 #include <config.h>
23 
24 #include <string.h>
25 
26 #include <libgnomekbd/gkbd-keyboard-drawing.h>
27 #include <libgnomekbd/gkbd-util.h>
28 
29 #include "cinnamon-region-panel-xkb.h"
30 
31 enum {
32 	COMBO_BOX_MODEL_COL_SORT,
33 	COMBO_BOX_MODEL_COL_VISIBLE,
34 	COMBO_BOX_MODEL_COL_XKB_ID,
35 	COMBO_BOX_MODEL_COL_COUNTRY_DESC,
36 	COMBO_BOX_MODEL_COL_LANGUAGE_DESC
37 };
38 
39 static gchar **search_pattern_list = NULL;
40 
41 static GtkWidget *preview_dialog = NULL;
42 
43 static GRegex *left_bracket_regex = NULL;
44 
45 #define RESPONSE_PREVIEW 1
46 
47 static void
xkb_preview_destroy_callback(GtkWidget * widget)48 xkb_preview_destroy_callback (GtkWidget * widget)
49 {
50 	preview_dialog = NULL;
51 }
52 
53 static gboolean
xkb_layout_chooser_selection_dupe(GtkDialog * dialog)54 xkb_layout_chooser_selection_dupe (GtkDialog * dialog)
55 {
56 	gchar *selected_id =
57 	    (gchar *) xkb_layout_chooser_get_selected_id (dialog);
58 	gchar **layouts_list, **pl;
59 	gboolean rv = FALSE;
60 	if (selected_id == NULL)
61 		return rv;
62 	layouts_list = pl = xkb_layouts_get_selected_list ();
63 	while (pl && *pl) {
64 		if (!g_ascii_strcasecmp (*pl++, selected_id)) {
65 			rv = TRUE;
66 			break;
67 		}
68 	}
69 	g_strfreev (layouts_list);
70 	return rv;
71 }
72 
73 void
xkb_layout_chooser_response(GtkDialog * dialog,gint response)74 xkb_layout_chooser_response (GtkDialog * dialog, gint response)
75 {
76 	switch (response)
77 	case GTK_RESPONSE_OK:{
78 			/* Handled by the main code */
79 			break;
80 	case RESPONSE_PREVIEW:{
81 				gchar *selected_id = (gchar *)
82 				    xkb_layout_chooser_get_selected_id
83 				    (dialog);
84 
85 				if (selected_id != NULL) {
86 					if (preview_dialog == NULL) {
87 						preview_dialog =
88 						    gkbd_keyboard_drawing_dialog_new
89 						    ();
90 						g_signal_connect (G_OBJECT
91 								  (preview_dialog),
92 								  "destroy",
93 								  G_CALLBACK
94 								  (xkb_preview_destroy_callback),
95 								  NULL);
96 						/* Put into the separate group to avoid conflict
97 						   with modal parent */
98 						gtk_window_group_add_window
99 						    (gtk_window_group_new
100 						     (),
101 						     GTK_WINDOW
102 						     (preview_dialog));
103 					};
104 					gkbd_keyboard_drawing_dialog_set_layout
105 					    (preview_dialog,
106 					     config_registry, selected_id);
107 
108 					gtk_widget_show_all
109 					    (preview_dialog);
110 				}
111 			}
112 
113 			return;
114 		}
115 	if (preview_dialog != NULL) {
116 		gtk_widget_destroy (preview_dialog);
117 	}
118 	if (search_pattern_list != NULL) {
119 		g_strfreev (search_pattern_list);
120 		search_pattern_list = NULL;
121 	}
122 	gtk_widget_destroy (GTK_WIDGET (dialog));
123 }
124 
125 static gchar *
xkl_create_description_from_list(const XklConfigItem * item,const XklConfigItem * subitem,const gchar * prop_name,const gchar * (* desc_getter)(const gchar * code))126 xkl_create_description_from_list (const XklConfigItem * item,
127 				  const XklConfigItem * subitem,
128 				  const gchar * prop_name,
129 				  const gchar *
130 				  (*desc_getter) (const gchar * code))
131 {
132 	gchar *rv = NULL, *code = NULL;
133 	gchar **list = NULL;
134 	const gchar *desc;
135 
136 	if (subitem != NULL)
137 		list =
138 		    (gchar
139 		     **) (g_object_get_data (G_OBJECT (subitem),
140 					     prop_name));
141 	if (list == NULL || *list == 0)
142 		list =
143 		    (gchar
144 		     **) (g_object_get_data (G_OBJECT (item), prop_name));
145 
146 	/* First try the parent id as such */
147 	desc = desc_getter (item->name);
148 	if (desc != NULL) {
149 		rv = g_utf8_strup (desc, -1);
150 	} else {
151 		code = g_utf8_strup (item->name, -1);
152 		desc = desc_getter (code);
153 		if (desc != NULL) {
154 			rv = g_utf8_strup (desc, -1);
155 		}
156 		g_free (code);
157 	}
158 
159 	if (list == NULL || *list == 0)
160 		return rv;
161 
162 	while (*list != 0) {
163 		code = *list++;
164 		desc = desc_getter (code);
165 		if (desc != NULL) {
166 			gchar *udesc = g_utf8_strup (desc, -1);
167 			if (rv == NULL) {
168 				rv = udesc;
169 			} else {
170 				gchar *orv = rv;
171 				rv = g_strdup_printf ("%s %s", rv, udesc);
172 				g_free (orv);
173 				g_free (udesc);
174 			}
175 		}
176 	}
177 	return rv;
178 }
179 
180 static void
xkl_layout_add_to_list(XklConfigRegistry * config,const XklConfigItem * item,const XklConfigItem * subitem,GtkBuilder * chooser_dialog)181 xkl_layout_add_to_list (XklConfigRegistry * config,
182 			const XklConfigItem * item,
183 			const XklConfigItem * subitem,
184 			GtkBuilder * chooser_dialog)
185 {
186 	GtkListStore *list_store =
187 	    GTK_LIST_STORE (gtk_builder_get_object (chooser_dialog,
188 						    "layout_list_model"));
189 	GtkTreeIter iter;
190 
191 	gchar *utf_variant_name =
192 	    subitem ?
193 	    xkb_layout_description_utf8 (gkbd_keyboard_config_merge_items
194 					 (item->name,
195 					  subitem->name)) :
196 	    xci_desc_to_utf8 (item);
197 
198 	const gchar *xkb_id =
199 	    subitem ? gkbd_keyboard_config_merge_items (item->name,
200 							subitem->name) :
201 	    item->name;
202 
203 	gchar *country_desc =
204 	    xkl_create_description_from_list (item, subitem,
205 					      XCI_PROP_COUNTRY_LIST,
206 					      xkl_get_country_name);
207 	gchar *language_desc =
208 	    xkl_create_description_from_list (item, subitem,
209 					      XCI_PROP_LANGUAGE_LIST,
210 					      xkl_get_language_name);
211 
212 	gchar *tmp = utf_variant_name;
213 	utf_variant_name =
214 	    g_regex_replace_literal (left_bracket_regex, tmp, -1, 0,
215 				     "&lt;", 0, NULL);
216 	g_free (tmp);
217 
218 	if (subitem
219 	    && g_object_get_data (G_OBJECT (subitem),
220 				  XCI_PROP_EXTRA_ITEM)) {
221 		gchar *buf =
222 		    g_strdup_printf ("<i>%s</i>", utf_variant_name);
223 		gtk_list_store_insert_with_values (list_store, &iter, -1,
224 						   COMBO_BOX_MODEL_COL_SORT,
225 						   utf_variant_name,
226 						   COMBO_BOX_MODEL_COL_VISIBLE,
227 						   buf,
228 						   COMBO_BOX_MODEL_COL_XKB_ID,
229 						   xkb_id,
230 						   COMBO_BOX_MODEL_COL_COUNTRY_DESC,
231 						   country_desc,
232 						   COMBO_BOX_MODEL_COL_LANGUAGE_DESC,
233 						   language_desc, -1);
234 		g_free (buf);
235 	} else
236 		gtk_list_store_insert_with_values (list_store, &iter,
237 						   -1,
238 						   COMBO_BOX_MODEL_COL_SORT,
239 						   utf_variant_name,
240 						   COMBO_BOX_MODEL_COL_VISIBLE,
241 						   utf_variant_name,
242 						   COMBO_BOX_MODEL_COL_XKB_ID,
243 						   xkb_id,
244 						   COMBO_BOX_MODEL_COL_COUNTRY_DESC,
245 						   country_desc,
246 						   COMBO_BOX_MODEL_COL_LANGUAGE_DESC,
247 						   language_desc, -1);
248 	g_free (utf_variant_name);
249 	g_free (country_desc);
250 	g_free (language_desc);
251 }
252 
253 static void
xkb_layout_filter_clear(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer user_data)254 xkb_layout_filter_clear (GtkEntry * entry,
255 			 GtkEntryIconPosition icon_pos,
256 			 GdkEvent * event, gpointer user_data)
257 {
258 	gtk_entry_set_text (entry, "");
259 }
260 
261 static void
xkb_layout_filter_changed(GtkBuilder * chooser_dialog)262 xkb_layout_filter_changed (GtkBuilder * chooser_dialog)
263 {
264 	GtkTreeModelFilter *filtered_model =
265 	    GTK_TREE_MODEL_FILTER (gtk_builder_get_object (chooser_dialog,
266 							   "filtered_layout_list_model"));
267 	GtkWidget *xkb_layout_filter = CWID ("xkb_layout_filter");
268 	const gchar *pattern =
269 	    gtk_entry_get_text (GTK_ENTRY (xkb_layout_filter));
270 	gchar *upattern = g_utf8_strup (pattern, -1);
271 
272 	if (!g_strcmp0 (pattern, "")) {
273 		g_object_set (G_OBJECT (xkb_layout_filter),
274 			      "secondary-icon-name", "edit-find-symbolic",
275 			      "secondary-icon-activatable", FALSE,
276 			      "secondary-icon-sensitive", FALSE, NULL);
277 	} else {
278 		g_object_set (G_OBJECT (xkb_layout_filter),
279 			      "secondary-icon-name", "edit-clear-symbolic",
280 			      "secondary-icon-activatable", TRUE,
281 			      "secondary-icon-sensitive", TRUE, NULL);
282 	}
283 
284 	if (search_pattern_list != NULL)
285 		g_strfreev (search_pattern_list);
286 
287 	search_pattern_list = g_strsplit (upattern, " ", -1);
288 	g_free (upattern);
289 
290 	gtk_tree_model_filter_refilter (filtered_model);
291 }
292 
293 static void
xkb_layout_chooser_selection_changed(GtkTreeSelection * selection,GtkBuilder * chooser_dialog)294 xkb_layout_chooser_selection_changed (GtkTreeSelection * selection,
295 				      GtkBuilder * chooser_dialog)
296 {
297 	GList *selected_layouts =
298 	    gtk_tree_selection_get_selected_rows (selection, NULL);
299 	GtkWidget *add_button = CWID ("btnOk");
300 	GtkWidget *preview_button = CWID ("btnPreview");
301 	gboolean anything_selected = g_list_length (selected_layouts) == 1;
302 	gboolean dupe =
303 	    xkb_layout_chooser_selection_dupe (GTK_DIALOG
304 					       (CWID
305 						("xkb_layout_chooser")));
306 
307 	gtk_widget_set_sensitive (add_button, anything_selected && !dupe);
308 	gtk_widget_set_sensitive (preview_button, anything_selected);
309 }
310 
311 static void
xkb_layout_chooser_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,GtkBuilder * chooser_dialog)312 xkb_layout_chooser_row_activated (GtkTreeView * tree_view,
313 				  GtkTreePath * path,
314 				  GtkTreeViewColumn * column,
315 				  GtkBuilder * chooser_dialog)
316 {
317 	GtkWidget *add_button = CWID ("btnOk");
318 	GtkWidget *dialog = CWID ("xkb_layout_chooser");
319 
320 	if (gtk_widget_is_sensitive (add_button))
321 		gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
322 }
323 
324 static gboolean
xkb_filter_layouts(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)325 xkb_filter_layouts (GtkTreeModel * model,
326 		    GtkTreeIter * iter, gpointer data)
327 {
328 	gchar *desc = NULL, *country_desc = NULL, *language_desc =
329 	    NULL, **pattern;
330 	gboolean rv = TRUE;
331 
332 	if (search_pattern_list == NULL || search_pattern_list[0] == NULL)
333 		return TRUE;
334 
335 	gtk_tree_model_get (model, iter,
336 			    COMBO_BOX_MODEL_COL_SORT, &desc,
337 			    COMBO_BOX_MODEL_COL_COUNTRY_DESC,
338 			    &country_desc,
339 			    COMBO_BOX_MODEL_COL_LANGUAGE_DESC,
340 			    &language_desc, -1);
341 
342 	pattern = search_pattern_list;
343 	do {
344 		gboolean is_pattern_found = FALSE;
345 		gchar *udesc = g_utf8_strup (desc, -1);
346 		if (udesc != NULL && g_strstr_len (udesc, -1, *pattern)) {
347 			is_pattern_found = TRUE;
348 		} else if (country_desc != NULL
349 			   && g_strstr_len (country_desc, -1, *pattern)) {
350 			is_pattern_found = TRUE;
351 		} else if (language_desc != NULL
352 			   && g_strstr_len (language_desc, -1, *pattern)) {
353 			is_pattern_found = TRUE;
354 		}
355 		g_free (udesc);
356 
357 		if (!is_pattern_found) {
358 			rv = FALSE;
359 			break;
360 		}
361 
362 	} while (*++pattern != NULL);
363 
364 	g_free (desc);
365 	g_free (country_desc);
366 	g_free (language_desc);
367 	return rv;
368 }
369 
370 GtkWidget *
xkb_layout_choose(GtkBuilder * dialog)371 xkb_layout_choose (GtkBuilder * dialog)
372 {
373 	GtkBuilder *chooser_dialog = gtk_builder_new ();
374 	GtkWidget *chooser, *xkb_filtered_layouts_list, *xkb_layout_filter;
375 	GtkTreeViewColumn *visible_column;
376 	GtkTreeSelection *selection;
377 	GtkListStore *model;
378 	GtkTreeModelFilter *filtered_model;
379 	gtk_builder_add_from_file (chooser_dialog, CINNAMONCC_UI_DIR
380 				   "/cinnamon-region-panel-layout-chooser.ui",
381 				   NULL);
382 	chooser = CWID ("xkb_layout_chooser");
383 	xkb_filtered_layouts_list = CWID ("xkb_filtered_layouts_list");
384 	xkb_layout_filter = CWID ("xkb_layout_filter");
385 
386 	g_object_set_data (G_OBJECT (chooser), "xkb_filtered_layouts_list",
387 			   xkb_filtered_layouts_list);
388 	visible_column =
389 	    gtk_tree_view_column_new_with_attributes ("Layout",
390 						      gtk_cell_renderer_text_new
391 						      (), "markup",
392 						      COMBO_BOX_MODEL_COL_VISIBLE,
393 						      NULL);
394 
395 	gtk_window_set_transient_for (GTK_WINDOW (chooser),
396 				      GTK_WINDOW
397 				      (gtk_widget_get_toplevel
398 				       (WID ("region_notebook"))));
399 
400 	gtk_tree_view_append_column (GTK_TREE_VIEW
401 				     (xkb_filtered_layouts_list),
402 				     visible_column);
403 	g_signal_connect_swapped (G_OBJECT (xkb_layout_filter),
404 				  "notify::text",
405 				  G_CALLBACK
406 				  (xkb_layout_filter_changed),
407 				  chooser_dialog);
408 
409 	g_signal_connect (G_OBJECT (xkb_layout_filter), "icon-release",
410 			  G_CALLBACK (xkb_layout_filter_clear), NULL);
411 
412 	selection =
413 	    gtk_tree_view_get_selection (GTK_TREE_VIEW
414 					 (xkb_filtered_layouts_list));
415 
416 	g_signal_connect (G_OBJECT (selection),
417 			  "changed",
418 			  G_CALLBACK
419 			  (xkb_layout_chooser_selection_changed),
420 			  chooser_dialog);
421 
422 	xkb_layout_chooser_selection_changed (selection, chooser_dialog);
423 
424 	g_signal_connect (G_OBJECT (xkb_filtered_layouts_list),
425 			  "row-activated",
426 			  G_CALLBACK (xkb_layout_chooser_row_activated),
427 			  chooser_dialog);
428 
429 	filtered_model =
430 	    GTK_TREE_MODEL_FILTER (gtk_builder_get_object
431 				   (chooser_dialog,
432 				    "filtered_layout_list_model"));
433 	model =
434 	    GTK_LIST_STORE (gtk_builder_get_object
435 			    (chooser_dialog, "layout_list_model"));
436 
437 	left_bracket_regex = g_regex_new ("<", 0, 0, NULL);
438 
439 	xkl_config_registry_search_by_pattern (config_registry,
440 					       NULL,
441 					       (TwoConfigItemsProcessFunc)
442 					       (xkl_layout_add_to_list),
443 					       chooser_dialog);
444 
445 	g_regex_unref (left_bracket_regex);
446 
447 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
448 					      COMBO_BOX_MODEL_COL_SORT,
449 					      GTK_SORT_ASCENDING);
450 
451 	gtk_tree_model_filter_set_visible_func (filtered_model,
452 						xkb_filter_layouts,
453 						NULL, NULL);
454 
455 	gtk_widget_grab_focus (xkb_layout_filter);
456 
457 	gtk_widget_show (chooser);
458 
459 	return chooser;
460 }
461 
462 gchar *
xkb_layout_chooser_get_selected_id(GtkDialog * dialog)463 xkb_layout_chooser_get_selected_id (GtkDialog * dialog)
464 {
465 	GtkTreeModel *filtered_list_model;
466 	GtkWidget *xkb_filtered_layouts_list =
467 	    g_object_get_data (G_OBJECT (dialog),
468 			       "xkb_filtered_layouts_list");
469 	GtkTreeIter viter;
470 	gchar *v_id;
471 	GtkTreeSelection *selection =
472 	    gtk_tree_view_get_selection (GTK_TREE_VIEW
473 					 (xkb_filtered_layouts_list));
474 	GList *selected_layouts =
475 	    gtk_tree_selection_get_selected_rows (selection,
476 						  &filtered_list_model);
477 
478 	if (g_list_length (selected_layouts) != 1)
479 		return NULL;
480 
481 	gtk_tree_model_get_iter (filtered_list_model,
482 				 &viter,
483 				 (GtkTreePath *) (selected_layouts->data));
484 	g_list_foreach (selected_layouts,
485 			(GFunc) gtk_tree_path_free, NULL);
486 	g_list_free (selected_layouts);
487 
488 	gtk_tree_model_get (filtered_list_model, &viter,
489 			    COMBO_BOX_MODEL_COL_XKB_ID, &v_id, -1);
490 
491 	return v_id;
492 }
493