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 "<", 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