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-notification-dialog"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <glib/gi18n.h>
9 : : #include <gtk/gtk.h>
10 : : #include <valent.h>
11 : :
12 : : #include "valent-notification-dialog.h"
13 : :
14 : :
15 : : struct _ValentNotificationDialog
16 : : {
17 : : GtkWindow parent_instance;
18 : :
19 : : ValentDevice *device;
20 : : ValentNotification *notification;
21 : : char *reply_id;
22 : :
23 : : /* template */
24 : : GtkButton *cancel_button;
25 : : GtkButton *reply_button;
26 : : GtkImage *icon_image;
27 : : GtkLabel *title_label;
28 : : GtkLabel *body_label;
29 : : GtkLabel *time_label;
30 : : GtkWidget *reply_frame;
31 : : GtkTextView *reply_entry;
32 : : };
33 : :
34 [ + + + - ]: 15 : G_DEFINE_FINAL_TYPE (ValentNotificationDialog, valent_notification_dialog, GTK_TYPE_WINDOW)
35 : :
36 : : enum {
37 : : PROP_0,
38 : : PROP_DEVICE,
39 : : PROP_NOTIFICATION,
40 : : PROP_REPLY_ID,
41 : : N_PROPERTIES
42 : : };
43 : :
44 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
45 : :
46 : :
47 : : static void
48 : 4 : valent_notification_dialog_sync (ValentNotificationDialog *self)
49 : : {
50 : 4 : gboolean repliable = FALSE;
51 : 4 : gboolean enabled = FALSE;
52 : :
53 [ + - ]: 4 : g_assert (VALENT_IS_NOTIFICATION_DIALOG (self));
54 : :
55 : : /* Check if the notification is repliable */
56 [ + + - + ]: 4 : repliable = (self->reply_id != NULL && *self->reply_id != '\0');
57 : 4 : gtk_widget_set_visible (GTK_WIDGET (self->reply_button), repliable);
58 : 4 : gtk_widget_set_visible (GTK_WIDGET (self->reply_frame), repliable);
59 : :
60 : : /* If it's repliable, check if the device action is enabled */
61 [ + + - + ]: 4 : if (repliable && self->device != NULL)
62 : 0 : enabled = g_action_group_get_action_enabled (G_ACTION_GROUP (self->device),
63 : : "notification.reply");
64 : :
65 : 4 : gtk_widget_set_sensitive (GTK_WIDGET (self->reply_entry), enabled);
66 : :
67 : : /* If it's enabled, check if a reply is ready to be sent */
68 [ - + ]: 4 : if (enabled)
69 : : {
70 : 0 : GtkTextBuffer *buffer;
71 : :
72 : 0 : buffer = gtk_text_view_get_buffer (self->reply_entry);
73 : 0 : enabled = gtk_text_buffer_get_char_count (buffer) > 0;
74 : : }
75 : :
76 : 4 : gtk_widget_action_set_enabled (GTK_WIDGET (self),
77 : : "notification.reply",
78 : : enabled);
79 : 4 : }
80 : :
81 : : static void
82 : 1 : valent_notification_dialog_set_notification (ValentNotificationDialog *self,
83 : : ValentNotification *notification)
84 : : {
85 : 1 : GIcon *icon;
86 : 1 : const char *title;
87 : 1 : const char *body;
88 : 1 : gint64 timestamp;
89 : :
90 [ + - ]: 1 : g_assert (VALENT_IS_NOTIFICATION_DIALOG (self));
91 [ + - - + ]: 1 : g_assert (notification == NULL || VALENT_IS_NOTIFICATION (notification));
92 : :
93 [ + - ]: 1 : if (!g_set_object (&self->notification, notification))
94 : : return;
95 : :
96 [ + - ]: 1 : if ((icon = valent_notification_get_icon (self->notification)) != NULL)
97 : 1 : gtk_image_set_from_gicon (self->icon_image, icon);
98 : :
99 [ + - ]: 1 : if ((title = valent_notification_get_title (self->notification)) != NULL)
100 : 1 : gtk_label_set_label (self->title_label, title);
101 : :
102 [ + - ]: 1 : if ((body = valent_notification_get_body (self->notification)) != NULL)
103 : : {
104 : 1 : g_autofree char *label = NULL;
105 : :
106 : 1 : label = valent_string_to_markup (body);
107 : 1 : gtk_label_set_label (self->body_label, label);
108 : : }
109 : :
110 : : // FIXME: better time string
111 [ - + ]: 1 : if ((timestamp = valent_notification_get_time (self->notification)) != 0)
112 : : {
113 : 1 : g_autoptr (GDateTime) date = NULL;
114 [ # # ]: 0 : g_autofree char *label = NULL;
115 : :
116 : 0 : date = g_date_time_new_from_unix_local (timestamp / 1000);
117 : 0 : label = g_date_time_format (date, "%c");
118 : 0 : gtk_label_set_label (self->time_label, label);
119 : : }
120 : :
121 : 1 : valent_notification_dialog_sync (self);
122 : : }
123 : :
124 : : /*
125 : : * GAction
126 : : */
127 : : static void
128 : 1 : dialog_cancel_action (GtkWidget *widget,
129 : : const char *action_name,
130 : : GVariant *parameter)
131 : : {
132 : 1 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (widget);
133 : :
134 [ + - ]: 1 : g_assert (VALENT_IS_NOTIFICATION_DIALOG (self));
135 : :
136 : 1 : gtk_window_destroy (GTK_WINDOW (self));
137 : 1 : }
138 : :
139 : : static void
140 : 0 : notification_close_action (GtkWidget *widget,
141 : : const char *action_name,
142 : : GVariant *parameter)
143 : : {
144 : 0 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (widget);
145 : 0 : const char *id = NULL;
146 : :
147 [ # # ]: 0 : g_assert (VALENT_IS_NOTIFICATION_DIALOG (self));
148 : :
149 : 0 : id = valent_notification_get_id (self->notification);
150 : 0 : g_action_group_activate_action (G_ACTION_GROUP (self->device),
151 : : "notification.close",
152 : : g_variant_new_string (id));
153 : 0 : }
154 : :
155 : : static void
156 : 0 : notification_reply_action (GtkWidget *widget,
157 : : const char *action_name,
158 : : GVariant *parameter)
159 : : {
160 : 0 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (widget);
161 : 0 : g_autofree char *message = NULL;
162 : 0 : GVariant *reply = NULL;
163 : 0 : GtkTextBuffer *buffer;
164 : :
165 [ # # ]: 0 : if (!g_action_group_get_action_enabled (G_ACTION_GROUP (self->device),
166 : : "notification.reply"))
167 : : return;
168 : :
169 : 0 : buffer = gtk_text_view_get_buffer (self->reply_entry);
170 : 0 : g_object_get (buffer, "text", &message, NULL);
171 [ # # # # ]: 0 : g_return_if_fail (message != NULL && *message != '\0');
172 : :
173 : 0 : reply = g_variant_new ("(ssv)", self->reply_id, message,
174 : : valent_notification_serialize (self->notification));
175 : 0 : g_action_group_activate_action (G_ACTION_GROUP (self->device),
176 : : "notification.reply",
177 : : reply);
178 : 0 : gtk_window_destroy (GTK_WINDOW (self));
179 : : }
180 : :
181 : : /*
182 : : * GObject
183 : : */
184 : : static void
185 : 1 : valent_notification_dialog_dispose (GObject *object)
186 : : {
187 : 1 : GtkWidget *widget = GTK_WIDGET (object);
188 : :
189 : 1 : gtk_widget_dispose_template (widget, VALENT_TYPE_NOTIFICATION_DIALOG);
190 : :
191 : 1 : G_OBJECT_CLASS (valent_notification_dialog_parent_class)->dispose (object);
192 : 1 : }
193 : :
194 : : static void
195 : 1 : valent_notification_dialog_constructed (GObject *object)
196 : : {
197 : 1 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (object);
198 : :
199 [ - + ]: 1 : if (self->device != NULL)
200 : : {
201 : 0 : gtk_widget_insert_action_group (GTK_WIDGET (self),
202 : : "device",
203 : : G_ACTION_GROUP (self->device));
204 : 0 : g_signal_connect_object (self->device,
205 : : "action-added::notification.reply",
206 : : G_CALLBACK (valent_notification_dialog_sync),
207 : : self,
208 : : G_CONNECT_SWAPPED);
209 : 0 : g_signal_connect_object (self->device,
210 : : "action-removed::notification.reply",
211 : : G_CALLBACK (valent_notification_dialog_sync),
212 : : self,
213 : : G_CONNECT_SWAPPED);
214 : 0 : g_signal_connect_object (self->device,
215 : : "action-enabled-changed::notification.reply",
216 : : G_CALLBACK (valent_notification_dialog_sync),
217 : : self,
218 : : G_CONNECT_SWAPPED);
219 : : }
220 : :
221 : 1 : valent_notification_dialog_sync (self);
222 : :
223 : 1 : G_OBJECT_CLASS (valent_notification_dialog_parent_class)->constructed (object);
224 : 1 : }
225 : :
226 : : static void
227 : 1 : valent_notification_dialog_finalize (GObject *object)
228 : : {
229 : 1 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (object);
230 : :
231 [ + - ]: 1 : g_clear_object (&self->notification);
232 [ - + ]: 1 : g_clear_pointer (&self->reply_id, g_free);
233 : :
234 : 1 : G_OBJECT_CLASS (valent_notification_dialog_parent_class)->finalize (object);
235 : 1 : }
236 : :
237 : : static void
238 : 2 : valent_notification_dialog_get_property (GObject *object,
239 : : guint prop_id,
240 : : GValue *value,
241 : : GParamSpec *pspec)
242 : : {
243 : 2 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (object);
244 : :
245 [ - + + - ]: 2 : switch (prop_id)
246 : : {
247 : 0 : case PROP_DEVICE:
248 : 0 : g_value_set_object (value, self->device);
249 : 0 : break;
250 : :
251 : 1 : case PROP_NOTIFICATION:
252 : 1 : g_value_set_object (value, self->notification);
253 : 1 : break;
254 : :
255 : 1 : case PROP_REPLY_ID:
256 : 1 : g_value_set_string (value, self->reply_id);
257 : 1 : break;
258 : :
259 : 0 : default:
260 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261 : : }
262 : 2 : }
263 : :
264 : : static void
265 : 3 : valent_notification_dialog_set_property (GObject *object,
266 : : guint prop_id,
267 : : const GValue *value,
268 : : GParamSpec *pspec)
269 : : {
270 : 3 : ValentNotificationDialog *self = VALENT_NOTIFICATION_DIALOG (object);
271 : :
272 [ + + + - ]: 3 : switch (prop_id)
273 : : {
274 : 1 : case PROP_DEVICE:
275 : 1 : self->device = g_value_get_object (value);
276 : 1 : break;
277 : :
278 : 1 : case PROP_NOTIFICATION:
279 : 1 : valent_notification_dialog_set_notification (self, g_value_get_object (value));
280 : 1 : break;
281 : :
282 : 1 : case PROP_REPLY_ID:
283 : 1 : valent_notification_dialog_set_reply_id (self, g_value_get_string (value));
284 : 1 : break;
285 : :
286 : 0 : default:
287 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
288 : : }
289 : 3 : }
290 : :
291 : : static void
292 : 1 : valent_notification_dialog_class_init (ValentNotificationDialogClass *klass)
293 : : {
294 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
295 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
296 : :
297 : 1 : object_class->dispose = valent_notification_dialog_dispose;
298 : 1 : object_class->constructed = valent_notification_dialog_constructed;
299 : 1 : object_class->finalize = valent_notification_dialog_finalize;
300 : 1 : object_class->get_property = valent_notification_dialog_get_property;
301 : 1 : object_class->set_property = valent_notification_dialog_set_property;
302 : :
303 : 1 : gtk_widget_class_set_template_from_resource (widget_class, "/plugins/notification/valent-notification-dialog.ui");
304 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, cancel_button);
305 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, reply_button);
306 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, icon_image);
307 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, title_label);
308 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, body_label);
309 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, time_label);
310 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, reply_frame);
311 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentNotificationDialog, reply_entry);
312 : :
313 : 1 : gtk_widget_class_install_action (widget_class, "dialog.cancel", NULL, dialog_cancel_action);
314 : :
315 : 1 : gtk_widget_class_install_action (widget_class, "notification.close", NULL, notification_close_action);
316 : 1 : gtk_widget_class_install_action (widget_class, "notification.reply", NULL, notification_reply_action);
317 : :
318 : : /**
319 : : * ValentNotificationDialog:device:
320 : : *
321 : : * The device that owns the notification.
322 : : */
323 : 2 : properties [PROP_DEVICE] =
324 : 1 : g_param_spec_object ("device", NULL, NULL,
325 : : VALENT_TYPE_DEVICE,
326 : : (G_PARAM_READWRITE |
327 : : G_PARAM_CONSTRUCT_ONLY |
328 : : G_PARAM_EXPLICIT_NOTIFY |
329 : : G_PARAM_STATIC_STRINGS));
330 : :
331 : : /**
332 : : * ValentNotificationDialog:notification:
333 : : *
334 : : * The notification the dialog represents.
335 : : */
336 : 2 : properties [PROP_NOTIFICATION] =
337 : 1 : g_param_spec_object ("notification", NULL, NULL,
338 : : VALENT_TYPE_NOTIFICATION,
339 : : (G_PARAM_READWRITE |
340 : : G_PARAM_CONSTRUCT_ONLY |
341 : : G_PARAM_EXPLICIT_NOTIFY |
342 : : G_PARAM_STATIC_STRINGS));
343 : :
344 : : /**
345 : : * ValentNotificationDialog:reply-id:
346 : : *
347 : : * The notification reply ID.
348 : : */
349 : 2 : properties [PROP_REPLY_ID] =
350 : 1 : g_param_spec_string ("reply-id", NULL, NULL,
351 : : NULL,
352 : : (G_PARAM_READWRITE |
353 : : G_PARAM_CONSTRUCT |
354 : : G_PARAM_EXPLICIT_NOTIFY |
355 : : G_PARAM_STATIC_STRINGS));
356 : :
357 : 1 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
358 : 1 : }
359 : :
360 : : static void
361 : 1 : valent_notification_dialog_init (ValentNotificationDialog *self)
362 : : {
363 : 1 : gtk_widget_init_template (GTK_WIDGET (self));
364 : :
365 : 1 : g_signal_connect_object (gtk_text_view_get_buffer (self->reply_entry),
366 : : "notify::text",
367 : : G_CALLBACK (valent_notification_dialog_sync),
368 : : self,
369 : : G_CONNECT_SWAPPED);
370 : 1 : }
371 : :
372 : : /**
373 : : * valent_notification_dialog_get_notification:
374 : : * @dialog: a `ValentNotificationDialog`
375 : : *
376 : : * Get the notification.
377 : : *
378 : : * Returns: (transfer none): a `ValentNotification`
379 : : */
380 : : ValentNotification *
381 : 1 : valent_notification_dialog_get_notification (ValentNotificationDialog *dialog)
382 : : {
383 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_NOTIFICATION_DIALOG (dialog), NULL);
384 : :
385 : 1 : return dialog->notification;
386 : : }
387 : :
388 : : /**
389 : : * valent_notification_dialog_get_reply_id: (get-property reply-id)
390 : : * @dialog: a `ValentNotificationDialog`
391 : : *
392 : : * Get the notification reply ID.
393 : : *
394 : : * Returns: (transfer none): the reply ID
395 : : */
396 : : const char *
397 : 2 : valent_notification_dialog_get_reply_id (ValentNotificationDialog *dialog)
398 : : {
399 [ + - ]: 2 : g_return_val_if_fail (VALENT_IS_NOTIFICATION_DIALOG (dialog), NULL);
400 : :
401 : 2 : return dialog->reply_id;
402 : : }
403 : :
404 : : /**
405 : : * valent_notification_dialog_set_reply_id: (set-property reply-id)
406 : : * @dialog: a `ValentNotificationDialog`
407 : : * @reply_id: (nullable): a notification reply ID
408 : : *
409 : : * Set the notification reply ID.
410 : : *
411 : : * If @reply_id is %NULL or empty, the notification can not be replied to.
412 : : */
413 : : void
414 : 2 : valent_notification_dialog_set_reply_id (ValentNotificationDialog *dialog,
415 : : const char *reply_id)
416 : : {
417 [ + - ]: 2 : g_return_if_fail (VALENT_IS_NOTIFICATION_DIALOG (dialog));
418 : :
419 [ + - ]: 2 : if (!g_set_str (&dialog->reply_id, reply_id))
420 : : return;
421 : :
422 : 2 : valent_notification_dialog_sync (dialog);
423 : 2 : g_object_notify_by_pspec (G_OBJECT (dialog), properties [PROP_REPLY_ID]);
424 : : }
425 : :
|