[gnome-todo] window: move notifications to dedicated subsystem



commit 1c874c7279825757e0afe1dea457ca4a9ceb99f3
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Jul 10 22:28:50 2015 -0300

    window: move notifications to dedicated subsystem
    
    Notifications are an important part of the application,
    and deserve a better implementation than some queues throwed
    away, like it was done.
    
    Now it has it's own subclass, with a nice widget which
    servers as an state machine for notifications.

 data/Makefile.am                           |    1 +
 data/todo.gresource.xml                    |    1 +
 data/ui/window.ui                          |   76 +----
 src/Makefile.am                            |    5 +
 src/gtd-task-list-view.c                   |   43 ++-
 src/gtd-types.h                            |    2 +
 src/gtd-window.c                           |  265 ++------------
 src/gtd-window.h                           |   15 +-
 src/notification/gtd-notification-widget.c |  277 ++++++++++++++
 src/notification/gtd-notification-widget.h |   42 +++
 src/notification/gtd-notification.c        |  544 ++++++++++++++++++++++++++++
 src/notification/gtd-notification.h        |   70 ++++
 12 files changed, 1010 insertions(+), 331 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index 8f3d8fe..45a72d7 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -29,6 +29,7 @@ EXTRA_DIST=                     \
   ui/edit-pane.ui \
   ui/initial-setup.ui \
   ui/list-view.ui \
+  ui/notification.ui \
   ui/storage-dialog.ui \
   ui/storage-popover.ui \
   ui/storage-row.ui \
diff --git a/data/todo.gresource.xml b/data/todo.gresource.xml
index 117d1ef..c81567a 100644
--- a/data/todo.gresource.xml
+++ b/data/todo.gresource.xml
@@ -5,6 +5,7 @@
     <file compressed="true" preprocess="xml-stripblanks">ui/edit-pane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/initial-setup.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/list-view.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/notification.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/storage-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/storage-popover.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/storage-row.ui</file>
diff --git a/data/ui/window.ui b/data/ui/window.ui
index 6f38c01..b8e6e2d 100644
--- a/data/ui/window.ui
+++ b/data/ui/window.ui
@@ -21,83 +21,11 @@
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <child type="overlay">
-          <object class="GtkRevealer" id="notification_revealer">
+          <object class="GtdNotificationWidget" id="notification_widget">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="transition_type">slide-down</property>
             <property name="halign">center</property>
             <property name="valign">start</property>
-            <child>
-              <object class="GtkFrame" id="notification_frame">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <child>
-                  <object class="GtkBox" id="notification_box">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="spacing">12</property>
-                    <child>
-                      <object class="GtkSpinner" id="notification_spinner">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="notification_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="notification_action_button">
-                        <property name="visible">False</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Undo</property>
-                        <signal name="clicked" 
handler="gtd_window__notification_notification_button_clicked" object="GtdWindow" swapped="no" />
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkButton" id="notification_close_button">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="relief">none</property>
-                        <signal name="clicked" 
handler="gtd_window__notification_notification_button_clicked" object="GtdWindow" swapped="no" />
-                        <child>
-                          <object class="GtkImage" id="notification_close_button_icon">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="icon_name">window-close-symbolic</property>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                  </object>
-                </child>
-                <style>
-                  <class name="app-notification" />
-                </style>
-              </object>
-            </child>
           </object>
         </child>
         <child>
@@ -105,6 +33,7 @@
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="transition_type">crossfade</property>
+            <signal name="notify::visible-child" handler="gtd_window__stack_visible_child_cb" 
object="GtdWindow" swapped="yes" />
             <child>
               <object class="GtkBox" id="main_box">
                 <property name="visible">True</property>
@@ -134,6 +63,7 @@
                     <property name="vexpand">True</property>
                     <property name="transition_duration">250</property>
                     <property name="transition_type">crossfade</property>
+                    <signal name="notify::visible-child" handler="gtd_window__stack_visible_child_cb" 
object="GtdWindow" swapped="yes" />
                     <child>
                       <object class="GtkScrolledWindow" id="lists_scrolled_window">
                         <property name="visible">True</property>
diff --git a/src/Makefile.am b/src/Makefile.am
index fbc235e..738eb84 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -5,6 +5,7 @@ AM_CPPFLAGS = \
        -DUI_DATA_DIR=\""$(pkgdatadir)/style"\" \
        -DGOA_API_IS_SUBJECT_TO_CHANGE \
        -I$(srcdir)/storage \
+       -I$(srcdir)/notification \
        $(GNOME_TODO_CFLAGS)
 
 bin_PROGRAMS = gnome-todo
@@ -15,6 +16,10 @@ BUILT_SOURCES = \
 
 gnome_todo_SOURCES = \
        $(BUILT_SOURCES) \
+       notification/gtd-notification.c \
+       notification/gtd-notification.h \
+       notification/gtd-notification-widget.c \
+       notification/gtd-notification-widget.h \
        storage/gtd-storage.c \
        storage/gtd-storage.h \
        storage/gtd-storage-dialog.c \
diff --git a/src/gtd-task-list-view.c b/src/gtd-task-list-view.c
index fe5c293..f61b607 100644
--- a/src/gtd-task-list-view.c
+++ b/src/gtd-task-list-view.c
@@ -20,6 +20,7 @@
 #include "gtd-edit-pane.h"
 #include "gtd-task-list-view.h"
 #include "gtd-manager.h"
+#include "gtd-notification.h"
 #include "gtd-task.h"
 #include "gtd-task-list.h"
 #include "gtd-task-row.h"
@@ -91,30 +92,27 @@ enum {
   LAST_PROP
 };
 
-static gboolean
-remove_task_action (RemoveTaskData *data)
+static void
+remove_task_action (GtdNotification *notification,
+                    gpointer         user_data)
 {
-  g_return_val_if_fail (data != NULL, G_SOURCE_REMOVE);
+  RemoveTaskData *data = user_data;
 
   gtd_manager_remove_task (data->view->priv->manager, data->task);
 
   g_free (data);
-
-  return G_SOURCE_REMOVE;
 }
 
