GCC Code Coverage Report


Directory: ./
File: shell/cc-shell-model.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 166 0.0%
Functions: 0 21 0.0%
Branches: 0 113 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2009, 2010 Intel, Inc.
3 * Copyright (c) 2010 Red Hat, Inc.
4 *
5 * The Control Center is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * The Control Center is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with the Control Center; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Author: Thomas Wood <thos@gnome.org>
20 */
21
22 #include "cc-shell-model.h"
23 #include "cc-util.h"
24
25 #include <string.h>
26
27 #include <gio/gdesktopappinfo.h>
28
29 struct _CcShellModel
30 {
31 GtkListStore parent;
32
33 GStrv sort_terms;
34 };
35
36 G_DEFINE_TYPE (CcShellModel, cc_shell_model, GTK_TYPE_LIST_STORE)
37
38 static gint
39 sort_by_name (GtkTreeModel *model,
40 GtkTreeIter *a,
41 GtkTreeIter *b)
42 {
43 g_autofree gchar *a_name = NULL;
44 g_autofree gchar *b_name = NULL;
45
46 gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1);
47 gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1);
48
49 return g_strcmp0 (a_name, b_name);
50 }
51
52 static gint
53 sort_by_name_with_terms (GtkTreeModel *model,
54 GtkTreeIter *a,
55 GtkTreeIter *b,
56 gchar **terms)
57 {
58 gboolean a_match, b_match;
59 g_autofree gchar *a_name = NULL;
60 g_autofree gchar *b_name = NULL;
61 gint i;
62
63 gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1);
64 gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1);
65
66 for (i = 0; terms[i]; ++i)
67 {
68 a_match = strstr (a_name, terms[i]) != NULL;
69 b_match = strstr (b_name, terms[i]) != NULL;
70
71 if (a_match && !b_match)
72 return -1;
73 else if (!a_match && b_match)
74 return 1;
75 }
76
77 return 0;
78 }
79
80 static gint
81 count_matches (gchar **keywords,
82 gchar **terms)
83 {
84 gint i, j, c;
85
86 if (!keywords || !terms)
87 return 0;
88
89 c = 0;
90
91 for (i = 0; terms[i]; ++i)
92 for (j = 0; keywords[j]; ++j)
93 if (strstr (keywords[j], terms[i]))
94 c += 1;
95
96 return c;
97 }
98
99 static gint
100 sort_by_keywords_with_terms (GtkTreeModel *model,
101 GtkTreeIter *a,
102 GtkTreeIter *b,
103 gchar **terms)
104 {
105 gint a_matches, b_matches;
106 g_auto(GStrv) a_keywords = NULL;
107 g_auto(GStrv) b_keywords = NULL;
108
109 gtk_tree_model_get (model, a, COL_KEYWORDS, &a_keywords, -1);
110 gtk_tree_model_get (model, b, COL_KEYWORDS, &b_keywords, -1);
111
112 a_matches = count_matches (a_keywords, terms);
113 b_matches = count_matches (b_keywords, terms);
114
115 if (a_matches > b_matches)
116 return -1;
117 else if (a_matches < b_matches)
118 return 1;
119
120 return 0;
121 }
122
123 static gint
124 sort_by_description_with_terms (GtkTreeModel *model,
125 GtkTreeIter *a,
126 GtkTreeIter *b,
127 gchar **terms)
128 {
129 gint a_matches, b_matches;
130 g_autofree gchar *a_description = NULL;
131 g_autofree gchar *b_description = NULL;
132 g_auto(GStrv) a_description_split = NULL;
133 g_auto(GStrv) b_description_split = NULL;
134
135 gtk_tree_model_get (model, a, COL_DESCRIPTION, &a_description, -1);
136 gtk_tree_model_get (model, b, COL_DESCRIPTION, &b_description, -1);
137
138 if (a_description && !b_description)
139 return -1;
140 else if (!a_description && b_description)
141 return 1;
142 else if (!a_description && !b_description)
143 return 0;
144
145 a_description_split = g_strsplit (a_description, " ", -1);
146 b_description_split = g_strsplit (b_description, " ", -1);
147
148 a_matches = count_matches (a_description_split, terms);
149 b_matches = count_matches (b_description_split, terms);
150
151 if (a_matches > b_matches)
152 return -1;
153 else if (a_matches < b_matches)
154 return 1;
155
156 return 0;
157 }
158
159 static gint
160 sort_with_terms (GtkTreeModel *model,
161 GtkTreeIter *a,
162 GtkTreeIter *b,
163 gchar **terms)
164 {
165 gint rval;
166
167 rval = sort_by_name_with_terms (model, a, b, terms);
168 if (rval)
169 return rval;
170
171 rval = sort_by_keywords_with_terms (model, a, b, terms);
172 if (rval)
173 return rval;
174
175 rval = sort_by_description_with_terms (model, a, b, terms);
176 if (rval)
177 return rval;
178
179 return sort_by_name (model, a, b);
180 }
181
182 static gint
183 cc_shell_model_sort_func (GtkTreeModel *model,
184 GtkTreeIter *a,
185 GtkTreeIter *b,
186 gpointer data)
187 {
188 CcShellModel *self = data;
189
190 if (!self->sort_terms || !self->sort_terms[0])
191 return sort_by_name (model, a, b);
192 else
193 return sort_with_terms (model, a, b, self->sort_terms);
194 }
195
196 static void
197 cc_shell_model_finalize (GObject *object)
198 {
199 CcShellModel *self = CC_SHELL_MODEL (object);
200
201 g_clear_pointer (&self->sort_terms, g_strfreev);
202
203 G_OBJECT_CLASS (cc_shell_model_parent_class)->finalize (object);
204 }
205
206 static void
207 cc_shell_model_class_init (CcShellModelClass *klass)
208 {
209 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
210
211 gobject_class->finalize = cc_shell_model_finalize;
212 }
213
214 static void
215 cc_shell_model_init (CcShellModel *self)
216 {
217 GType types[] = {G_TYPE_STRING, G_TYPE_STRING, G_TYPE_APP_INFO, G_TYPE_STRING, G_TYPE_UINT,
218 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_ICON, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_BOOLEAN };
219
220 gtk_list_store_set_column_types (GTK_LIST_STORE (self),
221 N_COLS, types);
222
223 gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self),
224 cc_shell_model_sort_func,
225 self, NULL);
226 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
227 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
228 GTK_SORT_ASCENDING);
229 }
230
231 CcShellModel *
232 cc_shell_model_new (void)
233 {
234 return g_object_new (CC_TYPE_SHELL_MODEL, NULL);
235 }
236
237 static char **
238 get_casefolded_keywords (GAppInfo *appinfo)
239 {
240 const char * const * keywords;
241 char **casefolded_keywords;
242 int i, n;
243
244 keywords = g_desktop_app_info_get_keywords (G_DESKTOP_APP_INFO (appinfo));
245 n = keywords ? g_strv_length ((char**) keywords) : 0;
246 casefolded_keywords = g_new (char*, n+1);
247
248 for (i = 0; i < n; i++)
249 casefolded_keywords[i] = cc_util_normalize_casefold_and_unaccent (keywords[i]);
250 casefolded_keywords[n] = NULL;
251
252 return casefolded_keywords;
253 }
254
255 static GIcon *
256 symbolicize_g_icon (GIcon *gicon)
257 {
258 const gchar * const *names;
259 g_autofree gchar *new_name = NULL;
260
261 if (!G_IS_THEMED_ICON (gicon))
262 return g_object_ref (gicon);
263
264 names = g_themed_icon_get_names (G_THEMED_ICON (gicon));
265
266 if (g_str_has_suffix (names[0], "-symbolic"))
267 return g_object_ref (gicon);
268
269 new_name = g_strdup_printf ("%s-symbolic", names[0]);
270 return g_themed_icon_new_with_default_fallbacks (new_name);
271 }
272
273 void
274 cc_shell_model_add_item (CcShellModel *model,
275 CcPanelCategory category,
276 GAppInfo *appinfo,
277 const char *id)
278 {
279 g_autoptr(GIcon) icon = NULL;
280 const gchar *name = g_app_info_get_name (appinfo);
281 const gchar *comment = g_app_info_get_description (appinfo);
282 g_auto(GStrv) keywords = NULL;
283 g_autofree gchar *casefolded_name = NULL;
284 g_autofree gchar *casefolded_description = NULL;
285
286 casefolded_name = cc_util_normalize_casefold_and_unaccent (name);
287 casefolded_description = cc_util_normalize_casefold_and_unaccent (comment);
288 keywords = get_casefolded_keywords (appinfo);
289 icon = symbolicize_g_icon (g_app_info_get_icon (appinfo));
290
291 gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, 0,
292 COL_NAME, name,
293 COL_CASEFOLDED_NAME, casefolded_name,
294 COL_APP, appinfo,
295 COL_ID, id,
296 COL_CATEGORY, category,
297 COL_DESCRIPTION, comment,
298 COL_CASEFOLDED_DESCRIPTION, casefolded_description,
299 COL_GICON, icon,
300 COL_KEYWORDS, keywords,
301 COL_VISIBILITY, CC_PANEL_VISIBLE,
302 -1);
303 }
304
305 gboolean
306 cc_shell_model_has_panel (CcShellModel *model,
307 const char *id)
308 {
309 GtkTreeIter iter;
310 gboolean valid;
311
312 g_assert (id);
313
314 valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
315 while (valid)
316 {
317 g_autofree gchar *panel_id = NULL;
318
319 gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, COL_ID, &panel_id, -1);
320 if (g_str_equal (id, panel_id))
321 return TRUE;
322
323 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
324 }
325
326 return FALSE;
327 }
328
329 gboolean
330 cc_shell_model_iter_matches_search (CcShellModel *model,
331 GtkTreeIter *iter,
332 const char *term)
333 {
334 g_autofree gchar *name = NULL;
335 g_autofree gchar *description = NULL;
336 gboolean result;
337 g_auto(GStrv) keywords = NULL;
338
339 gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
340 COL_CASEFOLDED_NAME, &name,
341 COL_CASEFOLDED_DESCRIPTION, &description,
342 COL_KEYWORDS, &keywords,
343 -1);
344
345 result = (strstr (name, term) != NULL);
346
347 if (!result && description)
348 result = (strstr (description, term) != NULL);
349
350 if (!result && keywords)
351 {
352 gint i;
353
354 for (i = 0; !result && keywords[i]; i++)
355 result = (strstr (keywords[i], term) == keywords[i]);
356 }
357
358 return result;
359 }
360
361 void
362 cc_shell_model_set_sort_terms (CcShellModel *self,
363 gchar **terms)
364 {
365 g_return_if_fail (CC_IS_SHELL_MODEL (self));
366
367 g_clear_pointer (&self->sort_terms, g_strfreev);
368 self->sort_terms = g_strdupv (terms);
369
370 /* trigger a re-sort */
371 gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self),
372 cc_shell_model_sort_func,
373 self,
374 NULL);
375 }
376
377 void
378 cc_shell_model_set_panel_visibility (CcShellModel *self,
379 const gchar *id,
380 CcPanelVisibility visibility)
381 {
382 GtkTreeModel *model;
383 GtkTreeIter iter;
384 gboolean valid;
385
386 g_return_if_fail (CC_IS_SHELL_MODEL (self));
387
388 model = GTK_TREE_MODEL (self);
389
390 /* Find the iter for the panel with the given id */
391 valid = gtk_tree_model_get_iter_first (model, &iter);
392
393 while (valid)
394 {
395 g_autofree gchar *item_id = NULL;
396
397 gtk_tree_model_get (model, &iter, COL_ID, &item_id, -1);
398
399 /* Found the iter */
400 if (g_str_equal (id, item_id))
401 break;
402
403 /* If not found, continue */
404 valid = gtk_tree_model_iter_next (model, &iter);
405 }
406
407 /* If we don't find any panel with the given id, we'll iterate until
408 * valid == FALSE, so we can use this variable to determine if the
409 * panel was found or not. It is a programming error to try to set
410 * the visibility of a non-existent panel.
411 */
412 g_assert (valid);
413
414 gtk_list_store_set (GTK_LIST_STORE (self), &iter, COL_VISIBILITY, visibility, -1);
415 }
416