Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 : : /*
3 : : * Copyright 2025 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 : : * - Ignacy KuchciĆski <ignacykuchcinski@gnome.org>
20 : : *
21 : : * SPDX-License-Identifier: GPL-3.0-or-later
22 : : */
23 : : #include "config.h"
24 : : #include <glib/gi18n.h>
25 : :
26 : : #define GNOME_DESKTOP_USE_UNSTABLE_API
27 : : #include <libgnome-desktop/gnome-wall-clock.h>
28 : : #include <adwaita.h>
29 : : #include <glib-object.h>
30 : : #include <gtk/gtk.h>
31 : :
32 : : #include "cc-time-editor.h"
33 : : #include "cc-time-row.h"
34 : :
35 : : #define CLOCK_SCHEMA "org.gnome.desktop.interface"
36 : : #define CLOCK_FORMAT_KEY "clock-format"
37 : :
38 : : /**
39 : : * CcTimeRow:
40 : : *
41 : : * An #AdwActionRow used to enter a time, represented as time string.
42 : : *
43 : : * The currently specified time is shown in a label. If the row is activated
44 : : * a popover is shown containing a #CcTimeEditor to edit the time.
45 : : */
46 : : struct _CcTimeRow {
47 : : AdwActionRow parent_instance;
48 : :
49 : : GtkWidget *arrow_box;
50 : : GtkLabel *current;
51 : : GtkPopover *popover;
52 : : CcTimeEditor *editor;
53 : : GSettings *clock_settings;
54 : : };
55 : :
56 [ # # # # : 0 : G_DEFINE_TYPE (CcTimeRow, cc_time_row, ADW_TYPE_ACTION_ROW)
# # ]
57 : :
58 : : typedef enum {
59 : : PROP_TIME = 1,
60 : : } CcTimeRowProperty;
61 : :
62 : : static GParamSpec *props[PROP_TIME + 1];
63 : :
64 : : static void cc_time_row_get_property (GObject *object,
65 : : guint property_id,
66 : : GValue *value,
67 : : GParamSpec *pspec);
68 : : static void cc_time_row_set_property (GObject *object,
69 : : guint property_id,
70 : : const GValue *value,
71 : : GParamSpec *pspec);
72 : : static void cc_time_row_dispose (GObject *object);
73 : : static void cc_time_row_finalize (GObject *object);
74 : : static void cc_time_row_size_allocate (GtkWidget *widget,
75 : : int width,
76 : : int height,
77 : : int baseline);
78 : : static gboolean cc_time_row_focus (GtkWidget *widget,
79 : : GtkDirectionType direction);
80 : : static void cc_time_row_activate (AdwActionRow *row);
81 : : static void popover_notify_visible_cb (GObject *object,
82 : : GParamSpec *pspec,
83 : : gpointer user_data);
84 : : static void update_current_label (CcTimeRow *self);
85 : : static void editor_time_changed_cb (CcTimeEditor *editor,
86 : : gpointer user_data);
87 : : static void time_row_clock_changed_cb (CcTimeRow *self);
88 : :
89 : : static void
90 : 0 : cc_time_row_class_init (CcTimeRowClass *klass)
91 : : {
92 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
93 : 0 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
94 : 0 : AdwActionRowClass *row_class = ADW_ACTION_ROW_CLASS (klass);
95 : :
96 : 0 : object_class->get_property = cc_time_row_get_property;
97 : 0 : object_class->set_property = cc_time_row_set_property;
98 : 0 : object_class->dispose = cc_time_row_dispose;
99 : 0 : object_class->finalize = cc_time_row_finalize;
100 : :
101 : 0 : widget_class->size_allocate = cc_time_row_size_allocate;
102 : 0 : widget_class->focus = cc_time_row_focus;
103 : :
104 : 0 : row_class->activate = cc_time_row_activate;
105 : :
106 : : /**
107 : : * CcTimeRow:time:
108 : : *
109 : : * Time displayed in the row or chosen in the editor,
110 : : * in seconds since midnight.
111 : : */
112 : 0 : props[PROP_TIME] =
113 : 0 : g_param_spec_uint ("time",
114 : : NULL, NULL,
115 : : 0, G_MAXUINT, 0,
116 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
117 : :
118 : 0 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
119 : :
120 : 0 : g_type_ensure (CC_TYPE_TIME_EDITOR);
121 : :
122 : 0 : gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/cc-time-row.ui");
123 : :
124 : 0 : gtk_widget_class_bind_template_child (widget_class, CcTimeRow, current);
125 : 0 : gtk_widget_class_bind_template_child (widget_class, CcTimeRow, arrow_box);
126 : 0 : gtk_widget_class_bind_template_child (widget_class, CcTimeRow, editor);
127 : 0 : gtk_widget_class_bind_template_child (widget_class, CcTimeRow, popover);
128 : 0 : gtk_widget_class_bind_template_callback (widget_class, popover_notify_visible_cb);
129 : 0 : gtk_widget_class_bind_template_callback (widget_class, editor_time_changed_cb);
130 : :
131 : 0 : gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX);
132 : 0 : }
133 : :
134 : : static void
135 : 0 : cc_time_row_init (CcTimeRow *self)
136 : : {
137 : 0 : gtk_widget_init_template (GTK_WIDGET (self));
138 : :
139 : 0 : self->clock_settings = g_settings_new (CLOCK_SCHEMA);
140 : :
141 : 0 : g_signal_connect_object (self->clock_settings, "changed::" CLOCK_FORMAT_KEY,
142 : : G_CALLBACK (time_row_clock_changed_cb), self,
143 : : G_CONNECT_SWAPPED);
144 : :
145 : 0 : update_current_label (self);
146 : 0 : }
147 : :
148 : : static void
149 : 0 : cc_time_row_get_property (GObject *object,
150 : : guint property_id,
151 : : GValue *value,
152 : : GParamSpec *pspec)
153 : : {
154 : 0 : CcTimeRow *self = CC_TIME_ROW (object);
155 : :
156 [ # # ]: 0 : switch ((CcTimeRowProperty) property_id)
157 : : {
158 : 0 : case PROP_TIME:
159 : 0 : g_value_set_uint (value, cc_time_row_get_time (self));
160 : 0 : break;
161 : 0 : default:
162 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
163 : 0 : break;
164 : : }
165 : 0 : }
166 : :
167 : : static void
168 : 0 : cc_time_row_set_property (GObject *object,
169 : : guint property_id,
170 : : const GValue *value,
171 : : GParamSpec *pspec)
172 : : {
173 : 0 : CcTimeRow *self = CC_TIME_ROW (object);
174 : :
175 [ # # ]: 0 : switch ((CcTimeRowProperty) property_id)
176 : : {
177 : 0 : case PROP_TIME:
178 : 0 : cc_time_row_set_time (self, g_value_get_uint (value));
179 : 0 : break;
180 : 0 : default:
181 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
182 : : }
183 : 0 : }
184 : :
185 : : static void
186 : 0 : cc_time_row_dispose (GObject *object)
187 : : {
188 : 0 : gtk_widget_dispose_template (GTK_WIDGET (object), CC_TYPE_TIME_ROW);
189 : :
190 : 0 : G_OBJECT_CLASS (cc_time_row_parent_class)->dispose (object);
191 : 0 : }
192 : :
193 : : static void
194 : 0 : cc_time_row_finalize (GObject *object)
195 : : {
196 : 0 : CcTimeRow *self = CC_TIME_ROW (object);
197 : :
198 [ # # ]: 0 : g_clear_object (&self->clock_settings);
199 : :
200 : 0 : G_OBJECT_CLASS (cc_time_row_parent_class)->finalize (object);
201 : 0 : }
202 : :
203 : : static void
204 : 0 : cc_time_row_size_allocate (GtkWidget *widget,
205 : : int width,
206 : : int height,
207 : : int baseline)
208 : : {
209 : 0 : CcTimeRow *self = CC_TIME_ROW (widget);
210 : :
211 : 0 : GTK_WIDGET_CLASS (cc_time_row_parent_class)->size_allocate (widget, width, height, baseline);
212 : :
213 : 0 : gtk_popover_present (self->popover);
214 : 0 : }
215 : :
216 : : static gboolean
217 : 0 : cc_time_row_focus (GtkWidget *widget,
218 : : GtkDirectionType direction)
219 : : {
220 : 0 : CcTimeRow *self = CC_TIME_ROW (widget);
221 : :
222 [ # # # # ]: 0 : if (self->popover != NULL && gtk_widget_get_visible (GTK_WIDGET (self->popover)))
223 : 0 : return gtk_widget_child_focus (GTK_WIDGET (self->popover), direction);
224 : : else
225 : 0 : return GTK_WIDGET_CLASS (cc_time_row_parent_class)->focus (widget, direction);
226 : : }
227 : :
228 : : static void
229 : 0 : cc_time_row_activate (AdwActionRow *row)
230 : : {
231 : 0 : CcTimeRow *self = CC_TIME_ROW (row);
232 : :
233 [ # # ]: 0 : if (gtk_widget_get_visible (self->arrow_box))
234 : 0 : gtk_popover_popup (self->popover);
235 : 0 : }
236 : :
237 : : static void
238 : 0 : popover_notify_visible_cb (GObject *object,
239 : : GParamSpec *pspec,
240 : : gpointer user_data)
241 : : {
242 : 0 : CcTimeRow *self = CC_TIME_ROW (user_data);
243 : :
244 [ # # ]: 0 : if (gtk_widget_get_visible (GTK_WIDGET (self->popover)))
245 : 0 : gtk_widget_add_css_class (GTK_WIDGET (self), "has-open-popup");
246 : : else
247 : 0 : gtk_widget_remove_css_class (GTK_WIDGET (self), "has-open-popup");
248 : 0 : }
249 : :
250 : : static void
251 : 0 : update_current_label (CcTimeRow *self)
252 : : {
253 : : GDesktopClockFormat clock_format;
254 : 0 : g_autofree char *time_str = NULL;
255 : 0 : g_autoptr (GDateTime) date_time = NULL;
256 : :
257 : 0 : clock_format = g_settings_get_enum (self->clock_settings, CLOCK_FORMAT_KEY);
258 : 0 : guint hours = cc_time_editor_get_hour (self->editor);
259 : 0 : guint minutes = cc_time_editor_get_minute (self->editor);
260 : :
261 : 0 : date_time = g_date_time_new_utc (1, 1, 1, hours % 24, minutes, 0);
262 : :
263 [ # # ]: 0 : if (clock_format == G_DESKTOP_CLOCK_FORMAT_12H)
264 : : {
265 : : /* Translators: This is time format used in 12-hour mode. */
266 : 0 : time_str = g_date_time_format (date_time, _("%-l:%M %p"));
267 : : }
268 : : else
269 : : {
270 : : /* Translators: This is time format used in 24-hour mode. */
271 : 0 : time_str = g_date_time_format (date_time, _("%-k:%M"));
272 : : }
273 : :
274 : 0 : gtk_label_set_label (self->current, time_str);
275 : 0 : }
276 : :
277 : : static void
278 : 0 : editor_time_changed_cb (CcTimeEditor *editor,
279 : : gpointer user_data)
280 : : {
281 : 0 : CcTimeRow *self = CC_TIME_ROW (user_data);
282 : :
283 : 0 : update_current_label (self);
284 : :
285 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIME]);
286 : 0 : }
287 : :
288 : : static void
289 : 0 : time_row_clock_changed_cb (CcTimeRow *self)
290 : : {
291 : 0 : update_current_label (self);
292 : 0 : }
293 : :
294 : : /**
295 : : * cc_time_row_new:
296 : : *
297 : : * Create a new #CcTimeRow.
298 : : *
299 : : * Returns: (transfer full): the new #CcTimeRow
300 : : */
301 : : CcTimeRow *
302 : 0 : cc_time_row_new (void)
303 : : {
304 : 0 : return g_object_new (CC_TYPE_TIME_ROW, NULL);
305 : : }
306 : :
307 : : /**
308 : : * cc_time_row_get_time:
309 : : * @self: a #CcTimeRow
310 : : *
311 : : * Get the value of #CcTimeRow:time.
312 : : *
313 : : * Returns: number of minutes since midnight currently specified by the row
314 : : */
315 : : guint
316 : 0 : cc_time_row_get_time (CcTimeRow *self)
317 : : {
318 : 0 : g_return_val_if_fail (CC_IS_TIME_ROW (self), 0);
319 : :
320 : 0 : guint hours = cc_time_editor_get_hour (self->editor);
321 : 0 : guint minutes = cc_time_editor_get_minute (self->editor);
322 : :
323 : 0 : return hours * 60 + minutes;
324 : : }
325 : :
326 : : /**
327 : : * cc_time_row_set_time:
328 : : * @self: a #CcTimeRow
329 : : * @time: the time, in minutes since midnight
330 : : *
331 : : * Set the value of #CcTimeRow:time.
332 : : */
333 : : void
334 : 0 : cc_time_row_set_time (CcTimeRow *self,
335 : : guint time)
336 : : {
337 : 0 : g_return_if_fail (CC_IS_TIME_ROW (self));
338 : :
339 : : guint hours, minutes;
340 : :
341 : 0 : hours = time / 60;
342 : 0 : minutes = time % 60;
343 : :
344 : 0 : cc_time_editor_set_time (self->editor, hours, minutes);
345 : :
346 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIME]);
347 : : }
|