Files
evolution/mail/mail-display.c
Dan Winship a9c8e8d594 Use gnome_vfs_mime_get_short_list_applications rather than
* mail-format.c (mail_lookup_handler): Use
	gnome_vfs_mime_get_short_list_applications rather than
	gnome_vfs_mime_get_default_application.

	* mail-display.c (pixmap_press): Construct the EPopupMenu array on
	the fly, based on the number of applications available to open the
	MIME type.
	(launch_cb): Figure out which menu item was clicked, and invoke
	the appropriate application. Ugh, messy, because of the EPopupMenu
	interface. Probably should get rewritten some day. Also, make this
	handle apps with expects_uris set too.

svn path=/trunk/; revision=10916
2001-07-09 16:37:04 +00:00

1899 lines
49 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 <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 "e-util/e-html-utils.h"
#include "addressbook/backend/ebook/e-book-util.h"
#include "e-searching-tokenizer.h"
#include "folder-browser-factory.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 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
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 (!strcmp (url, "x-evolution-decode-pgp:")) {
g_datalist_set_data (md->data, "show_pgp",
GINT_TO_POINTER (1));
mail_display_queue_redisplay (md);
} else
gnome_url_show (url);
}
static void
save_part (CamelMimePart *part)
{
GtkFileSelection *file_select;
char *filename;
if(save_pathname == NULL)
save_pathname = 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);
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_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 *tmpl, *tmpdir, *filename, *url, *argv[2];
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;
tmpl = g_strdup ("/tmp/evolution.XXXXXX");
#ifdef HAVE_MKDTEMP
tmpdir = mkdtemp (tmpl);
#else
tmpdir = mktemp (tmpl);
if (tmpdir) {
if (mkdir (tmpdir, S_IRWXU) == -1)
tmpdir = NULL;
}
#endif
if (!tmpdir) {
char *msg = g_strdup_printf (_("Could not create temporary "
"directory: %s"),
g_strerror (errno));
gnome_error_dialog (msg);
g_free (tmpl);
g_free (msg);
return;
}
filename = make_safe_filename (tmpdir, part);
if (!write_data_to_file (part, filename, TRUE)) {
g_free (tmpl);
g_free (filename);
return;
}
if (app->expects_uris == GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS) {
url = g_strdup_printf ("file:%s", filename);
g_free (filename);
filename = url;
}
argv[0] = app->command;
argv[1] = filename;
gnome_execute_async (tmpdir, 2, argv);
g_free (tmpdir);
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");
if (mail_part_is_inline (part))
camel_mime_part_set_disposition (part, "attachment");
else
camel_mime_part_set_disposition (part, "inline");
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;
}
if (mail_part_is_inline (part))
camel_mime_part_set_disposition (part, "attachment");
else
camel_mime_part_set_disposition (part, "inline");
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 };
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;
}
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) {
if (!mail_part_is_inline (part)) {
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)
pixbuf = pixbuf_for_mime_type (pbl->type);
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);
gtk_widget_set_usize (pbl->pixmap, 24, 24);
/* 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_destroy (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;
const MailConfigIdentity *id;
id = mail_config_get_default_identity ();
CORBA_exception_init (&ev);
if (id){
char *from_address;
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);
bonobo_property_bag_client_set_value_string (
prop_bag, "my_address",
id ? id->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) {
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)) {
return NULL;
}
save_part (part);
return NULL;
}
g_warning ("part not found");
#if 0
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 */
ba = g_hash_table_lookup (urls, url);
if (ba) {
return ba;
}
#endif
return NULL;
}
static gboolean
on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data)
{
MailDisplay *md = data;
GHashTable *urls;
CamelMedium *medium;
CamelDataWrapper *wrapper;
OAF_ServerInfo *component;
GtkWidget *embedded;
BonoboObjectClient *server;
Bonobo_PersistStream persist;
CORBA_Environment ev;
GByteArray *ba;
CamelStream *cstream;
BonoboStream *bstream;
char *cid;
cid = eb->classid;
if (!strncmp (cid, "popup:", 6))
cid += 6;
if (strncmp (cid, "cid:", 4) != 0)
return FALSE;
urls = g_datalist_get_data (md->data, "part_urls");
g_return_val_if_fail (urls != NULL, FALSE);
medium = g_hash_table_lookup (urls, cid);
g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), FALSE);
if (cid != eb->classid) {
/* This is a part wrapper */
#ifdef USE_OLD_DISPLAY_STYLE
GtkWidget *ebox;
#else
GtkWidget *button, *mainbox, *hbox, *arrow, *popup;
MailMimeHandler *handler;
#endif
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 (medium);
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 (cid);
pbl->pixmap = bonobo_ui_toolbar_icon_new ();
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);
#ifdef USE_OLD_DISPLAY_STYLE
ebox = gtk_event_box_new ();
gtk_widget_set_sensitive (GTK_WIDGET (ebox), TRUE);
gtk_widget_add_events (GTK_WIDGET (ebox),
GDK_BUTTON_PRESS_MASK);
gtk_object_set_data (GTK_OBJECT (ebox), "MailDisplay", md);
gtk_object_set_data (GTK_OBJECT (ebox), "CamelMimePart",
medium);
gtk_object_set_data_full (GTK_OBJECT (ebox), "mime_type",
g_strdup (eb->type),
(GDestroyNotify)g_free);
gtk_signal_connect (GTK_OBJECT (ebox), "button_press_event",
GTK_SIGNAL_FUNC (pixmap_press), md->scroll);
gtk_container_add (GTK_CONTAINER (ebox), pbl->pixmap);
gtk_widget_show_all (ebox);
gtk_container_add (GTK_CONTAINER (eb), ebox);
#else
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), medium);
hbox = gtk_hbox_new (FALSE, 2);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
if (mail_part_is_inline (CAMEL_MIME_PART (medium))) {
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",
medium);
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);
#endif
return TRUE;
}
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 (medium);
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 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, *md_addr;
if (camel_internet_address_get (from, 0, &md_name, &md_addr) &&
!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) {
CamelDataWrapper *data;
CamelStream *stream_mem;
g_return_if_fail (CAMEL_IS_MEDIUM (medium));
data = camel_medium_get_content_object (medium);
if (!mail_content_loaded (data, md)) {
gtk_html_end (html, handle, GTK_HTML_STREAM_ERROR);
return;
}
ba = g_byte_array_new ();
stream_mem = camel_stream_mem_new_with_byte_array (ba);
camel_data_wrapper_write_to_stream (data, stream_mem);
gtk_html_write (html, handle, ba->data, ba->len);
camel_object_unref (CAMEL_OBJECT (stream_mem));
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);
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_redisplay_when_loaded (md, ba, load_http,
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));
if (camel_internet_address_get (from, 0, &name, &addr))
e_book_query_address_locally (addr, ebook_callback, md);
}
}
gtk_html_end (html, handle, GTK_HTML_STREAM_ERROR);
}
struct _load_content_msg {
struct _mail_msg msg;
MailDisplay *display;
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 void
load_content_loaded (struct _mail_msg *mm)
{
struct _load_content_msg *m = (struct _load_content_msg *)mm;
if (m->display->current_message == m->message)
mail_display_queue_redisplay (m->display);
}
static void
load_content_free (struct _mail_msg *mm)
{
struct _load_content_msg *m = (struct _load_content_msg *)mm;
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,
};
void
mail_display_redisplay_when_loaded (MailDisplay *md,
gconstpointer key,
void (*callback)(MailDisplay *, gpointer),
gpointer data)
{
struct _load_content_msg *m;
GHashTable *loading;
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->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_html_write (GtkHTML *html, GtkHTMLStream *stream,
const char *format, ...)
{
char *buf;
va_list ap;
va_start (ap, format);
buf = g_strdup_vprintf (format, ap);
va_end (ap);
gtk_html_write (html, stream, buf, strlen (buf));
g_free (buf);
}
void
mail_text_write (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_full (buf,
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);
g_free (buf);
}
void
mail_error_write (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);
gtk_html_write (html, stream, "<em><font color=red>", 20);
gtk_html_write (html, stream, htmltext, strlen (htmltext));
gtk_html_write (html, stream, "</font></em><br>", 16);
g_free (htmltext);
g_free (buf);
}
static void
clear_data (CamelObject *object, gpointer event_data, gpointer user_data)
{
GData *data = user_data;
g_datalist_clear (&data);
}
/**
* 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)
{
md->last_active = NULL;
md->stream = gtk_html_begin (GTK_HTML (md->html));
if (!unscroll) {
/* This is a hack until there's a clean way to do this. */
GTK_HTML (md->html)->engine->newPage = FALSE;
}
mail_html_write (md->html, md->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 (md->html, md->stream, "<body marginwidth=0 marginheight=0>\n");
if (md->current_message) {
MailConfigDisplayStyle style = mail_config_get_message_display_style ();
if (style == MAIL_CONFIG_DISPLAY_SOURCE)
mail_format_raw_message (md->current_message, md);
else {
g_datalist_set_data (md->data, "full_headers", GINT_TO_POINTER (style == MAIL_CONFIG_DISPLAY_FULL_HEADERS));
mail_format_mime_message (md->current_message, md);
}
}
mail_html_write (md->html, md->stream, "</body></html>\n");
gtk_html_end (md->html, md->stream, GTK_HTML_STREAM_OK);
md->stream = NULL;
}
/**
* 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_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->stream = NULL;
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 ();
}
static void
mail_display_destroy (GtkObject *object)
{
MailDisplay *mail_display = MAIL_DISPLAY (object);
g_free (mail_display->selection);
g_datalist_clear (mail_display->data);
g_free (mail_display->data);
gtk_widget_destroy (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);
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);
}
static void
link_save_as (GtkWidget *w, MailDisplay *mail_display)
{
g_print ("FIXME save %s\n", mail_display->html->pointer_url);
}
static void
link_copy_location (GtkWidget *w, MailDisplay *mail_display)
{
g_free (mail_display->selection);
mail_display->selection = g_strdup (mail_display->html->pointer_url);
if (! gtk_selection_owner_set (GTK_WIDGET (mail_display->invisible), GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME))
g_warning ("Damn");
}
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 },
{ N_("Save Link as (FIXME)"), NULL,
GTK_SIGNAL_FUNC (link_save_as), NULL, MASK_URL },
{ 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;
};
/* 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);
g_free (pop);
}
}
static void
popup_widget_destroy_cb (GtkWidget *w, gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
gtk_widget_destroy (pop->win);
}
static void
popup_window_destroy_cb (GtkWidget *w, gpointer user_data)
{
PopupInfo *pop = (PopupInfo *) user_data;
if (pop->widget_destroy_handle) {
gtk_signal_disconnect (GTK_OBJECT (pop->w), pop->widget_destroy_handle);
pop->widget_destroy_handle = 0;
}
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);
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);
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);
gtk_widget_add_events (pop->w, GDK_BUTTON_PRESS_MASK);
if (pop->destroy_timeout == 0)
pop->destroy_timeout = gtk_timeout_add (5000, popup_timeout_cb, pop);
}
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 GtkWidget *
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);
pop->widget_destroy_handle = gtk_signal_connect (GTK_OBJECT (w),
"destroy",
GTK_SIGNAL_FUNC (popup_widget_destroy_cb),
pop);
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->win;
}
/* Copied from e-shell-view.c */
static GtkWidget *
find_socket (GtkContainer *container)
{
GList *children, *tmp;
children = gtk_container_children (container);
while (children) {
if (BONOBO_IS_SOCKET (children->data))
return children->data;
else if (GTK_IS_CONTAINER (children->data)) {
GtkWidget *socket = find_socket (children->data);
if (socket)
return socket;
}
tmp = children->next;
g_list_free_1 (children);
children = tmp;
}
return NULL;
}
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 *socket;
GtkWidget *popup_thing;
GtkWidget *win;
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)) {
popup_thing = bonobo_widget_new_control ("OAFIID:GNOME_Evolution_Addressbook_AddressPopup",
CORBA_OBJECT_NIL);
socket = find_socket (GTK_CONTAINER (popup_thing));
bonobo_widget_set_property (BONOBO_WIDGET (popup_thing),
"email", url+7,
NULL);
win = make_popup_window (popup_thing);
gtk_signal_connect_object (GTK_OBJECT (socket),
"destroy",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (win));
} 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;
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_ALWAYS);
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 ();
html_engine_set_tokenizer (GTK_HTML (html)->engine, e_searching_tokenizer_new ());
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);
mail_display->scroll = E_SCROLL_FRAME (scroll);
mail_display->html = GTK_HTML (html);
mail_display->stream = NULL;
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);