Commits

jjacky committed 357dff0

finished adding preferences; changed UpgradeAction values; added about

- Preferences: now fully functionnal
- UpgradeAction: was 0, 1 or 2. Now must be NONE, KALU or CMDLINE
- kalu would always check for NEWS regardless of settings, fixed
- added about dialog

Comments (0)

Files changed (5)

             }
             else if (strcmp ("options", section) == 0)
             {
-                unsigned int *verbose = NULL;
-                
                 if (strcmp (key, "PacmanConf") == 0)
                 {
                     setstringoption (value, "pacmanconf", &(config->pacmanconf));
                 }
                 else if (strcmp (key, "UpgradeAction") == 0)
                 {
-                    config->action = atoi (value);
+                    if (strcmp (value, "NONE") == 0)
+                    {
+                        config->action = UPGRADE_NO_ACTION;
+                    }
+                    else if (strcmp (value, "KALU") == 0)
+                    {
+                        config->action = UPGRADE_ACTION_KALU;
+                    }
+                    else if (strcmp (value, "CMDLINE") == 0)
+                    {
+                        config->action = UPGRADE_ACTION_CMDLINE;
+                    }
+                    else
+                    {
+                        set_error ("Invalid value for UpgradeAction: %s", value);
+                        success = FALSE;
+                        goto cleanup;
+                    }
                     debug ("config: action: %d", config->action);
                 }
                 else if (strcmp (key, "CmdLine") == 0)
                         || strcmp (key, "AutoChecks") == 0)
                 {
                     char *v, *s;
-                    unsigned int checks = 0;
+                    check_t checks = 0;
                     
                     for (v = value, s = (char *) 1; s; )
                     {
 
 #define _UNUSED_            __attribute__ ((unused)) 
 
-#define KALU_VERSION       "0.0.3"
+#define KALU_VERSION       "0.0.4"
 #define KALU_TAG            "Keeping Arch Linux Up-to-date"
 
 #define MAX_PATH            255
 typedef struct _config_t {
     gboolean         is_debug;
     char            *pacmanconf;
-    unsigned int     checks_manual;
-    unsigned int     checks_auto;
+    check_t          checks_manual;
+    check_t          checks_auto;
     int              interval;
     int              has_skip;
     int              skip_begin_hour;
     gint nb_news        = -1;
     unsigned int checks = (is_auto) ? config->checks_auto : config->checks_manual;
     
-    if (checks && CHECK_NEWS)
+    if (checks & CHECK_NEWS)
     {
         packages = NULL;
         if (news_has_updates (&packages, &xml_news, &error))
     set_kalpm_busy (TRUE);
     
     /* run in a separate thread, to not block/make GUI unresponsive */
-    g_thread_create ((GThreadFunc) kalu_check_work, (gpointer) is_auto, FALSE, NULL);
+    g_thread_create ((GThreadFunc) kalu_check_work, GINT_TO_POINTER (is_auto), FALSE, NULL);
 }
 
 static void
     show_prefs ();
 }
 
+static void
+menu_about_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    GtkAboutDialog *about;
+    GdkPixbuf *pixbuf;
+    const char *authors[] = {"Olivier Brunel", "Dave Gamble", "Pacman Development Team", NULL};
+    const char *artists[] = {"Painless Rob", NULL};
+    
+    about = GTK_ABOUT_DIALOG (gtk_about_dialog_new ());
+    pixbuf = gtk_widget_render_icon_pixbuf (GTK_WIDGET (about), "kalu-logo",
+                                            GTK_ICON_SIZE_DIALOG);
+    gtk_window_set_icon (GTK_WINDOW (about), pixbuf);
+    gtk_about_dialog_set_logo (about, pixbuf);
+    g_object_unref (G_OBJECT (pixbuf));
+    
+    gtk_about_dialog_set_program_name (about, "kalu");
+    gtk_about_dialog_set_version (about, KALU_VERSION);
+    gtk_about_dialog_set_comments (about, KALU_TAG);
+    gtk_about_dialog_set_website (about, "https://bitbucket.org/jjacky/kalu");
+    gtk_about_dialog_set_website_label (about, "https://bitbucket.org/jjacky/kalu");
+    gtk_about_dialog_set_copyright (about, "Copyright (C) 2012 Olivier Brunel");
+    gtk_about_dialog_set_license_type (about, GTK_LICENSE_GPL_3_0);
+    gtk_about_dialog_set_authors (about, authors);
+    gtk_about_dialog_set_artists (about, artists);
+    
+    gtk_dialog_run (GTK_DIALOG (about));
+    gtk_widget_destroy (GTK_WIDGET (about));
+}
+
 static gboolean
 menu_unmap_cb (GtkWidget *menu, GdkEvent *event _UNUSED_, gpointer data _UNUSED_)
 {
     gtk_widget_show (item);
     gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
     
+    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_ABOUT, NULL);
+    gtk_widget_set_tooltip_text (item, "Show Copyright & version information");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_about_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
     item = gtk_separator_menu_item_new ();
     gtk_widget_show (item);
     gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
     gboolean active = (kalpm_state.nb_upgrades + kalpm_state.nb_watched
                        + kalpm_state.nb_aur + kalpm_state.nb_watched_aur
                        + kalpm_state.nb_news > 0);
-    g_main_context_invoke (NULL, (GSourceFunc) set_status_icon, (gpointer) active);
+    g_main_context_invoke (NULL, (GSourceFunc) set_status_icon, GINT_TO_POINTER (active));
 }
 
 static gboolean
 #include "kalu.h"
 #include "preferences.h"
 #include "util.h"
+#include "watched.h"
+#include "util-gtk.h"
 
-static GtkWidget *window                = NULL;
+static gboolean
+focus_out_cb (GtkWidget *entry, GdkEvent *event _UNUSED_, gpointer data _UNUSED_);
+
+static GtkWidget *window                    = NULL;
+static GtkWidget *notebook                  = NULL;
 /* General */
-static GtkWidget *filechooser           = NULL;
-static GtkWidget *combo_interval        = NULL;
-static GtkWidget *button_skip           = NULL;
-static GtkWidget *spin_begin_hour       = NULL;
-static GtkWidget *spin_begin_minute     = NULL;
-static GtkWidget *spin_end_hour         = NULL;
-static GtkWidget *spin_end_minute       = NULL;
-static GtkWidget *auto_upgrades         = NULL;
-static GtkWidget *auto_watched          = NULL;
-static GtkWidget *auto_aur              = NULL;
-static GtkWidget *auto_watched_aur      = NULL;
-static GtkWidget *auto_news             = NULL;
-static GtkWidget *manual_upgrades       = NULL;
-static GtkWidget *manual_watched        = NULL;
-static GtkWidget *manual_aur            = NULL;
-static GtkWidget *manual_watched_aur    = NULL;
-static GtkWidget *manual_news           = NULL;
+static GtkWidget *filechooser               = NULL;
+static GtkWidget *combo_interval            = NULL;
+static GtkWidget *button_skip               = NULL;
+static GtkWidget *spin_begin_hour           = NULL;
+static GtkWidget *spin_begin_minute         = NULL;
+static GtkWidget *spin_end_hour             = NULL;
+static GtkWidget *spin_end_minute           = NULL;
+static GtkWidget *auto_upgrades             = NULL;
+static GtkWidget *auto_watched              = NULL;
+static GtkWidget *auto_aur                  = NULL;
+static GtkWidget *auto_watched_aur          = NULL;
+static GtkWidget *auto_news                 = NULL;
+static GtkWidget *manual_upgrades           = NULL;
+static GtkWidget *manual_watched            = NULL;
+static GtkWidget *manual_aur                = NULL;
+static GtkWidget *manual_watched_aur        = NULL;
+static GtkWidget *manual_news               = NULL;
+/* News */
+static GtkWidget *news_title_entry          = NULL;
+static GtkWidget *news_package_entry        = NULL;
+static GtkWidget *news_sep_entry            = NULL;
 /* Upgrades */
