Files
evolution/mail/mail-display.c
Jon Trowbridge 6642d01f14 Add ETable magic for our new "Needs Reply" column. (The next few entries
2001-12-11  Jon Trowbridge  <trow@ximian.com>

	* message-list.etspec: Add ETable magic for our new "Needs Reply"
	column.  (The next few entries are for bug #90)

	* message-list.h: Add COL_NEED_REPLY.

	* message-list.c: Move mail_need_reply_xpm to the end of
	states_pixmaps.
	(ml_duplicate_value): Handle COL_NEED_REPLY.
	(ml_free_value): Handle COL_NEED_REPLY.
	(ml_initialize_value): Handle COL_NEED_REPLY.
	(ml_value_is_empty): Handle COL_NEED_REPLY.  Added
	needs_reply_map[] array.
	(ml_value_to_string): Handle COL_NEED_REPLY.
	(ml_tree_value_at): Fix magic numbers, undoing my changes from the
	otehr day.  Add handler for COL_NEED_REPLY.
	(message_list_create_extras): Attach icons for COL_NEED_REPLY.
	(on_click): Undo my previous changes to display need-reply status
	in COL_MESSAGE_STATUS.  Add handing for COL_NEED_REPLY.

	* mail.h: Change mail_format_mime_message, mail_format_raw_message
	and the MailMimeHandlerFn typedef to take GtkHTML and
	GtkHTMLStream args, as per our changes in mail-format.c.

	* mail-format.c: Giant refactoring.  Remove the assumption
	throughout that we will always want to render into the GtkHTML
	object contained in the MailDisplay.  Instead, always pass in the
	GtkHTML and GtkHTMLStream that we want to write to.  Also, ignore
	theme work-arounds if the printing flag is set.  (This and what
	follows fixes bug #82)

	* mail-display.h: Remove GtkHTMLStream *stream from MailDisplay.
	We don't need it anymore.

	* mail-display.c (mail_display_render): Added.  Breaks the code
	that renders the message into the GtkHTML object out of
	mail_display_redisplay.
	(mail_display_redisplay): Call mail_display_render.
	(mail_display_init): Remove reference to ->stream.
	(mail_display_new): Remove reference to ->stream.

	* mail-callbacks.c (do_mail_print): Create a new GtkHTML to render
	our printed version into (via the new function
	mail_display_render.  Set the MailDisplay's printing flag to TRUE
	before we render, and set it back to FALSE afterwards.
	(do_mail_fetch_and_print): If the preview pane isn't open when we
	try to print, fetch the message before printing.
	(print_msg): Call do_mail_fetch_and_print.
	(print_preview_msg): Call do_mail_fetch_and_print.

	* folder-browser-ui.c: Remove "PrintMessage" and
	"PrintPreviewMessage" from message_pane_enables... these now work
	when the preview pane is closed.  Disable printing if multiple
	messages are selected.

svn path=/trunk/; revision=14981
2001-12-11 18:03:44 +00:00

2126 lines
56 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* mail-display.c: Mail display widget
*
* Author:
* Miguel de Icaza
* Bertrand Guiheneuf (bg@aful.org)
*
* (C) 2000 Ximian, Inc.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <libgnorba/gnorba.h>
#include <libgnomevfs/gnome-vfs-mime-info.h>
#include <libgnomevfs/gnome-vfs-mime-handlers.h>
#include <libgnomevfs/gnome-vfs.h>
#include <bonobo/bonobo-control-frame.h>
#include <bonobo/bonobo-stream-memory.h>
#include <bonobo/bonobo-ui-toolbar-icon.h>
#include <bonobo/bonobo-widget.h>
#include <bonobo/bonobo-socket.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk-pixbuf/gdk-pixbuf-loader.h>
#include <gal/util/e-util.h>
#include <gal/widgets/e-popup-menu.h>
#include <gal/widgets/e-unicode.h>
#include <gtk/gtkinvisible.h>
#include <gtkhtml/gtkhtml-embedded.h>
#include <gtkhtml/htmlengine.h> /* XXX */
#include <gtkhtml/htmlobject.h> /* XXX */
#include <gtkhtml/htmltext.h> /* XXX */
#include <gtkhtml/htmlinterval.h> /* XXX */
#include <gtkhtml/gtkhtml-stream.h>
#include "e-util/e-html-utils.h"
#include "e-util/e-mktemp.h"
#include "addressbook/backend/ebook/e-book-util.h"
#include "e-searching-tokenizer.h"
#include "folder-browser-factory.h"
#include "mail-stream-gtkhtml.h"
#include "mail-display.h"
#include "mail-config.h"
#include "mail-ops.h"
#include "mail-mt.h"
#include "mail.h"
#include "art/empty.xpm"
#define PARENT_TYPE (gtk_vbox_get_type ())
static GtkObjectClass *mail_display_parent_class;
struct _PixbufLoader {
CamelDataWrapper *wrapper; /* The data */
CamelStream *mstream;
GdkPixbufLoader *loader;
GtkHTMLEmbedded *eb;
char *type; /* Type of data, in case the conversion fails */
char *cid; /* Strdupped on creation, but not freed until
the hashtable is destroyed */
GtkWidget *pixmap;
guint32 destroy_id;
};
static GHashTable *thumbnail_cache = NULL;
static gchar *save_pathname = NULL; /* preserves last directory in save dialog */
/*----------------------------------------------------------------------*
* Callbacks
*----------------------------------------------------------------------*/
static void
write_data_written(CamelMimePart *part, char *name, int done, void *data)
{
int *ret = data;
/* should we popup a dialogue to say its done too? */
*ret = done;
}
static gboolean
write_data_to_file (CamelMimePart *part, const char *name, gboolean unique)
{
int fd;
int ret = FALSE;
g_return_val_if_fail (CAMEL_IS_MIME_PART (part), FALSE);
fd = open (name, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd == -1 && errno == EEXIST && !unique) {
GtkWidget *dlg;
GtkWidget *text;
dlg = gnome_dialog_new (_("Overwrite file?"),
GNOME_STOCK_BUTTON_YES,
GNOME_STOCK_BUTTON_NO,
NULL);
text = gtk_label_new (_("A file by that name already exists.\nOverwrite it?"));
gtk_box_pack_start (GTK_BOX (GNOME_DIALOG (dlg)->vbox), text, TRUE, TRUE, 4);
gtk_window_set_policy(GTK_WINDOW(dlg), FALSE, TRUE, FALSE);
gtk_widget_show (text);
if (gnome_dialog_run_and_close (GNOME_DIALOG (dlg)) != 0)
return FALSE;
}
if (fd != -1)
close (fd);
/* should this have progress of what its doing? */
mail_msg_wait (mail_save_part (part, name, write_data_written, &ret));
return ret;
}
static char *
make_safe_filename (const char *prefix,CamelMimePart *part)
{
const char *name = NULL;
char *safe, *p;
if (part) {
name = camel_mime_part_get_filename (part);
}
if (!name) {
/* This is a filename. Translators take note. */
name = _("attachment");
}
p = strrchr (name, '/');
if (p)
safe = g_strdup_printf ("%s%s", prefix, p);
else
safe = g_strdup_printf ("%s/%s", prefix, name);
p = strrchr (safe, '/') + 1;
if (p)
e_filename_make_safe (p);
return safe;
}
static void
save_data_cb (GtkWidget *widget, gpointer user_data)
{
GtkFileSelection *file_select = (GtkFileSelection *)
gtk_widget_get_ancestor (widget, GTK_TYPE_FILE_SELECTION);
gchar *p;
/* uh, this doesn't really feel right, but i dont know what to do better */
gtk_widget_hide (GTK_WIDGET (file_select));
write_data_to_file (user_data,
gtk_file_selection_get_filename (file_select),
FALSE);
/* preserve the pathname */
g_free(save_pathname);
save_pathname = g_strdup(gtk_file_selection_get_filename(file_select));
if((p = strrchr(save_pathname, '/')) != NULL)
p[0] = 0;
else {
g_free(save_pathname);
save_pathname = NULL;
}
gtk_widget_destroy (GTK_WIDGET (file_select));
}
static void
save_destroy_cb (GtkWidget *widget, CamelMimePart *part)
{
camel_object_unref (CAMEL_OBJECT (part));
}
static gboolean
idle_redisplay (gpointer data)
{
MailDisplay *md = data;
md->idle_id = 0;
mail_display_redisplay (md, FALSE);
return FALSE;
}
void
mail_display_queue_redisplay (MailDisplay *md)
{
if (!md->idle_id) {
md->idle_id = g_idle_add_full (G_PRIORITY_LOW, idle_redisplay,
md, NULL);
}
}
static void
mail_display_jump_to_anchor (MailDisplay *md, const char *url)
{
char *anchor = strstr (url, "#");
g_return_if_fail (anchor != NULL);
if (anchor)
gtk_html_jump_to_anchor (md->html, anchor + 1);
}
static void
on_link_clicked (GtkHTML *html, const char *url, MailDisplay *md)
{
if (!g_strncasecmp (url, "news:", 5) ||
!g_strncasecmp (url, "nntp:", 5))
g_warning ("Can't handle news URLs yet.");
else if (!g_strncasecmp (url, "mailto:", 7))
send_to_url (url);
else if (*url == '#')
mail_display_jump_to_anchor (md, url);
else
gnome_url_show (url);
}
static void
save_part (CamelMimePart *part)
{
GtkFileSelection *file_select;
char *filename;
g_return_if_fail (part != NULL);
camel_object_ref (CAMEL_OBJECT (part));
if (save_pathname == NULL)
save_pathname = g_strdup (g_get_home_dir ());
filename = make_safe_filename (save_pathname, part);
file_select = GTK_FILE_SELECTION (
gtk_file_selection_new (_("Save Attachment")));
gtk_file_selection_set_filename (file_select, filename);
/* set the GtkEntry with the locale filename by breaking abstraction */
e_utf8_gtk_entry_set_text (GTK_ENTRY (file_select->selection_entry), g_basename (filename));
g_free (filename);
gtk_signal_connect (GTK_OBJECT (file_select->ok_button), "clicked",
GTK_SIGNAL_FUNC (save_data_cb), part);
gtk_signal_connect_object (GTK_OBJECT (file_select->cancel_button),
"clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (file_select));
gtk_signal_connect (GTK_OBJECT (file_select), "destroy",
GTK_SIGNAL_FUNC (save_destroy_cb), part);
gtk_widget_show (GTK_WIDGET (file_select));
}
static void
save_cb (GtkWidget *widget, gpointer user_data)
{
CamelMimePart *part = gtk_object_get_data (GTK_OBJECT (user_data), "CamelMimePart");
save_part (part);
}
static void
launch_cb (GtkWidget *widget, gpointer user_data)
{
CamelMimePart *part = gtk_object_get_data (user_data, "CamelMimePart");
MailMimeHandler *handler;
GList *apps, *children, *c;
GnomeVFSMimeApplication *app;
char *command, *filename;
const char *tmpdir;
handler = mail_lookup_handler (gtk_object_get_data (user_data, "mime_type"));
g_return_if_fail (handler != NULL && handler->applications != NULL);
/* Yum. Too bad EPopupMenu doesn't allow per-item closures. */
children = gtk_container_children (GTK_CONTAINER (widget->parent));
g_return_if_fail (children != NULL && children->next != NULL && children->next->next != NULL);
for (c = children->next->next, apps = handler->applications; c && apps; c = c->next, apps = apps->next) {
if (c->data == widget)
break;
}
g_list_free (children);
g_return_if_fail (c != NULL && apps != NULL);
app = apps->data;
tmpdir = e_mkdtemp ("evolution.XXXXXX");
if (!tmpdir) {
char *msg = g_strdup_printf (_("Could not create temporary "
"directory: %s"),
g_strerror (errno));
gnome_error_dialog (msg);
g_free (msg);
return;
}
filename = make_safe_filename (tmpdir, part);
if (!write_data_to_file (part, filename, TRUE)) {
g_free (filename);
return;
}
command = g_strdup_printf ("%s %s%s &", app->command,
app->expects_uris == GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS ? "file:" : "",
filename);
system (command);
g_free (command);
g_free (filename);
}
static void
inline_cb (GtkWidget *widget, gpointer user_data)
{
MailDisplay *md = gtk_object_get_data (user_data, "MailDisplay");
CamelMimePart *part = gtk_object_get_data (user_data, "CamelMimePart");
mail_part_toggle_displayed (part, md);
mail_display_queue_redisplay (md);
}
static void
button_press (GtkWidget *widget, CamelMimePart *part)
{
MailDisplay *md;
md = gtk_object_get_data (GTK_OBJECT (widget), "MailDisplay");
if (md == NULL) {
g_warning ("No MailDisplay on button!");
return;
}
mail_part_toggle_displayed (part, md);
mail_display_queue_redisplay (md);
}
static gboolean
pixmap_press (GtkWidget *widget, GdkEventButton *event, EScrollFrame *user_data)
{
EPopupMenu *menu;
EPopupMenu save_item = { N_("Save to Disk..."), NULL,
GTK_SIGNAL_FUNC (save_cb), NULL, 0 };
EPopupMenu view_item = { N_("View Inline"), NULL,
GTK_SIGNAL_FUNC (inline_cb), NULL, 2 };
EPopupMenu open_item = { N_("Open in %s..."), NULL,
GTK_SIGNAL_FUNC (launch_cb), NULL, 1 };
MailDisplay *md;
CamelMimePart *part;
MailMimeHandler *handler;
int mask = 0, i, nitems;
#ifdef USE_OLD_DISPLAY_STYLE
if (event->button != 3) {
gtk_propagate_event (GTK_WIDGET (user_data),
(GdkEvent *)event);
return TRUE;
}
#endif
if (event->button != 1 && event->button != 3) {
gtk_propagate_event (GTK_WIDGET (user_data),
(GdkEvent *)event);
return TRUE;
}
/* Stop the signal, since we don't want the button's class method to
mess up our popup. */
gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "button_press_event");
part = gtk_object_get_data (GTK_OBJECT (widget), "CamelMimePart");
handler = mail_lookup_handler (gtk_object_get_data (GTK_OBJECT (widget),
"mime_type"));
if (handler && handler->applications)
nitems = g_list_length (handler->applications) + 2;
else
nitems = 3;
menu = g_new0 (EPopupMenu, nitems + 1);
/* Save item */
memcpy (&menu[0], &save_item, sizeof (menu[0]));
menu[0].name = _(menu[0].name);
/* Inline view item */
memcpy (&menu[1], &view_item, sizeof (menu[1]));
if (handler && handler->builtin) {
md = gtk_object_get_data (GTK_OBJECT (widget), "MailDisplay");
if (!mail_part_is_displayed_inline (part, md)) {
if (handler->component) {
OAF_Property *prop;
char *name;
prop = oaf_server_info_prop_find (
handler->component, "name");
if (!prop) {
prop = oaf_server_info_prop_find (
handler->component,
"description");
}
if (prop && prop->v._d == OAF_P_STRING)
name = prop->v._u.value_string;
else
name = "bonobo";
menu[1].name = g_strdup_printf (
_("View Inline (via %s)"), name);
} else
menu[1].name = g_strdup (_(menu[1].name));
} else
menu[1].name = g_strdup (_("Hide"));
} else {
menu[1].name = g_strdup (_(menu[1].name));
mask |= 2;
}
/* External views */
if (handler && handler->applications) {
GnomeVFSMimeApplication *app;
GList *apps;
int i;
apps = handler->applications;
for (i = 2; i < nitems; i++, apps = apps->next) {
app = apps->data;
memcpy (&menu[i], &open_item, sizeof (menu[i]));
menu[i].name = g_strdup_printf (_(menu[i].name), app->name);
}
} else {
memcpy (&menu[2], &open_item, sizeof (menu[2]));
menu[2].name = g_strdup_printf (_(menu[2].name),
_("External Viewer"));
mask |= 1;
}
e_popup_menu_run (menu, (GdkEvent *)event, mask, 0, widget);
for (i = 1; i < nitems; i++)
g_free (menu[i].name);
g_free (menu);
return TRUE;
}
static GdkPixbuf *
pixbuf_for_mime_type (const char *mime_type)
{
const char *icon_name;
char *filename = NULL;
GdkPixbuf *pixbuf = NULL;
icon_name = gnome_vfs_mime_get_value (mime_type, "icon-filename");
if (icon_name) {
if (*icon_name == '/') {
pixbuf = gdk_pixbuf_new_from_file (icon_name);
if (pixbuf)
return pixbuf;
}
filename = gnome_pixmap_file (icon_name);
if (!filename) {
char *fm_icon;
fm_icon = g_strdup_printf ("nautilus/%s", icon_name);
filename = gnome_pixmap_file (fm_icon);
if (!filename) {
fm_icon = g_strdup_printf ("mc/%s", icon_name);
filename = gnome_pixmap_file (fm_icon);
}
g_free (fm_icon);
}
}
if (filename) {
pixbuf = gdk_pixbuf_new_from_file (filename);
g_free (filename);
}
if (!pixbuf) {
filename = gnome_pixmap_file ("gnome-unknown.png");
if (filename) {
pixbuf = gdk_pixbuf_new_from_file (filename);
g_free (filename);
} else {
g_warning ("Could not get any icon for %s!",mime_type);
pixbuf = gdk_pixbuf_new_from_xpm_data (
(const char **)empty_xpm);
}
}
return pixbuf;
}
static gboolean
pixbuf_uncache (gpointer key)
{
GdkPixbuf *pixbuf;
pixbuf = g_hash_table_lookup (thumbnail_cache, key);
gdk_pixbuf_unref (pixbuf);
g_hash_table_remove (thumbnail_cache, key);
g_free (key);
return FALSE;
}
static gint
pixbuf_gen_idle (struct _PixbufLoader *pbl)
{
GdkPixbuf *pixbuf, *mini;
gboolean error = FALSE;
char tmp[4096];
int len, width, height, ratio;
gpointer orig_key;
/* Get the pixbuf from the cache */
if (g_hash_table_lookup_extended (thumbnail_cache, pbl->cid,
&orig_key, (gpointer *)&mini)) {
width = gdk_pixbuf_get_width (mini);
height = gdk_pixbuf_get_height (mini);
bonobo_ui_toolbar_icon_set_pixbuf (
BONOBO_UI_TOOLBAR_ICON (pbl->pixmap), mini);
gtk_widget_set_usize (pbl->pixmap, width, height);
/* Restart the cache-cleaning timer */
g_source_remove_by_user_data (orig_key);
g_timeout_add (5 * 60 * 1000, pixbuf_uncache, orig_key);
if (pbl->loader) {
gdk_pixbuf_loader_close (pbl->loader);
gtk_object_destroy (GTK_OBJECT (pbl->loader));
camel_object_unref (CAMEL_OBJECT (pbl->mstream));
}
gtk_signal_disconnect (GTK_OBJECT (pbl->eb), pbl->destroy_id);
g_free (pbl->type);
g_free (pbl->cid);
g_free (pbl);
return FALSE;
}
/* Not in cache, so get a pixbuf from the wrapper */
if (!GTK_IS_WIDGET (pbl->pixmap)) {
/* Widget has died */
if (pbl->mstream)
camel_object_unref (CAMEL_OBJECT (pbl->mstream));
if (pbl->loader) {
gdk_pixbuf_loader_close (pbl->loader);
gtk_object_destroy (GTK_OBJECT (pbl->loader));
}
g_free (pbl->type);
g_free (pbl->cid);
g_free (pbl);
return FALSE;
}
if (pbl->mstream) {
if (pbl->loader == NULL)
pbl->loader = gdk_pixbuf_loader_new ();
len = camel_stream_read (pbl->mstream, tmp, 4096);
if (len > 0) {
error = !gdk_pixbuf_loader_write (pbl->loader, tmp, len);
if (!error)
return TRUE;
} else if (!camel_stream_eos (pbl->mstream))
error = TRUE;
}
if (error || !pbl->mstream) {
if (pbl->type)
pixbuf = pixbuf_for_mime_type (pbl->type);
else
pixbuf = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/pgp-signature-nokey.png");
} else
pixbuf = gdk_pixbuf_loader_get_pixbuf (pbl->loader);
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
if (width >= height) {
if (width > 24) {
ratio = width / 24;
width = 24;
height /= ratio;
}
} else {
if (height > 24) {
ratio = height / 24;
height = 24;
width /= ratio;
}
}
mini = gdk_pixbuf_scale_simple (pixbuf, width, height,
GDK_INTERP_BILINEAR);
if (error || !pbl->mstream)
gdk_pixbuf_unref (pixbuf);
bonobo_ui_toolbar_icon_set_pixbuf (
BONOBO_UI_TOOLBAR_ICON (pbl->pixmap), mini);
/* Add the pixbuf to the cache */
g_hash_table_insert (thumbnail_cache, pbl->cid, mini);
g_timeout_add (5 * 60 * 1000, pixbuf_uncache, pbl->cid);
gtk_signal_disconnect (GTK_OBJECT (pbl->eb), pbl->destroy_id);
if (pbl->loader) {
gdk_pixbuf_loader_close (pbl->loader);
gtk_object_unref (GTK_OBJECT (pbl->loader));
camel_object_unref (CAMEL_OBJECT (pbl->mstream));
}
g_free (pbl->type);
g_free (pbl);
return FALSE;
}
/* Stop the idle function and free the pbl structure
as the widget that the pixbuf was to be rendered to
has died on us. */
static void
embeddable_destroy_cb (GtkObject *embeddable,
struct _PixbufLoader *pbl)
{
g_idle_remove_by_data (pbl);
if (pbl->mstream)
camel_object_unref (CAMEL_OBJECT (pbl->mstream));
if (pbl->loader) {
gdk_pixbuf_loader_close (pbl->loader);
gtk_object_destroy (GTK_OBJECT (pbl->loader));
}
g_free (pbl->type);
g_free (pbl->cid);
g_free (pbl);
};
static GtkWidget *
get_embedded_for_component (const char *iid, MailDisplay *md)
{
GtkWidget *embedded;
BonoboControlFrame *control_frame;
Bonobo_PropertyBag prop_bag;
/*
* First try a control.
*/
embedded = bonobo_widget_new_control (iid, NULL);
if (embedded == NULL) {
/*
* No control, try an embeddable instead.
*/
embedded = bonobo_widget_new_subdoc (iid, NULL);
if (embedded != NULL) {
/* FIXME: as of bonobo 0.18, there's an extra
* client_site dereference in the BonoboWidget
* destruction path that we have to balance out to
* prevent problems.
*/
bonobo_object_ref (BONOBO_OBJECT(bonobo_widget_get_client_site (
BONOBO_WIDGET (embedded))));
return embedded;
}
}
if (embedded == NULL)
return NULL;
control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET (embedded));
prop_bag = bonobo_control_frame_get_control_property_bag ( control_frame, NULL );
if (prop_bag != CORBA_OBJECT_NIL){
CORBA_Environment ev;
/*
* Now we can take care of business. Currently, the only control
* that needs something passed to it through a property bag is
* the iTip control, and it needs only the From email address,
* but perhaps in the future we can generalize this section of code
* to pass a bunch of useful things to all embedded controls.
*/
const CamelInternetAddress *from;
char *from_address;
CORBA_exception_init (&ev);
from = camel_mime_message_get_from (md->current_message);
from_address = camel_address_encode((CamelAddress *)from);
bonobo_property_bag_client_set_value_string (
prop_bag, "from_address",
from_address, &ev);
g_free(from_address);
Bonobo_Unknown_unref (prop_bag, &ev);
CORBA_exception_free (&ev);
}
return embedded;
}
static void *
save_url (MailDisplay *md, const char *url)
{
GHashTable *urls;
CamelMimePart *part;
urls = g_datalist_get_data (md->data, "part_urls");
g_return_val_if_fail (urls != NULL, NULL);
part = g_hash_table_lookup (urls, url);
if (part == NULL) {
GByteArray *ba;
urls = g_datalist_get_data (md->data, "data_urls");
g_return_val_if_fail (urls != NULL, NULL);
/* See if it's some piece of cached data if it is then pretend it
* is a mime part so that we can use the mime part saveing routines.
* It is gross but it keeps duplicated code to a minimum and helps
* out with ref counting and the like.
*/
ba = g_hash_table_lookup (urls, url);
if (ba) {
CamelStream *memstream;
CamelDataWrapper *wrapper;
const char *name;
name = strrchr (url, '/');
name = name ? name : url;
/* we have to copy the data here since the ba may be long gone
* by the time the user actually saves the file
*/
memstream = camel_stream_mem_new_with_buffer (ba->data, ba->len);
wrapper = camel_data_wrapper_new ();
camel_data_wrapper_construct_from_stream (wrapper, memstream);
camel_object_unref (CAMEL_OBJECT (memstream));
part = camel_mime_part_new ();
camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper);
camel_object_unref (CAMEL_OBJECT (wrapper));
camel_mime_part_set_filename (part, name);
}
} else {
camel_object_ref (CAMEL_OBJECT (part));
}
if (part) {
CamelDataWrapper *data;
g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL);
data = camel_medium_get_content_object ((CamelMedium *)part);
if (!mail_content_loaded (data, md, TRUE, NULL, NULL)) {
return NULL;
}
save_part (part);
camel_object_unref (CAMEL_OBJECT (part));
return NULL;
}
g_warning ("part not found");
return NULL;
}
static gboolean
do_attachment_header (GtkHTML *html, GtkHTMLEmbedded *eb,
CamelMimePart *part, MailDisplay *md)
{
GtkWidget *button, *mainbox, *hbox, *arrow, *popup;
MailMimeHandler *handler;
struct _PixbufLoader *pbl;
pbl = g_new0 (struct _PixbufLoader, 1);
if (g_strncasecmp (eb->type, "image/", 6) == 0) {
CamelDataWrapper *content;
content = camel_medium_get_content_object (CAMEL_MEDIUM (part));
if (!camel_data_wrapper_is_offline (content)) {
pbl->mstream = camel_stream_mem_new ();
camel_data_wrapper_write_to_stream (content, pbl->mstream);
camel_stream_reset (pbl->mstream);
}
}
pbl->type = g_strdup (eb->type);
pbl->cid = g_strdup (eb->classid + 6);
pbl->pixmap = bonobo_ui_toolbar_icon_new ();
gtk_widget_set_usize (pbl->pixmap, 24, 24);
pbl->eb = eb;
pbl->destroy_id = gtk_signal_connect (GTK_OBJECT (eb), "destroy",
embeddable_destroy_cb, pbl);
g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)pixbuf_gen_idle,
pbl, NULL);
mainbox = gtk_hbox_new (FALSE, 0);
button = gtk_button_new ();
gtk_object_set_data (GTK_OBJECT (button), "MailDisplay", md);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (button_press), part);
hbox = gtk_hbox_new (FALSE, 2);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
if (mail_part_is_displayed_inline (part, md))
arrow = gnome_stock_new_with_icon (GNOME_STOCK_PIXMAP_DOWN);
else
arrow = gnome_stock_new_with_icon (GNOME_STOCK_PIXMAP_FORWARD);
gtk_box_pack_start (GTK_BOX (hbox), arrow, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (hbox), pbl->pixmap, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (button), hbox);
popup = gtk_button_new ();
gtk_container_add (GTK_CONTAINER (popup),
gtk_arrow_new (GTK_ARROW_DOWN,
GTK_SHADOW_ETCHED_IN));
gtk_object_set_data (GTK_OBJECT (popup), "MailDisplay", md);
gtk_object_set_data (GTK_OBJECT (popup), "CamelMimePart", part);
gtk_object_set_data_full (GTK_OBJECT (popup), "mime_type",
g_strdup (eb->type), (GDestroyNotify)g_free);
gtk_signal_connect (GTK_OBJECT (popup), "button_press_event",
GTK_SIGNAL_FUNC (pixmap_press), md->scroll);
gtk_box_pack_start (GTK_BOX (mainbox), button, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (mainbox), popup, TRUE, TRUE, 0);
gtk_widget_show_all (mainbox);
handler = mail_lookup_handler (eb->type);
if (handler && handler->builtin)
gtk_widget_set_sensitive (button, TRUE);
else
gtk_widget_set_sensitive (button, FALSE);
gtk_container_add (GTK_CONTAINER (eb), mainbox);
return TRUE;
}
static gboolean
do_external_viewer (GtkHTML *html, GtkHTMLEmbedded *eb,
CamelMimePart *part, MailDisplay *md)
{
CamelDataWrapper *wrapper;
OAF_ServerInfo *component;
GtkWidget *embedded;
BonoboObjectClient *server;
Bonobo_PersistStream persist;
CORBA_Environment ev;
GByteArray *ba;
CamelStream *cstream;
BonoboStream *bstream;
component = gnome_vfs_mime_get_default_component (eb->type);
if (!component)
return FALSE;
embedded = get_embedded_for_component (component->iid, md);
CORBA_free (component);
if (!embedded)
return FALSE;
server = bonobo_widget_get_server (BONOBO_WIDGET (embedded));
persist = (Bonobo_PersistStream) bonobo_object_client_query_interface (
server, "IDL:Bonobo/PersistStream:1.0", NULL);
if (persist == CORBA_OBJECT_NIL) {
gtk_object_sink (GTK_OBJECT (embedded));
return FALSE;
}
/* Write the data to a CamelStreamMem... */
ba = g_byte_array_new ();
cstream = camel_stream_mem_new_with_byte_array (ba);
wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part));
camel_data_wrapper_write_to_stream (wrapper, cstream);
/* ...convert the CamelStreamMem to a BonoboStreamMem... */
bstream = bonobo_stream_mem_create (ba->data, ba->len, TRUE, FALSE);
camel_object_unref (CAMEL_OBJECT (cstream));
/* ...and hydrate the PersistStream from the BonoboStream. */
CORBA_exception_init (&ev);
Bonobo_PersistStream_load (persist,
bonobo_object_corba_objref (
BONOBO_OBJECT (bstream)),
eb->type, &ev);
bonobo_object_unref (BONOBO_OBJECT (bstream));
Bonobo_Unknown_unref (persist, &ev);
CORBA_Object_release (persist, &ev);
if (ev._major != CORBA_NO_EXCEPTION) {
gtk_object_sink (GTK_OBJECT (embedded));
CORBA_exception_free (&ev);
return FALSE;
}
CORBA_exception_free (&ev);
gtk_widget_show (embedded);
gtk_container_add (GTK_CONTAINER (eb), embedded);
return TRUE;
}
static gboolean
do_signature (GtkHTML *html, GtkHTMLEmbedded *eb,
CamelMimePart *part, MailDisplay *md)
{
GtkWidget *button;
struct _PixbufLoader *pbl;
pbl = g_new0 (struct _PixbufLoader, 1);
pbl->type = NULL;
pbl->cid = g_strdup (eb->classid);
pbl->pixmap = bonobo_ui_toolbar_icon_new ();
gtk_widget_set_usize (pbl->pixmap, 24, 24);
pbl->eb = eb;
pbl->destroy_id = gtk_signal_connect (GTK_OBJECT (eb), "destroy",
embeddable_destroy_cb, pbl);
g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)pixbuf_gen_idle,
pbl, NULL);
button = gtk_button_new ();
gtk_object_set_data (GTK_OBJECT (button), "MailDisplay", md);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (button_press), part);
gtk_container_add (GTK_CONTAINER (button), pbl->pixmap);
gtk_widget_show_all (button);
gtk_container_add (GTK_CONTAINER (eb), button);
return TRUE;
}
static gboolean
on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data)
{
MailDisplay *md = data;
GHashTable *urls;
CamelMimePart *part;
if (!eb->classid)
return FALSE;
urls = g_datalist_get_data (md->data, "part_urls");
if (!urls)
return FALSE;
if (!strncmp (eb->classid, "popup:", 6) && eb->type) {
part = g_hash_table_lookup (urls, eb->classid + 6);
if (!CAMEL_IS_MIME_PART (part))
return FALSE;
return do_attachment_header (html, eb, part, md);
} else if (!strncmp (eb->classid, "signature:", 10)) {
part = g_hash_table_lookup (urls, eb->classid);
if (!CAMEL_IS_MIME_PART (part))
return FALSE;
return do_signature (html, eb, part, md);
} else if (!strncmp (eb->classid, "cid:", 4) && eb->type) {
part = g_hash_table_lookup (urls, eb->classid);
if (!CAMEL_IS_MIME_PART (part))
return FALSE;
return do_external_viewer (html, eb, part, md);
}
return FALSE;
}
static void
load_http (MailDisplay *md, gpointer data)
{
char *url = data;
GHashTable *urls;
GnomeVFSHandle *handle;
GnomeVFSFileSize read;
GByteArray *ba;
char buf[8192];
urls = g_datalist_get_data (md->data, "data_urls");
ba = g_hash_table_lookup (urls, url);
g_return_if_fail (ba != NULL);
if (gnome_vfs_open (&handle, url, GNOME_VFS_OPEN_READ) != GNOME_VFS_OK) {
#if 0
printf ("failed to open %s\n", url);
#endif
g_free (url);
return;
}
while (gnome_vfs_read (handle, buf, sizeof (buf), &read) == GNOME_VFS_OK)
g_byte_array_append (ba, buf, read);
gnome_vfs_close (handle);
#if 0
if (!ba->len)
printf ("no data in %s\n", url);
#endif
g_free (url);
}
static void
ebook_callback (EBook *book, const gchar *addr, ECard *card, gpointer data)
{
MailDisplay *md = data;
if (card && md->current_message) {
const CamelInternetAddress *from = camel_mime_message_get_from (md->current_message);
const char *md_name = NULL, *md_addr = NULL;
/* We are extra anal, in case we are dealing with some sort of pathological message
w/o a From: header. */
if (from != NULL && camel_internet_address_get (from, 0, &md_name, &md_addr)) {
if (md_addr != NULL && !strcmp (addr, md_addr))
mail_display_load_images (md);
}
}
}
static void
on_url_requested (GtkHTML *html, const char *url, GtkHTMLStream *handle,
gpointer user_data)
{
MailDisplay *md = user_data;
GHashTable *urls;
CamelMedium *medium;
GByteArray *ba;
urls = g_datalist_get_data (md->data, "part_urls");
g_return_if_fail (urls != NULL);
/* See if it refers to a MIME part (cid: or http:) */
medium = g_hash_table_lookup (urls, url);
if (medium) {
CamelContentType *content_type;
CamelDataWrapper *data;
g_return_if_fail (CAMEL_IS_MEDIUM (medium));
data = camel_medium_get_content_object (medium);
if (!mail_content_loaded (data, md, FALSE, url, handle))
return;
content_type = camel_data_wrapper_get_mime_type_field (data);
if (header_content_type_is (content_type, "text", "*")) {
ba = mail_format_get_data_wrapper_text (data, md);
if (ba) {
gtk_html_write (html, handle, ba->data, ba->len);
g_byte_array_free (ba, TRUE);
}
} else {
CamelStream *html_stream;
html_stream = mail_stream_gtkhtml_new (html, handle);
camel_data_wrapper_write_to_stream (data, html_stream);
camel_object_unref (CAMEL_OBJECT (html_stream));
}
gtk_html_end (html, handle, GTK_HTML_STREAM_OK);
return;
}
urls = g_datalist_get_data (md->data, "data_urls");
g_return_if_fail (urls != NULL);
/* See if it's some piece of cached data */
ba = g_hash_table_lookup (urls, url);
if (ba) {
if (ba->len) {
gtk_html_write (html, handle, ba->data, ba->len);
/* printf ("-- begin --\n");
printf (ba->data);
printf ("-- end --\n"); */
}
gtk_html_end (html, handle, GTK_HTML_STREAM_OK);
return;
}
/* See if it's something we can load. */
if (strncmp (url, "http:", 5) == 0) {
if (mail_config_get_http_mode () == MAIL_CONFIG_HTTP_ALWAYS ||
g_datalist_get_data (md->data, "load_images")) {
ba = g_byte_array_new ();
g_hash_table_insert (urls, g_strdup (url), ba);
mail_display_stream_write_when_loaded (md, ba, url, load_http, handle,
g_strdup (url));
} else if (mail_config_get_http_mode () == MAIL_CONFIG_HTTP_SOMETIMES &&
!g_datalist_get_data (md->data, "checking_from")) {
const CamelInternetAddress *from = camel_mime_message_get_from (md->current_message);
const char *name, *addr;
g_datalist_set_data (md->data, "checking_from",
GINT_TO_POINTER (1));
/* Make sure we aren't deal w/ some sort of a pathological message w/o a From: header */
if (from != NULL && camel_internet_address_get (from, 0, &name, &addr))
e_book_query_address_locally (addr, ebook_callback, md);
else
gtk_html_end (html, handle, GTK_HTML_STREAM_ERROR);
}
}
}
struct _load_content_msg {
struct _mail_msg msg;
MailDisplay *display;
GtkHTMLStream *handle;
gint redisplay_counter;
gchar *url;
CamelMimeMessage *message;
void (*callback)(MailDisplay *, gpointer);
gpointer data;
};
static char *
load_content_desc (struct _mail_msg *mm, int done)
{
return g_strdup (_("Loading message content"));
}
static void
load_content_load (struct _mail_msg *mm)
{
struct _load_content_msg *m = (struct _load_content_msg *)mm;
m->callback (m->display, m->data);
}
static gboolean
try_part_urls (struct _load_content_msg *m)
{
GHashTable *urls;
CamelMedium *medium;
urls = g_datalist_get_data (m->display->data, "part_urls");
g_return_val_if_fail (urls != NULL, FALSE);
/* See if it refers to a MIME part (cid: or http:) */
medium = g_hash_table_lookup (urls, m->url);
if (medium) {
CamelDataWrapper *data;
CamelStream *html_stream;
g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), FALSE);
data = camel_medium_get_content_object (medium);
if (!mail_content_loaded (data, m->display, FALSE, m->url, m->handle)) {
g_warning ("This code should not be reached\n");
return TRUE;
}
html_stream = mail_stream_gtkhtml_new (m->display->html, m->handle);
camel_data_wrapper_write_to_stream (data, html_stream);
camel_object_unref (CAMEL_OBJECT (html_stream));
gtk_html_end (m->display->html, m->handle, GTK_HTML_STREAM_OK);
return TRUE;
}
return FALSE;
}
static gboolean
try_data_urls (struct _load_content_msg *m)
{
GHashTable *urls;
GByteArray *ba;
urls = g_datalist_get_data (m->display->data, "data_urls");
ba = g_hash_table_lookup (urls, m->url);
printf ("url: %s data: %p len: %d\n", m->url, ba, ba ? ba->len : -1);
if (ba) {
if (ba->len) {
printf ("writing ...\n");
gtk_html_write (m->display->html, m->handle, ba->data, ba->len);
}
gtk_html_end (m->display->html, m->handle, GTK_HTML_STREAM_OK);
return TRUE;
}
return FALSE;
}
static void
load_content_loaded (struct _mail_msg *mm)
{
struct _load_content_msg *m = (struct _load_content_msg *)mm;
if (GTK_OBJECT_DESTROYED (m->display))
return;
if (m->display->current_message == m->message) {
if (m->handle) {
printf ("handle: %p orig: %d actual: %d\n", m->handle,
m->redisplay_counter,
m->display->redisplay_counter);
if (m->redisplay_counter == m->display->redisplay_counter) {
if (!try_part_urls (m) && !try_data_urls (m))
gtk_html_end (m->display->html, m->handle, GTK_HTML_STREAM_ERROR);
}
} else
mail_display_redisplay (m->display, FALSE);
}
}
static void
load_content_free (struct _mail_msg *mm)
{
struct _load_content_msg *m = (struct _load_content_msg *)mm;
g_free (m->url);
gtk_object_unref (GTK_OBJECT (m->display));
camel_object_unref (CAMEL_OBJECT (m->message));
}
static struct _mail_msg_op load_content_op = {
load_content_desc,
load_content_load,
load_content_loaded,
load_content_free,
};
static void
stream_write_or_redisplay_when_loaded (MailDisplay *md,
gconstpointer key,
const gchar *url,
void (*callback)(MailDisplay *, gpointer),
GtkHTMLStream *handle,
gpointer data)
{
struct _load_content_msg *m;
GHashTable *loading;
if (GTK_OBJECT_DESTROYED (md))
return;
loading = g_datalist_get_data (md->data, "loading");
if (loading) {
if (g_hash_table_lookup (loading, key))
return;
} else {
loading = g_hash_table_new (NULL, NULL);
g_datalist_set_data_full (md->data, "loading", loading,
(GDestroyNotify)g_hash_table_destroy);
}
g_hash_table_insert (loading, (gpointer)key, GINT_TO_POINTER (1));
m = mail_msg_new (&load_content_op, NULL, sizeof (*m));
m->display = md;
gtk_object_ref (GTK_OBJECT (m->display));
m->handle = handle;
m->url = g_strdup (url);
m->redisplay_counter = md->redisplay_counter;
m->message = md->current_message;
camel_object_ref (CAMEL_OBJECT (m->message));
m->callback = callback;
m->data = data;
e_thread_put (mail_thread_queued, (EMsg *)m);
return;
}
void
mail_display_stream_write_when_loaded (MailDisplay *md,
gconstpointer key,
const gchar *url,
void (*callback)(MailDisplay *, gpointer),
GtkHTMLStream *handle,
gpointer data)
{
stream_write_or_redisplay_when_loaded (md, key, url, callback, handle, data);
}
void
mail_display_redisplay_when_loaded (MailDisplay *md,
gconstpointer key,
void (*callback)(MailDisplay *, gpointer),
gpointer data)
{
stream_write_or_redisplay_when_loaded (md, key, NULL, callback, NULL, data);
}
void
mail_text_write (GtkHTML *html, GtkHTMLStream *stream, const char *text)
{
char *htmltext;
htmltext = e_text_to_html_full (text, E_TEXT_TO_HTML_CONVERT_URLS |
E_TEXT_TO_HTML_CONVERT_ADDRESSES |
E_TEXT_TO_HTML_CONVERT_NL |
E_TEXT_TO_HTML_CONVERT_SPACES |
(mail_config_get_citation_highlight () ? E_TEXT_TO_HTML_MARK_CITATION : 0),
mail_config_get_citation_color ());
gtk_html_write (html, stream, "<tt>", 4);
gtk_html_write (html, stream, htmltext, strlen (htmltext));
gtk_html_write (html, stream, "</tt>", 5);
g_free (htmltext);
}
void
mail_error_printf (GtkHTML *html, GtkHTMLStream *stream,
const char *format, ...)
{
char *buf, *htmltext;
va_list ap;
va_start (ap, format);
buf = g_strdup_vprintf (format, ap);
va_end (ap);
htmltext = e_text_to_html (buf, E_TEXT_TO_HTML_CONVERT_NL | E_TEXT_TO_HTML_CONVERT_URLS);
g_free (buf);
gtk_html_stream_printf (stream, "<em><font color=red>");
gtk_html_stream_write (stream, htmltext, strlen (htmltext));
gtk_html_stream_printf (stream, "</font></em>");
g_free (htmltext);
}
static void
clear_data (CamelObject *object, gpointer event_data, gpointer user_data)
{
GData *data = user_data;
g_datalist_clear (&data);
}
void
mail_display_render (MailDisplay *md, GtkHTML *html)
{
GtkHTMLStream *stream;
g_return_if_fail (IS_MAIL_DISPLAY (md));
g_return_if_fail (GTK_IS_HTML (html));
stream = gtk_html_begin (html);
mail_html_write (html, stream,
"<!doctype html public \"-//W3C//DTD HTML 4.0 TRANSITIONAL//EN\">\n"
"<html>\n"
"<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\">\n</head>\n");
mail_html_write (html, stream, "<body marginwidth=0 marginheight=0>\n");
if (md->current_message) {
if (md->display_style == MAIL_CONFIG_DISPLAY_SOURCE)
mail_format_raw_message (md->current_message, md, html, stream);
else
mail_format_mime_message (md->current_message, md, html, stream);
}
mail_html_write (html, stream, "</body></html>\n");
gtk_html_end (html, stream, GTK_HTML_STREAM_OK);
}
/**
* mail_display_redisplay:
* @mail_display: the mail display object
* @unscroll: specifies whether or not to lose current scroll
*
* Force a redraw of the message display.
**/
void
mail_display_redisplay (MailDisplay *md, gboolean unscroll)
{
if (GTK_OBJECT_DESTROYED (md))
return;
md->last_active = NULL;
md->redisplay_counter++;
/* printf ("md %p redisplay %d\n", md, md->redisplay_counter); */
if (!unscroll) {
/* This is a hack until there's a clean way to do this. */
GTK_HTML (md->html)->engine->newPage = FALSE;
}
mail_display_render (md, md->html);
}
/**
* mail_display_set_message:
* @mail_display: the mail display object
* @medium: the input camel medium, or %NULL
*
* Makes the mail_display object show the contents of the medium
* param.
**/
void
mail_display_set_message (MailDisplay *md, CamelMedium *medium)
{
/* For the moment, we deal only with CamelMimeMessage, but in
* the future, we should be able to deal with any medium.
*/
if (medium && !CAMEL_IS_MIME_MESSAGE (medium))
return;
/* Clean up from previous message. */
if (md->current_message)
camel_object_unref (CAMEL_OBJECT (md->current_message));
md->current_message = (CamelMimeMessage*)medium;
g_datalist_init (md->data);
mail_display_redisplay (md, TRUE);
if (medium) {
camel_object_hook_event (CAMEL_OBJECT (medium), "finalize",
clear_data, *(md->data));
}
}
/**
* mail_display_set_message:
* @mail_display: the mail display object
* @medium: the input camel medium, or %NULL
*
* Makes the mail_display object show the contents of the medium
* param.
**/
void
mail_display_set_charset (MailDisplay *mail_display, const char *charset)
{
g_free (mail_display->charset);
mail_display->charset = g_strdup (charset);
mail_display_queue_redisplay (mail_display);
}
/**
* mail_display_load_images:
* @md: the mail display object
*
* Load all HTTP images in the current message
**/
void
mail_display_load_images (MailDisplay *md)
{
g_datalist_set_data (md->data, "load_images", GINT_TO_POINTER (1));
mail_display_redisplay (md, FALSE);
}
/*----------------------------------------------------------------------*
* Standard Gtk+ Class functions
*----------------------------------------------------------------------*/
static void
mail_display_init (GtkObject *object)
{
MailDisplay *mail_display = MAIL_DISPLAY (object);
mail_display->current_message = NULL;
mail_display->scroll = NULL;
mail_display->html = NULL;
mail_display->redisplay_counter = 0;
mail_display->last_active = NULL;
mail_display->idle_id = 0;
mail_display->selection = NULL;
mail_display->current_message = NULL;
mail_display->data = NULL;
mail_display->invisible = gtk_invisible_new ();
mail_display->display_style = mail_config_get_message_display_style ();
mail_display->printing = FALSE;
}
static void
mail_display_destroy (GtkObject *object)
{
MailDisplay *mail_display = MAIL_DISPLAY (object);
g_free (mail_display->charset);
g_free (mail_display->selection);
g_datalist_clear (mail_display->data);
g_free (mail_display->data);
mail_display->data = NULL;
if (mail_display->idle_id)
gtk_timeout_remove (mail_display->idle_id);
gtk_widget_unref (mail_display->invisible);
mail_display_parent_class->destroy (object);
}
static void
invisible_selection_get_callback (GtkWidget *widget,
GtkSelectionData *selection_data,
guint info,
guint time,
void *data)
{
MailDisplay *display;
display = MAIL_DISPLAY (data);
if (!display->selection)
return;
g_assert (info == 1);
gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, 8,
display->selection, strlen (display->selection));
}
static gint
invisible_selection_clear_event_callback (GtkWidget *widget,
GdkEventSelection *event,
void *data)
{
MailDisplay *display;
display = MAIL_DISPLAY (data);
g_free (display->selection);
display->selection = NULL;
return TRUE;
}
static void
mail_display_class_init (GtkObjectClass *object_class)
{
object_class->destroy = mail_display_destroy;
mail_display_parent_class = gtk_type_class (PARENT_TYPE);
thumbnail_cache = g_hash_table_new (g_str_hash, g_str_equal);
}
static void
link_open_in_browser (GtkWidget *w, MailDisplay *mail_display)
{
on_link_clicked (mail_display->html, mail_display->html->pointer_url,
mail_display);
}
#if 0
static void
link_save_as (GtkWidget *w, MailDisplay *mail_display)
{
g_print ("FIXME save %s\n", mail_display->html->pointer_url);
}
#endif
static void
link_copy_location (GtkWidget *w, MailDisplay *mail_display)
{
GdkAtom clipboard_atom;
g_free (mail_display->selection);
mail_display->selection = g_strdup (mail_display->html->pointer_url);
clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
if (clipboard_atom == GDK_NONE)
return; /* failed */
/* We don't check the return values of the following since there is not
* much we can do if we cannot assert the selection.
*/
gtk_selection_owner_set (GTK_WIDGET (mail_display->invisible),
GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
gtk_selection_owner_set (GTK_WIDGET (mail_display->invisible),
clipboard_atom,
GDK_CURRENT_TIME);
}
static void
image_save_as (GtkWidget *w, MailDisplay *mail_display)
{
const char *src;
src = gtk_object_get_data (GTK_OBJECT (mail_display), "current_src_uri");
g_warning ("loading uri=%s", src);
save_url (mail_display, src);
}
enum {
/*
* This is used to mask the link specific menu items.
*/
MASK_URL = 1,
/*
* This is used to mask src specific menu items.
*/
MASK_SRC = 2
};
#define SEPARATOR { "", NULL, (NULL), NULL, 0 }
#define TERMINATOR { NULL, NULL, (NULL), NULL, 0 }
static EPopupMenu link_menu [] = {
{ N_("Open Link in Browser"), NULL,
GTK_SIGNAL_FUNC (link_open_in_browser), NULL, MASK_URL },
{ N_("Copy Link Location"), NULL,
GTK_SIGNAL_FUNC (link_copy_location), NULL, MASK_URL },
#if 0
{ N_("Save Link as (FIXME)"), NULL,
GTK_SIGNAL_FUNC (link_save_as), NULL, MASK_URL },
#endif
{ N_("Save Image as..."), NULL,
GTK_SIGNAL_FUNC (image_save_as), NULL, MASK_SRC },
TERMINATOR
};
/*
* Create a window and popup our widget, with reasonable semantics for the popup
* disappearing, etc.
*/
typedef struct _PopupInfo PopupInfo;
struct _PopupInfo {
GtkWidget *w;
GtkWidget *win;
guint destroy_timeout;
guint widget_destroy_handle;
Bonobo_EventSource_ListenerId listener_id;
gboolean hidden;
};
/* Aiieee! Global Data! */
static GtkWidget *the_popup = NULL;
static void
popup_info_free (PopupInfo *pop)
{
if (pop) {
if (pop->destroy_timeout)
gtk_timeout_remove (pop->destroy_timeout);
bonobo_event_source_client_remove_listener (bonobo_widget_get_objref (BONOBO_WIDGET (pop->w)),
pop->listener_id,
NULL);
g_free (pop);
}
}
static void
popup_window_destroy_cb (GtkWidget *w, gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
the_popup = NULL;
popup_info_free (pop);
}
static gint
popup_timeout_cb (gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
pop->destroy_timeout = 0;
gtk_widget_destroy (pop->win);
return 0;
}
static gint
popup_enter_cb (GtkWidget *w, GdkEventCrossing *ev, gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
if (pop->destroy_timeout)
gtk_timeout_remove (pop->destroy_timeout);
pop->destroy_timeout = 0;
return 0;
}
static gint
popup_leave_cb (GtkWidget *w, GdkEventCrossing *ev, gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
if (pop->destroy_timeout)
gtk_timeout_remove (pop->destroy_timeout);
if (!pop->hidden)
pop->destroy_timeout = gtk_timeout_add (500, popup_timeout_cb, pop);
return 0;
}
static void
popup_realize_cb (GtkWidget *widget, gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
gtk_widget_add_events (pop->win, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
if (pop->destroy_timeout == 0) {
if (!pop->hidden) {
pop->destroy_timeout = gtk_timeout_add (5000, popup_timeout_cb, pop);
} else {
pop->destroy_timeout = 0;
}
}
}
static void
popup_size_allocate_cb (GtkWidget *widget, GtkAllocation *alloc, gpointer user_data)
{
gint x, y, w, h, xmax, ymax;
xmax = gdk_screen_width ();
ymax = gdk_screen_height ();
gdk_window_get_pointer (NULL, &x, &y, NULL);
w = alloc->width;
h = alloc->height;
x = CLAMP (x - w/2, 0, xmax - w);
y = CLAMP (y - h/2, 0, ymax - h);
gtk_widget_set_uposition (widget, x, y);
}
static PopupInfo *
make_popup_window (GtkWidget *w)
{
PopupInfo *pop = g_new0 (PopupInfo, 1);
GtkWidget *fr;
/* Only allow for one popup at a time. Ugly. */
if (the_popup)
gtk_widget_destroy (the_popup);
pop->w = w;
the_popup = pop->win = gtk_window_new (GTK_WINDOW_POPUP);
fr = gtk_frame_new (NULL);
gtk_container_add (GTK_CONTAINER (pop->win), fr);
gtk_container_add (GTK_CONTAINER (fr), w);
gtk_window_set_policy (GTK_WINDOW (pop->win), FALSE, FALSE, FALSE);
gtk_signal_connect (GTK_OBJECT (pop->win),
"destroy",
GTK_SIGNAL_FUNC (popup_window_destroy_cb),
pop);
gtk_signal_connect (GTK_OBJECT (pop->win),
"enter_notify_event",
GTK_SIGNAL_FUNC (popup_enter_cb),
pop);
gtk_signal_connect (GTK_OBJECT (pop->win),
"leave_notify_event",
GTK_SIGNAL_FUNC (popup_leave_cb),
pop);
gtk_signal_connect_after (GTK_OBJECT (pop->win),
"realize",
GTK_SIGNAL_FUNC (popup_realize_cb),
pop);
gtk_signal_connect (GTK_OBJECT (pop->win),
"size_allocate",
GTK_SIGNAL_FUNC (popup_size_allocate_cb),
pop);
gtk_widget_show (w);
gtk_widget_show (fr);
gtk_widget_show (pop->win);
return pop;
}
static void
listener_cb (BonoboListener *listener,
char *event_name,
CORBA_any *any,
CORBA_Environment *ev,
gpointer user_data)
{
PopupInfo *pop;
char *type;
pop = user_data;
if (pop->destroy_timeout)
gtk_timeout_remove (pop->destroy_timeout);
pop->destroy_timeout = 0;
type = bonobo_event_subtype (event_name);
if (!strcmp (type, "Destroy")) {
gtk_widget_destroy (GTK_WIDGET (pop->win));
} else if (!strcmp (type, "Hide")) {
pop->hidden = TRUE;
gtk_widget_hide (GTK_WIDGET (pop->win));
}
g_free (type);
}
static int
html_button_press_event (GtkWidget *widget, GdkEventButton *event, MailDisplay *mail_display)
{
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (event != NULL, FALSE);
if (event->type == GDK_BUTTON_PRESS) {
if (event->button == 3) {
HTMLEngine *e;
HTMLPoint *point;
GtkWidget *popup_thing;
e = GTK_HTML (widget)->engine;
point = html_engine_get_point_at (e, event->x + e->x_offset, event->y + e->y_offset, FALSE);
if (point) {
const gchar *url;
const gchar *src;
url = html_object_get_url (point->object);
src = html_object_get_src (point->object);
if (url && !g_strncasecmp (url, "mailto:", 7)) {
PopupInfo *pop;
gchar *url_decoded = g_strdup (url);
camel_url_decode (url_decoded);
popup_thing = bonobo_widget_new_control ("OAFIID:GNOME_Evolution_Addressbook_AddressPopup",
CORBA_OBJECT_NIL);
bonobo_widget_set_property (BONOBO_WIDGET (popup_thing),
"email", url_decoded+7,
NULL);
g_free (url_decoded);
pop = make_popup_window (popup_thing);
pop->listener_id =
bonobo_event_source_client_add_listener (bonobo_widget_get_objref (BONOBO_WIDGET (popup_thing)),
listener_cb, NULL, NULL, pop);
} else if (url || src) {
gint hide_mask = 0;
if (!url)
hide_mask |= MASK_URL;
if (!src)
hide_mask |= MASK_SRC;
g_free (gtk_object_get_data (GTK_OBJECT (mail_display), "current_src_uri"));
gtk_object_set_data (GTK_OBJECT (mail_display), "current_src_uri", g_strdup (src));
e_popup_menu_run (link_menu, (GdkEvent *) event, 0, hide_mask, mail_display);
}
html_point_destroy (point);
}
return TRUE;
}
}
return FALSE;
}
static inline void
set_underline (HTMLEngine *e, HTMLObject *o, gboolean underline)
{
HTMLText *text = HTML_TEXT (o);
html_text_set_font_style (text, e, underline
? html_text_get_font_style (text) | GTK_HTML_FONT_STYLE_UNDERLINE
: html_text_get_font_style (text) & ~GTK_HTML_FONT_STYLE_UNDERLINE);
html_engine_queue_draw (e, o);
}
static void
update_active (GtkWidget *widget, gint x, gint y, MailDisplay *mail_display)
{
HTMLEngine *e;
HTMLPoint *point;
const gchar *email;
e = GTK_HTML (widget)->engine;
point = html_engine_get_point_at (e, x + e->x_offset, y + e->y_offset, FALSE);
if (mail_display->last_active && (!point || mail_display->last_active != point->object)) {
set_underline (e, HTML_OBJECT (mail_display->last_active), FALSE);
mail_display->last_active = NULL;
}
if (point) {
email = (const gchar *) html_object_get_data (point->object, "email");
if (email && html_object_is_text (point->object)) {
set_underline (e, point->object, TRUE);
mail_display->last_active = point->object;
}
html_point_destroy (point);
}
}
static gint
html_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event, MailDisplay *mail_display)
{
update_active (widget, event->x, event->y, mail_display);
return TRUE;
}
static gint
html_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, MailDisplay *mail_display)
{
gint x, y;
g_return_val_if_fail (widget != NULL, 0);
g_return_val_if_fail (GTK_IS_HTML (widget), 0);
g_return_val_if_fail (event != NULL, 0);
if (event->is_hint)
gdk_window_get_pointer (GTK_LAYOUT (widget)->bin_window, &x, &y, NULL);
else {
x = event->x;
y = event->y;
}
update_active (widget, x, y, mail_display);
return TRUE;
}
static void
html_iframe_created (GtkWidget *w, GtkHTML *iframe, MailDisplay *mail_display)
{
gtk_signal_connect (GTK_OBJECT (iframe), "button_press_event",
GTK_SIGNAL_FUNC (html_button_press_event), mail_display);
gtk_signal_connect (GTK_OBJECT (iframe), "motion_notify_event",
GTK_SIGNAL_FUNC (html_motion_notify_event), mail_display);
gtk_signal_connect (GTK_OBJECT (iframe), "enter_notify_event",
GTK_SIGNAL_FUNC (html_enter_notify_event), mail_display);
}
static GNOME_Evolution_ShellView
retrieve_shell_view_interface_from_control (BonoboControl *control)
{
Bonobo_ControlFrame control_frame;
GNOME_Evolution_ShellView shell_view_interface;
CORBA_Environment ev;
control_frame = bonobo_control_get_control_frame (control);
if (control_frame == NULL)
return CORBA_OBJECT_NIL;
CORBA_exception_init (&ev);
shell_view_interface = Bonobo_Unknown_queryInterface (control_frame,
"IDL:GNOME/Evolution/ShellView:1.0",
&ev);
CORBA_exception_free (&ev);
if (shell_view_interface != CORBA_OBJECT_NIL)
gtk_object_set_data (GTK_OBJECT (control),
"mail_threads_shell_view_interface",
shell_view_interface);
else
g_warning ("Control frame doesn't have Evolution/ShellView.");
return shell_view_interface;
}
static void
set_status_message (const char *message, int busy)
{
EList *controls;
EIterator *it;
controls = folder_browser_factory_get_control_list ();
for (it = e_list_get_iterator (controls); e_iterator_is_valid (it); e_iterator_next (it)) {
BonoboControl *control;
GNOME_Evolution_ShellView shell_view_interface;
CORBA_Environment ev;
control = BONOBO_CONTROL (e_iterator_get (it));
shell_view_interface = gtk_object_get_data (GTK_OBJECT (control), "mail_threads_shell_view_interface");
if (shell_view_interface == CORBA_OBJECT_NIL)
shell_view_interface = retrieve_shell_view_interface_from_control (control);
CORBA_exception_init (&ev);
if (shell_view_interface != CORBA_OBJECT_NIL) {
if (message != NULL)
GNOME_Evolution_ShellView_setMessage (shell_view_interface,
message[0] ? message: "",
busy,
&ev);
}
CORBA_exception_free (&ev);
/* yeah we only set the first one. Why? Because it seems to leave
random ones lying around otherwise. Shrug. */
break;
}
gtk_object_unref (GTK_OBJECT(it));
}
/* For now show every url but possibly limit it to showing only http:
or ftp: urls */
static void
html_on_url (GtkHTML *html,
const char *url,
MailDisplay *mail_display)
{
static char *previous_url = NULL;
/* This all looks silly but yes, this is the proper way to mix
GtkHTML's on_url with BonoboUIComponent statusbar */
if (!url || (previous_url && (strcmp (url, previous_url) != 0)))
set_status_message ("", FALSE);
if (url) {
set_status_message (url, FALSE);
g_free (previous_url);
previous_url = g_strdup (url);
}
}
GtkWidget *
mail_display_new (void)
{
MailDisplay *mail_display = gtk_type_new (mail_display_get_type ());
GtkWidget *scroll, *html;
GdkAtom clipboard_atom;
HTMLTokenizer *tok;
gtk_box_set_homogeneous (GTK_BOX (mail_display), FALSE);
gtk_widget_show (GTK_WIDGET (mail_display));
scroll = e_scroll_frame_new (NULL, NULL);
e_scroll_frame_set_policy (E_SCROLL_FRAME (scroll),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
e_scroll_frame_set_shadow_type (E_SCROLL_FRAME (scroll), GTK_SHADOW_IN);
gtk_box_pack_start_defaults (GTK_BOX (mail_display), GTK_WIDGET (scroll));
gtk_widget_show (GTK_WIDGET (scroll));
html = gtk_html_new ();
tok = e_searching_tokenizer_new ();
html_engine_set_tokenizer (GTK_HTML (html)->engine, tok);
gtk_object_unref (GTK_OBJECT (tok));
gtk_html_set_default_content_type (GTK_HTML (html),
"text/html; charset=utf-8");
gtk_html_set_editable (GTK_HTML (html), FALSE);
gtk_signal_connect (GTK_OBJECT (html), "url_requested",
GTK_SIGNAL_FUNC (on_url_requested),
mail_display);
gtk_signal_connect (GTK_OBJECT (html), "object_requested",
GTK_SIGNAL_FUNC (on_object_requested),
mail_display);
gtk_signal_connect (GTK_OBJECT (html), "link_clicked",
GTK_SIGNAL_FUNC (on_link_clicked),
mail_display);
gtk_signal_connect (GTK_OBJECT (html), "button_press_event",
GTK_SIGNAL_FUNC (html_button_press_event), mail_display);
gtk_signal_connect (GTK_OBJECT (html), "motion_notify_event",
GTK_SIGNAL_FUNC (html_motion_notify_event), mail_display);
gtk_signal_connect (GTK_OBJECT (html), "enter_notify_event",
GTK_SIGNAL_FUNC (html_enter_notify_event), mail_display);
gtk_signal_connect (GTK_OBJECT (html), "iframe_created",
GTK_SIGNAL_FUNC (html_iframe_created), mail_display);
gtk_signal_connect (GTK_OBJECT (html), "on_url",
GTK_SIGNAL_FUNC (html_on_url), mail_display);
gtk_container_add (GTK_CONTAINER (scroll), html);
gtk_widget_show (GTK_WIDGET (html));
gtk_signal_connect (GTK_OBJECT (mail_display->invisible), "selection_get",
GTK_SIGNAL_FUNC (invisible_selection_get_callback), mail_display);
gtk_signal_connect (GTK_OBJECT (mail_display->invisible), "selection_clear_event",
GTK_SIGNAL_FUNC (invisible_selection_clear_event_callback), mail_display);
gtk_selection_add_target (mail_display->invisible,
GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 1);
clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
if (clipboard_atom != GDK_NONE)
gtk_selection_add_target (mail_display->invisible,
clipboard_atom, GDK_SELECTION_TYPE_STRING, 1);
mail_display->scroll = E_SCROLL_FRAME (scroll);
mail_display->html = GTK_HTML (html);
mail_display->last_active = NULL;
mail_display->data = g_new0 (GData *, 1);
g_datalist_init (mail_display->data);
return GTK_WIDGET (mail_display);
}
E_MAKE_TYPE (mail_display, "MailDisplay", MailDisplay, mail_display_class_init, mail_display_init, PARENT_TYPE);