-static gboolean
-undo_remove_task_action (RemoveTaskData *data)
+static void
+undo_remove_task_action (GtdNotification *notification,
+                         gpointer         user_data)
 {
+  RemoveTaskData  *data = user_data;
   GtdTaskList *list = gtd_task_get_list (data->task);
 
-  g_return_val_if_fail (data != NULL, G_SOURCE_REMOVE);
-
   gtd_task_list_save_task (list, data->task);
 
   g_free (data);
-
-  return G_SOURCE_REMOVE;
 }
 
 static void
@@ -192,6 +190,7 @@ gtd_task_list_view__remove_task_cb (GtdEditPane *pane,
                                     gpointer     user_data)
 {
   GtdTaskListViewPrivate *priv;
+  GtdNotification *notification;
   RemoveTaskData *data;
   GtdWindow *window;
   gchar *text;
@@ -211,15 +210,19 @@ gtd_task_list_view__remove_task_cb (GtdEditPane *pane,
 
   gtk_revealer_set_reveal_child (priv->edit_revealer, FALSE);
 
-  gtd_window_notify (window,
-                     7500, //ms
-                     TASK_REMOVED_NOTIFICATION_ID,
-                     text,
-                     _("Undo"),
-                     (GSourceFunc) remove_task_action,
-                     (GSourceFunc) undo_remove_task_action,
-                     FALSE,
-                     data);
+  /* Notify about the removal */
+  notification = gtd_notification_new (text, 7500.0);
+
+  gtd_notification_set_primary_action (notification,
+                                       (GtdNotificationActionFunc) remove_task_action,
+                                       data);
+
+  gtd_notification_set_secondary_action (notification,
+                                         _("Undo"),
+                                         (GtdNotificationActionFunc) undo_remove_task_action,
+                                         data);
+
+  gtd_window_notify (window, notification);
 
   g_free (text);
 }
diff --git a/src/gtd-types.h b/src/gtd-types.h
index f4f8a2f..5bd651a 100644
--- a/src/gtd-types.h
+++ b/src/gtd-types.h
@@ -29,6 +29,8 @@ typedef struct _GtdApplication          GtdApplication;
 typedef struct _GtdInitialSetupWindow   GtdInitialSetupWindow;
 typedef struct _GtdListView             GtdListView;
 typedef struct _GtdManager              GtdManager;
+typedef struct _GtdNotification         GtdNotification;
+typedef struct _GtdNotificationWidget   GtdNotificationWidget;
 typedef struct _GtdObject               GtdObject;
 typedef struct _GtdStorage              GtdStorage;
 typedef struct _GtdStoragePopover       GtdStoragePopover;
diff --git a/src/gtd-window.c b/src/gtd-window.c
index 9ae798f..271eef8 100644
--- a/src/gtd-window.c
+++ b/src/gtd-window.c
@@ -19,6 +19,8 @@
 #include "gtd-application.h"
 #include "gtd-task-list-view.h"
 #include "gtd-manager.h"
+#include "gtd-notification.h"
+#include "gtd-notification-widget.h"
 #include "gtd-storage-dialog.h"
 #include "gtd-task.h"
 #include "gtd-task-list.h"
@@ -35,15 +37,12 @@ typedef struct
   GtkFlowBox                    *lists_flowbox;
   GtkStack                      *main_stack;
   GtkWidget                     *new_list_popover;
-  GtkButton                     *notification_action_button;
-  GtkButton                     *notification_close_button;
-  GtkLabel                      *notification_label;
-  GtkRevealer                   *notification_revealer;
-  GtkSpinner                    *notification_spinner;
+  GtdNotificationWidget         *notification_widget;
   GtdTaskListView               *scheduled_list_view;
   GtkSearchBar                  *search_bar;
+  GtkToggleButton               *search_button;
   GtkSearchEntry                *search_entry;
-  GtkStackSwitcher              *stack;
+  GtkStack                      *stack;
   GtkStackSwitcher              *stack_switcher;
   GtdStorageDialog              *storage_dialog;
   GtdTaskListView               *today_list_view;
@@ -52,12 +51,8 @@ typedef struct
   /* mode */
   GtdWindowMode                  mode;
 
-  /*
-   * A queue of the next operations.
-   */
-  GQueue                        *notification_queue;
-  gint                           notification_delay_id;
-  gboolean                       consuming_notifications;
+  /* loading notification */
+  GtdNotification               *loading_notification;
 
   GtdManager                    *manager;
 } GtdWindowPrivate;
@@ -70,37 +65,10 @@ struct _GtdWindow
   GtdWindowPrivate     *priv;
 };
 
-typedef struct
-{
-  GtdWindow   *window;
-
-  gchar       *id;
-  gchar       *text;
-  gchar       *label;
-
-  gint         delay;
-  gboolean     show_spinner;
-
-  GSourceFunc  primary_action;
-  GSourceFunc  secondary_action;
-
-  gpointer     data;
-} NotificationData;
-
-#define LOADING_LISTS_NOTIFICATION_ID            "loading-lists-id"
-
 static void          gtd_window__change_storage_action           (GSimpleAction         *simple,
                                                                   GVariant              *parameter,
                                                                   gpointer               user_data);
 
