GCC Code Coverage Report


Directory: ./
File: panels/search/cc-search-panel.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 304 0.0%
Functions: 0 28 0.0%
Branches: 0 129 0.0%

Line Branch Exec Source
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 *
3 * Copyright (C) 2012 Red Hat, Inc
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Cosimo Cecchi <cosimoc@gnome.org>
19 */
20
21 #include "cc-list-row.h"
22 #include "cc-search-panel.h"
23 #include "cc-search-panel-row.h"
24 #include "cc-search-locations-dialog.h"
25 #include "cc-search-resources.h"
26
27 #include <gio/gdesktopappinfo.h>
28 #include <glib/gi18n.h>
29
30 struct _CcSearchPanel
31 {
32 CcPanel parent_instance;
33
34 GtkWidget *list_box;
35 AdwSwitchRow *app_search_row;
36 GtkWidget *search_group;
37 GtkWidget *settings_row;
38 CcSearchPanelRow *selected_row;
39
40 GSettings *search_settings;
41 GHashTable *sort_order;
42
43 CcSearchLocationsDialog *locations_dialog;
44 };
45
46 CC_PANEL_REGISTER (CcSearchPanel, cc_search_panel)
47
48 enum
49 {
50 PROP_0,
51 PROP_PARAMETERS
52 };
53
54 #define SHELL_PROVIDER_GROUP "Shell Search Provider"
55 #define SEARCH_LOCATIONS_DIALOG_PARAM "locations"
56
57 static gboolean
58 keynav_failed_cb (CcSearchPanel *self, GtkDirectionType direction, GtkWidget *list)
59 {
60 GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));
61
62 if (!toplevel)
63 return FALSE;
64
65 if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN)
66 return FALSE;
67
68 return gtk_widget_child_focus (toplevel, direction == GTK_DIR_UP ?
69 GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
70 }
71
72 static gint
73 list_sort_func (gconstpointer a,
74 gconstpointer b,
75 gpointer user_data)
76 {
77 CcSearchPanel *self = user_data;
78 GAppInfo *app_a, *app_b;
79 const gchar *id_a, *id_b;
80 gint idx_a, idx_b;
81 gpointer lookup;
82
83 app_a = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW ((gpointer*)a));
84 app_b = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW ((gpointer*)b));
85
86 id_a = g_app_info_get_id (app_a);
87 id_b = g_app_info_get_id (app_b);
88
89 /* find the index of the application in the GSettings preferences */
90 idx_a = -1;
91 idx_b = -1;
92
93 lookup = g_hash_table_lookup (self->sort_order, id_a);
94 if (lookup)
95 idx_a = GPOINTER_TO_INT (lookup) - 1;
96
97 lookup = g_hash_table_lookup (self->sort_order, id_b);
98 if (lookup)
99 idx_b = GPOINTER_TO_INT (lookup) - 1;
100
101 /* if neither app is found, use alphabetical order */
102 if ((idx_a == -1) && (idx_b == -1))
103 return g_utf8_collate (g_app_info_get_name (app_a), g_app_info_get_name (app_b));
104
105 /* if app_a isn't found, it's sorted after app_b */
106 if (idx_a == -1)
107 return 1;
108
109 /* if app_b isn't found, it's sorted after app_a */
110 if (idx_b == -1)
111 return -1;
112
113 /* finally, if both apps are found, return their order in the list */
114 return (idx_a - idx_b);
115 }
116
117 static void
118 search_panel_invalidate_sort_order (CcSearchPanel *self)
119 {
120 g_auto(GStrv) sort_order = NULL;
121 gint idx;
122
123 g_hash_table_remove_all (self->sort_order);
124 sort_order = g_settings_get_strv (self->search_settings, "sort-order");
125
126 for (idx = 0; sort_order[idx] != NULL; idx++)
127 g_hash_table_insert (self->sort_order, g_strdup (sort_order[idx]), GINT_TO_POINTER (idx + 1));
128
129 gtk_list_box_invalidate_sort (GTK_LIST_BOX (self->list_box));
130 }
131
132 static gint
133 propagate_compare_func (gconstpointer a,
134 gconstpointer b,
135 gpointer user_data)
136 {
137 CcSearchPanel *self = user_data;
138 const gchar *key_a = a, *key_b = b;
139 gint idx_a, idx_b;
140
141 idx_a = GPOINTER_TO_INT (g_hash_table_lookup (self->sort_order, key_a));
142 idx_b = GPOINTER_TO_INT (g_hash_table_lookup (self->sort_order, key_b));
143
144 return (idx_a - idx_b);
145 }
146
147 static void
148 search_panel_propagate_sort_order (CcSearchPanel *self)
149 {
150 g_autoptr(GList) keys = NULL;
151 GList *l;
152 g_autoptr(GPtrArray) sort_order = NULL;
153
154 sort_order = g_ptr_array_new ();
155 keys = g_hash_table_get_keys (self->sort_order);
156 keys = g_list_sort_with_data (keys, propagate_compare_func, self);
157
158 for (l = keys; l != NULL; l = l->next)
159 g_ptr_array_add (sort_order, l->data);
160
161 g_ptr_array_add (sort_order, NULL);
162 g_settings_set_strv (self->search_settings, "sort-order",
163 (const gchar **) sort_order->pdata);
164 }
165
166 static void
167 search_panel_move_selected (CcSearchPanel *self,
168 gboolean down)
169 {
170 GtkListBoxRow *other_row;
171 GAppInfo *app_info, *other_app_info;
172 const gchar *app_id, *other_app_id;
173 const gchar *last_good_app, *target_app;
174 GtkWidget *aux;
175 gint idx, other_idx;
176 gpointer idx_ptr;
177 gboolean found;
178
179 app_info = cc_search_panel_row_get_app_info (self->selected_row);
180 app_id = g_app_info_get_id (app_info);
181
182 /* The assertions are valid only as long as we don't move the first
183 or the last item. */
184
185 aux = GTK_WIDGET (self->selected_row);
186 other_row = down ? GTK_LIST_BOX_ROW (gtk_widget_get_next_sibling (aux)) :
187 GTK_LIST_BOX_ROW (gtk_widget_get_prev_sibling (aux));
188
189 other_app_info = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW (other_row));
190 other_app_id = g_app_info_get_id (other_app_info);
191
192 g_assert (other_app_id != NULL);
193
194 /* Check if we're moving one of the unsorted providers at the end of
195 the list; in that case, the value we obtain from the sort order table
196 is garbage.
197 We need to find the last app with a valid sort order, and
198 then set the sort order on all intermediate apps until we find the
199 one we want to move, if moving up, or the neighbor, if moving down.
200 */
201 last_good_app = target_app = app_id;
202 found = g_hash_table_lookup_extended (self->sort_order, last_good_app, NULL, &idx_ptr);
203 while (!found)
204 {
205 GAppInfo *tmp;
206 const char *tmp_id;
207
208 aux = gtk_widget_get_prev_sibling (aux);
209 if (aux == NULL)
210 {
211 last_good_app = NULL;
212 break;
213 }
214
215 tmp = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW (aux));
216 tmp_id = g_app_info_get_id (tmp);
217
218 last_good_app = tmp_id;
219 found = g_hash_table_lookup_extended (self->sort_order, tmp_id, NULL, &idx_ptr);
220 }
221
222 /* For simplicity's sake, set all sort orders to the previously visible state
223 first, and only then do the modification requested.
224
225 The loop actually sets the sort order on last_good_app even if we found a
226 valid one already, but I preferred to keep the logic simple, at the expense
227 of a small performance penalty.
228 */
229 if (found)
230 {
231 idx = GPOINTER_TO_INT (idx_ptr);
232 }
233 else
234 {
235 /* If not found, there is no configured app that has a sort order, so we start
236 from the first position and walk the entire list.
237 Sort orders are 1 based, so that 0 (NULL) is not a valid value.
238 */
239 idx = 1;
240 aux = gtk_widget_get_first_child (GTK_WIDGET (self->list_box));
241 }
242
243 while (last_good_app != target_app)
244 {
245 GAppInfo *tmp;
246 const char *tmp_id;
247
248 tmp = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW (aux));
249 tmp_id = g_app_info_get_id (tmp);
250
251 g_hash_table_replace (self->sort_order, g_strdup (tmp_id), GINT_TO_POINTER (idx));
252
253 aux = gtk_widget_get_next_sibling (aux);
254 idx++;
255 last_good_app = tmp_id;
256 }
257
258 other_idx = GPOINTER_TO_INT (g_hash_table_lookup (self->sort_order, app_id));
259 idx = down ? (other_idx + 1) : (other_idx - 1);
260
261 g_hash_table_replace (self->sort_order, g_strdup (other_app_id), GINT_TO_POINTER (other_idx));
262 g_hash_table_replace (self->sort_order, g_strdup (app_id), GINT_TO_POINTER (idx));
263
264 search_panel_propagate_sort_order (self);
265 }
266
267 static void
268 row_moved_cb (CcSearchPanel *self,
269 CcSearchPanelRow *dest_row,
270 CcSearchPanelRow *row)
271 {
272 gint source_idx = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (row));
273 gint dest_idx = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (dest_row));
274 gboolean down;
275
276 self->selected_row = row;
277
278 down = (source_idx - dest_idx) < 0;
279 for (int i = 0; i < ABS (source_idx - dest_idx); i++)
280 search_panel_move_selected (self, down);
281 }
282
283 static void
284 show_search_locations_dialog (CcSearchPanel *self)
285 {
286 CcShell *shell;
287 GtkWidget *toplevel;
288
289 if (self->locations_dialog == NULL)
290 {
291 self->locations_dialog = cc_search_locations_dialog_new ();
292 g_object_add_weak_pointer (G_OBJECT (self->locations_dialog),
293 (gpointer *) &self->locations_dialog);
294
295 shell = cc_panel_get_shell (CC_PANEL (self));
296 toplevel = cc_shell_get_toplevel (shell);
297 gtk_window_set_transient_for (GTK_WINDOW (self->locations_dialog), GTK_WINDOW (toplevel));
298 }
299
300 gtk_window_present (GTK_WINDOW (self->locations_dialog));
301 }
302
303 static GVariant *
304 switch_settings_mapping_set_generic (const GValue *value,
305 const GVariantType *expected_type,
306 GtkWidget *row,
307 gboolean default_enabled)
308 {
309 CcSearchPanel *self = g_object_get_data (G_OBJECT (row), "self");
310 GAppInfo *app_info = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW (row));
311 g_auto(GStrv) apps = NULL;
312 g_autoptr(GPtrArray) new_apps = NULL;
313 gint idx;
314 gboolean remove, found;
315
316 remove = !!g_value_get_boolean (value) == !!default_enabled;
317 found = FALSE;
318 new_apps = g_ptr_array_new_with_free_func (g_free);
319 apps = g_settings_get_strv (self->search_settings,
320 default_enabled ? "disabled" : "enabled");
321
322 for (idx = 0; apps[idx] != NULL; idx++)
323 {
324 if (g_strcmp0 (apps[idx], g_app_info_get_id (app_info)) == 0)
325 {
326 found = TRUE;
327
328 if (remove)
329 continue;
330 }
331
332 g_ptr_array_add (new_apps, g_strdup (apps[idx]));
333 }
334
335 if (!found && !remove)
336 g_ptr_array_add (new_apps, g_strdup (g_app_info_get_id (app_info)));
337
338 g_ptr_array_add (new_apps, NULL);
339
340 return g_variant_new_strv ((const gchar **) new_apps->pdata, -1);
341 }
342
343 static GVariant *
344 switch_settings_mapping_set_default_enabled (const GValue *value,
345 const GVariantType *expected_type,
346 gpointer user_data)
347 {
348 return switch_settings_mapping_set_generic (value, expected_type,
349 user_data, TRUE);
350 }
351
352 static GVariant *
353 switch_settings_mapping_set_default_disabled (const GValue *value,
354 const GVariantType *expected_type,
355 gpointer user_data)
356 {
357 return switch_settings_mapping_set_generic (value, expected_type,
358 user_data, FALSE);
359 }
360
361 static gboolean
362 switch_settings_mapping_get_generic (GValue *value,
363 GVariant *variant,
364 GtkWidget *row,
365 gboolean default_enabled)
366 {
367 GAppInfo *app_info = cc_search_panel_row_get_app_info (CC_SEARCH_PANEL_ROW (row));
368 g_autofree const gchar **apps = NULL;
369 gint idx;
370 gboolean found;
371
372 found = FALSE;
373 apps = g_variant_get_strv (variant, NULL);
374
375 for (idx = 0; apps[idx] != NULL; idx++)
376 {
377 if (g_strcmp0 (apps[idx], g_app_info_get_id (app_info)) == 0)
378 {
379 found = TRUE;
380 break;
381 }
382 }
383
384 g_value_set_boolean (value, !!default_enabled != !!found);
385
386 return TRUE;
387 }
388
389 static gboolean
390 switch_settings_mapping_get_default_enabled (GValue *value,
391 GVariant *variant,
392 gpointer user_data)
393 {
394 return switch_settings_mapping_get_generic (value, variant,
395 user_data, TRUE);
396 }
397
398 static gboolean
399 switch_settings_mapping_get_default_disabled (GValue *value,
400 GVariant *variant,
401 gpointer user_data)
402 {
403 return switch_settings_mapping_get_generic (value, variant,
404 user_data, FALSE);
405 }
406
407 static void
408 search_panel_update_enabled_move_actions (CcSearchPanel *self)
409 {
410 GtkWidget *child;
411
412 for (child = gtk_widget_get_first_child (GTK_WIDGET (self->list_box));
413 child;
414 child = gtk_widget_get_next_sibling (child))
415 {
416 GtkWidget *next_child;
417 gint row_idx;
418
419 if (!CC_IS_SEARCH_PANEL_ROW (child))
420 continue;
421
422 next_child = gtk_widget_get_next_sibling (GTK_WIDGET (child));
423 if (!CC_IS_SEARCH_PANEL_ROW (next_child))
424 continue;
425
426 row_idx = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (child));
427 gtk_widget_action_set_enabled (GTK_WIDGET (child), "row.move-up", row_idx != 0);
428 gtk_widget_action_set_enabled (GTK_WIDGET (child), "row.move-down", GTK_LIST_BOX_ROW (next_child) != NULL);
429 }
430 }
431
432 static void
433 search_panel_add_one_app_info (CcSearchPanel *self,
434 GAppInfo *app_info,
435 gboolean default_enabled)
436 {
437 CcSearchPanelRow *row;
438
439 /* reset valignment of the list box */
440 gtk_widget_set_valign (self->list_box, GTK_ALIGN_FILL);
441
442 row = cc_search_panel_row_new (app_info);
443 g_signal_connect_object (row, "move-row",
444 G_CALLBACK (row_moved_cb), self,
445 G_CONNECT_SWAPPED);
446 g_object_set_data (G_OBJECT (row), "self", self);
447 gtk_list_box_append (GTK_LIST_BOX (self->list_box), GTK_WIDGET (row));
448
449 if (default_enabled)
450 {
451 g_settings_bind_with_mapping (self->search_settings, "disabled",
452 cc_search_panel_row_get_switch (row), "active",
453 G_SETTINGS_BIND_DEFAULT,
454 switch_settings_mapping_get_default_enabled,
455 switch_settings_mapping_set_default_enabled,
456 row, NULL);
457 }
458 else
459 {
460 g_settings_bind_with_mapping (self->search_settings, "enabled",
461 cc_search_panel_row_get_switch (row), "active",
462 G_SETTINGS_BIND_DEFAULT,
463 switch_settings_mapping_get_default_disabled,
464 switch_settings_mapping_set_default_disabled,
465 row, NULL);
466 }
467 }
468
469 static void
470 search_panel_add_one_provider (CcSearchPanel *self,
471 GFile *provider)
472 {
473 g_autofree gchar *path = NULL;
474 g_autofree gchar *desktop_id = NULL;
475 g_autoptr(GKeyFile) keyfile = NULL;
476 g_autoptr(GAppInfo) app_info = NULL;
477 g_autoptr(GError) error = NULL;
478 gboolean default_disabled;
479
480 path = g_file_get_path (provider);
481 keyfile = g_key_file_new ();
482 g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &error);
483
484 if (error != NULL)
485 {
486 g_warning ("Error loading %s: %s - search provider will be ignored",
487 path, error->message);
488 return;
489 }
490
491 if (!g_key_file_has_group (keyfile, SHELL_PROVIDER_GROUP))
492 {
493 g_debug ("Shell search provider group missing from '%s', ignoring", path);
494 return;
495 }
496
497 desktop_id = g_key_file_get_string (keyfile, SHELL_PROVIDER_GROUP,
498 "DesktopId", &error);
499
500 if (error != NULL)
501 {
502 g_warning ("Unable to read desktop ID from %s: %s - search provider will be ignored",
503 path, error->message);
504 return;
505 }
506
507 app_info = G_APP_INFO (g_desktop_app_info_new (desktop_id));
508
509 if (app_info == NULL)
510 {
511 g_debug ("Could not find application with desktop ID '%s' referenced in '%s', ignoring",
512 desktop_id, path);
513 return;
514 }
515
516 default_disabled = g_key_file_get_boolean (keyfile, SHELL_PROVIDER_GROUP,
517 "DefaultDisabled", NULL);
518 search_panel_add_one_app_info (self, app_info, !default_disabled);
519 }
520
521 static void
522 search_providers_discover_ready (GObject *source,
523 GAsyncResult *result,
524 gpointer user_data)
525 {
526 g_autoptr(GList) providers = NULL;
527 GList *l;
528 CcSearchPanel *self = CC_SEARCH_PANEL (source);
529 g_autoptr(GError) error = NULL;
530
531 providers = g_task_propagate_pointer (G_TASK (result), &error);
532
533 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
534 return;
535
536 for (l = providers; l != NULL; l = l->next)
537 {
538 g_autoptr(GFile) provider = l->data;
539 search_panel_add_one_provider (self, provider);
540 }
541
542 /* propagate a write to GSettings, to make sure we always have
543 * all the providers in the list.
544 */
545 search_panel_propagate_sort_order (self);
546
547 search_panel_update_enabled_move_actions (self);
548 }
549
550 static GList *
551 search_providers_discover_one_directory (const gchar *system_dir,
552 GCancellable *cancellable)
553 {
554 GList *providers = NULL;
555 g_autofree gchar *providers_path = NULL;
556 g_autoptr(GFile) providers_location = NULL;
557 g_autoptr(GFileEnumerator) enumerator = NULL;
558 g_autoptr(GError) error = NULL;
559
560 providers_path = g_build_filename (system_dir, "gnome-shell", "search-providers", NULL);
561 providers_location = g_file_new_for_path (providers_path);
562
563 enumerator = g_file_enumerate_children (providers_location,
564 "standard::type,standard::name,standard::content-type",
565 G_FILE_QUERY_INFO_NONE,
566 cancellable, &error);
567
568 if (error != NULL)
569 {
570 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
571 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
572 g_warning ("Error opening %s: %s - search provider configuration won't be possible",
573 providers_path, error->message);
574
575 return NULL;
576 }
577
578 while (TRUE)
579 {
580 g_autoptr(GFileInfo) info = NULL;
581 GFile *provider;
582
583 info = g_file_enumerator_next_file (enumerator, cancellable, &error);
584 if (info == NULL)
585 {
586 if (error != NULL && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
587 g_warning ("Error reading from %s: %s - search providers might be missing from the panel",
588 providers_path, error->message);
589 return providers;
590 }
591 provider = g_file_get_child (providers_location, g_file_info_get_name (info));
592 providers = g_list_prepend (providers, provider);
593 }
594 }
595
596 static void
597 search_providers_discover_thread (GTask *task,
598 gpointer source_object,
599 gpointer task_data,
600 GCancellable *cancellable)
601 {
602 GList *providers = NULL;
603 const gchar * const *system_data_dirs;
604 int idx;
605
606 system_data_dirs = g_get_system_data_dirs ();
607 for (idx = 0; system_data_dirs[idx] != NULL; idx++)
608 {
609 providers = g_list_concat (search_providers_discover_one_directory (system_data_dirs[idx], cancellable),
610 providers);
611
612 if (g_task_return_error_if_cancelled (task))
613 {
614 g_list_free_full (providers, g_object_unref);
615 return;
616 }
617 }
618
619 g_task_return_pointer (task, providers, NULL);
620 }
621
622 static void
623 populate_search_providers (CcSearchPanel *self)
624 {
625 g_autoptr(GTask) task = NULL;
626
627 task = g_task_new (self, cc_panel_get_cancellable (CC_PANEL (self)),
628 search_providers_discover_ready, self);
629 g_task_run_in_thread (task, search_providers_discover_thread);
630 }
631
632 static void
633 cc_search_panel_set_property (GObject *object,
634 guint property_id,
635 const GValue *value,
636 GParamSpec *pspec)
637 {
638 switch (property_id)
639 {
640 case PROP_PARAMETERS:
641 {
642 GVariant *parameters = g_value_get_variant (value);
643 g_autoptr (GVariant) v = NULL;
644 const gchar *parameter = NULL;
645
646 if (parameters == NULL || g_variant_n_children (parameters) <= 0)
647 return;
648
649 g_variant_get_child (parameters, 0, "v", &v);
650 if (!g_variant_is_of_type (v, G_VARIANT_TYPE_STRING))
651 {
652 g_warning ("Wrong type for the second argument GVariant, expected 's' but got '%s'",
653 (gchar *)g_variant_get_type (v));
654 return;
655 }
656
657 parameter = g_variant_get_string (v, NULL);
658
659 if (g_str_equal (parameter, SEARCH_LOCATIONS_DIALOG_PARAM))
660 show_search_locations_dialog (CC_SEARCH_PANEL (object));
661 else
662 g_warning ("Ignoring unknown parameter %s", parameter);
663
664 return;
665 }
666 default:
667 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
668 }
669 }
670
671 static void
672 cc_search_panel_finalize (GObject *object)
673 {
674 CcSearchPanel *self = CC_SEARCH_PANEL (object);
675
676 g_clear_object (&self->search_settings);
677 g_hash_table_destroy (self->sort_order);
678
679 if (self->locations_dialog)
680 gtk_window_destroy (GTK_WINDOW (self->locations_dialog));
681
682 G_OBJECT_CLASS (cc_search_panel_parent_class)->finalize (object);
683 }
684
685 static void
686 cc_search_panel_init (CcSearchPanel *self)
687 {
688 g_resources_register (cc_search_get_resource ());
689
690 gtk_widget_init_template (GTK_WIDGET (self));
691
692 gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box),
693 (GtkListBoxSortFunc)list_sort_func, self, NULL);
694
695 gtk_widget_set_sensitive (self->settings_row, cc_search_locations_dialog_is_available ());
696
697 self->search_settings = g_settings_new ("org.gnome.desktop.search-providers");
698 g_settings_bind (self->search_settings,
699 "disable-external",
700 self->app_search_row,
701 "active",
702 G_SETTINGS_BIND_DEFAULT |
703 G_SETTINGS_BIND_INVERT_BOOLEAN);
704
705 g_object_bind_property (self->app_search_row,
706 "active",
707 self->search_group,
708 "sensitive",
709 G_BINDING_DEFAULT |
710 G_BINDING_SYNC_CREATE);
711
712 self->sort_order = g_hash_table_new_full (g_str_hash, g_str_equal,
713 g_free, NULL);
714 g_signal_connect_swapped (self->search_settings, "changed::sort-order",
715 G_CALLBACK (search_panel_invalidate_sort_order), self);
716 search_panel_invalidate_sort_order (self);
717
718 populate_search_providers (self);
719 }
720
721 static void
722 cc_search_panel_class_init (CcSearchPanelClass *klass)
723 {
724 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
725 GObjectClass *oclass = G_OBJECT_CLASS (klass);
726
727 oclass->finalize = cc_search_panel_finalize;
728 oclass->set_property = cc_search_panel_set_property;
729
730 g_type_ensure (CC_TYPE_LIST_ROW);
731
732 g_object_class_override_property (oclass, PROP_PARAMETERS, "parameters");
733
734 gtk_widget_class_set_template_from_resource (widget_class,
735 "/org/gnome/control-center/search/cc-search-panel.ui");
736
737 gtk_widget_class_bind_template_child (widget_class, CcSearchPanel, list_box);
738 gtk_widget_class_bind_template_child (widget_class, CcSearchPanel, app_search_row);
739 gtk_widget_class_bind_template_child (widget_class, CcSearchPanel, search_group);
740 gtk_widget_class_bind_template_child (widget_class, CcSearchPanel, settings_row);
741
742 gtk_widget_class_bind_template_callback (widget_class, show_search_locations_dialog);
743 gtk_widget_class_bind_template_callback (widget_class, keynav_failed_cb);
744 }
745