-static GtkWidget *button_upg_action     = NULL;
-static GtkWidget *upg_action_combo      = NULL;
-static GtkWidget *cmdline_label         = NULL;
-static GtkWidget *cmdline_entry         = NULL;
-static GtkWidget *cmdline_post_hbox     = NULL;
-static GtkListStore *cmdline_post_store = NULL;
-static GtkWidget *upg_title_entry       = NULL;
-static GtkWidget *upg_package_entry     = NULL;
-static GtkWidget *upg_sep_entry         = NULL;
+static GtkWidget *button_upg_action         = NULL;
+static GtkWidget *upg_action_combo          = NULL;
+static GtkWidget *cmdline_label             = NULL;
+static GtkWidget *cmdline_entry             = NULL;
+static GtkWidget *cmdline_post_hbox         = NULL;
+static GtkListStore *cmdline_post_store     = NULL;
+static GtkWidget *upg_title_entry           = NULL;
+static GtkWidget *upg_package_entry         = NULL;
+static GtkWidget *upg_sep_entry             = NULL;
+/* Watched */
+static GtkWidget *watched_title_entry       = NULL;
+static GtkWidget *watched_package_entry     = NULL;
+static GtkWidget *watched_sep_entry         = NULL;
+/* AUR */
+static GtkWidget *aur_cmdline_entry         = NULL;
+static GtkListStore *aur_ignore_store       = NULL;
+static GtkWidget *aur_title_entry           = NULL;
+static GtkWidget *aur_package_entry         = NULL;
+static GtkWidget *aur_sep_entry             = NULL;
+/* Watched */
+static GtkWidget *watched_aur_title_entry   = NULL;
+static GtkWidget *watched_aur_package_entry = NULL;
+static GtkWidget *watched_aur_sep_entry     = NULL;
+
+/* we keep a copy of templates like so, so that we can use it when refreshing
+ * the different templates. that is, values shown when a template is not set
+ * and therefore fallsback to another one.
+ * And because we only ever fallback on aur (for watched-aur) and upgrades, we
+ * only need to keep those two. */
+static struct _fallback_templates {
+    templates_t *tpl_upgrades;
+    templates_t *tpl_aur;
+} fallback_templates;
+
 
 static char *
 escape_text (const char *text)
 static void
 destroy_cb (GtkWidget *widget _UNUSED_)
 {
+    free (fallback_templates.tpl_upgrades->title);
+    free (fallback_templates.tpl_upgrades->package);
+    free (fallback_templates.tpl_upgrades->sep);
+    free (fallback_templates.tpl_upgrades);
+    free (fallback_templates.tpl_aur->title);
+    free (fallback_templates.tpl_aur->package);
+    free (fallback_templates.tpl_aur->sep);
+    free (fallback_templates.tpl_aur);
+    
     window = NULL;
 }
 
 }
 
 static void
+aur_action_toggled_cb (GtkToggleButton *button, gpointer data _UNUSED_)
+{
+    gtk_widget_set_sensitive (aur_cmdline_entry,
+        gtk_toggle_button_get_active (button));
+}
+
+static void
 insert_text_cb (GtkEditable *editable,
                 const gchar *text,
                 gint         length,
 }
 
 static void
-selection_changed_cb (GtkTreeSelection *selection, GtkWidget *buttons[2])
+selection_changed_cb (GtkTreeSelection *selection, GtkWidget *tree)
 {
     gboolean has_selection;
+    GtkWidget *btn_edit = g_object_get_data (G_OBJECT (tree), "btn-edit");
+    GtkWidget *btn_remove = g_object_get_data (G_OBJECT (tree), "btn-remove");
     
     has_selection = (gtk_tree_selection_count_selected_rows (selection) > 0);
-    gtk_widget_set_sensitive (buttons[0], has_selection);
-    gtk_widget_set_sensitive (buttons[1], has_selection); 
+    gtk_widget_set_sensitive (btn_edit, has_selection);
+    gtk_widget_set_sensitive (btn_remove, has_selection); 
 }
 
 static void
-btn_postsysupgrade_add_cb (GtkButton *button _UNUSED_, GtkTreeView *tree)
+btn_add_cb (GtkButton *button _UNUSED_, GtkTreeView *tree)
 {
+    GtkListStore *store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (tree), "store"));
     GtkTreeIter iter;
     GtkTreePath *path;
     GtkTreeViewColumn *column;
     
-    gtk_list_store_append (cmdline_post_store, &iter);
-    path = gtk_tree_model_get_path (GTK_TREE_MODEL (cmdline_post_store), &iter);
+    gtk_list_store_append (store, &iter);
+    path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
     column = gtk_tree_view_get_column (tree, 0);
     gtk_tree_view_set_cursor (tree, path, column, TRUE);
 }
 
 static void
-btn_postsysupgrade_edit_cb (GtkButton *button _UNUSED_, GtkTreeView *tree)
+btn_edit_cb (GtkButton *button _UNUSED_, GtkTreeView *tree)
 {
     GtkTreeSelection *selection;
     GtkTreeModel *model;
 }
 
 static void
-btn_postsysupgrade_remove_cb (GtkButton *button _UNUSED_, GtkTreeView *tree)
+btn_remove_cb (GtkButton *button _UNUSED_, GtkTreeView *tree)
 {
     GtkTreeSelection *selection;
     GtkTreeModel *model;
 }
 
 static void
-renderer_edited_cb (GtkCellRendererText *renderer _UNUSED_, gchar *path,
+renderer_edited_cb (GtkCellRendererText *renderer, gchar *path,
                     gchar *text, gpointer data _UNUSED_)
 {
-    GtkTreeModel *model = GTK_TREE_MODEL (cmdline_post_store);
+    GtkListStore *store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (renderer), "store"));
+    GtkTreeModel *model = GTK_TREE_MODEL (store);
     GtkTreeIter iter;
     
     if (gtk_tree_model_get_iter_from_string (model, &iter, path))
         char *s;
         gtk_tree_model_get (model, &iter, 0, &s, -1);
         free (s);
-        gtk_list_store_set (cmdline_post_store, &iter, 0, text, -1);
+        gtk_list_store_set (store, &iter, 0, text, -1);
     }
 }
 
