[balsa] Improve IMAP folder parent management



commit 30bfcf8103da5c53ef5fab220b23d1009fc147fb
Author: Albrecht Dreß <albrecht dress arcor de>
Date:   Mon Apr 1 11:39:26 2019 -0400

    Improve IMAP folder parent management
    
    Improve IMAP folder parent selection, subscription management
    
    * libbalsa/folder-scanners.[ch]: implement new function
    libbalsa_scanner_imap_tree() for scanning an IMAP server's
    folder structure and subscription state into a GtkTreeStore,
    requires a bunch of helper functions
    * libbalsa/imap-server.[ch]: implement new function
    libbalsa_imap_server_subscriptions() for subscribing to and
    unsubscribing from arrays of folders using a single IMAP
    connection
    * libbalsa/mailbox_imap.[ch]: remove unused function
    libbalsa_mailbox_imap_subscribe() (obsoleted by
    libbalsa_imap_server_subscriptions())
    * src/folder-conf.c: implement new style subscription and parent
    folder selection:
      • imap_apply_subscriptions() collects folders with modified
      subscription states from the GtkTreeView;
      • imap_update_subscriptions() applies changed subscription
      states, called from folder_conf_clicked_ok();
      • on_subscription_toggled() callback for subscription state checkbox;
      • create_imap_folder_dialog() create the dialogue for parent
      folder selection and subscription management;
      • folder_conf_imap_subscriptions() runs the subscription
      management dialogue;
      • fcw_subscribed_toggled() callback for activating the
      subscription management button;
      • folder_conf_imap_node() add button for subscription management;
      • select_parent_folder() find parent of a folder in the GtkTreeModel;
      • on_parent_double_click() callback for double-click folder selection;
      • browse_button_cb() modified, runs the parent selection dialogue;
      • remove obsolete functions browse_button_select_row_cb(),
      browse_button_row_activated(), browse_button_response(),
      folder_selection_func(), browse_button_data_free()
    * src/mailbox-node.c: remove obsolete mailbox tree view entries
    for (un)subscribing
    * src/save-restore.c: add the two new dialogues to the geometry manager

 ChangeLog                  |  39 ++++
 libbalsa/folder-scanners.c | 173 ++++++++++++++++++
 libbalsa/folder-scanners.h |  27 +++
 libbalsa/imap-server.c     |  52 ++++++
 libbalsa/imap-server.h     |   6 +
 libbalsa/mailbox_imap.c    |  23 ---
 libbalsa/mailbox_imap.h    |   3 -
 src/folder-conf.c          | 442 +++++++++++++++++++++++++++++----------------
 src/mailbox-node.c         |  24 ---
 src/save-restore.c         |   2 +
 10 files changed, 590 insertions(+), 201 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 0b9ba141d..47d58bba6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,42 @@
+2019-04-01  Albrecht Dreß  <albrecht dress arcor de>
+
+       Improve IMAP folder parent selection, subscription management
+
+       * libbalsa/folder-scanners.[ch]: implement new function
+       libbalsa_scanner_imap_tree() for scanning an IMAP server's
+       folder structure and subscription state into a GtkTreeStore,
+       requires a bunch of helper functions
+       * libbalsa/imap-server.[ch]: implement new function
+       libbalsa_imap_server_subscriptions() for subscribing to and
+       unsubscribing from arrays of folders using a single IMAP
+       connection
+       * libbalsa/mailbox_imap.[ch]: remove unused function
+       libbalsa_mailbox_imap_subscribe() (obsoleted by
+       libbalsa_imap_server_subscriptions())
+       * src/folder-conf.c: implement new style subscription and parent
+       folder selection:
+         • imap_apply_subscriptions() collects folders with modified
+         subscription states from the GtkTreeView;
+         • imap_update_subscriptions() applies changed subscription
+         states, called from folder_conf_clicked_ok();
+         • on_subscription_toggled() callback for subscription state checkbox;
+         • create_imap_folder_dialog() create the dialogue for parent
+         folder selection and subscription management;
+         • folder_conf_imap_subscriptions() runs the subscription
+         management dialogue;
+         • fcw_subscribed_toggled() callback for activating the
+         subscription management button;
+         • folder_conf_imap_node() add button for subscription management;
+         • select_parent_folder() find parent of a folder in the GtkTreeModel;
+         • on_parent_double_click() callback for double-click folder selection;
+         • browse_button_cb() modified, runs the parent selection dialogue;
+         • remove obsolete functions browse_button_select_row_cb(),
+         browse_button_row_activated(), browse_button_response(),
+         folder_selection_func(), browse_button_data_free()
+       * src/mailbox-node.c: remove obsolete mailbox tree view entries
+       for (un)subscribing
+       * src/save-restore.c: add the two new dialogues to the geometry manager
+
 2019-03-23  Albrecht Dreß  <albrecht dress arcor de>
 
        Do not include a trailing period ('.') when matching an URL.
diff --git a/libbalsa/folder-scanners.c b/libbalsa/folder-scanners.c
index a3e703725..1788ac6c0 100644
--- a/libbalsa/folder-scanners.c
+++ b/libbalsa/folder-scanners.c
@@ -24,9 +24,11 @@
 
 #include <string.h>
 #include <glib/gstdio.h>
+#include <glib/gi18n.h>
 
 #include "libbalsa.h"
 #include "libimap.h"
+#include "server.h"
 #include "imap-handle.h"
 #include "imap-commands.h"
 #include "imap-server.h"
@@ -447,3 +449,174 @@ libbalsa_scanner_imap_dir(gpointer rnode, LibBalsaServer * server,
         g_signal_handler_disconnect(G_OBJECT(handle), lsub_handler_id);
     libbalsa_imap_server_release_handle(LIBBALSA_IMAP_SERVER(server), handle);
 }
