Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-device-row"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <glib/gi18n.h>
10 : : #include <gtk/gtk.h>
11 : : #include <pango/pango.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-device-row.h"
15 : : #include "valent-ui-utils-private.h"
16 : :
17 : :
18 : : struct _ValentDeviceRow
19 : : {
20 : : GtkListBoxRow parent_instance;
21 : :
22 : : ValentDevice *device;
23 : : AdwAnimation *animation;
24 : : unsigned int selected : 1;
25 : : unsigned int selection_mode : 1;
26 : :
27 : : /* template */
28 : : GtkRevealer *revealer;
29 : : GtkCheckButton *selected_button;
30 : : GtkWidget *device_icon;
31 : : GtkLabel *device_name;
32 : : GtkLabel *device_status;
33 : : GtkStack *status_stack;
34 : : GtkWidget *battery_status;
35 : : GtkWidget *connectivity_status;
36 : : GtkImage *suffixes;
37 : : };
38 : :
39 [ + + + - ]: 48 : G_DEFINE_FINAL_TYPE (ValentDeviceRow, valent_device_row, GTK_TYPE_LIST_BOX_ROW)
40 : :
41 : : typedef enum {
42 : : PROP_DEVICE = 1,
43 : : PROP_SELECTED,
44 : : PROP_SELECTION_MODE,
45 : : } ValentDeviceRowProperty;
46 : :
47 : : static GParamSpec *properties[PROP_SELECTION_MODE + 1] = { NULL, };
48 : :
49 : : static void
50 : 0 : on_selection_enable (ValentDeviceRow *self)
51 : : {
52 : 0 : GtkRoot *root = NULL;
53 : 0 : GParamSpec *pspec = NULL;
54 : :
55 [ # # ]: 0 : if (self->selection_mode)
56 : : return;
57 : :
58 : : // FIXME
59 : 0 : root = gtk_widget_get_root (GTK_WIDGET (self));
60 [ # # ]: 0 : if (root != NULL)
61 : : {
62 : 0 : pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (root),
63 : : "selection-mode");
64 : : }
65 : :
66 [ # # ]: 0 : if (pspec != NULL)
67 : : {
68 : 0 : g_object_set (root, "selection-mode", TRUE, NULL);
69 : 0 : valent_device_row_set_selected (self, TRUE);
70 : : }
71 : : }
72 : :
73 : : static void
74 : 0 : on_selection_disable (ValentDeviceRow *self)
75 : : {
76 [ # # ]: 0 : if (!gtk_revealer_get_child_revealed (self->revealer))
77 : 0 : valent_device_row_set_selected (self, FALSE);
78 : 0 : }
79 : :
80 : : /*
81 : : * Battery
82 : : */
83 : : static void
84 : 0 : on_battery_state_changed (GActionGroup *action_group,
85 : : const char *action_name,
86 : : GVariant *value,
87 : : ValentDeviceRow *self)
88 : : {
89 : 0 : g_autofree char *label = NULL;
90 : 0 : gboolean charging = FALSE;
91 : 0 : gboolean is_present = FALSE;
92 : 0 : double percentage = 0.0;
93 : 0 : const char *icon_name;
94 : :
95 [ # # ]: 0 : g_assert (VALENT_IS_DEVICE_ROW (self));
96 : :
97 : 0 : g_variant_lookup (value, "is-present", "b", &is_present);
98 [ # # ]: 0 : if (!is_present)
99 : : {
100 : 0 : gtk_widget_set_visible (self->battery_status, FALSE);
101 : 0 : return;
102 : : }
103 : :
104 [ # # # # ]: 0 : if (!g_variant_lookup (value, "percentage", "d", &percentage) ||
105 : 0 : !g_variant_lookup (value, "charging", "b", &charging))
106 : : {
107 : 0 : gtk_widget_set_visible (self->battery_status, FALSE);
108 : 0 : return;
109 : : }
110 : :
111 [ # # ]: 0 : if (!g_variant_lookup (value, "icon-name", "&s", &icon_name))
112 : 0 : icon_name = "battery-missing-symbolic";
113 : :
114 [ # # ]: 0 : if (percentage >= 100.0)
115 : : {
116 : : /* TRANSLATORS: When the battery level is 100%
117 : : */
118 [ # # ]: 0 : label = g_strdup (_("Fully Charged"));
119 : : }
120 : : else
121 : : {
122 : 0 : int64_t total_seconds = 0;
123 : 0 : unsigned int total_minutes;
124 : 0 : unsigned int minutes;
125 : 0 : unsigned int hours;
126 : :
127 [ # # ]: 0 : if (charging)
128 : 0 : g_variant_lookup (value, "time-to-full", "x", &total_seconds);
129 : : else
130 : 0 : g_variant_lookup (value, "time-to-empty", "x", &total_seconds);
131 : :
132 [ # # ]: 0 : if (total_seconds > 0)
133 : : {
134 : 0 : total_minutes = (unsigned int)floor (total_seconds / 60);
135 : 0 : minutes = total_minutes % 60;
136 : 0 : hours = (unsigned int)floor (total_minutes / 60);
137 : : }
138 : :
139 [ # # ]: 0 : if (total_seconds <= 0)
140 : : {
141 : : /* TRANSLATORS: This is <percentage> (Estimating…)
142 : : */
143 : 0 : label = g_strdup_printf (_("%g%% (Estimating…)"), percentage);
144 : : }
145 [ # # ]: 0 : else if (charging)
146 : : {
147 : : /* TRANSLATORS: This is <percentage> (<hours>:<minutes> Until Full)
148 : : */
149 : 0 : label = g_strdup_printf (_("%g%% (%d∶%02d Until Full)"),
150 : : percentage, hours, minutes);
151 : : }
152 : : else
153 : : {
154 : : /* TRANSLATORS: This is <percentage> (<hours>:<minutes> Remaining)
155 : : */
156 : 0 : label = g_strdup_printf (_("%g%% (%d∶%02d Remaining)"),
157 : : percentage, hours, minutes);
158 : : }
159 : : }
160 : :
161 : 0 : gtk_image_set_from_icon_name (GTK_IMAGE (self->battery_status), icon_name);
162 : 0 : gtk_widget_set_tooltip_text (self->battery_status, label);
163 : 0 : gtk_widget_set_visible (self->battery_status, TRUE);
164 : : }
165 : :
166 : : static void
167 : 5 : on_battery_enabled_changed (GActionGroup *action_group,
168 : : const char *action_name,
169 : : gboolean enabled,
170 : : ValentDeviceRow *self)
171 : : {
172 : 10 : g_autoptr (GVariant) state = NULL;
173 : :
174 [ - + ]: 5 : if (enabled)
175 : 0 : state = g_action_group_get_action_state (action_group, action_name);
176 : :
177 [ # # ]: 0 : if (state != NULL)
178 : 0 : on_battery_state_changed (action_group, action_name, state, self);
179 : :
180 [ - + ]: 5 : gtk_widget_set_visible (self->battery_status, enabled);
181 : 5 : }
182 : :
183 : : /*
184 : : * Connectivity Status
185 : : */
186 : : static void
187 : 0 : on_connectivity_state_changed (GActionGroup *action_group,
188 : : const char *action_name,
189 : : GVariant *value,
190 : : ValentDeviceRow *self)
191 : : {
192 : 0 : const char *icon_name;
193 : 0 : const char *title;
194 : :
195 [ # # ]: 0 : g_assert (VALENT_IS_DEVICE_ROW (self));
196 : :
197 [ # # ]: 0 : if (g_variant_lookup (value, "icon-name", "&s", &icon_name))
198 : 0 : gtk_image_set_from_icon_name (GTK_IMAGE (self->connectivity_status), icon_name);
199 : :
200 [ # # ]: 0 : if (g_variant_lookup (value, "title", "&s", &title))
201 : 0 : gtk_widget_set_tooltip_text (self->battery_status, title);
202 : :
203 : 0 : gtk_widget_set_visible (self->connectivity_status, TRUE);
204 : 0 : }
205 : :
206 : : static void
207 : 5 : on_connectivity_enabled_changed (GActionGroup *action_group,
208 : : const char *action_name,
209 : : gboolean enabled,
210 : : ValentDeviceRow *self)
211 : : {
212 : 10 : g_autoptr (GVariant) state = NULL;
213 : :
214 [ - + ]: 5 : if (enabled)
215 : 0 : state = g_action_group_get_action_state (action_group, action_name);
216 : :
217 [ # # ]: 0 : if (state != NULL)
218 : 0 : on_connectivity_state_changed (action_group, action_name, state, self);
219 : :
220 [ - + ]: 5 : gtk_widget_set_visible (self->connectivity_status, enabled);
221 : 5 : }
222 : :
223 : : static void
224 : 5 : valent_device_row_sync (ValentDeviceRow *self)
225 : : {
226 : 5 : GActionGroup *action_group = G_ACTION_GROUP (self->device);
227 : 5 : ValentDeviceState state;
228 : 5 : gboolean enabled;
229 : :
230 [ + - ]: 5 : g_assert (VALENT_IS_DEVICE_ROW (self));
231 [ - + ]: 5 : g_assert (VALENT_IS_DEVICE (self->device));
232 : :
233 : 5 : state = valent_device_get_state (self->device);
234 [ + + ]: 5 : if ((state & VALENT_DEVICE_STATE_PAIRED) == 0)
235 : : {
236 : 4 : gtk_label_set_label (self->device_status, _("Unpaired"));
237 : 4 : gtk_widget_remove_css_class (GTK_WIDGET (self->device_status), "dim-label");
238 : : }
239 [ - + ]: 1 : else if ((state & VALENT_DEVICE_STATE_CONNECTED) == 0)
240 : : {
241 : 0 : gtk_label_set_label (self->device_status, _("Disconnected"));
242 : 0 : gtk_widget_add_css_class (GTK_WIDGET (self->device_status), "dim-label");
243 : : }
244 : : else
245 : : {
246 : 1 : gtk_label_set_label (self->device_status, _("Connected"));
247 : 1 : gtk_widget_remove_css_class (GTK_WIDGET (self->device_status), "dim-label");
248 : : }
249 : :
250 : 5 : enabled = g_action_group_get_action_enabled (action_group, "battery.state");
251 : 5 : on_battery_enabled_changed (action_group, "battery.state", enabled, self);
252 : :
253 : 5 : enabled = g_action_group_get_action_enabled (action_group, "connectivity_report.state");
254 : 5 : on_connectivity_enabled_changed (action_group, "connectivity_report.state", enabled, self);
255 : 5 : }
256 : :
257 : : /*
258 : : * GObject
259 : : */
260 : : static void
261 : 2 : valent_device_row_constructed (GObject *object)
262 : : {
263 : 2 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
264 : :
265 : 2 : G_OBJECT_CLASS (valent_device_row_parent_class)->constructed (object);
266 : :
267 [ + - ]: 2 : if (self->device != NULL)
268 : : {
269 : 2 : g_object_bind_property (self->device, "name",
270 : 2 : self->device_name, "label",
271 : : G_BINDING_SYNC_CREATE);
272 : 2 : g_object_bind_property (self->device, "icon-name",
273 : 2 : self->device_icon, "icon-name",
274 : : G_BINDING_SYNC_CREATE);
275 : 2 : g_signal_connect_object (self->device,
276 : : "notify::state",
277 : : G_CALLBACK (valent_device_row_sync),
278 : : self,
279 : : G_CONNECT_SWAPPED);
280 : 2 : g_signal_connect_object (self->device,
281 : : "action-state-changed::battery.state",
282 : : G_CALLBACK (on_battery_state_changed),
283 : : self,
284 : : G_CONNECT_DEFAULT);
285 : 2 : g_signal_connect_object (self->device,
286 : : "action-enabled-changed::battery.state",
287 : : G_CALLBACK (on_battery_enabled_changed),
288 : : self,
289 : : G_CONNECT_DEFAULT);
290 : 2 : g_signal_connect_object (self->device,
291 : : "action-state-changed::connectivity_report.state",
292 : : G_CALLBACK (on_connectivity_state_changed),
293 : : self, 0);
294 : 2 : g_signal_connect_object (self->device,
295 : : "action-enabled-changed::connectivity_report.state",
296 : : G_CALLBACK (on_connectivity_enabled_changed),
297 : : self,
298 : : G_CONNECT_DEFAULT);
299 : : }
300 : :
301 : 2 : valent_device_row_sync (self);
302 : 2 : }
303 : :
304 : : static void
305 : 2 : valent_device_row_dispose (GObject *object)
306 : : {
307 : 2 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
308 : :
309 [ + - ]: 2 : if (self->animation != NULL)
310 : : {
311 : 2 : adw_animation_skip (self->animation);
312 [ + - ]: 2 : g_clear_object (&self->animation);
313 : : }
314 : :
315 [ + - ]: 2 : g_clear_object (&self->device);
316 : :
317 : 2 : G_OBJECT_CLASS (valent_device_row_parent_class)->dispose (object);
318 : 2 : }
319 : :
320 : : static void
321 : 2 : valent_device_row_get_property (GObject *object,
322 : : guint prop_id,
323 : : GValue *value,
324 : : GParamSpec *pspec)
325 : : {
326 : 2 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
327 : :
328 [ - - + - ]: 2 : switch ((ValentDeviceRowProperty)prop_id)
329 : : {
330 : 0 : case PROP_DEVICE:
331 : 0 : g_value_set_object (value, self->device);
332 : 0 : break;
333 : :
334 : 0 : case PROP_SELECTED:
335 : 0 : g_value_set_boolean (value, self->selected);
336 : 0 : break;
337 : :
338 : 2 : case PROP_SELECTION_MODE:
339 : 2 : g_value_set_boolean (value, self->selection_mode);
340 : 2 : break;
341 : :
342 : 0 : default:
343 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
344 : : }
345 : 2 : }
346 : :
347 : : static void
348 : 7 : valent_device_row_set_property (GObject *object,
349 : : guint prop_id,
350 : : const GValue *value,
351 : : GParamSpec *pspec)
352 : : {
353 : 7 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
354 : :
355 [ + + + - ]: 7 : switch ((ValentDeviceRowProperty)prop_id)
356 : : {
357 : 2 : case PROP_DEVICE:
358 [ + - ]: 2 : g_assert (self->device == NULL);
359 : 2 : self->device = g_value_dup_object (value);
360 : 2 : break;
361 : :
362 : 2 : case PROP_SELECTED:
363 : 2 : valent_device_row_set_selected (self, g_value_get_boolean (value));
364 : 2 : break;
365 : :
366 : 3 : case PROP_SELECTION_MODE:
367 : 3 : valent_device_row_set_selection_mode (self, g_value_get_boolean (value));
368 : 3 : break;
369 : :
370 : 0 : default:
371 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
372 : : }
373 : 7 : }
374 : :
375 : : static void
376 : 2 : valent_device_row_class_init (ValentDeviceRowClass *klass)
377 : : {
378 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
379 : 2 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
380 : :
381 : 2 : object_class->constructed = valent_device_row_constructed;
382 : 2 : object_class->dispose = valent_device_row_dispose;
383 : 2 : object_class->get_property = valent_device_row_get_property;
384 : 2 : object_class->set_property = valent_device_row_set_property;
385 : :
386 : 2 : gtk_widget_class_set_template_from_resource (widget_class, "/plugins/gnome/valent-device-row.ui");
387 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, revealer);
388 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, selected_button);
389 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, device_icon);
390 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, device_name);
391 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, device_status);
392 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, status_stack);
393 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, battery_status);
394 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, connectivity_status);
395 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, suffixes);
396 : 2 : gtk_widget_class_bind_template_callback (widget_class, on_selection_enable);
397 : 2 : gtk_widget_class_bind_template_callback (widget_class, on_selection_disable);
398 : :
399 : : /**
400 : : * ValentDeviceRow:device
401 : : *
402 : : * The `ValentDevice` for this row.
403 : : */
404 : 4 : properties [PROP_DEVICE] =
405 : 2 : g_param_spec_object ("device", NULL, NULL,
406 : : VALENT_TYPE_DEVICE,
407 : : (G_PARAM_READWRITE |
408 : : G_PARAM_CONSTRUCT_ONLY |
409 : : G_PARAM_EXPLICIT_NOTIFY |
410 : : G_PARAM_STATIC_STRINGS));
411 : :
412 : : /**
413 : : * ValentDeviceRow:selected: (getter get_selected) (setter set_selected)
414 : : *
415 : : * Whether the row is selected.
416 : : */
417 : 4 : properties [PROP_SELECTED] =
418 : 2 : g_param_spec_boolean ("selected", NULL, NULL,
419 : : FALSE,
420 : : (G_PARAM_READWRITE |
421 : : G_PARAM_EXPLICIT_NOTIFY |
422 : : G_PARAM_STATIC_STRINGS));
423 : :
424 : : /**
425 : : * ValentDeviceRow:selection-mode: (getter get_selection_mode) (setter set_selection_mode)
426 : : *
427 : : * Whether the row is in selection mode.
428 : : */
429 : 4 : properties [PROP_SELECTION_MODE] =
430 : 2 : g_param_spec_boolean ("selection-mode", NULL, NULL,
431 : : FALSE,
432 : : (G_PARAM_READWRITE |
433 : : G_PARAM_CONSTRUCT |
434 : : G_PARAM_EXPLICIT_NOTIFY |
435 : : G_PARAM_STATIC_STRINGS));
436 : :
437 : 2 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
438 : 2 : }
439 : :
440 : : static void
441 : 2 : valent_device_row_init (ValentDeviceRow *self)
442 : : {
443 : 2 : AdwAnimationTarget *target = NULL;
444 : :
445 : 2 : gtk_widget_init_template (GTK_WIDGET (self));
446 : :
447 : 2 : target = adw_property_animation_target_new (G_OBJECT (self->suffixes),
448 : : "opacity");
449 : 2 : self->animation = adw_timed_animation_new (GTK_WIDGET (self),
450 : : 0.0, 1.0, 250,
451 : : target);
452 : 2 : }
453 : :
454 : : /**
455 : : * valent_device_row_get_device:
456 : : * @row: a `ValentDeviceRow`
457 : : *
458 : : * Get the `ValentDevice` for @row.
459 : : *
460 : : * Returns: (transfer none): a `ValentDevice`
461 : : */
462 : : ValentDevice *
463 : 0 : valent_device_row_get_device (ValentDeviceRow *row)
464 : : {
465 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_DEVICE_ROW (row), NULL);
466 : :
467 : 0 : return row->device;
468 : : }
469 : :
470 : : /**
471 : : * valent_device_row_get_selected: (get-property selected)
472 : : * @row: a `ValentDeviceRow`
473 : : *
474 : : * Get whether the row is selected.
475 : : *
476 : : * Returns: %TRUE if the row is selected, or %FALSE if not
477 : : */
478 : : gboolean
479 : 0 : valent_device_row_get_selected (ValentDeviceRow *row)
480 : : {
481 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_DEVICE_ROW (row), FALSE);
482 : :
483 : 0 : return row->selected;
484 : : }
485 : :
486 : : /**
487 : : * valent_device_row_set_selected: (set-property selected)
488 : : * @row: a `ValentDeviceRow`
489 : : * @selected: whether to select the row
490 : : *
491 : : * Set whether the row is selected.
492 : : */
493 : : void
494 : 2 : valent_device_row_set_selected (ValentDeviceRow *row,
495 : : gboolean selected)
496 : : {
497 [ + - ]: 2 : g_return_if_fail (VALENT_IS_DEVICE_ROW (row));
498 : :
499 : 2 : selected = !!selected;
500 [ - + ]: 2 : if (row->selected == selected)
501 : : return;
502 : :
503 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
504 : : GTK_ACCESSIBLE_STATE_SELECTED, selected,
505 : : -1);
506 : :
507 : 0 : row->selected = selected;
508 : 0 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_SELECTED]);
509 : : }
510 : :
511 : : /**
512 : : * valent_device_row_get_selection_mode:
513 : : * @row: a `ValentDeviceRow`
514 : : *
515 : : * Get whether selection mode is enabled.
516 : : *
517 : : * Returns: %TRUE if selection mode is enabled, or %FALSE if not
518 : : */
519 : : gboolean
520 : 0 : valent_device_row_get_selection_mode (ValentDeviceRow *row)
521 : : {
522 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_DEVICE_ROW (row), FALSE);
523 : :
524 : 0 : return row->selection_mode;
525 : : }
526 : :
527 : : /**
528 : : * valent_device_row_set_selection_mode: (set-property selection-mode)
529 : : * @row: a `ValentDeviceRow`
530 : : * @selection_mode: whether to select the row
531 : : *
532 : : * Set whether selection mode is enabled.
533 : : */
534 : : void
535 : 3 : valent_device_row_set_selection_mode (ValentDeviceRow *row,
536 : : gboolean selection_mode)
537 : : {
538 [ + - ]: 3 : g_return_if_fail (VALENT_IS_DEVICE_ROW (row));
539 : :
540 : 3 : selection_mode = !!selection_mode;
541 [ - + ]: 3 : if (row->selection_mode == selection_mode)
542 : : return;
543 : :
544 [ # # ]: 0 : if (selection_mode)
545 : : {
546 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
547 : : GTK_ACCESSIBLE_STATE_SELECTED, FALSE,
548 : : -1);
549 : : }
550 : : else
551 : : {
552 : 0 : gtk_accessible_reset_state (GTK_ACCESSIBLE (row),
553 : : GTK_ACCESSIBLE_STATE_SELECTED);
554 : : }
555 : :
556 : 0 : adw_animation_skip (row->animation);
557 : 0 : adw_timed_animation_set_reverse (ADW_TIMED_ANIMATION (row->animation),
558 : : selection_mode);
559 : 0 : adw_animation_play (row->animation);
560 : :
561 : 0 : row->selection_mode = selection_mode;
562 : 0 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_SELECTION_MODE]);
563 : : }
564 : :
|