Line |
Branch |
Exec |
Source |
1 |
|
|
/* cc-panel-list.c |
2 |
|
|
* |
3 |
|
|
* Copyright (C) 2016 Endless, 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: Georges Basile Stavracas Neto <gbsneto@gnome.org> |
19 |
|
|
*/ |
20 |
|
|
|
21 |
|
|
#define G_LOG_DOMAIN "cc-panel-list" |
22 |
|
|
|
23 |
|
|
#include <string.h> |
24 |
|
|
|
25 |
|
|
#include "cc-log.h" |
26 |
|
|
#include "cc-panel-list.h" |
27 |
|
|
#include "cc-util.h" |
28 |
|
|
|
29 |
|
|
typedef struct |
30 |
|
|
{ |
31 |
|
|
GtkWidget *row; |
32 |
|
|
GtkWidget *description_label; |
33 |
|
|
CcPanelCategory category; |
34 |
|
|
gchar *id; |
35 |
|
|
gchar *name; |
36 |
|
|
gchar *description; |
37 |
|
|
gchar **keywords; |
38 |
|
|
CcPanelVisibility visibility; |
39 |
|
|
} RowData; |
40 |
|
|
|
41 |
|
|
struct _CcPanelList |
42 |
|
|
{ |
43 |
|
|
AdwBin parent; |
44 |
|
|
|
45 |
|
|
GtkWidget *main_listbox; |
46 |
|
|
GtkWidget *search_listbox; |
47 |
|
|
GtkStack *stack; |
48 |
|
|
|
49 |
|
|
/* When clicking on Details or Devices row, show it |
50 |
|
|
* automatically select the first panel of the list. |
51 |
|
|
*/ |
52 |
|
|
gboolean autoselect_panel : 1; |
53 |
|
|
|
54 |
|
|
gchar *current_panel_id; |
55 |
|
|
gchar *search_query; |
56 |
|
|
gchar **search_words; |
57 |
|
|
|
58 |
|
|
CcPanelListView previous_view; |
59 |
|
|
CcPanelListView view; |
60 |
|
|
GHashTable *id_to_data; |
61 |
|
|
GHashTable *id_to_search_data; |
62 |
|
|
|
63 |
|
|
/* When true, the next row being activated will be vertically centered on |
64 |
|
|
* the visible part of panel list. Currently we do that for panels activated |
65 |
|
|
* from Search or from the set_active_panel_from_id() CcShell iface */ |
66 |
|
|
gboolean center_activated_row; |
67 |
|
|
}; |
68 |
|
|
|
69 |
|
✗ |
G_DEFINE_TYPE (CcPanelList, cc_panel_list, ADW_TYPE_BIN) |
70 |
|
|
|
71 |
|
|
enum |
72 |
|
|
{ |
73 |
|
|
PROP_0, |
74 |
|
|
PROP_SEARCH_MODE, |
75 |
|
|
PROP_SEARCH_QUERY, |
76 |
|
|
PROP_VIEW, |
77 |
|
|
N_PROPS |
78 |
|
|
}; |
79 |
|
|
|
80 |
|
|
enum |
81 |
|
|
{ |
82 |
|
|
SHOW_PANEL, |
83 |
|
|
LAST_SIGNAL |
84 |
|
|
}; |
85 |
|
|
|
86 |
|
|
static GParamSpec *properties [N_PROPS] = { NULL, }; |
87 |
|
|
static gint signals [LAST_SIGNAL] = { 0, }; |
88 |
|
|
|
89 |
|
|
/* |
90 |
|
|
* Auxiliary methods |
91 |
|
|
*/ |
92 |
|
|
static GtkWidget* |
93 |
|
✗ |
get_widget_from_view (CcPanelList *self, |
94 |
|
|
CcPanelListView view) |
95 |
|
|
{ |
96 |
|
✗ |
switch (view) |
97 |
|
|
{ |
98 |
|
✗ |
case CC_PANEL_LIST_MAIN: |
99 |
|
✗ |
return self->main_listbox; |
100 |
|
|
|
101 |
|
✗ |
case CC_PANEL_LIST_SEARCH: |
102 |
|
✗ |
return self->search_listbox; |
103 |
|
|
|
104 |
|
✗ |
default: |
105 |
|
✗ |
return NULL; |
106 |
|
|
} |
107 |
|
|
} |
108 |
|
|
|
109 |
|
|
static void |
110 |
|
✗ |
activate_row_below (CcPanelList *self, |
111 |
|
|
RowData *data) |
112 |
|
|
{ |
113 |
|
|
GtkListBoxRow *next_row; |
114 |
|
|
guint row_index; |
115 |
|
|
|
116 |
|
✗ |
row_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (data->row)); |
117 |
|
✗ |
next_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->main_listbox), |
118 |
|
✗ |
row_index + 1); |
119 |
|
|
|
120 |
|
|
/* Try the previous one if the current is invalid */ |
121 |
|
✗ |
if (!next_row) |
122 |
|
✗ |
next_row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->main_listbox), |
123 |
|
✗ |
row_index - 1); |
124 |
|
|
|
125 |
|
✗ |
if (next_row) |
126 |
|
✗ |
g_signal_emit_by_name (next_row, "activate"); |
127 |
|
✗ |
} |
128 |
|
|
|
129 |
|
|
static CcPanelListView |
130 |
|
✗ |
get_view_from_listbox (CcPanelList *self, |
131 |
|
|
GtkWidget *listbox) |
132 |
|
|
{ |
133 |
|
✗ |
if (listbox == self->main_listbox) |
134 |
|
✗ |
return CC_PANEL_LIST_MAIN; |
135 |
|
|
|
136 |
|
✗ |
return CC_PANEL_LIST_SEARCH; |
137 |
|
|
} |
138 |
|
|
|
139 |
|
|
static void |
140 |
|
✗ |
switch_to_view (CcPanelList *self, |
141 |
|
|
CcPanelListView view) |
142 |
|
|
{ |
143 |
|
|
GtkWidget *visible_child; |
144 |
|
|
gboolean should_crossfade; |
145 |
|
|
|
146 |
|
✗ |
CC_ENTRY; |
147 |
|
|
|
148 |
|
✗ |
if (self->view == view) |
149 |
|
✗ |
CC_RETURN (); |
150 |
|
|
|
151 |
|
✗ |
CC_TRACE_MSG ("Switching to view: %d", view); |
152 |
|
|
|
153 |
|
✗ |
self->previous_view = self->view; |
154 |
|
✗ |
self->view = view; |
155 |
|
|
|
156 |
|
|
/* |
157 |
|
|
* When changing to or from the search view, the animation should |
158 |
|
|
* be crossfade. Otherwise, it's the previous-forward movement. |
159 |
|
|
*/ |
160 |
|
✗ |
should_crossfade = view == CC_PANEL_LIST_SEARCH || |
161 |
|
✗ |
self->previous_view == CC_PANEL_LIST_SEARCH; |
162 |
|
|
|
163 |
|
✗ |
gtk_stack_set_transition_type (self->stack, |
164 |
|
|
should_crossfade ? GTK_STACK_TRANSITION_TYPE_CROSSFADE : |
165 |
|
|
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); |
166 |
|
|
|
167 |
|
✗ |
visible_child = get_widget_from_view (self, view); |
168 |
|
|
|
169 |
|
✗ |
gtk_stack_set_visible_child (self->stack, visible_child); |
170 |
|
|
|
171 |
|
|
/* For non-search views, make sure the displayed panel matches the |
172 |
|
|
* newly selected row |
173 |
|
|
*/ |
174 |
|
✗ |
if (self->autoselect_panel && |
175 |
|
|
view != CC_PANEL_LIST_SEARCH) |
176 |
|
|
{ |
177 |
|
✗ |
cc_panel_list_activate (self); |
178 |
|
|
} |
179 |
|
|
|
180 |
|
✗ |
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW]); |
181 |
|
✗ |
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_MODE]); |
182 |
|
|
|
183 |
|
✗ |
CC_EXIT; |
184 |
|
|
} |
185 |
|
|
|
186 |
|
|
static void |
187 |
|
✗ |
update_search (CcPanelList *self) |
188 |
|
|
{ |
189 |
|
|
/* |
190 |
|
|
* Only change to the search view is there's a |
191 |
|
|
* search query available. |
192 |
|
|
*/ |
193 |
|
✗ |
if (self->search_query && |
194 |
|
✗ |
g_utf8_strlen (self->search_query, -1) > 0) |
195 |
|
|
{ |
196 |
|
✗ |
if (self->view == CC_PANEL_LIST_MAIN) |
197 |
|
✗ |
switch_to_view (self, CC_PANEL_LIST_SEARCH); |
198 |
|
|
} |
199 |
|
✗ |
else if (self->view == CC_PANEL_LIST_SEARCH) |
200 |
|
|
{ |
201 |
|
|
/* Don't autoselect first panel when going back from search view */ |
202 |
|
✗ |
self->autoselect_panel = FALSE; |
203 |
|
|
|
204 |
|
✗ |
switch_to_view (self, self->previous_view); |
205 |
|
|
} |
206 |
|
|
|
207 |
|
✗ |
gtk_list_box_invalidate_filter (GTK_LIST_BOX (self->search_listbox)); |
208 |
|
✗ |
gtk_list_box_unselect_all (GTK_LIST_BOX (self->search_listbox)); |
209 |
|
✗ |
} |
210 |
|
|
|
211 |
|
|
static const gchar* |
212 |
|
✗ |
get_panel_id_from_row (CcPanelList *self, |
213 |
|
|
GtkListBoxRow *row) |
214 |
|
|
{ |
215 |
|
|
|
216 |
|
✗ |
RowData *row_data = g_object_get_data (G_OBJECT (row), "data"); |
217 |
|
|
|
218 |
|
✗ |
g_assert (row_data != NULL); |
219 |
|
✗ |
return row_data->id; |
220 |
|
|
} |
221 |
|
|
|
222 |
|
|
/* |
223 |
|
|
* RowData functions |
224 |
|
|
*/ |
225 |
|
|
static void |
226 |
|
✗ |
row_data_free (RowData *data) |
227 |
|
|
{ |
228 |
|
✗ |
g_strfreev (data->keywords); |
229 |
|
✗ |
g_free (data->description); |
230 |
|
✗ |
g_free (data->name); |
231 |
|
✗ |
g_free (data->id); |
232 |
|
✗ |
g_free (data); |
233 |
|
✗ |
} |
234 |
|
|
|
235 |
|
|
static RowData* |
236 |
|
✗ |
row_data_new (CcPanelCategory category, |
237 |
|
|
const gchar *id, |
238 |
|
|
const gchar *name, |
239 |
|
|
const gchar *description, |
240 |
|
|
const GStrv keywords, |
241 |
|
|
const gchar *icon, |
242 |
|
|
CcPanelVisibility visibility) |
243 |
|
|
{ |
244 |
|
|
GtkWidget *label, *grid, *image; |
245 |
|
|
RowData *data; |
246 |
|
|
|
247 |
|
✗ |
data = g_new0 (RowData, 1); |
248 |
|
✗ |
data->category = category; |
249 |
|
✗ |
data->row = gtk_list_box_row_new (); |
250 |
|
✗ |
data->id = g_strdup (id); |
251 |
|
✗ |
data->name = g_strdup (name); |
252 |
|
✗ |
data->description = g_strdup (description); |
253 |
|
✗ |
data->keywords = g_strdupv (keywords); |
254 |
|
|
|
255 |
|
|
/* Setup the row */ |
256 |
|
✗ |
grid = gtk_grid_new (); |
257 |
|
✗ |
gtk_widget_set_hexpand (grid, TRUE); |
258 |
|
✗ |
gtk_widget_set_margin_top (grid, 12); |
259 |
|
✗ |
gtk_widget_set_margin_bottom (grid, 12); |
260 |
|
✗ |
gtk_widget_set_margin_start (grid, 6); |
261 |
|
✗ |
gtk_widget_set_margin_end (grid, 6); |
262 |
|
✗ |
gtk_grid_set_column_spacing (GTK_GRID (grid), 12); |
263 |
|
|
|
264 |
|
|
/* Icon */ |
265 |
|
✗ |
image = gtk_image_new_from_icon_name (icon); |
266 |
|
|
|
267 |
|
✗ |
gtk_grid_attach (GTK_GRID (grid), image, 0, 0, 1, 1); |
268 |
|
|
|
269 |
|
|
/* Name label */ |
270 |
|
✗ |
label = gtk_label_new (name); |
271 |
|
✗ |
gtk_label_set_xalign (GTK_LABEL (label), 0.0); |
272 |
|
✗ |
gtk_widget_set_hexpand (label, TRUE); |
273 |
|
✗ |
gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1); |
274 |
|
✗ |
gtk_accessible_update_relation (GTK_ACCESSIBLE (data->row), |
275 |
|
|
GTK_ACCESSIBLE_RELATION_LABELLED_BY, |
276 |
|
|
label, |
277 |
|
|
NULL, |
278 |
|
|
-1); |
279 |
|
|
|
280 |
|
|
/* Description label */ |
281 |
|
✗ |
label = gtk_label_new (description); |
282 |
|
✗ |
gtk_label_set_xalign (GTK_LABEL (label), 0.0); |
283 |
|
✗ |
gtk_widget_set_hexpand (label, TRUE); |
284 |
|
✗ |
gtk_label_set_max_width_chars (GTK_LABEL (label), 25); |
285 |
|
✗ |
gtk_label_set_wrap (GTK_LABEL (label), TRUE); |
286 |
|
✗ |
gtk_widget_set_visible (label, FALSE); |
287 |
|
✗ |
gtk_accessible_update_relation (GTK_ACCESSIBLE (data->row), |
288 |
|
|
GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, |
289 |
|
|
label, |
290 |
|
|
NULL, |
291 |
|
|
-1); |
292 |
|
|
|
293 |
|
✗ |
gtk_widget_add_css_class (label, "dim-label"); |
294 |
|
✗ |
gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1); |
295 |
|
|
|
296 |
|
✗ |
data->description_label = label; |
297 |
|
|
|
298 |
|
✗ |
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (data->row), grid); |
299 |
|
|
|
300 |
|
✗ |
g_object_set_data_full (G_OBJECT (data->row), "data", data, (GDestroyNotify) row_data_free); |
301 |
|
|
|
302 |
|
✗ |
data->visibility = visibility; |
303 |
|
|
|
304 |
|
✗ |
return data; |
305 |
|
|
} |
306 |
|
|
|
307 |
|
|
/* |
308 |
|
|
* GtkListBox functions |
309 |
|
|
*/ |
310 |
|
|
static gboolean |
311 |
|
✗ |
filter_func (GtkListBoxRow *row, |
312 |
|
|
gpointer user_data) |
313 |
|
|
{ |
314 |
|
|
CcPanelList *self; |
315 |
|
|
RowData *data; |
316 |
|
✗ |
g_autofree gchar *panel_text = NULL; |
317 |
|
✗ |
g_autofree gchar *panel_description = NULL; |
318 |
|
✗ |
gboolean retval = TRUE; |
319 |
|
|
gint i, j; |
320 |
|
|
|
321 |
|
✗ |
self = CC_PANEL_LIST (user_data); |
322 |
|
✗ |
data = g_object_get_data (G_OBJECT (row), "data"); |
323 |
|
|
|
324 |
|
✗ |
if (!self->search_words) |
325 |
|
✗ |
return TRUE; |
326 |
|
|
|
327 |
|
✗ |
panel_text = cc_util_normalize_casefold_and_unaccent (data->name); |
328 |
|
✗ |
panel_description = cc_util_normalize_casefold_and_unaccent (data->description); |
329 |
|
|
|
330 |
|
✗ |
g_strstrip (panel_text); |
331 |
|
✗ |
g_strstrip (panel_description); |
332 |
|
|
|
333 |
|
|
/* |
334 |
|
|
* The description label is only visible when the search is |
335 |
|
|
* happening. |
336 |
|
|
*/ |
337 |
|
✗ |
gtk_widget_set_visible (data->description_label, self->view == CC_PANEL_LIST_SEARCH); |
338 |
|
|
|
339 |
|
✗ |
for (j = 0; retval && self->search_words[j] != NULL; j++) { |
340 |
|
✗ |
const gchar *search_word = self->search_words[j]; |
341 |
|
✗ |
gboolean match = FALSE; |
342 |
|
|
|
343 |
|
✗ |
if (search_word[0] == '\0') |
344 |
|
✗ |
continue; |
345 |
|
|
|
346 |
|
|
// Compare keywords |
347 |
|
✗ |
for (i = 0; !match && data->keywords[i] != NULL; i++) |
348 |
|
✗ |
match = (strstr (data->keywords[i], search_word) == data->keywords[i]); |
349 |
|
|
|
350 |
|
|
// Compare panel title and description |
351 |
|
✗ |
match = match || (g_strstr_len (panel_text, -1, search_word) != NULL || |
352 |
|
✗ |
g_strstr_len (panel_description, -1, search_word) != NULL); |
353 |
|
|
|
354 |
|
|
// All search words must match |
355 |
|
✗ |
retval = retval && match; |
356 |
|
|
} |
357 |
|
|
|
358 |
|
✗ |
return retval; |
359 |
|
|
} |
360 |
|
|
|
361 |
|
|
static const gchar * const panel_order[] = { |
362 |
|
|
/* Main page */ |
363 |
|
|
"wifi", |
364 |
|
|
"network", |
365 |
|
|
"wwan", |
366 |
|
|
"mobile-broadband", |
367 |
|
|
"bluetooth", |
368 |
|
|
|
369 |
|
|
"separator", |
370 |
|
|
|
371 |
|
|
"display", |
372 |
|
|
"sound", |
373 |
|
|
"power", |
374 |
|
|
"multitasking", |
375 |
|
|
"background", |
376 |
|
|
|
377 |
|
|
"separator", |
378 |
|
|
|
379 |
|
|
"applications", |
380 |
|
|
"notifications", |
381 |
|
|
"search", |
382 |
|
|
"online-accounts", |
383 |
|
|
"sharing", |
384 |
|
|
|
385 |
|
|
"separator", |
386 |
|
|
|
387 |
|
|
"mouse", |
388 |
|
|
"keyboard", |
389 |
|
|
"color", |
390 |
|
|
"printers", |
391 |
|
|
"wacom", |
392 |
|
|
|
393 |
|
|
"separator", |
394 |
|
|
|
395 |
|
|
"universal-access", |
396 |
|
|
"privacy", |
397 |
|
|
"system", |
398 |
|
|
"reset-settings", |
399 |
|
|
}; |
400 |
|
|
|
401 |
|
|
static guint |
402 |
|
✗ |
get_panel_id_index (const gchar *panel_id) |
403 |
|
|
{ |
404 |
|
|
guint i; |
405 |
|
|
|
406 |
|
✗ |
for (i = 0; i < G_N_ELEMENTS (panel_order); i++) |
407 |
|
|
{ |
408 |
|
✗ |
if (g_str_equal (panel_order[i], panel_id)) |
409 |
|
✗ |
return i; |
410 |
|
|
} |
411 |
|
|
|
412 |
|
✗ |
return 0; |
413 |
|
|
} |
414 |
|
|
|
415 |
|
|
static gint |
416 |
|
✗ |
sort_function (GtkListBoxRow *a, |
417 |
|
|
GtkListBoxRow *b, |
418 |
|
|
gpointer user_data) |
419 |
|
|
{ |
420 |
|
✗ |
CcPanelList *self = CC_PANEL_LIST (user_data); |
421 |
|
|
const gchar *a_id, *b_id; |
422 |
|
|
|
423 |
|
✗ |
a_id = get_panel_id_from_row (self, a); |
424 |
|
✗ |
b_id = get_panel_id_from_row (self, b); |
425 |
|
|
|
426 |
|
✗ |
return get_panel_id_index (a_id) - get_panel_id_index (b_id); |
427 |
|
|
} |
428 |
|
|
|
429 |
|
|
|
430 |
|
|
/* FIXME: This is now different from the "match all words" search. |
431 |
|
|
Maybe add a search score based on number of matches in filter_func()? */ |
432 |
|
|
static gint |
433 |
|
✗ |
search_sort_function (GtkListBoxRow *a, |
434 |
|
|
GtkListBoxRow *b, |
435 |
|
|
gpointer user_data) |
436 |
|
|
{ |
437 |
|
|
CcPanelList *self; |
438 |
|
|
RowData *a_data, *b_data; |
439 |
|
✗ |
g_autofree gchar *a_name = NULL; |
440 |
|
✗ |
g_autofree gchar *b_name = NULL; |
441 |
|
✗ |
g_autofree gchar *search = NULL; |
442 |
|
|
gchar *a_strstr, *b_strstr; |
443 |
|
|
gint a_distance, b_distance; |
444 |
|
|
|
445 |
|
✗ |
self = CC_PANEL_LIST (user_data); |
446 |
|
✗ |
search = NULL; |
447 |
|
✗ |
a_data = g_object_get_data (G_OBJECT (a), "data"); |
448 |
|
✗ |
b_data = g_object_get_data (G_OBJECT (b), "data"); |
449 |
|
|
|
450 |
|
✗ |
a_distance = b_distance = G_MAXINT; |
451 |
|
|
|
452 |
|
✗ |
a_name = cc_util_normalize_casefold_and_unaccent (a_data->name); |
453 |
|
✗ |
b_name = cc_util_normalize_casefold_and_unaccent (b_data->name); |
454 |
|
✗ |
g_strstrip (a_name); |
455 |
|
✗ |
g_strstrip (b_name); |
456 |
|
|
|
457 |
|
✗ |
if (self->search_query) |
458 |
|
|
{ |
459 |
|
✗ |
search = cc_util_normalize_casefold_and_unaccent (self->search_query); |
460 |
|
✗ |
g_strstrip (search); |
461 |
|
|
} |
462 |
|
|
|
463 |
|
|
/* Default result for empty search */ |
464 |
|
✗ |
if (!search || g_utf8_strlen (search, -1) == 0) |
465 |
|
✗ |
return g_strcmp0 (a_name, b_name); |
466 |
|
|
|
467 |
|
✗ |
a_strstr = g_strstr_len (a_name, -1, search); |
468 |
|
✗ |
b_strstr = g_strstr_len (b_name, -1, search); |
469 |
|
|
|
470 |
|
✗ |
if (a_strstr) |
471 |
|
✗ |
a_distance = g_strstr_len (a_name, -1, search) - a_name; |
472 |
|
|
|
473 |
|
✗ |
if (b_strstr) |
474 |
|
✗ |
b_distance = g_strstr_len (b_name, -1, search) - b_name; |
475 |
|
|
|
476 |
|
✗ |
return a_distance - b_distance; |
477 |
|
|
} |
478 |
|
|
|
479 |
|
|
static void |
480 |
|
✗ |
header_func (GtkListBoxRow *row, |
481 |
|
|
GtkListBoxRow *before, |
482 |
|
|
gpointer user_data) |
483 |
|
|
{ |
484 |
|
|
guint pid; |
485 |
|
|
|
486 |
|
✗ |
if (!before) |
487 |
|
✗ |
return; |
488 |
|
|
|
489 |
|
✗ |
pid = get_panel_id_index (get_panel_id_from_row (user_data, row)); |
490 |
|
✗ |
if (pid > 0 && g_str_equal (panel_order[pid-1], "separator")) |
491 |
|
✗ |
{ |
492 |
|
|
GtkWidget *separator; |
493 |
|
|
|
494 |
|
✗ |
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); |
495 |
|
✗ |
gtk_widget_set_hexpand (separator, TRUE); |
496 |
|
|
|
497 |
|
✗ |
gtk_list_box_row_set_header (row, separator); |
498 |
|
|
} |
499 |
|
|
else |
500 |
|
|
{ |
501 |
|
✗ |
gtk_list_box_row_set_header (row, NULL); |
502 |
|
|
} |
503 |
|
|
} |
504 |
|
|
|
505 |
|
|
/* |
506 |
|
|
* Callbacks |
507 |
|
|
*/ |
508 |
|
|
static void |
509 |
|
✗ |
row_activated_cb (GtkWidget *listbox, |
510 |
|
|
GtkListBoxRow *row, |
511 |
|
|
CcPanelList *self) |
512 |
|
|
{ |
513 |
|
|
RowData *data; |
514 |
|
✗ |
const gchar *parent_panel = 0; |
515 |
|
|
|
516 |
|
|
/* |
517 |
|
|
* Since we're not sure that the activated row is in the |
518 |
|
|
* current view, set the view here. |
519 |
|
|
*/ |
520 |
|
✗ |
switch_to_view (self, get_view_from_listbox (self, listbox)); |
521 |
|
|
|
522 |
|
✗ |
data = g_object_get_data (G_OBJECT (row), "data"); |
523 |
|
✗ |
if (data->category == CC_CATEGORY_SYSTEM) |
524 |
|
✗ |
parent_panel = "system"; |
525 |
|
✗ |
else if (data->category == CC_CATEGORY_PRIVACY) |
526 |
|
✗ |
parent_panel = "privacy"; |
527 |
|
|
|
528 |
|
✗ |
g_signal_emit (self, signals[SHOW_PANEL], 0, data->id, parent_panel); |
529 |
|
|
|
530 |
|
|
/* After selecting the panel and eventually changing the view, reset the |
531 |
|
|
* autoselect flag. If necessary, cc_panel_list_set_active_panel() will |
532 |
|
|
* set it to FALSE again. |
533 |
|
|
*/ |
534 |
|
✗ |
self->autoselect_panel = TRUE; |
535 |
|
✗ |
} |
536 |
|
|
|
537 |
|
|
static void |
538 |
|
✗ |
search_row_activated_cb (GtkWidget *listbox, |
539 |
|
|
GtkListBoxRow *row, |
540 |
|
|
CcPanelList *self) |
541 |
|
|
{ |
542 |
|
|
GtkWidget *child; |
543 |
|
|
RowData *data; |
544 |
|
|
|
545 |
|
✗ |
CC_ENTRY; |
546 |
|
|
|
547 |
|
✗ |
data = g_object_get_data (G_OBJECT (row), "data"); |
548 |
|
|
|
549 |
|
|
/* Select the correct row */ |
550 |
|
✗ |
for (child = gtk_widget_get_first_child (self->main_listbox); |
551 |
|
✗ |
child != NULL; |
552 |
|
✗ |
child = gtk_widget_get_next_sibling (child)) |
553 |
|
|
{ |
554 |
|
|
RowData *real_row_data; |
555 |
|
|
|
556 |
|
✗ |
real_row_data = g_object_get_data (G_OBJECT (child), "data"); |
557 |
|
|
|
558 |
|
|
/* |
559 |
|
|
* The main listbox has the Details & Devices rows, and neither |
560 |
|
|
* of them contains "data", so we have to ensure we have valid |
561 |
|
|
* data before going on. |
562 |
|
|
*/ |
563 |
|
✗ |
if (!real_row_data) |
564 |
|
✗ |
continue; |
565 |
|
|
|
566 |
|
✗ |
if (g_strcmp0 (real_row_data->id, data->id) == 0) |
567 |
|
|
{ |
568 |
|
|
GtkListBoxRow *real_row; |
569 |
|
|
|
570 |
|
✗ |
real_row = GTK_LIST_BOX_ROW (real_row_data->row); |
571 |
|
|
|
572 |
|
✗ |
gtk_list_box_select_row (GTK_LIST_BOX (self->main_listbox), real_row); |
573 |
|
✗ |
gtk_widget_grab_focus (GTK_WIDGET (real_row)); |
574 |
|
|
|
575 |
|
|
/* Don't autoselect first panel because we are already |
576 |
|
|
* activating a panel from search result */ |
577 |
|
✗ |
self->autoselect_panel = FALSE; |
578 |
|
|
|
579 |
|
|
/* center Search activated row on panel list */ |
580 |
|
✗ |
self->center_activated_row = TRUE; |
581 |
|
✗ |
g_signal_emit_by_name (real_row, "activate"); |
582 |
|
✗ |
break; |
583 |
|
|
} |
584 |
|
|
} |
585 |
|
|
|
586 |
|
✗ |
CC_EXIT; |
587 |
|
|
} |
588 |
|
|
|
589 |
|
|
static void |
590 |
|
✗ |
cc_panel_list_finalize (GObject *object) |
591 |
|
|
{ |
592 |
|
✗ |
CcPanelList *self = (CcPanelList *)object; |
593 |
|
|
|
594 |
|
✗ |
g_clear_pointer (&self->search_query, g_free); |
595 |
|
✗ |
g_clear_pointer (&self->search_words, g_strfreev); |
596 |
|
✗ |
g_clear_pointer (&self->current_panel_id, g_free); |
597 |
|
✗ |
g_clear_pointer (&self->id_to_data, g_hash_table_destroy); |
598 |
|
✗ |
g_clear_pointer (&self->id_to_search_data, g_hash_table_destroy); |
599 |
|
|
|
600 |
|
✗ |
G_OBJECT_CLASS (cc_panel_list_parent_class)->finalize (object); |
601 |
|
✗ |
} |
602 |
|
|
|
603 |
|
|
static void |
604 |
|
✗ |
cc_panel_list_get_property (GObject *object, |
605 |
|
|
guint prop_id, |
606 |
|
|
GValue *value, |
607 |
|
|
GParamSpec *pspec) |
608 |
|
|
{ |
609 |
|
✗ |
CcPanelList *self = CC_PANEL_LIST (object); |
610 |
|
|
|
611 |
|
✗ |
switch (prop_id) |
612 |
|
|
{ |
613 |
|
✗ |
case PROP_SEARCH_MODE: |
614 |
|
✗ |
g_value_set_boolean (value, self->view == CC_PANEL_LIST_SEARCH); |
615 |
|
✗ |
break; |
616 |
|
|
|
617 |
|
✗ |
case PROP_SEARCH_QUERY: |
618 |
|
✗ |
g_value_set_string (value, self->search_query); |
619 |
|
✗ |
break; |
620 |
|
|
|
621 |
|
✗ |
case PROP_VIEW: |
622 |
|
✗ |
g_value_set_int (value, self->view); |
623 |
|
✗ |
break; |
624 |
|
|
|
625 |
|
✗ |
default: |
626 |
|
✗ |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
627 |
|
|
} |
628 |
|
✗ |
} |
629 |
|
|
|
630 |
|
|
static void |
631 |
|
✗ |
cc_panel_list_set_property (GObject *object, |
632 |
|
|
guint prop_id, |
633 |
|
|
const GValue *value, |
634 |
|
|
GParamSpec *pspec) |
635 |
|
|
{ |
636 |
|
✗ |
CcPanelList *self = CC_PANEL_LIST (object); |
637 |
|
|
|
638 |
|
✗ |
switch (prop_id) |
639 |
|
|
{ |
640 |
|
✗ |
case PROP_SEARCH_MODE: |
641 |
|
✗ |
update_search (self); |
642 |
|
✗ |
break; |
643 |
|
|
|
644 |
|
✗ |
case PROP_SEARCH_QUERY: |
645 |
|
✗ |
cc_panel_list_set_search_query (self, g_value_get_string (value)); |
646 |
|
✗ |
break; |
647 |
|
|
|
648 |
|
✗ |
case PROP_VIEW: |
649 |
|
✗ |
switch_to_view (self, g_value_get_int (value)); |
650 |
|
✗ |
break; |
651 |
|
|
|
652 |
|
✗ |
default: |
653 |
|
✗ |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
654 |
|
|
} |
655 |
|
✗ |
} |
656 |
|
|
|
657 |
|
|
static gboolean |
658 |
|
✗ |
search_list_keynav_failed_cb (CcPanelList *self, |
659 |
|
|
GtkDirectionType direction) |
660 |
|
|
{ |
661 |
|
|
GtkWidget *toplevel; |
662 |
|
|
|
663 |
|
|
/* We are in the first result of search list and pressing Arrow Up, |
664 |
|
|
* so then we move focus back to search text entry */ |
665 |
|
✗ |
if (direction == GTK_DIR_UP) |
666 |
|
|
{ |
667 |
|
✗ |
toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self))); |
668 |
|
|
|
669 |
|
✗ |
if (!toplevel) |
670 |
|
✗ |
return FALSE; |
671 |
|
|
|
672 |
|
✗ |
return gtk_widget_child_focus (toplevel, GTK_DIR_TAB_BACKWARD); |
673 |
|
|
} |
674 |
|
|
|
675 |
|
✗ |
return FALSE; |
676 |
|
|
} |
677 |
|
|
|
678 |
|
|
|
679 |
|
|
static void |
680 |
|
✗ |
cc_panel_list_class_init (CcPanelListClass *klass) |
681 |
|
|
{ |
682 |
|
✗ |
GObjectClass *object_class = G_OBJECT_CLASS (klass); |
683 |
|
✗ |
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
684 |
|
|
|
685 |
|
✗ |
object_class->finalize = cc_panel_list_finalize; |
686 |
|
✗ |
object_class->get_property = cc_panel_list_get_property; |
687 |
|
✗ |
object_class->set_property = cc_panel_list_set_property; |
688 |
|
|
|
689 |
|
|
/** |
690 |
|
|
* CcPanelList:show-panel: |
691 |
|
|
* |
692 |
|
|
* Emitted when a panel is selected. |
693 |
|
|
*/ |
694 |
|
✗ |
signals[SHOW_PANEL] = g_signal_new ("show-panel", |
695 |
|
|
CC_TYPE_PANEL_LIST, |
696 |
|
|
G_SIGNAL_RUN_LAST, |
697 |
|
|
0, NULL, NULL, NULL, |
698 |
|
|
G_TYPE_NONE, |
699 |
|
|
2, |
700 |
|
|
G_TYPE_STRING, |
701 |
|
|
G_TYPE_STRING); |
702 |
|
|
|
703 |
|
|
/** |
704 |
|
|
* CcPanelList:search-mode: |
705 |
|
|
* |
706 |
|
|
* Whether the search is visible or not. |
707 |
|
|
*/ |
708 |
|
✗ |
properties[PROP_SEARCH_MODE] = g_param_spec_boolean ("search-mode", |
709 |
|
|
"Search mode", |
710 |
|
|
"Whether it's in search mode or not", |
711 |
|
|
FALSE, |
712 |
|
|
G_PARAM_READWRITE); |
713 |
|
|
|
714 |
|
|
/** |
715 |
|
|
* CcPanelList:search-query: |
716 |
|
|
* |
717 |
|
|
* The search that is being applied to sidelist. |
718 |
|
|
*/ |
719 |
|
✗ |
properties[PROP_SEARCH_QUERY] = g_param_spec_string ("search-query", |
720 |
|
|
"Search query", |
721 |
|
|
"The current search query", |
722 |
|
|
NULL, |
723 |
|
|
G_PARAM_READWRITE); |
724 |
|
|
|
725 |
|
|
/** |
726 |
|
|
* CcPanelList:view: |
727 |
|
|
* |
728 |
|
|
* The current view of the sidelist. |
729 |
|
|
*/ |
730 |
|
✗ |
properties[PROP_VIEW] = g_param_spec_int ("view", |
731 |
|
|
"View", |
732 |
|
|
"The current view of the sidelist", |
733 |
|
|
CC_PANEL_LIST_MAIN, |
734 |
|
|
CC_PANEL_LIST_SEARCH, |
735 |
|
|
CC_PANEL_LIST_MAIN, |
736 |
|
|
G_PARAM_READWRITE); |
737 |
|
|
|
738 |
|
✗ |
g_object_class_install_properties (object_class, N_PROPS, properties); |
739 |
|
|
|
740 |
|
✗ |
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/gtk/cc-panel-list.ui"); |
741 |
|
|
|
742 |
|
✗ |
gtk_widget_class_bind_template_child (widget_class, CcPanelList, main_listbox); |
743 |
|
✗ |
gtk_widget_class_bind_template_child (widget_class, CcPanelList, search_listbox); |
744 |
|
✗ |
gtk_widget_class_bind_template_child (widget_class, CcPanelList, stack); |
745 |
|
|
|
746 |
|
✗ |
gtk_widget_class_bind_template_callback (widget_class, row_activated_cb); |
747 |
|
✗ |
gtk_widget_class_bind_template_callback (widget_class, search_row_activated_cb); |
748 |
|
✗ |
gtk_widget_class_bind_template_callback (widget_class, search_list_keynav_failed_cb); |
749 |
|
✗ |
} |
750 |
|
|
|
751 |
|
|
static void |
752 |
|
✗ |
cc_panel_list_init (CcPanelList *self) |
753 |
|
|
{ |
754 |
|
✗ |
gtk_widget_init_template (GTK_WIDGET (self)); |
755 |
|
|
|
756 |
|
✗ |
self->id_to_data = g_hash_table_new (g_str_hash, g_str_equal); |
757 |
|
✗ |
self->id_to_search_data = g_hash_table_new (g_str_hash, g_str_equal); |
758 |
|
✗ |
self->view = CC_PANEL_LIST_MAIN; |
759 |
|
|
|
760 |
|
✗ |
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->main_listbox), |
761 |
|
|
sort_function, |
762 |
|
|
self, |
763 |
|
|
NULL); |
764 |
|
|
|
765 |
|
✗ |
gtk_list_box_set_header_func (GTK_LIST_BOX (self->main_listbox), |
766 |
|
|
header_func, |
767 |
|
|
self, |
768 |
|
|
NULL); |
769 |
|
|
|
770 |
|
|
/* Search listbox */ |
771 |
|
✗ |
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->search_listbox), |
772 |
|
|
search_sort_function, |
773 |
|
|
self, |
774 |
|
|
NULL); |
775 |
|
|
|
776 |
|
✗ |
gtk_list_box_set_filter_func (GTK_LIST_BOX (self->search_listbox), |
777 |
|
|
filter_func, |
778 |
|
|
self, |
779 |
|
|
NULL); |
780 |
|
✗ |
} |
781 |
|
|
|
782 |
|
|
GtkWidget* |
783 |
|
✗ |
cc_panel_list_new (void) |
784 |
|
|
{ |
785 |
|
✗ |
return g_object_new (CC_TYPE_PANEL_LIST, NULL); |
786 |
|
|
} |
787 |
|
|
|
788 |
|
|
void |
789 |
|
✗ |
cc_panel_list_center_activated_row (CcPanelList *self, |
790 |
|
|
gboolean val) |
791 |
|
|
{ |
792 |
|
✗ |
g_return_if_fail (CC_IS_PANEL_LIST (self)); |
793 |
|
|
|
794 |
|
✗ |
if (self->center_activated_row != val) |
795 |
|
✗ |
self->center_activated_row = val; |
796 |
|
|
} |
797 |
|
|
|
798 |
|
|
gboolean |
799 |
|
✗ |
cc_panel_list_activate (CcPanelList *self) |
800 |
|
|
{ |
801 |
|
|
GtkListBoxRow *row; |
802 |
|
|
GtkWidget *listbox; |
803 |
|
✗ |
guint i = 0; |
804 |
|
|
|
805 |
|
✗ |
CC_ENTRY; |
806 |
|
|
|
807 |
|
✗ |
g_return_val_if_fail (CC_IS_PANEL_LIST (self), FALSE); |
808 |
|
|
|
809 |
|
✗ |
listbox = get_widget_from_view (self, self->view); |
810 |
|
✗ |
if (!GTK_IS_LIST_BOX (listbox)) |
811 |
|
✗ |
CC_RETURN (FALSE); |
812 |
|
|
|
813 |
|
|
/* Select the first visible row */ |
814 |
|
|
do |
815 |
|
✗ |
row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (listbox), i++); |
816 |
|
✗ |
while (row && !(gtk_widget_get_visible (GTK_WIDGET (row)) && |
817 |
|
✗ |
gtk_widget_get_child_visible (GTK_WIDGET (row)))); |
818 |
|
|
|
819 |
|
|
/* If the row is valid, activate it */ |
820 |
|
✗ |
if (row) |
821 |
|
|
{ |
822 |
|
✗ |
gtk_list_box_select_row (GTK_LIST_BOX (listbox), row); |
823 |
|
✗ |
gtk_widget_grab_focus (GTK_WIDGET (row)); |
824 |
|
|
|
825 |
|
✗ |
g_signal_emit_by_name (row, "activate"); |
826 |
|
|
} |
827 |
|
|
|
828 |
|
✗ |
CC_RETURN (row != NULL); |
829 |
|
|
} |
830 |
|
|
|
831 |
|
|
const gchar* |
832 |
|
✗ |
cc_panel_list_get_search_query (CcPanelList *self) |
833 |
|
|
{ |
834 |
|
✗ |
g_return_val_if_fail (CC_IS_PANEL_LIST (self), NULL); |
835 |
|
|
|
836 |
|
✗ |
return self->search_query; |
837 |
|
|
} |
838 |
|
|
|
839 |
|
|
void |
840 |
|
✗ |
cc_panel_list_set_search_query (CcPanelList *self, |
841 |
|
|
const gchar *search) |
842 |
|
|
{ |
843 |
|
✗ |
g_return_if_fail (CC_IS_PANEL_LIST (self)); |
844 |
|
|
|
845 |
|
✗ |
if (g_strcmp0 (self->search_query, search) != 0) |
846 |
|
|
{ |
847 |
|
✗ |
g_autofree gchar *search_query_normalized; |
848 |
|
|
|
849 |
|
✗ |
g_clear_pointer (&self->search_query, g_free); |
850 |
|
✗ |
g_clear_pointer (&self->search_words, g_strfreev); |
851 |
|
|
|
852 |
|
✗ |
self->search_query = g_strdup (search); |
853 |
|
|
|
854 |
|
|
/* Split on spaces */ |
855 |
|
✗ |
search_query_normalized = cc_util_normalize_casefold_and_unaccent (search); |
856 |
|
✗ |
self->search_words = g_strsplit (g_strstrip (search_query_normalized), " ", 0); |
857 |
|
|
|
858 |
|
✗ |
update_search (self); |
859 |
|
|
|
860 |
|
✗ |
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_QUERY]); |
861 |
|
|
|
862 |
|
✗ |
gtk_list_box_invalidate_filter (GTK_LIST_BOX (self->search_listbox)); |
863 |
|
✗ |
gtk_list_box_invalidate_sort (GTK_LIST_BOX (self->search_listbox)); |
864 |
|
|
} |
865 |
|
|
} |
866 |
|
|
|
867 |
|
|
CcPanelListView |
868 |
|
✗ |
cc_panel_list_get_view (CcPanelList *self) |
869 |
|
|
{ |
870 |
|
✗ |
g_return_val_if_fail (CC_IS_PANEL_LIST (self), -1); |
871 |
|
|
|
872 |
|
✗ |
return self->view; |
873 |
|
|
} |
874 |
|
|
|
875 |
|
|
/** |
876 |
|
|
* cc_panel_list_get_current_panel: |
877 |
|
|
* @self: a #CcPanelList |
878 |
|
|
* |
879 |
|
|
* Returns: (allow-none): id string of current active panel on @self, or %NULL when there's none yet. |
880 |
|
|
*/ |
881 |
|
|
const gchar* |
882 |
|
✗ |
cc_panel_list_get_current_panel (CcPanelList *self) |
883 |
|
|
{ |
884 |
|
✗ |
g_return_val_if_fail (CC_IS_PANEL_LIST (self), NULL); |
885 |
|
|
|
886 |
|
✗ |
return self->current_panel_id; |
887 |
|
|
} |
888 |
|
|
|
889 |
|
|
void |
890 |
|
✗ |
cc_panel_list_add_panel (CcPanelList *self, |
891 |
|
|
CcPanelCategory category, |
892 |
|
|
const gchar *id, |
893 |
|
|
const gchar *title, |
894 |
|
|
const gchar *description, |
895 |
|
|
const GStrv keywords, |
896 |
|
|
const gchar *icon, |
897 |
|
|
CcPanelVisibility visibility) |
898 |
|
|
{ |
899 |
|
|
RowData *data, *search_data; |
900 |
|
|
|
901 |
|
✗ |
g_return_if_fail (CC_IS_PANEL_LIST (self)); |
902 |
|
|
|
903 |
|
|
/* Add the panel to the proper listbox */ |
904 |
|
✗ |
data = row_data_new (category, id, title, description, keywords, icon, visibility); |
905 |
|
✗ |
gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE); |
906 |
|
|
|
907 |
|
✗ |
gtk_list_box_append (GTK_LIST_BOX (self->main_listbox), data->row); |
908 |
|
|
|
909 |
|
|
/* And add to the search listbox too */ |
910 |
|
✗ |
search_data = row_data_new (category, id, title, description, keywords, icon, visibility); |
911 |
|
✗ |
gtk_widget_set_visible (search_data->row, visibility != CC_PANEL_HIDDEN); |
912 |
|
|
|
913 |
|
✗ |
gtk_list_box_append (GTK_LIST_BOX (self->search_listbox), search_data->row); |
914 |
|
|
|
915 |
|
✗ |
g_hash_table_insert (self->id_to_data, data->id, data); |
916 |
|
✗ |
g_hash_table_insert (self->id_to_search_data, search_data->id, search_data); |
917 |
|
|
} |
918 |
|
|
|
919 |
|
|
/* Scrolls sibebar so that @row is at middle of the visible part of list */ |
920 |
|
|
static void |
921 |
|
✗ |
cc_panel_list_scroll_to_center_row (CcPanelList *self, |
922 |
|
|
GtkWidget *row) |
923 |
|
|
{ |
924 |
|
|
double target_value; |
925 |
|
|
graphene_point_t p; |
926 |
|
|
GtkAdjustment *adj; |
927 |
|
|
GtkWidget *scrolled_window; |
928 |
|
|
|
929 |
|
✗ |
g_return_if_fail (GTK_IS_LIST_BOX_ROW (row)); |
930 |
|
|
|
931 |
|
✗ |
scrolled_window = gtk_widget_get_ancestor (row, GTK_TYPE_SCROLLED_WINDOW); |
932 |
|
✗ |
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)); |
933 |
|
✗ |
if (!adj) |
934 |
|
✗ |
return; |
935 |
|
|
|
936 |
|
✗ |
if (!gtk_widget_compute_point (row, GTK_WIDGET (self), &GRAPHENE_POINT_INIT (0, 0), &p)) |
937 |
|
✗ |
return; |
938 |
|
|
|
939 |
|
✗ |
target_value = p.y + gtk_widget_get_height (row) / 2; |
940 |
|
|
|
941 |
|
✗ |
gtk_adjustment_set_value (adj, target_value - gtk_adjustment_get_page_size (adj) / 2); |
942 |
|
|
} |
943 |
|
|
|
944 |
|
|
typedef struct { |
945 |
|
|
CcPanelList *panel_list; |
946 |
|
|
GtkWidget *row; |
947 |
|
|
} ScrollData; |
948 |
|
|
|
949 |
|
|
static gboolean |
950 |
|
✗ |
scroll_to_idle_cb (ScrollData *data) |
951 |
|
|
{ |
952 |
|
✗ |
cc_panel_list_scroll_to_center_row (data->panel_list, data->row); |
953 |
|
|
|
954 |
|
✗ |
g_object_unref (data->panel_list); |
955 |
|
✗ |
g_object_unref (data->row); |
956 |
|
✗ |
g_free (data); |
957 |
|
|
|
958 |
|
✗ |
return FALSE; |
959 |
|
|
} |
960 |
|
|
|
961 |
|
|
/** |
962 |
|
|
* cc_panel_list_set_active_panel: |
963 |
|
|
* @self: a #CcPanelList |
964 |
|
|
* @id: the id of the panel to be activated |
965 |
|
|
* |
966 |
|
|
* Sets the current active panel. |
967 |
|
|
*/ |
968 |
|
|
void |
969 |
|
✗ |
cc_panel_list_set_active_panel (CcPanelList *self, |
970 |
|
|
const gchar *id) |
971 |
|
|
{ |
972 |
|
|
GtkWidget *listbox; |
973 |
|
|
RowData *data; |
974 |
|
|
ScrollData *sdata; |
975 |
|
✗ |
gboolean scroll_to_center = FALSE; |
976 |
|
|
|
977 |
|
✗ |
g_return_if_fail (CC_IS_PANEL_LIST (self)); |
978 |
|
|
|
979 |
|
✗ |
data = g_hash_table_lookup (self->id_to_data, id); |
980 |
|
|
|
981 |
|
✗ |
g_assert (data != NULL); |
982 |
|
|
|
983 |
|
✗ |
if (self->center_activated_row) |
984 |
|
|
{ |
985 |
|
✗ |
scroll_to_center = TRUE; |
986 |
|
✗ |
self->center_activated_row = FALSE; |
987 |
|
|
} |
988 |
|
|
|
989 |
|
|
/* Stop if row is supposed to be always hidden */ |
990 |
|
✗ |
if (data->visibility == CC_PANEL_HIDDEN) |
991 |
|
|
{ |
992 |
|
✗ |
g_debug ("Panel '%s' is always hidden, stopping.", id); |
993 |
|
✗ |
cc_panel_list_activate (self); |
994 |
|
✗ |
return; |
995 |
|
|
} |
996 |
|
|
|
997 |
|
|
/* If the currently selected panel is not always visible, for example when |
998 |
|
|
* the panel is only visible on search and we're temporarily seeing it, make |
999 |
|
|
* sure to hide it after the user moves out. |
1000 |
|
|
*/ |
1001 |
|
✗ |
if (self->current_panel_id != NULL && g_strcmp0 (self->current_panel_id, id) != 0) |
1002 |
|
|
{ |
1003 |
|
|
RowData *current_row_data; |
1004 |
|
|
|
1005 |
|
✗ |
current_row_data = g_hash_table_lookup (self->id_to_data, self->current_panel_id); |
1006 |
|
|
|
1007 |
|
|
/* We cannot be showing a non-existent panel */ |
1008 |
|
✗ |
g_assert (current_row_data != NULL); |
1009 |
|
|
|
1010 |
|
✗ |
gtk_widget_set_visible (current_row_data->row, current_row_data->visibility == CC_PANEL_VISIBLE); |
1011 |
|
|
} |
1012 |
|
|
|
1013 |
|
✗ |
listbox = gtk_widget_get_parent (data->row); |
1014 |
|
|
|
1015 |
|
|
/* The row might be hidden now, so make sure it's visible */ |
1016 |
|
✗ |
gtk_widget_set_visible (data->row, TRUE); |
1017 |
|
|
|
1018 |
|
✗ |
gtk_list_box_select_row (GTK_LIST_BOX (listbox), GTK_LIST_BOX_ROW (data->row)); |
1019 |
|
✗ |
gtk_widget_grab_focus (data->row); |
1020 |
|
|
|
1021 |
|
|
/* When setting the active panel programatically, prevent from |
1022 |
|
|
* autoselecting the first panel of the new view. |
1023 |
|
|
*/ |
1024 |
|
✗ |
self->autoselect_panel = FALSE; |
1025 |
|
|
|
1026 |
|
✗ |
g_signal_emit_by_name (data->row, "activate"); |
1027 |
|
|
|
1028 |
|
|
/* Store the current panel id */ |
1029 |
|
✗ |
g_clear_pointer (&self->current_panel_id, g_free); |
1030 |
|
✗ |
self->current_panel_id = g_strdup (id); |
1031 |
|
|
|
1032 |
|
|
/* This centering is currently set for panels activated from Search |
1033 |
|
|
* or from set_active_panel_from_id() CcShell iface */ |
1034 |
|
✗ |
if (scroll_to_center) |
1035 |
|
|
{ |
1036 |
|
|
/* Scroll the sidebar to the selected panel row, as that row may be |
1037 |
|
|
* out of view when panel is launched from a search or from cli */ |
1038 |
|
✗ |
sdata = g_new (ScrollData, 1); |
1039 |
|
✗ |
sdata->panel_list = g_object_ref (self); |
1040 |
|
✗ |
sdata->row = g_object_ref (data->row); |
1041 |
|
|
|
1042 |
|
✗ |
g_idle_add (G_SOURCE_FUNC (scroll_to_idle_cb), sdata); |
1043 |
|
|
} |
1044 |
|
|
} |
1045 |
|
|
|
1046 |
|
|
/** |
1047 |
|
|
* cc_panel_list_set_panel_visibility: |
1048 |
|
|
* @self: a #CcPanelList |
1049 |
|
|
* @id: the id of the panel |
1050 |
|
|
* @visibility: visibility of panel with @id |
1051 |
|
|
* |
1052 |
|
|
* Sets the visibility of panel with @id. @id must be a valid |
1053 |
|
|
* id with a corresponding panel. |
1054 |
|
|
*/ |
1055 |
|
|
void |
1056 |
|
✗ |
cc_panel_list_set_panel_visibility (CcPanelList *self, |
1057 |
|
|
const gchar *id, |
1058 |
|
|
CcPanelVisibility visibility) |
1059 |
|
|
{ |
1060 |
|
|
RowData *data, *search_data; |
1061 |
|
|
|
1062 |
|
✗ |
g_return_if_fail (CC_IS_PANEL_LIST (self)); |
1063 |
|
|
|
1064 |
|
✗ |
data = g_hash_table_lookup (self->id_to_data, id); |
1065 |
|
✗ |
search_data = g_hash_table_lookup (self->id_to_search_data, id); |
1066 |
|
|
|
1067 |
|
✗ |
g_assert (data != NULL); |
1068 |
|
✗ |
g_assert (search_data != NULL); |
1069 |
|
|
|
1070 |
|
✗ |
data->visibility = visibility; |
1071 |
|
|
|
1072 |
|
|
/* If this is the currently selected row, and the panel can't be displayed |
1073 |
|
|
* (i.e. visibility != VISIBLE), then select the next possible row */ |
1074 |
|
✗ |
if (gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (data->row)) && |
1075 |
|
|
visibility != CC_PANEL_VISIBLE) |
1076 |
|
|
{ |
1077 |
|
✗ |
activate_row_below (self, data); |
1078 |
|
|
} |
1079 |
|
|
|
1080 |
|
✗ |
gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE); |
1081 |
|
✗ |
gtk_widget_set_visible (search_data->row, visibility =! CC_PANEL_HIDDEN); |
1082 |
|
|
} |
1083 |
|
|
|
1084 |
|
|
void |
1085 |
|
✗ |
cc_panel_list_set_selection_mode (CcPanelList *self, |
1086 |
|
|
GtkSelectionMode selection_mode) |
1087 |
|
|
{ |
1088 |
|
✗ |
g_return_if_fail (CC_IS_PANEL_LIST (self)); |
1089 |
|
|
|
1090 |
|
✗ |
gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->main_listbox), selection_mode); |
1091 |
|
|
|
1092 |
|
|
/* When selection mode changed, selection will be lost. So reselect */ |
1093 |
|
✗ |
if (selection_mode == GTK_SELECTION_SINGLE && self->current_panel_id) |
1094 |
|
|
{ |
1095 |
|
|
GtkWidget *listbox; |
1096 |
|
|
RowData *data; |
1097 |
|
|
|
1098 |
|
✗ |
data = g_hash_table_lookup (self->id_to_data, self->current_panel_id); |
1099 |
|
✗ |
listbox = gtk_widget_get_parent (data->row); |
1100 |
|
✗ |
gtk_list_box_select_row (GTK_LIST_BOX (listbox), GTK_LIST_BOX_ROW (data->row)); |
1101 |
|
|
} |
1102 |
|
|
} |
1103 |
|
|
|
1104 |
|
|
|