+#define refresh_entry_text(entry, tpl, tpl_item)     do {       \
+        if (!gtk_widget_get_sensitive (entry))                  \
+        {                                                       \
+            s = escape_text (fallback_templates.tpl->tpl_item); \
+            gtk_entry_set_text (GTK_ENTRY (entry), s);          \
+            free (s);                                           \
+        }                                                       \
+    } while (0)
+#define refresh_entry_text_watched_aur(entry, tpl_item) do {    \
+        if (!gtk_widget_get_sensitive (entry))                  \
+        {                                                       \
+            s = NULL;                                           \
+            if (NULL != fallback_templates.tpl_aur->tpl_item)   \
+            {                                                   \
+                s = fallback_templates.tpl_aur->tpl_item;       \
+            }                                                   \
+            if (NULL == s)                                      \
+            {                                                   \
+                s = fallback_templates.tpl_upgrades->tpl_item;  \
+            }                                                   \
+            s = escape_text (s);                                \
+            gtk_entry_set_text (GTK_ENTRY (entry), s);          \
+            free (s);                                           \
+        }                                                       \
+    } while (0)
+static void
+refresh_tpl_widgets (void)
+{
+    char *s;
+    
+    refresh_entry_text (news_title_entry, tpl_upgrades, title);
+    refresh_entry_text (news_package_entry, tpl_upgrades, package);
+    refresh_entry_text (news_sep_entry, tpl_upgrades, sep);
+    
+    refresh_entry_text (watched_title_entry, tpl_upgrades, title);
+    refresh_entry_text (watched_package_entry, tpl_upgrades, package);
+    refresh_entry_text (watched_sep_entry, tpl_upgrades, sep);
+    
+    refresh_entry_text (aur_title_entry, tpl_upgrades, title);
+    refresh_entry_text (aur_package_entry, tpl_upgrades, package);
+    refresh_entry_text (aur_sep_entry, tpl_upgrades, sep);
+    
+    /* watched-aur: falls back to aur first, then upgrades */
+    refresh_entry_text_watched_aur (watched_aur_title_entry, title);
+    refresh_entry_text_watched_aur (watched_aur_package_entry, package);
+    refresh_entry_text_watched_aur (watched_aur_sep_entry, sep);
+}
+#undef refresh_entry_text_watched_aur
+#undef refresh_entry_text
+
+static void
+tpl_toggled_cb (GtkToggleButton *button, GtkWidget *entry)
+{
+    gboolean is_active = gtk_toggle_button_get_active (button);
+    char **tpl_item = g_object_get_data (G_OBJECT (entry), "tpl-item");
+    char *old, **fallback, *s;
+    
+    /* switch entry's sensitive state */
+    gtk_widget_set_sensitive (entry, is_active);
+    
+    if (is_active)
+    {
+        /* try to restore old-value if there's one */
+        old = g_object_get_data (G_OBJECT (entry), "old-value");
+        if (NULL != old)
+        {
+            s = old;
+        }
+        else
+        {
+            s = (char *) "";
+        }
+        gtk_entry_set_text (GTK_ENTRY (entry), s);
+        
+        /* if this entry has a tpl-item we need to update it as well */
+        if (NULL != tpl_item)
+        {
+            free (*tpl_item);
+            *tpl_item = strdup (s);
+        }
+    }
+    else
+    {
+        /* store old value */
+        old = (char *) gtk_entry_get_text (GTK_ENTRY (entry));
+        g_object_set_data_full (G_OBJECT (entry), "old-value",
+            strdup (old), (GDestroyNotify) free);
+        /* now, replace the text with whatever we fall back on.
+         * watched-aur falls back to aur first, then, as others, to upgrades */
+        check_t type = (check_t) g_object_get_data (G_OBJECT (entry), "type");
+        fallback = NULL;
+        if (type & CHECK_WATCHED_AUR)
+        {
+            fallback = g_object_get_data (G_OBJECT (entry), "fallback-aur");
+        }
+        if (NULL == fallback || NULL == *fallback)
+        {
+            fallback = g_object_get_data (G_OBJECT (entry), "fallback");
+        }
+        s = escape_text (*fallback);
+        gtk_entry_set_text (GTK_ENTRY (entry), s);
+        free (s);
+        
+        /* if this entry has a tpl-item we need to reset it as well */
+        if (NULL != tpl_item)
+        {
+            free (*tpl_item);
+            *tpl_item = NULL;
+        }
+    }
+    /* if this entry has a tpl-item, we need te refresh every unsensitive
+     * widget (which might be falling back to it) */
+    if (NULL != tpl_item)
+    {
+        refresh_tpl_widgets ();
+    }
+}
+
+static gboolean
+focus_out_cb (GtkWidget *entry, GdkEvent *event _UNUSED_, gpointer data _UNUSED_)
+{
+    char **tpl_item = (char **) g_object_get_data (G_OBJECT (entry), "tpl-item");
+    if (NULL == tpl_item)
+    {
+        return FALSE;
+    }
+    
+    /* update fallback_templates item value */
+    free (*tpl_item);
+    *tpl_item = strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
+    
+    /* now update every unsensitive widget, so they're up-to-date when user
+     * switches pages */
+    refresh_tpl_widgets ();
+    
+    /* keep processing */
+    return FALSE;
+}
+
+#define add_label(text, tpl_item)    do {                               \
+        if (type & CHECK_UPGRADES)                                      \
+        {                                                               \
+            label = gtk_label_new (text);                               \
+        }                                                               \
+        else                                                            \
+        {                                                               \
+            label = gtk_check_button_new_with_label (text);             \
+            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (label),    \
+                NULL != template->tpl_item);                            \
+        }                                                               \
+        gtk_grid_attach (GTK_GRID (grid), label, 0, top, 1, 1);         \
+        gtk_widget_show (label);                                        \
+    } while (0)
+#define set_entry(entry, tpl_item)   do {                               \
+        g_object_set_data (G_OBJECT (entry), "type",                    \
+            (gpointer) type);                                           \
+        g_object_set_data (G_OBJECT (entry), "fallback",                \
+            (gpointer) &(fallback_templates.tpl_upgrades->tpl_item));   \
+        if (type & CHECK_WATCHED_AUR)                                   \
+        {                                                               \
+            g_object_set_data (G_OBJECT (entry), "fallback-aur",        \
+                (gpointer) &(fallback_templates.tpl_aur->tpl_item));    \
+        }                                                               \
+        if (type & (CHECK_UPGRADES | CHECK_AUR))                        \
+        {                                                               \
+            gpointer ptr;                                               \
+            if (type & CHECK_UPGRADES)                                  \
+            {                                                           \
+                ptr = &(fallback_templates.tpl_upgrades->tpl_item);     \
+            }                                                           \
+            else                                                        \
+            {                                                           \
+                ptr = &(fallback_templates.tpl_aur->tpl_item);          \
+            }                                                           \
+            g_object_set_data (G_OBJECT (entry), "tpl-item", ptr);      \
+            gtk_widget_add_events (entry, GDK_FOCUS_CHANGE_MASK);       \
+            g_signal_connect (G_OBJECT (entry), "focus-out-event",      \
+                G_CALLBACK (focus_out_cb), NULL);                       \
+        }                                                               \
+        if (NULL != template->tpl_item)                                 \
+        {                                                               \
+            s = template->tpl_item;                                     \
+        }                                                               \
+        else                                                            \
+        {                                                               \
+            gtk_widget_set_sensitive (entry, FALSE);                    \
+            s = NULL;                                                   \
+            if (type & CHECK_WATCHED_AUR                                \
+                && NULL != config->tpl_aur->tpl_item)                   \
+            {                                                           \
+                s = config->tpl_aur->tpl_item;                          \
+            }                                                           \
+            if (s == NULL)                                              \
+            {                                                           \
+                s = config->tpl_upgrades->tpl_item;                     \
+            }                                                           \
+        }                                                               \
+        s = escape_text (s);                                            \
+        gtk_entry_set_text (GTK_ENTRY (entry), s);                      \
+        free (s);                                                       \
+    } while (0)
+#define connect_signal(entry)   do {                        \
+        if (!(type & CHECK_UPGRADES))                       \
+        {                                                   \
+            g_signal_connect (G_OBJECT (label), "toggled",  \
+                              G_CALLBACK (tpl_toggled_cb),  \
+                              (gpointer) entry);            \
+        }                                                   \
+    } while (0)
+#define add_to_grid(entry)   do {                               \
+        gtk_grid_attach (GTK_GRID (grid), entry, 1, top, 3, 1); \
+        gtk_widget_show (entry);                                \
+    } while (0)
+static void
+add_template (GtkWidget    *grid,
+              int           top,
+              GtkWidget   **title_entry,
+              GtkWidget   **package_entry,
+              GtkWidget   **sep_entry,
+              templates_t  *template,
+              check_t       type)
+{
+    char *s, *tooltip;
+    GtkWidget *label;
+    
+    /* notification template */
+    label = gtk_label_new (NULL);
+    gtk_widget_set_size_request (label, 420, -1);
+    gtk_label_set_markup (GTK_LABEL (label), "<b>Notification template</b>");
+    gtk_widget_set_margin_top (label, 15);
+    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 4, 1);
+    gtk_widget_show (label);
+    
+    /* Title */
+    ++top;
+    add_label ("Title :", title);
+    
+    *title_entry = gtk_entry_new ();
+    /* set tooltip */
+    tooltip = g_strconcat (
+        "The following variables are available :"
+        "\n- <b>$NB</b> : number of packages/news items",
+        (type & (CHECK_UPGRADES | CHECK_WATCHED)) ?
+            "\n- <b>$DL</b> : total download size"
+            "\n- <b>$INS</b> : total installed size"
+            "\n- <b>$NET</b> : total net (post-install difference) size"
+            : NULL,
+        NULL);
+    gtk_widget_set_tooltip_markup (*title_entry, tooltip);
+    g_free (tooltip);
+    /* set value if any, else unsensitive */
+    set_entry (*title_entry, title);
+    /* if it's a check-button, connect to toggled (now that we have the related entry) */
+    connect_signal (*title_entry);
+    /* add to grid */
+    add_to_grid (*title_entry);
+    
+    
+    /* Package */
+    ++top;
+    add_label ((type & CHECK_NEWS) ? "News item :" : "Package :", package);
+    
+    *package_entry = gtk_entry_new ();
+    /* set tooltip */
+    tooltip = g_strconcat (
+        "The following variables are available :",
+        (type & CHECK_NEWS) ? "\n- <b>$NEWS</b> : the news title" :
+        "\n- <b>$PKG</b> : package name"
+        "\n- <b>$OLD</b> : old/current version number"
+        "\n- <b>$NEW</b> : new version number",
+        (type & (CHECK_UPGRADES | CHECK_WATCHED)) ?
+            "\n- <b>$DL</b> : download size"
+            "\n- <b>$INS</b> : installed size"
+            "\n- <b>$NET</b> : net (post-install difference) size"
+            : NULL,
+        NULL);
+    gtk_widget_set_tooltip_markup (*package_entry, tooltip);
+    g_free (tooltip);
+    /* set value if any, else unsensitive */
+    set_entry (*package_entry, package);
+    /* if it's a check-button, connect to toggled (now that we have the related entry) */
+    connect_signal (*package_entry);
+    /* add to grid */
+    add_to_grid (*package_entry);
+    
+    
+    /* Separator */
+    ++top;
+    add_label ("Separator :", sep);
+    
+    *sep_entry = gtk_entry_new ();
+    /* set tooltip */
+    gtk_widget_set_tooltip_text (*sep_entry, "No variables available.");
+    /* set value if any, else unsensitive */
+    set_entry (*sep_entry, sep);
+    /* if it's a check-button, connect to toggled (now that we have the related entry) */
+    connect_signal (*sep_entry);
+    /* add to grid */
+    add_to_grid (*sep_entry);
+}
+#undef add_to_grid
+#undef connect_signal
+#undef set_entry
+#undef add_label
+
+static void
+btn_manage_watched_cb (GtkButton *button _UNUSED_, gboolean is_aur)
+{
+    watched_manage (is_aur);
+}
+
+#define add_to_conf(...)    do {                                            \
+        need = snprintf (tmp, 1024, __VA_ARGS__);                           \
+        if (len + need >= alloc)                                            \
+        {                                                                   \
+            alloc += need + 1024;                                           \
+            conf = realloc (conf, (size_t) (alloc + 1) * sizeof (*conf));   \
+        }                                                                   \
+        if (need < 1024)                                                    \
+        {                                                                   \
+            strcat (conf, tmp);                                             \
+        }                                                                   \
+        else                                                                \
+        {                                                                   \
+            sprintf (conf + len, __VA_ARGS__);                              \
+        }                                                                   \
+        len += need;                                                        \
+    } while (0)
+#define error_on_page(page, errmsg)    do {                             \
+        gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page);  \
+        show_error ("Unable to apply/save preferences", errmsg,         \
+                    GTK_WINDOW (window));                               \
+        goto clean_on_error;                                            \
+    } while (0)
+#define tpl_field(tpl_name, tpl_key, name, key, entry, field, page)         \
+    do {                                                                    \
+        if (gtk_widget_get_sensitive (entry))                               \
+        {                                                                   \
+            s = (char *) gtk_entry_get_text (GTK_ENTRY (entry));            \
+            if (*s == '\0')                                                 \
+            {                                                               \
+                error_on_page (page, "Template missing field " field ".");  \
+            }                                                               \
+            if (!has_tpl)                                                   \
+            {                                                               \
+                add_to_conf ("[template-" name "]\n");                      \
+            }                                                               \
+            add_to_conf (key " = \"%s\"\n", s);                             \
+            new_config.tpl_name->tpl_key = strdup (s);                      \
+            has_tpl = TRUE;                                                 \
+        }                                                                   \
+    } while (0)
+#define free_tpl(tpl)  do {         \
+        if (tpl->title)             \
+        {                           \
+            free (tpl->title);      \
+        }                           \
+        if (tpl->package)           \
+        {                           \
+            free (tpl->package);    \
+        }                           \
+        if (tpl->sep)               \
+        {                           \
+            free (tpl->sep);        \
+        }                           \
+        free (tpl);                 \
+    } while (0)
+static void
+btn_save_cb (GtkButton *button _UNUSED_, gpointer data _UNUSED_)
+{
+    gchar *conf, tmp[1024];
+    int alloc = 1024, len = 0, need;
+    gchar *s;
+    gint nb, begin_hour, begin_minute, end_hour, end_minute;
+    check_t type;
+    gboolean has_tpl;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    config_t new_config;
+    
+    /* init the new kalu.conf we'll be writing */
+    conf = calloc ((size_t) alloc + 1, sizeof (*conf));
+    add_to_conf ("[options]\n");
+    /* also init the new config_t: copy the actual one */
+    memcpy (&new_config, config, sizeof (config_t));
+    /* and set to NULL all that matters (strings/lists/templates we'll re-set) */
+    new_config.pacmanconf       = NULL;
+    new_config.cmdline          = NULL;
+    new_config.cmdline_aur      = NULL;
+    new_config.cmdline_post     = NULL;
+    new_config.tpl_upgrades     = calloc (1, sizeof (templates_t));
+    new_config.tpl_watched      = calloc (1, sizeof (templates_t));
+    new_config.tpl_aur          = calloc (1, sizeof (templates_t));
+    new_config.tpl_watched_aur  = calloc (1, sizeof (templates_t));
+    new_config.tpl_news         = calloc (1, sizeof (templates_t));
+    new_config.aur_ignore       = NULL;
+    
+    /* General */
+    s = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (filechooser));
+    if (NULL == s)
+    {
+        error_on_page (0, "You need to select the configuration file to use for libalpm (pacman.conf)");
+    }
+    add_to_conf ("PacmanConf = %s\n", s);
+    new_config.pacmanconf = strdup (s);
+    
+    s = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_interval));
+    if (*s == '\0')
+    {
+        g_free (s);
+        error_on_page (0, "You need to specify an interval.");
+    }
+    nb = atoi (s);
+    if (nb <= 0)
+    {
+        g_free (s);
+        error_on_page (0, "Invalid value for the auto-check interval.");
+        return;
+    }
+    add_to_conf ("Interval = %s\n", s);
+    g_free (s);
+    new_config.interval = nb * 60; /* we store seconds, not minutes */
+    
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_skip)))
+    {
+        begin_hour = gtk_spin_button_get_value_as_int (
+            GTK_SPIN_BUTTON (spin_begin_hour));
+        begin_minute = gtk_spin_button_get_value_as_int (
+            GTK_SPIN_BUTTON (spin_begin_minute));
+        end_hour = gtk_spin_button_get_value_as_int (
+            GTK_SPIN_BUTTON (spin_end_hour));
+        end_minute = gtk_spin_button_get_value_as_int (
+            GTK_SPIN_BUTTON (spin_end_minute));
+        
+        if (begin_hour < 0 || begin_hour > 23
+            || begin_minute < 0 || begin_minute > 59
+            || end_hour < 0 || end_hour > 23
+            || end_minute < 0 || end_minute > 59)
+        {
+            error_on_page (0, "Invalid value for the auto-check interval.");
+        }
+        
+        add_to_conf ("SkipPeriod = %02d:%02d-%02d:%02d\n",
+                     begin_hour, begin_minute, end_hour, end_minute);
+        new_config.has_skip = TRUE;
+        new_config.skip_begin_hour = begin_hour;
+        new_config.skip_begin_minute = begin_minute;
+        new_config.skip_end_hour = end_hour;
+        new_config.skip_end_minute = end_minute;
+    }
+    else
+    {
+        new_config.has_skip = FALSE;
+    }
+    
+    type = 0;
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_news)))
+    {
+        type |= CHECK_NEWS;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_upgrades)))
+    {
+        type |= CHECK_UPGRADES;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_watched)))
+    {
+        type |= CHECK_WATCHED;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_aur)))
+    {
+        type |= CHECK_AUR;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_watched_aur)))
+    {
+        type |= CHECK_WATCHED_AUR;
+    }
+    if (type == 0)
+    {
+        error_on_page (0, "Nothing selected for automatic checks.");
+    }
+    add_to_conf ("AutoChecks =%s%s%s%s%s\n",
+                 (type & CHECK_NEWS)        ? " NEWS"        : "",
+                 (type & CHECK_UPGRADES)    ? " UPGRADES"    : "",
+                 (type & CHECK_WATCHED)     ? " WATCHED"     : "",
+                 (type & CHECK_AUR)         ? " AUR"         : "",
+                 (type & CHECK_WATCHED_AUR) ? " WATCHED_AUR" : ""
+                 );
+    new_config.checks_auto = type;
+    
+    type = 0;
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (manual_news)))
+    {
+        type |= CHECK_NEWS;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (manual_upgrades)))
+    {
+        type |= CHECK_UPGRADES;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (manual_watched)))
+    {
+        type |= CHECK_WATCHED;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (manual_aur)))
+    {
+        type |= CHECK_AUR;
+    }
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (manual_watched_aur)))
+    {
+        type |= CHECK_WATCHED_AUR;
+    }
+    if (type == 0)
+    {
+        error_on_page (0, "Nothing selected for manual checks.");
+    }
+    add_to_conf ("ManualChecks =%s%s%s%s%s\n",
+                 (type & CHECK_NEWS)        ? " NEWS"        : "",
+                 (type & CHECK_UPGRADES)    ? " UPGRADES"    : "",
+                 (type & CHECK_WATCHED)     ? " WATCHED"     : "",
+                 (type & CHECK_AUR)         ? " AUR"         : "",
+                 (type & CHECK_WATCHED_AUR) ? " WATCHED_AUR" : ""
+                 );
+    new_config.checks_manual = type;
+    
+    /* Upgrades */
+    s = (char *) gtk_entry_get_text (GTK_ENTRY (cmdline_entry));
+    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_upg_action)))
+    {
+        if (gtk_combo_box_get_active (GTK_COMBO_BOX (upg_action_combo)) == 0)
+        {
+            add_to_conf ("UpgradeAction = KALU\n");
+            new_config.action = UPGRADE_ACTION_KALU;
+        }
+        else
+        {
+            add_to_conf ("UpgradeAction = CMDLINE\n");
+            new_config.action = UPGRADE_ACTION_CMDLINE;
+            if (s == NULL || *s == '\0')
+            {
+                error_on_page (2, "You need to specify the command-line.");
+            }
+        }
+    }
+    else
+    {
+        add_to_conf ("UpgradeAction = NONE\n");
+        new_config.action = UPGRADE_NO_ACTION;
+    }
+    if (s != NULL && *s != '\0')
+    {
+        add_to_conf ("CmdLine = %s\n", s);
+        new_config.cmdline = strdup (s);
+    }
+    
+    model = GTK_TREE_MODEL (cmdline_post_store);
+    if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+        do
+        {
+            gtk_tree_model_get (model, &iter, 0, &s, -1);
+            if (s != NULL && *s != '\0')
+            {
+                add_to_conf ("PostSysUpgrade = %s\n", s);
+                new_config.cmdline_post = alpm_list_add (new_config.cmdline_post,
+                    strdup (s));
+            }
+        } while (gtk_tree_model_iter_next (model, &iter));
+    }
+    
+    /* AUR */
+    
+    if (gtk_widget_get_sensitive (aur_cmdline_entry))
+    {
+        s = (char *) gtk_entry_get_text (GTK_ENTRY (aur_cmdline_entry));
+        if (s == NULL || *s == '\0')
+        {
+            error_on_page (4, "You need to specify the command-line.");
+        }
+        add_to_conf ("CmdLineAur = %s\n", s);
+        new_config.cmdline_aur = strdup (s);
+    }
+    
+    model = GTK_TREE_MODEL (aur_ignore_store);
+    if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+        add_to_conf ("AurIgnore =");
+        do
+        {
+            gtk_tree_model_get (model, &iter, 0, &s, -1);
+            if (s != NULL && *s != '\0')
+            {
+                add_to_conf (" %s", s);
+                new_config.aur_ignore = alpm_list_add (new_config.aur_ignore,
+                    strdup (s));
+            }
+        } while (gtk_tree_model_iter_next (model, &iter));
+        add_to_conf ("\n");
+    }
+    
+    /* ** TEMPLATES ** */
+    
+    /* Upgrades */
+    has_tpl = FALSE;
+    tpl_field (tpl_upgrades, title, "upgrades", "Title", upg_title_entry, "Title", 2);
+    tpl_field (tpl_upgrades, package, "upgrades", "Package", upg_package_entry, "Package", 2);
+    tpl_field (tpl_upgrades, sep, "upgrades", "Sep", upg_sep_entry, "Separator", 2);
+    
+    /* Watched */
+    has_tpl = FALSE;
+    tpl_field (tpl_watched, title, "watched", "Title", watched_title_entry, "Title", 3);
+    tpl_field (tpl_watched, package, "watched", "Package", watched_package_entry, "Package", 3);
+    tpl_field (tpl_watched, sep, "watched", "Sep", watched_sep_entry, "Separator", 3);
+    
+    /* AUR */
+    has_tpl = FALSE;
+    tpl_field (tpl_aur, title, "aur", "Title", aur_title_entry, "Title", 4);
+    tpl_field (tpl_aur, package, "aur", "Package", aur_package_entry, "Package", 4);
+    tpl_field (tpl_aur, sep, "aur", "Sep", aur_sep_entry, "Separator", 4);
+    
+    /* Watched AUR */
+    has_tpl = FALSE;
+    tpl_field (tpl_watched_aur, title, "watched-aur", "Title",
+               watched_aur_title_entry, "Title", 5);
+    tpl_field (tpl_watched_aur, package, "watched-aur", "Package",
+               watched_aur_package_entry, "Package", 5);
+    tpl_field (tpl_watched_aur, sep, "watched-aur", "Sep",
+               watched_aur_sep_entry, "Separator", 5);
+    
+    /* News */
+    has_tpl = FALSE;
+    tpl_field (tpl_news, title, "news", "Title", news_title_entry, "Title", 1);
+    tpl_field (tpl_news, package, "news", "Package", news_package_entry, "News item", 1);
+    tpl_field (tpl_news, sep, "news", "Sep", news_sep_entry, "Separator", 1);
+    
+    /* save file */
+    char conffile[MAX_PATH];
+    GError *error = NULL;
+    snprintf (conffile, MAX_PATH - 1, "%s/.config/kalu/kalu.conf", g_get_home_dir ());
+    if (!g_file_set_contents (conffile, conf, -1, &error))
+    {
+        show_error ("Unable to write configuration file", error->message,
+                    GTK_WINDOW (window));
+        g_clear_error (&error);
+        goto clean_on_error;
+    }
+    debug ("preferences saved to %s:\n%s", conffile, conf);
+    g_free (conf);
+    
+    /* free the now unneeded strings/lists */
+    free (config->pacmanconf);
+    free (config->cmdline);
+    free (config->cmdline_aur);
+    FREELIST (config->cmdline_post);
+    free_tpl (config->tpl_upgrades);
+    free_tpl (config->tpl_watched);
+    free_tpl (config->tpl_aur);
+    free_tpl (config->tpl_watched_aur);
+    free_tpl (config->tpl_news);
+    FREELIST (config->aur_ignore);
+    /* copy new ones over */
+    memcpy (config, &new_config, sizeof (config_t));
+    
+    /* done */
+    gtk_widget_destroy (window);
+    return;
+    
+clean_on_error:
+    if (new_config.pacmanconf)
+    {
+        free (new_config.pacmanconf);
+    }
+    if (new_config.cmdline)
+    {
+        free (new_config.cmdline);
+    }
+    if (new_config.cmdline_aur)
+    {
+        free (new_config.cmdline_aur);
+    }
+    if (new_config.cmdline_post)
+    {
+        FREELIST (new_config.cmdline_post);
+    }
+    free_tpl (new_config.tpl_upgrades);
+    free_tpl (new_config.tpl_watched);
+    free_tpl (new_config.tpl_aur);
+    free_tpl (new_config.tpl_watched_aur);
+    free_tpl (new_config.tpl_news);
+    if (new_config.aur_ignore)
+    {
+        FREELIST (new_config.aur_ignore);
+    }
+    g_free (conf);
+}
+#undef free_tpl
+#undef tpl_field
+#undef error_on_page
+#undef add_to_conf
+
+static void
+add_list (GtkWidget     *grid,
+          int            top,
+          GtkListStore **store,
+          GtkWidget    **hbox,
+          const char    *column_title,
+          const char    *tooltip_add,
+          const char    *tooltip_edit,
+          const char    *tooltip_remove,
+          alpm_list_t   *list_data)
+{
+    GtkWidget *tree;
+    *store = gtk_list_store_new (1, G_TYPE_STRING);
+    tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (*store));
+    g_object_set_data (G_OBJECT (tree), "store", (gpointer) *store);
+    g_object_unref (*store);
+    
+    /* vbox - sort of a toolbar but vertical */
+    GtkWidget *vbox_tb;
+    GtkWidget *button;
+    GtkWidget *image;
+    *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_grid_attach (GTK_GRID (grid), *hbox, 0, top, 4, 1);
+    gtk_widget_show (*hbox);
+    
+    vbox_tb = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+    gtk_box_pack_start (GTK_BOX (*hbox), vbox_tb, FALSE, FALSE, 0);
+    gtk_widget_show (vbox_tb);
+    
+    /* button Add */
+    image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
+    button = gtk_button_new ();
+    gtk_button_set_image (GTK_BUTTON (button), image);
+    gtk_widget_set_tooltip_text (button, tooltip_add);
+    gtk_box_pack_start (GTK_BOX (vbox_tb), button, FALSE, FALSE, 0);
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (btn_add_cb), (gpointer) tree);
+    gtk_widget_show (button);
+    /* button Edit */
+    image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
+    button = gtk_button_new ();
+    g_object_set_data (G_OBJECT (tree), "btn-edit", (gpointer) button);
+    gtk_button_set_image (GTK_BUTTON (button), image);
+    gtk_widget_set_tooltip_text (button, tooltip_edit);
+    gtk_box_pack_start (GTK_BOX (vbox_tb), button, FALSE, FALSE, 0);
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (btn_edit_cb), (gpointer) tree);
+    gtk_widget_show (button);
+    /* button Remove */
+    image = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);
+    button = gtk_button_new ();
+    g_object_set_data (G_OBJECT (tree), "btn-remove", (gpointer) button);
+    gtk_button_set_image (GTK_BUTTON (button), image);
+    gtk_widget_set_tooltip_text (button, tooltip_remove);
+    gtk_box_pack_start (GTK_BOX (vbox_tb), button, FALSE, FALSE, 0);
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (btn_remove_cb), (gpointer) tree);
+    gtk_widget_show (button);
+    
+    /* switch sensitive of buttons based on selection */
+    GtkTreeSelection *selection;
+    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+    g_signal_connect (selection, "changed",
+                      G_CALLBACK (selection_changed_cb), (gpointer) tree);
+    selection_changed_cb (selection, tree);
+    
+    /* a scrolledwindow for the tree */
+    GtkWidget *scrolled;
+    scrolled = gtk_scrolled_window_new (
+        gtk_tree_view_get_hadjustment (GTK_TREE_VIEW (tree)),
+        gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree)));
+    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+        GTK_SHADOW_OUT);
+    gtk_widget_show (scrolled);
+    gtk_box_pack_start (GTK_BOX (*hbox), scrolled, TRUE, TRUE, 0);
+    
+    /* cell renderer & column(s) */
+    GtkCellRenderer *renderer;
+    GtkTreeViewColumn *column;
+    /* column: Command-line */
+    renderer = gtk_cell_renderer_text_new ();
+    g_object_set_data (G_OBJECT (renderer), "store", (gpointer) *store);
+    g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+    g_signal_connect (G_OBJECT (renderer), "edited",
+                      G_CALLBACK (renderer_edited_cb), NULL);
+    column = gtk_tree_view_column_new_with_attributes (column_title, renderer,
+                                                       "text", 0, NULL);
+    gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
+    
+    /* eo.columns  */
+    
+    /* fill data */
+    GtkTreeIter iter;
+    alpm_list_t *j;
+    for (j = list_data; j; j = alpm_list_next (j))
+    {
+        gtk_list_store_append (*store, &iter);
+        gtk_list_store_set (*store, &iter,
+            0,  strdup (j->data),
+            -1);
+    }
+    
+    gtk_container_add (GTK_CONTAINER (scrolled), tree);
+    gtk_widget_show (tree);
+}
 
 void
 show_prefs (void)
     gtk_widget_show (vbox);
     
     /* notebook */
