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