/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* e-summary.c * * Copyright (C) 2001 Ximian, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * Author: Iain Holmes */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "e-summary.h" #include "e-summary-preferences.h" #include "my-evolution-html.h" #include "Mailer.h" #include #include "e-util/e-dialog-utils.h" #include #define PARENT_TYPE (gtk_vbox_get_type ()) extern char *evolution_dir; static GList *all_summaries = NULL; static GtkObjectClass *e_summary_parent_class; struct _ESummaryMailFolderInfo { char *name; int count; int unread; }; struct _ESummaryPrivate { BonoboControl *control; GtkWidget *html_scroller; GtkWidget *html; GHashTable *protocol_hash; GList *connections; guint pending_reload_tag; guint tomorrow_timeout_id; gboolean frozen; int queued_draw_idle_id; }; typedef struct _ProtocolListener { ESummaryProtocolListener listener; void *closure; } ProtocolListener; static GHashTable *images_cache = NULL; static void free_protocol (gpointer key, gpointer value, gpointer user_data) { g_free (key); g_free (value); } static void destroy (GtkObject *object) { ESummary *summary; ESummaryPrivate *priv; summary = E_SUMMARY (object); priv = summary->priv; if (priv == NULL) { return; } all_summaries = g_list_remove (all_summaries, summary); if (priv->pending_reload_tag) { gtk_timeout_remove (priv->pending_reload_tag); priv->pending_reload_tag = 0; } if (priv->queued_draw_idle_id != 0) { g_source_remove (priv->queued_draw_idle_id); priv->queued_draw_idle_id = 0; } if (summary->mail) { e_summary_mail_free (summary); } if (summary->calendar) { e_summary_calendar_free (summary); } if (summary->rdf) { e_summary_rdf_free (summary); } if (summary->weather) { e_summary_weather_free (summary); } if (summary->tasks) { e_summary_tasks_free (summary); } if (summary->priv->control) { g_object_remove_weak_pointer (G_OBJECT (summary->priv->control), (void **) &summary->priv->control); summary->priv->control = NULL; } if (priv->tomorrow_timeout_id != 0) g_source_remove (priv->tomorrow_timeout_id); if (priv->protocol_hash) { g_hash_table_foreach (priv->protocol_hash, free_protocol, NULL); g_hash_table_destroy (priv->protocol_hash); } g_free (priv); summary->priv = NULL; e_summary_parent_class->destroy (object); } static gboolean draw_idle_cb (void *data) { ESummary *summary; GString *string; GtkHTMLStream *stream; char *html; char date[256], *date_utf; time_t t; summary = E_SUMMARY (data); string = g_string_new (HTML_1); t = time (NULL); strftime (date, 255, _("%A, %B %e %Y"), localtime (&t)); date_utf = e_utf8_from_locale_string (date); html = g_strdup_printf (HTML_2, date_utf); g_free (date_utf); g_string_append (string, html); g_free (html); g_string_append (string, HTML_3); /* Weather and RDF stuff here */ html = e_summary_weather_get_html (summary); if (html != NULL) { g_string_append (string, html); g_free (html); } html = e_summary_rdf_get_html (summary); if (html != NULL) { g_string_append (string, html); g_free (html); } g_string_append (string, HTML_4); html = (char *) e_summary_mail_get_html (summary); if (html != NULL) { g_string_append (string, html); } html = (char *) e_summary_calendar_get_html (summary); if (html != NULL) { g_string_append (string, html); } html = (char *) e_summary_tasks_get_html (summary); if (html != NULL) { g_string_append (string, html); } g_string_append (string, HTML_5); stream = gtk_html_begin (GTK_HTML (summary->priv->html)); GTK_HTML (summary->priv->html)->engine->newPage = FALSE; gtk_html_write (GTK_HTML (summary->priv->html), stream, string->str, strlen (string->str)); gtk_html_end (GTK_HTML (summary->priv->html), stream, GTK_HTML_STREAM_OK); g_string_free (string, TRUE); summary->priv->queued_draw_idle_id = 0; return FALSE; } void e_summary_draw (ESummary *summary) { g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); if (summary->mail == NULL || summary->calendar == NULL || summary->rdf == NULL || summary->weather == NULL || summary->tasks == NULL) { return; } if (summary->priv->queued_draw_idle_id != 0) return; summary->priv->queued_draw_idle_id = g_idle_add (draw_idle_cb, summary); } void e_summary_redraw_all (void) { GList *p; for (p = all_summaries; p; p = p->next) { e_summary_draw (E_SUMMARY (p->data)); } } static char * e_pixmap_file (const char *filename) { char *ret; char *edir; if (g_file_exists (filename)) { ret = g_strdup (filename); return ret; } /* Try the evolution images dir */ edir = g_concat_dir_and_file (EVOLUTION_IMAGESDIR, filename); if (g_file_exists (edir)) { ret = g_strdup (edir); g_free (edir); return ret; } g_free (edir); /* Try the evolution button images dir */ edir = g_concat_dir_and_file (EVOLUTION_BUTTONSDIR, filename); if (g_file_exists (edir)) { ret = g_strdup (edir); g_free (edir); return ret; } g_free (edir); /* Fall back to the gnome_pixmap_file */ ret = gnome_pixmap_file (filename); if (ret == NULL) { g_warning ("Could not find pixmap for %s", filename); } return ret; } struct _imgcache { char *buffer; int bufsize; }; static void e_summary_url_clicked (GtkHTML *html, const char *url, ESummary *summary) { char *protocol, *protocol_end; ProtocolListener *protocol_listener; protocol_end = strchr (url, ':'); if (protocol_end == NULL) { /* No url, let gnome work it out */ gnome_url_show (url, NULL); return; } protocol = g_strndup (url, protocol_end - url); protocol_listener = g_hash_table_lookup (summary->priv->protocol_hash, protocol); g_free (protocol); if (protocol_listener == NULL) { /* Again, let gnome work it out */ gnome_url_show (url, NULL); return; } protocol_listener->listener (summary, url, protocol_listener->closure); } static char * e_read_file_with_length (const char *filename, size_t *length) { int fd; struct stat stat_buf; char *buf; size_t bytes_read, size; g_return_val_if_fail (filename != NULL, NULL); fd = open (filename, O_RDONLY); g_return_val_if_fail (fd != -1, NULL); fstat (fd, &stat_buf); size = stat_buf.st_size; buf = g_new (char, size + 1); bytes_read = 0; while (bytes_read < size) { ssize_t rc; rc = read (fd, buf + bytes_read, size - bytes_read); if (rc < 0) { if (errno != EINTR) { close (fd); g_free (buf); return NULL; } } else if (rc == 0) { break; } else { bytes_read += rc; } } buf[bytes_read] = '\0'; if (length) { *length = bytes_read; } return buf; } static void e_summary_url_requested (GtkHTML *html, const char *url, GtkHTMLStream *stream, ESummary *summary) { char *filename; struct _imgcache *img = NULL; if (strncasecmp (url, "file:", 5) == 0) { url += 5; filename = e_pixmap_file (url); } else if (strchr (url, ':') >= strchr (url, '/')) { filename = e_pixmap_file (url); } else { filename = g_strdup (url); } if (filename == NULL) { gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR); return; } if (images_cache != NULL) { img = g_hash_table_lookup (images_cache, filename); } else { images_cache = g_hash_table_new (g_str_hash, g_str_equal); } if (img == NULL) { size_t length; char *contents; contents = e_read_file_with_length (filename, &length); if (contents == NULL) { return; } img = g_new (struct _imgcache, 1); img->buffer = contents; img->bufsize = length; g_hash_table_insert (images_cache, g_strdup (filename), img); } gtk_html_stream_write (stream, img->buffer, img->bufsize); gtk_html_stream_close (stream, GTK_HTML_STREAM_OK); } static void e_summary_evolution_protocol_listener (ESummary *summary, const char *uri, void *closure) { e_summary_change_current_view (summary, uri); } static void e_summary_class_init (GtkObjectClass *object_class) { object_class->destroy = destroy; e_summary_parent_class = g_type_class_ref(PARENT_TYPE); } static gboolean tomorrow_timeout (gpointer data); static void reset_tomorrow_timeout (ESummary *summary) { time_t now, day_end; now = time (NULL); if (summary->tz) day_end = time_day_end_with_zone (now, summary->tz); else day_end = time_day_end (now); /* (Yes, the number of milliseconds in a day is less than UINT_MAX) */ summary->priv->tomorrow_timeout_id = g_timeout_add ((day_end - now) * 1000, tomorrow_timeout, summary); } static gboolean tomorrow_timeout (gpointer data) { ESummary *summary = data; reset_tomorrow_timeout (summary); e_summary_reconfigure (summary); return FALSE; } #define DEFAULT_HTML "Summary%s" static void e_summary_init (ESummary *summary) { GConfClient *gconf_client; ESummaryPrivate *priv; char *def; summary->priv = g_new (ESummaryPrivate, 1); priv = summary->priv; priv->control = NULL; priv->frozen = TRUE; priv->pending_reload_tag = 0; priv->html_scroller = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->html_scroller), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); priv->html = gtk_html_new (); gtk_html_set_editable (GTK_HTML (priv->html), FALSE); gtk_html_set_default_content_type (GTK_HTML (priv->html), "text/html; charset=utf-8"); def = g_strdup_printf (DEFAULT_HTML, _("Please wait...")); gtk_html_load_from_string (GTK_HTML (priv->html), def, strlen (def)); g_free (def); g_signal_connect (priv->html, "url-requested", G_CALLBACK (e_summary_url_requested), summary); g_signal_connect (priv->html, "link-clicked", G_CALLBACK (e_summary_url_clicked), summary); gtk_container_add (GTK_CONTAINER (priv->html_scroller), priv->html); gtk_widget_show_all (priv->html_scroller); gtk_box_pack_start (GTK_BOX (summary), priv->html_scroller, TRUE, TRUE, 0); priv->protocol_hash = NULL; priv->connections = NULL; gconf_client = gconf_client_get_default (); summary->timezone = gconf_client_get_string (gconf_client, "/apps/evolution/calendar/display/timezone", NULL); if (!summary->timezone || !summary->timezone[0]) { g_free (summary->timezone); summary->timezone = g_strdup ("UTC"); } summary->tz = icaltimezone_get_builtin_timezone (summary->timezone); reset_tomorrow_timeout (summary); g_object_unref (gconf_client); priv->queued_draw_idle_id = 0; } E_MAKE_TYPE (e_summary, "ESummary", ESummary, e_summary_class_init, e_summary_init, PARENT_TYPE); GtkWidget * e_summary_new (ESummaryPrefs *prefs) { ESummary *summary; summary = gtk_type_new (e_summary_get_type ()); /* Just get a pointer to the global preferences */ summary->preferences = prefs; e_summary_add_protocol_listener (summary, "evolution", e_summary_evolution_protocol_listener, summary); e_summary_mail_init (summary); e_summary_calendar_init (summary); e_summary_tasks_init (summary); e_summary_rdf_init (summary); e_summary_weather_init (summary); all_summaries = g_list_prepend (all_summaries, summary); return GTK_WIDGET (summary); } BonoboControl * e_summary_get_control (ESummary *summary) { g_return_val_if_fail (summary != NULL, CORBA_OBJECT_NIL); g_return_val_if_fail (IS_E_SUMMARY (summary), CORBA_OBJECT_NIL); return summary->priv->control; } void e_summary_set_control (ESummary *summary, BonoboControl *control) { g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); if (summary->priv->control) g_object_remove_weak_pointer (G_OBJECT (summary->priv->control), (void **) &summary->priv->control); summary->priv->control = control; if (summary->priv->control) g_object_add_weak_pointer (G_OBJECT (summary->priv->control), (void **) &summary->priv->control); } static void do_summary_print (ESummary *summary) { GnomePrintContext *print_context; GnomePrintJob *print_master; GtkWidget *gpd; GnomePrintConfig *config = NULL; GtkWidget *preview_widget; gboolean preview = FALSE; gpd = gnome_print_dialog_new (NULL, _("Print Summary"), GNOME_PRINT_DIALOG_COPIES); switch (gtk_dialog_run (GTK_DIALOG (gpd))) { case GNOME_PRINT_DIALOG_RESPONSE_PRINT: preview = FALSE; break; case GNOME_PRINT_DIALOG_RESPONSE_PREVIEW: preview = TRUE; break; default: if (preview_widget != NULL) gtk_widget_destroy (preview_widget); gtk_widget_destroy (gpd); return; } config = gnome_print_dialog_get_config (GNOME_PRINT_DIALOG (gpd)); print_master = gnome_print_job_new (config); print_context = gnome_print_job_get_context (print_master); gtk_html_print (GTK_HTML (summary->priv->html), print_context); gnome_print_job_close (print_master); gtk_widget_destroy (gpd); if (preview) { preview_widget = gnome_print_job_preview_new (print_master, _("Print Preview")); gtk_widget_show (preview_widget); } else { int result = gnome_print_job_print (print_master); if (result == -1) e_notice (gpd, GTK_MESSAGE_ERROR, _("Printing of Summary failed")); } g_object_unref (print_master); } void e_summary_print (BonoboUIComponent *component, gpointer userdata, const char *cname) { ESummary *summary = userdata; do_summary_print (summary); } void e_summary_add_protocol_listener (ESummary *summary, const char *protocol, ESummaryProtocolListener listener, void *closure) { ProtocolListener *old; g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); g_return_if_fail (protocol != NULL); g_return_if_fail (listener != NULL); if (summary->priv->protocol_hash == NULL) { summary->priv->protocol_hash = g_hash_table_new (g_str_hash, g_str_equal); old = NULL; } else { old = g_hash_table_lookup (summary->priv->protocol_hash, protocol); } if (old != NULL) { return; } old = g_new (ProtocolListener, 1); old->listener = listener; old->closure = closure; g_hash_table_insert (summary->priv->protocol_hash, g_strdup (protocol), old); } static GNOME_Evolution_ShellView retrieve_shell_view_interface (BonoboControl *control) { Bonobo_ControlFrame control_frame; GNOME_Evolution_ShellView shell_view_interface; CORBA_Environment ev; control_frame = bonobo_control_get_control_frame (control, NULL); 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); if (BONOBO_EX (&ev)) shell_view_interface = CORBA_OBJECT_NIL; CORBA_exception_free (&ev); return shell_view_interface; } void e_summary_change_current_view (ESummary *summary, const char *uri) { GNOME_Evolution_ShellView svi; CORBA_Environment ev; g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); svi = retrieve_shell_view_interface (summary->priv->control); if (svi == CORBA_OBJECT_NIL) return; CORBA_exception_init (&ev); GNOME_Evolution_ShellView_changeCurrentView (svi, uri, &ev); CORBA_exception_free (&ev); bonobo_object_release_unref (svi, NULL); } void e_summary_set_message (ESummary *summary, const char *message, gboolean busy) { GNOME_Evolution_ShellView svi; CORBA_Environment ev; g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); svi = retrieve_shell_view_interface (summary->priv->control); if (svi == CORBA_OBJECT_NIL) return; CORBA_exception_init (&ev); GNOME_Evolution_ShellView_setMessage (svi, message ? message : "", busy, &ev); CORBA_exception_free (&ev); bonobo_object_release_unref (svi, NULL); } void e_summary_unset_message (ESummary *summary) { GNOME_Evolution_ShellView svi; CORBA_Environment ev; g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); svi = retrieve_shell_view_interface (summary->priv->control); if (svi == CORBA_OBJECT_NIL) return; CORBA_exception_init (&ev); GNOME_Evolution_ShellView_unsetMessage (svi, &ev); CORBA_exception_free (&ev); bonobo_object_release_unref (svi, NULL); } void e_summary_reconfigure (ESummary *summary) { if (summary->rdf != NULL) { e_summary_rdf_reconfigure (summary); } if (summary->weather != NULL) { e_summary_weather_reconfigure (summary); } if (summary->calendar != NULL) { e_summary_calendar_reconfigure (summary); } if (summary->tasks != NULL) { e_summary_tasks_reconfigure (summary); } e_summary_draw (summary); } void e_summary_reconfigure_all (void) { GList *p; /* This is here, because it only needs to be done once for all summaries */ e_summary_mail_reconfigure (); for (p = all_summaries; p; p = p->next) { e_summary_reconfigure (E_SUMMARY (p->data)); } } static gint e_summary_reload_timeout (gpointer closure) { ESummary *summary = closure; if (summary->rdf != NULL) e_summary_rdf_update (summary); if (summary->weather != NULL) e_summary_weather_update (summary); if (summary->calendar != NULL) e_summary_calendar_reconfigure (summary); if (summary->tasks != NULL) e_summary_tasks_reconfigure (summary); summary->priv->pending_reload_tag = 0; return FALSE; } void e_summary_reload (BonoboUIComponent *component, gpointer userdata, const char *cname) { ESummary *summary = userdata; /* This is an evil hack to work around a bug in gnome-vfs: gnome-vfs seems to not properly lock partially-constructed objects, so if you gnome_vfs_async_open and then immediately gnome_vfs_async_cancel, it is possible to start to destroy an object before it is totally constructed. Hilarity ensures. This is an evil and stupid hack, but it slows down our reload requests enough the gnome-vfs should be able to keep up. And given that these are not instantaneous operations to begin with, the users should be none the wiser. -JT */ if (summary->priv->pending_reload_tag) { gtk_timeout_remove (summary->priv->pending_reload_tag); } summary->priv->pending_reload_tag = gtk_timeout_add (80, e_summary_reload_timeout, summary); } int e_summary_count_connections (ESummary *summary) { GList *p; int count = 0; g_return_val_if_fail (IS_E_SUMMARY (summary), 0); for (p = summary->priv->connections; p; p = p->next) { ESummaryConnection *c; c = p->data; count += c->count (summary, c->closure); } return count; } GList * e_summary_add_connections (ESummary *summary) { GList *p; GList *connections = NULL; g_return_val_if_fail (IS_E_SUMMARY (summary), NULL); for (p = summary->priv->connections; p; p = p->next) { ESummaryConnection *c; GList *r; c = p->data; r = c->add (summary, c->closure); connections = g_list_concat (connections, r); } return connections; } void e_summary_set_online (ESummary *summary, GNOME_Evolution_OfflineProgressListener progress, gboolean online, ESummaryOnlineCallback callback, void *closure) { GList *p; g_return_if_fail (IS_E_SUMMARY (summary)); for (p = summary->priv->connections; p; p = p->next) { ESummaryConnection *c; c = p->data; c->callback = callback; c->callback_closure = closure; c->set_online (summary, progress, online, c->closure); if (callback != NULL) callback (summary, closure); } } void e_summary_add_online_connection (ESummary *summary, ESummaryConnection *connection) { g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); g_return_if_fail (connection != NULL); summary->priv->connections = g_list_prepend (summary->priv->connections, connection); } void e_summary_remove_online_connection (ESummary *summary, ESummaryConnection *connection) { GList *p; g_return_if_fail (summary != NULL); g_return_if_fail (IS_E_SUMMARY (summary)); g_return_if_fail (connection != NULL); p = g_list_find (summary->priv->connections, connection); g_return_if_fail (p != NULL); summary->priv->connections = g_list_remove_link (summary->priv->connections, p); g_list_free (p); }