+
+
+/* ---------------------------------------------------------------------
+ * IMAP folder tree functions (parent selection, subscription management)
+ * --------------------------------------------------------------------- */
+typedef struct {
+       gchar delim;
+       gboolean subscribed;
+       GHashTable *children;
+} folder_data_t;
+
+typedef struct {
+       GtkTreeStore *store;
+       GtkTreeIter *parent;
+} imap_tree_t;
+
+typedef struct {
+       ImapMboxHandle *handle;
+       gboolean subscriptions;
+} scan_data_t;
+
+static void imap_tree_scan(scan_data_t *scan_data,
+                                                  const gchar *list_path,
+                                                  GHashTable  *folders);
+static void imap_tree_to_store(gchar         *folder,
+                                                          folder_data_t *folder_data,
+                                                          imap_tree_t   *scan_data);
+
+GtkTreeStore *
+libbalsa_scanner_imap_tree(LibBalsaServer  *server,
+                                                  gboolean                     subscriptions,
+                                                  GError         **error)
+{
+       scan_data_t scan_data;
+       imap_tree_t imap_store = { NULL, NULL };
+
+       g_return_val_if_fail(LIBBALSA_IS_IMAP_SERVER(server) && (error != NULL), NULL);
+
+       scan_data.handle = libbalsa_imap_server_get_handle(LIBBALSA_IMAP_SERVER(server), error);
+       if (scan_data.handle != NULL) {
+               GHashTable *folders;
+
+               scan_data.subscriptions = subscriptions;
+
+               /* scan the whole IMAP server tree */
+               folders = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+               imap_tree_scan(&scan_data, "", folders);
+               libbalsa_imap_server_release_handle(LIBBALSA_IMAP_SERVER(server), scan_data.handle);
+
+               /* create the resulting tree store */
+               imap_store.store = gtk_tree_store_new(LB_SCANNER_IMAP_N_COLS,
+                       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, PANGO_TYPE_STYLE);
+               g_hash_table_foreach(folders, (GHFunc) imap_tree_to_store, &imap_store);
+               g_hash_table_unref(folders);
+       } else {
+               if (*error == NULL) {
+                       g_set_error(error, LIBBALSA_SCANNER_ERROR, LIBBALSA_SCANNER_ERROR_IMAP, _("Could not 
connect to “%s”"), server->host);
+               }
+       }
+
+       return imap_store.store;
+}
+
+
+static void
+imap_tree_scan_list_cb(ImapMboxHandle *handle,
+                                          int             delim,
+                                          ImapMboxFlags   flags,
+                                          gchar          *folder,
+                                          GHashTable     *folders)
+{
+       folder_data_t *folder_data;
+
+       folder_data = g_new0(folder_data_t, 1UL);
+       folder_data->delim = delim;
+       if (IMAP_MBOX_HAS_FLAG(flags, IMLIST_HASCHILDREN) != 0U) {
+               folder_data->children = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+       }
+       g_hash_table_insert(folders, g_strdup(folder), folder_data);
+}
+
+
+static void
+imap_tree_scan_lsub_cb(ImapMboxHandle *handle,
+                                          int             delim,
+                                          ImapMboxFlags   flags,
+                                          gchar          *folder,
+                                          GHashTable     *folders)
+{
+       folder_data_t *folder_data;
+
+       folder_data = g_hash_table_lookup(folders, folder);
+       if (folder_data != NULL) {
+               folder_data->subscribed = IMAP_MBOX_HAS_FLAG(flags, IMLIST_NOSELECT) == 0U;
+       } else {
+               g_critical("%s: cannot identify folder %s", __func__, folder);
+       }
+}
+
+
+static void
+imap_tree_scan_children(gchar         *folder_name,
+                                               folder_data_t *folder_data,
+                                               scan_data_t   *scan_data)
+{
+       if (folder_data->children != NULL) {
+               gchar *scan_name = g_strdup_printf("%s%c", folder_name, folder_data->delim);
+
+               imap_tree_scan(scan_data, scan_name, folder_data->children);
+               g_free(scan_name);
+       }
+}
+
+
+static void
+imap_tree_scan(scan_data_t *scan_data,
+                          const gchar *list_path,
+                          GHashTable  *folders)
+{
+       gulong list_handler_id;
+
+       list_handler_id = g_signal_connect(G_OBJECT(scan_data->handle), "list-response", 
G_CALLBACK(imap_tree_scan_list_cb), folders);
+       imap_mbox_list(scan_data->handle, list_path);
+       g_signal_handler_disconnect(G_OBJECT(scan_data->handle), list_handler_id);
+
+       if (scan_data->subscriptions) {
+               list_handler_id =
+                       g_signal_connect(G_OBJECT(scan_data->handle), "lsub-response", 
G_CALLBACK(imap_tree_scan_lsub_cb), folders);
+               imap_mbox_lsub(scan_data->handle, list_path);
+               g_signal_handler_disconnect(G_OBJECT(scan_data->handle), list_handler_id);
+       }
+
+       g_hash_table_foreach(folders, (GHFunc) imap_tree_scan_children, scan_data);
+}
+
+
+static void
+imap_tree_to_store(gchar                *folder,
+                                  folder_data_t *folder_data,
+                                  imap_tree_t   *tree_data)
+{
+       GtkTreeIter iter;
+       gchar *disp_name;
+
+       gtk_tree_store_append(tree_data->store, &iter, tree_data->parent);
+       if (tree_data->parent != NULL) {
+               gchar *parent_path;
+
+               gtk_tree_model_get(GTK_TREE_MODEL(tree_data->store), tree_data->parent, LB_SCANNER_IMAP_PATH, 
&parent_path, -1);
+               disp_name = g_strdup(&folder[strlen(parent_path) + 1UL]);
+               g_free(parent_path);
+       } else {
+               disp_name = g_strdup(folder);
+       }
+       gtk_tree_store_set(tree_data->store, &iter,
+               LB_SCANNER_IMAP_FOLDER, disp_name,
+               LB_SCANNER_IMAP_PATH, folder,
+               LB_SCANNER_IMAP_SUBS_NEW, folder_data->subscribed,
+               LB_SCANNER_IMAP_SUBS_OLD, folder_data->subscribed,
+               LB_SCANNER_IMAP_STYLE, PANGO_STYLE_NORMAL, -1);
+       g_free(disp_name);
+
+       if (folder_data->children != NULL) {
+               imap_tree_t child_data;
+
+               child_data.store = tree_data->store;
+               child_data.parent = &iter;
+               g_hash_table_foreach(folder_data->children, (GHFunc) imap_tree_to_store, &child_data);
+               g_hash_table_unref(folder_data->children);
+       }
+}
diff --git a/libbalsa/folder-scanners.h b/libbalsa/folder-scanners.h
index b52e47635..857b55bfb 100644
--- a/libbalsa/folder-scanners.h
+++ b/libbalsa/folder-scanners.h
@@ -51,4 +51,31 @@ void libbalsa_scanner_imap_dir(gpointer rnode, LibBalsaServer * server,
                                gpointer cb_data, 
                                GError **error);
 
+
+/* Scan the passed IMAP server and return a GtkTreeStore containing all folders and (if requested) their 
subscription states.  The
+ * columns in the returned store are:
+ * 0: the folder name (G_TYPE_STRING)
+ * 1: the full path of the folder on the server (G_TYPE_STRING)
+ * 2 and 3: the current subscription state (G_TYPE_BOOLEAN), doubled as to track state changes and avoid 
unnecessary (UN)SUBSCRIBE
+ *    commands
+ * 4: the Pango rendering style for the folder name (PANGO_TYPE_STYLE)
+ *
+ * If subscriptions is FALSE, only the folder structure is read, columns 2 and 3 are always FALSE, and 
column 4 is always
+ * PANGO_STYLE_NORMAL.
+ */
+GtkTreeStore *libbalsa_scanner_imap_tree(LibBalsaServer  *server,
+                                                                                gboolean                 
subscriptions,
+                                                                                GError         **error)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+typedef enum {
+       LB_SCANNER_IMAP_FOLDER = 0,
+       LB_SCANNER_IMAP_PATH,
+       LB_SCANNER_IMAP_SUBS_NEW,
+       LB_SCANNER_IMAP_SUBS_OLD,
+       LB_SCANNER_IMAP_STYLE,
+       LB_SCANNER_IMAP_N_COLS
+} lb_scanner_imap_tree_cols_t;
+
+
 #endif                         /* __FOLDER_SCANNERS_H__ */
diff --git a/libbalsa/imap-server.c b/libbalsa/imap-server.c
index 7f4a4ca1b..8f8b5a3bd 100644
--- a/libbalsa/imap-server.c
+++ b/libbalsa/imap-server.c
@@ -887,3 +887,55 @@ libbalsa_imap_server_get_use_idle(LibBalsaImapServer *server)
 {
     return server->use_idle;
 }
+
+gboolean
+libbalsa_imap_server_subscriptions(LibBalsaImapServer  *server,
+                                                                  GPtrArray           *subscribe,
+                                                                  GPtrArray               *unsubscribe,
+                                                                  GError                         **error)
+{
+       ImapMboxHandle *handle;
+       gboolean result;
+
+       g_return_val_if_fail(LIBBALSA_IS_IMAP_SERVER(server), FALSE);
+
+       handle = libbalsa_imap_server_get_handle(server, error);
+       if (handle != NULL) {
+               guint n;
+
+               result = TRUE;
+               if (subscribe != NULL) {
+                       for (n = 0U; result && (n < subscribe->len); n++) {
+                               const gchar *mailbox = (const gchar *) g_ptr_array_index(subscribe, n);
+
+                               if (imap_mbox_subscribe(handle, mailbox, TRUE) != IMR_OK) {
+                                       g_set_error(error, LIBBALSA_MAILBOX_ERROR, 
LIBBALSA_MAILBOX_ACCESS_ERROR,
+                                               _("subscribing to “%s” failed"), mailbox);
+                                       result = FALSE;
+                               } else {
+                                       g_debug("subscribed to %s, '%s'", LIBBALSA_SERVER(server)->host, 
mailbox);
+                               }
+                       }
+               }
+
+               if (result && (unsubscribe != NULL)) {
+                       for (n = 0U; result && (n < unsubscribe->len); n++) {
+                               const gchar *mailbox = (const gchar *) g_ptr_array_index(unsubscribe, n);
+
+                               if (imap_mbox_subscribe(handle, mailbox, FALSE) != IMR_OK) {
+                                       g_set_error(error, LIBBALSA_MAILBOX_ERROR, 
LIBBALSA_MAILBOX_ACCESS_ERROR,
+                                               _("unsubscribing from “%s” failed"), mailbox);
+                                       result = FALSE;
+                               } else {
+                                       g_debug("unsubscribed from %s, '%s'", LIBBALSA_SERVER(server)->host, 
mailbox);
+                               }
+                       }
+               }
+
+               libbalsa_imap_server_release_handle(server, handle);
+       } else {
+               result = FALSE;
+       }
+
+       return result;
+}
diff --git a/libbalsa/imap-server.h b/libbalsa/imap-server.h
index 220d02e1a..27fac1eb6 100644
--- a/libbalsa/imap-server.h
+++ b/libbalsa/imap-server.h
@@ -70,6 +70,8 @@ typedef enum {
 void libbalsa_imap_server_set_bug(LibBalsaImapServer *server,
                                   LibBalsaImapServerBug bug, gboolean hasp);
 
+
+
 gboolean libbalsa_imap_server_has_bug(LibBalsaImapServer *server,
                                       LibBalsaImapServerBug bug);
 void libbalsa_imap_server_set_use_status(LibBalsaImapServer *server,
@@ -79,5 +81,9 @@ gboolean libbalsa_imap_server_get_use_status(LibBalsaImapServer *server);
 void libbalsa_imap_server_set_use_idle(LibBalsaImapServer *server,
                                        gboolean use_idle);
 gboolean libbalsa_imap_server_get_use_idle(LibBalsaImapServer *server);
+gboolean libbalsa_imap_server_subscriptions(LibBalsaImapServer  *server,
+                                                                                       GPtrArray             
          *subscribe,
+                                                                                       GPtrArray             
          *unsubscribe,
+                                                                                       GError                
     **error);
 
 #endif /* __IMAP_SERVER_H__ */
diff --git a/libbalsa/mailbox_imap.c b/libbalsa/mailbox_imap.c
index 72bcd6078..d3e050c2b 100644
--- a/libbalsa/mailbox_imap.c
+++ b/libbalsa/mailbox_imap.c
@@ -1690,29 +1690,6 @@ libbalsa_mailbox_imap_load_config(LibBalsaMailbox * mailbox,
     libbalsa_mailbox_imap_update_url(mimap);
 }
 
-/* libbalsa_mailbox_imap_subscribe:
-   change subscribed status for a mailbox.
-   Can be called for a closed mailbox.
- */
-gboolean
-libbalsa_mailbox_imap_subscribe(LibBalsaMailboxImap * mailbox, 
-                                    gboolean subscribe)
-{
-    ImapResponse rc;
-    ImapMboxHandle* handle;
-
-    g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), FALSE);
-
-    handle = libbalsa_mailbox_imap_get_handle(mailbox, NULL);
-    if (!handle)
-       return FALSE;
-
-    II(rc, handle, imap_mbox_subscribe(handle, mailbox->path, subscribe));
-
-    libbalsa_mailbox_imap_release_handle(mailbox);
-    return rc == IMR_OK;
-}
-
 /* libbalsa_mailbox_imap_noop:
  * pings the connection with NOOP for an open IMAP mailbox.
  * this keeps the connections alive.
diff --git a/libbalsa/mailbox_imap.h b/libbalsa/mailbox_imap.h
index f7bb7a0cc..f1a19caf7 100644
--- a/libbalsa/mailbox_imap.h
+++ b/libbalsa/mailbox_imap.h
@@ -48,9 +48,6 @@ void libbalsa_mailbox_imap_set_path(LibBalsaMailboxImap * mailbox,
                                    const gchar * path);
 const gchar* libbalsa_mailbox_imap_get_path(LibBalsaMailboxImap * mailbox);
 
-gboolean libbalsa_mailbox_imap_subscribe(LibBalsaMailboxImap * mailbox, 
-                                         gboolean subscribe);
-
 GHashTable * libbalsa_mailbox_imap_get_matchings(LibBalsaMailboxImap* mbox,
                                                 LibBalsaCondition *condition,
                                                 gboolean only_recent,
diff --git a/src/folder-conf.c b/src/folder-conf.c
index fe20d7458..45b4dd412 100644
--- a/src/folder-conf.c
+++ b/src/folder-conf.c
@@ -31,6 +31,7 @@
 #include "pref-manager.h"
 #include "imap-server.h"
 #include "server-config.h"
+#include "folder-scanners.h"
 #include "geometry-manager.h"
 #include <glib/gi18n.h>
 
@@ -46,6 +47,7 @@ typedef gboolean (*CommonDialogFunc)(CommonDialogData * cdd);
 
 #define FOLDER_CONF_COMMON \
     GtkDialog *dialog; \
+    GtkTreeStore *store; \
     BalsaMailboxNode *mbnode; \
     CommonDialogFunc ok
 
@@ -129,6 +131,10 @@ folder_conf_response(GtkDialog * dialog, int response,
     default:
         gtk_widget_destroy(GTK_WIDGET(cdd->dialog));
         cdd->dialog = NULL;
+        if (cdd->store != NULL) {
+               g_object_unref(cdd->store);
+               cdd->store = NULL;
+        }
         if (cdd->mbnode) {
             /* Clearing the data signifies that the dialog has been
              * destroyed. It also triggers a call to
@@ -157,6 +163,55 @@ validate_folder(GtkWidget G_GNUC_UNUSED *w, FolderDialogData *fcw)
     gtk_dialog_set_response_sensitive(fcw->dialog, GTK_RESPONSE_OK, 
libbalsa_server_cfg_valid(fcw->server_cfg));
 }
 
+static gboolean
+imap_apply_subscriptions(GtkTreeModel *model,
+                                        GtkTreePath  *path,
+                                                GtkTreeIter  *iter,
+                                                gpointer      data)
+{
+       gchar *mbox_path;
+       gboolean new_state;
+       gboolean old_state;
+       GPtrArray **changed = (GPtrArray **) data;
+
+       gtk_tree_model_get(model, iter,
+                       LB_SCANNER_IMAP_PATH, &mbox_path,
+                       LB_SCANNER_IMAP_SUBS_NEW, &new_state,
+                       LB_SCANNER_IMAP_SUBS_OLD, &old_state, -1);
+       if (old_state != new_state) {
+               if (new_state) {
+                       g_ptr_array_add(changed[0], mbox_path);
+               } else {
+                       g_ptr_array_add(changed[1], mbox_path);
+               }
+       } else {
+               g_free(mbox_path);
+       }
+
+       return FALSE;
+}
+
+static void
+imap_update_subscriptions(FolderDialogData *fcw)
+{
+       if (fcw->store != NULL) {
+               GPtrArray *changed[2];          /* 0 subscribe, 1 unsubscribe */
+               GError *error = NULL;
+
+               changed[0] = g_ptr_array_new_full(4U, g_free);          /* count is a wild guess... */
+               changed[1] = g_ptr_array_new_full(4U, g_free);
+               gtk_tree_model_foreach(GTK_TREE_MODEL(fcw->store), imap_apply_subscriptions, changed);
+               if ((changed[0]->len > 0U) || (changed[1]->len > 0U)) {
+                       if (!libbalsa_imap_server_subscriptions(LIBBALSA_IMAP_SERVER(fcw->server), 
changed[0], changed[1], &error)) {
+                               libbalsa_information(LIBBALSA_INFORMATION_ERROR, _("Changing subscriptions 
failed: %s"), error->message);
+                               g_clear_error(&error);
+                       }
+               }
+               g_ptr_array_unref(changed[0]);
+               g_ptr_array_unref(changed[1]);
+       }
+}
+
 static gboolean
 folder_conf_clicked_ok(FolderDialogData * fcw)
 {
@@ -198,6 +253,7 @@ folder_conf_clicked_ok(FolderDialogData * fcw)
     fcw->mbnode->dir = g_strdup(gtk_entry_get_text(GTK_ENTRY(fcw->prefix)));
 
     libbalsa_server_config_changed(fcw->server); /* trigger config save */
+    imap_update_subscriptions(fcw);
 
     if (insert) {
        balsa_mblist_mailbox_node_append(NULL, fcw->mbnode);
@@ -213,6 +269,145 @@ folder_conf_clicked_ok(FolderDialogData * fcw)
     return TRUE;
 }
 
+static void
+on_subscription_toggled(GtkCellRendererToggle *cell_renderer,
+                                               gchar                 *path,
+                                               GtkTreeStore          *store)
+{
+       GtkTreeIter iter;
+
+       if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path)) {
+               gboolean state;
+               gboolean orig_state;
+
+               gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+                               LB_SCANNER_IMAP_SUBS_NEW, &state,
+                               LB_SCANNER_IMAP_SUBS_OLD, &orig_state, -1);
+               state = !state;
+               gtk_tree_store_set(store, &iter,
+                               LB_SCANNER_IMAP_SUBS_NEW, state,
+                               LB_SCANNER_IMAP_STYLE, (state == orig_state) ? PANGO_STYLE_NORMAL : 
PANGO_STYLE_ITALIC, -1);
+       }
+}
+
+static GtkWidget *
+create_imap_folder_dialog(LibBalsaServer  *server,
+                                                 GtkWindow       *parent,
+                                                 const gchar     *geometry_key,
+                                                 gboolean         with_subsrciptions,
+                                                 const gchar     *title,
+                                                 const gchar     *message,
+                                                 GtkTreeStore   **store,
+                                                 GtkWidget      **treeview)
+{
+       GtkWidget *dialog;
+       GtkWidget *vbox;
+       GtkWidget *label;
+       GtkWidget *scrolled_wind;
+       GtkTreeSelection *selection;
+       GtkCellRenderer *renderer;
+       GtkTreeViewColumn *column;
+       GError *error = NULL;
+
+       /* get the folder tree from the server if required */
+       if (*store == NULL) {
+               *store = libbalsa_scanner_imap_tree(server, with_subsrciptions, &error);
+               if (*store == NULL) {
+                       libbalsa_information(LIBBALSA_INFORMATION_ERROR, _("Cannot list IMAP folders: %s"), 
error->message);
+                       g_clear_error(&error);
+                       return NULL;
+               } else {
+                       g_object_ref(G_OBJECT(*store));
+               }
+       }
+
+       /* dialog */
+       if (with_subsrciptions) {
+               dialog = gtk_dialog_new_with_buttons(title, parent, GTK_DIALOG_MODAL | 
libbalsa_dialog_flags(),
+                               _("_Close"), GTK_RESPONSE_CLOSE, NULL);
+       } else {
+               dialog = gtk_dialog_new_with_buttons(title, parent, GTK_DIALOG_MODAL | 
libbalsa_dialog_flags(),
+                               _("_Cancel"), GTK_RESPONSE_REJECT, _("_OK"), GTK_RESPONSE_ACCEPT, NULL);
+       }
+       geometry_manager_attach(GTK_WINDOW(dialog), geometry_key);
+
+       /* content: vbox, message label, scrolled window */
+       vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
+       gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+       gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox);
+       gtk_widget_set_vexpand(vbox, TRUE);
+
+       label = gtk_label_new(message);
+       gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+       gtk_widget_set_halign(label, GTK_ALIGN_START);
+       gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+
+       scrolled_wind = gtk_scrolled_window_new(NULL,NULL);
+       gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_wind), GTK_SHADOW_ETCHED_IN);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_wind), GTK_POLICY_NEVER, 
GTK_POLICY_AUTOMATIC);
+       gtk_box_pack_start(GTK_BOX(vbox), scrolled_wind, TRUE, TRUE, 0);
+
+       /* folder tree */
+       *treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(*store));
+       gtk_container_add(GTK_CONTAINER(scrolled_wind), *treeview);
+       gtk_tree_view_expand_all(GTK_TREE_VIEW(*treeview));
+
+       renderer = gtk_cell_renderer_text_new();
+       column = gtk_tree_view_column_new_with_attributes(_("folder"), renderer,
+                       "text", LB_SCANNER_IMAP_FOLDER,
+                       "style", LB_SCANNER_IMAP_STYLE, NULL);
+       gtk_tree_view_column_set_expand(column, TRUE);
+       gtk_tree_view_column_set_sort_column_id(column, 0);
+       gtk_tree_view_append_column (GTK_TREE_VIEW(*treeview), column);
+       selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(*treeview));
+
+       /* subscriptions if requested */
+       if (with_subsrciptions) {
+               renderer = gtk_cell_renderer_toggle_new();
+               g_signal_connect(renderer, "toggled", G_CALLBACK(on_subscription_toggled), *store);
+               g_object_set(renderer, "activatable", TRUE, NULL);
+               column = gtk_tree_view_column_new_with_attributes(_("subscribed"), renderer,
+                               "active", LB_SCANNER_IMAP_SUBS_NEW, NULL);
+               gtk_tree_view_column_set_expand(column, FALSE);
+               gtk_tree_view_append_column (GTK_TREE_VIEW(*treeview), column);
+
+               gtk_tree_selection_set_mode(selection, GTK_SELECTION_NONE);
+       } else {
+               gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+       }
+
+       gtk_widget_show_all(vbox);
+
+       return dialog;
+}
+
+static void
+folder_conf_imap_subscriptions(GtkButton        *widget,
+                                                          FolderDialogData *fcw)
+{
+       GtkWidget *dialog;
+       GtkWidget *treeview;
+       gchar *label_str;
+
+       label_str = g_strdup_printf(_("Manage folder subscriptions of IMAP server “%s”"),
+                       (fcw->mbnode != NULL) ? fcw->mbnode->name : _("unknown"));
+       dialog = create_imap_folder_dialog(fcw->server, GTK_WINDOW(fcw->dialog), "IMAPSubscriptions", TRUE, 
_("Manage subscriptions"),
+                       label_str, &fcw->store, &treeview);
+       g_free(label_str);
+
+       if (dialog != NULL) {
+               (void) gtk_dialog_run(GTK_DIALOG(dialog));
+               gtk_widget_destroy(dialog);
+       }
+}
+
+static void
+fcw_subscribed_toggled(GtkToggleButton *toggle,
+                                          GtkWidget       *button)
+{
+       gtk_widget_set_sensitive(button, gtk_toggle_button_get_active(toggle));
+}
+
 /* folder_conf_imap_node:
    show the IMAP Folder configuration dialog for given mailbox node.
    If mn is NULL, setup it with default values for folder creation.
@@ -222,6 +417,8 @@ folder_conf_imap_node(BalsaMailboxNode *mn)
 {
     FolderDialogData *fcw;
     static FolderDialogData *fcw_new;
+    GtkWidget *box;
+    GtkWidget *button;
 
     /* Allow only one dialog per mailbox node, and one with mn == NULL
      * for creating a new folder. */
