2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtkvbox.h>
25 #include <gtk/gtkcontainer.h>
26 #include <gtk/gtkeditable.h>
27 #include <gtk/gtkwindow.h>
28 #include <gtk/gtktext.h>
29 #include <gtk/gtkmenu.h>
30 #include <gtk/gtkmenuitem.h>
37 #include "messageview.h"
38 #include "message_search.h"
39 #include "headerview.h"
40 #include "summaryview.h"
46 #include "sourcewindow.h"
47 #include "addressbook.h"
48 #include "alertpanel.h"
49 #include "inputdialog.h"
50 #include "manage_window.h"
52 #include "procheader.h"
56 #include "prefs_common.h"
57 #include "prefs_account.h"
61 #include "send_message.h"
62 #include "stock_pixmap.h"
64 #include "filtering.h"
67 static GList *messageview_list = NULL;
69 static void messageview_destroy_cb (GtkWidget *widget,
70 MessageView *messageview);
71 static void messageview_size_allocate_cb(GtkWidget *widget,
72 GtkAllocation *allocation);
73 static gboolean key_pressed (GtkWidget *widget,
75 MessageView *messageview);
77 static void return_receipt_show (NoticeView *noticeview,
79 static void return_receipt_send_clicked (NoticeView *noticeview,
81 static void partial_recv_show (NoticeView *noticeview,
83 static void partial_recv_dload_clicked (NoticeView *noticeview,
85 static void partial_recv_del_clicked (NoticeView *noticeview,
87 static void save_as_cb (gpointer data,
90 static void print_cb (gpointer data,
93 static void close_cb (gpointer data,
96 static void copy_cb (gpointer data,
99 static void allsel_cb (gpointer data,
102 static void search_cb (gpointer data,
106 static void set_charset_cb (gpointer data,
109 static void view_source_cb (gpointer data,
112 static void show_all_header_cb (gpointer data,
116 static void compose_cb (gpointer data,
119 static void reply_cb (gpointer data,
122 static void reedit_cb (gpointer data,
126 static PrefsAccount *select_account_from_list
128 static void addressbook_open_cb (gpointer data,
131 static void add_address_cb (gpointer data,
134 static void create_filter_cb (gpointer data,
137 static void create_processing_cb (gpointer data,
141 static void about_cb (gpointer data,
144 static void messageview_update (MessageView *msgview);
145 static gboolean messageview_update_msg (gpointer source, gpointer data);
147 static GList *msgview_list = NULL;
148 static GtkItemFactoryEntry msgview_entries[] =
150 {N_("/_File"), NULL, NULL, 0, "<Branch>"},
151 {N_("/_File/_Save as..."), NULL, save_as_cb, 0, NULL},
152 {N_("/_File/_Print..."), NULL, print_cb, 0, NULL},
153 {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
154 {N_("/_File/_Close"), NULL, close_cb, 0, NULL},
156 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
157 {N_("/_Edit/_Copy"), NULL, copy_cb, 0, NULL},
158 {N_("/_Edit/Select _all"), NULL, allsel_cb, 0, NULL},
159 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
160 {N_("/_Edit/_Find in current message..."),
161 NULL, search_cb, 0, NULL},
163 {N_("/_View"), NULL, NULL, 0, "<Branch>"},
165 #define CODESET_SEPARATOR \
166 {N_("/_View/_Code set/---"), NULL, NULL, 0, "<Separator>"}
167 #define CODESET_ACTION(action) \
168 NULL, set_charset_cb, action, "/View/Code set/Auto detect"
170 {N_("/_View/_Code set"), NULL, NULL, 0, "<Branch>"},
171 {N_("/_View/_Code set/_Auto detect"),
172 NULL, set_charset_cb, C_AUTO, "<RadioItem>"},
174 {N_("/_View/_Code set/7bit ascii (US-ASC_II)"),
175 CODESET_ACTION(C_US_ASCII)},
178 {N_("/_View/_Code set/Unicode (_UTF-8)"),
179 CODESET_ACTION(C_UTF_8)},
182 {N_("/_View/_Code set/Western European (ISO-8859-_1)"),
183 CODESET_ACTION(C_ISO_8859_1)},
184 {N_("/_View/_Code set/Western European (ISO-8859-15)"),
185 CODESET_ACTION(C_ISO_8859_15)},
188 {N_("/_View/_Code set/Central European (ISO-8859-_2)"),
189 CODESET_ACTION(C_ISO_8859_2)},
191 {N_("/_View/_Code set/_Baltic (ISO-8859-13)"),
192 CODESET_ACTION(C_ISO_8859_13)},
193 {N_("/_View/_Code set/Baltic (ISO-8859-_4)"),
194 CODESET_ACTION(C_ISO_8859_4)},
196 {N_("/_View/_Code set/Greek (ISO-8859-_7)"),
197 CODESET_ACTION(C_ISO_8859_7)},
199 {N_("/_View/_Code set/Turkish (ISO-8859-_9)"),
200 CODESET_ACTION(C_ISO_8859_9)},
202 {N_("/_View/_Code set/Cyrillic (ISO-8859-_5)"),
203 CODESET_ACTION(C_ISO_8859_5)},
204 {N_("/_View/_Code set/Cyrillic (KOI8-_R)"),
205 CODESET_ACTION(C_KOI8_R)},
206 {N_("/_View/_Code set/Cyrillic (KOI8-U)"),
207 CODESET_ACTION(C_KOI8_U)},
208 {N_("/_View/_Code set/Cyrillic (Windows-1251)"),
209 CODESET_ACTION(C_CP1251)},
212 {N_("/_View/_Code set/Japanese (ISO-2022-_JP)"),
213 CODESET_ACTION(C_ISO_2022_JP)},
215 {N_("/_View/_Code set/Japanese (ISO-2022-JP-2)"),
216 CODESET_ACTION(C_ISO_2022_JP_2)},
218 {N_("/_View/_Code set/Japanese (_EUC-JP)"),
219 CODESET_ACTION(C_EUC_JP)},
220 {N_("/_View/_Code set/Japanese (_Shift__JIS)"),
221 CODESET_ACTION(C_SHIFT_JIS)},
224 {N_("/_View/_Code set/Simplified Chinese (_GB2312)"),
225 CODESET_ACTION(C_GB2312)},
226 {N_("/_View/_Code set/Traditional Chinese (_Big5)"),
227 CODESET_ACTION(C_BIG5)},
228 {N_("/_View/_Code set/Traditional Chinese (EUC-_TW)"),
229 CODESET_ACTION(C_EUC_TW)},
230 {N_("/_View/_Code set/Chinese (ISO-2022-_CN)"),
231 CODESET_ACTION(C_ISO_2022_CN)},
233 {N_("/_View/_Code set/Korean (EUC-_KR)"),
234 CODESET_ACTION(C_EUC_KR)},
235 {N_("/_View/_Code set/Korean (ISO-2022-KR)"),
236 CODESET_ACTION(C_ISO_2022_KR)},
238 {N_("/_View/_Code set/Thai (TIS-620)"),
239 CODESET_ACTION(C_TIS_620)},
240 {N_("/_View/_Code set/Thai (Windows-874)"),
241 CODESET_ACTION(C_WINDOWS_874)},
244 #undef CODESET_SEPARATOR
245 #undef CODESET_ACTION
247 {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
248 {N_("/_View/Mess_age source"), NULL, view_source_cb, 0, NULL},
249 {N_("/_View/Show all _headers"),NULL, show_all_header_cb, 0, "<ToggleItem>"},
251 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
252 {N_("/_Message/Compose _new message"),
253 NULL, compose_cb, 0, NULL},
254 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
255 {N_("/_Message/_Reply"), NULL, reply_cb, COMPOSE_REPLY, NULL},
256 {N_("/_Message/Repl_y to/_all"),
257 NULL, reply_cb, COMPOSE_REPLY_TO_ALL, NULL},
258 {N_("/_Message/Repl_y to/_sender"),
259 NULL, reply_cb, COMPOSE_REPLY_TO_SENDER, NULL},
260 {N_("/_Message/Repl_y to/mailing _list"),
261 NULL, reply_cb, COMPOSE_REPLY_TO_LIST, NULL},
262 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
263 {N_("/_Message/_Forward"), NULL, reply_cb, COMPOSE_FORWARD, NULL},
264 {N_("/_Message/For_ward as attachment"),
265 NULL, reply_cb, COMPOSE_FORWARD_AS_ATTACH, NULL},
266 {N_("/_Message/Redirec_t"), NULL, reply_cb, COMPOSE_REDIRECT, NULL},
267 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
268 {N_("/_Message/Re-_edit"), NULL, reedit_cb, 0, NULL},
270 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
271 {N_("/_Tools/_Address book"), NULL, addressbook_open_cb, 0, NULL},
272 {N_("/_Tools/Add sender to address boo_k"),
273 NULL, add_address_cb, 0, NULL},
274 {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
275 {N_("/_Tools/_Create filter rule"),
276 NULL, NULL, 0, "<Branch>"},
277 {N_("/_Tools/_Create filter rule/_Automatically"),
278 NULL, create_filter_cb, FILTER_BY_AUTO, NULL},
279 {N_("/_Tools/_Create filter rule/by _From"),
280 NULL, create_filter_cb, FILTER_BY_FROM, NULL},
281 {N_("/_Tools/_Create filter rule/by _To"),
282 NULL, create_filter_cb, FILTER_BY_TO, NULL},
283 {N_("/_Tools/_Create filter rule/by _Subject"),
284 NULL, create_filter_cb, FILTER_BY_SUBJECT, NULL},
285 {N_("/_Tools/Create processing rule/"),
286 NULL, NULL, 0, "<Branch>"},
287 {N_("/_Tools/Create processing rule/_Automatically"),
288 NULL, create_processing_cb, FILTER_BY_AUTO, NULL},
289 {N_("/_Tools/Create processing rule/by _From"),
290 NULL, create_processing_cb, FILTER_BY_FROM, NULL},
291 {N_("/_Tools/Create processing rule/by _To"),
292 NULL, create_processing_cb, FILTER_BY_TO, NULL},
293 {N_("/_Tools/Create processing rule/by _Subject"),
294 NULL, create_processing_cb, FILTER_BY_SUBJECT, NULL},
295 {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
296 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
298 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
299 {N_("/_Help/_About"), NULL, about_cb, 0, NULL}
302 MessageView *messageview_create(MainWindow *mainwin)
304 MessageView *messageview;
306 HeaderView *headerview;
308 NoticeView *noticeview;
310 debug_print("Creating message view...\n");
311 messageview = g_new0(MessageView, 1);
313 headerview = headerview_create();
315 noticeview = noticeview_create(mainwin);
317 mimeview = mimeview_create(mainwin);
318 mimeview->textview = textview_create();
319 mimeview->textview->messageview = messageview;
320 mimeview->messageview = messageview;
322 vbox = gtk_vbox_new(FALSE, 0);
323 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET_PTR(headerview),
325 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET_PTR(noticeview),
327 gtk_box_pack_start(GTK_BOX(vbox),
328 GTK_WIDGET_PTR(mimeview), TRUE, TRUE, 0);
329 gtk_widget_show(vbox);
331 messageview->vbox = vbox;
332 messageview->new_window = FALSE;
333 messageview->window = NULL;
334 messageview->headerview = headerview;
335 messageview->mimeview = mimeview;
336 messageview->noticeview = noticeview;
337 messageview->mainwin = mainwin;
339 messageview->statusbar = NULL;
340 messageview->statusbar_cid = 0;
342 messageview->msginfo_update_callback_id =
343 hooks_register_hook(MSGINFO_UPDATE_HOOKLIST, messageview_update_msg, (gpointer) messageview);
348 GList *messageview_get_msgview_list(void)
353 void messageview_update_actions_menu(MessageView *msgview)
355 GtkItemFactory *ifactory;
357 /* Messages opened in a new window do not have a menu bar */
358 if (msgview->menubar == NULL)
360 ifactory = gtk_item_factory_from_widget(msgview->menubar);
361 action_update_msgview_menu(ifactory, "/Tools/Actions", msgview);
364 void messageview_add_toolbar(MessageView *msgview, GtkWidget *window)
366 GtkWidget *handlebox;
369 GtkWidget *statusbar;
370 guint n_menu_entries;
372 vbox = gtk_vbox_new(FALSE, 0);
373 gtk_widget_show(vbox);
374 gtk_container_add(GTK_CONTAINER(window), vbox);
376 n_menu_entries = sizeof(msgview_entries) / sizeof(msgview_entries[0]);
377 menubar = menubar_create(window, msgview_entries,
378 n_menu_entries, "<MessageView>", msgview);
379 gtk_widget_show(menubar);
380 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
382 handlebox = gtk_handle_box_new();
383 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
384 msgview->toolbar = toolbar_create(TOOLBAR_MSGVIEW, handlebox,
387 statusbar = gtk_statusbar_new();
388 gtk_widget_show(statusbar);
389 gtk_box_pack_end(GTK_BOX(vbox), statusbar, FALSE, FALSE, 0);
390 msgview->statusbar = statusbar;
391 msgview->statusbar_cid = gtk_statusbar_get_context_id
392 (GTK_STATUSBAR(statusbar), "Message View");
394 msgview->handlebox = handlebox;
395 msgview->menubar = menubar;
397 gtk_container_add(GTK_CONTAINER(vbox),
398 GTK_WIDGET_PTR(msgview));
400 messageview_update_actions_menu(msgview);
402 msgview_list = g_list_append(msgview_list, msgview);
405 MessageView *messageview_create_with_new_window(MainWindow *mainwin)
407 MessageView *msgview;
410 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
411 gtk_window_set_title(GTK_WINDOW(window), _("Sylpheed - Message View"));
412 gtk_window_set_wmclass(GTK_WINDOW(window), "message_view", "Sylpheed");
413 gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
414 gtk_widget_set_size_request(window, prefs_common.msgwin_width,
415 prefs_common.msgwin_height);
417 msgview = messageview_create(mainwin);
419 g_signal_connect(G_OBJECT(window), "size_allocate",
420 G_CALLBACK(messageview_size_allocate_cb),
422 g_signal_connect(G_OBJECT(window), "destroy",
423 G_CALLBACK(messageview_destroy_cb), msgview);
424 g_signal_connect(G_OBJECT(window), "key_press_event",
425 G_CALLBACK(key_pressed), msgview);
427 messageview_add_toolbar(msgview, window);
429 gtk_widget_grab_focus(msgview->mimeview->textview->text);
430 gtk_widget_show(window);
432 msgview->new_window = TRUE;
433 msgview->window = window;
434 msgview->visible = TRUE;
436 toolbar_set_style(msgview->toolbar->toolbar, msgview->handlebox,
437 prefs_common.toolbar_style);
438 messageview_init(msgview);
443 void messageview_init(MessageView *messageview)
445 headerview_init(messageview->headerview);
446 mimeview_init(messageview->mimeview);
447 /*messageview_set_font(messageview);*/
449 noticeview_hide(messageview->noticeview);
452 static void notification_convert_header(gchar *dest, gint len,
458 g_return_if_fail(src_ != NULL);
459 g_return_if_fail(dest != NULL);
463 Xstrndup_a(src, src_, len, return);
467 if (is_ascii_str(src)) {
468 strncpy2(dest, src, len);
469 dest[len - 1] = '\0';
472 conv_encode_header(dest, len, src, header_len, FALSE);
475 static gint disposition_notification_send(MsgInfo *msginfo)
478 gchar tmp[MAXPATHLEN + 1];
481 PrefsAccount *account;
484 FolderItem *queue, *outbox;
490 if ((!msginfo->returnreceiptto) &&
491 (!msginfo->dispositionnotificationto))
494 /* RFC2298: Test for Return-Path */
495 if (msginfo->dispositionnotificationto)
496 to = msginfo->dispositionnotificationto;
498 to = msginfo->returnreceiptto;
500 ok = procheader_get_header_from_msginfo(msginfo, buf, sizeof(buf),
503 gchar *to_addr = g_strdup(to);
504 extract_address(to_addr);
505 extract_address(buf);
506 ok = strcmp(to_addr, buf);
509 strncpy(buf, _("<No Return-Path found>"),
516 message = g_strdup_printf(
517 _("The notification address to which the "
518 "return receipt is to be sent\n"
519 "does not correspond to the return path:\n"
520 "Notification address: %s\n"
522 "It is advised to not to send the return "
523 "receipt."), to, buf);
524 val = alertpanel(_("Warning"), message, _("Send"),
525 _("+Don't Send"), NULL);
527 if (val != G_ALERTDEFAULT)
531 ac_list = account_find_all_from_address(NULL, msginfo->to);
532 ac_list = account_find_all_from_address(ac_list, msginfo->cc);
534 if (ac_list == NULL) {
535 alertpanel_error(_("This message is asking for a return "
536 "receipt notification\n"
537 "but according to its 'To:' and 'CC:' "
538 "headers it was not\nofficially addressed "
540 "Receipt notification cancelled."));
544 if (g_list_length(ac_list) > 1)
545 account = select_account_from_list(ac_list);
547 account = (PrefsAccount *) ac_list->data;
548 g_list_free(ac_list);
553 /* write to temporary file */
554 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg%d",
555 get_rc_dir(), G_DIR_SEPARATOR, (gint)msginfo);
557 if ((fp = fopen(tmp, "wb")) == NULL) {
558 FILE_OP_ERROR(tmp, "fopen");
562 /* chmod for security */
563 if (change_file_mode_rw(fp, tmp) < 0) {
564 FILE_OP_ERROR(tmp, "chmod");
565 g_warning("can't change file mode\n");
570 extract_address(addr);
573 /* write queue headers */
574 fprintf(fp, "AF:\n");
575 fprintf(fp, "NF:0\n");
576 fprintf(fp, "PS:10\n");
577 fprintf(fp, "SRH:1\n");
578 fprintf(fp, "SFN:\n");
579 fprintf(fp, "DSR:\n");
580 fprintf(fp, "MID:\n");
581 fprintf(fp, "CFG:\n");
582 fprintf(fp, "PT:0\n");
583 fprintf(fp, "S:%s\n", account->address);
584 fprintf(fp, "RQ:\n");
585 if (account->smtp_server)
586 fprintf(fp, "SSV:%s\n", account->smtp_server);
588 fprintf(fp, "SSV:\n");
589 fprintf(fp, "SSH:\n");
590 fprintf(fp, "R:<%s>\n", addrp);
594 /* check whether we need to save the message */
595 outbox = account_get_special_folder(account, F_OUTBOX);
596 if (folder_get_default_outbox() == outbox && !prefs_common.savemsg)
599 path = folder_item_get_identifier(outbox);
600 fprintf(fp, "SCF:%s\n", path);
607 get_rfc822_date(buf, sizeof(buf));
608 fprintf(fp, "Date: %s\n", buf);
611 if (account->name && *account->name) {
612 notification_convert_header
613 (buf, sizeof(buf), account->name,
615 fprintf(fp, "From: %s <%s>\n", buf, account->address);
617 fprintf(fp, "From: %s\n", account->address);
619 fprintf(fp, "To: %s\n", to);
622 notification_convert_header(buf, sizeof(buf), msginfo->subject,
623 strlen("Subject: "));
624 fprintf(fp, "Subject: Disposition notification: %s\n", buf);
627 generate_msgid(account->address, buf, sizeof buf);
628 fprintf(fp, "Message-Id: <%s>\n", buf);
630 if (fclose(fp) == EOF) {
631 FILE_OP_ERROR(tmp, "fclose");
636 /* put it in queue */
637 queue = account_get_special_folder(account, F_QUEUE);
638 if (!queue) queue = folder_get_default_queue();
640 g_warning("can't find queue folder\n");
644 folder_item_scan(queue);
645 if ((num = folder_item_add_msg(queue, tmp, NULL, TRUE)) < 0) {
646 g_warning("can't queue the message\n");
652 path = folder_item_fetch_msg(queue, num);
653 ok = procmsg_send_message_queue(path);
655 folder_item_remove_msg(queue, num);
660 GList *messageview_get_window_list(void)
662 return messageview_list;
665 static gboolean find_encrypted_func(GNode *node, gpointer data)
667 MimeInfo *mimeinfo = (MimeInfo *) node->data;
668 MimeInfo **encinfo = (MimeInfo **) data;
670 if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
678 static MimeInfo *find_encrypted_part(MimeInfo *rootinfo)
680 MimeInfo *encinfo = NULL;
682 g_node_traverse(rootinfo->node, G_IN_ORDER, G_TRAVERSE_ALL, -1,
683 find_encrypted_func, &encinfo);
688 gint messageview_show(MessageView *messageview, MsgInfo *msginfo,
689 gboolean all_headers)
692 MimeInfo *mimeinfo, *encinfo;
694 g_return_val_if_fail(msginfo != NULL, -1);
696 mimeinfo = procmime_scan_message(msginfo);
698 textview_show_error(messageview->mimeview->textview);
702 while ((encinfo = find_encrypted_part(mimeinfo)) != NULL) {
703 debug_print("decrypting message part\n");
704 if (privacy_mimeinfo_decrypt(encinfo) < 0)
708 file = procmsg_get_message_file_path(msginfo);
710 g_warning("can't get message file path.\n");
711 procmime_mimeinfo_free_all(mimeinfo);
712 textview_show_error(messageview->mimeview->textview);
716 if (messageview->msginfo != msginfo) {
717 procmsg_msginfo_free(messageview->msginfo);
718 messageview->msginfo = procmsg_msginfo_get_full_info(msginfo);
720 headerview_show(messageview->headerview, messageview->msginfo);
722 messageview->all_headers = all_headers;
723 textview_set_all_headers(messageview->mimeview->textview, all_headers);
725 mimeview_show_message(messageview->mimeview, mimeinfo, file);
727 if (messageview->msginfo->partial_recv)
728 partial_recv_show(messageview->noticeview,
729 messageview->msginfo);
730 else if ((messageview->msginfo->dispositionnotificationto ||
731 messageview->msginfo->returnreceiptto) &&
732 !MSG_IS_RETRCPT_SENT(messageview->msginfo->flags))
733 return_receipt_show(messageview->noticeview,
734 messageview->msginfo);
736 noticeview_hide(messageview->noticeview);
743 void messageview_reflect_prefs_pixmap_theme(void)
746 MessageView *msgview;
748 for (cur = msgview_list; cur != NULL; cur = cur->next) {
749 msgview = (MessageView*)cur->data;
750 toolbar_update(TOOLBAR_MSGVIEW, msgview);
751 mimeview_update(msgview->mimeview);
755 void messageview_clear(MessageView *messageview)
757 procmsg_msginfo_free(messageview->msginfo);
758 messageview->msginfo = NULL;
759 messageview->filtered = FALSE;
760 mimeview_clear(messageview->mimeview);
761 headerview_clear(messageview->headerview);
762 noticeview_hide(messageview->noticeview);
765 void messageview_destroy(MessageView *messageview)
767 debug_print("destroy messageview\n");
768 messageview_list = g_list_remove(messageview_list, messageview);
770 hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST,
771 messageview->msginfo_update_callback_id);
773 headerview_destroy(messageview->headerview);
774 mimeview_destroy(messageview->mimeview);
775 noticeview_destroy(messageview->noticeview);
777 procmsg_msginfo_free(messageview->msginfo);
778 toolbar_clear_list(TOOLBAR_MSGVIEW);
779 if (messageview->toolbar) {
780 toolbar_destroy(messageview->toolbar);
781 g_free(messageview->toolbar);
784 msgview_list = g_list_remove(msgview_list, messageview);
789 void messageview_delete(MessageView *msgview)
791 MsgInfo *msginfo = NULL;
792 FolderItem *trash = NULL;
793 PrefsAccount *ac = NULL;
795 if (msgview->msginfo && msgview->mainwin && msgview->mainwin->summaryview)
796 msginfo = summary_get_selected_msg(msgview->mainwin->summaryview);
798 /* need a procmsg_msginfo_equal() */
799 if (msginfo && msgview->msginfo &&
800 msginfo->msgnum == msgview->msginfo->msgnum &&
801 msginfo->folder == msgview->msginfo->folder) {
802 summary_delete(msgview->mainwin->summaryview);
804 msginfo = msgview->msginfo;
806 g_return_if_fail(msginfo != NULL);
808 /* to get the trash folder, we have to choose either
809 * the folder's or account's trash default - we prefer
810 * the one in the account prefs */
811 if (msginfo->folder) {
812 if (NULL != (ac = account_find_from_item(msginfo->folder)))
813 trash = account_get_special_folder(ac, F_TRASH);
814 if (!trash && msginfo->folder->folder)
815 trash = msginfo->folder->folder->trash;
816 /* if still not found, use the default */
818 trash = folder_get_default_trash();
821 g_return_if_fail(trash != NULL);
823 if (prefs_common.immediate_exec)
824 /* TODO: Delete from trash */
825 folder_item_move_msg(trash, msginfo);
827 procmsg_msginfo_set_to_folder(msginfo, trash);
828 procmsg_msginfo_set_flags(msginfo, MSG_DELETED, 0);
829 /* NOTE: does not update to next message in summaryview */
835 * \brief update messageview with currently selected message in summaryview
836 * leave unchanged if summaryview is empty
837 * \param pointer to MessageView
839 static void messageview_update(MessageView *msgview)
841 SummaryView *summaryview = (SummaryView*)msgview->mainwin->summaryview;
843 g_return_if_fail(summaryview != NULL);
845 if (summaryview->selected) {
846 GtkCTree *ctree = GTK_CTREE(summaryview->ctree);
847 MsgInfo *msginfo = gtk_ctree_node_get_row_data(ctree,
848 summaryview->selected);
849 g_return_if_fail(msginfo != NULL);
851 messageview_show(msgview, msginfo,
852 msgview->all_headers);
856 void messageview_quote_color_set(void)
860 void messageview_set_font(MessageView *messageview)
864 TextView *messageview_get_current_textview(MessageView *messageview)
866 TextView *text = NULL;
868 text = messageview->mimeview->textview;
873 MimeInfo *messageview_get_selected_mime_part(MessageView *messageview)
875 return mimeview_get_selected_part(messageview->mimeview);
878 void messageview_copy_clipboard(MessageView *messageview)
882 text = messageview_get_current_textview(messageview);
884 GtkTextView *textview = GTK_TEXT_VIEW(text->text);
885 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
886 GtkClipboard *clipboard
887 = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
889 gtk_text_buffer_copy_clipboard(buffer, clipboard);
893 void messageview_select_all(MessageView *messageview)
897 text = messageview_get_current_textview(messageview);
899 gtk_editable_select_region(GTK_EDITABLE(text->text), 0, -1);
902 void messageview_set_position(MessageView *messageview, gint pos)
906 text = messageview_get_current_textview(messageview);
908 textview_set_position(text, pos);
911 gboolean messageview_search_string(MessageView *messageview, const gchar *str,
916 text = messageview_get_current_textview(messageview);
918 return textview_search_string(text, str, case_sens);
922 gboolean messageview_search_string_backward(MessageView *messageview,
928 text = messageview_get_current_textview(messageview);
930 return textview_search_string_backward(text,
935 gboolean messageview_is_visible(MessageView *messageview)
937 return messageview->visible;
940 void messageview_save_as(MessageView *messageview)
942 gchar *filename = NULL;
946 if (!messageview->msginfo) return;
947 msginfo = messageview->msginfo;
949 if (msginfo->subject) {
950 Xstrdup_a(filename, msginfo->subject, return);
951 subst_for_filename(filename);
953 dest = filesel_select_file(_("Save as"), filename);
955 if (is_file_exist(dest)) {
958 aval = alertpanel(_("Overwrite"),
959 _("Overwrite existing file?"),
960 _("OK"), _("Cancel"), NULL);
961 if (G_ALERTDEFAULT != aval) return;
964 src = procmsg_get_message_file(msginfo);
965 if (copy_file(src, dest, TRUE) < 0) {
966 alertpanel_error(_("Can't save the file `%s'."),
972 static void messageview_destroy_cb(GtkWidget *widget, MessageView *messageview)
974 messageview_destroy(messageview);
977 static void messageview_size_allocate_cb(GtkWidget *widget,
978 GtkAllocation *allocation)
980 g_return_if_fail(allocation != NULL);
982 prefs_common.msgwin_width = allocation->width;
983 prefs_common.msgwin_height = allocation->height;
986 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
987 MessageView *messageview)
989 if (event && event->keyval == GDK_Escape && messageview->window)
990 gtk_widget_destroy(messageview->window);
994 void messageview_toggle_view_real(MessageView *messageview)
996 MainWindow *mainwin = messageview->mainwin;
997 union CompositeWin *cwin = &mainwin->win;
998 GtkWidget *vpaned = NULL;
999 GtkWidget *container = NULL;
1000 GtkItemFactory *ifactory = gtk_item_factory_from_widget(mainwin->menubar);
1002 switch (mainwin->type) {
1004 vpaned = cwin->sep_none.vpaned;
1005 container = cwin->sep_none.hpaned;
1007 case SEPARATE_FOLDER:
1008 vpaned = cwin->sep_folder.vpaned;
1009 container = mainwin->vbox_body;
1011 case SEPARATE_MESSAGE:
1016 if (vpaned->parent != NULL) {
1017 gtk_widget_ref(vpaned);
1018 gtkut_container_remove(GTK_CONTAINER(container), vpaned);
1019 gtk_widget_reparent(GTK_WIDGET_PTR(messageview), container);
1020 menu_set_sensitive(ifactory, "/View/Expand Summary View", FALSE);
1022 gtk_widget_reparent(GTK_WIDGET_PTR(messageview), vpaned);
1023 gtk_container_add(GTK_CONTAINER(container), vpaned);
1024 gtk_widget_unref(vpaned);
1025 menu_set_sensitive(ifactory, "/View/Expand Summary View", TRUE);
1026 gtk_widget_grab_focus(GTK_WIDGET(mainwin->summaryview->ctree));
1030 static void return_receipt_show(NoticeView *noticeview, MsgInfo *msginfo)
1032 noticeview_set_text(noticeview, _("This message asks for a return receipt"));
1033 noticeview_set_button_text(noticeview, _("Send receipt"));
1034 noticeview_set_button_press_callback(noticeview,
1035 GTK_SIGNAL_FUNC(return_receipt_send_clicked),
1036 (gpointer) msginfo);
1037 noticeview_show(noticeview);
1040 static void return_receipt_send_clicked(NoticeView *noticeview, MsgInfo *msginfo)
1042 MsgInfo *tmpmsginfo;
1045 file = procmsg_get_message_file_path(msginfo);
1047 g_warning("can't get message file path.\n");
1051 tmpmsginfo = procheader_parse_file(file, msginfo->flags, TRUE, TRUE);
1052 tmpmsginfo->folder = msginfo->folder;
1053 tmpmsginfo->msgnum = msginfo->msgnum;
1055 if (disposition_notification_send(tmpmsginfo) >= 0) {
1056 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
1057 noticeview_hide(noticeview);
1060 procmsg_msginfo_free(tmpmsginfo);
1064 static void partial_recv_show(NoticeView *noticeview, MsgInfo *msginfo)
1067 gchar *button1 = NULL;
1068 gchar *button2 = NULL;
1069 void *button1_cb = NULL;
1070 void *button2_cb = NULL;
1072 if (!pop3_msg_in_uidl_list(msginfo->account_server, msginfo->account_login,
1073 msginfo->partial_recv))
1075 switch (msginfo->planned_download) {
1076 case 0: /* unknown yet */
1077 text = g_strdup_printf(_("This message has been partially "
1078 "retrieved;\nit is %s large."),
1080 (off_t)(msginfo->total_size)));
1081 button1 = _("Mark for download");
1082 button2 = _("Mark for deletion");
1083 button1_cb = partial_recv_dload_clicked;
1084 button2_cb = partial_recv_del_clicked;
1087 text = g_strdup_printf(_("This message has been partially "
1088 "retrieved and is planned for "
1089 "download;\nit is %s large."),
1091 (off_t)(msginfo->total_size)));
1092 button1 = _("Mark for deletion");
1093 button1_cb = partial_recv_del_clicked;
1096 text = g_strdup_printf(_("This message has been partially "
1097 "retrieved and is planned for "
1098 "deletion;\nit is %s large."),
1100 (off_t)(msginfo->total_size)));
1101 button1 = _("Mark for download");
1102 button1_cb = partial_recv_dload_clicked;
1108 noticeview_set_text(noticeview, text);
1110 noticeview_set_button_text(noticeview, button1);
1111 noticeview_set_button_press_callback(noticeview,
1112 GTK_SIGNAL_FUNC(button1_cb), (gpointer) msginfo);
1114 if (button2 && button2_cb) {
1115 noticeview_set_2ndbutton_text(noticeview, button2);
1116 noticeview_set_2ndbutton_press_callback(noticeview,
1117 GTK_SIGNAL_FUNC(button2_cb), (gpointer) msginfo);
1119 noticeview_set_2ndbutton_text(noticeview, NULL);
1122 noticeview_show(noticeview);
1125 static void partial_recv_dload_clicked(NoticeView *noticeview,
1128 MsgInfo *tmpmsginfo;
1131 file = procmsg_get_message_file_path(msginfo);
1133 g_warning("can't get message file path.\n");
1137 tmpmsginfo = procheader_parse_file(file, msginfo->flags, TRUE, TRUE);
1138 tmpmsginfo->folder = msginfo->folder;
1139 tmpmsginfo->msgnum = msginfo->msgnum;
1141 if (pop3_mark_for_download(tmpmsginfo->account_server,
1142 tmpmsginfo->account_login,
1143 tmpmsginfo->partial_recv, file) == 0) {
1144 msginfo->planned_download = 1;
1145 partial_recv_show(noticeview, msginfo);
1148 procmsg_msginfo_free(tmpmsginfo);
1152 static void partial_recv_del_clicked(NoticeView *noticeview,
1155 MsgInfo *tmpmsginfo;
1158 file = procmsg_get_message_file_path(msginfo);
1160 g_warning("can't get message file path.\n");
1164 tmpmsginfo = procheader_parse_file(file, msginfo->flags, TRUE, TRUE);
1165 tmpmsginfo->folder = msginfo->folder;
1166 tmpmsginfo->msgnum = msginfo->msgnum;
1168 if (pop3_mark_for_delete(tmpmsginfo->account_server,
1169 tmpmsginfo->account_login,
1170 tmpmsginfo->partial_recv, file) == 0) {
1171 msginfo->planned_download = -1;
1172 partial_recv_show(noticeview, msginfo);
1175 procmsg_msginfo_free(tmpmsginfo);
1179 static void select_account_cb(GtkWidget *w, gpointer data)
1181 *(gint*)data = GPOINTER_TO_INT(gtk_object_get_user_data(GTK_OBJECT(w)));
1184 static PrefsAccount *select_account_from_list(GList *ac_list)
1190 g_return_val_if_fail(ac_list != NULL, NULL);
1191 g_return_val_if_fail(ac_list->data != NULL, NULL);
1193 optmenu = gtk_option_menu_new();
1194 menu = gtkut_account_menu_new(ac_list, select_account_cb, &account_id);
1197 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu);
1198 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), 0);
1199 account_id = ((PrefsAccount *) ac_list->data)->account_id;
1200 if (alertpanel_with_widget(
1201 _("Return Receipt Notification"),
1202 _("The message was sent to several of your "
1204 "Please choose which account do you want to "
1205 "use for sending the receipt notification:"),
1206 _("Send Notification"), _("+Cancel"), NULL,
1207 optmenu) != G_ALERTDEFAULT)
1209 return account_find_from_id(account_id);
1213 * \brief return selected messageview text, when nothing is
1214 * selected and message was filtered, return complete text
1216 * \param pointer to Messageview
1218 * \return pointer to text (needs to be free'd by calling func)
1220 gchar *messageview_get_selection(MessageView *msgview)
1224 GtkTextView *edit = NULL;
1225 GtkTextBuffer *textbuf;
1228 g_return_val_if_fail(msgview != NULL, NULL);
1230 textview = messageview_get_current_textview(msgview);
1231 g_return_val_if_fail(textview != NULL, NULL);
1233 edit = GTK_EDITABLE(textview->text);
1234 g_return_val_if_fail(edit != NULL, NULL);
1235 body_pos = textview->body_pos;
1237 textbuf = gtk_text_view_get_buffer(edit);
1239 if (gtk_text_buffer_get_selection_bounds(textbuf, NULL, NULL))
1240 return gtkut_text_view_get_selection(edit);
1241 else if (msgview->filtered) {
1242 GtkTextIter start_iter, end_iter;
1243 gtk_text_buffer_get_iter_at_offset(textbuf, &start_iter, body_pos);
1244 gtk_text_buffer_get_end_iter(textbuf, &end_iter);
1245 gtk_text_buffer_get_text(textbuf, &start_iter, &end_iter, FALSE);
1252 static void save_as_cb(gpointer data, guint action, GtkWidget *widget)
1254 MessageView *messageview = (MessageView *)data;
1255 messageview_save_as(messageview);
1258 static void print_cb(gpointer data, guint action, GtkWidget *widget)
1260 MessageView *messageview = (MessageView *)data;
1264 if (!messageview->msginfo) return;
1266 cmdline = input_dialog(_("Print"),
1267 _("Enter the print command line:\n"
1268 "(`%s' will be replaced with file name)"),
1269 prefs_common.print_cmd);
1270 if (!cmdline) return;
1271 if (!(p = strchr(cmdline, '%')) || *(p + 1) != 's' ||
1272 strchr(p + 2, '%')) {
1273 alertpanel_error(_("Print command line is invalid:\n`%s'"),
1279 procmsg_print_message(messageview->msginfo, cmdline);
1283 static void close_cb(gpointer data, guint action, GtkWidget *widget)
1285 MessageView *messageview = (MessageView *)data;
1286 gtk_widget_destroy(messageview->window);
1289 static void copy_cb(gpointer data, guint action, GtkWidget *widget)
1291 MessageView *messageview = (MessageView *)data;
1292 messageview_copy_clipboard(messageview);
1295 static void allsel_cb(gpointer data, guint action, GtkWidget *widget)
1297 MessageView *messageview = (MessageView *)data;
1298 messageview_select_all(messageview);
1301 static void search_cb(gpointer data, guint action, GtkWidget *widget)
1303 MessageView *messageview = (MessageView *)data;
1304 message_search(messageview);
1307 static void set_charset_cb(gpointer data, guint action, GtkWidget *widget)
1309 MessageView *messageview = (MessageView *)data;
1310 const gchar *charset;
1312 if (GTK_CHECK_MENU_ITEM(widget)->active) {
1313 charset = conv_get_charset_str((CharSet)action);
1314 g_free(messageview->forced_charset);
1315 messageview->forced_charset = g_strdup(charset);
1316 messageview_show(messageview, messageview->msginfo, FALSE);
1320 static void view_source_cb(gpointer data, guint action, GtkWidget *widget)
1322 MessageView *messageview = (MessageView *)data;
1323 SourceWindow *srcwin;
1325 if (!messageview->msginfo) return;
1327 srcwin = source_window_create();
1328 source_window_show_msg(srcwin, messageview->msginfo);
1329 source_window_show(srcwin);
1332 static void show_all_header_cb(gpointer data, guint action, GtkWidget *widget)
1334 MessageView *messageview = (MessageView *)data;
1335 MsgInfo *msginfo = messageview->msginfo;
1337 if (!msginfo) return;
1338 messageview->msginfo = NULL;
1339 messageview_show(messageview, msginfo,
1340 GTK_CHECK_MENU_ITEM(widget)->active);
1341 procmsg_msginfo_free(msginfo);
1342 main_window_set_menu_sensitive(messageview->mainwin);
1345 static void compose_cb(gpointer data, guint action, GtkWidget *widget)
1347 MessageView *messageview = (MessageView *)data;
1348 PrefsAccount *ac = NULL;
1349 FolderItem *item = NULL;
1351 if (messageview->msginfo)
1352 item = messageview->msginfo->folder;
1355 ac = account_find_from_item(item);
1356 if (ac && ac->protocol == A_NNTP &&
1357 item->stype == F_NEWS) {
1358 compose_new(ac, item->path, NULL);
1363 compose_new(ac, NULL, NULL);
1366 static void reply_cb(gpointer data, guint action, GtkWidget *widget)
1368 MessageView *messageview = (MessageView *)data;
1369 GSList *mlist = NULL;
1372 ComposeMode mode = (ComposeMode)action;
1375 msginfo = messageview->msginfo;
1376 mlist = g_slist_append(NULL, msginfo);
1378 textview = messageview_get_current_textview(messageview);
1379 text = gtkut_editable_get_selection
1380 (GTK_EDITABLE(textview->text));
1381 if (text && *text == '\0') {
1388 compose_reply(msginfo, prefs_common.reply_with_quote,
1389 FALSE, prefs_common.default_reply_list, FALSE, text);
1391 case COMPOSE_REPLY_WITH_QUOTE:
1392 compose_reply(msginfo, TRUE, FALSE, prefs_common.default_reply_list, FALSE, text);
1394 case COMPOSE_REPLY_WITHOUT_QUOTE:
1395 compose_reply(msginfo, FALSE, FALSE, prefs_common.default_reply_list, FALSE, NULL);
1397 case COMPOSE_REPLY_TO_SENDER:
1398 compose_reply(msginfo, prefs_common.reply_with_quote,
1399 FALSE, FALSE, TRUE, text);
1401 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1402 compose_followup_and_reply_to(msginfo,
1403 prefs_common.reply_with_quote,
1404 FALSE, FALSE, text);
1406 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1407 compose_reply(msginfo, TRUE, FALSE, FALSE, TRUE, text);
1409 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1410 compose_reply(msginfo, FALSE, FALSE, FALSE, TRUE, NULL);
1412 case COMPOSE_REPLY_TO_ALL:
1413 compose_reply(msginfo, prefs_common.reply_with_quote,
1414 TRUE, FALSE, FALSE, text);
1416 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1417 compose_reply(msginfo, TRUE, TRUE, FALSE, FALSE, text);
1419 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1420 compose_reply(msginfo, FALSE, TRUE, FALSE, FALSE, NULL);
1422 case COMPOSE_REPLY_TO_LIST:
1423 compose_reply(msginfo, prefs_common.reply_with_quote,
1424 FALSE, TRUE, FALSE, text);
1426 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1427 compose_reply(msginfo, TRUE, FALSE, TRUE, FALSE, text);
1429 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1430 compose_reply(msginfo, FALSE, FALSE, TRUE, FALSE, NULL);
1432 case COMPOSE_FORWARD:
1433 if (prefs_common.forward_as_attachment) {
1434 compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, mlist, text);
1437 compose_reply_mode(COMPOSE_FORWARD_INLINE, mlist, text);
1441 case COMPOSE_FORWARD_INLINE:
1442 compose_forward(NULL, msginfo, FALSE, text, FALSE);
1444 case COMPOSE_FORWARD_AS_ATTACH:
1445 compose_forward_multiple(NULL, mlist);
1447 case COMPOSE_REDIRECT:
1448 compose_redirect(NULL, msginfo);
1451 g_warning("compose_reply(): invalid Compose Mode: %d\n", mode);
1454 /* summary_set_marks_selected(summaryview); */
1456 g_slist_free(mlist);
1459 static void reedit_cb(gpointer data, guint action, GtkWidget *widget)
1461 MessageView *messageview = (MessageView *)data;
1464 if (!messageview->msginfo) return;
1465 msginfo = messageview->msginfo;
1466 if (!msginfo->folder) return;
1467 if (msginfo->folder->stype != F_OUTBOX &&
1468 msginfo->folder->stype != F_DRAFT &&
1469 msginfo->folder->stype != F_QUEUE) return;
1471 compose_reedit(msginfo);
1474 static void addressbook_open_cb(gpointer data, guint action, GtkWidget *widget)
1476 addressbook_open(NULL);
1479 static void add_address_cb(gpointer data, guint action, GtkWidget *widget)
1481 MessageView *messageview = (MessageView *)data;
1485 if (!messageview->msginfo) return;
1486 msginfo = messageview->msginfo;
1487 Xstrdup_a(from, msginfo->from, return);
1488 eliminate_address_comment(from);
1489 extract_address(from);
1490 addressbook_add_contact(msginfo->fromname, from, NULL);
1493 static void create_filter_cb(gpointer data, guint action, GtkWidget *widget)
1495 MessageView *messageview = (MessageView *)data;
1498 if (!messageview->msginfo) return;
1500 item = messageview->msginfo->folder;
1501 summary_msginfo_filter_open(item, messageview->msginfo,
1502 (PrefsFilterType)action, 0);
1505 static void create_processing_cb(gpointer data, guint action,
1508 MessageView *messageview = (MessageView *)data;
1511 if (!messageview->msginfo) return;
1513 item = messageview->msginfo->folder;
1514 summary_msginfo_filter_open(item, messageview->msginfo,
1515 (PrefsFilterType)action, 1);
1518 static void about_cb(gpointer data, guint action, GtkWidget *widget)
1523 static gboolean messageview_update_msg(gpointer source, gpointer data)
1525 MsgInfoUpdate *msginfo_update = (MsgInfoUpdate *) source;
1526 MessageView *messageview = (MessageView *)data;
1528 if (messageview->msginfo != msginfo_update->msginfo)
1531 if (msginfo_update->flags & MSGINFO_UPDATE_DELETED) {
1532 messageview_clear(messageview);
1533 messageview_update(messageview);
1539 void messageview_set_menu_sensitive(MessageView *messageview)
1541 GtkItemFactory *ifactory;
1542 GtkWidget *menuitem;
1544 if (!messageview && !messageview->new_window)
1546 /* do some smart things */
1547 if (!messageview->menubar) return;
1548 ifactory = gtk_item_factory_from_widget(messageview->menubar);
1549 if (!ifactory) return;
1550 if (messageview->mainwin->type == SEPARATE_MESSAGE) {
1551 menuitem = gtk_item_factory_get_widget(ifactory, "/View/Show all headers");
1552 gtk_check_menu_item_set_active
1553 (GTK_CHECK_MENU_ITEM(menuitem),
1554 messageview->mimeview->textview->show_all_headers);