-    GtkWidget *notebook;
     notebook = gtk_notebook_new ();
     gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0);
     gtk_widget_show (notebook);
     GtkWidget *label;
     GtkWidget *entry;
     GtkWidget *box;
-    char *s;
+    GtkWidget *button;
+    GtkWidget *hbox;
     
     /* [ General ] */
     top = 0;
     
     /*******************************************/
     
+    /* set the copy of those templates to be used for falling back. that is,
+     * they'll be updated whenever they're changed on prefs window, whereaas
+     * config if only updated upon (and if) saving changes */
+    fallback_templates.tpl_upgrades = calloc (1, sizeof (templates_t));
+    fallback_templates.tpl_upgrades->title = strdup (config->tpl_upgrades->title);
+    fallback_templates.tpl_upgrades->package = strdup (config->tpl_upgrades->package);
+    fallback_templates.tpl_upgrades->sep = strdup (config->tpl_upgrades->sep);
+    fallback_templates.tpl_aur = calloc (1, sizeof (templates_t));
+    if (NULL != config->tpl_aur->title)
+    {
+        fallback_templates.tpl_aur->title = strdup (config->tpl_aur->title);
+    }
+    if (NULL != config->tpl_aur->package)
+    {
+        fallback_templates.tpl_aur->package = strdup (config->tpl_aur->package);
+    }
+    if (NULL != config->tpl_aur->sep)
+    {
+        fallback_templates.tpl_aur->sep = strdup (config->tpl_aur->sep);
+    }
+    
+    /*******************************************/
+    
+    /* [ News ] */
+    top = 0;
+    grid = gtk_grid_new ();
+    lbl_page = gtk_label_new ("News");
+    
+    add_template (grid, top,
+                  &news_title_entry,
+                  &news_package_entry,
+                  &news_sep_entry,
+                  config->tpl_news,
+                  CHECK_NEWS);
+    
+    /* add page */
+    gtk_widget_show (grid);
+    gtk_notebook_append_page (GTK_NOTEBOOK (notebook), grid, lbl_page);
+    
+    /*******************************************/
+    
     /* [ Upgrades ] */
     top = 0;
     grid = gtk_grid_new ();
     
     /* UpgradeAction */
     button_upg_action = gtk_check_button_new_with_label ("Show a button \"Upgrade system\" on notifications");
