GCC Code Coverage Report


Directory: ./
File: panels/system/datetime/cc-tz-dialog.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 129 0.0%
Functions: 0 17 0.0%
Branches: 0 79 0.0%

Line Branch Exec Source
1 /* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* cc-tz-dialog.c
3 *
4 * Copyright 2022 Purism SPC
5 * Copyright 2022 Mohammed Sadiq <sadiq@sadiqpk.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * Author(s):
21 * Mohammed Sadiq <sadiq@sadiqpk.org>
22 *
23 * SPDX-License-Identifier: GPL-3.0-or-later
24 */
25
26 #undef G_LOG_DOMAIN
27 #define G_LOG_DOMAIN "cc-tz-dialog"
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #define _GNU_SOURCE
34 #include <string.h>
35 #include <glib/gi18n.h>
36
37 #include "cc-tz-dialog.h"
38 #include "tz.h"
39
40 struct _CcTzDialog
41 {
42 AdwDialog parent_instance;
43
44 GtkSearchEntry *location_entry;
45
46 GtkStack *main_stack;
47 AdwStatusPage *empty_page;
48 GtkScrolledWindow *tz_page;
49 GtkListView *tz_view;
50
51 TzDB *tz_db;
52 GListStore *tz_store;
53 GtkFilterListModel *tz_filtered_model;
54 GtkNoSelection *tz_selection_model;
55
56 CcTzItem *selected_item;
57 };
58
59 G_DEFINE_TYPE (CcTzDialog, cc_tz_dialog, ADW_TYPE_DIALOG)
60
61 enum {
62 TZ_SELECTED,
63 N_SIGNALS
64 };
65
66 static guint signals[N_SIGNALS];
67
68 static gboolean
69 match_tz_item (CcTzItem *item,
70 CcTzDialog *self)
71 {
72 g_auto(GStrv) strv = NULL;
73 g_autofree char *country = NULL;
74 g_autofree char *name = NULL;
75 g_autofree char *zone = NULL;
76 const char *search_terms;
77
78 g_assert (CC_IS_TZ_ITEM (item));
79 g_assert (CC_IS_TZ_DIALOG (self));
80
81 search_terms = gtk_editable_get_text (GTK_EDITABLE (self->location_entry));
82
83 if (!search_terms || !*search_terms)
84 return TRUE;
85
86 g_object_get (item,
87 "country", &country,
88 "name", &name,
89 "zone", &zone,
90 NULL);
91
92 if (!name || !zone || !country)
93 return FALSE;
94
95 /* Search for each word separated by spaces */
96 strv = g_strsplit (search_terms, " ", 0);
97
98 /*
99 * List the item only if the value contain each word.
100 * ie, for a search "as kol" it will match "Asia/Kolkata"
101 * not "Asia/Karachi"
102 */
103 for (guint i = 0; strv[i]; i++)
104 {
105 const char *str = strv[i];
106
107 if (!str || !*str)
108 continue;
109
110 if (!strcasestr (name, str) &&
111 !strcasestr (zone, str) &&
112 !strcasestr (country, str))
113 return FALSE;
114 }
115
116 return TRUE;
117 }
118
119 static void
120 load_tz (CcTzDialog *self)
121 {
122 GPtrArray *locations;
123
124 g_assert (CC_IS_TZ_DIALOG (self));
125
126 self->tz_db = tz_load_db ();
127 g_assert (self->tz_db);
128
129 locations = tz_get_locations (self->tz_db);
130 g_assert (locations);
131
132 for (guint i = 0; i < locations->len; i++)
133 {
134 g_autoptr(CcTzItem) item = NULL;
135 TzLocation *location;
136
137 location = locations->pdata[i];
138 item = cc_tz_item_new (location);
139
140 g_list_store_append (self->tz_store, item);
141 }
142 }
143
144 static void
145 tz_selection_model_changed_cb (CcTzDialog *self)
146 {
147 guint n_items;
148
149 g_assert (CC_IS_TZ_DIALOG (self));
150
151 n_items = g_list_model_get_n_items (G_LIST_MODEL (self->tz_selection_model));
152
153 if (n_items)
154 gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->tz_page));
155 else
156 gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->empty_page));
157 }
158
159 static void
160 tz_dialog_search_changed_cb (CcTzDialog *self)
161 {
162 GtkFilter *filter;
163
164 g_assert (CC_IS_TZ_DIALOG (self));
165
166 filter = gtk_filter_list_model_get_filter (self->tz_filtered_model);
167
168 gtk_filter_changed (filter, GTK_FILTER_CHANGE_DIFFERENT);
169 }
170
171 static void
172 tz_dialog_search_stopped_cb (CcTzDialog *self)
173 {
174 const char *search_text;
175 search_text = gtk_editable_get_text (GTK_EDITABLE (self->location_entry));
176
177 if (search_text && g_strcmp0 (search_text, "") != 0)
178 gtk_editable_set_text (GTK_EDITABLE (self->location_entry), "");
179 else
180 adw_dialog_close (ADW_DIALOG (self));
181 }
182
183 static void
184 tz_dialog_row_activated_cb (CcTzDialog *self,
185 guint position)
186 {
187 GListModel *model;
188
189 g_assert (CC_IS_TZ_DIALOG (self));
190
191 g_clear_object (&self->selected_item);
192
193 model = G_LIST_MODEL (self->tz_selection_model);
194 self->selected_item = g_list_model_get_item (model, position);
195
196 adw_dialog_close (ADW_DIALOG (self));
197 g_signal_emit (self, signals[TZ_SELECTED], 0);
198 }
199
200 static void
201 cc_tz_dialog_map (GtkWidget *widget)
202 {
203 CcTzDialog *self = (CcTzDialog *)widget;
204
205 gtk_editable_set_text (GTK_EDITABLE (self->location_entry), "");
206 gtk_widget_grab_focus (GTK_WIDGET (self->location_entry));
207
208 GTK_WIDGET_CLASS (cc_tz_dialog_parent_class)->map (widget);
209 }
210
211 static void
212 cc_tz_dialog_finalize (GObject *object)
213 {
214 CcTzDialog *self = (CcTzDialog *)object;
215
216 g_clear_object (&self->tz_store);
217 g_clear_pointer (&self->tz_db, tz_db_free);
218
219 G_OBJECT_CLASS (cc_tz_dialog_parent_class)->finalize (object);
220 }
221
222 static void
223 cc_tz_dialog_class_init (CcTzDialogClass *klass)
224 {
225 GObjectClass *object_class = G_OBJECT_CLASS (klass);
226 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
227
228 object_class->finalize = cc_tz_dialog_finalize;
229
230 widget_class->map = cc_tz_dialog_map;
231
232 signals[TZ_SELECTED] =
233 g_signal_new ("tz-selected",
234 G_TYPE_FROM_CLASS (klass),
235 G_SIGNAL_RUN_LAST,
236 0, NULL, NULL, NULL,
237 G_TYPE_NONE, 0);
238
239 gtk_widget_class_set_template_from_resource (widget_class,
240 "/org/gnome/control-center/"
241 "system/datetime/cc-tz-dialog.ui");
242
243 gtk_widget_class_bind_template_child (widget_class, CcTzDialog, location_entry);
244
245 gtk_widget_class_bind_template_child (widget_class, CcTzDialog, main_stack);
246 gtk_widget_class_bind_template_child (widget_class, CcTzDialog, empty_page);
247 gtk_widget_class_bind_template_child (widget_class, CcTzDialog, tz_page);
248 gtk_widget_class_bind_template_child (widget_class, CcTzDialog, tz_view);
249
250 gtk_widget_class_bind_template_callback (widget_class, tz_dialog_search_changed_cb);
251 gtk_widget_class_bind_template_callback (widget_class, tz_dialog_search_stopped_cb);
252 gtk_widget_class_bind_template_callback (widget_class, tz_dialog_row_activated_cb);
253 }
254
255 static void
256 cc_tz_dialog_init (CcTzDialog *self)
257 {
258 GtkSortListModel *tz_sorted_model;
259 GtkExpression *expression;
260 GtkSorter *sorter;
261 GtkFilter *filter;
262
263 gtk_widget_init_template (GTK_WIDGET (self));
264
265 self->tz_store = g_list_store_new (CC_TYPE_TZ_ITEM);
266 load_tz (self);
267
268 /* Sort items by name */
269 expression = gtk_property_expression_new (CC_TYPE_TZ_ITEM, NULL, "name");
270 sorter = GTK_SORTER (gtk_string_sorter_new (expression));
271 tz_sorted_model = gtk_sort_list_model_new (G_LIST_MODEL (self->tz_store), sorter);
272
273 filter = (GtkFilter *)gtk_custom_filter_new ((GtkCustomFilterFunc) match_tz_item, self, NULL);
274 self->tz_filtered_model = gtk_filter_list_model_new (G_LIST_MODEL (tz_sorted_model), filter);
275 self->tz_selection_model = gtk_no_selection_new (G_LIST_MODEL (self->tz_filtered_model));
276
277 g_signal_connect_object (self->tz_selection_model, "items-changed",
278 G_CALLBACK (tz_selection_model_changed_cb),
279 self, G_CONNECT_SWAPPED);
280 tz_selection_model_changed_cb (self);
281
282 gtk_list_view_set_model (self->tz_view, GTK_SELECTION_MODEL (self->tz_selection_model));
283 }
284
285 GtkWidget *
286 cc_tz_dialog_new (void)
287 {
288 return g_object_new (CC_TYPE_TZ_DIALOG, NULL);
289 }
290
291 gboolean
292 cc_tz_dialog_set_tz (CcTzDialog *self,
293 const char *timezone)
294 {
295 g_autofree gchar *tz = NULL;
296 guint n_items;
297
298 g_return_val_if_fail (CC_IS_TZ_DIALOG (self), FALSE);
299 g_return_val_if_fail (timezone && *timezone, FALSE);
300
301 n_items = g_list_model_get_n_items (G_LIST_MODEL (self->tz_store));
302 tz = tz_info_get_clean_name (self->tz_db, timezone);
303
304 for (guint i = 0; i < n_items; i++)
305 {
306 g_autoptr(CcTzItem) item = NULL;
307 TzLocation *loc;
308
309 item = g_list_model_get_item (G_LIST_MODEL (self->tz_store), i);
310 loc = cc_tz_item_get_location (item);
311
312 if (g_strcmp0 (loc->zone, tz ? tz : timezone) == 0)
313 {
314 g_set_object (&self->selected_item, item);
315
316 return TRUE;
317 }
318 }
319
320 return FALSE;
321 }
322
323 CcTzItem *
324 cc_tz_dialog_get_selected_tz (CcTzDialog *self)
325 {
326 g_return_val_if_fail (CC_IS_TZ_DIALOG (self), NULL);
327
328 return self->selected_item;
329 }
330
331 TzLocation *
332 cc_tz_dialog_get_selected_location (CcTzDialog *self)
333 {
334 g_return_val_if_fail (CC_IS_TZ_DIALOG (self), NULL);
335
336 if (!self->selected_item)
337 return NULL;
338
339 return cc_tz_item_get_location (self->selected_item);
340 }
341