@@ -233,7 +430,7 @@ folder_conf_imap_node(BalsaMailboxNode *mn)
         return;
     }
 
-    fcw = g_new(FolderDialogData, 1);
+    fcw = g_new0(FolderDialogData, 1);
     if (mn != NULL) {
        fcw->server = mn->server;
     } else {
@@ -272,9 +469,23 @@ folder_conf_imap_node(BalsaMailboxNode *mn)
     gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(fcw->dialog))), 
GTK_WIDGET(fcw->server_cfg));
     g_signal_connect(fcw->server_cfg, "changed", G_CALLBACK(validate_folder), fcw);
 
-    /* additional basic settings */
-    fcw->subscribed = libbalsa_server_cfg_add_check(fcw->server_cfg, TRUE, _("Subscribed _folders only"),
-       (mn != NULL) ? mn->subscribed : FALSE, NULL, NULL);
+    /* additional basic settings - subscription management */
+    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
+    fcw->subscribed = gtk_check_button_new_with_mnemonic(_("Subscribed _folders only"));
+    gtk_box_pack_start(GTK_BOX(box), fcw->subscribed, TRUE, TRUE, 0);
+    button = gtk_button_new_with_label(_("Manage subscriptions…"));
+    g_signal_connect(button, "clicked", G_CALLBACK(folder_conf_imap_subscriptions), fcw);
+    if (mn != NULL) {
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fcw->subscribed), mn->subscribed);
+       gtk_widget_set_sensitive(button, mn->subscribed);
+    } else {
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fcw->subscribed), FALSE);
+       gtk_widget_set_sensitive(button, FALSE);
+    }
+    gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
+    g_signal_connect(fcw->subscribed, "toggled", G_CALLBACK(fcw_subscribed_toggled), button);
+    libbalsa_server_cfg_add_row(fcw->server_cfg, TRUE, box, NULL);
+
     fcw->list_inbox = libbalsa_server_cfg_add_check(fcw->server_cfg, TRUE, _("Always show _Inbox"),
        (mn != NULL) ? mn->list_inbox : TRUE, NULL, NULL);
     fcw->prefix = libbalsa_server_cfg_add_entry(fcw->server_cfg, TRUE, _("Pr_efix:"),
@@ -332,162 +543,91 @@ validate_sub_folder(GtkWidget * w, SubfolderDialogData * sdd)
                                                           (sdd->folder_name)));
 }
 