-static gboolean      gtd_window__execute_notification_data       (NotificationData      *data,
-                                                                  gboolean               primary_action);
-
-
-static void          gtd_window_consume_notification             (GtdWindow             *window);
-
-void                 notification_data_free                      (NotificationData      *data);
-
 G_DEFINE_TYPE_WITH_PRIVATE (GtdWindow, gtd_window, GTK_TYPE_APPLICATION_WINDOW)
 
 static const GActionEntry gtd_window_entries[] = {
@@ -114,6 +82,20 @@ enum {
 };
 
 static void
+gtd_window__stack_visible_child_cb (GtdWindow *window)
+{
+  GtdWindowPrivate *priv;
+
+  priv = window->priv;
+
+  gtk_widget_set_visible (
+          GTK_WIDGET (priv->search_button),
+          g_strcmp0 (gtk_stack_get_visible_child_name (priv->main_stack), "overview") == 0 &&
+          g_strcmp0 (gtk_stack_get_visible_child_name (priv->stack), "lists") == 0);
+
+}
+
+static void
 gtd_window_update_list_counters (GtdTaskList *list,
                                  GtdTask     *task,
                                  GtdWindow   *window)
@@ -166,14 +148,6 @@ gtd_window_update_list_counters (GtdTaskList *list,
   g_free (new_title);
 }
 
-void
-notification_data_free (NotificationData *data)
-{
-  g_free (data->id);
-  g_free (data->text);
-  g_free (data->label);
-  g_free (data);
-}
 
 static void
 gtd_window__change_storage_action (GSimpleAction *simple,
@@ -190,93 +164,6 @@ gtd_window__change_storage_action (GSimpleAction *simple,
 }
 
 static void
-gtd_window__notification_notification_button_clicked (GtkButton *button,
-                                                      gpointer   user_data)
-{
-  GtdWindowPrivate *priv;
-  NotificationData *data;
-
-  g_return_if_fail (GTD_IS_WINDOW (user_data));
-
-  priv = GTD_WINDOW (user_data)->priv;
-  data = g_queue_peek_head (priv->notification_queue);
-
-  /* Cancel any previouly set timeouts */
-  if (priv->notification_delay_id > 0)
-    {
-      g_source_remove (priv->notification_delay_id);
-      priv->notification_delay_id = 0;
-    }
-
-  gtd_window__execute_notification_data (data, button == priv->notification_close_button);
-}
-
-static void
-gtd_window_consume_notification (GtdWindow *window)
-{
-  GtdWindowPrivate *priv;
-  NotificationData *data;
-
-  g_return_if_fail (GTD_IS_WINDOW (window));
-
-  priv = window->priv;
-
-  if (g_queue_is_empty (priv->notification_queue))
-    {
-      gtk_revealer_set_reveal_child (priv->notification_revealer, FALSE);
-      priv->consuming_notifications = FALSE;
-      return;
-    }
-
-  priv->consuming_notifications = TRUE;
-
-  /* Keep the current operation until it is finished or canceled */
-  data = g_queue_peek_head (priv->notification_queue);
-
-  gtk_button_set_label (priv->notification_action_button, data->label ? data->label : "");
-  gtk_label_set_markup (priv->notification_label, data->text);
-  gtk_revealer_set_reveal_child (priv->notification_revealer, TRUE);
-  gtk_widget_set_visible (GTK_WIDGET (priv->notification_action_button), data->label != NULL);
-
-  /* spinner */
-  gtk_widget_set_visible (GTK_WIDGET (priv->notification_spinner), data->show_spinner);
-  g_object_set (priv->notification_spinner, "active", data->show_spinner, NULL);
-
-  /* If there's a delay set, execute the action */
-  if (data->delay)
-    {
-      priv->notification_delay_id = g_timeout_add (data->delay,
-                                                   (GSourceFunc) gtd_window__execute_notification_data,
-                                                   data);
-    }
-}
-
-static gboolean
-gtd_window__execute_notification_data (NotificationData *data,
-                                       gboolean          primary_action)
-{
-  GtdWindowPrivate *priv = data->window->priv;
-  gboolean retval = G_SOURCE_REMOVE;
-
-  if (primary_action && data->primary_action)
-    retval = data->primary_action (data->data);
-  else if (!primary_action && data->secondary_action)
-    retval = data->secondary_action (data->data);
-
-  /* Remove the current notification from queue */
-  g_queue_pop_head (priv->notification_queue);
-
-  /* Continue consuming notifications */
-  gtd_window_consume_notification (data->window);
-
-  notification_data_free (data);
-
-  priv->notification_delay_id = 0;
-
-  return retval;
-}
-
-static void
 gtd_window__list_color_set (GtkColorChooser *button,
                             gpointer         user_data)
 {
@@ -384,23 +271,16 @@ gtd_window__manager_ready_changed (GObject    *source,
                                    gpointer    user_data)
 {
   GtdWindowPrivate *priv = GTD_WINDOW (user_data)->priv;
+
   g_return_if_fail (GTD_IS_WINDOW (user_data));
 
   if (gtd_object_get_ready (GTD_OBJECT (source)))
     {
-      gtd_window_cancel_notification (GTD_WINDOW (user_data), LOADING_LISTS_NOTIFICATION_ID);
+      gtd_notification_widget_cancel (priv->notification_widget, priv->loading_notification);
     }
   else
     {
-      gtd_window_notify (GTD_WINDOW (user_data),
-                         0,
-                         LOADING_LISTS_NOTIFICATION_ID,
-                         _("Loading your task lists…"),
-                         NULL,
-                         NULL,
-                         NULL,
-                         TRUE,
-                         NULL);
+      gtd_notification_widget_notify (priv->notification_widget, priv->loading_notification);
     }
 }
 
@@ -646,13 +526,11 @@ gtd_window_class_init (GtdWindowClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, list_view);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, main_stack);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, new_list_popover);
-  gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, notification_action_button);
-  gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, notification_label);
-  gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, notification_revealer);
-  gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, notification_spinner);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, notification_widget);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, scheduled_list_view);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, stack);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, search_bar);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, search_button);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, search_entry);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, stack_switcher);
   gtk_widget_class_bind_template_child_private (widget_class, GtdWindow, storage_dialog);
@@ -662,7 +540,7 @@ gtd_window_class_init (GtdWindowClass *klass)
   gtk_widget_class_bind_template_callback (widget_class, gtd_window__list_color_set);
   gtk_widget_class_bind_template_callback (widget_class, gtd_window__list_selected);
   gtk_widget_class_bind_template_callback (widget_class, gtd_window__on_key_press_event);