-    gtk_widget_set_size_request (button_upg_action, 420, -1);
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_upg_action),
         config->action != UPGRADE_NO_ACTION);
     gtk_grid_attach (GTK_GRID (grid), button_upg_action, 0, top, 4, 1);
     
     cmdline_entry = gtk_entry_new ();
     gtk_widget_set_sensitive (cmdline_entry, config->action != UPGRADE_NO_ACTION);
-    gtk_entry_set_text (GTK_ENTRY (cmdline_entry), config->cmdline);
+    if (config->cmdline)
+    {
+        gtk_entry_set_text (GTK_ENTRY (cmdline_entry), config->cmdline);
+    }
     gtk_grid_attach (GTK_GRID (grid), cmdline_entry, 2, top, 2, 1);
     
     if (config->action == UPGRADE_ACTION_CMDLINE)
     
     ++top;
     /* PostSysUpgrade */
-    GtkWidget *tree;
-    cmdline_post_store = gtk_list_store_new (1, G_TYPE_STRING);
-    tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cmdline_post_store));
-    gtk_widget_set_sensitive (tree, config->action != UPGRADE_NO_ACTION);
-    g_object_unref (cmdline_post_store);
-    
-    /* vbox - sort of a toolbar but vertical */
-    GtkWidget *vbox_tb;
-    GtkWidget *button;
-    GtkWidget *image;
-    cmdline_post_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
-    gtk_grid_attach (GTK_GRID (grid), cmdline_post_hbox, 0, top, 4, 1);
-    if (config->action != UPGRADE_ACTION_CMDLINE)
-    {
-        gtk_widget_show (cmdline_post_hbox);
-    }
-    
-    vbox_tb = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
-    gtk_box_pack_start (GTK_BOX (cmdline_post_hbox), vbox_tb, FALSE, FALSE, 0);
-    gtk_widget_show (vbox_tb);
-    
-    static GtkWidget *buttons[2];
-    
-    /* button Add */
-    image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
-    button = gtk_button_new ();
-    gtk_button_set_image (GTK_BUTTON (button), image);
-    gtk_widget_set_tooltip_text (button, "Add a new command-line");
-    gtk_box_pack_start (GTK_BOX (vbox_tb), button, FALSE, FALSE, 0);
-    g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (btn_postsysupgrade_add_cb), (gpointer) tree);
-    gtk_widget_show (button);
-    /* button Edit */
-    image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
-    button = gtk_button_new ();
-    buttons[0] = button;
-    gtk_button_set_image (GTK_BUTTON (button), image);
-    gtk_widget_set_tooltip_text (button, "Edit selected command-line");
-    gtk_box_pack_start (GTK_BOX (vbox_tb), button, FALSE, FALSE, 0);
-    g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (btn_postsysupgrade_edit_cb), (gpointer) tree);
-    gtk_widget_show (button);
-    /* button Remove */
-    image = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);
-    button = gtk_button_new ();
-    buttons[1] = button;
-    gtk_button_set_image (GTK_BUTTON (button), image);
-    gtk_widget_set_tooltip_text (button, "Remove selected command-line");
-    gtk_box_pack_start (GTK_BOX (vbox_tb), button, FALSE, FALSE, 0);
-    g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (btn_postsysupgrade_remove_cb), (gpointer) tree);
-    gtk_widget_show (button);
-    
-    /* switch sensitive of buttons based on selection */
-    GtkTreeSelection *selection;
-    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
-    g_signal_connect (selection, "changed",
-                      G_CALLBACK (selection_changed_cb), (gpointer) buttons);
-    selection_changed_cb (selection, buttons);
-    
-    /* a scrolledwindow for the tree */
-    GtkWidget *scrolled;
-    scrolled = gtk_scrolled_window_new (
-        gtk_tree_view_get_hadjustment (GTK_TREE_VIEW (tree)),
-        gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree)));
-    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
-        GTK_SHADOW_OUT);
-    gtk_widget_show (scrolled);
-    gtk_box_pack_start (GTK_BOX (cmdline_post_hbox), scrolled, TRUE, TRUE, 0);
-    
-    /* cell renderer & column(s) */
-    GtkCellRenderer *renderer;
-    GtkTreeViewColumn *column;
-    /* column: Command-line */
-    renderer = gtk_cell_renderer_text_new ();
-    g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
-    g_signal_connect (G_OBJECT (renderer), "edited",
-                      G_CALLBACK (renderer_edited_cb), NULL);
-    column = gtk_tree_view_column_new_with_attributes (
-        "After completing a system upgrade, ask whether to start the following :",
-       renderer,
-       "text", 0,
-       NULL);
-    gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
-    
-    /* eo.columns  */
+    add_list (grid, top, &cmdline_post_store, &cmdline_post_hbox,
+              "After completing a system upgrade, ask whether to start the following :",
+              "Add a new command-line",
+              "Edit selected command-line",
+              "Remove selected command-line",
+              config->cmdline_post);
     
     /* doing this now otherwise it's triggered with non-yet-existing widgets to hide/show */
     g_signal_connect (G_OBJECT (upg_action_combo), "changed",
                       G_CALLBACK (upg_action_changed_cb), NULL);
     
