[balsa] Improve IMAP folder parent management
- From: Peter Bloomfield <peterb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa] Improve IMAP folder parent management
- Date: Mon, 1 Apr 2019 15:40:46 +0000 (UTC)
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]