GCC Code Coverage Report


Directory: ./
File: panels/system/region/cc-format-chooser.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 271 0.0%
Functions: 0 27 0.0%
Branches: 0 129 0.0%

Line Branch Exec Source
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 <adwaita.h>
31
32 #include "cc-common-language.h"
33 #include "cc-format-preview.h"
34 #include "cc-util.h"
35
36 #define GNOME_DESKTOP_USE_UNSTABLE_API
37 #include <libgnome-desktop/gnome-languages.h>
38
39 struct _CcFormatChooser {
40 GtkDialog parent_instance;
41
42 GtkWidget *title_bar;
43 GtkWidget *title_buttons;
44 GtkWidget *cancel_button;
45 GtkWidget *back_button;
46 GtkWidget *done_button;
47 GtkWidget *empty_results_page;
48 GtkWidget *main_leaflet;
49 GtkWidget *region_filter_entry;
50 GtkWidget *region_list;
51 GtkWidget *region_list_stack;
52 GtkWidget *common_region_title;
53 GtkWidget *common_region_listbox;
54 GtkWidget *region_box;
55 GtkWidget *region_title;
56 GtkWidget *region_listbox;
57 GtkWidget *preview_box;
58 CcFormatPreview *format_preview;
59 gboolean adding;
60 gboolean showing_extra;
61 gboolean no_results;
62 gchar *region;
63 gchar *preview_region;
64 gchar **filter_words;
65 };
66
67 G_DEFINE_TYPE (CcFormatChooser, cc_format_chooser, GTK_TYPE_DIALOG)
68
69 static void
70 update_check_button_for_list (GtkWidget *list_box,
71 const gchar *locale_id)
72 {
73 GtkWidget *child;
74
75 for (child = gtk_widget_get_first_child (list_box);
76 child;
77 child = gtk_widget_get_next_sibling (child))
78 {
79 if (!GTK_IS_LIST_BOX_ROW (child))
80 continue;
81
82 GtkWidget *check = g_object_get_data (G_OBJECT (child), "check");
83 const gchar *region = g_object_get_data (G_OBJECT (child), "locale-id");
84 if (check == NULL || region == NULL)
85 continue;
86
87 if (g_strcmp0 (locale_id, region) == 0)
88 gtk_widget_set_opacity (check, 1.0);
89 else
90 gtk_widget_set_opacity (check, 0.0);
91 }
92 }
93
94 static void
95 set_locale_id (CcFormatChooser *chooser,
96 const gchar *locale_id)
97 {
98 g_free (chooser->region);
99 chooser->region = g_strdup (locale_id);
100
101 update_check_button_for_list (chooser->region_listbox, locale_id);
102 update_check_button_for_list (chooser->common_region_listbox, locale_id);
103 cc_format_preview_set_region (chooser->format_preview, locale_id);
104 }
105
106 static gint
107 sort_regions (gconstpointer a,
108 gconstpointer b,
109 gpointer data)
110 {
111 const gchar *la;
112 const gchar *lb;
113
114 if (g_object_get_data (G_OBJECT (a), "locale-id") == NULL)
115 return 1;
116 if (g_object_get_data (G_OBJECT (b), "locale-id") == NULL)
117 return -1;
118
119 la = g_object_get_data (G_OBJECT (a), "locale-name");
120 lb = g_object_get_data (G_OBJECT (b), "locale-name");
121
122 return g_strcmp0 (la, lb);
123 }
124
125 static GtkWidget *
126 padded_label_new (const char *text)
127 {
128 GtkWidget *widget, *label;
129
130 widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
131 g_object_set (widget, "margin-top", 4, NULL);
132 g_object_set (widget, "margin-bottom", 4, NULL);
133 g_object_set (widget, "margin-start", 10, NULL);
134 g_object_set (widget, "margin-end", 10, NULL);
135
136 label = gtk_label_new (text);
137 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
138 gtk_box_append (GTK_BOX (widget), label);
139
140 return widget;
141 }
142
143 static void
144 on_stop_search (CcFormatChooser *self)
145 {
146 const char *search_text;
147 search_text = gtk_editable_get_text (GTK_EDITABLE (self->region_filter_entry));
148
149 if (search_text && g_strcmp0 (search_text, "") != 0)
150 gtk_editable_set_text (GTK_EDITABLE (self->region_filter_entry), "");
151 else
152 gtk_window_close (GTK_WINDOW (self));
153 }
154
155 static void
156 format_chooser_back_button_clicked_cb (CcFormatChooser *self)
157 {
158 g_assert (CC_IS_FORMAT_CHOOSER (self));
159
160 gtk_window_set_title (GTK_WINDOW (self), _("Formats"));
161 adw_leaflet_set_visible_child (ADW_LEAFLET (self->main_leaflet), self->region_box);
162 gtk_stack_set_visible_child (GTK_STACK (self->title_buttons), self->cancel_button);
163 gtk_widget_set_visible (self->done_button, TRUE);
164 }
165
166 static void
167 set_preview_button_visible (GtkWidget *row,
168 gboolean visible)
169 {
170 GtkWidget *button;
171
172 button = g_object_get_data (G_OBJECT (row), "preview-button");
173 g_assert (button);
174
175 gtk_widget_set_opacity (button, visible);
176 gtk_widget_set_sensitive (button, visible);
177 }
178
179 static void
180 format_chooser_leaflet_fold_changed_cb (CcFormatChooser *self)
181 {
182 GtkWidget *child;
183 gboolean folded;
184
185 g_assert (CC_IS_FORMAT_CHOOSER (self));
186
187 folded = adw_leaflet_get_folded (ADW_LEAFLET (self->main_leaflet));
188
189 for (child = gtk_widget_get_first_child (self->common_region_listbox);
190 child;
191 child = gtk_widget_get_next_sibling (child))
192 {
193 if (GTK_IS_LIST_BOX_ROW (child))
194 set_preview_button_visible (child, folded);
195 }
196
197 for (child = gtk_widget_get_first_child (self->region_listbox);
198 child;
199 child = gtk_widget_get_next_sibling (child))
200 {
201 if (GTK_IS_LIST_BOX_ROW (child))
202 set_preview_button_visible (child, folded);
203 }
204
205 if (!folded)
206 {
207 cc_format_preview_set_region (self->format_preview, self->region);
208 gtk_window_set_title (GTK_WINDOW (self), _("Formats"));
209 adw_leaflet_set_visible_child (ADW_LEAFLET (self->main_leaflet), self->region_box);
210 gtk_stack_set_visible_child (GTK_STACK (self->title_buttons), self->cancel_button);
211 gtk_widget_set_visible (self->done_button, TRUE);
212 }
213 }
214
215 static void
216 preview_button_clicked_cb (CcFormatChooser *self,
217 GtkWidget *button)
218 {
219 GtkWidget *row;
220 const gchar *region, *locale_name;
221
222 g_assert (CC_IS_FORMAT_CHOOSER (self));
223 g_assert (GTK_IS_WIDGET (button));
224
225 row = gtk_widget_get_ancestor (button, GTK_TYPE_LIST_BOX_ROW);
226 g_assert (row);
227
228 region = g_object_get_data (G_OBJECT (row), "locale-id");
229 locale_name = g_object_get_data (G_OBJECT (row), "locale-name");
230 cc_format_preview_set_region (self->format_preview, region);
231
232 adw_leaflet_set_visible_child (ADW_LEAFLET (self->main_leaflet), self->preview_box);
233 gtk_stack_set_visible_child (GTK_STACK (self->title_buttons), self->back_button);
234 gtk_widget_set_visible (self->done_button, FALSE);
235
236 if (locale_name)
237 gtk_window_set_title (GTK_WINDOW (self), locale_name);
238 }
239
240 static GtkWidget *
241 region_widget_new (CcFormatChooser *self,
242 const gchar *locale_id)
243 {
244 gchar *locale_name;
245 gchar *locale_current_name;
246 gchar *locale_untranslated_name;
247 GtkWidget *row, *box, *button;
248 GtkWidget *check;
249
250 locale_name = gnome_get_country_from_locale (locale_id, locale_id);
251 if (!locale_name)
252 return NULL;
253
254 locale_current_name = gnome_get_country_from_locale (locale_id, NULL);
255 locale_untranslated_name = gnome_get_country_from_locale (locale_id, "C");
256
257 row = gtk_list_box_row_new ();
258 box = padded_label_new (locale_name);
259 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box);
260
261 check = gtk_image_new_from_icon_name ("object-select-symbolic");
262 gtk_widget_set_halign (check, GTK_ALIGN_START);
263 gtk_widget_set_hexpand (check, TRUE);
264 gtk_widget_set_opacity (check, 0.0);
265 gtk_box_append (GTK_BOX (box), check);
266
267 button = gtk_button_new_from_icon_name ("view-reveal-symbolic");
268 gtk_widget_set_tooltip_text (button, _("Preview"));
269 gtk_widget_add_css_class (button, "flat");
270 g_signal_connect_object (button, "clicked", G_CALLBACK (preview_button_clicked_cb),
271 self, G_CONNECT_SWAPPED);
272 gtk_box_append (GTK_BOX (box), button);
273
274 g_object_set_data (G_OBJECT (row), "check", check);
275 g_object_set_data (G_OBJECT (row), "preview-button", button);
276 g_object_set_data_full (G_OBJECT (row), "locale-id", g_strdup (locale_id), g_free);
277 g_object_set_data_full (G_OBJECT (row), "locale-name", locale_name, g_free);
278 g_object_set_data_full (G_OBJECT (row), "locale-current-name", locale_current_name, g_free);
279 g_object_set_data_full (G_OBJECT (row), "locale-untranslated-name", locale_untranslated_name, g_free);
280
281 return row;
282 }
283
284 static void
285 add_regions (CcFormatChooser *chooser,
286 gchar **locale_ids,
287 GHashTable *initial)
288 {
289 g_autoptr(GList) initial_locales = NULL;
290 GtkWidget *widget;
291 GList *l;
292
293 chooser->adding = TRUE;
294 initial_locales = g_hash_table_get_keys (initial);
295
296 /* Populate Common Locales */
297 for (l = initial_locales; l != NULL; l = l->next) {
298 if (!cc_common_language_has_font (l->data))
299 continue;
300
301 widget = region_widget_new (chooser, l->data);
302 if (!widget)
303 continue;
304
305 gtk_list_box_append (GTK_LIST_BOX (chooser->common_region_listbox), widget);
306 }
307
308 /* Populate All locales */
309 while (*locale_ids) {
310 gchar *locale_id;
311
312 locale_id = *locale_ids;
313 locale_ids ++;
314
315 if (!cc_common_language_has_font (locale_id))
316 continue;
317
318 widget = region_widget_new (chooser, locale_id);
319 if (!widget)
320 continue;
321
322 gtk_list_box_append (GTK_LIST_BOX (chooser->region_listbox), widget);
323 }
324
325 chooser->adding = FALSE;
326 }
327
328 static void
329 add_all_regions (CcFormatChooser *chooser)
330 {
331 g_auto(GStrv) locale_ids = NULL;
332 g_autoptr(GHashTable) initial = NULL;
333
334 locale_ids = gnome_get_all_locales ();
335 initial = cc_common_language_get_initial_languages ();
336 add_regions (chooser, locale_ids, initial);
337 }
338
339 static gboolean
340 match_all (gchar **words,
341 const gchar *str)
342 {
343 gchar **w;
344
345 for (w = words; *w; ++w)
346 if (!strstr (str, *w))
347 return FALSE;
348
349 return TRUE;
350 }
351
352 static gboolean
353 region_visible (GtkListBoxRow *row,
354 gpointer user_data)
355 {
356 CcFormatChooser *chooser = user_data;
357 g_autofree gchar *locale_name = NULL;
358 g_autofree gchar *locale_current_name = NULL;
359 g_autofree gchar *locale_untranslated_name = NULL;
360 gboolean match = TRUE;
361
362 if (!chooser->filter_words)
363 goto end;
364
365 locale_name =
366 cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-name"));
367 if (match_all (chooser->filter_words, locale_name))
368 goto end;
369
370 locale_current_name =
371 cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-current-name"));
372 if (match_all (chooser->filter_words, locale_current_name))
373 goto end;
374
375 locale_untranslated_name =
376 cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-untranslated-name"));
377
378 match = match_all (chooser->filter_words, locale_untranslated_name);
379
380 end:
381 if (match)
382 chooser->no_results = FALSE;
383 return match;
384 }
385
386 static void
387 filter_changed (CcFormatChooser *chooser)
388 {
389 g_autofree gchar *filter_contents = NULL;
390 gboolean visible;
391
392 g_clear_pointer (&chooser->filter_words, g_strfreev);
393
394 filter_contents =
395 cc_util_normalize_casefold_and_unaccent (gtk_editable_get_text (GTK_EDITABLE (chooser->region_filter_entry)));
396
397 /* The popular listbox is shown only if search is empty */
398 visible = filter_contents == NULL || *filter_contents == '\0';
399 gtk_widget_set_visible (chooser->common_region_listbox, visible);
400 gtk_widget_set_visible (chooser->common_region_title, visible);
401 gtk_widget_set_visible (chooser->region_title, visible);
402
403 /* Reset cached search state */
404 chooser->no_results = TRUE;
405
406 if (!filter_contents) {
407 gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->region_listbox));
408 gtk_list_box_set_placeholder (GTK_LIST_BOX (chooser->region_listbox), NULL);
409 return;
410 }
411 chooser->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
412 gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->region_listbox));
413
414 if (chooser->no_results)
415 gtk_stack_set_visible_child (GTK_STACK (chooser->region_list_stack),
416 GTK_WIDGET (chooser->empty_results_page));
417 else
418 gtk_stack_set_visible_child (GTK_STACK (chooser->region_list_stack),
419 GTK_WIDGET (chooser->region_list));
420 }
421
422 static void
423 row_activated (CcFormatChooser *chooser,
424 GtkListBoxRow *row)
425 {
426 const gchar *new_locale_id;
427
428 if (chooser->adding)
429 return;
430
431 new_locale_id = g_object_get_data (G_OBJECT (row), "locale-id");
432 if (g_strcmp0 (new_locale_id, chooser->region) == 0) {
433 gtk_dialog_response (GTK_DIALOG (chooser),
434 gtk_dialog_get_response_for_widget (GTK_DIALOG (chooser),
435 chooser->done_button));
436 } else {
437 set_locale_id (chooser, new_locale_id);
438 }
439 }
440
441 static void
442 activate_default (CcFormatChooser *chooser)
443 {
444 GtkWidget *focus;
445 const gchar *locale_id;
446
447 focus = gtk_window_get_focus (GTK_WINDOW (chooser));
448 if (!focus)
449 return;
450
451 locale_id = g_object_get_data (G_OBJECT (focus), "locale-id");
452 if (g_strcmp0 (locale_id, chooser->region) == 0)
453 return;
454
455 g_signal_stop_emission_by_name (chooser, "activate-default");
456 gtk_widget_activate (focus);
457 }
458
459 static void
460 cc_format_chooser_dispose (GObject *object)
461 {
462 CcFormatChooser *chooser = CC_FORMAT_CHOOSER (object);
463
464 g_clear_pointer (&chooser->filter_words, g_strfreev);
465 g_clear_pointer (&chooser->region, g_free);
466
467 G_OBJECT_CLASS (cc_format_chooser_parent_class)->dispose (object);
468 }
469
470 void
471 cc_format_chooser_class_init (CcFormatChooserClass *klass)
472 {
473 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
474 GObjectClass *object_class = G_OBJECT_CLASS (klass);
475
476 object_class->dispose = cc_format_chooser_dispose;
477
478 g_type_ensure (CC_TYPE_FORMAT_PREVIEW);
479
480 gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL);
481
482 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/system/region/cc-format-chooser.ui");
483
484 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, title_bar);
485 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, title_buttons);
486 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, cancel_button);
487 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, back_button);
488 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, done_button);
489 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, main_leaflet);
490 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_filter_entry);
491 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, common_region_title);
492 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, common_region_listbox);
493 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_box);
494 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_title);
495 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_listbox);
496 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_list);
497 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, region_list_stack);
498 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, preview_box);
499 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, empty_results_page);
500 gtk_widget_class_bind_template_child (widget_class, CcFormatChooser, format_preview);
501
502 gtk_widget_class_bind_template_callback (widget_class, format_chooser_back_button_clicked_cb);
503 gtk_widget_class_bind_template_callback (widget_class, format_chooser_leaflet_fold_changed_cb);
504 gtk_widget_class_bind_template_callback (widget_class, filter_changed);
505 gtk_widget_class_bind_template_callback (widget_class, row_activated);
506 gtk_widget_class_bind_template_callback (widget_class, on_stop_search);
507 }
508
509 void
510 cc_format_chooser_init (CcFormatChooser *chooser)
511 {
512 gtk_widget_init_template (GTK_WIDGET (chooser));
513
514 gtk_list_box_set_sort_func (GTK_LIST_BOX (chooser->common_region_listbox),
515 (GtkListBoxSortFunc)sort_regions, chooser, NULL);
516 gtk_list_box_set_sort_func (GTK_LIST_BOX (chooser->region_listbox),
517 (GtkListBoxSortFunc)sort_regions, chooser, NULL);
518 gtk_list_box_set_filter_func (GTK_LIST_BOX (chooser->region_listbox),
519 region_visible, chooser, NULL);
520
521 add_all_regions (chooser);
522 gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->region_listbox));
523 format_chooser_leaflet_fold_changed_cb (chooser);
524
525 g_signal_connect_object (chooser, "activate-default",
526 G_CALLBACK (activate_default), chooser, G_CONNECT_SWAPPED);
527 }
528
529 CcFormatChooser *
530 cc_format_chooser_new (void)
531 {
532 return CC_FORMAT_CHOOSER (g_object_new (CC_TYPE_FORMAT_CHOOSER,
533 "use-header-bar", 1,
534 NULL));
535 }
536
537 void
538 cc_format_chooser_clear_filter (CcFormatChooser *chooser)
539 {
540 g_return_if_fail (CC_IS_FORMAT_CHOOSER (chooser));
541 gtk_editable_set_text (GTK_EDITABLE (chooser->region_filter_entry), "");
542 }
543
544 const gchar *
545 cc_format_chooser_get_region (CcFormatChooser *chooser)
546 {
547 g_return_val_if_fail (CC_IS_FORMAT_CHOOSER (chooser), NULL);
548 return chooser->region;
549 }
550
551 void
552 cc_format_chooser_set_region (CcFormatChooser *chooser,
553 const gchar *region)
554 {
555 g_return_if_fail (CC_IS_FORMAT_CHOOSER (chooser));
556 set_locale_id (chooser, region);
557 }
558