-    /* fill data */
-    GtkTreeIter iter;
-    alpm_list_t *j;
-    for (j = config->cmdline_post; j; j = alpm_list_next (j))
-    {
-        gtk_list_store_append (cmdline_post_store, &iter);
-        gtk_list_store_set (cmdline_post_store, &iter,
-            0,  strdup (j->data),
-            -1);
-    }
-    
-    gtk_container_add (GTK_CONTAINER (scrolled), tree);
-    gtk_widget_show (tree);
-    
     ++top;
-    /* template */
-    label = gtk_label_new (NULL);
-    gtk_label_set_markup (GTK_LABEL (label), "<b>Notification template</b>");
-    gtk_widget_set_margin_top (label, 15);
-    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 4, 1);
-    gtk_widget_show (label);
-    
-    ++top;
-    label = gtk_label_new ("Title :");
-    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 1, 1);
-    gtk_widget_show (label);
-    
-    upg_title_entry = gtk_entry_new ();
-    gtk_widget_set_tooltip_markup (upg_title_entry,
-        "The following variables are available :\n"
-        "- <b>$NB</b> : number of packages/news items\n"
-        "- <b>$DL</b> : total download size\n"
-        "- <b>$INS</b> : total installed size\n"
-        "- <b>$NET</b> : total net (post-install difference) size\n"
-        );
-    s = escape_text (config->tpl_upgrades->title);
-    gtk_entry_set_text (GTK_ENTRY (upg_title_entry), s);
-    free (s);
-    gtk_grid_attach (GTK_GRID (grid), upg_title_entry, 1, top, 3, 1);
-    gtk_widget_show (upg_title_entry);
-    
-    ++top;
-    label = gtk_label_new ("Package :");
-    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 1, 1);
-    gtk_widget_show (label);
-    
-    upg_package_entry = gtk_entry_new ();
-    gtk_widget_set_tooltip_markup (upg_package_entry,
-        "The following variables are available :\n"
-        "- <b>$PKG</b> : package name\n"
-        "- <b>$OLD</b> : old/current version number\n"
-        "- <b>$NEW</b> : new version number\n"
-        "- <b>$DL</b> : download size\n"
-        "- <b>$INS</b> : installed size\n"
-        "- <b>$NET</b> : net (post-install difference) size\n"
-        );
-    s = escape_text (config->tpl_upgrades->package);
-    gtk_entry_set_text (GTK_ENTRY (upg_package_entry), s);
-    free (s);
-    gtk_grid_attach (GTK_GRID (grid), upg_package_entry, 1, top, 3, 1);
-    gtk_widget_show (upg_package_entry);
-    
-    ++top;
-    label = gtk_label_new ("Separator :");
-    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 1, 1);
-    gtk_widget_show (label);
-    
-    upg_sep_entry = gtk_entry_new ();
-    gtk_widget_set_tooltip_text (upg_sep_entry, "No variables available.");
-    s = escape_text (config->tpl_upgrades->sep);
-    gtk_entry_set_text (GTK_ENTRY (upg_sep_entry), s);
-    free (s);
-    gtk_grid_attach (GTK_GRID (grid), upg_sep_entry, 1, top, 3, 1);
-    gtk_widget_show (upg_sep_entry);
-    
+    add_template (grid, top,
+                  &upg_title_entry,
+                  &upg_package_entry,
+                  &upg_sep_entry,
+                  config->tpl_upgrades,
+                  CHECK_UPGRADES);
     
     /* add page */
     gtk_widget_show (grid);
     gtk_notebook_append_page (GTK_NOTEBOOK (notebook), grid, lbl_page);
     
