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 : 5 : gtk_label_set_label (self->device_status, _("Unpaired"));
237 : 5 : gtk_widget_remove_css_class (GTK_WIDGET (self->device_status), "dim-label");
238 : : }
239 [ # # ]: 0 : 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 : 0 : gtk_label_set_label (self->device_status, _("Connected"));
247 : 0 : 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,
294 : : G_CONNECT_DEFAULT);
295 : 2 : g_signal_connect_object (self->device,
296 : : "action-enabled-changed::connectivity_report.state",
297 : : G_CALLBACK (on_connectivity_enabled_changed),
298 : : self,
299 : : G_CONNECT_DEFAULT);
300 : : }
301 : :
302 : 2 : valent_device_row_sync (self);
303 : 2 : }
304 : :
305 : : static void
306 : 2 : valent_device_row_dispose (GObject *object)
307 : : {
308 : 2 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
309 : :
310 [ + - ]: 2 : if (self->animation != NULL)
311 : : {
312 : 2 : adw_animation_skip (self->animation);
313 [ + - ]: 2 : g_clear_object (&self->animation);
314 : : }
315 : :
316 [ + - ]: 2 : g_clear_object (&self->device);
317 : :
318 : 2 : G_OBJECT_CLASS (valent_device_row_parent_class)->dispose (object);
319 : 2 : }
320 : :
321 : : static void
322 : 2 : valent_device_row_get_property (GObject *object,
323 : : guint prop_id,
324 : : GValue *value,
325 : : GParamSpec *pspec)
326 : : {
327 : 2 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
328 : :
329 [ - - + - ]: 2 : switch ((ValentDeviceRowProperty)prop_id)
330 : : {
331 : 0 : case PROP_DEVICE:
332 : 0 : g_value_set_object (value, self->device);
333 : 0 : break;
334 : :
335 : 0 : case PROP_SELECTED:
336 : 0 : g_value_set_boolean (value, self->selected);
337 : 0 : break;
338 : :
339 : 2 : case PROP_SELECTION_MODE:
340 : 2 : g_value_set_boolean (value, self->selection_mode);
341 : 2 : break;
342 : :
343 : 0 : default:
344 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
345 : : }
346 : 2 : }
347 : :
348 : : static void
349 : 7 : valent_device_row_set_property (GObject *object,
350 : : guint prop_id,
351 : : const GValue *value,
352 : : GParamSpec *pspec)
353 : : {
354 : 7 : ValentDeviceRow *self = VALENT_DEVICE_ROW (object);
355 : :
356 [ + + + - ]: 7 : switch ((ValentDeviceRowProperty)prop_id)
357 : : {
358 : 2 : case PROP_DEVICE:
359 [ - + ]: 2 : g_assert (self->device == NULL);
360 : 2 : self->device = g_value_dup_object (value);
361 : 2 : break;
362 : :
363 : 2 : case PROP_SELECTED:
364 : 2 : valent_device_row_set_selected (self, g_value_get_boolean (value));
365 : 2 : break;
366 : :
367 : 3 : case PROP_SELECTION_MODE:
368 : 3 : valent_device_row_set_selection_mode (self, g_value_get_boolean (value));
369 : 3 : break;
370 : :
371 : 0 : default:
372 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
373 : : }
374 : 7 : }
375 : :
376 : : static void
377 : 2 : valent_device_row_class_init (ValentDeviceRowClass *klass)
378 : : {
379 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
380 : 2 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
381 : :
382 : 2 : object_class->constructed = valent_device_row_constructed;
383 : 2 : object_class->dispose = valent_device_row_dispose;
384 : 2 : object_class->get_property = valent_device_row_get_property;
385 : 2 : object_class->set_property = valent_device_row_set_property;
386 : :
387 : 2 : gtk_widget_class_set_template_from_resource (widget_class, "/plugins/gnome/valent-device-row.ui");
388 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, revealer);
389 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, selected_button);
390 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, device_icon);
391 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, device_name);
392 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, device_status);
393 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, status_stack);
394 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, battery_status);
395 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, connectivity_status);
396 : 2 : gtk_widget_class_bind_template_child (widget_class, ValentDeviceRow, suffixes);
397 : 2 : gtk_widget_class_bind_template_callback (widget_class, on_selection_enable);
398 : 2 : gtk_widget_class_bind_template_callback (widget_class, on_selection_disable);
399 : :
400 : : /**
401 : : * ValentDeviceRow:device
402 : : *
403 : : * The `ValentDevice` for this row.
404 : : */
405 : 4 : properties [PROP_DEVICE] =
406 : 2 : g_param_spec_object ("device", NULL, NULL,
407 : : VALENT_TYPE_DEVICE,
408 : : (G_PARAM_READWRITE |
409 : : G_PARAM_CONSTRUCT_ONLY |
410 : : G_PARAM_EXPLICIT_NOTIFY |
411 : : G_PARAM_STATIC_STRINGS));
412 : :
413 : : /**
414 : : * ValentDeviceRow:selected: (getter get_selected) (setter set_selected)
415 : : *
416 : : * Whether the row is selected.
417 : : */
418 : 4 : properties [PROP_SELECTED] =
419 : 2 : g_param_spec_boolean ("selected", NULL, NULL,
420 : : FALSE,
421 : : (G_PARAM_READWRITE |
422 : : G_PARAM_EXPLICIT_NOTIFY |
423 : : G_PARAM_STATIC_STRINGS));
424 : :
425 : : /**
426 : : * ValentDeviceRow:selection-mode: (getter get_selection_mode) (setter set_selection_mode)
427 : : *
428 : : * Whether the row is in selection mode.
429 : : */
430 : 4 : properties [PROP_SELECTION_MODE] =
431 : 2 : g_param_spec_boolean ("selection-mode", NULL, NULL,
432 : : FALSE,
433 : : (G_PARAM_READWRITE |
434 : : G_PARAM_CONSTRUCT |
435 : : G_PARAM_EXPLICIT_NOTIFY |
436 : : G_PARAM_STATIC_STRINGS));
437 : :
438 : 2 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
439 : 2 : }
440 : :
441 : : static void
442 : 2 : valent_device_row_init (ValentDeviceRow *self)
443 : : {
444 : 2 : AdwAnimationTarget *target = NULL;
445 : :
446 : 2 : gtk_widget_init_template (GTK_WIDGET (self));
447 : :
448 : 2 : target = adw_property_animation_target_new (G_OBJECT (self->suffixes),
449 : : "opacity");
450 : 2 : self->animation = adw_timed_animation_new (GTK_WIDGET (self),
451 : : 0.0, 1.0, 250,
452 : : target);
453 : 2 : }
454 : :
455 : : /**
456 : : * valent_device_row_get_device:
457 : : * @row: a `ValentDeviceRow`
458 : : *
459 : : * Get the `ValentDevice` for @row.
460 : : *
461 : : * Returns: (transfer none): a `ValentDevice`
462 : : */
463 : : ValentDevice *
464 : 0 : valent_device_row_get_device (ValentDeviceRow *row)
465 : : {
466 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_DEVICE_ROW (row), NULL);
467 : :
468 : 0 : return row->device;
469 : : }
470 : :
471 : : /**
472 : : * valent_device_row_get_selected: (get-property selected)
473 : : * @row: a `ValentDeviceRow`
474 : : *
475 : : * Get whether the row is selected.
476 : : *
477 : : * Returns: %TRUE if the row is selected, or %FALSE if not
478 : : */
479 : : gboolean
480 : 0 : valent_device_row_get_selected (ValentDeviceRow *row)
481 : : {
482 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_DEVICE_ROW (row), FALSE);
483 : :
484 : 0 : return row->selected;
485 : : }
486 : :
487 : : /**
488 : : * valent_device_row_set_selected: (set-property selected)
489 : : * @row: a `ValentDeviceRow`
490 : : * @selected: whether to select the row
491 : : *
492 : : * Set whether the row is selected.
493 : : */
494 : : void
495 : 2 : valent_device_row_set_selected (ValentDeviceRow *row,
496 : : gboolean selected)
497 : : {
498 [ - + ]: 2 : g_return_if_fail (VALENT_IS_DEVICE_ROW (row));
499 : :
500 : 2 : selected = !!selected;
501 [ - + ]: 2 : if (row->selected == selected)
502 : : return;
503 : :
504 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
505 : : GTK_ACCESSIBLE_STATE_SELECTED, selected,
506 : : -1);
507 : :
508 : 0 : row->selected = selected;
509 : 0 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_SELECTED]);
510 : : }
511 : :
512 : : /**
513 : : * valent_device_row_get_selection_mode:
514 : : * @row: a `ValentDeviceRow`
515 : : *
516 : : * Get whether selection mode is enabled.
517 : : *
518 : : * Returns: %TRUE if selection mode is enabled, or %FALSE if not
519 : : */
520 : : gboolean
521 : 0 : valent_device_row_get_selection_mode (ValentDeviceRow *row)
522 : : {
523 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_DEVICE_ROW (row), FALSE);
524 : :
525 : 0 : return row->selection_mode;
526 : : }
527 : :
528 : : /**
529 : : * valent_device_row_set_selection_mode: (set-property selection-mode)
530 : : * @row: a `ValentDeviceRow`
531 : : * @selection_mode: whether to select the row
532 : : *
533 : : * Set whether selection mode is enabled.
534 : : */
535 : : void
536 : 3 : valent_device_row_set_selection_mode (ValentDeviceRow *row,
537 : : gboolean selection_mode)
538 : : {
539 [ - + ]: 3 : g_return_if_fail (VALENT_IS_DEVICE_ROW (row));
540 : :
541 : 3 : selection_mode = !!selection_mode;
542 [ - + ]: 3 : if (row->selection_mode == selection_mode)
543 : : return;
544 : :
545 [ # # ]: 0 : if (selection_mode)
546 : : {
547 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
548 : : GTK_ACCESSIBLE_STATE_SELECTED, FALSE,
549 : : -1);
550 : : }
551 : : else
552 : : {
553 : 0 : gtk_accessible_reset_state (GTK_ACCESSIBLE (row),
554 : : GTK_ACCESSIBLE_STATE_SELECTED);
555 : : }
556 : :
557 : 0 : adw_animation_skip (row->animation);
558 : 0 : adw_timed_animation_set_reverse (ADW_TIMED_ANIMATION (row->animation),
559 : : selection_mode);
560 : 0 : adw_animation_play (row->animation);
561 : :
562 : 0 : row->selection_mode = selection_mode;
563 : 0 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_SELECTION_MODE]);
564 : : }
565 : :
|