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