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-date-label"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <glib/gi18n.h>
9 : : #include <gtk/gtk.h>
10 : : #include <valent.h>
11 : :
12 : : #include "valent-date-label.h"
13 : :
14 [ + - + - ]: 4 : G_DEFINE_ENUM_TYPE (ValentDateFormat, valent_date_format,
15 : : G_DEFINE_ENUM_VALUE (VALENT_DATE_FORMAT_ADAPTIVE, "adaptive"),
16 : : G_DEFINE_ENUM_VALUE (VALENT_DATE_FORMAT_ADAPTIVE_SHORT, "adaptive-short"),
17 : : G_DEFINE_ENUM_VALUE (VALENT_DATE_FORMAT_TIME, "time"))
18 : :
19 : : struct _ValentDateLabel
20 : : {
21 : : GtkWidget parent_instance;
22 : :
23 : : GtkWidget *label;
24 : : int64_t date;
25 : : ValentDateFormat mode;
26 : : double xalign;
27 : : };
28 : :
29 [ + + + - ]: 43 : G_DEFINE_FINAL_TYPE (ValentDateLabel, valent_date_label, GTK_TYPE_WIDGET)
30 : :
31 : : enum {
32 : : PROP_0,
33 : : PROP_DATE,
34 : : PROP_MODE,
35 : : PROP_XALIGN,
36 : : N_PROPERTIES
37 : : };
38 : :
39 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
40 : :
41 : : static GPtrArray *label_cache = NULL;
42 : : static unsigned int label_source = 0;
43 : :
44 : :
45 : : /*< private >
46 : : * valent_date_label_string_adaptive:
47 : : * @timestamp: a UNIX epoch timestamp (ms)
48 : : * @abbreviated: use abbreviations for brevity
49 : : *
50 : : * Create a user friendly date-time string for @timestamp, in a relative format.
51 : : *
52 : : * Examples:
53 : : * - "Just now"
54 : : * - "15 minutes"
55 : : * - "11:45 PM"
56 : : * - "Yesterday · 11:45 PM"
57 : : * - "Tuesday"
58 : : * - "February 29"
59 : : *
60 : : * Abbreviated Examples:
61 : : * - "Just now"
62 : : * - "15 mins"
63 : : * - "11:45 PM"
64 : : * - "Tue"
65 : : * - "Feb 29"
66 : : *
67 : : * Returns: (transfer full): a new string
68 : : */
69 : : static char *
70 : 13 : valent_date_label_string_adaptive (int64_t timestamp,
71 : : gboolean abbreviated)
72 : : {
73 : 26 : g_autoptr (GDateTime) dt = NULL;
74 [ + - ]: 13 : g_autoptr (GDateTime) now = NULL;
75 [ + - ]: 13 : g_autofree char *date_str = NULL;
76 : 13 : g_autofree char *time_str = NULL;
77 : 13 : GTimeSpan diff;
78 : :
79 : 13 : dt = g_date_time_new_from_unix_local (timestamp / 1000);
80 : 13 : now = g_date_time_new_now_local ();
81 : 13 : diff = g_date_time_difference (now, dt);
82 : :
83 : : /* TRANSLATORS: Less than a minute ago
84 : : */
85 [ + + ]: 13 : if (diff < G_TIME_SPAN_MINUTE)
86 [ - + ]: 10 : return g_strdup (_("Just now"));
87 : :
88 [ - + ]: 8 : if (diff < G_TIME_SPAN_HOUR)
89 : : {
90 : 0 : unsigned int n_minutes;
91 : :
92 : 0 : n_minutes = (diff / G_TIME_SPAN_MINUTE);
93 : :
94 [ # # ]: 0 : if (abbreviated)
95 : : {
96 : : /* TRANSLATORS: Time duration in minutes, abbreviated (eg. 15 mins)
97 : : */
98 : 0 : return g_strdup_printf (ngettext ("%d min", "%d mins", n_minutes),
99 : : n_minutes);
100 : : }
101 : : else
102 : : {
103 : : /* TRANSLATORS: Time duration in minutes (eg. 15 minutes)
104 : : */
105 : 0 : return g_strdup_printf (ngettext("%d minute", "%d minutes", n_minutes),
106 : : n_minutes);
107 : : }
108 : : }
109 : :
110 : 8 : time_str = g_date_time_format (dt, "%-l:%M %p");
111 [ - + ]: 8 : if (diff < G_TIME_SPAN_DAY)
112 : : {
113 : 0 : int today = g_date_time_get_day_of_month (now);
114 : 0 : int day = g_date_time_get_day_of_month (dt);
115 : :
116 [ # # ]: 0 : if (abbreviated || today == day)
117 : : return g_steal_pointer (&time_str);
118 : :
119 : : /* TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:45 PM)
120 : : */
121 : 0 : return g_strdup_printf (_("Yesterday · %s"), time_str);
122 : : }
123 : :
124 : : /* Less than a week ago (eg. Tuesday/Tue)
125 : : */
126 [ - + ]: 8 : if (diff < G_TIME_SPAN_DAY * 7)
127 : : {
128 [ # # ]: 0 : if (abbreviated)
129 : 0 : return g_date_time_format (dt, "%a");
130 : :
131 : : /* TRANSLATORS: Date and time (eg. Tuesday · 23:45:00 PM)
132 : : */
133 : 0 : date_str = g_date_time_format (dt, "%A");
134 : 0 : return g_strdup_printf (_("%s · %s"), date_str, time_str);
135 : : }
136 : :
137 : : /* More than a week ago (eg. Feb 29)
138 : : */
139 [ + + ]: 8 : if (abbreviated)
140 : 4 : return g_date_time_format (dt, "%b %-e");
141 : :
142 : : /* TRANSLATORS: Date and time (eg. February 29 · 23:45:00 PM)
143 : : */
144 : 4 : date_str = g_date_time_format(dt, "%B %-e");
145 : 4 : return g_strdup_printf (_("%s · %s"), date_str, time_str);
146 : : }
147 : :
148 : : /*< private >
149 : : * valent_date_label_string_absolute:
150 : : * @timestamp: a UNIX epoch timestamp (ms)
151 : : *
152 : : * Create a user friendly time string for @timestamp, in an absolute
153 : : * format.
154 : : *
155 : : * Examples:
156 : : * - "11:45 PM"
157 : : *
158 : : * Returns: (transfer full): a new string
159 : : */
160 : : static char *
161 : 3 : valent_date_label_string_time (int64_t timestamp)
162 : : {
163 : 6 : g_autoptr (GDateTime) dt = NULL;
164 : :
165 : 3 : dt = g_date_time_new_from_unix_local (timestamp / 1000);
166 [ + - ]: 3 : return g_date_time_format (dt, "%-l:%M %p");
167 : : }
168 : :
169 : : static void
170 : 11 : valent_date_label_sync (ValentDateLabel *label)
171 : : {
172 : 11 : g_autofree char *text = NULL;
173 : 11 : g_autofree char *tooltip_text = NULL;
174 : :
175 [ + - ]: 11 : g_return_if_fail (VALENT_IS_DATE_LABEL (label));
176 : :
177 [ + + + - ]: 11 : switch (label->mode)
178 : : {
179 : 3 : case VALENT_DATE_FORMAT_ADAPTIVE:
180 : 3 : text = valent_date_label_string_adaptive (label->date, FALSE);
181 : 3 : break;
182 : :
183 : 5 : case VALENT_DATE_FORMAT_ADAPTIVE_SHORT:
184 : 5 : text = valent_date_label_string_adaptive (label->date, TRUE);
185 : 5 : tooltip_text = valent_date_label_string_adaptive (label->date, FALSE);
186 : 5 : break;
187 : :
188 : 3 : case VALENT_DATE_FORMAT_TIME:
189 : 3 : text = valent_date_label_string_time (label->date);
190 : 3 : break;
191 : :
192 : : default:
193 : : break;
194 : : }
195 : :
196 : 11 : gtk_label_set_label (GTK_LABEL (label->label), text);
197 : 11 : gtk_widget_set_tooltip_text (GTK_WIDGET (label->label), tooltip_text);
198 [ + + ]: 11 : if (tooltip_text != NULL)
199 : : {
200 : 5 : gtk_accessible_update_property (GTK_ACCESSIBLE (label->label),
201 : : GTK_ACCESSIBLE_PROPERTY_LABEL, tooltip_text,
202 : : -1);
203 : : }
204 : : }
205 : :
206 : : static gboolean
207 : 0 : valent_date_label_sync_func (gpointer user_data)
208 : : {
209 [ # # ]: 0 : for (unsigned int i = 0; i < label_cache->len; i++)
210 : 0 : valent_date_label_sync (g_ptr_array_index (label_cache, i));
211 : :
212 : 0 : return G_SOURCE_CONTINUE;
213 : : }
214 : :
215 : : /*
216 : : * GObject
217 : : */
218 : : static void
219 : 6 : valent_date_label_finalize (GObject *object)
220 : : {
221 : 6 : ValentDateLabel *self = VALENT_DATE_LABEL (object);
222 : :
223 : : /* Remove from update list */
224 : 6 : g_ptr_array_remove (label_cache, self);
225 : :
226 [ + - ]: 6 : if (label_cache->len == 0)
227 : : {
228 [ + - ]: 6 : g_clear_handle_id (&label_source, g_source_remove);
229 [ + - ]: 6 : g_clear_pointer (&label_cache, g_ptr_array_unref);
230 : : }
231 : :
232 [ + - ]: 6 : g_clear_pointer (&self->label, gtk_widget_unparent);
233 : :
234 : 6 : G_OBJECT_CLASS (valent_date_label_parent_class)->finalize (object);
235 : 6 : }
236 : :
237 : : static void
238 : 19 : valent_date_label_get_property (GObject *object,
239 : : guint prop_id,
240 : : GValue *value,
241 : : GParamSpec *pspec)
242 : : {
243 : 19 : ValentDateLabel *self = VALENT_DATE_LABEL (object);
244 : :
245 [ + + + - ]: 19 : switch (prop_id)
246 : : {
247 : 3 : case PROP_DATE:
248 : 3 : g_value_set_int64 (value, valent_date_label_get_date (self));
249 : 3 : break;
250 : :
251 : 3 : case PROP_MODE:
252 : 3 : g_value_set_enum (value, valent_date_label_get_mode (self));
253 : 3 : break;
254 : :
255 : 13 : case PROP_XALIGN:
256 : 13 : g_value_set_double (value, self->xalign);
257 : 13 : break;
258 : :
259 : 0 : default:
260 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261 : : }
262 : 19 : }
263 : :
264 : : static void
265 : 19 : valent_date_label_set_property (GObject *object,
266 : : guint prop_id,
267 : : const GValue *value,
268 : : GParamSpec *pspec)
269 : : {
270 : 19 : ValentDateLabel *self = VALENT_DATE_LABEL (object);
271 : :
272 [ + + + - ]: 19 : switch (prop_id)
273 : : {
274 : 6 : case PROP_DATE:
275 : 6 : valent_date_label_set_date (self, g_value_get_int64 (value));
276 : 6 : break;
277 : :
278 : 6 : case PROP_MODE:
279 : 6 : valent_date_label_set_mode (self, g_value_get_enum (value));
280 : 6 : break;
281 : :
282 : 7 : case PROP_XALIGN:
283 : 7 : self->xalign = g_value_get_double (value);
284 : 7 : break;
285 : :
286 : 0 : default:
287 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
288 : : }
289 : 19 : }
290 : :
291 : : static void
292 : 4 : valent_date_label_class_init (ValentDateLabelClass *klass)
293 : : {
294 : 4 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
295 : 4 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
296 : :
297 : 4 : object_class->finalize = valent_date_label_finalize;
298 : 4 : object_class->get_property = valent_date_label_get_property;
299 : 4 : object_class->set_property = valent_date_label_set_property;
300 : :
301 : 4 : gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
302 : 4 : gtk_widget_class_set_css_name (widget_class, "date-label");
303 : :
304 : : /**
305 : : * ValentDateLabel:date
306 : : *
307 : : * The timestamp this label represents.
308 : : */
309 : 8 : properties [PROP_DATE] =
310 : 4 : g_param_spec_int64 ("date", NULL, NULL,
311 : : 0, G_MAXINT64,
312 : : 0,
313 : : (G_PARAM_READWRITE |
314 : : G_PARAM_EXPLICIT_NOTIFY |
315 : : G_PARAM_STATIC_STRINGS));
316 : :
317 : : /**
318 : : * ValentDateLabel:mode
319 : : *
320 : : * The brevity of the label.
321 : : */
322 : 8 : properties [PROP_MODE] =
323 : 4 : g_param_spec_enum ("mode", NULL, NULL,
324 : : VALENT_TYPE_DATE_FORMAT,
325 : : VALENT_DATE_FORMAT_ADAPTIVE,
326 : : (G_PARAM_READWRITE |
327 : : G_PARAM_EXPLICIT_NOTIFY |
328 : : G_PARAM_STATIC_STRINGS));
329 : :
330 : : /**
331 : : * ValentDateLabel:xalign
332 : : *
333 : : * The X alignment of the label.
334 : : */
335 : 8 : properties [PROP_XALIGN] =
336 : 4 : g_param_spec_double ("xalign", NULL, NULL,
337 : : 0.0, 1.0,
338 : : 0.5,
339 : : (G_PARAM_READWRITE |
340 : : G_PARAM_CONSTRUCT |
341 : : G_PARAM_STATIC_STRINGS));
342 : :
343 : 4 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
344 : 4 : }
345 : :
346 : : static void
347 : 6 : valent_date_label_init (ValentDateLabel *self)
348 : : {
349 : 6 : self->label = gtk_label_new (NULL);
350 : 6 : gtk_widget_insert_after (self->label, GTK_WIDGET (self), NULL);
351 : :
352 : 6 : g_object_bind_property (self, "xalign",
353 : 6 : self->label, "xalign",
354 : : G_BINDING_BIDIRECTIONAL);
355 : :
356 [ + - ]: 6 : if (label_cache == NULL)
357 : : {
358 : 6 : label_cache = g_ptr_array_new ();
359 : 6 : label_source = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE,
360 : : 60,
361 : : valent_date_label_sync_func,
362 : : NULL,
363 : : NULL);
364 : : }
365 : :
366 : 6 : g_ptr_array_add (label_cache, self);
367 : 6 : }
368 : :
369 : : /**
370 : : * valent_date_label_get_date:
371 : : * @label: a `ValentDateLabel`
372 : : *
373 : : * Get the UNIX epoch timestamp (ms) for @label.
374 : : *
375 : : * Returns: the timestamp
376 : : */
377 : : int64_t
378 : 3 : valent_date_label_get_date (ValentDateLabel *label)
379 : : {
380 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_DATE_LABEL (label), 0);
381 : :
382 : 3 : return label->date;
383 : : }
384 : :
385 : : /**
386 : : * valent_date_label_set_date:
387 : : * @label: a `ValentDateLabel`
388 : : * @date: a UNIX epoch timestamp
389 : : *
390 : : * Set the timestamp for @label to @date.
391 : : */
392 : : void
393 : 6 : valent_date_label_set_date (ValentDateLabel *label,
394 : : int64_t date)
395 : : {
396 [ + - ]: 6 : g_return_if_fail (VALENT_IS_DATE_LABEL (label));
397 : :
398 [ + - ]: 6 : if (label->date == date)
399 : : return;
400 : :
401 : 6 : label->date = date;
402 : 6 : valent_date_label_sync (label);
403 : 6 : g_object_notify_by_pspec (G_OBJECT (label), properties [PROP_DATE]);
404 : : }
405 : :
406 : : /**
407 : : * valent_date_label_get_mode:
408 : : * @label: a `ValentDateLabel`
409 : : *
410 : : * Get the display mode @label.
411 : : *
412 : : * Returns: the display mode
413 : : */
414 : : ValentDateFormat
415 : 3 : valent_date_label_get_mode (ValentDateLabel *label)
416 : : {
417 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_DATE_LABEL (label), 0);
418 : :
419 : 3 : return label->mode;
420 : : }
421 : :
422 : : /**
423 : : * valent_date_label_set_mode:
424 : : * @label: a `ValentDateLabel`
425 : : * @mode: a mode
426 : : *
427 : : * Set the mode of @label to @mode. Currently the options are `0` and `1`.
428 : : */
429 : : void
430 : 6 : valent_date_label_set_mode (ValentDateLabel *label,
431 : : ValentDateFormat mode)
432 : : {
433 [ + - ]: 6 : g_return_if_fail (VALENT_IS_DATE_LABEL (label));
434 : :
435 [ + + ]: 6 : if (label->mode == mode)
436 : : return;
437 : :
438 : 5 : label->mode = mode;
439 : 5 : valent_date_label_sync (label);
440 : 5 : g_object_notify_by_pspec (G_OBJECT (label), properties [PROP_MODE]);
441 : : }
442 : :
|