+    /*******************************************/
     
+    /* [ Watched ] */
+    top = 0;
+    grid = gtk_grid_new ();
+    lbl_page = gtk_label_new ("Watched");
     
+    button = gtk_button_new_with_label ("Manage watched packages...");
+    gtk_widget_set_margin_top (button, 10);
+    gtk_grid_attach (GTK_GRID (grid), button, 1, top, 2, 1);
+    gtk_widget_show (button);
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (btn_manage_watched_cb), GINT_TO_POINTER (FALSE));
     
+    ++top;
+    add_template (grid, top,
+                  &watched_title_entry,
+                  &watched_package_entry,
+                  &watched_sep_entry,
+                  config->tpl_watched,
+                  CHECK_WATCHED);
     
+    /* add page */
+    gtk_widget_show (grid);
+    gtk_notebook_append_page (GTK_NOTEBOOK (notebook), grid, lbl_page);
     
+    /*******************************************/
+    
+    /* [ AUR ] */
+    top = 0;
+    grid = gtk_grid_new ();
+    lbl_page = gtk_label_new ("AUR");
+    
+    /* CmdLine */
+    button = gtk_check_button_new_with_label ("Show a button \"Update AUR packages\" on notifications");
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+        config->cmdline_aur != NULL);
+    gtk_grid_attach (GTK_GRID (grid), button, 0, top, 4, 1);
+    gtk_widget_show (button);
+    g_signal_connect (G_OBJECT (button), "toggled",
+                      G_CALLBACK (aur_action_toggled_cb), NULL);
+    
+    ++top;
+    label = gtk_label_new ("When clicking the button, run the following :");
+    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 2, 1);
+    gtk_widget_show (label);
+    
+    aur_cmdline_entry = gtk_entry_new ();
+    if (config->cmdline_aur != NULL)
+    {
+        gtk_entry_set_text (GTK_ENTRY (aur_cmdline_entry), config->cmdline_aur);
+    }
+    else
+    {
+        gtk_widget_set_sensitive (aur_cmdline_entry, FALSE);
+    }
+    gtk_grid_attach (GTK_GRID (grid), aur_cmdline_entry, 2, top, 2, 1);
+    gtk_widget_show (aur_cmdline_entry);
+    
+    ++top;
+    /* AurIgnore */
+    label = gtk_label_new ("Do not check the AUR for the following packages :");
+    gtk_widget_set_margin_top (label, 10);
+    gtk_grid_attach (GTK_GRID (grid), label, 0, top, 4, 1);
+    gtk_widget_show (label);
+    
+    ++top;
+    add_list (grid, top, &aur_ignore_store, &hbox,
+              "Package name",
+              "Add a new package",
+              "Edit selected package",
+              "Remove selected package",
+              config->aur_ignore);
+    
+    /* template */
+    ++top;
+    add_template (grid, top,
+                  &aur_title_entry,
+                  &aur_package_entry,
+                  &aur_sep_entry,
+                  config->tpl_aur,
+                  CHECK_AUR);
+    
+    /* add page */
+    gtk_widget_show (grid);
+    gtk_notebook_append_page (GTK_NOTEBOOK (notebook), grid, lbl_page);
+    
+    /*******************************************/
+    
+    /* [ Watched AUR ] */
+    top = 0;
+    grid = gtk_grid_new ();
+    lbl_page = gtk_label_new ("Watched AUR");
+    
+    button = gtk_button_new_with_label ("Manage watched AUR packages...");
+    gtk_widget_set_margin_top (button, 10);
+    gtk_grid_attach (GTK_GRID (grid), button, 1, top, 2, 1);
+    gtk_widget_show (button);
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (btn_manage_watched_cb), GINT_TO_POINTER (TRUE));
+    
+    ++top;
+    add_template (grid, top,
+                  &watched_aur_title_entry,
+                  &watched_aur_package_entry,
+                  &watched_aur_sep_entry,
+                  config->tpl_watched_aur,
+                  CHECK_WATCHED_AUR);
+    
+    /* add page */
+    gtk_widget_show (grid);
+    gtk_notebook_append_page (GTK_NOTEBOOK (notebook), grid, lbl_page);
+    
+    /*******************************************/
+    
+    /* buttons */
+    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+    gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+    gtk_widget_show (hbox);
+    
+    GtkWidget *image;
+    button = gtk_button_new_with_label ("Save preferences");
+    image = gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
+    gtk_button_set_image (GTK_BUTTON (button), image);
+    gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 4);
+    gtk_widget_set_tooltip_text (button, "Apply and save preferences");
+    g_signal_connect (G_OBJECT (button), "clicked",
+                      G_CALLBACK (btn_save_cb), NULL);
+    gtk_widget_show (button);
+    
+    button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+    gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+    gtk_widget_show (button);
+    g_signal_connect_swapped (G_OBJECT (button), "clicked",
+                              G_CALLBACK (gtk_widget_destroy), (gpointer) window);
     
     /* signals */
     g_signal_connect (G_OBJECT (window), "destroy",
         GtkTreeModel *model;
         GtkTreeIter iter;
         watched_package_t *w_pkg, w_pkg_tmp;
-        gboolean is_aur = (gboolean) g_object_get_data (G_OBJECT (dialog), "is-aur");
+        gboolean is_aur = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), "is-aur"));
         
         if (is_aur)
         {
                                   "Yes, import changes.", NULL,
                                   "No, keep the list as is.", NULL,
                                   window_manage);