-/* callbacks for a `Browse...' button: */
-
-typedef struct _BrowseButtonData BrowseButtonData;
-struct _BrowseButtonData {
-    SubfolderDialogData *sdd;
-    GtkDialog *dialog;
-    GtkWidget *button;
-    BalsaMailboxNode *mbnode;
-};
-
-static void
-browse_button_select_row_cb(GtkTreeSelection * selection,
-                            BrowseButtonData * bbd)
-{
-    GtkTreeModel *model;
-    GtkTreeIter iter;
-    gboolean selected =
-        gtk_tree_selection_get_selected(selection, &model, &iter);
-
-    gtk_dialog_set_response_sensitive(bbd->dialog,
-                                      GTK_RESPONSE_OK, selected);
-    if (selected)
-        gtk_tree_model_get(model, &iter, 0, &bbd->mbnode, -1);
-    /* bbd->mbnode is unreffed when bbd is freed. */
-}
-
-static void
-browse_button_row_activated(GtkTreeView * tree_view, GtkTreePath * path,
-                            GtkTreeViewColumn * column,
-                            BrowseButtonData * bbd)
-{
-    gtk_dialog_response(bbd->dialog, GTK_RESPONSE_OK);
-}
-
-static void
-browse_button_response(GtkDialog * dialog, gint response,
-                       BrowseButtonData * bbd)
-{
-    if (response == GTK_RESPONSE_OK) {
-        BalsaMailboxNode *mbnode = bbd->mbnode;
-        if (!mbnode)
-            return;
-
-        bbd->sdd->parent = mbnode;
-        if (mbnode->dir)
-            gtk_entry_set_text(GTK_ENTRY(bbd->sdd->parent_folder),
-                               mbnode->dir);
-        if(mbnode->server)
-            gtk_label_set_label(GTK_LABEL(bbd->sdd->host_label),
-                                mbnode->server->host);
-    }
-    validate_sub_folder(NULL, bbd->sdd);
-    gtk_widget_set_sensitive(bbd->button, TRUE);
-    gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-
 static gboolean
-folder_selection_func(GtkTreeSelection * selection, GtkTreeModel * model,
-                     GtkTreePath * path, gboolean path_currently_selected,
-                     SubfolderDialogData * sdd)
+select_parent_folder(GtkTreeModel *model,
+                                        GtkTreePath  *path,
+                                        GtkTreeIter  *iter,
+                                        gpointer      data)
 {
-    GtkTreeIter iter;
-    BalsaMailboxNode *mbnode;
-    gboolean retval;
-
-    gtk_tree_model_get_iter(model, &iter, path);
-    gtk_tree_model_get(model, &iter, 0, &mbnode, -1);
-    retval = (LIBBALSA_IS_IMAP_SERVER(mbnode->server)
-             && (sdd->mbnode == NULL
-                 || sdd->mbnode->server == mbnode->server));
-    g_object_unref(mbnode);
-
-    return retval;
+       const gchar *find_folder = (const gchar *) data;
+       gchar *foldername;
+       gboolean found;
+
+       gtk_tree_model_get(model, iter,
+                       LB_SCANNER_IMAP_PATH, &foldername, -1);
+       if ((foldername != NULL) && (strcmp(foldername, find_folder) == 0)) {
+               GtkTreeSelection *selection;
+
+               selection = GTK_TREE_SELECTION(g_object_get_data(G_OBJECT(model), "selection"));
+               gtk_tree_selection_select_iter(selection, iter);
+               found = TRUE;
+       } else {
+               found = FALSE;
+       }
+       g_free(foldername);
+       return found;
 }
 
 static void
-browse_button_data_free(BrowseButtonData *bbd)
+on_parent_double_click(GtkTreeView G_GNUC_UNUSED       *treeview,
+                                          GtkTreePath G_GNUC_UNUSED       *path,
+                                          GtkTreeViewColumn G_GNUC_UNUSED *column,
+                       GtkDialog                       *dialog)
 {
-    if (bbd->mbnode)
-       g_object_unref(bbd->mbnode);
-    g_free(bbd);
+    gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
 }
 
 static void
-browse_button_cb(GtkWidget * widget, SubfolderDialogData * sdd)
+browse_button_cb(GtkWidget           *widget,
+                                SubfolderDialogData *sdd)
 {
-    GtkWidget *scroll, *dialog;
-    GtkRequisition req;
-    GtkWidget *tree_view = balsa_mblist_new();
-    GtkTreeSelection *selection =
-        gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
-    BrowseButtonData *bbd;
-    const geometry_t *main_size;
-
-    /*
-     * Make only IMAP nodes selectable:
-     */
-    gtk_tree_selection_set_select_function(selection,
-                                           (GtkTreeSelectionFunc) 
-                                           folder_selection_func, sdd,
-                                           NULL);
-
-    dialog =
-        gtk_dialog_new_with_buttons(_("Select parent folder"),
-                                    GTK_WINDOW(sdd->dialog),
-                                    GTK_DIALOG_DESTROY_WITH_PARENT |
-                                    libbalsa_dialog_flags(),
-                                    _("_Cancel"), GTK_RESPONSE_CANCEL,
-                                    _("_Help"), GTK_RESPONSE_HELP,
-                                    NULL);
-#if HAVE_MACOSX_DESKTOP
-    libbalsa_macosx_menu_for_parent(dialog, GTK_WINDOW(sdd->dialog));
-#endif
-    
-    scroll = gtk_scrolled_window_new(NULL, NULL);
-    gtk_box_pack_start(GTK_BOX
-                       (gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
-                       scroll, TRUE, TRUE, 0);
-    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
-                                   GTK_POLICY_AUTOMATIC,
-                                   GTK_POLICY_ALWAYS);
-    gtk_container_add(GTK_CONTAINER(scroll), tree_view);
-    gtk_widget_grab_focus(tree_view);
-
-    bbd = g_new(BrowseButtonData, 1);
-    bbd->sdd = sdd;
-    bbd->dialog = GTK_DIALOG(dialog);
-    bbd->button = widget;
-    bbd->mbnode = NULL;
-    g_object_weak_ref(G_OBJECT(dialog),
-                     (GWeakNotify) browse_button_data_free, bbd);
-    g_signal_connect(G_OBJECT(selection), "changed",
-                     G_CALLBACK(browse_button_select_row_cb), bbd);
-    g_signal_connect(G_OBJECT(tree_view), "row-activated",
-                     G_CALLBACK(browse_button_row_activated), bbd);
-
-    /* Force the mailbox list to be a reasonable size. */
-    gtk_widget_get_preferred_size(tree_view, NULL, &req);
-    /* don't mess with the width, it gets saved! */
-    main_size = geometry_manager_get("MainWindow");
-    g_assert(main_size != NULL);
-    if (req.height > main_size->height)
-        req.height = main_size->height;
-    else if (req.height < main_size->height / 2)
-        req.height = main_size->height / 2;
-    gtk_window_set_default_size(GTK_WINDOW(dialog), req.width, req.height);
-
-    /* To prevent multiple dialogs, desensitize the browse button. */
-    gtk_widget_set_sensitive(widget, FALSE);
-    /* OK button is insensitive until some row is selected. */
-    gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-                                      GTK_RESPONSE_OK, FALSE);
-
-    g_signal_connect(G_OBJECT(dialog), "response",
-                     G_CALLBACK(browse_button_response), bbd);
-    gtk_widget_show_all(GTK_WIDGET(dialog));
+       GtkWidget *dialog;
+       GtkWidget *treeview;
+       gchar *label_str;
+
+       label_str = g_strdup_printf(_("Select parent folder of “%s”"), sdd->mbnode->mailbox->name);
+       dialog = create_imap_folder_dialog(sdd->mbnode->server, GTK_WINDOW(sdd->dialog), "IMAPSelectParent", 
FALSE,
+                       _("Select parent folder"), label_str, &sdd->store, &treeview);
+       g_free(label_str);
+
+       if (dialog != NULL) {
+               GtkTreeModel *model;
+               GtkTreeSelection *selection;
+               const gchar *current_parent;
+               gint result;
+
+               /* select the parent item (if any) */
+               model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
+               selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+               current_parent = gtk_entry_get_text(GTK_ENTRY(sdd->parent_folder));
+               if (current_parent[0] != '\0') {
+                       g_object_set_data(G_OBJECT(model), "selection", selection);             /* needed in 
the callback */
+                       gtk_tree_model_foreach(model, select_parent_folder, (gpointer) current_parent);
+               } else {
+                       GtkTreeIter iter;
+
+                       /* no parent: select the first node */
+                       gtk_tree_model_get_iter_first(model, &iter);
+                       gtk_tree_selection_select_iter(selection, &iter);
+               }
+               g_signal_connect(treeview, "row-activated", G_CALLBACK(on_parent_double_click), dialog);
+
+               result = gtk_dialog_run(GTK_DIALOG(dialog));
+               if (result == GTK_RESPONSE_ACCEPT) {
+                       GtkTreeIter iter;
+
+                       if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+                               gchar *selected_path;
+
+                               gtk_tree_model_get(model, &iter,
+                                               LB_SCANNER_IMAP_PATH, &selected_path, -1);
+                               gtk_entry_set_text(GTK_ENTRY(sdd->parent_folder), selected_path);
+                               g_free(selected_path);
+                               validate_sub_folder(NULL, sdd);
+                       }
+               }
+               gtk_widget_destroy(dialog);
+       }
 }
 
 static gboolean