-  gtk_widget_class_bind_template_callback (widget_class, 
gtd_window__notification_notification_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, gtd_window__stack_visible_child_cb);
 }
 
 static void
@@ -670,7 +548,8 @@ gtd_window_init (GtdWindow *self)
 {
   self->priv = gtd_window_get_instance_private (self);
 
-  self->priv->notification_queue = g_queue_new ();
+  self->priv->loading_notification = gtd_notification_new (_("Loading your task lists…"), 0);
+  gtd_object_set_ready (GTD_OBJECT (self->priv->loading_notification), FALSE);
 
   /* add actions */
   g_action_map_add_action_entries (G_ACTION_MAP (self),
@@ -700,111 +579,43 @@ gtd_window_get_manager (GtdWindow *window)
 /**
  * gtd_window_notify:
  * @window: a #GtdWindow
- * @visible_time: time which the notification will be visible, 0 to show permanently
- * @text: text of the notification
- * @button_label: text to show on the notification button, %NULL to hide it
- * @primary_action: function to call when close button is clicked
- * @secondary_action: function to call when the alternative button is clicked
- * @show_spinner: whether show the spinner or not
- * @user_data: custom data
+ * @notification: a #GtdNotification
  *
  * Shows a notification on the top of the main window.
  *
  * Returns:
  */
 void
-gtd_window_notify (GtdWindow   *window,
-                   gint         visible_time,
-                   const gchar *id,
-                   const gchar *text,
-                   const gchar *button_label,
-                   GSourceFunc  primary_action,
-                   GSourceFunc  secondary_action,
-                   gboolean     show_spinner,
-                   gpointer     user_data)
+gtd_window_notify (GtdWindow       *window,
+                   GtdNotification *notification)
 {
   GtdWindowPrivate *priv;
-  NotificationData *data;
 
   g_return_if_fail (GTD_IS_WINDOW (window));
 
   priv = window->priv;
 
-  data = g_new0 (NotificationData, 1);
-  data->window = window;
-  data->delay = visible_time;
-  data->id = g_strdup (id);
-  data->text = g_strdup (text);
-  data->label = g_strdup (button_label);
-  data->primary_action = primary_action;
-  data->secondary_action = secondary_action;
-  data->show_spinner = show_spinner;
-  data->data = user_data;
-
-  g_queue_push_tail (priv->notification_queue, data);
-
-  /* If we're not consuming notifications, start it now */
-  if (!priv->consuming_notifications)
-    gtd_window_consume_notification (window);
+  gtd_notification_widget_notify (priv->notification_widget, notification);
 }
 
 /**
  * gtd_window_cancel_notification:
  * @window: a #GtdManager
- * @id: id of the given notification
+ * @notification: a #GtdNotification
  *
- * Cancels the notification with @id as id.
+ * Cancels @notification.
  *
  * Returns:
  */
 void
-gtd_window_cancel_notification (GtdWindow   *window,
-                                const gchar *id)
+gtd_window_cancel_notification (GtdWindow       *window,
+                                GtdNotification *notification)
 {
   GtdWindowPrivate *priv;
-  NotificationData *data;
-  GList *head;
-  GList *l;
-  gint index;
 
   g_return_if_fail (GTD_IS_WINDOW (window));
-  g_return_if_fail (id != NULL);
 
-  data = NULL;
   priv = window->priv;
-  index = 0;
-
-  /* Search for a notification with the given id */
-  head = priv->notification_queue->head;
-
-  for (l = head; l != NULL; l = l->next)
-    {
-      NotificationData *tmp = l->data;
-
-      if (tmp && g_strcmp0 (tmp->id, id) == 0)
-        {
-          data = l->data;
-          break;
-        }
-
-      index++;
-    }
-
-  g_queue_remove (priv->notification_queue, data);
-
-  /* If we're removing the current head, continue consuming the queue */
-  if (index == 0)
-    {
-      /* Remove any remaining timeouts */
-      if (priv->notification_delay_id > 0)
-        {
-          g_source_remove (priv->notification_delay_id);
-          priv->notification_delay_id = 0;
-        }
-
-      gtd_window_consume_notification (window);
-    }
 
-  if (data)
-    notification_data_free (data);
+  gtd_notification_widget_cancel (priv->notification_widget, notification);
 }
diff --git a/src/gtd-window.h b/src/gtd-window.h
index 0f19f4f..901f2ce 100644
--- a/src/gtd-window.h
+++ b/src/gtd-window.h
@@ -35,17 +35,10 @@ GtkWidget*                gtd_window_new                  (GtdApplication
 GtdManager*               gtd_window_get_manager          (GtdWindow            *window);
 
 void                      gtd_window_notify               (GtdWindow            *window,
-                                                           gint                  visible_time,
-                                                           const gchar          *id,
-                                                           const gchar          *text,
-                                                           const gchar          *button_label,
-                                                           GSourceFunc           primary_action,
-                                                           GSourceFunc           secondary_action,
-                                                           gboolean              show_spinner,
-                                                           gpointer              user_data);
-
-void                     gtd_window_cancel_notification  (GtdWindow             *window,
-                                                          const gchar           *id);
+                                                           GtdNotification      *notification);
+
+void                      gtd_window_cancel_notification  (GtdWindow             *window,
+                                                           GtdNotification      *notification);
 
 G_END_DECLS
 
diff --git a/src/notification/gtd-notification-widget.c b/src/notification/gtd-notification-widget.c
new file mode 100644
index 0000000..dbdcb14
--- /dev/null
+++ b/src/notification/gtd-notification-widget.c
@@ -0,0 +1,277 @@
+/* gtd-notification-widget.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gtd-notification.h"
+#include "gtd-notification-widget.h"
+
+typedef enum
+{
+  STATE_IDLE,
+  STATE_EXECUTING
+} GtdExecutionState;
+
+typedef struct
+{
+  /* widgets */
+  GtkButton          *secondary_button;
+  GtkSpinner         *spinner;
+  GtkLabel           *text_label;
+
+  /* internal data */
+  GQueue             *queue;
+  GtdNotification    *current_notification;
+  GtdExecutionState   state;
+
+  /* bindings */
+  GBinding           *has_secondary_action_binding;
+  GBinding           *message_label_binding;
+  GBinding           *ready_binding;
+  GBinding           *secondary_label_binding;
+} GtdNotificationWidgetPrivate;
+
+struct _GtdNotificationWidget
+{
+  GtkRevealer                   parent;
+
+  /*< private >*/
+  GtdNotificationWidgetPrivate *priv;
+};
+
+/* Prototypes */
+static void         gtd_notification_widget_execute_notification (GtdNotificationWidget *widget,
+                                                                  GtdNotification       *notification);
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtdNotificationWidget, gtd_notification_widget, GTK_TYPE_REVEALER)
+
+static void
+gtd_notification_widget_clear_bindings (GtdNotificationWidget *widget)
+{
+  GtdNotificationWidgetPrivate *priv = widget->priv;
+
+  g_clear_pointer (&priv->has_secondary_action_binding, g_binding_unbind);
+  g_clear_pointer (&priv->message_label_binding, g_binding_unbind);
+  g_clear_pointer (&priv->ready_binding, g_binding_unbind);
+  g_clear_pointer (&priv->secondary_label_binding, g_binding_unbind);
+}
+
+/*
+ * This method is called after a notification is dismissed
+ * or any action is taken, and it verifies if it should
+ * continue the execution of notifications.
+ */
+static void
+gtd_notification_widget_stop_or_run (GtdNotificationWidget *widget)
+{
+  GtdNotificationWidgetPrivate *priv = widget->priv;
+
+  g_clear_object (&priv->current_notification);
+  priv->current_notification = g_queue_pop_head (priv->queue);
+
+  if (priv->current_notification)
+    {
+      gtk_revealer_set_reveal_child (GTK_REVEALER (widget), TRUE);
+      gtd_notification_widget_execute_notification (widget, priv->current_notification);
+      priv->state = STATE_EXECUTING;
+    }
+  else
+    {
+      gtk_revealer_set_reveal_child (GTK_REVEALER (widget), FALSE);
+      priv->state = STATE_IDLE;
+    }
+}
+
+static void
+gtd_notification_widget__close_button_clicked_cb (GtdNotificationWidget *widget)
+{
+  GtdNotificationWidgetPrivate *priv = widget->priv;
+
+  gtd_notification_stop (priv->current_notification);
+  gtd_notification_execute_primary_action (priv->current_notification);
+}
+
+static void
+gtd_notification_widget__secondary_button_clicked_cb (GtdNotificationWidget *widget)
+{
+  GtdNotificationWidgetPrivate *priv = widget->priv;
+
+  gtd_notification_stop (priv->current_notification);
+  gtd_notification_execute_secondary_action (priv->current_notification);
+}
+
+static void
+gtd_notification_widget__notification_executed_cb (GtdNotification       *notification,
+                                                   GtdNotificationWidget *widget)
+{
+  gtd_notification_widget_clear_bindings (widget);
+  gtd_notification_widget_stop_or_run (widget);
+
+  g_signal_handlers_disconnect_by_func (notification,
+                                        gtd_notification_widget__notification_executed_cb,
+                                        widget);
+}
+
+static void
+gtd_notification_widget_execute_notification (GtdNotificationWidget *widget,
+                                              GtdNotification       *notification)
+{
+  GtdNotificationWidgetPrivate *priv = widget->priv;
+
+  g_signal_connect (notification,
+                    "executed",
+                    G_CALLBACK (gtd_notification_widget__notification_executed_cb),
+                    widget);
+
+  priv->has_secondary_action_binding =
+          g_object_bind_property (notification,
+                                  "has-secondary-action",
+                                  priv->secondary_button,
+                                  "visible",
+                                  G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+  priv->message_label_binding =
+          g_object_bind_property (notification,
+                                  "text",
+                                  priv->text_label,
+                                  "label",
+                                  G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+  priv->ready_binding =
+          g_object_bind_property (notification,
+                                  "ready",
+                                  priv->spinner,
+                                  "visible",
+                                  G_BINDING_DEFAULT | G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE);
+
+  priv->secondary_label_binding =
+          g_object_bind_property (notification,
+                                  "secondary-action-name",
+                                  priv->secondary_button,
+                                  "label",
+                                  G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+  gtd_notification_start (notification);
+}
+
+static void
+gtd_notification_widget_finalize (GObject *object)
+{
+  GtdNotificationWidget *self = (GtdNotificationWidget *)object;
+  GtdNotificationWidgetPrivate *priv = gtd_notification_widget_get_instance_private (self);
+
+  g_queue_free (priv->queue);
+
+  G_OBJECT_CLASS (gtd_notification_widget_parent_class)->finalize (object);
+}
+
+static void
+gtd_notification_widget_class_init (GtdNotificationWidgetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gtd_notification_widget_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/notification.ui");
+
+  gtk_widget_class_bind_template_child_private (widget_class, GtdNotificationWidget, secondary_button);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdNotificationWidget, spinner);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdNotificationWidget, text_label);
+
+  gtk_widget_class_bind_template_callback (widget_class, gtd_notification_widget__close_button_clicked_cb);
+  gtk_widget_class_bind_template_callback (widget_class, 
gtd_notification_widget__secondary_button_clicked_cb);
+}
+
+static void
+gtd_notification_widget_init (GtdNotificationWidget *self)
+{
+  self->priv = gtd_notification_widget_get_instance_private (self);
+  self->priv->queue = g_queue_new ();
+  self->priv->state = STATE_IDLE;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+/**
+ * gtd_notification_widget_new:
+ *
+ * Creates a new #GtdNotificationWidget.
+ *
+ * Returns: (transger full): a new #GtdNotificationWidget
+ */
+GtkWidget*
+gtd_notification_widget_new (void)
+{
+  return g_object_new (GTD_TYPE_NOTIFICATION_WIDGET, NULL);
+}
+
+/**
+ * gtd_notification_widget_notify:
+ *
+ * Adds @notification to the queue of notifications, and eventually
+ * consume it.
+ *
+ * Returns:
+ */
+void
+gtd_notification_widget_notify (GtdNotificationWidget *widget,
+                                GtdNotification       *notification)
+{
+  GtdNotificationWidgetPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION_WIDGET (widget));
+
+  priv = widget->priv;
+
+  if (!g_queue_find (priv->queue, notification))
+    {
+      g_queue_push_tail (priv->queue, notification);
+
+      if (priv->state == STATE_IDLE)
+        gtd_notification_widget_stop_or_run (widget);
+    }
+}
+
+/**
+ * gtd_notification_widget_cancel:
+ *
+ * Cancel @notification from being displayed. If @notification is not
+ * queued, nothing happens.
+ *
+ * Returns:
+ */
+void
+gtd_notification_widget_cancel (GtdNotificationWidget *widget,
+                                GtdNotification       *notification)
+{
+  GtdNotificationWidgetPrivate *priv;
+  GList *l;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION_WIDGET (widget));
+
+  priv = widget->priv;
+
+  if (notification == priv->current_notification)
+    {
+      gtd_notification_stop (notification);
+      gtd_notification_widget_stop_or_run (widget);
+    }
+  else if (g_queue_find (priv->queue, notification))
+    {
+      g_queue_remove (priv->queue, notification);
+    }
+}
diff --git a/src/notification/gtd-notification-widget.h b/src/notification/gtd-notification-widget.h
new file mode 100644
index 0000000..18b5882
--- /dev/null
+++ b/src/notification/gtd-notification-widget.h
@@ -0,0 +1,42 @@
+/* gtd-notification-widget.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTD_NOTIFICATION_WIDGET_H
+#define GTD_NOTIFICATION_WIDGET_H
+
+#include "gtd-types.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_NOTIFICATION_WIDGET (gtd_notification_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdNotificationWidget, gtd_notification_widget, GTD, NOTIFICATION_WIDGET, GtkRevealer)
+
+GtkWidget*           gtd_notification_widget_new                 (void);
+
+void                 gtd_notification_widget_notify              (GtdNotificationWidget *widget,
+                                                                  GtdNotification       *notification);
+
+void                 gtd_notification_widget_cancel              (GtdNotificationWidget *widget,
+                                                                  GtdNotification       *notification);
+
+G_END_DECLS
+
+#endif /* GTD_NOTIFICATION_WIDGET_H */
diff --git a/src/notification/gtd-notification.c b/src/notification/gtd-notification.c
new file mode 100644
index 0000000..ebe931d
--- /dev/null
+++ b/src/notification/gtd-notification.c
@@ -0,0 +1,544 @@
+/* gtd-notification.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gtd-notification.h"
+#include "gtd-object.h"
+
+#include <glib/gi18n.h>
+
+typedef struct
+{
+  gchar              *text;
+
+  gdouble             timeout;
+  gint                timeout_id;
+
+  GtdNotificationActionFunc primary_action;
+  gboolean            has_primary_action;
+  gpointer            primary_action_data;
+
+  GtdNotificationActionFunc secondary_action;
+  gboolean            has_secondary_action;
+  gpointer            secondary_action_data;
+  gchar              *secondary_action_name;
+} GtdNotificationPrivate;
+
+struct _GtdNotification
+{
+  GtdObject               parent;
+
+  /*< private >*/
+  GtdNotificationPrivate *priv;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtdNotification, gtd_notification, GTD_TYPE_OBJECT)
+
+enum
+{
+  PROP_0,
+  PROP_HAS_PRIMARY_ACTION,
+  PROP_HAS_SECONDARY_ACTION,
+  PROP_SECONDARY_ACTION_NAME,
+  PROP_TEXT,
+  PROP_TIMEOUT,
+  LAST_PROP
+};
+
+enum
+{
+  EXECUTED,
+  NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+static gboolean
+execute_action_cb (GtdNotification *notification)
+{
+  GtdNotificationPrivate *priv = notification->priv;
+
+  priv->timeout_id = 0;
+
+  gtd_notification_execute_primary_action (notification);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gtd_notification_finalize (GObject *object)
+{
+  GtdNotification *self = (GtdNotification *)object;
+  GtdNotificationPrivate *priv = gtd_notification_get_instance_private (self);
+
+  if (priv->timeout_id > 0)
+    g_source_remove (priv->timeout_id);
+
+  g_clear_pointer (&priv->secondary_action_name, g_free);
+  g_clear_pointer (&priv->text, g_free);
+
+  G_OBJECT_CLASS (gtd_notification_parent_class)->finalize (object);
+}
+
+static void
+gtd_notification_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GtdNotification *self = GTD_NOTIFICATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_HAS_PRIMARY_ACTION:
+      g_value_set_boolean (value, self->priv->has_primary_action);
+      break;
+
+    case PROP_HAS_SECONDARY_ACTION:
+      g_value_set_boolean (value, self->priv->has_secondary_action);
+      break;
+
+    case PROP_SECONDARY_ACTION_NAME:
+      g_value_set_string (value, self->priv->secondary_action_name ? self->priv->secondary_action_name : "");
+      break;
+
+    case PROP_TEXT:
+      g_value_set_string (value, gtd_notification_get_text (self));
+      break;
+
+    case PROP_TIMEOUT:
+      g_value_set_double (value, gtd_notification_get_timeout (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtd_notification_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  GtdNotification *self = GTD_NOTIFICATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_SECONDARY_ACTION_NAME:
+      gtd_notification_set_secondary_action (self,
+                                             g_value_get_string (value),
+                                             self->priv->secondary_action,
+                                             self->priv->secondary_action_data);
+      break;
+
+    case PROP_TEXT:
+      gtd_notification_set_text (self, g_value_get_string (value));
+      break;
+
+    case PROP_TIMEOUT:
+      gtd_notification_set_timeout (self, g_value_get_double (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtd_notification_class_init (GtdNotificationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtd_notification_finalize;
+  object_class->get_property = gtd_notification_get_property;
+  object_class->set_property = gtd_notification_set_property;
+
+  /**
+   * GtdNotification::has-primary-action:
+   *
+   * @TRUE if the notification has a primary action or @FALSE otherwise. The
+   * primary action is triggered on notification timeout or dismiss.
+   */
+  g_object_class_install_property (
+        object_class,
+        PROP_HAS_PRIMARY_ACTION,
+        g_param_spec_boolean ("has-primary-action",
+                              _("Whether the notification has a primary action"),
+                              _("Whether the notification has the primary action, activated on timeout or 
dismiss"),
+                              FALSE,
+                              G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
+
+  /**
+   * GtdNotification::has-secondary-action:
+   *
+   * @TRUE if the notification has a secondary action or @FALSE otherwise. The
+   * secondary action is triggered only by user explicit input.
+   */
+  g_object_class_install_property (
+        object_class,
+        PROP_HAS_SECONDARY_ACTION,
+        g_param_spec_boolean ("has-secondary-action",
+                              _("Whether the notification has a secondary action"),
+                              _("Whether the notification has the secondary action, activated by the user"),
+                              FALSE,
+                              G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY));
+
+  /**
+   * GtdNotification::secondary-action-name:
+   *
+   * The main text of the notification, usually a markuped text.
+   */
+  g_object_class_install_property (
+        object_class,
+        PROP_SECONDARY_ACTION_NAME,
+        g_param_spec_string ("secondary-action-name",
+                             _("Text of the secondary action button"),
+                             _("The text of the secondary action button"),
+                             "",
+                             G_PARAM_READWRITE));
+
+  /**
+   * GtdNotification::text:
+   *
+   * The main text of the notification, usually a markuped text.
+   */
+  g_object_class_install_property (
+        object_class,
+        PROP_TEXT,
+        g_param_spec_string ("text",
+                             _("Notification message"),
+                             _("The main message of the notification"),
+                             "",
+                             G_PARAM_READWRITE));
+
+  /**
+   * GtdNotification::timeout:
+   *
+   * The time the notification will be displayed.
+   */
+  g_object_class_install_property (
+        object_class,
+        PROP_TIMEOUT,
+        g_param_spec_double ("timeout",
+                             _("Notification timeout"),
+                             _("The time the notification is displayed"),
+                             0.0,
+                             30000.0,
+                             7500.00,
+                             G_PARAM_READWRITE));
+
+  /**
+   * GtdNotification::executed:
+   *
+   * The ::executed signal is emmited after the primary or secondary
+   * #GtdNotification action is executed.
+   */
+  signals[EXECUTED] = g_signal_new ("executed",
+                                    GTD_TYPE_NOTIFICATION,
+                                    G_SIGNAL_RUN_FIRST,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    NULL,
+                                    G_TYPE_NONE,
+                                    0);
+}
+
+static void
+gtd_notification_init (GtdNotification *self)
+{
+  self->priv = gtd_notification_get_instance_private (self);
+  self->priv->secondary_action_name = NULL;
+  self->priv->text = NULL;
+  self->priv->timeout = 7500.0;
+}
+
+/**
+ * gtd_notification_new:
+ *
+ * Creates a new notification with @text and @timeout. If @timeout is
+ * 0, the notification is indefinitely displayed.
+ *
+ * Returns: (transfer full): a new #GtdNotification
+ */
+GtdNotification*
+gtd_notification_new (const gchar *text,
+                      gdouble      timeout)
+{
+  return g_object_new (GTD_TYPE_NOTIFICATION,
+                       "text", text,
+                       "timeout", timeout,
+                       NULL);
+}
+
+/**
+ * gtd_notification_set_primary_action:
+ *
+ * Sets the primary action of @notification, which is triggered
+ * on dismiss or timeout.
+ *
+ * Returns:
+ */
+void
+gtd_notification_set_primary_action (GtdNotification           *notification,
+                                     GtdNotificationActionFunc  func,
+                                     gpointer                   user_data)
+{
+  GtdNotificationPrivate *priv;
+  gboolean has_action;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+  has_action = (func != NULL);
+
+  if (has_action != priv->has_primary_action)
+    {
+      priv->has_primary_action = has_action;
+
+      priv->primary_action = has_action ? func : NULL;
+      priv->primary_action_data = has_action ? user_data : NULL;
+
+      g_object_notify (G_OBJECT (notification), "has-primary-action");
+    }
+}
+
+/**
+ * gtd_notification_set_secondary_action:
+ *
+ * Sets the secondary action of @notification, which is triggered
+ * only on user explicit input.
+ *
+ * Returns:
+ */
+void
+gtd_notification_set_secondary_action (GtdNotification           *notification,
+                                       const gchar               *name,
+                                       GtdNotificationActionFunc  func,
+                                       gpointer                   user_data)
+{
+  GtdNotificationPrivate *priv;
+  gboolean has_action;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+  has_action = (func != NULL);
+
+  if (has_action != priv->has_secondary_action)
+    {
+      priv->has_secondary_action = has_action;
+
+      priv->secondary_action = has_action ? func : NULL;
+      priv->secondary_action_data = has_action ? user_data : NULL;
+
+      if (priv->secondary_action_name != name)
+        {
+          g_clear_pointer (&priv->secondary_action_name, g_free);
+          priv->secondary_action_name = g_strdup (name);
+
+          g_object_notify (G_OBJECT (notification), "secondary-action-name");
+        }
+
+      g_object_notify (G_OBJECT (notification), "has-secondary-action");
+    }
+}
+
+/**
+ * gtd_notification_get_text:
+ *
+ * Gets the text of @notification.
+ *
+ * Returns: (transfer none): the text of @notification.
+ */
+const gchar*
+gtd_notification_get_text (GtdNotification *notification)
+{
+  g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), NULL);
+
+  return notification->priv->text ? notification->priv->text : "";
+}
+
+/**
+ * gtd_notification_set_text:
+ *
+ * Sets the text of @notification to @text.
+ *
+ * Returns:
+ */
+void
+gtd_notification_set_text (GtdNotification *notification,
+                           const gchar     *text)
+{
+  GtdNotificationPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+
+  if (g_strcmp0 (priv->text, text) != 0)
+    {
+      g_clear_pointer (&priv->text, g_free);
+      priv->text = g_strdup (text);
+
+      g_object_notify (G_OBJECT (notification), "text");
+    }
+}
+
+/**
+ * gtd_notification_get_timeout:
+ *
+ * Retrieves the timeout of @notification.
+ *
+ * Returns: the timeout of @notification.
+ */
+gdouble
+gtd_notification_get_timeout (GtdNotification *notification)
+{
+  g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), 0.0);
+
+  return notification->priv->timeout;
+}
+
+/**
+ * gtd_notification_set_timeout:
+ *
+ * Sets the timeout of @notification to @timeout.
+ *
+ * Returns:
+ */
+void
+gtd_notification_set_timeout (GtdNotification *notification,
+                              gdouble          timeout)
+{
+  GtdNotificationPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+
+  if (priv->timeout != timeout)
+    {
+      priv->timeout = timeout;
+
+      g_object_notify (G_OBJECT (notification), "timeout");
+    }
+}
+
+/**
+ * gtd_notification_execute_primary_action:
+ *
+ * Executes the primary action of @notification if set.
+ *
+ * Returns:
+ */
+void
+gtd_notification_execute_primary_action (GtdNotification *notification)
+{
+  GtdNotificationPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+
+  if (priv->primary_action)
+    {
+      priv->primary_action (notification, priv->primary_action_data);
+
+      g_signal_emit (notification, signals[EXECUTED], 0);
+    }
+}
+
+/**
+ * gtd_notification_execute_secondary_action:
+ *
+ * Executes the secondary action of @notification if any.
+ *
+ * Returns:
+ */
+void
+gtd_notification_execute_secondary_action (GtdNotification *notification)
+{
+  GtdNotificationPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+
+  if (priv->secondary_action)
+    {
+      priv->secondary_action (notification, priv->secondary_action_data);
+
+      g_signal_emit (notification, signals[EXECUTED], 0);
+    }
+}
+
+/**
+ * gtd_notification_start:
+ *
+ * Starts the timeout of notification. Use @gtd_notification_stop
+ * to stop it.
+ *
+ * Returns:
+ */
+void
+gtd_notification_start (GtdNotification *notification)
+{
+  GtdNotificationPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+
+  if (priv->timeout != 0)
+    {
+      if (priv->timeout_id > 0)
+        {
+          g_source_remove (priv->timeout_id);
+          priv->timeout_id = 0;
+        }
+
+      priv->timeout_id = g_timeout_add (priv->timeout,
+                                        (GSourceFunc) execute_action_cb,
+                                        notification);
+    }
+}
+
+/**
+ * gtd_notification_stop:
+ *
+ * Stops the timeout of notification. Use @gtd_notification_start
+ * to start it.
+ *
+ * Returns:
+ */
+void
+gtd_notification_stop (GtdNotification *notification)
+{
+  GtdNotificationPrivate *priv;
+
+  g_return_if_fail (GTD_IS_NOTIFICATION (notification));
+
+  priv = notification->priv;
+
+  if (priv->timeout_id != 0)
+    {
+      g_source_remove (priv->timeout_id);
+      priv->timeout_id = 0;
+    }
+}
diff --git a/src/notification/gtd-notification.h b/src/notification/gtd-notification.h
new file mode 100644
index 0000000..2ae2298
--- /dev/null
+++ b/src/notification/gtd-notification.h
@@ -0,0 +1,70 @@
+/* gtd-notification.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTD_NOTIFICATION_H
+#define GTD_NOTIFICATION_H
+
+#include "gtd-object.h"
+#include "gtd-types.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_NOTIFICATION (gtd_notification_get_type())
+
+G_DECLARE_FINAL_TYPE (GtdNotification, gtd_notification, GTD, NOTIFICATION, GtdObject)
+
+typedef void (*GtdNotificationActionFunc) (GtdNotification *notification,
+                                           gpointer         user_data);
+
+
+GtdNotification*     gtd_notification_new                        (const gchar        *text,
+                                                                  gdouble             timeout);
+
+void                 gtd_notification_execute_primary_action     (GtdNotification    *notification);
+
+void                 gtd_notification_execute_secondary_action   (GtdNotification    *notification);
+
+void                 gtd_notification_start                      (GtdNotification    *notification);
+
+void                 gtd_notification_stop                       (GtdNotification    *notification);
+
+void                 gtd_notification_set_primary_action         (GtdNotification    *notification,
+                                                                  GtdNotificationActionFunc func,
+                                                                  gpointer            user_data);
+
+void                 gtd_notification_set_secondary_action       (GtdNotification    *notification,
+                                                                  const gchar        *name,
+                                                                  GtdNotificationActionFunc func,
+                                                                  gpointer            user_data);
+
+const gchar*         gtd_notification_get_text                   (GtdNotification    *notification);
+
+void                 gtd_notification_set_text                   (GtdNotification    *notification,
+                                                                  const gchar        *text);
+
+gdouble              gtd_notification_get_timeout                (GtdNotification    *notification);
+
+void                 gtd_notification_set_timeout                (GtdNotification    *notification,
+                                                                  gdouble             timeout);
+
+G_END_DECLS
+
+#endif /* GTD_NOTIFICATION_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]