-            g_object_set_data (G_OBJECT (dialog), "is-aur", (gpointer) is_aur);
+            g_object_set_data (G_OBJECT (dialog), "is-aur", GINT_TO_POINTER (is_aur));
             g_signal_connect (G_OBJECT (dialog), "response",
                               G_CALLBACK (monitor_response_cb), (gpointer) updates);
             gtk_widget_show (dialog);
 static void
 btn_reload_cb (GtkToolButton *tb_item, int from_disk)
 {
-    gboolean is_aur = (gboolean) g_object_get_data (G_OBJECT (tb_item), "is-aur");
+    gboolean is_aur = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tb_item), "is-aur"));
     if (from_disk)
     {
         GError *error = NULL;
 static void
 renderer_edited_cb (GtkCellRendererText *renderer, gchar *path, gchar *text, int col)
 {
-    gboolean is_aur = (gboolean) g_object_get_data (G_OBJECT (renderer), "is-aur");
+    gboolean is_aur = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), "is-aur"));
     GtkTreeModel *model;
     GtkTreeIter iter;
     
         tb_item = gtk_tool_button_new_from_stock (GTK_STOCK_ADD);
         gtk_widget_set_tooltip_text (GTK_WIDGET (tb_item), "Add a new package");
         g_signal_connect (G_OBJECT (tb_item), "clicked",
-                          G_CALLBACK (btn_add_cb), (gpointer) is_aur);
+                          G_CALLBACK (btn_add_cb), GINT_TO_POINTER (is_aur));
         gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tb_item, -1);
         gtk_widget_show (GTK_WIDGET (tb_item));
         /* button: Edit */
         gtk_widget_set_tooltip_text (GTK_WIDGET (tb_item), "Edit selected package");
         gtk_widget_set_sensitive (GTK_WIDGET (tb_item), FALSE);
         g_signal_connect (G_OBJECT (tb_item), "clicked",
-                          G_CALLBACK (btn_edit_cb), (gpointer) is_aur);
+                          G_CALLBACK (btn_edit_cb), GINT_TO_POINTER (is_aur));
         gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tb_item, -1);
         gtk_widget_show (GTK_WIDGET (tb_item));
         /* button: Remove */
         gtk_widget_set_tooltip_text (GTK_WIDGET (tb_item), "Remove selected package");
         gtk_widget_set_sensitive (GTK_WIDGET (tb_item), FALSE);
         g_signal_connect (G_OBJECT (tb_item), "clicked",
-                          G_CALLBACK (btn_remove_cb), (gpointer) is_aur);
+                          G_CALLBACK (btn_remove_cb), GINT_TO_POINTER (is_aur));
         gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tb_item, -1);
         gtk_widget_show (GTK_WIDGET (tb_item));
         /* --- */
         gtk_widget_show (GTK_WIDGET (tb_item));
         /* button: Reload from memory */
         tb_item = gtk_tool_button_new_from_stock (GTK_STOCK_UNDO);
-        g_object_set_data (G_OBJECT (tb_item), "is-aur", (gpointer) is_aur);
+        g_object_set_data (G_OBJECT (tb_item), "is-aur", GINT_TO_POINTER (is_aur));
         gtk_widget_set_tooltip_text (GTK_WIDGET (tb_item), "Reload list from memory (undo changes)");
         g_signal_connect (G_OBJECT (tb_item), "clicked",
                           G_CALLBACK (btn_reload_cb), (gpointer) 0);
         gtk_widget_show (GTK_WIDGET (tb_item));
         /* button: Reload from disk */
         tb_item = gtk_tool_button_new_from_stock (GTK_STOCK_REFRESH);
-        g_object_set_data (G_OBJECT (tb_item), "is-aur", (gpointer) is_aur);
+        g_object_set_data (G_OBJECT (tb_item), "is-aur", GINT_TO_POINTER (is_aur));
         gtk_widget_set_tooltip_text (GTK_WIDGET (tb_item), "Reload list from file");
         g_signal_connect (G_OBJECT (tb_item), "clicked",
                           G_CALLBACK (btn_reload_cb), (gpointer) 1);
                                                            NULL);
         g_object_set (renderer, "activatable", TRUE, NULL);
         g_signal_connect (G_OBJECT (renderer), "toggled",
-                          G_CALLBACK (renderer_toggle_cb), (gpointer) is_aur);
+                          G_CALLBACK (renderer_toggle_cb), GINT_TO_POINTER (is_aur));
         gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
     }
     /* column: Package */
     renderer = gtk_cell_renderer_text_new ();
     if (!is_update)
     {
-        g_object_set_data (G_OBJECT (renderer), "is-aur", (gpointer) is_aur);
+        g_object_set_data (G_OBJECT (renderer), "is-aur", GINT_TO_POINTER (is_aur));
         g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
         g_signal_connect (G_OBJECT (renderer), "edited",
                           G_CALLBACK (renderer_edited_cb), (gpointer) WCOL_NAME);
     {
         /* we need another renderer so we know which column gets edited */
         renderer = gtk_cell_renderer_text_new ();
-        g_object_set_data (G_OBJECT (renderer), "is-aur", (gpointer) is_aur);
+        g_object_set_data (G_OBJECT (renderer), "is-aur", GINT_TO_POINTER (is_aur));
         g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
         g_signal_connect (G_OBJECT (renderer), "edited",
                           G_CALLBACK (renderer_edited_cb), (gpointer) WCOL_OLD_VERSION);
         gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 4);
         gtk_widget_set_tooltip_text (button, "Save new version numbers of checked packages");
         g_signal_connect (G_OBJECT (button), "clicked",
-                          G_CALLBACK (btn_mark_cb), (gpointer) is_aur);
+                          G_CALLBACK (btn_mark_cb), GINT_TO_POINTER (is_aur));
         gtk_widget_show (button);
     }
     else
         gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 4);
         gtk_widget_set_tooltip_text (button, "Save list of watched packages");
         g_signal_connect (G_OBJECT (button), "clicked",
-                          G_CALLBACK (btn_save_cb), (gpointer) is_aur);
+                          G_CALLBACK (btn_save_cb), GINT_TO_POINTER (is_aur));
         gtk_widget_show (button);
     }
     /* Close */
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.