@@ -646,7 +786,7 @@ folder_conf_imap_sub_node(BalsaMailboxNode * mn)
         return;
     }
 
-    sdd = g_new(SubfolderDialogData, 1);
+    sdd = g_new0(SubfolderDialogData, 1);
     sdd->ok = (CommonDialogFunc) subfolder_conf_clicked_ok;
 
     sdd->mbnode = mn;
diff --git a/src/mailbox-node.c b/src/mailbox-node.c
index f349664f2..528c23335 100644
--- a/src/mailbox-node.c
+++ b/src/mailbox-node.c
@@ -967,22 +967,6 @@ mb_draftbox_cb(GtkWidget * widget, BalsaMailboxNode * mbnode)
     config_mailbox_set_as_special(mbnode->mailbox, SPECIAL_DRAFT);
 }
 
-static void
-mb_subscribe_cb(GtkWidget * widget, BalsaMailboxNode * mbnode)
-{
-    g_return_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mbnode->mailbox));
-    libbalsa_mailbox_imap_subscribe(LIBBALSA_MAILBOX_IMAP(mbnode->mailbox), 
-                                   TRUE);
-}
-
-static void
-mb_unsubscribe_cb(GtkWidget * widget, BalsaMailboxNode * mbnode)
-{
-    g_return_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mbnode->mailbox));
-    libbalsa_mailbox_imap_subscribe(LIBBALSA_MAILBOX_IMAP(mbnode->mailbox),
-                                    FALSE);
-}
-
 static void
 mb_rescan_cb(GtkWidget * widget, BalsaMailboxNode * mbnode)
 {
@@ -1108,14 +1092,6 @@ balsa_mailbox_node_get_context_menu(BalsaMailboxNode * mbnode)
     if (!special && !mbnode->config_prefix)
        add_menu_entry(menu, _("_Delete"), G_CALLBACK(mb_del_cb),  mbnode);
 
-    if(LIBBALSA_IS_MAILBOX_IMAP(mailbox)) {
-        add_menu_entry(menu, NULL, NULL, NULL);
-        add_menu_entry(menu, _("_Subscribe"),   
-                       G_CALLBACK(mb_subscribe_cb),   mbnode);
-        add_menu_entry(menu, _("_Unsubscribe"), 
-                       G_CALLBACK(mb_unsubscribe_cb), mbnode);
-    }
-
     if (!special) {
         add_menu_entry(menu, NULL, NULL, NULL);
         add_menu_entry(menu, _("Mark as _Inbox"),    
diff --git a/src/save-restore.c b/src/save-restore.c
index 75fc19ac7..ba7b025ab 100644
--- a/src/save-restore.c
+++ b/src/save-restore.c
@@ -692,6 +692,8 @@ config_global_load(void)
     geometry_manager_init("SendMsgWindow", 640, 480, FALSE);
     geometry_manager_init("MessageWindow", 400, 500, FALSE);
     geometry_manager_init("SourceView", 500, 400, FALSE);
+    geometry_manager_init("IMAPSubscriptions", 200, 160, FALSE);
+    geometry_manager_init("IMAPSelectParent", 200, 160, FALSE);
 #ifdef HAVE_GPGME
     geometry_manager_init("KeyDialog", 400, 200, FALSE);
     geometry_manager_init("KeyList", 300, 200, FALSE);



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