1 /* gtd-notification.c
2 *
3 * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "GtdNotification"
20
21 #include "gtd-notification.h"
22 #include "gtd-object.h"
23
24 #include <glib/gi18n.h>
25
26 /**
27 * SECTION:gtd-notification
28 * @short_description:a notification with actions
29 * @title:GtdNotification
30 * @image:notification.png
31 * @stability:Stable
32 *
33 * The #GtdNotification represents a notification shown at the top of
34 * the window. The action can have a primary action that is called when
35 * the notification is gone.
36 *
37 * Optionally, the notification may have a secondary action (see gtd_notification_set_secondary_action())
38 * shown as a button.
39 *
40 * The notification may also have a timeout, which is how long the notification
41 * is displayed. By default, the timeout is 7.5 seconds.
42 *
43 * Example:
44 * |[
45 * GtdNotification *notification;
46 *
47 * notification = gtd_notification_new ("Something happened!", 7500);
48 *
49 * gtd_notification_set_primary_action (notification,
50 * called_when_notification_is_closed,
51 * self);
52 *
53 * gtd_notification_set_secondary_action (notification,
54 * "Details",
55 * show_details,
56 * self);
57 * [...]
58 *
59 * gtd_window_send_notification (window, notification);
60 * ]|
61 */
62
63 typedef struct
64 {
65 gchar *text;
66
67 gdouble timeout;
68 gint timeout_id;
69
70 GtdNotificationActionFunc primary_action;
71 gboolean has_primary_action;
72 gpointer primary_action_data;
73
74 GtdNotificationActionFunc secondary_action;
75 gboolean has_secondary_action;
76 gpointer secondary_action_data;
77 gchar *secondary_action_name;
78 } GtdNotificationPrivate;
79
80 struct _GtdNotification
81 {
82 GtdObject parent;
83
84 /*< private >*/
85 GtdNotificationPrivate *priv;
86 };
87
88 G_DEFINE_TYPE_WITH_PRIVATE (GtdNotification, gtd_notification, GTD_TYPE_OBJECT)
89
90 enum
91 {
92 PROP_0,
93 PROP_HAS_PRIMARY_ACTION,
94 PROP_HAS_SECONDARY_ACTION,
95 PROP_SECONDARY_ACTION_NAME,
96 PROP_TEXT,
97 PROP_TIMEOUT,
98 LAST_PROP
99 };
100
101 enum
102 {
103 EXECUTED,
104 NUM_SIGNALS
105 };
106
107 static guint signals[NUM_SIGNALS] = { 0, };
108
109 static gboolean
execute_action_cb(GtdNotification * notification)110 execute_action_cb (GtdNotification *notification)
111 {
112 GtdNotificationPrivate *priv = notification->priv;
113
114 priv->timeout_id = 0;
115
116 gtd_notification_execute_primary_action (notification);
117
118 return G_SOURCE_REMOVE;
119 }
120
121 static void
gtd_notification_finalize(GObject * object)122 gtd_notification_finalize (GObject *object)
123 {
124 GtdNotification *self = (GtdNotification *)object;
125 GtdNotificationPrivate *priv = gtd_notification_get_instance_private (self);
126
127 if (priv->timeout_id > 0)
128 g_source_remove (priv->timeout_id);
129
130 g_clear_pointer (&priv->secondary_action_name, g_free);
131 g_clear_pointer (&priv->text, g_free);
132
133 G_OBJECT_CLASS (gtd_notification_parent_class)->finalize (object);
134 }
135
136 static void
gtd_notification_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)137 gtd_notification_get_property (GObject *object,
138 guint prop_id,
139 GValue *value,
140 GParamSpec *pspec)
141 {
142 GtdNotification *self = GTD_NOTIFICATION (object);
143
144 switch (prop_id)
145 {
146 case PROP_HAS_PRIMARY_ACTION:
147 g_value_set_boolean (value, self->priv->has_primary_action);
148 break;
149
150 case PROP_HAS_SECONDARY_ACTION:
151 g_value_set_boolean (value, self->priv->has_secondary_action);
152 break;
153
154 case PROP_SECONDARY_ACTION_NAME:
155 g_value_set_string (value, self->priv->secondary_action_name ? self->priv->secondary_action_name : "");
156 break;
157
158 case PROP_TEXT:
159 g_value_set_string (value, gtd_notification_get_text (self));
160 break;
161
162 case PROP_TIMEOUT:
163 g_value_set_double (value, gtd_notification_get_timeout (self));
164 break;
165
166 default:
167 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
168 }
169 }
170
171 static void
gtd_notification_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)172 gtd_notification_set_property (GObject *object,
173 guint prop_id,
174 const GValue *value,
175 GParamSpec *pspec)
176 {
177 GtdNotification *self = GTD_NOTIFICATION (object);
178
179 switch (prop_id)
180 {
181 case PROP_SECONDARY_ACTION_NAME:
182 gtd_notification_set_secondary_action (self,
183 g_value_get_string (value),
184 self->priv->secondary_action,
185 self->priv->secondary_action_data);
186 break;
187
188 case PROP_TEXT:
189 gtd_notification_set_text (self, g_value_get_string (value));
190 break;
191
192 case PROP_TIMEOUT:
193 gtd_notification_set_timeout (self, g_value_get_double (value));
194 break;
195
196 default:
197 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
198 }
199 }
200
201 static void
gtd_notification_class_init(GtdNotificationClass * klass)202 gtd_notification_class_init (GtdNotificationClass *klass)
203 {
204 GObjectClass *object_class = G_OBJECT_CLASS (klass);
205
206 object_class->finalize = gtd_notification_finalize;
207 object_class->get_property = gtd_notification_get_property;
208 object_class->set_property = gtd_notification_set_property;
209
210 /**
211 * GtdNotification::has-primary-action:
212 *
213 * @TRUE if the notification has a primary action or @FALSE otherwise. The
214 * primary action is triggered on notification timeout or dismiss.
215 */
216 g_object_class_install_property (
217 object_class,
218 PROP_HAS_PRIMARY_ACTION,
219 g_param_spec_boolean ("has-primary-action",
220 "Whether the notification has a primary action",
221 "Whether the notification has the primary action, activated on timeout or dismiss",
222 FALSE,
223 G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
224
225 /**
226 * GtdNotification::has-secondary-action:
227 *
228 * @TRUE if the notification has a secondary action or @FALSE otherwise. The
229 * secondary action is triggered only by user explicit input.
230 */
231 g_object_class_install_property (
232 object_class,
233 PROP_HAS_SECONDARY_ACTION,
234 g_param_spec_boolean ("has-secondary-action",
235 "Whether the notification has a secondary action",
236 "Whether the notification has the secondary action, activated by the user",
237 FALSE,
238 G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
239
240 /**
241 * GtdNotification::secondary-action-name:
242 *
243 * The main text of the notification, usually a markuped text.
244 */
245 g_object_class_install_property (
246 object_class,
247 PROP_SECONDARY_ACTION_NAME,
248 g_param_spec_string ("secondary-action-name",
249 "Text of the secondary action button",
250 "The text of the secondary action button",
251 "",
252 G_PARAM_READWRITE));
253
254 /**
255 * GtdNotification::text:
256 *
257 * The main text of the notification, usually a markuped text.
258 */
259 g_object_class_install_property (
260 object_class,
261 PROP_TEXT,
262 g_param_spec_string ("text",
263 "Notification message",
264 "The main message of the notification",
265 "",
266 G_PARAM_READWRITE));
267
268 /**
269 * GtdNotification::timeout:
270 *
271 * The time the notification will be displayed.
272 */
273 g_object_class_install_property (
274 object_class,
275 PROP_TIMEOUT,
276 g_param_spec_double ("timeout",
277 "Notification timeout",
278 "The time the notification is displayed",
279 0.0,
280 30000.0,
281 7500.00,
282 G_PARAM_READWRITE));
283
284 /**
285 * GtdNotification::executed:
286 *
287 * The ::executed signal is emmited after the primary or secondary
288 * #GtdNotification action is executed.
289 */
290 signals[EXECUTED] = g_signal_new ("executed",
291 GTD_TYPE_NOTIFICATION,
292 G_SIGNAL_RUN_FIRST,
293 0,
294 NULL,
295 NULL,
296 NULL,
297 G_TYPE_NONE,
298 0);
299 }
300
301 static void
gtd_notification_init(GtdNotification * self)302 gtd_notification_init (GtdNotification *self)
303 {
304 self->priv = gtd_notification_get_instance_private (self);
305 self->priv->secondary_action_name = NULL;
306 self->priv->text = NULL;
307 self->priv->timeout = 7500.0;
308 }
309
310 /**
311 * gtd_notification_new:
312 * @text: (nullable): text of the notification
313 * @timeout: time for the notification to stay visible
314 *
315 * Creates a new notification with @text and @timeout. If @timeout is
316 * 0, the notification is indefinitely displayed.
317 *
318 * Returns: (transfer full): a new #GtdNotification
319 */
320 GtdNotification*
gtd_notification_new(const gchar * text,gdouble timeout)321 gtd_notification_new (const gchar *text,
322 gdouble timeout)
323 {
324 return g_object_new (GTD_TYPE_NOTIFICATION,
325 "text", text,
326 "timeout", timeout,
327 NULL);
328 }
329
330 /**
331 * gtd_notification_set_primary_action:
332 * @notification: a #GtdNotification
333 * @func: (closure user_data) (scope call) (nullable): the primary action function
334 * @user_data: data passed to @func
335 *
336 * Sets the primary action of @notification, which is triggered
337 * on dismiss or timeout.
338 */
339 void
gtd_notification_set_primary_action(GtdNotification * notification,GtdNotificationActionFunc func,gpointer user_data)340 gtd_notification_set_primary_action (GtdNotification *notification,
341 GtdNotificationActionFunc func,
342 gpointer user_data)
343 {
344 GtdNotificationPrivate *priv;
345 gboolean has_action;
346
347 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
348
349 priv = notification->priv;
350 has_action = (func != NULL);
351
352 if (has_action != priv->has_primary_action)
353 {
354 priv->has_primary_action = has_action;
355
356 priv->primary_action = has_action ? func : NULL;
357 priv->primary_action_data = has_action ? user_data : NULL;
358
359 g_object_notify (G_OBJECT (notification), "has-primary-action");
360 }
361 }
362
363 /**
364 * gtd_notification_set_secondary_action:
365 * @notification: a #GtdNotification
366 * @name: the name of the secondary action
367 * @func: (closure user_data) (scope call) (nullable): the secondary action function
368 * @user_data: data passed to @func
369 *
370 * Sets the secondary action of @notification, which is triggered
371 * only on user explicit input.
372 */
373 void
gtd_notification_set_secondary_action(GtdNotification * notification,const gchar * name,GtdNotificationActionFunc func,gpointer user_data)374 gtd_notification_set_secondary_action (GtdNotification *notification,
375 const gchar *name,
376 GtdNotificationActionFunc func,
377 gpointer user_data)
378 {
379 GtdNotificationPrivate *priv;
380 gboolean has_action;
381
382 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
383
384 priv = notification->priv;
385 has_action = (func != NULL);
386
387 if (has_action != priv->has_secondary_action)
388 {
389 priv->has_secondary_action = has_action;
390
391 priv->secondary_action = has_action ? func : NULL;
392 priv->secondary_action_data = has_action ? user_data : NULL;
393
394 if (priv->secondary_action_name != name)
395 {
396 g_clear_pointer (&priv->secondary_action_name, g_free);
397 priv->secondary_action_name = g_strdup (name);
398
399 g_object_notify (G_OBJECT (notification), "secondary-action-name");
400 }
401
402 g_object_notify (G_OBJECT (notification), "has-secondary-action");
403 }
404 }
405
406 /**
407 * gtd_notification_get_text:
408 * @notification: a #GtdNotification
409 *
410 * Gets the text of @notification.
411 *
412 * Returns: (transfer none): the text of @notification.
413 */
414 const gchar*
gtd_notification_get_text(GtdNotification * notification)415 gtd_notification_get_text (GtdNotification *notification)
416 {
417 g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), NULL);
418
419 return notification->priv->text ? notification->priv->text : "";
420 }
421
422 /**
423 * gtd_notification_set_text:
424 * @notification: a #GtdNotification
425 * @text: the user-visible text of @notification
426 *
427 * Sets the text of @notification to @text.
428 */
429 void
gtd_notification_set_text(GtdNotification * notification,const gchar * text)430 gtd_notification_set_text (GtdNotification *notification,
431 const gchar *text)
432 {
433 GtdNotificationPrivate *priv;
434
435 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
436
437 priv = notification->priv;
438
439 if (g_strcmp0 (priv->text, text) != 0)
440 {
441 g_clear_pointer (&priv->text, g_free);
442 priv->text = g_strdup (text);
443
444 g_object_notify (G_OBJECT (notification), "text");
445 }
446 }
447
448 /**
449 * gtd_notification_get_timeout:
450 *
451 * Retrieves the timeout of @notification.
452 *
453 * Returns: the timeout of @notification.
454 */
455 gdouble
gtd_notification_get_timeout(GtdNotification * notification)456 gtd_notification_get_timeout (GtdNotification *notification)
457 {
458 g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), 0.0);
459
460 return notification->priv->timeout;
461 }
462
463 /**
464 * gtd_notification_set_timeout:
465 * @notification: a #GtdNotification
466 * @timeout: the time to wait before running @notification, in miliseconds
467 *
468 * Sets the timeout of @notification to @timeout. Set it to %0 to disable
469 * the timeout.
470 */
471 void
gtd_notification_set_timeout(GtdNotification * notification,gdouble timeout)472 gtd_notification_set_timeout (GtdNotification *notification,
473 gdouble timeout)
474 {
475 GtdNotificationPrivate *priv;
476
477 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
478
479 priv = notification->priv;
480
481 if (priv->timeout != timeout)
482 {
483 priv->timeout = timeout;
484
485 g_object_notify (G_OBJECT (notification), "timeout");
486 }
487 }
488
489 /**
490 * gtd_notification_execute_primary_action:
491 * @notification: a #GtdNotification
492 *
493 * Executes the primary action of @notification if set.
494 */
495 void
gtd_notification_execute_primary_action(GtdNotification * notification)496 gtd_notification_execute_primary_action (GtdNotification *notification)
497 {
498 GtdNotificationPrivate *priv;
499
500 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
501
502 priv = notification->priv;
503
504 if (priv->primary_action)
505 priv->primary_action (notification, priv->primary_action_data);
506
507 g_signal_emit (notification, signals[EXECUTED], 0);
508 }
509
510 /**
511 * gtd_notification_execute_secondary_action:
512 * @notification: a #GtdNotification
513 *
514 * Executes the secondary action of @notification if any.
515 */
516 void
gtd_notification_execute_secondary_action(GtdNotification * notification)517 gtd_notification_execute_secondary_action (GtdNotification *notification)
518 {
519 GtdNotificationPrivate *priv;
520
521 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
522
523 priv = notification->priv;
524
525 if (priv->secondary_action)
526 {
527 priv->secondary_action (notification, priv->secondary_action_data);
528
529 gtd_notification_stop (notification);
530
531 g_signal_emit (notification, signals[EXECUTED], 0);
532 }
533 }
534
535 /**
536 * gtd_notification_start:
537 * @notification: a #GtdNotification
538 *
539 * Starts the timeout of notification. Use gtd_notification_stop()
540 * to stop it.
541 */
542 void
gtd_notification_start(GtdNotification * notification)543 gtd_notification_start (GtdNotification *notification)
544 {
545 GtdNotificationPrivate *priv;
546
547 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
548
549 priv = notification->priv;
550
551 if (priv->timeout != 0)
552 {
553 if (priv->timeout_id > 0)
554 {
555 g_source_remove (priv->timeout_id);
556 priv->timeout_id = 0;
557 }
558
559 priv->timeout_id = g_timeout_add (priv->timeout,
560 (GSourceFunc) execute_action_cb,
561 notification);
562 }
563 }
564
565 /**
566 * gtd_notification_stop:
567 * @notification: a #GtdNotification
568 *
569 * Stops the timeout of notification. Use gtd_notification_start()
570 * to start it.
571 */
572 void
gtd_notification_stop(GtdNotification * notification)573 gtd_notification_stop (GtdNotification *notification)
574 {
575 GtdNotificationPrivate *priv;
576
577 g_return_if_fail (GTD_IS_NOTIFICATION (notification));
578
579 priv = notification->priv;
580
581 if (priv->timeout_id != 0)
582 {
583 g_source_remove (priv->timeout_id);
584 priv->timeout_id = 0;
585 }
586 }
587