Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 : : /*
3 : : * Copyright 2024 GNOME Foundation, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2 of the License, or (at your option) any later version.
9 : : *
10 : : * This library 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 GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General
16 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 : : *
18 : : * Authors:
19 : : * - Philip Withnall <pwithnall@gnome.org>
20 : : *
21 : : * SPDX-License-Identifier: GPL-3.0-or-later
22 : : */
23 : :
24 : : #include "config.h"
25 : : #include <glib/gi18n.h>
26 : :
27 : : #include <adwaita.h>
28 : : #include <glib-object.h>
29 : : #include <gtk/gtk.h>
30 : :
31 : : #include "cc-duration-editor.h"
32 : : #include "cc-duration-row.h"
33 : :
34 : : /* Copied from panels/common/cc-util.c in gnome-control-center */
35 : : static char *
36 : 0 : cc_util_time_to_string_text (gint64 msecs)
37 : : {
38 : 0 : g_autofree gchar *hours = NULL;
39 : 0 : g_autofree gchar *mins = NULL;
40 : 0 : g_autofree gchar *secs = NULL;
41 : : gint sec, min, hour, _time;
42 : :
43 : 0 : _time = (int) (msecs / 1000);
44 : 0 : sec = _time % 60;
45 : 0 : _time = _time - sec;
46 : 0 : min = (_time % (60*60)) / 60;
47 : 0 : _time = _time - (min * 60);
48 : 0 : hour = _time / (60*60);
49 : :
50 : 0 : hours = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d hour", "%d hours", hour), hour);
51 : 0 : mins = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", min), min);
52 : 0 : secs = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d second", "%d seconds", sec), sec);
53 : :
54 [ # # ]: 0 : if (hour > 0)
55 : : {
56 [ # # # # ]: 0 : if (min > 0 && sec > 0)
57 : : {
58 : : /* 5 hours 2 minutes 12 seconds */
59 : 0 : return g_strdup_printf (C_("hours minutes seconds", "%s %s %s"), hours, mins, secs);
60 : : }
61 [ # # ]: 0 : else if (min > 0)
62 : : {
63 : : /* 5 hours 2 minutes */
64 : 0 : return g_strdup_printf (C_("hours minutes", "%s %s"), hours, mins);
65 : : }
66 : : else
67 : : {
68 : : /* 5 hours */
69 : 0 : return g_strdup_printf (C_("hours", "%s"), hours);
70 : : }
71 : : }
72 [ # # ]: 0 : else if (min > 0)
73 : : {
74 [ # # ]: 0 : if (sec > 0)
75 : : {
76 : : /* 2 minutes 12 seconds */
77 : 0 : return g_strdup_printf (C_("minutes seconds", "%s %s"), mins, secs);
78 : : }
79 : : else
80 : : {
81 : : /* 2 minutes */
82 : 0 : return g_strdup_printf (C_("minutes", "%s"), mins);
83 : : }
84 : : }
85 [ # # ]: 0 : else if (sec > 0)
86 : : {
87 : : /* 10 seconds */
88 : 0 : return g_strdup (secs);
89 : : }
90 : : else
91 : : {
92 : : /* 0 seconds */
93 : 0 : return g_strdup (_("0 seconds"));
94 : : }
95 : : }
96 : :
97 : : /**
98 : : * CcDurationRow:
99 : : *
100 : : * An #AdwActionRow used to enter a duration, represented as hours and minutes.
101 : : *
102 : : * The currently specified duration is shown in a label. If the row is activated
103 : : * a popover is shown containing a #CcDurationEditor to edit the duration.
104 : : */
105 : : struct _CcDurationRow {
106 : : AdwActionRow parent_instance;
107 : :
108 : : GtkWidget *arrow_box;
109 : : GtkLabel *current;
110 : : GtkPopover *popover;
111 : : CcDurationEditor *editor;
112 : : };
113 : :
114 [ # # # # : 0 : G_DEFINE_TYPE (CcDurationRow, cc_duration_row, ADW_TYPE_ACTION_ROW)
# # ]
115 : :
116 : : typedef enum {
117 : : PROP_DURATION = 1,
118 : : PROP_MINIMUM,
119 : : PROP_MAXIMUM,
120 : : } CcDurationRowProperty;
121 : :
122 : : static GParamSpec *props[PROP_MAXIMUM + 1];
123 : :
124 : : static void cc_duration_row_get_property (GObject *object,
125 : : guint property_id,
126 : : GValue *value,
127 : : GParamSpec *pspec);
128 : : static void cc_duration_row_set_property (GObject *object,
129 : : guint property_id,
130 : : const GValue *value,
131 : : GParamSpec *pspec);
132 : : static void cc_duration_row_dispose (GObject *object);
133 : : static void cc_duration_row_size_allocate (GtkWidget *widget,
134 : : int width,
135 : : int height,
136 : : int baseline);
137 : : static gboolean cc_duration_row_focus (GtkWidget *widget,
138 : : GtkDirectionType direction);
139 : : static void cc_duration_row_activate (AdwActionRow *row);
140 : : static void popover_notify_visible_cb (GObject *object,
141 : : GParamSpec *pspec,
142 : : gpointer user_data);
143 : : static void update_current_label (CcDurationRow *self);
144 : : static void editor_notify_duration_cb (GObject *object,
145 : : GParamSpec *pspec,
146 : : gpointer user_data);
147 : : static void editor_notify_minimum_cb (GObject *object,
148 : : GParamSpec *pspec,
149 : : gpointer user_data);
150 : : static void editor_notify_maximum_cb (GObject *object,
151 : : GParamSpec *pspec,
152 : : gpointer user_data);
153 : :
154 : : static void
155 : 0 : cc_duration_row_class_init (CcDurationRowClass *klass)
156 : : {
157 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
158 : 0 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
159 : 0 : AdwActionRowClass *row_class = ADW_ACTION_ROW_CLASS (klass);
160 : :
161 : 0 : object_class->get_property = cc_duration_row_get_property;
162 : 0 : object_class->set_property = cc_duration_row_set_property;
163 : 0 : object_class->dispose = cc_duration_row_dispose;
164 : :
165 : 0 : widget_class->size_allocate = cc_duration_row_size_allocate;
166 : 0 : widget_class->focus = cc_duration_row_focus;
167 : :
168 : 0 : row_class->activate = cc_duration_row_activate;
169 : :
170 : : /**
171 : : * CcDurationRow:duration:
172 : : *
173 : : * Duration displayed in the row or chosen in the editor, in minutes.
174 : : */
175 : 0 : props[PROP_DURATION] =
176 : 0 : g_param_spec_uint ("duration",
177 : : NULL, NULL,
178 : : 0, G_MAXUINT, 0,
179 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
180 : :
181 : : /**
182 : : * CcDurationRow:minimum:
183 : : *
184 : : * Minimum allowed value (inclusive) for #CcDurationRow:duration, in
185 : : * minutes.
186 : : *
187 : : * If this is changed and the current value of #CcDurationRow:duration is
188 : : * lower than it, the value of #CcDurationRow:duration will automatically
189 : : * be clamped to the new minimum.
190 : : */
191 : 0 : props[PROP_MINIMUM] =
192 : 0 : g_param_spec_uint ("minimum",
193 : : NULL, NULL,
194 : : 0, G_MAXUINT, 0,
195 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
196 : :
197 : : /**
198 : : * CcDurationRow:maximum:
199 : : *
200 : : * Maximum allowed value (inclusive) for #CcDurationRow:duration, in
201 : : * minutes.
202 : : *
203 : : * If this is changed and the current value of #CcDurationRow:duration is
204 : : * higher than it, the value of #CcDurationRow:duration will automatically
205 : : * be clamped to the new maximum.
206 : : */
207 : 0 : props[PROP_MAXIMUM] =
208 : 0 : g_param_spec_uint ("maximum",
209 : : NULL, NULL,
210 : : 0, G_MAXUINT, G_MAXUINT,
211 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
212 : :
213 : 0 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
214 : :
215 : 0 : g_type_ensure (CC_TYPE_DURATION_EDITOR);
216 : :
217 : 0 : gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/cc-duration-row.ui");
218 : :
219 : 0 : gtk_widget_class_bind_template_child (widget_class, CcDurationRow, current);
220 : 0 : gtk_widget_class_bind_template_child (widget_class, CcDurationRow, arrow_box);
221 : 0 : gtk_widget_class_bind_template_child (widget_class, CcDurationRow, editor);
222 : 0 : gtk_widget_class_bind_template_child (widget_class, CcDurationRow, popover);
223 : 0 : gtk_widget_class_bind_template_callback (widget_class, popover_notify_visible_cb);
224 : 0 : gtk_widget_class_bind_template_callback (widget_class, editor_notify_duration_cb);
225 : 0 : gtk_widget_class_bind_template_callback (widget_class, editor_notify_minimum_cb);
226 : 0 : gtk_widget_class_bind_template_callback (widget_class, editor_notify_maximum_cb);
227 : :
228 : 0 : gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX);
229 : 0 : }
230 : :
231 : : static void
232 : 0 : cc_duration_row_init (CcDurationRow *self)
233 : : {
234 : 0 : gtk_widget_init_template (GTK_WIDGET (self));
235 : :
236 : 0 : update_current_label (self);
237 : 0 : }
238 : :
239 : : static void
240 : 0 : cc_duration_row_get_property (GObject *object,
241 : : guint property_id,
242 : : GValue *value,
243 : : GParamSpec *pspec)
244 : : {
245 : 0 : CcDurationRow *self = CC_DURATION_ROW (object);
246 : :
247 [ # # # # ]: 0 : switch ((CcDurationRowProperty) property_id)
248 : : {
249 : 0 : case PROP_DURATION:
250 : 0 : g_value_set_uint (value, cc_duration_row_get_duration (self));
251 : 0 : break;
252 : 0 : case PROP_MINIMUM:
253 : 0 : g_value_set_uint (value, cc_duration_row_get_minimum (self));
254 : 0 : break;
255 : 0 : case PROP_MAXIMUM:
256 : 0 : g_value_set_uint (value, cc_duration_row_get_maximum (self));
257 : 0 : break;
258 : 0 : default:
259 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
260 : 0 : break;
261 : : }
262 : 0 : }
263 : :
264 : : static void
265 : 0 : cc_duration_row_set_property (GObject *object,
266 : : guint property_id,
267 : : const GValue *value,
268 : : GParamSpec *pspec)
269 : : {
270 : 0 : CcDurationRow *self = CC_DURATION_ROW (object);
271 : :
272 [ # # # # ]: 0 : switch ((CcDurationRowProperty) property_id)
273 : : {
274 : 0 : case PROP_DURATION:
275 : 0 : cc_duration_row_set_duration (self, g_value_get_uint (value));
276 : 0 : break;
277 : 0 : case PROP_MINIMUM:
278 : 0 : cc_duration_row_set_minimum (self, g_value_get_uint (value));
279 : 0 : break;
280 : 0 : case PROP_MAXIMUM:
281 : 0 : cc_duration_row_set_maximum (self, g_value_get_uint (value));
282 : 0 : break;
283 : 0 : default:
284 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
285 : : }
286 : 0 : }
287 : :
288 : : static void
289 : 0 : cc_duration_row_dispose (GObject *object)
290 : : {
291 : 0 : gtk_widget_dispose_template (GTK_WIDGET (object), CC_TYPE_DURATION_ROW);
292 : :
293 : 0 : G_OBJECT_CLASS (cc_duration_row_parent_class)->dispose (object);
294 : 0 : }
295 : :
296 : : static void
297 : 0 : cc_duration_row_size_allocate (GtkWidget *widget,
298 : : int width,
299 : : int height,
300 : : int baseline)
301 : : {
302 : 0 : CcDurationRow *self = CC_DURATION_ROW (widget);
303 : :
304 : 0 : GTK_WIDGET_CLASS (cc_duration_row_parent_class)->size_allocate (widget, width, height, baseline);
305 : :
306 : 0 : gtk_popover_present (self->popover);
307 : 0 : }
308 : :
309 : : static gboolean
310 : 0 : cc_duration_row_focus (GtkWidget *widget,
311 : : GtkDirectionType direction)
312 : : {
313 : 0 : CcDurationRow *self = CC_DURATION_ROW (widget);
314 : :
315 [ # # # # ]: 0 : if (self->popover != NULL && gtk_widget_get_visible (GTK_WIDGET (self->popover)))
316 : 0 : return gtk_widget_child_focus (GTK_WIDGET (self->popover), direction);
317 : : else
318 : 0 : return GTK_WIDGET_CLASS (cc_duration_row_parent_class)->focus (widget, direction);
319 : : }
320 : :
321 : : static void
322 : 0 : cc_duration_row_activate (AdwActionRow *row)
323 : : {
324 : 0 : CcDurationRow *self = CC_DURATION_ROW (row);
325 : :
326 [ # # ]: 0 : if (gtk_widget_get_visible (self->arrow_box))
327 : 0 : gtk_popover_popup (self->popover);
328 : 0 : }
329 : :
330 : : static void
331 : 0 : popover_notify_visible_cb (GObject *object,
332 : : GParamSpec *pspec,
333 : : gpointer user_data)
334 : : {
335 : 0 : CcDurationRow *self = CC_DURATION_ROW (user_data);
336 : :
337 [ # # ]: 0 : if (gtk_widget_get_visible (GTK_WIDGET (self->popover)))
338 : 0 : gtk_widget_add_css_class (GTK_WIDGET (self), "has-open-popup");
339 : : else
340 : 0 : gtk_widget_remove_css_class (GTK_WIDGET (self), "has-open-popup");
341 : 0 : }
342 : :
343 : : static void
344 : 0 : update_current_label (CcDurationRow *self)
345 : : {
346 : 0 : g_autofree char *duration_str = NULL;
347 : :
348 : 0 : duration_str = cc_util_time_to_string_text (cc_duration_editor_get_duration (self->editor) * 60 * 1000);
349 : 0 : gtk_label_set_label (self->current, duration_str);
350 : 0 : }
351 : :
352 : : static void
353 : 0 : editor_notify_duration_cb (GObject *object,
354 : : GParamSpec *pspec,
355 : : gpointer user_data)
356 : : {
357 : 0 : CcDurationRow *self = CC_DURATION_ROW (user_data);
358 : :
359 : 0 : update_current_label (self);
360 : :
361 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DURATION]);
362 : 0 : }
363 : :
364 : : static void
365 : 0 : editor_notify_minimum_cb (GObject *object,
366 : : GParamSpec *pspec,
367 : : gpointer user_data)
368 : : {
369 : 0 : CcDurationRow *self = CC_DURATION_ROW (user_data);
370 : :
371 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MINIMUM]);
372 : 0 : }
373 : :
374 : : static void
375 : 0 : editor_notify_maximum_cb (GObject *object,
376 : : GParamSpec *pspec,
377 : : gpointer user_data)
378 : : {
379 : 0 : CcDurationRow *self = CC_DURATION_ROW (user_data);
380 : :
381 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MAXIMUM]);
382 : 0 : }
383 : :
384 : : /**
385 : : * cc_duration_row_new:
386 : : *
387 : : * Create a new #CcDurationRow.
388 : : *
389 : : * Returns: (transfer full): the new #CcDurationRow
390 : : */
391 : : CcDurationRow *
392 : 0 : cc_duration_row_new (void)
393 : : {
394 : 0 : return g_object_new (CC_TYPE_DURATION_ROW, NULL);
395 : : }
396 : :
397 : : /**
398 : : * cc_duration_row_get_duration:
399 : : * @self: a #CcDurationRow
400 : : *
401 : : * Get the value of #CcDurationRow:duration.
402 : : *
403 : : * Returns: number of minutes currently specified by the row
404 : : */
405 : : guint
406 : 0 : cc_duration_row_get_duration (CcDurationRow *self)
407 : : {
408 : 0 : g_return_val_if_fail (CC_IS_DURATION_ROW (self), 0);
409 : :
410 : 0 : return cc_duration_editor_get_duration (self->editor);
411 : : }
412 : :
413 : : /**
414 : : * cc_duration_row_set_duration:
415 : : * @self: a #CcDurationRow
416 : : * @duration: the duration, in minutes
417 : : *
418 : : * Set the value of #CcDurationRow:duration.
419 : : */
420 : : void
421 : 0 : cc_duration_row_set_duration (CcDurationRow *self,
422 : : guint duration)
423 : : {
424 : 0 : g_return_if_fail (CC_IS_DURATION_ROW (self));
425 : :
426 : 0 : cc_duration_editor_set_duration (self->editor, duration);
427 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DURATION]);
428 : : }
429 : :
430 : : /**
431 : : * cc_duration_row_get_minimum:
432 : : * @self: a #CcDurationRow
433 : : *
434 : : * Get the value of #CcDurationRow:minimum.
435 : : *
436 : : * Returns: minimum value allowed for the duration, in minutes
437 : : */
438 : : guint
439 : 0 : cc_duration_row_get_minimum (CcDurationRow *self)
440 : : {
441 : 0 : g_return_val_if_fail (CC_IS_DURATION_ROW (self), 0);
442 : :
443 : 0 : return cc_duration_editor_get_minimum (self->editor);
444 : : }
445 : :
446 : : /**
447 : : * cc_duration_row_set_minimum:
448 : : * @self: a #CcDurationRow
449 : : * @minimum: minimum value allowed for the duration, in minutes
450 : : *
451 : : * Set the value of #CcDurationRow:minimum to @minimum.
452 : : *
453 : : * If the current value of #CcDurationRow:duration is lower than @minimum, it
454 : : * will automatically be clamped to @minimum.
455 : : */
456 : : void
457 : 0 : cc_duration_row_set_minimum (CcDurationRow *self,
458 : : guint minimum)
459 : : {
460 : 0 : g_return_if_fail (CC_IS_DURATION_ROW (self));
461 : :
462 : 0 : cc_duration_editor_set_minimum (self->editor, minimum);
463 : : }
464 : :
465 : : /**
466 : : * cc_duration_row_get_maximum:
467 : : * @self: a #CcDurationRow
468 : : *
469 : : * Get the value of #CcDurationRow:maximum.
470 : : *
471 : : * Returns: maximum value allowed for the duration, in minutes
472 : : */
473 : : guint
474 : 0 : cc_duration_row_get_maximum (CcDurationRow *self)
475 : : {
476 : 0 : g_return_val_if_fail (CC_IS_DURATION_ROW (self), 0);
477 : :
478 : 0 : return cc_duration_editor_get_maximum (self->editor);
479 : : }
480 : :
481 : : /**
482 : : * cc_duration_row_set_maximum:
483 : : * @self: a #CcDurationRow
484 : : * @maximum: maximum value allowed for the duration, in minutes
485 : : *
486 : : * Set the value of #CcDurationRow:maximum to @maximum.
487 : : *
488 : : * If the current value of #CcDurationRow:duration is higher than @maximum,
489 : : * it will automatically be clamped to @maximum.
490 : : */
491 : : void
492 : 0 : cc_duration_row_set_maximum (CcDurationRow *self,
493 : : guint maximum)
494 : : {
495 : 0 : g_return_if_fail (CC_IS_DURATION_ROW (self));
496 : :
497 : 0 : cc_duration_editor_set_maximum (self->editor, maximum);
498 : : }
|