Files
evolution/mail/em-folder-tree.c
Matthew Barnes 018018fabe Remove more Express Mode hacks.
This removes all traces of Express Mode from all but the contact editor
and calendar appointment editor.  Need to evaluate the remaining cases
individually.
2013-03-31 12:10:54 -04:00

3609 lines
92 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*
* Authors:
* Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <libxml/tree.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include "em-vfolder-editor-rule.h"
#include "libemail-engine/e-mail-folder-utils.h"
#include "libemail-engine/e-mail-session.h"
#include "libemail-engine/mail-mt.h"
#include "libemail-engine/mail-ops.h"
#include "libemail-engine/mail-tools.h"
#include "em-utils.h"
#include "em-folder-tree.h"
#include "em-folder-utils.h"
#include "em-folder-selector.h"
#include "em-folder-properties.h"
#include "em-event.h"
#include "mail-send-recv.h"
#include "mail-vfolder-ui.h"
#include "e-mail-ui-session.h"
#define d(x)
#define EM_FOLDER_TREE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
typedef struct _AsyncContext AsyncContext;
#define EM_FOLDER_TREE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
struct _selected_uri {
gchar *key; /* store:path or account/path */
gchar *uri;
CamelService *service;
gchar *path;
};
struct _EMFolderTreePrivate {
EMailSession *session;
EAlertSink *alert_sink;
/* selected_uri structures of each path pending selection. */
GSList *select_uris;
/* Removed as they're encountered, so use this
* to find URI's not presnet but selected. */
GHashTable *select_uris_table;
guint32 excluded;
gboolean (*excluded_func) (EMFolderTree *folder_tree,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
gpointer excluded_data;
guint cursor_set:1; /* set to TRUE means we or something
* else has set the cursor, otherwise
* we need to set it when we set the
* selection */
guint autoscroll_id;
guint autoexpand_id;
GtkTreeRowReference *autoexpand_row;
guint loading_row_id;
guint loaded_row_id;
GtkTreeRowReference *drag_row;
gboolean skip_double_click;
GtkCellRenderer *text_renderer;
PangoEllipsizeMode ellipsize;
GtkWidget *selectable; /* an ESelectable, where to pass selectable calls */
/* Signal handler IDs */
gulong selection_changed_handler_id;
};
struct _AsyncContext {
EActivity *activity;
EMFolderTree *folder_tree;
GtkTreeRowReference *root;
gchar *full_name;
};
enum {
PROP_0,
PROP_ALERT_SINK,
PROP_COPY_TARGET_LIST,
PROP_ELLIPSIZE,
PROP_MODEL,
PROP_PASTE_TARGET_LIST,
PROP_SESSION
};
enum {
FOLDER_ACTIVATED, /* aka double-clicked or user hit enter */
FOLDER_SELECTED,
POPUP_EVENT,
HIDDEN_KEY_EVENT,
LAST_SIGNAL
};
/* Drag & Drop types */
enum DndDragType {
DND_DRAG_TYPE_FOLDER, /* drag an evo folder */
DND_DRAG_TYPE_TEXT_URI_LIST, /* drag to an mbox file */
NUM_DRAG_TYPES
};
enum DndDropType {
DND_DROP_TYPE_UID_LIST, /* drop a list of message uids */
DND_DROP_TYPE_FOLDER, /* drop an evo folder */
DND_DROP_TYPE_MESSAGE_RFC822, /* drop a message/rfc822 stream */
DND_DROP_TYPE_TEXT_URI_LIST, /* drop an mbox file */
NUM_DROP_TYPES
};
static GtkTargetEntry drag_types[] = {
{ (gchar *) "x-folder", 0, DND_DRAG_TYPE_FOLDER },
{ (gchar *) "text/uri-list", 0, DND_DRAG_TYPE_TEXT_URI_LIST },
};
static GtkTargetEntry drop_types[] = {
{ (gchar *) "x-uid-list" , 0, DND_DROP_TYPE_UID_LIST },
{ (gchar *) "x-folder", 0, DND_DROP_TYPE_FOLDER },
{ (gchar *) "message/rfc822", 0, DND_DROP_TYPE_MESSAGE_RFC822 },
{ (gchar *) "text/uri-list", 0, DND_DROP_TYPE_TEXT_URI_LIST },
};
static GdkAtom drag_atoms[NUM_DRAG_TYPES];
static GdkAtom drop_atoms[NUM_DROP_TYPES];
static guint signals[LAST_SIGNAL] = { 0 };
struct _folder_tree_selection_data {
GtkTreeModel *model;
GtkTreeIter *iter;
gboolean set;
};
/* Forward Declarations */
static void em_folder_tree_selectable_init (ESelectableInterface *interface);
G_DEFINE_TYPE_WITH_CODE (
EMFolderTree,
em_folder_tree,
GTK_TYPE_TREE_VIEW,
G_IMPLEMENT_INTERFACE (
E_TYPE_SELECTABLE,
em_folder_tree_selectable_init))
static void
async_context_free (AsyncContext *context)
{
if (context->activity != NULL)
g_object_unref (context->activity);
if (context->folder_tree != NULL)
g_object_unref (context->folder_tree);
gtk_tree_row_reference_free (context->root);
g_free (context->full_name);
g_slice_free (AsyncContext, context);
}
static void
folder_tree_get_folder_info_cb (CamelStore *store,
GAsyncResult *result,
AsyncContext *context)
{
struct _EMFolderTreeModelStoreInfo *si;
CamelFolderInfo *folder_info;
CamelFolderInfo *child_info;
EAlertSink *alert_sink;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter root;
GtkTreeIter iter;
GtkTreeIter titer;
gboolean is_store;
gboolean iter_is_placeholder;
gboolean valid;
GError *error = NULL;
alert_sink = e_activity_get_alert_sink (context->activity);
folder_info = camel_store_get_folder_info_finish (
store, result, &error);
tree_view = GTK_TREE_VIEW (context->folder_tree);
model = gtk_tree_view_get_model (tree_view);
/* Check if our parent folder has been deleted/unsubscribed. */
if (!gtk_tree_row_reference_valid (context->root)) {
g_clear_error (&error);
goto exit;
}
path = gtk_tree_row_reference_get_path (context->root);
valid = gtk_tree_model_get_iter (model, &root, path);
g_return_if_fail (valid);
gtk_tree_model_get (model, &root, COL_BOOL_IS_STORE, &is_store, -1);
/* If we had an error, then we need to re-set the
* load subdirs state and collapse the node. */
if (error != NULL) {
gtk_tree_store_set (
GTK_TREE_STORE (model), &root,
COL_BOOL_LOAD_SUBDIRS, TRUE, -1);
gtk_tree_view_collapse_row (tree_view, path);
}
gtk_tree_path_free (path);
if (e_activity_handle_cancellation (context->activity, error)) {
g_warn_if_fail (folder_info == NULL);
async_context_free (context);
g_error_free (error);
return;
/* XXX POP3 stores always return a "no folder" error because they
* have no folder hierarchy to scan. Just ignore the error. */
} else if (g_error_matches (
error, CAMEL_STORE_ERROR,
CAMEL_STORE_ERROR_NO_FOLDER)) {
g_warn_if_fail (folder_info == NULL);
async_context_free (context);
g_error_free (error);
return;
} else if (error != NULL) {
g_warn_if_fail (folder_info == NULL);
e_alert_submit (
alert_sink, "mail:folder-open",
error->message, NULL);
async_context_free (context);
g_error_free (error);
return;
}
/* If we've just set up an NNTP account, for example, and haven't
* subscribed to any folders yet, folder_info may legitimately be
* NULL at this point. We handle that case below. Proceed. */
/* Check if the store has been removed. */
si = em_folder_tree_model_lookup_store_info (
EM_FOLDER_TREE_MODEL (model), store);
if (si == NULL)
goto exit;
/* Make sure we still need to load the tree subfolders. */
iter_is_placeholder = FALSE;
/* Get the first child (which will be a placeholder row). */
valid = gtk_tree_model_iter_children (model, &iter, &root);
/* Traverse to the last valid iter, or the placeholder row. */
while (valid) {
gboolean is_store_node = FALSE;
gboolean is_folder_node = FALSE;
titer = iter; /* Preserve the last valid iter */
gtk_tree_model_get (
model, &iter,
COL_BOOL_IS_STORE, &is_store_node,
COL_BOOL_IS_FOLDER, &is_folder_node, -1);
/* Stop on a "Loading..." placeholder row. */
if (!is_store_node && !is_folder_node) {
iter_is_placeholder = TRUE;
break;
}
valid = gtk_tree_model_iter_next (model, &iter);
}
iter = titer;
child_info = folder_info;
/* FIXME Camel's IMAP code is totally on crack here: the
* folder_info we got back should be for the folder
* we're expanding, and folder_info->child should be
* what we want to fill our tree with... *sigh* */
if (folder_info != NULL) {
gboolean names_match;
names_match = (g_strcmp0 (
folder_info->full_name,
context->full_name) == 0);
if (names_match) {
child_info = folder_info->child;
if (child_info == NULL)
child_info = folder_info->next;
}
}
/* The folder being expanded has no children after all. Remove
* the "Loading..." placeholder row and collapse the parent. */
if (child_info == NULL) {
if (iter_is_placeholder)
gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
if (is_store) {
path = gtk_tree_model_get_path (model, &root);
gtk_tree_view_collapse_row (tree_view, path);
gtk_tree_path_free (path);
goto exit;
}
} else {
while (child_info != NULL) {
GtkTreeRowReference *reference;
/* Check if we already have this row cached. */
reference = g_hash_table_lookup (
si->full_hash, child_info->full_name);
if (reference == NULL) {
/* If we're on a placeholder row, reuse
* the row for the first child folder. */
if (iter_is_placeholder)
iter_is_placeholder = FALSE;
else
gtk_tree_store_append (
GTK_TREE_STORE (model),
&iter, &root);
em_folder_tree_model_set_folder_info (
EM_FOLDER_TREE_MODEL (model),
&iter, si, child_info, TRUE);
}
child_info = child_info->next;
}
/* Remove the "Loading..." placeholder row. */
if (iter_is_placeholder)
gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
}
gtk_tree_store_set (
GTK_TREE_STORE (model), &root,
COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
exit:
if (folder_info != NULL)
camel_store_free_folder_info (store, folder_info);
async_context_free (context);
}
static void
folder_tree_emit_popup_event (EMFolderTree *folder_tree,
GdkEvent *event)
{
g_signal_emit (folder_tree, signals[POPUP_EVENT], 0, event);
}
static void
folder_tree_free_select_uri (struct _selected_uri *u)
{
g_free (u->uri);
if (u->service)
g_object_unref (u->service);
g_free (u->key);
g_free (u->path);
g_free (u);
}
static gboolean
folder_tree_select_func (GtkTreeSelection *selection,
GtkTreeModel *model,
GtkTreePath *path,
gboolean selected)
{
EMFolderTreePrivate *priv;
GtkTreeView *tree_view;
gboolean is_store;
guint32 flags;
GtkTreeIter iter;
tree_view = gtk_tree_selection_get_tree_view (selection);
priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
if (selected)
return TRUE;
if (priv->excluded == 0 && priv->excluded_func == NULL)
return TRUE;
if (!gtk_tree_model_get_iter (model, &iter, path))
return TRUE;
if (priv->excluded_func != NULL)
return priv->excluded_func (
EM_FOLDER_TREE (tree_view), model,
&iter, priv->excluded_data);
gtk_tree_model_get (
model, &iter, COL_UINT_FLAGS, &flags,
COL_BOOL_IS_STORE, &is_store, -1);
if (is_store)
flags |= CAMEL_FOLDER_NOSELECT;
return (flags & priv->excluded) == 0;
}
/* NOTE: Removes and frees the selected uri structure */
static void
folder_tree_select_uri (EMFolderTree *folder_tree,
GtkTreePath *path,
struct _selected_uri *u)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeView *tree_view;
GtkTreeSelection *selection;
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_select_path (selection, path);
if (!priv->cursor_set) {
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
priv->cursor_set = TRUE;
}
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.8f, 0.0f);
g_hash_table_remove (priv->select_uris_table, u->key);
priv->select_uris = g_slist_remove (priv->select_uris, u);
folder_tree_free_select_uri (u);
}
static void
folder_tree_expand_node (const gchar *key,
EMFolderTree *folder_tree)
{
struct _EMFolderTreeModelStoreInfo *si = NULL;
GtkTreeRowReference *row;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path;
EMailSession *session;
CamelService *service;
const gchar *p;
gchar *uid;
gsize n;
struct _selected_uri *u;
if (!(p = strchr (key, '/')))
n = strlen (key);
else
n = (p - key);
uid = g_alloca (n + 1);
memcpy (uid, key, n);
uid[n] = '\0';
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
session = em_folder_tree_get_session (folder_tree);
service = camel_session_ref_service (CAMEL_SESSION (session), uid);
if (CAMEL_IS_STORE (service))
si = em_folder_tree_model_lookup_store_info (
EM_FOLDER_TREE_MODEL (model),
CAMEL_STORE (service));
if (service != NULL)
g_object_unref (service);
if (si == NULL)
return;
if (p != NULL && p[1]) {
if (!(row = g_hash_table_lookup (si->full_hash, p + 1)))
return;
} else
row = si->row;
path = gtk_tree_row_reference_get_path (row);
gtk_tree_view_expand_to_path (tree_view, path);
u = g_hash_table_lookup (folder_tree->priv->select_uris_table, key);
if (u)
folder_tree_select_uri (folder_tree, path, u);
gtk_tree_path_free (path);
}
static void
folder_tree_maybe_expand_row (EMFolderTreeModel *model,
GtkTreePath *tree_path,
GtkTreeIter *iter,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
CamelStore *store;
gchar *full_name;
gchar *key;
const gchar *uid;
struct _selected_uri *u;
gtk_tree_model_get (
GTK_TREE_MODEL (model), iter,
COL_STRING_FULL_NAME, &full_name,
COL_POINTER_CAMEL_STORE, &store, -1);
uid = camel_service_get_uid (CAMEL_SERVICE (store));
key = g_strdup_printf ("%s/%s", uid, full_name ? full_name : "");
u = g_hash_table_lookup (priv->select_uris_table, key);
if (u) {
gchar *c = strrchr (key, '/');
*c = '\0';
folder_tree_expand_node (key, folder_tree);
folder_tree_select_uri (folder_tree, tree_path, u);
}
g_free (full_name);
g_free (key);
}
static void
folder_tree_clear_selected_list (EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
g_slist_foreach (priv->select_uris, (GFunc) folder_tree_free_select_uri, NULL);
g_slist_free (priv->select_uris);
g_hash_table_destroy (priv->select_uris_table);
priv->select_uris = NULL;
priv->select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
priv->cursor_set = FALSE;
}
static void
folder_tree_cell_edited_cb (EMFolderTree *folder_tree,
const gchar *path_string,
const gchar *new_name)
{
CamelFolderInfo *folder_info;
CamelStore *store;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
gchar *old_name = NULL;
gchar *old_full_name = NULL;
gchar *new_full_name = NULL;
gchar *folder_uri;
gchar **strv;
gpointer parent;
guint index;
GError *local_error = NULL;
/* XXX Consider splitting this into separate async functions:
* em_folder_tree_rename_folder_async()
* em_folder_tree_rename_folder_finish() */
parent = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
path = gtk_tree_path_new_from_string (path_string);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_DISPLAY_NAME, &old_name,
COL_STRING_FULL_NAME, &old_full_name, -1);
if (!old_name || !old_full_name || g_strcmp0 (new_name, old_name) == 0)
goto exit;
/* Check for invalid characters. */
if (strchr (new_name, '/') != NULL) {
e_alert_run_dialog_for_args (
parent, "mail:no-rename-folder",
old_name, new_name,
_("Folder names cannot contain '/'"), NULL);
goto exit;
}
/* Build the new name from the old name. */
strv = g_strsplit_set (old_full_name, "/", 0);
index = g_strv_length (strv) - 1;
g_free (strv[index]);
strv[index] = g_strdup (new_name);
new_full_name = g_strjoinv ("/", strv);
g_strfreev (strv);
/* Check for duplicate folder name. */
/* FIXME camel_store_get_folder_info() may block. */
folder_info = camel_store_get_folder_info_sync (
store, new_full_name,
CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
if (folder_info != NULL) {
e_alert_run_dialog_for_args (
parent, "mail:no-rename-folder-exists",
old_name, new_name, NULL);
camel_store_free_folder_info (store, folder_info);
goto exit;
}
/* FIXME camel_store_rename_folder_sync() may block. */
camel_store_rename_folder_sync (
store, old_full_name, new_full_name, NULL, &local_error);
if (local_error != NULL) {
e_alert_run_dialog_for_args (
parent, "mail:no-rename-folder",
old_full_name, new_full_name,
local_error->message, NULL);
g_error_free (local_error);
goto exit;
}
folder_uri = e_mail_folder_uri_build (store, new_full_name);
em_folder_tree_set_selected (folder_tree, folder_uri, FALSE);
g_free (folder_uri);
exit:
g_free (old_name);
g_free (old_full_name);
g_free (new_full_name);
}
static gboolean
subdirs_contain_unread (GtkTreeModel *model,
GtkTreeIter *root)
{
guint unread;
GtkTreeIter iter;
if (!gtk_tree_model_iter_children (model, &iter, root))
return FALSE;
do {
gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
if (unread)
return TRUE;
if (gtk_tree_model_iter_has_child (model, &iter))
if (subdirs_contain_unread (model, &iter))
return TRUE;
} while (gtk_tree_model_iter_next (model, &iter));
return FALSE;
}
static void
folder_tree_render_display_name (GtkTreeViewColumn *column,
GtkCellRenderer *renderer,
GtkTreeModel *model,
GtkTreeIter *iter)
{
CamelService *service;
PangoWeight weight;
gboolean is_store, bold, subdirs_unread = FALSE;
gboolean editable;
guint unread;
gchar *name;
gtk_tree_model_get (
model, iter,
COL_STRING_DISPLAY_NAME, &name,
COL_POINTER_CAMEL_STORE, &service,
COL_BOOL_IS_STORE, &is_store,
COL_UINT_UNREAD, &unread, -1);
g_object_get (renderer, "editable", &editable, NULL);
bold = is_store || unread;
if (gtk_tree_model_iter_has_child (model, iter)) {
gboolean expanded = TRUE;
g_object_get (renderer, "is-expanded", &expanded, NULL);
if (!bold || !expanded)
subdirs_unread = subdirs_contain_unread (model, iter);
}
bold = !editable && (bold || subdirs_unread);
weight = bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
g_object_set (renderer, "weight", weight, NULL);
if (is_store) {
const gchar *display_name;
display_name = camel_service_get_display_name (service);
g_object_set (renderer, "text", display_name, NULL);
} else if (!editable && unread > 0) {
gchar *name_and_unread;
/* Translators: This is the string used for displaying the
* folder names in folder trees. The first "%s" will be
* replaced by the folder's name and "%u" will be replaced
* with the number of unread messages in the folder. The
* second %s will be replaced with a "+" letter for collapsed
* folders with unread messages in some subfolder too,
* or with an empty string for other cases.
*
* Most languages should translate this as "%s (%u%s)". The
* languages that use localized digits (like Persian) may
* need to replace "%u" with "%Iu". Right-to-left languages
* (like Arabic and Hebrew) may need to add bidirectional
* formatting codes to take care of the cases the folder
* name appears in either direction.
*
* Do not translate the "folder-display|" part. Remove it
* from your translation.
*/
name_and_unread = g_strdup_printf (
C_("folder-display", "%s (%u%s)"),
name, unread, subdirs_unread ? "+" : "");
g_object_set (renderer, "text", name_and_unread, NULL);
g_free (name_and_unread);
} else {
g_object_set (renderer, "text", name, NULL);
}
g_free (name);
}
static void
folder_tree_render_icon (GtkTreeViewColumn *column,
GtkCellRenderer *renderer,
GtkTreeModel *model,
GtkTreeIter *iter)
{
GtkTreeSelection *selection;
GtkTreePath *drag_dest_row;
GtkWidget *tree_view;
GIcon *icon;
guint unread;
guint old_unread;
gchar *icon_name;
gboolean is_selected;
gboolean is_drafts = FALSE;
gboolean is_drag_dest = FALSE;
guint32 fi_flags = 0;
gtk_tree_model_get (
model, iter,
COL_STRING_ICON_NAME, &icon_name,
COL_UINT_UNREAD_LAST_SEL, &old_unread,
COL_UINT_UNREAD, &unread,
COL_BOOL_IS_DRAFT, &is_drafts,
COL_UINT_FLAGS, &fi_flags,
-1);
if (icon_name == NULL)
return;
tree_view = gtk_tree_view_column_get_tree_view (column);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
is_selected = gtk_tree_selection_iter_is_selected (selection, iter);
gtk_tree_view_get_drag_dest_row (
GTK_TREE_VIEW (tree_view), &drag_dest_row, NULL);
if (drag_dest_row != NULL) {
GtkTreePath *path;
path = gtk_tree_model_get_path (model, iter);
if (gtk_tree_path_compare (path, drag_dest_row) == 0)
is_drag_dest = TRUE;
gtk_tree_path_free (path);
gtk_tree_path_free (drag_dest_row);
}
if (g_strcmp0 (icon_name, "folder") == 0) {
if (is_selected) {
g_free (icon_name);
icon_name = g_strdup ("folder-open");
} else if (is_drag_dest) {
g_free (icon_name);
icon_name = g_strdup ("folder-drag-accept");
}
}
icon = g_themed_icon_new (icon_name);
/* Show an emblem if there's new mail. */
if (!is_selected && unread > old_unread && !is_drafts && !(fi_flags & CAMEL_FOLDER_VIRTUAL)) {
GIcon *temp_icon;
GEmblem *emblem;
temp_icon = g_themed_icon_new ("emblem-new");
emblem = g_emblem_new (temp_icon);
g_object_unref (temp_icon);
temp_icon = g_emblemed_icon_new (icon, emblem);
g_object_unref (emblem);
g_object_unref (icon);
icon = temp_icon;
}
g_object_set (renderer, "gicon", icon, NULL);
g_object_unref (icon);
g_free (icon_name);
}
static void
folder_tree_selection_changed_cb (EMFolderTree *folder_tree,
GtkTreeSelection *selection)
{
GtkTreeModel *model;
GtkTreeIter iter;
GList *list;
CamelStore *store = NULL;
CamelFolderInfoFlags flags = 0;
guint unread = 0;
guint old_unread = 0;
gchar *folder_name = NULL;
list = gtk_tree_selection_get_selected_rows (selection, &model);
if (list == NULL)
goto exit;
gtk_tree_model_get_iter (model, &iter, list->data);
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &folder_name,
COL_UINT_FLAGS, &flags,
COL_UINT_UNREAD, &unread,
COL_UINT_UNREAD_LAST_SEL, &old_unread, -1);
/* Sync unread counts to distinguish new incoming mail. */
if (unread != old_unread)
gtk_tree_store_set (
GTK_TREE_STORE (model), &iter,
COL_UINT_UNREAD_LAST_SEL, unread, -1);
exit:
g_signal_emit (
folder_tree, signals[FOLDER_SELECTED], 0,
store, folder_name, flags);
g_free (folder_name);
g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
g_list_free (list);
}
static void
folder_tree_copy_expanded_cb (GtkTreeView *unused,
GtkTreePath *path,
GtkTreeView *tree_view)
{
gtk_tree_view_expand_row (tree_view, path, FALSE);
}
static void
folder_tree_copy_selection_cb (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
GtkTreeView *tree_view)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_select_path (selection, path);
/* Center the tree view on the selected path. */
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.0);
}
static void
folder_tree_copy_state (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GtkTreeModel *model;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
selection = em_folder_tree_model_get_selection (
EM_FOLDER_TREE_MODEL (model));
if (selection == NULL)
return;
gtk_tree_view_map_expanded_rows (
tree_view, (GtkTreeViewMappingFunc)
folder_tree_copy_expanded_cb, folder_tree);
gtk_tree_selection_selected_foreach (
selection, (GtkTreeSelectionForeachFunc)
folder_tree_copy_selection_cb, folder_tree);
}
static void
folder_tree_set_alert_sink (EMFolderTree *folder_tree,
EAlertSink *alert_sink)
{
g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
g_return_if_fail (folder_tree->priv->alert_sink == NULL);
folder_tree->priv->alert_sink = g_object_ref (alert_sink);
}
static void
folder_tree_set_session (EMFolderTree *folder_tree,
EMailSession *session)
{
g_return_if_fail (E_IS_MAIL_SESSION (session));
g_return_if_fail (folder_tree->priv->session == NULL);
folder_tree->priv->session = g_object_ref (session);
}
static GtkTargetList *
folder_tree_get_copy_target_list (EMFolderTree *folder_tree)
{
GtkTargetList *target_list = NULL;
if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
ESelectable *selectable;
selectable = E_SELECTABLE (folder_tree->priv->selectable);
target_list = e_selectable_get_copy_target_list (selectable);
}
return target_list;
}
static GtkTargetList *
folder_tree_get_paste_target_list (EMFolderTree *folder_tree)
{
GtkTargetList *target_list = NULL;
if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
ESelectable *selectable;
selectable = E_SELECTABLE (folder_tree->priv->selectable);
target_list = e_selectable_get_paste_target_list (selectable);
}
return target_list;
}
static void
folder_tree_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ALERT_SINK:
folder_tree_set_alert_sink (
EM_FOLDER_TREE (object),
g_value_get_object (value));
return;
case PROP_ELLIPSIZE:
em_folder_tree_set_ellipsize (
EM_FOLDER_TREE (object),
g_value_get_enum (value));
return;
case PROP_MODEL:
gtk_tree_view_set_model (
GTK_TREE_VIEW (object),
g_value_get_object (value));
return;
case PROP_SESSION:
folder_tree_set_session (
EM_FOLDER_TREE (object),
g_value_get_object (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
folder_tree_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ALERT_SINK:
g_value_set_object (
value,
em_folder_tree_get_alert_sink (
EM_FOLDER_TREE (object)));
return;
case PROP_COPY_TARGET_LIST:
g_value_set_boxed (
value,
folder_tree_get_copy_target_list (
EM_FOLDER_TREE (object)));
return;
case PROP_ELLIPSIZE:
g_value_set_enum (
value,
em_folder_tree_get_ellipsize (
EM_FOLDER_TREE (object)));
return;
case PROP_MODEL:
g_value_set_object (
value,
gtk_tree_view_get_model (
GTK_TREE_VIEW (object)));
return;
case PROP_PASTE_TARGET_LIST:
g_value_set_boxed (
value,
folder_tree_get_paste_target_list (
EM_FOLDER_TREE (object)));
return;
case PROP_SESSION:
g_value_set_object (
value,
em_folder_tree_get_session (
EM_FOLDER_TREE (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
folder_tree_dispose (GObject *object)
{
EMFolderTreePrivate *priv;
GtkTreeModel *model;
priv = EM_FOLDER_TREE_GET_PRIVATE (object);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (object));
if (priv->loaded_row_id != 0) {
g_signal_handler_disconnect (model, priv->loaded_row_id);
priv->loaded_row_id = 0;
}
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
if (priv->alert_sink != NULL) {
g_object_unref (priv->alert_sink);
priv->alert_sink = NULL;
}
if (priv->session != NULL) {
g_object_unref (priv->session);
priv->session = NULL;
}
if (priv->text_renderer != NULL) {
g_object_unref (priv->text_renderer);
priv->text_renderer = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (em_folder_tree_parent_class)->dispose (object);
}
static void
folder_tree_finalize (GObject *object)
{
EMFolderTreePrivate *priv;
priv = EM_FOLDER_TREE_GET_PRIVATE (object);
if (priv->select_uris != NULL) {
g_slist_foreach (
priv->select_uris,
(GFunc) folder_tree_free_select_uri, NULL);
g_slist_free (priv->select_uris);
priv->select_uris = NULL;
}
if (priv->select_uris_table) {
g_hash_table_destroy (priv->select_uris_table);
priv->select_uris_table = NULL;
}
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (em_folder_tree_parent_class)->finalize (object);
}
static void
folder_tree_constructed (GObject *object)
{
EMFolderTreePrivate *priv;
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
GtkTreeView *tree_view;
GtkTreeModel *model;
gulong handler_id;
priv = EM_FOLDER_TREE_GET_PRIVATE (object);
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (em_folder_tree_parent_class)->constructed (object);
tree_view = GTK_TREE_VIEW (object);
model = gtk_tree_view_get_model (tree_view);
selection = gtk_tree_view_get_selection (tree_view);
handler_id = g_signal_connect (
model, "loading-row",
G_CALLBACK (folder_tree_maybe_expand_row), object);
priv->loading_row_id = handler_id;
handler_id = g_signal_connect (
model, "loaded-row",
G_CALLBACK (folder_tree_maybe_expand_row), object);
priv->loaded_row_id = handler_id;
handler_id = g_signal_connect_swapped (
selection, "changed",
G_CALLBACK (folder_tree_selection_changed_cb), object);
priv->selection_changed_handler_id = handler_id;
column = gtk_tree_view_column_new ();
gtk_tree_view_append_column (tree_view, column);
renderer = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start (column, renderer, FALSE);
gtk_tree_view_column_add_attribute (
column, renderer, "visible", COL_BOOL_IS_FOLDER);
gtk_tree_view_column_set_cell_data_func (
column, renderer, (GtkTreeCellDataFunc)
folder_tree_render_icon, NULL, NULL);
renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (column, renderer, TRUE);
gtk_tree_view_column_set_cell_data_func (
column, renderer, (GtkTreeCellDataFunc)
folder_tree_render_display_name, NULL, NULL);
priv->text_renderer = g_object_ref (renderer);
g_object_bind_property (
object, "ellipsize",
renderer, "ellipsize",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (
renderer, "edited",
G_CALLBACK (folder_tree_cell_edited_cb), object);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
gtk_tree_selection_set_select_function (
selection, (GtkTreeSelectionFunc)
folder_tree_select_func, NULL, NULL);
gtk_tree_view_set_headers_visible (tree_view, FALSE);
gtk_tree_view_set_search_column (tree_view, COL_STRING_DISPLAY_NAME);
folder_tree_copy_state (EM_FOLDER_TREE (object));
gtk_widget_show (GTK_WIDGET (object));
}
static gboolean
folder_tree_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
EMFolderTreePrivate *priv;
GtkWidgetClass *widget_class;
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GtkTreePath *path;
gulong handler_id;
priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
tree_view = GTK_TREE_VIEW (widget);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
priv->cursor_set = TRUE;
if (event->button != 3)
goto chainup;
if (!gtk_tree_view_get_path_at_pos (
tree_view, event->x, event->y,
&path, NULL, NULL, NULL))
goto chainup;
/* Select and focus the row that was right-clicked, but prevent
* a "folder-selected" signal emission since this does not count
* as a folder selection in the sense we mean. */
handler_id = priv->selection_changed_handler_id;
g_signal_handler_block (selection, handler_id);
gtk_tree_selection_select_path (selection, path);
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
g_signal_handler_unblock (selection, handler_id);
gtk_tree_path_free (path);
folder_tree_emit_popup_event (
EM_FOLDER_TREE (tree_view), (GdkEvent *) event);
chainup:
/* Chain up to parent's button_press_event() method. */
widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
return widget_class->button_press_event (widget, event);
}
static gboolean
folder_tree_key_press_event (GtkWidget *widget,
GdkEventKey *event)
{
EMFolderTreePrivate *priv;
GtkWidgetClass *widget_class;
GtkTreeSelection *selection;
GtkTreeView *tree_view;
if (event && event->type == GDK_KEY_PRESS &&
(event->keyval == GDK_KEY_space ||
event->keyval == '.' ||
event->keyval == ',' ||
event->keyval == '[' ||
event->keyval == ']')) {
g_signal_emit (widget, signals[HIDDEN_KEY_EVENT], 0, event);
return TRUE;
}
priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
tree_view = GTK_TREE_VIEW (widget);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
priv->cursor_set = TRUE;
/* Chain up to parent's key_press_event() method. */
widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
return widget_class->key_press_event (widget, event);
}
static gboolean
folder_tree_popup_menu (GtkWidget *widget)
{
folder_tree_emit_popup_event (EM_FOLDER_TREE (widget), NULL);
return TRUE;
}
static void
folder_tree_row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column)
{
EMFolderTreePrivate *priv;
GtkTreeModel *model;
gchar *folder_name;
GtkTreeIter iter;
CamelStore *store;
CamelFolderInfoFlags flags;
priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
model = gtk_tree_view_get_model (tree_view);
if (priv->skip_double_click)
return;
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &folder_name,
COL_UINT_FLAGS, &flags, -1);
folder_tree_clear_selected_list (EM_FOLDER_TREE (tree_view));
g_signal_emit (
tree_view, signals[FOLDER_SELECTED], 0,
store, folder_name, flags);
g_signal_emit (
tree_view, signals[FOLDER_ACTIVATED], 0,
store, folder_name);
g_free (folder_name);
}
static gboolean
folder_tree_test_collapse_row (GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter cursor;
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &cursor))
goto exit;
/* Select the collapsed node IFF it is a
* parent of the currently selected folder. */
if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &cursor))
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
exit:
return FALSE;
}
static void
folder_tree_row_expanded (GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path)
{
EActivity *activity;
GCancellable *cancellable;
EMFolderTree *folder_tree;
AsyncContext *context;
GtkTreeModel *model;
CamelStore *store;
gchar *full_name;
gboolean load;
folder_tree = EM_FOLDER_TREE (tree_view);
model = gtk_tree_view_get_model (tree_view);
gtk_tree_model_get (
model, iter,
COL_STRING_FULL_NAME, &full_name,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_LOAD_SUBDIRS, &load, -1);
if (!load) {
g_free (full_name);
return;
}
gtk_tree_store_set (
GTK_TREE_STORE (model), iter,
COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
/* Retrieve folder info asynchronously. */
activity = em_folder_tree_new_activity (folder_tree);
cancellable = e_activity_get_cancellable (activity);
context = g_slice_new0 (AsyncContext);
context->activity = activity;
context->folder_tree = g_object_ref (folder_tree);
context->root = gtk_tree_row_reference_new (model, path);
context->full_name = g_strdup (full_name);
camel_store_get_folder_info (
store, full_name,
CAMEL_STORE_FOLDER_INFO_FAST |
CAMEL_STORE_FOLDER_INFO_RECURSIVE |
CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
G_PRIORITY_DEFAULT, cancellable,
(GAsyncReadyCallback) folder_tree_get_folder_info_cb,
context);
g_free (full_name);
}
static void
em_folder_tree_class_init (EMFolderTreeClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkTreeViewClass *tree_view_class;
g_type_class_add_private (class, sizeof (EMFolderTreePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = folder_tree_set_property;
object_class->get_property = folder_tree_get_property;
object_class->dispose = folder_tree_dispose;
object_class->finalize = folder_tree_finalize;
object_class->constructed = folder_tree_constructed;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->button_press_event = folder_tree_button_press_event;
widget_class->key_press_event = folder_tree_key_press_event;
widget_class->popup_menu = folder_tree_popup_menu;
tree_view_class = GTK_TREE_VIEW_CLASS (class);
tree_view_class->row_activated = folder_tree_row_activated;
tree_view_class->test_collapse_row = folder_tree_test_collapse_row;
tree_view_class->row_expanded = folder_tree_row_expanded;
g_object_class_install_property (
object_class,
PROP_ALERT_SINK,
g_param_spec_object (
"alert-sink",
NULL,
NULL,
E_TYPE_ALERT_SINK,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/* Inherited from ESelectableInterface */
g_object_class_override_property (
object_class,
PROP_COPY_TARGET_LIST,
"copy-target-list");
g_object_class_install_property (
object_class,
PROP_ELLIPSIZE,
g_param_spec_enum (
"ellipsize",
NULL,
NULL,
PANGO_TYPE_ELLIPSIZE_MODE,
PANGO_ELLIPSIZE_NONE,
G_PARAM_READWRITE));
/* XXX We override the GtkTreeView:model property to add
* G_PARAM_CONSTRUCT_ONLY so the model is set by the
* time we get to folder_tree_constructed(). */
g_object_class_install_property (
object_class,
PROP_MODEL,
g_param_spec_object (
"model",
"TreeView Model",
"The model for the tree view",
GTK_TYPE_TREE_MODEL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
/* Inherited from ESelectableInterface */
g_object_class_override_property (
object_class,
PROP_PASTE_TARGET_LIST,
"paste-target-list");
g_object_class_install_property (
object_class,
PROP_SESSION,
g_param_spec_object (
"session",
NULL,
NULL,
E_TYPE_MAIL_SESSION,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
signals[FOLDER_SELECTED] = g_signal_new (
"folder-selected",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EMFolderTreeClass, folder_selected),
NULL, NULL,
e_marshal_VOID__OBJECT_STRING_UINT,
G_TYPE_NONE, 3,
CAMEL_TYPE_STORE,
G_TYPE_STRING,
G_TYPE_UINT);
signals[FOLDER_ACTIVATED] = g_signal_new (
"folder-activated",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EMFolderTreeClass, folder_activated),
NULL, NULL,
e_marshal_VOID__OBJECT_STRING,
G_TYPE_NONE, 2,
CAMEL_TYPE_STORE,
G_TYPE_STRING);
signals[POPUP_EVENT] = g_signal_new (
"popup-event",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (EMFolderTreeClass, popup_event),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[HIDDEN_KEY_EVENT] = g_signal_new (
"hidden-key-event",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EMFolderTreeClass, hidden_key_event),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
}
static void
em_folder_tree_init (EMFolderTree *folder_tree)
{
GHashTable *select_uris_table;
AtkObject *a11y;
select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
folder_tree->priv = EM_FOLDER_TREE_GET_PRIVATE (folder_tree);
folder_tree->priv->select_uris_table = select_uris_table;
/* FIXME Gross hack. */
gtk_widget_set_can_focus (GTK_WIDGET (folder_tree), TRUE);
a11y = gtk_widget_get_accessible (GTK_WIDGET (folder_tree));
atk_object_set_name (a11y, _("Mail Folder Tree"));
}
/* Sets a selectable widget, which will be used for update-actions and
* select-all selectable interface functions. This can be NULL, then nothing
* can be selected and calling selectable function does nothing. */
void
em_folder_tree_set_selectable_widget (EMFolderTree *folder_tree,
GtkWidget *selectable)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
if (selectable != NULL)
g_return_if_fail (E_IS_SELECTABLE (selectable));
folder_tree->priv->selectable = selectable;
}
static void
folder_tree_selectable_update_actions (ESelectable *selectable,
EFocusTracker *focus_tracker,
GdkAtom *clipboard_targets,
gint n_clipboard_targets)
{
EMFolderTree *folder_tree;
folder_tree = EM_FOLDER_TREE (selectable);
g_return_if_fail (folder_tree != NULL);
if (folder_tree->priv->selectable != NULL) {
ESelectableInterface *interface;
ESelectable *selectable;
selectable = E_SELECTABLE (folder_tree->priv->selectable);
interface = E_SELECTABLE_GET_INTERFACE (selectable);
g_return_if_fail (interface->update_actions != NULL);
interface->update_actions (
selectable, focus_tracker,
clipboard_targets, n_clipboard_targets);
}
}
static void
folder_tree_selectable_cut_clipboard (ESelectable *selectable)
{
ESelectableInterface *interface;
EMFolderTree *folder_tree;
GtkWidget *proxy;
folder_tree = EM_FOLDER_TREE (selectable);
proxy = folder_tree->priv->selectable;
if (!E_IS_SELECTABLE (proxy))
return;
interface = E_SELECTABLE_GET_INTERFACE (proxy);
if (interface->cut_clipboard == NULL)
return;
if (gtk_widget_get_can_focus (proxy))
gtk_widget_grab_focus (proxy);
interface->cut_clipboard (E_SELECTABLE (proxy));
}
static void
folder_tree_selectable_copy_clipboard (ESelectable *selectable)
{
ESelectableInterface *interface;
EMFolderTree *folder_tree;
GtkWidget *proxy;
folder_tree = EM_FOLDER_TREE (selectable);
proxy = folder_tree->priv->selectable;
if (!E_IS_SELECTABLE (proxy))
return;
interface = E_SELECTABLE_GET_INTERFACE (proxy);
if (interface->copy_clipboard == NULL)
return;
if (gtk_widget_get_can_focus (proxy))
gtk_widget_grab_focus (proxy);
interface->copy_clipboard (E_SELECTABLE (proxy));
}
static void
folder_tree_selectable_paste_clipboard (ESelectable *selectable)
{
ESelectableInterface *interface;
EMFolderTree *folder_tree;
GtkWidget *proxy;
folder_tree = EM_FOLDER_TREE (selectable);
proxy = folder_tree->priv->selectable;
if (!E_IS_SELECTABLE (proxy))
return;
interface = E_SELECTABLE_GET_INTERFACE (proxy);
if (interface->paste_clipboard == NULL)
return;
if (gtk_widget_get_can_focus (proxy))
gtk_widget_grab_focus (proxy);
interface->paste_clipboard (E_SELECTABLE (proxy));
}
static void
folder_tree_selectable_delete_selection (ESelectable *selectable)
{
ESelectableInterface *interface;
EMFolderTree *folder_tree;
GtkWidget *proxy;
folder_tree = EM_FOLDER_TREE (selectable);
proxy = folder_tree->priv->selectable;
if (!E_IS_SELECTABLE (proxy))
return;
interface = E_SELECTABLE_GET_INTERFACE (proxy);
if (interface->delete_selection == NULL)
return;
if (gtk_widget_get_can_focus (proxy))
gtk_widget_grab_focus (proxy);
interface->delete_selection (E_SELECTABLE (proxy));
}
static void
folder_tree_selectable_select_all (ESelectable *selectable)
{
ESelectableInterface *interface;
EMFolderTree *folder_tree;
GtkWidget *proxy;
folder_tree = EM_FOLDER_TREE (selectable);
proxy = folder_tree->priv->selectable;
if (!E_IS_SELECTABLE (proxy))
return;
interface = E_SELECTABLE_GET_INTERFACE (proxy);
if (interface->select_all == NULL)
return;
if (gtk_widget_get_can_focus (proxy))
gtk_widget_grab_focus (proxy);
interface->select_all (E_SELECTABLE (proxy));
}
static void
em_folder_tree_selectable_init (ESelectableInterface *interface)
{
interface->update_actions = folder_tree_selectable_update_actions;
interface->cut_clipboard = folder_tree_selectable_cut_clipboard;
interface->copy_clipboard = folder_tree_selectable_copy_clipboard;
interface->paste_clipboard = folder_tree_selectable_paste_clipboard;
interface->delete_selection = folder_tree_selectable_delete_selection;
interface->select_all = folder_tree_selectable_select_all;
}
GtkWidget *
em_folder_tree_new (EMailSession *session,
EAlertSink *alert_sink)
{
EMFolderTreeModel *model;
g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
model = em_folder_tree_model_get_default ();
return em_folder_tree_new_with_model (session, alert_sink, model);
}
GtkWidget *
em_folder_tree_new_with_model (EMailSession *session,
EAlertSink *alert_sink,
EMFolderTreeModel *model)
{
g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
g_return_val_if_fail (EM_IS_FOLDER_TREE_MODEL (model), NULL);
return g_object_new (
EM_TYPE_FOLDER_TREE,
"alert-sink", alert_sink,
"session", session,
"model", model, NULL);
}
EActivity *
em_folder_tree_new_activity (EMFolderTree *folder_tree)
{
EActivity *activity;
EMailSession *session;
EAlertSink *alert_sink;
GCancellable *cancellable;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
activity = e_activity_new ();
alert_sink = em_folder_tree_get_alert_sink (folder_tree);
e_activity_set_alert_sink (activity, alert_sink);
cancellable = camel_operation_new ();
e_activity_set_cancellable (activity, cancellable);
g_object_unref (cancellable);
session = em_folder_tree_get_session (folder_tree);
e_mail_ui_session_add_activity (
E_MAIL_UI_SESSION (session), activity);
return activity;
}
PangoEllipsizeMode
em_folder_tree_get_ellipsize (EMFolderTree *folder_tree)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), 0);
return folder_tree->priv->ellipsize;
}
void
em_folder_tree_set_ellipsize (EMFolderTree *folder_tree,
PangoEllipsizeMode ellipsize)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
if (ellipsize == folder_tree->priv->ellipsize)
return;
folder_tree->priv->ellipsize = ellipsize;
g_object_notify (G_OBJECT (folder_tree), "ellipsize");
}
EAlertSink *
em_folder_tree_get_alert_sink (EMFolderTree *folder_tree)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
return folder_tree->priv->alert_sink;
}
EMailSession *
em_folder_tree_get_session (EMFolderTree *folder_tree)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
return folder_tree->priv->session;
}
static void
tree_drag_begin (GtkWidget *widget,
GdkDragContext *context,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeSelection *selection;
GtkTreeView *tree_view;
cairo_surface_t *s;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
tree_view = GTK_TREE_VIEW (widget);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return;
path = gtk_tree_model_get_path (model, &iter);
priv->drag_row = gtk_tree_row_reference_new (model, path);
s = gtk_tree_view_create_row_drag_icon (tree_view, path);
gtk_drag_set_icon_surface (context, s);
gtk_tree_path_free (path);
}
static void
tree_drag_data_get (GtkWidget *widget,
GdkDragContext *context,
GtkSelectionData *selection,
guint info,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeModel *model;
GtkTreePath *src_path;
CamelFolder *folder;
CamelStore *store;
GtkTreeIter iter;
gchar *folder_name = NULL;
gchar *folder_uri;
if (!priv->drag_row || !(src_path =
gtk_tree_row_reference_get_path (priv->drag_row)))
return;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
if (!gtk_tree_model_get_iter (model, &iter, src_path))
goto fail;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &folder_name, -1);
/* make sure user isn't trying to drag on a placeholder row */
if (folder_name == NULL)
goto fail;
folder_uri = e_mail_folder_uri_build (store, folder_name);
switch (info) {
case DND_DRAG_TYPE_FOLDER:
/* dragging to a new location in the folder tree */
gtk_selection_data_set (
selection, drag_atoms[info], 8,
(guchar *) folder_uri, strlen (folder_uri) + 1);
break;
case DND_DRAG_TYPE_TEXT_URI_LIST:
/* dragging to nautilus or something, probably */
/* FIXME camel_store_get_folder_sync() may block. */
if ((folder = camel_store_get_folder_sync (
store, folder_name, 0, NULL, NULL))) {
GPtrArray *uids = camel_folder_get_uids (folder);
em_utils_selection_set_urilist (selection, folder, uids);
camel_folder_free_uids (folder, uids);
g_object_unref (folder);
}
break;
default:
abort ();
}
g_free (folder_uri);
fail:
gtk_tree_path_free (src_path);
g_free (folder_name);
}
static gboolean
ask_drop_folder (EMFolderTree *folder_tree,
const gchar *src_folder_uri,
const gchar *des_full_name,
CamelStore *des_store,
gboolean is_move)
{
const gchar *key = is_move ? "prompt-on-folder-drop-move" : "prompt-on-folder-drop-copy";
EMailSession *session;
GSettings *settings;
gchar *set_value, *src_folder_name = NULL;
GError *error = NULL;
GtkWidget *widget;
GtkWindow *parent;
gint response;
g_return_val_if_fail (folder_tree != NULL, FALSE);
g_return_val_if_fail (src_folder_uri != NULL, FALSE);
g_return_val_if_fail (des_full_name != NULL || des_store != NULL, FALSE);
settings = g_settings_new ("org.gnome.evolution.mail");
set_value = g_settings_get_string (settings, key);
if (g_strcmp0 (set_value, "never") == 0) {
g_object_unref (settings);
g_free (set_value);
return FALSE;
} else if (g_strcmp0 (set_value, "always") == 0) {
g_object_unref (settings);
g_free (set_value);
return TRUE;
}
g_free (set_value);
session = em_folder_tree_get_session (folder_tree);
e_mail_folder_uri_parse (
CAMEL_SESSION (session),
src_folder_uri, NULL, &src_folder_name, &error);
if (error != NULL) {
g_warning (
"%s: Failed to convert '%s' to folder name: %s",
G_STRFUNC, src_folder_uri, error->message);
g_object_unref (settings);
g_error_free (error);
return FALSE;
}
parent = NULL;
widget = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
if (widget && gtk_widget_is_toplevel (widget) && GTK_IS_WINDOW (widget))
parent = GTK_WINDOW (widget);
widget = e_alert_dialog_new_for_args (
parent,
is_move ? "mail:ask-folder-drop-move" : "mail:ask-folder-drop-copy",
src_folder_name,
des_full_name && *des_full_name ? des_full_name :
camel_service_get_display_name (CAMEL_SERVICE (des_store)),
NULL);
response = gtk_dialog_run (GTK_DIALOG (widget));
gtk_widget_destroy (widget);
if (response == GTK_RESPONSE_OK)
g_settings_set_string (settings, key, "always");
else if (response == GTK_RESPONSE_CANCEL)
g_settings_set_string (settings, key, "never");
g_free (src_folder_name);
g_object_unref (settings);
return response == GTK_RESPONSE_YES || response == GTK_RESPONSE_OK;
}
/* Drop handling */
struct _DragDataReceivedAsync {
MailMsg base;
/* input data */
GdkDragContext *context;
/* Only selection->data and selection->length are valid */
GtkSelectionData *selection;
EMFolderTree *folder_tree;
EMailSession *session;
CamelStore *store;
gchar *full_name;
gchar *dest_folder_uri;
guint32 action;
guint info;
guint move : 1;
guint moved : 1;
guint aborted : 1;
};
static void
folder_tree_drop_folder (struct _DragDataReceivedAsync *m)
{
CamelFolder *folder;
CamelStore *parent_store;
GCancellable *cancellable;
const gchar *folder_name;
const gchar *full_name;
const guchar *data;
data = gtk_selection_data_get_data (m->selection);
d (printf (" * Drop folder '%s' onto '%s'\n", data, m->full_name));
cancellable = m->base.cancellable;
folder = e_mail_session_uri_to_folder_sync (
m->session, (gchar *) data, 0,
cancellable, &m->base.error);
if (folder == NULL)
return;
full_name = camel_folder_get_full_name (folder);
parent_store = camel_folder_get_parent_store (folder);
em_folder_utils_copy_folders (
parent_store, full_name, m->store,
m->full_name ? m->full_name : "", m->move);
folder_name = strrchr (full_name, '/');
if (folder_name)
folder_name++;
else
folder_name = full_name;
if (m->full_name) {
gchar *dest_root_name;
dest_root_name = g_strconcat (m->full_name, "/", folder_name, NULL);
m->dest_folder_uri = e_mail_folder_uri_build (m->store, dest_root_name);
g_free (dest_root_name);
} else {
m->dest_folder_uri = e_mail_folder_uri_build (m->store, folder_name);
}
g_object_unref (folder);
}
static gchar *
folder_tree_drop_async__desc (struct _DragDataReceivedAsync *m)
{
const guchar *data;
data = gtk_selection_data_get_data (m->selection);
if (m->info == DND_DROP_TYPE_FOLDER) {
gchar *folder_name = NULL;
gchar *res;
e_mail_folder_uri_parse (
CAMEL_SESSION (m->session),
(gchar *) data, NULL, &folder_name, NULL);
g_return_val_if_fail (folder_name != NULL, NULL);
if (m->move)
res = g_strdup_printf (
_("Moving folder %s"), folder_name);
else
res = g_strdup_printf (
_("Copying folder %s"), folder_name);
g_free (folder_name);
return res;
} else {
if (m->move)
return g_strdup_printf (
_("Moving messages into folder %s"),
m->full_name);
else
return g_strdup_printf (
_("Copying messages into folder %s"),
m->full_name);
}
}
static void
folder_tree_drop_async__exec (struct _DragDataReceivedAsync *m,
GCancellable *cancellable,
GError **error)
{
CamelFolder *folder;
/* for types other than folder, we can't drop to the root path */
if (m->info == DND_DROP_TYPE_FOLDER) {
/* copy or move (aka rename) a folder */
folder_tree_drop_folder (m);
} else if (m->full_name == NULL) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Cannot drop message(s) into toplevel store"));
} else if ((folder = camel_store_get_folder_sync (
m->store, m->full_name, 0, cancellable, error))) {
switch (m->info) {
case DND_DROP_TYPE_UID_LIST:
/* import a list of uids from another evo folder */
em_utils_selection_get_uidlist (
m->selection, m->session, folder, m->move,
cancellable, error);
m->moved = m->move && (!error || !*error);
break;
case DND_DROP_TYPE_MESSAGE_RFC822:
/* import a message/rfc822 stream */
em_utils_selection_get_message (m->selection, folder);
break;
case DND_DROP_TYPE_TEXT_URI_LIST:
/* import an mbox, maildir, or mh folder? */
em_utils_selection_get_urilist (m->selection, folder);
break;
default:
abort ();
}
g_object_unref (folder);
}
}
static void
folder_tree_drop_async__free (struct _DragDataReceivedAsync *m)
{
if (m->move && m->dest_folder_uri) {
GList *selected_list;
selected_list = g_list_append (NULL, m->dest_folder_uri);
em_folder_tree_set_selected_list (m->folder_tree, selected_list, FALSE);
g_list_free (selected_list);
}
g_object_unref (m->folder_tree);
g_object_unref (m->session);
g_object_unref (m->context);
g_object_unref (m->store);
g_free (m->full_name);
g_free (m->dest_folder_uri);
gtk_selection_data_free (m->selection);
}
static MailMsgInfo folder_tree_drop_async_info = {
sizeof (struct _DragDataReceivedAsync),
(MailMsgDescFunc) folder_tree_drop_async__desc,
(MailMsgExecFunc) folder_tree_drop_async__exec,
(MailMsgDoneFunc) NULL,
(MailMsgFreeFunc) folder_tree_drop_async__free
};
static void
tree_drag_data_action (struct _DragDataReceivedAsync *m)
{
m->move = m->action == GDK_ACTION_MOVE;
mail_msg_unordered_push (m);
}
static void
tree_drag_data_received (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *selection,
guint info,
guint time,
EMFolderTree *folder_tree)
{
GtkTreeViewDropPosition pos;
GtkTreeModel *model;
GtkTreeView *tree_view;
GtkTreePath *dest_path = NULL;
EMailSession *session;
struct _DragDataReceivedAsync *m;
gboolean is_store;
CamelStore *store;
GtkTreeIter iter;
gchar *full_name;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
session = em_folder_tree_get_session (folder_tree);
if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &dest_path, &pos))
return;
/* this means we are receiving no data */
if (gtk_selection_data_get_data (selection) == NULL) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
gtk_tree_path_free (dest_path);
return;
}
if (gtk_selection_data_get_length (selection) == -1) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
gtk_tree_path_free (dest_path);
return;
}
if (!gtk_tree_model_get_iter (model, &iter, dest_path)) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
gtk_tree_path_free (dest_path);
return;
}
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_IS_STORE, &is_store,
COL_STRING_FULL_NAME, &full_name, -1);
/* make sure user isn't try to drop on a placeholder row */
if (full_name == NULL && !is_store) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
gtk_tree_path_free (dest_path);
return;
}
if (info == DND_DROP_TYPE_FOLDER &&
!ask_drop_folder (folder_tree,
(const gchar *) gtk_selection_data_get_data (selection),
full_name, store,
gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE)) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
gtk_tree_path_free (dest_path);
g_free (full_name);
return;
}
m = mail_msg_new (&folder_tree_drop_async_info);
m->folder_tree = g_object_ref (folder_tree);
m->session = g_object_ref (session);
m->context = g_object_ref (context);
m->store = g_object_ref (store);
m->full_name = full_name;
m->dest_folder_uri = NULL;
m->action = gdk_drag_context_get_selected_action (context);
m->info = info;
/* need to copy, goes away once we exit */
m->selection = gtk_selection_data_copy (selection);
tree_drag_data_action (m);
gtk_tree_path_free (dest_path);
}
static gboolean
is_special_local_folder (const gchar *name)
{
return strcmp (name, "Drafts") == 0
|| strcmp (name, "Inbox") == 0
|| strcmp (name, "Outbox") == 0
|| strcmp (name, "Sent") == 0
|| strcmp (name, "Templates") == 0;
}
static GdkAtom
folder_tree_drop_target (EMFolderTree *folder_tree,
GdkDragContext *context,
GtkTreePath *path,
GdkDragAction *actions,
GdkDragAction *suggested_action)
{
EMFolderTreePrivate *p = folder_tree->priv;
gchar *dst_full_name = NULL;
gchar *src_full_name = NULL;
CamelStore *dst_store;
CamelStore *src_store = NULL;
GdkAtom atom = GDK_NONE;
gboolean is_store;
GtkTreeModel *model;
GtkTreeIter iter;
GList *targets;
const gchar *uid;
gboolean src_is_local;
gboolean src_is_vfolder;
gboolean dst_is_vfolder;
guint32 flags = 0;
/* This is a bit of a mess, but should handle all the cases properly */
model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
if (!gtk_tree_model_get_iter (model, &iter, path))
return GDK_NONE;
/* We may override these further down. */
*actions = gdk_drag_context_get_actions (context);
*suggested_action = gdk_drag_context_get_suggested_action (context);
gtk_tree_model_get (
model, &iter,
COL_BOOL_IS_STORE, &is_store,
COL_POINTER_CAMEL_STORE, &dst_store,
COL_STRING_FULL_NAME, &dst_full_name,
COL_UINT_FLAGS, &flags, -1);
uid = camel_service_get_uid (CAMEL_SERVICE (dst_store));
dst_is_vfolder = (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
targets = gdk_drag_context_list_targets (context);
/* Check for special destinations */
/* Don't allow copying/moving into the UNMATCHED vfolder. */
if (dst_is_vfolder)
if (g_strcmp0 (dst_full_name, CAMEL_UNMATCHED_NAME) == 0)
goto done;
/* Don't allow copying/moving into a vTrash folder. */
if (g_strcmp0 (dst_full_name, CAMEL_VTRASH_NAME) == 0)
goto done;
/* Don't allow copying/moving into a vJunk folder. */
if (g_strcmp0 (dst_full_name, CAMEL_VJUNK_NAME) == 0)
goto done;
if (flags & CAMEL_FOLDER_NOSELECT)
goto done;
if (p->drag_row) {
GtkTreePath *src_path = gtk_tree_row_reference_get_path (p->drag_row);
if (src_path) {
guint32 src_flags = 0;
if (gtk_tree_model_get_iter (model, &iter, src_path))
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &src_store,
COL_STRING_FULL_NAME, &src_full_name,
COL_UINT_FLAGS, &src_flags, -1);
/* can't dnd onto itself or below itself - bad things happen,
* no point dragging to where we were either */
if (gtk_tree_path_compare (path, src_path) == 0
|| gtk_tree_path_is_descendant (path, src_path)
|| (gtk_tree_path_is_ancestor (path, src_path)
&& gtk_tree_path_get_depth (path) ==
gtk_tree_path_get_depth (src_path) - 1)) {
gtk_tree_path_free (src_path);
goto done;
}
gtk_tree_path_free (src_path);
if ((src_flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX ||
(src_flags & CAMEL_FOLDER_SYSTEM) != 0) {
/* allow only copy of the Inbox and other system folders */
GdkAtom xfolder;
/* force copy for special local folders */
*suggested_action = GDK_ACTION_COPY;
*actions = GDK_ACTION_COPY;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
goto done;
}
}
}
/* Check for special sources, and vfolder stuff */
if (src_store != NULL && src_full_name != NULL) {
uid = camel_service_get_uid (CAMEL_SERVICE (src_store));
src_is_local =
(g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
src_is_vfolder =
(g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
/* FIXME: this is a total hack, but i think all we can do at present */
/* Check for dragging from special folders which can't be moved/copied */
/* Don't allow moving any of the the special local folders. */
if (src_is_local && is_special_local_folder (src_full_name)) {
GdkAtom xfolder;
/* force copy for special local folders */
*suggested_action = GDK_ACTION_COPY;
*actions = GDK_ACTION_COPY;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
goto done;
}
/* Don't allow copying/moving the UNMATCHED vfolder. */
if (src_is_vfolder)
if (g_strcmp0 (src_full_name, CAMEL_UNMATCHED_NAME) == 0)
goto done;
/* Don't allow copying/moving any vTrash folder. */
if (g_strcmp0 (src_full_name, CAMEL_VTRASH_NAME) == 0)
goto done;
/* Don't allow copying/moving any vJunk folder. */
if (g_strcmp0 (src_full_name, CAMEL_VJUNK_NAME) == 0)
goto done;
/* Don't allow copying/moving any maildir 'inbox'. */
if (g_strcmp0 (src_full_name, ".") == 0)
goto done;
/* Search Folders can only be dropped into other
* Search Folders. */
if (src_is_vfolder) {
/* force move only for vfolders */
*suggested_action = GDK_ACTION_MOVE;
if (dst_is_vfolder) {
GdkAtom xfolder;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
}
goto done;
}
}
/* Can't drag anything but a Search Folder into a Search Folder. */
if (dst_is_vfolder)
goto done;
/* Now we either have a store or a normal folder. */
if (is_store) {
GdkAtom xfolder;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
} else {
gint i;
while (targets != NULL) {
for (i = 0; i < NUM_DROP_TYPES; i++) {
if (targets->data == (gpointer) drop_atoms[i]) {
atom = drop_atoms[i];
goto done;
}
}
targets = targets->next;
}
}
done:
g_free (dst_full_name);
g_free (src_full_name);
return atom;
}
static gboolean
tree_drag_drop (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeViewColumn *column;
GtkTreeView *tree_view;
gint cell_x, cell_y;
GdkDragAction actions;
GdkDragAction suggested_action;
GtkTreePath *path;
GdkAtom target;
tree_view = GTK_TREE_VIEW (folder_tree);
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
if (!gtk_tree_view_get_path_at_pos (
tree_view, x, y, &path, &column, &cell_x, &cell_y))
return FALSE;
target = folder_tree_drop_target (
folder_tree, context, path,
&actions, &suggested_action);
gtk_tree_path_free (path);
return (target != GDK_NONE);
}
static void
tree_drag_end (GtkWidget *widget,
GdkDragContext *context,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
if (priv->drag_row != NULL) {
gtk_tree_row_reference_free (priv->drag_row);
priv->drag_row = NULL;
}
/* FIXME: undo anything done in drag-begin */
}
static void
tree_drag_leave (GtkWidget *widget,
GdkDragContext *context,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeView *tree_view;
tree_view = GTK_TREE_VIEW (folder_tree);
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
gtk_tree_view_set_drag_dest_row (
tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
}
#define SCROLL_EDGE_SIZE 15
static gboolean
tree_autoscroll (EMFolderTree *folder_tree)
{
GtkAdjustment *adjustment;
GtkTreeView *tree_view;
GtkScrollable *scrollable;
GdkRectangle rect;
GdkWindow *window;
GdkDisplay *display;
GdkDeviceManager *device_manager;
GdkDevice *device;
gdouble value;
gint offset, y;
/* Get the y pointer position relative to the treeview. */
tree_view = GTK_TREE_VIEW (folder_tree);
window = gtk_tree_view_get_bin_window (tree_view);
display = gdk_window_get_display (window);
device_manager = gdk_display_get_device_manager (display);
device = gdk_device_manager_get_client_pointer (device_manager);
gdk_window_get_device_position (window, device, NULL, &y, NULL);
/* Rect is in coorinates relative to the scrolled window,
* relative to the treeview. */
gtk_tree_view_get_visible_rect (tree_view, &rect);
/* Move y into the same coordinate system as rect. */
y += rect.y;
/* See if we are near the top edge. */
offset = y - (rect.y + 2 * SCROLL_EDGE_SIZE);
if (offset > 0) {
/* See if we are near the bottom edge. */
offset = y - (rect.y + rect.height - 2 * SCROLL_EDGE_SIZE);
if (offset < 0)
return TRUE;
}
scrollable = GTK_SCROLLABLE (folder_tree);
adjustment = gtk_scrollable_get_vadjustment (scrollable);
value = gtk_adjustment_get_value (adjustment);
gtk_adjustment_set_value (adjustment, MAX (value + offset, 0.0));
return TRUE;
}
static gboolean
tree_autoexpand (EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeView *tree_view;
GtkTreePath *path;
tree_view = GTK_TREE_VIEW (folder_tree);
path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
gtk_tree_view_expand_row (tree_view, path, FALSE);
gtk_tree_path_free (path);
return TRUE;
}
static gboolean
tree_drag_motion (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeViewDropPosition pos;
GtkTreeView *tree_view;
GtkTreeModel *model;
GdkDragAction actions;
GdkDragAction suggested_action;
GdkDragAction chosen_action = 0;
GtkTreePath *path = NULL;
GtkTreeIter iter;
GdkAtom target;
gint i;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos))
return FALSE;
if (priv->autoscroll_id == 0)
priv->autoscroll_id = g_timeout_add (
150, (GSourceFunc) tree_autoscroll, folder_tree);
gtk_tree_model_get_iter (model, &iter, path);
if (gtk_tree_model_iter_has_child (model, &iter) &&
!gtk_tree_view_row_expanded (tree_view, path)) {
if (priv->autoexpand_id != 0) {
GtkTreePath *autoexpand_path;
autoexpand_path = gtk_tree_row_reference_get_path (
priv->autoexpand_row);
if (gtk_tree_path_compare (autoexpand_path, path) != 0) {
/* row changed, restart timer */
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row =
gtk_tree_row_reference_new (model, path);
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = g_timeout_add (
600, (GSourceFunc)
tree_autoexpand, folder_tree);
}
gtk_tree_path_free (autoexpand_path);
} else {
priv->autoexpand_id = g_timeout_add (
600, (GSourceFunc)
tree_autoexpand, folder_tree);
priv->autoexpand_row =
gtk_tree_row_reference_new (model, path);
}
} else if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
target = folder_tree_drop_target (
folder_tree, context, path,
&actions, &suggested_action);
for (i = 0; target != GDK_NONE && i < NUM_DROP_TYPES; i++) {
if (drop_atoms[i] != target)
continue;
switch (i) {
case DND_DROP_TYPE_UID_LIST:
case DND_DROP_TYPE_FOLDER:
chosen_action = suggested_action;
if (chosen_action == GDK_ACTION_COPY &&
(actions & GDK_ACTION_MOVE))
chosen_action = GDK_ACTION_MOVE;
gtk_tree_view_set_drag_dest_row (
tree_view, path,
GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
break;
default:
gtk_tree_view_set_drag_dest_row (
tree_view, path,
GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
chosen_action = suggested_action;
break;
}
break;
}
gdk_drag_status (context, chosen_action, time);
gtk_tree_path_free (path);
return chosen_action != 0;
}
void
em_folder_tree_enable_drag_and_drop (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
static gint setup = 0;
gint i;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
if (!setup) {
for (i = 0; i < NUM_DRAG_TYPES; i++)
drag_atoms[i] = gdk_atom_intern (drag_types[i].target, FALSE);
for (i = 0; i < NUM_DROP_TYPES; i++)
drop_atoms[i] = gdk_atom_intern (drop_types[i].target, FALSE);
setup = 1;
}
gtk_drag_source_set (
GTK_WIDGET (tree_view), GDK_BUTTON1_MASK,drag_types,
NUM_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
gtk_drag_dest_set (
GTK_WIDGET (tree_view), GTK_DEST_DEFAULT_ALL, drop_types,
NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
g_signal_connect (
tree_view, "drag-begin",
G_CALLBACK (tree_drag_begin), folder_tree);
g_signal_connect (
tree_view, "drag-data-get",
G_CALLBACK (tree_drag_data_get), folder_tree);
g_signal_connect (
tree_view, "drag-data-received",
G_CALLBACK (tree_drag_data_received), folder_tree);
g_signal_connect (
tree_view, "drag-drop",
G_CALLBACK (tree_drag_drop), folder_tree);
g_signal_connect (
tree_view, "drag-end",
G_CALLBACK (tree_drag_end), folder_tree);
g_signal_connect (
tree_view, "drag-leave",
G_CALLBACK (tree_drag_leave), folder_tree);
g_signal_connect (
tree_view, "drag-motion",
G_CALLBACK (tree_drag_motion), folder_tree);
}
void
em_folder_tree_set_excluded (EMFolderTree *folder_tree,
guint32 flags)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
folder_tree->priv->excluded = flags;
}
void
em_folder_tree_set_excluded_func (EMFolderTree *folder_tree,
EMFTExcludeFunc exclude,
gpointer data)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
g_return_if_fail (exclude != NULL);
folder_tree->priv->excluded_func = exclude;
folder_tree->priv->excluded_data = data;
}
GList *
em_folder_tree_get_selected_uris (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GtkTreeModel *model;
GList *list = NULL, *rows, *l;
GSList *sl;
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
/* at first, add lost uris */
for (sl = folder_tree->priv->select_uris; sl; sl = g_slist_next (sl)) {
const gchar *uri;
uri = ((struct _selected_uri *) sl->data)->uri;
list = g_list_append (list, g_strdup (uri));
}
rows = gtk_tree_selection_get_selected_rows (selection, &model);
for (l = rows; l; l = g_list_next (l)) {
GtkTreeIter iter;
GtkTreePath *path = l->data;
if (gtk_tree_model_get_iter (model, &iter, path)) {
CamelStore *store;
gchar *folder_name;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &folder_name, -1);
if (CAMEL_IS_STORE (store) && folder_name != NULL) {
gchar *folder_uri;
folder_uri = e_mail_folder_uri_build (
store, folder_name);
list = g_list_prepend (list, folder_uri);
}
g_free (folder_name);
}
gtk_tree_path_free (path);
}
g_list_free (rows);
return g_list_reverse (list);
}
static void
get_selected_uris_path_iterate (GtkTreeModel *model,
GtkTreePath *treepath,
GtkTreeIter *iter,
gpointer data)
{
GList **list = (GList **) data;
gchar *full_name;
gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name, -1);
*list = g_list_append (*list, full_name);
}
GList *
em_folder_tree_get_selected_paths (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GList *list = NULL;
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_selected_foreach (
selection, get_selected_uris_path_iterate, &list);
return list;
}
void
em_folder_tree_set_selected_list (EMFolderTree *folder_tree,
GList *list,
gboolean expand_only)
{
EMFolderTreePrivate *priv = folder_tree->priv;
EMailSession *session;
session = em_folder_tree_get_session (folder_tree);
/* FIXME: need to remove any currently selected stuff? */
if (!expand_only)
folder_tree_clear_selected_list (folder_tree);
for (; list; list = list->next) {
CamelStore *store;
struct _selected_uri *u;
const gchar *folder_uri;
const gchar *uid;
gchar *folder_name;
gchar *expand_key;
gchar *end;
gboolean success;
/* This makes sure all our parents up to the root are
* expanded. */
folder_uri = list->data;
success = e_mail_folder_uri_parse (
CAMEL_SESSION (session), folder_uri,
&store, &folder_name, NULL);
if (!success)
continue;
uid = camel_service_get_uid (CAMEL_SERVICE (store));
expand_key = g_strdup_printf ("%s/%s", uid, folder_name);
g_free (folder_name);
u = g_malloc0 (sizeof (*u));
u->uri = g_strdup (folder_uri);
u->service = CAMEL_SERVICE (store); /* takes ownership */
u->key = g_strdup (expand_key);
if (!expand_only) {
g_hash_table_insert (
priv->select_uris_table, u->key, u);
priv->select_uris =
g_slist_append (priv->select_uris, u);
}
end = strrchr (expand_key, '/');
do {
folder_tree_expand_node (expand_key, folder_tree);
*end = 0;
end = strrchr (expand_key, '/');
} while (end);
if (expand_only)
folder_tree_free_select_uri (u);
g_free (expand_key);
}
}
#if 0
static void
dump_fi (CamelFolderInfo *fi,
gint depth)
{
gint i;
while (fi != NULL) {
for (i = 0; i < depth; i++)
fputs (" ", stdout);
printf ("path='%s'; full_name='%s'\n", fi->path, fi->full_name);
if (fi->child)
dump_fi (fi->child, depth + 1);
fi = fi->sibling;
}
}
#endif
void
em_folder_tree_set_selected (EMFolderTree *folder_tree,
const gchar *uri,
gboolean expand_only)
{
GList *l = NULL;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
if (uri && uri[0])
l = g_list_append (l, (gpointer) uri);
em_folder_tree_set_selected_list (folder_tree, l, expand_only);
g_list_free (l);
}
void
em_folder_tree_select_next_path (EMFolderTree *folder_tree,
gboolean skip_read_folders)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter, parent, child;
GtkTreePath *current_path, *path = NULL;
guint unread = 0;
EMFolderTreePrivate *priv = folder_tree->priv;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
current_path = gtk_tree_model_get_path (model, &iter);
do {
if (gtk_tree_model_iter_has_child (model, &iter)) {
gtk_tree_model_iter_children (model, &child, &iter);
path = gtk_tree_model_get_path (model, &child);
iter = child;
} else {
while (1) {
gboolean has_parent;
has_parent = gtk_tree_model_iter_parent (
model, &parent, &iter);
if (gtk_tree_model_iter_next (model, &iter)) {
path = gtk_tree_model_get_path (model, &iter);
break;
} else {
if (has_parent) {
iter = parent;
} else {
/* Reached end. Wrapup*/
gtk_tree_model_get_iter_first (model, &iter);
path = gtk_tree_model_get_path (model, &iter);
break;
}
}
}
}
gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
/* TODO : Flags here for better options */
} while (skip_read_folders && unread <=0 &&
gtk_tree_path_compare (current_path, path));
}
if (path) {
if (!gtk_tree_view_row_expanded (tree_view, path))
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_selection_select_path (selection, path);
if (!priv->cursor_set) {
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
priv->cursor_set = TRUE;
}
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5f, 0.0f);
}
return;
}
static gboolean
folder_tree_descend (GtkTreeModel *model,
GtkTreeIter *iter,
GtkTreeIter *root)
{
GtkTreeIter parent;
gint n_children;
/* Finds the rightmost descendant of the given root. */
if (root == NULL) {
n_children = gtk_tree_model_iter_n_children (model, NULL);
/* This will invalidate the iterator and return FALSE. */
if (n_children == 0)
return gtk_tree_model_get_iter_first (model, iter);
gtk_tree_model_iter_nth_child (
model, &parent, NULL, n_children - 1);
} else
parent = *root;
n_children = gtk_tree_model_iter_n_children (model, &parent);
while (n_children > 0) {
GtkTreeIter child;
gtk_tree_model_iter_nth_child (
model, &child, &parent, n_children - 1);
parent = child;
n_children = gtk_tree_model_iter_n_children (model, &parent);
}
*iter = parent;
return TRUE;
}
void
em_folder_tree_select_prev_path (EMFolderTree *folder_tree,
gboolean skip_read_folders)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreePath *path = NULL;
GtkTreePath *sentinel;
GtkTreeIter iter;
guint unread = 0;
EMFolderTreePrivate *priv = folder_tree->priv;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
/* Nothing selected means nothing to do. */
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return;
/* This prevents us from looping over the model indefinitely,
* looking for unread messages when there are none. */
sentinel = gtk_tree_model_get_path (model, &iter);
do {
GtkTreeIter descendant;
if (path != NULL)
gtk_tree_path_free (path);
path = gtk_tree_model_get_path (model, &iter);
if (gtk_tree_path_prev (path)) {
gtk_tree_model_get_iter (model, &iter, path);
folder_tree_descend (model, &descendant, &iter);
gtk_tree_path_free (path);
path = gtk_tree_model_get_path (model, &descendant);
} else if (gtk_tree_path_get_depth (path) > 1) {
gtk_tree_path_up (path);
} else {
folder_tree_descend (model, &descendant, NULL);
gtk_tree_path_free (path);
path = gtk_tree_model_get_path (model, &descendant);
}
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
} while (skip_read_folders && unread <= 0 &&
gtk_tree_path_compare (path, sentinel) != 0);
if (!gtk_tree_view_row_expanded (tree_view, path))
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_selection_select_path (selection, path);
if (!priv->cursor_set) {
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
priv->cursor_set = TRUE;
}
gtk_tree_view_scroll_to_cell (
tree_view, path, NULL, TRUE, 0.5f, 0.0f);
gtk_tree_path_free (sentinel);
gtk_tree_path_free (path);
}
void
em_folder_tree_edit_selected (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path = NULL;
GtkTreeIter iter;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
column = gtk_tree_view_get_column (tree_view, 0);
selection = gtk_tree_view_get_selection (tree_view);
renderer = folder_tree->priv->text_renderer;
if (gtk_tree_selection_get_selected (selection, &model, &iter))
path = gtk_tree_model_get_path (model, &iter);
if (path == NULL)
return;
/* Make the text cell renderer editable, but only temporarily.
* We don't want editing to be activated by simply clicking on
* the folder name. Too easy for accidental edits to occur. */
g_object_set (renderer, "editable", TRUE, NULL);
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_view_set_cursor_on_cell (
tree_view, path, column, renderer, TRUE);
g_object_set (renderer, "editable", FALSE, NULL);
gtk_tree_path_free (path);
}
gboolean
em_folder_tree_get_selected (EMFolderTree *folder_tree,
CamelStore **out_store,
gchar **out_folder_name)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
CamelStore *store = NULL;
gchar *folder_name = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return FALSE;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &folder_name, -1);
/* We should always get a valid store. */
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
/* If a store is selected, the folder name will be NULL.
* Treat this as though nothing is selected, so that callers
* can assume a TRUE return value means a folder is selected. */
if (folder_name == NULL)
return FALSE;
/* FIXME We really should be storing the CamelStore as a GObject
* so it gets referenced. The pointer type is a relic of
* days before Camel used GObject. */
if (out_store != NULL)
*out_store = g_object_ref (store);
if (out_folder_name != NULL)
*out_folder_name = folder_name;
else
g_free (folder_name);
return TRUE;
}
gboolean
em_folder_tree_store_root_selected (EMFolderTree *folder_tree,
CamelStore **out_store)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
CamelStore *store = NULL;
gboolean is_store = FALSE;
g_return_val_if_fail (folder_tree != NULL, FALSE);
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return FALSE;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_IS_STORE, &is_store, -1);
/* We should always get a valid store. */
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
if (!is_store)
return FALSE;
if (out_store != NULL)
*out_store = g_object_ref (store);
return TRUE;
}
gchar *
em_folder_tree_get_selected_uri (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
CamelStore *store;
gchar *folder_name;
gchar *folder_uri = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return NULL;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &folder_name, -1);
if (CAMEL_IS_STORE (store) && folder_name != NULL)
folder_uri = e_mail_folder_uri_build (store, folder_name);
else if (CAMEL_IS_STORE (store))
folder_uri = e_mail_folder_uri_build (store, "");
g_free (folder_name);
return folder_uri;
}
CamelStore *
em_folder_tree_get_selected_store (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
CamelStore *store = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
/* Don't use em_folder_tree_get_selected() here because we
* want this to work whether a folder or store is selected. */
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_selected (selection, &model, &iter))
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store, -1);
return CAMEL_IS_STORE (store) ? store : NULL;
}
void
em_folder_tree_set_skip_double_click (EMFolderTree *folder_tree,
gboolean skip)
{
folder_tree->priv->skip_double_click = skip;
}
/* stores come first, then by uri */
static gint
sort_by_store_and_uri (gconstpointer name1,
gconstpointer name2)
{
const gchar *n1 = name1, *n2 = name2;
gboolean is_store1, is_store2;
if (n1 == NULL || n2 == NULL) {
if (n1 == n2)
return 0;
else
return n1 ? -1 : 1;
}
is_store1 = g_str_has_prefix (n1, "Store ");
is_store2 = g_str_has_prefix (n2, "Store ");
if ((is_store1 || is_store2) && (!is_store1 || !is_store2)) {
return is_store1 ? -1 : 1;
}
return strcmp (n1, n2);
}
/* restores state of a tree (collapsed/expanded) as stores in the given key_file */
void
em_folder_tree_restore_state (EMFolderTree *folder_tree,
GKeyFile *key_file)
{
EMFolderTreeModel *folder_tree_model;
EMailSession *session;
GtkTreeModel *tree_model;
GtkTreeView *tree_view;
GtkTreeIter iter;
gboolean valid;
gchar **groups_arr;
GSList *groups, *group;
gint ii;
/* Make sure we have a key file to restore state from. */
if (key_file == NULL)
return;
tree_view = GTK_TREE_VIEW (folder_tree);
tree_model = gtk_tree_view_get_model (tree_view);
folder_tree_model = EM_FOLDER_TREE_MODEL (tree_model);
session = em_folder_tree_model_get_session (folder_tree_model);
g_return_if_fail (E_IS_MAIL_SESSION (session));
/* Set the initial folder tree expanded state in two stages:
*
* 1) Iterate over the "Store" and "Folder" state file groups
* and apply the "Expanded" keys where possible.
*
* 2) Iterate over the top-level nodes in the folder tree
* (these are all stores) and expand those that have no
* corresponding "Expanded" key in the state file. This
* ensures that new stores are expanded by default.
*/
/* Stage 1 */
/* Collapse all so we have a clean slate. */
gtk_tree_view_collapse_all (tree_view);
groups_arr = g_key_file_get_groups (key_file, NULL);
groups = NULL;
for (ii = 0; groups_arr[ii] != NULL; ii++) {
groups = g_slist_prepend (groups, groups_arr[ii]);
}
groups = g_slist_sort (groups, sort_by_store_and_uri);
for (group = groups; group != NULL; group = group->next) {
GtkTreeRowReference *reference = NULL;
CamelStore *store = NULL;
const gchar *group_name = group->data;
const gchar *key = STATE_KEY_EXPANDED;
gchar *folder_name = NULL;
gboolean expanded = FALSE;
gboolean success = FALSE;
if (g_str_has_prefix (group_name, "Store ")) {
CamelService *service;
const gchar *uid = group_name + 6;
service = camel_session_ref_service (
CAMEL_SESSION (session), uid);
if (CAMEL_IS_STORE (service)) {
store = g_object_ref (service);
success = TRUE;
}
if (service != NULL)
g_object_unref (service);
expanded = TRUE;
} else if (g_str_has_prefix (group_name, "Folder ")) {
const gchar *uri = group_name + 7;
success = e_mail_folder_uri_parse (
CAMEL_SESSION (session), uri,
&store, &folder_name, NULL);
expanded = FALSE;
}
if (g_key_file_has_key (key_file, group_name, key, NULL))
expanded = g_key_file_get_boolean (
key_file, group_name, key, NULL);
if (expanded && success) {
EMFolderTreeModelStoreInfo *si;
si = em_folder_tree_model_lookup_store_info (
folder_tree_model, store);
if (si != NULL) {
if (folder_name != NULL)
reference = g_hash_table_lookup (
si->full_hash, folder_name);
else
reference = si->row;
}
}
if (gtk_tree_row_reference_valid (reference)) {
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_row_reference_get_path (reference);
gtk_tree_model_get_iter (tree_model, &iter, path);
gtk_tree_view_expand_row (tree_view, path, FALSE);
gtk_tree_path_free (path);
}
if (store != NULL)
g_object_unref (store);
g_free (folder_name);
}
g_slist_free (groups);
g_strfreev (groups_arr);
/* Stage 2 */
valid = gtk_tree_model_get_iter_first (tree_model, &iter);
while (valid) {
CamelStore *store;
CamelService *service;
const gchar *key = STATE_KEY_EXPANDED;
const gchar *uid;
gchar *group_name;
gtk_tree_model_get (
tree_model, &iter,
COL_POINTER_CAMEL_STORE, &store, -1);
if (!CAMEL_IS_STORE (store))
goto next;
service = CAMEL_SERVICE (store);
uid = camel_service_get_uid (service);
group_name = g_strdup_printf ("Store %s", uid);
/* Expand stores that have no "Expanded" key. */
if (!g_key_file_has_key (key_file, group_name, key, NULL)) {
GtkTreePath *path;
path = gtk_tree_model_get_path (tree_model, &iter);
gtk_tree_view_expand_row (tree_view, path, FALSE);
gtk_tree_path_free (path);
}
g_free (group_name);
next:
valid = gtk_tree_model_iter_next (tree_model, &iter);
}
}