1 /*
2 * Copyright (C) 2013 Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 *
17 * Written by:
18 * Matthias Clasen
19 */
20
21 #define _GNU_SOURCE
22 #include <config.h>
23 #include "cc-format-chooser.h"
24
25 #include <errno.h>
26 #include <locale.h>
27 #include <langinfo.h>
28 #include <string.h>
29 #include <glib/gi18n.h>
30 #include <handy.h>
31
32 #include "list-box-helper.h"
33 #include "cc-common-language.h"
34 #include "cc-format-preview.h"
35 #include "cc-util.h"
36
37 #define GNOME_DESKTOP_USE_UNSTABLE_API
38 #include <libgnome-desktop/gnome-languages.h>
39
40 struct _CcFormatChooser {
41 GtkDialog parent_instance;
42
43 GtkWidget *title_bar;
44 GtkWidget *title_buttons;
45 GtkWidget *cancel_button;
46 GtkWidget *back_button;
47 GtkWidget *done_button;
48 GtkWidget *empty_results_view;
49 GtkWidget *main_leaflet;
50 GtkWidget *region_filter_entry;
51 GtkWidget *region_list;
52 GtkWidget *region_list_stack;
53 GtkWidget *common_region_title;
54 GtkWidget *common_region_listbox;
55 GtkWidget *region_title;
56 GtkWidget *region_listbox;
57 CcFormatPreview *format_preview;
58 gboolean adding;
59 gboolean showing_extra;
60 gboolean no_results;
61 gchar *region;
62 gchar *preview_region;
63 gchar **filter_words;
64 };
65
G_DEFINE_TYPE(CcFormatChooser,cc_format_chooser,GTK_TYPE_DIALOG)66 G_DEFINE_TYPE (CcFormatChooser, cc_format_chooser, GTK_TYPE_DIALOG)
67
68 static void
69 update_check_button_for_list (GtkWidget *list_box,
70 const gchar *locale_id)
71 {
72 g_autoptr(GList) children = NULL;
73 GList *l;
74
75 g_assert (GTK_IS_CONTAINER (list_box));
76
77 children = gtk_container_get_children (GTK_CONTAINER (list_box));
78 for (l = children; l; l = l->next)
79 {
80 GtkWidget *row = l->data;
81 GtkWidget *check = g_object_get_data (G_OBJECT (row), "check");
82 const gchar *region = g_object_get_data (G_OBJECT (row), "locale-id");
83 if (check == NULL || region == NULL)
84 continue;
85
86 if (g_strcmp0 (locale_id, region) == 0)
87 gtk_widget_set_opacity (check, 1.0);
88 else
89 gtk_widget_set_opacity (check, 0.0);
90 }
91 }
92
93 static void
set_locale_id(CcFormatChooser * chooser,const gchar * locale_id)94 set_locale_id (CcFormatChooser *chooser,
95 const gchar *locale_id)
96 {
97 g_free (chooser->region);
98 chooser->region = g_strdup (locale_id);
99
100 update_check_button_for_list (chooser->region_listbox, locale_id);
101 update_check_button_for_list (chooser->common_region_listbox, locale_id);
102 cc_format_preview_set_region (chooser->format_preview, locale_id);
103 }
104
105 static gint
sort_regions(gconstpointer a,gconstpointer b,gpointer data)106 sort_regions (gconstpointer a,
107 gconstpointer b,
108 gpointer data)
109 {
110 const gchar *la;
111 const gchar *lb;
112
113 if (g_object_get_data (G_OBJECT (a), "locale-id") == NULL)
114 return 1;
115 if (g_object_get_data (G_OBJECT (b), "locale-id") == NULL)
116 return -1;
117
118 la = g_object_get_data (G_OBJECT (a), "locale-name");
119 lb = g_object_get_data (G_OBJECT (b), "locale-name");
120
121 return g_strcmp0 (la, lb);
122 }
123
124 static GtkWidget *
padded_label_new(const char * text)125 padded_label_new (const char *text)
126 {
127 GtkWidget *widget, *label;
128
129 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
130 g_object_set (widget, "margin-top", 4, NULL);
131 g_object_set (widget, "margin-bottom", 4, NULL);
132 g_object_set (widget, "margin-start", 10, NULL);
133 g_object_set (widget, "margin-end", 10, NULL);
134
135 label = gtk_label_new (text);
136 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
137 gtk_widget_show (label);
138 gtk_container_add (GTK_CONTAINER (widget), label);
139
140 return widget;
141 }
142
143 static void
format_chooser_back_button_clicked_cb(CcFormatChooser * self)144 format_chooser_back_button_clicked_cb (CcFormatChooser *self)
145 {
146 g_assert (CC_IS_FORMAT_CHOOSER (self));
147
148 gtk_header_bar_set_title (GTK_HEADER_BAR (self->title_bar), _("Formats"));
149 hdy_leaflet_set_visible_child_name (HDY_LEAFLET (self->main_leaflet), "region-list");
150 gtk_stack_set_visible_child (GTK_STACK (self->title_buttons), self->cancel_button);
151 gtk_widget_show (self->done_button);
152 }
153
154 static void
cc_format_chooser_preview_button_set_visible(GtkListBoxRow * row,gpointer user_data)155 cc_format_chooser_preview_button_set_visible (GtkListBoxRow *row,
156 gpointer user_data)
157 {
158 GtkWidget *button;
159 gboolean visible;
160
161 g_assert (GTK_IS_LIST_BOX_ROW (row));
162
163 visible = GPOINTER_TO_INT (user_data);
164 button = g_object_get_data (G_OBJECT (row), "preview-button");
165 g_assert (button);
166
167 gtk_widget_set_opacity (button, visible);
168 gtk_widget_set_sensitive (button, visible);
169 }
170
171 static void
format_chooser_leaflet_fold_changed_cb(CcFormatChooser * self)172 format_chooser_leaflet_fold_changed_cb (CcFormatChooser *self)
173 {
174 gboolean folded;
175
176 g_assert (CC_IS_FORMAT_CHOOSER (self));
177
178 folded = hdy_leaflet_get_folded (HDY_LEAFLET (self->main_leaflet));
179 gtk_container_foreach (GTK_CONTAINER (self->common_region_listbox),
180 (GtkCallback)cc_format_chooser_preview_button_set_visible,
181 GINT_TO_POINTER (folded));
182 gtk_container_foreach (GTK_CONTAINER (self->region_listbox),
183 (GtkCallback)cc_format_chooser_preview_button_set_visible,
184 GINT_TO_POINTER (folded));
185
186 if (!folded)
187 {
188 cc_format_preview_set_region (self->format_preview, self->region);
189 gtk_header_bar_set_title (GTK_HEADER_BAR (self->title_bar), _("Formats"));
190 hdy_leaflet_set_visible_child_name (HDY_LEAFLET (self->main_leaflet), "region-list");
191 gtk_stack_set_visible_child (GTK_STACK (self->title_buttons), self->cancel_button);
192 gtk_widget_show (self->done_button);
193 }
194 }
195
196 static void
preview_button_clicked_cb(CcFormatChooser * self,GtkWidget * button)197 preview_button_clicked_cb (CcFormatChooser *self,
198 GtkWidget *button)
199 {
200 GtkWidget *row;
201 const gchar *region, *locale_name;
202
203 g_assert (CC_IS_FORMAT_CHOOSER (self));
204 g_assert (GTK_IS_WIDGET (button));
205
206 row = gtk_widget_get_ancestor (button, GTK_TYPE_LIST_BOX_ROW);
207 g_assert (row);
208
209 region = g_object_get_data (G_OBJECT (row), "locale-id");
210 locale_name = g_object_get_data (G_OBJECT (row), "locale-name");
211 cc_format_preview_set_region (self->format_preview, region);
212
213 hdy_leaflet_set_visible_child_name (HDY_LEAFLET (self->main_leaflet), "preview");
214 gtk_stack_set_visible_child (GTK_STACK (self->title_buttons), self->back_button);
215 gtk_widget_hide (self->done_button);
216
217 if (locale_name)
218 gtk_header_bar_set_title (GTK_HEADER_BAR (self->title_bar), locale_name);
219 }
220
221 static GtkWidget *
region_widget_new(CcFormatChooser * self,const gchar * locale_id)222 region_widget_new (CcFormatChooser *self,
223 const gchar *locale_id)
224 {
225 gchar *locale_name;
226 gchar *locale_current_name;
227 gchar *locale_untranslated_name;
228 GtkWidget *row, *box, *button;
229 GtkWidget *check;
230
231 locale_name = gnome_get_country_from_locale (locale_id, locale_id);
232 if (!locale_name)
233 return NULL;
234
235 locale_current_name = gnome_get_country_from_locale (locale_id, NULL);
236 locale_untranslated_name = gnome_get_country_from_locale (locale_id, "C");
237
238 row = gtk_list_box_row_new ();
239 gtk_widget_show (row);
240 box = padded_label_new (locale_name);
241 gtk_widget_show (box);
242 gtk_container_add (GTK_CONTAINER (row), box);
243
244 button = gtk_button_new_from_icon_name ("view-layout-symbolic", GTK_ICON_SIZE_BUTTON);
245 g_signal_connect_object (button, "clicked", G_CALLBACK (preview_button_clicked_cb),
246 self, G_CONNECT_SWAPPED);
247 gtk_widget_show (button);
248 gtk_box_pack_end (GTK_BOX (box), button, FALSE, TRUE, 0);
249
250 check = gtk_image_new ();
251 gtk_widget_show (check);
252 gtk_image_set_from_icon_name (GTK_IMAGE (check), "object-select-symbolic", GTK_ICON_SIZE_MENU);
253 gtk_widget_set_opacity (check, 0.0);
254 g_object_set (check, "icon-size", GTK_ICON_SIZE_MENU, NULL);
255 gtk_container_add (GTK_CONTAINER (box), check);
256
257 g_object_set_data (G_OBJECT (row), "check", check);
258 g_object_set_data (G_OBJECT (row), "preview-button", button);
259 g_object_set_data_full (G_OBJECT (row), "locale-id", g_strdup (locale_id), g_free);
260 g_object_set_data_full (G_OBJECT (row), "locale-name", locale_name, g_free);
261 g_object_set_data_full (G_OBJECT (row), "locale-current-name", locale_current_name, g_free);
262 g_object_set_data_full (G_OBJECT (row), "locale-untranslated-name", locale_untranslated_name, g_free);
263
264 return row;
265 }
266
267 static void
add_regions(CcFormatChooser * chooser,gchar ** locale_ids,GHashTable * initial)268 add_regions (CcFormatChooser *chooser,
269 gchar **locale_ids,
270 GHashTable *initial)
271 {
272 g_autoptr(GList) initial_locales = NULL;
273 GtkWidget *widget;
274 GList *l;
275
276 chooser->adding = TRUE;
277 initial_locales = g_hash_table_get_keys (initial);
278
279 /* Populate Common Locales */
280 for (l = initial_locales; l != NULL; l = l->next) {
281 if (!cc_common_language_has_font (l->data))
282 continue;
283
284 widget = region_widget_new (chooser, l->data);
285 if (!widget)
286 continue;
287
288 gtk_widget_show (widget);
289 gtk_container_add (GTK_CONTAINER (chooser->common_region_listbox), widget);
290 }
291
292 /* Populate All locales */
293 while (*locale_ids) {
294 gchar *locale_id;
295
296 locale_id = *locale_ids;
297 locale_ids ++;
298
299 if (!cc_common_language_has_font (locale_id))
300 continue;
301
302 widget = region_widget_new (chooser, locale_id);
303 if (!widget)
304 continue;
305
306 gtk_widget_show (widget);
307 gtk_container_add (GTK_CONTAINER (chooser->region_listbox), widget);
308 }
309
310 chooser->adding = FALSE;
311 }
312
313 static void
add_all_regions(CcFormatChooser * chooser)314 add_all_regions (CcFormatChooser *chooser)
315 {
316 g_auto(GStrv) locale_ids = NULL;
317 g_autoptr(GHashTable) initial = NULL;
318
319 locale_ids = gnome_get_all_locales ();
320 initial = cc_common_language_get_initial_languages ();
321 add_regions (chooser, locale_ids, initial);
322 }
323
324 static gboolean
match_all(gchar ** words,const gchar * str)325 match_all (gchar **words,
326 const gchar *str)
327 {
328 gchar **w;
329
330 for (w = words; *w; ++w)
331 if (!strstr (str, *w))
332 return FALSE;
333
334 return TRUE;
335 }
336
337 static gboolean
region_visible(GtkListBoxRow * row,gpointer user_data)338 region_visible (GtkListBoxRow *row,
339 gpointer user_data)
340 {
341 CcFormatChooser *chooser = user_data;
342 g_autofree gchar *locale_name = NULL;
343 g_autofree gchar *locale_current_name = NULL;
344 g_autofree gchar *locale_untranslated_name = NULL;
345 gboolean match = TRUE;
346
347 if (!chooser->filter_words)
348 goto end;
349
350 locale_name =
351 cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-name"));
352 if (match_all (chooser->filter_words, locale_name))
353 goto end;
354
355 locale_current_name =
356 cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-current-name"));
357 if (match_all (chooser->filter_words, locale_current_name))
358 goto end;
359
360 locale_untranslated_name =
361 cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-untranslated-name"));
362
363 match = match_all (chooser->filter_words, locale_untranslated_name);
364
365 end:
366 if (match)
367 chooser->no_results = FALSE;
368 return match;
369 }
370
371 static void
filter_changed(CcFormatChooser * chooser)372 filter_changed (CcFormatChooser *chooser)
373 {
374 g_autofree gchar *filter_contents = NULL;
375 gboolean visible;
376
377 g_clear_pointer (&chooser->filter_words, g_strfreev);
378
379 filter_contents =
380 cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (chooser->region_filter_entry)));
381
382 /* The popular listbox is shown only if search is empty */
383 visible = filter_contents == NULL || *filter_contents == '\0';
384 gtk_widget_set_visible (chooser->common_region_listbox, visible);
385 gtk_widget_set_visible (chooser->common_region_title, visible);
386 gtk_widget_set_visible (chooser->region_title, visible);
387
388 /* Reset cached search state */
389 chooser->no_results = TRUE;
390
391 if (!filter_contents) {
392 gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->region_listbox));
393 gtk_list_box_set_placeholder (GTK_LIST_BOX (chooser->region_listbox), NULL);
394 return;
395 }
396 chooser->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
397 gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->region_listbox));
398
399 if (chooser->no_results)
400 gtk_stack_set_visible_child (GTK_STACK (chooser->region_list_stack),
401 GTK_WIDGET (chooser->empty_results_view));
402 else
403 gtk_stack_set_visible_child (GTK_STACK (chooser->region_list_stack),
404 GTK_WIDGET (chooser->region_list));
405 }
406
407 static void
row_activated(CcFormatChooser * chooser,GtkListBoxRow * row)408 row_activated (CcFormatChooser *chooser,
409 GtkListBoxRow *row)
410 {
411 const gchar *new_locale_id;
412
413 if (chooser->adding)
414 return;
415
416 new_locale_id = g_object_get_data (G_OBJECT (row), "locale-id");
417 if (g_strcmp0 (new_locale_id, chooser->region) == 0) {
418 gtk_dialog_response (GTK_DIALOG (chooser),
419 gtk_dialog_get_response_for_widget (GTK_DIALOG (chooser),
420 chooser->done_button));
421 } else {
422 set_locale_id (chooser, new_locale_id);
423 }
424 }
425
426 static void
activate_default(CcFormatChooser * chooser)427 activate_default (CcFormatChooser *chooser)
428 {
429 GtkWidget *focus;
430 const gchar *locale_id;
431
432 focus = gtk_window_get_focus (GTK_WINDOW (chooser));
433 if (!focus)
434 return;
435
436 locale_id = g_object_get_data (G_OBJECT (focus), "locale-id");
437 if (g_strcmp0 (locale_id, chooser->region) == 0)
438 return;
439
440 g_signal_stop_emission_by_name (chooser, "activate-default");
441 gtk_widget_activate (focus);
442 }
443
444 static void
cc_format_chooser_dispose(GObject * object)445 cc_format_chooser_dispose (GObject *object)
446 {
447 CcFormatChooser *chooser = CC_FORMAT_CHOOSER (object);
448
449 g_clear_pointer (&chooser->filter_words, g_strfreev);
450 g_clear_pointer (&chooser->region, g_free);
451
452 G_OBJECT_CLASS (cc_format_chooser_parent_class)->dispose (object);
453 }
454
455 void
cc_format_chooser_class_init(CcFormatChooserClass * klass)456 cc_format_chooser_class_init (CcFormatChooserClass *klass)
457 {
458 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
459 GObjectClass *object_class = G_OBJECT_CLASS (klass);
460
461 object_class->dispose = cc_format_chooser_dispose;
462
463 g_type_ensure (CC_TYPE_FORMAT_PREVIEW);
464
465 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/region/cc-format-chooser.ui");
466
467 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, title_bar);
468 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, title_buttons);
469 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, cancel_button);
470 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, back_button);
471 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, done_button);
472 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, main_leaflet);
473 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_filter_entry);
474 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, common_region_title);
475 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, common_region_listbox);
476 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_title);
477 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_listbox);
478 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_list);
479 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_list_stack);
480 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, empty_results_view);
481 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, format_preview);
482
483 gtk_widget_class_bind_template_callback (widget_class, format_chooser_back_button_clicked_cb);
484 gtk_widget_class_bind_template_callback (widget_class, format_chooser_leaflet_fold_changed_cb);
485 gtk_widget_class_bind_template_callback (widget_class, filter_changed);
486 gtk_widget_class_bind_template_callback (widget_class, row_activated);
487 }
488
489 void
cc_format_chooser_init(CcFormatChooser * chooser)490 cc_format_chooser_init (CcFormatChooser *chooser)
491 {
492 gtk_widget_init_template (GTK_WIDGET (chooser));
493
494 gtk_list_box_set_sort_func (GTK_LIST_BOX (chooser->common_region_listbox),
495 (GtkListBoxSortFunc)sort_regions, chooser, NULL);
496 gtk_list_box_set_sort_func (GTK_LIST_BOX (chooser->region_listbox),
497 (GtkListBoxSortFunc)sort_regions, chooser, NULL);
498 gtk_list_box_set_filter_func (GTK_LIST_BOX (chooser->region_listbox),
499 region_visible, chooser, NULL);
500 gtk_list_box_set_header_func (GTK_LIST_BOX (chooser->region_listbox),
501 cc_list_box_update_header_func, NULL, NULL);
502 gtk_list_box_set_header_func (GTK_LIST_BOX (chooser->common_region_listbox),
503 cc_list_box_update_header_func, NULL, NULL);
504
505 add_all_regions (chooser);
506 gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->region_listbox));
507 format_chooser_leaflet_fold_changed_cb (chooser);
508
509 g_signal_connect_object (chooser, "activate-default",
510 G_CALLBACK (activate_default), chooser, G_CONNECT_SWAPPED);
511 }
512
513 CcFormatChooser *
cc_format_chooser_new(void)514 cc_format_chooser_new (void)
515 {
516 return CC_FORMAT_CHOOSER (g_object_new (CC_TYPE_FORMAT_CHOOSER,
517 "use-header-bar", 1,
518 NULL));
519 }
520
521 void
cc_format_chooser_clear_filter(CcFormatChooser * chooser)522 cc_format_chooser_clear_filter (CcFormatChooser *chooser)
523 {
524 g_return_if_fail (CC_IS_FORMAT_CHOOSER (chooser));
525 gtk_entry_set_text (GTK_ENTRY (chooser->region_filter_entry), "");
526 }
527
528 const gchar *
cc_format_chooser_get_region(CcFormatChooser * chooser)529 cc_format_chooser_get_region (CcFormatChooser *chooser)
530 {
531 g_return_val_if_fail (CC_IS_FORMAT_CHOOSER (chooser), NULL);
532 return chooser->region;
533 }
534
535 void
cc_format_chooser_set_region(CcFormatChooser * chooser,const gchar * region)536 cc_format_chooser_set_region (CcFormatChooser *chooser,
537 const gchar *region)
538 {
539 g_return_if_fail (CC_IS_FORMAT_CHOOSER (chooser));
540 set_locale_id (chooser, region);
541 }
542