From b9ca7b1ef5cd1f96ae6e28ae78d12c1e3258c23f Mon Sep 17 00:00:00 2001 From: hiro Date: Wed, 12 Jan 2005 11:22:08 +0000 Subject: Initial import of Sylpheed (GTK2 version). git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@1 ee746299-78ed-0310-b773-934348b2243d --- src/Makefile.am | 212 + src/about.c | 251 ++ src/about.h | 25 + src/account.c | 990 +++++ src/account.h | 64 + src/action.c | 1269 ++++++ src/action.h | 56 + src/addr_compl.c | 954 +++++ src/addr_compl.h | 54 + src/addrbook.c | 2010 ++++++++++ src/addrbook.h | 114 + src/addrcache.c | 1232 ++++++ src/addrcache.h | 123 + src/addressadd.c | 401 ++ src/addressadd.h | 31 + src/addressbook.c | 3503 +++++++++++++++++ src/addressbook.h | 49 + src/addressitem.h | 158 + src/addrindex.c | 1892 +++++++++ src/addrindex.h | 130 + src/addritem.c | 989 +++++ src/addritem.h | 174 + src/alertpanel.c | 353 ++ src/alertpanel.h | 67 + src/base64.c | 168 + src/base64.h | 46 + src/codeconv.c | 1769 +++++++++ src/codeconv.h | 238 ++ src/colorlabel.c | 338 ++ src/colorlabel.h | 34 + src/compose.c | 6204 ++++++++++++++++++++++++++++++ src/compose.h | 214 ++ src/customheader.c | 100 + src/customheader.h | 40 + src/defs.h | 110 + src/displayheader.c | 59 + src/displayheader.h | 37 + src/editaddress.c | 1198 ++++++ src/editaddress.h | 29 + src/editbook.c | 348 ++ src/editbook.h | 29 + src/editgroup.c | 540 +++ src/editgroup.h | 26 + src/editjpilot.c | 447 +++ src/editjpilot.h | 33 + src/editldap.c | 600 +++ src/editldap.h | 29 + src/editldap_basedn.c | 332 ++ src/editldap_basedn.h | 30 + src/editvcard.c | 329 ++ src/editvcard.h | 29 + src/export.c | 263 ++ src/export.h | 29 + src/filesel.c | 145 + src/filesel.h | 27 + src/filter.c | 1218 ++++++ src/filter.h | 194 + src/folder.c | 1529 ++++++++ src/folder.h | 386 ++ src/foldersel.c | 493 +++ src/foldersel.h | 39 + src/folderview.c | 2378 ++++++++++++ src/folderview.h | 96 + src/grouplistdialog.c | 567 +++ src/grouplistdialog.h | 29 + src/gtksctree.c | 573 +++ src/gtksctree.h | 66 + src/gtkshruler.c | 218 ++ src/gtkshruler.h | 63 + src/gtkutils.c | 686 ++++ src/gtkutils.h | 166 + src/headerview.c | 348 ++ src/headerview.h | 55 + src/html.c | 777 ++++ src/html.h | 87 + src/imageview.c | 251 ++ src/imageview.h | 52 + src/imap.c | 3976 +++++++++++++++++++ src/imap.h | 114 + src/import.c | 268 ++ src/import.h | 29 + src/importldif.c | 846 ++++ src/importldif.h | 34 + src/inc.c | 1337 +++++++ src/inc.h | 103 + src/inputdialog.c | 332 ++ src/inputdialog.h | 39 + src/intl.h | 22 + src/jpilot.c | 1732 +++++++++ src/jpilot.h | 116 + src/ldif.c | 933 +++++ src/ldif.h | 119 + src/logwindow.c | 221 ++ src/logwindow.h | 55 + src/main.c | 721 ++++ src/main.h | 32 + src/mainwindow.c | 3174 +++++++++++++++ src/mainwindow.h | 177 + src/manage_window.c | 92 + src/manage_window.h | 58 + src/manual.c | 85 + src/manual.h | 36 + src/mbox.c | 455 +++ src/mbox.h | 47 + src/md5.c | 433 +++ src/md5.h | 49 + src/menu.c | 262 ++ src/menu.h | 90 + src/message_search.c | 226 ++ src/message_search.h | 29 + src/messageview.c | 877 +++++ src/messageview.h | 96 + src/mgutils.c | 220 ++ src/mgutils.h | 62 + src/mh.c | 1282 ++++++ src/mh.h | 38 + src/mimeview.c | 998 +++++ src/mimeview.h | 81 + src/news.c | 1056 +++++ src/news.h | 59 + src/nntp.c | 431 +++ src/nntp.h | 109 + src/passphrase.c | 342 ++ src/passphrase.h | 34 + src/pixmaps/address.xpm | 23 + src/pixmaps/book.xpm | 23 + src/pixmaps/category.xpm | 35 + src/pixmaps/checkbox_off.xpm | 20 + src/pixmaps/checkbox_on.xpm | 20 + src/pixmaps/clip.xpm | 17 + src/pixmaps/complete.xpm | 17 + src/pixmaps/continue.xpm | 42 + src/pixmaps/deleted.xpm | 15 + src/pixmaps/dir-close.xpm | 100 + src/pixmaps/dir-noselect.xpm | 94 + src/pixmaps/dir-open.xpm | 83 + src/pixmaps/error.xpm | 45 + src/pixmaps/forwarded.xpm | 23 + src/pixmaps/group.xpm | 97 + src/pixmaps/inbox.xpm | 25 + src/pixmaps/interface.xpm | 24 + src/pixmaps/jpilot.xpm | 25 + src/pixmaps/ldap.xpm | 25 + src/pixmaps/linewrap.xpm | 29 + src/pixmaps/mail.xpm | 41 + src/pixmaps/mark.xpm | 16 + src/pixmaps/new.xpm | 62 + src/pixmaps/offline.xpm | 228 ++ src/pixmaps/online.xpm | 232 ++ src/pixmaps/outbox.xpm | 27 + src/pixmaps/replied.xpm | 24 + src/pixmaps/stock_add_16.xpm | 35 + src/pixmaps/stock_close.xpm | 29 + src/pixmaps/stock_dialog_error_48.xpm | 115 + src/pixmaps/stock_dialog_info_48.xpm | 115 + src/pixmaps/stock_dialog_question_48.xpm | 115 + src/pixmaps/stock_dialog_warning_48.xpm | 83 + src/pixmaps/stock_down_arrow.xpm | 100 + src/pixmaps/stock_exec.xpm | 107 + src/pixmaps/stock_mail.xpm | 143 + src/pixmaps/stock_mail_attach.xpm | 134 + src/pixmaps/stock_mail_compose.xpm | 144 + src/pixmaps/stock_mail_forward.xpm | 153 + src/pixmaps/stock_mail_receive.xpm | 175 + src/pixmaps/stock_mail_receive_all.xpm | 181 + src/pixmaps/stock_mail_reply.xpm | 154 + src/pixmaps/stock_mail_reply_to_all.xpm | 126 + src/pixmaps/stock_mail_send.xpm | 162 + src/pixmaps/stock_mail_send_queue.xpm | 244 ++ src/pixmaps/stock_paste.xpm | 132 + src/pixmaps/stock_preferences.xpm | 80 + src/pixmaps/stock_properties.xpm | 140 + src/pixmaps/stock_remove_16.xpm | 28 + src/pixmaps/stock_search.xpm | 155 + src/pixmaps/stock_trash.xpm | 112 + src/pixmaps/stock_up_arrow.xpm | 100 + src/pixmaps/sylpheed-logo.xpm | 53 + src/pixmaps/tb_address_book.xpm | 56 + src/pixmaps/trash.xpm | 29 + src/pixmaps/unread.xpm | 50 + src/pixmaps/vcard.xpm | 25 + src/pop.c | 862 +++++ src/pop.h | 153 + src/prefs.c | 817 ++++ src/prefs.h | 169 + src/prefs_account.c | 2295 +++++++++++ src/prefs_account.h | 175 + src/prefs_actions.c | 666 ++++ src/prefs_actions.h | 29 + src/prefs_common.c | 3545 +++++++++++++++++ src/prefs_common.h | 246 ++ src/prefs_customheader.c | 623 +++ src/prefs_customheader.h | 29 + src/prefs_display_header.c | 631 +++ src/prefs_display_header.h | 27 + src/prefs_filter.c | 841 ++++ src/prefs_filter.h | 58 + src/prefs_filter_edit.c | 2035 ++++++++++ src/prefs_filter_edit.h | 28 + src/prefs_folder_item.c | 579 +++ src/prefs_folder_item.h | 29 + src/prefs_summary_column.c | 537 +++ src/prefs_summary_column.h | 30 + src/prefs_template.c | 533 +++ src/prefs_template.h | 25 + src/procheader.c | 764 ++++ src/procheader.h | 91 + src/procmime.c | 1128 ++++++ src/procmime.h | 176 + src/procmsg.c | 1519 ++++++++ src/procmsg.h | 280 ++ src/progressdialog.c | 135 + src/progressdialog.h | 46 + src/quote_fmt.h | 13 + src/quote_fmt_lex.h | 47 + src/quote_fmt_lex.l | 46 + src/quote_fmt_parse.y | 459 +++ src/quoted-printable.c | 231 ++ src/quoted-printable.h | 36 + src/recv.c | 227 ++ src/recv.h | 46 + src/rfc2015.c | 1395 +++++++ src/rfc2015.h | 48 + src/select-keys.c | 525 +++ src/select-keys.h | 29 + src/send_message.c | 620 +++ src/send_message.h | 46 + src/session.c | 734 ++++ src/session.h | 200 + src/setup.c | 95 + src/setup.h | 29 + src/sigstatus.c | 244 ++ src/sigstatus.h | 33 + src/simple-gettext.c | 386 ++ src/smtp.c | 565 +++ src/smtp.h | 115 + src/socket.c | 1293 +++++++ src/socket.h | 117 + src/sourcewindow.c | 183 + src/sourcewindow.h | 45 + src/ssl.c | 146 + src/ssl.h | 58 + src/statusbar.c | 125 + src/statusbar.h | 38 + src/stock_pixmap.c | 185 + src/stock_pixmap.h | 93 + src/stringtable.c | 163 + src/stringtable.h | 38 + src/summary_search.c | 474 +++ src/summary_search.h | 29 + src/summaryview.c | 4195 ++++++++++++++++++++ src/summaryview.h | 239 ++ src/syldap.c | 1129 ++++++ src/syldap.h | 111 + src/sylpheed-marshal.list | 2 + src/template.c | 219 ++ src/template.h | 45 + src/textview.c | 1664 ++++++++ src/textview.h | 90 + src/undo.c | 646 ++++ src/undo.h | 80 + src/unmime.c | 133 + src/unmime.h | 29 + src/utils.c | 3189 +++++++++++++++ src/utils.h | 428 +++ src/uuencode.c | 101 + src/uuencode.h | 24 + src/vcard.c | 777 ++++ src/vcard.h | 105 + src/version.h.in | 27 + src/xml.c | 619 +++ src/xml.h | 106 + 272 files changed, 108021 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/about.c create mode 100644 src/about.h create mode 100644 src/account.c create mode 100644 src/account.h create mode 100644 src/action.c create mode 100644 src/action.h create mode 100644 src/addr_compl.c create mode 100644 src/addr_compl.h create mode 100644 src/addrbook.c create mode 100644 src/addrbook.h create mode 100644 src/addrcache.c create mode 100644 src/addrcache.h create mode 100644 src/addressadd.c create mode 100644 src/addressadd.h create mode 100644 src/addressbook.c create mode 100644 src/addressbook.h create mode 100644 src/addressitem.h create mode 100644 src/addrindex.c create mode 100644 src/addrindex.h create mode 100644 src/addritem.c create mode 100644 src/addritem.h create mode 100644 src/alertpanel.c create mode 100644 src/alertpanel.h create mode 100644 src/base64.c create mode 100644 src/base64.h create mode 100644 src/codeconv.c create mode 100644 src/codeconv.h create mode 100644 src/colorlabel.c create mode 100644 src/colorlabel.h create mode 100644 src/compose.c create mode 100644 src/compose.h create mode 100644 src/customheader.c create mode 100644 src/customheader.h create mode 100644 src/defs.h create mode 100644 src/displayheader.c create mode 100644 src/displayheader.h create mode 100644 src/editaddress.c create mode 100644 src/editaddress.h create mode 100644 src/editbook.c create mode 100644 src/editbook.h create mode 100644 src/editgroup.c create mode 100644 src/editgroup.h create mode 100644 src/editjpilot.c create mode 100644 src/editjpilot.h create mode 100644 src/editldap.c create mode 100644 src/editldap.h create mode 100644 src/editldap_basedn.c create mode 100644 src/editldap_basedn.h create mode 100644 src/editvcard.c create mode 100644 src/editvcard.h create mode 100644 src/export.c create mode 100644 src/export.h create mode 100644 src/filesel.c create mode 100644 src/filesel.h create mode 100644 src/filter.c create mode 100644 src/filter.h create mode 100644 src/folder.c create mode 100644 src/folder.h create mode 100644 src/foldersel.c create mode 100644 src/foldersel.h create mode 100644 src/folderview.c create mode 100644 src/folderview.h create mode 100644 src/grouplistdialog.c create mode 100644 src/grouplistdialog.h create mode 100644 src/gtksctree.c create mode 100644 src/gtksctree.h create mode 100644 src/gtkshruler.c create mode 100644 src/gtkshruler.h create mode 100644 src/gtkutils.c create mode 100644 src/gtkutils.h create mode 100644 src/headerview.c create mode 100644 src/headerview.h create mode 100644 src/html.c create mode 100644 src/html.h create mode 100644 src/imageview.c create mode 100644 src/imageview.h create mode 100644 src/imap.c create mode 100644 src/imap.h create mode 100644 src/import.c create mode 100644 src/import.h create mode 100644 src/importldif.c create mode 100644 src/importldif.h create mode 100644 src/inc.c create mode 100644 src/inc.h create mode 100644 src/inputdialog.c create mode 100644 src/inputdialog.h create mode 100644 src/intl.h create mode 100644 src/jpilot.c create mode 100644 src/jpilot.h create mode 100644 src/ldif.c create mode 100644 src/ldif.h create mode 100644 src/logwindow.c create mode 100644 src/logwindow.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/mainwindow.c create mode 100644 src/mainwindow.h create mode 100644 src/manage_window.c create mode 100644 src/manage_window.h create mode 100644 src/manual.c create mode 100644 src/manual.h create mode 100644 src/mbox.c create mode 100644 src/mbox.h create mode 100644 src/md5.c create mode 100644 src/md5.h create mode 100644 src/menu.c create mode 100644 src/menu.h create mode 100644 src/message_search.c create mode 100644 src/message_search.h create mode 100644 src/messageview.c create mode 100644 src/messageview.h create mode 100644 src/mgutils.c create mode 100644 src/mgutils.h create mode 100644 src/mh.c create mode 100644 src/mh.h create mode 100644 src/mimeview.c create mode 100644 src/mimeview.h create mode 100644 src/news.c create mode 100644 src/news.h create mode 100644 src/nntp.c create mode 100644 src/nntp.h create mode 100644 src/passphrase.c create mode 100644 src/passphrase.h create mode 100644 src/pixmaps/address.xpm create mode 100644 src/pixmaps/book.xpm create mode 100644 src/pixmaps/category.xpm create mode 100644 src/pixmaps/checkbox_off.xpm create mode 100644 src/pixmaps/checkbox_on.xpm create mode 100644 src/pixmaps/clip.xpm create mode 100644 src/pixmaps/complete.xpm create mode 100644 src/pixmaps/continue.xpm create mode 100644 src/pixmaps/deleted.xpm create mode 100644 src/pixmaps/dir-close.xpm create mode 100644 src/pixmaps/dir-noselect.xpm create mode 100644 src/pixmaps/dir-open.xpm create mode 100644 src/pixmaps/error.xpm create mode 100644 src/pixmaps/forwarded.xpm create mode 100644 src/pixmaps/group.xpm create mode 100644 src/pixmaps/inbox.xpm create mode 100644 src/pixmaps/interface.xpm create mode 100644 src/pixmaps/jpilot.xpm create mode 100644 src/pixmaps/ldap.xpm create mode 100644 src/pixmaps/linewrap.xpm create mode 100644 src/pixmaps/mail.xpm create mode 100644 src/pixmaps/mark.xpm create mode 100644 src/pixmaps/new.xpm create mode 100644 src/pixmaps/offline.xpm create mode 100644 src/pixmaps/online.xpm create mode 100644 src/pixmaps/outbox.xpm create mode 100644 src/pixmaps/replied.xpm create mode 100644 src/pixmaps/stock_add_16.xpm create mode 100644 src/pixmaps/stock_close.xpm create mode 100644 src/pixmaps/stock_dialog_error_48.xpm create mode 100644 src/pixmaps/stock_dialog_info_48.xpm create mode 100644 src/pixmaps/stock_dialog_question_48.xpm create mode 100644 src/pixmaps/stock_dialog_warning_48.xpm create mode 100644 src/pixmaps/stock_down_arrow.xpm create mode 100644 src/pixmaps/stock_exec.xpm create mode 100644 src/pixmaps/stock_mail.xpm create mode 100644 src/pixmaps/stock_mail_attach.xpm create mode 100644 src/pixmaps/stock_mail_compose.xpm create mode 100644 src/pixmaps/stock_mail_forward.xpm create mode 100644 src/pixmaps/stock_mail_receive.xpm create mode 100644 src/pixmaps/stock_mail_receive_all.xpm create mode 100644 src/pixmaps/stock_mail_reply.xpm create mode 100644 src/pixmaps/stock_mail_reply_to_all.xpm create mode 100644 src/pixmaps/stock_mail_send.xpm create mode 100644 src/pixmaps/stock_mail_send_queue.xpm create mode 100644 src/pixmaps/stock_paste.xpm create mode 100644 src/pixmaps/stock_preferences.xpm create mode 100644 src/pixmaps/stock_properties.xpm create mode 100644 src/pixmaps/stock_remove_16.xpm create mode 100644 src/pixmaps/stock_search.xpm create mode 100644 src/pixmaps/stock_trash.xpm create mode 100644 src/pixmaps/stock_up_arrow.xpm create mode 100644 src/pixmaps/sylpheed-logo.xpm create mode 100644 src/pixmaps/tb_address_book.xpm create mode 100644 src/pixmaps/trash.xpm create mode 100644 src/pixmaps/unread.xpm create mode 100644 src/pixmaps/vcard.xpm create mode 100644 src/pop.c create mode 100644 src/pop.h create mode 100644 src/prefs.c create mode 100644 src/prefs.h create mode 100644 src/prefs_account.c create mode 100644 src/prefs_account.h create mode 100644 src/prefs_actions.c create mode 100644 src/prefs_actions.h create mode 100644 src/prefs_common.c create mode 100644 src/prefs_common.h create mode 100644 src/prefs_customheader.c create mode 100644 src/prefs_customheader.h create mode 100644 src/prefs_display_header.c create mode 100644 src/prefs_display_header.h create mode 100644 src/prefs_filter.c create mode 100644 src/prefs_filter.h create mode 100644 src/prefs_filter_edit.c create mode 100644 src/prefs_filter_edit.h create mode 100644 src/prefs_folder_item.c create mode 100644 src/prefs_folder_item.h create mode 100644 src/prefs_summary_column.c create mode 100644 src/prefs_summary_column.h create mode 100644 src/prefs_template.c create mode 100644 src/prefs_template.h create mode 100644 src/procheader.c create mode 100644 src/procheader.h create mode 100644 src/procmime.c create mode 100644 src/procmime.h create mode 100644 src/procmsg.c create mode 100644 src/procmsg.h create mode 100644 src/progressdialog.c create mode 100644 src/progressdialog.h create mode 100644 src/quote_fmt.h create mode 100644 src/quote_fmt_lex.h create mode 100644 src/quote_fmt_lex.l create mode 100644 src/quote_fmt_parse.y create mode 100644 src/quoted-printable.c create mode 100644 src/quoted-printable.h create mode 100644 src/recv.c create mode 100644 src/recv.h create mode 100644 src/rfc2015.c create mode 100644 src/rfc2015.h create mode 100644 src/select-keys.c create mode 100644 src/select-keys.h create mode 100644 src/send_message.c create mode 100644 src/send_message.h create mode 100644 src/session.c create mode 100644 src/session.h create mode 100644 src/setup.c create mode 100644 src/setup.h create mode 100644 src/sigstatus.c create mode 100644 src/sigstatus.h create mode 100644 src/simple-gettext.c create mode 100644 src/smtp.c create mode 100644 src/smtp.h create mode 100644 src/socket.c create mode 100644 src/socket.h create mode 100644 src/sourcewindow.c create mode 100644 src/sourcewindow.h create mode 100644 src/ssl.c create mode 100644 src/ssl.h create mode 100644 src/statusbar.c create mode 100644 src/statusbar.h create mode 100644 src/stock_pixmap.c create mode 100644 src/stock_pixmap.h create mode 100644 src/stringtable.c create mode 100644 src/stringtable.h create mode 100644 src/summary_search.c create mode 100644 src/summary_search.h create mode 100644 src/summaryview.c create mode 100644 src/summaryview.h create mode 100644 src/syldap.c create mode 100644 src/syldap.h create mode 100644 src/sylpheed-marshal.list create mode 100644 src/template.c create mode 100644 src/template.h create mode 100644 src/textview.c create mode 100644 src/textview.h create mode 100644 src/undo.c create mode 100644 src/undo.h create mode 100644 src/unmime.c create mode 100644 src/unmime.h create mode 100644 src/utils.c create mode 100644 src/utils.h create mode 100644 src/uuencode.c create mode 100644 src/uuencode.h create mode 100644 src/vcard.c create mode 100644 src/vcard.h create mode 100644 src/version.h.in create mode 100644 src/xml.c create mode 100644 src/xml.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..f930389a --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,212 @@ +bin_PROGRAMS = sylpheed + +sylpheed_SOURCES = \ + intl.h \ + defs.h \ + version.h \ + main.c main.h \ + mainwindow.c mainwindow.h \ + folderview.c folderview.h \ + summaryview.c summaryview.h \ + messageview.c messageview.h \ + headerview.c headerview.h \ + textview.c textview.h \ + imageview.c imageview.h \ + mimeview.c mimeview.h \ + summary_search.c summary_search.h \ + message_search.c message_search.h \ + colorlabel.c colorlabel.h \ + folder.c folder.h \ + procmsg.c procmsg.h \ + procheader.c procheader.h \ + filter.c filter.h \ + action.c action.h \ + compose.c compose.h \ + gtkshruler.c gtkshruler.h \ + gtksctree.c gtksctree.h \ + menu.c menu.h \ + stock_pixmap.c stock_pixmap.h \ + prefs.c prefs.h \ + prefs_common.c prefs_common.h \ + prefs_filter.c prefs_filter.h \ + prefs_filter_edit.c prefs_filter_edit.h \ + prefs_account.c prefs_account.h \ + prefs_folder_item.c prefs_folder_item.h \ + prefs_display_header.c prefs_display_header.h \ + prefs_customheader.c prefs_customheader.h \ + prefs_summary_column.c prefs_summary_column.h \ + prefs_template.c prefs_template.h \ + prefs_actions.c prefs_actions.h \ + account.c account.h \ + displayheader.c displayheader.h \ + customheader.c customheader.h \ + template.c template.h \ + addressbook.c addressbook.h \ + addr_compl.c addr_compl.h \ + addressitem.h \ + addritem.c addritem.h \ + addrcache.c addrcache.h \ + addrbook.c addrbook.h \ + addrindex.c addrindex.h \ + mgutils.c mgutils.h \ + vcard.c vcard.h \ + ldif.c ldif.h \ + importldif.c importldif.h \ + jpilot.c jpilot.h \ + syldap.c syldap.h \ + editbook.c editbook.h \ + editgroup.c editgroup.h \ + editaddress.c editaddress.h \ + editvcard.c editvcard.h \ + editjpilot.c editjpilot.h \ + editldap.c editldap.h \ + editldap_basedn.c editldap_basedn.h \ + addressadd.c addressadd.h \ + filesel.c filesel.h \ + foldersel.c foldersel.h \ + statusbar.c statusbar.h \ + logwindow.c logwindow.h \ + sourcewindow.c sourcewindow.h \ + manage_window.c manage_window.h \ + undo.c undo.h \ + alertpanel.c alertpanel.h \ + inputdialog.c inputdialog.h \ + progressdialog.c progressdialog.h \ + grouplistdialog.c grouplistdialog.h \ + about.c about.h \ + setup.c setup.h \ + utils.c utils.h \ + gtkutils.c gtkutils.h \ + codeconv.c codeconv.h \ + unmime.c unmime.h \ + base64.c base64.h \ + quoted-printable.c quoted-printable.h \ + uuencode.c uuencode.h \ + md5.c md5.h \ + socket.c socket.h \ + ssl.c ssl.h \ + session.c session.h \ + smtp.c smtp.h \ + pop.c pop.h \ + mh.c mh.h \ + mbox.c mbox.h \ + send_message.c send_message.h \ + recv.c recv.h \ + inc.c inc.h \ + import.c import.h \ + export.c export.h \ + nntp.c nntp.h \ + news.c news.h \ + imap.c imap.h \ + xml.c xml.h \ + html.c html.h \ + procmime.c procmime.h \ + rfc2015.c rfc2015.h \ + passphrase.c passphrase.h \ + select-keys.c select-keys.h \ + sigstatus.c sigstatus.h \ + simple-gettext.c \ + manual.c manual.h \ + stringtable.c stringtable.h \ + quote_fmt_lex.l quote_fmt_lex.h \ + quote_fmt_parse.y quote_fmt.h \ + sylpheed-marshal.c sylpheed-marshal.h + +BUILT_SOURCES = \ + quote_fmt_lex.c \ + quote_fmt_parse.c \ + quote_fmt_parse.h \ + sylpheed-marshal.c \ + sylpheed-marshal.h + +EXTRA_DIST = \ + quote_fmt_parse.h \ + sylpheed-marshal.list \ + version.h.in \ + pixmaps/clip.xpm \ + pixmaps/deleted.xpm \ + pixmaps/dir-close.xpm \ + pixmaps/dir-open.xpm \ + pixmaps/dir-noselect.xpm \ + pixmaps/forwarded.xpm \ + pixmaps/group.xpm \ + pixmaps/inbox.xpm \ + pixmaps/mark.xpm \ + pixmaps/checkbox_on.xpm \ + pixmaps/checkbox_off.xpm \ + pixmaps/new.xpm \ + pixmaps/offline.xpm \ + pixmaps/online.xpm \ + pixmaps/outbox.xpm \ + pixmaps/replied.xpm \ + pixmaps/trash.xpm \ + pixmaps/unread.xpm \ + pixmaps/linewrap.xpm \ + pixmaps/continue.xpm \ + pixmaps/complete.xpm \ + pixmaps/error.xpm \ + pixmaps/stock_dialog_error_48.xpm \ + pixmaps/stock_dialog_info_48.xpm \ + pixmaps/stock_dialog_question_48.xpm \ + pixmaps/stock_dialog_warning_48.xpm \ + pixmaps/stock_mail.xpm \ + pixmaps/stock_mail_attach.xpm \ + pixmaps/stock_mail_receive.xpm \ + pixmaps/stock_mail_receive_all.xpm \ + pixmaps/stock_mail_send.xpm \ + pixmaps/stock_mail_send_queue.xpm \ + pixmaps/stock_mail_compose.xpm \ + pixmaps/stock_mail_reply.xpm \ + pixmaps/stock_mail_reply_to_all.xpm \ + pixmaps/stock_mail_forward.xpm \ + pixmaps/stock_preferences.xpm \ + pixmaps/stock_properties.xpm \ + pixmaps/stock_search.xpm \ + pixmaps/stock_close.xpm \ + pixmaps/stock_exec.xpm \ + pixmaps/stock_trash.xpm \ + pixmaps/stock_up_arrow.xpm \ + pixmaps/stock_down_arrow.xpm \ + pixmaps/stock_paste.xpm \ + pixmaps/stock_add_16.xpm \ + pixmaps/stock_remove_16.xpm \ + pixmaps/tb_address_book.xpm \ + pixmaps/sylpheed-logo.xpm \ + pixmaps/address.xpm \ + pixmaps/book.xpm \ + pixmaps/category.xpm \ + pixmaps/interface.xpm \ + pixmaps/jpilot.xpm \ + pixmaps/ldap.xpm \ + pixmaps/vcard.xpm \ + pixmaps/mail.xpm + +INCLUDES = \ + -DG_LOG_DOMAIN=\"Sylpheed\" \ + -I$(top_srcdir)/intl \ + $(GTK_CFLAGS) \ + $(GDK_PIXBUF_CFLAGS) \ + $(GPGME_CFLAGS) \ + -I$(includedir) + +sylpheed_LDADD = \ + $(INTLLIBS) \ + $(GTK_LIBS) \ + $(GPGME_LIBS) \ + $(LDAP_LIBS) \ + $(LIBICONV) + +AM_CPPFLAGS = \ + -DLOCALEDIR=\""$(localedir)"\" \ + -DMANUALDIR=\""$(manualdir)"\" \ + -DFAQDIR=\""$(faqdir)"\" \ + -DTARGET_ALIAS=\""$(target_triplet)"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" + +AM_YFLAGS = -d + +sylpheed-marshal.h: sylpheed-marshal.list + $(GLIB_GENMARSHAL) $< --header --prefix=sylpheed_marshal > $@ + +sylpheed-marshal.c: sylpheed-marshal.list + $(GLIB_GENMARSHAL) $< --body --prefix=sylpheed_marshal > $@ diff --git a/src/about.c b/src/about.c new file mode 100644 index 00000000..47758d5d --- /dev/null +++ b/src/about.c @@ -0,0 +1,251 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_SYS_UTSNAME_H +# include +#endif + +#include "intl.h" +#include "about.h" +#include "gtkutils.h" +#include "stock_pixmap.h" +#include "prefs_common.h" +#include "utils.h" +#include "version.h" + +static GtkWidget *window; + +static void about_create(void); +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event); +static void about_uri_clicked(GtkButton *button, gpointer data); + +void about_show(void) +{ + if (!window) + about_create(); + else { + gtk_widget_hide(window); + gtk_widget_show(window); + } +} + +static void about_create(void) +{ + GtkWidget *vbox; + GtkWidget *pixmap; + GtkWidget *label; + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *scrolledwin; + GtkWidget *text; + GtkWidget *confirm_area; + GtkWidget *ok_button; + GtkTextBuffer *buffer; + GtkTextIter iter; + GtkStyle *style; + GdkColormap *cmap; + GdkColor uri_color[2] = {{0, 0, 0, 0xffff}, {0, 0xffff, 0, 0}}; + gboolean success[2]; + +#if HAVE_SYS_UTSNAME_H + struct utsname utsbuf; +#endif + gchar buf[1024]; + gint i; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("About")); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_widget_set_size_request(window, 518, 358); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + gtk_widget_realize(window); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(window), vbox); + + pixmap = stock_pixmap_widget(window, STOCK_PIXMAP_SYLPHEED_LOGO); + gtk_box_pack_start(GTK_BOX(vbox), pixmap, FALSE, FALSE, 0); + + label = gtk_label_new("version "VERSION); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + +#if HAVE_SYS_UTSNAME_H + uname(&utsbuf); + g_snprintf(buf, sizeof(buf), + "GTK+ version %d.%d.%d\n" + "Operating System: %s %s (%s)", + gtk_major_version, gtk_minor_version, gtk_micro_version, + utsbuf.sysname, utsbuf.release, utsbuf.machine); +#else + g_snprintf(buf, sizeof(buf), + "GTK+ version %d.%d.%d\n" + "Operating System: Windoze", + gtk_major_version, gtk_minor_version, gtk_micro_version); +#endif + + label = gtk_label_new(buf); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + g_snprintf(buf, sizeof(buf), + "Compiled-in features:%s", +#if HAVE_GDK_IMLIB + " gdk_imlib" +#endif +#if HAVE_GDK_PIXBUF + " gdk-pixbuf" +#endif +#if USE_THREADS + " gthread" +#endif +#if INET6 + " IPv6" +#endif +#if HAVE_ICONV + " iconv" +#endif +#if HAVE_LIBCOMPFACE + " compface" +#endif +#if USE_GPGME + " GnuPG" +#endif +#if USE_SSL + " OpenSSL" +#endif +#if USE_LDAP + " LDAP" +#endif +#if USE_JPILOT + " JPilot" +#endif + ""); + + label = gtk_label_new(buf); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + label = gtk_label_new + ("Copyright (C) 1999-2004 Hiroyuki Yamamoto "); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + button = gtk_button_new_with_label(" "HOMEPAGE_URI" "); + gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0); + gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(about_uri_clicked), NULL); + buf[0] = ' '; + for (i = 1; i <= strlen(HOMEPAGE_URI); i++) buf[i] = '_'; + strcpy(buf + i, " "); + gtk_label_set_pattern(GTK_LABEL(GTK_BIN(button)->child), buf); + cmap = gdk_window_get_colormap(window->window); + gdk_colormap_alloc_colors(cmap, uri_color, 2, FALSE, TRUE, success); + if (success[0] == TRUE && success[1] == TRUE) { + gtk_widget_ensure_style(GTK_BIN(button)->child); + style = gtk_style_copy + (gtk_widget_get_style(GTK_BIN(button)->child)); + style->fg[GTK_STATE_NORMAL] = uri_color[0]; + style->fg[GTK_STATE_ACTIVE] = uri_color[1]; + style->fg[GTK_STATE_PRELIGHT] = uri_color[0]; + gtk_widget_set_style(GTK_BIN(button)->child, style); + } else + g_warning("about_create(): color allocation failed.\n"); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); + + text = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + gtk_container_add(GTK_CONTAINER(scrolledwin), text); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + gtk_text_buffer_get_iter_at_offset(buffer, &iter, 0); + +#if USE_GPGME + gtk_text_buffer_insert(buffer, &iter, + _("GPGME is copyright 2001 by Werner Koch \n\n"), -1); +#endif /* USE_GPGME */ + + gtk_text_buffer_insert(buffer, &iter, + _("This program is free software; you can redistribute it and/or modify " + "it under the terms of the GNU General Public License as published by " + "the Free Software Foundation; either version 2, or (at your option) " + "any later version.\n\n"), -1); + + gtk_text_buffer_insert(buffer, &iter, + _("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.\n\n"), -1); + + gtk_text_buffer_insert(buffer, &iter, + _("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."), -1); + + gtkut_button_set_create(&confirm_area, &ok_button, _("OK"), + NULL, NULL, NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_button); + g_signal_connect_closure + (G_OBJECT(ok_button), "clicked", + g_cclosure_new_swap(G_CALLBACK(gtk_widget_hide_on_delete), + window, NULL), FALSE); + + gtk_widget_show_all(window); +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event) +{ + if (event && event->keyval == GDK_Escape) + gtk_widget_hide(window); + return FALSE; +} + +static void about_uri_clicked(GtkButton *button, gpointer data) +{ + open_uri(HOMEPAGE_URI, prefs_common.uri_cmd); +} diff --git a/src/about.h b/src/about.h new file mode 100644 index 00000000..55496fc7 --- /dev/null +++ b/src/about.h @@ -0,0 +1,25 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ABOUT_H__ +#define __ABOUT_H__ + +void about_show(void); + +#endif /* __ABOUT_H__ */ diff --git a/src/account.c b/src/account.c new file mode 100644 index 00000000..49069651 --- /dev/null +++ b/src/account.c @@ -0,0 +1,990 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "mainwindow.h" +#include "folderview.h" +#include "folder.h" +#include "account.h" +#include "prefs.h" +#include "prefs_account.h" +#include "procmsg.h" +#include "procheader.h" +#include "compose.h" +#include "manage_window.h" +#include "stock_pixmap.h" +#include "statusbar.h" +#include "inc.h" +#include "gtkutils.h" +#include "utils.h" +#include "alertpanel.h" + +typedef enum +{ + COL_DEFAULT = 0, + COL_GETALL = 1, + COL_NAME = 2, + COL_PROTOCOL = 3, + COL_SERVER = 4 +} EditAccountColumnPos; + +# define N_EDIT_ACCOUNT_COLS 5 + +#define PREFSBUFSIZE 1024 + +PrefsAccount *cur_account; + +static GList *account_list = NULL; + +static struct EditAccount { + GtkWidget *window; + GtkWidget *clist; + GtkWidget *close_btn; +} edit_account; + +static GdkPixmap *markxpm; +static GdkBitmap *markxpmmask; +static GdkPixmap *checkboxonxpm; +static GdkPixmap *checkboxonxpmmask; +static GdkPixmap *checkboxoffxpm; +static GdkPixmap *checkboxoffxpmmask; + +static void account_edit_create (void); + +static void account_edit_prefs (void); +static void account_delete (void); + +static void account_up (void); +static void account_down (void); + +static void account_set_default (void); + +static void account_edit_close (void); +static gint account_delete_event (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static void account_selected (GtkCList *clist, + gint row, + gint column, + GdkEvent *event, + gpointer data); +static void account_row_moved (GtkCList *clist, + gint source_row, + gint dest_row); +static gboolean account_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); + +static gint account_clist_set_row (PrefsAccount *ac_prefs, + gint row); +static void account_clist_set (void); + +static void account_list_set (void); + +void account_read_config_all(void) +{ + GSList *ac_label_list = NULL, *cur; + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + PrefsAccount *ac_prefs; + + debug_print(_("Reading all config for each account...\n")); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL); + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + return; + } + g_free(rcpath); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (!strncmp(buf, "[Account: ", 10)) { + strretchomp(buf); + memmove(buf, buf + 1, strlen(buf)); + buf[strlen(buf) - 1] = '\0'; + debug_print(_("Found label: %s\n"), buf); + ac_label_list = g_slist_append(ac_label_list, + g_strdup(buf)); + } + } + fclose(fp); + + /* read config data from file */ + cur_account = NULL; + for (cur = ac_label_list; cur != NULL; cur = cur->next) { + ac_prefs = prefs_account_new(); + prefs_account_read_config(ac_prefs, (gchar *)cur->data); + account_list = g_list_append(account_list, ac_prefs); + if (ac_prefs->is_default) + cur_account = ac_prefs; + } + /* if default is not set, assume first account as default */ + if (!cur_account && account_list) { + ac_prefs = (PrefsAccount *)account_list->data; + account_set_as_default(ac_prefs); + cur_account = ac_prefs; + } + + account_set_menu(); + main_window_reflect_prefs_all(); + + while (ac_label_list) { + g_free(ac_label_list->data); + ac_label_list = g_slist_remove(ac_label_list, + ac_label_list->data); + } +} + +void account_write_config_all(void) +{ + prefs_account_write_config_all(account_list); +} + +PrefsAccount *account_find_from_smtp_server(const gchar *address, + const gchar *smtp_server) +{ + GList *cur; + PrefsAccount *ac; + + g_return_val_if_fail(address != NULL, NULL); + g_return_val_if_fail(smtp_server != NULL, NULL); + + for (cur = account_list; cur != NULL; cur = cur->next) { + ac = (PrefsAccount *)cur->data; + if (!strcmp2(address, ac->address) && + !strcmp2(smtp_server, ac->smtp_server)) + return ac; + } + + return NULL; +} + +/* + * account_find_from_address: + * @address: Email address string. + * + * Find a mail (not news) account with the specified email address. + * + * Return value: The found account, or NULL if not found. + */ +PrefsAccount *account_find_from_address(const gchar *address) +{ + GList *cur; + PrefsAccount *ac; + + g_return_val_if_fail(address != NULL, NULL); + + for (cur = account_list; cur != NULL; cur = cur->next) { + ac = (PrefsAccount *)cur->data; + if (ac->protocol != A_NNTP && ac->address && + strcasestr(address, ac->address) != NULL) + return ac; + } + + return NULL; +} + +PrefsAccount *account_find_from_id(gint id) +{ + GList *cur; + PrefsAccount *ac; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ac = (PrefsAccount *)cur->data; + if (id == ac->account_id) + return ac; + } + + return NULL; +} + +PrefsAccount *account_find_from_item(FolderItem *item) +{ + PrefsAccount *ac; + + g_return_val_if_fail(item != NULL, NULL); + + ac = item->account; + if (!ac) { + FolderItem *cur_item = item->parent; + while (cur_item != NULL) { + if (cur_item->account && cur_item->ac_apply_sub) { + ac = cur_item->account; + break; + } + cur_item = cur_item->parent; + } + } + if (!ac) + ac = item->folder->account; + + return ac; +} + +PrefsAccount *account_find_from_message_file(const gchar *file) +{ + static HeaderEntry hentry[] = {{"From:", NULL, FALSE}, + {"X-Sylpheed-Account-Id:", NULL, FALSE}, + {"AID:", NULL, FALSE}}; + + enum + { + H_FROM = 0, + H_X_SYLPHEED_ACCOUNT_ID = 1, + H_AID = 2 + }; + + PrefsAccount *ac = NULL; + FILE *fp; + gchar *str; + gchar buf[BUFFSIZE]; + gint hnum; + + g_return_val_if_fail(file != NULL, NULL); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + str = buf + strlen(hentry[hnum].name); + if (hnum == H_FROM) + ac = account_find_from_address(str); + else if (hnum == H_X_SYLPHEED_ACCOUNT_ID || hnum == H_AID) { + PrefsAccount *tmp_ac; + + tmp_ac = account_find_from_id(atoi(str)); + if (tmp_ac) { + ac = tmp_ac; + break; + } + } + } + + fclose(fp); + return ac; +} + +PrefsAccount *account_find_from_msginfo(MsgInfo *msginfo) +{ + gchar *file; + PrefsAccount *ac; + + file = procmsg_get_message_file(msginfo); + ac = account_find_from_message_file(file); + g_free(file); + + if (!ac && msginfo->folder) + ac = account_find_from_item(msginfo->folder); + + return ac; +} + +void account_set_menu(void) +{ + main_window_set_account_menu(account_list); +} + +void account_foreach(AccountFunc func, gpointer user_data) +{ + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) + if (func((PrefsAccount *)cur->data, user_data) != 0) + return; +} + +GList *account_get_list(void) +{ + return account_list; +} + +void account_edit_open(void) +{ + inc_lock(); + + if (compose_get_compose_list()) { + alertpanel_notice(_("Some composing windows are open.\n" + "Please close all the composing windows before editing the accounts.")); + inc_unlock(); + return; + } + + debug_print(_("Opening account edit window...\n")); + + if (!edit_account.window) + account_edit_create(); + + account_clist_set(); + + manage_window_set_transient(GTK_WINDOW(edit_account.window)); + gtk_widget_grab_focus(edit_account.close_btn); + gtk_widget_show(edit_account.window); + + manage_window_focus_in(edit_account.window, NULL, NULL); +} + +void account_add(void) +{ + PrefsAccount *ac_prefs; + + ac_prefs = prefs_account_open(NULL); + + if (!ac_prefs) return; + + account_list = g_list_append(account_list, ac_prefs); + + if (ac_prefs->is_default) + account_set_as_default(ac_prefs); + + account_clist_set(); + + if (ac_prefs->protocol == A_IMAP4 || ac_prefs->protocol == A_NNTP) { + Folder *folder; + + if (ac_prefs->protocol == A_IMAP4) { + folder = folder_new(F_IMAP, ac_prefs->account_name, + ac_prefs->recv_server); + } else { + folder = folder_new(F_NEWS, ac_prefs->account_name, + ac_prefs->nntp_server); + } + + folder->account = ac_prefs; + ac_prefs->folder = REMOTE_FOLDER(folder); + folder_add(folder); + if (ac_prefs->protocol == A_IMAP4) { + if (main_window_toggle_online_if_offline + (main_window_get())) { + folder->klass->create_tree(folder); + statusbar_pop_all(); + } + } + folderview_set_all(); + } +} + +void account_open(PrefsAccount *ac_prefs) +{ + gboolean prev_default; + gchar *ac_name; + + g_return_if_fail(ac_prefs != NULL); + + prev_default = ac_prefs->is_default; + Xstrdup_a(ac_name, ac_prefs->account_name ? ac_prefs->account_name : "", + return); + + prefs_account_open(ac_prefs); + + if (!prev_default && ac_prefs->is_default) + account_set_as_default(ac_prefs); + + if (ac_prefs->folder && strcmp2(ac_name, ac_prefs->account_name) != 0) { + folder_set_name(FOLDER(ac_prefs->folder), + ac_prefs->account_name); + folderview_set_all(); + } + + account_write_config_all(); + account_set_menu(); + main_window_reflect_prefs_all(); +} + +void account_set_as_default(PrefsAccount *ac_prefs) +{ + PrefsAccount *ap; + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ap = (PrefsAccount *)cur->data; + if (ap->is_default) + ap->is_default = FALSE; + } + + ac_prefs->is_default = TRUE; +} + +PrefsAccount *account_get_default(void) +{ + PrefsAccount *ap; + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ap = (PrefsAccount *)cur->data; + if (ap->is_default) + return ap; + } + + return NULL; +} + +void account_set_missing_folder(void) +{ + PrefsAccount *ap; + GList *cur; + + for (cur = account_list; cur != NULL; cur = cur->next) { + ap = (PrefsAccount *)cur->data; + if ((ap->protocol == A_IMAP4 || ap->protocol == A_NNTP) && + !ap->folder) { + Folder *folder; + + if (ap->protocol == A_IMAP4) { + folder = folder_new(F_IMAP, ap->account_name, + ap->recv_server); + } else { + folder = folder_new(F_NEWS, ap->account_name, + ap->nntp_server); + } + + folder->account = ap; + ap->folder = REMOTE_FOLDER(folder); + folder_add(folder); + if (ap->protocol == A_IMAP4) { + if (main_window_toggle_online_if_offline + (main_window_get())) { + folder->klass->create_tree(folder); + statusbar_pop_all(); + } + } + } + } +} + +FolderItem *account_get_special_folder(PrefsAccount *ac_prefs, + SpecialFolderItemType type) +{ + FolderItem *item = NULL; + + g_return_val_if_fail(ac_prefs != NULL, NULL); + + switch (type) { + case F_INBOX: + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->inbox; + if (!item) + item = folder_get_default_inbox(); + break; + case F_OUTBOX: + if (ac_prefs->set_sent_folder && ac_prefs->sent_folder) { + item = folder_find_item_from_identifier + (ac_prefs->sent_folder); + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->outbox; + if (!item) + item = folder_get_default_outbox(); + } + break; + case F_DRAFT: + if (ac_prefs->set_draft_folder && ac_prefs->draft_folder) { + item = folder_find_item_from_identifier + (ac_prefs->draft_folder); + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->draft; + if (!item) + item = folder_get_default_draft(); + } + break; + case F_QUEUE: + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->queue; + if (!item) + item = folder_get_default_queue(); + break; + case F_TRASH: + if (ac_prefs->set_trash_folder && ac_prefs->trash_folder) { + item = folder_find_item_from_identifier + (ac_prefs->trash_folder); + } + if (!item) { + if (ac_prefs->folder) + item = FOLDER(ac_prefs->folder)->trash; + if (!item) + item = folder_get_default_trash(); + } + break; + default: + break; + } + + return item; +} + +void account_destroy(PrefsAccount *ac_prefs) +{ + g_return_if_fail(ac_prefs != NULL); + + folder_unref_account_all(ac_prefs); + + prefs_account_free(ac_prefs); + account_list = g_list_remove(account_list, ac_prefs); + + if (cur_account == ac_prefs) cur_account = NULL; + if (!cur_account && account_list) { + cur_account = account_get_default(); + if (!cur_account) { + ac_prefs = (PrefsAccount *)account_list->data; + account_set_as_default(ac_prefs); + cur_account = ac_prefs; + } + } +} + + +static void account_edit_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *hbox; + GtkWidget *scrolledwin; + GtkWidget *clist; + gchar *titles[N_EDIT_ACCOUNT_COLS]; + gint i; + + GtkWidget *vbox2; + GtkWidget *add_btn; + GtkWidget *edit_btn; + GtkWidget *del_btn; + GtkWidget *up_btn; + GtkWidget *down_btn; + + GtkWidget *default_btn; + + GtkWidget *hbbox; + GtkWidget *close_btn; + + debug_print(_("Creating account edit window...\n")); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 500, 320); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + gtk_window_set_title (GTK_WINDOW (window), _("Edit accounts")); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + g_signal_connect (G_OBJECT (window), "delete_event", + G_CALLBACK (account_delete_event), NULL); + g_signal_connect (G_OBJECT (window), "key_press_event", + G_CALLBACK (account_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT (window); + gtk_widget_realize(window); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (window), vbox); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new + (_("New messages will be checked in this order. Check the boxes\n" + "on the `G' column to enable message retrieval by `Get all'.")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 4); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); + + scrolledwin = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwin); + gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + titles[COL_DEFAULT] = "D"; + titles[COL_GETALL] = "G"; + titles[COL_NAME] = _("Name"); + titles[COL_PROTOCOL] = _("Protocol"); + titles[COL_SERVER] = _("Server"); + + clist = gtk_clist_new_with_titles (N_EDIT_ACCOUNT_COLS, titles); + gtk_widget_show (clist); + gtk_container_add (GTK_CONTAINER (scrolledwin), clist); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_DEFAULT , 10); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_GETALL , 11); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME , 100); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_PROTOCOL, 100); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_SERVER , 100); + gtk_clist_set_column_justification (GTK_CLIST(clist), COL_DEFAULT, + GTK_JUSTIFY_CENTER); + gtk_clist_set_column_justification (GTK_CLIST(clist), COL_GETALL, + GTK_JUSTIFY_CENTER); + gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE); + + for (i = 0; i < N_EDIT_ACCOUNT_COLS; i++) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[i].button, + GTK_CAN_FOCUS); + + g_signal_connect (G_OBJECT (clist), "select_row", + G_CALLBACK (account_selected), NULL); + g_signal_connect_after (G_OBJECT (clist), "row_move", + G_CALLBACK (account_row_moved), NULL); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0); + + add_btn = gtk_button_new_with_label (_("Add")); + gtk_widget_show (add_btn); + gtk_box_pack_start (GTK_BOX (vbox2), add_btn, FALSE, FALSE, 4); + g_signal_connect (G_OBJECT(add_btn), "clicked", + G_CALLBACK (account_add), NULL); + + edit_btn = gtk_button_new_with_label (_("Edit")); + gtk_widget_show (edit_btn); + gtk_box_pack_start (GTK_BOX (vbox2), edit_btn, FALSE, FALSE, 4); + g_signal_connect (G_OBJECT(edit_btn), "clicked", + G_CALLBACK (account_edit_prefs), NULL); + + del_btn = gtk_button_new_with_label (_(" Delete ")); + gtk_widget_show (del_btn); + gtk_box_pack_start (GTK_BOX (vbox2), del_btn, FALSE, FALSE, 4); + g_signal_connect (G_OBJECT(del_btn), "clicked", + G_CALLBACK (account_delete), NULL); + + down_btn = gtk_button_new_with_label (_("Down")); + gtk_widget_show (down_btn); + gtk_box_pack_end (GTK_BOX (vbox2), down_btn, FALSE, FALSE, 4); + g_signal_connect (G_OBJECT(down_btn), "clicked", + G_CALLBACK (account_down), NULL); + + up_btn = gtk_button_new_with_label (_("Up")); + gtk_widget_show (up_btn); + gtk_box_pack_end (GTK_BOX (vbox2), up_btn, FALSE, FALSE, 4); + g_signal_connect (G_OBJECT(up_btn), "clicked", + G_CALLBACK (account_up), NULL); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new(FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0); + + default_btn = gtk_button_new_with_label (_(" Set as default account ")); + gtk_widget_show (default_btn); + gtk_box_pack_start (GTK_BOX (vbox2), default_btn, TRUE, FALSE, 0); + g_signal_connect (G_OBJECT(default_btn), "clicked", + G_CALLBACK (account_set_default), NULL); + + gtkut_button_set_create(&hbbox, &close_btn, _("Close"), + NULL, NULL, NULL, NULL); + gtk_widget_show(hbbox); + gtk_box_pack_end (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0); + gtk_widget_grab_default (close_btn); + + g_signal_connect (G_OBJECT (close_btn), "clicked", + G_CALLBACK (account_edit_close), NULL); + + stock_pixmap_gdk(clist, STOCK_PIXMAP_MARK, &markxpm, &markxpmmask); + stock_pixmap_gdk(clist, STOCK_PIXMAP_CHECKBOX_ON, + &checkboxonxpm, &checkboxonxpmmask); + stock_pixmap_gdk(clist, STOCK_PIXMAP_CHECKBOX_OFF, + &checkboxoffxpm, &checkboxoffxpmmask); + + edit_account.window = window; + edit_account.clist = clist; + edit_account.close_btn = close_btn; +} + +static void account_edit_prefs(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + PrefsAccount *ac_prefs; + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + ac_prefs = gtk_clist_get_row_data(clist, row); + account_open(ac_prefs); + + account_clist_set(); +} + +static void account_delete(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + PrefsAccount *ac_prefs; + gint row; + + if (!clist->selection) return; + + if (alertpanel(_("Delete account"), + _("Do you really want to delete this account?"), + _("Yes"), _("+No"), NULL) != G_ALERTDEFAULT) + return; + + row = GPOINTER_TO_INT(clist->selection->data); + ac_prefs = gtk_clist_get_row_data(clist, row); + if (ac_prefs->folder) { + FolderItem *item; + + item = main_window_get()->summaryview->folder_item; + if (item && item->folder == FOLDER(ac_prefs->folder)) + summary_clear_all(main_window_get()->summaryview); + folder_destroy(FOLDER(ac_prefs->folder)); + folderview_set_all(); + } + account_destroy(ac_prefs); + account_clist_set(); +} + +static void account_up(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0) + gtk_clist_row_move(clist, row, row - 1); +} + +static void account_down(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row < clist->rows - 1) + gtk_clist_row_move(clist, row, row + 1); +} + +static void account_set_default(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + gint row; + PrefsAccount *ac_prefs; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + ac_prefs = gtk_clist_get_row_data(clist, row); + account_set_as_default(ac_prefs); + account_clist_set(); + + cur_account = ac_prefs; + account_set_menu(); + main_window_reflect_prefs_all(); +} + +static void account_edit_close(void) +{ + account_list_set(); + account_write_config_all(); + + if (!cur_account && account_list) { + PrefsAccount *ac_prefs = (PrefsAccount *)account_list->data; + account_set_as_default(ac_prefs); + cur_account = ac_prefs; + } + + account_set_menu(); + main_window_reflect_prefs_all(); + + gtk_widget_hide(edit_account.window); + + inc_unlock(); +} + +static gint account_delete_event(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + account_edit_close(); + return TRUE; +} + +static void account_selected(GtkCList *clist, gint row, gint column, + GdkEvent *event, gpointer data) +{ + if (event && event->type == GDK_2BUTTON_PRESS) { + account_edit_prefs(); + return; + } + + if (column == COL_GETALL) { + PrefsAccount *ac; + + ac = gtk_clist_get_row_data(clist, row); + if (ac->protocol == A_POP3 || ac->protocol == A_IMAP4 || + ac->protocol == A_NNTP) { + ac->recv_at_getall ^= TRUE; + account_clist_set_row(ac, row); + } + } +} + +static void account_row_moved(GtkCList *clist, gint source_row, gint dest_row) +{ + account_list_set(); + if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) + gtk_clist_moveto(clist, dest_row, -1, 0.5, 0.0); +} + +static gboolean account_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + account_edit_close(); + return FALSE; +} + +/* set one CList row or add new row */ +static gint account_clist_set_row(PrefsAccount *ac_prefs, gint row) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + gchar *text[N_EDIT_ACCOUNT_COLS]; + gboolean has_getallbox; + gboolean getall; + + text[COL_DEFAULT] = ""; + text[COL_GETALL] = ""; + text[COL_NAME] = ac_prefs->account_name; +#if USE_SSL + text[COL_PROTOCOL] = ac_prefs->protocol == A_POP3 ? + (ac_prefs->ssl_pop == SSL_TUNNEL ? + "POP3 (SSL)" : + ac_prefs->ssl_pop == SSL_STARTTLS ? + "POP3 (TLS)" : "POP3") : + ac_prefs->protocol == A_IMAP4 ? + (ac_prefs->ssl_imap == SSL_TUNNEL ? + "IMAP4 (SSL)" : + ac_prefs->ssl_imap == SSL_STARTTLS ? + "IMAP4 (TLS)" : "IMAP4") : + ac_prefs->protocol == A_NNTP ? + (ac_prefs->ssl_nntp == SSL_TUNNEL ? + "NNTP (SSL)" : "NNTP") : + ""; +#else + text[COL_PROTOCOL] = ac_prefs->protocol == A_POP3 ? "POP3" : + ac_prefs->protocol == A_IMAP4 ? "IMAP4" : + ac_prefs->protocol == A_NNTP ? "NNTP" : ""; +#endif + text[COL_SERVER] = ac_prefs->protocol == A_NNTP + ? ac_prefs->nntp_server : ac_prefs->recv_server; + + if (row < 0) + row = gtk_clist_append(clist, text); + else { + gtk_clist_set_text(clist, row, COL_DEFAULT, text[COL_DEFAULT]); + gtk_clist_set_text(clist, row, COL_GETALL, text[COL_GETALL]); + gtk_clist_set_text(clist, row, COL_NAME, text[COL_NAME]); + gtk_clist_set_text(clist, row, COL_PROTOCOL, text[COL_PROTOCOL]); + gtk_clist_set_text(clist, row, COL_SERVER, text[COL_SERVER]); + } + + has_getallbox = (ac_prefs->protocol == A_POP3 || + ac_prefs->protocol == A_IMAP4 || + ac_prefs->protocol == A_NNTP); + getall = has_getallbox && ac_prefs->recv_at_getall; + + if (ac_prefs->is_default) + gtk_clist_set_pixmap(clist, row, COL_DEFAULT, + markxpm, markxpmmask); + if (getall) + gtk_clist_set_pixmap(clist, row, COL_GETALL, + checkboxonxpm, checkboxonxpmmask); + else if (has_getallbox) + gtk_clist_set_pixmap(clist, row, COL_GETALL, + checkboxoffxpm, checkboxoffxpmmask); + + gtk_clist_set_row_data(clist, row, ac_prefs); + + return row; +} + +/* set CList from account list */ +static void account_clist_set(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + GList *cur; + gint row = -1, prev_row = -1; + + if (clist->selection) + prev_row = GPOINTER_TO_INT(clist->selection->data); + + gtk_clist_freeze(clist); + gtk_clist_clear(clist); + + for (cur = account_list; cur != NULL; cur = cur->next) { + row = account_clist_set_row((PrefsAccount *)cur->data, -1); + if ((PrefsAccount *)cur->data == cur_account) { + gtk_clist_select_row(clist, row, -1); + gtkut_clist_set_focus_row(clist, row); + } + } + + if (prev_row >= 0) { + row = prev_row; + gtk_clist_select_row(clist, row, -1); + gtkut_clist_set_focus_row(clist, row); + } + + if (row >= 0 && + gtk_clist_row_is_visible(clist, row) != GTK_VISIBILITY_FULL) + gtk_clist_moveto(clist, row, -1, 0.5, 0); + + gtk_clist_thaw(clist); +} + +/* set account list from CList */ +static void account_list_set(void) +{ + GtkCList *clist = GTK_CLIST(edit_account.clist); + gint row; + PrefsAccount *ac_prefs; + + while (account_list) + account_list = g_list_remove(account_list, account_list->data); + + for (row = 0; (ac_prefs = gtk_clist_get_row_data(clist, row)) != NULL; + row++) + account_list = g_list_append(account_list, ac_prefs); +} diff --git a/src/account.h b/src/account.h new file mode 100644 index 00000000..2ec2a384 --- /dev/null +++ b/src/account.h @@ -0,0 +1,64 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ACCOUNT_H__ +#define __ACCOUNT_H__ + +#include + +#include "prefs.h" +#include "prefs_account.h" +#include "folder.h" +#include "procmsg.h" + +typedef gint (*AccountFunc) (PrefsAccount *ac_prefs, + gpointer user_data); + +extern PrefsAccount *cur_account; + +void account_read_config_all (void); +void account_write_config_all (void); + +PrefsAccount *account_find_from_smtp_server (const gchar *address, + const gchar *smtp_server); +PrefsAccount *account_find_from_address (const gchar *address); +PrefsAccount *account_find_from_id (gint id); +PrefsAccount *account_find_from_item (FolderItem *item); +PrefsAccount *account_find_from_message_file (const gchar *file); +PrefsAccount *account_find_from_msginfo (MsgInfo *msginfo); + +void account_set_menu (void); + +void account_foreach (AccountFunc func, + gpointer user_data); +GList *account_get_list (void); + +void account_edit_open (void); +void account_add (void); +void account_open (PrefsAccount *ac_prefs); +void account_set_as_default (PrefsAccount *ac_prefs); +PrefsAccount *account_get_default (void); + +void account_set_missing_folder(void); +FolderItem *account_get_special_folder(PrefsAccount *ac_prefs, + SpecialFolderItemType type); + +void account_destroy (PrefsAccount *ac_prefs); + +#endif /* __ACCOUNT_H__ */ diff --git a/src/action.c b/src/action.c new file mode 100644 index 00000000..4c97f73a --- /dev/null +++ b/src/action.c @@ -0,0 +1,1269 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto & The Sylpheed Claws Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#ifdef GDK_WINDOWING_X11 +# include +#endif /* GDK_WINDOWING_X11 */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "utils.h" +#include "gtkutils.h" +#include "manage_window.h" +#include "mainwindow.h" +#include "prefs_common.h" +#include "alertpanel.h" +#include "inputdialog.h" +#include "action.h" +#include "compose.h" +#include "procmsg.h" +#include "textview.h" + +typedef struct _Children Children; +typedef struct _ChildInfo ChildInfo; +typedef struct _UserStringDialog UserStringDialog; + +struct _Children +{ + GtkWidget *dialog; + GtkWidget *text; + GtkWidget *input_entry; + GtkWidget *input_hbox; + GtkWidget *abort_btn; + GtkWidget *close_btn; + GtkWidget *scrolledwin; + + gchar *action; + ActionType action_type; + GSList *list; + gint nb; + gint open_in; + gboolean output; + + GtkWidget *msg_text; + + gboolean is_selection; +}; + +struct _ChildInfo +{ + Children *children; + gchar *cmd; + pid_t pid; + gint chld_in; + gint chld_out; + gint chld_err; + gint chld_status; + gint tag_in; + gint tag_out; + gint tag_err; + gint tag_status; + gint new_out; + + GString *output; +}; + +static void action_update_menu (GtkItemFactory *ifactory, + gchar *branch_path, + gpointer callback, + gpointer data); +static void compose_actions_execute_cb (Compose *compose, + guint action_nb, + GtkWidget *widget); +static void mainwin_actions_execute_cb (MainWindow *mainwin, + guint action_nb, + GtkWidget *widget); +static void msgview_actions_execute_cb (MessageView *msgview, + guint action_nb, + GtkWidget *widget); +static void message_actions_execute (MessageView *msgview, + guint action_nb, + GSList *msg_list); + +static gboolean execute_actions (gchar *action, + GSList *msg_list, + GtkWidget *text, + gint body_pos, + MimeInfo *partinfo); + +static gchar *parse_action_cmd (gchar *action, + MsgInfo *msginfo, + GSList *msg_list, + MimeInfo *partinfo, + const gchar *user_str, + const gchar *user_hidden_str, + const gchar *sel_str); +static gboolean parse_append_filename (GString *cmd, + MsgInfo *msginfo); + +static gboolean parse_append_msgpart (GString *cmd, + MsgInfo *msginfo, + MimeInfo *partinfo); + +static ChildInfo *fork_child (gchar *cmd, + const gchar *msg_str, + Children *children); + +static gint wait_for_children (Children *children); + +static void free_children (Children *children); + +static void childinfo_close_pipes (ChildInfo *child_info); + +static void create_io_dialog (Children *children); +static void update_io_dialog (Children *children); + +static void hide_io_dialog_cb (GtkWidget *widget, + gpointer data); +static gint io_dialog_key_pressed_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer data); + +static void catch_output (gpointer data, + gint source, + GdkInputCondition cond); +static void catch_input (gpointer data, + gint source, + GdkInputCondition cond); +static void catch_status (gpointer data, + gint source, + GdkInputCondition cond); + +static gchar *get_user_string (const gchar *action, + ActionType type); + + +ActionType action_get_type(const gchar *action_str) +{ + const gchar *p; + ActionType action_type = ACTION_NONE; + + g_return_val_if_fail(action_str, ACTION_ERROR); + g_return_val_if_fail(*action_str, ACTION_ERROR); + + p = action_str; + + if (p[0] == '|') { + action_type |= ACTION_PIPE_IN; + p++; + } else if (p[0] == '>') { + action_type |= ACTION_USER_IN; + p++; + } else if (p[0] == '*') { + action_type |= ACTION_USER_HIDDEN_IN; + p++; + } + + if (p[0] == '\0') + return ACTION_ERROR; + + while (*p && action_type != ACTION_ERROR) { + if (p[0] == '%') { + switch (p[1]) { + case 'f': + action_type |= ACTION_SINGLE; + break; + case 'F': + action_type |= ACTION_MULTIPLE; + break; + case 'p': + action_type |= ACTION_SINGLE; + break; + case 's': + action_type |= ACTION_SELECTION_STR; + break; + case 'u': + action_type |= ACTION_USER_STR; + break; + case 'h': + action_type |= ACTION_USER_HIDDEN_STR; + break; + default: + action_type = ACTION_ERROR; + break; + } + } else if (p[0] == '|') { + if (p[1] == '\0') + action_type |= ACTION_PIPE_OUT; + } else if (p[0] == '>') { + if (p[1] == '\0') + action_type |= ACTION_INSERT; + } else if (p[0] == '&') { + if (p[1] == '\0') + action_type |= ACTION_ASYNC; + } + p++; + } + + return action_type; +} + +static gchar *parse_action_cmd(gchar *action, MsgInfo *msginfo, + GSList *msg_list, MimeInfo *partinfo, + const gchar *user_str, + const gchar *user_hidden_str, + const gchar *sel_str) +{ + GString *cmd; + gchar *p; + GSList *cur; + + p = action; + + if (p[0] == '|' || p[0] == '>' || p[0] == '*') + p++; + + cmd = g_string_sized_new(strlen(action)); + + while (p[0] && + !((p[0] == '|' || p[0] == '>' || p[0] == '&') && !p[1])) { + if (p[0] == '%' && p[1]) { + switch (p[1]) { + case 'f': + if (!parse_append_filename(cmd, msginfo)) { + g_string_free(cmd, TRUE); + return NULL; + } + p++; + break; + case 'F': + for (cur = msg_list; cur != NULL; + cur = cur->next) { + MsgInfo *msg = (MsgInfo *)cur->data; + + if (!parse_append_filename(cmd, msg)) { + g_string_free(cmd, TRUE); + return NULL; + } + if (cur->next) + g_string_append_c(cmd, ' '); + } + p++; + break; + case 'p': + if (!parse_append_msgpart(cmd, msginfo, + partinfo)) { + g_string_free(cmd, TRUE); + return NULL; + } + p++; + break; + case 's': + if (sel_str) + g_string_append(cmd, sel_str); + p++; + break; + case 'u': + if (user_str) + g_string_append(cmd, user_str); + p++; + break; + case 'h': + if (user_hidden_str) + g_string_append(cmd, user_hidden_str); + p++; + break; + default: + g_string_append_c(cmd, p[0]); + g_string_append_c(cmd, p[1]); + p++; + } + } else { + g_string_append_c(cmd, p[0]); + } + p++; + } + if (cmd->len == 0) { + g_string_free(cmd, TRUE); + return NULL; + } + + p = cmd->str; + g_string_free(cmd, FALSE); + return p; +} + +static gboolean parse_append_filename(GString *cmd, MsgInfo *msginfo) +{ + gchar *filename; + gchar *p, *q; + gchar escape_ch[] = "\\ "; + + g_return_val_if_fail(msginfo, FALSE); + + filename = procmsg_get_message_file(msginfo); + + if (!filename) { + alertpanel_error(_("Could not get message file %d"), + msginfo->msgnum); + return FALSE; + } + + p = filename; + while ((q = strpbrk(p, "$\"`'\\ \t*?[]&|;<>()!#~")) != NULL) { + escape_ch[1] = *q; + *q = '\0'; + g_string_append(cmd, p); + g_string_append(cmd, escape_ch); + p = q + 1; + } + g_string_append(cmd, p); + + g_free(filename); + + return TRUE; +} + +static gboolean parse_append_msgpart(GString *cmd, MsgInfo *msginfo, + MimeInfo *partinfo) +{ + gboolean single_part = FALSE; + gchar *filename; + gchar *part_filename; + gint ret; + + if (!partinfo) { + partinfo = procmime_scan_message(msginfo); + if (!partinfo) { + alertpanel_error(_("Could not get message part.")); + return FALSE; + } + + single_part = TRUE; + } + + filename = procmsg_get_message_file_path(msginfo); + part_filename = procmime_get_tmp_file_name(partinfo); + + ret = procmime_get_part(part_filename, filename, partinfo); + + if (single_part) + procmime_mimeinfo_free_all(partinfo); + g_free(filename); + + if (ret < 0) { + alertpanel_error(_("Can't get part of multipart message")); + g_free(part_filename); + return FALSE; + } + + g_string_append(cmd, part_filename); + + g_free(part_filename); + + return TRUE; +} + +void action_update_mainwin_menu(GtkItemFactory *ifactory, MainWindow *mainwin) +{ + action_update_menu(ifactory, "/Tools/Actions", + mainwin_actions_execute_cb, mainwin); +} + +void action_update_msgview_menu(GtkItemFactory *ifactory, MessageView *msgview) +{ + action_update_menu(ifactory, "/Tools/Actions", + msgview_actions_execute_cb, msgview); +} + +void action_update_compose_menu(GtkItemFactory *ifactory, Compose *compose) +{ + action_update_menu(ifactory, "/Tools/Actions", + compose_actions_execute_cb, compose); +} + +static void action_update_menu(GtkItemFactory *ifactory, gchar *branch_path, + gpointer callback, gpointer data) +{ + GtkWidget *menuitem; + gchar *menu_path; + GSList *cur; + gchar *action, *action_p; + GList *amenu; + GtkItemFactoryEntry ifentry = {NULL, NULL, NULL, 0, ""}; + + ifentry.path = branch_path; + menuitem = gtk_item_factory_get_widget(ifactory, branch_path); + g_return_if_fail(menuitem != NULL); + + amenu = GTK_MENU_SHELL(menuitem)->children; + while (amenu != NULL) { + GList *alist = amenu->next; + gtk_widget_destroy(GTK_WIDGET(amenu->data)); + amenu = alist; + } + + ifentry.accelerator = NULL; + ifentry.callback_action = 0; + ifentry.callback = callback; + ifentry.item_type = NULL; + + for (cur = prefs_common.actions_list; cur; cur = cur->next) { + action = g_strdup((gchar *)cur->data); + action_p = strstr(action, ": "); + if (action_p && action_p[2] && + action_get_type(&action_p[2]) != ACTION_ERROR) { + action_p[0] = '\0'; + menu_path = g_strdup_printf("%s/%s", branch_path, + action); + ifentry.path = menu_path; + gtk_item_factory_create_item(ifactory, &ifentry, data, + 1); + g_free(menu_path); + } + g_free(action); + ifentry.callback_action++; + } +} + +static void compose_actions_execute_cb(Compose *compose, guint action_nb, + GtkWidget *widget) +{ + gchar *buf, *action; + ActionType action_type; + + g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list)); + + buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb); + g_return_if_fail(buf != NULL); + action = strstr(buf, ": "); + g_return_if_fail(action != NULL); + + /* Point to the beginning of the command-line */ + action += 2; + + action_type = action_get_type(action); + if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE)) { + alertpanel_warning + (_("The selected action cannot be used in the compose window\n" + "because it contains %%f, %%F or %%p.")); + return; + } + + execute_actions(action, NULL, compose->text, 0, NULL); +} + +static void mainwin_actions_execute_cb(MainWindow *mainwin, guint action_nb, + GtkWidget *widget) +{ + GSList *msg_list; + + msg_list = summary_get_selected_msg_list(mainwin->summaryview); + message_actions_execute(mainwin->messageview, action_nb, msg_list); + g_slist_free(msg_list); +} + +static void msgview_actions_execute_cb(MessageView *msgview, guint action_nb, + GtkWidget *widget) +{ + GSList *msg_list = NULL; + + if (msgview->msginfo) + msg_list = g_slist_append(msg_list, msgview->msginfo); + message_actions_execute(msgview, action_nb, msg_list); + g_slist_free(msg_list); +} + +static void message_actions_execute(MessageView *msgview, guint action_nb, + GSList *msg_list) +{ + TextView *textview; + MimeInfo *partinfo; + gchar *buf; + gchar *action; + GtkWidget *text = NULL; + guint body_pos = 0; + + g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list)); + + buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb); + + g_return_if_fail(buf); + g_return_if_fail((action = strstr(buf, ": "))); + + /* Point to the beginning of the command-line */ + action += 2; + + textview = messageview_get_current_textview(msgview); + if (textview) { + text = textview->text; + body_pos = textview->body_pos; + } + partinfo = messageview_get_selected_mime_part(msgview); + + execute_actions(action, msg_list, text, body_pos, partinfo); +} + +static gboolean execute_actions(gchar *action, GSList *msg_list, + GtkWidget *text, gint body_pos, + MimeInfo *partinfo) +{ + GSList *children_list = NULL; + gint is_ok = TRUE; + gint msg_list_len; + Children *children; + ChildInfo *child_info; + ActionType action_type; + MsgInfo *msginfo; + gchar *cmd; + gchar *sel_str = NULL; + gchar *msg_str = NULL; + gchar *user_str = NULL; + gchar *user_hidden_str = NULL; + GtkTextIter start_iter, end_iter; + gboolean is_selection = FALSE; + + g_return_val_if_fail(action && *action, FALSE); + + action_type = action_get_type(action); + + if (action_type == ACTION_ERROR) + return FALSE; /* ERR: syntax error */ + + if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && !msg_list) + return FALSE; /* ERR: file command without selection */ + + msg_list_len = g_slist_length(msg_list); + + if (action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN | ACTION_INSERT)) { + if (msg_list_len > 1) + return FALSE; /* ERR: pipe + multiple selection */ + if (!text) + return FALSE; /* ERR: pipe and no displayed text */ + } + + if (action_type & ACTION_SELECTION_STR) { + if (!text) + return FALSE; /* ERR: selection string but no text */ + } + + if (text) { + GtkTextBuffer *textbuf; + + textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + is_selection = gtk_text_buffer_get_selection_bounds + (textbuf, &start_iter, &end_iter); + if (!is_selection) { + gtk_text_buffer_get_start_iter(textbuf, &start_iter); + gtk_text_buffer_get_end_iter(textbuf, &end_iter); + } + msg_str = gtk_text_buffer_get_text + (textbuf, &start_iter, &end_iter, FALSE); + if (is_selection) + sel_str = g_strdup(msg_str); + } + + if (action_type & ACTION_USER_STR) { + if (!(user_str = get_user_string(action, ACTION_USER_STR))) { + g_free(msg_str); + g_free(sel_str); + return FALSE; + } + } + + if (action_type & ACTION_USER_HIDDEN_STR) { + if (!(user_hidden_str = + get_user_string(action, ACTION_USER_HIDDEN_STR))) { + g_free(msg_str); + g_free(sel_str); + g_free(user_str); + return FALSE; + } + } + + if (text && (action_type & ACTION_PIPE_OUT)) { + GtkTextBuffer *textbuf; + textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + gtk_text_buffer_delete(textbuf, &start_iter, &end_iter); + } + + children = g_new0(Children, 1); + + children->action = g_strdup(action); + children->action_type = action_type; + children->msg_text = text; + children->is_selection = is_selection; + + if ((action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)) && + ((action_type & ACTION_SINGLE) == 0 || msg_list_len == 1)) + children->open_in = 1; + + if (action_type & ACTION_SINGLE) { + GSList *cur; + + for (cur = msg_list; cur && is_ok == TRUE; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!msginfo) { + is_ok = FALSE; /* ERR: msginfo missing */ + break; + } + cmd = parse_action_cmd(action, msginfo, msg_list, + partinfo, user_str, + user_hidden_str, sel_str); + if (!cmd) { + debug_print("Action command error\n"); + is_ok = FALSE; /* ERR: incorrect command */ + break; + } + if ((child_info = fork_child(cmd, msg_str, children))) { + children_list = g_slist_append(children_list, + child_info); + } + g_free(cmd); + } + } else { + cmd = parse_action_cmd(action, NULL, msg_list, partinfo, + user_str, user_hidden_str, sel_str); + if (cmd) { + if ((child_info = fork_child(cmd, msg_str, children))) { + children_list = g_slist_append(children_list, + child_info); + } + g_free(cmd); + } else + is_ok = FALSE; /* ERR: incorrect command */ + } + + g_free(msg_str); + g_free(sel_str); + g_free(user_str); + g_free(user_hidden_str); + + if (!children_list) { + /* If not waiting for children, return */ + free_children(children); + } else { + GSList *cur; + + children->list = children_list; + children->nb = g_slist_length(children_list); + + for (cur = children_list; cur; cur = cur->next) { + child_info = (ChildInfo *) cur->data; + child_info->tag_status = + gdk_input_add(child_info->chld_status, + GDK_INPUT_READ, + catch_status, child_info); + } + + create_io_dialog(children); + } + + return is_ok; +} + +static ChildInfo *fork_child(gchar *cmd, const gchar *msg_str, + Children *children) +{ + gint chld_in[2], chld_out[2], chld_err[2], chld_status[2]; + gchar *cmdline[4]; + pid_t pid, gch_pid; + ChildInfo *child_info; + gint sync; + + sync = !(children->action_type & ACTION_ASYNC); + + chld_in[0] = chld_in[1] = chld_out[0] = chld_out[1] = chld_err[0] + = chld_err[1] = chld_status[0] = chld_status[1] = -1; + + if (sync) { + if (pipe(chld_status) || pipe(chld_in) || pipe(chld_out) || + pipe(chld_err)) { + alertpanel_error(_("Command could not be started. " + "Pipe creation failed.\n%s"), + g_strerror(errno)); + /* Closing fd = -1 fails silently */ + close(chld_in[0]); + close(chld_in[1]); + close(chld_out[0]); + close(chld_out[1]); + close(chld_err[0]); + close(chld_err[1]); + close(chld_status[0]); + close(chld_status[1]); + return NULL; /* Pipe error */ + } + } + + debug_print("Forking child and grandchild.\n"); + debug_print("Executing: /bin/sh -c %s\n", cmd); + + pid = fork(); + if (pid == 0) { /* Child */ + if (setpgid(0, 0)) + perror("setpgid"); + +#ifdef GDK_WINDOWING_X11 + close(ConnectionNumber(gdk_display)); +#endif /* GDK_WINDOWING_X11 */ + + gch_pid = fork(); + + if (gch_pid == 0) { + if (setpgid(0, getppid())) + perror("setpgid"); + + if (sync) { + if (children->action_type & + (ACTION_PIPE_IN | + ACTION_USER_IN | + ACTION_USER_HIDDEN_IN)) { + close(fileno(stdin)); + dup (chld_in[0]); + } + close(chld_in[0]); + close(chld_in[1]); + + close(fileno(stdout)); + dup (chld_out[1]); + close(chld_out[0]); + close(chld_out[1]); + + close(fileno(stderr)); + dup (chld_err[1]); + close(chld_err[0]); + close(chld_err[1]); + } + + cmdline[0] = "sh"; + cmdline[1] = "-c"; + cmdline[2] = cmd; + cmdline[3] = NULL; + execvp("/bin/sh", cmdline); + + perror("execvp"); + _exit(1); + } else if (gch_pid < (pid_t) 0) { /* Fork error */ + if (sync) + write(chld_status[1], "1\n", 2); + perror("fork"); + _exit(1); + } else { /* Child */ + if (sync) { + close(chld_in[0]); + close(chld_in[1]); + close(chld_out[0]); + close(chld_out[1]); + close(chld_err[0]); + close(chld_err[1]); + close(chld_status[0]); + + debug_print("Child: Waiting for grandchild\n"); + waitpid(gch_pid, NULL, 0); + debug_print("Child: grandchild ended\n"); + write(chld_status[1], "0\n", 2); + close(chld_status[1]); + } + _exit(0); + } + } else if (pid < 0) { /* Fork error */ + alertpanel_error(_("Could not fork to execute the following " + "command:\n%s\n%s"), + cmd, g_strerror(errno)); + return NULL; + } + + /* Parent */ + + if (!sync) { + waitpid(pid, NULL, 0); + return NULL; + } + + close(chld_in[0]); + if (!(children->action_type & + (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN))) + close(chld_in[1]); + close(chld_out[1]); + close(chld_err[1]); + close(chld_status[1]); + + child_info = g_new0(ChildInfo, 1); + + child_info->children = children; + + child_info->pid = pid; + child_info->cmd = g_strdup(cmd); + child_info->new_out = FALSE; + child_info->output = g_string_new(NULL); + child_info->chld_in = + (children->action_type & + (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN)) + ? chld_in [1] : -1; + child_info->chld_out = chld_out[0]; + child_info->chld_err = chld_err[0]; + child_info->chld_status = chld_status[0]; + child_info->tag_in = -1; + child_info->tag_out = gdk_input_add(chld_out[0], GDK_INPUT_READ, + catch_output, child_info); + child_info->tag_err = gdk_input_add(chld_err[0], GDK_INPUT_READ, + catch_output, child_info); + + if (!(children->action_type & + (ACTION_PIPE_IN | ACTION_PIPE_OUT | ACTION_INSERT))) + return child_info; + + if ((children->action_type & ACTION_PIPE_IN) && msg_str) { + write(chld_in[1], msg_str, strlen(msg_str)); + if (!(children->action_type & + (ACTION_USER_IN | ACTION_USER_HIDDEN_IN))) + close(chld_in[1]); + child_info->chld_in = -1; /* No more input */ + } + + return child_info; +} + +static void kill_children_cb(GtkWidget *widget, gpointer data) +{ + GSList *cur; + Children *children = (Children *) data; + ChildInfo *child_info; + + for (cur = children->list; cur; cur = cur->next) { + child_info = (ChildInfo *)(cur->data); + debug_print("Killing child group id %d\n", child_info->pid); + if (child_info->pid && kill(-child_info->pid, SIGTERM) < 0) + perror("kill"); + } +} + +static gint wait_for_children(Children *children) +{ + gboolean new_output; + ChildInfo *child_info; + GSList *cur; + gint nb = children->nb; + + children->nb = 0; + + cur = children->list; + new_output = FALSE; + while (cur) { + child_info = (ChildInfo *)cur->data; + if (child_info->pid) + children->nb++; + new_output |= child_info->new_out; + cur = cur->next; + } + + children->output |= new_output; + + if (new_output || (children->dialog && (nb != children->nb))) + update_io_dialog(children); + + if (children->nb) + return FALSE; + + if (!children->dialog) { + free_children(children); + } else if (!children->output) { + gtk_widget_destroy(children->dialog); + } + + return FALSE; +} + +static void send_input(GtkWidget *w, gpointer data) +{ + Children *children = (Children *) data; + ChildInfo *child_info = (ChildInfo *) children->list->data; + + child_info->tag_in = gdk_input_add(child_info->chld_in, + GDK_INPUT_WRITE, + catch_input, children); + gtk_widget_set_sensitive(children->input_hbox, FALSE); +} + +static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data) +{ + hide_io_dialog_cb(w, data); + return TRUE; +} + +static void hide_io_dialog_cb(GtkWidget *w, gpointer data) +{ + + Children *children = (Children *)data; + + if (!children->nb) { + g_signal_handlers_disconnect_matched + (G_OBJECT(children->dialog), + (GSignalMatchType)G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, children); + gtk_widget_destroy(children->dialog); + free_children(children); + } +} + +static gint io_dialog_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + hide_io_dialog_cb(widget, data); + return TRUE; +} + +static void childinfo_close_pipes(ChildInfo *child_info) +{ + /* stdout and stderr pipes are guaranteed to be removed by + * their handler, but in case where we receive child exit notification + * before grand-child's pipes closing signals, we check them and close + * them if necessary + */ + if (child_info->tag_in > 0) + gdk_input_remove(child_info->tag_in); + if (child_info->tag_out > 0) + gdk_input_remove(child_info->tag_out); + if (child_info->tag_err > 0) + gdk_input_remove(child_info->tag_err); + + if (child_info->chld_in >= 0) + close(child_info->chld_in); + if (child_info->chld_out >= 0) + close(child_info->chld_out); + if (child_info->chld_err >= 0) + close(child_info->chld_err); + + close(child_info->chld_status); +} + +static void free_children(Children *children) +{ + ChildInfo *child_info; + + debug_print("Freeing children data %p\n", children); + + g_free(children->action); + while (children->list != NULL) { + child_info = (ChildInfo *)children->list->data; + g_free(child_info->cmd); + g_string_free(child_info->output, TRUE); + children->list = g_slist_remove(children->list, child_info); + g_free(child_info); + } + g_free(children); +} + +static void update_io_dialog(Children *children) +{ + GSList *cur; + + debug_print("Updating actions input/output dialog.\n"); + + if (!children->nb) { + gtk_widget_set_sensitive(children->abort_btn, FALSE); + gtk_widget_set_sensitive(children->close_btn, TRUE); + if (children->input_hbox) + gtk_widget_set_sensitive(children->input_hbox, FALSE); + gtk_widget_grab_focus(children->close_btn); + g_signal_connect(G_OBJECT(children->dialog), + "key_press_event", + G_CALLBACK(io_dialog_key_pressed_cb), + children); + } + + if (children->output) { + GtkWidget *text = children->text; + GtkTextBuffer *textbuf; + GtkTextIter iter, start_iter, end_iter; + gchar *caption; + ChildInfo *child_info; + + gtk_widget_show(children->scrolledwin); + textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + gtk_text_buffer_get_start_iter(textbuf, &start_iter); + gtk_text_buffer_get_end_iter(textbuf, &end_iter); + iter = start_iter; + + for (cur = children->list; cur; cur = cur->next) { + child_info = (ChildInfo *)cur->data; + if (child_info->pid) + caption = g_strdup_printf + (_("--- Running: %s\n"), + child_info->cmd); + else + caption = g_strdup_printf + (_("--- Ended: %s\n"), + child_info->cmd); + + gtk_text_buffer_insert(textbuf, &iter, caption, -1); + gtk_text_buffer_insert(textbuf, &iter, + child_info->output->str, -1); + g_free(caption); + child_info->new_out = FALSE; + } + } +} + +static void create_io_dialog(Children *children) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *entry = NULL; + GtkWidget *input_hbox = NULL; + GtkWidget *send_button; + GtkWidget *label; + GtkWidget *text; + GtkWidget *scrolledwin; + GtkWidget *hbox; + GtkWidget *abort_button; + GtkWidget *close_button; + + debug_print("Creating action IO dialog\n"); + + dialog = gtk_dialog_new(); + gtk_container_set_border_width + (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + gtk_window_set_title(GTK_WINDOW(dialog), _("Action's input/output")); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + manage_window_set_transient(GTK_WINDOW(dialog)); + g_signal_connect(G_OBJECT(dialog), "delete_event", + G_CALLBACK(delete_io_dialog_cb), children); + g_signal_connect(G_OBJECT(dialog), "destroy", + G_CALLBACK(hide_io_dialog_cb), + children); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); + gtk_widget_show(vbox); + + label = gtk_label_new(children->action); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + gtk_widget_show(label); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); + gtk_widget_set_size_request(scrolledwin, 480, 200); + gtk_widget_hide(scrolledwin); + + text = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + gtk_container_add(GTK_CONTAINER(scrolledwin), text); + gtk_widget_show(text); + + if (children->open_in) { + input_hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(input_hbox); + + entry = gtk_entry_new(); + gtk_widget_set_size_request(entry, 320, -1); + g_signal_connect(G_OBJECT(entry), "activate", + G_CALLBACK(send_input), children); + gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 0); + if (children->action_type & ACTION_USER_HIDDEN_IN) + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + gtk_widget_show(entry); + + send_button = gtk_button_new_with_label(_(" Send ")); + g_signal_connect(G_OBJECT(send_button), "clicked", + G_CALLBACK(send_input), children); + gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE, + FALSE, 0); + gtk_widget_show(send_button); + + gtk_box_pack_start(GTK_BOX(vbox), input_hbox, FALSE, FALSE, 0); + gtk_widget_grab_focus(entry); + } + + gtkut_button_set_create(&hbox, &abort_button, _("Abort"), + &close_button, _("Close"), NULL, NULL); + g_signal_connect(G_OBJECT(abort_button), "clicked", + G_CALLBACK(kill_children_cb), children); + g_signal_connect(G_OBJECT(close_button), "clicked", + G_CALLBACK(hide_io_dialog_cb), children); + gtk_widget_show(hbox); + + if (children->nb) + gtk_widget_set_sensitive(close_button, FALSE); + + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox); + + children->dialog = dialog; + children->scrolledwin = scrolledwin; + children->text = text; + children->input_hbox = children->open_in ? input_hbox : NULL; + children->input_entry = children->open_in ? entry : NULL; + children->abort_btn = abort_button; + children->close_btn = close_button; + + gtk_widget_show(dialog); +} + +static void catch_status(gpointer data, gint source, GdkInputCondition cond) +{ + ChildInfo *child_info = (ChildInfo *)data; + gchar buf; + gint c; + + gdk_input_remove(child_info->tag_status); + + c = read(source, &buf, 1); + debug_print("Child returned %c\n", buf); + + waitpid(-child_info->pid, NULL, 0); + childinfo_close_pipes(child_info); + child_info->pid = 0; + + wait_for_children(child_info->children); +} + +static void catch_input(gpointer data, gint source, GdkInputCondition cond) +{ + Children *children = (Children *)data; + ChildInfo *child_info = (ChildInfo *)children->list->data; + gchar *input; + gint c, count, len; + + debug_print("Sending input to grand child.\n"); + if (!(cond && GDK_INPUT_WRITE)) + return; + + gdk_input_remove(child_info->tag_in); + child_info->tag_in = -1; + + input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry), + 0, -1); + len = strlen(input); + count = 0; + + do { + c = write(child_info->chld_in, input + count, len - count); + if (c >= 0) + count += c; + } while (c >= 0 && count < len); + + if (c >= 0) + write(child_info->chld_in, "\n", 2); + + g_free(input); + + gtk_entry_set_text(GTK_ENTRY(children->input_entry), ""); + gtk_widget_set_sensitive(children->input_hbox, TRUE); + close(child_info->chld_in); + child_info->chld_in = -1; + debug_print("Input to grand child sent.\n"); +} + +static void catch_output(gpointer data, gint source, GdkInputCondition cond) +{ + ChildInfo *child_info = (ChildInfo *)data; + gint c, i; + gchar buf[BUFFSIZE]; + + debug_print("Catching grand child's output.\n"); + if (child_info->children->action_type & + (ACTION_PIPE_OUT | ACTION_INSERT) + && source == child_info->chld_out) { + GtkTextView *text = + GTK_TEXT_VIEW(child_info->children->msg_text); + GtkTextBuffer *textbuf = gtk_text_view_get_buffer(text); + GtkTextIter iter1, iter2; + GtkTextMark *mark; + + mark = gtk_text_buffer_get_insert(textbuf); + gtk_text_buffer_get_iter_at_mark(textbuf, &iter1, mark); + gtk_text_buffer_get_iter_at_mark(textbuf, &iter2, mark); + + while (TRUE) { + c = read(source, buf, sizeof(buf) - 1); + if (c == 0) + break; + gtk_text_buffer_insert(textbuf, &iter2, buf, c); + } + + if (child_info->children->is_selection) { + gtk_text_buffer_place_cursor(textbuf, &iter1); + gtk_text_buffer_move_mark_by_name + (textbuf, "selection_bound", &iter2); + } + } else { + c = read(source, buf, sizeof(buf) - 1); + for (i = 0; i < c; i++) + g_string_append_c(child_info->output, buf[i]); + if (c > 0) + child_info->new_out = TRUE; + } + if (c == 0) { + if (source == child_info->chld_out) { + gdk_input_remove(child_info->tag_out); + child_info->tag_out = -1; + close(child_info->chld_out); + child_info->chld_out = -1; + } else { + gdk_input_remove(child_info->tag_err); + child_info->tag_err = -1; + close(child_info->chld_err); + child_info->chld_err = -1; + } + } + + wait_for_children(child_info->children); +} + +static gchar *get_user_string(const gchar *action, ActionType type) +{ + gchar *message; + gchar *user_str = NULL; + + switch (type) { + case ACTION_USER_HIDDEN_STR: + message = g_strdup_printf + (_("Enter the argument for the following action:\n" + "(`%%h' will be replaced with the argument)\n" + " %s"), + action); + user_str = input_dialog_with_invisible + (_("Action's hidden user argument"), message, NULL); + break; + case ACTION_USER_STR: + message = g_strdup_printf + (_("Enter the argument for the following action:\n" + "(`%%u' will be replaced with the argument)\n" + " %s"), + action); + user_str = input_dialog + (_("Action's user argument"), message, NULL); + break; + default: + g_warning("Unsupported action type %d", type); + } + + return user_str; +} diff --git a/src/action.h b/src/action.h new file mode 100644 index 00000000..373073da --- /dev/null +++ b/src/action.h @@ -0,0 +1,56 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto & The Sylpheed Claws Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ACTION_H__ +#define __ACTION_H__ + +#include +#include + +#include "mainwindow.h" +#include "messageview.h" +#include "compose.h" + +typedef enum +{ + ACTION_NONE = 1 << 0, + ACTION_PIPE_IN = 1 << 1, + ACTION_PIPE_OUT = 1 << 2, + ACTION_SINGLE = 1 << 3, + ACTION_MULTIPLE = 1 << 4, + ACTION_ASYNC = 1 << 5, + ACTION_USER_IN = 1 << 6, + ACTION_USER_HIDDEN_IN = 1 << 7, + ACTION_INSERT = 1 << 8, + ACTION_USER_STR = 1 << 9, + ACTION_USER_HIDDEN_STR = 1 << 10, + ACTION_SELECTION_STR = 1 << 11, + ACTION_ERROR = 1 << 30 +} ActionType; + +ActionType action_get_type (const gchar *action_str); + +void action_update_mainwin_menu (GtkItemFactory *ifactory, + MainWindow *mainwin); +void action_update_msgview_menu (GtkItemFactory *ifactory, + MessageView *msgview); +void action_update_compose_menu (GtkItemFactory *ifactory, + Compose *compose); + +#endif /* __ACTION_H__ */ diff --git a/src/addr_compl.c b/src/addr_compl.c new file mode 100644 index 00000000..848147e7 --- /dev/null +++ b/src/addr_compl.c @@ -0,0 +1,954 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * + * Copyright (C) 2000-2004 by Alfons Hoogervorst & The Sylpheed Claws Team. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "intl.h" +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#if (HAVE_WCTYPE_H && HAVE_WCHAR_H) +# include +# include +#endif + +#include "xml.h" +#include "addr_compl.h" +#include "utils.h" +#include "addressbook.h" +#include "main.h" + +/* How it works: + * + * The address book is read into memory. We set up an address list + * containing all address book entries. Next we make the completion + * list, which contains all the completable strings, and store a + * reference to the address entry it belongs to. + * After calling the g_completion_complete(), we get a reference + * to a valid email address. + * + * Completion is very simplified. We never complete on another prefix, + * i.e. we neglect the next smallest possible prefix for the current + * completion cache. This is simply done so we might break up the + * addresses a little more (e.g. break up alfons@proteus.demon.nl into + * something like alfons, proteus, demon, nl; and then completing on + * any of those words). + */ + +/* address_entry - structure which refers to the original address entry in the + * address book + */ +typedef struct +{ + gchar *name; + gchar *address; +} address_entry; + +/* completion_entry - structure used to complete addresses, with a reference + * the the real address information. + */ +typedef struct +{ + gchar *string; /* string to complete */ + address_entry *ref; /* address the string belongs to */ +} completion_entry; + +/*******************************************************************************/ + +static gint g_ref_count; /* list ref count */ +static GList *g_completion_list; /* list of strings to be checked */ +static GList *g_address_list; /* address storage */ +static GCompletion *g_completion; /* completion object */ + +/* To allow for continuing completion we have to keep track of the state + * using the following variables. No need to create a context object. */ + +static gint g_completion_count; /* nr of addresses incl. the prefix */ +static gint g_completion_next; /* next prev address */ +static GSList *g_completion_addresses; /* unique addresses found in the + completion cache. */ +static gchar *g_completion_prefix; /* last prefix. (this is cached here + * because the prefix passed to g_completion + * is g_strdown()'ed */ + +/*******************************************************************************/ + +/* completion_func() - used by GTK to find the string data to be used for + * completion + */ +static gchar *completion_func(gpointer data) +{ + g_return_val_if_fail(data != NULL, NULL); + + return ((completion_entry *)data)->string; +} + +static void init_all(void) +{ + g_completion = g_completion_new(completion_func); + g_return_if_fail(g_completion != NULL); +} + +static void free_all(void) +{ + GList *walk; + + walk = g_list_first(g_completion_list); + for (; walk != NULL; walk = g_list_next(walk)) { + completion_entry *ce = (completion_entry *) walk->data; + g_free(ce->string); + g_free(walk->data); + } + g_list_free(g_completion_list); + g_completion_list = NULL; + + walk = g_address_list; + for (; walk != NULL; walk = g_list_next(walk)) { + address_entry *ae = (address_entry *) walk->data; + g_free(ae->name); + g_free(ae->address); + g_free(walk->data); + } + g_list_free(g_address_list); + g_address_list = NULL; + + g_completion_free(g_completion); + g_completion = NULL; +} + +/* add_address() - adds address to the completion list. this function looks + * complicated, but it's only allocation checks. + */ +static gint add_address(const gchar *name, const gchar *address) +{ + address_entry *ae; + completion_entry *ce1; + completion_entry *ce2; + + if (!name || !address) return -1; + + ae = g_new0(address_entry, 1); + ce1 = g_new0(completion_entry, 1), + ce2 = g_new0(completion_entry, 1); + + g_return_val_if_fail(ae != NULL, -1); + g_return_val_if_fail(ce1 != NULL && ce2 != NULL, -1); + + ae->name = g_strdup(name); + ae->address = g_strdup(address); + + /* GCompletion list is case sensitive */ + ce1->string = g_utf8_strdown(name, -1); + ce2->string = g_utf8_strdown(address, -1); + + ce1->ref = ce2->ref = ae; + + g_completion_list = g_list_append(g_completion_list, ce1); + g_completion_list = g_list_append(g_completion_list, ce2); + g_address_list = g_list_append(g_address_list, ae); + + return 0; +} + +/* read_address_book() + */ +static void read_address_book(void) { + addressbook_load_completion( add_address ); +} + +/* start_address_completion() - returns the number of addresses + * that should be matched for completion. + */ +gint start_address_completion(void) +{ + clear_completion_cache(); + if (!g_ref_count) { + init_all(); + /* open the address book */ + read_address_book(); + /* merge the completion entry list into g_completion */ + if (g_completion_list) + g_completion_add_items(g_completion, g_completion_list); + } + g_ref_count++; + debug_print("start_address_completion ref count %d\n", g_ref_count); + + return g_list_length(g_completion_list); +} + +/* get_address_from_edit() - returns a possible address (or a part) + * from an entry box. To make life easier, we only look at the last valid address + * component; address completion only works at the last string component in + * the entry box. + */ +gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos) +{ + const gchar *edit_text, *p; + gint cur_pos; + gboolean in_quote = FALSE; + gboolean in_bracket = FALSE; + gchar *str; + + edit_text = gtk_entry_get_text(entry); + if (edit_text == NULL) return NULL; + + cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry)); + + /* scan for a separator. doesn't matter if walk points at null byte. */ + for (p = g_utf8_offset_to_pointer(edit_text, cur_pos); + p > edit_text; + p = g_utf8_prev_char(p)) { + if (*p == '"') + in_quote ^= TRUE; + else if (!in_quote) { + if (!in_bracket && *p == ',') + break; + else if (*p == '>') + in_bracket = TRUE; + else if (*p == '<') + in_bracket = FALSE; + } + } + + /* have something valid */ + if (g_utf8_strlen(p, -1) == 0) + return NULL; + +#define IS_VALID_CHAR(x) \ + (isalnum(x) || (x) == '"' || (x) == '<' || ((guchar)(x) > 0x7f)) + + /* now scan back until we hit a valid character */ + for (; *p && !IS_VALID_CHAR(*p); p = g_utf8_next_char(p)) + ; + +#undef IS_VALID_CHAR + + if (g_utf8_strlen(p, -1) == 0) + return NULL; + + if (start_pos) *start_pos = g_utf8_pointer_to_offset(edit_text, p); + + str = g_strdup(p); + + return str; +} + +/* replace_address_in_edit() - replaces an incompleted address with a completed one. + */ +void replace_address_in_edit(GtkEntry *entry, const gchar *newtext, + gint start_pos) +{ + if (!newtext) return; + + gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1); + gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext), + &start_pos); + gtk_editable_set_position(GTK_EDITABLE(entry), -1); +} + +/* complete_address() - tries to complete an addres, and returns the + * number of addresses found. use get_complete_address() to get one. + * returns zero if no match was found, otherwise the number of addresses, + * with the original prefix at index 0. + */ +guint complete_address(const gchar *str) +{ + GList *result; + gchar *d; + guint count, cpl; + completion_entry *ce; + + g_return_val_if_fail(str != NULL, 0); + + clear_completion_cache(); + g_completion_prefix = g_strdup(str); + + /* g_completion is case sensitive */ + d = g_utf8_strdown(str, -1); + result = g_completion_complete(g_completion, d, NULL); + + count = g_list_length(result); + if (count) { + /* create list with unique addresses */ + for (cpl = 0, result = g_list_first(result); + result != NULL; + result = g_list_next(result)) { + ce = (completion_entry *)(result->data); + if (NULL == g_slist_find(g_completion_addresses, + ce->ref)) { + cpl++; + g_completion_addresses = + g_slist_append(g_completion_addresses, + ce->ref); + } + } + count = cpl + 1; /* index 0 is the original prefix */ + g_completion_next = 1; /* we start at the first completed one */ + } else { + g_free(g_completion_prefix); + g_completion_prefix = NULL; + } + + g_completion_count = count; + + g_free(d); + + return count; +} + +/* get_complete_address() - returns a complete address. the returned + * string should be freed + */ +gchar *get_complete_address(gint index) +{ + const address_entry *p; + gchar *address = NULL; + + if (index < g_completion_count) { + if (index == 0) + address = g_strdup(g_completion_prefix); + else { + /* get something from the unique addresses */ + p = (address_entry *)g_slist_nth_data + (g_completion_addresses, index - 1); + if (p != NULL) { + if (!p->name || p->name[0] == '\0') + address = g_strdup_printf(p->address); + else if (p->name[0] != '"' && + strpbrk(p->name, ",.[]<>") != NULL) + address = g_strdup_printf + ("\"%s\" <%s>", p->name, p->address); + else + address = g_strdup_printf + ("%s <%s>", p->name, p->address); + } + } + } + + return address; +} + +gchar *get_next_complete_address(void) +{ + if (is_completion_pending()) { + gchar *res; + + res = get_complete_address(g_completion_next); + g_completion_next += 1; + if (g_completion_next >= g_completion_count) + g_completion_next = 0; + + return res; + } else + return NULL; +} + +gchar *get_prev_complete_address(void) +{ + if (is_completion_pending()) { + int n = g_completion_next - 2; + + /* real previous */ + n = (n + (g_completion_count * 5)) % g_completion_count; + + /* real next */ + g_completion_next = n + 1; + if (g_completion_next >= g_completion_count) + g_completion_next = 0; + return get_complete_address(n); + } else + return NULL; +} + +guint get_completion_count(void) +{ + if (is_completion_pending()) + return g_completion_count; + else + return 0; +} + +/* should clear up anything after complete_address() */ +void clear_completion_cache(void) +{ + if (is_completion_pending()) { + if (g_completion_prefix) + g_free(g_completion_prefix); + + if (g_completion_addresses) { + g_slist_free(g_completion_addresses); + g_completion_addresses = NULL; + } + + g_completion_count = g_completion_next = 0; + } +} + +gboolean is_completion_pending(void) +{ + /* check if completion pending, i.e. we might satisfy a request for the next + * or previous address */ + return g_completion_count; +} + +/* invalidate_address_completion() - should be called if address book + * changed; + */ +gint invalidate_address_completion(void) +{ + if (g_ref_count) { + /* simply the same as start_address_completion() */ + debug_print("Invalidation request for address completion\n"); + free_all(); + init_all(); + read_address_book(); + if (g_completion_list) + g_completion_add_items(g_completion, g_completion_list); + clear_completion_cache(); + } + + return g_list_length(g_completion_list); +} + +gint end_address_completion(void) +{ + clear_completion_cache(); + + if (0 == --g_ref_count) + free_all(); + + debug_print("end_address_completion ref count %d\n", g_ref_count); + + return g_ref_count; +} + + +/* address completion entry ui. the ui (completion list was inspired by galeon's + * auto completion list). remaining things powered by sylpheed's completion engine. + */ + +#define ENTRY_DATA_TAB_HOOK "tab_hook" /* used to lookup entry */ +#define WINDOW_DATA_COMPL_ENTRY "compl_entry" /* used to store entry for compl. window */ +#define WINDOW_DATA_COMPL_CLIST "compl_clist" /* used to store clist for compl. window */ + +static void address_completion_mainwindow_set_focus (GtkWindow *window, + GtkWidget *widget, + gpointer data); +static gboolean address_completion_entry_key_pressed (GtkEntry *entry, + GdkEventKey *ev, + gpointer data); +static gboolean address_completion_complete_address_in_entry + (GtkEntry *entry, + gboolean next); +static void address_completion_create_completion_window (GtkEntry *entry); + +static void completion_window_select_row(GtkCList *clist, + gint row, + gint col, + GdkEvent *event, + GtkWidget **completion_window); +static gboolean completion_window_button_press + (GtkWidget *widget, + GdkEventButton *event, + GtkWidget **completion_window); +static gboolean completion_window_key_press + (GtkWidget *widget, + GdkEventKey *event, + GtkWidget **completion_window); + + +static void completion_window_advance_to_row(GtkCList *clist, gint row) +{ + g_return_if_fail(row < g_completion_count); + gtk_clist_select_row(clist, row, 0); +} + +static void completion_window_advance_selection(GtkCList *clist, gboolean forward) +{ + int row; + + g_return_if_fail(clist != NULL); + g_return_if_fail(clist->selection != NULL); + + row = GPOINTER_TO_INT(clist->selection->data); + + row = forward ? (row + 1) % g_completion_count : + (row - 1) < 0 ? g_completion_count - 1 : row - 1; + + gtk_clist_freeze(clist); + completion_window_advance_to_row(clist, row); + gtk_clist_thaw(clist); +} + +#if 0 +/* completion_window_accept_selection() - accepts the current selection in the + * clist, and destroys the window */ +static void completion_window_accept_selection(GtkWidget **window, + GtkCList *clist, + GtkEntry *entry) +{ + gchar *address = NULL, *text = NULL; + gint cursor_pos, row; + + g_return_if_fail(window != NULL); + g_return_if_fail(*window != NULL); + g_return_if_fail(clist != NULL); + g_return_if_fail(entry != NULL); + g_return_if_fail(clist->selection != NULL); + + /* FIXME: I believe it's acceptable to access the selection member directly */ + row = GPOINTER_TO_INT(clist->selection->data); + + /* we just need the cursor position */ + address = get_address_from_edit(entry, &cursor_pos); + g_free(address); + gtk_clist_get_text(clist, row, 0, &text); + replace_address_in_edit(entry, text, cursor_pos); + + clear_completion_cache(); + gtk_widget_destroy(*window); + *window = NULL; +} +#endif + +/* completion_window_apply_selection() - apply the current selection in the + * clist */ +static void completion_window_apply_selection(GtkCList *clist, GtkEntry *entry) +{ + gchar *address = NULL, *text = NULL; + gint cursor_pos, row; + + g_return_if_fail(clist != NULL); + g_return_if_fail(entry != NULL); + g_return_if_fail(clist->selection != NULL); + + row = GPOINTER_TO_INT(clist->selection->data); + + address = get_address_from_edit(entry, &cursor_pos); + g_free(address); + gtk_clist_get_text(clist, row, 0, &text); + replace_address_in_edit(entry, text, cursor_pos); +} + +/* should be called when creating the main window containing address + * completion entries */ +void address_completion_start(GtkWidget *mainwindow) +{ + start_address_completion(); + + /* register focus change hook */ + g_signal_connect(G_OBJECT(mainwindow), "set_focus", + G_CALLBACK(address_completion_mainwindow_set_focus), + mainwindow); +} + +/* Need unique data to make unregistering signal handler possible for the auto + * completed entry */ +#define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa)) + +void address_completion_register_entry(GtkEntry *entry) +{ + g_return_if_fail(entry != NULL); + g_return_if_fail(GTK_IS_ENTRY(entry)); + + /* add hooked property */ + g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry); + + /* add keypress event */ + g_signal_connect_closure + (G_OBJECT(entry), "key_press_event", + g_cclosure_new + (G_CALLBACK(address_completion_entry_key_pressed), + COMPLETION_UNIQUE_DATA, NULL), + FALSE); +} + +void address_completion_unregister_entry(GtkEntry *entry) +{ + GObject *entry_obj; + + g_return_if_fail(entry != NULL); + g_return_if_fail(GTK_IS_ENTRY(entry)); + + entry_obj = g_object_get_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK); + g_return_if_fail(entry_obj); + g_return_if_fail(entry_obj == G_OBJECT(entry)); + + /* has the hooked property? */ + g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL); + + /* remove the hook */ + g_signal_handlers_disconnect_by_func + (G_OBJECT(entry), + G_CALLBACK(address_completion_entry_key_pressed), + COMPLETION_UNIQUE_DATA); +} + +/* should be called when main window with address completion entries + * terminates. + * NOTE: this function assumes that it is called upon destruction of + * the window */ +void address_completion_end(GtkWidget *mainwindow) +{ + /* if address_completion_end() is really called on closing the window, + * we don't need to unregister the set_focus_cb */ + end_address_completion(); +} + +/* if focus changes to another entry, then clear completion cache */ +static void address_completion_mainwindow_set_focus(GtkWindow *window, + GtkWidget *widget, + gpointer data) +{ + if (widget && GTK_IS_ENTRY(widget) && + g_object_get_data(G_OBJECT(widget), ENTRY_DATA_TAB_HOOK)) + clear_completion_cache(); +} + +/* watch for tabs in one of the address entries. if no tab then clear the + * completion cache */ +static gboolean address_completion_entry_key_pressed(GtkEntry *entry, + GdkEventKey *ev, + gpointer data) +{ + if (ev->keyval == GDK_Tab) { + if (address_completion_complete_address_in_entry(entry, TRUE)) { + address_completion_create_completion_window(entry); + /* route a void character to the default handler */ + /* this is a dirty hack; we're actually changing a key + * reported by the system. */ + ev->keyval = GDK_AudibleBell_Enable; + ev->state &= ~GDK_SHIFT_MASK; + //gtk_signal_emit_stop_by_name(G_OBJECT(entry), + // "key_press_event"); + return TRUE; // + } else { + /* old behaviour */ + } + } else if (ev->keyval == GDK_Shift_L + || ev->keyval == GDK_Shift_R + || ev->keyval == GDK_Control_L + || ev->keyval == GDK_Control_R + || ev->keyval == GDK_Caps_Lock + || ev->keyval == GDK_Shift_Lock + || ev->keyval == GDK_Meta_L + || ev->keyval == GDK_Meta_R + || ev->keyval == GDK_Alt_L + || ev->keyval == GDK_Alt_R) { + /* these buttons should not clear the cache... */ + } else + clear_completion_cache(); + + return FALSE; +} + +/* initialize the completion cache and put first completed string + * in entry. this function used to do back cycling but this is not + * currently used. since the address completion behaviour has been + * changed regularly, we keep the feature in case someone changes + * his / her mind again. :) */ +static gboolean address_completion_complete_address_in_entry(GtkEntry *entry, + gboolean next) +{ + gint ncount, cursor_pos; + gchar *address, *new = NULL; + gboolean completed = FALSE; + + g_return_val_if_fail(entry != NULL, FALSE); + + if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE; + + /* get an address component from the cursor */ + address = get_address_from_edit(entry, &cursor_pos); + if (!address) return FALSE; + + /* still something in the cache */ + if (is_completion_pending()) { + new = next ? get_next_complete_address() : + get_prev_complete_address(); + } else { + if (0 < (ncount = complete_address(address))) + new = get_next_complete_address(); + } + + if (new) { + /* prevent "change" signal */ + /* replace_address_in_edit(entry, new, cursor_pos); */ + g_free(new); + completed = TRUE; + } + + g_free(address); + + return completed; +} + +static void address_completion_create_completion_window(GtkEntry *entry_) +{ + static GtkWidget *completion_window; + gint x, y, height, width, depth; + GtkWidget *scroll, *clist; + GtkRequisition r; + guint count = 0; + GtkWidget *entry = GTK_WIDGET(entry_); + + if (completion_window) { + gtk_widget_destroy(completion_window); + completion_window = NULL; + } + + scroll = gtk_scrolled_window_new(NULL, NULL); + clist = gtk_clist_new(1); + gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE); + + completion_window = gtk_window_new(GTK_WINDOW_POPUP); + + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(completion_window), scroll); + gtk_container_add(GTK_CONTAINER(scroll), clist); + + /* set the unique data so we can always get back the entry and + * clist window to which this completion window has been attached */ + g_object_set_data(G_OBJECT(completion_window), + WINDOW_DATA_COMPL_ENTRY, entry_); + g_object_set_data(G_OBJECT(completion_window), + WINDOW_DATA_COMPL_CLIST, clist); + + g_signal_connect(G_OBJECT(clist), "select_row", + G_CALLBACK(completion_window_select_row), + &completion_window); + + for (count = 0; count < get_completion_count(); count++) { + gchar *text[] = {NULL, NULL}; + + text[0] = get_complete_address(count); + gtk_clist_append(GTK_CLIST(clist), text); + g_free(text[0]); + } + + gdk_window_get_geometry(entry->window, &x, &y, &width, &height, &depth); + gdk_window_get_deskrelative_origin (entry->window, &x, &y); + y += height; + gtk_window_move(GTK_WINDOW(completion_window), x, y); + + gtk_widget_size_request(clist, &r); + gtk_widget_set_size_request(completion_window, width, r.height); + gtk_widget_show_all(completion_window); + gtk_widget_size_request(clist, &r); + + if ((y + r.height) > gdk_screen_height()) { + gtk_window_set_policy(GTK_WINDOW(completion_window), + TRUE, FALSE, FALSE); + gtk_widget_set_size_request(completion_window, width, + gdk_screen_height () - y); + } + + g_signal_connect(G_OBJECT(completion_window), + "button-press-event", + G_CALLBACK(completion_window_button_press), + &completion_window); + g_signal_connect(G_OBJECT(completion_window), + "key-press-event", + G_CALLBACK(completion_window_key_press), + &completion_window); + gdk_pointer_grab(completion_window->window, TRUE, + GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK, + NULL, NULL, GDK_CURRENT_TIME); + gtk_grab_add(completion_window); + + /* this gets rid of the irritating focus rectangle that doesn't + * follow the selection */ + GTK_WIDGET_UNSET_FLAGS(clist, GTK_CAN_FOCUS); + gtk_clist_select_row(GTK_CLIST(clist), 1, 0); +} + + +/* row selection sends completed address to entry. + * note: event is NULL if selected by anything else than a mouse button. */ +static void completion_window_select_row(GtkCList *clist, gint row, gint col, + GdkEvent *event, + GtkWidget **completion_window) +{ + GtkEntry *entry; + + g_return_if_fail(completion_window != NULL); + g_return_if_fail(*completion_window != NULL); + + entry = GTK_ENTRY(g_object_get_data(G_OBJECT(*completion_window), + WINDOW_DATA_COMPL_ENTRY)); + g_return_if_fail(entry != NULL); + + completion_window_apply_selection(clist, entry); + + if (!event || event->type != GDK_BUTTON_RELEASE) + return; + + clear_completion_cache(); + gtk_widget_destroy(*completion_window); + *completion_window = NULL; +} + +/* completion_window_button_press() - check is mouse click is anywhere + * else (not in the completion window). in that case the completion + * window is destroyed, and the original prefix is restored */ +static gboolean completion_window_button_press(GtkWidget *widget, + GdkEventButton *event, + GtkWidget **completion_window) +{ + GtkWidget *event_widget, *entry; + gchar *prefix; + gint cursor_pos; + gboolean restore = TRUE; + + g_return_val_if_fail(completion_window != NULL, FALSE); + g_return_val_if_fail(*completion_window != NULL, FALSE); + + entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*completion_window), + WINDOW_DATA_COMPL_ENTRY)); + g_return_val_if_fail(entry != NULL, FALSE); + + event_widget = gtk_get_event_widget((GdkEvent *)event); + if (event_widget != widget) { + while (event_widget) { + if (event_widget == widget) + return FALSE; + else if (event_widget == entry) { + restore = FALSE; + break; + } + event_widget = event_widget->parent; + } + } + + if (restore) { + prefix = get_complete_address(0); + g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos)); + replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos); + g_free(prefix); + } + + clear_completion_cache(); + gtk_widget_destroy(*completion_window); + *completion_window = NULL; + + return TRUE; +} + +static gboolean completion_window_key_press(GtkWidget *widget, + GdkEventKey *event, + GtkWidget **completion_window) +{ + GdkEventKey tmp_event; + GtkWidget *entry; + gchar *prefix; + gint cursor_pos; + GtkWidget *clist; + + g_return_val_if_fail(completion_window != NULL, FALSE); + g_return_val_if_fail(*completion_window != NULL, FALSE); + + entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*completion_window), + WINDOW_DATA_COMPL_ENTRY)); + clist = GTK_WIDGET(g_object_get_data(G_OBJECT(*completion_window), + WINDOW_DATA_COMPL_CLIST)); + g_return_val_if_fail(entry != NULL, FALSE); + + /* allow keyboard navigation in the alternatives clist */ + if (event->keyval == GDK_Up || event->keyval == GDK_Down || + event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down) { + completion_window_advance_selection + (GTK_CLIST(clist), + event->keyval == GDK_Down || + event->keyval == GDK_Page_Down ? TRUE : FALSE); + return FALSE; + } + + /* also make tab / shift tab go to next previous completion entry. we're + * changing the key value */ + if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) { + event->keyval = (event->state & GDK_SHIFT_MASK) + ? GDK_Up : GDK_Down; + /* need to reset shift state if going up */ + if (event->state & GDK_SHIFT_MASK) + event->state &= ~GDK_SHIFT_MASK; + completion_window_advance_selection(GTK_CLIST(clist), + event->keyval == GDK_Down ? TRUE : FALSE); + return FALSE; + } + + /* look for presses that accept the selection */ + if (event->keyval == GDK_Return || event->keyval == GDK_space) { + clear_completion_cache(); + gtk_widget_destroy(*completion_window); + *completion_window = NULL; + return FALSE; + } + + /* key state keys should never be handled */ + if (event->keyval == GDK_Shift_L + || event->keyval == GDK_Shift_R + || event->keyval == GDK_Control_L + || event->keyval == GDK_Control_R + || event->keyval == GDK_Caps_Lock + || event->keyval == GDK_Shift_Lock + || event->keyval == GDK_Meta_L + || event->keyval == GDK_Meta_R + || event->keyval == GDK_Alt_L + || event->keyval == GDK_Alt_R) { + return FALSE; + } + + /* other key, let's restore the prefix (orignal text) */ + prefix = get_complete_address(0); + g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos)); + replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos); + g_free(prefix); + clear_completion_cache(); + + /* make sure anything we typed comes in the edit box */ + tmp_event.type = event->type; + tmp_event.window = entry->window; + tmp_event.send_event = TRUE; + tmp_event.time = event->time; + tmp_event.state = event->state; + tmp_event.keyval = event->keyval; + tmp_event.length = event->length; + tmp_event.string = event->string; + gtk_widget_event(entry, (GdkEvent *)&tmp_event); + + /* and close the completion window */ + gtk_widget_destroy(*completion_window); + *completion_window = NULL; + + return TRUE; +} diff --git a/src/addr_compl.h b/src/addr_compl.h new file mode 100644 index 00000000..7eca78ae --- /dev/null +++ b/src/addr_compl.h @@ -0,0 +1,54 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * + * Copyright (C) 2000-2004 by Alfons Hoogervorst & The Sylpheed Claws Team. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ADDR_COMPL_H__ +#define __ADDR_COMPL_H__ + +gint start_address_completion (void); +gint invalidate_address_completion (void); + +guint complete_address (const gchar *str); + +gchar *get_address_from_edit (GtkEntry *entry, + gint *start_pos); +void replace_address_in_edit (GtkEntry *entry, + const gchar *newtext, + gint start_pos); + +gchar *get_complete_address (gint index); + +gchar *get_next_complete_address (void); +gchar *get_prev_complete_address (void); +guint get_completion_count (void); + +gboolean is_completion_pending (void); + +void clear_completion_cache (void); + +gint end_address_completion (void); + +/* ui functions */ + +void address_completion_start (GtkWidget *mainwindow); +void address_completion_register_entry (GtkEntry *entry); +void address_completion_unregister_entry (GtkEntry *entry); +void address_completion_end (GtkWidget *mainwindow); + +#endif /* __ADDR_COMPL_H__ */ diff --git a/src/addrbook.c b/src/addrbook.c new file mode 100644 index 00000000..32c0e777 --- /dev/null +++ b/src/addrbook.c @@ -0,0 +1,2010 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * General functions for accessing external address book files. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xml.h" +#include "mgutils.h" +#include "addritem.h" +#include "addrcache.h" +#include "addrbook.h" + +#ifndef DEV_STANDALONE +#include "prefs.h" +#include "codeconv.h" +#endif + +#define ADDRBOOK_MAX_SEARCH_COUNT 1000 +#define ADDRBOOK_PREFIX "addrbook-" +#define ADDRBOOK_SUFFIX ".xml" +#define FILE_NUMDIGITS 6 + +#define ID_TIME_OFFSET 998000000 +/* +* Create new address book. +*/ +AddressBookFile *addrbook_create_book() { + AddressBookFile *book; + + book = g_new0( AddressBookFile, 1 ); + book->name = NULL; + book->path = NULL; + book->fileName = NULL; + book->retVal = MGU_SUCCESS; + book->addressCache = addrcache_create(); + + book->tempList = NULL; + book->readFlag = FALSE; + book->dirtyFlag = FALSE; + book->modifyFlag = TRUE; + book->accessFlag = FALSE; + book->tempHash = NULL; + return book; +} + +/* +* Specify name to be used. +*/ +void addrbook_set_name( AddressBookFile *book, const gchar *value ) { + g_return_if_fail( book != NULL ); + book->name = mgu_replace_string( book->name, value ); +} +void addrbook_set_path( AddressBookFile *book, const gchar *value ) { + g_return_if_fail( book != NULL ); + book->path = mgu_replace_string( book->path, value ); + book->dirtyFlag = TRUE; +} +void addrbook_set_file( AddressBookFile *book, const gchar *value ) { + g_return_if_fail( book != NULL ); + book->fileName = mgu_replace_string( book->fileName, value ); + book->dirtyFlag = TRUE; +} +void addrbook_set_accessed( AddressBookFile *book, const gboolean value ) { + g_return_if_fail( book != NULL ); + book->accessFlag = value; +} +gboolean addrbook_get_modified( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, FALSE ); + return book->modifyFlag; +} +gboolean addrbook_get_accessed( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, FALSE ); + return book->accessFlag; +} +gboolean addrbook_get_read_flag( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, FALSE ); + return book->readFlag; +} +gint addrbook_get_status( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, -1 ); + return book->retVal; +} +ItemFolder *addrbook_get_root_folder( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, NULL ); + return addrcache_get_root_folder( book->addressCache ); +} +GList *addrbook_get_list_folder( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, NULL ); + return addrcache_get_list_folder( book->addressCache ); +} +GList *addrbook_get_list_person( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, NULL ); + return addrcache_get_list_person( book->addressCache ); +} +gchar *addrbook_get_name( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, NULL ); + return book->name; +} + +/* +* Empty address book. +*/ +void addrbook_empty_book( AddressBookFile *book ) { + g_return_if_fail( book != NULL ); + + /* Free up folders and hash table */ + addrcache_clear( book->addressCache ); + + g_list_free( book->tempList ); + book->tempList = NULL; + + /* Reset to initial state */ + book->retVal = MGU_SUCCESS; + book->tempHash = NULL; + book->readFlag = FALSE; + book->dirtyFlag = FALSE; + book->modifyFlag = FALSE; + book->accessFlag = FALSE; +} + +/* +* Free address book. +*/ +void addrbook_free_book( AddressBookFile *book ) { + g_return_if_fail( book != NULL ); + + g_free( book->name ); + g_free( book->path ); + g_free( book->fileName ); + book->name = NULL; + book->path = NULL; + book->fileName = NULL; + + /* Free up folders and hash table */ + addrcache_free( book->addressCache ); + book->addressCache = NULL; + + g_list_free( book->tempList ); + book->tempList = NULL; + + book->retVal = MGU_SUCCESS; + book->tempHash = NULL; + book->readFlag = FALSE; + book->dirtyFlag = FALSE; + book->modifyFlag = FALSE; + book->accessFlag = FALSE; + + g_free( book ); +} + +/* +* Print list of items. +*/ +void addrbook_print_item_list( GList *list, FILE *stream ) { + GList *node = list; + + while( node ) { + AddrItemObject *obj = node->data; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + addritem_print_item_person( ( ItemPerson * ) obj, stream ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + addritem_print_item_group( ( ItemGroup * ) obj, stream ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER ) { + addritem_print_item_folder( ( ItemFolder * ) obj, stream ); + } + node = g_list_next( node ); + } + fprintf( stream, "\t---\n" ); +} + +/* +* Print address book. +*/ +void addrbook_print_book( AddressBookFile *book, FILE *stream ) { + g_return_if_fail( book != NULL ); + + fprintf( stream, "AddressBook:\n" ); + fprintf( stream, "\tname : '%s'\n", book->name ); + fprintf( stream, "\tpath : '%s'\n", book->path ); + fprintf( stream, "\tfile : '%s'\n", book->fileName ); + fprintf( stream, "\tstatus: %d : '%s'\n", book->retVal, mgu_error2string( book->retVal ) ); + addrcache_print( book->addressCache, stream ); +} + +/* +* Dump entire address book traversing folders. +*/ +void addrbook_dump_book( AddressBookFile *book, FILE *stream ) { + ItemFolder *folder; + + g_return_if_fail( book != NULL ); + + addrbook_print_book( book, stream ); + folder = book->addressCache->rootFolder; + addritem_print_item_folder( folder, stream ); +} + +/* +* Remove group from address book. +* param: group Group to remove. +* return: Group, or NULL if not found. Note that object should still be freed. +*/ +ItemGroup *addrbook_remove_group( AddressBookFile *book, ItemGroup *group ) { + ItemGroup *item; + + g_return_val_if_fail( book != NULL, NULL ); + + item = addrcache_remove_group( book->addressCache, group ); + if( item ) book->dirtyFlag = TRUE; + return item; +} + +/* +* Remove specified person from address book. +* param: person Person to remove. +* return: Person, or NULL if not found. Note that object should still be freed. +*/ +ItemPerson *addrbook_remove_person( AddressBookFile *book, ItemPerson *person ) { + ItemPerson *item; + + g_return_val_if_fail( book != NULL, NULL ); + + item = addrcache_remove_person( book->addressCache, person ); + if( item ) book->dirtyFlag = TRUE; + return item; +} + +/* +* Remove email address in address book for specified person. +* param: person Person. +* email EMail to remove. +* return: EMail object, or NULL if not found. Note that object should still be freed. +*/ +ItemEMail *addrbook_person_remove_email( AddressBookFile *book, ItemPerson *person, ItemEMail *email ) { + ItemEMail *item; + + g_return_val_if_fail( book != NULL, NULL ); + + item = addrcache_person_remove_email( book->addressCache, person, email ); + if( item ); book->dirtyFlag = TRUE; + return item; +} + +/* ********************************************************************** +* Read/Write XML data file... +* =========================== +* Notes: +* 1) The address book is structured as follows: +* +* address-book +* person +* address-list +* address +* attribute-list +* attribute +* group +* member-list +* member +* folder +* item-list +* item +* +* 2) This sequence of elements was chosen so that the most important +* elements (person and their email addresses) appear first. +* +* 3) Groups then appear. When groups are loaded, person's email +* addresses have already been loaded and can be found. +* +* 4) Finally folders are loaded. Any forward and backward references +* to folders, groups and persons in the folders are resolved after +* loading. +* +* *********************************************************************** +*/ + +/* Element tag names */ +#define AB_ELTAG_ADDRESS "address" +#define AB_ELTAG_ATTRIBUTE "attribute" +#define AB_ELTAG_ATTRIBUTE_LIST "attribute-list" +#define AB_ELTAG_ADDRESS_LIST "address-list" +#define AB_ELTAG_MEMBER "member" +#define AB_ELTAG_MEMBER_LIST "member-list" +#define AB_ELTAG_ITEM "item" +#define AB_ELTAG_ITEM_LIST "item-list" +#define AB_ELTAG_ADDRESS_BOOK "address-book" +#define AB_ELTAG_PERSON "person" +#define AB_ELTAG_GROUP "group" +#define AB_ELTAG_FOLDER "folder" + +/* Attribute tag names */ +#define AB_ATTAG_TYPE "type" +#define AB_ATTAG_UID "uid" +#define AB_ATTAG_NAME "name" +#define AB_ATTAG_REMARKS "remarks" +#define AB_ATTAG_FIRST_NAME "first-name" +#define AB_ATTAG_LAST_NAME "last-name" +#define AB_ATTAG_NICK_NAME "nick-name" +#define AB_ATTAG_COMMON_NAME "cn" +#define AB_ATTAG_ALIAS "alias" +#define AB_ATTAG_EMAIL "email" +#define AB_ATTAG_EID "eid" +#define AB_ATTAG_PID "pid" + +/* Attribute values */ +#define AB_ATTAG_VAL_PERSON "person" +#define AB_ATTAG_VAL_GROUP "group" +#define AB_ATTAG_VAL_FOLDER "folder" + +/* +* Parse address item for person. +*/ +static void addrbook_parse_address( AddressBookFile *book, XMLFile *file, ItemPerson *person ) { + GList *attr; + gchar *name, *value; + ItemEMail *email = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( ! email ) email = addritem_create_item_email(); + if( strcmp( name, AB_ATTAG_UID ) == 0 ) { + ADDRITEM_ID(email) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_ALIAS ) == 0 ) { + ADDRITEM_NAME(email) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_EMAIL ) == 0 ) { + email->address = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_REMARKS ) == 0 ) { + email->remarks = g_strdup( value ); + } + attr = g_list_next( attr ); + } + if( email ) { + if( person ) { + addrcache_person_add_email( book->addressCache, person, email ); + } + else { + addritem_free_item_email( email ); + email = NULL; + } + } +} + +/* +* Parse email address list. +*/ +static void addrbook_parse_addr_list( AddressBookFile *book, XMLFile *file, ItemPerson *person ){ + GList *attr; + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_ADDRESS ) ) { + attr = xml_get_current_tag_attr(file); + addrbook_parse_address( book, file, person ); + addrbook_parse_addr_list( book, file, person ); + } + } +} + +/* +* Parse attibute for person. +*/ +static void addrbook_parse_attribute( XMLFile *file, ItemPerson *person ) { + GList *attr; + gchar *name, *value; + gchar *element; + UserAttribute *uAttr = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( ! uAttr ) uAttr = addritem_create_attribute(); + if( strcmp( name, AB_ATTAG_UID ) == 0 ) { + addritem_attrib_set_id( uAttr, value ); + } + else if( strcmp( name, AB_ATTAG_NAME ) == 0 ) { + addritem_attrib_set_name( uAttr, value ); + } + attr = g_list_next( attr ); + } + + element = xml_get_element( file ); + addritem_attrib_set_value( uAttr, element ); + + if( uAttr ) { + if( person ) { + addritem_person_add_attribute( person, uAttr ); + } + else { + addritem_free_attribute( uAttr ); + uAttr = NULL; + } + } +} + +/* +* Parse attribute list. +*/ +static void addrbook_parse_attr_list( AddressBookFile *book, XMLFile *file, ItemPerson *person ){ + GList *attr; + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_ATTRIBUTE ) ) { + attr = xml_get_current_tag_attr(file); + addrbook_parse_attribute( file, person ); + addrbook_parse_attr_list( book, file, person ); + } + } +} + +/* +* Parse person. +*/ +static void addrbook_parse_person( AddressBookFile *book, XMLFile *file ) { + GList *attr; + gchar *name, *value; + ItemPerson *person = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( ! person ) person = addritem_create_item_person(); + if( strcmp( name, AB_ATTAG_UID ) == 0 ) { + ADDRITEM_ID(person) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_FIRST_NAME ) == 0 ) { + person->firstName = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_LAST_NAME ) == 0 ) { + person->lastName = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_NICK_NAME ) == 0 ) { + person->nickName = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_COMMON_NAME ) == 0 ) { + ADDRITEM_NAME(person) = g_strdup( value ); + } + attr = g_list_next( attr ); + } + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_ADDRESS_LIST ) ) { + addrbook_parse_addr_list( book, file, person ); + if( person ) { + addrcache_hash_add_person( book->addressCache, person ); + } + } + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_ATTRIBUTE_LIST ) ) { + addrbook_parse_attr_list( book, file, person ); + } +} + +/* +* Parse group member. +*/ +static void addrbook_parse_member( AddressBookFile *book, XMLFile *file, ItemGroup *group ) { + GList *attr; + gchar *name, *value; + gchar *pid = NULL, *eid = NULL; + ItemEMail *email = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( strcmp( name, AB_ATTAG_PID ) == 0 ) { + pid = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_EID ) == 0 ) { + eid = g_strdup( value ); + } + attr = g_list_next( attr ); + } + email = addrcache_get_email( book->addressCache, pid, eid ); + if( email ) { + if( group ) { + addrcache_group_add_email( book->addressCache, group, email ); + } + else { + addritem_free_item_email( email ); + email = NULL; + } + } +} + +/* +* Parse group member list. +*/ +static void addrbook_parse_member_list( AddressBookFile *book, XMLFile *file, ItemGroup *group ){ + GList *attr; + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_MEMBER ) ) { + attr = xml_get_current_tag_attr(file); + addrbook_parse_member( book, file, group ); + addrbook_parse_member_list( book, file, group ); + } + else { + attr = xml_get_current_tag_attr( file ); + } + } +} + +/* +* Parse group. +*/ +static void addrbook_parse_group( AddressBookFile *book, XMLFile *file ) { + GList *attr; + gchar *name, *value; + ItemGroup *group = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( ! group ) group = addritem_create_item_group(); + if( strcmp( name, AB_ATTAG_UID ) == 0 ) { + ADDRITEM_ID(group) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_NAME ) == 0 ) { + ADDRITEM_NAME(group) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_REMARKS ) == 0 ) { + group->remarks = g_strdup( value ); + } + attr = g_list_next( attr ); + } + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_MEMBER_LIST ) ) { + if( group ) { + addrcache_hash_add_group( book->addressCache, group ); + } + addrbook_parse_member_list( book, file, group ); + } +} + +/* +* Parse folder item. +*/ +static void addrbook_parse_folder_item( AddressBookFile *book, XMLFile *file, ItemFolder *folder ) { + GList *attr; + gchar *name, *value; + gchar *uid = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( strcmp( name, AB_ATTAG_UID ) == 0 ) { + uid = g_strdup( value ); + } + attr = g_list_next( attr ); + } + if( folder ) { + if( uid ) { + folder->listItems = g_list_append( folder->listItems, uid ); + } + } +} + +/* +* Parse folder item list. +*/ +static void addrbook_parse_folder_list( AddressBookFile *book, XMLFile *file, ItemFolder *folder ){ + GList *attr; + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_ITEM ) ) { + attr = xml_get_current_tag_attr(file); + addrbook_parse_folder_item( book, file, folder ); + addrbook_parse_folder_list( book, file, folder ); + } + else { + attr = xml_get_current_tag_attr( file ); + } + } +} + +/* +* Parse folder. +*/ +static void addrbook_parse_folder( AddressBookFile *book, XMLFile *file ) { + GList *attr; + gchar *name, *value; + ItemFolder *folder = NULL; + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( ! folder ) { + folder = addritem_create_item_folder(); + } + if( strcmp( name, AB_ATTAG_UID ) == 0 ) { + ADDRITEM_ID(folder) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_NAME ) == 0 ) { + ADDRITEM_NAME(folder) = g_strdup( value ); + } + else if( strcmp( name, AB_ATTAG_REMARKS ) == 0 ) { + folder->remarks = g_strdup( value ); + } + attr = g_list_next( attr ); + } + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_ITEM_LIST ) ) { + if( folder ) { + if( addrcache_hash_add_folder( book->addressCache, folder ) ) { + book->tempList = g_list_append( book->tempList, folder ); + ADDRITEM_PARENT(folder) = NULL; /* We will resolve folder later */ + } + } + addrbook_parse_folder_list( book, file, folder ); + } +} + +/* +* Parse address book. +* Return: TRUE if data read successfully, FALSE if error reading data. +*/ +static gboolean addrbook_read_tree( AddressBookFile *book, XMLFile *file ) { + gboolean retVal; + GList *attr; + gchar *name, *value; + + book->retVal = MGU_BAD_FORMAT; + if( xml_get_dtd( file ) ) { + return FALSE; + } + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if( ! xml_compare_tag( file, AB_ELTAG_ADDRESS_BOOK ) ) { + return FALSE; + } + + attr = xml_get_current_tag_attr(file); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + if( strcmp( name, AB_ATTAG_NAME ) == 0 ) { + addrbook_set_name( book, value ); + } + attr = g_list_next( attr ); + } + + retVal = TRUE; + for (;;) { + if (! file->level ) break; + /* Get next item tag (person, group or folder) */ + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_PERSON ) ) { + addrbook_parse_person( book, file ); + } + else if( xml_compare_tag( file, AB_ELTAG_GROUP ) ) { + addrbook_parse_group( book, file ); + } + else if( xml_compare_tag( file, AB_ELTAG_FOLDER ) ) { + addrbook_parse_folder( book, file ); + } + } + if( retVal ) book->retVal = MGU_SUCCESS; + return retVal; +} + +/* +* Resolve folder items visitor function. +*/ +static void addrbook_res_items_vis( gpointer key, gpointer value, gpointer data ) { + AddressBookFile *book = data; + AddrItemObject *obj = ( AddrItemObject * ) value; + ItemFolder *rootFolder = book->addressCache->rootFolder; + if( obj->parent == NULL ) { + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + rootFolder->listPerson = g_list_append( rootFolder->listPerson, obj ); + ADDRITEM_PARENT(obj) = ADDRITEM_OBJECT(rootFolder); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + rootFolder->listGroup = g_list_append( rootFolder->listGroup, obj ); + ADDRITEM_PARENT(obj) = ADDRITEM_OBJECT(rootFolder); + } + } +} + +/* +* Resolve folder items. Lists of UID's are replaced with pointers to data items. +*/ +static void addrbook_resolve_folder_items( AddressBookFile *book ) { + GList *nodeFolder = NULL; + GList *listRemove = NULL; + GList *node = NULL; + ItemFolder *rootFolder = book->addressCache->rootFolder; + nodeFolder = book->tempList; + while( nodeFolder ) { + ItemFolder *folder = nodeFolder->data; + listRemove = NULL; + node = folder->listItems; + while( node ) { + gchar *uid = node->data; + AddrItemObject *aio = addrcache_get_object( book->addressCache, uid ); + if( aio ) { + if( aio->type == ITEMTYPE_FOLDER ) { + ItemFolder *item = ( ItemFolder * ) aio; + folder->listFolder = g_list_append( folder->listFolder, item ); + ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder); + addrcache_hash_add_folder( book->addressCache, folder ); + } + else if( aio->type == ITEMTYPE_PERSON ) { + ItemPerson *item = ( ItemPerson * ) aio; + folder->listPerson = g_list_append( folder->listPerson, item ); + ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder); + } + else if( aio->type == ITEMTYPE_GROUP ) { + ItemGroup *item = ( ItemGroup * ) aio; + folder->listGroup = g_list_append( folder->listGroup, item ); + ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder); + } + /* Replace data with pointer to item */ + g_free( uid ); + node->data = aio; + } + else { + /* Not found, append to remove list. */ + listRemove = g_list_append( listRemove, uid ); + } + node = g_list_next( node ); + } + rootFolder->listFolder = g_list_append( rootFolder->listFolder, folder ); + + /* Process remove list */ + node = listRemove; + while( node ) { + gchar *uid = node->data; + folder->listItems = g_list_remove( folder->listItems, uid ); + g_free( uid ); + node = g_list_next( node ); + } + g_list_free( listRemove ); + nodeFolder = g_list_next( nodeFolder ); + } + + /* Remove folders with parents. */ + listRemove = NULL; + node = rootFolder->listFolder; + while( node ) { + ItemFolder *folder = ( ItemFolder * ) node->data; + if( ADDRITEM_PARENT(folder) ) { + /* Remove folders with parents */ + listRemove = g_list_append( listRemove, folder ); + } + else { + /* Add to root folder */ + ADDRITEM_PARENT(folder) = ADDRITEM_OBJECT(book->addressCache->rootFolder); + } + node = g_list_next( node ); + } + + /* Process remove list */ + node = listRemove; + while( node ) { + rootFolder->listFolder = g_list_remove( rootFolder->listFolder, node->data ); + node = g_list_next( node ); + } + g_list_free( listRemove ); + + /* Move all unparented persons and groups into root folder */ + g_hash_table_foreach( book->addressCache->itemHash, addrbook_res_items_vis, book ); + + /* Free up some more */ + nodeFolder = book->tempList; + while( nodeFolder ) { + ItemFolder *folder = nodeFolder->data; + g_list_free( folder->listItems ); + folder->listItems = NULL; + nodeFolder = g_list_next( nodeFolder ); + } + g_list_free( book->tempList ); + book->tempList = NULL; + +} + +/* +* Read address book file. +*/ +gint addrbook_read_data( AddressBookFile *book ) { + XMLFile *file = NULL; + gchar *fileSpec = NULL; + + g_return_val_if_fail( book != NULL, -1 ); + + fileSpec = g_strconcat( book->path, G_DIR_SEPARATOR_S, book->fileName, NULL ); + book->retVal = MGU_OPEN_FILE; + book->accessFlag = FALSE; + book->modifyFlag = FALSE; + file = xml_open_file( fileSpec ); + g_free( fileSpec ); + if( file ) { + book->tempList = NULL; + + /* Trap for parsing errors. */ + if( setjmp( book->jumper ) ) { + xml_close_file( file ); + return book->retVal; + } + addrbook_read_tree( book, file ); + xml_close_file( file ); + + /* Resolve folder items */ + addrbook_resolve_folder_items( book ); + book->tempList = NULL; + book->readFlag = TRUE; + book->dirtyFlag = FALSE; + } + return book->retVal; +} + +static void addrbook_write_elem_s( FILE *fp, gint lvl, gchar *name ) { + gint i; + for( i = 0; i < lvl; i++ ) fputs( " ", fp ); + fputs( "<", fp ); + fputs( name, fp ); +} + +static void addrbook_write_elem_e( FILE *fp, gint lvl, gchar *name ) { + gint i; + for( i = 0; i < lvl; i++ ) fputs( " ", fp ); + fputs( "\n", fp ); +} + +static void addrbook_write_attr( FILE *fp, gchar *name, gchar *value ) { + fputs( " ", fp ); + fputs( name, fp ); + fputs( "=\"", fp ); + xml_file_put_escape_str( fp, value ); + fputs( "\"", fp ); +} + +/* +* Write file hash table visitor function. +*/ +static void addrbook_write_item_person_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + FILE *fp = ( FILE * ) data; + GList *node; + + if( ! obj ) return; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + ItemPerson *person = ( ItemPerson * ) value; + if( person ) { + addrbook_write_elem_s( fp, 1, AB_ELTAG_PERSON ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(person) ); + addrbook_write_attr( fp, AB_ATTAG_FIRST_NAME, person->firstName ); + addrbook_write_attr( fp, AB_ATTAG_LAST_NAME, person->lastName ); + addrbook_write_attr( fp, AB_ATTAG_NICK_NAME, person->nickName ); + addrbook_write_attr( fp, AB_ATTAG_COMMON_NAME, ADDRITEM_NAME(person) ); + fputs( " >\n", fp); + + /* Output email addresses */ + addrbook_write_elem_s( fp, 2, AB_ELTAG_ADDRESS_LIST ); + fputs( ">\n", fp ); + node = person->listEMail; + while ( node ) { + ItemEMail *email = node->data; + addrbook_write_elem_s( fp, 3, AB_ELTAG_ADDRESS ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(email) ); + addrbook_write_attr( fp, AB_ATTAG_ALIAS, ADDRITEM_NAME(email) ); + addrbook_write_attr( fp, AB_ATTAG_EMAIL, email->address ); + addrbook_write_attr( fp, AB_ATTAG_REMARKS, email->remarks ); + fputs( " />\n", fp); + node = g_list_next( node ); + } + addrbook_write_elem_e( fp, 2, AB_ELTAG_ADDRESS_LIST ); + + /* Output user attributes */ + addrbook_write_elem_s( fp, 2, AB_ELTAG_ATTRIBUTE_LIST ); + fputs( ">\n", fp ); + node = person->listAttrib; + while ( node ) { + UserAttribute *attrib = node->data; + addrbook_write_elem_s( fp, 3, AB_ELTAG_ATTRIBUTE ); + addrbook_write_attr( fp, AB_ATTAG_UID, attrib->uid ); + addrbook_write_attr( fp, AB_ATTAG_NAME, attrib->name ); + fputs( " >", fp); + xml_file_put_escape_str( fp, attrib->value ); + addrbook_write_elem_e( fp, 0, AB_ELTAG_ATTRIBUTE ); + node = g_list_next( node ); + } + addrbook_write_elem_e( fp, 2, AB_ELTAG_ATTRIBUTE_LIST ); + addrbook_write_elem_e( fp, 1, AB_ELTAG_PERSON ); + } + } +} + +/* +* Write file hash table visitor function. +*/ +static void addrbook_write_item_group_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + FILE *fp = ( FILE * ) data; + GList *node; + + if( ! obj ) return; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + ItemGroup *group = ( ItemGroup * ) value; + if( group ) { + addrbook_write_elem_s( fp, 1, AB_ELTAG_GROUP ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(group) ); + addrbook_write_attr( fp, AB_ATTAG_NAME, ADDRITEM_NAME(group) ); + addrbook_write_attr( fp, AB_ATTAG_REMARKS, group->remarks ); + fputs( " >\n", fp ); + + /* Output email address links */ + addrbook_write_elem_s( fp, 2, AB_ELTAG_MEMBER_LIST ); + fputs( ">\n", fp ); + node = group->listEMail; + while ( node ) { + ItemEMail *email = node->data; + ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(email); + addrbook_write_elem_s( fp, 3, AB_ELTAG_MEMBER ); + addrbook_write_attr( fp, AB_ATTAG_PID, ADDRITEM_ID(person) ); + addrbook_write_attr( fp, AB_ATTAG_EID, ADDRITEM_ID(email) ); + fputs( " />\n", fp ); + node = g_list_next( node ); + } + addrbook_write_elem_e( fp, 2, AB_ELTAG_MEMBER_LIST ); + addrbook_write_elem_e( fp, 1, AB_ELTAG_GROUP ); + } + } +} + +/* +* Write file hash table visitor function. +*/ +static void addrbook_write_item_folder_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + FILE *fp = ( FILE * ) data; + GList *node; + + if( ! obj ) return; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER ) { + ItemFolder *folder = ( ItemFolder * ) value; + if( folder ) { + addrbook_write_elem_s( fp, 1, AB_ELTAG_FOLDER ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(folder) ); + addrbook_write_attr( fp, AB_ATTAG_NAME, ADDRITEM_NAME(folder) ); + addrbook_write_attr( fp, AB_ATTAG_REMARKS, folder->remarks ); + fputs( " >\n", fp ); + addrbook_write_elem_s( fp, 2, AB_ELTAG_ITEM_LIST ); + fputs( ">\n", fp ); + + /* Output persons */ + node = folder->listPerson; + while ( node ) { + ItemPerson *item = node->data; + addrbook_write_elem_s( fp, 3, AB_ELTAG_ITEM ); + addrbook_write_attr( fp, AB_ATTAG_TYPE, AB_ATTAG_VAL_PERSON ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(item ) ); + fputs( " />\n", fp ); + node = g_list_next( node ); + } + + /* Output groups */ + node = folder->listGroup; + while ( node ) { + ItemGroup *item = node->data; + addrbook_write_elem_s( fp, 3, AB_ELTAG_ITEM ); + addrbook_write_attr( fp, AB_ATTAG_TYPE, AB_ATTAG_VAL_GROUP ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(item ) ); + fputs( " />\n", fp ); + node = g_list_next( node ); + } + + /* Output folders */ + node = folder->listFolder; + while ( node ) { + ItemFolder *item = node->data; + addrbook_write_elem_s( fp, 3, AB_ELTAG_ITEM ); + addrbook_write_attr( fp, AB_ATTAG_TYPE, AB_ATTAG_VAL_FOLDER ); + addrbook_write_attr( fp, AB_ATTAG_UID, ADDRITEM_ID(item ) ); + fputs( " />\n", fp ); + node = g_list_next( node ); + } + addrbook_write_elem_e( fp, 2, AB_ELTAG_ITEM_LIST ); + addrbook_write_elem_e( fp, 1, AB_ELTAG_FOLDER ); + } + } +} + +/* +* Output address book data to specified file. +* return: Status code. +*/ +gint addrbook_write_to( AddressBookFile *book, gchar *newFile ) { + FILE *fp; + gchar *fileSpec; +#ifndef DEV_STANDALONE + PrefFile *pfile; +#endif + + g_return_val_if_fail( book != NULL, -1 ); + g_return_val_if_fail( newFile != NULL, -1 ); + + fileSpec = g_strconcat( book->path, G_DIR_SEPARATOR_S, newFile, NULL ); + + book->retVal = MGU_OPEN_FILE; +#ifdef DEV_STANDALONE + fp = fopen( fileSpec, "wb" ); + g_free( fileSpec ); + if( fp ) { + fputs( "\n", fp ); +#else + pfile = prefs_file_open( fileSpec ); + g_free( fileSpec ); + if( pfile ) { + fp = pfile->fp; + fprintf( fp, "\n", + conv_get_internal_charset_str() ); +#endif + addrbook_write_elem_s( fp, 0, AB_ELTAG_ADDRESS_BOOK ); + addrbook_write_attr( fp, AB_ATTAG_NAME, book->name ); + fputs( " >\n", fp ); + + /* Output all persons */ + g_hash_table_foreach( book->addressCache->itemHash, addrbook_write_item_person_vis, fp ); + + /* Output all groups */ + g_hash_table_foreach( book->addressCache->itemHash, addrbook_write_item_group_vis, fp ); + + /* Output all folders */ + g_hash_table_foreach( book->addressCache->itemHash, addrbook_write_item_folder_vis, fp ); + + addrbook_write_elem_e( fp, 0, AB_ELTAG_ADDRESS_BOOK ); + book->retVal = MGU_SUCCESS; +#ifdef DEV_STANDALONE + fclose( fp ); +#else + if( prefs_file_close( pfile ) < 0 ) { + book->retVal = MGU_ERROR_WRITE; + } +#endif + } + + fileSpec = NULL; + return book->retVal; +} + +/* +* Output address book data to original file. +* return: Status code. +*/ +gint addrbook_save_data( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, -1 ); + + book->retVal = MGU_NO_FILE; + if( book->fileName == NULL || *book->fileName == '\0' ) return book->retVal; + if( book->path == NULL || *book->path == '\0' ) return book->retVal; + + addrbook_write_to( book, book->fileName ); + if( book->retVal == MGU_SUCCESS ) { + book->dirtyFlag = FALSE; + } + return book->retVal; +} + +/* ********************************************************************** +* Address book edit interface functions... +* *********************************************************************** +*/ + +/* +* Move person's email item. +* param: book Address book. +* person Person. +* itemMove Item to move. +* itemTarget Target item before which to move item. +*/ +ItemEMail *addrbook_move_email_before( AddressBookFile *book, ItemPerson *person, + ItemEMail *itemMove, ItemEMail *itemTarget ) +{ + ItemEMail *email = NULL; + + g_return_val_if_fail( book != NULL, NULL ); + + email = addritem_move_email_before( person, itemMove, itemTarget ); + if( email ) { + book->dirtyFlag = TRUE; + } + return email; +} + +/* +* Move person's email item. +* param: book Address book. +* person Person. +* itemMove Item to move. +* itemTarget Target item after which to move item. +*/ +ItemEMail *addrbook_move_email_after( AddressBookFile *book, ItemPerson *person, + ItemEMail *itemMove, ItemEMail *itemTarget ) +{ + ItemEMail *email = NULL; + + g_return_val_if_fail( book != NULL, NULL ); + + email = addritem_move_email_after( person, itemMove, itemTarget ); + if( email ) { + book->dirtyFlag = TRUE; + } + return email; +} + +/* +* Hash table visitor function. +*/ +static gboolean addrbook_free_simple_hash_vis( gpointer *key, gpointer *value, gpointer *data ) { + g_free( key ); + key = NULL; + value = NULL; + return TRUE; +} + +/* +* Update address book email list for specified person. +* Enter: book Address book. +* person Person to update. +* listEMail New list of email addresses. +* Note: The existing email addresses are replaced with the new addresses. Any references +* to old addresses in the groups are re-linked to the new addresses. All old addresses +* linked to the person are removed. +*/ +void addrbook_update_address_list( AddressBookFile *book, ItemPerson *person, GList *listEMail ) { + GList *node; + GList *oldData; + GList *listGroup; + + g_return_if_fail( book != NULL ); + g_return_if_fail( person != NULL ); + + /* Remember old list */ + oldData = person->listEMail; + + /* Attach new address list to person. */ + node = listEMail; + while( node ) { + ItemEMail *email = node->data; + if( ADDRITEM_ID(email) == NULL ) { + /* Allocate an ID */ + addrcache_id_email( book->addressCache, email ); + } + ADDRITEM_PARENT(email) = ADDRITEM_OBJECT(person); + node = g_list_next( node ); + } + person->listEMail = listEMail; + + /* Get groups where person's email is listed */ + listGroup = addrcache_get_group_for_person( book->addressCache, person ); + if( listGroup ) { + GHashTable *hashEMail; + GList *nodeGrp; + + /* Load hash table with new address entries */ + hashEMail = g_hash_table_new( g_str_hash, g_str_equal ); + node = listEMail; + while( node ) { + ItemEMail *email = node->data; + gchar *addr = g_strdup( email->address ); + g_strdown( addr ); + if( ! g_hash_table_lookup( hashEMail, addr ) ) { + g_hash_table_insert( hashEMail, addr, email ); + } + node = g_list_next( node ); + } + + /* Re-parent new addresses to existing groups, where email address match. */ + nodeGrp = listGroup; + while( nodeGrp ) { + ItemGroup *group = ( ItemGroup * ) nodeGrp->data; + GList *groupEMail = group->listEMail; + GList *nodeGrpEM; + GList *listRemove = NULL; + + /* Process each email item linked to group */ + nodeGrpEM = groupEMail; + while( nodeGrpEM ) { + ItemEMail *emailGrp = ( ItemEMail * ) nodeGrpEM->data; + if( ADDRITEM_PARENT(emailGrp) == ADDRITEM_OBJECT(person) ) { + /* Found an email address for this person */ + ItemEMail *emailNew = NULL; + gchar *addr = g_strdup( emailGrp->address ); + g_strdown( addr ); + emailNew = ( ItemEMail * ) g_hash_table_lookup( hashEMail, addr ); + g_free( addr ); + if( emailNew ) { + /* Point to this entry */ + nodeGrpEM->data = emailNew; + } + else { + /* Mark for removal */ + listRemove = g_list_append( listRemove, emailGrp ); + } + } + /* Move on to next email link */ + nodeGrpEM = g_list_next( nodeGrpEM ); + } + + /* Process all removed links in current group */ + nodeGrpEM = listRemove; + while( nodeGrpEM ) { + ItemEMail *emailGrp = nodeGrpEM->data; + groupEMail = g_list_remove( groupEMail, emailGrp ); + nodeGrpEM = g_list_next( nodeGrpEM ); + } + + /* Move on to next group */ + nodeGrp = g_list_next( nodeGrp ); + + } + + /* Clear hash table */ + g_hash_table_foreach_remove( hashEMail, ( GHRFunc ) addrbook_free_simple_hash_vis, NULL ); + g_hash_table_destroy( hashEMail ); + hashEMail = NULL; + g_list_free( listGroup ); + listGroup = NULL; + } + + /* Free up old data */ + addritem_free_list_email( oldData ); + oldData = NULL; + book->dirtyFlag = TRUE; + +} + +/* +* Add person and address data to address book. +* Enter: book Address book. +* folder Folder where to add person, or NULL for root folder. +* listEMail New list of email addresses. +* Return: Person added. +* Note: A new person is created with specified list of email addresses. All objects inserted +* into address book. +*/ +ItemPerson *addrbook_add_address_list( AddressBookFile *book, ItemFolder *folder, GList *listEMail ) { + ItemPerson *person; + ItemFolder *f = folder; + GList *node; + + g_return_val_if_fail( book != NULL, NULL ); + + if( ! f ) f = book->addressCache->rootFolder; + person = addritem_create_item_person(); + addrcache_id_person( book->addressCache, person ); + addrcache_folder_add_person( book->addressCache, f, person ); + + node = listEMail; + while( node ) { + ItemEMail *email = node->data; + if( ADDRITEM_ID(email) == NULL ) { + addrcache_id_email( book->addressCache, email ); + } + addrcache_person_add_email( book->addressCache, person, email ); + node = g_list_next( node ); + } + book->dirtyFlag = TRUE; + return person; +} + +#if 0 +/* +* Load hash table visitor function. +*/ +static void addrbook_load_hash_table_email_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + + if( ADDRITEM_TYPE(obj) == ITEMTYPE_EMAIL ) { + GHashTable *table = ( GHashTable * ) data; + gchar *newKey = g_strdup( key ); + ItemEMail *email = ( ItemEMail * ) obj; + if( ! g_hash_table_lookup( table, newKey ) ) { + g_hash_table_insert( table, newKey, email ); + } + } +} + +/* +* Load hash table with links to email addresses. +*/ +static void addrbook_load_hash_table_email( AddressBookFile *book, GHashTable *table ) { + g_return_if_fail( book != NULL ); + g_return_if_fail( table != NULL ); + g_hash_table_foreach( book->addressCache->itemHash, addrbook_load_hash_table_email_vis, table ); +} +#endif + +/* +* Build available email list visitor function. +*/ +static void addrbook_build_avail_email_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + AddressBookFile *book = data; + ItemPerson *person = ( ItemPerson * ) obj; + GList *node = person->listEMail; + while( node ) { + ItemEMail *email = node->data; + /* gchar *newKey = g_strdup( ADDRITEM_ID(email) ); */ + + if( ! g_hash_table_lookup( book->tempHash, ADDRITEM_ID(email) ) ) { + book->tempList = g_list_append( book->tempList, email ); + } + node = g_list_next( node ); + } + } +} + +/* +* Return link list of available email items (which have not already been linked to +* groups). Note that the list contains references to items and should be g_free() +* when done. Do *NOT* attempt to used the addrbook_free_xxx() functions... this will +* destroy the addressbook data! +* Return: List of items, or NULL if none. +*/ +GList *addrbook_get_available_email_list( AddressBookFile *book, ItemGroup *group ) { + GList *list = NULL; + GHashTable *table; + + g_return_val_if_fail( book != NULL, NULL ); + + /* Load hash table with group email entries */ + table = g_hash_table_new( g_str_hash, g_str_equal ); + if( group ) { + list = group->listEMail; + while( list ) { + ItemEMail *email = list->data; + g_hash_table_insert( table, ADDRITEM_ID(email), email ); + list = g_list_next( list ); + } + } + + /* Build list of available email addresses which exclude those already in groups */ + book->tempList = NULL; + book->tempHash = table; + g_hash_table_foreach( book->addressCache->itemHash, addrbook_build_avail_email_vis, book ); + list = book->tempList; + book->tempList = NULL; + book->tempHash = NULL; + + /* Clear hash table */ + g_hash_table_destroy( table ); + table = NULL; + + return list; +} + +/* +* Update address book email list for specified group. +* Enter: book Address book. +* group group to update. +* listEMail New list of email addresses. This should *NOT* be g_free() when done. +* Note: The existing email addresses are replaced with the new addresses. Any references +* to old addresses in the groups are re-linked to the new addresses. All old addresses +* linked to the person are removed. +*/ +void addrbook_update_group_list( AddressBookFile *book, ItemGroup *group, GList *listEMail ) { + GList *oldData; + + g_return_if_fail( book != NULL ); + g_return_if_fail( group != NULL ); + + /* Remember old list */ + oldData = group->listEMail; + group->listEMail = listEMail; + mgu_clear_list( oldData ); + oldData = NULL; + book->dirtyFlag = TRUE; +} + +/* +* Add group and email list to address book. +* Enter: book Address book. +* folder Parent folder, or NULL for root folder. +* listEMail New list of email addresses. This should *NOT* be g_free() when done. +* Return: Group object. +* Note: The existing email addresses are replaced with the new addresses. Any references +* to old addresses in the groups are re-linked to the new addresses. All old addresses +* linked to the person are removed. +*/ +ItemGroup *addrbook_add_group_list( AddressBookFile *book, ItemFolder *folder, GList *listEMail ) { + ItemGroup *group = NULL; + ItemFolder *f = folder; + + g_return_val_if_fail( book != NULL, NULL ); + + if( ! f ) f = book->addressCache->rootFolder; + group = addritem_create_item_group(); + addrcache_id_group( book->addressCache, group ); + addrcache_folder_add_group( book->addressCache, f, group ); + group->listEMail = listEMail; + book->dirtyFlag = TRUE; + return group; +} + +/* +* Add new folder to address book. +* Enter: book Address book. +* parent Parent folder. +* Return: Folder that was added. This should *NOT* be g_free() when done. +*/ +ItemFolder *addrbook_add_new_folder( AddressBookFile *book, ItemFolder *parent ) { + ItemFolder *folder = NULL; + ItemFolder *p = parent; + + g_return_val_if_fail( book != NULL, NULL ); + + if( ! p ) p = book->addressCache->rootFolder; + folder = addritem_create_item_folder(); + addrcache_id_folder( book->addressCache, folder ); + if( addrcache_hash_add_folder( book->addressCache, folder ) ) { + p->listFolder = g_list_append( p->listFolder, folder ); + ADDRITEM_PARENT(folder) = ADDRITEM_OBJECT(p); + book->dirtyFlag = TRUE; + } + else { + addritem_free_item_folder( folder ); + folder = NULL; + } + return folder; +} + +/* +* Update address book attribute list for specified person. +* Enter: book Address book. +* person Person to update. +* listAttrib New list of attributes. +* Note: The existing email addresses are replaced with the new addresses. All old attributes +* linked to the person are removed. +*/ +void addrbook_update_attrib_list( AddressBookFile *book, ItemPerson *person, GList *listAttrib ) { + GList *node; + GList *oldData; + + g_return_if_fail( book != NULL ); + g_return_if_fail( person != NULL ); + + /* Remember old list */ + oldData = person->listAttrib; + + /* Attach new address list to person. */ + node = listAttrib; + while( node ) { + UserAttribute *attrib = node->data; + if( attrib->uid == NULL ) { + /* Allocate an ID */ + addrcache_id_attribute( book->addressCache, attrib ); + } + node = g_list_next( node ); + } + person->listAttrib = listAttrib; + + /* Free up old data */ + addritem_free_list_attribute( oldData ); + oldData = NULL; + book->dirtyFlag = TRUE; + +} + +/* +* Add attribute data for person to address book. +* Enter: book Address book. +* person New person object. +* listAttrib New list of attributes. +* Note: Only attributes are inserted into address book. +*/ +void addrbook_add_attrib_list( AddressBookFile *book, ItemPerson *person, GList *listAttrib ) { + GList *node; + + g_return_if_fail( book != NULL ); + g_return_if_fail( person != NULL ); + + node = listAttrib; + while( node ) { + UserAttribute *attrib = node->data; + if( attrib->uid == NULL ) { + addrcache_id_attribute( book->addressCache, attrib ); + } + addritem_person_add_attribute( person, attrib ); + node = g_list_next( node ); + } + book->dirtyFlag = TRUE; +} + +/* +* Return address book file for specified object. +* Enter: aio Book item object. +* Return: Address book, or NULL if not found. +*/ +AddressBookFile *addrbook_item_get_bookfile( AddrItemObject *aio ) { + AddressBookFile *book = NULL; + + if( aio ) { + ItemFolder *parent = NULL; + ItemFolder *root = NULL; + if( aio->type == ITEMTYPE_EMAIL ) { + ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(aio); + if( person ) { + parent = ( ItemFolder * ) ADDRITEM_PARENT(person); + } + } + else { + parent = ( ItemFolder * ) ADDRITEM_PARENT(aio); + } + if( parent ) { + root = addrcache_find_root_folder( parent ); + } + if( root ) { + book = ( AddressBookFile * ) ADDRITEM_PARENT(root); + } + } + return book; +} + +/* +* Remove folder from address book. Children are re-parented to parent folder. +* param: folder Folder to remove. +* return: Folder, or NULL if not found. Note that object should still be freed. +*/ +ItemFolder *addrbook_remove_folder( AddressBookFile *book, ItemFolder *folder ) { + ItemFolder *f; + + g_return_val_if_fail( book != NULL, NULL ); + + f = addrcache_remove_folder( book->addressCache, folder ); + if( f ) book->dirtyFlag = TRUE; + return f; +} + +/* +* Remove folder from address book. Children are deleted. +* param: folder Folder to remove. +* return: Folder, or NULL if not found. Note that object should still be freed. +*/ +ItemFolder *addrbook_remove_folder_delete( AddressBookFile *book, ItemFolder *folder ) { + ItemFolder *f; + + g_return_val_if_fail( book != NULL, NULL ); + + f = addrcache_remove_folder_delete( book->addressCache, folder ); + if( f ) book->dirtyFlag = TRUE; + return f; +} + +#define WORK_BUFLEN 1024 +#define ADDRBOOK_DIGITS "0123456789" + +/* +* Return list of existing address book files. +* Enter: book Address book file. +* Return: File list. +*/ +GList *addrbook_get_bookfile_list( AddressBookFile *book ) { + gchar *adbookdir; + DIR *dp; + struct dirent *entry; + struct stat statbuf; + gchar buf[ WORK_BUFLEN ]; + gchar numbuf[ WORK_BUFLEN ]; + gint len, lenpre, lensuf, lennum; + long int val, maxval; + GList *fileList = NULL; + + g_return_val_if_fail( book != NULL, NULL ); + + if( book->path == NULL || *book->path == '\0' ) { + book->retVal = MGU_NO_PATH; + return NULL; + } + + strcpy( buf, book->path ); + len = strlen( buf ); + if( len > 0 ) { + if( buf[ len-1 ] != G_DIR_SEPARATOR ) { + buf[ len ] = G_DIR_SEPARATOR; + buf[ ++len ] = '\0'; + } + } + + adbookdir = g_strdup( buf ); + strcat( buf, ADDRBOOK_PREFIX ); + + if( ( dp = opendir( adbookdir ) ) == NULL ) { + book->retVal = MGU_OPEN_DIRECTORY; + g_free( adbookdir ); + return NULL; + } + + lenpre = strlen( ADDRBOOK_PREFIX ); + lensuf = strlen( ADDRBOOK_SUFFIX ); + lennum = FILE_NUMDIGITS + lenpre; + maxval = -1; + + while( ( entry = readdir( dp ) ) != NULL ) { + gchar *endptr = NULL; + gint i; + gboolean flg; + + strcpy( buf, adbookdir ); + strcat( buf, entry->d_name ); + stat( buf, &statbuf ); + if( S_IFREG & statbuf.st_mode ) { + if( strncmp( entry->d_name, ADDRBOOK_PREFIX, lenpre ) == 0 ) { + if( strncmp( (entry->d_name) + lennum, ADDRBOOK_SUFFIX, lensuf ) == 0 ) { + strncpy( numbuf, (entry->d_name) + lenpre, FILE_NUMDIGITS ); + numbuf[ FILE_NUMDIGITS ] = '\0'; + flg = TRUE; + for( i = 0; i < FILE_NUMDIGITS; i++ ) { + if( ! strchr( ADDRBOOK_DIGITS, numbuf[i] ) ) { + flg = FALSE; + break; + } + } + if( flg ) { + /* Get value */ + val = strtol( numbuf, &endptr, 10 ); + if( endptr && val > -1 ) { + if( val > maxval ) maxval = val; + fileList = g_list_append( fileList, g_strdup( entry->d_name ) ); + } + } + } + } + } + } + closedir( dp ); + g_free( adbookdir ); + + book->maxValue = maxval; + book->retVal = MGU_SUCCESS; + return fileList; +} + +/* +* Return file name for specified file number. +* Enter: fileNum File number. +* Return: File name, or NULL if file number too large. Should be g_free() when done. +*/ +gchar *addrbook_gen_new_file_name( gint fileNum ) { + gchar fmt[ 30 ]; + gchar buf[ WORK_BUFLEN ]; + gint n = fileNum; + long int nmax; + + if( n < 1 ) n = 1; + nmax = -1 + (long int) pow( 10, FILE_NUMDIGITS ); + if( fileNum > nmax ) return NULL; + sprintf( fmt, "%%s%%0%dd%%s", FILE_NUMDIGITS ); + sprintf( buf, fmt, ADDRBOOK_PREFIX, n, ADDRBOOK_SUFFIX ); + return g_strdup( buf ); +} + +/* ********************************************************************** +* Address book test functions... +* *********************************************************************** +*/ + +#if 0 +static void addrbook_show_attribs( GList *attr ) { + while( attr ) { + gchar *name = ((XMLAttr *)attr->data)->name; + gchar *value = ((XMLAttr *)attr->data)->value; + printf( "\tn/v = %s : %s\n", name, value ); + attr = g_list_next( attr ); + } + printf( "\t---\n" ); +} +#endif + +/* +* Test email address list. +*/ +static void addrbook_chkparse_addr_list( AddressBookFile *book, XMLFile *file ){ + guint prev_level; + GList *attr; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + if( xml_compare_tag( file, AB_ELTAG_ADDRESS ) ) { + addrbook_chkparse_addr_list( book, file ); + } + } +} + +/* +* Test user attributes for person. +*/ +static void addrbook_chkparse_attribute( AddressBookFile *book, XMLFile *file ) { + GList *attr; + gchar *element; + + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + element = xml_get_element( file ); + /* printf( "\t\tattrib value : %s\n", element ); */ +} + +/* +* Test attribute list. +*/ +static void addrbook_chkparse_attr_list( AddressBookFile *book, XMLFile *file ){ + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_ATTRIBUTE ) ) { + addrbook_chkparse_attribute( book, file ); + addrbook_chkparse_attr_list( book, file ); + } + } +} + +/* +* Test person. +*/ +static void addrbook_chkparse_person( AddressBookFile *book, XMLFile *file ) { + GList *attr; + + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_ADDRESS_LIST ) ) { + addrbook_chkparse_addr_list( book, file ); + } + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_ATTRIBUTE_LIST ) ) { + addrbook_chkparse_attr_list( book, file ); + } +} + +/* +* Test group member list. +*/ +static void addrbook_chkparse_member_list( AddressBookFile *book, XMLFile *file ){ + GList *attr; + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_MEMBER ) ) { + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + addrbook_chkparse_member_list( book, file ); + } + else { + attr = xml_get_current_tag_attr( file ); + /* addrbook_show_attribs( attr ); */ + } + } +} + +/* +* Test group. +*/ +static void addrbook_chkparse_group( AddressBookFile *book, XMLFile *file ) { + GList *attr; + + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_MEMBER_LIST ) ) { + addrbook_chkparse_member_list( book, file ); + } +} + +/* +* Test folder item list. +*/ +static void addrbook_chkparse_folder_list( AddressBookFile *book, XMLFile *file ){ + GList *attr; + guint prev_level; + + for (;;) { + prev_level = file->level; + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + if (file->level < prev_level) return; + if( xml_compare_tag( file, AB_ELTAG_ITEM ) ) { + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + addrbook_chkparse_folder_list( book, file ); + } + else { + attr = xml_get_current_tag_attr( file ); + /* addrbook_show_attribs( attr ); */ + } + } +} + +/* +* Test folder. +*/ +static void addrbook_chkparse_folder( AddressBookFile *book, XMLFile *file ) { + GList *attr; + + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + if( xml_parse_next_tag( file ) ) { /* Consume closing tag */ + longjmp( book->jumper, 1 ); + } + if( xml_compare_tag( file, AB_ELTAG_ITEM_LIST ) ) { + addrbook_chkparse_folder_list( book, file ); + } +} + +/* +* Test address book. +*/ +static gboolean addrbook_chkread_tree( AddressBookFile *book, XMLFile *file ) { + GList *attr; + gboolean retVal; + + if( xml_get_dtd( file ) ) { + return FALSE; + } + if( xml_parse_next_tag( file ) ) { + return FALSE; + } + + if( ! xml_compare_tag( file, AB_ELTAG_ADDRESS_BOOK ) ) { + return FALSE; + } + + attr = xml_get_current_tag_attr(file); + /* addrbook_show_attribs( attr ); */ + + retVal = TRUE; + for (;;) { + if (! file->level ) break; + /* Get item tag */ + if( xml_parse_next_tag( file ) ) { + longjmp( book->jumper, 1 ); + } + /* Get next tag (person, group or folder) */ + if( xml_compare_tag( file, AB_ELTAG_PERSON ) ) { + addrbook_chkparse_person( book, file ); + } + else if( xml_compare_tag( file, AB_ELTAG_GROUP ) ) { + addrbook_chkparse_group( book, file ); + } + else if( xml_compare_tag( file, AB_ELTAG_FOLDER ) ) { + addrbook_chkparse_folder( book, file ); + } + } + return retVal; +} + +/* +* Test address book file by parsing contents. +* Enter: book Address book file to check. +* fileName File name to check. +* Return: MGU_SUCCESS if file appears to be valid format. +*/ +gint addrbook_test_read_file( AddressBookFile *book, gchar *fileName ) { + XMLFile *file = NULL; + gchar *fileSpec = NULL; + + g_return_val_if_fail( book != NULL, -1 ); + + fileSpec = g_strconcat( book->path, G_DIR_SEPARATOR_S, fileName, NULL ); + book->retVal = MGU_OPEN_FILE; + file = xml_open_file( fileSpec ); + g_free( fileSpec ); + if( file ) { + book->retVal = MGU_BAD_FORMAT; + if( setjmp( book->jumper ) ) { + /* printf( "Caught Ya!!!\n" ); */ + xml_close_file( file ); + return book->retVal; + } + if( addrbook_chkread_tree( book, file ) ) { + book->retVal = MGU_SUCCESS; + } + xml_close_file( file ); + } + return book->retVal; +} + +/* +* Return link list of all persons in address book. Note that the list contains +* references to items. Do *NOT* attempt to use the addrbook_free_xxx() functions... +* this will destroy the addressbook data! +* Return: List of items, or NULL if none. +*/ +GList *addrbook_get_all_persons( AddressBookFile *book ) { + g_return_val_if_fail( book != NULL, NULL ); + return addrcache_get_all_persons( book->addressCache ); +} + +/* +* Add person and address data to address book. +* Enter: book Address book. +* folder Folder where to add person, or NULL for root folder. +* name Common name. +* address EMail address. +* remarks Remarks. +* Return: Person added. Do not *NOT* to use the addrbook_free_xxx() functions... +* this will destroy the address book data. +*/ +ItemPerson *addrbook_add_contact( AddressBookFile *book, ItemFolder *folder, const gchar *name, + const gchar *address, const gchar *remarks ) +{ + ItemPerson *person = NULL; + + g_return_val_if_fail( book != NULL, NULL ); + + person = addrcache_add_contact( book->addressCache, folder, name, address, remarks ); + if( person ) book->dirtyFlag = TRUE; + return person; +} + +/* +* End of Source. +*/ diff --git a/src/addrbook.h b/src/addrbook.h new file mode 100644 index 00000000..1902be5d --- /dev/null +++ b/src/addrbook.h @@ -0,0 +1,114 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Definitions necessary to access external address book files. + */ + +#ifndef __ADDRBOOK_H__ +#define __ADDRBOOK_H__ + +#include +#include +#include + +#include "addritem.h" +#include "addrcache.h" + +/* Address book file */ +typedef struct _AddressBookFile AddressBookFile; + +struct _AddressBookFile { + gchar *name; + gchar *path; + gchar *fileName; + AddressCache *addressCache; + gint retVal; + gint maxValue; + GList *tempList; + GHashTable *tempHash; + gboolean readFlag; + gboolean dirtyFlag; + gboolean modifyFlag; + gboolean accessFlag; + jmp_buf jumper; +}; + +/* Function prototypes */ + +AddressBookFile *addrbook_create_book ( void ); +void addrbook_empty_book ( AddressBookFile *book ); +void addrbook_free_book ( AddressBookFile *book ); +void addrbook_print_book ( AddressBookFile *book, FILE *stream ); +void addrbook_dump_hash ( AddressBookFile *book, FILE *stream ); +void addrbook_dump_book ( AddressBookFile *book, FILE *stream ); +void addrbook_set_name ( AddressBookFile *book, const gchar *value ); +void addrbook_set_path ( AddressBookFile *book, const gchar *value ); +void addrbook_set_file ( AddressBookFile *book, const gchar *value ); +void addrbook_set_accessed ( AddressBookFile *book, const gboolean value ); +gboolean addrbook_get_modified ( AddressBookFile *book ); +gboolean addrbook_get_accessed ( AddressBookFile *book ); +gboolean addrbook_get_read_flag ( AddressBookFile *book ); +gint addrbook_get_status ( AddressBookFile *book ); +ItemFolder *addrbook_get_root_folder ( AddressBookFile *book ); +GList *addrbook_get_list_folder ( AddressBookFile *book ); +GList *addrbook_get_list_person ( AddressBookFile *book ); +gchar *addrbook_get_name ( AddressBookFile *book ); + +ItemPerson *addrbook_remove_person ( AddressBookFile *book, ItemPerson *person ); +ItemGroup *addrbook_remove_group ( AddressBookFile *book, ItemGroup *group ); +ItemEMail *addrbook_person_remove_email ( AddressBookFile *book, ItemPerson *person, + ItemEMail *email ); + +gint addrbook_read_data ( AddressBookFile *book ); +gint addrbook_save_data ( AddressBookFile *book ); + +ItemEMail *addrbook_move_email_before ( AddressBookFile *book, ItemPerson *person, + ItemEMail *itemMove, ItemEMail *itemTarget ); +ItemEMail *addrbook_move_email_after ( AddressBookFile *book, ItemPerson *person, + ItemEMail *itemMove, ItemEMail *itemTarget ); + +void addrbook_update_address_list ( AddressBookFile *book, ItemPerson *person, + GList *listEMail ); +ItemPerson *addrbook_add_address_list ( AddressBookFile *book, ItemFolder *folder, + GList *listEMail ); +GList *addrbook_get_available_email_list( AddressBookFile *book, ItemGroup *group ); +void addrbook_update_group_list ( AddressBookFile *book, ItemGroup *group, + GList *listEMail ); +ItemGroup *addrbook_add_group_list ( AddressBookFile *book, ItemFolder *folder, + GList *listEMail ); +ItemFolder *addrbook_add_new_folder ( AddressBookFile *book, ItemFolder *parent ); + +void addrbook_update_attrib_list ( AddressBookFile *book, ItemPerson *person, GList *listAttrib ); +void addrbook_add_attrib_list ( AddressBookFile *book, ItemPerson *person, GList *listAttrib ); + +ItemFolder *addrbook_remove_folder ( AddressBookFile *book, ItemFolder *folder ); +ItemFolder *addrbook_remove_folder_delete( AddressBookFile *book, ItemFolder *folder ); + +GList *addrbook_get_bookfile_list ( AddressBookFile *book ); +gchar *addrbook_gen_new_file_name ( gint fileNum ); +gint addrbook_test_read_file ( AddressBookFile *book, gchar *fileName ); + +GList *addrbook_get_all_persons ( AddressBookFile *book ); + +ItemPerson *addrbook_add_contact ( AddressBookFile *book, ItemFolder *folder, + const gchar *name, const gchar *address, + const gchar *remarks ); + +#endif /* __ADDRBOOK_H__ */ diff --git a/src/addrcache.c b/src/addrcache.c new file mode 100644 index 00000000..6a491256 --- /dev/null +++ b/src/addrcache.c @@ -0,0 +1,1232 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Functions to maintain address cache. + */ + +#include +#include +#include +#include + +/* #include "mgutils.h" */ +#include "addritem.h" +#include "addrcache.h" + +#define ID_TIME_OFFSET 998000000 +#define ADDRCACHE_MAX_SEARCH_COUNT 1000 + +/* +* Create new address cache. +*/ +AddressCache *addrcache_create() { + AddressCache *cache; + gint t; + + cache = g_new0( AddressCache, 1 ); + cache->itemHash = g_hash_table_new( g_str_hash, g_str_equal ); + + cache->dataRead = FALSE; + cache->modified = FALSE; + cache->modifyTime = 0; + + /* Generate the next ID using system time */ + cache->nextID = 1; + t = time( NULL ); + if( t > 0 ) { + cache->nextID = t - ID_TIME_OFFSET; + } + + cache->tempList = NULL; + cache->rootFolder = addritem_create_item_folder(); + cache->rootFolder->isRoot = TRUE; + ADDRITEM_PARENT(cache->rootFolder) = NULL; + return cache; +} + +/* +* Properties. +*/ +ItemFolder *addrcache_get_root_folder( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return cache->rootFolder; +} +GList *addrcache_get_list_folder( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return cache->rootFolder->listFolder; +} +GList *addrcache_get_list_person( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return cache->rootFolder->listPerson; +} + +/* +* Generate next ID. +*/ +void addrcache_next_id( AddressCache *cache ) { + g_return_if_fail( cache != NULL ); + cache->nextID++; +} + +/* +* Refresh internal variables. This can be used force a reload. +*/ +void addrcache_refresh( AddressCache *cache ) { + cache->dataRead = FALSE; + cache->modified = TRUE; + cache->modifyTime = 0; +} + +/* +* Free hash table visitor function. +*/ +static gint addrcache_free_item_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + addritem_free_item_person( ( ItemPerson * ) obj ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + addritem_free_item_group( ( ItemGroup * ) obj ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER ) { + addritem_free_item_folder( ( ItemFolder * ) obj ); + } + key = NULL; + value = NULL; + return 0; +} + +/* +* Free hash table of address cache items. +*/ +static void addrcache_free_item_hash( GHashTable *table ) { + g_return_if_fail( table != NULL ); + g_hash_table_freeze( table ); + g_hash_table_foreach_remove( table, addrcache_free_item_vis, NULL ); + g_hash_table_thaw( table ); + g_hash_table_destroy( table ); +} + +/* +* Free up folders and groups. +*/ +static void addrcache_free_all_folders( ItemFolder *parent ) { + GList *node = parent->listFolder; + while( node ) { + ItemFolder *folder = node->data; + addrcache_free_all_folders( folder ); + node = g_list_next( node ); + } + g_list_free( parent->listPerson ); + g_list_free( parent->listGroup ); + g_list_free( parent->listFolder ); + parent->listPerson = NULL; + parent->listGroup = NULL; + parent->listFolder = NULL; +} + +/* +* Clear the address cache. +*/ +void addrcache_clear( AddressCache *cache ) { + g_return_if_fail( cache != NULL ); + + /* Free up folders and hash table */ + addrcache_free_all_folders( cache->rootFolder ); + addrcache_free_item_hash( cache->itemHash ); + cache->itemHash = NULL; + ADDRITEM_PARENT(cache->rootFolder) = NULL; + addritem_free_item_folder( cache->rootFolder ); + cache->rootFolder = NULL; + g_list_free( cache->tempList ); + cache->tempList = NULL; + + /* Reset to initial state */ + cache->itemHash = g_hash_table_new( g_str_hash, g_str_equal ); + cache->rootFolder = addritem_create_item_folder(); + cache->rootFolder->isRoot = TRUE; + ADDRITEM_PARENT(cache->rootFolder) = NULL; + + addrcache_refresh( cache ); + +} + +/* +* Free address cache. +*/ +void addrcache_free( AddressCache *cache ) { + g_return_if_fail( cache != NULL ); + + addrcache_free_all_folders( cache->rootFolder ); + addrcache_free_item_hash( cache->itemHash ); + cache->itemHash = NULL; + ADDRITEM_PARENT(cache->rootFolder) = NULL; + addritem_free_item_folder( cache->rootFolder ); + cache->rootFolder = NULL; + g_list_free( cache->tempList ); + cache->tempList = NULL; + g_free( cache ); +} + +/* +* Check whether file has changed by comparing with cache. +* return: TRUE if file has changed. +*/ +gboolean addrcache_check_file( AddressCache *cache, gchar *path ) { + gboolean retVal; + struct stat filestat; + retVal = TRUE; + if( path ) { + if( 0 == lstat( path, &filestat ) ) { + if( filestat.st_mtime == cache->modifyTime ) retVal = FALSE; + } + } + return retVal; +} + +/* +* Save file time to cache. +* return: TRUE if time marked. +*/ +gboolean addrcache_mark_file( AddressCache *cache, gchar *path ) { + gboolean retVal = FALSE; + struct stat filestat; + if( path ) { + if( 0 == lstat( path, &filestat ) ) { + cache->modifyTime = filestat.st_mtime; + retVal = TRUE; + } + } + return retVal; +} + +/* +* Print list of items. +*/ +void addrcache_print_item_list( GList *list, FILE *stream ) { + GList *node = list; + while( node ) { + AddrItemObject *obj = node->data; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + addritem_print_item_person( ( ItemPerson * ) obj, stream ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + addritem_print_item_group( ( ItemGroup * ) obj, stream ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER ) { + addritem_print_item_folder( ( ItemFolder * ) obj, stream ); + } + node = g_list_next( node ); + } + fprintf( stream, "\t---\n" ); +} + +/* +* Print item hash table visitor function. +*/ +static void addrcache_print_item_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + FILE *stream = ( FILE * ) data; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + addritem_print_item_person( ( ItemPerson * ) obj, stream ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + addritem_print_item_group( ( ItemGroup * ) obj, stream ); + } + else if( ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER ) { + addritem_print_item_folder( ( ItemFolder * ) obj, stream ); + } +} + +/* +* Dump entire address cache hash table contents. +*/ +void addrcache_print( AddressCache *cache, FILE *stream ) { + g_return_if_fail( cache != NULL ); + fprintf( stream, "AddressCache:\n" ); + fprintf( stream, "next id : %d\n", cache->nextID ); + fprintf( stream, "mod time : %ld\n", cache->modifyTime ); + fprintf( stream, "modified : %s\n", cache->modified ? "yes" : "no" ); + fprintf( stream, "data read: %s\n", cache->dataRead ? "yes" : "no" ); +} + +/* +* Dump entire address cache hash table contents. +*/ +void addrcache_dump_hash( AddressCache *cache, FILE *stream ) { + g_return_if_fail( cache != NULL ); + addrcache_print( cache, stream ); + g_hash_table_foreach( cache->itemHash, addrcache_print_item_vis, stream ); +} + +/* + * Allocate ID for person. + */ +void addrcache_id_person( AddressCache *cache, ItemPerson *person ) { + g_return_if_fail( cache != NULL ); + g_return_if_fail( person != NULL ); + if( ADDRITEM_ID(person) ) return; + addrcache_next_id( cache ); + ADDRITEM_ID(person) = g_strdup_printf( "%d", cache->nextID ); +} + +/* + * Allocate ID for group. + */ +void addrcache_id_group( AddressCache *cache, ItemGroup *group ) { + g_return_if_fail( cache != NULL ); + g_return_if_fail( group != NULL ); + if( ADDRITEM_ID(group) ) return; + addrcache_next_id( cache ); + ADDRITEM_ID(group) = g_strdup_printf( "%d", cache->nextID ); +} + +/* + * Allocate ID for folder. + */ +void addrcache_id_folder( AddressCache *cache, ItemFolder *folder ) { + g_return_if_fail( cache != NULL ); + g_return_if_fail( folder != NULL ); + if( ADDRITEM_ID(folder) ) return; + addrcache_next_id( cache ); + ADDRITEM_ID(folder) = g_strdup_printf( "%d", cache->nextID ); +} + +/* + * Allocate ID for email address. + */ +void addrcache_id_email( AddressCache *cache, ItemEMail *email ) { + g_return_if_fail( cache != NULL ); + g_return_if_fail( email != NULL ); + if( ADDRITEM_ID(email) ) return; + addrcache_next_id( cache ); + ADDRITEM_ID(email) = g_strdup_printf( "%d", cache->nextID ); +} + +/* + * Allocate ID for user attribute. + */ +void addrcache_id_attribute( AddressCache *cache, UserAttribute *attrib ) { + g_return_if_fail( cache != NULL ); + g_return_if_fail( attrib != NULL ); + if( attrib->uid ) return; + addrcache_next_id( cache ); + attrib->uid = g_strdup_printf( "%d", cache->nextID ); +} + +/* +* Add person to hash table. +* return: TRUE if item added. +*/ +gboolean addrcache_hash_add_person( AddressCache *cache, ItemPerson *person ) { + if( g_hash_table_lookup( cache->itemHash, ADDRITEM_ID(person) ) ) { + return FALSE; + } + g_hash_table_insert( cache->itemHash, ADDRITEM_ID(person), person ); + return TRUE; +} + +/* +* Add group to hash table. +* return: TRUE if item added. +*/ +gboolean addrcache_hash_add_group( AddressCache *cache, ItemGroup *group ) { + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( group != NULL, FALSE ); + + if( g_hash_table_lookup( cache->itemHash, ADDRITEM_ID(group) ) ) { + return FALSE; + } + g_hash_table_insert( cache->itemHash, ADDRITEM_ID(group), group ); + return TRUE; +} + +/* +* Add folder to hash table. +* return: TRUE if item added. +*/ +gboolean addrcache_hash_add_folder( AddressCache *cache, ItemFolder *folder ) { + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( folder != NULL, FALSE ); + + if( g_hash_table_lookup( cache->itemHash, ADDRITEM_ID(folder) ) ) { + return FALSE; + } + g_hash_table_insert( cache->itemHash, ADDRITEM_ID(folder), folder ); + return TRUE; +} + +/* +* Add person to specified folder in cache. +*/ +gboolean addrcache_folder_add_person( AddressCache *cache, ItemFolder *folder, ItemPerson *item ) { + gboolean retVal = FALSE; + + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( folder != NULL, FALSE ); + g_return_val_if_fail( item != NULL, FALSE ); + + retVal = addrcache_hash_add_person( cache, item ); + if( retVal ) { + addritem_folder_add_person( folder, item ); + } + return retVal; +} + +/* +* Add folder to specified folder in cache. +*/ +gboolean addrcache_folder_add_folder( AddressCache *cache, ItemFolder *folder, ItemFolder *item ) { + gboolean retVal = FALSE; + + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( folder != NULL, FALSE ); + g_return_val_if_fail( item != NULL, FALSE ); + + retVal = addrcache_hash_add_folder( cache, item ); + if( retVal ) { + addritem_folder_add_folder( folder, item ); + } + return TRUE; +} + +/* +* Add folder to specified folder in cache. +*/ +gboolean addrcache_folder_add_group( AddressCache *cache, ItemFolder *folder, ItemGroup *item ) { + gboolean retVal = FALSE; + + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( folder != NULL, FALSE ); + g_return_val_if_fail( item != NULL, FALSE ); + + retVal = addrcache_hash_add_group( cache, item ); + if( retVal ) { + addritem_folder_add_group( folder, item ); + } + return retVal; +} + +/* +* Add person to address cache. +* return: TRUE if item added. +*/ +gboolean addrcache_add_person( AddressCache *cache, ItemPerson *person ) { + gboolean retVal = FALSE; + + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( person != NULL, FALSE ); + + retVal = addrcache_hash_add_person( cache, person ); + if( retVal ) { + addritem_folder_add_person( cache->rootFolder, person ); + } + return retVal; +} + +/* +* Add EMail address to person. +* return: TRUE if item added. +*/ +gboolean addrcache_person_add_email( AddressCache *cache, ItemPerson *person, ItemEMail *email ) { + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( person != NULL, FALSE ); + g_return_val_if_fail( email != NULL, FALSE ); + + addritem_person_add_email( person, email ); + return TRUE; +} + +/* +* Add group to address cache. +* return: TRUE if item added. +*/ +gboolean addrcache_add_group( AddressCache *cache, ItemGroup *group ) { + gboolean retVal = FALSE; + + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( group != NULL, FALSE ); + + retVal = addrcache_hash_add_group( cache, group ); + if( retVal ) { + addritem_folder_add_group( cache->rootFolder, group ); + } + return retVal; +} + +/* +* Add EMail address to person. +* return: TRUE if item added. +*/ +gboolean addrcache_group_add_email( AddressCache *cache, ItemGroup *group, ItemEMail *email ) { + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( group != NULL, FALSE ); + g_return_val_if_fail( email != NULL, FALSE ); + + addritem_group_add_email( group, email ); + return TRUE; +} + +/* +* Add folder to address cache. +* return: TRUE if item added. +*/ +gboolean addrcache_add_folder( AddressCache *cache, ItemFolder *folder ) { + gboolean retVal = FALSE; + + g_return_val_if_fail( cache != NULL, FALSE ); + g_return_val_if_fail( folder != NULL, FALSE ); + + retVal = addrcache_hash_add_folder( cache, folder ); + if( retVal ) { + addritem_folder_add_folder( cache->rootFolder, folder ); + } + return retVal; +} + +/* +* Return pointer to object (either person or group) for specified ID. +* param: uid Object ID. +* return: Object, or NULL if not found. +*/ +AddrItemObject *addrcache_get_object( AddressCache *cache, const gchar *uid ) { + AddrItemObject *obj = NULL; + gchar *uidH; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + /* Check for matching UID */ + uidH = ADDRITEM_ID(obj); + if( uidH ) { + if( strcmp( uidH, uid ) == 0 ) return obj; + } + } + return NULL; +} + +/* +* Return pointer for specified object ID. +* param: uid Object ID. +* return: Person object, or NULL if not found. +*/ +ItemPerson *addrcache_get_person( AddressCache *cache, const gchar *uid ) { + ItemPerson *person = NULL; + AddrItemObject *obj = addrcache_get_object( cache, uid ); + + if( obj ) { + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + person = ( ItemPerson * ) obj; + } + } + return person; +} + +/* +* Return pointer for specified object ID. +* param: uid group ID. +* return: Group object, or NULL if not found. +*/ +ItemGroup *addrcache_get_group( AddressCache *cache, const gchar *uid ) { + ItemGroup *group = NULL; + AddrItemObject *obj = addrcache_get_object( cache, uid ); + + if( obj ) { + if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + group = ( ItemGroup * ) obj; + } + } + return group; +} + +/* +* Find email address in address cache. +* param: uid Object ID for person. +* eid EMail ID. +* return: email object for specified object ID and email ID, or NULL if not found. +*/ +ItemEMail *addrcache_get_email( AddressCache *cache, const gchar *uid, const gchar *eid ) { + AddrItemObject *objP; + + if( eid == NULL || *eid == '\0' ) return NULL; + + objP = addrcache_get_object( cache, uid ); + if( objP ) { + if( ADDRITEM_TYPE(objP) == ITEMTYPE_PERSON ) { + /* Sequential search through email addresses */ + ItemPerson *person = ( ItemPerson * ) objP; + GList *nodeMail = person->listEMail; + while( nodeMail ) { + AddrItemObject *objE = nodeMail->data; + gchar *ide = ADDRITEM_ID(objE); + if( ide ) { + if( strcmp( ide, eid ) == 0 ) { + return ( ItemEMail * ) objE; + } + } + nodeMail = g_list_next( nodeMail ); + } + } + } + return NULL; +} + +/* +* Remove attribute from person. +* param: uid Object ID for person. +* aid Attribute ID. +* return: UserAttribute object, or NULL if not found. Note that object should still be freed. +*/ +UserAttribute *addrcache_person_remove_attrib_id( AddressCache *cache, const gchar *uid, const gchar *aid ) { + UserAttribute *attrib = NULL; + ItemPerson *person; + + if( aid == NULL || *aid == '\0' ) return NULL; + + person = addrcache_get_person( cache, uid ); + if( person ) { + attrib = addritem_person_remove_attrib_id( person, aid ); + } + return attrib; +} + +/* +* Remove attribute from person. +* param: person Person. +* attrib Attribute to remove. +* return: UserAttribute object. Note that object should still be freed. +*/ +UserAttribute *addrcache_person_remove_attribute( AddressCache *cache, ItemPerson *person, UserAttribute *attrib ) { + UserAttribute *found = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( person && attrib ) { + found = addritem_person_remove_attribute( person, attrib ); + } + return found; +} + +/* +* Remove group from address cache for specified ID. +* param: uid Object ID. +* return: Group, or NULL if not found. Note that object should still be freed. +*/ +ItemGroup *addrcache_remove_group_id( AddressCache *cache, const gchar *uid ) { + AddrItemObject *obj = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + ItemGroup *group = ( ItemGroup * ) obj; + ItemFolder *parent = ( ItemFolder * ) ADDRITEM_PARENT(group); + if( ! parent ) parent = cache->rootFolder; + /* Remove group from parent's list and hash table */ + parent->listGroup = g_list_remove( parent->listGroup, group ); + g_hash_table_remove( cache->itemHash, uid ); + return ( ItemGroup * ) obj; + } + } + return NULL; +} + +/* +* Remove group from address cache. +* param: group Group to remove. +* return: Group, or NULL if not found. Note that object should still be freed. +*/ +ItemGroup *addrcache_remove_group( AddressCache *cache, ItemGroup *group ) { + AddrItemObject *obj = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( group ) { + gchar *uid = ADDRITEM_ID(group); + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + ItemFolder *parent = ( ItemFolder * ) ADDRITEM_PARENT(group); + if( ! parent ) parent = cache->rootFolder; + + /* Remove group from parent's list and hash table */ + parent->listGroup = g_list_remove( parent->listGroup, obj ); + g_hash_table_remove( cache->itemHash, uid ); + return group; + } + } + return NULL; +} + +/* +* Remove person's email address from all groups in folder. +*/ +static void addrcache_foldergrp_rem_person( ItemFolder *folder, ItemPerson *person ) { + GList *nodeGrp = folder->listGroup; + + while( nodeGrp ) { + ItemGroup *group = nodeGrp->data; + if( group ) { + /* Remove each email address that belongs to the person from the list */ + GList *node = person->listEMail; + while( node ) { + group->listEMail = g_list_remove( group->listEMail, node->data ); + node = g_list_next( node ); + } + } + nodeGrp = g_list_next( nodeGrp ); + } +} + +/* +* Remove person from address cache for specified ID. Note that person still retains +* their EMail addresses. Also, links to these email addresses will be severed from +* the group. +* param: uid Object ID. +* return: Person, or NULL if not found. Note that object should still be freed. +*/ +ItemPerson *addrcache_remove_person_id( AddressCache *cache, const gchar *uid ) { + AddrItemObject *obj = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + /* Remove person's email addresses from all groups where */ + /* referenced and from hash table. */ + ItemPerson *person = ( ItemPerson * ) obj; + ItemFolder *parent = ( ItemFolder * ) ADDRITEM_PARENT(person); + if( ! parent ) parent = cache->rootFolder; + /* Remove emails from groups, remove from parent's list */ + /* and hash table */ + addrcache_foldergrp_rem_person( parent, person ); + parent->listPerson = g_list_remove( parent->listPerson, person ); + g_hash_table_remove( cache->itemHash, uid ); + return person; + } + } + return NULL; +} + +/* +* Remove specified person from address cache. +* param: person Person to remove. +* return: Person, or NULL if not found. Note that object should still be freed. +*/ +ItemPerson *addrcache_remove_person( AddressCache *cache, ItemPerson *person ) { + AddrItemObject *obj = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( person ) { + gchar *uid = ADDRITEM_ID(person); + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + /* Remove person's email addresses from all groups where */ + /* referenced and from hash table. */ + ItemFolder *parent = ( ItemFolder * ) ADDRITEM_PARENT(person); + if( ! parent ) parent = cache->rootFolder; + addrcache_foldergrp_rem_person( parent, person ); + parent->listPerson = g_list_remove( parent->listPerson, person ); + g_hash_table_remove( cache->itemHash, uid ); + return person; + } + } + } + return NULL; +} + +/* +* Remove email from group item hash table visitor function. +*/ +static void addrcache_allgrp_rem_email_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + ItemEMail *email = ( ItemEMail * ) data; + + if( !email ) return; + if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + ItemGroup *group = ( ItemGroup * ) value; + if( group ) { + /* Remove each email address that belongs to the person from the list */ + group->listEMail = g_list_remove( group->listEMail, email ); + } + } +} + +/* +* Remove email address in address cache for specified ID. +* param: uid Object ID for person. +* eid EMail ID. +* return: EMail object, or NULL if not found. Note that object should still be freed. +*/ +ItemEMail *addrcache_person_remove_email_id( AddressCache *cache, const gchar *uid, const gchar *eid ) { + ItemEMail *email = NULL; + ItemPerson *person; + + if( eid == NULL || *eid == '\0' ) return NULL; + + person = addrcache_get_person( cache, uid ); + if( person ) { + email = addritem_person_remove_email_id( person, eid ); + if( email ) { + /* Remove email from all groups. */ + g_hash_table_foreach( cache->itemHash, addrcache_allgrp_rem_email_vis, email ); + + /* Remove email from person's address list */ + if( person->listEMail ) { + person->listEMail = g_list_remove( person->listEMail, email ); + } + /* Unlink reference to person. */ + ADDRITEM_PARENT(email) = NULL; + } + } + return email; +} + +/* +* Remove email address in address cache for specified person. +* param: person Person. +* email EMail to remove. +* return: EMail object, or NULL if not found. Note that object should still be freed. +*/ +ItemEMail *addrcache_person_remove_email( AddressCache *cache, ItemPerson *person, ItemEMail *email ) { + ItemEMail *found = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( person && email ) { + found = addritem_person_remove_email( person, email ); + if( found ) { + /* Remove email from all groups. */ + g_hash_table_foreach( cache->itemHash, addrcache_allgrp_rem_email_vis, email ); + + /* Remove email from person's address list */ + if( person->listEMail ) { + person->listEMail = g_list_remove( person->listEMail, email ); + } + /* Unlink reference to person. */ + ADDRITEM_PARENT(email) = NULL; + } + } + return found; +} + +/* +* Return link list of address items for root level folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to use the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_folder_get_address_list( AddressCache *cache, ItemFolder *folder ) { + GList *list = NULL; + GList *node = NULL; + ItemFolder *f = folder; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( ! f ) f = cache->rootFolder; + node = f->listPerson; + while( node ) { + list = g_list_append( list, node->data ); + node = g_list_next( node ); + } + node = f->listGroup; + while( node ) { + list = g_list_append( list, node->data ); + node = g_list_next( node ); + } + return list; +} + +/* +* Return link list of persons for specified folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to use the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_folder_get_person_list( AddressCache *cache, ItemFolder *folder ) { + ItemFolder *f = folder; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( ! f ) f = cache->rootFolder; + return addritem_folder_get_person_list( f ); +} + +/* +* Return link list of group items for specified folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to use the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_folder_get_group_list( AddressCache *cache, ItemFolder *folder ) { + ItemFolder *f = folder; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( ! f ) f = cache->rootFolder; + return addritem_folder_get_group_list( f ); +} + +/* +* Return link list of folder items for specified folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to used the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_folder_get_folder_list( AddressCache *cache, ItemFolder *folder ) { + GList *node = NULL; + GList *list = NULL; + ItemFolder *f = folder; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( ! f ) f = cache->rootFolder; + node = f->listFolder; + while( node ) { + list = g_list_append( list, node->data ); + node = g_list_next( node ); + } + return list; +} + +/* +* Return link list of address items for root level folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to used the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_get_address_list( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return addrcache_folder_get_address_list( cache, cache->rootFolder ); +} + +/* +* Return link list of persons for root level folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to used the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_get_person_list( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return addritem_folder_get_person_list( cache->rootFolder ); +} + +/* +* Return link list of group items in root level folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to used the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_get_group_list( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return cache->rootFolder->listGroup; +} + +/* +* Return link list of folder items in root level folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to used the +* addrcache_free_xxx() functions... this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_get_folder_list( AddressCache *cache ) { + g_return_val_if_fail( cache != NULL, NULL ); + return cache->rootFolder->listFolder; +} + +/* +* Group visitor function. +*/ +static void addrcache_get_grp_person_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + + if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + AddressCache *cache = data; + ItemGroup *group = ( ItemGroup * ) obj; + ItemPerson *person = ( ItemPerson * ) cache->tempList->data; + GList *node = group->listEMail; + while( node ) { + ItemEMail *email = ( ItemEMail * ) node->data; + if( ADDRITEM_PARENT(email) == ADDRITEM_OBJECT(person) ) { + if( ! g_list_find( cache->tempList, group ) ) { + cache->tempList = g_list_append( cache->tempList, group ); + } + } + node = g_list_next( node ); + } + } +} + +/* +* Return link list of groups which contain a reference to specified person's email +* address. +*/ +GList *addrcache_get_group_for_person( AddressCache *cache, ItemPerson *person ) { + GList *list = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + cache->tempList = NULL; + cache->tempList = g_list_append( cache->tempList, person ); + g_hash_table_foreach( cache->itemHash, addrcache_get_grp_person_vis, cache ); + cache->tempList = g_list_remove( cache->tempList, person ); + list = cache->tempList; + cache->tempList = NULL; + return list; +} + +/* +* Find root folder for specified folder. +* Enter: folder Folder to search. +* Return: root folder, or NULL if not found. +*/ +ItemFolder *addrcache_find_root_folder( ItemFolder *folder ) { + ItemFolder *item = folder; + gint count = 0; + + while( item ) { + if( item->isRoot ) break; + if( ++count > ADDRCACHE_MAX_SEARCH_COUNT ) { + item = NULL; + break; + } + item = ( ItemFolder * ) ADDRITEM_PARENT(folder); + } + return item; +} + +/* +* Get all person visitor function. +*/ +static void addrcache_get_all_persons_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + + if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) { + AddressCache *cache = data; + cache->tempList = g_list_append( cache->tempList, obj ); + } +} + +/* +* Return link list of all persons in address cache. Note that the list contains +* references to items. Do *NOT* attempt to use the addrcache_free_xxx() functions... +* this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_get_all_persons( AddressCache *cache ) { + GList *list = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + cache->tempList = NULL; + g_hash_table_foreach( cache->itemHash, addrcache_get_all_persons_vis, cache ); + list = cache->tempList; + cache->tempList = NULL; + return list; +} + +/* +* Get all groups visitor function. +*/ +static void addrcache_get_all_groups_vis( gpointer key, gpointer value, gpointer data ) { + AddrItemObject *obj = ( AddrItemObject * ) value; + + if( ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP ) { + AddressCache *cache = data; + cache->tempList = g_list_append( cache->tempList, obj ); + } +} + +/* +* Return link list of all groups in address cache. Note that the list contains +* references to items. Do *NOT* attempt to use the addrcache_free_xxx() functions... +* this will destroy the address cache data! +* Return: List of items, or NULL if none. +*/ +GList *addrcache_get_all_groups( AddressCache *cache ) { + GList *list = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + cache->tempList = NULL; + g_hash_table_foreach( cache->itemHash, addrcache_get_all_groups_vis, cache ); + list = cache->tempList; + cache->tempList = NULL; + return list; +} + +/* +* Remove folder from cache. Children are re-parented to parent folder. +* param: folder Folder to remove. +* return: Folder, or NULL if not found. Note that object should still be freed. +*/ +ItemFolder *addrcache_remove_folder( AddressCache *cache, ItemFolder *folder ) { + AddrItemObject *obj = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( folder ) { + gchar *uid = ADDRITEM_ID(folder); + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + ItemFolder *parent = ( ItemFolder * ) ADDRITEM_PARENT(folder); + GList *node; + AddrItemObject *aio; + if( ! parent ) parent = cache->rootFolder; + + /* Re-parent children in folder */ + node = folder->listFolder; + while( node ) { + aio = ( AddrItemObject * ) node->data; + parent->listFolder = g_list_append( parent->listFolder, aio ); + aio->parent = ADDRITEM_OBJECT(parent); + node = g_list_next( node ); + } + node = folder->listPerson; + while( node ) { + aio = ( AddrItemObject * ) node->data; + parent->listPerson = g_list_append( parent->listPerson, aio ); + aio->parent = ADDRITEM_OBJECT(parent); + node = g_list_next( node ); + } + node = folder->listGroup; + while( node ) { + aio = ( AddrItemObject * ) node->data; + parent->listGroup = g_list_append( parent->listGroup, aio ); + aio->parent = ADDRITEM_OBJECT(parent); + node = g_list_next( node ); + } + + /* Remove folder from parent's list and hash table */ + parent->listFolder = g_list_remove( parent->listFolder, folder ); + ADDRITEM_PARENT(folder) = NULL; + g_hash_table_remove( cache->itemHash, uid ); + return folder; + } + } + return NULL; +} + +/* +* Remove folder from cache. Children are deleted. +* param: folder Folder to remove. +* return: Folder, or NULL if not found. Note that object should still be freed. +*/ +ItemFolder *addrcache_remove_folder_delete( AddressCache *cache, ItemFolder *folder ) { + AddrItemObject *obj = NULL; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( folder ) { + gchar *uid = ADDRITEM_ID(folder); + if( uid == NULL || *uid == '\0' ) return NULL; + obj = ( AddrItemObject * ) g_hash_table_lookup( cache->itemHash, uid ); + if( obj ) { + ItemFolder *parent = ( ItemFolder * ) ADDRITEM_PARENT(folder); + if( ! parent ) parent = cache->rootFolder; + + /* Remove groups */ + while( folder->listGroup ) { + ItemGroup *item = ( ItemGroup * ) folder->listGroup->data; + item = addrcache_remove_group( cache, item ); + if( item ) { + addritem_free_item_group( item ); + item = NULL; + } + } + + while( folder->listPerson ) { + ItemPerson *item = ( ItemPerson * ) folder->listPerson->data; + item = addrcache_remove_person( cache, item ); + if( item ) { + addritem_free_item_person( item ); + item = NULL; + } + } + + /* Recursive deletion of folder */ + while( folder->listFolder ) { + ItemFolder *item = ( ItemFolder * ) folder->listFolder->data; + item = addrcache_remove_folder_delete( cache, item ); + if( item ) { + addritem_free_item_folder( item ); + item = NULL; + } + } + + /* Remove folder from parent's list and hash table */ + parent->listFolder = g_list_remove( parent->listFolder, folder ); + ADDRITEM_PARENT(folder) = NULL; + g_hash_table_remove( cache->itemHash, uid ); + return folder; + } + } + return NULL; +} + +/* +* Add person and address data to cache. +* Enter: cache Cache. +* folder Folder where to add person, or NULL for root folder. +* name Common name. +* address EMail address. +* remarks Remarks. +* Return: Person added. Do not *NOT* to use the addrbook_free_xxx() functions... +* this will destroy the address book data. +*/ +ItemPerson *addrcache_add_contact( AddressCache *cache, ItemFolder *folder, const gchar *name, + const gchar *address, const gchar *remarks ) +{ + ItemPerson *person = NULL; + ItemEMail *email = NULL; + ItemFolder *f = folder; + + g_return_val_if_fail( cache != NULL, NULL ); + + if( ! f ) f = cache->rootFolder; + + /* Create person object */ + person = addritem_create_item_person(); + addritem_person_set_common_name( person, name ); + addrcache_id_person( cache, person ); + addrcache_folder_add_person( cache, f, person ); + + /* Create email object */ + email = addritem_create_item_email(); + addritem_email_set_address( email, address ); + addritem_email_set_remarks( email, remarks ); + addrcache_id_email( cache, email ); + addritem_person_add_email( person, email ); + + return person; +} + +/* +* End of Source. +*/ diff --git a/src/addrcache.h b/src/addrcache.h new file mode 100644 index 00000000..46a0264f --- /dev/null +++ b/src/addrcache.h @@ -0,0 +1,123 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Definitions for address cache. + */ + +#ifndef __ADDRCACHE_H__ +#define __ADDRCACHE_H__ + +#include +#include +#include +#include "addritem.h" + +/* Address cache */ +typedef struct _AddressCache AddressCache; + +struct _AddressCache { + gint nextID; + gboolean dataRead; + gboolean modified; + time_t modifyTime; + GHashTable *itemHash; + GList *tempList; + ItemFolder *rootFolder; +}; + +/* Function prototypes */ +AddressCache *addrcache_create(); +ItemFolder *addrcache_get_root_folder ( AddressCache *cache ); +GList *addrcache_get_list_folder ( AddressCache *cache ); +GList *addrcache_get_list_person ( AddressCache *cache ); + +void addrcache_refresh ( AddressCache *cache ); +/* void addrcache_empty ( AddressCache *cache ); */ +void addrcache_clear ( AddressCache *cache ); +void addrcache_free ( AddressCache *cache ); +gboolean addrcache_check_file ( AddressCache *cache, gchar *path ); +gboolean addrcache_mark_file ( AddressCache *cache, gchar *path ); + +void addrcache_print_item_list ( GList *list, FILE *stream ); +void addrcache_print ( AddressCache *cache, FILE *stream ); +void addrcache_dump_hash ( AddressCache *cache, FILE *stream ); + +void addrcache_id_person ( AddressCache *cache, ItemPerson *person ); +void addrcache_id_group ( AddressCache *cache, ItemGroup *group ); +void addrcache_id_folder ( AddressCache *cache, ItemFolder *folder ); +void addrcache_id_email ( AddressCache *cache, ItemEMail *email ); +void addrcache_id_attribute ( AddressCache *cache, UserAttribute *attrib ); + +gboolean addrcache_hash_add_person ( AddressCache *cache, ItemPerson *person ); +gboolean addrcache_hash_add_group ( AddressCache *cache, ItemGroup *group ); +gboolean addrcache_hash_add_folder ( AddressCache *cache, ItemFolder *folder ); + +gboolean addrcache_folder_add_person ( AddressCache *cache, ItemFolder *folder, ItemPerson *item ); +gboolean addrcache_folder_add_folder ( AddressCache *cache, ItemFolder *folder, ItemFolder *item ); +gboolean addrcache_folder_add_group ( AddressCache *cache, ItemFolder *folder, ItemGroup *item ); + +gboolean addrcache_add_person ( AddressCache *cache, ItemPerson *person ); +gboolean addrcache_add_group ( AddressCache *cache, ItemGroup *group ); +gboolean addrcache_person_add_email ( AddressCache *cache, ItemPerson *person, ItemEMail *email ); +gboolean addrcache_group_add_email ( AddressCache *cache, ItemGroup *group, ItemEMail *email ); +gboolean addrcache_add_folder ( AddressCache *cache, ItemFolder *folder ); + +AddrItemObject *addrcache_get_object ( AddressCache *cache, const gchar *uid ); +ItemPerson *addrcache_get_person ( AddressCache *cache, const gchar *uid ); +ItemGroup *addrcache_get_group ( AddressCache *cache, const gchar *uid ); +ItemEMail *addrcache_get_email ( AddressCache *cache, const gchar *uid, const gchar *eid ); + +UserAttribute *addrcache_person_remove_attrib_id ( AddressCache *cache, const gchar *uid, + const gchar *aid ); +UserAttribute *addrcache_person_remove_attribute ( AddressCache *cache, ItemPerson *person, + UserAttribute *attrib ); + +ItemGroup *addrcache_remove_group_id ( AddressCache *cache, const gchar *uid ); +ItemGroup *addrcache_remove_group ( AddressCache *cache, ItemGroup *group ); + +ItemPerson *addrcache_remove_person_id ( AddressCache *cache, const gchar *uid ); +ItemPerson *addrcache_remove_person ( AddressCache *cache, ItemPerson *person ); +ItemEMail *addrcache_person_remove_email_id ( AddressCache *cache, const gchar *uid, const gchar *eid ); +ItemEMail *addrcache_person_remove_email ( AddressCache *cache, ItemPerson *person, ItemEMail *email ); + +GList *addrcache_folder_get_address_list ( AddressCache *cache, ItemFolder *folder ); +GList *addrcache_folder_get_person_list ( AddressCache *cache, ItemFolder *folder ); +GList *addrcache_folder_get_group_list ( AddressCache *cache, ItemFolder *folder ); +GList *addrcache_folder_get_folder_list ( AddressCache *cache, ItemFolder *folder ); + +GList *addrcache_get_address_list ( AddressCache *cache ); +GList *addrcache_get_person_list ( AddressCache *cache ); +GList *addrcache_get_group_list ( AddressCache *cache ); +GList *addrcache_get_folder_list ( AddressCache *cache ); + +GList *addrcache_get_group_for_person ( AddressCache *cache, ItemPerson *person ); + +ItemFolder *addrcache_find_root_folder ( ItemFolder *folder ); +GList *addrcache_get_all_persons ( AddressCache *cache ); +GList *addrcache_get_all_groups ( AddressCache *cache ); + +ItemFolder *addrcache_remove_folder ( AddressCache *cache, ItemFolder *folder ); +ItemFolder *addrcache_remove_folder_delete ( AddressCache *cache, ItemFolder *folder ); + +ItemPerson *addrcache_add_contact ( AddressCache *cache, ItemFolder *folder, + const gchar *name, const gchar *address, + const gchar *remarks ); + +#endif /* __ADDRCACHE_H__ */ diff --git a/src/addressadd.c b/src/addressadd.c new file mode 100644 index 00000000..dd553fce --- /dev/null +++ b/src/addressadd.c @@ -0,0 +1,401 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Add address to address book dialog. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "gtkutils.h" +#include "stock_pixmap.h" +#include "prefs_common.h" +#include "addressadd.h" +#include "addritem.h" +#include "addrbook.h" +#include "addrindex.h" +#include "manage_window.h" + +typedef struct { + AddressBookFile *book; + ItemFolder *folder; +} FolderInfo; + +static struct _AddressAdd_dlg { + GtkWidget *window; + GtkWidget *label_name; + GtkWidget *label_address; + GtkWidget *label_remarks; + GtkWidget *tree_folder; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; + FolderInfo *fiSelected; +} addressadd_dlg; + +static GdkPixmap *folderXpm; +static GdkBitmap *folderXpmMask; +static GdkPixmap *bookXpm; +static GdkBitmap *bookXpmMask; + +static gboolean addressadd_cancelled; + +static FolderInfo *addressadd_create_folderinfo( AddressBookFile *abf, ItemFolder *folder ) +{ + FolderInfo *fi = g_new0( FolderInfo, 1 ); + fi->book = abf; + fi->folder = folder; + return fi; +} + +static void addressadd_free_folderinfo( FolderInfo *fi ) { + fi->book = NULL; + fi->folder = NULL; + g_free( fi ); +} + +/* +* Edit functions. +*/ +static void addressadd_status_show( gchar *msg ) { + if( addressadd_dlg.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(addressadd_dlg.statusbar), addressadd_dlg.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(addressadd_dlg.statusbar), addressadd_dlg.status_cid, msg ); + } + } +} + +static gint addressadd_delete_event( GtkWidget *widget, GdkEventAny *event, gboolean *cancelled ) { + addressadd_cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean addressadd_key_pressed( GtkWidget *widget, GdkEventKey *event, gboolean *cancelled ) { + if (event && event->keyval == GDK_Escape) { + addressadd_cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void addressadd_ok( GtkWidget *widget, gboolean *cancelled ) { + addressadd_cancelled = FALSE; + gtk_main_quit(); +} + +static void addressadd_cancel( GtkWidget *widget, gboolean *cancelled ) { + addressadd_cancelled = TRUE; + gtk_main_quit(); +} + +static void addressadd_folder_select( GtkCTree *ctree, gint row, gint column, + GdkEvent *event, gpointer data ) +{ + addressadd_dlg.fiSelected = gtk_clist_get_row_data( GTK_CLIST(ctree), row ); +} + +static gboolean addressadd_tree_button( GtkCTree *ctree, GdkEventButton *event, gpointer data ) { + if( ! event ) return FALSE; + if( event->button == 1 ) { + /* Handle double click */ + if( event->type == GDK_2BUTTON_PRESS ) { + addressadd_cancelled = FALSE; + gtk_main_quit(); + } + } + + return FALSE; +} + +static void addressadd_create( void ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *label_name; + GtkWidget *label_addr; + GtkWidget *label_rems; + GtkWidget *tree_folder; + GtkWidget *vlbox; + GtkWidget *tree_win; + GtkWidget *hbbox; + GtkWidget *hsep; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *hsbox; + GtkWidget *statusbar; + gint top; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request( window, 300, 400 ); + gtk_container_set_border_width( GTK_CONTAINER(window), 0 ); + gtk_window_set_title( GTK_WINDOW(window), _("Add Address to Book") ); + gtk_window_set_position( GTK_WINDOW(window), GTK_WIN_POS_MOUSE ); + gtk_window_set_modal( GTK_WINDOW(window), TRUE ); + g_signal_connect( G_OBJECT(window), "delete_event", + G_CALLBACK(addressadd_delete_event), NULL ); + g_signal_connect( G_OBJECT(window), "key_press_event", + G_CALLBACK(addressadd_key_pressed), NULL ); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_container_set_border_width( GTK_CONTAINER(vbox), 0 ); + + table = gtk_table_new(3, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + /* First row */ + top = 0; + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + label_name = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), label_name, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label_name), 0, 0.5); + + /* Second row */ + top = 1; + label = gtk_label_new(_("Address")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + + label_addr = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), label_addr, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label_addr), 0, 0.5); + + /* Third row */ + top = 2; + label = gtk_label_new(_("Remarks")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + label_rems = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), label_rems, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label_rems), 0, 0.5); + + /* Address book/folder tree */ + vlbox = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox), vlbox, TRUE, TRUE, 0); + gtk_container_set_border_width( GTK_CONTAINER(vlbox), 8 ); + + tree_win = gtk_scrolled_window_new( NULL, NULL ); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(tree_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS ); + gtk_box_pack_start( GTK_BOX(vlbox), tree_win, TRUE, TRUE, 0 ); + + tree_folder = gtk_ctree_new( 1, 0 ); + gtk_container_add( GTK_CONTAINER(tree_win), tree_folder ); + gtk_clist_column_titles_show( GTK_CLIST(tree_folder) ); + gtk_clist_set_column_title( GTK_CLIST(tree_folder), 0, _( "Select Address Book Folder" ) ); + gtk_ctree_set_line_style( GTK_CTREE(tree_folder), GTK_CTREE_LINES_DOTTED ); + gtk_clist_set_selection_mode( GTK_CLIST(tree_folder), GTK_SELECTION_BROWSE ); + gtk_ctree_set_expander_style( GTK_CTREE(tree_folder), GTK_CTREE_EXPANDER_SQUARE ); + gtk_ctree_set_indent( GTK_CTREE(tree_folder), CTREE_INDENT ); + gtk_clist_set_auto_sort( GTK_CLIST(tree_folder), TRUE ); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(hbbox), 0 ); + gtk_widget_grab_default(ok_btn); + + hsep = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(vbox), hsep, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(addressadd_ok), NULL); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(addressadd_cancel), NULL); + g_signal_connect(G_OBJECT(tree_folder), "select_row", + G_CALLBACK(addressadd_folder_select), NULL); + g_signal_connect(G_OBJECT(tree_folder), "button_press_event", + G_CALLBACK(addressadd_tree_button), NULL); + + gtk_widget_show_all(vbox); + + addressadd_dlg.window = window; + addressadd_dlg.label_name = label_name; + addressadd_dlg.label_address = label_addr; + addressadd_dlg.label_remarks = label_rems; + addressadd_dlg.tree_folder = tree_folder; + addressadd_dlg.ok_btn = ok_btn; + addressadd_dlg.cancel_btn = cancel_btn; + addressadd_dlg.statusbar = statusbar; + addressadd_dlg.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Address Add" ); + + gtk_widget_show_all( window ); + + stock_pixmap_gdk( window, STOCK_PIXMAP_BOOK, &bookXpm, &bookXpmMask ); + stock_pixmap_gdk( window, STOCK_PIXMAP_DIR_OPEN, + &folderXpm, &folderXpmMask ); +} + +static void addressadd_load_folder( GtkCTreeNode *parentNode, ItemFolder *parentFolder, + FolderInfo *fiParent ) +{ + GtkCTree *tree = GTK_CTREE( addressadd_dlg.tree_folder ); + GList *list; + ItemFolder *folder; + gchar *fName; + gchar **name; + GtkCTreeNode *node; + FolderInfo *fi; + + list = parentFolder->listFolder; + while( list ) { + folder = list->data; + fName = g_strdup( ADDRITEM_NAME(folder) ); + name = &fName; + node = gtk_ctree_insert_node( tree, parentNode, NULL, name, FOLDER_SPACING, + folderXpm, folderXpmMask, folderXpm, folderXpmMask, + FALSE, TRUE ); + g_free( fName ); + fi = addressadd_create_folderinfo( fiParent->book, folder ); + gtk_ctree_node_set_row_data_full( tree, node, fi, + ( GtkDestroyNotify ) addressadd_free_folderinfo ); + addressadd_load_folder( node, folder, fi ); + list = g_list_next( list ); + } +} + +static void addressadd_load_data( AddressIndex *addrIndex ) { + AddressDataSource *ds; + GList *list, *nodeDS; + gchar **name; + gchar *dsName; + ItemFolder *rootFolder; + AddressBookFile *abf; + FolderInfo *fi; + GtkCTree *tree = GTK_CTREE( addressadd_dlg.tree_folder ); + GtkCTreeNode *node; + + gtk_clist_clear( GTK_CLIST( tree ) ); + list = addrindex_get_interface_list( addrIndex ); + while( list ) { + AddressInterface *interface = list->data; + if( interface->type == ADDR_IF_BOOK ) { + nodeDS = interface->listSource; + while( nodeDS ) { + ds = nodeDS->data; + dsName = g_strdup( addrindex_ds_get_name( ds ) ); + + /* Read address book */ + if( ! addrindex_ds_get_read_flag( ds ) ) { + addrindex_ds_read_data( ds ); + } + + /* Add node for address book */ + abf = ds->rawDataSource; + name = &dsName; + node = gtk_ctree_insert_node( tree, NULL, NULL, + name, FOLDER_SPACING, bookXpm, + bookXpmMask, bookXpm, bookXpmMask, + FALSE, TRUE ); + g_free( dsName ); + + fi = addressadd_create_folderinfo( abf, NULL ); + gtk_ctree_node_set_row_data_full( tree, node, fi, + ( GtkDestroyNotify ) addressadd_free_folderinfo ); + + rootFolder = addrindex_ds_get_root_folder( ds ); + addressadd_load_folder( node, rootFolder, fi ); + + nodeDS = g_list_next( nodeDS ); + } + } + list = g_list_next( list ); + } +} + +gboolean addressadd_selection( AddressIndex *addrIndex, const gchar *name, const gchar *address, const gchar *remarks ) { + gboolean retVal = FALSE; + ItemPerson *person = NULL; + + addressadd_cancelled = FALSE; + if( ! addressadd_dlg.window ) addressadd_create(); + gtk_widget_grab_focus(addressadd_dlg.ok_btn); + gtk_widget_show(addressadd_dlg.window); + manage_window_set_transient(GTK_WINDOW(addressadd_dlg.window)); + + addressadd_dlg.fiSelected = NULL; + addressadd_status_show( "" ); + addressadd_load_data( addrIndex ); + gtk_clist_select_row( GTK_CLIST( addressadd_dlg.tree_folder ), 0, 0 ); + gtk_widget_show(addressadd_dlg.window); + + gtk_label_set_text( GTK_LABEL(addressadd_dlg.label_name ), "" ); + gtk_label_set_text( GTK_LABEL(addressadd_dlg.label_address ), "" ); + gtk_label_set_text( GTK_LABEL(addressadd_dlg.label_remarks ), "" ); + if( name ) + gtk_label_set_text( GTK_LABEL(addressadd_dlg.label_name ), name ); + if( address ) + gtk_label_set_text( GTK_LABEL(addressadd_dlg.label_address ), address ); + if( remarks ) + gtk_label_set_text( GTK_LABEL(addressadd_dlg.label_remarks ), remarks ); + + gtk_main(); + gtk_widget_hide( addressadd_dlg.window ); + + if( ! addressadd_cancelled ) { + if( addressadd_dlg.fiSelected ) { + FolderInfo *fi = addressadd_dlg.fiSelected; + person = addrbook_add_contact( fi->book, fi->folder, name, address, remarks ); + if( person ) retVal = TRUE; + } + } + + gtk_clist_clear( GTK_CLIST( addressadd_dlg.tree_folder ) ); + + return retVal; +} + +/* +* End of Source. +*/ + diff --git a/src/addressadd.h b/src/addressadd.h new file mode 100644 index 00000000..9206bf98 --- /dev/null +++ b/src/addressadd.h @@ -0,0 +1,31 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Add address to address book dialog. + */ + +#ifndef __ADDRESS_ADD_H__ +#define __ADDRESS_ADD_H__ + +#include "addrindex.h" + +gboolean addressadd_selection( AddressIndex *addrIndex, const gchar *name, const gchar *address, const gchar *remarks ); + +#endif /* __ADDRESS_ADD_H__ */ diff --git a/src/addressbook.c b/src/addressbook.c new file mode 100644 index 00000000..c0d717e1 --- /dev/null +++ b/src/addressbook.c @@ -0,0 +1,3503 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "addressbook.h" +#include "manage_window.h" +#include "prefs_common.h" +#include "alertpanel.h" +#include "inputdialog.h" +#include "menu.h" +#include "stock_pixmap.h" +#include "xml.h" +#include "prefs.h" +#include "procmime.h" +#include "utils.h" +#include "gtkutils.h" +#include "codeconv.h" +#include "about.h" +#include "addr_compl.h" + +#include "mgutils.h" +#include "addressitem.h" +#include "addritem.h" +#include "addrcache.h" +#include "addrbook.h" +#include "addrindex.h" +#include "addressadd.h" +#include "vcard.h" +#include "editvcard.h" +#include "editgroup.h" +#include "editaddress.h" +#include "editbook.h" +#include "ldif.h" +#include "importldif.h" + +#ifdef USE_JPILOT +#include "jpilot.h" +#include "editjpilot.h" +#endif + +#ifdef USE_LDAP +#include +#include "syldap.h" +#include "editldap.h" + +#define ADDRESSBOOK_LDAP_BUSYMSG "Busy" +#endif + +typedef enum +{ + COL_NAME = 0, + COL_ADDRESS = 1, + COL_REMARKS = 2 +} AddressBookColumnPos; + +#define N_COLS 3 +#define COL_NAME_WIDTH 164 +#define COL_ADDRESS_WIDTH 156 + +#define COL_FOLDER_WIDTH 170 +#define ADDRESSBOOK_WIDTH 640 +#define ADDRESSBOOK_HEIGHT 360 + +#define ADDRESSBOOK_MSGBUF_SIZE 2048 + +static GdkPixmap *folderxpm; +static GdkBitmap *folderxpmmask; +static GdkPixmap *folderopenxpm; +static GdkBitmap *folderopenxpmmask; +static GdkPixmap *groupxpm; +static GdkBitmap *groupxpmmask; +static GdkPixmap *interfacexpm; +static GdkBitmap *interfacexpmmask; +static GdkPixmap *bookxpm; +static GdkBitmap *bookxpmmask; +static GdkPixmap *addressxpm; +static GdkBitmap *addressxpmmask; +static GdkPixmap *vcardxpm; +static GdkBitmap *vcardxpmmask; +static GdkPixmap *jpilotxpm; +static GdkBitmap *jpilotxpmmask; +static GdkPixmap *categoryxpm; +static GdkBitmap *categoryxpmmask; +static GdkPixmap *ldapxpm; +static GdkBitmap *ldapxpmmask; + +/* Message buffer */ +static gchar addressbook_msgbuf[ ADDRESSBOOK_MSGBUF_SIZE ]; + +/* Address list selection */ +static GList *_addressListSelection_ = NULL; + +/* Address index file and interfaces */ +static AddressIndex *_addressIndex_ = NULL; +static GList *_addressInterfaceList_ = NULL; +static GList *_addressIFaceSelection_ = NULL; +#define ADDRESSBOOK_IFACE_SELECTION "1/y,3/y,4/y,2/n" + +static AddressBook_win addrbook; + +static GHashTable *_addressBookTypeHash_ = NULL; +static GList *_addressBookTypeList_ = NULL; + +static void addressbook_create (void); +static gint addressbook_close (void); +static void addressbook_button_set_sensitive (void); + +/* callback functions */ +static void addressbook_del_clicked (GtkButton *button, + gpointer data); +static void addressbook_reg_clicked (GtkButton *button, + gpointer data); +static void addressbook_to_clicked (GtkButton *button, + gpointer data); +static void addressbook_lup_clicked (GtkButton *button, + gpointer data); + +static void addressbook_tree_selected (GtkCTree *ctree, + GtkCTreeNode *node, + gint column, + gpointer data); +static void addressbook_list_selected (GtkCList *clist, + gint row, + gint column, + GdkEvent *event, + gpointer data); +static void addressbook_list_row_selected (GtkCTree *clist, + GtkCTreeNode *node, + gint column, + gpointer data); +static void addressbook_list_row_unselected (GtkCTree *clist, + GtkCTreeNode *node, + gint column, + gpointer data); +static void addressbook_person_expand_node (GtkCTree *ctree, + GList *node, + gpointer *data ); +static void addressbook_person_collapse_node (GtkCTree *ctree, + GList *node, + gpointer *data ); +static void addressbook_entry_gotfocus (GtkWidget *widget); + +#if 0 +static void addressbook_entry_changed (GtkWidget *widget); +#endif + +static gboolean addressbook_list_button_pressed (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static gboolean addressbook_list_button_released(GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static gboolean addressbook_tree_button_pressed (GtkWidget *ctree, + GdkEventButton *event, + gpointer data); +static gboolean addressbook_tree_button_released(GtkWidget *ctree, + GdkEventButton *event, + gpointer data); +static void addressbook_popup_close (GtkMenuShell *menu_shell, + gpointer data); + +static void addressbook_new_folder_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_new_group_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_treenode_edit_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_treenode_delete_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void addressbook_change_node_name (GtkCTreeNode *node, + const gchar *name); + +static void addressbook_new_address_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_edit_address_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_delete_address_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void close_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_file_save_cb (gpointer data, + guint action, + GtkWidget *widget); + +/* Data source edit stuff */ +static void addressbook_new_book_cb (gpointer data, + guint action, + GtkWidget *widget); +static void addressbook_new_vcard_cb (gpointer data, + guint action, + GtkWidget *widget); + +#ifdef USE_JPILOT +static void addressbook_new_jpilot_cb (gpointer data, + guint action, + GtkWidget *widget); +#endif + +#ifdef USE_LDAP +static void addressbook_new_ldap_cb (gpointer data, + guint action, + GtkWidget *widget); +#endif + +static void addressbook_set_clist (AddressObject *obj); + +static void addressbook_load_tree (void); +void addressbook_read_file (void); + +static GtkCTreeNode *addressbook_add_object (GtkCTreeNode *node, + AddressObject *obj); +static AddressDataSource *addressbook_find_datasource + (GtkCTreeNode *node ); + +static AddressBookFile *addressbook_get_book_file(); + +static GtkCTreeNode *addressbook_node_add_folder + (GtkCTreeNode *node, + AddressDataSource *ds, + ItemFolder *itemFolder, + AddressObjectType otype); +static GtkCTreeNode *addressbook_node_add_group (GtkCTreeNode *node, + AddressDataSource *ds, + ItemGroup *itemGroup); +/* static GtkCTreeNode *addressbook_node_add_category */ +/* (GtkCTreeNode *node, */ +/* AddressDataSource *ds, */ +/* ItemFolder *itemFolder); */ +static void addressbook_tree_remove_children (GtkCTree *ctree, + GtkCTreeNode *parent); +static void addressbook_move_nodes_up (GtkCTree *ctree, + GtkCTreeNode *node); +static GtkCTreeNode *addressbook_find_group_node (GtkCTreeNode *parent, + ItemGroup *group); + +/* static void addressbook_delete_object (AddressObject *obj); */ + +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static gint addressbook_list_compare_func (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +/* static gint addressbook_obj_name_compare (gconstpointer a, */ +/* gconstpointer b); */ + +/* static void addressbook_book_show_message (AddressBookFile *book); */ +/* static void addressbook_vcard_show_message (VCardFile *vcf); */ +#ifdef USE_JPILOT +/* static void addressbook_jpilot_show_message (JPilotFile *jpf); */ +#endif +#ifdef USE_LDAP +static void addressbook_ldap_show_message (SyldapServer *server); +#endif + +/* LUT's and IF stuff */ +static void addressbook_free_adapter (GtkCTreeNode *node); +static void addressbook_free_child_adapters (GtkCTreeNode *node); +AddressTypeControlItem *addrbookctl_lookup (gint ot); +AddressTypeControlItem *addrbookctl_lookup_iface(AddressIfType ifType); + +void addrbookctl_build_map (GtkWidget *window); +void addrbookctl_build_iflist (void); +AdapterInterface *addrbookctl_find_interface (AddressIfType ifType); +void addrbookctl_build_ifselect (void); + +static void addrbookctl_free_interface (AdapterInterface *adapter); +static void addrbookctl_free_datasource (AdapterDSource *adapter); +static void addrbookctl_free_folder (AdapterFolder *adapter); +static void addrbookctl_free_group (AdapterGroup *adapter); + +static void addressbook_list_select_clear (void); +static void addressbook_list_select_add (AddressObject *obj); +static void addressbook_list_select_remove (AddressObject *obj); + +static void addressbook_import_ldif_cb (void); + +static GtkItemFactoryEntry addressbook_entries[] = +{ + {N_("/_File"), NULL, NULL, 0, ""}, + {N_("/_File/New _Book"), "B", addressbook_new_book_cb, 0, NULL}, + {N_("/_File/New _vCard"), "D", addressbook_new_vcard_cb, 0, NULL}, +#ifdef USE_JPILOT + {N_("/_File/New _JPilot"), "J", addressbook_new_jpilot_cb, 0, NULL}, +#endif +#ifdef USE_LDAP + {N_("/_File/New _Server"), "S", addressbook_new_ldap_cb, 0, NULL}, +#endif + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Edit"), NULL, addressbook_treenode_edit_cb, 0, NULL}, + {N_("/_File/_Delete"), NULL, addressbook_treenode_delete_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Save"), "S", addressbook_file_save_cb, 0, NULL}, + {N_("/_File/_Close"), "W", close_cb, 0, NULL}, + {N_("/_Address"), NULL, NULL, 0, ""}, + {N_("/_Address/New _Address"), "N", addressbook_new_address_cb, 0, NULL}, + {N_("/_Address/New _Group"), "G", addressbook_new_group_cb, 0, NULL}, + {N_("/_Address/New _Folder"), "R", addressbook_new_folder_cb, 0, NULL}, + {N_("/_Address/---"), NULL, NULL, 0, ""}, + {N_("/_Address/_Edit"), "Return", addressbook_edit_address_cb, 0, NULL}, + {N_("/_Address/_Delete"), NULL, addressbook_delete_address_cb, 0, NULL}, + {N_("/_Tools"), NULL, NULL, 0, ""}, + {N_("/_Tools/Import _LDIF file"), NULL, addressbook_import_ldif_cb, 0, NULL}, + {N_("/_Help"), NULL, NULL, 0, ""}, + {N_("/_Help/_About"), NULL, about_show, 0, NULL} +}; + +/* New options to be added. */ +/* + {N_("/_Edit"), NULL, NULL, 0, ""}, + {N_("/_Edit/C_ut"), "X", NULL, 0, NULL}, + {N_("/_Edit/_Copy"), "C", NULL, 0, NULL}, + {N_("/_Edit/_Paste"), "V", NULL, 0, NULL}, + {N_("/_Tools"), NULL, NULL, 0, ""}, + {N_("/_Tools/Import _Mozilla"), NULL, NULL, 0, NULL}, + {N_("/_Tools/Import _vCard"), NULL, NULL, 0, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/Export _LDIF file"), NULL, NULL, 0, NULL}, + {N_("/_Tools/Export v_Card"), NULL, NULL, 0, NULL}, +*/ + +static GtkItemFactoryEntry addressbook_tree_popup_entries[] = +{ + {N_("/New _Address"), NULL, addressbook_new_address_cb, 0, NULL}, + {N_("/New _Group"), NULL, addressbook_new_group_cb, 0, NULL}, + {N_("/New _Folder"), NULL, addressbook_new_folder_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Edit"), NULL, addressbook_treenode_edit_cb, 0, NULL}, + {N_("/_Delete"), NULL, addressbook_treenode_delete_cb, 0, NULL} +}; + +static GtkItemFactoryEntry addressbook_list_popup_entries[] = +{ + {N_("/New _Address"), NULL, addressbook_new_address_cb, 0, NULL}, + {N_("/New _Group"), NULL, addressbook_new_group_cb, 0, NULL}, + {N_("/New _Folder"), NULL, addressbook_new_folder_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Edit"), NULL, addressbook_edit_address_cb, 0, NULL}, + {N_("/_Delete"), NULL, addressbook_delete_address_cb, 0, NULL} +}; + +void addressbook_open(Compose *target) +{ + if (!addrbook.window) { + addressbook_read_file(); + addressbook_create(); + addressbook_load_tree(); + gtk_ctree_select(GTK_CTREE(addrbook.ctree), + GTK_CTREE_NODE(GTK_CLIST(addrbook.ctree)->row_list)); + } else + gtk_widget_hide(addrbook.window); + + gtk_widget_show_all(addrbook.window); + + addressbook_set_target_compose(target); +} + +void addressbook_set_target_compose(Compose *target) +{ + addrbook.target_compose = target; + + addressbook_button_set_sensitive(); +} + +Compose *addressbook_get_target_compose(void) +{ + return addrbook.target_compose; +} + +void addressbook_refresh(void) +{ + if (addrbook.window) { + if (addrbook.treeSelected) { + gtk_ctree_select(GTK_CTREE(addrbook.ctree), + addrbook.treeSelected); + } + } + addressbook_export_to_file(); +} + +/* +* Create the address book widgets. The address book contains two CTree widgets: the +* address index tree on the left and the address list on the right. +* +* The address index tree displays a hierarchy of interfaces and groups. Each node in +* this tree is linked to an address Adapter. Adapters have been created for interfaces, +* data sources and folder objects. +* +* The address list displays group, person and email objects. These items are linked +* directly to ItemGroup, ItemPerson and ItemEMail objects inside the address book data +* sources. +* +* In the tradition of MVC architecture, the data stores have been separated from the +* GUI components. The addrindex.c file provides the interface to all data stores. +*/ +static void addressbook_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *vbox2; + GtkWidget *ctree_swin; + GtkWidget *ctree; + GtkWidget *clist_vbox; + GtkWidget *clist_swin; + GtkWidget *clist; + GtkWidget *paned; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *statusbar; + GtkWidget *hbbox; + GtkWidget *hsbox; + GtkWidget *del_btn; + GtkWidget *reg_btn; + GtkWidget *lup_btn; + GtkWidget *to_btn; + GtkWidget *cc_btn; + GtkWidget *bcc_btn; + GtkWidget *tree_popup; + GtkWidget *list_popup; + GtkItemFactory *tree_factory; + GtkItemFactory *list_factory; + GtkItemFactory *menu_factory; + gint n_entries; + GList *nodeIf; + + gchar *titles[N_COLS]; + gchar *text; + gint i; + + debug_print("Creating addressbook window...\n"); + + titles[COL_NAME] = _("Name"); + titles[COL_ADDRESS] = _("E-Mail address"); + titles[COL_REMARKS] = _("Remarks"); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Address book")); + gtk_widget_set_size_request + (window, ADDRESSBOOK_WIDTH, ADDRESSBOOK_HEIGHT); + gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, TRUE); + gtk_window_set_wmclass(GTK_WINDOW(window), "addressbook", "Sylpheed"); + gtk_widget_realize(window); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(addressbook_close), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + n_entries = sizeof(addressbook_entries) / + sizeof(addressbook_entries[0]); + menubar = menubar_create(window, addressbook_entries, n_entries, + "", NULL); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + menu_factory = gtk_item_factory_from_widget(menubar); + + vbox2 = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), BORDER_WIDTH); + gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0); + + ctree_swin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ctree_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(ctree_swin, COL_FOLDER_WIDTH + 40, -1); + + /* Address index */ + ctree = gtk_ctree_new(1, 0); + gtk_container_add(GTK_CONTAINER(ctree_swin), ctree); + gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE); + gtk_clist_set_column_width(GTK_CLIST(ctree), 0, COL_FOLDER_WIDTH); + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED); + gtk_ctree_set_expander_style(GTK_CTREE(ctree), + GTK_CTREE_EXPANDER_SQUARE); + gtk_ctree_set_indent(GTK_CTREE(ctree), CTREE_INDENT); + gtk_clist_set_compare_func(GTK_CLIST(ctree), + addressbook_list_compare_func); + + g_signal_connect(G_OBJECT(ctree), "tree_select_row", + G_CALLBACK(addressbook_tree_selected), NULL); + g_signal_connect(G_OBJECT(ctree), "button_press_event", + G_CALLBACK(addressbook_tree_button_pressed), + NULL); + g_signal_connect(G_OBJECT(ctree), "button_release_event", + G_CALLBACK(addressbook_tree_button_released), + NULL); + + clist_vbox = gtk_vbox_new(FALSE, 4); + + clist_swin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(clist_vbox), clist_swin, TRUE, TRUE, 0); + + /* Address list */ + clist = gtk_ctree_new_with_titles(N_COLS, 0, titles); + gtk_container_add(GTK_CONTAINER(clist_swin), clist); + gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_EXTENDED); + gtk_ctree_set_line_style(GTK_CTREE(clist), GTK_CTREE_LINES_NONE); + gtk_ctree_set_expander_style(GTK_CTREE(clist), GTK_CTREE_EXPANDER_SQUARE); + gtk_ctree_set_indent(GTK_CTREE(clist), CTREE_INDENT); + gtk_clist_set_column_width(GTK_CLIST(clist), COL_NAME, + COL_NAME_WIDTH); + gtk_clist_set_column_width(GTK_CLIST(clist), COL_ADDRESS, + COL_ADDRESS_WIDTH); + gtk_clist_set_compare_func(GTK_CLIST(clist), + addressbook_list_compare_func); + + for (i = 0; i < N_COLS; i++) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[i].button, + GTK_CAN_FOCUS); + + g_signal_connect(G_OBJECT(clist), "tree_select_row", + G_CALLBACK(addressbook_list_row_selected), NULL); + g_signal_connect(G_OBJECT(clist), "tree_unselect_row", + G_CALLBACK(addressbook_list_row_unselected), NULL); + g_signal_connect(G_OBJECT(clist), "button_press_event", + G_CALLBACK(addressbook_list_button_pressed), + NULL); + g_signal_connect(G_OBJECT(clist), "button_release_event", + G_CALLBACK(addressbook_list_button_released), + NULL); + g_signal_connect(G_OBJECT(clist), "select_row", + G_CALLBACK(addressbook_list_selected), NULL); + g_signal_connect(G_OBJECT(clist), "tree_expand", + G_CALLBACK(addressbook_person_expand_node), NULL); + g_signal_connect(G_OBJECT(clist), "tree_collapse", + G_CALLBACK(addressbook_person_collapse_node), NULL); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(clist_vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new(_("Name:")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); + + address_completion_register_entry(GTK_ENTRY(entry)); + g_signal_connect(G_OBJECT(entry), "focus_in_event", + G_CALLBACK(addressbook_entry_gotfocus), NULL); + +#if 0 + g_signal_connect(G_OBJECT(entry), "changed", + G_CALLBACK(addressbook_entry_changed), NULL); +#endif + + paned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0); + gtk_paned_add1(GTK_PANED(paned), ctree_swin); + gtk_paned_add2(GTK_PANED(paned), clist_vbox); + + /* Status bar */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + hbbox = gtk_hbutton_box_new(); + gtk_button_box_set_layout(GTK_BUTTON_BOX(hbbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing(GTK_BOX(hbbox), 2); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + + del_btn = gtk_button_new_with_label(_("Delete")); + GTK_WIDGET_SET_FLAGS(del_btn, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(hbbox), del_btn, TRUE, TRUE, 0); + reg_btn = gtk_button_new_with_label(_("Add")); + GTK_WIDGET_SET_FLAGS(reg_btn, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(hbbox), reg_btn, TRUE, TRUE, 0); + lup_btn = gtk_button_new_with_label(_("Lookup")); + GTK_WIDGET_SET_FLAGS(lup_btn, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(hbbox), lup_btn, TRUE, TRUE, 0); + + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(addressbook_del_clicked), NULL); + g_signal_connect(G_OBJECT(reg_btn), "clicked", + G_CALLBACK(addressbook_reg_clicked), NULL); + g_signal_connect(G_OBJECT(lup_btn), "clicked", + G_CALLBACK(addressbook_lup_clicked), NULL); + + to_btn = gtk_button_new_with_label + (prefs_common.trans_hdr ? _("To:") : "To:"); + GTK_WIDGET_SET_FLAGS(to_btn, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(hbbox), to_btn, TRUE, TRUE, 0); + cc_btn = gtk_button_new_with_label + (prefs_common.trans_hdr ? _("Cc:") : "Cc:"); + GTK_WIDGET_SET_FLAGS(cc_btn, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(hbbox), cc_btn, TRUE, TRUE, 0); + bcc_btn = gtk_button_new_with_label + (prefs_common.trans_hdr ? _("Bcc:") : "Bcc:"); + GTK_WIDGET_SET_FLAGS(bcc_btn, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(hbbox), bcc_btn, TRUE, TRUE, 0); + + g_signal_connect(G_OBJECT(to_btn), "clicked", + G_CALLBACK(addressbook_to_clicked), + GINT_TO_POINTER(COMPOSE_ENTRY_TO)); + g_signal_connect(G_OBJECT(cc_btn), "clicked", + G_CALLBACK(addressbook_to_clicked), + GINT_TO_POINTER(COMPOSE_ENTRY_CC)); + g_signal_connect(G_OBJECT(bcc_btn), "clicked", + G_CALLBACK(addressbook_to_clicked), + GINT_TO_POINTER(COMPOSE_ENTRY_BCC)); + + /* Build icons for interface */ + stock_pixmap_gdk( window, STOCK_PIXMAP_INTERFACE, + &interfacexpm, &interfacexpmmask ); + + /* Build control tables */ + addrbookctl_build_map(window); + addrbookctl_build_iflist(); + addrbookctl_build_ifselect(); + + /* Add each interface into the tree as a root level folder */ + nodeIf = _addressInterfaceList_; + while( nodeIf ) { + AdapterInterface *adapter = nodeIf->data; + AddressInterface *iface = adapter->interface; + nodeIf = g_list_next(nodeIf); + + if(iface->useInterface) { + AddressTypeControlItem *atci = adapter->atci; + text = atci->displayName; + adapter->treeNode = + gtk_ctree_insert_node( GTK_CTREE(ctree), + NULL, NULL, &text, FOLDER_SPACING, + interfacexpm, interfacexpmmask, + interfacexpm, interfacexpmmask, + FALSE, FALSE ); + menu_set_sensitive( menu_factory, atci->menuCommand, adapter->haveLibrary ); + gtk_ctree_node_set_row_data( GTK_CTREE(ctree), adapter->treeNode, adapter ); + } + } + + /* Popup menu */ + n_entries = sizeof(addressbook_tree_popup_entries) / + sizeof(addressbook_tree_popup_entries[0]); + tree_popup = menu_create_items(addressbook_tree_popup_entries, + n_entries, + "", &tree_factory, + NULL); + g_signal_connect(G_OBJECT(tree_popup), "selection_done", + G_CALLBACK(addressbook_popup_close), NULL); + n_entries = sizeof(addressbook_list_popup_entries) / + sizeof(addressbook_list_popup_entries[0]); + list_popup = menu_create_items(addressbook_list_popup_entries, + n_entries, + "", &list_factory, + NULL); + + addrbook.window = window; + addrbook.menubar = menubar; + addrbook.ctree = ctree; + addrbook.clist = clist; + addrbook.entry = entry; + addrbook.statusbar = statusbar; + addrbook.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Addressbook Window" ); + + addrbook.del_btn = del_btn; + addrbook.reg_btn = reg_btn; + addrbook.lup_btn = lup_btn; + addrbook.to_btn = to_btn; + addrbook.cc_btn = cc_btn; + addrbook.bcc_btn = bcc_btn; + + addrbook.tree_popup = tree_popup; + addrbook.list_popup = list_popup; + addrbook.tree_factory = tree_factory; + addrbook.list_factory = list_factory; + addrbook.menu_factory = menu_factory; + + addrbook.listSelected = NULL; + address_completion_start(window); + gtk_widget_show_all(window); + +} + +static gint addressbook_close(void) +{ + gtk_widget_hide(addrbook.window); + addressbook_export_to_file(); + return TRUE; +} + +static void addressbook_status_show( gchar *msg ) { + if( addrbook.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(addrbook.statusbar), addrbook.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(addrbook.statusbar), addrbook.status_cid, msg ); + } + } +} + +static void addressbook_ds_show_message( AddressDataSource *ds ) { + gint retVal; + gchar *name; + *addressbook_msgbuf = '\0'; + if( ds ) { + name = addrindex_ds_get_name( ds ); + retVal = addrindex_ds_get_status_code( ds ); + if( retVal == MGU_SUCCESS ) { + if( ds ) { + sprintf( addressbook_msgbuf, "%s", name ); + } + } + else { + if( ds == NULL ) { + sprintf( addressbook_msgbuf, "%s", mgu_error2string( retVal ) ); + } + else { + sprintf( addressbook_msgbuf, "%s: %s", name, mgu_error2string( retVal ) ); + } + } + } + addressbook_status_show( addressbook_msgbuf ); +} + +static void addressbook_button_set_sensitive(void) +{ + gboolean to_sens = FALSE; + gboolean cc_sens = FALSE; + gboolean bcc_sens = FALSE; + + if (!addrbook.window) return; + + if (addrbook.target_compose) { + to_sens = TRUE; + cc_sens = TRUE; + if (addrbook.target_compose->use_bcc) + bcc_sens = TRUE; + } + + gtk_widget_set_sensitive(addrbook.to_btn, to_sens); + gtk_widget_set_sensitive(addrbook.cc_btn, cc_sens); + gtk_widget_set_sensitive(addrbook.bcc_btn, bcc_sens); +} + +/* +* Delete one or more objects from address list. +*/ +static void addressbook_del_clicked(GtkButton *button, gpointer data) +{ + GtkCTree *clist = GTK_CTREE(addrbook.clist); + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + AddressObject *pobj, *obj; + AdapterDSource *ads = NULL; + GtkCTreeNode *nodeList; + gboolean procFlag; + AlertValue aval; + AddressBookFile *abf = NULL; + AddressDataSource *ds = NULL; + + pobj = gtk_ctree_node_get_row_data(ctree, addrbook.opened ); + g_return_if_fail(pobj != NULL); + + nodeList = addrbook.listSelected; + obj = gtk_ctree_node_get_row_data( clist, nodeList ); + if( obj == NULL) return; + ds = addressbook_find_datasource( addrbook.treeSelected ); + if( ds == NULL ) return; + + procFlag = FALSE; + if( pobj->type == ADDR_DATASOURCE ) { + ads = ADAPTER_DSOURCE(pobj); + if( ads->subType == ADDR_BOOK ) procFlag = TRUE; + } + else if( pobj->type == ADDR_ITEM_FOLDER ) { + procFlag = TRUE; + } + else if( pobj->type == ADDR_ITEM_GROUP ) { + procFlag = TRUE; + } + if( ! procFlag ) return; + abf = ds->rawDataSource; + if( abf == NULL ) return; + + /* Confirm deletion */ + aval = alertpanel( _("Delete address(es)"), + _("Really delete the address(es)?"), + _("Yes"), _("No"), NULL ); + if( aval != G_ALERTDEFAULT ) return; + + /* Process deletions */ + if( pobj->type == ADDR_DATASOURCE || pobj->type == ADDR_ITEM_FOLDER ) { + /* Items inside folders */ + GList *node; + node = _addressListSelection_; + while( node ) { + AddrItemObject *aio = node->data; + node = g_list_next( node ); + if( aio->type == ADDR_ITEM_GROUP ) { + ItemGroup *item = ( ItemGroup * ) aio; + GtkCTreeNode *nd = NULL; + + nd = addressbook_find_group_node( addrbook.opened, item ); + item = addrbook_remove_group( abf, item ); + if( item ) { + addritem_free_item_group( item ); + item = NULL; + } + /* Remove group from parent node */ + gtk_ctree_remove_node( ctree, nd ); + } + else if( aio->type == ADDR_ITEM_PERSON ) { + ItemPerson *item = ( ItemPerson * ) aio; + item = addrbook_remove_person( abf, item ); + if( item ) { + addritem_free_item_person( item ); + item = NULL; + } + } + else if( aio->type == ADDR_ITEM_EMAIL ) { + ItemEMail *item = ( ItemEMail * ) aio; + ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(item); + item = addrbook_person_remove_email( abf, person, item ); + if( item ) { + addritem_free_item_email( item ); + item = NULL; + } + } + } + addressbook_list_select_clear(); + gtk_ctree_select( ctree, addrbook.opened); + return; + } + else if( pobj->type == ADDR_ITEM_GROUP ) { + /* Items inside groups */ + GList *node; + node = _addressListSelection_; + while( node ) { + AddrItemObject *aio = node->data; + node = g_list_next( node ); + if( aio->type == ADDR_ITEM_EMAIL ) { + ItemEMail *item = ( ItemEMail * ) aio; + ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(item); + item = addrbook_person_remove_email( abf, person, item ); + if( item ) { + addritem_print_item_email( item, stdout ); + addritem_free_item_email( item ); + item = NULL; + } + } + } + addressbook_list_select_clear(); + gtk_ctree_select( ctree, addrbook.opened); + return; + } + + gtk_ctree_node_set_row_data( clist, nodeList, NULL ); + gtk_ctree_remove_node( clist, nodeList ); + addressbook_list_select_remove( obj ); + +} + +static void addressbook_reg_clicked(GtkButton *button, gpointer data) +{ + addressbook_new_address_cb( NULL, 0, NULL ); +} + +gchar *addressbook_format_address( AddressObject * obj ) { + gchar *buf = NULL; + gchar *name = NULL; + gchar *address = NULL; + + if( obj->type == ADDR_ITEM_EMAIL ) { + ItemPerson *person = NULL; + ItemEMail *email = ( ItemEMail * ) obj; + + person = ( ItemPerson * ) ADDRITEM_PARENT(email); + if( email->address ) { + if( ADDRITEM_NAME(email) ) { + name = ADDRITEM_NAME(email); + if( *name == '\0' ) { + name = ADDRITEM_NAME(person); + } + } + else if( ADDRITEM_NAME(person) ) { + name = ADDRITEM_NAME(person); + } + else { + buf = g_strdup( email->address ); + } + address = email->address; + } + } + else if( obj->type == ADDR_ITEM_PERSON ) { + ItemPerson *person = ( ItemPerson * ) obj; + GList *node = person->listEMail; + + name = ADDRITEM_NAME(person); + if( node ) { + ItemEMail *email = ( ItemEMail * ) node->data; + address = email->address; + } + } + if( address ) { + if( name && name[0] != '\0' ) { + if( name[0] != '"' && strpbrk( name, ",.[]<>" ) != NULL ) + buf = g_strdup_printf( "\"%s\" <%s>", name, address ); + else + buf = g_strdup_printf( "%s <%s>", name, address ); + } + else { + buf = g_strdup( address ); + } + } + + return buf; +} + +static void addressbook_to_clicked(GtkButton *button, gpointer data) +{ + GList *node = _addressListSelection_; + if (!addrbook.target_compose) return; + while( node ) { + AddressObject *obj = node->data; + Compose *compose = addrbook.target_compose; + node = g_list_next( node ); + if( obj->type == ADDR_ITEM_PERSON || obj->type == ADDR_ITEM_EMAIL ) { + gchar *addr = addressbook_format_address( obj ); + compose_entry_append( compose, addr, (ComposeEntryType) data ); + g_free( addr ); + addr = NULL; + } + else if( obj->type == ADDR_ITEM_GROUP ) { + ItemGroup *group = ( ItemGroup * ) obj; + GList *nodeMail = group->listEMail; + while( nodeMail ) { + ItemEMail *email = nodeMail->data; + gchar *addr = addressbook_format_address( ( AddressObject * ) email ); + compose_entry_append( compose, addr, (ComposeEntryType) data ); + g_free( addr ); + nodeMail = g_list_next( nodeMail ); + } + } + } +} + +static void addressbook_menubar_set_sensitive( gboolean sensitive ) { + menu_set_sensitive( addrbook.menu_factory, "/File/New Book", sensitive ); + menu_set_sensitive( addrbook.menu_factory, "/File/New vCard", sensitive ); +#ifdef USE_JPILOT + menu_set_sensitive( addrbook.menu_factory, "/File/New JPilot", sensitive ); +#endif +#ifdef USE_LDAP + menu_set_sensitive( addrbook.menu_factory, "/File/New Server", sensitive ); +#endif + menu_set_sensitive( addrbook.menu_factory, "/File/Edit", sensitive ); + menu_set_sensitive( addrbook.menu_factory, "/File/Delete", sensitive ); + + menu_set_sensitive( addrbook.menu_factory, "/Address/New Address", sensitive ); + menu_set_sensitive( addrbook.menu_factory, "/Address/New Group", sensitive ); + menu_set_sensitive( addrbook.menu_factory, "/Address/New Folder", sensitive ); + gtk_widget_set_sensitive( addrbook.reg_btn, sensitive ); + gtk_widget_set_sensitive( addrbook.del_btn, sensitive ); +} + +static void addressbook_menuitem_set_sensitive( AddressObject *obj, GtkCTreeNode *node ) { + gboolean canEdit = FALSE; + gboolean canAdd = FALSE; + gboolean canEditTr = TRUE; + gboolean editAddress = FALSE; + AddressTypeControlItem *atci = NULL; + AddressDataSource *ds = NULL; + AddressInterface *iface = NULL; + + if( obj == NULL ) return; + if( obj->type == ADDR_INTERFACE ) { + AdapterInterface *adapter = ADAPTER_INTERFACE(obj); + iface = adapter->interface; + if( iface ) { + if( iface->haveLibrary ) { + /* Enable appropriate File / New command */ + atci = adapter->atci; + menu_set_sensitive( addrbook.menu_factory, atci->menuCommand, TRUE ); + } + } + canEditTr = FALSE; + } + else if( obj->type == ADDR_DATASOURCE ) { + AdapterDSource *ads = ADAPTER_DSOURCE(obj); + ds = ads->dataSource; + iface = ds->interface; + if( ! iface->readOnly ) { + canAdd = canEdit = editAddress = TRUE; + } + if( ! iface->haveLibrary ) { + canAdd = canEdit = editAddress = FALSE; + } + } + else if( obj->type == ADDR_ITEM_FOLDER ) { + ds = addressbook_find_datasource( addrbook.treeSelected ); + if( ds ) { + iface = ds->interface; + if( ! iface->readOnly ) { + canAdd = editAddress = TRUE; + } + } + } + else if( obj->type == ADDR_ITEM_GROUP ) { + ds = addressbook_find_datasource( addrbook.treeSelected ); + if( ds ) { + iface = ds->interface; + if( ! iface->readOnly ) { + editAddress = TRUE; + } + } + } + + if( addrbook.listSelected == NULL ) canEdit = FALSE; + + /* Enable add */ + menu_set_sensitive( addrbook.menu_factory, "/Address/New Address", editAddress ); + menu_set_sensitive( addrbook.menu_factory, "/Address/New Group", canAdd ); + menu_set_sensitive( addrbook.menu_factory, "/Address/New Folder", canAdd ); + gtk_widget_set_sensitive( addrbook.reg_btn, editAddress ); + + /* Enable edit */ + menu_set_sensitive( addrbook.menu_factory, "/Address/Edit", canEdit ); + menu_set_sensitive( addrbook.menu_factory, "/Address/Delete", canEdit ); + gtk_widget_set_sensitive( addrbook.del_btn, canEdit ); + + menu_set_sensitive( addrbook.menu_factory, "/File/Edit", canEditTr ); + menu_set_sensitive( addrbook.menu_factory, "/File/Delete", canEditTr ); +} + +static void addressbook_tree_selected(GtkCTree *ctree, GtkCTreeNode *node, + gint column, gpointer data) +{ + AddressObject *obj = NULL; + AdapterDSource *ads = NULL; + AddressDataSource *ds = NULL; + ItemFolder *rootFolder = NULL; + + addrbook.treeSelected = node; + addrbook.listSelected = NULL; + addressbook_status_show( "" ); + if( addrbook.entry != NULL ) gtk_entry_set_text(GTK_ENTRY(addrbook.entry), ""); + + if( addrbook.clist ) gtk_clist_clear( GTK_CLIST(addrbook.clist) ); + if( node ) obj = gtk_ctree_node_get_row_data( ctree, node ); + if( obj == NULL ) return; + + addrbook.opened = node; + + if( obj->type == ADDR_DATASOURCE ) { + /* Read from file */ + static gboolean tVal = TRUE; + + ads = ADAPTER_DSOURCE(obj); + if( ads == NULL ) return; + ds = ads->dataSource; + if( ds == NULL ) return; + + if( addrindex_ds_get_modify_flag( ds ) ) { + addrindex_ds_read_data( ds ); + } + + if( ! addrindex_ds_get_read_flag( ds ) ) { + addrindex_ds_read_data( ds ); + } + addressbook_ds_show_message( ds ); + + if( ! addrindex_ds_get_access_flag( ds ) ) { + /* Remove existing folders and groups */ + gtk_clist_freeze( GTK_CLIST(ctree) ); + addressbook_tree_remove_children( ctree, node ); + gtk_clist_thaw( GTK_CLIST(ctree) ); + + /* Load folders into the tree */ + rootFolder = addrindex_ds_get_root_folder( ds ); + if( ds->type == ADDR_IF_JPILOT ) { + addressbook_node_add_folder( node, ds, rootFolder, ADDR_CATEGORY ); + } + else { + addressbook_node_add_folder( node, ds, rootFolder, ADDR_ITEM_FOLDER ); + } + addrindex_ds_set_access_flag( ds, &tVal ); + gtk_ctree_expand( ctree, node ); + } + } + + /* Update address list */ + addressbook_set_clist( obj ); + + /* Setup main menu selections */ + addressbook_menubar_set_sensitive( FALSE ); + addressbook_menuitem_set_sensitive( obj, node ); + + addressbook_list_select_clear(); + +} + +static void addressbook_list_selected(GtkCList *clist, gint row, gint column, + GdkEvent *event, gpointer data) +{ + if (event && event->type == GDK_2BUTTON_PRESS) { + /* Handle double click */ + if (prefs_common.add_address_by_click && + addrbook.target_compose) + addressbook_to_clicked(NULL, GINT_TO_POINTER(COMPOSE_ENTRY_TO)); + else + addressbook_edit_address_cb(NULL, 0, NULL); + } +} + +#if 0 +static void addressbook_list_select_show() { + GList *node = _addressListSelection_; + gchar *addr = NULL; + printf( "show selection...>>>\n" ); + while( node != NULL ) { + AddressObject *obj = ( AddressObject * ) node->data; + if( obj ) { + printf( "- %d : '%s'\n", obj->type, obj->name ); + if( obj->type == ADDR_ITEM_GROUP ) { + ItemGroup *group = ( ItemGroup * ) obj; + GList *node = group->listEMail; + while( node ) { + ItemEMail *email = node->data; + addr = addressbook_format_address( ( AddressObject * ) email ); + if( addr ) { + printf( "\tgrp >%s<\n", addr ); + g_free( addr ); + } + node = g_list_next( node ); + } + } + else { + addr = addressbook_format_address( obj ); + if( addr ) { + printf( "\t>%s<\n", addr ); + g_free( addr ); + } + } + } + else { + printf( "- NULL" ); + } + node = g_list_next( node ); + } + printf( "show selection...<<<\n" ); +} +#endif + +static void addressbook_list_select_clear() { + if( _addressListSelection_ ) { + g_list_free( _addressListSelection_ ); + } + _addressListSelection_ = NULL; +} + +static void addressbook_list_select_add( AddressObject *obj ) { + if( obj ) { + if( obj->type == ADDR_ITEM_PERSON || + obj->type == ADDR_ITEM_EMAIL || + obj->type == ADDR_ITEM_GROUP ) { + if( ! g_list_find( _addressListSelection_, obj ) ) { + _addressListSelection_ = g_list_append( _addressListSelection_, obj ); + } + } + } + /* addressbook_list_select_show(); */ +} + +static void addressbook_list_select_remove( AddressObject *obj ) { + if( obj == NULL ) return; + if( _addressListSelection_ ) { + _addressListSelection_ = g_list_remove( _addressListSelection_, obj ); + } + /* addressbook_list_select_show(); */ +} + +static void addressbook_list_row_selected( GtkCTree *clist, GtkCTreeNode *node, gint column, gpointer data ) { + GtkEntry *entry = GTK_ENTRY(addrbook.entry); + AddressObject *obj = NULL; + AddressObject *pobj = NULL; + AdapterDSource *ads = NULL; + AddressInterface *iface = NULL; + AddressDataSource *ds = NULL; + gboolean canEdit = FALSE; + gboolean canDelete = FALSE; + + gtk_entry_set_text( entry, "" ); + addrbook.listSelected = node; + obj = gtk_ctree_node_get_row_data( clist, node ); + if( obj != NULL ) { + /* printf( "list select: %d : '%s'\n", obj->type, obj->name ); */ + addressbook_list_select_add( obj ); + } + + pobj = gtk_ctree_node_get_row_data( GTK_CTREE(addrbook.ctree), addrbook.treeSelected ); + if( pobj == NULL ) return; + + menu_set_insensitive_all( GTK_MENU_SHELL(addrbook.list_popup) ); + + if( pobj->type == ADDR_DATASOURCE ) { + ads = ADAPTER_DSOURCE(pobj); + ds = ads->dataSource; + iface = ds->interface; + if( ! iface->readOnly ) { + canEdit = TRUE; + menu_set_sensitive( addrbook.list_factory, "/New Address", TRUE ); + menu_set_sensitive( addrbook.list_factory, "/New Folder", TRUE ); + menu_set_sensitive( addrbook.list_factory, "/New Group", TRUE ); + gtk_widget_set_sensitive( addrbook.reg_btn, TRUE ); + } + } + else if( pobj->type != ADDR_INTERFACE ) { + ds = addressbook_find_datasource( addrbook.treeSelected ); + iface = ds->interface; + if( ! iface->readOnly ) { + if( pobj->type == ADDR_ITEM_FOLDER || pobj->type == ADDR_ITEM_GROUP ) { + canEdit = TRUE; + menu_set_sensitive( addrbook.list_factory, "/New Address", TRUE ); + gtk_widget_set_sensitive( addrbook.reg_btn, TRUE ); + } + if( pobj->type == ADDR_ITEM_FOLDER ) { + canEdit = TRUE; + menu_set_sensitive( addrbook.list_factory, "/New Folder", TRUE ); + menu_set_sensitive( addrbook.list_factory, "/New Group", TRUE ); + } + } + } + if( obj == NULL ) canEdit = FALSE; + canDelete = canEdit; + if( GTK_CLIST(clist)->selection && GTK_CLIST(clist)->selection->next ) canEdit = FALSE; + + menu_set_sensitive( addrbook.list_factory, "/Edit", canEdit ); + menu_set_sensitive( addrbook.list_factory, "/Delete", canDelete ); + + menu_set_sensitive( addrbook.menu_factory, "/Address/Edit", canEdit ); + menu_set_sensitive( addrbook.menu_factory, "/Address/Delete", canDelete ); + + gtk_widget_set_sensitive( addrbook.del_btn, canDelete ); + +} + +static void addressbook_list_row_unselected( GtkCTree *ctree, GtkCTreeNode *node, gint column, gpointer data ) { + AddressObject *obj; + + obj = gtk_ctree_node_get_row_data( ctree, node ); + if( obj != NULL ) { + /* g_print( "list unselect: %d : '%s'\n", obj->type, obj->name ); */ + addressbook_list_select_remove( obj ); + } +} + +static void addressbook_entry_gotfocus( GtkWidget *widget ) { + gtk_editable_select_region( GTK_EDITABLE(addrbook.entry), 0, -1 ); +} + +static gboolean addressbook_list_button_pressed(GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + if( ! event ) return FALSE; + if( event->button == 3 ) { + gtk_menu_popup( GTK_MENU(addrbook.list_popup), NULL, NULL, NULL, NULL, + event->button, event->time ); + } + return FALSE; +} + +static gboolean addressbook_list_button_released(GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + return FALSE; +} + +static gboolean addressbook_tree_button_pressed(GtkWidget *ctree, + GdkEventButton *event, + gpointer data) +{ + GtkCList *clist = GTK_CLIST(ctree); + gint row, column; + AddressObject *obj = NULL; + /* GtkCTreeNode *node; */ + AdapterDSource *ads = NULL; + AddressInterface *iface = NULL; + AddressDataSource *ds = NULL; + /* AddressTypeControlItem *atci = NULL; */ + gboolean canEdit = FALSE; + + if( ! event ) return FALSE; + addressbook_menubar_set_sensitive( FALSE ); +/* */ + if( gtk_clist_get_selection_info( clist, event->x, event->y, &row, &column ) ) { + gtk_clist_select_row( clist, row, column ); + gtkut_clist_set_focus_row(clist, row); + obj = gtk_clist_get_row_data( clist, row ); + } +/* */ + menu_set_insensitive_all(GTK_MENU_SHELL(addrbook.tree_popup)); + + if( obj == NULL ) return FALSE; + if (obj->type == ADDR_DATASOURCE) { + ads = ADAPTER_DSOURCE(obj); + ds = ads->dataSource; + iface = ds->interface; + canEdit = TRUE; + if( ! iface->readOnly ) { + menu_set_sensitive( addrbook.tree_factory, "/New Address", TRUE ); + menu_set_sensitive( addrbook.tree_factory, "/New Folder", TRUE ); + menu_set_sensitive( addrbook.tree_factory, "/New Group", TRUE ); + gtk_widget_set_sensitive( addrbook.reg_btn, TRUE ); + } + } + else if (obj->type == ADDR_ITEM_FOLDER) { + ds = addressbook_find_datasource( addrbook.treeSelected ); + iface = ds->interface; + if( ! iface->readOnly ) { + canEdit = TRUE; + menu_set_sensitive( addrbook.tree_factory, "/New Address", TRUE ); + menu_set_sensitive( addrbook.tree_factory, "/New Folder", TRUE ); + menu_set_sensitive( addrbook.tree_factory, "/New Group", TRUE ); + gtk_widget_set_sensitive( addrbook.reg_btn, TRUE ); + } + } + else if (obj->type == ADDR_ITEM_GROUP) { + ds = addressbook_find_datasource( addrbook.treeSelected ); + iface = ds->interface; + if( ! iface->readOnly ) { + canEdit = TRUE; + menu_set_sensitive( addrbook.tree_factory, "/New Address", TRUE ); + gtk_widget_set_sensitive( addrbook.reg_btn, TRUE ); + } + } + + /* Enable edit */ + menu_set_sensitive( addrbook.tree_factory, "/Edit", canEdit ); + menu_set_sensitive( addrbook.tree_factory, "/Delete", canEdit ); + menu_set_sensitive( addrbook.menu_factory, "/File/Edit", canEdit ); + menu_set_sensitive( addrbook.menu_factory, "/File/Delete", canEdit ); + + if( event->button == 3 ) { + gtk_menu_popup(GTK_MENU(addrbook.tree_popup), NULL, NULL, NULL, NULL, + event->button, event->time); + } + + return FALSE; +} + +static gboolean addressbook_tree_button_released(GtkWidget *ctree, + GdkEventButton *event, + gpointer data) +{ + gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened); + gtkut_ctree_set_focus_row(GTK_CTREE(addrbook.ctree), addrbook.opened); + return FALSE; +} + +static void addressbook_popup_close(GtkMenuShell *menu_shell, gpointer data) +{ + if (!addrbook.opened) return; + + gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened); + gtkut_ctree_set_focus_row(GTK_CTREE(addrbook.ctree), + addrbook.opened); +} + +static void addressbook_new_folder_cb(gpointer data, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + AddressObject *obj = NULL; + AddressDataSource *ds = NULL; + AddressBookFile *abf = NULL; + ItemFolder *parentFolder = NULL; + ItemFolder *folder = NULL; + + if( ! addrbook.treeSelected ) return; + obj = gtk_ctree_node_get_row_data( ctree, addrbook.treeSelected ); + if( obj == NULL ) return; + ds = addressbook_find_datasource( addrbook.treeSelected ); + if( ds == NULL ) return; + + if( obj->type == ADDR_DATASOURCE ) { + if( ADAPTER_DSOURCE(obj)->subType != ADDR_BOOK ) return; + } + else if( obj->type == ADDR_ITEM_FOLDER ) { + parentFolder = ADAPTER_FOLDER(obj)->itemFolder; + } + else { + return; + } + + abf = ds->rawDataSource; + if( abf == NULL ) return; + folder = addressbook_edit_folder( abf, parentFolder, NULL ); + if( folder ) { + GtkCTreeNode *nn; + nn = addressbook_node_add_folder( addrbook.treeSelected, ds, folder, ADDR_ITEM_FOLDER ); + gtk_ctree_expand( ctree, addrbook.treeSelected ); + if( addrbook.treeSelected == addrbook.opened ) addressbook_set_clist(obj); + } + +} + +static void addressbook_new_group_cb(gpointer data, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + AddressObject *obj = NULL; + AddressDataSource *ds = NULL; + AddressBookFile *abf = NULL; + ItemFolder *parentFolder = NULL; + ItemGroup *group = NULL; + + if( ! addrbook.treeSelected ) return; + obj = gtk_ctree_node_get_row_data(ctree, addrbook.treeSelected); + if( obj == NULL ) return; + ds = addressbook_find_datasource( addrbook.treeSelected ); + if( ds == NULL ) return; + + if( obj->type == ADDR_DATASOURCE ) { + if( ADAPTER_DSOURCE(obj)->subType != ADDR_BOOK ) return; + } + else if( obj->type == ADDR_ITEM_FOLDER ) { + parentFolder = ADAPTER_FOLDER(obj)->itemFolder; + } + else { + return; + } + + abf = ds->rawDataSource; + if( abf == NULL ) return; + group = addressbook_edit_group( abf, parentFolder, NULL ); + if( group ) { + GtkCTreeNode *nn; + nn = addressbook_node_add_group( addrbook.treeSelected, ds, group ); + gtk_ctree_expand( ctree, addrbook.treeSelected ); + if( addrbook.treeSelected == addrbook.opened ) addressbook_set_clist(obj); + } + +} + +static void addressbook_change_node_name(GtkCTreeNode *node, const gchar *name) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + gchar *text[1]; + guint8 spacing; + GdkPixmap *pix_cl, *pix_op; + GdkBitmap *mask_cl, *mask_op; + gboolean is_leaf, expanded; + + gtk_ctree_get_node_info(ctree, node, text, &spacing, + &pix_cl, &mask_cl, &pix_op, &mask_op, + &is_leaf, &expanded); + gtk_ctree_set_node_info(ctree, node, name, spacing, + pix_cl, mask_cl, pix_op, mask_op, + is_leaf, expanded); +} + +/* +* Edit data source. +* Enter: obj Address object to edit. +* node Node in tree. +* Return: New name of data source. +*/ +static gchar *addressbook_edit_datasource( AddressObject *obj, GtkCTreeNode *node ) { + gchar *newName = NULL; + AddressDataSource *ds = NULL; + AddressInterface *iface = NULL; + AdapterDSource *ads = NULL; + + ds = addressbook_find_datasource( node ); + if( ds == NULL ) return NULL; + iface = ds->interface; + if( ! iface->haveLibrary ) return NULL; + + /* Read data from data source */ + if( ! addrindex_ds_get_read_flag( ds ) ) { + addrindex_ds_read_data( ds ); + } + + /* Handle edit */ + ads = ADAPTER_DSOURCE(obj); + if( ads->subType == ADDR_BOOK ) { + if( addressbook_edit_book( _addressIndex_, ads ) == NULL ) return NULL; + } + else if( ads->subType == ADDR_VCARD ) { + if( addressbook_edit_vcard( _addressIndex_, ads ) == NULL ) return NULL; + } +#ifdef USE_JPILOT + else if( ads->subType == ADDR_JPILOT ) { + if( addressbook_edit_jpilot( _addressIndex_, ads ) == NULL ) return NULL; + } +#endif +#ifdef USE_LDAP + else if( ads->subType == ADDR_LDAP ) { + if( addressbook_edit_ldap( _addressIndex_, ads ) == NULL ) return NULL; + } +#endif + else { + return NULL; + } + newName = obj->name; + return newName; +} + +/* +* Edit an object that is in the address tree area. +*/ +static void addressbook_treenode_edit_cb(gpointer data, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + AddressObject *obj; + AddressDataSource *ds = NULL; + AddressBookFile *abf = NULL; + GtkCTreeNode *node = NULL, *parentNode = NULL; + gchar *name = NULL; + + if( ! addrbook.treeSelected ) return; + node = addrbook.treeSelected; + if( GTK_CTREE_ROW(node)->level == 1 ) return; + obj = gtk_ctree_node_get_row_data( ctree, node ); + if( obj == NULL ) return; + parentNode = GTK_CTREE_ROW(node)->parent; + + ds = addressbook_find_datasource( node ); + if( ds == NULL ) return; + + if( obj->type == ADDR_DATASOURCE ) { + name = addressbook_edit_datasource( obj, node ); + if( name == NULL ) return; + } + else { + abf = ds->rawDataSource; + if( abf == NULL ) return; + if( obj->type == ADDR_ITEM_FOLDER ) { + AdapterFolder *adapter = ADAPTER_FOLDER(obj); + ItemFolder *item = adapter->itemFolder; + ItemFolder *parentFolder = NULL; + parentFolder = ( ItemFolder * ) ADDRITEM_PARENT(item); + if( addressbook_edit_folder( abf, parentFolder, item ) == NULL ) return; + name = ADDRITEM_NAME(item); + } + else if( obj->type == ADDR_ITEM_GROUP ) { + AdapterGroup *adapter = ADAPTER_GROUP(obj); + ItemGroup *item = adapter->itemGroup; + ItemFolder *parentFolder = NULL; + parentFolder = ( ItemFolder * ) ADDRITEM_PARENT(item); + if( addressbook_edit_group( abf, parentFolder, item ) == NULL ) return; + name = ADDRITEM_NAME(item); + } + } + if( name && parentNode ) { + /* Update node in tree view */ + addressbook_change_node_name( node, name ); + gtk_ctree_sort_node(ctree, parentNode); + gtk_ctree_expand( ctree, node ); + gtk_ctree_select( ctree, node ); + } +} + +/* +* Delete an item from the tree widget. +*/ +static void addressbook_treenode_delete_cb(gpointer data, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + GtkCTreeNode *node = NULL; + AddressObject *obj; + gchar *message; + AlertValue aval; + AddressBookFile *abf = NULL; + AdapterDSource *ads = NULL; + AddressInterface *iface = NULL; + AddressDataSource *ds = NULL; + gboolean remFlag = FALSE; + + if( ! addrbook.treeSelected ) return; + node = addrbook.treeSelected; + if( GTK_CTREE_ROW(node)->level == 1 ) return; + + obj = gtk_ctree_node_get_row_data( ctree, node ); + g_return_if_fail(obj != NULL); + + if( obj->type == ADDR_DATASOURCE ) { + ads = ADAPTER_DSOURCE(obj); + if( ads == NULL ) return; + ds = ads->dataSource; + if( ds == NULL ) return; + } + else { + /* Must be folder or something else */ + ds = addressbook_find_datasource( node ); + if( ds == NULL ) return; + + /* Only allow deletion from non-readOnly data sources */ + iface = ds->interface; + if( iface->readOnly ) return; + } + + /* Confirm deletion */ + if( obj->type == ADDR_ITEM_FOLDER ) { + message = g_strdup_printf( _( + "Do you want to delete the folder AND all addresses in `%s' ? \n" \ + "If deleting the folder only, addresses will be moved into parent folder." ), + obj->name ); + aval = alertpanel( _("Delete"), message, _("Folder only"), _("Folder and Addresses"), _("Cancel") ); + g_free(message); + if( aval == G_ALERTOTHER ) return; + } + else { + message = g_strdup_printf(_("Really delete `%s' ?"), obj->name); + aval = alertpanel(_("Delete"), message, _("Yes"), _("No"), NULL); + g_free(message); + if (aval != G_ALERTDEFAULT) return; + } + + /* Proceed with deletion */ + if( obj->type == ADDR_DATASOURCE ) { + /* Remove data source. */ + if( addrindex_index_remove_datasource( _addressIndex_, ds ) ) { + addressbook_free_child_adapters( node ); + remFlag = TRUE; + } + } + else { + abf = addressbook_get_book_file(); + if( abf == NULL ) return; + } + + if( obj->type == ADDR_ITEM_FOLDER ) { + AdapterFolder *adapter = ADAPTER_FOLDER(obj); + ItemFolder *item = adapter->itemFolder; + if( aval == G_ALERTDEFAULT ) { + /* Remove folder only */ + item = addrbook_remove_folder( abf, item ); + if( item ) { + addritem_free_item_folder( item ); + addressbook_move_nodes_up( ctree, node ); + remFlag = TRUE; + } + } + else if( aval == G_ALERTALTERNATE ) { + /* Remove folder and addresses */ + item = addrbook_remove_folder_delete( abf, item ); + if( item ) { + addritem_free_item_folder( item ); + addressbook_free_child_adapters( node ); + remFlag = TRUE; + } + } + } + else if( obj->type == ADDR_ITEM_GROUP ) { + AdapterGroup *adapter = ADAPTER_GROUP(obj); + ItemGroup *item = adapter->itemGroup; + + item = addrbook_remove_group( abf, item ); + if( item ) { + addritem_free_item_group( item ); + remFlag = TRUE; + } + } + + if( remFlag ) { + /* Free up adapter and remove node. */ + addressbook_free_adapter( node ); + gtk_ctree_remove_node(ctree, node ); + } +} + +static void addressbook_new_address_cb( gpointer data, guint action, GtkWidget *widget ) { + AddressObject *pobj = NULL; + AddressDataSource *ds = NULL; + AddressBookFile *abf = NULL; + + pobj = gtk_ctree_node_get_row_data(GTK_CTREE(addrbook.ctree), addrbook.treeSelected); + if( pobj == NULL ) return; + ds = addressbook_find_datasource( GTK_CTREE_NODE(addrbook.treeSelected) ); + if( ds == NULL ) return; + + abf = ds->rawDataSource; + if( abf == NULL ) return; + + if( pobj->type == ADDR_DATASOURCE ) { + if( ADAPTER_DSOURCE(pobj)->subType == ADDR_BOOK ) { + /* New address */ + ItemPerson *person = addressbook_edit_person( abf, NULL, NULL, FALSE ); + if( person ) { + if( addrbook.treeSelected == addrbook.opened ) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } + } + } + else if( pobj->type == ADDR_ITEM_FOLDER ) { + /* New address */ + ItemFolder *folder = ADAPTER_FOLDER(pobj)->itemFolder; + ItemPerson *person = addressbook_edit_person( abf, folder, NULL, FALSE ); + if( person ) { + if (addrbook.treeSelected == addrbook.opened) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } + } + else if( pobj->type == ADDR_ITEM_GROUP ) { + /* New address in group */ + ItemGroup *group = ADAPTER_GROUP(pobj)->itemGroup; + if( addressbook_edit_group( abf, NULL, group ) == NULL ) return; + if (addrbook.treeSelected == addrbook.opened) { + /* Change node name in tree. */ + addressbook_change_node_name( addrbook.treeSelected, ADDRITEM_NAME(group) ); + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } +} + +/* +* Search for specified group in address index tree. +*/ +static GtkCTreeNode *addressbook_find_group_node( GtkCTreeNode *parent, ItemGroup *group ) { + GtkCTreeNode *node = NULL; + GtkCTreeRow *currRow; + + currRow = GTK_CTREE_ROW( parent ); + if( currRow ) { + node = currRow->children; + while( node ) { + AddressObject *obj = gtk_ctree_node_get_row_data( GTK_CTREE(addrbook.ctree), node ); + if( obj->type == ADDR_ITEM_GROUP ) { + ItemGroup *g = ADAPTER_GROUP(obj)->itemGroup; + if( g == group ) return node; + } + currRow = GTK_CTREE_ROW(node); + node = currRow->sibling; + } + } + return NULL; +} + +static AddressBookFile *addressbook_get_book_file() { + AddressBookFile *abf = NULL; + AddressDataSource *ds = NULL; + + ds = addressbook_find_datasource( addrbook.treeSelected ); + if( ds == NULL ) return NULL; + if( ds->type == ADDR_IF_BOOK ) abf = ds->rawDataSource; + return abf; +} + +static void addressbook_tree_remove_children( GtkCTree *ctree, GtkCTreeNode *parent ) { + GtkCTreeNode *node; + GtkCTreeRow *row; + + /* Remove existing folders and groups */ + row = GTK_CTREE_ROW( parent ); + if( row ) { + while( (node = row->children) ) { + gtk_ctree_remove_node( ctree, node ); + } + } +} + +static void addressbook_move_nodes_up( GtkCTree *ctree, GtkCTreeNode *node ) { + GtkCTreeNode *parent, *child; + GtkCTreeRow *currRow; + currRow = GTK_CTREE_ROW( node ); + if( currRow ) { + parent = currRow->parent; + while( (child = currRow->children) ) { + gtk_ctree_move( ctree, child, parent, node ); + } + gtk_ctree_sort_node( ctree, parent ); + } +} + +static void addressbook_edit_address_cb( gpointer data, guint action, GtkWidget *widget ) { + GtkCTree *clist = GTK_CTREE(addrbook.clist); + GtkCTree *ctree; + AddressObject *obj = NULL, *pobj = NULL; + AddressDataSource *ds = NULL; + GtkCTreeNode *node = NULL, *parentNode = NULL; + gchar *name = NULL; + AddressBookFile *abf = NULL; + + if( addrbook.listSelected == NULL ) return; + obj = gtk_ctree_node_get_row_data( clist, addrbook.listSelected ); + g_return_if_fail(obj != NULL); + + ctree = GTK_CTREE( addrbook.ctree ); + pobj = gtk_ctree_node_get_row_data( ctree, addrbook.treeSelected ); + node = gtk_ctree_find_by_row_data( ctree, addrbook.treeSelected, obj ); + + ds = addressbook_find_datasource( GTK_CTREE_NODE(addrbook.treeSelected) ); + if( ds == NULL ) return; + + abf = addressbook_get_book_file(); + if( abf == NULL ) return; + if( obj->type == ADDR_ITEM_EMAIL ) { + ItemEMail *email = ( ItemEMail * ) obj; + ItemPerson *person; + if( email == NULL ) return; + if( pobj && pobj->type == ADDR_ITEM_GROUP ) { + /* Edit parent group */ + AdapterGroup *adapter = ADAPTER_GROUP(pobj); + ItemGroup *itemGrp = adapter->itemGroup; + if( addressbook_edit_group( abf, NULL, itemGrp ) == NULL ) return; + name = ADDRITEM_NAME(itemGrp); + node = addrbook.treeSelected; + parentNode = GTK_CTREE_ROW(node)->parent; + } + else { + /* Edit person - email page */ + person = ( ItemPerson * ) ADDRITEM_PARENT(email); + if( addressbook_edit_person( abf, NULL, person, TRUE ) == NULL ) return; + gtk_ctree_select( ctree, addrbook.opened ); + invalidate_address_completion(); + return; + } + } + else if( obj->type == ADDR_ITEM_PERSON ) { + /* Edit person - basic page */ + ItemPerson *person = ( ItemPerson * ) obj; + if( addressbook_edit_person( abf, NULL, person, FALSE ) == NULL ) return; + gtk_ctree_select( ctree, addrbook.opened ); + invalidate_address_completion(); + return; + } + else if( obj->type == ADDR_ITEM_GROUP ) { + ItemGroup *itemGrp = ( ItemGroup * ) obj; + if( addressbook_edit_group( abf, NULL, itemGrp ) == NULL ) return; + parentNode = addrbook.treeSelected; + node = addressbook_find_group_node( parentNode, itemGrp ); + name = ADDRITEM_NAME(itemGrp); + } + else { + return; + } + + /* Update tree node with node name */ + if( node == NULL ) return; + addressbook_change_node_name( node, name ); + gtk_ctree_sort_node( ctree, parentNode ); + gtk_ctree_select( ctree, addrbook.opened ); +} + +static void addressbook_delete_address_cb(gpointer data, guint action, + GtkWidget *widget) +{ + addressbook_del_clicked(NULL, NULL); +} + +static void close_cb(gpointer data, guint action, GtkWidget *widget) +{ + addressbook_close(); +} + +static void addressbook_file_save_cb( gpointer data, guint action, GtkWidget *widget ) { + addressbook_export_to_file(); +} + +static void addressbook_person_expand_node( GtkCTree *ctree, GList *node, gpointer *data ) { + if( node ) { + ItemPerson *person = gtk_ctree_node_get_row_data( ctree, GTK_CTREE_NODE(node) ); + if( person ) addritem_person_set_opened( person, TRUE ); + } +} + +static void addressbook_person_collapse_node( GtkCTree *ctree, GList *node, gpointer *data ) { + if( node ) { + ItemPerson *person = gtk_ctree_node_get_row_data( ctree, GTK_CTREE_NODE(node) ); + if( person ) addritem_person_set_opened( person, FALSE ); + } +} + +static gchar *addressbook_format_item_clist( ItemPerson *person, ItemEMail *email ) { + gchar *str = NULL; + gchar *eMailAlias = ADDRITEM_NAME(email); + if( eMailAlias && *eMailAlias != '\0' ) { + if( person ) { + str = g_strdup_printf( "%s - %s", ADDRITEM_NAME(person), eMailAlias ); + } + else { + str = g_strdup( eMailAlias ); + } + } + return str; +} + +static void addressbook_load_group( GtkCTree *clist, ItemGroup *itemGroup ) { + GList *items = itemGroup->listEMail; + AddressTypeControlItem *atci = addrbookctl_lookup( ADDR_ITEM_EMAIL ); + for( ; items != NULL; items = g_list_next( items ) ) { + GtkCTreeNode *nodeEMail = NULL; + gchar *text[N_COLS]; + ItemEMail *email = items->data; + ItemPerson *person; + gchar *str = NULL; + + if( ! email ) continue; + + person = ( ItemPerson * ) ADDRITEM_PARENT(email); + str = addressbook_format_item_clist( person, email ); + if( str ) { + text[COL_NAME] = str; + } + else { + text[COL_NAME] = ADDRITEM_NAME(person); + } + text[COL_ADDRESS] = email->address; + text[COL_REMARKS] = email->remarks; + nodeEMail = gtk_ctree_insert_node( + clist, NULL, NULL, + text, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, + atci->iconXpmOpen, atci->maskXpmOpen, + FALSE, FALSE ); + gtk_ctree_node_set_row_data( clist, nodeEMail, email ); + g_free( str ); + str = NULL; + } +} + +static void addressbook_folder_load_person( GtkCTree *clist, ItemFolder *itemFolder ) { + GList *items; + AddressTypeControlItem *atci = addrbookctl_lookup( ADDR_ITEM_PERSON ); + AddressTypeControlItem *atciMail = addrbookctl_lookup( ADDR_ITEM_EMAIL ); + + if( atci == NULL ) return; + if( atciMail == NULL ) return; + + /* Load email addresses */ + items = addritem_folder_get_person_list( itemFolder ); + for( ; items != NULL; items = g_list_next( items ) ) { + GtkCTreeNode *nodePerson = NULL; + GtkCTreeNode *nodeEMail = NULL; + gchar *text[N_COLS]; + gboolean flgFirst = TRUE, haveAddr = FALSE; + /* gint row; */ + ItemPerson *person; + GList *node; + + person = ( ItemPerson * ) items->data; + if( person == NULL ) continue; + + text[COL_NAME] = NULL; + node = person->listEMail; + while( node ) { + ItemEMail *email = node->data; + gchar *eMailAddr = NULL; + node = g_list_next( node ); + + text[COL_ADDRESS] = email->address; + text[COL_REMARKS] = email->remarks; + eMailAddr = ADDRITEM_NAME(email); + if( eMailAddr && *eMailAddr == '\0' ) eMailAddr = NULL; + if( flgFirst ) { + /* First email belongs with person */ + gchar *str = addressbook_format_item_clist( person, email ); + if( str ) { + text[COL_NAME] = str; + } + else { + text[COL_NAME] = ADDRITEM_NAME(person); + } + nodePerson = gtk_ctree_insert_node( + clist, NULL, NULL, + text, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, + atci->iconXpmOpen, atci->maskXpmOpen, + FALSE, person->isOpened ); + g_free( str ); + str = NULL; + gtk_ctree_node_set_row_data(clist, nodePerson, person ); + } + else { + /* Subsequent email is a child node of person */ + text[COL_NAME] = ADDRITEM_NAME(email); + nodeEMail = gtk_ctree_insert_node( + clist, nodePerson, NULL, + text, FOLDER_SPACING, + atciMail->iconXpm, atciMail->maskXpm, + atciMail->iconXpmOpen, atciMail->maskXpmOpen, + FALSE, TRUE ); + gtk_ctree_node_set_row_data(clist, nodeEMail, email ); + } + flgFirst = FALSE; + haveAddr = TRUE; + } + if( ! haveAddr ) { + /* Have name without EMail */ + text[COL_NAME] = ADDRITEM_NAME(person); + text[COL_ADDRESS] = NULL; + text[COL_REMARKS] = NULL; + nodePerson = gtk_ctree_insert_node( + clist, NULL, NULL, + text, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, + atci->iconXpmOpen, atci->maskXpmOpen, + FALSE, person->isOpened ); + gtk_ctree_node_set_row_data(clist, nodePerson, person ); + } + } + gtk_ctree_sort_node(GTK_CTREE(clist), NULL); + + /* Free up the list */ + mgu_clear_list( items ); + g_list_free( items ); +} + +static void addressbook_folder_load_group( GtkCTree *clist, ItemFolder *itemFolder ) { + GList *items; + AddressTypeControlItem *atci = addrbookctl_lookup( ADDR_ITEM_GROUP ); + + /* Load any groups */ + if( ! atci ) return; + items = addritem_folder_get_group_list( itemFolder ); + for( ; items != NULL; items = g_list_next( items ) ) { + GtkCTreeNode *nodeGroup = NULL; + gchar *text[N_COLS]; + ItemGroup *group = items->data; + if( group == NULL ) continue; + text[COL_NAME] = ADDRITEM_NAME(group); + text[COL_ADDRESS] = NULL; + text[COL_REMARKS] = NULL; + nodeGroup = gtk_ctree_insert_node(clist, NULL, NULL, + text, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, + atci->iconXpmOpen, atci->maskXpmOpen, + FALSE, FALSE); + gtk_ctree_node_set_row_data(clist, nodeGroup, group ); + } + gtk_ctree_sort_node(clist, NULL); + + /* Free up the list */ + mgu_clear_list( items ); + g_list_free( items ); +} + +#if 0 +/* + * Load data sources into list. + */ +static void addressbook_node_load_datasource( GtkCTree *clist, AddressObject *obj ) { + AdapterInterface *adapter; + AddressInterface *iface; + AddressTypeControlItem *atci = NULL; + /* AddressDataSource *ds; */ + GtkCTreeNode *newNode, *node; + GtkCTreeRow *row; + GtkCell *cell = NULL; + gchar *text[N_COLS]; + + adapter = ADAPTER_INTERFACE(obj); + if( adapter == NULL ) return; + iface = adapter->interface; + atci = adapter->atci; + if( atci == NULL ) return; + + /* Create nodes in list copying values for data sources in tree */ + row = GTK_CTREE_ROW( adapter->treeNode ); + if( row ) { + node = row->children; + while( node ) { + gpointer data = gtk_ctree_node_get_row_data( clist, node ); + row = GTK_CTREE_ROW( node ); + cell = ( ( GtkCListRow * )row )->cell; + text[COL_NAME] = cell->u.text; + text[COL_ADDRESS] = NULL; + text[COL_REMARKS] = NULL; + newNode = gtk_ctree_insert_node( clist, NULL, NULL, + text, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, + atci->iconXpmOpen, atci->maskXpmOpen, + FALSE, FALSE); + gtk_ctree_node_set_row_data( clist, newNode, data ); + node = row->sibling; + + } + } + gtk_ctree_sort_node( clist, NULL ); +} +#endif + +static AddressDataSource *addressbook_find_datasource( GtkCTreeNode *node ) { + AddressDataSource *ds = NULL; + AddressObject *ao; + + g_return_val_if_fail(addrbook.ctree != NULL, NULL); + + while( node ) { + if( GTK_CTREE_ROW(node)->level < 2 ) return NULL; + ao = gtk_ctree_node_get_row_data( GTK_CTREE(addrbook.ctree), node ); + if( ao ) { +/* printf( "ao->type = %d\n", ao->type ); */ + if( ao->type == ADDR_DATASOURCE ) { + AdapterDSource *ads = ADAPTER_DSOURCE(ao); +/* printf( "found it\n" ); */ + ds = ads->dataSource; + break; + } + } + node = GTK_CTREE_ROW(node)->parent; + } + return ds; +} + +/* +* Load address list widget with children of specified object. +* Enter: obj Parent object to be loaded. +*/ +static void addressbook_set_clist( AddressObject *obj ) { + GtkCTree *ctreelist = GTK_CTREE(addrbook.clist); + GtkCList *clist = GTK_CLIST(addrbook.clist); + AddressDataSource *ds = NULL; + AdapterDSource *ads = NULL; + + if( obj == NULL ) { + gtk_clist_clear(clist); + return; + } + + if( obj->type == ADDR_INTERFACE ) { + /* printf( "set_clist: loading datasource...\n" ); */ + /* addressbook_node_load_datasource( clist, obj ); */ + return; + } + + gtk_clist_freeze(clist); + gtk_clist_clear(clist); + + if( obj->type == ADDR_DATASOURCE ) { + ads = ADAPTER_DSOURCE(obj); + ds = ADAPTER_DSOURCE(obj)->dataSource; + if( ds ) { + /* Load root folder */ + ItemFolder *rootFolder = NULL; + rootFolder = addrindex_ds_get_root_folder( ds ); + addressbook_folder_load_person( ctreelist, addrindex_ds_get_root_folder( ds ) ); + addressbook_folder_load_group( ctreelist, addrindex_ds_get_root_folder( ds ) ); + } + } + else { + if( obj->type == ADDR_ITEM_GROUP ) { + /* Load groups */ + ItemGroup *itemGroup = ADAPTER_GROUP(obj)->itemGroup; + addressbook_load_group( ctreelist, itemGroup ); + } + else if( obj->type == ADDR_ITEM_FOLDER ) { + /* Load folders */ + ItemFolder *itemFolder = ADAPTER_FOLDER(obj)->itemFolder; + addressbook_folder_load_person( ctreelist, itemFolder ); + addressbook_folder_load_group( ctreelist, itemFolder ); + } + } + + gtk_clist_sort(clist); + gtk_clist_thaw(clist); +} + +/* +* Free adaptor for specified node. +*/ +static void addressbook_free_adapter( GtkCTreeNode *node ) { + AddressObject *ao; + + g_return_if_fail(addrbook.ctree != NULL); + + if( node ) { + if( GTK_CTREE_ROW(node)->level < 2 ) return; + ao = gtk_ctree_node_get_row_data( GTK_CTREE(addrbook.ctree), node ); + if( ao == NULL ) return; + if( ao->type == ADDR_INTERFACE ) { + AdapterInterface *ai = ADAPTER_INTERFACE(ao); + addrbookctl_free_interface( ai ); + } + else if( ao->type == ADDR_DATASOURCE ) { + AdapterDSource *ads = ADAPTER_DSOURCE(ao); + addrbookctl_free_datasource( ads ); + } + else if( ao->type == ADDR_ITEM_FOLDER ) { + AdapterFolder *af = ADAPTER_FOLDER(ao); + addrbookctl_free_folder( af ); + } + else if( ao->type == ADDR_ITEM_GROUP ) { + AdapterGroup *ag = ADAPTER_GROUP(ao); + addrbookctl_free_group( ag ); + } + gtk_ctree_node_set_row_data( GTK_CTREE(addrbook.ctree), node, NULL ); + } +} + +/* +* Free all children adapters. +*/ +static void addressbook_free_child_adapters( GtkCTreeNode *node ) { + GtkCTreeNode *parent, *child; + GtkCTreeRow *currRow; + + if( node == NULL ) return; + currRow = GTK_CTREE_ROW( node ); + if( currRow ) { + parent = currRow->parent; + child = currRow->children; + while( child ) { + addressbook_free_child_adapters( child ); + addressbook_free_adapter( child ); + currRow = GTK_CTREE_ROW( child ); + child = currRow->sibling; + } + } +} + +AdapterDSource *addressbook_create_ds_adapter( AddressDataSource *ds, + AddressObjectType otype, gchar *name ) +{ + AdapterDSource *adapter = g_new0( AdapterDSource, 1 ); + ADDRESS_OBJECT(adapter)->type = ADDR_DATASOURCE; + ADDRESS_OBJECT_NAME(adapter) = g_strdup( name ); + adapter->dataSource = ds; + adapter->subType = otype; + return adapter; +} + +void addressbook_ads_set_name( AdapterDSource *adapter, gchar *value ) { + ADDRESS_OBJECT_NAME(adapter) = mgu_replace_string( ADDRESS_OBJECT_NAME(adapter), value ); +} + +/* + * Load tree from address index with the initial data. + */ +static void addressbook_load_tree( void ) { + GtkCTree *ctree = GTK_CTREE( addrbook.ctree ); + GList *nodeIf, *nodeDS; + AdapterInterface *adapter; + AddressInterface *iface; + AddressTypeControlItem *atci; + AddressDataSource *ds; + AdapterDSource *ads; + GtkCTreeNode *node, *newNode; + gchar *name; + + nodeIf = _addressInterfaceList_; + while( nodeIf ) { + adapter = nodeIf->data; + node = adapter->treeNode; + iface = adapter->interface; + atci = adapter->atci; + if( iface ) { + if( iface->useInterface ) { + /* Load data sources below interface node */ + nodeDS = iface->listSource; + while( nodeDS ) { + ds = nodeDS->data; + newNode = NULL; + name = addrindex_ds_get_name( ds ); + ads = addressbook_create_ds_adapter( ds, atci->objectType, name ); + newNode = addressbook_add_object( node, ADDRESS_OBJECT(ads) ); + nodeDS = g_list_next( nodeDS ); + } + gtk_ctree_expand( ctree, node ); + } + } + nodeIf = g_list_next( nodeIf ); + } +} + +/* + * Convert the old address book to new format. + */ +static gboolean addressbook_convert( AddressIndex *addrIndex ) { + gboolean retVal = FALSE; + gboolean errFlag = TRUE; + gchar *msg = NULL; + + /* Read old address book, performing conversion */ + debug_print( "Reading and converting old address book...\n" ); + addrindex_set_file_name( addrIndex, ADDRESSBOOK_OLD_FILE ); + addrindex_read_data( addrIndex ); + if( addrIndex->retVal == MGU_NO_FILE ) { + /* We do not have a file - new user */ + debug_print( "New user... create new books...\n" ); + addrindex_create_new_books( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + /* Save index file */ + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_save_data( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + retVal = TRUE; + errFlag = FALSE; + } + else { + msg = _( "New user, could not save index file." ); + } + } + else { + msg = _( "New user, could not save address book files." ); + } + } + else { + /* We have an old file */ + if( addrIndex->wasConverted ) { + /* Converted successfully - save address index */ + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_save_data( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + msg = _( "Old address book converted successfully." ); + retVal = TRUE; + errFlag = FALSE; + } + else { + msg = _("Old address book converted,\n" + "could not save new address index file" ); + } + } + else { + /* File conversion failed - just create new books */ + debug_print( "File conversion failed... just create new books...\n" ); + addrindex_create_new_books( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + /* Save index */ + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_save_data( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + msg = _("Could not convert address book,\n" + "but created empty new address book files." ); + retVal = TRUE; + errFlag = FALSE; + } + else { + msg = _("Could not convert address book,\n" + "could not create new address book files." ); + } + } + else { + msg = _("Could not convert address book\n" + "and could not create new address book files." ); + } + } + } + if( errFlag ) { + debug_print( "Error\n%s\n", msg ); + alertpanel( _( "Addressbook conversion error" ), msg, _( "Close" ), NULL, NULL ); + } + else if( msg ) { + debug_print( "Warning\n%s\n", msg ); + alertpanel( _( "Addressbook conversion" ), msg, _( "Close" ), NULL, NULL ); + } + + return retVal; +} + +void addressbook_read_file( void ) { + AddressIndex *addrIndex = NULL; + + debug_print( "Reading address index...\n" ); + if( _addressIndex_ ) { + debug_print( "address book already read!!!\n" ); + return; + } + + addrIndex = addrindex_create_index(); + + /* Use new address book index. */ + addrindex_set_file_path( addrIndex, get_rc_dir() ); + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_read_data( addrIndex ); + if( addrIndex->retVal == MGU_NO_FILE ) { + /* Conversion required */ + debug_print( "Converting...\n" ); + if( addressbook_convert( addrIndex ) ) { + _addressIndex_ = addrIndex; + } + } + else if( addrIndex->retVal == MGU_SUCCESS ) { + _addressIndex_ = addrIndex; + } + else { + /* Error reading address book */ + debug_print( "Could not read address index.\n" ); + addrindex_print_index( addrIndex, stdout ); + alertpanel( _( "Addressbook Error" ), + _( "Could not read address index" ), + _( "Close" ), NULL, NULL ); + } + debug_print( "done.\n" ); +} + +#if 0 +void addressbook_read_file_old( void ) { + AddressIndex *addrIndex = NULL; + gboolean errFlag = TRUE; + gchar *msg = NULL; + + if( _addressIndex_ ) { + debug_print( "address book already read!!!\n" ); + return; + } + + addrIndex = addrindex_create_index(); + + /* Use use new address book. */ + /* addrindex_set_file_path( addrIndex, "/home/match/tmp/empty-dir" ); */ + addrindex_set_file_path( addrIndex, get_rc_dir() ); + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + + debug_print( "Reading address index...\n" ); + addrindex_read_data( addrIndex ); + if( addrIndex->retVal == MGU_NO_FILE ) { + /* Read old address book, performing conversion */ + debug_print( "Reading and converting old address book...\n" ); + addrindex_set_file_name( addrIndex, ADDRESSBOOK_OLD_FILE ); + addrindex_read_data( addrIndex ); + if( addrIndex->retVal == MGU_NO_FILE ) { + /* We do not have a file - new user */ + debug_print( "New user... create new books...\n" ); + addrindex_create_new_books( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + /* Save index file */ + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_save_data( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + errFlag = FALSE; + } + else { + msg = g_strdup( _( "New user, could not save index file." ) ); + } + } + else { + msg = g_strdup( _( "New user, could not save address book files." ) ); + } + } + else { + /* We have an old file */ + if( addrIndex->wasConverted ) { + /* Converted successfully - save address index */ + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_save_data( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + msg = g_strdup( _( "Old address book converted successfully." ) ); + errFlag = FALSE; + } + else { + msg = g_strdup( _( + "Old address book converted, " \ + "could not save new address index file" ) ); + } + } + else { + /* File conversion failed - just create new books */ + debug_print( "File conversion failed... just create new books...\n" ); + addrindex_create_new_books( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + /* Save index */ + addrindex_set_file_name( addrIndex, ADDRESSBOOK_INDEX_FILE ); + addrindex_save_data( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + msg = g_strdup( _( + "Could not convert address book, " \ + "but created empty new address book files." ) ); + errFlag = FALSE; + } + else { + msg = g_strdup( _( + "Could not convert address book, " \ + "could not create new address book files." ) ); + } + } + else { + msg = g_strdup( _( + "Could not convert address book " \ + "and could not create new address book files." ) ); + } + } + } + } + else if( addrIndex->retVal == MGU_SUCCESS ) { + errFlag = FALSE; + } + else { + debug_print( "Could not read address index.\n" ); + addrindex_print_index( addrIndex, stdout ); + msg = g_strdup( _( "Could not read address index" ) ); + } + _addressIndex_ = addrIndex; + + if( errFlag ) { + debug_print( "Error\n%s\n", msg ); + alertpanel( _( "Addressbook Conversion Error" ), msg, + _( "Close" ), NULL, NULL ); + } + else { + if( msg ) { + debug_print( "Warning\n%s\n", msg ); + alertpanel( _( "Addressbook Conversion" ), msg, + _( "Close" ), NULL, NULL ); + } + } + if( msg ) g_free( msg ); + debug_print( "done.\n" ); +} +#endif + +/* +* Add object into the address index tree widget. +* Enter: node Parent node. +* obj Object to add. +* Return: Node that was added, or NULL if object not added. +*/ +static GtkCTreeNode *addressbook_add_object(GtkCTreeNode *node, + AddressObject *obj) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + GtkCTreeNode *added; + AddressObject *pobj; + AddressObjectType otype; + AddressTypeControlItem *atci = NULL; + + g_return_val_if_fail(node != NULL, NULL); + g_return_val_if_fail(obj != NULL, NULL); + + pobj = gtk_ctree_node_get_row_data(ctree, node); + g_return_val_if_fail(pobj != NULL, NULL); + + /* Determine object type to be displayed */ + if( obj->type == ADDR_DATASOURCE ) { + otype = ADAPTER_DSOURCE(obj)->subType; + } + else { + otype = obj->type; + } + + /* Handle any special conditions. */ + added = node; + atci = addrbookctl_lookup( otype ); + if( atci ) { + if( atci->showInTree ) { + /* Add object to tree */ + gchar **name; + name = &obj->name; + added = gtk_ctree_insert_node( ctree, node, NULL, name, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, atci->iconXpmOpen, atci->maskXpmOpen, + atci->treeLeaf, atci->treeExpand ); + gtk_ctree_node_set_row_data(ctree, added, obj); + } + } + + gtk_ctree_sort_node(ctree, node); + + return added; +} + +/* +* Add group into the address index tree. +* Enter: node Parent node. +* ds Data source. +* itemGroup Group to add. +* Return: Inserted node. +*/ +static GtkCTreeNode *addressbook_node_add_group( GtkCTreeNode *node, AddressDataSource *ds, ItemGroup *itemGroup ) { + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + GtkCTreeNode *newNode; + AdapterGroup *adapter; + AddressTypeControlItem *atci = NULL; + gchar **name; + + if( ds == NULL ) return NULL; + if( node == NULL || itemGroup == NULL ) return NULL; + + name = &itemGroup->obj.name; + + atci = addrbookctl_lookup( ADDR_ITEM_GROUP ); + + adapter = g_new0( AdapterGroup, 1 ); + ADDRESS_OBJECT_TYPE(adapter) = ADDR_ITEM_GROUP; + ADDRESS_OBJECT_NAME(adapter) = g_strdup( ADDRITEM_NAME(itemGroup) ); + adapter->itemGroup = itemGroup; + + newNode = gtk_ctree_insert_node( ctree, node, NULL, name, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, atci->iconXpm, atci->maskXpm, + atci->treeLeaf, atci->treeExpand ); + gtk_ctree_node_set_row_data( ctree, newNode, adapter ); + gtk_ctree_sort_node( ctree, node ); + return newNode; +} + +/* +* Add folder into the address index tree. +* Enter: node Parent node. +* ds Data source. +* itemFolder Folder to add. +* otype Object type to display. +* Return: Inserted node. +*/ +static GtkCTreeNode *addressbook_node_add_folder( + GtkCTreeNode *node, AddressDataSource *ds, ItemFolder *itemFolder, AddressObjectType otype ) +{ + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + GtkCTreeNode *newNode = NULL; + AdapterFolder *adapter; + AddressTypeControlItem *atci = NULL; + GList *listItems = NULL; + gchar **name; + ItemFolder *rootFolder; + + if( ds == NULL ) return NULL; + if( node == NULL || itemFolder == NULL ) return NULL; + + /* Determine object type */ + atci = addrbookctl_lookup( otype ); + if( atci == NULL ) return NULL; + + rootFolder = addrindex_ds_get_root_folder( ds ); + if( itemFolder == rootFolder ) { + newNode = node; + } + else { + name = &itemFolder->obj.name; + + adapter = g_new0( AdapterFolder, 1 ); + ADDRESS_OBJECT_TYPE(adapter) = ADDR_ITEM_FOLDER; + ADDRESS_OBJECT_NAME(adapter) = g_strdup( ADDRITEM_NAME(itemFolder) ); + adapter->itemFolder = itemFolder; + + newNode = gtk_ctree_insert_node( ctree, node, NULL, name, FOLDER_SPACING, + atci->iconXpm, atci->maskXpm, atci->iconXpm, atci->maskXpm, + atci->treeLeaf, atci->treeExpand ); + gtk_ctree_node_set_row_data( ctree, newNode, adapter ); + } + + listItems = itemFolder->listFolder; + while( listItems ) { + ItemFolder *item = listItems->data; + addressbook_node_add_folder( newNode, ds, item, otype ); + listItems = g_list_next( listItems ); + } + listItems = itemFolder->listGroup; + while( listItems ) { + ItemGroup *item = listItems->data; + addressbook_node_add_group( newNode, ds, item ); + listItems = g_list_next( listItems ); + } + gtk_ctree_sort_node( ctree, node ); + return newNode; +} + +#if 0 +static void addressbook_delete_object(AddressObject *obj) { + AdapterDSource *ads = NULL; + AddressDataSource *ds = NULL; + if (!obj) return; + + /* Remove data source. */ + /* printf( "Delete obj type : %d\n", obj->type ); */ + + ads = ADAPTER_DSOURCE(obj); + if( ads == NULL ) return; + ds = ads->dataSource; + if( ds == NULL ) return; + + /* Remove data source */ + if( addrindex_index_remove_datasource( _addressIndex_, ds ) ) { + addrindex_free_datasource( _addressIndex_, ds ); + } + /* Free up Adapter object */ + g_free( ADAPTER_DSOURCE(obj) ); +} +#endif + +void addressbook_export_to_file( void ) { + if( _addressIndex_ ) { + /* Save all new address book data */ + debug_print( "Saving address books...\n" ); + addrindex_save_all_books( _addressIndex_ ); + + debug_print( "Exporting addressbook to file...\n" ); + addrindex_save_data( _addressIndex_ ); + if( _addressIndex_->retVal != MGU_SUCCESS ) { + addrindex_print_index( _addressIndex_, stdout ); + } + + /* Notify address completion of new data */ + invalidate_address_completion(); + } +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + addressbook_close(); + return FALSE; +} + +/* +* Comparsion using names of AddressItem objects. +*/ +/* +static gint addressbook_list_compare_func(GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2) +{ + AddressObject *obj1 = ((GtkCListRow *)ptr1)->data; + AddressObject *obj2 = ((GtkCListRow *)ptr2)->data; + gchar *name1 = NULL, *name2 = NULL; + if( obj1 ) name1 = obj1->name; + if( obj2 ) name2 = obj2->name; + if( ! name1 ) return ( name2 != NULL ); + if( ! name2 ) return -1; + return strcasecmp(name1, name2); +} +*/ + +/* +* Comparison using cell contents (text in first column). +*/ +static gint addressbook_list_compare_func( GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2 ) { + GtkCell *cell1 = ((GtkCListRow *)ptr1)->cell; + GtkCell *cell2 = ((GtkCListRow *)ptr2)->cell; + gchar *name1 = NULL, *name2 = NULL; + if( cell1 ) name1 = cell1->u.text; + if( cell2 ) name2 = cell2->u.text; + if( ! name1 ) return ( name2 != NULL ); + if( ! name2 ) return -1; + return strcasecmp( name1, name2 ); +} + +/* static */ +gint addressbook_obj_name_compare(gconstpointer a, gconstpointer b) +{ + const AddressObject *obj = a; + const gchar *name = b; + AddressTypeControlItem *atci = NULL; + + if (!obj || !name) return -1; + + atci = addrbookctl_lookup( obj->type ); + if( ! atci ) return -1; + if( ! obj->name ) return -1; + return strcasecmp(obj->name, name); +} + +#if 0 +static void addressbook_book_show_message( AddressBookFile *abf ) { + *addressbook_msgbuf = '\0'; + if( abf ) { + if( abf->retVal == MGU_SUCCESS ) { + sprintf( addressbook_msgbuf, "%s", abf->name ); + } + else { + sprintf( addressbook_msgbuf, "%s: %s", abf->name, mgu_error2string( abf->retVal ) ); + } + } + addressbook_status_show( addressbook_msgbuf ); +} +#endif + +static void addressbook_new_book_cb( gpointer data, guint action, GtkWidget *widget ) { + AdapterDSource *ads; + AdapterInterface *adapter; + + adapter = addrbookctl_find_interface( ADDR_IF_BOOK ); + if( adapter == NULL ) return; + if( addrbook.treeSelected == NULL ) return; + if( addrbook.treeSelected != adapter->treeNode ) return; + ads = addressbook_edit_book( _addressIndex_, NULL ); + if( ads ) { + addressbook_add_object( addrbook.treeSelected, ADDRESS_OBJECT(ads) ); + if( addrbook.treeSelected == addrbook.opened ) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } +} + +static void addressbook_new_vcard_cb( gpointer data, guint action, GtkWidget *widget ) { + AdapterDSource *ads; + AdapterInterface *adapter; + + adapter = addrbookctl_find_interface( ADDR_IF_VCARD ); + if( adapter == NULL ) return; + if( addrbook.treeSelected != adapter->treeNode ) return; + ads = addressbook_edit_vcard( _addressIndex_, NULL ); + if( ads ) { + addressbook_add_object( addrbook.treeSelected, ADDRESS_OBJECT(ads) ); + if( addrbook.treeSelected == addrbook.opened ) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } +} + +#if 0 +static void addressbook_vcard_show_message( VCardFile *vcf ) { + *addressbook_msgbuf = '\0'; + if( vcf ) { + if( vcf->retVal == MGU_SUCCESS ) { + sprintf( addressbook_msgbuf, "%s", vcf->name ); + } + else { + sprintf( addressbook_msgbuf, "%s: %s", vcf->name, mgu_error2string( vcf->retVal ) ); + } + } + addressbook_status_show( addressbook_msgbuf ); +} +#endif + +#ifdef USE_JPILOT +static void addressbook_new_jpilot_cb( gpointer data, guint action, GtkWidget *widget ) { + AdapterDSource *ads; + AdapterInterface *adapter; + AddressInterface *iface; + + adapter = addrbookctl_find_interface( ADDR_IF_JPILOT ); + if( adapter == NULL ) return; + if( addrbook.treeSelected != adapter->treeNode ) return; + iface = adapter->interface; + if( ! iface->haveLibrary ) return; + ads = addressbook_edit_jpilot( _addressIndex_, NULL ); + if( ads ) { + addressbook_add_object( addrbook.treeSelected, ADDRESS_OBJECT(ads) ); + if( addrbook.treeSelected == addrbook.opened ) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } +} + +#if 0 +static void addressbook_jpilot_show_message( JPilotFile *jpf ) { + *addressbook_msgbuf = '\0'; + if( jpf ) { + if( jpf->retVal == MGU_SUCCESS ) { + sprintf( addressbook_msgbuf, "%s", jpf->name ); + } + else { + sprintf( addressbook_msgbuf, "%s: %s", jpf->name, mgu_error2string( jpf->retVal ) ); + } + } + addressbook_status_show( addressbook_msgbuf ); +} +#endif +#endif /* USE_JPILOT */ + +#ifdef USE_LDAP +static void addressbook_new_ldap_cb( gpointer data, guint action, GtkWidget *widget ) { + AdapterDSource *ads; + AdapterInterface *adapter; + AddressInterface *iface; + + adapter = addrbookctl_find_interface( ADDR_IF_LDAP ); + if( adapter == NULL ) return; + if( addrbook.treeSelected != adapter->treeNode ) return; + iface = adapter->interface; + if( ! iface->haveLibrary ) return; + ads = addressbook_edit_ldap( _addressIndex_, NULL ); + if( ads ) { + addressbook_add_object( addrbook.treeSelected, ADDRESS_OBJECT(ads) ); + if( addrbook.treeSelected == addrbook.opened ) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), addrbook.opened ); + } + } +} + +static void addressbook_ldap_show_message( SyldapServer *svr ) { + *addressbook_msgbuf = '\0'; + if( svr ) { + if( svr->busyFlag ) { + sprintf( addressbook_msgbuf, "%s: %s", svr->name, ADDRESSBOOK_LDAP_BUSYMSG ); + } + else { + if( svr->retVal == MGU_SUCCESS ) { + sprintf( addressbook_msgbuf, "%s", svr->name ); + } + else { + sprintf( addressbook_msgbuf, "%s: %s", svr->name, mgu_error2string( svr->retVal ) ); + } + } + } + addressbook_status_show( addressbook_msgbuf ); +} + +static void ldapsearch_callback( SyldapServer *sls ) { + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + AddressObject *obj; + AdapterDSource *ads = NULL; + AddressDataSource *ds = NULL; + AddressInterface *iface = NULL; + + if( sls == NULL ) return; + if( ! addrbook.treeSelected ) return; + if( GTK_CTREE_ROW( addrbook.treeSelected )->level == 1 ) return; + + obj = gtk_ctree_node_get_row_data( ctree, addrbook.treeSelected ); + if( obj == NULL ) return; + if( obj->type == ADDR_DATASOURCE ) { + ads = ADAPTER_DSOURCE(obj); + if( ads->subType == ADDR_LDAP ) { + SyldapServer *server; + + ds = ads->dataSource; + if( ds == NULL ) return; + iface = ds->interface; + if( ! iface->haveLibrary ) return; + server = ds->rawDataSource; + if( server == sls ) { + /* Read from cache */ + gtk_widget_show_all(addrbook.window); + addressbook_set_clist( obj ); + addressbook_ldap_show_message( sls ); + gtk_widget_show_all(addrbook.window); + gtk_entry_set_text( GTK_ENTRY(addrbook.entry), "" ); + } + } + } +} +#endif + +/* + * Lookup button handler. + */ +static void addressbook_lup_clicked( GtkButton *button, gpointer data ) { + GtkCTree *ctree = GTK_CTREE(addrbook.ctree); + AddressObject *obj; + AdapterDSource *ads = NULL; +#ifdef USE_LDAP + AddressDataSource *ds = NULL; + AddressInterface *iface = NULL; +#endif /* USE_LDAP */ + gchar *sLookup; + + if( ! addrbook.treeSelected ) return; + if( GTK_CTREE_ROW( addrbook.treeSelected )->level == 1 ) return; + + obj = gtk_ctree_node_get_row_data( ctree, addrbook.treeSelected ); + if( obj == NULL ) return; + + sLookup = gtk_editable_get_chars( GTK_EDITABLE(addrbook.entry), 0, -1 ); + g_strchomp( sLookup ); + + if( obj->type == ADDR_DATASOURCE ) { + ads = ADAPTER_DSOURCE(obj); +#ifdef USE_LDAP + if( ads->subType == ADDR_LDAP ) { + SyldapServer *server; + + ds = ads->dataSource; + if( ds == NULL ) return; + iface = ds->interface; + if( ! iface->haveLibrary ) return; + server = ds->rawDataSource; + if( server ) { + syldap_cancel_read( server ); + if( *sLookup == '\0' || strlen( sLookup ) < 1 ) return; + syldap_set_search_value( server, sLookup ); + syldap_set_callback( server, ldapsearch_callback ); + syldap_read_data_th( server ); + addressbook_ldap_show_message( server ); + } + } +#endif /* USE_LDAP */ + } + + g_free( sLookup ); +} + +/* ********************************************************************** +* Build lookup tables. +* *********************************************************************** +*/ + +/* +* Build table that controls the rendering of object types. +*/ +void addrbookctl_build_map( GtkWidget *window ) { + AddressTypeControlItem *atci; + + /* Build icons */ + stock_pixmap_gdk(window, STOCK_PIXMAP_DIR_CLOSE, &folderxpm, &folderxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_DIR_OPEN, &folderopenxpm, &folderopenxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_GROUP, &groupxpm, &groupxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_VCARD, &vcardxpm, &vcardxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_BOOK, &bookxpm, &bookxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_ADDRESS, &addressxpm, &addressxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_JPILOT, &jpilotxpm, &jpilotxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_CATEGORY, &categoryxpm, &categoryxpmmask); + stock_pixmap_gdk(window, STOCK_PIXMAP_LDAP, &ldapxpm, &ldapxpmmask); + + _addressBookTypeHash_ = g_hash_table_new( g_int_hash, g_int_equal ); + _addressBookTypeList_ = NULL; + + /* Interface */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_INTERFACE; + atci->interfaceType = ADDR_IF_NONE; + atci->showInTree = TRUE; + atci->treeExpand = TRUE; + atci->treeLeaf = FALSE; + atci->displayName = _( "Interface" ); + atci->iconXpm = folderxpm; + atci->maskXpm = folderxpmmask; + atci->iconXpmOpen = folderopenxpm; + atci->maskXpmOpen = folderopenxpmmask; + atci->menuCommand = NULL; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* Address book */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_BOOK; + atci->interfaceType = ADDR_IF_BOOK; + atci->showInTree = TRUE; + atci->treeExpand = TRUE; + atci->treeLeaf = FALSE; + atci->displayName = _( "Address Book" ); + atci->iconXpm = bookxpm; + atci->maskXpm = bookxpmmask; + atci->iconXpmOpen = bookxpm; + atci->maskXpmOpen = bookxpmmask; + atci->menuCommand = "/File/New Book"; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* Item person */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_ITEM_PERSON; + atci->interfaceType = ADDR_IF_NONE; + atci->showInTree = FALSE; + atci->treeExpand = FALSE; + atci->treeLeaf = FALSE; + atci->displayName = _( "Person" ); + atci->iconXpm = NULL; + atci->maskXpm = NULL; + atci->iconXpmOpen = NULL; + atci->maskXpmOpen = NULL; + atci->menuCommand = NULL; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* Item email */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_ITEM_EMAIL; + atci->interfaceType = ADDR_IF_NONE; + atci->showInTree = FALSE; + atci->treeExpand = FALSE; + atci->treeLeaf = TRUE; + atci->displayName = _( "EMail Address" ); + atci->iconXpm = addressxpm; + atci->maskXpm = addressxpmmask; + atci->iconXpmOpen = addressxpm; + atci->maskXpmOpen = addressxpmmask; + atci->menuCommand = NULL; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* Item group */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_ITEM_GROUP; + atci->interfaceType = ADDR_IF_BOOK; + atci->showInTree = TRUE; + atci->treeExpand = FALSE; + atci->treeLeaf = FALSE; + atci->displayName = _( "Group" ); + atci->iconXpm = groupxpm; + atci->maskXpm = groupxpmmask; + atci->iconXpmOpen = groupxpm; + atci->maskXpmOpen = groupxpmmask; + atci->menuCommand = NULL; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* Item folder */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_ITEM_FOLDER; + atci->interfaceType = ADDR_IF_BOOK; + atci->showInTree = TRUE; + atci->treeExpand = FALSE; + atci->treeLeaf = FALSE; + atci->displayName = _( "Folder" ); + atci->iconXpm = folderxpm; + atci->maskXpm = folderxpmmask; + atci->iconXpmOpen = folderopenxpm; + atci->maskXpmOpen = folderopenxpmmask; + atci->menuCommand = NULL; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* vCard */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_VCARD; + atci->interfaceType = ADDR_IF_VCARD; + atci->showInTree = TRUE; + atci->treeExpand = TRUE; + atci->treeLeaf = TRUE; + atci->displayName = _( "vCard" ); + atci->iconXpm = vcardxpm; + atci->maskXpm = vcardxpmmask; + atci->iconXpmOpen = vcardxpm; + atci->maskXpmOpen = vcardxpmmask; + atci->menuCommand = "/File/New vCard"; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* JPilot */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_JPILOT; + atci->interfaceType = ADDR_IF_JPILOT; + atci->showInTree = TRUE; + atci->treeExpand = TRUE; + atci->treeLeaf = FALSE; + atci->displayName = _( "JPilot" ); + atci->iconXpm = jpilotxpm; + atci->maskXpm = jpilotxpmmask; + atci->iconXpmOpen = jpilotxpm; + atci->maskXpmOpen = jpilotxpmmask; + atci->menuCommand = "/File/New JPilot"; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* Category */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_CATEGORY; + atci->interfaceType = ADDR_IF_JPILOT; + atci->showInTree = TRUE; + atci->treeExpand = TRUE; + atci->treeLeaf = TRUE; + atci->displayName = _( "JPilot" ); + atci->iconXpm = categoryxpm; + atci->maskXpm = categoryxpmmask; + atci->iconXpmOpen = categoryxpm; + atci->maskXpmOpen = categoryxpmmask; + atci->menuCommand = NULL; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + + /* LDAP Server */ + atci = g_new0( AddressTypeControlItem, 1 ); + atci->objectType = ADDR_LDAP; + atci->interfaceType = ADDR_IF_LDAP; + atci->showInTree = TRUE; + atci->treeExpand = TRUE; + atci->treeLeaf = TRUE; + atci->displayName = _( "LDAP Server" ); + atci->iconXpm = ldapxpm; + atci->maskXpm = ldapxpmmask; + atci->iconXpmOpen = ldapxpm; + atci->maskXpmOpen = ldapxpmmask; + atci->menuCommand = "/File/New Server"; + g_hash_table_insert( _addressBookTypeHash_, &atci->objectType, atci ); + _addressBookTypeList_ = g_list_append( _addressBookTypeList_, atci ); + +} + +/* +* Search for specified object type. +*/ +AddressTypeControlItem *addrbookctl_lookup( gint ot ) { + gint objType = ot; + return ( AddressTypeControlItem * ) g_hash_table_lookup( _addressBookTypeHash_, &objType ); +} + +/* +* Search for specified interface type. +*/ +AddressTypeControlItem *addrbookctl_lookup_iface( AddressIfType ifType ) { + GList *node = _addressBookTypeList_; + while( node ) { + AddressTypeControlItem *atci = node->data; + if( atci->interfaceType == ifType ) return atci; + node = g_list_next( node ); + } + return NULL; +} + +static void addrbookctl_free_address( AddressObject *obj ) { + g_free( obj->name ); + obj->type = ADDR_NONE; + obj->name = NULL; +} + +static void addrbookctl_free_interface( AdapterInterface *adapter ) { + addrbookctl_free_address( ADDRESS_OBJECT(adapter) ); + adapter->interface = NULL; + adapter->interfaceType = ADDR_IF_NONE; + adapter->atci = NULL; + adapter->enabled = FALSE; + adapter->haveLibrary = FALSE; + adapter->treeNode = NULL; + g_free( adapter ); +} + +static void addrbookctl_free_datasource( AdapterDSource *adapter ) { + addrbookctl_free_address( ADDRESS_OBJECT(adapter) ); + adapter->dataSource = NULL; + adapter->subType = ADDR_NONE; + g_free( adapter ); +} + +static void addrbookctl_free_folder( AdapterFolder *adapter ) { + addrbookctl_free_address( ADDRESS_OBJECT(adapter) ); + adapter->itemFolder = NULL; + g_free( adapter ); +} + +static void addrbookctl_free_group( AdapterGroup *adapter ) { + addrbookctl_free_address( ADDRESS_OBJECT(adapter) ); + adapter->itemGroup = NULL; + g_free( adapter ); +} + +/* + * Build GUI interface list. + */ +void addrbookctl_build_iflist() { + AddressTypeControlItem *atci; + AdapterInterface *adapter; + GList *list = NULL; + + if( _addressIndex_ == NULL ) { + _addressIndex_ = addrindex_create_index(); + } + _addressInterfaceList_ = NULL; + list = addrindex_get_interface_list( _addressIndex_ ); + while( list ) { + AddressInterface *interface = list->data; + atci = addrbookctl_lookup_iface( interface->type ); + if( atci ) { + adapter = g_new0( AdapterInterface, 1 ); + adapter->interfaceType = interface->type; + adapter->atci = atci; + adapter->interface = interface; + adapter->treeNode = NULL; + adapter->enabled = TRUE; + adapter->haveLibrary = interface->haveLibrary; + ADDRESS_OBJECT(adapter)->type = ADDR_INTERFACE; + ADDRESS_OBJECT_NAME(adapter) = g_strdup( atci->displayName ); + _addressInterfaceList_ = g_list_append( _addressInterfaceList_, adapter ); + } + list = g_list_next( list ); + } +} + +void addrbookctl_free_selection( GList *list ) { + GList *node = list; + while( node ) { + AdapterInterface *adapter = node->data; + adapter = NULL; + node = g_list_next( node ); + } + g_list_free( list ); +} + +/* +* Find GUI interface type specified interface type. +* Return: Interface item, or NULL if not found. +*/ +AdapterInterface *addrbookctl_find_interface( AddressIfType ifType ) { + GList *node = _addressInterfaceList_; + while( node ) { + AdapterInterface *adapter = node->data; + if( adapter->interfaceType == ifType ) return adapter; + node = g_list_next( node ); + } + return NULL; +} + +/* +* Build interface list selection. +*/ +void addrbookctl_build_ifselect() { + GList *newList = NULL; + gchar *selectStr; + gchar **splitStr; + gint ifType; + gint i; + gchar *endptr = NULL; + gboolean enabled; + AdapterInterface *adapter; + /* GList *node; */ + + selectStr = g_strdup( ADDRESSBOOK_IFACE_SELECTION ); + + /* Parse string */ + splitStr = g_strsplit( selectStr, ",", -1 ); + for( i = 0; i < ADDRESSBOOK_MAX_IFACE; i++ ) { + if( splitStr[i] ) { + /* printf( "%d : %s\n", i, splitStr[i] ); */ + ifType = strtol( splitStr[i], &endptr, 10 ); + enabled = TRUE; + if( *endptr ) { + if( strcmp( endptr, "/n" ) == 0 ) { + enabled = FALSE; + } + } + /* printf( "\t%d : %s\n", ifType, enabled ? "yes" : "no" ); */ + adapter = addrbookctl_find_interface( ifType ); + if( adapter ) { + newList = g_list_append( newList, adapter ); + } + } + else { + break; + } + } + /* printf( "i=%d\n", i ); */ + g_strfreev( splitStr ); + g_free( selectStr ); + + /* Replace existing list */ + mgu_clear_list( _addressIFaceSelection_ ); + g_list_free( _addressIFaceSelection_ ); + _addressIFaceSelection_ = newList; + newList = NULL; + +} + +/* ********************************************************************** +* Add sender to address book. +* *********************************************************************** +*/ + +/* + * This function is used by the Add sender to address book function. + */ +gboolean addressbook_add_contact( const gchar *name, const gchar *address, const gchar *remarks ) { + debug_print( "addressbook_add_contact: name/address: %s - %s\n", name, address ); + if( addressadd_selection( _addressIndex_, name, address, remarks ) ) { + debug_print( "addressbook_add_contact - added\n" ); + addressbook_refresh(); + } + return TRUE; +} + +/* ********************************************************************** +* Address completion support. +* *********************************************************************** +*/ + +/* +* This function is used by the address completion function to load +* addresses. +* Enter: callBackFunc Function to be called when an address is +* to be loaded. +* Return: TRUE if data loaded, FALSE if address index not loaded. +*/ +gboolean addressbook_load_completion( gint (*callBackFunc) ( const gchar *, const gchar * ) ) { + /* AddressInterface *interface; */ + AddressDataSource *ds; + GList *nodeIf, *nodeDS; + GList *listP, *nodeP; + GList *nodeM; + gchar *sName, *sAddress, *sAlias, *sFriendly; + + debug_print( "addressbook_load_completion\n" ); + + if( _addressIndex_ == NULL ) return FALSE; + + nodeIf = addrindex_get_interface_list( _addressIndex_ ); + while( nodeIf ) { + AddressInterface *interface = nodeIf->data; + nodeDS = interface->listSource; + while( nodeDS ) { + ds = nodeDS->data; + + /* Read address book */ + if( ! addrindex_ds_get_read_flag( ds ) ) { + addrindex_ds_read_data( ds ); + } + + /* Get all persons */ + listP = addrindex_ds_get_all_persons( ds ); + nodeP = listP; + while( nodeP ) { + ItemPerson *person = nodeP->data; + nodeM = person->listEMail; + + /* Figure out name to use */ + sName = person->nickName; + if( sName == NULL || *sName == '\0' ) { + sName = ADDRITEM_NAME(person); + } + + /* Process each E-Mail address */ + while( nodeM ) { + ItemEMail *email = nodeM->data; + /* Have mail */ + sFriendly = sName; + sAddress = email->address; + if( sAddress || *sAddress != '\0' ) { + sAlias = ADDRITEM_NAME(email); + if( sAlias && *sAlias != '\0' ) { + sFriendly = sAlias; + } + ( callBackFunc ) ( sFriendly, sAddress ); + } + + nodeM = g_list_next( nodeM ); + } + nodeP = g_list_next( nodeP ); + } + /* Free up the list */ + g_list_free( listP ); + + nodeDS = g_list_next( nodeDS ); + } + nodeIf = g_list_next( nodeIf ); + } + debug_print( "addressbook_load_completion... done\n" ); + + return TRUE; +} + +/* ********************************************************************** +* Address Import. +* *********************************************************************** +*/ + +/* +* Import LDIF file. +*/ +static void addressbook_import_ldif_cb() { + AddressDataSource *ds = NULL; + AdapterDSource *ads = NULL; + AddressBookFile *abf = NULL; + AdapterInterface *adapter; + GtkCTreeNode *newNode; + + adapter = addrbookctl_find_interface( ADDR_IF_BOOK ); + if ( !adapter || !adapter->treeNode ) return; + + abf = addressbook_imp_ldif( _addressIndex_ ); + if ( !abf ) return; + + ds = addrindex_index_add_datasource( _addressIndex_, ADDR_IF_BOOK, abf ); + ads = addressbook_create_ds_adapter( ds, ADDR_BOOK, NULL ); + addressbook_ads_set_name( ads, abf->name ); + newNode = addressbook_add_object( adapter->treeNode, ADDRESS_OBJECT(ads) ); + if ( newNode ) { + gtk_ctree_select( GTK_CTREE(addrbook.ctree), newNode ); + addrbook.treeSelected = newNode; + } + + /* Notify address completion */ + invalidate_address_completion(); +} + +/* +* End of Source. +*/ diff --git a/src/addressbook.h b/src/addressbook.h new file mode 100644 index 00000000..59512d53 --- /dev/null +++ b/src/addressbook.h @@ -0,0 +1,49 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ADDRESSBOOK_H__ +#define __ADDRESSBOOK_H__ + +#include +#include + +#include "compose.h" + +void addressbook_open (Compose *target); +void addressbook_set_target_compose (Compose *target); +Compose *addressbook_get_target_compose (void); +void addressbook_read_file (void); +void addressbook_export_to_file (void); +gint addressbook_obj_name_compare (gconstpointer a, + gconstpointer b); +/* static gint addressbook_obj_name_compare(gconstpointer a, + gconstpointer b); */ + +/* provisional API for accessing the address book */ + +void addressbook_access (void); +void addressbook_unaccess (void); + +gboolean addressbook_add_contact ( const gchar *name, + const gchar *address, + const gchar *remarks ); + +gboolean addressbook_load_completion ( gint (*callBackFunc) ( const gchar *, const gchar * ) ); + +#endif /* __ADDRESSBOOK_H__ */ diff --git a/src/addressitem.h b/src/addressitem.h new file mode 100644 index 00000000..919bf40e --- /dev/null +++ b/src/addressitem.h @@ -0,0 +1,158 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Address item data. Shared among GUI components only. + */ + +#ifndef __ADDRESSITEM_H__ +#define __ADDRESSITEM_H__ + +#include +#include +#include + +#include "compose.h" +#include "addrindex.h" + +#define ADDRESS_OBJECT(obj) ((AddressObject *)obj) +#define ADDRESS_OBJECT_TYPE(obj) (ADDRESS_OBJECT(obj)->type) +#define ADDRESS_OBJECT_NAME(obj) (ADDRESS_OBJECT(obj)->name) + +#define ADAPTER_INTERFACE(obj) ((AdapterInterface *)obj) +#define ADAPTER_FOLDER(obj) ((AdapterFolder *)obj) +#define ADAPTER_GROUP(obj) ((AdapterGroup *)obj) +#define ADAPTER_DSOURCE(obj) ((AdapterDSource *)obj) + +typedef enum { + ADDR_NONE, + ADDR_ITEM_PERSON, + ADDR_ITEM_EMAIL, + ADDR_ITEM_FOLDER, + ADDR_ITEM_GROUP, + ADDR_INTERFACE, + ADDR_DATASOURCE, + ADDR_BOOK, /* Sub-type */ + ADDR_VCARD, /* Sub-type */ + ADDR_JPILOT, /* Sub-type */ + ADDR_CATEGORY, /* Sub-type */ + ADDR_LDAP /* Sub-type */ +} AddressObjectType; + +typedef struct _AddressBook_win AddressBook_win; +struct _AddressBook_win +{ + GtkWidget *window; + GtkWidget *menubar; + GtkWidget *ctree; + GtkWidget *clist; + GtkWidget *entry; + GtkWidget *statusbar; + + GtkWidget *del_btn; + GtkWidget *reg_btn; + GtkWidget *lup_btn; + GtkWidget *to_btn; + GtkWidget *cc_btn; + GtkWidget *bcc_btn; + + GtkWidget *tree_popup; + GtkWidget *list_popup; + GtkItemFactory *tree_factory; + GtkItemFactory *list_factory; + GtkItemFactory *menu_factory; + + GtkCTreeNode *treeSelected; + GtkCTreeNode *opened; + GtkCTreeNode *listSelected; + + Compose *target_compose; + gint status_cid; +}; + +typedef struct _AddressTypeControlItem AddressTypeControlItem; +struct _AddressTypeControlItem { + AddressObjectType objectType; + AddressIfType interfaceType; + gchar *displayName; + gboolean showInTree; + gboolean treeExpand; + gboolean treeLeaf; + gchar *menuCommand; + GdkPixmap *iconXpm; + GdkBitmap *maskXpm; + GdkPixmap *iconXpmOpen; + GdkBitmap *maskXpmOpen; +}; + +typedef struct _AddressObject AddressObject; +struct _AddressObject { + AddressObjectType type; + gchar *name; +}; + +typedef struct _AdapterInterface AdapterInterface; +struct _AdapterInterface { + AddressObject obj; + AddressInterface *interface; + AddressIfType interfaceType; + AddressTypeControlItem *atci; + gboolean enabled; + gboolean haveLibrary; + GtkCTreeNode *treeNode; +}; + +typedef struct _AdapterDSource AdapterDSource; +struct _AdapterDSource { + AddressObject obj; + AddressDataSource *dataSource; + AddressObjectType subType; +}; + +typedef struct _AdapterFolder AdapterFolder; +struct _AdapterFolder { + AddressObject obj; + ItemFolder *itemFolder; +}; + +typedef struct _AdapterGroup AdapterGroup; +struct _AdapterGroup { + AddressObject obj; + ItemGroup *itemGroup; +}; + +typedef struct _AddressFileSelection AddressFileSelection; +struct _AddressFileSelection { + GtkWidget *fileSelector; + gboolean cancelled; +}; + +AdapterDSource *addressbook_create_ds_adapter ( AddressDataSource *ds, + AddressObjectType otype, + gchar *name ); + +void addressbook_ads_set_name ( AdapterDSource *adapter, + gchar *value ); + +#endif /* __ADDRESSITEM_H__ */ + +/* +* End of Source. +*/ + diff --git a/src/addrindex.c b/src/addrindex.c new file mode 100644 index 00000000..07d57711 --- /dev/null +++ b/src/addrindex.c @@ -0,0 +1,1892 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * General functions for accessing address index file. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include + +#include "intl.h" +#include "mgutils.h" +#include "addritem.h" +#include "addrcache.h" +#include "addrbook.h" +#include "addrindex.h" +#include "xml.h" + +#ifndef DEV_STANDALONE +#include "prefs.h" +#include "codeconv.h" +#endif + +#include "vcard.h" + +#ifdef USE_JPILOT +#include "jpilot.h" +#endif + +#ifdef USE_LDAP +#include "syldap.h" +#endif + +#define TAG_ADDRESS_INDEX "addressbook" + +#define TAG_IF_ADDRESS_BOOK "book_list" +#define TAG_IF_VCARD "vcard_list" +#define TAG_IF_JPILOT "jpilot_list" +#define TAG_IF_LDAP "ldap_list" + +#define TAG_DS_ADDRESS_BOOK "book" +#define TAG_DS_VCARD "vcard" +#define TAG_DS_JPILOT "jpilot" +#define TAG_DS_LDAP "server" + +/* XML Attribute names */ +#define ATTAG_BOOK_NAME "name" +#define ATTAG_BOOK_FILE "file" + +#define ATTAG_VCARD_NAME "name" +#define ATTAG_VCARD_FILE "file" + +#define ATTAG_JPILOT_NAME "name" +#define ATTAG_JPILOT_FILE "file" +#define ATTAG_JPILOT_CUSTOM_1 "custom-1" +#define ATTAG_JPILOT_CUSTOM_2 "custom-2" +#define ATTAG_JPILOT_CUSTOM_3 "custom-3" +#define ATTAG_JPILOT_CUSTOM_4 "custom-4" +#define ATTAG_JPILOT_CUSTOM "custom-" + +#define ATTAG_LDAP_NAME "name" +#define ATTAG_LDAP_HOST "host" +#define ATTAG_LDAP_PORT "port" +#define ATTAG_LDAP_BASE_DN "base-dn" +#define ATTAG_LDAP_BIND_DN "bind-dn" +#define ATTAG_LDAP_BIND_PASS "bind-pass" +#define ATTAG_LDAP_CRITERIA "criteria" +#define ATTAG_LDAP_MAX_ENTRY "max-entry" +#define ATTAG_LDAP_TIMEOUT "timeout" + +#if 0 +N_("Common address") +N_("Personal address") +#endif + +#define DISP_NEW_COMMON _("Common address") +#define DISP_NEW_PERSONAL _("Personal address") + +/* Old address book */ +#define TAG_IF_OLD_COMMON "common_address" +#define TAG_IF_OLD_PERSONAL "personal_address" + +#define DISP_OLD_COMMON _("Common address") +#define DISP_OLD_PERSONAL _("Personal address") + +typedef struct _AddressIfAttr AddressIfAttrib; +struct _AddressIfAttr { + gchar *name; + gchar *value; +}; + +/* +* Build interface with default values. +*/ +static AddressInterface *addrindex_create_interface( gint type, gchar *name, gchar *tagIf, gchar *tagDS ) { + AddressInterface *iface = g_new0( AddressInterface, 1 ); + ADDRITEM_TYPE(iface) = ITEMTYPE_INTERFACE; + ADDRITEM_ID(iface) = NULL; + ADDRITEM_NAME(iface) = g_strdup( name ); + ADDRITEM_PARENT(iface) = NULL; + ADDRITEM_SUBTYPE(iface) = type; + iface->type = type; + iface->name = g_strdup( name ); + iface->listTag = g_strdup( tagIf ); + iface->itemTag = g_strdup( tagDS ); + iface->legacyFlag = FALSE; + iface->haveLibrary = TRUE; + iface->useInterface = TRUE; + iface->readOnly = TRUE; + iface->getAccessFlag = NULL; + iface->getModifyFlag = NULL; + iface->getReadFlag = NULL; + iface->getStatusCode = NULL; + iface->getReadData = NULL; + iface->getRootFolder = NULL; + iface->getListFolder = NULL; + iface->getListPerson = NULL; + iface->getAllPersons = NULL; + iface->getAllGroups = NULL; + iface->getName = NULL; + iface->listSource = NULL; + return iface; +} + +/* +* Build table of interfaces. +*/ +static void addrindex_build_if_list( AddressIndex *addrIndex ) { + AddressInterface *iface; + + iface = addrindex_create_interface( ADDR_IF_BOOK, "Address Book", TAG_IF_ADDRESS_BOOK, TAG_DS_ADDRESS_BOOK ); + iface->readOnly = FALSE; + iface->getModifyFlag = ( void * ) addrbook_get_modified; + iface->getAccessFlag = ( void * ) addrbook_get_accessed; + iface->getReadFlag = ( void * ) addrbook_get_read_flag; + iface->getStatusCode = ( void * ) addrbook_get_status; + iface->getReadData = ( void * ) addrbook_read_data; + iface->getRootFolder = ( void * ) addrbook_get_root_folder; + iface->getListFolder = ( void * ) addrbook_get_list_folder; + iface->getListPerson = ( void * ) addrbook_get_list_person; + iface->getAllPersons = ( void * ) addrbook_get_all_persons; + iface->getName = ( void * ) addrbook_get_name; + iface->setAccessFlag = ( void * ) addrbook_set_accessed; + addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface ); + ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex); + + iface = addrindex_create_interface( ADDR_IF_VCARD, "vCard", TAG_IF_VCARD, TAG_DS_VCARD ); + iface->getModifyFlag = ( void * ) vcard_get_modified; + iface->getAccessFlag = ( void * ) vcard_get_accessed; + iface->getReadFlag = ( void * ) vcard_get_read_flag; + iface->getStatusCode = ( void * ) vcard_get_status; + iface->getReadData = ( void * ) vcard_read_data; + iface->getRootFolder = ( void * ) vcard_get_root_folder; + iface->getListFolder = ( void * ) vcard_get_list_folder; + iface->getListPerson = ( void * ) vcard_get_list_person; + iface->getAllPersons = ( void * ) vcard_get_all_persons; + iface->getName = ( void * ) vcard_get_name; + iface->setAccessFlag = ( void * ) vcard_set_accessed; + addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface ); + ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex); + + iface = addrindex_create_interface( ADDR_IF_JPILOT, "JPilot", TAG_IF_JPILOT, TAG_DS_JPILOT ); +#ifdef USE_JPILOT + /* iface->haveLibrary = jpilot_test_pilot_lib(); */ + iface->haveLibrary = TRUE; + iface->useInterface = iface->haveLibrary; + iface->getModifyFlag = ( void * ) jpilot_get_modified; + iface->getAccessFlag = ( void * ) jpilot_get_accessed; + iface->getReadFlag = ( void * ) jpilot_get_read_flag; + iface->getStatusCode = ( void * ) jpilot_get_status; + iface->getReadData = ( void * ) jpilot_read_data; + iface->getRootFolder = ( void * ) jpilot_get_root_folder; + iface->getListFolder = ( void * ) jpilot_get_list_folder; + iface->getListPerson = ( void * ) jpilot_get_list_person; + iface->getAllPersons = ( void * ) jpilot_get_all_persons; + iface->getName = ( void * ) jpilot_get_name; + iface->setAccessFlag = ( void * ) jpilot_set_accessed; +#else + iface->useInterface = FALSE; + iface->haveLibrary = FALSE; +#endif + addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface ); + ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex); + + iface = addrindex_create_interface( ADDR_IF_LDAP, "LDAP", TAG_IF_LDAP, TAG_DS_LDAP ); +#ifdef USE_LDAP + /* iface->haveLibrary = syldap_test_ldap_lib(); */ + iface->haveLibrary = TRUE; + iface->useInterface = iface->haveLibrary; + iface->getAccessFlag = ( void * ) syldap_get_accessed; + /* iface->getModifyFlag = ( void * ) syldap_get_modified; */ + /* iface->getReadFlag = ( void * ) syldap_get_read_flag; */ + iface->getStatusCode = ( void * ) syldap_get_status; + iface->getReadData = ( void * ) syldap_read_data; + iface->getRootFolder = ( void * ) syldap_get_root_folder; + iface->getListFolder = ( void * ) syldap_get_list_folder; + iface->getListPerson = ( void * ) syldap_get_list_person; + iface->getName = ( void * ) syldap_get_name; + iface->setAccessFlag = ( void * ) syldap_set_accessed; +#else + iface->useInterface = FALSE; + iface->haveLibrary = FALSE; +#endif + addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface ); + ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex); + + /* Two old legacy data sources */ + iface = addrindex_create_interface( ADDR_IF_COMMON, "Old Address - common", TAG_IF_OLD_COMMON, NULL ); + iface->legacyFlag = TRUE; + addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface ); + ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex); + + iface = addrindex_create_interface( ADDR_IF_COMMON, "Old Address - personal", TAG_IF_OLD_PERSONAL, NULL ); + iface->legacyFlag = TRUE; + addrIndex->interfaceList = g_list_append( addrIndex->interfaceList, iface ); + ADDRITEM_PARENT(iface) = ADDRITEM_OBJECT(addrIndex); + +} + +/* +* Free name-value pairs. +*/ +static void addrindex_free_attributes( GList *list ) { + GList *node = list; + while( node ) { + AddressIfAttrib *nv = node->data; + g_free( nv->name ); nv->name = NULL; + g_free( nv->value ); nv->value = NULL; + g_free( nv ); + node->data = NULL; + node = g_list_next( node ); + } + g_list_free( list ); +} + +/* +* Free up data source. +*/ +void addrindex_free_datasource( AddressIndex *addrIndex, AddressDataSource *ds ) { + AddressInterface *iface = NULL; + g_return_if_fail( addrIndex != NULL ); + g_return_if_fail( ds != NULL ); + + if( ds->interface == NULL ) { + iface = addrindex_get_interface( addrIndex, ds->type ); + } + if( iface == NULL ) return; + + if( iface->useInterface ) { + if( iface->type == ADDR_IF_BOOK ) { + AddressBookFile *abf = ds->rawDataSource; + if( abf ) { + addrbook_free_book( abf ); + } + } + else if( iface->type == ADDR_IF_VCARD ) { + VCardFile *vcf = ds->rawDataSource; + if( vcf ) { + vcard_free( vcf ); + } + } +#ifdef USE_JPILOT + else if( iface->type == ADDR_IF_JPILOT ) { + JPilotFile *jpf = ds->rawDataSource; + if( jpf ) { + jpilot_free( jpf ); + } + } +#endif +#ifdef USE_LDAP + else if( iface->type == ADDR_IF_LDAP ) { + SyldapServer *server = ds->rawDataSource; + if( server ) { + syldap_free( server ); + } + } +#endif + } + else { + GList *list = ds->rawDataSource; + addrindex_free_attributes( list ); + } + + g_free( ADDRITEM_ID(addrIndex) ); + g_free( ADDRITEM_NAME(addrIndex) ); + + ADDRITEM_TYPE(addrIndex) = ITEMTYPE_NONE; + ADDRITEM_ID(addrIndex) = NULL; + ADDRITEM_NAME(addrIndex) = NULL; + ADDRITEM_PARENT(addrIndex) = NULL; + ADDRITEM_SUBTYPE(addrIndex) = 0; + ds->type = ADDR_IF_NONE; + ds->rawDataSource = NULL; + ds->interface = NULL; + + ds->type = ADDR_IF_NONE; + ds->rawDataSource = NULL; + ds->interface = NULL; + g_free( ds ); +} + +static void addrindex_free_all_datasources( AddressInterface *iface ) { + GList *node = iface->listSource; + while( node ) { + AddressDataSource *ds = node->data; + if( iface->useInterface ) { + if( iface->type == ADDR_IF_BOOK ) { + AddressBookFile *abf = ds->rawDataSource; + if( abf ) { + addrbook_free_book( abf ); + } + } + else if( iface->type == ADDR_IF_VCARD ) { + VCardFile *vcf = ds->rawDataSource; + if( vcf ) { + vcard_free( vcf ); + } + } +#ifdef USE_JPILOT + else if( iface->type == ADDR_IF_JPILOT ) { + JPilotFile *jpf = ds->rawDataSource; + if( jpf ) { + jpilot_free( jpf ); + } + } +#endif +#ifdef USE_LDAP + else if( iface->type == ADDR_IF_LDAP ) { + SyldapServer *server = ds->rawDataSource; + if( server ) { + syldap_free( server ); + } + } +#endif + } + else { + GList *list = ds->rawDataSource; + addrindex_free_attributes( list ); + } + + ds->type = ADDR_IF_NONE; + ds->rawDataSource = NULL; + ds->interface = NULL; + g_free( ds ); + node->data = NULL; + node = g_list_next( node ); + } +} + +static void addrindex_free_interface( AddressInterface *iface ) { + addrindex_free_all_datasources( iface ); + + g_free( ADDRITEM_ID(iface) ); + g_free( ADDRITEM_NAME(iface) ); + g_free( iface->name ); + g_free( iface->listTag ); + g_free( iface->itemTag ); + + ADDRITEM_TYPE(iface) = ITEMTYPE_NONE; + ADDRITEM_ID(iface) = NULL; + ADDRITEM_NAME(iface) = NULL; + ADDRITEM_PARENT(iface) = NULL; + ADDRITEM_SUBTYPE(iface) = 0; + iface->type = ADDR_IF_NONE; + iface->name = NULL; + iface->listTag = NULL; + iface->itemTag = NULL; + iface->legacyFlag = FALSE; + iface->useInterface = FALSE; + iface->haveLibrary = FALSE; + + g_list_free( iface->listSource ); + iface->listSource = NULL; +} + +/* +* Create new object. +*/ +AddressIndex *addrindex_create_index() { + AddressIndex *addrIndex = g_new0( AddressIndex, 1 ); + + ADDRITEM_TYPE(addrIndex) = ITEMTYPE_INDEX; + ADDRITEM_ID(addrIndex) = NULL; + ADDRITEM_NAME(addrIndex) = g_strdup( "Address Index" ); + ADDRITEM_PARENT(addrIndex) = NULL; + ADDRITEM_SUBTYPE(addrIndex) = 0; + addrIndex->filePath = NULL; + addrIndex->fileName = NULL; + addrIndex->retVal = MGU_SUCCESS; + addrIndex->needsConversion = FALSE; + addrIndex->wasConverted = FALSE; + addrIndex->conversionError = FALSE; + addrIndex->interfaceList = NULL; + addrIndex->lastType = ADDR_IF_NONE; + addrIndex->dirtyFlag = FALSE; + addrindex_build_if_list( addrIndex ); + return addrIndex; +} + +/* +* Specify file to be used. +*/ +void addrindex_set_file_path( AddressIndex *addrIndex, const gchar *value ) { + g_return_if_fail( addrIndex != NULL ); + addrIndex->filePath = mgu_replace_string( addrIndex->filePath, value ); +} +void addrindex_set_file_name( AddressIndex *addrIndex, const gchar *value ) { + g_return_if_fail( addrIndex != NULL ); + addrIndex->fileName = mgu_replace_string( addrIndex->fileName, value ); +} +void addrindex_set_dirty( AddressIndex *addrIndex, const gboolean value ) { + g_return_if_fail( addrIndex != NULL ); + addrIndex->dirtyFlag = value; +} + +/* +* Return list of interfaces. +*/ +GList *addrindex_get_interface_list( AddressIndex *addrIndex ) { + g_return_val_if_fail( addrIndex != NULL, NULL ); + return addrIndex->interfaceList; +} + +/* +* Free up object. +*/ +void addrindex_free_index( AddressIndex *addrIndex ) { + GList *node; + + g_return_if_fail( addrIndex != NULL ); + + g_free( ADDRITEM_ID(addrIndex) ); + g_free( ADDRITEM_NAME(addrIndex) ); + g_free( addrIndex->filePath ); + g_free( addrIndex->fileName ); + ADDRITEM_TYPE(addrIndex) = ITEMTYPE_NONE; + ADDRITEM_ID(addrIndex) = NULL; + ADDRITEM_NAME(addrIndex) = NULL; + ADDRITEM_PARENT(addrIndex) = NULL; + ADDRITEM_SUBTYPE(addrIndex) = 0; + addrIndex->filePath = NULL; + addrIndex->fileName = NULL; + addrIndex->retVal = MGU_SUCCESS; + addrIndex->needsConversion = FALSE; + addrIndex->wasConverted = FALSE; + addrIndex->conversionError = FALSE; + addrIndex->lastType = ADDR_IF_NONE; + addrIndex->dirtyFlag = FALSE; + node = addrIndex->interfaceList; + while( node ) { + AddressInterface *iface = node->data; + addrindex_free_interface( iface ); + node = g_list_next( node ); + } + g_list_free( addrIndex->interfaceList ); + addrIndex->interfaceList = NULL; + g_free( addrIndex ); +} + +/* +* Print address index. +*/ +void addrindex_print_index( AddressIndex *addrIndex, FILE *stream ) { + g_return_if_fail( addrIndex != NULL ); + fprintf( stream, "AddressIndex:\n" ); + fprintf( stream, "\tfile path: '%s'\n", addrIndex->filePath ); + fprintf( stream, "\tfile name: '%s'\n", addrIndex->fileName ); + fprintf( stream, "\t status: %d : '%s'\n", addrIndex->retVal, mgu_error2string( addrIndex->retVal ) ); + fprintf( stream, "\tconverted: '%s'\n", addrIndex->wasConverted ? "yes" : "no" ); + fprintf( stream, "\tcvt error: '%s'\n", addrIndex->conversionError ? "yes" : "no" ); + fprintf( stream, "\t---\n" ); +} + +/* +* Retrieve specified interface from index. +*/ +AddressInterface *addrindex_get_interface( AddressIndex *addrIndex, AddressIfType ifType ) { + AddressInterface *retVal = NULL; + GList *node; + + g_return_val_if_fail( addrIndex != NULL, NULL ); + + node = addrIndex->interfaceList; + while( node ) { + AddressInterface *iface = node->data; + node = g_list_next( node ); + if( iface->type == ifType ) { + retVal = iface; + break; + } + } + return retVal; +} + +AddressDataSource *addrindex_create_datasource() { + AddressDataSource *ds = NULL; + ds = g_new0( AddressDataSource, 1 ); + ADDRITEM_TYPE(ds) = ITEMTYPE_DATASOURCE; + ADDRITEM_ID(ds) = NULL; + ADDRITEM_NAME(ds) = NULL; + ADDRITEM_PARENT(ds) = NULL; + ADDRITEM_SUBTYPE(ds) = 0; + ds->type = ADDR_IF_NONE; + ds->rawDataSource = NULL; + ds->interface = NULL; + return ds; +} + +/* +* Add data source to index. +* Enter: addrIndex Address index object. +* ifType Interface type to add. +* dataSource Actual data source to add. +* Return: TRUE if data source was added. +* Note: The raw data object (for example, AddressBookFile or VCardFile object) should be +* supplied as the dataSource argument. +*/ +AddressDataSource *addrindex_index_add_datasource( AddressIndex *addrIndex, AddressIfType ifType, gpointer dataSource ) { + AddressInterface *iface; + AddressDataSource *ds = NULL; + + g_return_val_if_fail( addrIndex != NULL, NULL ); + g_return_val_if_fail( dataSource != NULL, NULL ); + + iface = addrindex_get_interface( addrIndex, ifType ); + if( iface ) { + ds = addrindex_create_datasource(); + ADDRITEM_PARENT(ds) = ADDRITEM_OBJECT(iface); + ds->type = ifType; + ds->rawDataSource = dataSource; + ds->interface = iface; + iface->listSource = g_list_append( iface->listSource, ds ); + addrIndex->dirtyFlag = TRUE; + } + return ds; +} + +/* +* Remove data source from index. +* Enter: addrIndex Address index object. +* dataSource Data source to remove. +* Return: Data source if removed, or NULL if data source was not found in +* index. Note the this object must still be freed. +*/ +AddressDataSource *addrindex_index_remove_datasource( AddressIndex *addrIndex, AddressDataSource *dataSource ) { + AddressDataSource *retVal = FALSE; + AddressInterface *iface; + + g_return_val_if_fail( addrIndex != NULL, NULL ); + g_return_val_if_fail( dataSource != NULL, NULL ); + + iface = addrindex_get_interface( addrIndex, dataSource->type ); + if( iface ) { + iface->listSource = g_list_remove( iface->listSource, dataSource ); + addrIndex->dirtyFlag = TRUE; + dataSource->interface = NULL; + retVal = dataSource; + } + return retVal; +} + +static AddressInterface *addrindex_tag_get_interface( AddressIndex *addrIndex, gchar *tag, AddressIfType ifType ) { + AddressInterface *retVal = NULL; + GList *node = addrIndex->interfaceList; + + while( node ) { + AddressInterface *iface = node->data; + node = g_list_next( node ); + if( tag ) { + if( strcmp( iface->listTag, tag ) == 0 ) { + retVal = iface; + break; + } + } + else { + if( iface->type == ifType ) { + retVal = iface; + break; + } + } + } + return retVal; +} + +static AddressInterface *addrindex_tag_get_datasource( AddressIndex *addrIndex, AddressIfType ifType, gchar *tag ) { + AddressInterface *retVal = NULL; + GList *node = addrIndex->interfaceList; + + while( node ) { + AddressInterface *iface = node->data; + node = g_list_next( node ); + if( iface->type == ifType && iface->itemTag ) { + if( strcmp( iface->itemTag, tag ) == 0 ) { + retVal = iface; + break; + } + } + } + return retVal; +} + +/* ********************************************************************** +* Interface XML parsing functions. +* *********************************************************************** +*/ +#if 0 +static void show_attribs( GList *attr ) { + while( attr ) { + gchar *name = ((XMLAttr *)attr->data)->name; + gchar *value = ((XMLAttr *)attr->data)->value; + printf( "\tattr value : %s :%s:\n", name, value ); + attr = g_list_next( attr ); + } + printf( "\t---\n" ); +} +#endif + +static void addrindex_write_elem_s( FILE *fp, gint lvl, gchar *name ) { + gint i; + for( i = 0; i < lvl; i++ ) fputs( " ", fp ); + fputs( "<", fp ); + fputs( name, fp ); +} + +static void addrindex_write_elem_e( FILE *fp, gint lvl, gchar *name ) { + gint i; + for( i = 0; i < lvl; i++ ) fputs( " ", fp ); + fputs( "\n", fp ); +} + +static void addrindex_write_attr( FILE *fp, gchar *name, gchar *value ) { + fputs( " ", fp ); + fputs( name, fp ); + fputs( "=\"", fp ); + xml_file_put_escape_str( fp, value ); + fputs( "\"", fp ); +} + +/* +* Return list of name-value pairs. +*/ +static GList *addrindex_read_attributes( XMLFile *file ) { + GList *list = NULL; + AddressIfAttrib *nv; + GList *attr; + gchar *name; + gchar *value; + + attr = xml_get_current_tag_attr( file ); + while( attr ) { + name = ((XMLAttr *)attr->data)->name; + value = ((XMLAttr *)attr->data)->value; + nv = g_new0( AddressIfAttrib, 1 ); + nv->name = g_strdup( name ); + nv->value = g_strdup( value ); + list = g_list_append( list, nv ); + attr = g_list_next( attr ); + } + return list; +} + +/* +* Output name-value pairs. +*/ +static void addrindex_write_attributes( FILE *fp, gchar *tag, GList *list, gint lvl ) { + GList *node; + AddressIfAttrib *nv; + if( list ) { + addrindex_write_elem_s( fp, lvl, tag ); + node = list; + while( node ) { + nv = node->data; + addrindex_write_attr( fp, nv->name, nv->value ); + node = g_list_next( node ); + } + fputs(" />\n", fp); + } +} + +#if 0 +static void addrindex_print_attributes( GList *list, FILE *stream ) { + GList *node = list; + while( node ) { + AddressIfAttrib *nv = node->data; + fprintf( stream, "%s : %s\n", nv->name, nv->value ); + node = g_list_next( node ); + } +} +#endif + +static AddressDataSource *addrindex_parse_book( XMLFile *file ) { + AddressDataSource *ds = g_new0( AddressDataSource, 1 ); + AddressBookFile *abf; + GList *attr; + + abf = addrbook_create_book(); + attr = xml_get_current_tag_attr( file ); + while( attr ) { + gchar *name = ((XMLAttr *)attr->data)->name; + gchar *value = ((XMLAttr *)attr->data)->value; + if( strcmp( name, ATTAG_BOOK_NAME ) == 0 ) { + addrbook_set_name( abf, value ); + } + else if( strcmp( name, ATTAG_BOOK_FILE ) == 0) { + addrbook_set_file( abf, value ); + } + attr = g_list_next( attr ); + } + ds->rawDataSource = abf; + return ds; +} + +static void addrindex_write_book( FILE *fp, AddressDataSource *ds, gint lvl ) { + AddressBookFile *abf = ds->rawDataSource; + if( abf ) { + addrindex_write_elem_s( fp, lvl, TAG_DS_ADDRESS_BOOK ); + addrindex_write_attr( fp, ATTAG_BOOK_NAME, abf->name ); + addrindex_write_attr( fp, ATTAG_BOOK_FILE, abf->fileName ); + fputs( " />\n", fp ); + } +} + +static AddressDataSource *addrindex_parse_vcard( XMLFile *file ) { + AddressDataSource *ds = g_new0( AddressDataSource, 1 ); + VCardFile *vcf; + GList *attr; + + vcf = vcard_create(); + attr = xml_get_current_tag_attr( file ); + while( attr ) { + gchar *name = ((XMLAttr *)attr->data)->name; + gchar *value = ((XMLAttr *)attr->data)->value; + if( strcmp( name, ATTAG_VCARD_NAME ) == 0 ) { + vcard_set_name( vcf, value ); + } + else if( strcmp( name, ATTAG_VCARD_FILE ) == 0) { + vcard_set_file( vcf, value ); + } + attr = g_list_next( attr ); + } + ds->rawDataSource = vcf; + return ds; +} + +static void addrindex_write_vcard( FILE *fp, AddressDataSource *ds, gint lvl ) { + VCardFile *vcf = ds->rawDataSource; + if( vcf ) { + addrindex_write_elem_s( fp, lvl, TAG_DS_VCARD ); + addrindex_write_attr( fp, ATTAG_VCARD_NAME, vcf->name ); + addrindex_write_attr( fp, ATTAG_VCARD_FILE, vcf->path ); + fputs( " />\n", fp ); + } +} + +#ifdef USE_JPILOT +static AddressDataSource *addrindex_parse_jpilot( XMLFile *file ) { + AddressDataSource *ds = g_new0( AddressDataSource, 1 ); + JPilotFile *jpf; + GList *attr; + + jpf = jpilot_create(); + attr = xml_get_current_tag_attr( file ); + while( attr ) { + gchar *name = ((XMLAttr *)attr->data)->name; + gchar *value = ((XMLAttr *)attr->data)->value; + if( strcmp( name, ATTAG_JPILOT_NAME ) == 0 ) { + jpilot_set_name( jpf, value ); + } + else if( strcmp( name, ATTAG_JPILOT_FILE ) == 0 ) { + jpilot_set_file( jpf, value ); + } + else if( strcmp( name, ATTAG_JPILOT_CUSTOM_1 ) == 0 ) { + jpilot_add_custom_label( jpf, value ); + } + else if( strcmp( name, ATTAG_JPILOT_CUSTOM_2 ) == 0 ) { + jpilot_add_custom_label( jpf, value ); + } + else if( strcmp( name, ATTAG_JPILOT_CUSTOM_3 ) == 0 ) { + jpilot_add_custom_label( jpf, value ); + } + else if( strcmp( name, ATTAG_JPILOT_CUSTOM_4 ) == 0 ) { + jpilot_add_custom_label( jpf, value ); + } + attr = g_list_next( attr ); + } + ds->rawDataSource = jpf; + return ds; +} + +static void addrindex_write_jpilot( FILE *fp,AddressDataSource *ds, gint lvl ) { + JPilotFile *jpf = ds->rawDataSource; + if( jpf ) { + gint ind; + GList *node; + GList *customLbl = jpilot_get_custom_labels( jpf ); + addrindex_write_elem_s( fp, lvl, TAG_DS_JPILOT ); + addrindex_write_attr( fp, ATTAG_JPILOT_NAME, jpf->name ); + addrindex_write_attr( fp, ATTAG_JPILOT_FILE, jpf->path ); + node = customLbl; + ind = 1; + while( node ) { + gchar name[256]; + sprintf( name, "%s%d", ATTAG_JPILOT_CUSTOM, ind ); + addrindex_write_attr( fp, name, node->data ); + ind++; + node = g_list_next( node ); + } + fputs( " />\n", fp ); + } +} +#else +/* Just read/write name-value pairs */ +static AddressDataSource *addrindex_parse_jpilot( XMLFile *file ) { + AddressDataSource *ds = g_new0( AddressDataSource, 1 ); + GList *list = addrindex_read_attributes( file ); + ds->rawDataSource = list; + return ds; +} + +static void addrindex_write_jpilot( FILE *fp, AddressDataSource *ds, gint lvl ) { + GList *list = ds->rawDataSource; + if( list ) { + addrindex_write_attributes( fp, TAG_DS_JPILOT, list, lvl ); + } +} +#endif + +#ifdef USE_LDAP +static AddressDataSource *addrindex_parse_ldap( XMLFile *file ) { + AddressDataSource *ds = g_new0( AddressDataSource, 1 ); + SyldapServer *server; + GList *attr; + + server = syldap_create(); + attr = xml_get_current_tag_attr( file ); + while( attr ) { + gchar *name = ((XMLAttr *)attr->data)->name; + gchar *value = ((XMLAttr *)attr->data)->value; + gint ivalue = atoi( value ); + if( strcmp( name, ATTAG_LDAP_NAME ) == 0 ) { + syldap_set_name( server, value ); + } + else if( strcmp( name, ATTAG_LDAP_HOST ) == 0 ) { + syldap_set_host( server, value ); + } + else if( strcmp( name, ATTAG_LDAP_PORT ) == 0 ) { + syldap_set_port( server, ivalue ); + } + else if( strcmp( name, ATTAG_LDAP_BASE_DN ) == 0 ) { + syldap_set_base_dn( server, value ); + } + else if( strcmp( name, ATTAG_LDAP_BIND_DN ) == 0 ) { + syldap_set_bind_dn( server, value ); + } + else if( strcmp( name, ATTAG_LDAP_BIND_PASS ) == 0 ) { + syldap_set_bind_password( server, value ); + } + else if( strcmp( name, ATTAG_LDAP_CRITERIA ) == 0 ) { + syldap_set_search_criteria( server, value ); + } + else if( strcmp( name, ATTAG_LDAP_MAX_ENTRY ) == 0 ) { + syldap_set_max_entries( server, ivalue ); + } + else if( strcmp( name, ATTAG_LDAP_TIMEOUT ) == 0 ) { + syldap_set_timeout( server, ivalue ); + } + attr = g_list_next( attr ); + } + + ds->rawDataSource = server; + return ds; +} + +static void addrindex_write_ldap( FILE *fp, AddressDataSource *ds, gint lvl ) { + SyldapServer *server = ds->rawDataSource; + if( server ) { + gchar value[256]; + + addrindex_write_elem_s( fp, lvl, TAG_DS_LDAP ); + addrindex_write_attr( fp, ATTAG_LDAP_NAME, server->name ); + addrindex_write_attr( fp, ATTAG_LDAP_HOST, server->hostName ); + + sprintf( value, "%d", server->port ); + addrindex_write_attr( fp, ATTAG_LDAP_PORT, value ); + + addrindex_write_attr( fp, ATTAG_LDAP_BASE_DN, server->baseDN ); + addrindex_write_attr( fp, ATTAG_LDAP_BIND_DN, server->bindDN ); + addrindex_write_attr( fp, ATTAG_LDAP_BIND_PASS, server->bindPass ); + addrindex_write_attr( fp, ATTAG_LDAP_CRITERIA, server->searchCriteria ); + + sprintf( value, "%d", server->maxEntries ); + addrindex_write_attr( fp, ATTAG_LDAP_MAX_ENTRY, value ); + sprintf( value, "%d", server->timeOut ); + addrindex_write_attr( fp, ATTAG_LDAP_TIMEOUT, value ); + + fputs(" />\n", fp); + } +} +#else +/* Just read/write name-value pairs */ +static AddressDataSource *addrindex_parse_ldap( XMLFile *file ) { + AddressDataSource *ds = g_new0( AddressDataSource, 1 ); + GList *list = addrindex_read_attributes( file ); + ds->rawDataSource = list; + return ds; +} + +static void addrindex_write_ldap( FILE *fp, AddressDataSource *ds, gint lvl ) { + GList *list = ds->rawDataSource; + if( list ) { + addrindex_write_attributes( fp, TAG_DS_LDAP, list, lvl ); + } +} +#endif + +/* ********************************************************************** +* Address index I/O functions. +* *********************************************************************** +*/ +static void addrindex_read_index( AddressIndex *addrIndex, XMLFile *file ) { + guint prev_level; + /* gchar *element; */ + /* GList *attr; */ + XMLTag *xtag; + AddressInterface *iface = NULL, *dsIFace = NULL; + AddressDataSource *ds; + + for (;;) { + prev_level = file->level; + xml_parse_next_tag( file ); + if( file->level < prev_level ) return; + + xtag = xml_get_current_tag( file ); + /* printf( "tag : %s\n", xtag->tag ); */ + + iface = addrindex_tag_get_interface( addrIndex, xtag->tag, ADDR_IF_NONE ); + if( iface ) { + addrIndex->lastType = iface->type; + if( iface->legacyFlag ) addrIndex->needsConversion = TRUE; + /* printf( "found : %s\n", iface->name ); */ + } + else { + dsIFace = addrindex_tag_get_datasource( addrIndex, addrIndex->lastType, xtag->tag ); + if( dsIFace ) { + /* Add data source to list */ + /* printf( "\tdata source: %s\n", dsIFace->name ); */ + ds = NULL; + if( addrIndex->lastType == ADDR_IF_BOOK ) { + ds = addrindex_parse_book( file ); + if( ds->rawDataSource ) { + addrbook_set_path( ds->rawDataSource, addrIndex->filePath ); + /* addrbook_print_book( ds->rawDataSource, stdout ); */ + } + } + else if( addrIndex->lastType == ADDR_IF_VCARD ) { + ds = addrindex_parse_vcard( file ); + /* if( ds->rawDataSource ) { */ + /* vcard_print_file( ds->rawDataSource, stdout ); */ + /* } */ + } + else if( addrIndex->lastType == ADDR_IF_JPILOT ) { + ds = addrindex_parse_jpilot( file ); + /* + if( ds->rawDataSource ) { + jpilot_print_file( ds->rawDataSource, stdout ); + // addrindex_print_attributes( ds->rawDataSource, stdout ); + } + */ + } + else if( addrIndex->lastType == ADDR_IF_LDAP ) { + ds = addrindex_parse_ldap( file ); + /* + if( ds->rawDataSource ) { + syldap_print_data( ds->rawDataSource, stdout ); + // addrindex_print_attributes( ds->rawDataSource, stdout ); + } + */ + } + if( ds ) { + ds->type = addrIndex->lastType; + ds->interface = dsIFace; + dsIFace->listSource = g_list_append( dsIFace->listSource, ds ); + } + /* printf( "=============================\n\n" ); */ + } + } + /* + element = xml_get_element( file ); + attr = xml_get_current_tag_attr( file ); + if( _interfaceLast_ && ! _interfaceLast_->legacyFlag ) { + show_attribs( attr ); + printf( "\ttag value : %s :\n", element ); + } + */ + addrindex_read_index( addrIndex, file ); + } +} + +static gint addrindex_read_file( AddressIndex *addrIndex ) { + XMLFile *file = NULL; + gchar *fileSpec = NULL; + + g_return_val_if_fail( addrIndex != NULL, -1 ); + + fileSpec = g_strconcat( addrIndex->filePath, G_DIR_SEPARATOR_S, addrIndex->fileName, NULL ); + addrIndex->retVal = MGU_NO_FILE; + file = xml_open_file( fileSpec ); + g_free( fileSpec ); + + if( file == NULL ) { + /* fprintf( stdout, " file '%s' does not exist.\n", addrIndex->fileName ); */ + return addrIndex->retVal; + } + + addrIndex->retVal = MGU_BAD_FORMAT; + if( xml_get_dtd( file ) == 0 ) { + if( xml_parse_next_tag( file ) == 0 ) { + if( xml_compare_tag( file, TAG_ADDRESS_INDEX ) ) { + addrindex_read_index( addrIndex, file ); + addrIndex->retVal = MGU_SUCCESS; + } + } + } + xml_close_file( file ); + + return addrIndex->retVal; +} + +static void addrindex_write_index( AddressIndex *addrIndex, FILE *fp ) { + GList *nodeIF, *nodeDS; + gint lvlList = 1; + gint lvlItem = 1 + lvlList; + + nodeIF = addrIndex->interfaceList; + while( nodeIF ) { + AddressInterface *iface = nodeIF->data; + if( ! iface->legacyFlag ) { + nodeDS = iface->listSource; + addrindex_write_elem_s( fp, lvlList, iface->listTag ); + fputs( ">\n", fp ); + while( nodeDS ) { + AddressDataSource *ds = nodeDS->data; + if( ds ) { + if( iface->type == ADDR_IF_BOOK ) { + addrindex_write_book( fp, ds, lvlItem ); + } + if( iface->type == ADDR_IF_VCARD ) { + addrindex_write_vcard( fp, ds, lvlItem ); + } + if( iface->type == ADDR_IF_JPILOT ) { + addrindex_write_jpilot( fp, ds, lvlItem ); + } + if( iface->type == ADDR_IF_LDAP ) { + addrindex_write_ldap( fp, ds, lvlItem ); + } + } + nodeDS = g_list_next( nodeDS ); + } + addrindex_write_elem_e( fp, lvlList, iface->listTag ); + } + nodeIF = g_list_next( nodeIF ); + } +} + +/* +* Write data to specified file. +* Enter: addrIndex Address index object. +* newFile New file name. +* return: Status code, from addrIndex->retVal. +* Note: File will be created in directory specified by addrIndex. +*/ +gint addrindex_write_to( AddressIndex *addrIndex, const gchar *newFile ) { + FILE *fp; + gchar *fileSpec; +#ifndef DEV_STANDALONE + PrefFile *pfile; +#endif + + g_return_val_if_fail( addrIndex != NULL, -1 ); + + fileSpec = g_strconcat( addrIndex->filePath, G_DIR_SEPARATOR_S, newFile, NULL ); + addrIndex->retVal = MGU_OPEN_FILE; +#ifdef DEV_STANDALONE + fp = fopen( fileSpec, "wb" ); + g_free( fileSpec ); + if( fp ) { + fputs( "\n", fp ); +#else + pfile = prefs_file_open( fileSpec ); + g_free( fileSpec ); + if( pfile ) { + fp = pfile->fp; + fprintf( fp, "\n", + conv_get_internal_charset_str() ); +#endif + addrindex_write_elem_s( fp, 0, TAG_ADDRESS_INDEX ); + fputs( ">\n", fp ); + + addrindex_write_index( addrIndex, fp ); + addrindex_write_elem_e( fp, 0, TAG_ADDRESS_INDEX ); + + addrIndex->retVal = MGU_SUCCESS; +#ifdef DEV_STANDALONE + fclose( fp ); +#else + if( prefs_file_close( pfile ) < 0 ) { + addrIndex->retVal = MGU_ERROR_WRITE; + } +#endif + } + + fileSpec = NULL; + return addrIndex->retVal; +} + +/* +* Save address index data to original file. +* return: Status code, from addrIndex->retVal. +*/ +gint addrindex_save_data( AddressIndex *addrIndex ) { + g_return_val_if_fail( addrIndex != NULL, -1 ); + + addrIndex->retVal = MGU_NO_FILE; + if( addrIndex->fileName == NULL || *addrIndex->fileName == '\0' ) return addrIndex->retVal; + if( addrIndex->filePath == NULL || *addrIndex->filePath == '\0' ) return addrIndex->retVal; + + addrindex_write_to( addrIndex, addrIndex->fileName ); + if( addrIndex->retVal == MGU_SUCCESS ) { + addrIndex->dirtyFlag = FALSE; + } + return addrIndex->retVal; +} + +/* +* Save all address book files which may have changed. +* Return: Status code, set if there was a problem saving data. +*/ +gint addrindex_save_all_books( AddressIndex *addrIndex ) { + gint retVal = MGU_SUCCESS; + GList *nodeIf, *nodeDS; + + nodeIf = addrIndex->interfaceList; + while( nodeIf ) { + AddressInterface *iface = nodeIf->data; + if( iface->type == ADDR_IF_BOOK ) { + nodeDS = iface->listSource; + while( nodeDS ) { + AddressDataSource *ds = nodeDS->data; + AddressBookFile *abf = ds->rawDataSource; + if( abf->dirtyFlag ) { + if( abf->readFlag ) { + addrbook_save_data( abf ); + if( abf->retVal != MGU_SUCCESS ) { + retVal = abf->retVal; + } + } + } + nodeDS = g_list_next( nodeDS ); + } + break; + } + nodeIf = g_list_next( nodeIf ); + } + return retVal; +} + + +/* ********************************************************************** +* Address book conversion to new format. +* *********************************************************************** +*/ + +#define ELTAG_IF_OLD_FOLDER "folder" +#define ELTAG_IF_OLD_GROUP "group" +#define ELTAG_IF_OLD_ITEM "item" +#define ELTAG_IF_OLD_NAME "name" +#define ELTAG_IF_OLD_ADDRESS "address" +#define ELTAG_IF_OLD_REMARKS "remarks" +#define ATTAG_IF_OLD_NAME "name" + +#define TEMPNODE_ROOT 0 +#define TEMPNODE_FOLDER 1 +#define TEMPNODE_GROUP 2 +#define TEMPNODE_ADDRESS 3 + +typedef struct _AddressCvt_Node AddressCvtNode; +struct _AddressCvt_Node { + gint type; + gchar *name; + gchar *address; + gchar *remarks; + GList *list; +}; + +/* +* Parse current address item. +*/ +static AddressCvtNode *addrindex_parse_item( XMLFile *file ) { + gchar *element; + guint level; + AddressCvtNode *nn; + + nn = g_new0( AddressCvtNode, 1 ); + nn->type = TEMPNODE_ADDRESS; + nn->list = NULL; + + level = file->level; + + for (;;) { + xml_parse_next_tag(file); + if (file->level < level) return nn; + + element = xml_get_element( file ); + if( xml_compare_tag( file, ELTAG_IF_OLD_NAME ) ) { + nn->name = g_strdup( element ); + } + if( xml_compare_tag( file, ELTAG_IF_OLD_ADDRESS ) ) { + nn->address = g_strdup( element ); + } + if( xml_compare_tag( file, ELTAG_IF_OLD_REMARKS ) ) { + nn->remarks = g_strdup( element ); + } + xml_parse_next_tag(file); + } +} + +/* +* Create a temporary node below specified node. +*/ +static AddressCvtNode *addrindex_add_object( AddressCvtNode *node, gint type, gchar *name, gchar *addr, char *rem ) { + AddressCvtNode *nn; + nn = g_new0( AddressCvtNode, 1 ); + nn->type = type; + nn->name = g_strdup( name ); + nn->remarks = g_strdup( rem ); + node->list = g_list_append( node->list, nn ); + return nn; +} + +/* +* Process current temporary node. +*/ +static void addrindex_add_obj( XMLFile *file, AddressCvtNode *node ) { + GList *attr; + guint prev_level; + AddressCvtNode *newNode = NULL; + gchar *name; + gchar *value; + + for (;;) { + prev_level = file->level; + xml_parse_next_tag( file ); + if (file->level < prev_level) return; + name = NULL; + value = NULL; + + if( xml_compare_tag( file, ELTAG_IF_OLD_GROUP ) ) { + attr = xml_get_current_tag_attr(file); + if (attr) { + name = ((XMLAttr *)attr->data)->name; + if( strcmp( name, ATTAG_IF_OLD_NAME ) == 0 ) { + value = ((XMLAttr *)attr->data)->value; + } + } + newNode = addrindex_add_object( node, TEMPNODE_GROUP, value, "", "" ); + addrindex_add_obj( file, newNode ); + + } + else if( xml_compare_tag( file, ELTAG_IF_OLD_FOLDER ) ) { + attr = xml_get_current_tag_attr(file); + if (attr) { + name = ((XMLAttr *)attr->data)->name; + if( strcmp( name, ATTAG_IF_OLD_NAME ) == 0 ) { + value = ((XMLAttr *)attr->data)->value; + } + } + newNode = addrindex_add_object( node, TEMPNODE_FOLDER, value, "", "" ); + addrindex_add_obj( file, newNode ); + } + else if( xml_compare_tag( file, ELTAG_IF_OLD_ITEM ) ) { + newNode = addrindex_parse_item( file ); + node->list = g_list_append( node->list, newNode ); + } + else { + /* printf( "invalid: !!! \n" ); */ + attr = xml_get_current_tag_attr( file ); + } + } +} + +/* +* Consume all nodes below current tag. +*/ +static void addrindex_consume_tree( XMLFile *file ) { + guint prev_level; + gchar *element; + GList *attr; + XMLTag *xtag; + + for (;;) { + prev_level = file->level; + xml_parse_next_tag( file ); + if (file->level < prev_level) return; + + xtag = xml_get_current_tag( file ); + /* printf( "tag : %s\n", xtag->tag ); */ + element = xml_get_element( file ); + attr = xml_get_current_tag_attr( file ); + /* show_attribs( attr ); */ + /* printf( "\ttag value : %s :\n", element ); */ + addrindex_consume_tree( file ); + } +} + +/* +* Print temporary tree. +*/ +static void addrindex_print_node( AddressCvtNode *node, FILE *stream ) { + GList *list; + + fprintf( stream, "Node:\ttype :%d:\n", node->type ); + fprintf( stream, "\tname :%s:\n", node->name ); + fprintf( stream, "\taddr :%s:\n", node->address ); + fprintf( stream, "\trems :%s:\n", node->remarks ); + if( node->list ) { + fprintf( stream, "\t--list----\n" ); + } + list = node->list; + while( list ) { + AddressCvtNode *lNode = list->data; + list = g_list_next( list ); + addrindex_print_node( lNode, stream ); + } + fprintf( stream, "\t==list-%d==\n", node->type ); +} + +/* +* Free up temporary tree. +*/ +static void addrindex_free_node( AddressCvtNode *node ) { + GList *list = node->list; + + while( list ) { + AddressCvtNode *lNode = list->data; + list = g_list_next( list ); + addrindex_free_node( lNode ); + } + node->type = TEMPNODE_ROOT; + g_free( node->name ); + g_free( node->address ); + g_free( node->remarks ); + g_list_free( node->list ); + g_free( node ); +} + +/* +* Process address book for specified node. +*/ +static void addrindex_process_node( + AddressBookFile *abf, AddressCvtNode *node, ItemFolder *parent, + ItemGroup *parentGrp, ItemFolder *folderGrp ) +{ + GList *list; + ItemFolder *itemFolder = NULL; + ItemGroup *itemGParent = parentGrp; + ItemFolder *itemGFolder = folderGrp; + AddressCache *cache = abf->addressCache; + + if( node->type == TEMPNODE_ROOT ) { + itemFolder = parent; + } + else if( node->type == TEMPNODE_FOLDER ) { + itemFolder = addritem_create_item_folder(); + addritem_folder_set_name( itemFolder, node->name ); + addrcache_id_folder( cache, itemFolder ); + addrcache_folder_add_folder( cache, parent, itemFolder ); + itemGFolder = NULL; + } + else if( node->type == TEMPNODE_GROUP ) { + ItemGroup *itemGroup; + gchar *fName; + + /* Create a folder for group */ + fName = g_strdup_printf( "Cvt - %s", node->name ); + itemGFolder = addritem_create_item_folder(); + addritem_folder_set_name( itemGFolder, fName ); + addrcache_id_folder( cache, itemGFolder ); + addrcache_folder_add_folder( cache, parent, itemGFolder ); + g_free( fName ); + + /* Add group into folder */ + itemGroup = addritem_create_item_group(); + addritem_group_set_name( itemGroup, node->name ); + addrcache_id_group( cache, itemGroup ); + addrcache_folder_add_group( cache, itemGFolder, itemGroup ); + itemGParent = itemGroup; + } + else if( node->type == TEMPNODE_ADDRESS ) { + ItemPerson *itemPerson; + ItemEMail *itemEMail; + + /* Create person and email objects */ + itemPerson = addritem_create_item_person(); + addritem_person_set_common_name( itemPerson, node->name ); + addrcache_id_person( cache, itemPerson ); + itemEMail = addritem_create_item_email(); + addritem_email_set_address( itemEMail, node->address ); + addritem_email_set_remarks( itemEMail, node->remarks ); + addrcache_id_email( cache, itemEMail ); + addrcache_person_add_email( cache, itemPerson, itemEMail ); + + /* Add person into appropriate folder */ + if( itemGFolder ) { + addrcache_folder_add_person( cache, itemGFolder, itemPerson ); + } + else { + addrcache_folder_add_person( cache, parent, itemPerson ); + } + + /* Add email address only into group */ + if( parentGrp ) { + addrcache_group_add_email( cache, parentGrp, itemEMail ); + } + } + + list = node->list; + while( list ) { + AddressCvtNode *lNode = list->data; + list = g_list_next( list ); + addrindex_process_node( abf, lNode, itemFolder, itemGParent, itemGFolder ); + } +} + +/* +* Process address book to specified file number. +*/ +static gboolean addrindex_process_book( AddressIndex *addrIndex, XMLFile *file, gchar *displayName ) { + gboolean retVal = FALSE; + AddressBookFile *abf = NULL; + AddressCvtNode *rootNode = NULL; + gchar *newFile = NULL; + GList *fileList = NULL; + gint fileNum = 0; + + /* Setup root node */ + rootNode = g_new0( AddressCvtNode, 1 ); + rootNode->type = TEMPNODE_ROOT; + rootNode->name = g_strdup( "root" ); + rootNode->list = NULL; + addrindex_add_obj( file, rootNode ); + /* addrindex_print_node( rootNode, stdout ); */ + + /* Create new address book */ + abf = addrbook_create_book(); + addrbook_set_name( abf, displayName ); + addrbook_set_path( abf, addrIndex->filePath ); + + /* Determine next available file number */ + fileList = addrbook_get_bookfile_list( abf ); + if( fileList ) { + fileNum = 1 + abf->maxValue; + } + g_list_free( fileList ); + fileList = NULL; + + newFile = addrbook_gen_new_file_name( fileNum ); + if( newFile ) { + addrbook_set_file( abf, newFile ); + } + + addrindex_process_node( abf, rootNode, abf->addressCache->rootFolder, NULL, NULL ); + + /* addrbook_dump_book( abf, stdout ); */ + addrbook_save_data( abf ); + addrIndex->retVal = abf->retVal; + if( abf->retVal == MGU_SUCCESS ) retVal = TRUE; + + addrbook_free_book( abf ); + abf = NULL; + addrindex_free_node( rootNode ); + rootNode = NULL; + + /* Create entries in address index */ + if( retVal ) { + abf = addrbook_create_book(); + addrbook_set_name( abf, displayName ); + addrbook_set_path( abf, addrIndex->filePath ); + addrbook_set_file( abf, newFile ); + addrindex_index_add_datasource( addrIndex, ADDR_IF_BOOK, abf ); + } + + return retVal; +} + +/* +* Process tree converting data. +*/ +static void addrindex_convert_tree( AddressIndex *addrIndex, XMLFile *file ) { + guint prev_level; + gchar *element; + GList *attr; + XMLTag *xtag; + + /* Process file */ + for (;;) { + prev_level = file->level; + xml_parse_next_tag( file ); + if (file->level < prev_level) return; + + xtag = xml_get_current_tag( file ); + /* printf( "tag : %d : %s\n", prev_level, xtag->tag ); */ + if( strcmp( xtag->tag, TAG_IF_OLD_COMMON ) == 0 ) { + if( addrindex_process_book( addrIndex, file, DISP_OLD_COMMON ) ) { + addrIndex->needsConversion = FALSE; + addrIndex->wasConverted = TRUE; + continue; + } + return; + } + if( strcmp( xtag->tag, TAG_IF_OLD_PERSONAL ) == 0 ) { + if( addrindex_process_book( addrIndex, file, DISP_OLD_PERSONAL ) ) { + addrIndex->needsConversion = FALSE; + addrIndex->wasConverted = TRUE; + continue; + } + return; + } + element = xml_get_element( file ); + attr = xml_get_current_tag_attr( file ); + /* show_attribs( attr ); */ + /* printf( "\ttag value : %s :\n", element ); */ + addrindex_consume_tree( file ); + } +} + +static gint addrindex_convert_data( AddressIndex *addrIndex ) { + XMLFile *file = NULL; + gchar *fileSpec; + + fileSpec = g_strconcat( addrIndex->filePath, G_DIR_SEPARATOR_S, addrIndex->fileName, NULL ); + addrIndex->retVal = MGU_NO_FILE; + file = xml_open_file( fileSpec ); + g_free( fileSpec ); + + if( file == NULL ) { + /* fprintf( stdout, " file '%s' does not exist.\n", addrIndex->fileName ); */ + return addrIndex->retVal; + } + + addrIndex->retVal = MGU_BAD_FORMAT; + if( xml_get_dtd( file ) == 0 ) { + if( xml_parse_next_tag( file ) == 0 ) { + if( xml_compare_tag( file, TAG_ADDRESS_INDEX ) ) { + addrindex_convert_tree( addrIndex, file ); + } + } + } + xml_close_file( file ); + return addrIndex->retVal; +} + +/* +* Create a new address book file. +*/ +static gboolean addrindex_create_new_book( AddressIndex *addrIndex, gchar *displayName ) { + gboolean retVal = FALSE; + AddressBookFile *abf = NULL; + gchar *newFile = NULL; + GList *fileList = NULL; + gint fileNum = 0; + + /* Create new address book */ + abf = addrbook_create_book(); + addrbook_set_name( abf, displayName ); + addrbook_set_path( abf, addrIndex->filePath ); + + /* Determine next available file number */ + fileList = addrbook_get_bookfile_list( abf ); + if( fileList ) { + fileNum = 1 + abf->maxValue; + } + g_list_free( fileList ); + fileList = NULL; + + newFile = addrbook_gen_new_file_name( fileNum ); + if( newFile ) { + addrbook_set_file( abf, newFile ); + } + + addrbook_save_data( abf ); + addrIndex->retVal = abf->retVal; + if( abf->retVal == MGU_SUCCESS ) retVal = TRUE; + addrbook_free_book( abf ); + abf = NULL; + + /* Create entries in address index */ + if( retVal ) { + abf = addrbook_create_book(); + addrbook_set_name( abf, displayName ); + addrbook_set_path( abf, addrIndex->filePath ); + addrbook_set_file( abf, newFile ); + addrindex_index_add_datasource( addrIndex, ADDR_IF_BOOK, abf ); + } + + return retVal; +} + +/* +* Read data for address index performing a conversion if necesary. +* Enter: addrIndex Address index object. +* return: Status code, from addrIndex->retVal. +* Note: New address book files will be created in directory specified by +* addrIndex. Three files will be created, for the following: +* "Common addresses" +* "Personal addresses" +* "Gathered addresses" - a new address book. +*/ +gint addrindex_read_data( AddressIndex *addrIndex ) { + g_return_val_if_fail( addrIndex != NULL, -1 ); + + addrIndex->conversionError = FALSE; + addrindex_read_file( addrIndex ); + if( addrIndex->retVal == MGU_SUCCESS ) { + if( addrIndex->needsConversion ) { + if( addrindex_convert_data( addrIndex ) == MGU_SUCCESS ) { + addrIndex->conversionError = TRUE; + } + else { + addrIndex->conversionError = TRUE; + } + } + addrIndex->dirtyFlag = TRUE; + } + return addrIndex->retVal; +} + +/* +* Create new address books for a new address index. +* Enter: addrIndex Address index object. +* return: Status code, from addrIndex->retVal. +* Note: New address book files will be created in directory specified by +* addrIndex. Three files will be created, for the following: +* "Common addresses" +* "Personal addresses" +* "Gathered addresses" - a new address book. +*/ +gint addrindex_create_new_books( AddressIndex *addrIndex ) { + gboolean flg; + + g_return_val_if_fail( addrIndex != NULL, -1 ); + + flg = addrindex_create_new_book( addrIndex, DISP_NEW_COMMON ); + if( flg ) { + flg = addrindex_create_new_book( addrIndex, DISP_NEW_PERSONAL ); + addrIndex->dirtyFlag = TRUE; + } + return addrIndex->retVal; +} + +/* ********************************************************************** +* New interface stuff. +* *********************************************************************** +*/ + +/* + * Return modified flag for specified data source. + */ +gboolean addrindex_ds_get_modify_flag( AddressDataSource *ds ) { + gboolean retVal = FALSE; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getModifyFlag ) { + retVal = ( iface->getModifyFlag ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return accessed flag for specified data source. + */ +gboolean addrindex_ds_get_access_flag( AddressDataSource *ds ) { + gboolean retVal = FALSE; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getAccessFlag ) { + retVal = ( iface->getAccessFlag ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return data read flag for specified data source. + */ +gboolean addrindex_ds_get_read_flag( AddressDataSource *ds ) { + gboolean retVal = TRUE; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getReadFlag ) { + retVal = ( iface->getReadFlag ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return status code for specified data source. + */ +gint addrindex_ds_get_status_code( AddressDataSource *ds ) { + gint retVal = MGU_SUCCESS; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getStatusCode ) { + retVal = ( iface->getStatusCode ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return data read flag for specified data source. + */ +gint addrindex_ds_read_data( AddressDataSource *ds ) { + gint retVal = MGU_SUCCESS; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getReadData ) { + retVal = ( iface->getReadData ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return data read flag for specified data source. + */ +ItemFolder *addrindex_ds_get_root_folder( AddressDataSource *ds ) { + ItemFolder *retVal = NULL; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getRootFolder ) { + retVal = ( iface->getRootFolder ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return list of folders for specified data source. + */ +GList *addrindex_ds_get_list_folder( AddressDataSource *ds ) { + GList *retVal = FALSE; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getListFolder ) { + retVal = ( iface->getListFolder ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return list of persons in root folder for specified data source. + */ +GList *addrindex_ds_get_list_person( AddressDataSource *ds ) { + GList *retVal = FALSE; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getListPerson ) { + retVal = ( iface->getListPerson ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return name for specified data source. + */ +gchar *addrindex_ds_get_name( AddressDataSource *ds ) { + gchar *retVal = FALSE; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getName ) { + retVal = ( iface->getName ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Set the access flag inside the data source. + */ +void addrindex_ds_set_access_flag( AddressDataSource *ds, gboolean *value ) { + AddressInterface *iface; + + if( ds == NULL ) return; + iface = ds->interface; + if( iface == NULL ) return; + if( iface->setAccessFlag ) { + ( iface->setAccessFlag ) ( ds->rawDataSource, value ); + } +} + +/* + * Return read only flag for specified data source. + */ +gboolean addrindex_ds_get_readonly( AddressDataSource *ds ) { + AddressInterface *iface; + if( ds == NULL ) return TRUE; + iface = ds->interface; + if( iface == NULL ) return TRUE; + return iface->readOnly; +} + +/* + * Return list of all persons for specified data source. + */ +GList *addrindex_ds_get_all_persons( AddressDataSource *ds ) { + GList *retVal = NULL; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getAllPersons ) { + retVal = ( iface->getAllPersons ) ( ds->rawDataSource ); + } + return retVal; +} + +/* + * Return list of all groups for specified data source. + */ +GList *addrindex_ds_get_all_groups( AddressDataSource *ds ) { + GList *retVal = NULL; + AddressInterface *iface; + + if( ds == NULL ) return retVal; + iface = ds->interface; + if( iface == NULL ) return retVal; + if( iface->getAllGroups ) { + retVal = ( iface->getAllGroups ) ( ds->rawDataSource ); + } + return retVal; +} + +/* +* End of Source. +*/ diff --git a/src/addrindex.h b/src/addrindex.h new file mode 100644 index 00000000..a31ed8e4 --- /dev/null +++ b/src/addrindex.h @@ -0,0 +1,130 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * General functions for accessing address index file. + */ + +#ifndef __ADDRINDEX_H__ +#define __ADDRINDEX_H__ + +#include +#include +#include "addritem.h" + +#define ADDRESSBOOK_MAX_IFACE 4 +#define ADDRESSBOOK_INDEX_FILE "addrbook--index.xml" +#define ADDRESSBOOK_OLD_FILE "addressbook.xml" + +typedef enum { + ADDR_IF_NONE, + ADDR_IF_BOOK, + ADDR_IF_VCARD, + ADDR_IF_JPILOT, + ADDR_IF_LDAP, + ADDR_IF_COMMON, + ADDR_IF_PERSONAL +} AddressIfType; + +typedef struct _AddressIndex AddressIndex; +struct _AddressIndex { + AddrItemObject obj; + gchar *filePath; + gchar *fileName; + gint retVal; + gboolean needsConversion; + gboolean wasConverted; + gboolean conversionError; + AddressIfType lastType; + gboolean dirtyFlag; + GList *interfaceList; +}; + +typedef struct _AddressInterface AddressInterface; +struct _AddressInterface { + AddrItemObject obj; + AddressIfType type; + gchar *name; + gchar *listTag; + gchar *itemTag; + gboolean legacyFlag; + gboolean useInterface; + gboolean haveLibrary; + gboolean readOnly; + GList *listSource; + gboolean (*getModifyFlag)( void * ); + gboolean (*getAccessFlag)( void * ); + gboolean (*getReadFlag)( void * ); + gint (*getStatusCode)( void * ); + gint (*getReadData)( void * ); + ItemFolder *(*getRootFolder)( void * ); + GList *(*getListFolder)( void * ); + GList *(*getListPerson)( void * ); + GList *(*getAllPersons)( void * ); + GList *(*getAllGroups)( void * ); + gchar *(*getName)( void * ); + void (*setAccessFlag)( void *, void * ); +}; + +typedef struct _AddressDataSource AddressDataSource; +struct _AddressDataSource { + AddrItemObject obj; + AddressIfType type; + AddressInterface *interface; + gpointer rawDataSource; +}; + +AddressIndex *addrindex_create_index (); +void addrindex_set_file_path ( AddressIndex *addrIndex, const gchar *value ); +void addrindex_set_file_name ( AddressIndex *addrIndex, const gchar *value ); +void addrindex_set_dirty ( AddressIndex *addrIndex, const gboolean value ); +GList *addrindex_get_interface_list ( AddressIndex *addrIndex ); +void addrindex_free_index ( AddressIndex *addrIndex ); +void addrindex_print_index ( AddressIndex *addrIndex, FILE *stream ); + +AddressInterface *addrindex_get_interface ( AddressIndex *addrIndex, AddressIfType ifType ); +AddressDataSource *addrindex_index_add_datasource ( AddressIndex *addrIndex, AddressIfType ifType, gpointer dataSource ); +AddressDataSource *addrindex_index_remove_datasource ( AddressIndex *addrIndex, AddressDataSource *dataSource ); +void addrindex_free_datasource ( AddressIndex *addrIndex, AddressDataSource *ds ); + +gint addrindex_read_data ( AddressIndex *addrIndex ); +gint addrindex_write_to ( AddressIndex *addrIndex, const gchar *newFile ); +gint addrindex_save_data ( AddressIndex *addrIndex ); +gint addrindex_create_new_books ( AddressIndex *addrIndex ); +gint addrindex_save_all_books ( AddressIndex *addrIndex ); + +gboolean addrindex_ds_get_modify_flag ( AddressDataSource *ds ); +gboolean addrindex_ds_get_access_flag ( AddressDataSource *ds ); +gboolean addrindex_ds_get_read_flag ( AddressDataSource *ds ); +gint addrindex_ds_get_status_code ( AddressDataSource *ds ); +gint addrindex_ds_read_data ( AddressDataSource *ds ); +ItemFolder *addrindex_ds_get_root_folder( AddressDataSource *ds ); +GList *addrindex_ds_get_list_folder ( AddressDataSource *ds ); +GList *addrindex_ds_get_list_person ( AddressDataSource *ds ); +gchar *addrindex_ds_get_name ( AddressDataSource *ds ); +void addrindex_ds_set_access_flag ( AddressDataSource *ds, gboolean *value ); +gboolean addrindex_ds_get_readonly ( AddressDataSource *ds ); +GList *addrindex_ds_get_all_persons ( AddressDataSource *ds ); +GList *addrindex_ds_get_all_groups ( AddressDataSource *ds ); + +#endif /* __ADDRINDEX_H__ */ + +/* +* End of Source. +*/ diff --git a/src/addritem.c b/src/addritem.c new file mode 100644 index 00000000..0fdfb019 --- /dev/null +++ b/src/addritem.c @@ -0,0 +1,989 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * General primitive address item objects. + */ + +#include +#include +#include + +#include "addritem.h" +#include "mgutils.h" + +/* +* Create new email address item. +*/ +ItemEMail *addritem_create_item_email( void ) { + ItemEMail *item; + item = g_new0( ItemEMail, 1 ); + ADDRITEM_TYPE(item) = ITEMTYPE_EMAIL; + ADDRITEM_ID(item) = NULL; + ADDRITEM_NAME(item) = NULL; + ADDRITEM_PARENT(item) = NULL; + ADDRITEM_SUBTYPE(item) = 0; + item->address = NULL; + item->remarks = NULL; + return item; +} + +/* +* Create copy of specified email address item. +*/ +ItemEMail *addritem_copy_item_email( ItemEMail *item ) { + ItemEMail *itemNew = NULL; + if( item ) { + itemNew = addritem_create_item_email(); + ADDRITEM_TYPE(itemNew) = ADDRITEM_TYPE(item); + ADDRITEM_ID(itemNew) = g_strdup( ADDRITEM_ID(item) ); + ADDRITEM_NAME(itemNew) = g_strdup( ADDRITEM_NAME(item) ); + ADDRITEM_PARENT(itemNew) = ADDRITEM_PARENT(item); + itemNew->address = g_strdup( item->address ); + itemNew->remarks = g_strdup( item->remarks ); + } + return itemNew; +} + +void addritem_email_set_id( ItemEMail *email, const gchar *value ) { + ADDRITEM_ID(email) = mgu_replace_string( ADDRITEM_ID(email), value ); +} +void addritem_email_set_alias( ItemEMail *email, const gchar *value ) { + ADDRITEM_NAME(email) = mgu_replace_string( ADDRITEM_NAME(email), value ); +} +void addritem_email_set_address( ItemEMail *email, const gchar *value ) { + email->address = mgu_replace_string( email->address, value ); +} +void addritem_email_set_remarks( ItemEMail *email, const gchar *value ) { + email->remarks = mgu_replace_string( email->remarks, value ); +} + +/* +* Free address item email. +*/ +void addritem_free_item_email( ItemEMail *item ) { + g_return_if_fail( item != NULL ); + + /* Free internal stuff */ + g_free( ADDRITEM_ID(item) ); + g_free( ADDRITEM_NAME(item) ); + g_free( item->address ); + g_free( item->remarks ); + + ADDRITEM_OBJECT(item)->type = ITEMTYPE_NONE; + ADDRITEM_ID(item) = NULL; + ADDRITEM_NAME(item) = NULL; + ADDRITEM_PARENT(item) = NULL; + ADDRITEM_SUBTYPE(item) = 0; + item->address = NULL; + item->remarks = NULL; + g_free( item ); +} + +/* +* Create new attribute. +*/ +UserAttribute *addritem_create_attribute( void ) { + UserAttribute *item; + item = g_new0( UserAttribute, 1 ); + item->uid = NULL; + item->name = NULL; + item->value = NULL; + return item; +} + +/* +* Create copy of specified attribute. +*/ +UserAttribute *addritem_copy_attribute( UserAttribute *item ) { + UserAttribute *itemNew = NULL; + if( item ) { + itemNew = addritem_create_attribute(); + itemNew->uid = g_strdup( item->uid ); + itemNew->name = g_strdup( item->name ); + itemNew->value = g_strdup( item->value ); + } + return itemNew; +} + +void addritem_attrib_set_id( UserAttribute *item, const gchar *value ) { + g_return_if_fail( item != NULL ); + item->uid = mgu_replace_string( item->uid, value ); +} +void addritem_attrib_set_name( UserAttribute *item, const gchar *value ) { + g_return_if_fail( item != NULL ); + item->name = mgu_replace_string( item->name, value ); +} +void addritem_attrib_set_value( UserAttribute *item, const gchar *value ) { + g_return_if_fail( item != NULL ); + item->value = mgu_replace_string( item->value, value ); +} + +/* +* Free user attribute. +*/ +void addritem_free_attribute( UserAttribute *item ) { + g_return_if_fail( item != NULL ); + g_free( item->uid ); + g_free( item->name ); + g_free( item->value ); + item->uid = NULL; + item->name = NULL; + item->value = NULL; + g_free( item ); +} + +/* +* Create new address book person. +*/ +ItemPerson *addritem_create_item_person( void ) { + ItemPerson *person; + person = g_new0( ItemPerson, 1 ); + ADDRITEM_TYPE(person) = ITEMTYPE_PERSON; + ADDRITEM_ID(person) = NULL; + ADDRITEM_NAME(person) = NULL; + ADDRITEM_PARENT(person) = NULL; + ADDRITEM_SUBTYPE(person) = 0; + person->firstName = NULL; + person->lastName = NULL; + person->nickName = NULL; + person->listEMail = NULL; + person->listAttrib = NULL; + person->externalID = NULL; + person->isOpened = FALSE; + return person; +} + +void addritem_person_set_id( ItemPerson *person, const gchar *value ) { + ADDRITEM_ID(person) = mgu_replace_string( ADDRITEM_ID(person), value ); +} +void addritem_person_set_first_name( ItemPerson *person, const gchar *value ) { + person->firstName = mgu_replace_string( person->firstName, value ); +} +void addritem_person_set_last_name( ItemPerson *person, const gchar *value ) { + person->lastName = mgu_replace_string( person->lastName, value ); +} +void addritem_person_set_nick_name( ItemPerson *person, const gchar *value ) { + person->nickName = mgu_replace_string( person->nickName, value ); +} +void addritem_person_set_common_name( ItemPerson *person, const gchar *value ) { + ADDRITEM_NAME(person) = mgu_replace_string( ADDRITEM_NAME(person), value ); +} +void addritem_person_set_external_id( ItemPerson *person, const gchar *value ) { + person->externalID = mgu_replace_string( person->externalID, value ); +} +void addritem_person_set_opened( ItemPerson *person, const gboolean value ) { + person->isOpened = value; +} + +/* +* Free linked list of item addresses. +*/ +void addritem_free_list_email( GList *list ) { + GList *node = list; + while( node ) { + addritem_free_item_email( node->data ); + node->data = NULL; + node = g_list_next( node ); + } + g_list_free( list ); +} + +/* +* Free linked list of attributes. +*/ +void addritem_free_list_attribute( GList *list ) { + GList *node = list; + while( node ) { + addritem_free_attribute( node->data ); + node->data = NULL; + node = g_list_next( node ); + } + g_list_free( list ); +} + +/* +* Free address person. +*/ +void addritem_free_item_person( ItemPerson *person ) { + g_return_if_fail( person != NULL ); + + /* Free internal stuff */ + g_free( ADDRITEM_ID(person) ); + g_free( ADDRITEM_NAME(person) ); + g_free( person->firstName ); + g_free( person->lastName ); + g_free( person->nickName ); + g_free( person->externalID ); + addritem_free_list_email( person->listEMail ); + addritem_free_list_attribute( person->listAttrib ); + + ADDRITEM_OBJECT(person)->type = ITEMTYPE_NONE; + ADDRITEM_ID(person) = NULL; + ADDRITEM_NAME(person) = NULL; + ADDRITEM_PARENT(person) = NULL; + ADDRITEM_SUBTYPE(person) = 0; + person->firstName = NULL; + person->lastName = NULL; + person->nickName = NULL; + person->externalID = NULL; + person->listEMail = NULL; + person->listAttrib = NULL; + + g_free( person ); +} + +/* +* Print address item. +*/ +void addritem_print_item_email( ItemEMail *item, FILE *stream ) { + g_return_if_fail( item != NULL ); + fprintf( stream, "\t\tt/id: %d : '%s'\n", ADDRITEM_TYPE(item), ADDRITEM_ID(item) ); + fprintf( stream, "\t\tsubty: %d\n", ADDRITEM_SUBTYPE(item) ); + fprintf( stream, "\t\talis: '%s'\n", ADDRITEM_NAME(item) ); + fprintf( stream, "\t\taddr: '%s'\n", item->address ); + fprintf( stream, "\t\trems: '%s'\n", item->remarks ); + fprintf( stream, "\t\t---\n" ); +} + +/* +* Print user attribute. +*/ +void addritem_print_attribute( UserAttribute *item, FILE *stream ) { + g_return_if_fail( item != NULL ); + fprintf( stream, "\t\tuid : '%s'\n", item->uid ); + fprintf( stream, "\t\tname : '%s'\n", item->name ); + fprintf( stream, "\t\tvalue: '%s'\n", item->value ); + fprintf( stream, "\t\t---\n" ); +} + +/* +* Print person item. +*/ +void addritem_print_item_person( ItemPerson *person, FILE *stream ) { + GList *node; + g_return_if_fail( person != NULL ); + fprintf( stream, "Person:\n" ); + fprintf( stream, "\tt/uid: %d : '%s'\n", ADDRITEM_TYPE(person), ADDRITEM_ID(person) ); + fprintf( stream, "\tsubty: %d\n", ADDRITEM_SUBTYPE(person) ); + fprintf( stream, "\tcommn: '%s'\n", ADDRITEM_NAME(person) ); + fprintf( stream, "\tfirst: '%s'\n", person->firstName ); + fprintf( stream, "\tlast : '%s'\n", person->lastName ); + fprintf( stream, "\tnick : '%s'\n", person->nickName ); + fprintf( stream, "\textID: '%s'\n", person->externalID ); + fprintf( stream, "\teMail:\n" ); + fprintf( stream, "\t---\n" ); + node = person->listEMail; + while( node ) { + addritem_print_item_email( node->data, stream ); + node = g_list_next( node ); + } + fprintf( stream, "\tuAttr:\n" ); + fprintf( stream, "\t---\n" ); + node = person->listAttrib; + while( node ) { + addritem_print_attribute( node->data, stream ); + node = g_list_next( node ); + } + fprintf( stream, "\t===\n" ); +} + +/* +* Add EMail address to person. +* return: TRUE if item added. +*/ +gboolean addritem_person_add_email( ItemPerson *person, ItemEMail *email ) { + GList *node; + + g_return_val_if_fail( person != NULL, FALSE ); + g_return_val_if_fail( email != NULL, FALSE ); + + node = person->listEMail; + while( node ) { + if( node->data == email ) return FALSE; + node = g_list_next( node ); + } + person->listEMail = g_list_append( person->listEMail, email ); + ADDRITEM_PARENT(email) = ADDRITEM_OBJECT(person); + return TRUE; +} + +/* +* Return email object with specified ID. +* param: person Person object. +* eid EMail ID. +* return: EMail object, or NULL if not found. +*/ +ItemEMail *addritem_person_get_email( ItemPerson *person, const gchar *eid ) { + ItemEMail *email = NULL; + GList *node; + + g_return_val_if_fail( person != NULL, NULL ); + if( eid == NULL || *eid == '\0' ) return NULL; + + /* Look for email */ + node = person->listEMail; + while( node ) { + AddrItemObject *objE = node->data; + gchar *ide = ADDRITEM_ID(objE); + if( ide ) { + if( strcmp( ide, eid ) == 0 ) { + email = ( ItemEMail * ) objE; + } + } + node = g_list_next( node ); + } + return email; +} + +/* +* Remove email address for specified person. +* param: person Person object. +* eid EMail ID. +* return: EMail object, or NULL if not found. Note that object should still be freed. +*/ +ItemEMail *addritem_person_remove_email_id( ItemPerson *person, const gchar *eid ) { + ItemEMail *email = NULL; + GList *node; + + g_return_val_if_fail( person != NULL, NULL ); + if( eid == NULL || *eid == '\0' ) return NULL; + + /* Look for email */ + node = person->listEMail; + while( node ) { + AddrItemObject *objE = node->data; + gchar *ide = ADDRITEM_ID(objE); + if( ide ) { + if( strcmp( ide, eid ) == 0 ) { + email = ( ItemEMail * ) objE; + } + } + node = g_list_next( node ); + } + + if( email ) { + /* Remove email from person's address list */ + if( person->listEMail ) { + person->listEMail = g_list_remove( person->listEMail, email ); + } + /* Unlink reference to person. */ + ADDRITEM_PARENT(email) = NULL; + } + return email; +} + +/* +* Remove email address for specified. +* param: person Person. +* email EMail to remove. +* return: EMail object, or NULL if not found. Note that object should still be freed. +*/ +ItemEMail *addritem_person_remove_email( ItemPerson *person, ItemEMail *email ) { + gboolean found = FALSE; + GList *node; + + g_return_val_if_fail( person != NULL, NULL ); + if( email == NULL ) return NULL; + + /* Look for email */ + node = person->listEMail; + while( node ) { + if( node-> data == email ) { + found = TRUE; + break; + } + node = g_list_next( node ); + } + + if( found ) { + /* Remove email from person's address list */ + if( person->listEMail ) { + person->listEMail = g_list_remove( person->listEMail, email ); + } + /* Unlink reference to person. */ + ADDRITEM_PARENT(email) = NULL; + return email; + } + return NULL; +} + +/* +* Add user attribute to person. +* return: TRUE if item added. +*/ +void addritem_person_add_attribute( ItemPerson *person, UserAttribute *attrib ) { + g_return_if_fail( person != NULL ); + person->listAttrib = g_list_append( person->listAttrib, attrib ); +} + +/* +* Return attribute with specified ID. +* param: person Person object. +* aid Attribute ID. +* return: UserAttribute object, or NULL if not found. Note that object should still be freed. +*/ +UserAttribute *addritem_person_get_attribute( ItemPerson *person, const gchar *aid ) { + UserAttribute *attrib = NULL; + GList *node; + + g_return_val_if_fail( person != NULL, NULL ); + if( aid == NULL || *aid == '\0' ) return NULL; + + /* Look for attribute */ + node = person->listAttrib; + while( node ) { + UserAttribute *attr = node->data; + gchar *ida = attr->uid; + if( ida ) { + if( strcmp( ida, aid ) == 0 ) { + attrib = attr; + } + } + node = g_list_next( node ); + } + return attrib; +} + +/* +* Remove attribute from person. +* param: person Person object. +* aid Attribute ID. +* return: UserAttribute object, or NULL if not found. Note that object should still be freed. +*/ +UserAttribute *addritem_person_remove_attrib_id( ItemPerson *person, const gchar *aid ) { + UserAttribute *attrib = NULL; + GList *node; + + g_return_val_if_fail( person != NULL, NULL ); + if( aid == NULL || *aid == '\0' ) return NULL; + + /* Look for attribute */ + node = person->listAttrib; + while( node ) { + UserAttribute *attr = node->data; + gchar *ida = attr->uid; + if( ida ) { + if( strcmp( ida, aid ) == 0 ) { + attrib = attr; + } + } + node = g_list_next( node ); + } + + /* Remove email from person's address list */ + if( person->listAttrib ) { + person->listAttrib = g_list_remove( person->listAttrib, attrib ); + } + return attrib; +} + +/* +* Remove attribute from person. +* param: person Person. +* attrib Attribute to remove. +* return: UserAttribute object. Note that object should still be freed. +*/ +UserAttribute *addritem_person_remove_attribute( ItemPerson *person, UserAttribute *attrib ) { + gboolean found = FALSE; + GList *node; + + g_return_val_if_fail( person != NULL, NULL ); + if( attrib == NULL ) return NULL; + + /* Look for attribute */ + node = person->listAttrib; + while( node ) { + if( node-> data == attrib ) { + found = TRUE; + break; + } + node = g_list_next( node ); + } + + if( found ) { + /* Remove attribute */ + if( person->listAttrib ) { + person->listAttrib = g_list_remove( person->listAttrib, attrib ); + } + } + return attrib; +} + +/* +* Create new address book group. +*/ +ItemGroup *addritem_create_item_group( void ) { + ItemGroup *group; + + group = g_new0( ItemGroup, 1 ); + ADDRITEM_TYPE(group) = ITEMTYPE_GROUP; + ADDRITEM_ID(group) = NULL; + ADDRITEM_NAME(group) = NULL; + ADDRITEM_PARENT(group) = NULL; + ADDRITEM_SUBTYPE(group) = 0; + group->remarks = NULL; + group->listEMail = NULL; + return group; +} + +/* +* Specify name to be used. +*/ +void addritem_group_set_id( ItemGroup *group, const gchar *value ) { + ADDRITEM_ID(group) = mgu_replace_string( ADDRITEM_ID(group), value ); +} +void addritem_group_set_name( ItemGroup *group, const gchar *value ) { + ADDRITEM_NAME(group) = mgu_replace_string( ADDRITEM_NAME(group), value ); +} +void addritem_group_set_remarks( ItemGroup *group, const gchar *value ) { + group->remarks = mgu_replace_string( group->remarks, value ); +} + +/* +* Free address group. +*/ +void addritem_free_item_group( ItemGroup *group ) { + g_return_if_fail( group != NULL ); + + /* Free internal stuff */ + g_free( ADDRITEM_ID(group) ); + g_free( ADDRITEM_NAME(group) ); + g_free( group->remarks ); + mgu_clear_list( group->listEMail ); + g_list_free( group->listEMail ); + + ADDRITEM_TYPE(group) = ITEMTYPE_NONE; + ADDRITEM_ID(group) = NULL; + ADDRITEM_NAME(group) = NULL; + ADDRITEM_PARENT(group) = NULL; + ADDRITEM_SUBTYPE(group) = 0; + group->remarks = NULL; + group->listEMail = NULL; + + g_free( group ); +} + +/* +* Add EMail address to group. +* return: TRUE if item added. +*/ +gboolean addritem_group_add_email( ItemGroup *group, ItemEMail *email ) { + GList *node; + + g_return_val_if_fail( group != NULL, FALSE ); + g_return_val_if_fail( email != NULL, FALSE ); + + node = group->listEMail; + while( node ) { + if( node->data == email ) return FALSE; + node = g_list_next( node ); + } + group->listEMail = g_list_append( group->listEMail, email ); + return TRUE; +} + +/* +* Remove email address for specified group. +* param: group Group from which to remove address. +* email EMail to remove +* return: EMail object, or NULL if email not found in group. Note that this object is +* referenced (linked) to a group and should *NOT* be freed. This object should only be +* freed after removing from a person. +*/ +ItemEMail *addritem_group_remove_email( ItemGroup *group, ItemEMail *email ) { + if( group && email ) { + GList *node = group->listEMail; + while( node ) { + if( node->data == email ) { + group->listEMail = g_list_remove( group->listEMail, email ); + return email; + } + node = g_list_next( node ); + } + } + return NULL; +} + +/* +* Remove email address for specified group and ID. +* param: group Group from which to remove address. +* eid EMail ID. +* return: EMail object, or NULL if email not found in group. Note that this object is +* referenced (linked) to a group and should *NOT* be freed. This object should only be +* freed after removing from a person. +*/ +ItemEMail *addritem_group_remove_email_id( ItemGroup *group, const gchar *eid ) { + if( group ) { + GList *node = group->listEMail; + while( node ) { + ItemEMail *email = ( ItemEMail * ) node->data; + if( strcmp( ADDRITEM_ID( email ), eid ) == 0 ) { + group->listEMail = g_list_remove( group->listEMail, email ); + return email; + } + node = g_list_next( node ); + } + } + return NULL; +} + +/* +* Print address group item. +*/ +void addritem_print_item_group( ItemGroup *group, FILE *stream ) { + GList *node; + ItemPerson *person; + ItemEMail *item; + g_return_if_fail( group != NULL ); + fprintf( stream, "Group:\n" ); + fprintf( stream, "\tt/u: %d : '%s'\n", ADDRITEM_TYPE(group), ADDRITEM_ID(group) ); + fprintf( stream, "\tsub: %d\n", ADDRITEM_SUBTYPE(group) ); + fprintf( stream, "\tgrp: '%s'\n", ADDRITEM_NAME(group) ); + fprintf( stream, "\trem: '%s'\n", group->remarks ); + fprintf( stream, "\t---\n" ); + node = group->listEMail; + while( node ) { + item = node->data; + person = ( ItemPerson * ) ADDRITEM_PARENT(item); + if( person ) { + fprintf( stream, "\t\tpid : '%s'\n", ADDRITEM_ID(person) ); + fprintf( stream, "\t\tcomn: '%s'\n", ADDRITEM_NAME(person) ); + } + else { + fprintf( stream, "\t\tpid : ???\n" ); + fprintf( stream, "\t\tcomn: ???\n" ); + } + addritem_print_item_email( item, stream ); + node = g_list_next( node ); + } + fprintf( stream, "\t***\n" ); +} + +/* +* Create new address folder. +*/ +ItemFolder *addritem_create_item_folder( void ) { + ItemFolder *folder; + folder = g_new0( ItemFolder, 1 ); + ADDRITEM_TYPE(folder) = ITEMTYPE_FOLDER; + ADDRITEM_ID(folder) = NULL; + ADDRITEM_NAME(folder) = NULL; + ADDRITEM_PARENT(folder) = NULL; + ADDRITEM_SUBTYPE(folder) = 0; + folder->remarks = NULL; + folder->isRoot = FALSE; + folder->listItems = NULL; + folder->listFolder = NULL; + folder->listPerson = NULL; + folder->listGroup = NULL; + folder->userData = NULL; + return folder; +} + +/* +* Specify name to be used. +*/ +void addritem_folder_set_id( ItemFolder *folder, const gchar *value ) { + ADDRITEM_ID(folder) = mgu_replace_string( ADDRITEM_ID(folder), value ); +} +void addritem_folder_set_name( ItemFolder *folder, const gchar *value ) { + ADDRITEM_NAME(folder) = mgu_replace_string( ADDRITEM_NAME(folder), value ); +} +void addritem_folder_set_remarks( ItemFolder *folder, const gchar *value ) { + folder->remarks = mgu_replace_string( folder->remarks, value ); +} + +/* +* Free address folder. Note: this does not free up the lists of children +* (folders, groups and person). This should be done prior to calling this +* function. +*/ +void addritem_free_item_folder( ItemFolder *folder ) { + g_return_if_fail( folder != NULL ); + + /* Free internal stuff */ + g_free( ADDRITEM_ID(folder) ); + g_free( ADDRITEM_NAME(folder) ); + g_free( folder->remarks ); + mgu_clear_list( folder->listItems ); + g_list_free( folder->listItems ); + + ADDRITEM_TYPE(folder) = ITEMTYPE_NONE; + ADDRITEM_ID(folder) = NULL; + ADDRITEM_NAME(folder) = NULL; + ADDRITEM_PARENT(folder) = NULL; + ADDRITEM_SUBTYPE(folder) = 0; + folder->isRoot = FALSE; + folder->remarks = NULL; + folder->listItems = NULL; + folder->listFolder = NULL; + folder->listGroup = NULL; + folder->listPerson = NULL; + + g_free( folder->userData ); + folder->userData = NULL; + + g_free( folder ); +} + +/* +* Free up folders recursively. Note: this does not free up the lists of children +* (folders, groups and person). This should be done prior to calling this +* function. +*/ +void addritem_free_item_folder_recurse( ItemFolder *parent ) { + GList *node = parent->listFolder; + + while( node ) { + ItemFolder *folder = node->data; + addritem_free_item_folder_recurse( folder ); + node = g_list_next( node ); + } + g_list_free( parent->listPerson ); + g_list_free( parent->listGroup ); + g_list_free( parent->listFolder ); + parent->listPerson = NULL; + parent->listGroup = NULL; + parent->listFolder = NULL; +} + +/* +* Free up list of person in specified folder. +*/ +void addritem_folder_free_person( ItemFolder *folder ) { + GList *node; + + g_return_if_fail( folder != NULL ); + + /* Free up folder of persons. */ + node = folder->listPerson; + while( node ) { + ItemPerson *person = node->data; + addritem_free_item_person( person ); + person = NULL; + node = g_list_next( node ); + } +} + +/* +* Add person into folder. +* return: TRUE if person added. +*/ +gboolean addritem_folder_add_person( ItemFolder *folder, ItemPerson *item ) { + g_return_val_if_fail( folder != NULL, FALSE ); + g_return_val_if_fail( item != NULL, FALSE ); + + folder->listPerson = g_list_append( folder->listPerson, item ); + ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder); + return TRUE; +} + +/* +* Add folder into folder. +* return: TRUE if folder added. +*/ +gboolean addritem_folder_add_folder( ItemFolder *folder, ItemFolder *item ) { + g_return_val_if_fail( folder != NULL, FALSE ); + g_return_val_if_fail( item != NULL, FALSE ); + + folder->listFolder = g_list_append( folder->listFolder, item ); + ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder); + return TRUE; +} + +/* +* Add group into folder. +* return: TRUE if folder added. +*/ +gboolean addritem_folder_add_group( ItemFolder *folder, ItemGroup *item ) { + g_return_val_if_fail( folder != NULL, FALSE ); + g_return_val_if_fail( item != NULL, FALSE ); + + folder->listGroup = g_list_append( folder->listGroup, item ); + ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder); + return TRUE; +} + +/* +* Print address folder item. +*/ +void addritem_print_item_folder( ItemFolder *folder, FILE *stream ) { + GList *node; + /* ItemPerson *person; */ + ItemFolder *parent; + + g_return_if_fail( folder != NULL ); + + fprintf( stream, "Folder:\n" ); + fprintf( stream, "\tt/u: %d : '%s'\n", ADDRITEM_TYPE(folder), ADDRITEM_ID(folder) ); + fprintf( stream, "\tsub: %d\n", ADDRITEM_SUBTYPE(folder) ); + fprintf( stream, "\tnam: '%s'\n", ADDRITEM_NAME(folder) ); + fprintf( stream, "\trem: '%s'\n", folder->remarks ); + fprintf( stream, "\t---\n" ); + parent = ( ItemFolder * ) ADDRITEM_PARENT(folder); + if( parent ) { + fprintf( stream, "\tpar: %s : %s\n", ADDRITEM_ID(parent), ADDRITEM_NAME(parent) ); + } + else { + fprintf( stream, "\tpar: NULL\n" ); + } + node = folder->listFolder; + while( node ) { + AddrItemObject *aio = node->data; + if( aio ) { + if( aio->type == ITEMTYPE_FOLDER ) { + ItemFolder *item = ( ItemFolder * ) aio; + addritem_print_item_folder( item, stream ); + } + } + else { + fprintf( stream, "\t\tpid : ???\n" ); + } + + node = g_list_next( node ); + } + + node = folder->listPerson; + while( node ) { + AddrItemObject *aio = node->data; + if( aio ) { + if( aio->type == ITEMTYPE_PERSON ) { + ItemPerson *item = ( ItemPerson * ) aio; + addritem_print_item_person( item, stream ); + } + } + else { + fprintf( stream, "\t\tpid : ???\n" ); + } + + node = g_list_next( node ); + } + + node = folder->listGroup; + while( node ) { + AddrItemObject *aio = node->data; + if( aio ) { + if( aio->type == ITEMTYPE_GROUP ) { + ItemGroup *item = ( ItemGroup * ) aio; + addritem_print_item_group( item, stream ); + } + } + else { + fprintf( stream, "\t\tpid : ???\n" ); + } + node = g_list_next( node ); + } + fprintf( stream, "\t###\n" ); +} + +/* +* Return link list of persons for specified folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to use the +* addritem_free_xxx() functions... this will destroy the addressbook data! +* Return: List of items, or NULL if none. +*/ +GList *addritem_folder_get_person_list( ItemFolder *folder ) { + GList *list = NULL; + GList *node = NULL; + + g_return_val_if_fail( folder != NULL, NULL ); + + node = folder->listPerson; + while( node ) { + ItemPerson *person = node->data; + list = g_list_append( list, person ); + node = g_list_next( node ); + } + return list; +} + +/* +* Return link list of groups for specified folder. Note that the list contains +* references to items and should be g_free() when done. Do *NOT* attempt to use the +* addritem_free_xxx() functions... this will destroy the addressbook data! +* Return: List of items, or NULL if none. +*/ +GList *addritem_folder_get_group_list( ItemFolder *folder ) { + GList *list = NULL; + GList *node = NULL; + + g_return_val_if_fail( folder != NULL, NULL ); + + node = folder->listGroup; + while( node ) { + ItemGroup *group = node->data; + list = g_list_append( list, group ); + node = g_list_next( node ); + } + return list; +} + +/* +* Move person's email item. +* param: person Person. +* itemMove Item to move. +* itemTarget Target item before which to move item. +*/ + +ItemEMail *addritem_move_email_before( ItemPerson *person, ItemEMail *itemMove, ItemEMail *itemTarget ) { + gint posT, posM; + + g_return_val_if_fail( person != NULL, NULL ); + + if( itemTarget == NULL ) return NULL; + if( itemMove == NULL ) return NULL; + if( itemMove == itemTarget ) return itemMove; + + posT = g_list_index( person->listEMail, itemTarget ); + if( posT < 0 ) return NULL; + posM = g_list_index( person->listEMail, itemMove ); + if( posM < 0 ) return NULL; + person->listEMail = g_list_remove( person->listEMail, itemMove ); + person->listEMail = g_list_insert( person->listEMail, itemMove, posT ); + return itemMove; +} + +/* +* Move person's email item. +* param: person Person. +* itemMove Item to move. +* itemTarget Target item after which to move item. +*/ +ItemEMail *addritem_move_email_after( ItemPerson *person, ItemEMail *itemMove, ItemEMail *itemTarget ) { + gint posT, posM; + + g_return_val_if_fail( person != NULL, NULL ); + + if( itemTarget == NULL ) return NULL; + if( itemMove == NULL ) return NULL; + if( itemMove == itemTarget ) return itemMove; + + posT = g_list_index( person->listEMail, itemTarget ); + if( posT < 0 ) return NULL; + posM = g_list_index( person->listEMail, itemMove ); + if( posM < 0 ) return NULL; + person->listEMail = g_list_remove( person->listEMail, itemMove ); + person->listEMail = g_list_insert( person->listEMail, itemMove, 1+posT ); + return itemMove; +} + +/* +* End of Source. +*/ diff --git a/src/addritem.h b/src/addritem.h new file mode 100644 index 00000000..80cbdaa5 --- /dev/null +++ b/src/addritem.h @@ -0,0 +1,174 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Address item data. + */ + +#ifndef __ADDRITEM_H__ +#define __ADDRITEM_H__ + +#include +#include + +#define ADDRITEM_OBJECT(obj) ((AddrItemObject *)obj) +#define ADDRITEM_TYPE(obj) (ADDRITEM_OBJECT(obj)->type) +#define ADDRITEM_NAME(obj) (ADDRITEM_OBJECT(obj)->name) +#define ADDRITEM_ID(obj) (ADDRITEM_OBJECT(obj)->uid) +#define ADDRITEM_PARENT(obj) (ADDRITEM_OBJECT(obj)->parent) +#define ADDRITEM_SUBTYPE(obj) (ADDRITEM_OBJECT(obj)->subType) + +typedef enum { + ITEMTYPE_NONE, + ITEMTYPE_PERSON, + ITEMTYPE_EMAIL, + ITEMTYPE_FOLDER, + ITEMTYPE_GROUP, + ITEMTYPE_INDEX, + ITEMTYPE_INTERFACE, + ITEMTYPE_DATASOURCE +} ItemObjectType; + +typedef struct _AddrItemObject AddrItemObject; +struct _AddrItemObject { + ItemObjectType type; + gchar *name; + gchar *uid; + AddrItemObject *parent; + gint subType; +}; + +typedef struct _ItemPerson ItemPerson; +struct _ItemPerson { + AddrItemObject obj; + gchar *firstName; + gchar *lastName; + gchar *nickName; + gchar *externalID; + GList *listEMail; + GList *listAttrib; + gboolean isOpened; +}; + +typedef struct _ItemEMail ItemEMail; +struct _ItemEMail { + AddrItemObject obj; + gchar *address; + gchar *remarks; +}; + +typedef struct _UserAttribute UserAttribute; +struct _UserAttribute { + gchar *uid; + gchar *name; + gchar *value; +}; + +typedef struct _ItemFolder ItemFolder; +struct _ItemFolder { + AddrItemObject obj; + gchar *remarks; + gboolean isRoot; + GList *listItems; + GList *listFolder; + GList *listPerson; + GList *listGroup; + gpointer userData; +}; + +typedef struct _ItemGroup ItemGroup; +struct _ItemGroup { + AddrItemObject obj; + gchar *remarks; + GList *listEMail; +}; + +/* Function prototypes */ +ItemEMail *addritem_create_item_email ( void ); +ItemEMail *addritem_copy_item_email ( ItemEMail *item ); +void addritem_email_set_id ( ItemEMail *email, const gchar *value ); +void addritem_email_set_alias ( ItemEMail *email, const gchar *value ); +void addritem_email_set_address ( ItemEMail *email, const gchar *value ); +void addritem_email_set_remarks ( ItemEMail *email, const gchar *value ); +void addritem_free_item_email ( ItemEMail *item ); + +UserAttribute *addritem_create_attribute( void ); +UserAttribute *addritem_copy_attribute ( UserAttribute *item ); +void addritem_attrib_set_id ( UserAttribute *item, const gchar *value ); +void addritem_attrib_set_name ( UserAttribute *item, const gchar *value ); +void addritem_attrib_set_value ( UserAttribute *item, const gchar *value ); +void addritem_free_attribute ( UserAttribute *item ); + +ItemPerson *addritem_create_item_person ( void ); +void addritem_person_set_id ( ItemPerson *person, const gchar *value ); +void addritem_person_set_first_name ( ItemPerson *person, const gchar *value ); +void addritem_person_set_last_name ( ItemPerson *person, const gchar *value ); +void addritem_person_set_nick_name ( ItemPerson *person, const gchar *value ); +void addritem_person_set_common_name ( ItemPerson *person, const gchar *value ); +void addritem_person_set_external_id ( ItemPerson *person, const gchar *value ); +void addritem_person_set_opened ( ItemPerson *person, const gboolean value ); +void addritem_free_item_person ( ItemPerson *person ); +void addritem_free_list_email ( GList *list ); +void addritem_free_list_attribute ( GList *list ); + +ItemGroup *addritem_create_item_group ( void ); +void addritem_free_item_group ( ItemGroup *group ); +void addritem_print ( ItemGroup *group, FILE *stream ); +void addritem_group_set_id ( ItemGroup *group, const gchar *value ); +void addritem_group_set_name ( ItemGroup *group, const gchar *value ); +void addritem_group_set_remarks ( ItemGroup *group, const gchar *value ); + +void addritem_print_item_email ( ItemEMail *item, FILE *stream ); +void addritem_print_attribute ( UserAttribute *item, FILE *stream ); +void addritem_print_item_person ( ItemPerson *person, FILE *stream ); +void addritem_print_item_group ( ItemGroup *group, FILE *stream ); +void addritem_print_item_folder ( ItemFolder *folder, FILE *stream ); + +gboolean addritem_person_add_email ( ItemPerson *person, ItemEMail *email ); +ItemEMail *addritem_person_get_email ( ItemPerson *person, const gchar *eid ); +ItemEMail *addritem_person_remove_email_id ( ItemPerson *person, const gchar *eid ); +ItemEMail *addritem_person_remove_email ( ItemPerson *person, ItemEMail *email ); + +void addritem_person_add_attribute ( ItemPerson *person, UserAttribute *attrib ); +UserAttribute *addritem_person_get_attribute ( ItemPerson *person, const gchar *aid ); +UserAttribute *addritem_person_remove_attrib_id ( ItemPerson *person, const gchar *aid ); +UserAttribute *addritem_person_remove_attribute ( ItemPerson *person, UserAttribute *attrib ); + +ItemFolder *addritem_create_item_folder ( void ); +void addritem_folder_set_id ( ItemFolder *folder, const gchar *value ); +void addritem_folder_set_name ( ItemFolder *folder, const gchar *value ); +void addritem_folder_set_remarks ( ItemFolder *folder, const gchar *value ); +void addritem_free_item_folder ( ItemFolder *folder ); +void addritem_free_item_folder_recurse ( ItemFolder *parent ); + +gboolean addritem_group_add_email ( ItemGroup *group, ItemEMail *email ); +ItemEMail *addritem_group_remove_email ( ItemGroup *group, ItemEMail *email ); +ItemEMail *addritem_group_remove_email_id ( ItemGroup *group, const gchar *eid ); + +gboolean addritem_folder_add_person ( ItemFolder *folder, ItemPerson *item ); +gboolean addritem_folder_add_folder ( ItemFolder *folder, ItemFolder *item ); +gboolean addritem_folder_add_group ( ItemFolder *folder, ItemGroup *item ); +void addritem_folder_free_person ( ItemFolder *folder ); +GList *addritem_folder_get_person_list ( ItemFolder *folder ); +GList *addritem_folder_get_group_list ( ItemFolder *folder ); + +ItemEMail *addritem_move_email_before ( ItemPerson *person, ItemEMail *itemMove, ItemEMail *itemTarget ); +ItemEMail *addritem_move_email_after ( ItemPerson *person, ItemEMail *itemMove, ItemEMail *itemTarget ); + +#endif /* __ADDRITEM_H__ */ diff --git a/src/alertpanel.c b/src/alertpanel.c new file mode 100644 index 00000000..bc865358 --- /dev/null +++ b/src/alertpanel.c @@ -0,0 +1,353 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include + +#include "intl.h" +#include "alertpanel.h" +#include "manage_window.h" +#include "utils.h" +#include "gtkutils.h" +#include "inc.h" +#include "stock_pixmap.h" +#include "prefs_common.h" + +#define ALERT_PANEL_WIDTH 380 +#define TITLE_HEIGHT 72 +#define MESSAGE_HEIGHT 62 + +static gboolean alertpanel_is_open = FALSE; +static AlertValue value; + +static GtkWidget *dialog; + +static void alertpanel_show (void); +static void alertpanel_create (const gchar *title, + const gchar *message, + const gchar *button1_label, + const gchar *button2_label, + const gchar *button3_label, + gboolean can_disable, + AlertType type); + +static void alertpanel_button_toggled (GtkToggleButton *button, + gpointer data); +static void alertpanel_button_clicked (GtkWidget *widget, + gpointer data); +static gint alertpanel_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean alertpanel_close (GtkWidget *widget, + GdkEventAny *event, + gpointer data); + +AlertValue alertpanel(const gchar *title, + const gchar *message, + const gchar *button1_label, + const gchar *button2_label, + const gchar *button3_label) +{ + if (alertpanel_is_open) + return -1; + else + alertpanel_is_open = TRUE; + + alertpanel_create(title, message, button1_label, button2_label, + button3_label, FALSE, ALERT_QUESTION); + alertpanel_show(); + + debug_print("return value = %d\n", value); + return value; +} + +void alertpanel_message(const gchar *title, const gchar *message, + AlertType type) +{ + if (alertpanel_is_open) + return; + else + alertpanel_is_open = TRUE; + + alertpanel_create(title, message, NULL, NULL, NULL, FALSE, type); + alertpanel_show(); +} + +AlertValue alertpanel_message_with_disable(const gchar *title, + const gchar *message, + AlertType type) +{ + if (alertpanel_is_open) + return 0; + else + alertpanel_is_open = TRUE; + + alertpanel_create(title, message, NULL, NULL, NULL, TRUE, type); + alertpanel_show(); + + return value; +} + +void alertpanel_notice(const gchar *format, ...) +{ + va_list args; + gchar buf[256]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + strretchomp(buf); + + alertpanel_message(_("Notice"), buf, ALERT_NOTICE); +} + +void alertpanel_warning(const gchar *format, ...) +{ + va_list args; + gchar buf[256]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + strretchomp(buf); + + alertpanel_message(_("Warning"), buf, ALERT_WARNING); +} + +void alertpanel_error(const gchar *format, ...) +{ + va_list args; + gchar buf[256]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + strretchomp(buf); + + alertpanel_message(_("Error"), buf, ALERT_ERROR); +} + +static void alertpanel_show(void) +{ + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + manage_window_set_transient(GTK_WINDOW(dialog)); + value = G_ALERTWAIT; + + inc_lock(); + while ((value & G_ALERT_VALUE_MASK) == G_ALERTWAIT) + gtk_main_iteration(); + + gtk_widget_destroy(dialog); + GTK_EVENTS_FLUSH(); + + alertpanel_is_open = FALSE; + inc_unlock(); +} + +static void alertpanel_create(const gchar *title, + const gchar *message, + const gchar *button1_label, + const gchar *button2_label, + const gchar *button3_label, + gboolean can_disable, + AlertType type) +{ + static PangoFontDescription *font_desc; + StockPixmap stock_pixmap; + GtkWidget *pixmapwid; + GtkWidget *label; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *spc_vbox; + GtkWidget *msg_vbox; + GtkWidget *disable_chkbtn; + GtkWidget *confirm_area; + GtkWidget *button1; + GtkWidget *button2; + GtkWidget *button3; + const gchar *label2; + const gchar *label3; + + debug_print(_("Creating alert panel dialog...\n")); + + dialog = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dialog), title); + gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE); + gtk_container_set_border_width + (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + g_signal_connect(G_OBJECT(dialog), "delete_event", + G_CALLBACK(alertpanel_deleted), + (gpointer)G_ALERTOTHER); + g_signal_connect(G_OBJECT(dialog), "key_press_event", + G_CALLBACK(alertpanel_close), + (gpointer)G_ALERTOTHER); + gtk_widget_realize(dialog); + + /* for title label */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), + hbox, TRUE, TRUE, 8); + + /* title icon and label */ + switch (type) { + case ALERT_NOTICE: + stock_pixmap = STOCK_PIXMAP_DIALOG_INFO; break; + case ALERT_QUESTION: + stock_pixmap = STOCK_PIXMAP_DIALOG_QUESTION; break; + case ALERT_WARNING: + stock_pixmap = STOCK_PIXMAP_DIALOG_WARNING; break; + case ALERT_ERROR: + stock_pixmap = STOCK_PIXMAP_DIALOG_ERROR; break; + default: + stock_pixmap = STOCK_PIXMAP_DIALOG_QUESTION; break; + } + pixmapwid = stock_pixmap_widget(dialog, stock_pixmap); + gtk_box_pack_start(GTK_BOX(hbox), pixmapwid, FALSE, FALSE, 16); + + label = gtk_label_new(title); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + if (!font_desc) { + gchar *fontstr = prefs_common.titlefont + ? prefs_common.titlefont + : DEFAULT_TITLE_FONT; + font_desc = pango_font_description_from_string(fontstr); + } + if (font_desc) + gtk_widget_modify_font(label, font_desc); + + /* for message and button(s) */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), + vbox); + + spc_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), spc_vbox, FALSE, FALSE, 0); + gtk_widget_set_size_request(spc_vbox, -1, 16); + + msg_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), msg_vbox, FALSE, FALSE, 0); + + /* for message label */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(msg_vbox), hbox, FALSE, FALSE, 0); + + /* message label */ + label = gtk_label_new(message); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 24); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); + + if (can_disable) { + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 8); + + disable_chkbtn = gtk_check_button_new_with_label + (_("Show this message next time")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(disable_chkbtn), + TRUE); + gtk_box_pack_start(GTK_BOX(hbox), disable_chkbtn, + FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(disable_chkbtn), "toggled", + G_CALLBACK(alertpanel_button_toggled), + GUINT_TO_POINTER(G_ALERTDISABLE)); + } else { + spc_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), spc_vbox, FALSE, FALSE, 0); + gtk_widget_set_size_request(spc_vbox, -1, 20); + } + + /* for button(s) */ + if (!button1_label) + button1_label = _("OK"); + label2 = button2_label; + label3 = button3_label; + if (label2 && *label2 == '+') label2++; + if (label3 && *label3 == '+') label3++; + + gtkut_button_set_create(&confirm_area, + &button1, button1_label, + button2_label ? &button2 : NULL, label2, + button3_label ? &button3 : NULL, label3); + + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(button1); + gtk_widget_grab_focus(button1); + if (button2_label && *button2_label == '+') { + gtk_widget_grab_default(button2); + gtk_widget_grab_focus(button2); + } + if (button3_label && *button3_label == '+') { + gtk_widget_grab_default(button3); + gtk_widget_grab_focus(button3); + } + + g_signal_connect(G_OBJECT(button1), "clicked", + G_CALLBACK(alertpanel_button_clicked), + GUINT_TO_POINTER(G_ALERTDEFAULT)); + if (button2_label) + g_signal_connect(G_OBJECT(button2), "clicked", + G_CALLBACK(alertpanel_button_clicked), + GUINT_TO_POINTER(G_ALERTALTERNATE)); + if (button3_label) + g_signal_connect(G_OBJECT(button3), "clicked", + G_CALLBACK(alertpanel_button_clicked), + GUINT_TO_POINTER(G_ALERTOTHER)); + + gtk_widget_show_all(dialog); +} + +static void alertpanel_button_toggled(GtkToggleButton *button, + gpointer data) +{ + if (gtk_toggle_button_get_active(button)) + value &= ~GPOINTER_TO_UINT(data); + else + value |= GPOINTER_TO_UINT(data); +} + +static void alertpanel_button_clicked(GtkWidget *widget, gpointer data) +{ + value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data; +} + +static gint alertpanel_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data; + return TRUE; +} + +static gboolean alertpanel_close(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + if (event->type == GDK_KEY_PRESS) + if (((GdkEventKey *)event)->keyval != GDK_Escape) + return FALSE; + + value = (value & ~G_ALERT_VALUE_MASK) | (AlertValue)data; + return FALSE; +} diff --git a/src/alertpanel.h b/src/alertpanel.h new file mode 100644 index 00000000..8b0d4e1b --- /dev/null +++ b/src/alertpanel.h @@ -0,0 +1,67 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ALERTPANEL_H__ +#define __ALERTPANEL_H__ + +#include +#include + +typedef enum +{ + G_ALERTDEFAULT, + G_ALERTALTERNATE, + G_ALERTOTHER, + G_ALERTWAIT, + + G_ALERTDISABLE = 1 << 16 +} AlertValue; + +typedef enum +{ + ALERT_NOTICE, + ALERT_QUESTION, + ALERT_WARNING, + ALERT_ERROR +} AlertType; + +#define G_ALERT_VALUE_MASK 0x0000ffff + +AlertValue alertpanel (const gchar *title, + const gchar *message, + const gchar *button1_label, + const gchar *button2_label, + const gchar *button3_label); + +void alertpanel_message (const gchar *title, + const gchar *message, + AlertType type); + +AlertValue alertpanel_message_with_disable (const gchar *title, + const gchar *message, + AlertType type); + +void alertpanel_notice (const gchar *format, + ...) G_GNUC_PRINTF(1, 2); +void alertpanel_warning (const gchar *format, + ...) G_GNUC_PRINTF(1, 2); +void alertpanel_error (const gchar *format, + ...) G_GNUC_PRINTF(1, 2); + +#endif /* __ALERTPANEL_H__ */ diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 00000000..484cd286 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,168 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include +#include + +#include "base64.h" + +static const gchar base64char[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const gchar base64val[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +}; + +#define BASE64VAL(c) (isascii((guchar)c) ? base64val[(gint)(c)] : -1) + +void base64_encode(gchar *out, const guchar *in, gint inlen) +{ + const guchar *inp = in; + gchar *outp = out; + + while (inlen >= 3) { + *outp++ = base64char[(inp[0] >> 2) & 0x3f]; + *outp++ = base64char[((inp[0] & 0x03) << 4) | + ((inp[1] >> 4) & 0x0f)]; + *outp++ = base64char[((inp[1] & 0x0f) << 2) | + ((inp[2] >> 6) & 0x03)]; + *outp++ = base64char[inp[2] & 0x3f]; + + inp += 3; + inlen -= 3; + } + + if (inlen > 0) { + *outp++ = base64char[(inp[0] >> 2) & 0x3f]; + if (inlen == 1) { + *outp++ = base64char[(inp[0] & 0x03) << 4]; + *outp++ = '='; + } else { + *outp++ = base64char[((inp[0] & 0x03) << 4) | + ((inp[1] >> 4) & 0x0f)]; + *outp++ = base64char[((inp[1] & 0x0f) << 2)]; + } + *outp++ = '='; + } + + *outp = '\0'; +} + +gint base64_decode(guchar *out, const gchar *in, gint inlen) +{ + const gchar *inp = in; + guchar *outp = out; + gchar buf[4]; + + if (inlen < 0) + inlen = G_MAXINT; + + while (inlen >= 4 && *inp != '\0') { + buf[0] = *inp++; + inlen--; + if (BASE64VAL(buf[0]) == -1) break; + + buf[1] = *inp++; + inlen--; + if (BASE64VAL(buf[1]) == -1) break; + + buf[2] = *inp++; + inlen--; + if (buf[2] != '=' && BASE64VAL(buf[2]) == -1) break; + + buf[3] = *inp++; + inlen--; + if (buf[3] != '=' && BASE64VAL(buf[3]) == -1) break; + + *outp++ = ((BASE64VAL(buf[0]) << 2) & 0xfc) | + ((BASE64VAL(buf[1]) >> 4) & 0x03); + if (buf[2] != '=') { + *outp++ = ((BASE64VAL(buf[1]) & 0x0f) << 4) | + ((BASE64VAL(buf[2]) >> 2) & 0x0f); + if (buf[3] != '=') { + *outp++ = ((BASE64VAL(buf[2]) & 0x03) << 6) | + (BASE64VAL(buf[3]) & 0x3f); + } + } + } + + return outp - out; +} + +Base64Decoder *base64_decoder_new(void) +{ + Base64Decoder *decoder; + + decoder = g_new0(Base64Decoder, 1); + return decoder; +} + +void base64_decoder_free(Base64Decoder *decoder) +{ + g_free(decoder); +} + +gint base64_decoder_decode(Base64Decoder *decoder, + const gchar *in, guchar *out) +{ + gint len, total_len = 0; + gint buf_len; + gchar buf[4]; + + g_return_val_if_fail(decoder != NULL, -1); + g_return_val_if_fail(in != NULL, -1); + g_return_val_if_fail(out != NULL, -1); + + buf_len = decoder->buf_len; + memcpy(buf, decoder->buf, sizeof(buf)); + + for (;;) { + while (buf_len < 4) { + gchar c = *in; + + in++; + if (c == '\0') break; + if (c == '\r' || c == '\n') continue; + if (c != '=' && BASE64VAL(c) == -1) + return -1; + buf[buf_len++] = c; + } + if (buf_len < 4 || buf[0] == '=' || buf[1] == '=') { + decoder->buf_len = buf_len; + memcpy(decoder->buf, buf, sizeof(buf)); + return total_len; + } + len = base64_decode(out, buf, 4); + out += len; + total_len += len; + buf_len = 0; + if (len < 3) { + decoder->buf_len = 0; + return total_len; + } + } +} diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 00000000..4aa55758 --- /dev/null +++ b/src/base64.h @@ -0,0 +1,46 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __BASE64_H__ +#define __BASE64_H__ + +#include + +typedef struct _Base64Decoder Base64Decoder; + +struct _Base64Decoder +{ + gint buf_len; + gchar buf[4]; +}; + +void base64_encode (gchar *out, + const guchar *in, + gint inlen); +gint base64_decode (guchar *out, + const gchar *in, + gint inlen); + +Base64Decoder *base64_decoder_new (void); +void base64_decoder_free (Base64Decoder *decoder); +gint base64_decoder_decode (Base64Decoder *decoder, + const gchar *in, + guchar *out); + +#endif /* __BASE64_H__ */ diff --git a/src/codeconv.c b/src/codeconv.c new file mode 100644 index 00000000..7c5b449b --- /dev/null +++ b/src/codeconv.c @@ -0,0 +1,1769 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#if HAVE_LOCALE_H +# include +#endif + +#if HAVE_ICONV +# include +#endif + +#include "intl.h" +#include "codeconv.h" +#include "unmime.h" +#include "base64.h" +#include "quoted-printable.h" +#include "utils.h" +#include "prefs_common.h" + +typedef enum +{ + JIS_ASCII, + JIS_KANJI, + JIS_HWKANA, + JIS_AUXKANJI +} JISState; + +#define SUBST_CHAR '_' +#define ESC '\033' + +#define iseuckanji(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xfe) +#define iseuchwkana1(c) \ + (((c) & 0xff) == 0x8e) +#define iseuchwkana2(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf) +#define iseucaux(c) \ + (((c) & 0xff) == 0x8f) +#define issjiskanji1(c) \ + ((((c) & 0xff) >= 0x81 && ((c) & 0xff) <= 0x9f) || \ + (((c) & 0xff) >= 0xe0 && ((c) & 0xff) <= 0xfc)) +#define issjiskanji2(c) \ + ((((c) & 0xff) >= 0x40 && ((c) & 0xff) <= 0x7e) || \ + (((c) & 0xff) >= 0x80 && ((c) & 0xff) <= 0xfc)) +#define issjishwkana(c) \ + (((c) & 0xff) >= 0xa1 && ((c) & 0xff) <= 0xdf) + +#define K_IN() \ + if (state != JIS_KANJI) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = 'B'; \ + state = JIS_KANJI; \ + } + +#define K_OUT() \ + if (state != JIS_ASCII) { \ + *out++ = ESC; \ + *out++ = '('; \ + *out++ = 'B'; \ + state = JIS_ASCII; \ + } + +#define HW_IN() \ + if (state != JIS_HWKANA) { \ + *out++ = ESC; \ + *out++ = '('; \ + *out++ = 'I'; \ + state = JIS_HWKANA; \ + } + +#define AUX_IN() \ + if (state != JIS_AUXKANJI) { \ + *out++ = ESC; \ + *out++ = '$'; \ + *out++ = '('; \ + *out++ = 'D'; \ + state = JIS_AUXKANJI; \ + } + +void conv_jistoeuc(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + const guchar *in = inbuf; + guchar *out = outbuf; + JISState state = JIS_ASCII; + + while (*in != '\0') { + if (*in == ESC) { + in++; + if (*in == '$') { + if (*(in + 1) == '@' || *(in + 1) == 'B') { + state = JIS_KANJI; + in += 2; + } else if (*(in + 1) == '(' && + *(in + 2) == 'D') { + state = JIS_AUXKANJI; + in += 3; + } else { + /* unknown escape sequence */ + state = JIS_ASCII; + } + } else if (*in == '(') { + if (*(in + 1) == 'B' || *(in + 1) == 'J') { + state = JIS_ASCII; + in += 2; + } else if (*(in + 1) == 'I') { + state = JIS_HWKANA; + in += 2; + } else { + /* unknown escape sequence */ + state = JIS_ASCII; + } + } else { + /* unknown escape sequence */ + state = JIS_ASCII; + } + } else if (*in == 0x0e) { + state = JIS_HWKANA; + in++; + } else if (*in == 0x0f) { + state = JIS_ASCII; + in++; + } else { + switch (state) { + case JIS_ASCII: + *out++ = *in++; + break; + case JIS_KANJI: + *out++ = *in++ | 0x80; + if (*in == '\0') break; + *out++ = *in++ | 0x80; + break; + case JIS_HWKANA: + *out++ = 0x8e; + *out++ = *in++ | 0x80; + break; + case JIS_AUXKANJI: + *out++ = 0x8f; + *out++ = *in++ | 0x80; + if (*in == '\0') break; + *out++ = *in++ | 0x80; + break; + } + } + } + + *out = '\0'; +} + +#define JIS_HWDAKUTEN 0x5e +#define JIS_HWHANDAKUTEN 0x5f + +static gint conv_jis_hantozen(guchar *outbuf, guchar jis_code, guchar sound_sym) +{ + static guint16 h2z_tbl[] = { + /* 0x20 - 0x2f */ + 0x0000, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521, + 0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543, + /* 0x30 - 0x3f */ + 0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d, + 0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d, + /* 0x40 - 0x4f */ + 0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c, + 0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e, + /* 0x50 - 0x5f */ + 0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569, + 0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c + }; + + static guint16 dakuten_tbl[] = { + /* 0x30 - 0x3f */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x252c, 0x252e, + 0x2530, 0x2532, 0x2534, 0x2536, 0x2538, 0x253a, 0x253c, 0x253e, + /* 0x40 - 0x4f */ + 0x2540, 0x2542, 0x2545, 0x2547, 0x2549, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x2550, 0x2553, 0x2556, 0x2559, 0x255c, 0x0000 + }; + + static guint16 handakuten_tbl[] = { + /* 0x4a - 0x4e */ + 0x2551, 0x2554, 0x2557, 0x255a, 0x255d + }; + + guint16 out_code; + + jis_code &= 0x7f; + sound_sym &= 0x7f; + + if (jis_code < 0x21 || jis_code > 0x5f) + return 0; + + if (sound_sym == JIS_HWDAKUTEN && + jis_code >= 0x36 && jis_code <= 0x4e) { + out_code = dakuten_tbl[jis_code - 0x30]; + if (out_code != 0) { + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 2; + } + } + + if (sound_sym == JIS_HWHANDAKUTEN && + jis_code >= 0x4a && jis_code <= 0x4e) { + out_code = handakuten_tbl[jis_code - 0x4a]; + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 2; + } + + out_code = h2z_tbl[jis_code - 0x20]; + *outbuf = out_code >> 8; + *(outbuf + 1) = out_code & 0xff; + return 1; +} + +void conv_euctojis(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + const guchar *in = inbuf; + guchar *out = outbuf; + JISState state = JIS_ASCII; + + while (*in != '\0') { + if (isascii(*in)) { + K_OUT(); + *out++ = *in++; + } else if (iseuckanji(*in)) { + if (iseuckanji(*(in + 1))) { + K_IN(); + *out++ = *in++ & 0x7f; + *out++ = *in++ & 0x7f; + } else { + K_OUT(); + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (iseuchwkana1(*in)) { + if (iseuchwkana2(*(in + 1))) { + if (prefs_common.allow_jisx0201_kana) { + HW_IN(); + in++; + *out++ = *in++ & 0x7f; + } else { + guchar jis_ch[2]; + gint len; + + if (iseuchwkana1(*(in + 2)) && + iseuchwkana2(*(in + 3))) + len = conv_jis_hantozen + (jis_ch, + *(in + 1), *(in + 3)); + else + len = conv_jis_hantozen + (jis_ch, + *(in + 1), '\0'); + if (len == 0) + in += 2; + else { + K_IN(); + in += len * 2; + *out++ = jis_ch[0]; + *out++ = jis_ch[1]; + } + } + } else { + K_OUT(); + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (iseucaux(*in)) { + in++; + if (iseuckanji(*in) && iseuckanji(*(in + 1))) { + AUX_IN(); + *out++ = *in++ & 0x7f; + *out++ = *in++ & 0x7f; + } else { + K_OUT(); + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } + } else { + K_OUT(); + *out++ = SUBST_CHAR; + in++; + } + } + + K_OUT(); + *out = '\0'; +} + +void conv_sjistoeuc(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + const guchar *in = inbuf; + guchar *out = outbuf; + + while (*in != '\0') { + if (isascii(*in)) { + *out++ = *in++; + } else if (issjiskanji1(*in)) { + if (issjiskanji2(*(in + 1))) { + guchar out1 = *in; + guchar out2 = *(in + 1); + guchar row; + + row = out1 < 0xa0 ? 0x70 : 0xb0; + if (out2 < 0x9f) { + out1 = (out1 - row) * 2 - 1; + out2 -= out2 > 0x7f ? 0x20 : 0x1f; + } else { + out1 = (out1 - row) * 2; + out2 -= 0x7e; + } + + *out++ = out1 | 0x80; + *out++ = out2 | 0x80; + in += 2; + } else { + *out++ = SUBST_CHAR; + in++; + if (*in != '\0' && !isascii(*in)) { + *out++ = SUBST_CHAR; + in++; + } + } + } else if (issjishwkana(*in)) { + *out++ = 0x8e; + *out++ = *in++; + } else { + *out++ = SUBST_CHAR; + in++; + } + } + + *out = '\0'; +} + +void conv_jistoutf8(gchar *outbuf, gint outlen, const gchar *inbuf) +{ +#if HAVE_ICONV + gchar *tmpstr; + + tmpstr = conv_iconv_strdup(inbuf, CS_ISO_2022_JP, CS_UTF_8); + if (tmpstr) { + strncpy2(outbuf, tmpstr, outlen); + g_free(tmpstr); + } else + strncpy2(outbuf, inbuf, outlen); +#else + strncpy2(outbuf, inbuf, outlen); +#endif /* HAVE_ICONV */ +} + +void conv_sjistoutf8(gchar *outbuf, gint outlen, const gchar *inbuf) +{ +#if HAVE_ICONV + gchar *tmpstr; + + tmpstr = conv_iconv_strdup(inbuf, CS_SHIFT_JIS, CS_UTF_8); + if (tmpstr) { + strncpy2(outbuf, tmpstr, outlen); + g_free(tmpstr); + } else + strncpy2(outbuf, inbuf, outlen); +#else + strncpy2(outbuf, inbuf, outlen); +#endif /* HAVE_ICONV */ +} + +void conv_euctoutf8(gchar *outbuf, gint outlen, const gchar *inbuf) +{ +#if HAVE_ICONV + gchar *tmpstr; + + tmpstr = conv_iconv_strdup(inbuf, CS_EUC_JP, CS_UTF_8); + if (tmpstr) { + strncpy2(outbuf, tmpstr, outlen); + g_free(tmpstr); + } else + strncpy2(outbuf, inbuf, outlen); +#else + strncpy2(outbuf, inbuf, outlen); +#endif /* HAVE_ICONV */ +} + +void conv_anytoeuc(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + switch (conv_guess_ja_encoding(inbuf)) { + case C_ISO_2022_JP: + conv_jistoeuc(outbuf, outlen, inbuf); + break; + case C_SHIFT_JIS: + conv_sjistoeuc(outbuf, outlen, inbuf); + break; + default: + strncpy2(outbuf, inbuf, outlen); + break; + } +} + +void conv_anytoutf8(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + switch (conv_guess_ja_encoding(inbuf)) { + case C_ISO_2022_JP: + conv_jistoutf8(outbuf, outlen, inbuf); + break; + case C_SHIFT_JIS: + conv_sjistoutf8(outbuf, outlen, inbuf); + break; + case C_EUC_JP: + conv_euctoutf8(outbuf, outlen, inbuf); + break; + default: + strncpy2(outbuf, inbuf, outlen); + break; + } +} + +void conv_anytojis(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + switch (conv_guess_ja_encoding(inbuf)) { + case C_EUC_JP: + conv_euctojis(outbuf, outlen, inbuf); + break; + default: + strncpy2(outbuf, inbuf, outlen); + break; + } +} + +static gchar valid_eucjp_tbl[][96] = { + /* 0xa2a0 - 0xa2ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0 }, + + /* 0xa3a0 - 0xa3ff */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, + + /* 0xa4a0 - 0xa4ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa5a0 - 0xa5ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa6a0 - 0xa6ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa7a0 - 0xa7ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + /* 0xa8a0 - 0xa8ff */ + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +static gboolean isprintableeuckanji(guchar c1, guchar c2) +{ + if (c1 <= 0xa0 || c1 >= 0xf5) + return FALSE; + if (c2 <= 0xa0 || c2 == 0xff) + return FALSE; + + if (c1 >= 0xa9 && c1 <= 0xaf) + return FALSE; + + if (c1 >= 0xa2 && c1 <= 0xa8) + return (gboolean)valid_eucjp_tbl[c1 - 0xa2][c2 - 0xa0]; + + if (c1 == 0xcf) { + if (c2 >= 0xd4 && c2 <= 0xfe) + return FALSE; + } else if (c1 == 0xf4) { + if (c2 >= 0xa7 && c2 <= 0xfe) + return FALSE; + } + + return TRUE; +} + +void conv_unreadable_eucjp(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + if (isascii(*p)) { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + /* printable 7 bit code */ + p++; + } else if (iseuckanji(*p)) { + if (isprintableeuckanji(*p, *(p + 1))) { + /* printable euc-jp code */ + p += 2; + } else { + /* substitute unprintable code */ + *p++ = SUBST_CHAR; + if (*p != '\0') { + if (isascii(*p)) + p++; + else + *p++ = SUBST_CHAR; + } + } + } else if (iseuchwkana1(*p)) { + if (iseuchwkana2(*(p + 1))) + /* euc-jp hankaku kana */ + p += 2; + else + *p++ = SUBST_CHAR; + } else if (iseucaux(*p)) { + if (iseuckanji(*(p + 1)) && iseuckanji(*(p + 2))) { + /* auxiliary kanji */ + p += 3; + } else + *p++ = SUBST_CHAR; + } else + /* substitute unprintable 1 byte code */ + *p++ = SUBST_CHAR; + } +} + +void conv_unreadable_8bit(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + else if (!isascii(*p)) *p = SUBST_CHAR; + p++; + } +} + +void conv_unreadable_latin(gchar *str) +{ + register guchar *p = str; + + while (*p != '\0') { + /* convert CR+LF -> LF */ + if (*p == '\r' && *(p + 1) == '\n') + memmove(p, p + 1, strlen(p)); + else if ((*p & 0xff) >= 0x7f && (*p & 0xff) <= 0x9f) + *p = SUBST_CHAR; + p++; + } +} + +void conv_unreadable_locale(gchar *str) +{ + switch (conv_get_locale_charset()) { + case C_US_ASCII: + case C_ISO_8859_1: + case C_ISO_8859_2: + case C_ISO_8859_3: + case C_ISO_8859_4: + case C_ISO_8859_5: + case C_ISO_8859_6: + case C_ISO_8859_7: + case C_ISO_8859_8: + case C_ISO_8859_9: + case C_ISO_8859_10: + case C_ISO_8859_11: + case C_ISO_8859_13: + case C_ISO_8859_14: + case C_ISO_8859_15: + conv_unreadable_latin(str); + break; + case C_EUC_JP: + conv_unreadable_eucjp(str); + break; + default: + break; + } +} + +#define NCV '\0' + +void conv_mb_alnum(gchar *str) +{ + static guchar char_tbl[] = { + /* 0xa0 - 0xaf */ + NCV, ' ', NCV, NCV, ',', '.', NCV, ':', + ';', '?', '!', NCV, NCV, NCV, NCV, NCV, + /* 0xb0 - 0xbf */ + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + /* 0xc0 - 0xcf */ + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, '(', ')', NCV, NCV, '[', ']', + /* 0xd0 - 0xdf */ + '{', '}', NCV, NCV, NCV, NCV, NCV, NCV, + NCV, NCV, NCV, NCV, '+', '-', NCV, NCV, + /* 0xe0 - 0xef */ + NCV, '=', NCV, '<', '>', NCV, NCV, NCV, + NCV, NCV, NCV, NCV, NCV, NCV, NCV, NCV + }; + + register guchar *p = str; + register gint len; + + len = strlen(str); + + while (len > 1) { + if (*p == 0xa3) { + register guchar ch = *(p + 1); + + if (ch >= 0xb0 && ch <= 0xfa) { + /* [a-zA-Z] */ + *p = ch & 0x7f; + p++; + len--; + memmove(p, p + 1, len); + len--; + } else { + p += 2; + len -= 2; + } + } else if (*p == 0xa1) { + register guchar ch = *(p + 1); + + if (ch >= 0xa0 && ch <= 0xef && + NCV != char_tbl[ch - 0xa0]) { + *p = char_tbl[ch - 0xa0]; + p++; + len--; + memmove(p, p + 1, len); + len--; + } else { + p += 2; + len -= 2; + } + } else if (iseuckanji(*p)) { + p += 2; + len -= 2; + } else { + p++; + len--; + } + } +} + +CharSet conv_guess_ja_encoding(const gchar *str) +{ + const guchar *p = str; + CharSet guessed = C_US_ASCII; + + while (*p != '\0') { + if (*p == ESC && (*(p + 1) == '$' || *(p + 1) == '(')) { + if (guessed == C_US_ASCII) + return C_ISO_2022_JP; + p += 2; + } else if (isascii(*p)) { + p++; + } else if (iseuckanji(*p) && iseuckanji(*(p + 1))) { + if (*p >= 0xfd && *p <= 0xfe) + return C_EUC_JP; + else if (guessed == C_SHIFT_JIS) { + if ((issjiskanji1(*p) && + issjiskanji2(*(p + 1))) || + issjishwkana(*p)) + guessed = C_SHIFT_JIS; + else + guessed = C_EUC_JP; + } else + guessed = C_EUC_JP; + p += 2; + } else if (issjiskanji1(*p) && issjiskanji2(*(p + 1))) { + if (iseuchwkana1(*p) && iseuchwkana2(*(p + 1))) + guessed = C_SHIFT_JIS; + else + return C_SHIFT_JIS; + p += 2; + } else if (issjishwkana(*p)) { + guessed = C_SHIFT_JIS; + p++; + } else { + p++; + } + } + + return guessed; +} + +void conv_jistodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + conv_jistoutf8(outbuf, outlen, inbuf); +} + +void conv_sjistodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + conv_sjistoutf8(outbuf, outlen, inbuf); +} + +void conv_euctodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + conv_euctoutf8(outbuf, outlen, inbuf); +} + +void conv_anytodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + conv_anytoutf8(outbuf, outlen, inbuf); +} + +void conv_ustodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + strncpy2(outbuf, inbuf, outlen); + conv_unreadable_8bit(outbuf); +} + +void conv_latintodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + strncpy2(outbuf, inbuf, outlen); + //conv_unreadable_latin(outbuf); +} + +void conv_localetodisp(gchar *outbuf, gint outlen, const gchar *inbuf) +{ +#if HAVE_ICONV + gchar *tmpstr; + + tmpstr = conv_iconv_strdup(inbuf, conv_get_locale_charset_str(), + conv_get_internal_charset_str()); + if (tmpstr) { + strncpy2(outbuf, tmpstr, outlen); + g_free(tmpstr); + } else + strncpy2(outbuf, inbuf, outlen); +#else + strncpy2(outbuf, inbuf, outlen); +#endif /* HAVE_ICONV */ +} + +void conv_noconv(gchar *outbuf, gint outlen, const gchar *inbuf) +{ + strncpy2(outbuf, inbuf, outlen); +} + +CodeConverter *conv_code_converter_new(const gchar *src_charset) +{ + CodeConverter *conv; + + conv = g_new0(CodeConverter, 1); + conv->code_conv_func = conv_get_code_conv_func(src_charset, NULL); + conv->charset_str = g_strdup(src_charset); + conv->charset = conv_get_charset_from_str(src_charset); + + return conv; +} + +void conv_code_converter_destroy(CodeConverter *conv) +{ + g_free(conv->charset_str); + g_free(conv); +} + +gint conv_convert(CodeConverter *conv, gchar *outbuf, gint outlen, + const gchar *inbuf) +{ +#if HAVE_ICONV + if (conv->code_conv_func != conv_noconv) + conv->code_conv_func(outbuf, outlen, inbuf); + else { + gchar *str; + + str = conv_iconv_strdup(inbuf, conv->charset_str, NULL); + if (!str) + return -1; + else { + strncpy2(outbuf, str, outlen); + g_free(str); + } + } +#else /* !HAVE_ICONV */ + conv->code_conv_func(outbuf, outlen, inbuf); +#endif + + return 0; +} + +gchar *conv_codeset_strdup(const gchar *inbuf, + const gchar *src_code, const gchar *dest_code) +{ + gchar *buf; + size_t len; + CodeConvFunc conv_func; + + conv_func = conv_get_code_conv_func(src_code, dest_code); + if (conv_func != conv_noconv) { + len = (strlen(inbuf) + 1) * 3; + buf = g_malloc(len); + if (!buf) return NULL; + + conv_func(buf, len, inbuf); + return g_realloc(buf, strlen(buf) + 1); + } + +#if HAVE_ICONV + return conv_iconv_strdup(inbuf, src_code, dest_code); +#else + return g_strdup(inbuf); +#endif /* HAVE_ICONV */ +} + +CodeConvFunc conv_get_code_conv_func(const gchar *src_charset_str, + const gchar *dest_charset_str) +{ + CodeConvFunc code_conv = conv_noconv; + CharSet src_charset; + CharSet dest_charset; + + if (!src_charset_str) + src_charset = conv_get_locale_charset(); + else + src_charset = conv_get_charset_from_str(src_charset_str); + + /* auto detection mode */ + if (!src_charset_str && !dest_charset_str) { + //if (src_charset == C_EUC_JP || src_charset == C_SHIFT_JIS) + // return conv_anytodisp; + //else + return conv_noconv; + } + + dest_charset = conv_get_charset_from_str(dest_charset_str); + + if (dest_charset == C_US_ASCII) + return conv_ustodisp; + + switch (src_charset) { + case C_ISO_2022_JP: + case C_ISO_2022_JP_2: + case C_ISO_2022_JP_3: + if (dest_charset == C_AUTO) + code_conv = conv_jistodisp; + else if (dest_charset == C_EUC_JP) + code_conv = conv_jistoeuc; + else if (dest_charset == C_UTF_8) + code_conv = conv_jistoutf8; + break; + case C_US_ASCII: + if (dest_charset == C_AUTO) + code_conv = conv_ustodisp; + break; + case C_ISO_8859_1: + case C_ISO_8859_2: + case C_ISO_8859_3: + case C_ISO_8859_4: + case C_ISO_8859_5: + case C_ISO_8859_6: + case C_ISO_8859_7: + case C_ISO_8859_8: + case C_ISO_8859_9: + case C_ISO_8859_10: + case C_ISO_8859_11: + case C_ISO_8859_13: + case C_ISO_8859_14: + case C_ISO_8859_15: + break; + case C_SHIFT_JIS: + if (dest_charset == C_AUTO) + code_conv = conv_sjistodisp; + else if (dest_charset == C_EUC_JP) + code_conv = conv_sjistoeuc; + else if (dest_charset == C_UTF_8) + code_conv = conv_sjistoutf8; + break; + case C_EUC_JP: + if (dest_charset == C_AUTO) + code_conv = conv_euctodisp; + else if (dest_charset == C_ISO_2022_JP || + dest_charset == C_ISO_2022_JP_2 || + dest_charset == C_ISO_2022_JP_3) + code_conv = conv_euctojis; + else if (dest_charset == C_UTF_8) + code_conv = conv_euctoutf8; + break; + default: + break; + } + + return code_conv; +} + +#if HAVE_ICONV +gchar *conv_iconv_strdup(const gchar *inbuf, + const gchar *src_code, const gchar *dest_code) +{ + iconv_t cd; + const gchar *inbuf_p; + gchar *outbuf; + gchar *outbuf_p; + size_t in_size; + size_t in_left; + size_t out_size; + size_t out_left; + size_t n_conv; + size_t len; + + if (!src_code) + src_code = conv_get_outgoing_charset_str(); + if (!dest_code) + dest_code = conv_get_locale_charset_str(); + + /* don't convert if current codeset is US-ASCII */ + if (!strcasecmp(dest_code, CS_US_ASCII)) + return g_strdup(inbuf); + + /* don't convert if src and dest codeset are identical */ + if (!strcasecmp(src_code, dest_code)) + return g_strdup(inbuf); + + cd = iconv_open(dest_code, src_code); + if (cd == (iconv_t)-1) + return NULL; + + inbuf_p = inbuf; + in_size = strlen(inbuf); + in_left = in_size; + out_size = (in_size + 1) * 2; + outbuf = g_malloc(out_size); + outbuf_p = outbuf; + out_left = out_size; + +#define EXPAND_BUF() \ +{ \ + len = outbuf_p - outbuf; \ + out_size *= 2; \ + outbuf = g_realloc(outbuf, out_size); \ + outbuf_p = outbuf + len; \ + out_left = out_size - len; \ +} + + while ((n_conv = iconv(cd, (ICONV_CONST gchar **)&inbuf_p, &in_left, + &outbuf_p, &out_left)) == (size_t)-1) { + if (EILSEQ == errno) { + inbuf_p++; + in_left--; + if (out_left == 0) { + EXPAND_BUF(); + } + *outbuf_p++ = SUBST_CHAR; + out_left--; + } else if (EINVAL == errno) { + break; + } else if (E2BIG == errno) { + EXPAND_BUF(); + } else { + g_warning("conv_iconv_strdup(): %s\n", + g_strerror(errno)); + break; + } + } + + while ((n_conv = iconv(cd, NULL, NULL, &outbuf_p, &out_left)) == + (size_t)-1) { + if (E2BIG == errno) { + EXPAND_BUF(); + } else { + g_warning("conv_iconv_strdup(): %s\n", + g_strerror(errno)); + break; + } + } + +#undef EXPAND_BUF + + len = outbuf_p - outbuf; + outbuf = g_realloc(outbuf, len + 1); + outbuf[len] = '\0'; + + iconv_close(cd); + + return outbuf; +} +#endif /* HAVE_ICONV */ + +static const struct { + CharSet charset; + gchar *const name; +} charsets[] = { + {C_US_ASCII, CS_US_ASCII}, + {C_US_ASCII, CS_ANSI_X3_4_1968}, + {C_UTF_8, CS_UTF_8}, + {C_UTF_7, CS_UTF_7}, + {C_ISO_8859_1, CS_ISO_8859_1}, + {C_ISO_8859_2, CS_ISO_8859_2}, + {C_ISO_8859_3, CS_ISO_8859_3}, + {C_ISO_8859_4, CS_ISO_8859_4}, + {C_ISO_8859_5, CS_ISO_8859_5}, + {C_ISO_8859_6, CS_ISO_8859_6}, + {C_ISO_8859_7, CS_ISO_8859_7}, + {C_ISO_8859_8, CS_ISO_8859_8}, + {C_ISO_8859_9, CS_ISO_8859_9}, + {C_ISO_8859_10, CS_ISO_8859_10}, + {C_ISO_8859_11, CS_ISO_8859_11}, + {C_ISO_8859_13, CS_ISO_8859_13}, + {C_ISO_8859_14, CS_ISO_8859_14}, + {C_ISO_8859_15, CS_ISO_8859_15}, + {C_BALTIC, CS_BALTIC}, + {C_CP1250, CS_CP1250}, + {C_CP1251, CS_CP1251}, + {C_CP1252, CS_CP1252}, + {C_CP1253, CS_CP1253}, + {C_CP1254, CS_CP1254}, + {C_CP1255, CS_CP1255}, + {C_CP1256, CS_CP1256}, + {C_CP1257, CS_CP1257}, + {C_CP1258, CS_CP1258}, + {C_WINDOWS_1250, CS_WINDOWS_1250}, + {C_WINDOWS_1251, CS_WINDOWS_1251}, + {C_WINDOWS_1252, CS_WINDOWS_1252}, + {C_WINDOWS_1253, CS_WINDOWS_1253}, + {C_WINDOWS_1254, CS_WINDOWS_1254}, + {C_WINDOWS_1255, CS_WINDOWS_1255}, + {C_WINDOWS_1256, CS_WINDOWS_1256}, + {C_WINDOWS_1257, CS_WINDOWS_1257}, + {C_WINDOWS_1258, CS_WINDOWS_1258}, + {C_KOI8_R, CS_KOI8_R}, + {C_KOI8_T, CS_KOI8_T}, + {C_KOI8_U, CS_KOI8_U}, + {C_ISO_2022_JP, CS_ISO_2022_JP}, + {C_ISO_2022_JP_2, CS_ISO_2022_JP_2}, + {C_ISO_2022_JP_3, CS_ISO_2022_JP_3}, + {C_EUC_JP, CS_EUC_JP}, + {C_EUC_JP, CS_EUCJP}, + {C_SHIFT_JIS, CS_SHIFT_JIS}, + {C_SHIFT_JIS, CS_SHIFT__JIS}, + {C_SHIFT_JIS, CS_SJIS}, + {C_ISO_2022_KR, CS_ISO_2022_KR}, + {C_EUC_KR, CS_EUC_KR}, + {C_ISO_2022_CN, CS_ISO_2022_CN}, + {C_EUC_CN, CS_EUC_CN}, + {C_GB2312, CS_GB2312}, + {C_GBK, CS_GBK}, + {C_EUC_TW, CS_EUC_TW}, + {C_BIG5, CS_BIG5}, + {C_BIG5_HKSCS, CS_BIG5_HKSCS}, + {C_TIS_620, CS_TIS_620}, + {C_WINDOWS_874, CS_WINDOWS_874}, + {C_GEORGIAN_PS, CS_GEORGIAN_PS}, + {C_TCVN5712_1, CS_TCVN5712_1}, +}; + +static const struct { + gchar *const locale; + CharSet charset; + CharSet out_charset; +} locale_table[] = { + {"ja_JP.eucJP" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.EUC-JP" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.EUC" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.ujis" , C_EUC_JP , C_ISO_2022_JP}, + {"ja_JP.SJIS" , C_SHIFT_JIS , C_ISO_2022_JP}, + {"ja_JP.JIS" , C_ISO_2022_JP , C_ISO_2022_JP}, + {"ja_JP" , C_EUC_JP , C_ISO_2022_JP}, + {"ko_KR.EUC-KR" , C_EUC_KR , C_EUC_KR}, + {"ko_KR" , C_EUC_KR , C_EUC_KR}, + {"zh_CN.GB2312" , C_GB2312 , C_GB2312}, + {"zh_CN.GBK" , C_GBK , C_GB2312}, + {"zh_CN" , C_GB2312 , C_GB2312}, + {"zh_HK" , C_BIG5_HKSCS , C_BIG5_HKSCS}, + {"zh_TW.eucTW" , C_EUC_TW , C_BIG5}, + {"zh_TW.EUC-TW" , C_EUC_TW , C_BIG5}, + {"zh_TW.Big5" , C_BIG5 , C_BIG5}, + {"zh_TW" , C_BIG5 , C_BIG5}, + + {"ru_RU.KOI8-R" , C_KOI8_R , C_KOI8_R}, + {"ru_RU.KOI8R" , C_KOI8_R , C_KOI8_R}, + {"ru_RU.CP1251" , C_WINDOWS_1251, C_KOI8_R}, + {"ru_RU" , C_ISO_8859_5 , C_KOI8_R}, + {"tg_TJ" , C_KOI8_T , C_KOI8_T}, + {"ru_UA" , C_KOI8_U , C_KOI8_U}, + {"uk_UA.CP1251" , C_WINDOWS_1251, C_KOI8_U}, + {"uk_UA" , C_KOI8_U , C_KOI8_U}, + + {"be_BY" , C_WINDOWS_1251, C_WINDOWS_1251}, + {"bg_BG" , C_WINDOWS_1251, C_WINDOWS_1251}, + + {"yi_US" , C_WINDOWS_1255, C_WINDOWS_1255}, + + {"af_ZA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"br_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ca_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"da_DK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_AT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_DE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"de_LU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_AU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_BW" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_CA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_DK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_HK" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_IE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_NZ" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_PH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_SG" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_US" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_ZA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"en_ZW" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_AR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_BO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_CR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_DO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_EC" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_GT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_HN" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_MX" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_NI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_PY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_SV" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_US" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_UY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"es_VE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"et_EE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"eu_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fi_FI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fo_FO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_CA" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"fr_LU" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ga_IE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"gl_ES" , C_ISO_8859_1 , C_ISO_8859_1}, + {"gv_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"id_ID" , C_ISO_8859_1 , C_ISO_8859_1}, + {"is_IS" , C_ISO_8859_1 , C_ISO_8859_1}, + {"it_CH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"it_IT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"kl_GL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"kw_GB" , C_ISO_8859_1 , C_ISO_8859_1}, + {"ms_MY" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nl_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nl_NL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"nn_NO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"no_NO" , C_ISO_8859_1 , C_ISO_8859_1}, + {"oc_FR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"pt_BR" , C_ISO_8859_1 , C_ISO_8859_1}, + {"pt_PT" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sq_AL" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sv_FI" , C_ISO_8859_1 , C_ISO_8859_1}, + {"sv_SE" , C_ISO_8859_1 , C_ISO_8859_1}, + {"tl_PH" , C_ISO_8859_1 , C_ISO_8859_1}, + {"uz_UZ" , C_ISO_8859_1 , C_ISO_8859_1}, + {"wa_BE" , C_ISO_8859_1 , C_ISO_8859_1}, + + {"bs_BA" , C_ISO_8859_2 , C_ISO_8859_2}, + {"cs_CZ" , C_ISO_8859_2 , C_ISO_8859_2}, + {"hr_HR" , C_ISO_8859_2 , C_ISO_8859_2}, + {"hu_HU" , C_ISO_8859_2 , C_ISO_8859_2}, + {"pl_PL" , C_ISO_8859_2 , C_ISO_8859_2}, + {"ro_RO" , C_ISO_8859_2 , C_ISO_8859_2}, + {"sk_SK" , C_ISO_8859_2 , C_ISO_8859_2}, + {"sl_SI" , C_ISO_8859_2 , C_ISO_8859_2}, + + {"sr_YU@cyrillic" , C_ISO_8859_5 , C_ISO_8859_5}, + {"sr_YU" , C_ISO_8859_2 , C_ISO_8859_2}, + + {"mt_MT" , C_ISO_8859_3 , C_ISO_8859_3}, + + {"lt_LT.iso88594" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT.ISO8859-4" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT.ISO_8859-4" , C_ISO_8859_4 , C_ISO_8859_4}, + {"lt_LT" , C_ISO_8859_13 , C_ISO_8859_13}, + + {"mk_MK" , C_ISO_8859_5 , C_ISO_8859_5}, + + {"ar_AE" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_BH" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_DZ" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_EG" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_IQ" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_JO" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_KW" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_LB" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_LY" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_MA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_OM" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_QA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SA" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SD" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_SY" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_TN" , C_ISO_8859_6 , C_ISO_8859_6}, + {"ar_YE" , C_ISO_8859_6 , C_ISO_8859_6}, + + {"el_GR" , C_ISO_8859_7 , C_ISO_8859_7}, + {"he_IL" , C_ISO_8859_8 , C_ISO_8859_8}, + {"iw_IL" , C_ISO_8859_8 , C_ISO_8859_8}, + {"tr_TR" , C_ISO_8859_9 , C_ISO_8859_9}, + + {"lv_LV" , C_ISO_8859_13 , C_ISO_8859_13}, + {"mi_NZ" , C_ISO_8859_13 , C_ISO_8859_13}, + + {"cy_GB" , C_ISO_8859_14 , C_ISO_8859_14}, + + {"ar_IN" , C_UTF_8 , C_UTF_8}, + {"en_IN" , C_UTF_8 , C_UTF_8}, + {"se_NO" , C_UTF_8 , C_UTF_8}, + {"ta_IN" , C_UTF_8 , C_UTF_8}, + {"te_IN" , C_UTF_8 , C_UTF_8}, + {"ur_PK" , C_UTF_8 , C_UTF_8}, + + {"th_TH" , C_TIS_620 , C_TIS_620}, + /* {"th_TH" , C_WINDOWS_874}, */ + /* {"th_TH" , C_ISO_8859_11}, */ + + {"ka_GE" , C_GEORGIAN_PS , C_GEORGIAN_PS}, + {"vi_VN.TCVN" , C_TCVN5712_1 , C_TCVN5712_1}, + + {"C" , C_US_ASCII , C_US_ASCII}, + {"POSIX" , C_US_ASCII , C_US_ASCII}, + {"ANSI_X3.4-1968" , C_US_ASCII , C_US_ASCII}, +}; + +static GHashTable *conv_get_charset_to_str_table(void) +{ + static GHashTable *table; + gint i; + + if (table) + return table; + + table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) { + if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset)) + == NULL) { + g_hash_table_insert + (table, GUINT_TO_POINTER(charsets[i].charset), + charsets[i].name); + } + } + + return table; +} + +static GHashTable *conv_get_charset_from_str_table(void) +{ + static GHashTable *table; + gint i; + + if (table) + return table; + + table = g_hash_table_new(str_case_hash, str_case_equal); + + for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) { + g_hash_table_insert(table, charsets[i].name, + GUINT_TO_POINTER(charsets[i].charset)); + } + + return table; +} + +const gchar *conv_get_charset_str(CharSet charset) +{ + GHashTable *table; + + table = conv_get_charset_to_str_table(); + return g_hash_table_lookup(table, GUINT_TO_POINTER(charset)); +} + +CharSet conv_get_charset_from_str(const gchar *charset) +{ + GHashTable *table; + + if (!charset) return C_AUTO; + + table = conv_get_charset_from_str_table(); + return GPOINTER_TO_UINT(g_hash_table_lookup(table, charset)); +} + +CharSet conv_get_locale_charset(void) +{ + static CharSet cur_charset = -1; + const gchar *cur_locale; + const gchar *p; + gint i; + + if (cur_charset != -1) + return cur_charset; + + cur_locale = conv_get_current_locale(); + if (!cur_locale) { + cur_charset = C_US_ASCII; + return cur_charset; + } + + if (strcasestr(cur_locale, "UTF-8")) { + cur_charset = C_UTF_8; + return cur_charset; + } + + if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') { + cur_charset = C_ISO_8859_15; + return cur_charset; + } + + for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) { + const gchar *p; + + /* "ja_JP.EUC" matches with "ja_JP.eucJP", "ja_JP.EUC" and + "ja_JP". "ja_JP" matches with "ja_JP.xxxx" and "ja" */ + if (!strncasecmp(cur_locale, locale_table[i].locale, + strlen(locale_table[i].locale))) { + cur_charset = locale_table[i].charset; + return cur_charset; + } else if ((p = strchr(locale_table[i].locale, '_')) && + !strchr(p + 1, '.')) { + if (strlen(cur_locale) == 2 && + !strncasecmp(cur_locale, locale_table[i].locale, 2)) { + cur_charset = locale_table[i].charset; + return cur_charset; + } + } + } + + cur_charset = C_AUTO; + return cur_charset; +} + +const gchar *conv_get_locale_charset_str(void) +{ + static const gchar *codeset = NULL; + + if (!codeset) + codeset = conv_get_charset_str(conv_get_locale_charset()); + + return codeset ? codeset : CS_UTF_8; +} + +CharSet conv_get_internal_charset(void) +{ + return C_UTF_8; +} + +const gchar *conv_get_internal_charset_str(void) +{ + return CS_UTF_8; +} + +CharSet conv_get_outgoing_charset(void) +{ + static CharSet out_charset = -1; + const gchar *cur_locale; + const gchar *p; + gint i; + + if (out_charset != -1) + return out_charset; + + cur_locale = conv_get_current_locale(); + if (!cur_locale) { + out_charset = C_AUTO; + return out_charset; + } + + if ((p = strcasestr(cur_locale, "@euro")) && p[5] == '\0') { + out_charset = C_ISO_8859_15; + return out_charset; + } + + for (i = 0; i < sizeof(locale_table) / sizeof(locale_table[0]); i++) { + const gchar *p; + + if (!strncasecmp(cur_locale, locale_table[i].locale, + strlen(locale_table[i].locale))) { + out_charset = locale_table[i].out_charset; + break; + } else if ((p = strchr(locale_table[i].locale, '_')) && + !strchr(p + 1, '.')) { + if (strlen(cur_locale) == 2 && + !strncasecmp(cur_locale, locale_table[i].locale, 2)) { + out_charset = locale_table[i].out_charset; + break; + } + } + } + +#if !HAVE_ICONV + /* encoding conversion without iconv() is only supported + on Japanese locale for now */ + if (out_charset == C_ISO_2022_JP) + return out_charset; + else + return conv_get_locale_charset(); +#endif + + return out_charset; +} + +const gchar *conv_get_outgoing_charset_str(void) +{ + CharSet out_charset; + const gchar *str; + + if (prefs_common.outgoing_charset) { + if (!isalpha((guchar)prefs_common.outgoing_charset[0])) { + g_free(prefs_common.outgoing_charset); + prefs_common.outgoing_charset = g_strdup(CS_AUTO); + } else if (strcmp(prefs_common.outgoing_charset, CS_AUTO) != 0) + return prefs_common.outgoing_charset; + } + + out_charset = conv_get_outgoing_charset(); + str = conv_get_charset_str(out_charset); + + return str ? str : CS_UTF_8; +} + +gboolean conv_is_multibyte_encoding(CharSet encoding) +{ + switch (encoding) { + case C_EUC_JP: + case C_EUC_KR: + case C_EUC_TW: + case C_EUC_CN: + case C_ISO_2022_JP: + case C_ISO_2022_JP_2: + case C_ISO_2022_JP_3: + case C_ISO_2022_KR: + case C_ISO_2022_CN: + case C_SHIFT_JIS: + case C_GB2312: + case C_BIG5: + case C_UTF_8: + case C_UTF_7: + return TRUE; + default: + return FALSE; + } +} + +const gchar *conv_get_current_locale(void) +{ + const gchar *cur_locale; + + cur_locale = g_getenv("LC_ALL"); + if (!cur_locale) cur_locale = g_getenv("LC_CTYPE"); + if (!cur_locale) cur_locale = g_getenv("LANG"); + if (!cur_locale) cur_locale = setlocale(LC_CTYPE, NULL); + + debug_print("current locale: %s\n", + cur_locale ? cur_locale : "(none)"); + + return cur_locale; +} + +void conv_unmime_header_overwrite(gchar *str) +{ + gchar *buf; + gint buflen; + CharSet cur_charset; + + cur_charset = conv_get_locale_charset(); + + if (cur_charset == C_EUC_JP) { + buflen = strlen(str) * 2 + 1; + Xalloca(buf, buflen, return); + conv_anytodisp(buf, buflen, str); + unmime_header(str, buf); + } else { + buflen = strlen(str) + 1; + Xalloca(buf, buflen, return); + unmime_header(buf, str); + strncpy2(str, buf, buflen); + } +} + +void conv_unmime_header(gchar *outbuf, gint outlen, const gchar *str, + const gchar *charset) +{ + CharSet cur_charset; + + cur_charset = conv_get_locale_charset(); + + if (cur_charset == C_EUC_JP) { + gchar *buf; + gint buflen; + + buflen = strlen(str) * 2 + 1; + Xalloca(buf, buflen, return); + conv_anytodisp(buf, buflen, str); + unmime_header(outbuf, buf); + } else + unmime_header(outbuf, str); +} + +#define MAX_LINELEN 76 +#define MAX_HARD_LINELEN 996 +#define MIMESEP_BEGIN "=?" +#define MIMESEP_END "?=" + +#define B64LEN(len) ((len) / 3 * 4 + ((len) % 3 ? 4 : 0)) + +#define LBREAK_IF_REQUIRED(cond, is_plain_text) \ +{ \ + if (len - (destp - (guchar *)dest) < MAX_LINELEN + 2) { \ + *destp = '\0'; \ + return; \ + } \ + \ + if ((cond) && *srcp) { \ + if (destp > (guchar *)dest && left < MAX_LINELEN - 1) { \ + if (isspace(*(destp - 1))) \ + destp--; \ + else if (is_plain_text && isspace(*srcp)) \ + srcp++; \ + if (*srcp) { \ + *destp++ = '\n'; \ + *destp++ = ' '; \ + left = MAX_LINELEN - 1; \ + } \ + } \ + } \ +} + +void conv_encode_header(gchar *dest, gint len, const gchar *src, + gint header_len, gboolean addr_field) +{ + const gchar *cur_encoding; + const gchar *out_encoding; + gint mimestr_len; + gchar *mimesep_enc; + gint left; + const guchar *srcp = src; + guchar *destp = dest; + gboolean use_base64; + + if (MB_CUR_MAX > 1) { + use_base64 = TRUE; + mimesep_enc = "?B?"; + } else { + use_base64 = FALSE; + mimesep_enc = "?Q?"; + } + + cur_encoding = conv_get_locale_charset_str(); + if (!strcmp(cur_encoding, CS_US_ASCII)) + cur_encoding = CS_ISO_8859_1; + out_encoding = conv_get_outgoing_charset_str(); + if (!strcmp(out_encoding, CS_US_ASCII)) + out_encoding = CS_ISO_8859_1; + + mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) + + strlen(mimesep_enc) + strlen(MIMESEP_END); + + left = MAX_LINELEN - header_len; + + while (*srcp) { + LBREAK_IF_REQUIRED(left <= 0, TRUE); + + while (isspace(*srcp)) { + *destp++ = *srcp++; + left--; + LBREAK_IF_REQUIRED(left <= 0, TRUE); + } + + /* output as it is if the next word is ASCII string */ + if (!is_next_nonascii(srcp)) { + gint word_len; + + word_len = get_next_word_len(srcp); + LBREAK_IF_REQUIRED(left < word_len, TRUE); + while (word_len > 0) { + LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE) + *destp++ = *srcp++; + left--; + word_len--; + } + + continue; + } + + /* don't include parentheses in encoded strings */ + if (addr_field && (*srcp == '(' || *srcp == ')')) { + LBREAK_IF_REQUIRED(left < 2, FALSE); + *destp++ = *srcp++; + left--; + } + + while (1) { + gint mb_len = 0; + gint cur_len = 0; + gchar *part_str; + gchar *out_str; + gchar *enc_str; + const guchar *p = srcp; + gint out_str_len; + gint out_enc_str_len; + gint mime_block_len; + gboolean cont = FALSE; + + while (*p != '\0') { + if (isspace(*p) && !is_next_nonascii(p + 1)) + break; + /* don't include parentheses in encoded + strings */ + if (addr_field && (*p == '(' || *p == ')')) + break; + + if (MB_CUR_MAX > 1) { + mb_len = mblen(p, MB_CUR_MAX); + if (mb_len < 0) { + g_warning("conv_encode_header(): invalid multibyte character encountered\n"); + mb_len = 1; + } + } else + mb_len = 1; + + Xstrndup_a(part_str, srcp, cur_len + mb_len, ); + out_str = conv_codeset_strdup + (part_str, cur_encoding, out_encoding); + if (!out_str) { + g_warning("conv_encode_header(): code conversion failed\n"); + conv_unreadable_8bit(part_str); + out_str = g_strdup(part_str); + } + out_str_len = strlen(out_str); + + if (use_base64) + out_enc_str_len = B64LEN(out_str_len); + else + out_enc_str_len = + qp_get_q_encoding_len(out_str); + + g_free(out_str); + + if (mimestr_len + out_enc_str_len <= left) { + cur_len += mb_len; + p += mb_len; + } else if (cur_len == 0) { + LBREAK_IF_REQUIRED(1, FALSE); + continue; + } else { + cont = TRUE; + break; + } + } + + if (cur_len > 0) { + Xstrndup_a(part_str, srcp, cur_len, ); + out_str = conv_codeset_strdup + (part_str, cur_encoding, out_encoding); + if (!out_str) { + g_warning("conv_encode_header(): code conversion failed\n"); + conv_unreadable_8bit(part_str); + out_str = g_strdup(part_str); + } + out_str_len = strlen(out_str); + + if (use_base64) + out_enc_str_len = B64LEN(out_str_len); + else + out_enc_str_len = + qp_get_q_encoding_len(out_str); + + Xalloca(enc_str, out_enc_str_len + 1, ); + if (use_base64) + base64_encode(enc_str, out_str, out_str_len); + else + qp_q_encode(enc_str, out_str); + + g_free(out_str); + + /* output MIME-encoded string block */ + mime_block_len = mimestr_len + strlen(enc_str); + g_snprintf(destp, mime_block_len + 1, + MIMESEP_BEGIN "%s%s%s" MIMESEP_END, + out_encoding, mimesep_enc, enc_str); + destp += mime_block_len; + srcp += cur_len; + + left -= mime_block_len; + } + + LBREAK_IF_REQUIRED(cont, FALSE); + + if (cur_len == 0) + break; + } + } + + *destp = '\0'; +} + +#undef LBREAK_IF_REQUIRED diff --git a/src/codeconv.h b/src/codeconv.h new file mode 100644 index 00000000..21539fd6 --- /dev/null +++ b/src/codeconv.h @@ -0,0 +1,238 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __CODECONV_H__ +#define __CODECONV_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +typedef struct _CodeConverter CodeConverter; + +typedef enum +{ + C_AUTO, + C_US_ASCII, + C_UTF_8, + C_UTF_7, + C_ISO_8859_1, + C_ISO_8859_2, + C_ISO_8859_3, + C_ISO_8859_4, + C_ISO_8859_5, + C_ISO_8859_6, + C_ISO_8859_7, + C_ISO_8859_8, + C_ISO_8859_9, + C_ISO_8859_10, + C_ISO_8859_11, + C_ISO_8859_13, + C_ISO_8859_14, + C_ISO_8859_15, + C_BALTIC, + C_CP1250, + C_CP1251, + C_CP1252, + C_CP1253, + C_CP1254, + C_CP1255, + C_CP1256, + C_CP1257, + C_CP1258, + C_WINDOWS_1250, + C_WINDOWS_1251, + C_WINDOWS_1252, + C_WINDOWS_1253, + C_WINDOWS_1254, + C_WINDOWS_1255, + C_WINDOWS_1256, + C_WINDOWS_1257, + C_WINDOWS_1258, + C_KOI8_R, + C_KOI8_T, + C_KOI8_U, + C_ISO_2022_JP, + C_ISO_2022_JP_2, + C_ISO_2022_JP_3, + C_EUC_JP, + C_SHIFT_JIS, + C_ISO_2022_KR, + C_EUC_KR, + C_ISO_2022_CN, + C_EUC_CN, + C_GB2312, + C_GBK, + C_EUC_TW, + C_BIG5, + C_BIG5_HKSCS, + C_TIS_620, + C_WINDOWS_874, + C_GEORGIAN_PS, + C_TCVN5712_1 +} CharSet; + +typedef void (*CodeConvFunc) (gchar *outbuf, gint outlen, const gchar *inbuf); + +struct _CodeConverter +{ + CodeConvFunc code_conv_func; + gchar *charset_str; + CharSet charset; +}; + +#define CS_AUTO "AUTO" +#define CS_US_ASCII "US-ASCII" +#define CS_ANSI_X3_4_1968 "ANSI_X3.4-1968" +#define CS_UTF_8 "UTF-8" +#define CS_UTF_7 "UTF-7" +#define CS_ISO_8859_1 "ISO-8859-1" +#define CS_ISO_8859_2 "ISO-8859-2" +#define CS_ISO_8859_3 "ISO-8859-3" +#define CS_ISO_8859_4 "ISO-8859-4" +#define CS_ISO_8859_5 "ISO-8859-5" +#define CS_ISO_8859_6 "ISO-8859-6" +#define CS_ISO_8859_7 "ISO-8859-7" +#define CS_ISO_8859_8 "ISO-8859-8" +#define CS_ISO_8859_9 "ISO-8859-9" +#define CS_ISO_8859_10 "ISO-8859-10" +#define CS_ISO_8859_11 "ISO-8859-11" +#define CS_ISO_8859_13 "ISO-8859-13" +#define CS_ISO_8859_14 "ISO-8859-14" +#define CS_ISO_8859_15 "ISO-8859-15" +#define CS_BALTIC "BALTIC" +#define CS_CP1250 "CP1250" +#define CS_CP1251 "CP1251" +#define CS_CP1252 "CP1252" +#define CS_CP1253 "CP1253" +#define CS_CP1254 "CP1254" +#define CS_CP1255 "CP1255" +#define CS_CP1256 "CP1256" +#define CS_CP1257 "CP1257" +#define CS_CP1258 "CP1258" +#define CS_WINDOWS_1250 "Windows-1250" +#define CS_WINDOWS_1251 "Windows-1251" +#define CS_WINDOWS_1252 "Windows-1252" +#define CS_WINDOWS_1253 "Windows-1253" +#define CS_WINDOWS_1254 "Windows-1254" +#define CS_WINDOWS_1255 "Windows-1255" +#define CS_WINDOWS_1256 "Windows-1256" +#define CS_WINDOWS_1257 "Windows-1257" +#define CS_WINDOWS_1258 "Windows-1258" +#define CS_KOI8_R "KOI8-R" +#define CS_KOI8_T "KOI8-T" +#define CS_KOI8_U "KOI8-U" +#define CS_ISO_2022_JP "ISO-2022-JP" +#define CS_ISO_2022_JP_2 "ISO-2022-JP-2" +#define CS_ISO_2022_JP_3 "ISO-2022-JP-3" +#define CS_EUC_JP "EUC-JP" +#define CS_EUCJP "EUCJP" +#define CS_SHIFT_JIS "Shift_JIS" +#define CS_SHIFT__JIS "SHIFT-JIS" +#define CS_SJIS "SJIS" +#define CS_X_SJIS "X-SJIS" +#define CS_ISO_2022_KR "ISO-2022-KR" +#define CS_EUC_KR "EUC-KR" +#define CS_ISO_2022_CN "ISO-2022-CN" +#define CS_EUC_CN "EUC-CN" +#define CS_GB2312 "GB2312" +#define CS_GBK "GBK" +#define CS_EUC_TW "EUC-TW" +#define CS_BIG5 "Big5" +#define CS_BIG5_HKSCS "BIG5-HKSCS" +#define CS_TIS_620 "TIS-620" +#define CS_WINDOWS_874 "Windows-874" +#define CS_GEORGIAN_PS "GEORGIAN-PS" +#define CS_TCVN5712_1 "TCVN5712-1" + +void conv_jistoeuc (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_euctojis (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_sjistoeuc (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_anytoeuc (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_anytojis (gchar *outbuf, gint outlen, const gchar *inbuf); + +void conv_jistoutf8 (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_sjistoutf8 (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_euctoutf8 (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_anytoutf8 (gchar *outbuf, gint outlen, const gchar *inbuf); + +void conv_unreadable_eucjp (gchar *str); +void conv_unreadable_8bit (gchar *str); +void conv_unreadable_latin (gchar *str); +void conv_unreadable_locale (gchar *str); + +//void conv_mb_alnum(gchar *str); + +CharSet conv_guess_ja_encoding(const gchar *str); + +void conv_jistodisp (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_sjistodisp (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_euctodisp (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_ustodisp (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_latintodisp (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_noconv (gchar *outbuf, gint outlen, const gchar *inbuf); +void conv_localetodisp (gchar *outbuf, gint outlen, const gchar *inbuf); + +CodeConverter *conv_code_converter_new (const gchar *src_charset); +void conv_code_converter_destroy (CodeConverter *conv); +gint conv_convert (CodeConverter *conv, + gchar *outbuf, + gint outlen, + const gchar *inbuf); + +gchar *conv_codeset_strdup (const gchar *inbuf, + const gchar *src_code, + const gchar *dest_code); + +CodeConvFunc conv_get_code_conv_func (const gchar *src_charset_str, + const gchar *dest_charset_str); + +#if HAVE_ICONV +gchar *conv_iconv_strdup (const gchar *inbuf, + const gchar *src_code, + const gchar *dest_code); +#endif + +const gchar *conv_get_charset_str (CharSet charset); +CharSet conv_get_charset_from_str (const gchar *charset); +CharSet conv_get_locale_charset (void); +const gchar *conv_get_locale_charset_str (void); +CharSet conv_get_internal_charset (void); +const gchar *conv_get_internal_charset_str (void); +CharSet conv_get_outgoing_charset (void); +const gchar *conv_get_outgoing_charset_str (void); +gboolean conv_is_multibyte_encoding (CharSet encoding); + +const gchar *conv_get_current_locale (void); + +void conv_unmime_header_overwrite (gchar *str); +void conv_unmime_header (gchar *outbuf, + gint outlen, + const gchar *str, + const gchar *charset); +void conv_encode_header (gchar *dest, + gint len, + const gchar *src, + gint header_len, + gboolean addr_field); + + +#endif /* __CODECONV_H__ */ diff --git a/src/colorlabel.c b/src/colorlabel.c new file mode 100644 index 00000000..f99a9317 --- /dev/null +++ b/src/colorlabel.c @@ -0,0 +1,338 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001-2004 Hiroyuki Yamamoto & The Sylpheed Claws Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* (alfons) - based on a contribution by Satoshi Nagayasu; revised for colorful + * menu and more Sylpheed integration. The idea to put the code in a separate + * file is just that it make it easier to allow "user changeable" label colors. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "colorlabel.h" +#include "gtkutils.h" +#include "utils.h" + +static gchar *labels[] = { + N_("Orange"), + N_("Red") , + N_("Pink"), + N_("Sky blue"), + N_("Blue"), + N_("Green"), + N_("Brown") +}; + +typedef enum LabelColorChangeFlags_ { + LCCF_COLOR = 1 << 0, + LCCF_LABEL = 1 << 1, + LCCF_ALL = LCCF_COLOR | LCCF_LABEL +} LabelColorChangeFlags; + +/* XXX: if you add colors, make sure you also check the procmsg.h. + * color indices are stored as 3 bits; that explains the max. of 7 colors */ +static struct +{ + LabelColorChangeFlags changed; + GdkColor color; + + /* XXX: note that the label member is supposed to be dynamically + * allocated and fffreed */ + gchar *label; + GtkWidget *widget; +} label_colors[] = { + { LCCF_ALL, { 0, 0xffff, (0x99 << 8), 0x0 }, NULL, NULL }, + { LCCF_ALL, { 0, 0xffff, 0, 0 }, NULL, NULL }, + { LCCF_ALL, { 0, 0xffff, (0x66 << 8), 0xffff }, NULL, NULL }, + { LCCF_ALL, { 0, 0x0, (0xcc << 8), 0xffff }, NULL, NULL }, + { LCCF_ALL, { 0, 0x0, 0x0, 0xffff }, NULL, NULL }, + { LCCF_ALL, { 0, 0x0, 0x99 << 8, 0x0 }, NULL, NULL }, + { LCCF_ALL, { 0, 0x66 << 8, 0x33 << 8, 0x33 << 8 }, NULL, NULL } +}; + +#define LABEL_COLOR_WIDTH 28 +#define LABEL_COLOR_HEIGHT 16 + +#define LABEL_COLORS_ELEMS (sizeof label_colors / sizeof label_colors[0]) + +#define G_RETURN_VAL_IF_INVALID_COLOR(color, val) \ + g_return_val_if_fail((color) >= 0 && (color) < LABEL_COLORS_ELEMS, (val)) + +static void colorlabel_recreate (gint); +static void colorlabel_recreate_label (gint); + +gint colorlabel_get_color_count(void) +{ + return LABEL_COLORS_ELEMS; +} + +GdkColor colorlabel_get_color(gint color_index) +{ + GdkColor invalid = { 0 }; + + G_RETURN_VAL_IF_INVALID_COLOR(color_index, invalid); + + return label_colors[color_index].color; +} + +gchar *colorlabel_get_color_text(gint color_index) +{ + G_RETURN_VAL_IF_INVALID_COLOR(color_index, NULL); + + colorlabel_recreate_label(color_index); + return label_colors[color_index].label; +} + +static gboolean colorlabel_drawing_area_expose_event_cb + (GtkWidget *widget, GdkEventExpose *expose, gpointer data) +{ + GdkDrawable *drawable = widget->window; + gulong c = (gulong) GPOINTER_TO_INT(data); + GdkColor color; + GdkGC *gc; + + color.red = ((c >> 16UL) & 0xFFUL) << 8UL; + color.green = ((c >> 8UL) & 0xFFUL) << 8UL; + color.blue = ((c) & 0xFFUL) << 8UL; + + gdk_colormap_alloc_color(gtk_widget_get_colormap(widget), &color, FALSE, TRUE); + + gc = gdk_gc_new(drawable); + + gdk_gc_set_foreground(gc, &color); + gdk_draw_rectangle(drawable, gc, + TRUE, 0, 0, widget->allocation.width - 1, + widget->allocation.height - 1); + gdk_draw_rectangle(drawable, widget->style->black_gc, + FALSE, 0, 0, widget->allocation.width - 1, + widget->allocation.height - 1); + + gdk_gc_unref(gc); + + return FALSE; +} + +static GtkWidget *colorlabel_create_color_widget(GdkColor color) +{ + GtkWidget *widget; + + widget = gtk_drawing_area_new(); + gtk_drawing_area_size(GTK_DRAWING_AREA(widget), + LABEL_COLOR_WIDTH - 2, LABEL_COLOR_HEIGHT - 4); + +#define CL(x) (((gulong) (x) >> (gulong) 8) & 0xFFUL) +#define CR(r, g, b) ((CL(r) << (gulong) 16) | \ + (CL(g) << (gulong) 8) | \ + (CL(b))) + + g_signal_connect(G_OBJECT(widget), "expose_event", + G_CALLBACK(colorlabel_drawing_area_expose_event_cb), + GINT_TO_POINTER + ((gint)CR(color.red, color.green, color.blue))); + + return widget; +} + +/* XXX: this function to check if menus with colors and labels should + * be recreated */ +gboolean colorlabel_changed(void) +{ + gint n; + + for (n = 0; n < LABEL_COLORS_ELEMS; n++) { + if (label_colors[n].changed) + return TRUE; + } + + return FALSE; +} + +/* XXX: colorlabel_recreate_XXX are there to make sure everything + * is initialized ok, without having to call a global _xxx_init_ + * function */ +static void colorlabel_recreate_color(gint color) +{ + GtkWidget *widget; + + if (!(label_colors[color].changed & LCCF_COLOR)) + return; + + widget = colorlabel_create_color_widget(label_colors[color].color); + g_return_if_fail(widget); + + if (label_colors[color].widget) + gtk_widget_destroy(label_colors[color].widget); + + label_colors[color].widget = widget; + label_colors[color].changed &= ~LCCF_COLOR; +} + +static void colorlabel_recreate_label(gint color) +{ + if (!label_colors[color].changed & LCCF_LABEL) + return; + + if (label_colors[color].label == NULL) + label_colors[color].label = g_strdup(gettext(labels[color])); + + label_colors[color].changed &= ~LCCF_LABEL; +} + +/* XXX: call this function everytime when you're doing important + * stuff with the label_colors[] array */ +static void colorlabel_recreate(gint color) +{ + colorlabel_recreate_label(color); + colorlabel_recreate_color(color); +} + +static void colorlabel_recreate_all(void) +{ + gint n; + + for ( n = 0; n < LABEL_COLORS_ELEMS; n++) + colorlabel_recreate(n); +} + +/* colorlabel_create_check_color_menu_item() - creates a color + * menu item with a check box */ +GtkWidget *colorlabel_create_check_color_menu_item(gint color_index) +{ + GtkWidget *label; + GtkWidget *hbox; + GtkWidget *align; + GtkWidget *item; + + G_RETURN_VAL_IF_INVALID_COLOR(color_index, NULL); + + item = gtk_check_menu_item_new(); + + colorlabel_recreate(color_index); + + /* XXX: gnome-core::panel::menu.c is a great example of + * how to create pixmap menus */ + label = gtk_label_new(label_colors[color_index].label); + + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_widget_show(label); + hbox = gtk_hbox_new(FALSE, 0); + gtk_widget_show(hbox); + gtk_container_add(GTK_CONTAINER(item), hbox); + + align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0); + gtk_widget_show(align); + gtk_container_set_border_width(GTK_CONTAINER(align), 1); + + gtk_container_add(GTK_CONTAINER(align), label_colors[color_index].widget); + gtk_widget_show(label_colors[color_index].widget); + gtk_widget_set_size_request + (align, LABEL_COLOR_WIDTH, LABEL_COLOR_HEIGHT); + + gtk_box_pack_start(GTK_BOX(hbox), align, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 4); + + return item; +} + +/* colorlabel_create_color_menu() - creates a color menu without + * checkitems, probably for use in combo items */ +GtkWidget *colorlabel_create_color_menu(void) +{ + GtkWidget *label; + GtkWidget *hbox; + GtkWidget *align; + GtkWidget *item; + GtkWidget *menu; + gint i; + + colorlabel_recreate_all(); + + /* create the menu items. each item has its color code attached */ + menu = gtk_menu_new(); + g_object_set_data(G_OBJECT(menu), "label_color_menu", menu); + +#if 0 + item = gtk_menu_item_new_with_label(_("None")); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_object_set_data(G_OBJECT(item), "color", GUINT_TO_POINTER(0)); + gtk_widget_show(item); +#endif + + /* and the color items */ + for (i = 0; i < LABEL_COLORS_ELEMS; i++) { + GtkWidget *widget = colorlabel_create_color_widget(label_colors[i].color); + + item = gtk_menu_item_new(); + g_object_set_data(G_OBJECT(item), "color", + GUINT_TO_POINTER(i + 1)); + + label = gtk_label_new(label_colors[i].label); + + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_widget_show(label); + hbox = gtk_hbox_new(FALSE, 0); + gtk_widget_show(hbox); + gtk_container_add(GTK_CONTAINER(item), hbox); + + align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0); + gtk_widget_show(align); + gtk_container_set_border_width(GTK_CONTAINER(align), 1); + + gtk_container_add(GTK_CONTAINER(align), widget); + gtk_widget_show(widget); + gtk_widget_set_size_request + (align, LABEL_COLOR_WIDTH, LABEL_COLOR_HEIGHT); + + gtk_box_pack_start(GTK_BOX(hbox), align, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 4); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + gtk_widget_show(item); + } + + gtk_widget_show(menu); + + return menu; +} + +guint colorlabel_get_color_menu_active_item(GtkWidget *menu) +{ + GtkWidget *menuitem; + guint color; + + g_return_val_if_fail + (g_object_get_data(G_OBJECT(menu), "label_color_menu"), 0); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + color = GPOINTER_TO_UINT + (g_object_get_data(G_OBJECT(menuitem), "color")); + return color; +} diff --git a/src/colorlabel.h b/src/colorlabel.h new file mode 100644 index 00000000..8bb23c31 --- /dev/null +++ b/src/colorlabel.h @@ -0,0 +1,34 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Hiroyuki Yamamoto & The Sylpheed Claws Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#if !defined(COLORLABEL_H__) +#define COLORLABEL_H__ + +#include + +gint colorlabel_get_color_count (void); +GdkColor colorlabel_get_color (gint color_index); +gchar *colorlabel_get_color_text (gint color_index); +gboolean colorlabel_changed (void); +GtkWidget *colorlabel_create_check_color_menu_item + (gint color_index); +GtkWidget *colorlabel_create_color_menu (void); +guint colorlabel_get_color_menu_active_item (GtkWidget *menu); + +#endif /* COLORLABEL_H__ */ diff --git a/src/compose.c b/src/compose.c new file mode 100644 index 00000000..441ab942 --- /dev/null +++ b/src/compose.c @@ -0,0 +1,6204 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#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 +#include +#include +#include +/* #include */ +#include +#include +#include +#include + +#if (HAVE_WCTYPE_H && HAVE_WCHAR_H) +# include +# include +#endif + +#include "intl.h" +#include "main.h" +#include "mainwindow.h" +#include "compose.h" +#include "addressbook.h" +#include "folderview.h" +#include "procmsg.h" +#include "menu.h" +#include "stock_pixmap.h" +#include "send_message.h" +#include "imap.h" +#include "news.h" +#include "customheader.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "action.h" +#include "account.h" +#include "filesel.h" +#include "procheader.h" +#include "procmime.h" +#include "statusbar.h" +#include "about.h" +#include "base64.h" +#include "quoted-printable.h" +#include "codeconv.h" +#include "utils.h" +#include "gtkutils.h" +#include "socket.h" +#include "alertpanel.h" +#include "manage_window.h" +#include "gtkshruler.h" +#include "folder.h" +#include "addr_compl.h" +#include "quote_fmt.h" +#include "template.h" +#include "undo.h" + +#if USE_GPGME +# include "rfc2015.h" +#endif + +typedef enum +{ + COL_MIMETYPE = 0, + COL_SIZE = 1, + COL_NAME = 2 +} AttachColumnPos; + +#define N_ATTACH_COLS 3 + +typedef enum +{ + COMPOSE_ACTION_MOVE_BEGINNING_OF_LINE, + COMPOSE_ACTION_MOVE_FORWARD_CHARACTER, + COMPOSE_ACTION_MOVE_BACKWARD_CHARACTER, + COMPOSE_ACTION_MOVE_FORWARD_WORD, + COMPOSE_ACTION_MOVE_BACKWARD_WORD, + COMPOSE_ACTION_MOVE_END_OF_LINE, + COMPOSE_ACTION_MOVE_NEXT_LINE, + COMPOSE_ACTION_MOVE_PREVIOUS_LINE, + COMPOSE_ACTION_DELETE_FORWARD_CHARACTER, + COMPOSE_ACTION_DELETE_BACKWARD_CHARACTER, + COMPOSE_ACTION_DELETE_FORWARD_WORD, + COMPOSE_ACTION_DELETE_BACKWARD_WORD, + COMPOSE_ACTION_DELETE_LINE, + COMPOSE_ACTION_DELETE_LINE_N, + COMPOSE_ACTION_DELETE_TO_LINE_END +} ComposeAction; + +#define B64_LINE_SIZE 57 +#define B64_BUFFSIZE 77 + +#define MAX_REFERENCES_LEN 999 + +static GdkColor quote_color = {0, 0, 0, 0xbfff}; + +static GList *compose_list = NULL; + +static Compose *compose_create (PrefsAccount *account, + ComposeMode mode); +static void compose_connect_changed_callbacks (Compose *compose); +static void compose_toolbar_create (Compose *compose, + GtkWidget *container); +static GtkWidget *compose_account_option_menu_create + (Compose *compose); +static void compose_set_template_menu (Compose *compose); +static void compose_template_apply (Compose *compose, + Template *tmpl, + gboolean replace); +static void compose_destroy (Compose *compose); + +static void compose_entry_show (Compose *compose, + ComposeEntryType type); +static GtkEntry *compose_get_entry (Compose *compose, + ComposeEntryType type); +static void compose_entries_set (Compose *compose, + const gchar *mailto); +static void compose_entries_set_from_item (Compose *compose, + FolderItem *item, + ComposeMode mode); +static gint compose_parse_header (Compose *compose, + MsgInfo *msginfo); +static gchar *compose_parse_references (const gchar *ref, + const gchar *msgid); + +static gchar *compose_quote_fmt (Compose *compose, + MsgInfo *msginfo, + const gchar *fmt, + const gchar *qmark, + const gchar *body); + +static void compose_reply_set_entry (Compose *compose, + MsgInfo *msginfo, + ComposeMode mode); +static void compose_reedit_set_entry (Compose *compose, + MsgInfo *msginfo); +static void compose_insert_sig (Compose *compose, + gboolean replace); +static gchar *compose_get_signature_str (Compose *compose); +static void compose_insert_file (Compose *compose, + const gchar *file); +static void compose_attach_append (Compose *compose, + const gchar *file, + const gchar *filename, + const gchar *content_type); +static void compose_attach_parts (Compose *compose, + MsgInfo *msginfo); +static void compose_wrap_line (Compose *compose); +static void compose_wrap_line_all (Compose *compose); +static void compose_wrap_line_all_full (Compose *compose, + gboolean autowrap); +static void compose_set_title (Compose *compose); +static void compose_select_account (Compose *compose, + PrefsAccount *account, + gboolean init); + +static gboolean compose_check_for_valid_recipient + (Compose *compose); +static gboolean compose_check_entries (Compose *compose); + +static gint compose_send (Compose *compose); +static gint compose_write_to_file (Compose *compose, + const gchar *file, + gboolean is_draft); +static gint compose_write_body_to_file (Compose *compose, + const gchar *file); +static gint compose_redirect_write_to_file (Compose *compose, + const gchar *file); +static gint compose_remove_reedit_target (Compose *compose); +static gint compose_queue (Compose *compose, + const gchar *file); +static void compose_write_attach (Compose *compose, + FILE *fp); +static gint compose_write_headers (Compose *compose, + FILE *fp, + const gchar *charset, + EncodingType encoding, + gboolean is_draft); +static gint compose_redirect_write_headers (Compose *compose, + FILE *fp); + +static void compose_convert_header (gchar *dest, + gint len, + gchar *src, + gint header_len, + gboolean addr_field); +static void compose_generate_msgid (Compose *compose, + gchar *buf, + gint len); + +static void compose_attach_info_free (AttachInfo *ainfo); +static void compose_attach_remove_selected (Compose *compose); + +static void compose_attach_property (Compose *compose); +static void compose_attach_property_create (gboolean *cancelled); +static void attach_property_ok (GtkWidget *widget, + gboolean *cancelled); +static void attach_property_cancel (GtkWidget *widget, + gboolean *cancelled); +static gint attach_property_delete_event (GtkWidget *widget, + GdkEventAny *event, + gboolean *cancelled); +static gboolean attach_property_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gboolean *cancelled); + +static void compose_exec_ext_editor (Compose *compose); +static gint compose_exec_ext_editor_real (const gchar *file); +static gboolean compose_ext_editor_kill (Compose *compose); +static void compose_input_cb (gpointer data, + gint source, + GdkInputCondition condition); +static void compose_set_ext_editor_sensitive (Compose *compose, + gboolean sensitive); + +static void compose_undo_state_changed (UndoMain *undostruct, + gint undo_state, + gint redo_state, + gpointer data); + +static gint calc_cursor_xpos (GtkTextView *text, + gint extra, + gint char_width); + +/* callback functions */ + +static gboolean compose_edit_size_alloc (GtkEditable *widget, + GtkAllocation *allocation, + GtkSHRuler *shruler); + +static void toolbar_send_cb (GtkWidget *widget, + gpointer data); +static void toolbar_send_later_cb (GtkWidget *widget, + gpointer data); +static void toolbar_draft_cb (GtkWidget *widget, + gpointer data); +static void toolbar_insert_cb (GtkWidget *widget, + gpointer data); +static void toolbar_attach_cb (GtkWidget *widget, + gpointer data); +static void toolbar_sig_cb (GtkWidget *widget, + gpointer data); +static void toolbar_ext_editor_cb (GtkWidget *widget, + gpointer data); +static void toolbar_linewrap_cb (GtkWidget *widget, + gpointer data); +static void toolbar_address_cb (GtkWidget *widget, + gpointer data); + +static void account_activated (GtkMenuItem *menuitem, + gpointer data); + +static void attach_selected (GtkCList *clist, + gint row, + gint column, + GdkEvent *event, + gpointer data); +static gboolean attach_button_pressed (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static gboolean attach_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); + +static void compose_send_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_send_later_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void compose_draft_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void compose_attach_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_insert_file_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_insert_sig_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void compose_close_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void compose_address_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_template_activate_cb(GtkWidget *widget, + gpointer data); + +static void compose_ext_editor_cb (gpointer data, + guint action, + GtkWidget *widget); + +static gint compose_delete_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static void compose_destroy_cb (GtkWidget *widget, + Compose *compose); + +static void compose_undo_cb (Compose *compose); +static void compose_redo_cb (Compose *compose); +static void compose_cut_cb (Compose *compose); +static void compose_copy_cb (Compose *compose); +static void compose_paste_cb (Compose *compose); +static void compose_paste_as_quote_cb (Compose *compose); +static void compose_allsel_cb (Compose *compose); + +static void compose_action_cb (Compose *compose, + ComposeAction action); + +static void compose_grab_focus_cb (GtkWidget *widget, + Compose *compose); + +static void compose_changed_cb (GtkTextBuffer *textbuf, + Compose *compose); + +static void compose_toggle_autowrap_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void compose_toggle_to_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_cc_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_bcc_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_replyto_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_followupto_cb(gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_attach_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_ruler_cb (gpointer data, + guint action, + GtkWidget *widget); +#if USE_GPGME +static void compose_toggle_sign_cb (gpointer data, + guint action, + GtkWidget *widget); +static void compose_toggle_encrypt_cb (gpointer data, + guint action, + GtkWidget *widget); +#endif + +static void compose_attach_drag_received_cb (GtkWidget *widget, + GdkDragContext *drag_context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data); +static void compose_insert_drag_received_cb (GtkWidget *widget, + GdkDragContext *drag_context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data); + +static void to_activated (GtkWidget *widget, + Compose *compose); +static void newsgroups_activated (GtkWidget *widget, + Compose *compose); +static void cc_activated (GtkWidget *widget, + Compose *compose); +static void bcc_activated (GtkWidget *widget, + Compose *compose); +static void replyto_activated (GtkWidget *widget, + Compose *compose); +static void followupto_activated (GtkWidget *widget, + Compose *compose); +static void subject_activated (GtkWidget *widget, + Compose *compose); + +static void text_activated (GtkWidget *widget, + Compose *compose); +static void text_inserted (GtkTextBuffer *buffer, + GtkTextIter *iter, + const gchar *text, + gint len, + Compose *compose); + +static gboolean compose_send_control_enter (Compose *compose); + +static GtkItemFactoryEntry compose_popup_entries[] = +{ + {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL}, + {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL} +}; + +static GtkItemFactoryEntry compose_entries[] = +{ + {N_("/_File"), NULL, NULL, 0, ""}, + {N_("/_File/_Send"), "Return", + compose_send_cb, 0, NULL}, + {N_("/_File/Send _later"), "S", + compose_send_later_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/Save to _draft folder"), + "D", compose_draft_cb, 0, NULL}, + {N_("/_File/Save and _keep editing"), + "S", compose_draft_cb, 1, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Attach file"), "M", compose_attach_cb, 0, NULL}, + {N_("/_File/_Insert file"), "I", compose_insert_file_cb, 0, NULL}, + {N_("/_File/Insert si_gnature"), "G", compose_insert_sig_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Close"), "W", compose_close_cb, 0, NULL}, + + {N_("/_Edit"), NULL, NULL, 0, ""}, + {N_("/_Edit/_Undo"), "Z", compose_undo_cb, 0, NULL}, + {N_("/_Edit/_Redo"), "Y", compose_redo_cb, 0, NULL}, + {N_("/_Edit/---"), NULL, NULL, 0, ""}, + {N_("/_Edit/Cu_t"), "X", compose_cut_cb, 0, NULL}, + {N_("/_Edit/_Copy"), "C", compose_copy_cb, 0, NULL}, + {N_("/_Edit/_Paste"), "V", compose_paste_cb, 0, NULL}, + {N_("/_Edit/Paste as _quotation"), + NULL, compose_paste_as_quote_cb, 0, NULL}, + {N_("/_Edit/Select _all"), "A", compose_allsel_cb, 0, NULL}, + {N_("/_Edit/A_dvanced"), NULL, NULL, 0, ""}, + {N_("/_Edit/A_dvanced/Move a character backward"), + "B", + compose_action_cb, + COMPOSE_ACTION_MOVE_BACKWARD_CHARACTER, + NULL}, + {N_("/_Edit/A_dvanced/Move a character forward"), + "F", + compose_action_cb, + COMPOSE_ACTION_MOVE_FORWARD_CHARACTER, + NULL}, + {N_("/_Edit/A_dvanced/Move a word backward"), + NULL, /* "B" */ + compose_action_cb, + COMPOSE_ACTION_MOVE_BACKWARD_WORD, + NULL}, + {N_("/_Edit/A_dvanced/Move a word forward"), + NULL, /* "F" */ + compose_action_cb, + COMPOSE_ACTION_MOVE_FORWARD_WORD, + NULL}, + {N_("/_Edit/A_dvanced/Move to beginning of line"), + NULL, /* "A" */ + compose_action_cb, + COMPOSE_ACTION_MOVE_BEGINNING_OF_LINE, + NULL}, + {N_("/_Edit/A_dvanced/Move to end of line"), + "E", + compose_action_cb, + COMPOSE_ACTION_MOVE_END_OF_LINE, + NULL}, + {N_("/_Edit/A_dvanced/Move to previous line"), + "P", + compose_action_cb, + COMPOSE_ACTION_MOVE_PREVIOUS_LINE, + NULL}, + {N_("/_Edit/A_dvanced/Move to next line"), + "N", + compose_action_cb, + COMPOSE_ACTION_MOVE_NEXT_LINE, + NULL}, + {N_("/_Edit/A_dvanced/Delete a character backward"), + "H", + compose_action_cb, + COMPOSE_ACTION_DELETE_BACKWARD_CHARACTER, + NULL}, + {N_("/_Edit/A_dvanced/Delete a character forward"), + "D", + compose_action_cb, + COMPOSE_ACTION_DELETE_FORWARD_CHARACTER, + NULL}, + {N_("/_Edit/A_dvanced/Delete a word backward"), + NULL, /* "W" */ + compose_action_cb, + COMPOSE_ACTION_DELETE_BACKWARD_WORD, + NULL}, + {N_("/_Edit/A_dvanced/Delete a word forward"), + NULL, /* "D", */ + compose_action_cb, + COMPOSE_ACTION_DELETE_FORWARD_WORD, + NULL}, + {N_("/_Edit/A_dvanced/Delete line"), + "U", + compose_action_cb, + COMPOSE_ACTION_DELETE_LINE, + NULL}, + {N_("/_Edit/A_dvanced/Delete to end of line"), + "K", + compose_action_cb, + COMPOSE_ACTION_DELETE_TO_LINE_END, + NULL}, + {N_("/_Edit/---"), NULL, NULL, 0, ""}, + {N_("/_Edit/_Wrap current paragraph"), + "L", compose_wrap_line, 0, NULL}, + {N_("/_Edit/Wrap all long _lines"), + "L", compose_wrap_line_all, 0, NULL}, + {N_("/_Edit/Aut_o wrapping"), "L", compose_toggle_autowrap_cb, 0, ""}, + {N_("/_View"), NULL, NULL, 0, ""}, + {N_("/_View/_To"), NULL, compose_toggle_to_cb , 0, ""}, + {N_("/_View/_Cc"), NULL, compose_toggle_cc_cb , 0, ""}, + {N_("/_View/_Bcc"), NULL, compose_toggle_bcc_cb , 0, ""}, + {N_("/_View/_Reply to"), NULL, compose_toggle_replyto_cb, 0, ""}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Followup to"), NULL, compose_toggle_followupto_cb, 0, ""}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/R_uler"), NULL, compose_toggle_ruler_cb, 0, ""}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Attachment"), NULL, compose_toggle_attach_cb, 0, ""}, + + {N_("/_Tools"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Address book"), "A", compose_address_cb , 0, NULL}, + {N_("/_Tools/_Template"), NULL, NULL, 0, ""}, + {N_("/_Tools/Actio_ns"), NULL, NULL, 0, ""}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/Edit with e_xternal editor"), + "X", compose_ext_editor_cb, 0, NULL}, +#if USE_GPGME + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/PGP Si_gn"), NULL, compose_toggle_sign_cb , 0, ""}, + {N_("/_Tools/PGP _Encrypt"), NULL, compose_toggle_encrypt_cb, 0, ""}, +#endif /* USE_GPGME */ + + {N_("/_Help"), NULL, NULL, 0, ""}, + {N_("/_Help/_About"), NULL, about_show, 0, NULL} +}; + +static GtkTargetEntry compose_mime_types[] = +{ + {"text/uri-list", 0, 0} +}; + + +void compose_new(PrefsAccount *account, FolderItem *item, const gchar *mailto, + GPtrArray *attach_files) +{ + Compose *compose; + GtkTextView *text; + GtkTextBuffer *buffer; + GtkTextIter iter; + + if (!account) account = cur_account; + g_return_if_fail(account != NULL); + + compose = compose_create(account, COMPOSE_NEW); + + undo_block(compose->undostruct); + + if (prefs_common.auto_sig) + compose_insert_sig(compose, FALSE); + + text = GTK_TEXT_VIEW(compose->text); + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_place_cursor(buffer, &iter); + + if (account->protocol != A_NNTP) { + if (mailto && *mailto != '\0') { + compose_entries_set(compose, mailto); + gtk_widget_grab_focus(compose->subject_entry); + } else if (item) { + compose_entries_set_from_item + (compose, item, COMPOSE_NEW); + if (item->auto_to) + gtk_widget_grab_focus(compose->subject_entry); + else + gtk_widget_grab_focus(compose->to_entry); + } else + gtk_widget_grab_focus(compose->to_entry); + } else { + if (mailto && *mailto != '\0') { + compose_entry_append(compose, mailto, + COMPOSE_ENTRY_NEWSGROUPS); + gtk_widget_grab_focus(compose->subject_entry); + } else + gtk_widget_grab_focus(compose->newsgroups_entry); + } + + if (attach_files) { + gint i; + gchar *file; + + for (i = 0; i < attach_files->len; i++) { + file = g_ptr_array_index(attach_files, i); + compose_attach_append(compose, file, file, NULL); + } + } + + undo_unblock(compose->undostruct); + + compose_connect_changed_callbacks(compose); + + if (prefs_common.auto_exteditor) + compose_exec_ext_editor(compose); +} + +void compose_reply(MsgInfo *msginfo, FolderItem *item, ComposeMode mode, + const gchar *body) +{ + Compose *compose; + PrefsAccount *account; + GtkTextBuffer *buffer; + GtkTextIter iter; + gboolean quote = FALSE; + + g_return_if_fail(msginfo != NULL); + g_return_if_fail(msginfo->folder != NULL); + + if (COMPOSE_QUOTE_MODE(mode) == COMPOSE_WITH_QUOTE) + quote = TRUE; + + account = account_find_from_item(msginfo->folder); + if (!account && msginfo->to && prefs_common.reply_account_autosel) { + gchar *to; + Xstrdup_a(to, msginfo->to, return); + extract_address(to); + account = account_find_from_address(to); + } + if (!account) account = cur_account; + g_return_if_fail(account != NULL); + + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_FORWARDED); + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_REPLIED); + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_set_perm_flags(msginfo, MSG_REPLIED); + + compose = compose_create(account, COMPOSE_REPLY); + + compose->replyinfo = procmsg_msginfo_get_full_info(msginfo); + if (!compose->replyinfo) + compose->replyinfo = procmsg_msginfo_copy(msginfo); + + if (compose_parse_header(compose, msginfo) < 0) return; + + undo_block(compose->undostruct); + + compose_reply_set_entry(compose, msginfo, mode); + if (item) + compose_entries_set_from_item(compose, item, COMPOSE_REPLY); + + if (quote) { + gchar *qmark; + gchar *quote_str; + + if (prefs_common.quotemark && *prefs_common.quotemark) + qmark = prefs_common.quotemark; + else + qmark = "> "; + + quote_str = compose_quote_fmt(compose, compose->replyinfo, + prefs_common.quotefmt, + qmark, body); + } + + if (prefs_common.auto_sig) + compose_insert_sig(compose, FALSE); + + if (quote && prefs_common.linewrap_quote) + compose_wrap_line_all(compose); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text)); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_place_cursor(buffer, &iter); + + gtk_widget_grab_focus(compose->text); + + undo_unblock(compose->undostruct); + + compose_connect_changed_callbacks(compose); + + if (prefs_common.auto_exteditor) + compose_exec_ext_editor(compose); +} + +void compose_forward(GSList *mlist, FolderItem *item, gboolean as_attach, + const gchar *body) +{ + Compose *compose; + PrefsAccount *account; + GtkTextView *text; + GtkTextBuffer *buffer; + GtkTextIter iter; + GSList *cur; + MsgInfo *msginfo; + + g_return_if_fail(mlist != NULL); + + msginfo = (MsgInfo *)mlist->data; + g_return_if_fail(msginfo->folder != NULL); + + account = account_find_from_item(msginfo->folder); + if (!account) account = cur_account; + g_return_if_fail(account != NULL); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_REPLIED); + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_FORWARDED); + } + msginfo = (MsgInfo *)mlist->data; + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_list_unset_perm_flags(mlist, MSG_REPLIED); + + compose = compose_create(account, COMPOSE_FORWARD); + + undo_block(compose->undostruct); + + compose_entry_set(compose, "Fw: ", COMPOSE_ENTRY_SUBJECT); + if (mlist->next == NULL && msginfo->subject && *msginfo->subject) + compose_entry_append(compose, msginfo->subject, + COMPOSE_ENTRY_SUBJECT); + if (item) + compose_entries_set_from_item(compose, item, COMPOSE_FORWARD); + + text = GTK_TEXT_VIEW(compose->text); + buffer = gtk_text_view_get_buffer(text); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (as_attach) { + gchar *msgfile; + + msgfile = procmsg_get_message_file_path(msginfo); + if (!is_file_exist(msgfile)) + g_warning(_("%s: file not exist\n"), msgfile); + else + compose_attach_append(compose, msgfile, msgfile, + "message/rfc822"); + + g_free(msgfile); + } else { + gchar *qmark; + gchar *quote_str; + MsgInfo *full_msginfo; + + full_msginfo = procmsg_msginfo_get_full_info(msginfo); + if (!full_msginfo) + full_msginfo = procmsg_msginfo_copy(msginfo); + + if (cur != mlist) { + GtkTextMark *mark; + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark + (buffer, &iter, mark); + gtk_text_buffer_insert + (buffer, &iter, "\n\n", 2); + } + + if (prefs_common.fw_quotemark && + *prefs_common.fw_quotemark) + qmark = prefs_common.fw_quotemark; + else + qmark = "> "; + + quote_str = compose_quote_fmt(compose, full_msginfo, + prefs_common.fw_quotefmt, + qmark, body); + compose_attach_parts(compose, msginfo); + + procmsg_msginfo_free(full_msginfo); + + if (body) break; + } + } + + if (prefs_common.auto_sig) + compose_insert_sig(compose, FALSE); + + if (prefs_common.linewrap_quote) + compose_wrap_line_all(compose); + + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_place_cursor(buffer, &iter); + + undo_unblock(compose->undostruct); + + compose_connect_changed_callbacks(compose); + + if (account->protocol != A_NNTP) + gtk_widget_grab_focus(compose->to_entry); + else + gtk_widget_grab_focus(compose->newsgroups_entry); + + if (prefs_common.auto_exteditor) + compose_exec_ext_editor(compose); +} + +void compose_redirect(MsgInfo *msginfo, FolderItem *item) +{ + Compose *compose; + PrefsAccount *account; + GtkTextView *text; + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + FILE *fp; + gchar buf[BUFFSIZE]; + + g_return_if_fail(msginfo != NULL); + g_return_if_fail(msginfo->folder != NULL); + + account = account_find_from_item(msginfo->folder); + if (!account) account = cur_account; + g_return_if_fail(account != NULL); + + compose = compose_create(account, COMPOSE_REDIRECT); + compose->targetinfo = procmsg_msginfo_copy(msginfo); + + if (compose_parse_header(compose, msginfo) < 0) return; + + undo_block(compose->undostruct); + + if (msginfo->subject) + compose_entry_set(compose, msginfo->subject, + COMPOSE_ENTRY_SUBJECT); + compose_entries_set_from_item(compose, item, COMPOSE_REDIRECT); + + text = GTK_TEXT_VIEW(compose->text); + buffer = gtk_text_view_get_buffer(text); + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + + if ((fp = procmime_get_first_text_content(msginfo)) == NULL) + g_warning(_("Can't get text part\n")); + else { + while (fgets(buf, sizeof(buf), fp) != NULL) { + strcrchomp(buf); + gtk_text_buffer_insert(buffer, &iter, buf, -1); + } + fclose(fp); + } + compose_attach_parts(compose, msginfo); + + if (account->protocol != A_NNTP) + gtk_widget_grab_focus(compose->to_entry); + else + gtk_widget_grab_focus(compose->newsgroups_entry); + + gtk_text_view_set_editable(text, FALSE); + + undo_unblock(compose->undostruct); + + compose_connect_changed_callbacks(compose); +} + +void compose_reedit(MsgInfo *msginfo) +{ + Compose *compose; + PrefsAccount *account; + GtkTextView *text; + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + FILE *fp; + gchar buf[BUFFSIZE]; + + g_return_if_fail(msginfo != NULL); + g_return_if_fail(msginfo->folder != NULL); + + account = account_find_from_msginfo(msginfo); + if (!account) account = cur_account; + g_return_if_fail(account != NULL); + + compose = compose_create(account, COMPOSE_REEDIT); + compose->targetinfo = procmsg_msginfo_copy(msginfo); + + if (compose_parse_header(compose, msginfo) < 0) return; + + undo_block(compose->undostruct); + + compose_reedit_set_entry(compose, msginfo); + + text = GTK_TEXT_VIEW(compose->text); + buffer = gtk_text_view_get_buffer(text); + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + + if ((fp = procmime_get_first_text_content(msginfo)) == NULL) + g_warning(_("Can't get text part\n")); + else { + while (fgets(buf, sizeof(buf), fp) != NULL) { + strcrchomp(buf); + gtk_text_buffer_insert(buffer, &iter, buf, -1); + } + fclose(fp); + } + compose_attach_parts(compose, msginfo); + + gtk_widget_grab_focus(compose->text); + + undo_unblock(compose->undostruct); + + compose_connect_changed_callbacks(compose); + + if (prefs_common.auto_exteditor) + compose_exec_ext_editor(compose); +} + +GList *compose_get_compose_list(void) +{ + return compose_list; +} + +static void compose_entry_show(Compose *compose, ComposeEntryType type) +{ + GtkItemFactory *ifactory; + + ifactory = gtk_item_factory_from_widget(compose->menubar); + + switch (type) { + case COMPOSE_ENTRY_CC: + menu_set_active(ifactory, "/View/Cc", TRUE); + break; + case COMPOSE_ENTRY_BCC: + menu_set_active(ifactory, "/View/Bcc", TRUE); + break; + case COMPOSE_ENTRY_REPLY_TO: + menu_set_active(ifactory, "/View/Reply to", TRUE); + break; + case COMPOSE_ENTRY_FOLLOWUP_TO: + menu_set_active(ifactory, "/View/Followup to", TRUE); + break; + case COMPOSE_ENTRY_TO: + menu_set_active(ifactory, "/View/To", TRUE); + break; + default: + break; + } +} + +static GtkEntry *compose_get_entry(Compose *compose, ComposeEntryType type) +{ + GtkEntry *entry; + + switch (type) { + case COMPOSE_ENTRY_CC: + entry = GTK_ENTRY(compose->cc_entry); + break; + case COMPOSE_ENTRY_BCC: + entry = GTK_ENTRY(compose->bcc_entry); + break; + case COMPOSE_ENTRY_REPLY_TO: + entry = GTK_ENTRY(compose->reply_entry); + break; + case COMPOSE_ENTRY_SUBJECT: + entry = GTK_ENTRY(compose->subject_entry); + break; + case COMPOSE_ENTRY_NEWSGROUPS: + entry = GTK_ENTRY(compose->newsgroups_entry); + break; + case COMPOSE_ENTRY_FOLLOWUP_TO: + entry = GTK_ENTRY(compose->followup_entry); + break; + case COMPOSE_ENTRY_TO: + default: + entry = GTK_ENTRY(compose->to_entry); + break; + } + + return entry; +} + +void compose_entry_set(Compose *compose, const gchar *text, + ComposeEntryType type) +{ + GtkEntry *entry; + + if (!text) return; + + compose_entry_show(compose, type); + entry = compose_get_entry(compose, type); + + gtk_entry_set_text(entry, text); +} + +void compose_entry_append(Compose *compose, const gchar *text, + ComposeEntryType type) +{ + GtkEntry *entry; + const gchar *str; + gint pos; + + if (!text || *text == '\0') return; + + compose_entry_show(compose, type); + entry = compose_get_entry(compose, type); + + if (type != COMPOSE_ENTRY_SUBJECT) { + str = gtk_entry_get_text(entry); + if (*str != '\0') { + pos = entry->text_length; + gtk_editable_insert_text(GTK_EDITABLE(entry), + ", ", -1, &pos); + } + } + + pos = entry->text_length; + gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &pos); +} + +static void compose_entries_set(Compose *compose, const gchar *mailto) +{ + gchar *to = NULL; + gchar *cc = NULL; + gchar *bcc = NULL; + gchar *subject = NULL; + gchar *body = NULL; + + scan_mailto_url(mailto, &to, &cc, &bcc, &subject, &body); + + if (to) + compose_entry_set(compose, to, COMPOSE_ENTRY_TO); + if (cc) + compose_entry_set(compose, cc, COMPOSE_ENTRY_CC); + if (bcc) + compose_entry_set(compose, bcc, COMPOSE_ENTRY_BCC); + if (subject) + compose_entry_set(compose, subject, COMPOSE_ENTRY_SUBJECT); + if (body) { + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + + buffer = gtk_text_view_get_buffer(text); + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + + gtk_text_buffer_insert(buffer, &iter, body, -1); + gtk_text_buffer_insert(buffer, &iter, "\n", 1); + } + + g_free(to); + g_free(cc); + g_free(bcc); + g_free(subject); + g_free(body); +} + +static void compose_entries_set_from_item(Compose *compose, FolderItem *item, + ComposeMode mode) +{ + g_return_if_fail(item != NULL); + + if (item->auto_to) { + if (mode != COMPOSE_REPLY || item->use_auto_to_on_reply) + compose_entry_set(compose, item->auto_to, + COMPOSE_ENTRY_TO); + } + if (item->auto_cc) + compose_entry_set(compose, item->auto_cc, COMPOSE_ENTRY_CC); + if (item->auto_bcc) + compose_entry_set(compose, item->auto_bcc, COMPOSE_ENTRY_BCC); + if (item->auto_replyto) + compose_entry_set(compose, item->auto_replyto, + COMPOSE_ENTRY_REPLY_TO); +} + +#undef ACTIVATE_MENU + +static gint compose_parse_header(Compose *compose, MsgInfo *msginfo) +{ + static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE}, + {"Cc:", NULL, TRUE}, + {"References:", NULL, FALSE}, + {"Bcc:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Followup-To:", NULL, TRUE}, + {"List-Post:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + enum + { + H_REPLY_TO = 0, + H_CC = 1, + H_REFERENCES = 2, + H_BCC = 3, + H_NEWSGROUPS = 4, + H_FOLLOWUP_TO = 5, + H_LIST_POST = 6 + }; + + FILE *fp; + + g_return_val_if_fail(msginfo != NULL, -1); + + if ((fp = procmsg_open_message(msginfo)) == NULL) return -1; + procheader_get_header_fields(fp, hentry); + fclose(fp); + + if (hentry[H_REPLY_TO].body != NULL) { + conv_unmime_header_overwrite(hentry[H_REPLY_TO].body); + compose->replyto = hentry[H_REPLY_TO].body; + hentry[H_REPLY_TO].body = NULL; + } + if (hentry[H_CC].body != NULL) { + conv_unmime_header_overwrite(hentry[H_CC].body); + compose->cc = hentry[H_CC].body; + hentry[H_CC].body = NULL; + } + if (hentry[H_REFERENCES].body != NULL) { + if (compose->mode == COMPOSE_REEDIT) + compose->references = hentry[H_REFERENCES].body; + else { + compose->references = compose_parse_references + (hentry[H_REFERENCES].body, msginfo->msgid); + g_free(hentry[H_REFERENCES].body); + } + hentry[H_REFERENCES].body = NULL; + } + if (hentry[H_BCC].body != NULL) { + if (compose->mode == COMPOSE_REEDIT) { + conv_unmime_header_overwrite(hentry[H_BCC].body); + compose->bcc = hentry[H_BCC].body; + } else + g_free(hentry[H_BCC].body); + hentry[H_BCC].body = NULL; + } + if (hentry[H_NEWSGROUPS].body != NULL) { + compose->newsgroups = hentry[H_NEWSGROUPS].body; + hentry[H_NEWSGROUPS].body = NULL; + } + if (hentry[H_FOLLOWUP_TO].body != NULL) { + conv_unmime_header_overwrite(hentry[H_FOLLOWUP_TO].body); + compose->followup_to = hentry[H_FOLLOWUP_TO].body; + hentry[H_FOLLOWUP_TO].body = NULL; + } + if (hentry[H_LIST_POST].body != NULL) { + gchar *to = NULL; + + extract_address(hentry[H_LIST_POST].body); + if (hentry[H_LIST_POST].body[0] != '\0') { + scan_mailto_url(hentry[H_LIST_POST].body, + &to, NULL, NULL, NULL, NULL); + if (to) { + g_free(compose->ml_post); + compose->ml_post = to; + } + } + g_free(hentry[H_LIST_POST].body); + hentry[H_LIST_POST].body = NULL; + } + + if (compose->mode == COMPOSE_REEDIT && msginfo->inreplyto) + compose->inreplyto = g_strdup(msginfo->inreplyto); + else if (compose->mode != COMPOSE_REEDIT && + msginfo->msgid && *msginfo->msgid) { + compose->inreplyto = g_strdup(msginfo->msgid); + + if (!compose->references) { + if (msginfo->inreplyto && *msginfo->inreplyto) + compose->references = + g_strdup_printf("<%s>\n\t<%s>", + msginfo->inreplyto, + msginfo->msgid); + else + compose->references = + g_strconcat("<", msginfo->msgid, ">", + NULL); + } + } + + return 0; +} + +static gchar *compose_parse_references(const gchar *ref, const gchar *msgid) +{ + GSList *ref_id_list, *cur; + GString *new_ref; + gchar *new_ref_str; + + ref_id_list = references_list_append(NULL, ref); + if (!ref_id_list) return NULL; + if (msgid && *msgid) + ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid)); + + for (;;) { + gint len = 0; + + for (cur = ref_id_list; cur != NULL; cur = cur->next) + /* "<" + Message-ID + ">" + CR+LF+TAB */ + len += strlen((gchar *)cur->data) + 5; + + if (len > MAX_REFERENCES_LEN) { + /* remove second message-ID */ + if (ref_id_list && ref_id_list->next && + ref_id_list->next->next) { + g_free(ref_id_list->next->data); + ref_id_list = g_slist_remove + (ref_id_list, ref_id_list->next->data); + } else { + slist_free_strings(ref_id_list); + g_slist_free(ref_id_list); + return NULL; + } + } else + break; + } + + new_ref = g_string_new(""); + for (cur = ref_id_list; cur != NULL; cur = cur->next) { + if (new_ref->len > 0) + g_string_append(new_ref, "\n\t"); + g_string_sprintfa(new_ref, "<%s>", (gchar *)cur->data); + } + + slist_free_strings(ref_id_list); + g_slist_free(ref_id_list); + + new_ref_str = new_ref->str; + g_string_free(new_ref, FALSE); + + return new_ref_str; +} + +static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo, + const gchar *fmt, const gchar *qmark, + const gchar *body) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + static MsgInfo dummyinfo; + gchar *quote_str = NULL; + gchar *buf; + gchar *p, *lastp; + gint len; + + if (!msginfo) + msginfo = &dummyinfo; + + if (qmark != NULL) { + quote_fmt_init(msginfo, NULL, NULL); + quote_fmt_scan_string(qmark); + quote_fmt_parse(); + + buf = quote_fmt_get_buffer(); + if (buf == NULL) + alertpanel_error(_("Quote mark format error.")); + else + Xstrdup_a(quote_str, buf, return NULL) + } + + if (fmt && *fmt != '\0') { + quote_fmt_init(msginfo, quote_str, body); + quote_fmt_scan_string(fmt); + quote_fmt_parse(); + + buf = quote_fmt_get_buffer(); + if (buf == NULL) { + alertpanel_error(_("Message reply/forward format error.")); + return NULL; + } + } else + buf = ""; + + buffer = gtk_text_view_get_buffer(text); + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + + for (p = buf; *p != '\0'; ) { + lastp = strchr(p, '\n'); + len = lastp ? lastp - p + 1 : -1; + gtk_text_buffer_insert(buffer, &iter, p, len); + if (lastp) + p = lastp + 1; + else + break; + } + + return buf; +} + +static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo, + ComposeMode mode) +{ + GSList *cc_list = NULL; + GSList *cur; + gchar *from = NULL; + gchar *replyto = NULL; + GHashTable *to_table; + gboolean to_all = FALSE, to_ml = FALSE, ignore_replyto = FALSE; + + g_return_if_fail(compose->account != NULL); + g_return_if_fail(msginfo != NULL); + + switch (COMPOSE_MODE(mode)) { + case COMPOSE_REPLY_TO_SENDER: + ignore_replyto = TRUE; + break; + case COMPOSE_REPLY_TO_ALL: + to_all = TRUE; + break; + case COMPOSE_REPLY_TO_LIST: + to_ml = TRUE; + break; + default: + break; + } + + if (compose->account->protocol != A_NNTP) { + if (!compose->replyto && to_ml && compose->ml_post) + compose_entry_set(compose, compose->ml_post, + COMPOSE_ENTRY_TO); + else + compose_entry_set(compose, + (compose->replyto && !ignore_replyto) + ? compose->replyto + : msginfo->from ? msginfo->from : "", + COMPOSE_ENTRY_TO); + } else { + if (ignore_replyto) { + compose_entry_set(compose, + msginfo->from ? msginfo->from : "", + COMPOSE_ENTRY_TO); + } else { + compose_entry_set(compose, + compose->followup_to ? compose->followup_to + : compose->newsgroups ? compose->newsgroups + : "", + COMPOSE_ENTRY_NEWSGROUPS); + } + } + + if (msginfo->subject && *msginfo->subject) { + gchar *buf; + guchar *p; + + buf = g_strdup(msginfo->subject); + + if (msginfo->folder && msginfo->folder->trim_compose_subject) + trim_subject(buf); + + while (!strncasecmp(buf, "Re:", 3)) { + p = buf + 3; + while (isspace(*p)) p++; + memmove(buf, p, strlen(p) + 1); + } + + compose_entry_set(compose, "Re: ", COMPOSE_ENTRY_SUBJECT); + compose_entry_append(compose, buf, COMPOSE_ENTRY_SUBJECT); + + g_free(buf); + } else + compose_entry_set(compose, "Re: ", COMPOSE_ENTRY_SUBJECT); + + if (!compose->replyto && to_ml && compose->ml_post) return; + if (!to_all || compose->account->protocol == A_NNTP) return; + + if (compose->replyto) { + Xstrdup_a(replyto, compose->replyto, return); + extract_address(replyto); + } + if (msginfo->from) { + Xstrdup_a(from, msginfo->from, return); + extract_address(from); + } + + if (replyto && from) + cc_list = address_list_append(cc_list, from); + cc_list = address_list_append(cc_list, msginfo->to); + cc_list = address_list_append(cc_list, compose->cc); + + to_table = g_hash_table_new(g_str_hash, g_str_equal); + if (replyto) + g_hash_table_insert(to_table, replyto, GINT_TO_POINTER(1)); + if (compose->account) + g_hash_table_insert(to_table, compose->account->address, + GINT_TO_POINTER(1)); + + /* remove address on To: and that of current account */ + for (cur = cc_list; cur != NULL; ) { + GSList *next = cur->next; + + if (g_hash_table_lookup(to_table, cur->data) != NULL) + cc_list = g_slist_remove(cc_list, cur->data); + else + g_hash_table_insert(to_table, cur->data, cur); + + cur = next; + } + g_hash_table_destroy(to_table); + + if (cc_list) { + for (cur = cc_list; cur != NULL; cur = cur->next) + compose_entry_append(compose, (gchar *)cur->data, + COMPOSE_ENTRY_CC); + slist_free_strings(cc_list); + g_slist_free(cc_list); + } +} + +static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo) +{ + g_return_if_fail(msginfo != NULL); + + compose_entry_set(compose, msginfo->to , COMPOSE_ENTRY_TO); + compose_entry_set(compose, compose->cc , COMPOSE_ENTRY_CC); + compose_entry_set(compose, compose->bcc , COMPOSE_ENTRY_BCC); + compose_entry_set(compose, compose->replyto, COMPOSE_ENTRY_REPLY_TO); + compose_entry_set(compose, msginfo->subject, COMPOSE_ENTRY_SUBJECT); +} + +static void compose_insert_sig(Compose *compose, gboolean replace) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + gint cur_pos; + + g_return_if_fail(compose->account != NULL); + + buffer = gtk_text_view_get_buffer(text); + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + cur_pos = gtk_text_iter_get_offset(&iter); + + if (replace) + gtk_text_buffer_get_end_iter(buffer, &iter); + + if (replace && compose->sig_str) { + GtkTextIter first_iter, start_iter, end_iter; + gboolean found; + + gtk_text_buffer_get_start_iter(buffer, &first_iter); + + if (compose->sig_str[0] == '\0') + found = FALSE; + else + found = gtk_text_iter_forward_search + (&first_iter, compose->sig_str, + GTK_TEXT_SEARCH_TEXT_ONLY, + &start_iter, &end_iter, NULL); + + if (found) + gtk_text_buffer_delete(buffer, &start_iter, &end_iter); + } + + g_free(compose->sig_str); + compose->sig_str = compose_get_signature_str(compose); + if (compose->sig_str) { + if (!replace) + gtk_text_buffer_insert(buffer, &iter, "\n\n", 2); + gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1); + } else + compose->sig_str = g_strdup(""); + + if (cur_pos > gtk_text_buffer_get_char_count(buffer)) + cur_pos = gtk_text_buffer_get_char_count(buffer); + + gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos); + gtk_text_buffer_move_mark(buffer, mark, &iter); +} + +static gchar *compose_get_signature_str(Compose *compose) +{ + gchar *sig_body = NULL; + gchar *sig_str = NULL; + gchar *utf8_sig_str = NULL; + + g_return_val_if_fail(compose->account != NULL, NULL); + + if (!compose->account->sig_path) + return NULL; + + if (compose->account->sig_type == SIG_FILE) { + if (!is_file_or_fifo_exist(compose->account->sig_path)) { + g_warning("can't open signature file: %s\n", + compose->account->sig_path); + return NULL; + } + } + + if (compose->account->sig_type == SIG_COMMAND) + sig_body = get_command_output(compose->account->sig_path); + else { + gchar *tmp; + + tmp = file_read_to_str(compose->account->sig_path); + if (!tmp) + return NULL; + sig_body = normalize_newlines(tmp); + g_free(tmp); + } + + if (prefs_common.sig_sep) { + sig_str = g_strconcat(prefs_common.sig_sep, "\n", sig_body, + NULL); + g_free(sig_body); + } else + sig_str = sig_body; + + if (sig_str) { + utf8_sig_str = conv_codeset_strdup + (sig_str, conv_get_locale_charset_str(), CS_UTF_8); + g_free(sig_str); + } + + return utf8_sig_str; +} + +static void compose_insert_file(Compose *compose, const gchar *file) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + const gchar *cur_encoding; + gchar buf[BUFFSIZE]; + gint len; + FILE *fp; + + g_return_if_fail(file != NULL); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return; + } + + buffer = gtk_text_view_get_buffer(text); + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + + cur_encoding = conv_get_locale_charset_str(); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + gchar *str; + + str = conv_codeset_strdup(buf, cur_encoding, CS_UTF_8); + if (!str) continue; + + /* strip if DOS/Windows file, + replace with if Macintosh file. */ + strcrchomp(str); + len = strlen(str); + if (len > 0 && str[len - 1] != '\n') { + while (--len >= 0) + if (str[len] == '\r') str[len] = '\n'; + } + + gtk_text_buffer_insert(buffer, &iter, str, -1); + g_free(str); + } + + fclose(fp); +} + +static void compose_attach_append(Compose *compose, const gchar *file, + const gchar *filename, + const gchar *content_type) +{ + AttachInfo *ainfo; + gchar *text[N_ATTACH_COLS]; + FILE *fp; + off_t size; + gint row; + + g_return_if_fail(file != NULL); + g_return_if_fail(*file != '\0'); + + if (!is_file_exist(file)) { + g_warning(_("File %s doesn't exist\n"), file); + return; + } + if ((size = get_file_size(file)) < 0) { + g_warning(_("Can't get file size of %s\n"), file); + return; + } + if (size == 0) { + alertpanel_notice(_("File %s is empty."), file); + return; + } + if ((fp = fopen(file, "rb")) == NULL) { + alertpanel_error(_("Can't read %s."), file); + return; + } + fclose(fp); + + compose_changed_cb(NULL, compose); + + if (!compose->use_attach) { + GtkItemFactory *ifactory; + + ifactory = gtk_item_factory_from_widget(compose->menubar); + menu_set_active(ifactory, "/View/Attachment", TRUE); + } + + ainfo = g_new0(AttachInfo, 1); + ainfo->file = g_strdup(file); + + if (content_type) { + ainfo->content_type = g_strdup(content_type); + if (!strcasecmp(content_type, "message/rfc822")) { + MsgInfo *msginfo; + MsgFlags flags = {0, 0}; + const gchar *name; + + if (procmime_get_encoding_for_text_file(file) == ENC_7BIT) + ainfo->encoding = ENC_7BIT; + else + ainfo->encoding = ENC_8BIT; + + msginfo = procheader_parse_file(file, flags, FALSE); + if (msginfo && msginfo->subject) + name = msginfo->subject; + else + name = g_basename(filename ? filename : file); + + ainfo->name = g_strdup_printf(_("Message: %s"), name); + + procmsg_msginfo_free(msginfo); + } else { + if (!g_strncasecmp(content_type, "text", 4)) + ainfo->encoding = procmime_get_encoding_for_text_file(file); + else + ainfo->encoding = ENC_BASE64; + ainfo->name = g_strdup + (g_basename(filename ? filename : file)); + } + } else { + ainfo->content_type = procmime_get_mime_type(file); + if (!ainfo->content_type) { + ainfo->content_type = + g_strdup("application/octet-stream"); + ainfo->encoding = ENC_BASE64; + } else if (!g_strncasecmp(ainfo->content_type, "text", 4)) + ainfo->encoding = + procmime_get_encoding_for_text_file(file); + else + ainfo->encoding = ENC_BASE64; + ainfo->name = g_strdup(g_basename(filename ? filename : file)); + } + ainfo->size = size; + + text[COL_MIMETYPE] = ainfo->content_type; + text[COL_SIZE] = to_human_readable(size); + text[COL_NAME] = ainfo->name; + + row = gtk_clist_append(GTK_CLIST(compose->attach_clist), text); + gtk_clist_set_row_data(GTK_CLIST(compose->attach_clist), row, ainfo); +} + +#define IS_FIRST_PART_TEXT(info) \ + ((info->mime_type == MIME_TEXT || info->mime_type == MIME_TEXT_HTML) || \ + (info->mime_type == MIME_MULTIPART && info->content_type && \ + !strcasecmp(info->content_type, "multipart/alternative") && \ + (info->children && \ + (info->children->mime_type == MIME_TEXT || \ + info->children->mime_type == MIME_TEXT_HTML)))) + +static void compose_attach_parts(Compose *compose, MsgInfo *msginfo) +{ + MimeInfo *mimeinfo; + MimeInfo *child; + gchar *infile; + gchar *outfile; + + mimeinfo = procmime_scan_message(msginfo); + if (!mimeinfo) return; + + /* skip first text (presumably message body) */ + child = mimeinfo->children; + if (!child || IS_FIRST_PART_TEXT(mimeinfo)) { + procmime_mimeinfo_free_all(mimeinfo); + return; + } + if (IS_FIRST_PART_TEXT(child)) + child = child->next; + + infile = procmsg_get_message_file_path(msginfo); + + while (child != NULL) { + if (child->children || child->mime_type == MIME_MULTIPART) { + child = procmime_mimeinfo_next(child); + continue; + } + + outfile = procmime_get_tmp_file_name(child); + if (procmime_get_part(outfile, infile, child) < 0) + g_warning(_("Can't get the part of multipart message.")); + else + compose_attach_append + (compose, outfile, + child->filename ? child->filename : child->name, + child->content_type); + + child = child->next; + } + + g_free(infile); + procmime_mimeinfo_free_all(mimeinfo); +} + +#undef IS_FIRST_PART_TEXT + +#define CHAR_BUF_SIZE 8 + +#define GET_CHAR(iter_p, buf, len) \ +{ \ + GtkTextIter end_iter; \ + gchar *tmp; \ + end_iter = *iter_p; \ + \ + gtk_text_iter_forward_char(&end_iter); \ + tmp = gtk_text_buffer_get_text(textbuf, iter_p, &end_iter, FALSE); \ + if (tmp) { \ + glong items_read, items_witten; \ + GError *error = NULL; \ + gunichar *wide_char; \ + \ + strncpy2(buf, tmp, CHAR_BUF_SIZE); \ + wide_char = g_utf8_to_ucs4(tmp, -1, \ + &items_read, &items_witten, \ + &error); \ + if (error != NULL) { \ + g_warning("%s\n", error->message); \ + g_error_free(error); \ + } \ + len = wide_char && g_unichar_iswide(*wide_char) ? 2 : 1; \ + g_free(wide_char); \ + } else { \ + buf[0] = '\0'; \ + len = 1; \ + } \ + g_free(tmp); \ +} + +#define INDENT_CHARS ">|#" +#define SPACE_CHARS " \t" + +#warning FIXME_GTK2 +static void compose_wrap_line(Compose *compose) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *textbuf; + GtkTextMark *mark; + GtkTextIter insert_iter, iter; + gint ch_len, last_ch_len; + gchar cbuf[CHAR_BUF_SIZE], last_ch; + guint text_len; + gint p_start, p_end; + gint line_pos, cur_pos; + gint line_len, cur_len; + gboolean line_end; + gboolean quoted; + + textbuf = gtk_text_view_get_buffer(text); + text_len = gtk_text_buffer_get_char_count(textbuf); + mark = gtk_text_buffer_get_insert(textbuf); + gtk_text_buffer_get_iter_at_mark(textbuf, &insert_iter, mark); + cur_pos = gtk_text_iter_get_offset(&insert_iter); + GET_CHAR(&insert_iter, cbuf, ch_len); + + if ((ch_len == 1 && *cbuf == '\n') || cur_pos == text_len) { + GtkTextIter prev_iter; + + if (cur_pos == 0) + return; /* on the paragraph mark */ + prev_iter = insert_iter; + gtk_text_iter_backward_char(&prev_iter); + GET_CHAR(&prev_iter, cbuf, ch_len); + if (ch_len == 1 && *cbuf == '\n') + return; /* on the paragraph mark */ + } + + /* find paragraph start. */ + line_end = quoted = FALSE; + for (iter = insert_iter; gtk_text_iter_backward_char(&iter); ) { + GET_CHAR(&iter, cbuf, ch_len); + if (ch_len == 1 && *cbuf == '\n') { + if (quoted) + return; /* quoted part */ + if (line_end) { + gtk_text_iter_forward_chars(&iter, 2); + break; + } + line_end = TRUE; + } else { + if (ch_len == 1 && strchr(INDENT_CHARS, *cbuf)) + quoted = TRUE; + else if (ch_len != 1 || !isspace(*(guchar *)cbuf)) + quoted = FALSE; + + line_end = FALSE; + } + } + p_start = gtk_text_iter_get_offset(&iter); + + /* find paragraph end. */ + line_end = FALSE; + for (iter = insert_iter; gtk_text_iter_forward_char(&iter); ) { + GET_CHAR(&iter, cbuf, ch_len); + if (ch_len == 1 && *cbuf == '\n') { + if (line_end) { + gtk_text_iter_backward_char(&iter); + break; + } + line_end = TRUE; + } else { + if (line_end && ch_len == 1 && + strchr(INDENT_CHARS, *cbuf)) + return; /* quoted part */ + + line_end = FALSE; + } + } + p_end = gtk_text_iter_get_offset(&iter); + + if (p_end >= text_len) + p_end = text_len; + + if (p_start >= p_end) + return; + + line_len = cur_len = 0; + last_ch_len = 0; + last_ch = '\0'; + line_pos = p_start; + for (cur_pos = p_start; cur_pos < p_end; cur_pos++) { + gboolean space = FALSE; + + gtk_text_buffer_get_iter_at_offset(textbuf, &iter, cur_pos); + GET_CHAR(&iter, cbuf, ch_len); + + if (ch_len < 0) { + cbuf[0] = '\0'; + ch_len = 1; + } + + if (ch_len == 1 && isspace(*(guchar *)cbuf)) + space = TRUE; + + if (ch_len == 1 && *cbuf == '\n') { + gboolean replace = FALSE; + GtkTextIter next_iter = iter; + + gtk_text_iter_forward_char(&next_iter); + + if (last_ch_len == 1 && !isspace((guchar)last_ch)) { + if (cur_pos + 1 < p_end) { + GET_CHAR(&next_iter, cbuf, ch_len); + if (ch_len == 1 && + !isspace(*(guchar *)cbuf)) + replace = TRUE; + } + } + gtk_text_buffer_delete(textbuf, &iter, &next_iter); + if (replace) { + gtk_text_buffer_insert(textbuf, &iter, " ", 1); + space = TRUE; + } else { + p_end--; + cur_pos--; + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, cur_pos); + continue; + } + } + + last_ch_len = ch_len; + last_ch = *cbuf; + + if (space) { + line_pos = cur_pos + 1; + line_len = cur_len + ch_len; + } + + gtk_text_buffer_get_iter_at_offset(textbuf, &iter, line_pos); + + if (cur_len + ch_len > prefs_common.linewrap_len && + line_len > 0) { + gint tlen = ch_len; + GtkTextIter prev_iter = iter; + + gtk_text_iter_backward_char(&prev_iter); + + if (ch_len == 1 && isspace(*(guchar *)cbuf)) { + gtk_text_buffer_delete + (textbuf, &prev_iter, &iter); + iter = prev_iter; + p_end--; + cur_pos--; + line_pos--; + cur_len--; + line_len--; + } + ch_len = tlen; + + gtk_text_buffer_insert(textbuf, &iter, "\n", 1); + p_end++; + cur_pos++; + line_pos++; + cur_len = cur_len - line_len + ch_len; + line_len = 0; + continue; + } + + if (ch_len > 1) { + line_pos = cur_pos + 1; + line_len = cur_len + ch_len; + } + cur_len += ch_len; + } +} + +#undef WRAP_DEBUG +#ifdef WRAP_DEBUG +/* Darko: used when I debug wrapping */ +void dump_text(GtkTextBuffer *textbuf, int pos, int tlen, int breakoncr) +{ + gint clen; + gchar cbuf[CHAR_BUF_SIZE]; + + printf("%d [", pos); + gtk_text_buffer_get_iter_at_offset(textbuf, &iter, pos); + gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter, pos + tlen); + for (; gtk_text_iter_forward_char(&iter) && + gtk_text_iter_compare(&iter, &end_iter) < 0; ) { + GET_CHAR(&iter, cbuf, clen); + if (clen < 0) break; + if (breakoncr && clen == 1 && cbuf[0] == '\n') + break; + fwrite(cbuf, clen, 1, stdout); + } + printf("]\n"); +} +#endif + +typedef enum { + WAIT_FOR_SPACE, + WAIT_FOR_INDENT_CHAR, + WAIT_FOR_INDENT_CHAR_OR_SPACE +} IndentState; + +/* return indent length, we allow: + > followed by spaces/tabs + | followed by spaces/tabs + uppercase characters immediately followed by >, + and the repeating sequences of the above */ +/* return indent length */ +static guint get_indent_length(GtkTextBuffer *textbuf, guint start_pos, + guint text_len) +{ + guint i_len = 0; + guint i, ch_len, alnum_cnt = 0; + IndentState state = WAIT_FOR_INDENT_CHAR; + gchar cbuf[CHAR_BUF_SIZE]; + gboolean is_space; + gboolean is_indent; + +#warning FIXME_GTK2 use GtkTextIter + for (i = start_pos; i < text_len; i++) { + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset(textbuf, &iter, i); + GET_CHAR(&iter, cbuf, ch_len); + if (ch_len > 1) + break; + + if (cbuf[0] == '\n') + break; + + is_indent = strchr(INDENT_CHARS, cbuf[0]) ? TRUE : FALSE; + is_space = strchr(SPACE_CHARS, cbuf[0]) ? TRUE : FALSE; + + switch (state) { + case WAIT_FOR_SPACE: + if (is_space == FALSE) + goto out; + state = WAIT_FOR_INDENT_CHAR_OR_SPACE; + break; + case WAIT_FOR_INDENT_CHAR_OR_SPACE: + if (is_indent == FALSE && is_space == FALSE && + !isupper((guchar)cbuf[0])) + goto out; + if (is_space == TRUE) { + alnum_cnt = 0; + state = WAIT_FOR_INDENT_CHAR_OR_SPACE; + } else if (is_indent == TRUE) { + alnum_cnt = 0; + state = WAIT_FOR_SPACE; + } else { + alnum_cnt++; + state = WAIT_FOR_INDENT_CHAR; + } + break; + case WAIT_FOR_INDENT_CHAR: + if (is_indent == FALSE && !isupper((guchar)cbuf[0])) + goto out; + if (is_indent == TRUE) { + alnum_cnt = 0; + state = WAIT_FOR_SPACE; + } else { + alnum_cnt++; + } + break; + } + + i_len++; + } + +out: + if ((i_len > 0) && (state == WAIT_FOR_INDENT_CHAR)) + i_len -= alnum_cnt; + + return i_len; +} + +/* insert quotation string when line was wrapped */ +static guint ins_quote(GtkTextBuffer *textbuf, GtkTextIter *iter, + guint indent_len, + guint prev_line_pos, guint text_len, + gchar *quote_fmt) +{ + guint ins_len = 0; + + if (indent_len) { + GtkTextIter iter1, iter2; + gchar *text; + + gtk_text_buffer_get_iter_at_offset(textbuf, &iter1, + prev_line_pos); + gtk_text_buffer_get_iter_at_offset(textbuf, &iter2, + prev_line_pos + indent_len); + text = gtk_text_buffer_get_text(textbuf, &iter1, &iter2, FALSE); + if (!text) return 0; + + gtk_text_buffer_insert(textbuf, iter, text, -1); + ins_len = g_utf8_strlen(text, -1); + + g_free(text); + } + + return ins_len; +} + +/* check if we should join the next line */ +static gboolean join_next_line_is_needed(GtkTextBuffer *textbuf, + guint start_pos, guint tlen, + guint prev_ilen, gboolean autowrap) +{ + guint indent_len, ch_len; + gboolean do_join = FALSE; + gchar cbuf[CHAR_BUF_SIZE]; + + indent_len = get_indent_length(textbuf, start_pos, tlen); + + if ((autowrap || indent_len > 0) && indent_len == prev_ilen) { + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset(textbuf, &iter, + start_pos + indent_len); + GET_CHAR(&iter, cbuf, ch_len); + if (ch_len > 0 && (cbuf[0] != '\n')) + do_join = TRUE; + } + + return do_join; +} + +static void compose_wrap_line_all(Compose *compose) +{ + compose_wrap_line_all_full(compose, FALSE); +} + +static void compose_wrap_line_all_full(Compose *compose, gboolean autowrap) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *textbuf; + GtkTextIter iter, end_iter; + guint tlen; + guint line_pos = 0, cur_pos = 0, p_pos = 0; + gint line_len = 0, cur_len = 0; + gint ch_len; + gboolean is_new_line = TRUE, do_delete = FALSE; + guint i_len = 0; + gboolean linewrap_quote = TRUE; + gboolean set_editable_pos = FALSE; + gint editable_pos = 0; + guint linewrap_len = prefs_common.linewrap_len; + gchar *qfmt = prefs_common.quotemark; + gchar cbuf[CHAR_BUF_SIZE]; + + textbuf = gtk_text_view_get_buffer(text); + tlen = gtk_text_buffer_get_char_count(textbuf); + + for (; cur_pos < tlen; cur_pos++) { + /* mark position of new line - needed for quotation wrap */ + if (is_new_line) { + if (linewrap_quote) + i_len = get_indent_length + (textbuf, cur_pos, tlen); + + is_new_line = FALSE; + p_pos = cur_pos; + } + + gtk_text_buffer_get_iter_at_offset(textbuf, &iter, cur_pos); + GET_CHAR(&iter, cbuf, ch_len); + + /* fix line length for tabs */ + if (ch_len == 1 && *cbuf == '\t') { + guint tab_width = 8; + guint tab_offset = line_len % tab_width; + + if (tab_offset) { + line_len += tab_width - tab_offset - 1; + cur_len = line_len; + } + } + + /* we have encountered line break */ + if (ch_len == 1 && *cbuf == '\n') { + gint clen; + gchar cb[CHAR_BUF_SIZE]; + + /* should we join the next line */ + if ((autowrap || i_len != cur_len) && do_delete && + join_next_line_is_needed + (textbuf, cur_pos + 1, tlen, i_len, autowrap)) + do_delete = TRUE; + else + do_delete = FALSE; + + /* skip delete if it is continuous URL */ + if (do_delete && (line_pos - p_pos <= i_len) && + gtkut_text_buffer_is_uri_string(textbuf, line_pos, + tlen)) + do_delete = FALSE; + + /* should we delete to perform smart wrapping */ + if (line_len < linewrap_len && do_delete) { + /* get rid of newline */ + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, cur_pos); + end_iter = iter; + gtk_text_iter_forward_char(&end_iter); + gtk_text_buffer_delete + (textbuf, &iter, &end_iter); + tlen--; + + /* if text starts with quote fmt or with + indent string, delete them */ + if (i_len) { + guint ilen; + ilen = gtkut_text_buffer_str_compare_n + (textbuf, cur_pos, p_pos, i_len, + tlen); + if (ilen) { + end_iter = iter; + gtk_text_iter_forward_chars + (&end_iter, ilen); + gtk_text_buffer_delete + (textbuf, + &iter, &end_iter); + tlen -= ilen; + } + } + + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, cur_pos); + GET_CHAR(&iter, cb, clen); + /* insert space between the next line */ + if (cur_pos > 0) { + gint clen_prev; + gchar cb_prev[MB_LEN_MAX]; + GtkTextIter iter_prev = iter; + + gtk_text_iter_backward_char(&iter_prev); + GET_CHAR(&iter_prev, cb_prev, + clen_prev); + if ((clen_prev != clen && clen > 1) || + (clen == 1 && + !isspace((guchar)cb[0]))) { + gtk_text_buffer_insert + (textbuf, &iter, + " ", 1); + tlen++; + } + } + + /* and start over with current line */ + cur_pos = p_pos - 1; + line_pos = cur_pos; + line_len = cur_len = 0; + do_delete = FALSE; + is_new_line = TRUE; + /* move beginning of line if we are on + linebreak */ +#warning FIXME_GTK2 + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, line_pos); + GET_CHAR(&iter, cb, clen); + if (clen == 1 && *cb == '\n') + line_pos++; + continue; + } + + /* mark new line beginning */ + line_pos = cur_pos + 1; + line_len = cur_len = 0; + do_delete = FALSE; + is_new_line = TRUE; + continue; + } + + if (ch_len < 0) { + cbuf[0] = '\0'; + ch_len = 1; + } + + /* possible line break */ + if (ch_len == 1 && isspace(*(guchar *)cbuf)) { + line_pos = cur_pos + 1; + line_len = cur_len + ch_len; + } + + /* are we over wrapping length set in preferences ? */ + if (cur_len + ch_len > linewrap_len) { + gint clen; + + /* force wrapping if it is one long word but not URL */ + if (line_pos - p_pos <= i_len) + if (!gtkut_text_buffer_is_uri_string + (textbuf, line_pos, tlen)) + line_pos = cur_pos - 1; + + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, line_pos); + GET_CHAR(&iter, cbuf, clen); + + /* if next character is space delete it */ + if (clen == 1 && isspace(*(guchar *)cbuf)) { + if (p_pos + i_len != line_pos || + !gtkut_text_buffer_is_uri_string + (textbuf, line_pos, tlen)) { + /* workaround for correct cursor + position */ + if (set_editable_pos == FALSE) { + GtkTextMark *mark; + + mark = gtk_text_buffer_get_insert(textbuf); + gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark); + editable_pos = gtk_text_iter_get_offset(&iter); + if (editable_pos == line_pos) + set_editable_pos = TRUE; + } + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, line_pos); + end_iter = iter; + gtk_text_iter_backward_char(&end_iter); + gtk_text_buffer_delete + (textbuf, &iter, &end_iter); + tlen--; + cur_pos--; + line_pos--; + cur_len--; + line_len--; + } + } + + /* if it is URL at beginning of line don't wrap */ + if (p_pos + i_len == line_pos && + gtkut_text_buffer_is_uri_string(textbuf, + line_pos, tlen)) { + continue; + } + + /* insert CR */ + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, line_pos); + gtk_text_buffer_insert(textbuf, &iter, "\n", 1); + tlen++; + line_pos++; + /* for loop will increase it */ + cur_pos = line_pos - 1; + /* start over with current line */ + is_new_line = TRUE; + line_len = cur_len = 0; + if (autowrap || i_len > 0) + do_delete = TRUE; + else + do_delete = FALSE; + + /* should we insert quotation ? */ + if (linewrap_quote && i_len) { + /* only if line is not already quoted */ + if (!gtkut_text_buffer_str_compare + (textbuf, line_pos, tlen, qfmt)) { + guint ins_len; + + if (line_pos - p_pos > i_len) { + ins_len = ins_quote + (textbuf, &iter, i_len, + p_pos, tlen, qfmt); + tlen += ins_len; + } + } + } + continue; + } + + if (ch_len > 1) { + line_pos = cur_pos + 1; + line_len = cur_len + ch_len; + } + /* advance to next character in buffer */ + cur_len += ch_len; + } + + if (set_editable_pos && editable_pos <= tlen) { + gtk_text_buffer_get_iter_at_offset + (textbuf, &iter, editable_pos); + gtk_text_buffer_place_cursor(textbuf, &iter); + } +} + +#undef GET_CHAR +#undef CHAR_BUF_SIZE + +static void compose_set_title(Compose *compose) +{ + gchar *str; + gchar *edited; + + edited = compose->modified ? _(" [Edited]") : ""; + if (compose->account && compose->account->address) + str = g_strdup_printf(_("%s - Compose message%s"), + compose->account->address, edited); + else + str = g_strdup_printf(_("Compose message%s"), edited); + gtk_window_set_title(GTK_WINDOW(compose->window), str); + g_free(str); +} + +static void compose_select_account(Compose *compose, PrefsAccount *account, + gboolean init) +{ + GtkItemFactory *ifactory; + + g_return_if_fail(account != NULL); + + compose->account = account; + + compose_set_title(compose); + + ifactory = gtk_item_factory_from_widget(compose->menubar); + + if (account->protocol == A_NNTP) { + gtk_widget_show(compose->newsgroups_hbox); + gtk_widget_show(compose->newsgroups_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 2, 4); + compose->use_newsgroups = TRUE; + + menu_set_active(ifactory, "/View/To", FALSE); + menu_set_sensitive(ifactory, "/View/To", TRUE); + menu_set_active(ifactory, "/View/Cc", FALSE); + menu_set_sensitive(ifactory, "/View/Cc", TRUE); + menu_set_sensitive(ifactory, "/View/Followup to", TRUE); + } else { + gtk_widget_hide(compose->newsgroups_hbox); + gtk_widget_hide(compose->newsgroups_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 2, 0); + gtk_widget_queue_resize(compose->table_vbox); + compose->use_newsgroups = FALSE; + + menu_set_active(ifactory, "/View/To", TRUE); + menu_set_sensitive(ifactory, "/View/To", FALSE); + menu_set_active(ifactory, "/View/Cc", TRUE); + menu_set_sensitive(ifactory, "/View/Cc", FALSE); + menu_set_active(ifactory, "/View/Followup to", FALSE); + menu_set_sensitive(ifactory, "/View/Followup to", FALSE); + } + + if (account->set_autocc) { + compose_entry_show(compose, COMPOSE_ENTRY_CC); + if (account->auto_cc && compose->mode != COMPOSE_REEDIT) + compose_entry_set(compose, account->auto_cc, + COMPOSE_ENTRY_CC); + } + if (account->set_autobcc) { + compose_entry_show(compose, COMPOSE_ENTRY_BCC); + if (account->auto_bcc && compose->mode != COMPOSE_REEDIT) + compose_entry_set(compose, account->auto_bcc, + COMPOSE_ENTRY_BCC); + } + if (account->set_autoreplyto) { + compose_entry_show(compose, COMPOSE_ENTRY_REPLY_TO); + if (account->auto_replyto && compose->mode != COMPOSE_REEDIT) + compose_entry_set(compose, account->auto_replyto, + COMPOSE_ENTRY_REPLY_TO); + } + +#if USE_GPGME + if (account->default_sign) + menu_set_active(ifactory, "/Tools/PGP Sign", TRUE); + if (account->default_encrypt) + menu_set_active(ifactory, "/Tools/PGP Encrypt", TRUE); +#endif /* USE_GPGME */ + + if (!init && compose->mode != COMPOSE_REDIRECT && prefs_common.auto_sig) + compose_insert_sig(compose, TRUE); +} + +static gboolean compose_check_for_valid_recipient(Compose *compose) +{ + const gchar *to_raw = "", *cc_raw = "", *bcc_raw = ""; + const gchar *newsgroups_raw = ""; + gchar *to, *cc, *bcc; + gchar *newsgroups; + + if (compose->use_to) + to_raw = gtk_entry_get_text(GTK_ENTRY(compose->to_entry)); + if (compose->use_cc) + cc_raw = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry)); + if (compose->use_bcc) + bcc_raw = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry)); + if (compose->use_newsgroups) + newsgroups_raw = gtk_entry_get_text + (GTK_ENTRY(compose->newsgroups_entry)); + + Xstrdup_a(to, to_raw, return FALSE); + Xstrdup_a(cc, cc_raw, return FALSE); + Xstrdup_a(bcc, bcc_raw, return FALSE); + Xstrdup_a(newsgroups, newsgroups_raw, return FALSE); + g_strstrip(to); + g_strstrip(cc); + g_strstrip(bcc); + g_strstrip(newsgroups); + + if (*to == '\0' && *cc == '\0' && *bcc == '\0' && *newsgroups == '\0') + return FALSE; + else + return TRUE; +} + +static gboolean compose_check_entries(Compose *compose) +{ + const gchar *str; + + if (compose_check_for_valid_recipient(compose) == FALSE) { + alertpanel_error(_("Recipient is not specified.")); + return FALSE; + } + + str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry)); + if (*str == '\0') { + AlertValue aval; + + aval = alertpanel(_("Send"), + _("Subject is empty. Send it anyway?"), + _("Yes"), _("No"), NULL); + if (aval != G_ALERTDEFAULT) + return FALSE; + } + + return TRUE; +} + +static gint compose_send(Compose *compose) +{ + gchar tmp[MAXPATHLEN + 1]; + gint ok = 0; + static gboolean lock = FALSE; + + if (lock) return 1; + + g_return_val_if_fail(compose->account != NULL, -1); + + lock = TRUE; + + if (compose_check_entries(compose) == FALSE) { + lock = FALSE; + return 1; + } + + if (!main_window_toggle_online_if_offline(main_window_get())) { + lock = FALSE; + return 1; + } + + /* write to temporary file */ + g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.%p", + get_tmp_dir(), G_DIR_SEPARATOR, compose); + + if (compose->mode == COMPOSE_REDIRECT) { + if (compose_redirect_write_to_file(compose, tmp) < 0) { + lock = FALSE; + return -1; + } + } else { + if (prefs_common.linewrap_at_send) + compose_wrap_line_all(compose); + + if (compose_write_to_file(compose, tmp, FALSE) < 0) { + lock = FALSE; + return -1; + } + } + + if (!compose->to_list && !compose->newsgroup_list) { + g_warning(_("can't get recipient list.")); + unlink(tmp); + lock = FALSE; + return -1; + } + + if (compose->to_list) { + PrefsAccount *ac; + + if (compose->account->protocol != A_NNTP) + ac = compose->account; + else { + ac = account_find_from_address(compose->account->address); + if (!ac) { + if (cur_account && cur_account->protocol != A_NNTP) + ac = cur_account; + else + ac = account_get_default(); + } + if (!ac || ac->protocol == A_NNTP) { + alertpanel_error(_("Account for sending mail is not specified.\n" + "Please select a mail account before sending.")); + unlink(tmp); + lock = FALSE; + return -1; + } + } + ok = send_message(tmp, ac, compose->to_list); + statusbar_pop_all(); + } + + if (ok == 0 && compose->newsgroup_list) { + ok = news_post(FOLDER(compose->account->folder), tmp); + if (ok < 0) { + alertpanel_error(_("Error occurred while posting the message to %s ."), + compose->account->nntp_server); + unlink(tmp); + lock = FALSE; + return -1; + } + } + + if (ok == 0) { + if (compose->mode == COMPOSE_REEDIT) { + compose_remove_reedit_target(compose); + if (compose->targetinfo) + folderview_update_item + (compose->targetinfo->folder, TRUE); + } + /* save message to outbox */ + if (prefs_common.savemsg) { + FolderItem *outbox; + + outbox = account_get_special_folder + (compose->account, F_OUTBOX); + if (procmsg_save_to_outbox(outbox, tmp, FALSE) < 0) + alertpanel_error + (_("Can't save the message to outbox.")); + else + folderview_update_item(outbox, TRUE); + } + } + + unlink(tmp); + lock = FALSE; + return ok; +} + +#if USE_GPGME +/* interfaces to rfc2015 to keep out the prefs stuff there. + * returns 0 on success and -1 on error. */ +static gint compose_create_signers_list(Compose *compose, GSList **pkey_list) +{ + const gchar *key_id = NULL; + GSList *key_list; + + switch (compose->account->sign_key) { + case SIGN_KEY_DEFAULT: + *pkey_list = NULL; + return 0; + case SIGN_KEY_BY_FROM: + key_id = compose->account->address; + break; + case SIGN_KEY_CUSTOM: + key_id = compose->account->sign_key_id; + break; + default: + break; + } + + key_list = rfc2015_create_signers_list(key_id); + + if (!key_list) { + alertpanel_error(_("Could not find any key associated with " + "currently selected key id `%s'."), key_id); + return -1; + } + + *pkey_list = key_list; + return 0; +} + +/* clearsign message body text */ +static gint compose_clearsign_text(Compose *compose, gchar **text) +{ + GSList *key_list; + gchar *tmp_file; + + tmp_file = get_tmp_file(); + if (str_write_to_file(*text, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (compose_create_signers_list(compose, &key_list) < 0 || + rfc2015_clearsign(tmp_file, key_list) < 0) { + unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(*text); + *text = file_read_to_str(tmp_file); + unlink(tmp_file); + g_free(tmp_file); + if (*text == NULL) + return -1; + + return 0; +} +#endif /* USE_GPGME */ + +static gint compose_write_to_file(Compose *compose, const gchar *file, + gboolean is_draft) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + FILE *fp; + size_t len; + gchar *chars; + gchar *buf; + gchar *canon_buf; + const gchar *out_codeset; + EncodingType encoding; + + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + /* chmod for security */ + if (change_file_mode_rw(fp, file) < 0) { + FILE_OP_ERROR(file, "chmod"); + g_warning(_("can't change file mode\n")); + } + + /* get all composed text */ + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text)); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + if (is_ascii_str(chars)) { + buf = chars; + chars = NULL; + out_codeset = CS_US_ASCII; + encoding = ENC_7BIT; + } else { + const gchar *src_codeset; + + out_codeset = conv_get_outgoing_charset_str(); + if (!strcasecmp(out_codeset, CS_US_ASCII)) + out_codeset = CS_ISO_8859_1; + + if (prefs_common.encoding_method == CTE_BASE64) + encoding = ENC_BASE64; + else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE) + encoding = ENC_QUOTED_PRINTABLE; + else if (prefs_common.encoding_method == CTE_8BIT) + encoding = ENC_8BIT; + else + encoding = procmime_get_encoding_for_charset(out_codeset); + +#if USE_GPGME + if (!is_draft && + compose->use_signing && !compose->account->clearsign && + encoding == ENC_8BIT) + encoding = ENC_BASE64; +#endif + + src_codeset = CS_UTF_8; + + debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n", + src_codeset, out_codeset, procmime_get_encoding_str(encoding)); + + buf = conv_codeset_strdup(chars, src_codeset, out_codeset); + if (!buf) { + AlertValue aval; + gchar *msg; + + msg = g_strdup_printf(_("Can't convert the character encoding of the message from\n" + "%s to %s.\n" + "Send it anyway?"), src_codeset, out_codeset); + aval = alertpanel + (_("Error"), msg, _("Yes"), _("+No"), NULL); + g_free(msg); + + if (aval != G_ALERTDEFAULT) { + g_free(chars); + fclose(fp); + unlink(file); + return -1; + } else { + buf = chars; + out_codeset = src_codeset; + chars = NULL; + } + } + } + g_free(chars); + + canon_buf = canonicalize_str(buf); + g_free(buf); + buf = canon_buf; + +#if USE_GPGME + if (!is_draft && compose->use_signing && compose->account->clearsign) { + if (compose_clearsign_text(compose, &buf) < 0) { + g_warning("clearsign failed\n"); + fclose(fp); + unlink(file); + g_free(buf); + return -1; + } + } +#endif + + /* write headers */ + if (compose_write_headers + (compose, fp, out_codeset, encoding, is_draft) < 0) { + g_warning(_("can't write headers\n")); + fclose(fp); + unlink(file); + g_free(buf); + return -1; + } + + if (compose->use_attach && + GTK_CLIST(compose->attach_clist)->row_list) { +#if USE_GPGME + /* This prolog message is ignored by mime software and + * because it would make our signing/encryption task + * tougher, we don't emit it in that case */ + if (!compose->use_signing && !compose->use_encryption) +#endif + fputs("This is a multi-part message in MIME format.\n", fp); + + fprintf(fp, "\n--%s\n", compose->boundary); + fprintf(fp, "Content-Type: text/plain; charset=%s\n", + out_codeset); +#if USE_GPGME + if (compose->use_signing && !compose->account->clearsign) + fprintf(fp, "Content-Disposition: inline\n"); +#endif + fprintf(fp, "Content-Transfer-Encoding: %s\n", + procmime_get_encoding_str(encoding)); + fputc('\n', fp); + } + + /* write body */ + len = strlen(buf); + if (encoding == ENC_BASE64) { + gchar outbuf[B64_BUFFSIZE]; + gint i, l; + + for (i = 0; i < len; i += B64_LINE_SIZE) { + l = MIN(B64_LINE_SIZE, len - i); + base64_encode(outbuf, buf + i, l); + fputs(outbuf, fp); + fputc('\n', fp); + } + } else if (encoding == ENC_QUOTED_PRINTABLE) { + gchar *outbuf; + size_t outlen; + + outbuf = g_malloc(len * 4); + qp_encode_line(outbuf, buf); + outlen = strlen(outbuf); + if (fwrite(outbuf, sizeof(gchar), outlen, fp) != outlen) { + FILE_OP_ERROR(file, "fwrite"); + fclose(fp); + unlink(file); + g_free(outbuf); + g_free(buf); + return -1; + } + g_free(outbuf); + } else if (fwrite(buf, sizeof(gchar), len, fp) != len) { + FILE_OP_ERROR(file, "fwrite"); + fclose(fp); + unlink(file); + g_free(buf); + return -1; + } + g_free(buf); + + if (compose->use_attach && + GTK_CLIST(compose->attach_clist)->row_list) + compose_write_attach(compose, fp); + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + unlink(file); + return -1; + } + +#if USE_GPGME + if (is_draft) { + uncanonicalize_file_replace(file); + return 0; + } + + if ((compose->use_signing && !compose->account->clearsign) || + compose->use_encryption) { + if (canonicalize_file_replace(file) < 0) { + unlink(file); + return -1; + } + } + + if (compose->use_signing && !compose->account->clearsign) { + GSList *key_list; + + if (compose_create_signers_list(compose, &key_list) < 0 || + rfc2015_sign(file, key_list) < 0) { + unlink(file); + return -1; + } + } + if (compose->use_encryption) { + if (rfc2015_encrypt(file, compose->to_list, + compose->account->ascii_armored) < 0) { + unlink(file); + return -1; + } + } +#endif /* USE_GPGME */ + + uncanonicalize_file_replace(file); + + return 0; +} + +static gint compose_write_body_to_file(Compose *compose, const gchar *file) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + FILE *fp; + size_t len; + gchar *chars, *tmp; + + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + /* chmod for security */ + if (change_file_mode_rw(fp, file) < 0) { + FILE_OP_ERROR(file, "chmod"); + g_warning(_("can't change file mode\n")); + } + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text)); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + + chars = conv_codeset_strdup + (tmp, CS_UTF_8, conv_get_locale_charset_str()); + + g_free(tmp); + + if (!chars) { + fclose(fp); + unlink(file); + return -1; + } + + /* write body */ + len = strlen(chars); + if (fwrite(chars, sizeof(gchar), len, fp) != len) { + FILE_OP_ERROR(file, "fwrite"); + g_free(chars); + fclose(fp); + unlink(file); + return -1; + } + + g_free(chars); + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + unlink(file); + return -1; + } + return 0; +} + +static gint compose_redirect_write_to_file(Compose *compose, const gchar *file) +{ + FILE *fp; + FILE *fdest; + size_t len; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(compose->account != NULL, -1); + g_return_val_if_fail(compose->account->address != NULL, -1); + g_return_val_if_fail(compose->mode == COMPOSE_REDIRECT, -1); + g_return_val_if_fail(compose->targetinfo != NULL, -1); + + if ((fp = procmsg_open_message(compose->targetinfo)) == NULL) + return -1; + + if ((fdest = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + fclose(fp); + return -1; + } + + if (change_file_mode_rw(fdest, file) < 0) { + FILE_OP_ERROR(file, "chmod"); + g_warning(_("can't change file mode\n")); + } + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) == 0) { + if (g_strncasecmp(buf, "Return-Path:", + strlen("Return-Path:")) == 0 || + g_strncasecmp(buf, "Delivered-To:", + strlen("Delivered-To:")) == 0 || + g_strncasecmp(buf, "Received:", + strlen("Received:")) == 0 || + g_strncasecmp(buf, "Subject:", + strlen("Subject:")) == 0 || + g_strncasecmp(buf, "X-UIDL:", + strlen("X-UIDL:")) == 0) + continue; + + if (fputs(buf, fdest) == EOF) + goto error; + +#if 0 + if (g_strncasecmp(buf, "From:", strlen("From:")) == 0) { + fputs("\n (by way of ", fdest); + if (compose->account->name) { + compose_convert_header(buf, sizeof(buf), + compose->account->name, + strlen(" (by way of "), + FALSE); + fprintf(fdest, "%s <%s>", buf, + compose->account->address); + } else + fputs(compose->account->address, fdest); + fputs(")", fdest); + } +#endif + + if (fputs("\n", fdest) == EOF) + goto error; + } + + compose_redirect_write_headers(compose, fdest); + + while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) { + if (fwrite(buf, sizeof(gchar), len, fdest) != len) { + FILE_OP_ERROR(file, "fwrite"); + goto error; + } + } + + fclose(fp); + if (fclose(fdest) == EOF) { + FILE_OP_ERROR(file, "fclose"); + unlink(file); + return -1; + } + + return 0; +error: + fclose(fp); + fclose(fdest); + unlink(file); + + return -1; +} + +static gint compose_remove_reedit_target(Compose *compose) +{ + FolderItem *item; + MsgInfo *msginfo = compose->targetinfo; + + g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1); + if (!msginfo) return -1; + + item = msginfo->folder; + g_return_val_if_fail(item != NULL, -1); + + folder_item_scan(item); + if (procmsg_msg_exist(msginfo) && + (item->stype == F_DRAFT || item->stype == F_QUEUE)) { + if (folder_item_remove_msg(item, msginfo) < 0) { + g_warning(_("can't remove the old message\n")); + return -1; + } + } + + return 0; +} + +static gint compose_queue(Compose *compose, const gchar *file) +{ + FolderItem *queue; + gchar *tmp; + FILE *fp, *src_fp; + GSList *cur; + gchar buf[BUFFSIZE]; + gint num; + MsgFlags flag = {0, 0}; + + debug_print(_("queueing message...\n")); + g_return_val_if_fail(compose->to_list != NULL || + compose->newsgroup_list != NULL, + -1); + g_return_val_if_fail(compose->account != NULL, -1); + + tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(), + G_DIR_SEPARATOR, compose); + if ((fp = fopen(tmp, "wb")) == NULL) { + FILE_OP_ERROR(tmp, "fopen"); + g_free(tmp); + return -1; + } + if ((src_fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + fclose(fp); + unlink(tmp); + g_free(tmp); + return -1; + } + if (change_file_mode_rw(fp, tmp) < 0) { + FILE_OP_ERROR(tmp, "chmod"); + g_warning(_("can't change file mode\n")); + } + + /* queueing variables */ + fprintf(fp, "AF:\n"); + fprintf(fp, "NF:0\n"); + fprintf(fp, "PS:10\n"); + fprintf(fp, "SRH:1\n"); + fprintf(fp, "SFN:\n"); + fprintf(fp, "DSR:\n"); + if (compose->msgid) + fprintf(fp, "MID:<%s>\n", compose->msgid); + else + fprintf(fp, "MID:\n"); + fprintf(fp, "CFG:\n"); + fprintf(fp, "PT:0\n"); + fprintf(fp, "S:%s\n", compose->account->address); + fprintf(fp, "RQ:\n"); + if (compose->account->smtp_server) + fprintf(fp, "SSV:%s\n", compose->account->smtp_server); + else + fprintf(fp, "SSV:\n"); + if (compose->account->nntp_server) + fprintf(fp, "NSV:%s\n", compose->account->nntp_server); + else + fprintf(fp, "NSV:\n"); + fprintf(fp, "SSH:\n"); + if (compose->to_list) { + fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data); + for (cur = compose->to_list->next; cur != NULL; + cur = cur->next) + fprintf(fp, ",<%s>", (gchar *)cur->data); + fprintf(fp, "\n"); + } else + fprintf(fp, "R:\n"); + /* Sylpheed account ID */ + fprintf(fp, "AID:%d\n", compose->account->account_id); + fprintf(fp, "\n"); + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + if (fputs(buf, fp) == EOF) { + FILE_OP_ERROR(tmp, "fputs"); + fclose(fp); + fclose(src_fp); + unlink(tmp); + g_free(tmp); + return -1; + } + } + + fclose(src_fp); + if (fclose(fp) == EOF) { + FILE_OP_ERROR(tmp, "fclose"); + unlink(tmp); + g_free(tmp); + return -1; + } + + queue = account_get_special_folder(compose->account, F_QUEUE); + if (!queue) { + g_warning(_("can't find queue folder\n")); + unlink(tmp); + g_free(tmp); + return -1; + } + folder_item_scan(queue); + if ((num = folder_item_add_msg(queue, tmp, &flag, TRUE)) < 0) { + g_warning(_("can't queue the message\n")); + unlink(tmp); + g_free(tmp); + return -1; + } + g_free(tmp); + + if (compose->mode == COMPOSE_REEDIT) { + compose_remove_reedit_target(compose); + if (compose->targetinfo && + compose->targetinfo->folder != queue) + folderview_update_item + (compose->targetinfo->folder, TRUE); + } + + folder_item_scan(queue); + folderview_update_item(queue, TRUE); + + return 0; +} + +static void compose_write_attach(Compose *compose, FILE *fp) +{ + AttachInfo *ainfo; + GtkCList *clist = GTK_CLIST(compose->attach_clist); + gint row; + FILE *attach_fp; + gchar filename[BUFFSIZE]; + gint len; + + for (row = 0; (ainfo = gtk_clist_get_row_data(clist, row)) != NULL; + row++) { + if ((attach_fp = fopen(ainfo->file, "rb")) == NULL) { + g_warning("Can't open file %s\n", ainfo->file); + continue; + } + + fprintf(fp, "\n--%s\n", compose->boundary); + + if (!g_strcasecmp(ainfo->content_type, "message/rfc822")) { + fprintf(fp, "Content-Type: %s\n", ainfo->content_type); + fprintf(fp, "Content-Disposition: inline\n"); + } else { + compose_convert_header(filename, sizeof(filename), + ainfo->name, 12, FALSE); + fprintf(fp, "Content-Type: %s;\n" + " name=\"%s\"\n", + ainfo->content_type, filename); + fprintf(fp, "Content-Disposition: attachment;\n" + " filename=\"%s\"\n", filename); + } + + fprintf(fp, "Content-Transfer-Encoding: %s\n\n", + procmime_get_encoding_str(ainfo->encoding)); + + if (ainfo->encoding == ENC_BASE64) { + gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE]; + FILE *tmp_fp = attach_fp; + gchar *tmp_file = NULL; + ContentType content_type; + + content_type = + procmime_scan_mime_type(ainfo->content_type); + if (content_type == MIME_TEXT || + content_type == MIME_TEXT_HTML || + content_type == MIME_MESSAGE_RFC822) { + tmp_file = get_tmp_file(); + if (canonicalize_file(ainfo->file, tmp_file) < 0) { + g_free(tmp_file); + fclose(attach_fp); + continue; + } + if ((tmp_fp = fopen(tmp_file, "rb")) == NULL) { + FILE_OP_ERROR(tmp_file, "fopen"); + unlink(tmp_file); + g_free(tmp_file); + fclose(attach_fp); + continue; + } + } + + while ((len = fread(inbuf, sizeof(gchar), + B64_LINE_SIZE, tmp_fp)) + == B64_LINE_SIZE) { + base64_encode(outbuf, inbuf, B64_LINE_SIZE); + fputs(outbuf, fp); + fputc('\n', fp); + } + if (len > 0 && feof(tmp_fp)) { + base64_encode(outbuf, inbuf, len); + fputs(outbuf, fp); + fputc('\n', fp); + } + + if (tmp_file) { + fclose(tmp_fp); + unlink(tmp_file); + g_free(tmp_file); + } + } else if (ainfo->encoding == ENC_QUOTED_PRINTABLE) { + gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4]; + + while (fgets(inbuf, sizeof(inbuf), attach_fp) != NULL) { + qp_encode_line(outbuf, inbuf); + fputs(outbuf, fp); + } + } else { + gchar buf[BUFFSIZE]; + + while (fgets(buf, sizeof(buf), attach_fp) != NULL) { + strcrchomp(buf); + fputs(buf, fp); + } + } + + fclose(attach_fp); + } + + fprintf(fp, "\n--%s--\n", compose->boundary); +} + +#define QUOTE_IF_REQUIRED(out, str) \ +{ \ + if (*str != '"' && strpbrk(str, ",.[]<>")) { \ + gchar *__tmp; \ + gint len; \ + \ + len = strlen(str) + 3; \ + Xalloca(__tmp, len, return -1); \ + g_snprintf(__tmp, len, "\"%s\"", str); \ + out = __tmp; \ + } else { \ + Xstrdup_a(out, str, return -1); \ + } \ +} + +#define PUT_RECIPIENT_HEADER(header, str) \ +{ \ + if (*str != '\0') { \ + gchar *dest; \ + \ + Xstrdup_a(dest, str, return -1); \ + g_strstrip(dest); \ + if (*dest != '\0') { \ + compose->to_list = address_list_append \ + (compose->to_list, dest); \ + compose_convert_header \ + (buf, sizeof(buf), dest, strlen(header) + 2, \ + TRUE); \ + fprintf(fp, "%s: %s\n", header, buf); \ + } \ + } \ +} + +#define IS_IN_CUSTOM_HEADER(header) \ + (compose->account->add_customhdr && \ + custom_header_find(compose->account->customhdr_list, header) != NULL) + +static gint compose_write_headers(Compose *compose, FILE *fp, + const gchar *charset, EncodingType encoding, + gboolean is_draft) +{ + gchar buf[BUFFSIZE]; + const gchar *entry_str; + gchar *str; + gchar *name; + /* struct utsname utsbuf; */ + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(charset != NULL, -1); + g_return_val_if_fail(compose->account != NULL, -1); + g_return_val_if_fail(compose->account->address != NULL, -1); + + /* Date */ + if (compose->account->add_date) { + get_rfc822_date(buf, sizeof(buf)); + fprintf(fp, "Date: %s\n", buf); + } + + /* From */ + if (compose->account->name && *compose->account->name) { + compose_convert_header + (buf, sizeof(buf), compose->account->name, + strlen("From: "), TRUE); + QUOTE_IF_REQUIRED(name, buf); + fprintf(fp, "From: %s <%s>\n", + name, compose->account->address); + } else + fprintf(fp, "From: %s\n", compose->account->address); + + slist_free_strings(compose->to_list); + g_slist_free(compose->to_list); + compose->to_list = NULL; + + /* To */ + if (compose->use_to) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry)); + PUT_RECIPIENT_HEADER("To", entry_str); + } + + slist_free_strings(compose->newsgroup_list); + g_slist_free(compose->newsgroup_list); + compose->newsgroup_list = NULL; + + /* Newsgroups */ + if (compose->use_newsgroups) { + entry_str = gtk_entry_get_text + (GTK_ENTRY(compose->newsgroups_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + remove_space(str); + if (*str != '\0') { + compose->newsgroup_list = + newsgroup_list_append + (compose->newsgroup_list, str); + compose_convert_header(buf, sizeof(buf), str, + strlen("Newsgroups: "), + TRUE); + fprintf(fp, "Newsgroups: %s\n", buf); + } + } + } + + /* Cc */ + if (compose->use_cc) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry)); + PUT_RECIPIENT_HEADER("Cc", entry_str); + } + + /* Bcc */ + if (compose->use_bcc) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry)); + PUT_RECIPIENT_HEADER("Bcc", entry_str); + } + + if (!is_draft && !compose->to_list && !compose->newsgroup_list) + return -1; + + /* Subject */ + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry)); + if (*entry_str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + if (*str != '\0') { + compose_convert_header(buf, sizeof(buf), str, + strlen("Subject: "), FALSE); + fprintf(fp, "Subject: %s\n", buf); + } + } + + /* Message-ID */ + if (compose->account->gen_msgid) { + compose_generate_msgid(compose, buf, sizeof(buf)); + fprintf(fp, "Message-Id: <%s>\n", buf); + compose->msgid = g_strdup(buf); + } + + /* In-Reply-To */ + if (compose->inreplyto && compose->to_list) + fprintf(fp, "In-Reply-To: <%s>\n", compose->inreplyto); + + /* References */ + if (compose->references) + fprintf(fp, "References: %s\n", compose->references); + + /* Followup-To */ + if (compose->use_followupto && !IS_IN_CUSTOM_HEADER("Followup-To")) { + entry_str = gtk_entry_get_text + (GTK_ENTRY(compose->followup_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + remove_space(str); + if (*str != '\0') { + compose_convert_header(buf, sizeof(buf), str, + strlen("Followup-To: "), + TRUE); + fprintf(fp, "Followup-To: %s\n", buf); + } + } + } + + /* Reply-To */ + if (compose->use_replyto && !IS_IN_CUSTOM_HEADER("Reply-To")) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->reply_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + if (*str != '\0') { + compose_convert_header(buf, sizeof(buf), str, + strlen("Reply-To: "), + TRUE); + fprintf(fp, "Reply-To: %s\n", buf); + } + } + } + + /* Organization */ + if (compose->account->organization && + !IS_IN_CUSTOM_HEADER("Organization")) { + compose_convert_header(buf, sizeof(buf), + compose->account->organization, + strlen("Organization: "), FALSE); + fprintf(fp, "Organization: %s\n", buf); + } + + /* Program version and system info */ + /* uname(&utsbuf); */ + if (compose->to_list && !IS_IN_CUSTOM_HEADER("X-Mailer")) { + fprintf(fp, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n", + prog_version, + gtk_major_version, gtk_minor_version, gtk_micro_version, + TARGET_ALIAS); + /* utsbuf.sysname, utsbuf.release, utsbuf.machine); */ + } + if (compose->newsgroup_list && !IS_IN_CUSTOM_HEADER("X-Newsreader")) { + fprintf(fp, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n", + prog_version, + gtk_major_version, gtk_minor_version, gtk_micro_version, + TARGET_ALIAS); + /* utsbuf.sysname, utsbuf.release, utsbuf.machine); */ + } + + /* custom headers */ + if (compose->account->add_customhdr) { + GSList *cur; + + for (cur = compose->account->customhdr_list; cur != NULL; + cur = cur->next) { + CustomHeader *chdr = (CustomHeader *)cur->data; + + if (strcasecmp(chdr->name, "Date") != 0 && + strcasecmp(chdr->name, "From") != 0 && + strcasecmp(chdr->name, "To") != 0 && + /* strcasecmp(chdr->name, "Sender") != 0 && */ + strcasecmp(chdr->name, "Message-Id") != 0 && + strcasecmp(chdr->name, "In-Reply-To") != 0 && + strcasecmp(chdr->name, "References") != 0 && + strcasecmp(chdr->name, "Mime-Version") != 0 && + strcasecmp(chdr->name, "Content-Type") != 0 && + strcasecmp(chdr->name, "Content-Transfer-Encoding") + != 0) { + compose_convert_header + (buf, sizeof(buf), + chdr->value ? chdr->value : "", + strlen(chdr->name) + 2, FALSE); + fprintf(fp, "%s: %s\n", chdr->name, buf); + } + } + } + + /* MIME */ + fprintf(fp, "Mime-Version: 1.0\n"); + if (compose->use_attach && + GTK_CLIST(compose->attach_clist)->row_list) { + compose->boundary = generate_mime_boundary(NULL); + fprintf(fp, + "Content-Type: multipart/mixed;\n" + " boundary=\"%s\"\n", compose->boundary); + } else { + fprintf(fp, "Content-Type: text/plain; charset=%s\n", charset); +#if USE_GPGME + if (compose->use_signing && !compose->account->clearsign) + fprintf(fp, "Content-Disposition: inline\n"); +#endif + fprintf(fp, "Content-Transfer-Encoding: %s\n", + procmime_get_encoding_str(encoding)); + } + + /* X-Sylpheed header */ + if (is_draft) + fprintf(fp, "X-Sylpheed-Account-Id: %d\n", + compose->account->account_id); + + /* separator between header and body */ + fputs("\n", fp); + + return 0; +} + +static gint compose_redirect_write_headers(Compose *compose, FILE *fp) +{ + gchar buf[BUFFSIZE]; + const gchar *entry_str; + gchar *str; + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(compose->account != NULL, -1); + g_return_val_if_fail(compose->account->address != NULL, -1); + + /* Resent-Date */ + get_rfc822_date(buf, sizeof(buf)); + fprintf(fp, "Resent-Date: %s\n", buf); + + /* Resent-From */ + if (compose->account->name) { + compose_convert_header + (buf, sizeof(buf), compose->account->name, + strlen("Resent-From: "), TRUE); + fprintf(fp, "Resent-From: %s <%s>\n", + buf, compose->account->address); + } else + fprintf(fp, "Resent-From: %s\n", compose->account->address); + + slist_free_strings(compose->to_list); + g_slist_free(compose->to_list); + compose->to_list = NULL; + + /* Resent-To */ + if (compose->use_to) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry)); + PUT_RECIPIENT_HEADER("Resent-To", entry_str); + } + if (compose->use_cc) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry)); + PUT_RECIPIENT_HEADER("Resent-Cc", entry_str); + } + if (compose->use_bcc) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry)); + PUT_RECIPIENT_HEADER("Bcc", entry_str); + } + + slist_free_strings(compose->newsgroup_list); + g_slist_free(compose->newsgroup_list); + compose->newsgroup_list = NULL; + + /* Newsgroups */ + if (compose->use_newsgroups) { + entry_str = gtk_entry_get_text + (GTK_ENTRY(compose->newsgroups_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + remove_space(str); + if (*str != '\0') { + compose->newsgroup_list = + newsgroup_list_append + (compose->newsgroup_list, str); + compose_convert_header(buf, sizeof(buf), str, + strlen("Newsgroups: "), + TRUE); + fprintf(fp, "Newsgroups: %s\n", buf); + } + } + } + + if (!compose->to_list && !compose->newsgroup_list) + return -1; + + /* Subject */ + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + if (*str != '\0') { + compose_convert_header(buf, sizeof(buf), str, + strlen("Subject: "), FALSE); + fprintf(fp, "Subject: %s\n", buf); + } + } + + /* Resent-Message-Id */ + if (compose->account->gen_msgid) { + compose_generate_msgid(compose, buf, sizeof(buf)); + fprintf(fp, "Resent-Message-Id: <%s>\n", buf); + compose->msgid = g_strdup(buf); + } + + /* Followup-To */ + if (compose->use_followupto) { + entry_str = gtk_entry_get_text + (GTK_ENTRY(compose->followup_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + remove_space(str); + if (*str != '\0') { + compose_convert_header(buf, sizeof(buf), str, + strlen("Followup-To: "), + TRUE); + fprintf(fp, "Followup-To: %s\n", buf); + } + } + } + + /* Resent-Reply-To */ + if (compose->use_replyto) { + entry_str = gtk_entry_get_text(GTK_ENTRY(compose->reply_entry)); + if (*entry_str != '\0') { + Xstrdup_a(str, entry_str, return -1); + g_strstrip(str); + if (*str != '\0') { + compose_convert_header + (buf, sizeof(buf), str, + strlen("Resent-Reply-To: "), TRUE); + fprintf(fp, "Resent-Reply-To: %s\n", buf); + } + } + } + + fputs("\n", fp); + + return 0; +} + +#undef IS_IN_CUSTOM_HEADER + +static void compose_convert_header(gchar *dest, gint len, gchar *src, + gint header_len, gboolean addr_field) +{ + gchar *str; + const gchar *cur_encoding; + + g_return_if_fail(src != NULL); + g_return_if_fail(dest != NULL); + + if (len < 1) return; + + g_strchomp(src); + +#warning FIXME_GTK2 redundant code conversion + cur_encoding = conv_get_locale_charset_str(); + if (!strcmp(cur_encoding, CS_US_ASCII)) + cur_encoding = CS_ISO_8859_1; + str = conv_codeset_strdup(src, CS_UTF_8, cur_encoding); + if (str) + conv_encode_header(dest, len, str, header_len, addr_field); + g_free(str); +} + +static void compose_generate_msgid(Compose *compose, gchar *buf, gint len) +{ + struct tm *lt; + time_t t; + gchar *addr; + + t = time(NULL); + lt = localtime(&t); + + if (compose->account && compose->account->address && + *compose->account->address) { + if (strchr(compose->account->address, '@')) + addr = g_strdup(compose->account->address); + else + addr = g_strconcat(compose->account->address, "@", + get_domain_name(), NULL); + } else + addr = g_strconcat(g_get_user_name(), "@", get_domain_name(), + NULL); + + g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x.%s", + lt->tm_year + 1900, lt->tm_mon + 1, + lt->tm_mday, lt->tm_hour, + lt->tm_min, lt->tm_sec, + (guint)random(), addr); + + debug_print(_("generated Message-ID: %s\n"), buf); + + g_free(addr); +} + +static void compose_add_entry_field(GtkWidget *table, GtkWidget **hbox, + GtkWidget **entry, gint *count, + const gchar *label_str, + gboolean is_addr_entry) +{ + GtkWidget *label; + + if (GTK_TABLE(table)->nrows < (*count) + 1) + gtk_table_resize(GTK_TABLE(table), (*count) + 1, 2); + + *hbox = gtk_hbox_new(FALSE, 0); + label = gtk_label_new + (prefs_common.trans_hdr ? gettext(label_str) : label_str); + gtk_box_pack_end(GTK_BOX(*hbox), label, FALSE, FALSE, 0); + gtk_table_attach(GTK_TABLE(table), *hbox, 0, 1, *count, (*count) + 1, + GTK_FILL, 0, 2, 0); + *entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(*entry), MAX_ENTRY_LENGTH); + gtk_table_attach_defaults + (GTK_TABLE(table), *entry, 1, 2, *count, (*count) + 1); + if (GTK_TABLE(table)->nrows > (*count) + 1) + gtk_table_set_row_spacing(GTK_TABLE(table), *count, 4); + + if (is_addr_entry) + address_completion_register_entry(GTK_ENTRY(*entry)); + + (*count)++; +} + +static Compose *compose_create(PrefsAccount *account, ComposeMode mode) +{ + Compose *compose; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *handlebox; + + GtkWidget *vbox2; + + GtkWidget *table_vbox; + GtkWidget *label; + GtkWidget *from_optmenu_hbox; + GtkWidget *to_entry; + GtkWidget *to_hbox; + GtkWidget *newsgroups_entry; + GtkWidget *newsgroups_hbox; + GtkWidget *subject_entry; + GtkWidget *cc_entry; + GtkWidget *cc_hbox; + GtkWidget *bcc_entry; + GtkWidget *bcc_hbox; + GtkWidget *reply_entry; + GtkWidget *reply_hbox; + GtkWidget *followup_entry; + GtkWidget *followup_hbox; + + GtkWidget *paned; + + GtkWidget *attach_scrwin; + GtkWidget *attach_clist; + + GtkWidget *edit_vbox; + GtkWidget *ruler_hbox; + GtkWidget *ruler; + GtkWidget *scrolledwin; + GtkWidget *text; + + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + GtkWidget *table; + GtkWidget *hbox; + + UndoMain *undostruct; + + gchar *titles[N_ATTACH_COLS]; + guint n_menu_entries; + GtkStyle *style, *new_style; + GdkColormap *cmap; + GdkColor color[1]; + gboolean success[1]; + GtkWidget *popupmenu; + GtkItemFactory *popupfactory; + GtkItemFactory *ifactory; + GtkWidget *tmpl_menu; + gint n_entries; + gint count = 0; + gint i; + + static GdkGeometry geometry; + + g_return_val_if_fail(account != NULL, NULL); + + debug_print(_("Creating compose window...\n")); + compose = g_new0(Compose, 1); + + titles[COL_MIMETYPE] = _("MIME type"); + titles[COL_SIZE] = _("Size"); + titles[COL_NAME] = _("Name"); + + compose->account = account; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE); + gtk_widget_set_size_request(window, -1, prefs_common.compose_height); + gtk_window_set_wmclass(GTK_WINDOW(window), "compose", "Sylpheed"); + + if (!geometry.max_width) { + geometry.max_width = gdk_screen_width(); + geometry.max_height = gdk_screen_height(); + } + gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, + &geometry, GDK_HINT_MAX_SIZE); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(compose_delete_cb), compose); + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(compose_destroy_cb), compose); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + gtk_widget_realize(window); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]); + menubar = menubar_create(window, compose_entries, + n_menu_entries, "", compose); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + + handlebox = gtk_handle_box_new(); + gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0); + + compose_toolbar_create(compose, handlebox); + + vbox2 = gtk_vbox_new(FALSE, 2); + gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), BORDER_WIDTH); + + table_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox2), table_vbox, FALSE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(table_vbox), + BORDER_WIDTH * 2); + + table = gtk_table_new(8, 2, FALSE); + gtk_box_pack_start(GTK_BOX(table_vbox), table, FALSE, TRUE, 0); + + /* option menu for selecting accounts */ + hbox = gtk_hbox_new(FALSE, 0); + label = gtk_label_new(prefs_common.trans_hdr ? _("From:") : "From:"); + gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, count, count + 1, + GTK_FILL, 0, 2, 0); + from_optmenu_hbox = compose_account_option_menu_create(compose); + gtk_table_attach_defaults(GTK_TABLE(table), from_optmenu_hbox, + 1, 2, count, count + 1); + gtk_table_set_row_spacing(GTK_TABLE(table), 0, 4); + count++; + + /* header labels and entries */ + compose_add_entry_field(table, &to_hbox, &to_entry, &count, + "To:", TRUE); + compose_add_entry_field(table, &newsgroups_hbox, &newsgroups_entry, + &count, "Newsgroups:", FALSE); + compose_add_entry_field(table, &cc_hbox, &cc_entry, &count, + "Cc:", TRUE); + compose_add_entry_field(table, &bcc_hbox, &bcc_entry, &count, + "Bcc:", TRUE); + compose_add_entry_field(table, &reply_hbox, &reply_entry, &count, + "Reply-To:", TRUE); + compose_add_entry_field(table, &followup_hbox, &followup_entry, &count, + "Followup-To:", FALSE); + compose_add_entry_field(table, &hbox, &subject_entry, &count, + "Subject:", FALSE); + + gtk_table_set_col_spacings(GTK_TABLE(table), 4); + + g_signal_connect(G_OBJECT(to_entry), "activate", + G_CALLBACK(to_activated), compose); + g_signal_connect(G_OBJECT(newsgroups_entry), "activate", + G_CALLBACK(newsgroups_activated), compose); + g_signal_connect(G_OBJECT(cc_entry), "activate", + G_CALLBACK(cc_activated), compose); + g_signal_connect(G_OBJECT(bcc_entry), "activate", + G_CALLBACK(bcc_activated), compose); + g_signal_connect(G_OBJECT(reply_entry), "activate", + G_CALLBACK(replyto_activated), compose); + g_signal_connect(G_OBJECT(followup_entry), "activate", + G_CALLBACK(followupto_activated), compose); + g_signal_connect(G_OBJECT(subject_entry), "activate", + G_CALLBACK(subject_activated), compose); + + g_signal_connect(G_OBJECT(to_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + g_signal_connect(G_OBJECT(newsgroups_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + g_signal_connect(G_OBJECT(cc_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + g_signal_connect(G_OBJECT(bcc_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + g_signal_connect(G_OBJECT(reply_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + g_signal_connect(G_OBJECT(followup_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + g_signal_connect(G_OBJECT(subject_entry), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); + + /* attachment list */ + attach_scrwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(attach_scrwin, -1, 80); + + attach_clist = gtk_clist_new_with_titles(N_ATTACH_COLS, titles); + gtk_clist_set_column_justification(GTK_CLIST(attach_clist), COL_SIZE, + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_width(GTK_CLIST(attach_clist), COL_MIMETYPE, 240); + gtk_clist_set_column_width(GTK_CLIST(attach_clist), COL_SIZE, 64); + gtk_clist_set_selection_mode(GTK_CLIST(attach_clist), + GTK_SELECTION_EXTENDED); + for (i = 0; i < N_ATTACH_COLS; i++) + GTK_WIDGET_UNSET_FLAGS + (GTK_CLIST(attach_clist)->column[i].button, + GTK_CAN_FOCUS); + gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist); + + g_signal_connect(G_OBJECT(attach_clist), "select_row", + G_CALLBACK(attach_selected), compose); + g_signal_connect(G_OBJECT(attach_clist), "button_press_event", + G_CALLBACK(attach_button_pressed), compose); + g_signal_connect(G_OBJECT(attach_clist), "key_press_event", + G_CALLBACK(attach_key_pressed), compose); + + /* drag and drop */ + gtk_drag_dest_set(attach_clist, + GTK_DEST_DEFAULT_ALL, compose_mime_types, 1, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + g_signal_connect(G_OBJECT(attach_clist), "drag_data_received", + G_CALLBACK(compose_attach_drag_received_cb), + compose); + + /* pane between attach clist and text */ + paned = gtk_vpaned_new(); + gtk_paned_add1(GTK_PANED(paned), attach_scrwin); + gtk_widget_ref(paned); + gtk_widget_show_all(paned); + + edit_vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox2), edit_vbox, TRUE, TRUE, 0); + + /* ruler */ + ruler_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0); + + ruler = gtk_shruler_new(); + gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0); + gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE, + BORDER_WIDTH + 1); + gtk_widget_set_size_request(ruler_hbox, 1, -1); + + /* text widget */ + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0); + gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, + -1); + + text = gtk_text_view_new(); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_text_buffer_add_selection_clipboard(buffer, clipboard); + /* GTK_STEXT(text)->default_tab_width = 8; */ + gtk_container_add(GTK_CONTAINER(scrolledwin), text); + + g_signal_connect(G_OBJECT(text), "grab_focus", + G_CALLBACK(compose_grab_focus_cb), compose); +#warning FIXME_GTK2 +#if 0 + g_signal_connect(G_OBJECT(text), "activate", + G_CALLBACK(text_activated), compose); +#endif + g_signal_connect(G_OBJECT(text), "insert_text", + G_CALLBACK(text_inserted), compose); + g_signal_connect_after(G_OBJECT(text), "size_allocate", + G_CALLBACK(compose_edit_size_alloc), + ruler); + + /* drag and drop */ + gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types, 1, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + g_signal_connect(G_OBJECT(text), "drag_data_received", + G_CALLBACK(compose_insert_drag_received_cb), + compose); + + gtk_widget_show_all(vbox); + + style = gtk_widget_get_style(text); + new_style = gtk_style_copy(style); + +#warning FIXME_GTK2 use normal API for setting font + if (prefs_common.textfont) { + PangoFontDescription *font_desc; + + font_desc = pango_font_description_from_string + (prefs_common.textfont); + if (font_desc) { + if (new_style->font_desc) + pango_font_description_free + (new_style->font_desc); + new_style->font_desc = font_desc; + } + } + + gtk_widget_set_style(text, new_style); + + color[0] = quote_color; + cmap = gdk_window_get_colormap(window->window); + gdk_colormap_alloc_colors(cmap, color, 1, FALSE, TRUE, success); + if (success[0] == FALSE) { + g_warning("Compose: color allocation failed.\n"); + style = gtk_widget_get_style(text); + quote_color = style->black; + } + + n_entries = sizeof(compose_popup_entries) / + sizeof(compose_popup_entries[0]); + popupmenu = menu_create_items(compose_popup_entries, n_entries, + "", &popupfactory, + compose); + + ifactory = gtk_item_factory_from_widget(menubar); + menu_set_sensitive(ifactory, "/Edit/Undo", FALSE); + menu_set_sensitive(ifactory, "/Edit/Redo", FALSE); + + tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template"); + + gtk_widget_hide(bcc_hbox); + gtk_widget_hide(bcc_entry); + gtk_widget_hide(reply_hbox); + gtk_widget_hide(reply_entry); + gtk_widget_hide(followup_hbox); + gtk_widget_hide(followup_entry); + gtk_widget_hide(ruler_hbox); + gtk_table_set_row_spacing(GTK_TABLE(table), 4, 0); + gtk_table_set_row_spacing(GTK_TABLE(table), 5, 0); + gtk_table_set_row_spacing(GTK_TABLE(table), 6, 0); + + if (account->protocol == A_NNTP) { + gtk_widget_hide(to_hbox); + gtk_widget_hide(to_entry); + gtk_widget_hide(cc_hbox); + gtk_widget_hide(cc_entry); + gtk_table_set_row_spacing(GTK_TABLE(table), 1, 0); + gtk_table_set_row_spacing(GTK_TABLE(table), 3, 0); + } else { + gtk_widget_hide(newsgroups_hbox); + gtk_widget_hide(newsgroups_entry); + gtk_table_set_row_spacing(GTK_TABLE(table), 2, 0); + } + + switch (prefs_common.toolbar_style) { + case TOOLBAR_NONE: + gtk_widget_hide(handlebox); + break; + case TOOLBAR_ICON: + gtk_toolbar_set_style(GTK_TOOLBAR(compose->toolbar), + GTK_TOOLBAR_ICONS); + break; + case TOOLBAR_TEXT: + gtk_toolbar_set_style(GTK_TOOLBAR(compose->toolbar), + GTK_TOOLBAR_TEXT); + break; + case TOOLBAR_BOTH: + gtk_toolbar_set_style(GTK_TOOLBAR(compose->toolbar), + GTK_TOOLBAR_BOTH); + break; + } + + undostruct = undo_init(text); + undo_set_change_state_func(undostruct, &compose_undo_state_changed, + menubar); + + address_completion_start(window); + + compose->window = window; + compose->vbox = vbox; + compose->menubar = menubar; + compose->handlebox = handlebox; + + compose->vbox2 = vbox2; + + compose->table_vbox = table_vbox; + compose->table = table; + compose->to_hbox = to_hbox; + compose->to_entry = to_entry; + compose->newsgroups_hbox = newsgroups_hbox; + compose->newsgroups_entry = newsgroups_entry; + compose->subject_entry = subject_entry; + compose->cc_hbox = cc_hbox; + compose->cc_entry = cc_entry; + compose->bcc_hbox = bcc_hbox; + compose->bcc_entry = bcc_entry; + compose->reply_hbox = reply_hbox; + compose->reply_entry = reply_entry; + compose->followup_hbox = followup_hbox; + compose->followup_entry = followup_entry; + + compose->paned = paned; + + compose->attach_scrwin = attach_scrwin; + compose->attach_clist = attach_clist; + + compose->edit_vbox = edit_vbox; + compose->ruler_hbox = ruler_hbox; + compose->ruler = ruler; + compose->scrolledwin = scrolledwin; + compose->text = text; + + compose->focused_editable = NULL; + + compose->popupmenu = popupmenu; + compose->popupfactory = popupfactory; + + compose->tmpl_menu = tmpl_menu; + + compose->mode = mode; + + compose->targetinfo = NULL; + compose->replyinfo = NULL; + + compose->replyto = NULL; + compose->cc = NULL; + compose->bcc = NULL; + compose->followup_to = NULL; + + compose->ml_post = NULL; + + compose->inreplyto = NULL; + compose->references = NULL; + compose->msgid = NULL; + compose->boundary = NULL; + + compose->autowrap = prefs_common.autowrap; + + compose->use_to = FALSE; + compose->use_cc = FALSE; + compose->use_bcc = FALSE; + compose->use_replyto = FALSE; + compose->use_newsgroups = FALSE; + compose->use_followupto = FALSE; + compose->use_attach = FALSE; + +#if USE_GPGME + compose->use_signing = FALSE; + compose->use_encryption = FALSE; +#endif /* USE_GPGME */ + + compose->modified = FALSE; + + compose->paste_as_quotation = FALSE; + + compose->to_list = NULL; + compose->newsgroup_list = NULL; + + compose->undostruct = undostruct; + + compose->sig_str = NULL; + + compose->exteditor_file = NULL; + compose->exteditor_pid = -1; + compose->exteditor_readdes = -1; + compose->exteditor_tag = -1; + + compose_select_account(compose, account, TRUE); + + menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap); + menu_set_active(ifactory, "/View/Ruler", prefs_common.show_ruler); + + if (mode == COMPOSE_REDIRECT) { + menu_set_sensitive(ifactory, "/File/Save to draft folder", FALSE); + menu_set_sensitive(ifactory, "/File/Save and keep editing", FALSE); + menu_set_sensitive(ifactory, "/File/Attach file", FALSE); + menu_set_sensitive(ifactory, "/File/Insert file", FALSE); + menu_set_sensitive(ifactory, "/File/Insert signature", FALSE); + menu_set_sensitive(ifactory, "/Edit/Cut", FALSE); + menu_set_sensitive(ifactory, "/Edit/Paste", FALSE); + menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", FALSE); + menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", FALSE); + menu_set_sensitive(ifactory, "/Edit/Auto wrapping", FALSE); + menu_set_sensitive(ifactory, "/Edit/Advanced", FALSE); + menu_set_sensitive(ifactory, "/View/Attachment", FALSE); + menu_set_sensitive(ifactory, "/Tools/Template", FALSE); + menu_set_sensitive(ifactory, "/Tools/Actions", FALSE); + menu_set_sensitive(ifactory, "/Tools/Edit with external editor", FALSE); +#if USE_GPGME + menu_set_sensitive(ifactory, "/Tools/PGP Sign", FALSE); + menu_set_sensitive(ifactory, "/Tools/PGP Encrypt", FALSE); +#endif /* USE_GPGME */ + + gtk_widget_set_sensitive(compose->insert_btn, FALSE); + gtk_widget_set_sensitive(compose->attach_btn, FALSE); + gtk_widget_set_sensitive(compose->sig_btn, FALSE); + gtk_widget_set_sensitive(compose->exteditor_btn, FALSE); + gtk_widget_set_sensitive(compose->linewrap_btn, FALSE); + + menu_set_sensitive_all(GTK_MENU_SHELL(compose->popupmenu), + FALSE); + } + + addressbook_set_target_compose(compose); + action_update_compose_menu(ifactory, compose); + compose_set_template_menu(compose); + + compose_list = g_list_append(compose_list, compose); + + gtk_widget_show(window); + + return compose; +} + +static void compose_connect_changed_callbacks(Compose *compose) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text);; + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer(text); + + g_signal_connect(G_OBJECT(buffer), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->to_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->newsgroups_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->cc_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->bcc_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->reply_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->followup_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); + g_signal_connect(G_OBJECT(compose->subject_entry), "changed", + G_CALLBACK(compose_changed_cb), compose); +} + +static void compose_toolbar_create(Compose *compose, GtkWidget *container) +{ + GtkWidget *toolbar; + GtkWidget *icon_wid; + GtkWidget *send_btn; + GtkWidget *sendl_btn; + GtkWidget *draft_btn; + GtkWidget *insert_btn; + GtkWidget *attach_btn; + GtkWidget *sig_btn; + GtkWidget *exteditor_btn; + GtkWidget *linewrap_btn; + GtkWidget *addrbook_btn; + + toolbar = gtk_toolbar_new(); + gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar), + GTK_ORIENTATION_HORIZONTAL); + gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH); + gtk_container_add(GTK_CONTAINER(container), toolbar); + gtk_container_set_border_width(GTK_CONTAINER(container), 2); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_SEND); + send_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Send"), + _("Send message"), + "Send", + icon_wid, + G_CALLBACK(toolbar_send_cb), + compose); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_SEND_QUEUE); + sendl_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Send later"), + _("Put into queue folder and send later"), + "Send later", + icon_wid, + G_CALLBACK(toolbar_send_later_cb), + compose); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL); + draft_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Draft"), + _("Save to draft folder"), + "Draft", + icon_wid, + G_CALLBACK(toolbar_draft_cb), + compose); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_PASTE); + insert_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Insert"), + _("Insert file"), + "Insert", + icon_wid, + G_CALLBACK(toolbar_insert_cb), + compose); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_ATTACH); + attach_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Attach"), + _("Attach file"), + "Attach", + icon_wid, + G_CALLBACK(toolbar_attach_cb), + compose); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL); + sig_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Signature"), + _("Insert signature"), + "Signature", + icon_wid, + G_CALLBACK(toolbar_sig_cb), compose); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_COMPOSE); + exteditor_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Editor"), + _("Edit with external editor"), + "Editor", + icon_wid, + G_CALLBACK(toolbar_ext_editor_cb), + compose); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_LINEWRAP); + linewrap_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Linewrap"), + _("Wrap all long lines"), + "Linewrap", + icon_wid, + G_CALLBACK(toolbar_linewrap_cb), + compose); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_ADDRESS_BOOK); + addrbook_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Address"), + _("Address book"), + "Address", + icon_wid, + G_CALLBACK(toolbar_address_cb), + compose); + + compose->toolbar = toolbar; + compose->send_btn = send_btn; + compose->sendl_btn = sendl_btn; + compose->draft_btn = draft_btn; + compose->insert_btn = insert_btn; + compose->attach_btn = attach_btn; + compose->sig_btn = sig_btn; + compose->exteditor_btn = exteditor_btn; + compose->linewrap_btn = linewrap_btn; + compose->addrbook_btn = addrbook_btn; + + gtk_widget_show_all(toolbar); +} + +static GtkWidget *compose_account_option_menu_create(Compose *compose) +{ + GList *accounts; + GtkWidget *hbox; + GtkWidget *optmenu; + GtkWidget *menu; + gint num = 0, def_menu = 0; + + accounts = account_get_list(); + g_return_val_if_fail(accounts != NULL, NULL); + + hbox = gtk_hbox_new(FALSE, 0); + optmenu = gtk_option_menu_new(); + gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0); + menu = gtk_menu_new(); + + for (; accounts != NULL; accounts = accounts->next, num++) { + PrefsAccount *ac = (PrefsAccount *)accounts->data; + GtkWidget *menuitem; + gchar *name; + + if (ac == compose->account) def_menu = num; + + if (ac->name) + name = g_strdup_printf("%s: %s <%s>", + ac->account_name, + ac->name, ac->address); + else + name = g_strdup_printf("%s: %s", + ac->account_name, ac->address); + MENUITEM_ADD(menu, menuitem, name, ac); + g_free(name); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(account_activated), + compose); + } + + gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu); + gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), def_menu); + + return hbox; +} + +static void compose_set_template_menu(Compose *compose) +{ + GSList *tmpl_list, *cur; + GtkWidget *menu; + GtkWidget *item; + + tmpl_list = template_get_config(); + + menu = gtk_menu_new(); + + for (cur = tmpl_list; cur != NULL; cur = cur->next) { + Template *tmpl = (Template *)cur->data; + + item = gtk_menu_item_new_with_label(tmpl->name); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(compose_template_activate_cb), + compose); + g_object_set_data(G_OBJECT(item), "template", tmpl); + gtk_widget_show(item); + } + + gtk_widget_show(menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu); +} + +void compose_reflect_prefs_all(void) +{ + GList *cur; + Compose *compose; + + for (cur = compose_list; cur != NULL; cur = cur->next) { + compose = (Compose *)cur->data; + compose_set_template_menu(compose); + } +} + +static void compose_template_apply(Compose *compose, Template *tmpl, + gboolean replace) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + gchar *qmark; + gchar *parsed_str; + + if (!tmpl || !tmpl->value) return; + + buffer = gtk_text_view_get_buffer(text); + + if (tmpl->to && *tmpl->to != '\0') + compose_entry_set(compose, tmpl->to, COMPOSE_ENTRY_TO); + if (tmpl->cc && *tmpl->cc != '\0') + compose_entry_set(compose, tmpl->cc, COMPOSE_ENTRY_CC); + if (tmpl->subject && *tmpl->subject != '\0') + compose_entry_set(compose, tmpl->subject, COMPOSE_ENTRY_SUBJECT); + + if (replace) + gtk_text_buffer_set_text(buffer, "", 0); + + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + + if (compose->replyinfo == NULL) { + parsed_str = compose_quote_fmt(compose, NULL, tmpl->value, + NULL, NULL); + } else { + if (prefs_common.quotemark && *prefs_common.quotemark) + qmark = prefs_common.quotemark; + else + qmark = "> "; + + parsed_str = compose_quote_fmt(compose, compose->replyinfo, + tmpl->value, qmark, NULL); + } + + if (replace && parsed_str && prefs_common.auto_sig) + compose_insert_sig(compose, FALSE); + + if (replace && parsed_str) { + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_place_cursor(buffer, &iter); + } + + if (parsed_str) + compose_changed_cb(NULL, compose); +} + +static void compose_destroy(Compose *compose) +{ + gint row; + GtkCList *clist = GTK_CLIST(compose->attach_clist); + AttachInfo *ainfo; + + /* NOTE: address_completion_end() does nothing with the window + * however this may change. */ + address_completion_end(compose->window); + + slist_free_strings(compose->to_list); + g_slist_free(compose->to_list); + slist_free_strings(compose->newsgroup_list); + g_slist_free(compose->newsgroup_list); + + procmsg_msginfo_free(compose->targetinfo); + procmsg_msginfo_free(compose->replyinfo); + + g_free(compose->replyto); + g_free(compose->cc); + g_free(compose->bcc); + g_free(compose->newsgroups); + g_free(compose->followup_to); + + g_free(compose->ml_post); + + g_free(compose->inreplyto); + g_free(compose->references); + g_free(compose->msgid); + g_free(compose->boundary); + + if (compose->undostruct) + undo_destroy(compose->undostruct); + + g_free(compose->sig_str); + + g_free(compose->exteditor_file); + + for (row = 0; (ainfo = gtk_clist_get_row_data(clist, row)) != NULL; + row++) + compose_attach_info_free(ainfo); + + if (addressbook_get_target_compose() == compose) + addressbook_set_target_compose(NULL); + + prefs_common.compose_width = compose->scrolledwin->allocation.width; + prefs_common.compose_height = compose->window->allocation.height; + + gtk_widget_destroy(compose->paned); + + g_free(compose); + + compose_list = g_list_remove(compose_list, compose); +} + +static void compose_attach_info_free(AttachInfo *ainfo) +{ + g_free(ainfo->file); + g_free(ainfo->content_type); + g_free(ainfo->name); + g_free(ainfo); +} + +static void compose_attach_remove_selected(Compose *compose) +{ + GtkCList *clist = GTK_CLIST(compose->attach_clist); + AttachInfo *ainfo; + gint row; + + while (clist->selection != NULL) { + row = GPOINTER_TO_INT(clist->selection->data); + ainfo = gtk_clist_get_row_data(clist, row); + compose_attach_info_free(ainfo); + gtk_clist_remove(clist, row); + } +} + +static struct _AttachProperty +{ + GtkWidget *window; + GtkWidget *mimetype_entry; + GtkWidget *encoding_optmenu; + GtkWidget *path_entry; + GtkWidget *filename_entry; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; +} attach_prop; + +static void compose_attach_property(Compose *compose) +{ + GtkCList *clist = GTK_CLIST(compose->attach_clist); + AttachInfo *ainfo; + gint row; + GtkOptionMenu *optmenu; + static gboolean cancelled; + + if (!clist->selection) return; + row = GPOINTER_TO_INT(clist->selection->data); + + ainfo = gtk_clist_get_row_data(clist, row); + if (!ainfo) return; + + if (!attach_prop.window) + compose_attach_property_create(&cancelled); + gtk_widget_grab_focus(attach_prop.ok_btn); + gtk_widget_show(attach_prop.window); + manage_window_set_transient(GTK_WINDOW(attach_prop.window)); + + optmenu = GTK_OPTION_MENU(attach_prop.encoding_optmenu); + if (ainfo->encoding == ENC_UNKNOWN) + gtk_option_menu_set_history(optmenu, ENC_BASE64); + else + gtk_option_menu_set_history(optmenu, ainfo->encoding); + + gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry), + ainfo->content_type ? ainfo->content_type : ""); + gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry), + ainfo->file ? ainfo->file : ""); + gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry), + ainfo->name ? ainfo->name : ""); + + for (;;) { + const gchar *entry_text; + gchar *text; + gchar *cnttype = NULL; + gchar *file = NULL; + off_t size = 0; + GtkWidget *menu; + GtkWidget *menuitem; + + cancelled = FALSE; + gtk_main(); + + if (cancelled == TRUE) { + gtk_widget_hide(attach_prop.window); + break; + } + + entry_text = gtk_entry_get_text + (GTK_ENTRY(attach_prop.mimetype_entry)); + if (*entry_text != '\0') { + gchar *p; + + text = g_strstrip(g_strdup(entry_text)); + if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) { + cnttype = g_strdup(text); + g_free(text); + } else { + alertpanel_error(_("Invalid MIME type.")); + g_free(text); + continue; + } + } + + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + ainfo->encoding = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); + + entry_text = gtk_entry_get_text + (GTK_ENTRY(attach_prop.path_entry)); + if (*entry_text != '\0') { + if (is_file_exist(entry_text) && + (size = get_file_size(entry_text)) > 0) + file = g_strdup(entry_text); + else { + alertpanel_error + (_("File doesn't exist or is empty.")); + g_free(cnttype); + continue; + } + } + + entry_text = gtk_entry_get_text + (GTK_ENTRY(attach_prop.filename_entry)); + if (*entry_text != '\0') { + g_free(ainfo->name); + ainfo->name = g_strdup(entry_text); + } + + if (cnttype) { + g_free(ainfo->content_type); + ainfo->content_type = cnttype; + } + if (file) { + g_free(ainfo->file); + ainfo->file = file; + } + if (size) + ainfo->size = size; + + gtk_clist_set_text(clist, row, COL_MIMETYPE, + ainfo->content_type); + gtk_clist_set_text(clist, row, COL_SIZE, + to_human_readable(ainfo->size)); + gtk_clist_set_text(clist, row, COL_NAME, ainfo->name); + + gtk_widget_hide(attach_prop.window); + break; + } +} + +#define SET_LABEL_AND_ENTRY(str, entry, top) \ +{ \ + label = gtk_label_new(str); \ + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \ + GTK_FILL, 0, 0, 0); \ + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \ + \ + entry = gtk_entry_new(); \ + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \ + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \ +} + +static void compose_attach_property_create(gboolean *cancelled) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *mimetype_entry; + GtkWidget *hbox; + GtkWidget *optmenu; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *path_entry; + GtkWidget *filename_entry; + GtkWidget *hbbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + debug_print("Creating attach_property window...\n"); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 480, -1); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_window_set_title(GTK_WINDOW(window), _("Properties")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(attach_property_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(attach_property_key_pressed), + cancelled); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + + table = gtk_table_new(4, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + SET_LABEL_AND_ENTRY(_("MIME type"), mimetype_entry, 0); + + label = gtk_label_new(_("Encoding")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + optmenu = gtk_option_menu_new(); + gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new(); + MENUITEM_ADD(optmenu_menu, menuitem, "7bit", ENC_7BIT); + gtk_widget_set_sensitive(menuitem, FALSE); + MENUITEM_ADD(optmenu_menu, menuitem, "8bit", ENC_8BIT); + gtk_widget_set_sensitive(menuitem, FALSE); + MENUITEM_ADD(optmenu_menu, menuitem, "quoted-printable", + ENC_QUOTED_PRINTABLE); + MENUITEM_ADD(optmenu_menu, menuitem, "base64", ENC_BASE64); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), optmenu_menu); + + SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2); + SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3); + + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(attach_property_ok), + cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(attach_property_cancel), + cancelled); + + gtk_widget_show_all(vbox); + + attach_prop.window = window; + attach_prop.mimetype_entry = mimetype_entry; + attach_prop.encoding_optmenu = optmenu; + attach_prop.path_entry = path_entry; + attach_prop.filename_entry = filename_entry; + attach_prop.ok_btn = ok_btn; + attach_prop.cancel_btn = cancel_btn; +} + +#undef SET_LABEL_AND_ENTRY + +static void attach_property_ok(GtkWidget *widget, gboolean *cancelled) +{ + *cancelled = FALSE; + gtk_main_quit(); +} + +static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled) +{ + *cancelled = TRUE; + gtk_main_quit(); +} + +static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event, + gboolean *cancelled) +{ + *cancelled = TRUE; + gtk_main_quit(); + + return TRUE; +} + +static gboolean attach_property_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gboolean *cancelled) +{ + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void compose_exec_ext_editor(Compose *compose) +{ + gchar *tmp; + pid_t pid; + gint pipe_fds[2]; + + tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(), + G_DIR_SEPARATOR, compose); + + if (pipe(pipe_fds) < 0) { + perror("pipe"); + g_free(tmp); + return; + } + + if ((pid = fork()) < 0) { + perror("fork"); + g_free(tmp); + return; + } + + if (pid != 0) { + /* close the write side of the pipe */ + close(pipe_fds[1]); + + compose->exteditor_file = g_strdup(tmp); + compose->exteditor_pid = pid; + compose->exteditor_readdes = pipe_fds[0]; + + compose_set_ext_editor_sensitive(compose, FALSE); + + compose->exteditor_tag = + gdk_input_add(pipe_fds[0], GDK_INPUT_READ, + compose_input_cb, compose); + } else { /* process-monitoring process */ + pid_t pid_ed; + + if (setpgid(0, 0)) + perror("setpgid"); + + /* close the read side of the pipe */ + close(pipe_fds[0]); + + if (compose_write_body_to_file(compose, tmp) < 0) { + fd_write_all(pipe_fds[1], "2\n", 2); + _exit(1); + } + + pid_ed = compose_exec_ext_editor_real(tmp); + if (pid_ed < 0) { + fd_write_all(pipe_fds[1], "1\n", 2); + _exit(1); + } + + /* wait until editor is terminated */ + waitpid(pid_ed, NULL, 0); + + fd_write_all(pipe_fds[1], "0\n", 2); + + close(pipe_fds[1]); + _exit(0); + } + + g_free(tmp); +} + +static gint compose_exec_ext_editor_real(const gchar *file) +{ + static gchar *def_cmd = "emacs %s"; + gchar buf[1024]; + gchar *p; + gchar **cmdline; + pid_t pid; + + g_return_val_if_fail(file != NULL, -1); + + if ((pid = fork()) < 0) { + perror("fork"); + return -1; + } + + if (pid != 0) return pid; + + /* grandchild process */ + + if (setpgid(0, getppid())) + perror("setpgid"); + + if (prefs_common.ext_editor_cmd && + (p = strchr(prefs_common.ext_editor_cmd, '%')) && + *(p + 1) == 's' && !strchr(p + 2, '%')) { + g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file); + } else { + if (prefs_common.ext_editor_cmd) + g_warning(_("External editor command line is invalid: `%s'\n"), + prefs_common.ext_editor_cmd); + g_snprintf(buf, sizeof(buf), def_cmd, file); + } + + cmdline = strsplit_with_quote(buf, " ", 1024); + execvp(cmdline[0], cmdline); + + perror("execvp"); + g_strfreev(cmdline); + + _exit(1); +} + +static gboolean compose_ext_editor_kill(Compose *compose) +{ + pid_t pgid = compose->exteditor_pid * -1; + gint ret; + + ret = kill(pgid, 0); + + if (ret == 0 || (ret == -1 && EPERM == errno)) { + AlertValue val; + gchar *msg; + + msg = g_strdup_printf + (_("The external editor is still working.\n" + "Force terminating the process?\n" + "process group id: %d"), -pgid); + val = alertpanel(_("Notice"), msg, _("Yes"), _("+No"), NULL); + g_free(msg); + + if (val == G_ALERTDEFAULT) { + gdk_input_remove(compose->exteditor_tag); + close(compose->exteditor_readdes); + + if (kill(pgid, SIGTERM) < 0) perror("kill"); + waitpid(compose->exteditor_pid, NULL, 0); + + g_warning(_("Terminated process group id: %d"), -pgid); + g_warning(_("Temporary file: %s"), + compose->exteditor_file); + + compose_set_ext_editor_sensitive(compose, TRUE); + + g_free(compose->exteditor_file); + compose->exteditor_file = NULL; + compose->exteditor_pid = -1; + compose->exteditor_readdes = -1; + compose->exteditor_tag = -1; + } else + return FALSE; + } + + return TRUE; +} + +static void compose_input_cb(gpointer data, gint source, + GdkInputCondition condition) +{ + gchar buf[3]; + Compose *compose = (Compose *)data; + gint i = 0; + + debug_print(_("Compose: input from monitoring process\n")); + + gdk_input_remove(compose->exteditor_tag); + + for (;;) { + if (read(source, &buf[i], 1) < 1) { + buf[0] = '3'; + break; + } + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + i++; + if (i == sizeof(buf) - 1) + break; + } + + waitpid(compose->exteditor_pid, NULL, 0); + + if (buf[0] == '0') { /* success */ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer(text); + + gtk_text_buffer_set_text(buffer, "", 0); + compose_insert_file(compose, compose->exteditor_file); + compose_changed_cb(NULL, compose); + + if (unlink(compose->exteditor_file) < 0) + FILE_OP_ERROR(compose->exteditor_file, "unlink"); + } else if (buf[0] == '1') { /* failed */ + g_warning(_("Couldn't exec external editor\n")); + if (unlink(compose->exteditor_file) < 0) + FILE_OP_ERROR(compose->exteditor_file, "unlink"); + } else if (buf[0] == '2') { + g_warning(_("Couldn't write to file\n")); + } else if (buf[0] == '3') { + g_warning(_("Pipe read failed\n")); + } + + close(source); + + compose_set_ext_editor_sensitive(compose, TRUE); + + g_free(compose->exteditor_file); + compose->exteditor_file = NULL; + compose->exteditor_pid = -1; + compose->exteditor_readdes = -1; + compose->exteditor_tag = -1; +} + +static void compose_set_ext_editor_sensitive(Compose *compose, + gboolean sensitive) +{ + GtkItemFactory *ifactory; + + ifactory = gtk_item_factory_from_widget(compose->menubar); + + menu_set_sensitive(ifactory, "/File/Send", sensitive); + menu_set_sensitive(ifactory, "/File/Send later", sensitive); + menu_set_sensitive(ifactory, "/File/Save to draft folder", + sensitive); + menu_set_sensitive(ifactory, "/File/Insert file", sensitive); + menu_set_sensitive(ifactory, "/File/Insert signature", sensitive); + menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive); + menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive); + menu_set_sensitive(ifactory, "/Tools/Edit with external editor", + sensitive); + + gtk_widget_set_sensitive(compose->text, sensitive); + gtk_widget_set_sensitive(compose->send_btn, sensitive); + gtk_widget_set_sensitive(compose->sendl_btn, sensitive); + gtk_widget_set_sensitive(compose->draft_btn, sensitive); + gtk_widget_set_sensitive(compose->insert_btn, sensitive); + gtk_widget_set_sensitive(compose->sig_btn, sensitive); + gtk_widget_set_sensitive(compose->exteditor_btn, sensitive); + gtk_widget_set_sensitive(compose->linewrap_btn, sensitive); +} + +/** + * compose_undo_state_changed: + * + * Change the sensivity of the menuentries undo and redo + **/ +static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state, + gint redo_state, gpointer data) +{ + GtkWidget *widget = GTK_WIDGET(data); + GtkItemFactory *ifactory; + + g_return_if_fail(widget != NULL); + + ifactory = gtk_item_factory_from_widget(widget); + + switch (undo_state) { + case UNDO_STATE_TRUE: + if (!undostruct->undo_state) { + debug_print ("Set_undo - Testpoint\n"); + undostruct->undo_state = TRUE; + menu_set_sensitive(ifactory, "/Edit/Undo", TRUE); + } + break; + case UNDO_STATE_FALSE: + if (undostruct->undo_state) { + undostruct->undo_state = FALSE; + menu_set_sensitive(ifactory, "/Edit/Undo", FALSE); + } + break; + case UNDO_STATE_UNCHANGED: + break; + case UNDO_STATE_REFRESH: + menu_set_sensitive(ifactory, "/Edit/Undo", + undostruct->undo_state); + break; + default: + g_warning("Undo state not recognized"); + break; + } + + switch (redo_state) { + case UNDO_STATE_TRUE: + if (!undostruct->redo_state) { + undostruct->redo_state = TRUE; + menu_set_sensitive(ifactory, "/Edit/Redo", TRUE); + } + break; + case UNDO_STATE_FALSE: + if (undostruct->redo_state) { + undostruct->redo_state = FALSE; + menu_set_sensitive(ifactory, "/Edit/Redo", FALSE); + } + break; + case UNDO_STATE_UNCHANGED: + break; + case UNDO_STATE_REFRESH: + menu_set_sensitive(ifactory, "/Edit/Redo", + undostruct->redo_state); + break; + default: + g_warning("Redo state not recognized"); + break; + } +} + +static gint calc_cursor_xpos(GtkTextView *text, gint extra, gint char_width) +{ +#if 0 + gint cursor_pos; + + cursor_pos = (text->cursor_pos_x - extra) / char_width; + cursor_pos = MAX(cursor_pos, 0); + + return cursor_pos; +#endif +#warning FIXME_GTK2 + return 0; +} + +/* callback functions */ + +/* compose_edit_size_alloc() - called when resized. don't know whether Gtk + * includes "non-client" (windows-izm) in calculation, so this calculation + * may not be accurate. + */ +static gboolean compose_edit_size_alloc(GtkEditable *widget, + GtkAllocation *allocation, + GtkSHRuler *shruler) +{ + if (prefs_common.show_ruler) { + gint char_width = 0, char_height = 0; + gint line_width_in_chars; + + gtkut_get_font_size(GTK_WIDGET(widget), + &char_width, &char_height); + line_width_in_chars = + (allocation->width - allocation->x) / char_width; + + /* got the maximum */ + gtk_ruler_set_range(GTK_RULER(shruler), + 0.0, line_width_in_chars, + calc_cursor_xpos(GTK_TEXT_VIEW(widget), + allocation->x, + char_width), + /*line_width_in_chars*/ char_width); + } + + return TRUE; +} + +static void toolbar_send_cb(GtkWidget *widget, gpointer data) +{ + compose_send_cb(data, 0, NULL); +} + +static void toolbar_send_later_cb(GtkWidget *widget, gpointer data) +{ + compose_send_later_cb(data, 0, NULL); +} + +static void toolbar_draft_cb(GtkWidget *widget, gpointer data) +{ + compose_draft_cb(data, 0, NULL); +} + +static void toolbar_insert_cb(GtkWidget *widget, gpointer data) +{ + compose_insert_file_cb(data, 0, NULL); +} + +static void toolbar_attach_cb(GtkWidget *widget, gpointer data) +{ + compose_attach_cb(data, 0, NULL); +} + +static void toolbar_sig_cb(GtkWidget *widget, gpointer data) +{ + Compose *compose = (Compose *)data; + + compose_insert_sig(compose, FALSE); +} + +static void toolbar_ext_editor_cb(GtkWidget *widget, gpointer data) +{ + Compose *compose = (Compose *)data; + + compose_exec_ext_editor(compose); +} + +static void toolbar_linewrap_cb(GtkWidget *widget, gpointer data) +{ + Compose *compose = (Compose *)data; + + compose_wrap_line_all(compose); +} + +static void toolbar_address_cb(GtkWidget *widget, gpointer data) +{ + compose_address_cb(data, 0, NULL); +} + +static void account_activated(GtkMenuItem *menuitem, gpointer data) +{ + Compose *compose = (Compose *)data; + + PrefsAccount *ac; + + ac = (PrefsAccount *)g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID); + g_return_if_fail(ac != NULL); + + if (ac != compose->account) + compose_select_account(compose, ac, FALSE); +} + +static void attach_selected(GtkCList *clist, gint row, gint column, + GdkEvent *event, gpointer data) +{ + Compose *compose = (Compose *)data; + + if (event && event->type == GDK_2BUTTON_PRESS) + compose_attach_property(compose); +} + +static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + Compose *compose = (Compose *)data; + GtkCList *clist = GTK_CLIST(compose->attach_clist); + gint row, column; + + if (!event) return FALSE; + + if (event->button == 3) { + if ((clist->selection && !clist->selection->next) || + !clist->selection) { + gtk_clist_unselect_all(clist); + if (gtk_clist_get_selection_info(clist, + event->x, event->y, + &row, &column)) { + gtk_clist_select_row(clist, row, column); + gtkut_clist_set_focus_row(clist, row); + } + } + gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL, + NULL, NULL, event->button, event->time); + } + + return FALSE; +} + +static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + Compose *compose = (Compose *)data; + + if (!event) return FALSE; + + switch (event->keyval) { + case GDK_Delete: + compose_attach_remove_selected(compose); + break; + } + + return FALSE; +} + +static void compose_send_cb(gpointer data, guint action, GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + gint val; + + val = compose_send(compose); + + if (val == 0) gtk_widget_destroy(compose->window); +} + +static void compose_send_later_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + FolderItem *queue; + gchar tmp[MAXPATHLEN + 1]; + + if (compose_check_entries(compose) == FALSE) + return; + + queue = account_get_special_folder(compose->account, F_QUEUE); + if (!queue) { + g_warning("can't find queue folder\n"); + return; + } + if (!FOLDER_IS_LOCAL(queue->folder) && + !main_window_toggle_online_if_offline(main_window_get())) + return; + + g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.%p", + get_tmp_dir(), G_DIR_SEPARATOR, compose); + + if (compose->mode == COMPOSE_REDIRECT) { + if (compose_redirect_write_to_file(compose, tmp) < 0) { + alertpanel_error(_("Can't queue the message.")); + return; + } + } else { + if (prefs_common.linewrap_at_send) + compose_wrap_line_all(compose); + + if (compose_write_to_file(compose, tmp, FALSE) < 0) { + alertpanel_error(_("Can't queue the message.")); + return; + } + } + + if (compose_queue(compose, tmp) < 0) { + alertpanel_error(_("Can't queue the message.")); + return; + } + + if (unlink(tmp) < 0) + FILE_OP_ERROR(tmp, "unlink"); + + gtk_widget_destroy(compose->window); +} + +static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + FolderItem *draft; + gchar *tmp; + gint msgnum; + MsgFlags flag = {0, 0}; + static gboolean lock = FALSE; + + if (lock) return; + + draft = account_get_special_folder(compose->account, F_DRAFT); + g_return_if_fail(draft != NULL); + + lock = TRUE; + + tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(), + G_DIR_SEPARATOR, compose); + + if (compose_write_to_file(compose, tmp, TRUE) < 0) { + g_free(tmp); + lock = FALSE; + return; + } + + folder_item_scan(draft); + if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) { + unlink(tmp); + g_free(tmp); + lock = FALSE; + return; + } + g_free(tmp); + draft->mtime = 0; /* force updating */ + + if (compose->mode == COMPOSE_REEDIT) { + compose_remove_reedit_target(compose); + if (compose->targetinfo && + compose->targetinfo->folder != draft) + folderview_update_item(compose->targetinfo->folder, + TRUE); + } + + folder_item_scan(draft); + folderview_update_item(draft, TRUE); + + lock = FALSE; + + /* 0: quit editing 1: keep editing */ + if (action == 0) + gtk_widget_destroy(compose->window); + else { + struct stat s; + gchar *path; + + path = folder_item_fetch_msg(draft, msgnum); + g_return_if_fail(path != NULL); + if (stat(path, &s) < 0) { + FILE_OP_ERROR(path, "stat"); + g_free(path); + lock = FALSE; + return; + } + g_free(path); + + procmsg_msginfo_free(compose->targetinfo); + compose->targetinfo = g_new0(MsgInfo, 1); + compose->targetinfo->msgnum = msgnum; + compose->targetinfo->size = s.st_size; + compose->targetinfo->mtime = s.st_mtime; + compose->targetinfo->folder = draft; + compose->mode = COMPOSE_REEDIT; + } +} + +static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + const gchar *file; + + file = filesel_select_file(_("Select file"), NULL); + + if (file && *file) + compose_attach_append(compose, file, file, NULL); +} + +static void compose_insert_file_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + const gchar *file; + + file = filesel_select_file(_("Select file"), NULL); + + if (file && *file) + compose_insert_file(compose, file); +} + +static void compose_insert_sig_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + compose_insert_sig(compose, FALSE); +} + +static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + compose_close_cb(data, 0, NULL); + return TRUE; +} + +static void compose_close_cb(gpointer data, guint action, GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + AlertValue val; + + if (compose->exteditor_tag != -1) { + if (!compose_ext_editor_kill(compose)) + return; + } + + if (compose->modified) { + val = alertpanel(_("Discard message"), + _("This message has been modified. discard it?"), + _("Discard"), _("to Draft"), _("Cancel")); + + switch (val) { + case G_ALERTDEFAULT: + break; + case G_ALERTALTERNATE: + compose_draft_cb(data, 0, NULL); + return; + default: + return; + } + } + + gtk_widget_destroy(compose->window); +} + +static void compose_address_cb(gpointer data, guint action, GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + addressbook_open(compose); +} + +static void compose_template_activate_cb(GtkWidget *widget, gpointer data) +{ + Compose *compose = (Compose *)data; + Template *tmpl; + gchar *msg; + AlertValue val; + + tmpl = g_object_get_data(G_OBJECT(widget), "template"); + g_return_if_fail(tmpl != NULL); + + msg = g_strdup_printf(_("Do you want to apply the template `%s' ?"), + tmpl->name); + val = alertpanel(_("Apply template"), msg, + _("Replace"), _("Insert"), _("Cancel")); + g_free(msg); + + if (val == G_ALERTDEFAULT) + compose_template_apply(compose, tmpl, TRUE); + else if (val == G_ALERTALTERNATE) + compose_template_apply(compose, tmpl, FALSE); +} + +static void compose_ext_editor_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + compose_exec_ext_editor(compose); +} + +static void compose_destroy_cb(GtkWidget *widget, Compose *compose) +{ + compose_destroy(compose); +} + +static void compose_undo_cb(Compose *compose) +{ + undo_undo(compose->undostruct); +} + +static void compose_redo_cb(Compose *compose) +{ + undo_redo(compose->undostruct); +} + +static void compose_cut_cb(Compose *compose) +{ + if (compose->focused_editable && + GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) { + if (GTK_IS_EDITABLE(compose->focused_editable)) { + gtk_editable_cut_clipboard + (GTK_EDITABLE(compose->focused_editable)); + } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) { + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + buffer = gtk_text_view_get_buffer(text); + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + gtk_text_buffer_cut_clipboard(buffer, clipboard, TRUE); + } + } +} + +static void compose_copy_cb(Compose *compose) +{ + if (compose->focused_editable && + GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) { + if (GTK_IS_EDITABLE(compose->focused_editable)) { + gtk_editable_copy_clipboard + (GTK_EDITABLE(compose->focused_editable)); + } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) { + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + buffer = gtk_text_view_get_buffer(text); + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + gtk_text_buffer_copy_clipboard(buffer, clipboard); + } + } +} + +static void compose_paste_cb(Compose *compose) +{ + if (compose->focused_editable && + GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) { + if (GTK_IS_EDITABLE(compose->focused_editable)) { + gtk_editable_paste_clipboard + (GTK_EDITABLE(compose->focused_editable)); + } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) { + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + buffer = gtk_text_view_get_buffer(text); + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + gtk_text_buffer_paste_clipboard(buffer, clipboard, + NULL, TRUE); + } + } +} + +static void compose_paste_as_quote_cb(Compose *compose) +{ + compose->paste_as_quotation = TRUE; + + if (compose->focused_editable && + GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) { + if (GTK_IS_EDITABLE(compose->focused_editable)) { + gtk_editable_paste_clipboard + (GTK_EDITABLE(compose->focused_editable)); + } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) { + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + buffer = gtk_text_view_get_buffer(text); + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + gtk_text_buffer_paste_clipboard(buffer, clipboard, + NULL, TRUE); + } + } + + compose->paste_as_quotation = FALSE; +} + +static void compose_allsel_cb(Compose *compose) +{ + if (compose->focused_editable && + GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) { + if (GTK_IS_EDITABLE(compose->focused_editable)) { + gtk_editable_select_region + (GTK_EDITABLE(compose->focused_editable), + 0, -1); + } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) { + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_place_cursor(buffer, &iter); + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_move_mark_by_name + (buffer, "selection_bound", &iter); + } + } +} + +static void textview_move_beginning_of_line(GtkTextView *text) +{ +} + +static void textview_move_forward_character(GtkTextView *text) +{ +} + +static void textview_move_backward_character(GtkTextView *text) +{ +} + +static void textview_move_forward_word(GtkTextView *text) +{ +} + +static void textview_move_backward_word(GtkTextView *text) +{ +} + +static void textview_move_end_of_line(GtkTextView *text) +{ +} + +static void textview_move_next_line(GtkTextView *text) +{ +} + +static void textview_move_previous_line(GtkTextView *text) +{ +} + +static void textview_delete_forward_character(GtkTextView *text) +{ +} + +static void textview_delete_backward_character(GtkTextView *text) +{ +} + +static void textview_delete_forward_word(GtkTextView *text) +{ +} + +static void textview_delete_backward_word(GtkTextView *text) +{ +} + +static void textview_delete_line(GtkTextView *text) +{ +} + +static void textview_delete_to_line_end(GtkTextView *text) +{ +} + +static void compose_action_cb(Compose *compose, ComposeAction action) +{ + GtkTextView *text = GTK_TEXT_VIEW(compose->text); + static struct { + void (*do_action) (GtkTextView *text); + } action_table[] = { + {textview_move_beginning_of_line}, + {textview_move_forward_character}, + {textview_move_backward_character}, + {textview_move_forward_word}, + {textview_move_backward_word}, + {textview_move_end_of_line}, + {textview_move_next_line}, + {textview_move_previous_line}, + {textview_delete_forward_character}, + {textview_delete_backward_character}, + {textview_delete_forward_word}, + {textview_delete_backward_word}, + {textview_delete_line}, + {NULL}, /* textview_delete_line_n */ + {textview_delete_to_line_end} + }; + + if (!GTK_WIDGET_HAS_FOCUS(text)) return; + + if (action >= COMPOSE_ACTION_MOVE_BEGINNING_OF_LINE && + action <= COMPOSE_ACTION_DELETE_TO_LINE_END) + action_table[action].do_action(text); +} + +static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose) +{ + if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget)) + compose->focused_editable = widget; +} + +static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose) +{ + if (compose->modified == FALSE) { + compose->modified = TRUE; + compose_set_title(compose); + } +} + +static void compose_toggle_autowrap_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active; + if (compose->autowrap) + compose_wrap_line_all_full(compose, TRUE); +} + +static void compose_toggle_to_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(compose->to_hbox); + gtk_widget_show(compose->to_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 1, 4); + compose->use_to = TRUE; + } else { + gtk_widget_hide(compose->to_hbox); + gtk_widget_hide(compose->to_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 1, 0); + gtk_widget_queue_resize(compose->table_vbox); + compose->use_to = FALSE; + } + + if (addressbook_get_target_compose() == compose) + addressbook_set_target_compose(compose); +} + +static void compose_toggle_cc_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(compose->cc_hbox); + gtk_widget_show(compose->cc_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 3, 4); + compose->use_cc = TRUE; + } else { + gtk_widget_hide(compose->cc_hbox); + gtk_widget_hide(compose->cc_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 3, 0); + gtk_widget_queue_resize(compose->table_vbox); + compose->use_cc = FALSE; + } + + if (addressbook_get_target_compose() == compose) + addressbook_set_target_compose(compose); +} + +static void compose_toggle_bcc_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(compose->bcc_hbox); + gtk_widget_show(compose->bcc_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 4, 4); + compose->use_bcc = TRUE; + } else { + gtk_widget_hide(compose->bcc_hbox); + gtk_widget_hide(compose->bcc_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 4, 0); + gtk_widget_queue_resize(compose->table_vbox); + compose->use_bcc = FALSE; + } + + if (addressbook_get_target_compose() == compose) + addressbook_set_target_compose(compose); +} + +static void compose_toggle_replyto_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(compose->reply_hbox); + gtk_widget_show(compose->reply_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 5, 4); + compose->use_replyto = TRUE; + } else { + gtk_widget_hide(compose->reply_hbox); + gtk_widget_hide(compose->reply_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 5, 0); + gtk_widget_queue_resize(compose->table_vbox); + compose->use_replyto = FALSE; + } +} + +static void compose_toggle_followupto_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(compose->followup_hbox); + gtk_widget_show(compose->followup_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 6, 4); + compose->use_followupto = TRUE; + } else { + gtk_widget_hide(compose->followup_hbox); + gtk_widget_hide(compose->followup_entry); + gtk_table_set_row_spacing(GTK_TABLE(compose->table), 6, 0); + gtk_widget_queue_resize(compose->table_vbox); + compose->use_followupto = FALSE; + } +} + +static void compose_toggle_attach_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_ref(compose->edit_vbox); + + gtkut_container_remove(GTK_CONTAINER(compose->vbox2), + compose->edit_vbox); + gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox); + gtk_box_pack_start(GTK_BOX(compose->vbox2), compose->paned, + TRUE, TRUE, 0); + gtk_widget_show(compose->paned); + + gtk_widget_unref(compose->edit_vbox); + gtk_widget_unref(compose->paned); + + compose->use_attach = TRUE; + } else { + gtk_widget_ref(compose->paned); + gtk_widget_ref(compose->edit_vbox); + + gtkut_container_remove(GTK_CONTAINER(compose->vbox2), + compose->paned); + gtkut_container_remove(GTK_CONTAINER(compose->paned), + compose->edit_vbox); + gtk_box_pack_start(GTK_BOX(compose->vbox2), + compose->edit_vbox, TRUE, TRUE, 0); + + gtk_widget_unref(compose->edit_vbox); + + compose->use_attach = FALSE; + } +} + +#if USE_GPGME +static void compose_toggle_sign_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) + compose->use_signing = TRUE; + else + compose->use_signing = FALSE; +} + +static void compose_toggle_encrypt_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) + compose->use_encryption = TRUE; + else + compose->use_encryption = FALSE; +} +#endif /* USE_GPGME */ + +static void compose_toggle_ruler_cb(gpointer data, guint action, + GtkWidget *widget) +{ + Compose *compose = (Compose *)data; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(compose->ruler_hbox); + prefs_common.show_ruler = TRUE; + } else { + gtk_widget_hide(compose->ruler_hbox); + gtk_widget_queue_resize(compose->edit_vbox); + prefs_common.show_ruler = FALSE; + } +} + +static void compose_attach_drag_received_cb (GtkWidget *widget, + GdkDragContext *drag_context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + Compose *compose = (Compose *)user_data; + GList *list, *tmp; + + list = uri_list_extract_filenames((const gchar *)data->data); + for (tmp = list; tmp != NULL; tmp = tmp->next) + compose_attach_append + (compose, (const gchar *)tmp->data, + (const gchar *)tmp->data, NULL); + if (list) compose_changed_cb(NULL, compose); + list_free_strings(list); + g_list_free(list); +} + +static void compose_insert_drag_received_cb (GtkWidget *widget, + GdkDragContext *drag_context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + Compose *compose = (Compose *)user_data; + GList *list, *tmp; + + list = uri_list_extract_filenames((const gchar *)data->data); + for (tmp = list; tmp != NULL; tmp = tmp->next) + compose_insert_file(compose, (const gchar *)tmp->data); + list_free_strings(list); + g_list_free(list); +} + +static void to_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + if (GTK_WIDGET_VISIBLE(compose->newsgroups_entry)) + gtk_widget_grab_focus(compose->newsgroups_entry); + else if (GTK_WIDGET_VISIBLE(compose->cc_entry)) + gtk_widget_grab_focus(compose->cc_entry); + else if (GTK_WIDGET_VISIBLE(compose->bcc_entry)) + gtk_widget_grab_focus(compose->bcc_entry); + else if (GTK_WIDGET_VISIBLE(compose->reply_entry)) + gtk_widget_grab_focus(compose->reply_entry); + else if (GTK_WIDGET_VISIBLE(compose->followup_entry)) + gtk_widget_grab_focus(compose->followup_entry); + else + gtk_widget_grab_focus(compose->subject_entry); +} + +static void newsgroups_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + if (GTK_WIDGET_VISIBLE(compose->cc_entry)) + gtk_widget_grab_focus(compose->cc_entry); + else if (GTK_WIDGET_VISIBLE(compose->bcc_entry)) + gtk_widget_grab_focus(compose->bcc_entry); + else if (GTK_WIDGET_VISIBLE(compose->reply_entry)) + gtk_widget_grab_focus(compose->reply_entry); + else if (GTK_WIDGET_VISIBLE(compose->followup_entry)) + gtk_widget_grab_focus(compose->followup_entry); + else + gtk_widget_grab_focus(compose->subject_entry); +} + +static void cc_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + if (GTK_WIDGET_VISIBLE(compose->bcc_entry)) + gtk_widget_grab_focus(compose->bcc_entry); + else if (GTK_WIDGET_VISIBLE(compose->reply_entry)) + gtk_widget_grab_focus(compose->reply_entry); + else if (GTK_WIDGET_VISIBLE(compose->followup_entry)) + gtk_widget_grab_focus(compose->followup_entry); + else + gtk_widget_grab_focus(compose->subject_entry); +} + +static void bcc_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + if (GTK_WIDGET_VISIBLE(compose->reply_entry)) + gtk_widget_grab_focus(compose->reply_entry); + else if (GTK_WIDGET_VISIBLE(compose->followup_entry)) + gtk_widget_grab_focus(compose->followup_entry); + else + gtk_widget_grab_focus(compose->subject_entry); +} + +static void replyto_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + if (GTK_WIDGET_VISIBLE(compose->followup_entry)) + gtk_widget_grab_focus(compose->followup_entry); + else + gtk_widget_grab_focus(compose->subject_entry); +} + +static void followupto_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + gtk_widget_grab_focus(compose->subject_entry); +} + +static void subject_activated(GtkWidget *widget, Compose *compose) +{ + if (compose_send_control_enter(compose)) return; + + gtk_widget_grab_focus(compose->text); +} + +static void text_activated(GtkWidget *widget, Compose *compose) +{ + compose_send_control_enter(compose); +} + +static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter, + const gchar *text, gint len, Compose *compose) +{ + g_return_if_fail(text != NULL); + + g_signal_handlers_block_by_func(G_OBJECT(buffer), + G_CALLBACK(text_inserted), + compose); + + if (compose->paste_as_quotation) { + gchar *new_text; + gchar *qmark; + + if (len < 0) + len = strlen(text); + new_text = g_strndup(text, len); + if (prefs_common.quotemark && *prefs_common.quotemark) + qmark = prefs_common.quotemark; + else + qmark = "> "; + gtk_text_buffer_place_cursor(buffer, iter); + compose_quote_fmt(compose, NULL, "%Q", qmark, new_text); + g_free(new_text); + } else + gtk_text_buffer_insert(buffer, iter, text, len); + + if (compose->autowrap) + compose_wrap_line_all_full(compose, TRUE); + + g_signal_handlers_unblock_by_func(G_OBJECT(buffer), + G_CALLBACK(text_inserted), + compose); + g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text"); +} + +static gboolean compose_send_control_enter(Compose *compose) +{ + GdkEvent *ev; + GdkEventKey *kev; + GtkItemFactory *ifactory; + GtkAccelKey *accel; + GtkWidget *send_menu; + GSList *list; + GdkModifierType ignored_mods = + (GDK_LOCK_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK | + GDK_MOD4_MASK | GDK_MOD5_MASK); + + ev = gtk_get_current_event(); + if (ev->type != GDK_KEY_PRESS) return FALSE; + + kev = (GdkEventKey *)ev; + if (!(kev->keyval == GDK_Return && (kev->state & GDK_CONTROL_MASK))) + return FALSE; + + ifactory = gtk_item_factory_from_widget(compose->menubar); + send_menu = gtk_item_factory_get_widget(ifactory, "/File/Send"); + list = gtk_accel_groups_from_object(G_OBJECT(send_menu)); + if (!list) + return FALSE; + + accel = (GtkAccelKey *)list->data; + if (accel && accel->accel_key == kev->keyval && + (accel->accel_mods & ~ignored_mods) == + (kev->state & ~ignored_mods)) { + compose_send_cb(compose, 0, NULL); + return TRUE; + } + + return FALSE; +} diff --git a/src/compose.h b/src/compose.h new file mode 100644 index 00000000..7fe7a294 --- /dev/null +++ b/src/compose.h @@ -0,0 +1,214 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __COMPOSE_H__ +#define __COMPOSE_H__ + +#include +#include +#include + +typedef struct _Compose Compose; +typedef struct _AttachInfo AttachInfo; + +#include "procmsg.h" +#include "procmime.h" +#include "folder.h" +#include "addressbook.h" +#include "prefs_account.h" +#include "undo.h" + +typedef enum +{ + COMPOSE_ENTRY_TO, + COMPOSE_ENTRY_CC, + COMPOSE_ENTRY_BCC, + COMPOSE_ENTRY_REPLY_TO, + COMPOSE_ENTRY_SUBJECT, + COMPOSE_ENTRY_NEWSGROUPS, + COMPOSE_ENTRY_FOLLOWUP_TO +} ComposeEntryType; + +typedef enum +{ + COMPOSE_REPLY = 1, + COMPOSE_REPLY_TO_SENDER = 2, + COMPOSE_REPLY_TO_ALL = 3, + COMPOSE_REPLY_TO_LIST = 4, + COMPOSE_FORWARD = 5, + COMPOSE_FORWARD_AS_ATTACH = 6, + COMPOSE_NEW = 7, + COMPOSE_REDIRECT = 8, + COMPOSE_REEDIT = 9, + + COMPOSE_WITH_QUOTE = 1 << 16, + COMPOSE_WITHOUT_QUOTE = 2 << 16, + + COMPOSE_MODE_MASK = 0xffff, + COMPOSE_QUOTE_MODE_MASK = 0x30000 +} ComposeMode; + +#define COMPOSE_MODE(mode) ((mode) & COMPOSE_MODE_MASK) +#define COMPOSE_QUOTE_MODE(mode) ((mode) & COMPOSE_QUOTE_MODE_MASK) + +struct _Compose +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *menubar; + + GtkWidget *handlebox; + GtkWidget *toolbar; + GtkWidget *send_btn; + GtkWidget *sendl_btn; + GtkWidget *draft_btn; + GtkWidget *insert_btn; + GtkWidget *attach_btn; + GtkWidget *sig_btn; + GtkWidget *exteditor_btn; + GtkWidget *linewrap_btn; + GtkWidget *addrbook_btn; + + GtkWidget *vbox2; + + GtkWidget *table_vbox; + GtkWidget *table; + GtkWidget *to_hbox; + GtkWidget *to_entry; + GtkWidget *newsgroups_hbox; + GtkWidget *newsgroups_entry; + GtkWidget *subject_entry; + GtkWidget *cc_hbox; + GtkWidget *cc_entry; + GtkWidget *bcc_hbox; + GtkWidget *bcc_entry; + GtkWidget *reply_hbox; + GtkWidget *reply_entry; + GtkWidget *followup_hbox; + GtkWidget *followup_entry; + + GtkWidget *paned; + + GtkWidget *attach_scrwin; + GtkWidget *attach_clist; + + GtkWidget *edit_vbox; + GtkWidget *ruler_hbox; + GtkWidget *ruler; + GtkWidget *scrolledwin; + GtkWidget *text; + + GtkWidget *focused_editable; + + GtkWidget *popupmenu; + + GtkItemFactory *popupfactory; + + GtkWidget *tmpl_menu; + + ComposeMode mode; + + MsgInfo *targetinfo; + MsgInfo *replyinfo; + + gchar *replyto; + gchar *cc; + gchar *bcc; + gchar *newsgroups; + gchar *followup_to; + + gchar *ml_post; + + gchar *inreplyto; + gchar *references; + gchar *msgid; + gchar *boundary; + + gboolean autowrap; + + gboolean use_to; + gboolean use_cc; + gboolean use_bcc; + gboolean use_replyto; + gboolean use_newsgroups; + gboolean use_followupto; + gboolean use_attach; + + /* privacy settings */ + gboolean use_signing; + gboolean use_encryption; + + gboolean modified; + + gboolean paste_as_quotation; + + GSList *to_list; + GSList *newsgroup_list; + + PrefsAccount *account; + + UndoMain *undostruct; + + gchar *sig_str; + + /* external editor */ + gchar *exteditor_file; + pid_t exteditor_pid; + gint exteditor_readdes; + gint exteditor_tag; +}; + +struct _AttachInfo +{ + gchar *file; + gchar *content_type; + EncodingType encoding; + gchar *name; + off_t size; +}; + +void compose_new (PrefsAccount *account, + FolderItem *item, + const gchar *mailto, + GPtrArray *attach_files); + +void compose_reply (MsgInfo *msginfo, + FolderItem *item, + ComposeMode mode, + const gchar *body); +void compose_forward (GSList *mlist, + FolderItem *item, + gboolean as_attach, + const gchar *body); +void compose_redirect (MsgInfo *msginfo, + FolderItem *item); +void compose_reedit (MsgInfo *msginfo); + +GList *compose_get_compose_list (void); + +void compose_entry_set (Compose *compose, + const gchar *text, + ComposeEntryType type); +void compose_entry_append (Compose *compose, + const gchar *text, + ComposeEntryType type); + +void compose_reflect_prefs_all (void); + +#endif /* __COMPOSE_H__ */ diff --git a/src/customheader.c b/src/customheader.c new file mode 100644 index 00000000..2bcb0357 --- /dev/null +++ b/src/customheader.c @@ -0,0 +1,100 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "customheader.h" +#include "utils.h" + + +gchar *custom_header_get_str(CustomHeader *ch) +{ + return g_strdup_printf("%i:%s: %s", + ch->account_id, ch->name, + ch->value ? ch->value : ""); +} + +CustomHeader *custom_header_read_str(const gchar *buf) +{ + CustomHeader *ch; + gchar *account_id_str; + gint id; + gchar *name; + gchar *value; + gchar *tmp; + + Xstrdup_a(tmp, buf, return NULL); + + account_id_str = tmp; + + name = strchr(account_id_str, ':'); + if (!name) + return NULL; + else { + gchar *endp; + + *name++ = '\0'; + id = strtol(account_id_str, &endp, 10); + if (*endp != '\0') return NULL; + } + + value = strchr(name, ':'); + if (!value) return NULL; + + *value++ = '\0'; + + g_strstrip(name); + g_strstrip(value); + + ch = g_new0(CustomHeader, 1); + ch->account_id = id; + ch->name = *name ? g_strdup(name) : NULL; + ch->value = *value ? g_strdup(value) : NULL; + + return ch; +} + +CustomHeader *custom_header_find(GSList *header_list, const gchar *header) +{ + GSList *cur; + CustomHeader *chdr; + + for (cur = header_list; cur != NULL; cur = cur->next) { + chdr = (CustomHeader *)cur->data; + if (!strcasecmp(chdr->name, header)) + return chdr; + } + + return NULL; +} + +void custom_header_free(CustomHeader *ch) +{ + if (!ch) return; + + g_free(ch->name); + g_free(ch->value); + g_free(ch); +} diff --git a/src/customheader.h b/src/customheader.h new file mode 100644 index 00000000..8554ea7f --- /dev/null +++ b/src/customheader.h @@ -0,0 +1,40 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __CUSTOMHEADER_H__ +#define __CUSTOMHEADER_H__ + +#include + +struct _CustomHeader +{ + gint account_id; + gchar *name; + gchar *value; +}; + +typedef struct _CustomHeader CustomHeader; + +gchar *custom_header_get_str (CustomHeader *ch); +CustomHeader *custom_header_read_str (const gchar *buf); +CustomHeader *custom_header_find (GSList *header_list, + const gchar *header); +void custom_header_free (CustomHeader *ch); + +#endif /* __CUSTOMHEADER_H__ */ diff --git a/src/defs.h b/src/defs.h new file mode 100644 index 00000000..ed5699db --- /dev/null +++ b/src/defs.h @@ -0,0 +1,110 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __DEFS_H__ +#define __DEFS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if HAVE_PATHS_H +# include +#endif + +#if HAVE_SYS_PARAM_H +# include +#endif + +#define INBOX_DIR "inbox" +#define OUTBOX_DIR "sent" +#define QUEUE_DIR "queue" +#define DRAFT_DIR "draft" +#define TRASH_DIR "trash" +#define RC_DIR ".sylpheed-2.0" +#define NEWS_CACHE_DIR "newscache" +#define IMAP_CACHE_DIR "imapcache" +#define MIME_TMP_DIR "mimetmp" +#define COMMON_RC "sylpheedrc" +#define ACCOUNT_RC "accountrc" +#define FILTER_RC "filterrc" +#define FILTER_LIST "filter.xml" +#define FILTER_HEADER_RC "filterheaderrc" +#define CUSTOM_HEADER_RC "customheaderrc" +#define DISPLAY_HEADER_RC "dispheaderrc" +#define MENU_RC "menurc" +#define ACTIONS_RC "actionsrc" +#define COMMAND_HISTORY "command_history" +#define TEMPLATE_DIR "templates" +#define TMP_DIR "tmp" +#define NEWSGROUP_LIST ".newsgroup_list" +#define ADDRESS_BOOK "addressbook.xml" +#define MANUAL_HTML_INDEX "sylpheed.html" +#define FAQ_HTML_INDEX "sylpheed-faq.html" +#define HOMEPAGE_URI "http://sylpheed.good-day.net/" +#define FOLDER_LIST "folderlist.xml" +#define CACHE_FILE ".sylpheed2_cache" +#define MARK_FILE ".sylpheed_mark" +#define CACHE_VERSION 19 +#define MARK_VERSION 2 + +#define DEFAULT_SIGNATURE ".signature" +#define DEFAULT_INC_PATH "/usr/bin/mh/inc" +#define DEFAULT_INC_PROGRAM "inc" +/* #define DEFAULT_INC_PATH "/usr/bin/imget" */ +/* #define DEFAULT_INC_PROGRAM "imget" */ +#define DEFAULT_SENDMAIL_CMD "/usr/sbin/sendmail -t -i" +#define DEFAULT_BROWSER_CMD "mozilla-firefox -remote 'openURL(%s,new-window)'" + +#ifdef _PATH_MAILDIR +# define DEFAULT_SPOOL_PATH _PATH_MAILDIR +#else +# define DEFAULT_SPOOL_PATH "/var/spool/mail" +#endif + +#define BUFFSIZE 8192 + +#ifndef MAXPATHLEN +# define MAXPATHLEN 4095 +#endif + +#define DEFAULT_HEIGHT 460 +#define DEFAULT_FOLDERVIEW_WIDTH 179 +#define DEFAULT_MAINVIEW_WIDTH 600 +#define DEFAULT_SUMMARY_HEIGHT 140 +#define DEFAULT_HEADERVIEW_HEIGHT 40 +#define DEFAULT_COMPOSE_HEIGHT 560 +#define BORDER_WIDTH 2 +#define CTREE_INDENT 18 +#define FOLDER_SPACING 4 +#define MAX_ENTRY_LENGTH 8191 +#define COLOR_DIM 35000 +#define UI_REFRESH_INTERVAL 50000 /* usec */ +#define FOLDER_UPDATE_INTERVAL 1500 /* msec */ +#define PROGRESS_UPDATE_INTERVAL 200 /* msec */ +#define SESSION_TIMEOUT_INTERVAL 60 /* sec */ +#define MAX_HISTORY_SIZE 16 + +#define DEFAULT_NORMAL_FONT "Helvetica 12" +#define DEFAULT_MESSAGE_FONT "Helvetica 14" +#define DEFAULT_BOLD_FONT "Helvetica Bold 12" +#define DEFAULT_SMALL_FONT "Helvetica 10" +#define DEFAULT_TITLE_FONT "Helvetica 16" + +#endif /* __DEFS_H__ */ diff --git a/src/displayheader.c b/src/displayheader.c new file mode 100644 index 00000000..1db374cd --- /dev/null +++ b/src/displayheader.c @@ -0,0 +1,59 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "displayheader.h" + +gchar *display_header_prop_get_str(DisplayHeaderProp *dp) +{ + return g_strconcat(dp->hidden ? "-" : "", dp->name, NULL); +} + +DisplayHeaderProp *display_header_prop_read_str(gchar *buf) +{ + DisplayHeaderProp *dp; + + dp = g_new0(DisplayHeaderProp, 1); + + dp->hidden = FALSE; + if (*buf == '-') { + dp->hidden = TRUE; + buf++; + } + if (*buf == '\0') { + g_free(dp); + return NULL; + } + dp->name = g_strdup(buf); + + return dp; +} + +void display_header_prop_free(DisplayHeaderProp *dp) +{ + if (!dp) return; + + g_free(dp->name); + g_free(dp); +} diff --git a/src/displayheader.h b/src/displayheader.h new file mode 100644 index 00000000..e7baa2be --- /dev/null +++ b/src/displayheader.h @@ -0,0 +1,37 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __DISPLAYHEADER_H__ +#define __DISPLAYHEADER_H__ + +#include + +typedef struct _DisplayHeaderProp DisplayHeaderProp; + +struct _DisplayHeaderProp +{ + gchar *name; + gboolean hidden; +}; + +gchar *display_header_prop_get_str (DisplayHeaderProp *dp); +DisplayHeaderProp *display_header_prop_read_str (gchar *buf); +void display_header_prop_free (DisplayHeaderProp *dp); + +#endif /* __DISPLAYHEADER_H__ */ diff --git a/src/editaddress.c b/src/editaddress.c new file mode 100644 index 00000000..35c57d20 --- /dev/null +++ b/src/editaddress.c @@ -0,0 +1,1198 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "mgutils.h" +#include "addressbook.h" +#include "addressitem.h" +#include "addritem.h" +#include "addrbook.h" +#include "manage_window.h" +#include "gtkutils.h" +#include "codeconv.h" + +#include "prefs_common.h" + +/* +static struct _AddressEdit_dlg { + GtkWidget *window; + GtkWidget *name_entry; + GtkWidget *addr_entry; + GtkWidget *rem_entry; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; +} addredit; +*/ + +static struct _PersonEdit_dlg { + GtkWidget *window; + GtkWidget *notebook; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; + + /* Basic data tab */ + GtkWidget *entry_name; + GtkWidget *entry_first; + GtkWidget *entry_last; + GtkWidget *entry_nick; + + /* EMail data tab */ + GtkWidget *entry_email; + GtkWidget *entry_alias; + GtkWidget *entry_remarks; + GtkWidget *clist_email; + + /* Attribute data tab */ + GtkWidget *entry_atname; + GtkWidget *entry_atvalue; + GtkWidget *clist_attrib; + + gint rowIndEMail; + gint rowIndAttrib; + gboolean editNew; + +} personeditdlg; + +typedef enum { + EMAIL_COL_EMAIL = 0, + EMAIL_COL_ALIAS = 1, + EMAIL_COL_REMARKS = 2 +} PersonEditEMailColumnPos; + +typedef enum { + ATTRIB_COL_NAME = 0, + ATTRIB_COL_VALUE = 1 +} PersonEditAttribColumnPos; + +#define EDITPERSON_WIDTH 520 +#define EDITPERSON_HEIGHT 340 + +#define EMAIL_N_COLS 3 +#define EMAIL_COL_WIDTH_EMAIL 180 +#define EMAIL_COL_WIDTH_ALIAS 80 + +#define ATTRIB_N_COLS 2 +#define ATTRIB_COL_WIDTH_NAME 120 +#define ATTRIB_COL_WIDTH_VALUE 180 + +#define PAGE_BASIC 0 +#define PAGE_EMAIL 1 +#define PAGE_ATTRIBUTES 2 + +#if 0 +#define SET_LABEL_AND_ENTRY(str, entry, top) \ +{ \ + label = gtk_label_new(str); \ + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \ + GTK_FILL, 0, 0, 0); \ + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \ + \ + entry = gtk_entry_new(); \ + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \ + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \ +} + +static void edit_address_ok(GtkWidget *widget, gboolean *cancelled) +{ + *cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_address_cancel(GtkWidget *widget, gboolean *cancelled) +{ + *cancelled = TRUE; + gtk_main_quit(); +} + +static gint edit_address_delete_event(GtkWidget *widget, GdkEventAny *event, + gboolean *cancelled) +{ + *cancelled = TRUE; + gtk_main_quit(); + + return TRUE; +} + +static gboolean edit_address_key_pressed(GtkWidget *widget, GdkEventKey *event, + gboolean *cancelled) +{ + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void addressbook_edit_address_create(gboolean *cancelled) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *name_entry; + GtkWidget *addr_entry; + GtkWidget *rem_entry; + GtkWidget *hbbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + debug_print("Creating edit_address window...\n"); + + window = gtk_window_new(GTK_WINDOW_DIALOG); + gtk_widget_set_size_request(window, 400, -1); + /* gtk_container_set_border_width(GTK_CONTAINER(window), 8); */ + gtk_window_set_title(GTK_WINDOW(window), _("Edit address")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_address_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_address_key_pressed), + cancelled); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + + table = gtk_table_new(3, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + SET_LABEL_AND_ENTRY(_("Name"), name_entry, 0); + SET_LABEL_AND_ENTRY(_("Address"), addr_entry, 1); + SET_LABEL_AND_ENTRY(_("Remarks"), rem_entry, 2); + + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_address_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_address_cancel), cancelled); + + gtk_widget_show_all(vbox); + + addredit.window = window; + addredit.name_entry = name_entry; + addredit.addr_entry = addr_entry; + addredit.rem_entry = rem_entry; + addredit.ok_btn = ok_btn; + addredit.cancel_btn = cancel_btn; +} + +AddressItem *addressbook_edit_address(AddressItem *item) +{ + static gboolean cancelled; + const gchar *str; + + if (!addredit.window) + addressbook_edit_address_create(&cancelled); + gtk_widget_grab_focus(addredit.ok_btn); + gtk_widget_grab_focus(addredit.name_entry); + gtk_widget_show(addredit.window); + manage_window_set_transient(GTK_WINDOW(addredit.window)); + + gtk_entry_set_text(GTK_ENTRY(addredit.name_entry), ""); + gtk_entry_set_text(GTK_ENTRY(addredit.addr_entry), ""); + gtk_entry_set_text(GTK_ENTRY(addredit.rem_entry), ""); + + if (item) { + if (ADDRESS_OBJECT_NAME(item)) + gtk_entry_set_text(GTK_ENTRY(addredit.name_entry), + ADDRESS_OBJECT_NAME(item)); + if (item->address) + gtk_entry_set_text(GTK_ENTRY(addredit.addr_entry), + item->address); + if (item->remarks) + gtk_entry_set_text(GTK_ENTRY(addredit.rem_entry), + item->remarks); + } + + gtk_main(); + gtk_widget_hide(addredit.window); + if (cancelled == TRUE) return NULL; + + str = gtk_entry_get_text(GTK_ENTRY(addredit.name_entry)); + if (*str == '\0') return NULL; + + if (!item) { + item = mgu_create_address(); + ADDRESS_OBJECT_TYPE(item) = ADDR_ITEM; + } + + g_free(ADDRESS_OBJECT_NAME(item)); + ADDRESS_OBJECT_NAME(item) = g_strdup(str); + + str = gtk_entry_get_text(GTK_ENTRY(addredit.addr_entry)); + g_free(item->address); + if (*str == '\0') + item->address = NULL; + else + item->address = g_strdup(str); + + str = gtk_entry_get_text(GTK_ENTRY(addredit.rem_entry)); + g_free(item->remarks); + if (*str == '\0') + item->remarks = NULL; + else + item->remarks = g_strdup(str); + + return item; +} +#endif /* 0 */ + +static void edit_person_status_show( gchar *msg ) { + if( personeditdlg.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(personeditdlg.statusbar), personeditdlg.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(personeditdlg.statusbar), personeditdlg.status_cid, msg ); + } + } +} + +static void edit_person_ok(GtkWidget *widget, gboolean *cancelled) { + *cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_person_cancel(GtkWidget *widget, gboolean *cancelled) { + *cancelled = TRUE; + gtk_main_quit(); +} + +static gint edit_person_delete_event(GtkWidget *widget, GdkEventAny *event, gboolean *cancelled) { + *cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_person_key_pressed(GtkWidget *widget, GdkEventKey *event, gboolean *cancelled) { + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static gchar *_title_new_ = NULL; +static gchar *_title_edit_ = NULL; + +static void edit_person_set_window_title( gint pageNum ) { + gchar *sTitle; + + if( _title_new_ == NULL ) { + _title_new_ = g_strdup( _("Add New Person") ); + _title_edit_ = g_strdup( _("Edit Person Details") ); + } + + if( pageNum == PAGE_BASIC ) { + if( personeditdlg.editNew ) { + gtk_window_set_title( GTK_WINDOW(personeditdlg.window), _title_new_ ); + } + else { + gtk_window_set_title( GTK_WINDOW(personeditdlg.window), _title_edit_ ); + } + } + else { + if( personeditdlg.entry_name == NULL ) { + sTitle = g_strdup( _title_edit_ ); + } + else { + gchar *name; + name = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_name), 0, -1 ); + sTitle = g_strdup_printf( "%s - %s", _title_edit_, name ); + g_free( name ); + } + gtk_window_set_title( GTK_WINDOW(personeditdlg.window), sTitle ); + g_free( sTitle ); + } +} + +static void edit_person_email_clear( gpointer data ) { + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_email), "" ); + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_alias), "" ); + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_remarks), "" ); +} + +static void edit_person_attrib_clear( gpointer data ) { + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_atname), "" ); + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_atvalue), "" ); +} + +static void edit_person_switch_page( GtkNotebook *notebook, GtkNotebookPage *page, + gint pageNum, gpointer user_data) +{ + edit_person_set_window_title( pageNum ); + edit_person_status_show( "" ); +} + +/* +* Load clist with a copy of person's email addresses. +*/ +void edit_person_load_email( ItemPerson *person ) { + GList *node = person->listEMail; + GtkCList *clist = GTK_CLIST(personeditdlg.clist_email); + gchar *text[ EMAIL_N_COLS ]; + while( node ) { + ItemEMail *emorig = ( ItemEMail * ) node->data; + ItemEMail *email = addritem_copy_item_email( emorig ); + gint row; + text[ EMAIL_COL_EMAIL ] = email->address; + text[ EMAIL_COL_ALIAS ] = email->obj.name; + text[ EMAIL_COL_REMARKS ] = email->remarks; + + row = gtk_clist_append( clist, text ); + gtk_clist_set_row_data( clist, row, email ); + node = g_list_next( node ); + } +} + +static void edit_person_email_list_selected( GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data ) { + ItemEMail *email = gtk_clist_get_row_data( clist, row ); + if( email ) { + if( email->address ) + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_email), email->address ); + if( ADDRITEM_NAME(email) ) + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_alias), ADDRITEM_NAME(email) ); + if( email->remarks ) + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_remarks), email->remarks ); + } + personeditdlg.rowIndEMail = row; + edit_person_status_show( NULL ); +} + +static void edit_person_email_move( gint dir ) { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_email); + gint row = personeditdlg.rowIndEMail + dir; + ItemEMail *email = gtk_clist_get_row_data( clist, row ); + if( email ) { + gtk_clist_row_move( clist, personeditdlg.rowIndEMail, row ); + personeditdlg.rowIndEMail = row; + } + edit_person_email_clear( NULL ); + edit_person_status_show( NULL ); +} + +static void edit_person_email_move_up( gpointer data ) { + edit_person_email_move( -1 ); +} + +static void edit_person_email_move_down( gpointer data ) { + edit_person_email_move( +1 ); +} + +static void edit_person_email_delete( gpointer data ) { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_email); + gint row = personeditdlg.rowIndEMail; + ItemEMail *email = gtk_clist_get_row_data( clist, row ); + edit_person_email_clear( NULL ); + if( email ) { + /* Remove list entry */ + gtk_clist_remove( clist, row ); + addritem_free_item_email( email ); + email = NULL; + } + + /* Position hilite bar */ + email = gtk_clist_get_row_data( clist, row ); + if( ! email ) { + personeditdlg.rowIndEMail = -1 + row; + } + edit_person_status_show( NULL ); +} + +static ItemEMail *edit_person_email_edit( gboolean *error, ItemEMail *email ) { + ItemEMail *retVal = NULL; + gchar *sEmail, *sAlias, *sRemarks, *sEmail_; + + *error = TRUE; + sEmail_ = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_email), 0, -1 ); + sAlias = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_alias), 0, -1 ); + sRemarks = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_remarks), 0, -1 ); + sEmail = mgu_email_check_empty( sEmail_ ); + g_free( sEmail_ ); + + if( sEmail ) { + if( email == NULL ) { + email = addritem_create_item_email(); + } + addritem_email_set_address( email, sEmail ); + addritem_email_set_alias( email, sAlias ); + addritem_email_set_remarks( email, sRemarks ); + retVal = email; + *error = FALSE; + } + else { + edit_person_status_show( _( "An E-Mail address must be supplied." ) ); + } + + g_free( sEmail ); + g_free( sAlias ); + g_free( sRemarks ); + + return retVal; +} + +static void edit_person_email_modify( gpointer data ) { + gboolean errFlg = FALSE; + GtkCList *clist = GTK_CLIST(personeditdlg.clist_email); + gint row = personeditdlg.rowIndEMail; + ItemEMail *email = gtk_clist_get_row_data( clist, row ); + if( email ) { + edit_person_email_edit( &errFlg, email ); + if( ! errFlg ) { + gtk_clist_set_text( clist, row, EMAIL_COL_EMAIL, email->address ); + gtk_clist_set_text( clist, row, EMAIL_COL_ALIAS, email->obj.name ); + gtk_clist_set_text( clist, row, EMAIL_COL_REMARKS, email->remarks ); + edit_person_email_clear( NULL ); + } + } +} + +static void edit_person_email_add( gpointer data ) { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_email); + gboolean errFlg = FALSE; + ItemEMail *email = NULL; + gint row = personeditdlg.rowIndEMail; + if( gtk_clist_get_row_data( clist, row ) == NULL ) row = 0; + + email = edit_person_email_edit( &errFlg, NULL ); + if( ! errFlg ) { + gchar *text[ EMAIL_N_COLS ]; + text[ EMAIL_COL_EMAIL ] = email->address; + text[ EMAIL_COL_ALIAS ] = email->obj.name; + text[ EMAIL_COL_REMARKS ] = email->remarks; + + row = gtk_clist_insert( clist, 1 + row, text ); + gtk_clist_set_row_data( clist, row, email ); + gtk_clist_select_row( clist, row, 0 ); + edit_person_email_clear( NULL ); + } +} + +/* +* Load clist with a copy of person's email addresses. +*/ +void edit_person_load_attrib( ItemPerson *person ) { + GList *node = person->listAttrib; + GtkCList *clist = GTK_CLIST(personeditdlg.clist_attrib); + gchar *text[ ATTRIB_N_COLS ]; + while( node ) { + UserAttribute *atorig = ( UserAttribute * ) node->data; + UserAttribute *attrib = addritem_copy_attribute( atorig ); + gint row; + text[ ATTRIB_COL_NAME ] = attrib->name; + text[ ATTRIB_COL_VALUE ] = attrib->value; + + row = gtk_clist_append( clist, text ); + gtk_clist_set_row_data( clist, row, attrib ); + node = g_list_next( node ); + } +} + +static void edit_person_attrib_list_selected( GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data ) { + UserAttribute *attrib = gtk_clist_get_row_data( clist, row ); + if( attrib ) { + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_atname), attrib->name ); + gtk_entry_set_text( GTK_ENTRY(personeditdlg.entry_atvalue), attrib->value ); + } + personeditdlg.rowIndAttrib = row; + edit_person_status_show( NULL ); +} + +static void edit_person_attrib_delete( gpointer data ) { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_attrib); + gint row = personeditdlg.rowIndAttrib; + UserAttribute *attrib = gtk_clist_get_row_data( clist, row ); + edit_person_attrib_clear( NULL ); + if( attrib ) { + /* Remove list entry */ + gtk_clist_remove( clist, row ); + addritem_free_attribute( attrib ); + attrib = NULL; + } + + /* Position hilite bar */ + attrib = gtk_clist_get_row_data( clist, row ); + if( ! attrib ) { + personeditdlg.rowIndAttrib = -1 + row; + } + edit_person_status_show( NULL ); +} + +static UserAttribute *edit_person_attrib_edit( gboolean *error, UserAttribute *attrib ) { + UserAttribute *retVal = NULL; + gchar *sName, *sValue, *sName_, *sValue_; + + *error = TRUE; + sName_ = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_atname), 0, -1 ); + sValue_ = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_atvalue), 0, -1 ); + sName = mgu_email_check_empty( sName_ ); + sValue = mgu_email_check_empty( sValue_ ); + g_free( sName_ ); + g_free( sValue_ ); + + if( sName && sValue ) { + if( attrib == NULL ) { + attrib = addritem_create_attribute(); + } + addritem_attrib_set_name( attrib, sName ); + addritem_attrib_set_value( attrib, sValue ); + retVal = attrib; + *error = FALSE; + } + else { + edit_person_status_show( _( "A Name and Value must be supplied." ) ); + } + + g_free( sName ); + g_free( sValue ); + + return retVal; +} + +static void edit_person_attrib_modify( gpointer data ) { + gboolean errFlg = FALSE; + GtkCList *clist = GTK_CLIST(personeditdlg.clist_attrib); + gint row = personeditdlg.rowIndAttrib; + UserAttribute *attrib = gtk_clist_get_row_data( clist, row ); + if( attrib ) { + edit_person_attrib_edit( &errFlg, attrib ); + if( ! errFlg ) { + gtk_clist_set_text( clist, row, ATTRIB_COL_NAME, attrib->name ); + gtk_clist_set_text( clist, row, ATTRIB_COL_VALUE, attrib->value ); + edit_person_attrib_clear( NULL ); + } + } +} + +static void edit_person_attrib_add( gpointer data ) { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_attrib); + gboolean errFlg = FALSE; + UserAttribute *attrib = NULL; + gint row = personeditdlg.rowIndAttrib; + if( gtk_clist_get_row_data( clist, row ) == NULL ) row = 0; + + attrib = edit_person_attrib_edit( &errFlg, NULL ); + if( ! errFlg ) { + gchar *text[ EMAIL_N_COLS ]; + text[ ATTRIB_COL_NAME ] = attrib->name; + text[ ATTRIB_COL_VALUE ] = attrib->value; + + row = gtk_clist_insert( clist, 1 + row, text ); + gtk_clist_set_row_data( clist, row, attrib ); + gtk_clist_select_row( clist, row, 0 ); + edit_person_attrib_clear( NULL ); + } +} + +static void addressbook_edit_person_dialog_create( gboolean *cancelled ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *vnbox; + GtkWidget *notebook; + GtkWidget *hbbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *hsbox; + GtkWidget *statusbar; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, EDITPERSON_WIDTH, EDITPERSON_HEIGHT ); + /* gtk_container_set_border_width(GTK_CONTAINER(window), 0); */ + gtk_window_set_title(GTK_WINDOW(window), _("Edit Person Data")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_person_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_person_key_pressed), + cancelled); + + vbox = gtk_vbox_new(FALSE, 4); + /* gtk_container_set_border_width(GTK_CONTAINER(vbox), BORDER_WIDTH); */ + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + vnbox = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width(GTK_CONTAINER(vnbox), 4); + gtk_widget_show(vnbox); + gtk_box_pack_start(GTK_BOX(vbox), vnbox, TRUE, TRUE, 0); + + /* Notebook */ + notebook = gtk_notebook_new(); + gtk_widget_show(notebook); + gtk_box_pack_start(GTK_BOX(vnbox), notebook, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(notebook), 6); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vnbox), hbbox, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_person_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_person_cancel), cancelled); + g_signal_connect(G_OBJECT(notebook), "switch_page", + G_CALLBACK(edit_person_switch_page), NULL); + + gtk_widget_show_all(vbox); + + personeditdlg.window = window; + personeditdlg.notebook = notebook; + personeditdlg.ok_btn = ok_btn; + personeditdlg.cancel_btn = cancel_btn; + personeditdlg.statusbar = statusbar; + personeditdlg.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit Person Dialog" ); + +} + +void addressbook_edit_person_page_basic( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *entry_name; + GtkWidget *entry_fn; + GtkWidget *entry_ln; + GtkWidget *entry_nn; + const gchar *locale; + gint top = 0; + + vbox = gtk_vbox_new( FALSE, 8 ); + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( personeditdlg.notebook ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( personeditdlg.notebook ), + gtk_notebook_get_nth_page( GTK_NOTEBOOK( personeditdlg.notebook ), pageNum ), label ); + + table = gtk_table_new( 4, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + +#define ATTACH_ROW(text, entry) \ +{ \ + label = gtk_label_new(text); \ + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \ + GTK_FILL, 0, 0, 0); \ + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \ + \ + entry = gtk_entry_new(); \ + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \ + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \ + top++; \ +} + + ATTACH_ROW(_("Display Name"), entry_name); + locale = conv_get_current_locale(); + if (locale && + (!g_strncasecmp(locale, "ja", 2) || + !g_strncasecmp(locale, "ko", 2) || + !g_strncasecmp(locale, "zh", 2))) { + ATTACH_ROW(_("Last Name"), entry_ln); + ATTACH_ROW(_("First Name"), entry_fn); + } else { + ATTACH_ROW(_("First Name"), entry_fn); + ATTACH_ROW(_("Last Name"), entry_ln); + } + ATTACH_ROW(_("Nick Name"), entry_nn); + +#undef ATTACH_ROW + + gtk_widget_show_all(vbox); + + personeditdlg.entry_name = entry_name; + personeditdlg.entry_first = entry_fn; + personeditdlg.entry_last = entry_ln; + personeditdlg.entry_nick = entry_nn; +} + +void addressbook_edit_person_page_email( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *vboxl; + GtkWidget *vboxb; + GtkWidget *vbuttonbox; + GtkWidget *buttonUp; + GtkWidget *buttonDown; + GtkWidget *buttonDel; + GtkWidget *buttonMod; + GtkWidget *buttonAdd; + GtkWidget *buttonClr; + + GtkWidget *table; + GtkWidget *label; + GtkWidget *clist_swin; + GtkWidget *clist; + GtkWidget *entry_email; + GtkWidget *entry_alias; + GtkWidget *entry_remarks; + gint top; + + gchar *titles[ EMAIL_N_COLS ]; + gint i; + + titles[ EMAIL_COL_EMAIL ] = _("E-Mail Address"); + titles[ EMAIL_COL_ALIAS ] = _("Alias"); + titles[ EMAIL_COL_REMARKS ] = _("Remarks"); + + vbox = gtk_vbox_new( FALSE, 8 ); + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( personeditdlg.notebook ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( personeditdlg.notebook ), + gtk_notebook_get_nth_page( GTK_NOTEBOOK( personeditdlg.notebook ), pageNum ), label ); + + /* Split into two areas */ + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( vbox ), hbox ); + + /* EMail list */ + vboxl = gtk_vbox_new( FALSE, 4 ); + gtk_container_add( GTK_CONTAINER( hbox ), vboxl ); + gtk_container_set_border_width( GTK_CONTAINER(vboxl), 4 ); + + /* Address list */ + clist_swin = gtk_scrolled_window_new( NULL, NULL ); + gtk_container_add( GTK_CONTAINER(vboxl), clist_swin ); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + + clist = gtk_clist_new_with_titles( EMAIL_N_COLS, titles ); + gtk_container_add( GTK_CONTAINER(clist_swin), clist ); + gtk_clist_set_selection_mode( GTK_CLIST(clist), GTK_SELECTION_BROWSE ); + gtk_clist_set_column_width( GTK_CLIST(clist), EMAIL_COL_EMAIL, EMAIL_COL_WIDTH_EMAIL ); + gtk_clist_set_column_width( GTK_CLIST(clist), EMAIL_COL_ALIAS, EMAIL_COL_WIDTH_ALIAS ); + + for( i = 0; i < EMAIL_N_COLS; i++ ) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[i].button, GTK_CAN_FOCUS); + + /* Data entry area */ + table = gtk_table_new( 4, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vboxl), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 4 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 4); + gtk_table_set_col_spacings(GTK_TABLE(table), 4); + + /* First row */ + top = 0; + label = gtk_label_new(_("E-Mail Address")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_email = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_email, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Alias")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_alias = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_alias, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Remarks")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_remarks = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_remarks, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Button box */ + vboxb = gtk_vbox_new( FALSE, 4 ); + gtk_box_pack_start(GTK_BOX(hbox), vboxb, FALSE, FALSE, 2); + + vbuttonbox = gtk_vbutton_box_new(); + gtk_button_box_set_layout( GTK_BUTTON_BOX(vbuttonbox), GTK_BUTTONBOX_START ); + gtk_box_set_spacing( GTK_BOX(vbuttonbox), 8 ); + gtk_container_set_border_width( GTK_CONTAINER(vbuttonbox), 4 ); + gtk_container_add( GTK_CONTAINER(vboxb), vbuttonbox ); + + /* Buttons */ + buttonUp = gtk_button_new_with_label( _( "Move Up" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonUp ); + + buttonDown = gtk_button_new_with_label( _( "Move Down" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonDown ); + + buttonDel = gtk_button_new_with_label( _( "Delete" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonDel ); + + buttonMod = gtk_button_new_with_label( _( "Modify" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonMod ); + + buttonAdd = gtk_button_new_with_label( _( "Add" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonAdd ); + + buttonClr = gtk_button_new_with_label( _( "Clear" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonClr ); + + gtk_widget_show_all(vbox); + + /* Event handlers */ + g_signal_connect( G_OBJECT(clist), "select_row", + G_CALLBACK( edit_person_email_list_selected), NULL ); + g_signal_connect( G_OBJECT(buttonUp), "clicked", + G_CALLBACK( edit_person_email_move_up ), NULL ); + g_signal_connect( G_OBJECT(buttonDown), "clicked", + G_CALLBACK( edit_person_email_move_down ), NULL ); + g_signal_connect( G_OBJECT(buttonDel), "clicked", + G_CALLBACK( edit_person_email_delete ), NULL ); + g_signal_connect( G_OBJECT(buttonMod), "clicked", + G_CALLBACK( edit_person_email_modify ), NULL ); + g_signal_connect( G_OBJECT(buttonAdd), "clicked", + G_CALLBACK( edit_person_email_add ), NULL ); + g_signal_connect( G_OBJECT(buttonClr), "clicked", + G_CALLBACK( edit_person_email_clear ), NULL ); + + personeditdlg.clist_email = clist; + personeditdlg.entry_email = entry_email; + personeditdlg.entry_alias = entry_alias; + personeditdlg.entry_remarks = entry_remarks; +} + +void addressbook_edit_person_page_attrib( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *vboxl; + GtkWidget *vboxb; + GtkWidget *vbuttonbox; + GtkWidget *buttonDel; + GtkWidget *buttonMod; + GtkWidget *buttonAdd; + GtkWidget *buttonClr; + + GtkWidget *table; + GtkWidget *label; + GtkWidget *clist_swin; + GtkWidget *clist; + GtkWidget *entry_name; + GtkWidget *entry_value; + gint top; + + gchar *titles[ ATTRIB_N_COLS ]; + gint i; + + titles[ ATTRIB_COL_NAME ] = _("Name"); + titles[ ATTRIB_COL_VALUE ] = _("Value"); + + vbox = gtk_vbox_new( FALSE, 8 ); + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( personeditdlg.notebook ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( personeditdlg.notebook ), + gtk_notebook_get_nth_page( GTK_NOTEBOOK( personeditdlg.notebook ), pageNum ), label ); + + /* Split into two areas */ + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( vbox ), hbox ); + + /* Attribute list */ + vboxl = gtk_vbox_new( FALSE, 4 ); + gtk_container_add( GTK_CONTAINER( hbox ), vboxl ); + gtk_container_set_border_width( GTK_CONTAINER(vboxl), 4 ); + + /* Address list */ + clist_swin = gtk_scrolled_window_new( NULL, NULL ); + gtk_container_add( GTK_CONTAINER(vboxl), clist_swin ); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + + clist = gtk_clist_new_with_titles( ATTRIB_N_COLS, titles ); + gtk_container_add( GTK_CONTAINER(clist_swin), clist ); + gtk_clist_set_selection_mode( GTK_CLIST(clist), GTK_SELECTION_BROWSE ); + gtk_clist_set_column_width( GTK_CLIST(clist), ATTRIB_COL_NAME, ATTRIB_COL_WIDTH_NAME ); + gtk_clist_set_column_width( GTK_CLIST(clist), ATTRIB_COL_VALUE, ATTRIB_COL_WIDTH_VALUE ); + + for( i = 0; i < ATTRIB_N_COLS; i++ ) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[i].button, GTK_CAN_FOCUS); + + /* Data entry area */ + table = gtk_table_new( 4, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vboxl), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 4 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 4); + gtk_table_set_col_spacings(GTK_TABLE(table), 4); + + /* First row */ + top = 0; + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_name = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_name, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Value")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_value = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_value, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Button box */ + vboxb = gtk_vbox_new( FALSE, 4 ); + gtk_box_pack_start(GTK_BOX(hbox), vboxb, FALSE, FALSE, 2); + + vbuttonbox = gtk_vbutton_box_new(); + gtk_button_box_set_layout( GTK_BUTTON_BOX(vbuttonbox), GTK_BUTTONBOX_START ); + gtk_box_set_spacing( GTK_BOX(vbuttonbox), 8 ); + gtk_container_set_border_width( GTK_CONTAINER(vbuttonbox), 4 ); + gtk_container_add( GTK_CONTAINER(vboxb), vbuttonbox ); + + /* Buttons */ + buttonDel = gtk_button_new_with_label( _( "Delete" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonDel ); + + buttonMod = gtk_button_new_with_label( _( "Modify" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonMod ); + + buttonAdd = gtk_button_new_with_label( _( "Add" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonAdd ); + + buttonClr = gtk_button_new_with_label( _( "Clear" ) ); + gtk_container_add( GTK_CONTAINER(vbuttonbox), buttonClr ); + + gtk_widget_show_all(vbox); + + /* Event handlers */ + g_signal_connect( G_OBJECT(clist), "select_row", + G_CALLBACK( edit_person_attrib_list_selected), NULL ); + g_signal_connect( G_OBJECT(buttonDel), "clicked", + G_CALLBACK( edit_person_attrib_delete ), NULL ); + g_signal_connect( G_OBJECT(buttonMod), "clicked", + G_CALLBACK( edit_person_attrib_modify ), NULL ); + g_signal_connect( G_OBJECT(buttonAdd), "clicked", + G_CALLBACK( edit_person_attrib_add ), NULL ); + g_signal_connect( G_OBJECT(buttonClr), "clicked", + G_CALLBACK( edit_person_attrib_clear ), NULL ); + + personeditdlg.clist_attrib = clist; + personeditdlg.entry_atname = entry_name; + personeditdlg.entry_atvalue = entry_value; +} + +static void addressbook_edit_person_create( gboolean *cancelled ) { + addressbook_edit_person_dialog_create( cancelled ); + addressbook_edit_person_page_basic( PAGE_BASIC, _( "Basic Data" ) ); + addressbook_edit_person_page_email( PAGE_EMAIL, _( "E-Mail Address" ) ); + addressbook_edit_person_page_attrib( PAGE_ATTRIBUTES, _( "User Attributes" ) ); + gtk_widget_show_all( personeditdlg.window ); +} + +/* +* Return list of email items. +*/ +static GList *edit_person_build_email_list() { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_email); + GList *listEMail = NULL; + ItemEMail *email; + gint row = 0; + while( (email = gtk_clist_get_row_data( clist, row )) ) { + listEMail = g_list_append( listEMail, email ); + row++; + } + return listEMail; +} + +/* +* Return list of attributes. +*/ +static GList *edit_person_build_attrib_list() { + GtkCList *clist = GTK_CLIST(personeditdlg.clist_attrib); + GList *listAttrib = NULL; + UserAttribute *attrib; + gint row = 0; + while( (attrib = gtk_clist_get_row_data( clist, row )) ) { + listAttrib = g_list_append( listAttrib, attrib ); + row++; + } + return listAttrib; +} + +/* +* Edit person. +* Enter: abf Address book. +* parent Parent folder for person (or NULL if adding to root folder). Argument is +* only required for new objects). +* person Person to edit, or NULL for a new person object. +* pgMail If TRUE, E-Mail page will be activated. +* Return: Edited object, or NULL if cancelled. +*/ +ItemPerson *addressbook_edit_person( AddressBookFile *abf, ItemFolder *parent, ItemPerson *person, gboolean pgMail ) { + static gboolean cancelled; + GList *listEMail = NULL; + GList *listAttrib = NULL; + gchar *cn = NULL; + + if (!personeditdlg.window) + addressbook_edit_person_create(&cancelled); + gtk_widget_grab_focus(personeditdlg.ok_btn); + gtk_widget_grab_focus(personeditdlg.entry_name); + gtk_widget_show(personeditdlg.window); + manage_window_set_transient(GTK_WINDOW(personeditdlg.window)); + + /* Clear all fields */ + personeditdlg.rowIndEMail = -1; + personeditdlg.rowIndAttrib = -1; + edit_person_status_show( "" ); + gtk_clist_clear( GTK_CLIST(personeditdlg.clist_email) ); + gtk_clist_clear( GTK_CLIST(personeditdlg.clist_attrib) ); + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_name), "" ); + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_first), "" ); + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_last), "" ); + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_nick), "" ); + + personeditdlg.editNew = FALSE; + if( person ) { + if( ADDRITEM_NAME(person) ) + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_name), ADDRITEM_NAME(person) ); + if( person->firstName ) + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_first), person->firstName ); + if( person->lastName ) + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_last), person->lastName ); + if( person->nickName ) + gtk_entry_set_text(GTK_ENTRY(personeditdlg.entry_nick), person->nickName ); + edit_person_load_email( person ); + edit_person_load_attrib( person ); + } + else { + personeditdlg.editNew = TRUE; + } + + /* Select appropriate start page */ + if( pgMail ) { + gtk_notebook_set_current_page( GTK_NOTEBOOK(personeditdlg.notebook), PAGE_EMAIL ); + } + else { + gtk_notebook_set_current_page( GTK_NOTEBOOK(personeditdlg.notebook), PAGE_BASIC ); + } + + gtk_clist_select_row( GTK_CLIST(personeditdlg.clist_email), 0, 0 ); + gtk_clist_select_row( GTK_CLIST(personeditdlg.clist_attrib), 0, 0 ); + edit_person_email_clear( NULL ); + edit_person_attrib_clear( NULL ); + + gtk_main(); + gtk_widget_hide( personeditdlg.window ); + + listEMail = edit_person_build_email_list(); + listAttrib = edit_person_build_attrib_list(); + if( cancelled ) { + addritem_free_list_email( listEMail ); + gtk_clist_clear( GTK_CLIST(personeditdlg.clist_email) ); + gtk_clist_clear( GTK_CLIST(personeditdlg.clist_attrib) ); + return NULL; + } + + cn = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_name), 0, -1 ); + if( person ) { + /* Update email/attribute list */ + addrbook_update_address_list( abf, person, listEMail ); + addrbook_update_attrib_list( abf, person, listAttrib ); + } + else { + /* Create new person and email/attribute list */ + if( cn == NULL || *cn == '\0' ) { + /* Wasting our time */ + if( listEMail == NULL && listAttrib == NULL ) cancelled = TRUE; + } + if( ! cancelled ) { + person = addrbook_add_address_list( abf, parent, listEMail ); + addrbook_add_attrib_list( abf, person, listAttrib ); + } + } + + if( !cancelled ) { + /* Set person stuff */ + gchar *name; + addritem_person_set_common_name( person, cn ); + name = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_first), 0, -1 ); + addritem_person_set_first_name( person, name ); + g_free( name ); + name = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_last), 0, -1 ); + addritem_person_set_last_name( person, name ); + g_free( name ); + name = gtk_editable_get_chars( GTK_EDITABLE(personeditdlg.entry_nick), 0, -1 ); + addritem_person_set_nick_name( person, name ); + g_free( name ); + } + g_free( cn ); + + listEMail = NULL; + + gtk_clist_clear( GTK_CLIST(personeditdlg.clist_email) ); + gtk_clist_clear( GTK_CLIST(personeditdlg.clist_attrib) ); + + return person; +} + +/* +* End of Source. +*/ + diff --git a/src/editaddress.h b/src/editaddress.h new file mode 100644 index 00000000..0d8d38d0 --- /dev/null +++ b/src/editaddress.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit address item data. + */ + +#ifndef __EDITADDRESS_H__ +#define __EDITADDRESS_H__ + +ItemPerson *addressbook_edit_person( AddressBookFile *abf, ItemFolder *parent, ItemPerson *person, gboolean pgMail ); + +#endif /* __EDITADDRESS_H__ */ diff --git a/src/editbook.c b/src/editbook.c new file mode 100644 index 00000000..68208c03 --- /dev/null +++ b/src/editbook.c @@ -0,0 +1,348 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit new address book entry. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "utils.h" +#include "prefs_common.h" +#include "mgutils.h" +#include "addressbook.h" +#include "addressitem.h" +#include "addrindex.h" +#include "addrbook.h" +#include "manage_window.h" +#include "gtkutils.h" + +#define ADDRESSBOOK_GUESS_BOOK "MyAddressBook" + +static struct _AddrBookEdit_Dlg { + GtkWidget *window; + GtkWidget *name_entry; + GtkWidget *file_label; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *check_btn; + /* GtkWidget *file_btn; */ + GtkWidget *statusbar; + gint status_cid; + AddressBookFile *bookFile; +} addrbookedit_dlg; + +/* static struct _AddressFileSelection vcard_file_selector; */ + +/* +* Edit functions. +*/ +void edit_book_status_show( gchar *msg ) { + if( addrbookedit_dlg.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(addrbookedit_dlg.statusbar), addrbookedit_dlg.status_cid ); + if( msg ) { + gtk_statusbar_push( + GTK_STATUSBAR(addrbookedit_dlg.statusbar), addrbookedit_dlg.status_cid, msg ); + } + else { + gtk_statusbar_push( + GTK_STATUSBAR(addrbookedit_dlg.statusbar), addrbookedit_dlg.status_cid, "" ); + } + } +} + +static void edit_book_ok( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_book_cancel( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); +} + +static gint edit_book_delete_event( GtkWidget *widget, GdkEventAny *event, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_book_key_pressed( GtkWidget *widget, GdkEventKey *event, gboolean *cancelled ) { + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void edit_book_file_check( void ) { + gint t; + gchar *sMsg; + AddressBookFile *abf = addrbookedit_dlg.bookFile; + + t = addrbook_test_read_file( abf, abf->fileName ); + if( t == MGU_SUCCESS ) { + sMsg = _("File appears to be Ok."); + } + else if( t == MGU_BAD_FORMAT ) { + sMsg = _("File does not appear to be a valid address book format."); + } + else { + sMsg = _("Could not read file."); + } + edit_book_status_show( sMsg ); +} + +static void edit_book_enable_buttons( gboolean enable ) { + gtk_widget_set_sensitive( addrbookedit_dlg.check_btn, enable ); + /* gtk_widget_set_sensitive( addrbookedit_dlg.file_btn, enable ); */ +} + +static void edit_book_name_focus( GtkWidget *widget, GdkEventFocus *event, gpointer data) { + edit_book_status_show( "" ); +} + +static gchar *edit_book_guess_file( AddressBookFile *abf ) { + gchar *newFile = NULL; + GList *fileList = NULL; + gint fileNum = 1; + fileList = addrbook_get_bookfile_list( abf ); + if( fileList ) { + fileNum = 1 + abf->maxValue; + } + newFile = addrbook_gen_new_file_name( fileNum ); + g_list_free( fileList ); + fileList = NULL; + return newFile; +} + +static void addressbook_edit_book_create( gboolean *cancelled ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *name_entry; + GtkWidget *file_label; + GtkWidget *hbbox; + GtkWidget *hsep; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *check_btn; + /* GtkWidget *file_btn; */ + GtkWidget *statusbar; + GtkWidget *hsbox; + gint top; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 450, -1); + gtk_container_set_border_width( GTK_CONTAINER(window), 0 ); + gtk_window_set_title(GTK_WINDOW(window), _("Edit Addressbook")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_book_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_book_key_pressed), + cancelled); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_container_set_border_width( GTK_CONTAINER(vbox), 0 ); + + table = gtk_table_new(2, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8 ); + + /* First row */ + top = 0; + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + name_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), name_entry, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + check_btn = gtk_button_new_with_label( _(" Check File ")); + gtk_table_attach(GTK_TABLE(table), check_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Second row */ + top = 1; + label = gtk_label_new(_("File")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + file_label = gtk_label_new( "" ); + gtk_misc_set_alignment(GTK_MISC(file_label), 0, 0.5); + gtk_table_attach(GTK_TABLE(table), file_label, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* file_btn = gtk_button_new_with_label( _(" ... ")); */ + /* gtk_table_attach(GTK_TABLE(table), file_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); */ + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(hbbox), 0 ); + gtk_widget_grab_default(ok_btn); + + hsep = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(vbox), hsep, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(name_entry), "focus_in_event", + G_CALLBACK(edit_book_name_focus), NULL ); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_book_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_book_cancel), cancelled); +/* g_signal_connect(G_OBJECT(file_btn), "clicked", */ +/* G_CALLBACK(edit_book_file_select), NULL); */ + g_signal_connect(G_OBJECT(check_btn), "clicked", + G_CALLBACK(edit_book_file_check), NULL); + + gtk_widget_show_all(vbox); + + addrbookedit_dlg.window = window; + addrbookedit_dlg.name_entry = name_entry; + addrbookedit_dlg.file_label = file_label; + addrbookedit_dlg.ok_btn = ok_btn; + addrbookedit_dlg.cancel_btn = cancel_btn; + addrbookedit_dlg.check_btn = check_btn; + /* addrbookedit_dlg.file_btn = file_btn; */ + addrbookedit_dlg.statusbar = statusbar; + addrbookedit_dlg.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit Addressbook Dialog" ); +} + +AdapterDSource *addressbook_edit_book( AddressIndex *addrIndex, AdapterDSource *ads ) { + static gboolean cancelled; + gchar *sName; + AddressDataSource *ds = NULL; + AddressBookFile *abf; + gboolean fin; + gboolean newBook = FALSE; + gchar *newFile = NULL; + + if (!addrbookedit_dlg.window) + addressbook_edit_book_create(&cancelled); + gtk_widget_grab_focus(addrbookedit_dlg.ok_btn); + gtk_widget_grab_focus(addrbookedit_dlg.name_entry); + gtk_widget_show(addrbookedit_dlg.window); + manage_window_set_transient(GTK_WINDOW(addrbookedit_dlg.window)); + + edit_book_status_show( "" ); + gtk_label_set_text( GTK_LABEL(addrbookedit_dlg.file_label), "" ); + if( ads ) { + ds = ads->dataSource; + abf = ds->rawDataSource; + if (abf->name) + gtk_entry_set_text(GTK_ENTRY(addrbookedit_dlg.name_entry), abf->name); + if( abf->fileName ) + gtk_label_set_text(GTK_LABEL(addrbookedit_dlg.file_label), abf->fileName); + gtk_window_set_title( GTK_WINDOW(addrbookedit_dlg.window), _("Edit Addressbook")); + edit_book_enable_buttons( TRUE ); + } + else { + gchar *tmp = NULL; + newBook = TRUE; + abf = addrbook_create_book(); + addrbook_set_path( abf, addrIndex->filePath ); + + /* Take initial guess at file name */ + newFile = edit_book_guess_file( abf ); + if( newFile ) { + tmp = g_strdup_printf( "<%s>", newFile ); + gtk_label_set_text(GTK_LABEL(addrbookedit_dlg.file_label), tmp ); + g_free( tmp ); + } + g_free( newFile ); + + gtk_entry_set_text( GTK_ENTRY(addrbookedit_dlg.name_entry), ADDRESSBOOK_GUESS_BOOK ); + gtk_window_set_title( GTK_WINDOW(addrbookedit_dlg.window), _("Add New Addressbook") ); + edit_book_enable_buttons( FALSE ); + } + + addrbookedit_dlg.bookFile = abf; + + gtk_main(); + gtk_widget_hide(addrbookedit_dlg.window); + + if( cancelled == TRUE ) { + if( newBook ) { + addrbook_free_book( abf ); + abf = NULL; + } + return NULL; + } + + fin = FALSE; + sName = gtk_editable_get_chars( GTK_EDITABLE(addrbookedit_dlg.name_entry), 0, -1 ); + if( *sName == '\0' ) fin = TRUE; + + if( fin ) { + if( newBook ) { + addrbook_free_book( abf ); + abf = NULL; + } + } + else { + if( newBook ) { + /* Get final file name in case it changed */ + newFile = edit_book_guess_file( abf ); + addrbook_set_file( abf, newFile ); + g_free( newFile ); + ds = addrindex_index_add_datasource( addrIndex, ADDR_IF_BOOK, abf ); + ads = addressbook_create_ds_adapter( ds, ADDR_BOOK, NULL ); + } + addressbook_ads_set_name( ads, sName ); + addrbook_set_name( abf, sName ); + abf->dirtyFlag = TRUE; + } + g_free( sName ); + + /* Save data */ + if( abf ) addrbook_save_data( abf ); + + return ads; +} + +/* +* End of Source. +*/ diff --git a/src/editbook.h b/src/editbook.h new file mode 100644 index 00000000..13e49651 --- /dev/null +++ b/src/editbook.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit new address book data. + */ + +#ifndef __EDITBOOK_H__ +#define __EDITBOOK_H__ + +AdapterDSource *addressbook_edit_book( AddressIndex *addrIndex, AdapterDSource *ads ); + +#endif /* __EDITBOOK_H__ */ diff --git a/src/editgroup.c b/src/editgroup.c new file mode 100644 index 00000000..59948608 --- /dev/null +++ b/src/editgroup.c @@ -0,0 +1,540 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include + +#include "intl.h" +#include "addressbook.h" +#include "addressitem.h" +#include "addrbook.h" +#include "addritem.h" + +#include "mgutils.h" + +#include "prefs_common.h" + +#include "alertpanel.h" +#include "inputdialog.h" +#include "manage_window.h" +#include "gtkutils.h" + +#define ADDRESSBOOK_GUESS_FOLDER_NAME "NewFolder" +#define ADDRESSBOOK_GUESS_GROUP_NAME "NewGroup" + +#define EDITGROUP_WIDTH 580 +#define EDITGROUP_HEIGHT 340 + +typedef enum { + GROUP_COL_NAME = 0, + GROUP_COL_EMAIL = 1, + GROUP_COL_REMARKS = 2 +} GroupEditEMailColumnPos; + +#define GROUP_N_COLS 3 +#define GROUP_COL_WIDTH_NAME 140 +#define GROUP_COL_WIDTH_EMAIL 120 + +static struct _GroupEdit_dlg { + GtkWidget *window; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; + + /* Basic data tab */ + GtkWidget *entry_name; + GtkCList *clist_group; + GtkCList *clist_avail; + + GHashTable *hashEMail; + gint rowIndGroup; + gint rowIndAvail; + +} groupeditdlg; + + +static gchar *_edit_group_dfl_message_ = NULL; + +static void edit_group_status_show( gchar *msg ) { + if( groupeditdlg.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(groupeditdlg.statusbar), groupeditdlg.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(groupeditdlg.statusbar), groupeditdlg.status_cid, msg ); + } + } +} + +static void edit_group_ok(GtkWidget *widget, gboolean *cancelled) { + gchar *sName; + gboolean errFlag = TRUE; + + sName = gtk_editable_get_chars( GTK_EDITABLE(groupeditdlg.entry_name), 0, -1 ); + if( sName ) { + g_strstrip( sName ); + if( *sName != '\0' ) { + gtk_entry_set_text(GTK_ENTRY(groupeditdlg.entry_name), sName ); + *cancelled = FALSE; + gtk_main_quit(); + errFlag = FALSE; + } + } + if( errFlag ) { + edit_group_status_show( _( "A Group Name must be supplied." ) ); + } + g_free( sName ); +} + +static void edit_group_cancel(GtkWidget *widget, gboolean *cancelled) { + *cancelled = TRUE; + gtk_main_quit(); +} + +static gint edit_group_delete_event(GtkWidget *widget, GdkEventAny *event, gboolean *cancelled) { + *cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_group_key_pressed(GtkWidget *widget, GdkEventKey *event, gboolean *cancelled) { + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static gchar *edit_group_format_item_clist( ItemPerson *person, ItemEMail *email ) { + gchar *str = NULL; + gchar *aName = ADDRITEM_NAME(email); + if( aName == NULL || *aName == '\0' ) return str; + if( person ) { + str = g_strdup_printf( "%s - %s", ADDRITEM_NAME(person), aName ); + } + else { + str = g_strdup( aName ); + } + return str; +} + +static gint edit_group_clist_add_email( GtkCList *clist, ItemEMail *email ) { + ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(email); + gchar *str = edit_group_format_item_clist( person, email ); + gchar *text[ GROUP_N_COLS ]; + gint row; + if( str ) { + text[ GROUP_COL_NAME ] = str; + } + else { + text[ GROUP_COL_NAME ] = ADDRITEM_NAME(person); + } + text[ GROUP_COL_EMAIL ] = email->address; + text[ GROUP_COL_REMARKS ] = email->remarks; + row = gtk_clist_append( clist, text ); + gtk_clist_set_row_data( clist, row, email ); + return row; +} + +static void edit_group_load_clist( GtkCList *clist, GList *listEMail ) { + GList *node = listEMail; + while( node ) { + ItemEMail *email = node->data; + edit_group_clist_add_email( clist, email ); + node = g_list_next( node ); + } +} + +static void edit_group_group_selected( GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data ) { + groupeditdlg.rowIndGroup = row; +} + +static void edit_group_avail_selected( GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data ) { + groupeditdlg.rowIndAvail = row; +} + +static gint edit_group_move_email( GtkCList *clist_from, GtkCList *clist_to, gint row ) { + ItemEMail *email = gtk_clist_get_row_data( clist_from, row ); + gint rrow = -1; + if( email ) { + gtk_clist_remove( clist_from, row ); + rrow = edit_group_clist_add_email( clist_to, email ); + gtk_clist_select_row( clist_to, rrow, 0 ); + } + return rrow; +} + +static void edit_group_to_group( GtkWidget *widget, gpointer data ) { + groupeditdlg.rowIndGroup = edit_group_move_email( groupeditdlg.clist_avail, + groupeditdlg.clist_group, groupeditdlg.rowIndAvail ); +} + +static void edit_group_to_avail( GtkWidget *widget, gpointer data ) { + groupeditdlg.rowIndAvail = edit_group_move_email( groupeditdlg.clist_group, + groupeditdlg.clist_avail, groupeditdlg.rowIndGroup ); +} + +static gboolean edit_group_list_group_button( GtkCList *clist, GdkEventButton *event, gpointer data ) { + if( ! event ) return FALSE; + if( event->button == 1 ) { + if( event->type == GDK_2BUTTON_PRESS ) { + edit_group_to_avail( NULL, NULL ); + } + } + return FALSE; +} + +static gboolean edit_group_list_avail_button( GtkCList *clist, GdkEventButton *event, gpointer data ) { + if( ! event ) return FALSE; + if( event->button == 1 ) { + if( event->type == GDK_2BUTTON_PRESS ) { + edit_group_to_group( NULL, NULL ); + } + } + return FALSE; +} + +static gint edit_group_list_compare_func( GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2 ) { + GtkCell *cell1 = ((GtkCListRow *)ptr1)->cell; + GtkCell *cell2 = ((GtkCListRow *)ptr2)->cell; + gchar *name1 = NULL, *name2 = NULL; + if( cell1 ) name1 = cell1->u.text; + if( cell2 ) name2 = cell2->u.text; + if( ! name1 ) return ( name2 != NULL ); + if( ! name2 ) return -1; + return strcasecmp( name1, name2 ); +} + +static void addressbook_edit_group_create( gboolean *cancelled ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *hbbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *hsbox; + GtkWidget *statusbar; + + GtkWidget *hboxg; + GtkWidget *table; + GtkWidget *label; + GtkWidget *entry_name; + GtkWidget *hboxl; + GtkWidget *vboxl; + GtkWidget *hboxh; + + GtkWidget *clist_swin; + GtkWidget *clist_group; + GtkWidget *clist_avail; + + GtkWidget *buttonGroup; + GtkWidget *buttonAvail; + gint top; + + gchar *titles[ GROUP_N_COLS ]; + gint i; + + titles[ GROUP_COL_NAME ] = _( "Name" ); + titles[ GROUP_COL_EMAIL ] = _("E-Mail Address"); + titles[ GROUP_COL_REMARKS ] = _("Remarks"); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, EDITGROUP_WIDTH, EDITGROUP_HEIGHT ); + gtk_container_set_border_width(GTK_CONTAINER(window), 0); + gtk_window_set_title(GTK_WINDOW(window), _("Edit Group Data")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_group_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_group_key_pressed), + cancelled); + + vbox = gtk_vbox_new( FALSE, 6 ); + gtk_container_set_border_width(GTK_CONTAINER(vbox), BORDER_WIDTH); + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( window ), vbox ); + + /* Group area */ + hboxg = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start(GTK_BOX(vbox), hboxg, FALSE, FALSE, 0); + + /* Data entry area */ + table = gtk_table_new( 1, 3, FALSE); + gtk_box_pack_start(GTK_BOX(hboxg), table, TRUE, TRUE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 4 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 0); + gtk_table_set_col_spacings(GTK_TABLE(table), 4); + + /* First row */ + top = 0; + label = gtk_label_new(_("Group Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_name = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_name, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* List area */ + hboxl = gtk_hbox_new( FALSE, 6 ); + gtk_container_set_border_width( GTK_CONTAINER(hboxl), 8 ); + gtk_box_pack_start(GTK_BOX(vbox), hboxl, TRUE, TRUE, 0); + + /* Group list */ + vboxl = gtk_vbox_new( FALSE, 0 ); + gtk_box_pack_start(GTK_BOX(hboxl), vboxl, TRUE, TRUE, 0); + + hboxh = gtk_hbox_new( FALSE, 0 ); + gtk_container_set_border_width( GTK_CONTAINER(hboxh), 4 ); + gtk_box_pack_start(GTK_BOX(vboxl), hboxh, FALSE, FALSE, 0); + label = gtk_label_new(_("Addresses in Group")); + gtk_box_pack_start(GTK_BOX(hboxh), label, TRUE, TRUE, 0); + buttonAvail = gtk_button_new_with_label( _( " -> " ) ); + gtk_box_pack_end(GTK_BOX(hboxh), buttonAvail, FALSE, FALSE, 0); + + clist_swin = gtk_scrolled_window_new( NULL, NULL ); + gtk_box_pack_start(GTK_BOX(vboxl), clist_swin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + + clist_group = gtk_clist_new_with_titles( GROUP_N_COLS, titles ); + gtk_container_add( GTK_CONTAINER(clist_swin), clist_group ); + gtk_clist_set_selection_mode( GTK_CLIST(clist_group), GTK_SELECTION_BROWSE ); + gtk_clist_set_column_width( GTK_CLIST(clist_group), GROUP_COL_NAME, GROUP_COL_WIDTH_NAME ); + gtk_clist_set_column_width( GTK_CLIST(clist_group), GROUP_COL_EMAIL, GROUP_COL_WIDTH_EMAIL ); + gtk_clist_set_compare_func( GTK_CLIST(clist_group), edit_group_list_compare_func ); + gtk_clist_set_auto_sort( GTK_CLIST(clist_group), TRUE ); + + for( i = 0; i < GROUP_N_COLS; i++ ) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist_group)->column[i].button, GTK_CAN_FOCUS); + + /* Available list */ + vboxl = gtk_vbox_new( FALSE, 0 ); + gtk_box_pack_start(GTK_BOX(hboxl), vboxl, TRUE, TRUE, 0); + + hboxh = gtk_hbox_new( FALSE, 0 ); + gtk_container_set_border_width( GTK_CONTAINER(hboxh), 4 ); + gtk_box_pack_start(GTK_BOX(vboxl), hboxh, FALSE, FALSE, 0); + buttonGroup = gtk_button_new_with_label( _( " <- " ) ); + gtk_box_pack_start(GTK_BOX(hboxh), buttonGroup, FALSE, FALSE, 0); + label = gtk_label_new(_("Available Addresses")); + gtk_box_pack_end(GTK_BOX(hboxh), label, TRUE, TRUE, 0); + + clist_swin = gtk_scrolled_window_new( NULL, NULL ); + gtk_box_pack_start(GTK_BOX(vboxl), clist_swin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + + clist_avail = gtk_clist_new_with_titles( GROUP_N_COLS, titles ); + gtk_container_add( GTK_CONTAINER(clist_swin), clist_avail ); + gtk_clist_set_selection_mode( GTK_CLIST(clist_avail), GTK_SELECTION_BROWSE ); + gtk_clist_set_column_width( GTK_CLIST(clist_avail), GROUP_COL_NAME, GROUP_COL_WIDTH_NAME ); + gtk_clist_set_column_width( GTK_CLIST(clist_avail), GROUP_COL_EMAIL, GROUP_COL_WIDTH_EMAIL ); + gtk_clist_set_compare_func( GTK_CLIST(clist_avail), edit_group_list_compare_func ); + gtk_clist_set_auto_sort( GTK_CLIST(clist_avail), TRUE ); + + for( i = 0; i < GROUP_N_COLS; i++ ) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist_avail)->column[i].button, GTK_CAN_FOCUS); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_group_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_group_cancel), cancelled); + + gtk_widget_show_all(vbox); + + /* Event handlers */ + g_signal_connect(G_OBJECT(clist_group), "select_row", + G_CALLBACK( edit_group_group_selected), NULL); + g_signal_connect(G_OBJECT(clist_avail), "select_row", + G_CALLBACK( edit_group_avail_selected), NULL); + g_signal_connect(G_OBJECT(buttonGroup), "clicked", + G_CALLBACK( edit_group_to_group ), NULL); + g_signal_connect(G_OBJECT(buttonAvail), "clicked", + G_CALLBACK( edit_group_to_avail ), NULL); + g_signal_connect(G_OBJECT(clist_avail), "button_press_event", + G_CALLBACK(edit_group_list_avail_button), NULL); + g_signal_connect(G_OBJECT(clist_group), "button_press_event", + G_CALLBACK(edit_group_list_group_button), NULL); + + groupeditdlg.window = window; + groupeditdlg.ok_btn = ok_btn; + groupeditdlg.cancel_btn = cancel_btn; + groupeditdlg.statusbar = statusbar; + groupeditdlg.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit Group Dialog" ); + + groupeditdlg.entry_name = entry_name; + groupeditdlg.clist_group = GTK_CLIST( clist_group ); + groupeditdlg.clist_avail = GTK_CLIST( clist_avail ); + + if( ! _edit_group_dfl_message_ ) { + _edit_group_dfl_message_ = _( "Move E-Mail Addresses to or from Group with arrow buttons" ); + } +} + +/* +* Return list of email items. +*/ +static GList *edit_group_build_email_list() { + GtkCList *clist = GTK_CLIST(groupeditdlg.clist_group); + GList *listEMail = NULL; + ItemEMail *email; + gint row = 0; + while( (email = gtk_clist_get_row_data( clist, row )) ) { + listEMail = g_list_append( listEMail, email ); + row++; + } + return listEMail; +} + +/* +* Edit group. +* Enter: abf Address book. +* folder Parent folder for group (or NULL if adding to root folder). Argument is +* only required for new objects). +* group Group to edit, or NULL for a new group object. +* Return: Edited object, or NULL if cancelled. +*/ +ItemGroup *addressbook_edit_group( AddressBookFile *abf, ItemFolder *parent, ItemGroup *group ) { + static gboolean cancelled; + GList *listEMail = NULL; + gchar *name; + + if (!groupeditdlg.window) + addressbook_edit_group_create(&cancelled); + gtk_widget_grab_focus(groupeditdlg.ok_btn); + gtk_widget_grab_focus(groupeditdlg.entry_name); + gtk_widget_show(groupeditdlg.window); + manage_window_set_transient(GTK_WINDOW(groupeditdlg.window)); + + /* Clear all fields */ + groupeditdlg.rowIndGroup = -1; + groupeditdlg.rowIndAvail = -1; + edit_group_status_show( "" ); + gtk_clist_clear( GTK_CLIST(groupeditdlg.clist_group) ); + gtk_clist_clear( GTK_CLIST(groupeditdlg.clist_avail) ); + + if( group ) { + if( ADDRITEM_NAME(group) ) + gtk_entry_set_text(GTK_ENTRY(groupeditdlg.entry_name), ADDRITEM_NAME(group) ); + edit_group_load_clist( groupeditdlg.clist_group, group->listEMail ); + gtk_window_set_title( GTK_WINDOW(groupeditdlg.window), _("Edit Group Details")); + } + else { + gtk_window_set_title( GTK_WINDOW(groupeditdlg.window), _("Add New Group")); + gtk_entry_set_text(GTK_ENTRY(groupeditdlg.entry_name), ADDRESSBOOK_GUESS_GROUP_NAME ); + } + + listEMail = addrbook_get_available_email_list( abf, group ); + edit_group_load_clist( groupeditdlg.clist_avail, listEMail ); + mgu_clear_list( listEMail ); + listEMail = NULL; + gtk_clist_select_row( groupeditdlg.clist_group, 0, 0 ); + gtk_clist_select_row( groupeditdlg.clist_avail, 0, 0 ); + + edit_group_status_show( _edit_group_dfl_message_ ); + + gtk_main(); + gtk_widget_hide( groupeditdlg.window ); + + if( cancelled ) { + return NULL; + } + + listEMail = edit_group_build_email_list(); + if( group ) { + /* Update email list */ + addrbook_update_group_list( abf, group, listEMail ); + } + else { + /* Create new person and email list */ + group = addrbook_add_group_list( abf, parent, listEMail ); + } + name = gtk_editable_get_chars( GTK_EDITABLE(groupeditdlg.entry_name), 0, -1 ); + addritem_group_set_name( group, name ); + g_free( name ); + + listEMail = NULL; + return group; +} + +/* +* Edit folder. +* Enter: abf Address book. +* parent Parent folder for folder (or NULL if adding to root folder). Argument is +* only required for new objects). +* folder Folder to edit, or NULL for a new folder object. +* Return: Edited object, or NULL if cancelled. +*/ +ItemFolder *addressbook_edit_folder( AddressBookFile *abf, ItemFolder *parent, ItemFolder *folder ) { + gchar *name = NULL; + + if( folder ) { + name = g_strdup( ADDRITEM_NAME(folder) ); + name = input_dialog( _("Edit folder"), _("Input the new name of folder:"), name ); + } + else { + name = input_dialog( _("New folder"), + _("Input the name of new folder:"), + _(ADDRESSBOOK_GUESS_FOLDER_NAME) ); + } + if( ! name ) return NULL; + g_strstrip( name ); + if( *name == '\0' ) { + g_free( name ); + return NULL; + } + if( folder ) { + if( strcasecmp( name, ADDRITEM_NAME(folder) ) == 0 ) { + g_free( name ); + return NULL; + } + } + + if( ! folder ) { + folder = addrbook_add_new_folder( abf, parent ); + } + addritem_folder_set_name( folder, name ); + g_free( name ); + return folder; +} + +/* +* End of Source. +*/ + diff --git a/src/editgroup.h b/src/editgroup.h new file mode 100644 index 00000000..6a3571dd --- /dev/null +++ b/src/editgroup.h @@ -0,0 +1,26 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __EDITGROUP_H__ +#define __EDITGROUP_H__ + +ItemGroup *addressbook_edit_group( AddressBookFile *abf, ItemFolder *folder, ItemGroup *group ); +ItemFolder *addressbook_edit_folder( AddressBookFile *abf, ItemFolder *parent, ItemFolder *folder ); + +#endif /* __EDITGROUP_H__ */ diff --git a/src/editjpilot.c b/src/editjpilot.c new file mode 100644 index 00000000..b2b36f01 --- /dev/null +++ b/src/editjpilot.c @@ -0,0 +1,447 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit JPilot address book data. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_JPILOT + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "addressbook.h" +#include "prefs_common.h" +#include "addressitem.h" +#include "jpilot.h" +#include "mgutils.h" +#include "gtkutils.h" +#include "manage_window.h" + +#define ADDRESSBOOK_GUESS_JPILOT "MyJPilot" +#define JPILOT_NUM_CUSTOM_LABEL 4 + +static struct _JPilotEdit { + GtkWidget *window; + GtkWidget *name_entry; + GtkWidget *file_entry; + GtkWidget *custom_check[JPILOT_NUM_CUSTOM_LABEL]; + GtkWidget *custom_label[JPILOT_NUM_CUSTOM_LABEL]; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; +} jpilotedit; + +static struct _AddressFileSelection jpilot_file_selector; + +/* +* Edit functions. +*/ +void edit_jpilot_status_show( gchar *msg ) { + if( jpilotedit.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(jpilotedit.statusbar), jpilotedit.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(jpilotedit.statusbar), jpilotedit.status_cid, msg ); + } + } +} + +static gint edit_jpilot_delete_event( GtkWidget *widget, GdkEventAny *event, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_jpilot_key_pressed( GtkWidget *widget, GdkEventKey *event, gboolean *cancelled ) { + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void edit_jpilot_ok( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_jpilot_cancel( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); +} + +static void edit_jpilot_fill_check_box( JPilotFile *jpf ) { + gint i; + GList *node, *customLbl = NULL; + gchar *labelName; + gboolean done, checked; + for( i = 0; i < JPILOT_NUM_CUSTOM_LABEL; i++ ) { + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( jpilotedit.custom_check[i] ), FALSE ); + gtk_label_set_text( GTK_LABEL( jpilotedit.custom_label[i] ), "" ); + } + + done = FALSE; + i = 0; + customLbl = jpilot_load_custom_label( jpf, customLbl ); + node = customLbl; + while( ! done ) { + if( node ) { + labelName = node->data; + gtk_label_set_text( GTK_LABEL( jpilotedit.custom_label[i] ), labelName ); + checked = jpilot_test_custom_label( jpf, labelName ); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( jpilotedit.custom_check[i] ), checked ); + i++; + if( i >= JPILOT_NUM_CUSTOM_LABEL ) done = TRUE; + node = g_list_next( node ); + } + else { + done = TRUE; + } + } + mgu_free_dlist( customLbl ); + customLbl = NULL; +} + +static void edit_jpilot_fill_check_box_new() { + gint i; + for( i = 0; i < JPILOT_NUM_CUSTOM_LABEL; i++ ) { + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( jpilotedit.custom_check[i] ), FALSE ); + gtk_label_set_text( GTK_LABEL( jpilotedit.custom_label[i] ), "" ); + } +} + +static void edit_jpilot_read_check_box( JPilotFile *pilotFile ) { + gint i; + gchar *labelName; + jpilot_clear_custom_labels( pilotFile ); + for( i = 0; i < JPILOT_NUM_CUSTOM_LABEL; i++ ) { + if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(jpilotedit.custom_check[i]) ) ) { + labelName = GTK_LABEL(jpilotedit.custom_label[i])->label; + jpilot_add_custom_label( pilotFile, labelName ); + } + } +} + +static void edit_jpilot_file_check( void ) { + gint t = -1; + gchar *sFile; + gchar *sMsg; + gboolean flg; + + flg = FALSE; + sFile = gtk_editable_get_chars( GTK_EDITABLE(jpilotedit.file_entry), 0, -1 ); + if( sFile ) { + g_strchomp( sFile ); g_strchug( sFile ); + if( *sFile != '\0' ) { + /* Attempt to read file */ + JPilotFile *jpf = jpilot_create_path( sFile ); + t = jpilot_read_data( jpf ); + if( t == MGU_SUCCESS ) { + /* Set check boxes */ + edit_jpilot_fill_check_box( jpf ); + flg = TRUE; + } + jpilot_free( jpf ); + jpf = NULL; + } + } + if( ! flg ) { + /* Clear all check boxes */ + edit_jpilot_fill_check_box_new(); + } + g_free( sFile ); + + /* Display appropriate message */ + if( t == MGU_SUCCESS ) { + sMsg = ""; + } + else if( t == MGU_BAD_FORMAT || t == MGU_OO_MEMORY ) { + sMsg = _("File does not appear to be JPilot format."); + } + else { + sMsg = _("Could not read file."); + } + edit_jpilot_status_show( sMsg ); +} + +static void edit_jpilot_file_ok( GtkWidget *widget, gpointer data ) { + const gchar *sFile; + AddressFileSelection *afs; + GtkWidget *fileSel; + + afs = ( AddressFileSelection * ) data; + fileSel = afs->fileSelector; + sFile = gtk_file_selection_get_filename( GTK_FILE_SELECTION(fileSel) ); + + afs->cancelled = FALSE; + gtk_entry_set_text( GTK_ENTRY(jpilotedit.file_entry), sFile ); + gtk_widget_hide( afs->fileSelector ); + gtk_grab_remove( afs->fileSelector ); + edit_jpilot_file_check(); + gtk_widget_grab_focus( jpilotedit.file_entry ); +} + +static void edit_jpilot_file_cancel( GtkWidget *widget, gpointer data ) { + AddressFileSelection *afs = ( AddressFileSelection * ) data; + afs->cancelled = TRUE; + gtk_widget_hide( afs->fileSelector ); + gtk_grab_remove( afs->fileSelector ); + gtk_widget_grab_focus( jpilotedit.file_entry ); +} + +static void edit_jpilot_file_select_create( AddressFileSelection *afs ) { + GtkWidget *fileSelector; + + fileSelector = gtk_file_selection_new( _("Select JPilot File") ); + gtk_file_selection_hide_fileop_buttons( GTK_FILE_SELECTION(fileSelector) ); + g_signal_connect( G_OBJECT (GTK_FILE_SELECTION(fileSelector)->ok_button), + "clicked", G_CALLBACK (edit_jpilot_file_ok), ( gpointer ) afs ); + g_signal_connect( G_OBJECT (GTK_FILE_SELECTION(fileSelector)->cancel_button), + "clicked", G_CALLBACK (edit_jpilot_file_cancel), ( gpointer ) afs ); + afs->fileSelector = fileSelector; + afs->cancelled = TRUE; +} + +static void edit_jpilot_file_select( void ) { + gchar *sFile; + + if (! jpilot_file_selector.fileSelector ) + edit_jpilot_file_select_create( & jpilot_file_selector ); + + sFile = gtk_editable_get_chars( GTK_EDITABLE(jpilotedit.file_entry), 0, -1 ); + gtk_file_selection_set_filename( GTK_FILE_SELECTION( jpilot_file_selector.fileSelector ), sFile ); + g_free( sFile ); + gtk_widget_show( jpilot_file_selector.fileSelector ); + gtk_grab_add( jpilot_file_selector.fileSelector ); +} + +static void addressbook_edit_jpilot_create( gboolean *cancelled ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *name_entry; + GtkWidget *file_entry; + GtkWidget *vbox_custom; + GtkWidget *frame_custom; + GtkWidget *custom_check[JPILOT_NUM_CUSTOM_LABEL]; + GtkWidget *custom_label[JPILOT_NUM_CUSTOM_LABEL]; + GtkWidget *hlbox; + GtkWidget *hbbox; + GtkWidget *hsep; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *check_btn; + GtkWidget *file_btn; + GtkWidget *hsbox; + GtkWidget *statusbar; + gint top, i; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 450, -1); + gtk_container_set_border_width(GTK_CONTAINER(window), 0); + gtk_window_set_title(GTK_WINDOW(window), _("Edit JPilot Entry")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_jpilot_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_jpilot_key_pressed), + cancelled); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_container_set_border_width( GTK_CONTAINER(vbox), 0 ); + + table = gtk_table_new(2 + JPILOT_NUM_CUSTOM_LABEL, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + /* First row */ + top = 0; + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + name_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), name_entry, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + check_btn = gtk_button_new_with_label( _(" Check File ")); + gtk_table_attach(GTK_TABLE(table), check_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Second row */ + top = 1; + label = gtk_label_new(_("File")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + file_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), file_entry, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + file_btn = gtk_button_new_with_label( _(" ... ")); + gtk_table_attach(GTK_TABLE(table), file_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Third row */ + top = 2; + frame_custom = gtk_frame_new(_("Additional e-Mail address item(s)")); + gtk_table_attach(GTK_TABLE(table), frame_custom, 1, 2, top, (top + JPILOT_NUM_CUSTOM_LABEL), GTK_FILL, 0, 0, 0); + + /* Now do custom labels. */ + vbox_custom = gtk_vbox_new (FALSE, 8); + for( i = 0; i < JPILOT_NUM_CUSTOM_LABEL; i++ ) { + hlbox = gtk_hbox_new( FALSE, 0 ); + custom_check[i] = gtk_check_button_new(); + custom_label[i] = gtk_label_new( "" ); + gtk_box_pack_start( GTK_BOX(hlbox), custom_check[i], FALSE, FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hlbox), custom_label[i], TRUE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(vbox_custom), hlbox, TRUE, TRUE, 0 ); + gtk_misc_set_alignment(GTK_MISC(custom_label[i]), 0, 0.5); + top++; + } + gtk_container_add (GTK_CONTAINER (frame_custom), vbox_custom); + gtk_container_set_border_width( GTK_CONTAINER(vbox_custom), 8 ); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(hbbox), 0 ); + gtk_widget_grab_default(ok_btn); + + hsep = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(vbox), hsep, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_jpilot_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_jpilot_cancel), cancelled); + g_signal_connect(G_OBJECT(file_btn), "clicked", + G_CALLBACK(edit_jpilot_file_select), NULL); + g_signal_connect(G_OBJECT(check_btn), "clicked", + G_CALLBACK(edit_jpilot_file_check), NULL); + + gtk_widget_show_all(vbox); + + jpilotedit.window = window; + jpilotedit.name_entry = name_entry; + jpilotedit.file_entry = file_entry; + jpilotedit.ok_btn = ok_btn; + jpilotedit.cancel_btn = cancel_btn; + jpilotedit.statusbar = statusbar; + jpilotedit.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit JPilot Dialog" ); + for( i = 0; i < JPILOT_NUM_CUSTOM_LABEL; i++ ) { + jpilotedit.custom_check[i] = custom_check[i]; + jpilotedit.custom_label[i] = custom_label[i]; + } +} + +AdapterDSource *addressbook_edit_jpilot( AddressIndex *addrIndex, AdapterDSource *ads ) { + static gboolean cancelled; + gchar *sName; + gchar *sFile; + AddressDataSource *ds = NULL; + JPilotFile *jpf = NULL; + gboolean fin; + + if( ! jpilotedit.window ) + addressbook_edit_jpilot_create(&cancelled); + gtk_widget_grab_focus(jpilotedit.ok_btn); + gtk_widget_grab_focus(jpilotedit.name_entry); + gtk_widget_show(jpilotedit.window); + manage_window_set_transient(GTK_WINDOW(jpilotedit.window)); + + edit_jpilot_status_show( "" ); + if( ads ) { + ds = ads->dataSource; + jpf = ds->rawDataSource; + if (jpf->name) + gtk_entry_set_text(GTK_ENTRY(jpilotedit.name_entry), jpf->name); + if (jpf->path) + gtk_entry_set_text(GTK_ENTRY(jpilotedit.file_entry), jpf->path); + gtk_window_set_title( GTK_WINDOW(jpilotedit.window), _("Edit JPilot Entry")); + edit_jpilot_fill_check_box( jpf ); + } + else { + gchar *guessFile = jpilot_find_pilotdb(); + gtk_entry_set_text(GTK_ENTRY(jpilotedit.name_entry), ADDRESSBOOK_GUESS_JPILOT ); + gtk_entry_set_text(GTK_ENTRY(jpilotedit.file_entry), guessFile ); + gtk_window_set_title( GTK_WINDOW(jpilotedit.window), _("Add New JPilot Entry")); + edit_jpilot_fill_check_box_new(); + if( *guessFile != '\0' ) { + edit_jpilot_file_check(); + } + } + + gtk_main(); + gtk_widget_hide(jpilotedit.window); + if (cancelled == TRUE) return NULL; + + fin = FALSE; + sName = gtk_editable_get_chars( GTK_EDITABLE(jpilotedit.name_entry), 0, -1 ); + sFile = gtk_editable_get_chars( GTK_EDITABLE(jpilotedit.file_entry), 0, -1 ); + if( *sName == '\0' ) fin = TRUE; + if( *sFile == '\0' ) fin = TRUE; + + if( ! fin ) { + if( ! ads ) { + jpf = jpilot_create(); + ds = addrindex_index_add_datasource( addrIndex, ADDR_IF_JPILOT, jpf ); + ads = addressbook_create_ds_adapter( ds, ADDR_JPILOT, NULL ); + } + addressbook_ads_set_name( ads, sName ); + jpilot_set_name( jpf, sName ); + jpilot_set_file( jpf, sFile ); + edit_jpilot_read_check_box( jpf ); + } + g_free( sName ); + g_free( sFile ); + + return ads; +} + +#endif /* USE_JPILOT */ + +/* +* End of Source. +*/ + diff --git a/src/editjpilot.h b/src/editjpilot.h new file mode 100644 index 00000000..e866255e --- /dev/null +++ b/src/editjpilot.h @@ -0,0 +1,33 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit JPilot address book data. + */ + +#ifndef __EDITJPILOT_H__ +#define __EDITJPILOT_H__ + +#ifdef USE_JPILOT + +AdapterDSource *addressbook_edit_jpilot( AddressIndex *addrIndex, AdapterDSource *ads ); + +#endif /* USE_JPILOT */ + +#endif /* __EDITJPILOT_H__ */ diff --git a/src/editldap.c b/src/editldap.c new file mode 100644 index 00000000..4ef4e4a3 --- /dev/null +++ b/src/editldap.c @@ -0,0 +1,600 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit LDAP address book data. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_LDAP + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "addressbook.h" +#include "prefs_common.h" +#include "addressitem.h" +#include "mgutils.h" +#include "syldap.h" +#include "editldap_basedn.h" +#include "manage_window.h" +#include "gtkutils.h" + +#define ADDRESSBOOK_GUESS_LDAP_NAME "MyServer" +#define ADDRESSBOOK_GUESS_LDAP_SERVER "localhost" + +#define LDAPEDIT_TABLE_ROWS 6 +#define LDAPEDIT_TABLE_COLS 3 + +static struct _LDAPEdit { + GtkWidget *window; + GtkWidget *notebook; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; + GtkWidget *entry_name; + GtkWidget *entry_server; + GtkWidget *spinbtn_port; + GtkWidget *entry_baseDN; + GtkWidget *spinbtn_timeout; + GtkWidget *entry_bindDN; + GtkWidget *entry_bindPW; + GtkWidget *entry_criteria; + GtkWidget *spinbtn_maxentry; +} ldapedit; + +/* +* Edit functions. +*/ +static void edit_ldap_status_show( gchar *msg ) { + if( ldapedit.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(ldapedit.statusbar), ldapedit.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(ldapedit.statusbar), ldapedit.status_cid, msg ); + } + } +} + +static void edit_ldap_ok( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_ldap_cancel( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); +} + +static gint edit_ldap_delete_event( GtkWidget *widget, GdkEventAny *event, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_ldap_key_pressed( GtkWidget *widget, GdkEventKey *event, gboolean *cancelled ) { + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void edit_ldap_switch_page( GtkWidget *widget ) { + edit_ldap_status_show( "" ); +} + +static void edit_ldap_server_check( void ) { + gchar *sHost, *sBind, *sPass; + gint iPort, iTime; + gchar *sMsg; + gchar *sBaseDN = NULL; + gint iBaseDN = 0; + gboolean flg; + + edit_ldap_status_show( "" ); + flg = FALSE; + sHost = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_server), 0, -1 ); + sBind = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindDN), 0, -1 ); + sPass = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindPW), 0, -1 ); + iPort = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ) ); + iTime = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ) ); + g_strchomp( sHost ); g_strchug( sHost ); + g_strchomp( sBind ); g_strchug( sBind ); + g_strchomp( sPass ); g_strchug( sPass ); + if( *sHost != '\0' ) { + /* Test connection to server */ + if( syldap_test_connect_s( sHost, iPort ) ) { + /* Attempt to read base DN */ + GList *baseDN = syldap_read_basedn_s( sHost, iPort, sBind, sPass, iTime ); + if( baseDN ) { + GList *node = baseDN; + while( node ) { + ++iBaseDN; + if( ! sBaseDN ) { + sBaseDN = g_strdup( node->data ); + } + node = g_list_next( node ); + } + mgu_free_dlist( baseDN ); + baseDN = node = NULL; + } + flg = TRUE; + } + } + g_free( sHost ); + g_free( sBind ); + g_free( sPass ); + + if( sBaseDN ) { + /* Load search DN */ + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), sBaseDN); + g_free( sBaseDN ); + } + + /* Display appropriate message */ + if( flg ) { + sMsg = _( "Connected successfully to server" ); + } + else { + sMsg = _( "Could not connect to server" ); + } + edit_ldap_status_show( sMsg ); +} + +static void edit_ldap_basedn_select( void ) { + gchar *sHost, *sBind, *sPass, *sBase; + gint iPort, iTime; + gchar *selectDN; + + sHost = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_server), 0, -1 ); + sBase = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_baseDN), 0, -1 ); + sBind = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindDN), 0, -1 ); + sPass = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindPW), 0, -1 ); + iPort = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ) ); + iTime = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ) ); + g_strchomp( sHost ); g_strchug( sHost ); + g_strchomp( sBind ); g_strchug( sBind ); + g_strchomp( sPass ); g_strchug( sPass ); + selectDN = edit_ldap_basedn_selection( sHost, iPort, sBase, iTime, sBind, sPass ); + if( selectDN ) { + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), selectDN); + g_free( selectDN ); + selectDN = NULL; + } + g_free( sHost ); + g_free( sBase ); + g_free( sBind ); + g_free( sPass ); +} + +static void edit_ldap_search_reset( void ) { + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), SYLDAP_DFL_CRITERIA ); +} + +static void addressbook_edit_ldap_dialog_create( gboolean *cancelled ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *notebook; + GtkWidget *hbbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *hsbox; + GtkWidget *statusbar; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 450, -1); + gtk_container_set_border_width(GTK_CONTAINER(window), 0); + gtk_window_set_title(GTK_WINDOW(window), _("Edit LDAP Server")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_ldap_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_ldap_key_pressed), + cancelled); + + vbox = gtk_vbox_new( FALSE, 6 ); + /* gtk_container_set_border_width(GTK_CONTAINER(vbox), BORDER_WIDTH); */ + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( window ), vbox ); + + /* Notebook */ + notebook = gtk_notebook_new(); + gtk_widget_show( notebook ); + gtk_box_pack_start( GTK_BOX( vbox ), notebook, TRUE, TRUE, 0 ); + gtk_container_set_border_width( GTK_CONTAINER( notebook ), 6 ); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_ldap_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_ldap_cancel), cancelled); + g_signal_connect(G_OBJECT(notebook), "switch_page", + G_CALLBACK(edit_ldap_switch_page), NULL ); + + gtk_widget_show_all(vbox); + + ldapedit.window = window; + ldapedit.notebook = notebook; + ldapedit.ok_btn = ok_btn; + ldapedit.cancel_btn = cancel_btn; + ldapedit.statusbar = statusbar; + ldapedit.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit LDAP Server Dialog" ); +} + +void addressbook_edit_ldap_page_basic( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *entry_name; + GtkWidget *entry_server; + GtkWidget *hbox_spin; + GtkObject *spinbtn_port_adj; + GtkWidget *spinbtn_port; + GtkWidget *entry_baseDN; + GtkWidget *check_btn; + GtkWidget *lookdn_btn; + gint top; + + vbox = gtk_vbox_new( FALSE, 8 ); + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( ldapedit.notebook ), vbox ); + /* gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); */ + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( ldapedit.notebook ), + gtk_notebook_get_nth_page( GTK_NOTEBOOK( ldapedit.notebook ), pageNum ), label ); + + table = gtk_table_new( LDAPEDIT_TABLE_ROWS, LDAPEDIT_TABLE_COLS, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + /* First row */ + top = 0; + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_name = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_name, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Hostname")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_server = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_server, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Port")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + hbox_spin = gtk_hbox_new (FALSE, 8); + spinbtn_port_adj = gtk_adjustment_new (389, 1, 65535, 100, 1000, 1000); + spinbtn_port = gtk_spin_button_new(GTK_ADJUSTMENT (spinbtn_port_adj), 1, 0); + gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_port, FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_port, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_port), TRUE); + gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + check_btn = gtk_button_new_with_label( _(" Check Server ")); + gtk_table_attach(GTK_TABLE(table), check_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Search Base")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_baseDN = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_baseDN, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + lookdn_btn = gtk_button_new_with_label( _(" ... ")); + gtk_table_attach(GTK_TABLE(table), lookdn_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + g_signal_connect(G_OBJECT(check_btn), "clicked", + G_CALLBACK(edit_ldap_server_check), NULL); + g_signal_connect(G_OBJECT(lookdn_btn), "clicked", + G_CALLBACK(edit_ldap_basedn_select), NULL); + + gtk_widget_show_all(vbox); + + ldapedit.entry_name = entry_name; + ldapedit.entry_server = entry_server; + ldapedit.spinbtn_port = spinbtn_port; + ldapedit.entry_baseDN = entry_baseDN; +} + +void addressbook_edit_ldap_page_extended( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *entry_bindDN; + GtkWidget *entry_bindPW; + GtkWidget *entry_criteria; + GtkWidget *hbox_spin; + GtkObject *spinbtn_timeout_adj; + GtkWidget *spinbtn_timeout; + GtkObject *spinbtn_maxentry_adj; + GtkWidget *spinbtn_maxentry; + GtkWidget *reset_btn; + gint top; + + vbox = gtk_vbox_new( FALSE, 8 ); + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( ldapedit.notebook ), vbox ); + /* gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); */ + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( ldapedit.notebook ), + gtk_notebook_get_nth_page( GTK_NOTEBOOK( ldapedit.notebook ), pageNum ), label ); + + table = gtk_table_new( LDAPEDIT_TABLE_ROWS, LDAPEDIT_TABLE_COLS, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + /* First row */ + top = 0; + label = gtk_label_new(_("Search Criteria")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_criteria = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_criteria, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + reset_btn = gtk_button_new_with_label( _(" Reset ")); + gtk_table_attach(GTK_TABLE(table), reset_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Bind DN")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_bindDN = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_bindDN, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Bind Password")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry_bindPW = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), entry_bindPW, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Timeout (secs)")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + hbox_spin = gtk_hbox_new (FALSE, 8); + spinbtn_timeout_adj = gtk_adjustment_new (0, 0, 300, 1, 10, 10); + spinbtn_timeout = gtk_spin_button_new(GTK_ADJUSTMENT (spinbtn_timeout_adj), 1, 0); + gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_timeout, FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_timeout, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_timeout), TRUE); + gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Maximum Entries")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + hbox_spin = gtk_hbox_new (FALSE, 8); + spinbtn_maxentry_adj = gtk_adjustment_new (0, 0, 500, 1, 10, 10); + spinbtn_maxentry = gtk_spin_button_new(GTK_ADJUSTMENT (spinbtn_maxentry_adj), 1, 0); + gtk_box_pack_start (GTK_BOX (hbox_spin), spinbtn_maxentry, FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_maxentry, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_maxentry), TRUE); + gtk_table_attach(GTK_TABLE(table), hbox_spin, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + g_signal_connect(G_OBJECT(reset_btn), "clicked", + G_CALLBACK(edit_ldap_search_reset), NULL); + + gtk_widget_show_all(vbox); + + ldapedit.entry_criteria = entry_criteria; + ldapedit.entry_bindDN = entry_bindDN; + ldapedit.entry_bindPW = entry_bindPW; + ldapedit.spinbtn_timeout = spinbtn_timeout; + ldapedit.spinbtn_maxentry = spinbtn_maxentry; +} + +static void addressbook_edit_ldap_create( gboolean *cancelled ) { + gint page = 0; + addressbook_edit_ldap_dialog_create( cancelled ); + addressbook_edit_ldap_page_basic( page++, _( "Basic" ) ); + addressbook_edit_ldap_page_extended( page++, _( "Extended" ) ); + gtk_widget_show_all( ldapedit.window ); +} + +void edit_ldap_set_optmenu( GtkOptionMenu *optmenu, const gint value ) { + GList *cur; + GtkWidget *menu; + GtkWidget *menuitem; + gint menuVal; + gint n = 0; + + g_return_if_fail(optmenu != NULL); + + menu = gtk_option_menu_get_menu(optmenu); + for( cur = GTK_MENU_SHELL(menu)->children; cur != NULL; cur = cur->next ) { + menuitem = GTK_WIDGET(cur->data); + menuVal = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "user_data")); + if( menuVal == value ) { + gtk_option_menu_set_history(optmenu, n); + return; + } + n++; + } + gtk_option_menu_set_history(optmenu, 0); +} + +gint edit_ldap_get_optmenu( GtkOptionMenu *optmenu ) { + GtkWidget *menu; + GtkWidget *menuitem; + + g_return_val_if_fail(optmenu != NULL, -1); + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "user_data")); +} + +AdapterDSource *addressbook_edit_ldap( AddressIndex *addrIndex, AdapterDSource *ads ) { + static gboolean cancelled; + gchar *sName, *sHost, *sBase, *sBind, *sPass, *sCrit; + gint iPort, iMaxE, iTime; + AddressDataSource *ds = NULL; + SyldapServer *server = NULL; + gboolean fin; + + if (!ldapedit.window) + addressbook_edit_ldap_create(&cancelled); + gtk_notebook_set_current_page( GTK_NOTEBOOK(ldapedit.notebook), 0 ); + gtk_widget_grab_focus(ldapedit.ok_btn); + gtk_widget_grab_focus(ldapedit.entry_name); + gtk_widget_show(ldapedit.window); + manage_window_set_transient(GTK_WINDOW(ldapedit.window)); + + edit_ldap_status_show( "" ); + if( ads ) { + ds = ads->dataSource; + server = ds->rawDataSource; + if (server->name) + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_name), server->name); + if (server->hostName) + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_server), server->hostName); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ), server->port ); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ), server->timeOut ); + if (server->baseDN) + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), server->baseDN); + if (server->searchCriteria) + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), server->searchCriteria); + if (server->bindDN) + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindDN), server->bindDN); + if (server->bindPass) + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindPW), server->bindPass); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ), server->maxEntries ); + gtk_window_set_title( GTK_WINDOW(ldapedit.window), _("Edit LDAP Server")); + } + else { + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_name), ADDRESSBOOK_GUESS_LDAP_NAME ); + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_server), ADDRESSBOOK_GUESS_LDAP_SERVER ); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ), SYLDAP_DFL_PORT ); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ), SYLDAP_DFL_TIMEOUT ); + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_baseDN), ""); + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_criteria), SYLDAP_DFL_CRITERIA ); + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindDN), ""); + gtk_entry_set_text(GTK_ENTRY(ldapedit.entry_bindPW), ""); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ), SYLDAP_MAX_ENTRIES ); + gtk_window_set_title( GTK_WINDOW(ldapedit.window), _("Add New LDAP Server")); + } + + gtk_main(); + gtk_widget_hide(ldapedit.window); + if (cancelled == TRUE) return NULL; + + fin = FALSE; + sName = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_name), 0, -1 ); + sHost = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_server), 0, -1 ); + iPort = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_port ) ); + iTime = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_timeout ) ); + sBase = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_baseDN), 0, -1 ); + sCrit = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_criteria), 0, -1 ); + sBind = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindDN), 0, -1 ); + sPass = gtk_editable_get_chars( GTK_EDITABLE(ldapedit.entry_bindPW), 0, -1 ); + iMaxE = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( ldapedit.spinbtn_maxentry ) ); + + if( *sName == '\0' ) fin = TRUE; + if( *sHost == '\0' ) fin = TRUE; + if( *sBase == '\0' ) fin = TRUE; + + if( ! fin ) { + if( ! ads ) { + server = syldap_create(); + ds = addrindex_index_add_datasource( addrIndex, ADDR_IF_LDAP, server ); + ads = addressbook_create_ds_adapter( ds, ADDR_LDAP, NULL ); + } + addressbook_ads_set_name( ads, sName ); + syldap_set_name( server, sName ); + syldap_set_host( server, sHost ); + syldap_set_port( server, iPort ); + syldap_set_base_dn( server, sBase ); + syldap_set_bind_dn( server, sBind ); + syldap_set_bind_password( server, sPass ); + syldap_set_search_criteria( server, sCrit ); + syldap_set_max_entries( server, iMaxE ); + syldap_set_timeout( server, iTime ); + } + g_free( sName ); + g_free( sHost ); + g_free( sBase ); + g_free( sBind ); + g_free( sPass ); + g_free( sCrit ); + + return ads; +} + +#endif /* USE_LDAP */ + +/* +* End of Source. +*/ diff --git a/src/editldap.h b/src/editldap.h new file mode 100644 index 00000000..d1bab628 --- /dev/null +++ b/src/editldap.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __EDITLDAP_H__ +#define __EDITLDAP_H__ + +#ifdef USE_LDAP + +AdapterDSource *addressbook_edit_ldap( AddressIndex *addrIndex, AdapterDSource *ads ); + +#endif /* USE_LDAP */ + +#endif /* __EDITLDAP_H__ */ diff --git a/src/editldap_basedn.c b/src/editldap_basedn.c new file mode 100644 index 00000000..54bca814 --- /dev/null +++ b/src/editldap_basedn.c @@ -0,0 +1,332 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * LDAP Base DN selection dialog. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_LDAP + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "prefs_common.h" +#include "syldap.h" +#include "mgutils.h" +#include "gtkutils.h" +#include "manage_window.h" + +static struct _LDAPEdit_basedn { + GtkWidget *window; + GtkWidget *host_label; + GtkWidget *port_label; + GtkWidget *basedn_entry; + GtkWidget *basedn_list; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; +} ldapedit_basedn; + +static gboolean ldapedit_basedn_cancelled; +static gboolean ldapedit_basedn_bad_server; + +/* +* Edit functions. +*/ +static void edit_ldap_bdn_status_show( gchar *msg ) { + if( ldapedit_basedn.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(ldapedit_basedn.statusbar), ldapedit_basedn.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(ldapedit_basedn.statusbar), ldapedit_basedn.status_cid, msg ); + } + } +} + +static gint edit_ldap_bdn_delete_event( GtkWidget *widget, GdkEventAny *event, gboolean *cancelled ) { + ldapedit_basedn_cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_ldap_bdn_key_pressed( GtkWidget *widget, GdkEventKey *event, gboolean *cancelled ) { + if (event && event->keyval == GDK_Escape) { + ldapedit_basedn_cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void edit_ldap_bdn_ok( GtkWidget *widget, gboolean *cancelled ) { + ldapedit_basedn_cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_ldap_bdn_cancel( GtkWidget *widget, gboolean *cancelled ) { + ldapedit_basedn_cancelled = TRUE; + gtk_main_quit(); +} + +static void edit_ldap_bdn_list_select( GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data ) { + gchar *text = NULL; + + if( gtk_clist_get_text( clist, row, 0, &text ) ) { + if( text ) { + gtk_entry_set_text(GTK_ENTRY(ldapedit_basedn.basedn_entry), text ); + } + } +} + +static gboolean edit_ldap_bdn_list_button( GtkCList *clist, GdkEventButton *event, gpointer data ) { + if( ! event ) return FALSE; + if( event->button == 1 ) { + if( event->type == GDK_2BUTTON_PRESS ) { + ldapedit_basedn_cancelled = FALSE; + gtk_main_quit(); + } + } + return FALSE; +} + +static void edit_ldap_bdn_create(void) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *host_label; + GtkWidget *port_label; + GtkWidget *basedn_list; + GtkWidget *vlbox; + GtkWidget *lwindow; + GtkWidget *basedn_entry; + GtkWidget *hbbox; + GtkWidget *hsep; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *hsbox; + GtkWidget *statusbar; + gint top; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 300, 270); + gtk_container_set_border_width(GTK_CONTAINER(window), 0); + gtk_window_set_title(GTK_WINDOW(window), _("Edit LDAP - Select Search Base")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_ldap_bdn_delete_event), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_ldap_bdn_key_pressed), NULL); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_container_set_border_width( GTK_CONTAINER(vbox), 0 ); + + table = gtk_table_new(3, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + /* First row */ + top = 0; + label = gtk_label_new(_("Hostname")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + host_label = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), host_label, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(host_label), 0, 0.5); + + /* Second row */ + top = 1; + label = gtk_label_new(_("Port")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + + port_label = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), port_label, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(port_label), 0, 0.5); + + /* Third row */ + top = 2; + label = gtk_label_new(_("Search Base")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + basedn_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), basedn_entry, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Basedn list */ + vlbox = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox), vlbox, TRUE, TRUE, 0); + gtk_container_set_border_width( GTK_CONTAINER(vlbox), 8 ); + + lwindow = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(lwindow), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(vlbox), lwindow, TRUE, TRUE, 0); + + basedn_list = gtk_clist_new(1); + gtk_container_add(GTK_CONTAINER(lwindow), basedn_list); + gtk_clist_column_titles_show( GTK_CLIST(basedn_list) ); + gtk_clist_set_column_title( GTK_CLIST(basedn_list), 0, _( "Available Search Base(s)" ) ); + gtk_clist_set_selection_mode(GTK_CLIST(basedn_list), GTK_SELECTION_BROWSE); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(hbbox), 0 ); + gtk_widget_grab_default(ok_btn); + + hsep = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(vbox), hsep, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_ldap_bdn_ok), NULL); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_ldap_bdn_cancel), NULL); + g_signal_connect(G_OBJECT(basedn_list), "select_row", + G_CALLBACK(edit_ldap_bdn_list_select), NULL); + g_signal_connect(G_OBJECT(basedn_list), "button_press_event", + G_CALLBACK(edit_ldap_bdn_list_button), NULL); + + gtk_widget_show_all(vbox); + + ldapedit_basedn.window = window; + ldapedit_basedn.host_label = host_label; + ldapedit_basedn.port_label = port_label; + ldapedit_basedn.basedn_entry = basedn_entry; + ldapedit_basedn.basedn_list = basedn_list; + ldapedit_basedn.ok_btn = ok_btn; + ldapedit_basedn.cancel_btn = cancel_btn; + ldapedit_basedn.statusbar = statusbar; + ldapedit_basedn.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit LDAP Select Base DN" ); +} + +void edit_ldap_bdn_load_data( const gchar *hostName, const gint iPort, const gint tov, const gchar* bindDN, + const gchar *bindPW ) { + gchar *sHost; + gchar *sMsg = NULL; + gchar sPort[20]; + gboolean flgConn; + gboolean flgDN; + + edit_ldap_bdn_status_show( "" ); + gtk_clist_clear(GTK_CLIST(ldapedit_basedn.basedn_list)); + ldapedit_basedn_bad_server = TRUE; + flgConn = flgDN = FALSE; + sHost = g_strdup( hostName ); + sprintf( sPort, "%d", iPort ); + gtk_label_set_text(GTK_LABEL(ldapedit_basedn.host_label), hostName); + gtk_label_set_text(GTK_LABEL(ldapedit_basedn.port_label), sPort); + if( *sHost != '\0' ) { + /* Test connection to server */ + if( syldap_test_connect_s( sHost, iPort ) ) { + /* Attempt to read base DN */ + GList *baseDN = syldap_read_basedn_s( sHost, iPort, bindDN, bindPW, tov ); + if( baseDN ) { + GList *node = baseDN; + gchar *text[2] = { NULL, NULL }; + + while( node ) { + text[0] = (gchar *)node->data; + gtk_clist_append(GTK_CLIST(ldapedit_basedn.basedn_list), text); + node = g_list_next( node ); + flgDN = TRUE; + } + mgu_free_dlist( baseDN ); + baseDN = node = NULL; + } + ldapedit_basedn_bad_server = FALSE; + flgConn = TRUE; + } + } + g_free( sHost ); + + /* Display appropriate message */ + if( flgConn ) { + if( ! flgDN ) { + sMsg = _( "Could not read Search Base(s) from server - please set manually" ); + } + } + else { + sMsg = _( "Could not connect to server" ); + } + edit_ldap_bdn_status_show( sMsg ); +} + +gchar *edit_ldap_basedn_selection( const gchar *hostName, const gint port, gchar *baseDN, const gint tov, + const gchar* bindDN, const gchar *bindPW ) { + gchar *retVal = NULL; + + ldapedit_basedn_cancelled = FALSE; + if( ! ldapedit_basedn.window ) edit_ldap_bdn_create(); + gtk_widget_grab_focus(ldapedit_basedn.ok_btn); + gtk_widget_show(ldapedit_basedn.window); + manage_window_set_transient(GTK_WINDOW(ldapedit_basedn.window)); + + edit_ldap_bdn_status_show( "" ); + edit_ldap_bdn_load_data( hostName, port, tov, bindDN, bindPW ); + gtk_widget_show(ldapedit_basedn.window); + + gtk_entry_set_text(GTK_ENTRY(ldapedit_basedn.basedn_entry), baseDN); + + gtk_main(); + gtk_widget_hide(ldapedit_basedn.window); + if( ldapedit_basedn_cancelled ) return NULL; + if( ldapedit_basedn_bad_server ) return NULL; + + retVal = gtk_editable_get_chars( GTK_EDITABLE(ldapedit_basedn.basedn_entry), 0, -1 ); + g_strchomp( retVal ); g_strchug( retVal ); + if( *retVal == '\0' ) { + g_free( retVal ); + retVal = NULL; + } + return retVal; +} + +#endif /* USE_LDAP */ + +/* +* End of Source. +*/ + diff --git a/src/editldap_basedn.h b/src/editldap_basedn.h new file mode 100644 index 00000000..a4affd09 --- /dev/null +++ b/src/editldap_basedn.h @@ -0,0 +1,30 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __EDITLDAP_BASEDN_H__ +#define __EDITLDAP_BASEDN_H__ + +#ifdef USE_LDAP + +gchar *edit_ldap_basedn_selection( const gchar *hostName, const gint port, gchar *baseDN, const gint tov, + const gchar* bindDN, const gchar *bindPW ); + +#endif /* USE_LDAP */ + +#endif /* __EDITLDAP_BASEDN_H__ */ diff --git a/src/editvcard.c b/src/editvcard.c new file mode 100644 index 00000000..eaa18800 --- /dev/null +++ b/src/editvcard.c @@ -0,0 +1,329 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit vCard address book data. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "addressbook.h" +#include "prefs_common.h" +#include "addressitem.h" +#include "vcard.h" +#include "mgutils.h" +#include "gtkutils.h" +#include "manage_window.h" + +#define ADDRESSBOOK_GUESS_VCARD "MyGnomeCard" + +static struct _VCardEdit { + GtkWidget *window; + GtkWidget *name_entry; + GtkWidget *file_entry; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *statusbar; + gint status_cid; +} vcardedit; + +static struct _AddressFileSelection vcard_file_selector; + +/* +* Edit functions. +*/ +void edit_vcard_status_show( gchar *msg ) { + if( vcardedit.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(vcardedit.statusbar), vcardedit.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(vcardedit.statusbar), vcardedit.status_cid, msg ); + } + } +} + +static void edit_vcard_ok( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = FALSE; + gtk_main_quit(); +} + +static void edit_vcard_cancel( GtkWidget *widget, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); +} + +static void edit_vcard_file_check( void ) { + gint t; + gchar *sFile; + gchar *sMsg; + + sFile = gtk_editable_get_chars( GTK_EDITABLE(vcardedit.file_entry), 0, -1 ); + t = vcard_test_read_file( sFile ); + g_free( sFile ); + if( t == MGU_SUCCESS ) { + sMsg = ""; + } + else if( t == MGU_BAD_FORMAT ) { + sMsg = _("File does not appear to be vCard format."); + } + else { + sMsg = _("Could not read file."); + } + edit_vcard_status_show( sMsg ); +} + +static void edit_vcard_file_ok( GtkWidget *widget, gpointer data ) { + const gchar *sFile; + AddressFileSelection *afs; + GtkWidget *fileSel; + + afs = ( AddressFileSelection * ) data; + fileSel = afs->fileSelector; + sFile = gtk_file_selection_get_filename( GTK_FILE_SELECTION(fileSel) ); + + afs->cancelled = FALSE; + gtk_entry_set_text( GTK_ENTRY(vcardedit.file_entry), sFile ); + gtk_widget_hide( afs->fileSelector ); + gtk_grab_remove( afs->fileSelector ); + edit_vcard_file_check(); + gtk_widget_grab_focus( vcardedit.file_entry ); +} + +static void edit_vcard_file_cancel( GtkWidget *widget, gpointer data ) { + AddressFileSelection *afs = ( AddressFileSelection * ) data; + afs->cancelled = TRUE; + gtk_widget_hide( afs->fileSelector ); + gtk_grab_remove( afs->fileSelector ); + gtk_widget_grab_focus( vcardedit.file_entry ); +} + +static void edit_vcard_file_select_create( AddressFileSelection *afs ) { + GtkWidget *fileSelector; + + fileSelector = gtk_file_selection_new( _("Select vCard File") ); + gtk_file_selection_hide_fileop_buttons( GTK_FILE_SELECTION(fileSelector) ); + g_signal_connect( G_OBJECT (GTK_FILE_SELECTION(fileSelector)->ok_button), + "clicked", G_CALLBACK (edit_vcard_file_ok), ( gpointer ) afs ); + g_signal_connect( G_OBJECT (GTK_FILE_SELECTION(fileSelector)->cancel_button), + "clicked", G_CALLBACK (edit_vcard_file_cancel), ( gpointer ) afs ); + afs->fileSelector = fileSelector; + afs->cancelled = TRUE; +} + +static void edit_vcard_file_select( void ) { + gchar *sFile; + + if (! vcard_file_selector.fileSelector ) + edit_vcard_file_select_create( & vcard_file_selector ); + + sFile = gtk_editable_get_chars( GTK_EDITABLE(vcardedit.file_entry), 0, -1 ); + gtk_file_selection_set_filename( GTK_FILE_SELECTION( vcard_file_selector.fileSelector ), sFile ); + g_free( sFile ); + gtk_widget_show( vcard_file_selector.fileSelector ); + gtk_grab_add( vcard_file_selector.fileSelector ); +} + +static gint edit_vcard_delete_event( GtkWidget *widget, GdkEventAny *event, gboolean *cancelled ) { + *cancelled = TRUE; + gtk_main_quit(); + return TRUE; +} + +static gboolean edit_vcard_key_pressed( GtkWidget *widget, GdkEventKey *event, gboolean *cancelled ) { + if (event && event->keyval == GDK_Escape) { + *cancelled = TRUE; + gtk_main_quit(); + } + return FALSE; +} + +static void addressbook_edit_vcard_create( gboolean *cancelled ) { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *name_entry; + GtkWidget *file_entry; + GtkWidget *hbbox; + GtkWidget *hsep; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *check_btn; + GtkWidget *file_btn; + GtkWidget *statusbar; + GtkWidget *hsbox; + gint top; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 450, -1); + gtk_container_set_border_width( GTK_CONTAINER(window), 0 ); + gtk_window_set_title(GTK_WINDOW(window), _("Edit vCard Entry")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_vcard_delete_event), + cancelled); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_vcard_key_pressed), + cancelled); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_container_set_border_width( GTK_CONTAINER(vbox), 0 ); + + table = gtk_table_new(2, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8 ); + + /* First row */ + top = 0; + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + name_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), name_entry, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + check_btn = gtk_button_new_with_label( _(" Check File ")); + gtk_table_attach(GTK_TABLE(table), check_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Second row */ + top = 1; + label = gtk_label_new(_("File")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + file_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), file_entry, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + file_btn = gtk_button_new_with_label( _(" ... ")); + gtk_table_attach(GTK_TABLE(table), file_btn, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(hbbox), 0 ); + gtk_widget_grab_default(ok_btn); + + hsep = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(vbox), hsep, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_vcard_ok), cancelled); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_vcard_cancel), cancelled); + g_signal_connect(G_OBJECT(file_btn), "clicked", + G_CALLBACK(edit_vcard_file_select), NULL); + g_signal_connect(G_OBJECT(check_btn), "clicked", + G_CALLBACK(edit_vcard_file_check), NULL); + + gtk_widget_show_all(vbox); + + vcardedit.window = window; + vcardedit.name_entry = name_entry; + vcardedit.file_entry = file_entry; + vcardedit.ok_btn = ok_btn; + vcardedit.cancel_btn = cancel_btn; + vcardedit.statusbar = statusbar; + vcardedit.status_cid = gtk_statusbar_get_context_id( GTK_STATUSBAR(statusbar), "Edit vCard Dialog" ); +} + +AdapterDSource *addressbook_edit_vcard( AddressIndex *addrIndex, AdapterDSource *ads ) { + static gboolean cancelled; + gchar *sName; + gchar *sFile; + AddressDataSource *ds = NULL; + VCardFile *vcf = NULL; + gboolean fin; + + if( ! vcardedit.window ) + addressbook_edit_vcard_create(&cancelled); + gtk_widget_grab_focus(vcardedit.ok_btn); + gtk_widget_grab_focus(vcardedit.name_entry); + gtk_widget_show(vcardedit.window); + manage_window_set_transient(GTK_WINDOW(vcardedit.window)); + + edit_vcard_status_show( "" ); + if( ads ) { + ds = ads->dataSource; + vcf = ds->rawDataSource; + if (vcf->name) + gtk_entry_set_text(GTK_ENTRY(vcardedit.name_entry), vcf->name); + if (vcf->path) + gtk_entry_set_text(GTK_ENTRY(vcardedit.file_entry), vcf->path); + gtk_window_set_title( GTK_WINDOW(vcardedit.window), _("Edit vCard Entry")); + } + else { + gtk_entry_set_text(GTK_ENTRY(vcardedit.name_entry), ADDRESSBOOK_GUESS_VCARD ); + gtk_entry_set_text(GTK_ENTRY(vcardedit.file_entry), vcard_find_gnomecard() ); + gtk_window_set_title( GTK_WINDOW(vcardedit.window), _("Add New vCard Entry")); + } + + gtk_main(); + gtk_widget_hide(vcardedit.window); + if (cancelled == TRUE) return NULL; + + fin = FALSE; + sName = gtk_editable_get_chars( GTK_EDITABLE(vcardedit.name_entry), 0, -1 ); + sFile = gtk_editable_get_chars( GTK_EDITABLE(vcardedit.file_entry), 0, -1 ); + if( *sName == '\0' ) fin = TRUE; + if( *sFile == '\0' ) fin = TRUE; + + if( ! fin ) { + if( ! ads ) { + vcf = vcard_create(); + ds = addrindex_index_add_datasource( addrIndex, ADDR_IF_VCARD, vcf ); + ads = addressbook_create_ds_adapter( ds, ADDR_VCARD, NULL ); + } + addressbook_ads_set_name( ads, sName ); + vcard_set_name( vcf, sName ); + vcard_set_file( vcf, sFile ); + } + g_free( sName ); + g_free( sFile ); + + return ads; +} + +/* +* End of Source. +*/ + diff --git a/src/editvcard.h b/src/editvcard.h new file mode 100644 index 00000000..1dd0979b --- /dev/null +++ b/src/editvcard.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit vCard address book data. + */ + +#ifndef __EDITVCARD_H__ +#define __EDITVCARD_H__ + +AdapterDSource *addressbook_edit_vcard( AddressIndex *addrIndex, AdapterDSource *ads ); + +#endif /* __EDITVCARD_H__ */ diff --git a/src/export.c b/src/export.c new file mode 100644 index 00000000..e78ddab9 --- /dev/null +++ b/src/export.c @@ -0,0 +1,263 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "inc.h" +#include "mbox.h" +#include "filesel.h" +#include "foldersel.h" +#include "gtkutils.h" +#include "manage_window.h" +#include "folder.h" +#include "utils.h" + +static GtkWidget *window; +static GtkWidget *src_entry; +static GtkWidget *file_entry; +static GtkWidget *src_button; +static GtkWidget *file_button; +static GtkWidget *ok_button; +static GtkWidget *cancel_button; +static gboolean export_ack; + +static void export_create(void); +static void export_ok_cb(GtkWidget *widget, gpointer data); +static void export_cancel_cb(GtkWidget *widget, gpointer data); +static void export_srcsel_cb(GtkWidget *widget, gpointer data); +static void export_filesel_cb(GtkWidget *widget, gpointer data); +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data); +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data); + +gint export_mbox(FolderItem *default_src) +{ + gint ok = 0; + gchar *src_id = NULL; + + if (!window) + export_create(); + else + gtk_widget_show(window); + + change_dir(startup_dir); + + if (default_src && default_src->path) + src_id = folder_item_get_identifier(default_src); + + if (src_id) { + gtk_entry_set_text(GTK_ENTRY(src_entry), src_id); + g_free(src_id); + } else + gtk_entry_set_text(GTK_ENTRY(src_entry), ""); + gtk_entry_set_text(GTK_ENTRY(file_entry), ""); + gtk_widget_grab_focus(file_entry); + + manage_window_set_transient(GTK_WINDOW(window)); + + gtk_main(); + + if (export_ack) { + const gchar *srcdir, *utf8mbox; + FolderItem *src; + + srcdir = gtk_entry_get_text(GTK_ENTRY(src_entry)); + utf8mbox = gtk_entry_get_text(GTK_ENTRY(file_entry)); + + if (utf8mbox && *utf8mbox) { + gchar *mbox; + + mbox = g_filename_from_utf8 + (utf8mbox, -1, NULL, NULL, NULL); + if (!mbox) { + g_warning("faild to convert character set\n"); + mbox = g_strdup(utf8mbox); + } + + src = folder_find_item_from_identifier(srcdir); + if (!src) + g_warning("Can't find the folder.\n"); + else + ok = export_to_mbox(src, mbox); + + g_free(mbox); + } + } + + gtk_widget_hide(window); + + return ok; +} + +static void export_create(void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *desc_label; + GtkWidget *table; + GtkWidget *file_label; + GtkWidget *src_label; + GtkWidget *confirm_area; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Export")); + gtk_container_set_border_width(GTK_CONTAINER(window), 5); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(delete_event), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + vbox = gtk_vbox_new(FALSE, 4); + gtk_container_add(GTK_CONTAINER(window), vbox); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 4); + + desc_label = gtk_label_new + (_("Specify target folder and mbox file.")); + gtk_box_pack_start(GTK_BOX(hbox), desc_label, FALSE, FALSE, 0); + + table = gtk_table_new(2, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(table), 8); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + gtk_widget_set_size_request(table, 420, -1); + + src_label = gtk_label_new(_("Source dir:")); + gtk_table_attach(GTK_TABLE(table), src_label, 0, 1, 0, 1, + GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); + gtk_misc_set_alignment(GTK_MISC(src_label), 1, 0.5); + + file_label = gtk_label_new(_("Exporting file:")); + gtk_table_attach(GTK_TABLE(table), file_label, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); + gtk_misc_set_alignment(GTK_MISC(file_label), 1, 0.5); + + src_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), src_entry, 1, 2, 0, 1, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + file_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), file_entry, 1, 2, 1, 2, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + src_button = gtk_button_new_with_label(_(" Select... ")); + gtk_table_attach(GTK_TABLE(table), src_button, 2, 3, 0, 1, + 0, 0, 0, 0); + g_signal_connect(G_OBJECT(src_button), "clicked", + G_CALLBACK(export_srcsel_cb), NULL); + + file_button = gtk_button_new_with_label(_(" Select... ")); + gtk_table_attach(GTK_TABLE(table), file_button, 2, 3, 1, 2, + 0, 0, 0, 0); + g_signal_connect(G_OBJECT(file_button), "clicked", + G_CALLBACK(export_filesel_cb), NULL); + + gtkut_button_set_create(&confirm_area, + &ok_button, _("OK"), + &cancel_button, _("Cancel"), + NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_button); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(export_ok_cb), NULL); + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(export_cancel_cb), NULL); + + gtk_widget_show_all(window); +} + +static void export_ok_cb(GtkWidget *widget, gpointer data) +{ + export_ack = TRUE; + if (gtk_main_level() > 1) + gtk_main_quit(); +} + +static void export_cancel_cb(GtkWidget *widget, gpointer data) +{ + export_ack = FALSE; + if (gtk_main_level() > 1) + gtk_main_quit(); +} + +static void export_filesel_cb(GtkWidget *widget, gpointer data) +{ + const gchar *filename; + gchar *utf8_filename; + + filename = filesel_select_file(_("Select exporting file"), NULL); + if (!filename) return; + + utf8_filename = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + if (!utf8_filename) { + g_warning("export_filesel_cb(): faild to convert character set.\n"); + utf8_filename = g_strdup(filename); + } + gtk_entry_set_text(GTK_ENTRY(file_entry), utf8_filename); + g_free(utf8_filename); +} + +static void export_srcsel_cb(GtkWidget *widget, gpointer data) +{ + FolderItem *src; + + src = foldersel_folder_sel(NULL, FOLDER_SEL_ALL, NULL); + if (src && src->path) + gtk_entry_set_text(GTK_ENTRY(src_entry), src->path); +} + +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + export_cancel_cb(NULL, NULL); + return TRUE; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) + export_cancel_cb(NULL, NULL); + return FALSE; +} diff --git a/src/export.h b/src/export.h new file mode 100644 index 00000000..34d73a06 --- /dev/null +++ b/src/export.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __EXPORT_H__ +#define __EXPORT_H__ + +#include + +#include "folder.h" + +gint export_mbox(FolderItem *default_src); + +#endif /* __EXPORT_H__ */ diff --git a/src/filesel.c b/src/filesel.c new file mode 100644 index 00000000..57bc5896 --- /dev/null +++ b/src/filesel.c @@ -0,0 +1,145 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "filesel.h" +#include "manage_window.h" +#include "gtkutils.h" + +static GtkWidget *filesel; +static gboolean filesel_ack; +static gboolean filesel_fin; + +static void filesel_create(const gchar *title); +static void filesel_ok_cb(GtkWidget *widget, gpointer data); +static void filesel_cancel_cb(GtkWidget *widget, gpointer data); +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data); +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data); + +gchar *filesel_select_file(const gchar *title, const gchar *file) +{ + static gchar *filename = NULL; + static gchar *cwd = NULL; + + filesel_create(title); + + manage_window_set_transient(GTK_WINDOW(filesel)); + + if (filename) { + g_free(filename); + filename = NULL; + } + + if (!cwd) + cwd = g_strconcat(startup_dir, G_DIR_SEPARATOR_S, NULL); + + gtk_file_selection_set_filename(GTK_FILE_SELECTION(filesel), cwd); + + if (file) { + gtk_file_selection_set_filename(GTK_FILE_SELECTION(filesel), + file); + gtk_editable_select_region + (GTK_EDITABLE(GTK_FILE_SELECTION(filesel)->selection_entry), + 0, -1); + } + + gtk_widget_show(filesel); + + filesel_ack = filesel_fin = FALSE; + + while (filesel_fin == FALSE) + gtk_main_iteration(); + + if (filesel_ack) { + const gchar *str; + + str = gtk_file_selection_get_filename + (GTK_FILE_SELECTION(filesel)); + if (str && str[0] != '\0') { + gchar *dir; + + filename = g_strdup(str); + dir = g_dirname(str); + g_free(cwd); + cwd = g_strconcat(dir, G_DIR_SEPARATOR_S, NULL); + g_free(dir); + } + } + + manage_window_focus_out(filesel, NULL, NULL); + gtk_widget_destroy(filesel); + GTK_EVENTS_FLUSH(); + + return filename; +} + +static void filesel_create(const gchar *title) +{ + filesel = gtk_file_selection_new(title); + gtk_window_set_position(GTK_WINDOW(filesel), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(filesel), TRUE); + gtk_window_set_wmclass + (GTK_WINDOW(filesel), "file_selection", "Sylpheed"); + + g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), + "clicked", G_CALLBACK(filesel_ok_cb), + NULL); + g_signal_connect + (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), + "clicked", G_CALLBACK(filesel_cancel_cb), + NULL); + g_signal_connect(G_OBJECT(filesel), "delete_event", + G_CALLBACK(delete_event), NULL); + g_signal_connect(G_OBJECT(filesel), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(filesel); +} + +static void filesel_ok_cb(GtkWidget *widget, gpointer data) +{ + filesel_ack = TRUE; + filesel_fin = TRUE; +} + +static void filesel_cancel_cb(GtkWidget *widget, gpointer data) +{ + filesel_ack = FALSE; + filesel_fin = TRUE; +} + +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + filesel_cancel_cb(NULL, NULL); + return TRUE; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) + filesel_cancel_cb(NULL, NULL); + return FALSE; +} diff --git a/src/filesel.h b/src/filesel.h new file mode 100644 index 00000000..e4018585 --- /dev/null +++ b/src/filesel.h @@ -0,0 +1,27 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __FILESEL_H__ +#define __FILESEL_H__ + +#include + +gchar *filesel_select_file(const gchar *title, const gchar *file); + +#endif /* __FILESEL_H__ */ diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 00000000..1fa17a70 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,1218 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "procheader.h" +#include "filter.h" +#include "folder.h" +#include "utils.h" +#include "xml.h" +#include "prefs.h" +#include "prefs_account.h" + +typedef enum +{ + FLT_O_CONTAIN = 1 << 0, + FLT_O_CASE_SENS = 1 << 1, + FLT_O_REGEX = 1 << 2 +} FilterOldFlag; + +static gboolean filter_match_cond (FilterCond *cond, + MsgInfo *msginfo, + GSList *hlist, + FilterInfo *fltinfo); +static gboolean filter_match_header_cond(FilterCond *cond, + GSList *hlist); + +static void filter_cond_free (FilterCond *cond); +static void filter_action_free (FilterAction *action); + + +gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo) +{ + MsgInfo *msginfo; + gint ret = 0; + + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + if (!fltlist) return 0; + + msginfo = procheader_parse_file(file, fltinfo->flags, FALSE); + if (!msginfo) return 0; + msginfo->file_path = g_strdup(file); + + ret = filter_apply_msginfo(fltlist, msginfo, fltinfo); + + procmsg_msginfo_free(msginfo); + + return ret; +} + +gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo, + FilterInfo *fltinfo) +{ + gchar *file; + GSList *hlist, *cur; + FilterRule *rule; + gint ret = 0; + + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + if (!fltlist) return 0; + + file = procmsg_get_message_file(msginfo); + hlist = procheader_get_header_list_from_file(file); + if (!hlist) { + g_free(file); + return 0; + } + + for (cur = fltlist; cur != NULL; cur = cur->next) { + rule = (FilterRule *)cur->data; + if (!rule->enabled) continue; + if (filter_match_rule(rule, msginfo, hlist, fltinfo)) { + ret = filter_action_exec(rule, msginfo, file, fltinfo); + if (ret < 0) { + g_warning("filter_action_exec() returned error\n"); + break; + } + if (fltinfo->drop_done == TRUE || + fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE) + break; + } + } + + procheader_header_list_destroy(hlist); + g_free(file); + + return ret; +} + +gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file, + FilterInfo *fltinfo) +{ + FolderItem *dest_folder = NULL; + FilterAction *action; + GSList *cur; + gchar *cmdline; + gboolean copy_to_self = FALSE; + + g_return_val_if_fail(rule != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(fltinfo != NULL, -1); + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_MARK: + debug_print("filter_action_exec(): mark\n"); + MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_COLOR_LABEL: + debug_print("filter_action_exec(): color label: %d\n", + action->int_value); + MSG_UNSET_PERM_FLAGS(fltinfo->flags, + MSG_CLABEL_FLAG_MASK); + MSG_SET_COLORLABEL_VALUE(fltinfo->flags, + action->int_value); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_MARK_READ: + debug_print("filter_action_exec(): mark as read\n"); + if (msginfo->folder) { + if (MSG_IS_NEW(fltinfo->flags)) + msginfo->folder->new--; + if (MSG_IS_UNREAD(fltinfo->flags)) + msginfo->folder->unread--; + } + MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_EXEC: + cmdline = g_strconcat(action->str_value, " ", file, + NULL); + execute_command_line(cmdline, FALSE); + g_free(cmdline); + fltinfo->actions[action->type] = TRUE; + break; + case FLT_ACTION_EXEC_ASYNC: + cmdline = g_strconcat(action->str_value, " ", file, + NULL); + execute_command_line(cmdline, TRUE); + g_free(cmdline); + fltinfo->actions[action->type] = TRUE; + break; + default: + break; + } + } + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_MOVE: + case FLT_ACTION_COPY: + dest_folder = folder_find_item_from_identifier + (action->str_value); + if (!dest_folder) { + g_warning("dest folder '%s' not found\n", + action->str_value); + return -1; + } + debug_print("filter_action_exec(): %s: dest_folder = %s\n", + action->type == FLT_ACTION_COPY ? + "copy" : "move", action->str_value); + + if (msginfo->folder) { + gint val; + + /* local filtering */ + if (msginfo->folder == dest_folder) + copy_to_self = TRUE; + else { + if (action->type == FLT_ACTION_COPY) { + val = folder_item_copy_msg + (dest_folder, msginfo); + if (val == -1) + return -1; + } + fltinfo->actions[action->type] = TRUE; + } + } else { + if (folder_item_add_msg(dest_folder, file, + &fltinfo->flags, + FALSE) < 0) + return -1; + fltinfo->actions[action->type] = TRUE; + } + + fltinfo->dest_list = g_slist_append(fltinfo->dest_list, + dest_folder); + if (action->type == FLT_ACTION_MOVE) { + fltinfo->move_dest = dest_folder; + fltinfo->drop_done = TRUE; + } + break; + default: + break; + } + } + + if (fltinfo->drop_done == TRUE) + return 0; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + switch (action->type) { + case FLT_ACTION_NOT_RECEIVE: + debug_print("filter_action_exec(): don't receive\n"); + fltinfo->drop_done = TRUE; + fltinfo->actions[action->type] = TRUE; + return 0; + case FLT_ACTION_DELETE: + debug_print("filter_action_exec(): delete\n"); + if (msginfo->folder) { + /* local filtering */ + if (copy_to_self == FALSE) + fltinfo->actions[action->type] = TRUE; + } else + fltinfo->actions[action->type] = TRUE; + + fltinfo->drop_done = TRUE; + return 0; + case FLT_ACTION_STOP_EVAL: + debug_print("filter_action_exec(): stop evaluation\n"); + fltinfo->actions[action->type] = TRUE; + return 0; + default: + break; + } + } + + return 0; +} + +static gboolean strmatch_regex(const gchar *haystack, const gchar *needle) +{ + gint ret = 0; + regex_t preg; + regmatch_t pmatch[1]; + + ret = regcomp(&preg, needle, REG_EXTENDED|REG_ICASE); + if (ret != 0) return FALSE; + + ret = regexec(&preg, haystack, 1, pmatch, 0); + regfree(&preg); + + if (ret == REG_NOMATCH) return FALSE; + + if (pmatch[0].rm_so != -1) + return TRUE; + else + return FALSE; +} + +gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist, + FilterInfo *fltinfo) +{ + FilterCond *cond; + GSList *cur; + gboolean matched; + + g_return_val_if_fail(rule->cond_list != NULL, FALSE); + g_return_val_if_fail(rule->action_list != NULL, FALSE); + + switch (rule->timing) { + case FLT_TIMING_ANY: + break; + case FLT_TIMING_ON_RECEIVE: + if (msginfo->folder != NULL) + return FALSE; + break; + case FLT_TIMING_MANUAL: + if (msginfo->folder == NULL) + return FALSE; + break; + default: + break; + } + + if (rule->bool_op == FLT_AND) { + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + matched = filter_match_cond(cond, msginfo, hlist, + fltinfo); + if (matched == FALSE) + return FALSE; + } + + return TRUE; + } else if (rule->bool_op == FLT_OR) { + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + matched = filter_match_cond(cond, msginfo, hlist, + fltinfo); + if (matched == TRUE) + return TRUE; + } + + return FALSE; + } + + return FALSE; +} + +static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo, + GSList *hlist, FilterInfo *fltinfo) +{ + gboolean matched = FALSE; + gchar *file; + gchar *cmdline; + PrefsAccount *cond_ac; + + switch (cond->type) { + case FLT_COND_HEADER: + case FLT_COND_ANY_HEADER: + case FLT_COND_TO_OR_CC: + return filter_match_header_cond(cond, hlist); + case FLT_COND_BODY: + matched = procmime_find_string(msginfo, cond->str_value, + cond->match_func); + break; + case FLT_COND_CMD_TEST: + file = procmsg_get_message_file(msginfo); + cmdline = g_strconcat(cond->str_value, " ", file, NULL); + matched = (execute_command_line(cmdline, FALSE) == 0); + g_free(cmdline); + g_free(file); + break; + case FLT_COND_SIZE_GREATER: + matched = (msginfo->size > cond->int_value * 1024); + break; + case FLT_COND_AGE_GREATER: + matched = (time(NULL) - msginfo->date_t > + cond->int_value * 24 * 60 * 60); + break; + case FLT_COND_ACCOUNT: + cond_ac = account_find_from_id(cond->int_value); + matched = (cond_ac != NULL && cond_ac == fltinfo->account); + break; + default: + g_warning("filter_match_cond(): unknown condition: %d\n", + cond->type); + return FALSE; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) + matched = !matched; + + return matched; +} + +static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist) +{ + gboolean matched = FALSE; + GSList *cur; + Header *header; + + for (cur = hlist; cur != NULL; cur = cur->next) { + header = (Header *)cur->data; + + switch (cond->type) { + case FLT_COND_HEADER: + if (!strcasecmp(header->name, cond->header_name)) { + if (!cond->str_value || + cond->match_func(header->body, + cond->str_value)) + matched = TRUE; + } + break; + case FLT_COND_ANY_HEADER: + if (!cond->str_value || + cond->match_func(header->body, cond->str_value)) + matched = TRUE; + break; + case FLT_COND_TO_OR_CC: + if (!strcasecmp(header->name, "To") || + !strcasecmp(header->name, "Cc")) { + if (!cond->str_value || + cond->match_func(header->body, + cond->str_value)) + matched = TRUE; + } + break; + default: + break; + } + + if (matched == TRUE) + break; + } + + if (FLT_IS_NOT_MATCH(cond->match_flag)) + matched = !matched; + + return matched; +} + +#define RETURN_IF_TAG_NOT_MATCH(tag_name) \ + if (strcmp2(xmlnode->tag->tag, tag_name) != 0) { \ + g_warning("tag name != \"" tag_name "\"\n"); \ + filter_cond_list_free(cond_list); \ + filter_action_list_free(cond_list); \ + return FALSE; \ + } \ + +#define STR_SWITCH(sw) { const gchar *sw_str = sw; +#define STR_CASE_BEGIN(str) if (!strcmp(sw_str, str)) { +#define STR_CASE(str) } else if (!strcmp(sw_str, str)) { +#define STR_CASE_END } } + +static gboolean filter_xml_node_func(GNode *node, gpointer data) +{ + XMLNode *xmlnode; + GList *list; + const gchar *name = NULL; + FilterTiming timing = FLT_TIMING_ANY; + gboolean enabled = TRUE; + FilterRule *rule; + FilterBoolOp bool_op = FLT_OR; + GSList *cond_list = NULL; + GSList *action_list = NULL; + GNode *child, *cond_child, *action_child; + GSList **fltlist = (GSList **)data; + + if (g_node_depth(node) != 2) return FALSE; + g_return_val_if_fail(node->data != NULL, FALSE); + + xmlnode = node->data; + RETURN_IF_TAG_NOT_MATCH("rule"); + + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "timing")) { + if (!strcmp(attr->value, "any")) + timing = FLT_TIMING_ANY; + else if (!strcmp(attr->value, "receive")) + timing = FLT_TIMING_ON_RECEIVE; + else if (!strcmp(attr->value, "manual")) + timing = FLT_TIMING_MANUAL; + } else if (!strcmp(attr->name, "enabled")) { + if (!strcmp(attr->value, "true")) + enabled = TRUE; + else + enabled = FALSE; + } + } + + /* condition list */ + child = node->children; + if (!child) { + g_warning(" must have children\n"); + return FALSE; + } + xmlnode = child->data; + RETURN_IF_TAG_NOT_MATCH("condition-list"); + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (attr && attr->name && attr->value && + !strcmp(attr->name, "bool")) { + if (!strcmp(attr->value, "or")) + bool_op = FLT_OR; + else + bool_op = FLT_AND; + } + } + + for (cond_child = child->children; cond_child != NULL; + cond_child = cond_child->next) { + const gchar *type = NULL; + const gchar *name = NULL; + const gchar *value = NULL; + FilterCond *cond; + FilterCondType cond_type = FLT_COND_HEADER; + FilterMatchType match_type = FLT_CONTAIN; + FilterMatchFlag match_flag = 0; + + xmlnode = cond_child->data; + if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue; + + for (list = xmlnode->tag->attr; list != NULL; list = list->next) { + XMLAttr *attr = (XMLAttr *)list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "type")) + type = attr->value; + else if (!strcmp(attr->name, "name")) + name = attr->value; + } + + if (type) { + filter_rule_match_type_str_to_enum + (type, &match_type, &match_flag); + } + value = xmlnode->element; + + STR_SWITCH(xmlnode->tag->tag) + STR_CASE_BEGIN("match-header") + cond_type = FLT_COND_HEADER; + STR_CASE("match-any-header") + cond_type = FLT_COND_ANY_HEADER; + STR_CASE("match-to-or-cc") + cond_type = FLT_COND_TO_OR_CC; + STR_CASE("match-body-text") + cond_type = FLT_COND_BODY; + STR_CASE("command-test") + cond_type = FLT_COND_CMD_TEST; + STR_CASE("size") + cond_type = FLT_COND_SIZE_GREATER; + STR_CASE("age") + cond_type = FLT_COND_AGE_GREATER; + STR_CASE("account-id") + cond_type = FLT_COND_ACCOUNT; + STR_CASE_END + + cond = filter_cond_new(cond_type, match_type, match_flag, + name, value); + cond_list = g_slist_append(cond_list, cond); + } + + /* action list */ + child = child->next; + if (!child) { + g_warning(" must have multiple children\n"); + filter_cond_list_free(cond_list); + return FALSE; + } + xmlnode = child->data; + RETURN_IF_TAG_NOT_MATCH("action-list"); + + for (action_child = child->children; action_child != NULL; + action_child = action_child->next) { + FilterAction *action; + FilterActionType action_type = FLT_ACTION_NONE; + + xmlnode = action_child->data; + if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue; + + STR_SWITCH(xmlnode->tag->tag) + STR_CASE_BEGIN("move") + action_type = FLT_ACTION_MOVE; + STR_CASE("copy") + action_type = FLT_ACTION_COPY; + STR_CASE("not-receive") + action_type = FLT_ACTION_NOT_RECEIVE; + STR_CASE("delete") + action_type = FLT_ACTION_DELETE; + STR_CASE("exec") + action_type = FLT_ACTION_EXEC; + STR_CASE("exec-async") + action_type = FLT_ACTION_EXEC_ASYNC; + STR_CASE("mark") + action_type = FLT_ACTION_MARK; + STR_CASE("color-label") + action_type = FLT_ACTION_COLOR_LABEL; + STR_CASE("mark-as-read") + action_type = FLT_ACTION_MARK_READ; + STR_CASE("forward") + action_type = FLT_ACTION_FORWARD; + STR_CASE("forward-as-attachment") + action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT; + STR_CASE("redirect") + action_type = FLT_ACTION_REDIRECT; + STR_CASE("stop-eval") + action_type = FLT_ACTION_STOP_EVAL; + STR_CASE_END + + action = filter_action_new(action_type, xmlnode->element); + action_list = g_slist_append(action_list, action); + } + + if (name && cond_list && action_list) { + rule = filter_rule_new(name, bool_op, cond_list, action_list); + rule->timing = timing; + rule->enabled = enabled; + *fltlist = g_slist_prepend(*fltlist, rule); + } + + return FALSE; +} + +#undef RETURN_IF_TAG_NOT_MATCH +#undef STR_SWITCH +#undef STR_CASE_BEGIN +#undef STR_CASE +#undef STR_CASE_END + +GSList *filter_xml_node_to_filter_list(GNode *node) +{ + GSList *fltlist = NULL; + + g_return_val_if_fail(node != NULL, NULL); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2, + filter_xml_node_func, &fltlist); + fltlist = g_slist_reverse(fltlist); + + return fltlist; +} + +#define NODE_NEW(tag, text) \ + node = xml_node_new(xml_tag_new(tag), text) +#define ADD_ATTR(name, value) \ + xml_tag_add_attr(node->tag, xml_attr_new(name, value)) + +void filter_write_config(GSList *fltlist) +{ + gchar *rcpath; + PrefFile *pfile; + GSList *cur; + + debug_print("Writing filter configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST, + NULL); + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning("failed to write filter configuration to file\n"); + g_free(rcpath); + return; + } + + xml_file_put_xml_decl(pfile->fp); + fputs("\n\n", pfile->fp); + + for (cur = fltlist; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + GSList *cur_cond; + GSList *cur_action; + gchar match_type[16]; + + fputs(" fp); + xml_file_put_escape_str(pfile->fp, rule->name); + fprintf(pfile->fp, "\" timing=\"%s\"", + rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" : + rule->timing == FLT_TIMING_MANUAL ? "manual" : "any"); + fprintf(pfile->fp, " enabled=\"%s\">\n", + rule->enabled ? "true" : "false"); + + fprintf(pfile->fp, " \n", + rule->bool_op == FLT_OR ? "or" : "and"); + + for (cur_cond = rule->cond_list; cur_cond != NULL; + cur_cond = cur_cond->next) { + FilterCond *cond = (FilterCond *)cur_cond->data; + XMLNode *node = NULL; + + switch (cond->match_type) { + case FLT_CONTAIN: + strcpy(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-contain" : "contains"); + break; + case FLT_EQUAL: + strcpy(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "is-not" : "is"); + break; + case FLT_REGEX: + strcpy(match_type, + FLT_IS_NOT_MATCH(cond->match_flag) + ? "not-regex" : "regex"); + break; + default: + match_type[0] = '\0'; + break; + } + + switch (cond->type) { + case FLT_COND_HEADER: + NODE_NEW("match-header", cond->str_value); + ADD_ATTR("type", match_type); + ADD_ATTR("name", cond->header_name); + break; + case FLT_COND_ANY_HEADER: + NODE_NEW("match-any-header", cond->str_value); + ADD_ATTR("type", match_type); + break; + case FLT_COND_TO_OR_CC: + NODE_NEW("match-to-or-cc", cond->str_value); + ADD_ATTR("type", match_type); + break; + case FLT_COND_BODY: + NODE_NEW("match-body-text", cond->str_value); + ADD_ATTR("type", match_type); + break; + case FLT_COND_CMD_TEST: + NODE_NEW("command-test", cond->str_value); + break; + case FLT_COND_SIZE_GREATER: + NODE_NEW("size", itos(cond->int_value)); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "lt" : "gt"); + break; + case FLT_COND_AGE_GREATER: + NODE_NEW("age", itos(cond->int_value)); + ADD_ATTR("type", + FLT_IS_NOT_MATCH(cond->match_flag) + ? "lt" : "gt"); + break; + case FLT_COND_ACCOUNT: + NODE_NEW("account-id", itos(cond->int_value)); + break; + default: + break; + } + + if (node) { + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + } + + fputs(" \n", pfile->fp); + + fputs(" \n", pfile->fp); + + for (cur_action = rule->action_list; cur_action != NULL; + cur_action = cur_action->next) { + FilterAction *action = (FilterAction *)cur_action->data; + XMLNode *node = NULL; + + switch (action->type) { + case FLT_ACTION_MOVE: + NODE_NEW("move", action->str_value); + break; + case FLT_ACTION_COPY: + NODE_NEW("copy", action->str_value); + break; + case FLT_ACTION_NOT_RECEIVE: + NODE_NEW("not-receive", NULL); + break; + case FLT_ACTION_DELETE: + NODE_NEW("delete", NULL); + break; + case FLT_ACTION_EXEC: + NODE_NEW("exec", action->str_value); + break; + case FLT_ACTION_EXEC_ASYNC: + NODE_NEW("exec-async", action->str_value); + break; + case FLT_ACTION_MARK: + NODE_NEW("mark", NULL); + break; + case FLT_ACTION_COLOR_LABEL: + NODE_NEW("color-label", action->str_value); + break; + case FLT_ACTION_MARK_READ: + NODE_NEW("mark-as-read", NULL); + break; + case FLT_ACTION_FORWARD: + NODE_NEW("forward", action->str_value); + break; + case FLT_ACTION_FORWARD_AS_ATTACHMENT: + NODE_NEW("forward-as-attachment", + action->str_value); + break; + case FLT_ACTION_REDIRECT: + NODE_NEW("redirect", action->str_value); + break; + case FLT_ACTION_STOP_EVAL: + NODE_NEW("stop-eval", NULL); + break; + default: + break; + } + + if (node) { + fputs(" ", pfile->fp); + xml_file_put_node(pfile->fp, node); + xml_free_node(node); + } + } + + fputs(" \n", pfile->fp); + + fputs(" \n", pfile->fp); + } + + fputs("\n", pfile->fp); + + g_free(rcpath); + + if (prefs_file_close(pfile) < 0) { + g_warning(_("failed to write configuration to file\n")); + return; + } +} + +#undef NODE_NEW +#undef ADD_ATTR + +gchar *filter_get_str(FilterRule *rule) +{ + gchar *str; + FilterCond *cond1, *cond2; + FilterAction *action = NULL; + GSList *cur; + gint flag1 = 0, flag2 = 0; + + cond1 = (FilterCond *)rule->cond_list->data; + if (rule->cond_list->next) + cond2 = (FilterCond *)rule->cond_list->next->data; + else + cond2 = NULL; + + /* new -> old flag conversion */ + switch (cond1->match_type) { + case FLT_CONTAIN: + case FLT_EQUAL: + flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN; + if (FLT_IS_CASE_SENS(cond1->match_flag)) + flag1 |= FLT_O_CASE_SENS; + break; + case FLT_REGEX: + flag1 = FLT_O_REGEX; break; + default: + break; + } + if (cond2) { + switch (cond2->match_type) { + case FLT_CONTAIN: + case FLT_EQUAL: + flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN; + if (FLT_IS_CASE_SENS(cond2->match_flag)) + flag2 |= FLT_O_CASE_SENS; + break; + case FLT_REGEX: + flag2 = FLT_O_REGEX; break; + default: + break; + } + } else + flag2 = FLT_O_CONTAIN; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + if (action->type == FLT_ACTION_MOVE || + action->type == FLT_ACTION_NOT_RECEIVE || + action->type == FLT_ACTION_DELETE) + break; + } + + str = g_strdup_printf + ("%s:%s:%c:%s:%s:%s:%d:%d:%c", + cond1->header_name, cond1->str_value ? cond1->str_value : "", + (cond2 && cond2->header_name) ? + (rule->bool_op == FLT_AND ? '&' : '|') : ' ', + (cond2 && cond2->header_name) ? cond2->header_name : "", + (cond2 && cond2->str_value) ? cond2->str_value : "", + (action && action->str_value) ? action->str_value : "", + flag1, flag2, + (action && action->type == FLT_ACTION_MOVE) ? 'm' : + (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' : + (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' '); + + return str; +} + +#define PARSE_ONE_PARAM(p, srcp) \ +{ \ + p = strchr(srcp, '\t'); \ + if (!p) return NULL; \ + else \ + *p++ = '\0'; \ +} + +FilterRule *filter_read_str(const gchar *str) +{ + FilterRule *rule; + FilterBoolOp bool_op; + gint flag; + FilterCond *cond; + FilterMatchType match_type; + FilterMatchFlag match_flag; + FilterAction *action; + GSList *cond_list = NULL; + GSList *action_list = NULL; + gchar *tmp; + gchar *rule_name; + gchar *name1, *body1, *op, *name2, *body2, *dest; + gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL; + + Xstrdup_a(tmp, str, return NULL); + + name1 = tmp; + PARSE_ONE_PARAM(body1, name1); + PARSE_ONE_PARAM(op, body1); + PARSE_ONE_PARAM(name2, op); + PARSE_ONE_PARAM(body2, name2); + PARSE_ONE_PARAM(dest, body2); + if (strchr(dest, '\t')) { + gchar *p; + + PARSE_ONE_PARAM(flag1, dest); + PARSE_ONE_PARAM(flag2, flag1); + PARSE_ONE_PARAM(action1, flag2); + if ((p = strchr(action1, '\t'))) *p = '\0'; + } + + bool_op = (*op == '&') ? FLT_AND : FLT_OR; + + if (*name1) { + if (flag1) + flag = (FilterOldFlag)strtoul(flag1, NULL, 10); + else + flag = FLT_O_CONTAIN; + match_type = FLT_CONTAIN; + match_flag = 0; + if (flag & FLT_O_REGEX) + match_type = FLT_REGEX; + else if (!(flag & FLT_O_CONTAIN)) + match_flag = FLT_NOT_MATCH; + if (flag & FLT_O_CASE_SENS) + match_flag |= FLT_CASE_SENS; + cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag, + name1, body1); + cond_list = g_slist_append(cond_list, cond); + } + if (*name2) { + if (flag2) + flag = (FilterOldFlag)strtoul(flag2, NULL, 10); + else + flag = FLT_O_CONTAIN; + match_type = FLT_CONTAIN; + match_flag = 0; + if (flag & FLT_O_REGEX) + match_type = FLT_REGEX; + else if (!(flag & FLT_O_CONTAIN)) + match_flag = FLT_NOT_MATCH; + if (flag & FLT_O_CASE_SENS) + match_flag |= FLT_CASE_SENS; + cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag, + name2, body2); + cond_list = g_slist_append(cond_list, cond); + } + + action = filter_action_new(FLT_ACTION_MOVE, + *dest ? g_strdup(dest) : NULL); + if (action1) { + switch (*action1) { + case 'm': action->type = FLT_ACTION_MOVE; break; + case 'n': action->type = FLT_ACTION_NOT_RECEIVE; break; + case 'd': action->type = FLT_ACTION_DELETE; break; + default: g_warning("Invalid action: `%c'\n", *action1); + } + } + action_list = g_slist_append(action_list, action); + + Xstrdup_a(rule_name, str, return NULL); + subst_char(rule_name, '\t', ':'); + + rule = filter_rule_new(rule_name, bool_op, cond_list, action_list); + + return rule; +} + +FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op, + GSList *cond_list, GSList *action_list) +{ + FilterRule *rule; + + rule = g_new0(FilterRule, 1); + rule->name = g_strdup(name); + rule->bool_op = bool_op; + rule->cond_list = cond_list; + rule->action_list = action_list; + rule->timing = FLT_TIMING_ANY; + rule->enabled = TRUE; + + return rule; +} + +FilterCond *filter_cond_new(FilterCondType type, + FilterMatchType match_type, + FilterMatchFlag match_flag, + const gchar *header, const gchar *value) +{ + FilterCond *cond; + + cond = g_new0(FilterCond, 1); + cond->type = type; + cond->match_type = match_type; + cond->match_flag = match_flag; + + if (type == FLT_COND_HEADER) + cond->header_name = + (header && *header) ? g_strdup(header) : NULL; + else + cond->header_name = NULL; + + cond->str_value = (value && *value) ? g_strdup(value) : NULL; + if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER) + cond->int_value = atoi(value); + else + cond->int_value = 0; + + if (match_type == FLT_REGEX) + cond->match_func = strmatch_regex; + else if (match_type == FLT_EQUAL) { + if (FLT_IS_CASE_SENS(match_flag)) + cond->match_func = str_find_equal; + else + cond->match_func = str_case_find_equal; + } else { + if (FLT_IS_CASE_SENS(match_flag)) + cond->match_func = str_find; + else + cond->match_func = str_case_find; + } + + return cond; +} + +FilterAction *filter_action_new(FilterActionType type, const gchar *str) +{ + FilterAction *action; + + action = g_new0(FilterAction, 1); + action->type = type; + + action->str_value = (str && *str) ? g_strdup(str) : NULL; + if (type == FLT_ACTION_COLOR_LABEL && str) + action->int_value = atoi(str); + else + action->int_value = 0; + + return action; +} + +FilterInfo *filter_info_new(void) +{ + FilterInfo *fltinfo; + + fltinfo = g_new0(FilterInfo, 1); + fltinfo->dest_list = NULL; + fltinfo->move_dest = NULL; + fltinfo->drop_done = FALSE; + + return fltinfo; +} + +void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path, + const gchar *new_path) +{ + FilterAction *action; + GSList *cur; + gchar *base; + gchar *dest_path; + gint oldpathlen; + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + action = (FilterAction *)cur->data; + + if (action->type != FLT_ACTION_MOVE && + action->type != FLT_ACTION_COPY) + continue; + + oldpathlen = strlen(old_path); + if (action->str_value && + !strncmp(old_path, action->str_value, oldpathlen)) { + base = action->str_value + oldpathlen; + while (*base == G_DIR_SEPARATOR) base++; + if (*base == '\0') + dest_path = g_strdup(new_path); + else + dest_path = g_strconcat(new_path, + G_DIR_SEPARATOR_S, + base, NULL); + g_free(action->str_value); + action->str_value = dest_path; + } + } +} + +void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path) +{ + FilterAction *action; + GSList *cur; + GSList *next; + + for (cur = rule->action_list; cur != NULL; cur = next) { + action = (FilterAction *)cur->data; + next = cur->next; + + if (action->type != FLT_ACTION_MOVE && + action->type != FLT_ACTION_COPY) + continue; + + if (action->str_value && + !strncmp(path, action->str_value, strlen(path))) { + rule->action_list = g_slist_remove + (rule->action_list, action); + filter_action_free(action); + } + } +} + +void filter_rule_match_type_str_to_enum(const gchar *match_type, + FilterMatchType *type, + FilterMatchFlag *flag) +{ + g_return_if_fail(match_type != NULL); + + *type = FLT_CONTAIN; + *flag = 0; + + if (!strcmp(match_type, "contains")) { + *type = FLT_CONTAIN; + } else if (!strcmp(match_type, "not-contain")) { + *type = FLT_CONTAIN; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "is")) { + *type = FLT_EQUAL; + } else if (!strcmp(match_type, "is-not")) { + *type = FLT_EQUAL; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "regex")) { + *type = FLT_REGEX; + } else if (!strcmp(match_type, "not-regex")) { + *type = FLT_REGEX; + *flag = FLT_NOT_MATCH; + } else if (!strcmp(match_type, "gt")) { + } else if (!strcmp(match_type, "lt")) { + *flag = FLT_NOT_MATCH; + } +} + +void filter_rule_free(FilterRule *rule) +{ + if (!rule) return; + + g_free(rule->name); + + filter_cond_list_free(rule->cond_list); + filter_action_list_free(rule->action_list); + + g_free(rule); +} + +void filter_cond_list_free(GSList *cond_list) +{ + GSList *cur; + + for (cur = cond_list; cur != NULL; cur = cur->next) + filter_cond_free((FilterCond *)cur->data); + g_slist_free(cond_list); +} + +void filter_action_list_free(GSList *action_list) +{ + GSList *cur; + + for (cur = action_list; cur != NULL; cur = cur->next) + filter_action_free((FilterAction *)cur->data); + g_slist_free(action_list); +} + +static void filter_cond_free(FilterCond *cond) +{ + g_free(cond->header_name); + g_free(cond->str_value); + g_free(cond); +} + +static void filter_action_free(FilterAction *action) +{ + g_free(action->str_value); + g_free(action); +} + +void filter_info_free(FilterInfo *fltinfo) +{ + g_slist_free(fltinfo->dest_list); + g_free(fltinfo); +} diff --git a/src/filter.h b/src/filter.h new file mode 100644 index 00000000..ab1ff711 --- /dev/null +++ b/src/filter.h @@ -0,0 +1,194 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __FILTER_H__ +#define __FILTER_H__ + +#include + +#include "folder.h" +#include "procmsg.h" +#include "utils.h" + +typedef struct _FilterCond FilterCond; +typedef struct _FilterAction FilterAction; +typedef struct _FilterRule FilterRule; +typedef struct _FilterInfo FilterInfo; + +typedef enum +{ + FLT_TIMING_ANY, + FLT_TIMING_ON_RECEIVE, + FLT_TIMING_MANUAL +} FilterTiming; + +typedef enum +{ + FLT_COND_HEADER, + FLT_COND_ANY_HEADER, + FLT_COND_TO_OR_CC, + FLT_COND_BODY, + FLT_COND_CMD_TEST, + FLT_COND_SIZE_GREATER, + FLT_COND_AGE_GREATER, + FLT_COND_ACCOUNT +} FilterCondType; + +typedef enum +{ + FLT_CONTAIN, + FLT_EQUAL, + FLT_REGEX +} FilterMatchType; + +typedef enum +{ + FLT_NOT_MATCH = 1 << 0, + FLT_CASE_SENS = 1 << 1 +} FilterMatchFlag; + +typedef enum +{ + FLT_OR, + FLT_AND +} FilterBoolOp; + +typedef enum +{ + FLT_ACTION_MOVE, + FLT_ACTION_COPY, + FLT_ACTION_NOT_RECEIVE, + FLT_ACTION_DELETE, + FLT_ACTION_EXEC, + FLT_ACTION_EXEC_ASYNC, + FLT_ACTION_MARK, + FLT_ACTION_COLOR_LABEL, + FLT_ACTION_MARK_READ, + FLT_ACTION_FORWARD, + FLT_ACTION_FORWARD_AS_ATTACHMENT, + FLT_ACTION_REDIRECT, + FLT_ACTION_STOP_EVAL, + FLT_ACTION_NONE +} FilterActionType; + +#define FLT_IS_NOT_MATCH(flag) ((flag & FLT_NOT_MATCH) != 0) +#define FLT_IS_CASE_SENS(flag) ((flag & FLT_CASE_SENS) != 0) + +struct _FilterCond +{ + FilterCondType type; + + gchar *header_name; + + gchar *str_value; + gint int_value; + + FilterMatchType match_type; + FilterMatchFlag match_flag; + + StrFindFunc match_func; +}; + +struct _FilterAction +{ + FilterActionType type; + + gchar *str_value; + gint int_value; +}; + +struct _FilterRule +{ + gchar *name; + + FilterBoolOp bool_op; + + GSList *cond_list; + GSList *action_list; + + FilterTiming timing; + gboolean enabled; +}; + +struct _FilterInfo +{ + PrefsAccount *account; + MsgFlags flags; + + gboolean actions[FLT_ACTION_NONE]; + GSList *dest_list; + FolderItem *move_dest; + gboolean drop_done; +}; + +gint filter_apply (GSList *fltlist, + const gchar *file, + FilterInfo *fltinfo); +gint filter_apply_msginfo (GSList *fltlist, + MsgInfo *msginfo, + FilterInfo *fltinfo); + +gint filter_action_exec (FilterRule *rule, + MsgInfo *msginfo, + const gchar *file, + FilterInfo *fltinfo); + +gboolean filter_match_rule (FilterRule *rule, + MsgInfo *msginfo, + GSList *hlist, + FilterInfo *fltinfo); + +/* read / write config */ +GSList *filter_xml_node_to_filter_list (GNode *node); +void filter_write_config (GSList *fltlist); + +/* for old filterrc */ +gchar *filter_get_str (FilterRule *rule); +FilterRule *filter_read_str (const gchar *str); + +FilterRule *filter_rule_new (const gchar *name, + FilterBoolOp bool_op, + GSList *cond_list, + GSList *action_list); +FilterCond *filter_cond_new (FilterCondType type, + FilterMatchType match_type, + FilterMatchFlag match_flag, + const gchar *header, + const gchar *body); +FilterAction *filter_action_new (FilterActionType type, + const gchar *str); +FilterInfo *filter_info_new (void); + +void filter_rule_rename_dest_path (FilterRule *rule, + const gchar *old_path, + const gchar *new_path); +void filter_rule_delete_action_by_dest_path + (FilterRule *rule, + const gchar *path); + +void filter_rule_match_type_str_to_enum (const gchar *type_str, + FilterMatchType *type, + FilterMatchFlag *flag); + +void filter_rule_free (FilterRule *rule); +void filter_cond_list_free (GSList *cond_list); +void filter_action_list_free (GSList *action_list); +void filter_info_free (FilterInfo *info); + +#endif /* __FILTER_H__ */ diff --git a/src/folder.c b/src/folder.c new file mode 100644 index 00000000..08800e53 --- /dev/null +++ b/src/folder.c @@ -0,0 +1,1529 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include + +#include "intl.h" +#include "folder.h" +#include "session.h" +#include "imap.h" +#include "news.h" +#include "mh.h" +#include "utils.h" +#include "xml.h" +#include "codeconv.h" +#include "prefs.h" +#include "account.h" +#include "prefs_account.h" + +static GList *folder_list = NULL; + +static void folder_init (Folder *folder, + const gchar *name); + +static gboolean folder_read_folder_func (GNode *node, + gpointer data); +static gchar *folder_get_list_path (void); +static void folder_write_list_recursive (GNode *node, + gpointer data); + + +Folder *folder_new(FolderType type, const gchar *name, const gchar *path) +{ + Folder *folder = NULL; + + name = name ? name : path; + switch (type) { + case F_MH: + folder = mh_get_class()->folder_new(name, path); + break; + case F_IMAP: + folder = imap_get_class()->folder_new(name, path); + break; + case F_NEWS: + folder = news_get_class()->folder_new(name, path); + break; + default: + return NULL; + } + + return folder; +} + +static void folder_init(Folder *folder, const gchar *name) +{ + FolderItem *item; + + g_return_if_fail(folder != NULL); + + folder_set_name(folder, name); + folder->account = NULL; + folder->inbox = NULL; + folder->outbox = NULL; + folder->draft = NULL; + folder->queue = NULL; + folder->trash = NULL; + folder->ui_func = NULL; + folder->ui_func_data = NULL; + item = folder_item_new(name, NULL); + item->folder = folder; + folder->node = item->node = g_node_new(item); + folder->data = NULL; +} + +void folder_local_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder_init(folder, name); + LOCAL_FOLDER(folder)->rootpath = g_strdup(path); +} + +void folder_remote_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder_init(folder, name); + REMOTE_FOLDER(folder)->session = NULL; +} + +void folder_destroy(Folder *folder) +{ + g_return_if_fail(folder != NULL); + g_return_if_fail(folder->klass->destroy != NULL); + + folder->klass->destroy(folder); + + folder_list = g_list_remove(folder_list, folder); + + folder_tree_destroy(folder); + g_free(folder->name); + g_free(folder); +} + +void folder_local_folder_destroy(LocalFolder *lfolder) +{ + g_return_if_fail(lfolder != NULL); + + g_free(lfolder->rootpath); +} + +void folder_remote_folder_destroy(RemoteFolder *rfolder) +{ + g_return_if_fail(rfolder != NULL); + + if (rfolder->session) + session_destroy(rfolder->session); +} + +#if 0 +Folder *mbox_folder_new(const gchar *name, const gchar *path) +{ + /* not yet implemented */ + return NULL; +} + +Folder *maildir_folder_new(const gchar *name, const gchar *path) +{ + /* not yet implemented */ + return NULL; +} +#endif + +FolderItem *folder_item_new(const gchar *name, const gchar *path) +{ + FolderItem *item; + + item = g_new0(FolderItem, 1); + + item->stype = F_NORMAL; + item->name = g_strdup(name); + item->path = g_strdup(path); + item->mtime = 0; + item->new = 0; + item->unread = 0; + item->total = 0; + item->unmarked_num = 0; + item->last_num = -1; + item->no_sub = FALSE; + item->no_select = FALSE; + item->collapsed = FALSE; + item->threaded = TRUE; + item->opened = FALSE; + item->node = NULL; + item->parent = NULL; + item->folder = NULL; + item->account = NULL; + item->ac_apply_sub = FALSE; + item->auto_to = NULL; + item->use_auto_to_on_reply = FALSE; + item->auto_cc = NULL; + item->auto_bcc = NULL; + item->auto_replyto = NULL; + item->mark_queue = NULL; + item->data = NULL; + + return item; +} + +void folder_item_append(FolderItem *parent, FolderItem *item) +{ + g_return_if_fail(parent != NULL); + g_return_if_fail(parent->folder != NULL); + g_return_if_fail(parent->node != NULL); + g_return_if_fail(item != NULL); + + item->parent = parent; + item->folder = parent->folder; + item->node = g_node_append_data(parent->node, item); +} + +static gboolean folder_item_remove_func(GNode *node, gpointer data) +{ + FolderItem *item = FOLDER_ITEM(node->data); + + folder_item_destroy(item); + return FALSE; +} + +void folder_item_remove(FolderItem *item) +{ + GNode *node; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(item->node != NULL); + + node = item->node; + + if (item->folder->node == node) + item->folder->node = NULL; + + g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1, + folder_item_remove_func, NULL); + g_node_destroy(node); +} + +void folder_item_remove_children(FolderItem *item) +{ + GNode *node, *next; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(item->node != NULL); + + node = item->node->children; + while (node != NULL) { + next = node->next; + folder_item_remove(FOLDER_ITEM(node->data)); + node = next; + } +} + +void folder_item_destroy(FolderItem *item) +{ + Folder *folder; + + g_return_if_fail(item != NULL); + + folder = item->folder; + if (folder) { + if (folder->inbox == item) + folder->inbox = NULL; + else if (folder->outbox == item) + folder->outbox = NULL; + else if (folder->draft == item) + folder->draft = NULL; + else if (folder->queue == item) + folder->queue = NULL; + else if (folder->trash == item) + folder->trash = NULL; + } + + g_free(item->name); + g_free(item->path); + g_free(item->auto_to); + g_free(item->auto_cc); + g_free(item->auto_bcc); + g_free(item->auto_replyto); + g_free(item); +} + +void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data) +{ + g_return_if_fail(folder != NULL); + + folder->ui_func = func; + folder->ui_func_data = data; +} + +void folder_set_name(Folder *folder, const gchar *name) +{ + g_return_if_fail(folder != NULL); + + g_free(folder->name); + folder->name = name ? g_strdup(name) : NULL; + if (folder->node && folder->node->data) { + FolderItem *item = (FolderItem *)folder->node->data; + + g_free(item->name); + item->name = name ? g_strdup(name) : NULL; + } +} + +void folder_tree_destroy(Folder *folder) +{ + g_return_if_fail(folder != NULL); + + if (folder->node) + folder_item_remove(FOLDER_ITEM(folder->node->data)); +} + +void folder_add(Folder *folder) +{ + Folder *cur_folder; + GList *cur; + gint i; + + g_return_if_fail(folder != NULL); + + for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) { + cur_folder = FOLDER(cur->data); + if (FOLDER_TYPE(folder) == F_MH) { + if (FOLDER_TYPE(cur_folder) != F_MH) break; + } else if (FOLDER_TYPE(folder) == F_IMAP) { + if (FOLDER_TYPE(cur_folder) != F_MH && + FOLDER_TYPE(cur_folder) != F_IMAP) break; + } else if (FOLDER_TYPE(folder) == F_NEWS) { + if (FOLDER_TYPE(cur_folder) != F_MH && + FOLDER_TYPE(cur_folder) != F_IMAP && + FOLDER_TYPE(cur_folder) != F_NEWS) break; + } + } + + folder_list = g_list_insert(folder_list, folder, i); +} + +GList *folder_get_list(void) +{ + return folder_list; +} + +gint folder_read_list(void) +{ + GNode *node; + XMLNode *xmlnode; + gchar *path; + + path = folder_get_list_path(); + if (!is_file_exist(path)) return -1; + node = xml_parse_file(path); + if (!node) return -1; + + xmlnode = node->data; + if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) { + g_warning("wrong folder list\n"); + xml_free_tree(node); + return -1; + } + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2, + folder_read_folder_func, NULL); + + xml_free_tree(node); + if (folder_list) + return 0; + else + return -1; +} + +void folder_write_list(void) +{ + GList *list; + Folder *folder; + gchar *path; + PrefFile *pfile; + + path = folder_get_list_path(); + if ((pfile = prefs_file_open(path)) == NULL) return; + + fprintf(pfile->fp, "\n", + conv_get_internal_charset_str()); + fputs("\n\n", pfile->fp); + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + folder_write_list_recursive(folder->node, pfile->fp); + } + + fputs("\n", pfile->fp); + + if (prefs_file_close(pfile) < 0) + g_warning("failed to write folder list.\n"); +} + +struct TotalMsgStatus +{ + guint new; + guint unread; + guint total; + GString *str; +}; + +static gboolean folder_get_status_full_all_func(GNode *node, gpointer data) +{ + FolderItem *item; + struct TotalMsgStatus *status = (struct TotalMsgStatus *)data; + gchar *id; + + g_return_val_if_fail(node->data != NULL, FALSE); + + item = FOLDER_ITEM(node->data); + + if (!item->path) return FALSE; + + status->new += item->new; + status->unread += item->unread; + status->total += item->total; + + if (status->str) { + id = folder_item_get_identifier(item); + g_string_sprintfa(status->str, "%5d %5d %5d %s\n", + item->new, item->unread, + item->total, id); + g_free(id); + } + + return FALSE; +} + +static void folder_get_status_full_all(GString *str, guint *new, guint *unread, + guint *total) +{ + GList *list; + Folder *folder; + struct TotalMsgStatus status; + + status.new = status.unread = status.total = 0; + status.str = str; + + debug_print("Counting total number of messages...\n"); + + for (list = folder_list; list != NULL; list = list->next) { + folder = FOLDER(list->data); + if (folder->node) + g_node_traverse(folder->node, G_PRE_ORDER, + G_TRAVERSE_ALL, -1, + folder_get_status_full_all_func, + &status); + } + + *new = status.new; + *unread = status.unread; + *total = status.total; +} + +gchar *folder_get_status(GPtrArray *folders, gboolean full) +{ + guint new, unread, total; + GString *str; + gint i; + gchar *ret; + + new = unread = total = 0; + + str = g_string_new(NULL); + + if (folders) { + for (i = 0; i < folders->len; i++) { + FolderItem *item; + + item = g_ptr_array_index(folders, i); + new += item->new; + unread += item->unread; + total += item->total; + + if (full) { + gchar *id; + + id = folder_item_get_identifier(item); + g_string_sprintfa(str, "%5d %5d %5d %s\n", + item->new, item->unread, + item->total, id); + g_free(id); + } + } + } else { + folder_get_status_full_all(full ? str : NULL, + &new, &unread, &total); + } + + if (full) + g_string_sprintfa(str, "%5d %5d %5d\n", new, unread, total); + else + g_string_sprintfa(str, "%d %d %d\n", new, unread, total); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +Folder *folder_find_from_path(const gchar *path) +{ + GList *list; + Folder *folder; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (FOLDER_TYPE(folder) == F_MH && + !path_cmp(LOCAL_FOLDER(folder)->rootpath, path)) + return folder; + } + + return NULL; +} + +Folder *folder_find_from_name(const gchar *name, FolderType type) +{ + GList *list; + Folder *folder; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (FOLDER_TYPE(folder) == type && + strcmp2(name, folder->name) == 0) + return folder; + } + + return NULL; +} + +static gboolean folder_item_find_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gpointer *d = data; + const gchar *path = d[0]; + + if (path_cmp(path, item->path) != 0) + return FALSE; + + d[1] = item; + + return TRUE; +} + +FolderItem *folder_find_item_from_path(const gchar *path) +{ + Folder *folder; + gpointer d[2]; + + folder = folder_get_default_folder(); + g_return_val_if_fail(folder != NULL, NULL); + + d[0] = (gpointer)path; + d[1] = NULL; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_item_find_func, d); + return d[1]; +} + +FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name) +{ + GNode *node; + FolderItem *child; + + for (node = item->node->children; node != NULL; node = node->next) { + child = FOLDER_ITEM(node->data); + if (strcmp2(g_basename(child->path), name) == 0) + return child; + } + + return NULL; +} + +static const struct { + gchar *str; + FolderType type; +} type_str_table[] = { + {"#mh" , F_MH}, + {"#mbox" , F_MBOX}, + {"#maildir", F_MAILDIR}, + {"#imap" , F_IMAP}, + {"#news" , F_NEWS} +}; + +static gchar *folder_get_type_string(FolderType type) +{ + gint i; + + for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]); + i++) { + if (type_str_table[i].type == type) + return type_str_table[i].str; + } + + return NULL; +} + +static FolderType folder_get_type_from_string(const gchar *str) +{ + gint i; + + for (i = 0; i < sizeof(type_str_table) / sizeof(type_str_table[0]); + i++) { + if (g_strcasecmp(type_str_table[i].str, str) == 0) + return type_str_table[i].type; + } + + return F_UNKNOWN; +} + +gchar *folder_get_identifier(Folder *folder) +{ + gchar *type_str; + + g_return_val_if_fail(folder != NULL, NULL); + + type_str = folder_get_type_string(FOLDER_TYPE(folder)); + return g_strconcat(type_str, "/", folder->name, NULL); +} + +gchar *folder_item_get_identifier(FolderItem *item) +{ + gchar *id; + gchar *folder_id; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->path != NULL, NULL); + + folder_id = folder_get_identifier(item->folder); + id = g_strconcat(folder_id, "/", item->path, NULL); + g_free(folder_id); + + return id; +} + +FolderItem *folder_find_item_from_identifier(const gchar *identifier) +{ + Folder *folder; + gpointer d[2]; + gchar *str; + gchar *p; + gchar *name; + gchar *path; + FolderType type; + + g_return_val_if_fail(identifier != NULL, NULL); + + if (*identifier != '#') + return folder_find_item_from_path(identifier); + + Xstrdup_a(str, identifier, return NULL); + + p = strchr(str, '/'); + if (!p) + return folder_find_item_from_path(identifier); + *p = '\0'; + p++; + type = folder_get_type_from_string(str); + if (type == F_UNKNOWN) + return folder_find_item_from_path(identifier); + + name = p; + p = strchr(p, '/'); + if (!p) + return folder_find_item_from_path(identifier); + *p = '\0'; + p++; + + folder = folder_find_from_name(name, type); + if (!folder) + return folder_find_item_from_path(identifier); + + path = p; + + d[0] = (gpointer)path; + d[1] = NULL; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_item_find_func, d); + return d[1]; +} + +Folder *folder_get_default_folder(void) +{ + return folder_list ? FOLDER(folder_list->data) : NULL; +} + +FolderItem *folder_get_default_inbox(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->inbox; +} + +FolderItem *folder_get_default_outbox(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->outbox; +} + +FolderItem *folder_get_default_draft(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->draft; +} + +FolderItem *folder_get_default_queue(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->queue; +} + +FolderItem *folder_get_default_trash(void) +{ + Folder *folder; + + if (!folder_list) return NULL; + folder = FOLDER(folder_list->data); + g_return_val_if_fail(folder != NULL, NULL); + return folder->trash; +} + +#define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type) \ +{ \ + if (!folder->member) { \ + item = folder_item_new(dir, dir); \ + item->stype = type; \ + folder_item_append(rootitem, item); \ + folder->member = item; \ + } \ +} + +void folder_set_missing_folders(void) +{ + Folder *folder; + FolderItem *rootitem; + FolderItem *item; + GList *list; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (FOLDER_TYPE(folder) != F_MH) continue; + rootitem = FOLDER_ITEM(folder->node->data); + g_return_if_fail(rootitem != NULL); + + if (folder->inbox && folder->outbox && folder->draft && + folder->queue && folder->trash) + continue; + + if (folder->klass->create_tree(folder) < 0) { + g_warning("%s: can't create the folder tree.\n", + LOCAL_FOLDER(folder)->rootpath); + continue; + } + + CREATE_FOLDER_IF_NOT_EXIST(inbox, INBOX_DIR, F_INBOX); + CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX); + CREATE_FOLDER_IF_NOT_EXIST(draft, DRAFT_DIR, F_DRAFT); + CREATE_FOLDER_IF_NOT_EXIST(queue, QUEUE_DIR, F_QUEUE); + CREATE_FOLDER_IF_NOT_EXIST(trash, TRASH_DIR, F_TRASH); + } +} + +static gboolean folder_unref_account_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + PrefsAccount *account = data; + + if (item->account == account) + item->account = NULL; + + return FALSE; +} + +void folder_unref_account_all(PrefsAccount *account) +{ + Folder *folder; + GList *list; + + if (!account) return; + + for (list = folder_list; list != NULL; list = list->next) { + folder = list->data; + if (folder->account == account) + folder->account = NULL; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_unref_account_func, account); + } +} + +#undef CREATE_FOLDER_IF_NOT_EXIST + +gchar *folder_get_path(Folder *folder) +{ + gchar *path; + + g_return_val_if_fail(folder != NULL, NULL); + + if (FOLDER_TYPE(folder) == F_MH) { + path = g_filename_from_utf8(LOCAL_FOLDER(folder)->rootpath, + -1, NULL, NULL, NULL); + if (!path) { + g_warning("folder_get_path: faild to convert character set\n"); + path = g_strdup(LOCAL_FOLDER(folder)->rootpath); + } + } else if (FOLDER_TYPE(folder) == F_IMAP) { + g_return_val_if_fail(folder->account != NULL, NULL); + path = g_strconcat(get_imap_cache_dir(), + G_DIR_SEPARATOR_S, + folder->account->recv_server, + G_DIR_SEPARATOR_S, + folder->account->userid, + NULL); + } else if (FOLDER_TYPE(folder) == F_NEWS) { + g_return_val_if_fail(folder->account != NULL, NULL); + path = g_strconcat(get_news_cache_dir(), + G_DIR_SEPARATOR_S, + folder->account->nntp_server, + NULL); + } else + path = NULL; + + return path; +} + +gchar *folder_item_get_path(FolderItem *item) +{ + gchar *folder_path; + gchar *item_path = NULL, *path; + + g_return_val_if_fail(item != NULL, NULL); + + folder_path = folder_get_path(item->folder); + g_return_val_if_fail(folder_path != NULL, NULL); + + if (item->path) { + item_path = g_filename_from_utf8(item->path, -1, + NULL, NULL, NULL); + if (!item_path) { + g_warning("folder_item_get_path: faild to convert character set\n"); + item_path = g_strdup(item->path); + } + } + + if (folder_path[0] == G_DIR_SEPARATOR) { + if (item_path) + path = g_strconcat(folder_path, G_DIR_SEPARATOR_S, + item_path, NULL); + else + path = g_strdup(folder_path); + } else { + if (item_path) + path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + folder_path, G_DIR_SEPARATOR_S, + item_path, NULL); + else + path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + folder_path, NULL); + } + + g_free(item_path); + g_free(folder_path); + return path; +} + +gint folder_item_scan(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + + folder = item->folder; + return folder->klass->scan(folder, item); +} + +static void folder_item_scan_foreach_func(gpointer key, gpointer val, + gpointer data) +{ + folder_item_scan(FOLDER_ITEM(key)); +} + +void folder_item_scan_foreach(GHashTable *table) +{ + g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL); +} + +GSList *folder_item_get_msg_list(FolderItem *item, gboolean use_cache) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + + folder = item->folder; + return folder->klass->get_msg_list(folder, item, use_cache); +} + +gchar *folder_item_fetch_msg(FolderItem *item, gint num) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + + folder = item->folder; + + return folder->klass->fetch_msg(folder, item, num); +} + +gint folder_item_fetch_all_msg(FolderItem *item) +{ + Folder *folder; + GSList *mlist; + GSList *cur; + gint num = 0; + gint ret = 0; + + g_return_val_if_fail(item != NULL, -1); + + debug_print("fetching all messages in %s ...\n", item->path); + + folder = item->folder; + + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data ? + folder->ui_func_data : GINT_TO_POINTER(num)); + + mlist = folder_item_get_msg_list(item, TRUE); + + for (cur = mlist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + gchar *msg; + + num++; + if (folder->ui_func) + folder->ui_func(folder, item, + folder->ui_func_data ? + folder->ui_func_data : + GINT_TO_POINTER(num)); + + msg = folder_item_fetch_msg(item, msginfo->msgnum); + if (!msg) { + g_warning("Can't fetch message %d. Aborting.\n", + msginfo->msgnum); + ret = -1; + break; + } + g_free(msg); + } + + procmsg_msg_list_free(mlist); + + return ret; +} + +MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, NULL); + + folder = item->folder; + + return folder->klass->get_msginfo(folder, item, num); +} + +gint folder_item_add_msg(FolderItem *dest, const gchar *file, MsgFlags *flags, + gboolean remove_source) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(dest->folder->klass->add_msg != NULL, -1); + + folder = dest->folder; + + return folder->klass->add_msg(folder, dest, file, flags, remove_source); +} + +gint folder_item_add_msgs(FolderItem *dest, GSList *file_list, + gboolean remove_source, gint *first) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + g_return_val_if_fail(dest->folder->klass->add_msgs != NULL, -1); + + folder = dest->folder; + + return folder->klass->add_msgs(folder, dest, file_list, remove_source, + first); +} + +gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(dest->folder->klass->move_msg != NULL, -1); + + folder = dest->folder; + + return folder->klass->move_msg(folder, dest, msginfo); +} + +gint folder_item_move_msgs(FolderItem *dest, GSList *msglist) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + g_return_val_if_fail(dest->folder->klass->move_msgs != NULL, -1); + + folder = dest->folder; + + return folder->klass->move_msgs(folder, dest, msglist); +} + +gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msginfo != NULL, -1); + g_return_val_if_fail(dest->folder->klass->copy_msg != NULL, -1); + + folder = dest->folder; + + return folder->klass->copy_msg(folder, dest, msginfo); +} + +gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist) +{ + Folder *folder; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + g_return_val_if_fail(dest->folder->klass->copy_msgs != NULL, -1); + + folder = dest->folder; + + return folder->klass->copy_msgs(folder, dest, msglist); +} + +gint folder_item_remove_msg(FolderItem *item, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->folder->klass->remove_msg != NULL, -1); + + folder = item->folder; + + return folder->klass->remove_msg(folder, item, msginfo); +} + +gint folder_item_remove_msgs(FolderItem *item, GSList *msglist) +{ + Folder *folder; + gint ret = 0; + + g_return_val_if_fail(item != NULL, -1); + + folder = item->folder; + if (folder->klass->remove_msgs) { + return folder->klass->remove_msgs(folder, item, msglist); + } + + while (msglist != NULL) { + MsgInfo *msginfo = (MsgInfo *)msglist->data; + + ret = folder_item_remove_msg(item, msginfo); + if (ret != 0) break; + msglist = msglist->next; + } + + return ret; +} + +gint folder_item_remove_all_msg(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->folder->klass->remove_all_msg != NULL, -1); + + folder = item->folder; + + return folder->klass->remove_all_msg(folder, item); +} + +gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(item->folder->klass->is_msg_changed != NULL, + FALSE); + + folder = item->folder; + return folder->klass->is_msg_changed(folder, item, msginfo); +} + +gint folder_item_close(FolderItem *item) +{ + Folder *folder; + + g_return_val_if_fail(item != NULL, -1); + + item->opened = FALSE; + folder = item->folder; + return folder->klass->close(folder, item); +} + +gchar *folder_item_get_cache_file(FolderItem *item) +{ + gchar *path; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->path != NULL, NULL); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, NULL); + if (!is_dir_exist(path)) + make_dir_hier(path); + file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL); + g_free(path); + + return file; +} + +gchar *folder_item_get_mark_file(FolderItem *item) +{ + gchar *path; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->path != NULL, NULL); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, NULL); + if (!is_dir_exist(path)) + make_dir_hier(path); + file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL); + g_free(path); + + return file; +} + +static gboolean folder_build_tree(GNode *node, gpointer data) +{ + Folder *folder = FOLDER(data); + FolderItem *item; + XMLNode *xmlnode; + GList *list; + SpecialFolderItemType stype = F_NORMAL; + const gchar *name = NULL; + const gchar *path = NULL; + PrefsAccount *account = NULL; + gboolean no_sub = FALSE, no_select = FALSE, collapsed = FALSE, + threaded = TRUE, ac_apply_sub = FALSE; + FolderSortKey sort_key = SORT_BY_NONE; + FolderSortType sort_type = SORT_ASCENDING; + gint new = 0, unread = 0, total = 0; + time_t mtime = 0; + gboolean use_auto_to_on_reply = FALSE; + gchar *auto_to = NULL, *auto_cc = NULL, *auto_bcc = NULL, + *auto_replyto = NULL; + gboolean trim_summary_subject = FALSE, trim_compose_subject = FALSE; + + g_return_val_if_fail(node->data != NULL, FALSE); + if (!node->parent) return FALSE; + + xmlnode = node->data; + if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) { + g_warning("tag name != \"folderitem\"\n"); + return FALSE; + } + + list = xmlnode->tag->attr; + for (; list != NULL; list = list->next) { + XMLAttr *attr = list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "type")) { + if (!strcasecmp(attr->value, "normal")) + stype = F_NORMAL; + else if (!strcasecmp(attr->value, "inbox")) + stype = F_INBOX; + else if (!strcasecmp(attr->value, "outbox")) + stype = F_OUTBOX; + else if (!strcasecmp(attr->value, "draft")) + stype = F_DRAFT; + else if (!strcasecmp(attr->value, "queue")) + stype = F_QUEUE; + else if (!strcasecmp(attr->value, "trash")) + stype = F_TRASH; + } else if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "path")) + path = attr->value; + else if (!strcmp(attr->name, "mtime")) + mtime = strtoul(attr->value, NULL, 10); + else if (!strcmp(attr->name, "new")) + new = atoi(attr->value); + else if (!strcmp(attr->name, "unread")) + unread = atoi(attr->value); + else if (!strcmp(attr->name, "total")) + total = atoi(attr->value); + else if (!strcmp(attr->name, "no_sub")) + no_sub = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "no_select")) + no_select = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "collapsed")) + collapsed = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "threaded")) + threaded = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "sort_key")) { + if (!strcmp(attr->value, "none")) + sort_key = SORT_BY_NONE; + else if (!strcmp(attr->value, "number")) + sort_key = SORT_BY_NUMBER; + else if (!strcmp(attr->value, "size")) + sort_key = SORT_BY_SIZE; + else if (!strcmp(attr->value, "date")) + sort_key = SORT_BY_DATE; + else if (!strcmp(attr->value, "from")) + sort_key = SORT_BY_FROM; + else if (!strcmp(attr->value, "subject")) + sort_key = SORT_BY_SUBJECT; + else if (!strcmp(attr->value, "score")) + sort_key = SORT_BY_SCORE; + else if (!strcmp(attr->value, "label")) + sort_key = SORT_BY_LABEL; + else if (!strcmp(attr->value, "mark")) + sort_key = SORT_BY_MARK; + else if (!strcmp(attr->value, "unread")) + sort_key = SORT_BY_UNREAD; + else if (!strcmp(attr->value, "mime")) + sort_key = SORT_BY_MIME; + else if (!strcmp(attr->value, "to")) + sort_key = SORT_BY_TO; + } else if (!strcmp(attr->name, "sort_type")) { + if (!strcmp(attr->value, "ascending")) + sort_type = SORT_ASCENDING; + else + sort_type = SORT_DESCENDING; + } else if (!strcmp(attr->name, "account_id")) { + account = account_find_from_id(atoi(attr->value)); + if (!account) g_warning("account_id: %s not found\n", + attr->value); + } else if (!strcmp(attr->name, "account_apply_sub")) + ac_apply_sub = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "to")) + auto_to = g_strdup(attr->value); + else if (!strcmp(attr->name, "use_auto_to_on_reply")) + use_auto_to_on_reply = + *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "cc")) + auto_cc = g_strdup(attr->value); + else if (!strcmp(attr->name, "bcc")) + auto_bcc = g_strdup(attr->value); + else if (!strcmp(attr->name, "replyto")) + auto_replyto = g_strdup(attr->value); + else if (!strcmp(attr->name, "trim_summary_subject")) { + trim_summary_subject = + *attr->value == '1' ? TRUE : FALSE; + } else if (!strcmp(attr->name, "trim_compose_subject")) { + trim_compose_subject = + *attr->value = '1' ? TRUE : FALSE; + } + } + + item = folder_item_new(name, path); + item->stype = stype; + item->mtime = mtime; + item->new = new; + item->unread = unread; + item->total = total; + item->no_sub = no_sub; + item->no_select = no_select; + item->collapsed = collapsed; + item->threaded = threaded; + item->sort_key = sort_key; + item->sort_type = sort_type; + item->node = node; + item->parent = FOLDER_ITEM(node->parent->data); + item->folder = folder; + switch (stype) { + case F_INBOX: folder->inbox = item; break; + case F_OUTBOX: folder->outbox = item; break; + case F_DRAFT: folder->draft = item; break; + case F_QUEUE: folder->queue = item; break; + case F_TRASH: folder->trash = item; break; + default: break; + } + item->account = account; + item->ac_apply_sub = ac_apply_sub; + item->auto_to = auto_to; + item->use_auto_to_on_reply = use_auto_to_on_reply; + item->auto_cc = auto_cc; + item->auto_bcc = auto_bcc; + item->auto_replyto = auto_replyto; + item->trim_summary_subject = trim_summary_subject; + item->trim_compose_subject = trim_compose_subject; + node->data = item; + xml_free_node(xmlnode); + + return FALSE; +} + +static gboolean folder_read_folder_func(GNode *node, gpointer data) +{ + Folder *folder; + FolderItem *item; + XMLNode *xmlnode; + GList *list; + FolderType type = F_UNKNOWN; + const gchar *name = NULL; + const gchar *path = NULL; + PrefsAccount *account = NULL; + gboolean collapsed = FALSE, threaded = TRUE, ac_apply_sub = FALSE; + + if (g_node_depth(node) != 2) return FALSE; + g_return_val_if_fail(node->data != NULL, FALSE); + + xmlnode = node->data; + if (strcmp2(xmlnode->tag->tag, "folder") != 0) { + g_warning("tag name != \"folder\"\n"); + return TRUE; + } + g_node_unlink(node); + list = xmlnode->tag->attr; + for (; list != NULL; list = list->next) { + XMLAttr *attr = list->data; + + if (!attr || !attr->name || !attr->value) continue; + if (!strcmp(attr->name, "type")) { + if (!strcasecmp(attr->value, "mh")) + type = F_MH; + else if (!strcasecmp(attr->value, "mbox")) + type = F_MBOX; + else if (!strcasecmp(attr->value, "maildir")) + type = F_MAILDIR; + else if (!strcasecmp(attr->value, "imap")) + type = F_IMAP; + else if (!strcasecmp(attr->value, "news")) + type = F_NEWS; + } else if (!strcmp(attr->name, "name")) + name = attr->value; + else if (!strcmp(attr->name, "path")) + path = attr->value; + else if (!strcmp(attr->name, "collapsed")) + collapsed = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "threaded")) + threaded = *attr->value == '1' ? TRUE : FALSE; + else if (!strcmp(attr->name, "account_id")) { + account = account_find_from_id(atoi(attr->value)); + if (!account) g_warning("account_id: %s not found\n", + attr->value); + } else if (!strcmp(attr->name, "account_apply_sub")) + ac_apply_sub = *attr->value == '1' ? TRUE : FALSE; + } + + folder = folder_new(type, name, path); + g_return_val_if_fail(folder != NULL, FALSE); + folder->account = account; + if (account && (type == F_IMAP || type == F_NEWS)) + account->folder = REMOTE_FOLDER(folder); + item = FOLDER_ITEM(folder->node->data); + node->data = item; + item->node = node; + g_node_destroy(folder->node); + folder->node = node; + folder_add(folder); + item->collapsed = collapsed; + item->threaded = threaded; + item->account = account; + item->ac_apply_sub = ac_apply_sub; + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folder_build_tree, folder); + + return FALSE; +} + +static gchar *folder_get_list_path(void) +{ + static gchar *filename = NULL; + + if (!filename) + filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + FOLDER_LIST, NULL); + + return filename; +} + +#define PUT_ESCAPE_STR(fp, attr, str) \ +{ \ + fputs(" " attr "=\"", fp); \ + xml_file_put_escape_str(fp, str); \ + fputs("\"", fp); \ +} + +static void folder_write_list_recursive(GNode *node, gpointer data) +{ + FILE *fp = (FILE *)data; + FolderItem *item; + gint i, depth; + static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap", + "news", "unknown"}; + static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox", + "draft", "queue", "trash"}; + static gchar *sort_key_str[] = {"none", "number", "size", "date", + "from", "subject", "score", "label", + "mark", "unread", "mime", "to"}; + + g_return_if_fail(node != NULL); + g_return_if_fail(fp != NULL); + + item = FOLDER_ITEM(node->data); + g_return_if_fail(item != NULL); + + depth = g_node_depth(node); + for (i = 0; i < depth; i++) + fputs(" ", fp); + if (depth == 1) { + Folder *folder = item->folder; + + fprintf(fp, "name) + PUT_ESCAPE_STR(fp, "name", folder->name); + if (FOLDER_TYPE(folder) == F_MH) + PUT_ESCAPE_STR(fp, "path", + LOCAL_FOLDER(folder)->rootpath); + if (item->collapsed && node->children) + fputs(" collapsed=\"1\"", fp); + if (folder->account) + fprintf(fp, " account_id=\"%d\"", + folder->account->account_id); + if (item->ac_apply_sub) + fputs(" account_apply_sub=\"1\"", fp); + } else { + fprintf(fp, "stype]); + if (item->name) + PUT_ESCAPE_STR(fp, "name", item->name); + if (item->path) + PUT_ESCAPE_STR(fp, "path", item->path); + + if (item->no_sub) + fputs(" no_sub=\"1\"", fp); + if (item->no_select) + fputs(" no_select=\"1\"", fp); + if (item->collapsed && node->children) + fputs(" collapsed=\"1\"", fp); + if (item->threaded) + fputs(" threaded=\"1\"", fp); + else + fputs(" threaded=\"0\"", fp); + + if (item->sort_key != SORT_BY_NONE) { + fprintf(fp, " sort_key=\"%s\"", + sort_key_str[item->sort_key]); + if (item->sort_type == SORT_ASCENDING) + fprintf(fp, " sort_type=\"ascending\""); + else + fprintf(fp, " sort_type=\"descending\""); + } + + fprintf(fp, + " mtime=\"%lu\" new=\"%d\" unread=\"%d\" total=\"%d\"", + item->mtime, item->new, item->unread, item->total); + + if (item->account) + fprintf(fp, " account_id=\"%d\"", + item->account->account_id); + if (item->ac_apply_sub) + fputs(" account_apply_sub=\"1\"", fp); + + if (item->auto_to) + PUT_ESCAPE_STR(fp, "to", item->auto_to); + if (item->use_auto_to_on_reply) + fputs(" use_auto_to_on_reply=\"1\"", fp); + if (item->auto_cc) + PUT_ESCAPE_STR(fp, "cc", item->auto_cc); + if (item->auto_bcc) + PUT_ESCAPE_STR(fp, "bcc", item->auto_bcc); + if (item->auto_replyto) + PUT_ESCAPE_STR(fp, "replyto", item->auto_replyto); + + if (item->trim_summary_subject) + fputs(" trim_summary_subject=\"1\"", fp); + if (item->trim_compose_subject) + fputs(" trim_compose_subject=\"1\"", fp); + } + + if (node->children) { + GNode *child; + fputs(">\n", fp); + + child = node->children; + while (child) { + GNode *cur; + + cur = child; + child = cur->next; + folder_write_list_recursive(cur, data); + } + + for (i = 0; i < depth; i++) + fputs(" ", fp); + fprintf(fp, "\n", depth == 1 ? "folder" : "folderitem"); + } else + fputs(" />\n", fp); +} + +#undef PUT_ESCAPE_STR diff --git a/src/folder.h b/src/folder.h new file mode 100644 index 00000000..3537e6a1 --- /dev/null +++ b/src/folder.h @@ -0,0 +1,386 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __FOLDER_H__ +#define __FOLDER_H__ + +#include +#include + +typedef struct _Folder Folder; +typedef struct _FolderClass FolderClass; + +typedef struct _LocalFolder LocalFolder; +typedef struct _RemoteFolder RemoteFolder; +#if 0 +typedef struct _MboxFolder MboxFolder; +typedef struct _MaildirFolder MaildirFolder; +#endif + +typedef struct _FolderItem FolderItem; + +#define FOLDER(obj) ((Folder *)obj) +#define FOLDER_CLASS(obj) (FOLDER(obj)->klass) +#define FOLDER_TYPE(obj) (FOLDER(obj)->klass->type) + +#define LOCAL_FOLDER(obj) ((LocalFolder *)obj) +#define REMOTE_FOLDER(obj) ((RemoteFolder *)obj) + +#define FOLDER_IS_LOCAL(obj) (FOLDER_TYPE(obj) == F_MH || \ + FOLDER_TYPE(obj) == F_MBOX || \ + FOLDER_TYPE(obj) == F_MAILDIR) + +#if 0 +#define MBOX_FOLDER(obj) ((MboxFolder *)obj) +#define MAILDIR_FOLDER(obj) ((MaildirFolder *)obj) +#endif + +#define FOLDER_ITEM(obj) ((FolderItem *)obj) + +#define FOLDER_ITEM_CAN_ADD(obj) \ + ((obj) && FOLDER_ITEM(obj)->folder && \ + FOLDER_ITEM(obj)->path && \ + (FOLDER_IS_LOCAL(FOLDER_ITEM(obj)->folder) || \ + FOLDER_TYPE(FOLDER_ITEM(obj)->folder) == F_IMAP) && \ + !FOLDER_ITEM(obj)->no_select) + +typedef enum +{ + F_MH, + F_MBOX, + F_MAILDIR, + F_IMAP, + F_NEWS, + F_UNKNOWN +} FolderType; + +typedef enum +{ + F_NORMAL, + F_INBOX, + F_OUTBOX, + F_DRAFT, + F_QUEUE, + F_TRASH +} SpecialFolderItemType; + +typedef enum +{ + SORT_BY_NONE, + SORT_BY_NUMBER, + SORT_BY_SIZE, + SORT_BY_DATE, + SORT_BY_FROM, + SORT_BY_SUBJECT, + SORT_BY_SCORE, + SORT_BY_LABEL, + SORT_BY_MARK, + SORT_BY_UNREAD, + SORT_BY_MIME, + SORT_BY_TO +} FolderSortKey; + +typedef enum +{ + SORT_ASCENDING, + SORT_DESCENDING +} FolderSortType; + +typedef void (*FolderUIFunc) (Folder *folder, + FolderItem *item, + gpointer data); +typedef void (*FolderDestroyNotify) (Folder *folder, + FolderItem *item, + gpointer data); + +#include "prefs_account.h" +#include "session.h" +#include "procmsg.h" + +struct _Folder +{ + FolderClass *klass; + + gchar *name; + PrefsAccount *account; + + FolderItem *inbox; + FolderItem *outbox; + FolderItem *draft; + FolderItem *queue; + FolderItem *trash; + + FolderUIFunc ui_func; + gpointer ui_func_data; + + GNode *node; + + gpointer data; +}; + +struct _FolderClass +{ + FolderType type; + + /* virtual functions */ + Folder * (*folder_new) (const gchar *name, + const gchar *path); + void (*destroy) (Folder *folder); + + gint (*scan_tree) (Folder *folder); + gint (*create_tree) (Folder *folder); + + GSList * (*get_msg_list) (Folder *folder, + FolderItem *item, + gboolean use_cache); + /* return value is locale charset */ + gchar * (*fetch_msg) (Folder *folder, + FolderItem *item, + gint num); + MsgInfo * (*get_msginfo) (Folder *folder, + FolderItem *item, + gint num); + gint (*add_msg) (Folder *folder, + FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); + gint (*add_msgs) (Folder *folder, + FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); + gint (*move_msg) (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); + gint (*move_msgs) (Folder *folder, + FolderItem *dest, + GSList *msglist); + gint (*copy_msg) (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); + gint (*copy_msgs) (Folder *folder, + FolderItem *dest, + GSList *msglist); + gint (*remove_msg) (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); + gint (*remove_msgs) (Folder *folder, + FolderItem *item, + GSList *msglist); + gint (*remove_all_msg) (Folder *folder, + FolderItem *item); + gboolean (*is_msg_changed) (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); + gint (*close) (Folder *folder, + FolderItem *item); + gint (*scan) (Folder *folder, + FolderItem *item); + + FolderItem * (*create_folder) (Folder *folder, + FolderItem *parent, + const gchar *name); + gint (*rename_folder) (Folder *folder, + FolderItem *item, + const gchar *name); + gint (*remove_folder) (Folder *folder, + FolderItem *item); +}; + +struct _LocalFolder +{ + Folder folder; + + gchar *rootpath; +}; + +struct _RemoteFolder +{ + Folder folder; + + Session *session; +}; + +#if 0 +struct _MboxFolder +{ + LocalFolder lfolder; +}; + +struct _MaildirFolder +{ + LocalFolder lfolder; +}; +#endif + +struct _FolderItem +{ + SpecialFolderItemType stype; + + gchar *name; /* UTF-8 */ + gchar *path; /* UTF-8 */ + + time_t mtime; + + gint new; + gint unread; + gint total; + gint unmarked_num; + + gint last_num; + + /* special flags */ + guint no_sub : 1; /* no child allowed? */ + guint no_select : 1; /* not selectable? */ + guint collapsed : 1; /* collapsed item */ + guint threaded : 1; /* threaded folder view */ + + guint opened : 1; /* opened by summary view */ + guint updated : 1; /* folderview should be updated */ + + FolderSortKey sort_key; + FolderSortType sort_type; + + GNode *node; + + FolderItem *parent; + + Folder *folder; + + PrefsAccount *account; + + gboolean ac_apply_sub; + + gchar *auto_to; + gboolean use_auto_to_on_reply; + gchar *auto_cc; + gchar *auto_bcc; + gchar *auto_replyto; + + gboolean trim_summary_subject; + gboolean trim_compose_subject; + + GSList *mark_queue; + + gpointer data; +}; + +Folder *folder_new (FolderType type, + const gchar *name, + const gchar *path); +void folder_local_folder_init (Folder *folder, + const gchar *name, + const gchar *path); +void folder_remote_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +void folder_destroy (Folder *folder); +void folder_local_folder_destroy (LocalFolder *lfolder); +void folder_remote_folder_destroy(RemoteFolder *rfolder); + +FolderItem *folder_item_new (const gchar *name, + const gchar *path); +void folder_item_append (FolderItem *parent, + FolderItem *item); +void folder_item_remove (FolderItem *item); +void folder_item_remove_children (FolderItem *item); +void folder_item_destroy (FolderItem *item); + +void folder_set_ui_func (Folder *folder, + FolderUIFunc func, + gpointer data); +void folder_set_name (Folder *folder, + const gchar *name); +void folder_tree_destroy (Folder *folder); + +void folder_add (Folder *folder); + +GList *folder_get_list (void); +gint folder_read_list (void); +void folder_write_list (void); + +gchar *folder_get_status (GPtrArray *folders, + gboolean full); + +Folder *folder_find_from_path (const gchar *path); +Folder *folder_find_from_name (const gchar *name, + FolderType type); +FolderItem *folder_find_item_from_path (const gchar *path); +FolderItem *folder_find_child_item_by_name (FolderItem *item, + const gchar *name); +gchar *folder_get_identifier (Folder *folder); +gchar *folder_item_get_identifier (FolderItem *item); +FolderItem *folder_find_item_from_identifier (const gchar *identifier); + +Folder *folder_get_default_folder (void); +FolderItem *folder_get_default_inbox (void); +FolderItem *folder_get_default_outbox (void); +FolderItem *folder_get_default_draft (void); +FolderItem *folder_get_default_queue (void); +FolderItem *folder_get_default_trash (void); + +void folder_set_missing_folders (void); +void folder_unref_account_all (PrefsAccount *account); + +/* return value is locale encoded file name */ +gchar *folder_get_path (Folder *folder); +gchar *folder_item_get_path (FolderItem *item); + +gint folder_item_scan (FolderItem *item); +void folder_item_scan_foreach (GHashTable *table); +GSList *folder_item_get_msg_list (FolderItem *item, + gboolean use_cache); +/* return value is locale charset */ +gchar *folder_item_fetch_msg (FolderItem *item, + gint num); +gint folder_item_fetch_all_msg (FolderItem *item); +MsgInfo *folder_item_get_msginfo (FolderItem *item, + gint num); +gint folder_item_add_msg (FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); +gint folder_item_add_msgs (FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); +gint folder_item_move_msg (FolderItem *dest, + MsgInfo *msginfo); +gint folder_item_move_msgs (FolderItem *dest, + GSList *msglist); +gint folder_item_copy_msg (FolderItem *dest, + MsgInfo *msginfo); +gint folder_item_copy_msgs (FolderItem *dest, + GSList *msglist); +gint folder_item_remove_msg (FolderItem *item, + MsgInfo *msginfo); +gint folder_item_remove_msgs (FolderItem *item, + GSList *msglist); +gint folder_item_remove_all_msg (FolderItem *item); +gboolean folder_item_is_msg_changed (FolderItem *item, + MsgInfo *msginfo); +/* return value is locale chaset */ +gchar *folder_item_get_cache_file (FolderItem *item); +gchar *folder_item_get_mark_file (FolderItem *item); + +gint folder_item_close (FolderItem *item); + +#endif /* __FOLDER_H__ */ diff --git a/src/foldersel.c b/src/foldersel.c new file mode 100644 index 00000000..7ca6afbf --- /dev/null +++ b/src/foldersel.c @@ -0,0 +1,493 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "utils.h" +#include "gtkutils.h" +#include "stock_pixmap.h" +#include "foldersel.h" +#include "alertpanel.h" +#include "manage_window.h" +#include "folderview.h" +#include "inputdialog.h" +#include "folder.h" + +static GdkPixmap *folderxpm; +static GdkBitmap *folderxpmmask; +static GdkPixmap *folderopenxpm; +static GdkBitmap *folderopenxpmmask; +static GdkPixmap *foldernoselectxpm; +static GdkBitmap *foldernoselectxpmmask; + +static GtkWidget *window; +static GtkWidget *ctree; +static GtkWidget *entry; +static GtkWidget *ok_button; +static GtkWidget *cancel_button; +static GtkWidget *new_button; + +static FolderItem *folder_item; +static FolderItem *selected_item; + +static gboolean cancelled; +static gboolean finished; + +static void foldersel_create (void); +static void foldersel_init (void); +static void foldersel_set_tree (Folder *cur_folder, + FolderSelectionType type); + +static void foldersel_selected (GtkCList *clist, + gint row, + gint column, + GdkEvent *event, + gpointer data); + +static void foldersel_ok (GtkButton *button, + gpointer data); +static void foldersel_cancel (GtkButton *button, + gpointer data); +static void foldersel_new_folder(GtkButton *button, + gpointer data); +static void foldersel_activated (void); +static gint delete_event (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); + +static gint foldersel_clist_compare (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); + +FolderItem *foldersel_folder_sel(Folder *cur_folder, + FolderSelectionType type, + const gchar *default_folder) +{ + GtkCTreeNode *node; + + selected_item = NULL; + + if (!window) { + foldersel_create(); + foldersel_init(); + } else + gtk_widget_show(window); + manage_window_set_transient(GTK_WINDOW(window)); + + foldersel_set_tree(cur_folder, type); + + if (folder_item) { + node = gtk_ctree_find_by_row_data + (GTK_CTREE(ctree), NULL, folder_item); + if (node) { + gint row; + + row = gtkut_ctree_get_nth_from_node + (GTK_CTREE(ctree), node); + gtk_clist_select_row(GTK_CLIST(ctree), row, -1); + gtkut_clist_set_focus_row(GTK_CLIST(ctree), row); + gtk_ctree_node_moveto(GTK_CTREE(ctree), node, -1, + 0.5, 0); + } + } + gtk_widget_grab_focus(ok_button); + gtk_widget_grab_focus(ctree); + + cancelled = finished = FALSE; + + while (finished == FALSE) + gtk_main_iteration(); + + gtk_widget_hide(window); + gtk_entry_set_text(GTK_ENTRY(entry), ""); + gtk_clist_clear(GTK_CLIST(ctree)); + + if (!cancelled && + selected_item && selected_item->path && !selected_item->no_select) { + folder_item = selected_item; + return folder_item; + } else + return NULL; +} + +static void foldersel_create(void) +{ + GtkWidget *vbox; + GtkWidget *scrolledwin; + GtkWidget *confirm_area; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Select folder")); + gtk_container_set_border_width(GTK_CONTAINER(window), 4); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + gtk_window_set_wmclass + (GTK_WINDOW(window), "folder_selection", "Sylpheed"); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(delete_event), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + vbox = gtk_vbox_new(FALSE, 4); + gtk_container_add(GTK_CONTAINER(window), vbox); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_size_request(scrolledwin, 300, 360); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); + + ctree = gtk_ctree_new(1, 0); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_CLIST(ctree)->vadjustment); + gtk_container_add(GTK_CONTAINER(scrolledwin), ctree); + gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE); + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED); + gtk_ctree_set_expander_style(GTK_CTREE(ctree), + GTK_CTREE_EXPANDER_SQUARE); + gtk_ctree_set_indent(GTK_CTREE(ctree), CTREE_INDENT); + gtk_clist_set_compare_func(GTK_CLIST(ctree), foldersel_clist_compare); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[0].button, + GTK_CAN_FOCUS); + /* g_signal_connect(G_OBJECT(ctree), "tree_select_row", + G_CALLBACK(foldersel_selected), NULL); */ + g_signal_connect(G_OBJECT(ctree), "select_row", + G_CALLBACK(foldersel_selected), NULL); + + entry = gtk_entry_new(); + gtk_entry_set_editable(GTK_ENTRY(entry), FALSE); + gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(entry), "activate", + G_CALLBACK(foldersel_activated), NULL); + + gtkut_button_set_create(&confirm_area, + &ok_button, _("OK"), + &cancel_button, _("Cancel"), + &new_button, _("New folder")); + + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_button); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(foldersel_ok), NULL); + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(foldersel_cancel), NULL); + g_signal_connect(G_OBJECT(new_button), "clicked", + G_CALLBACK(foldersel_new_folder), NULL); + + gtk_widget_show_all(window); +} + +static void foldersel_init(void) +{ + stock_pixmap_gdk(ctree, STOCK_PIXMAP_DIR_CLOSE, + &folderxpm, &folderxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_DIR_OPEN, + &folderopenxpm, &folderopenxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_DIR_NOSELECT, + &foldernoselectxpm, &foldernoselectxpmmask); +} + +static gboolean foldersel_gnode_func(GtkCTree *ctree, guint depth, + GNode *gnode, GtkCTreeNode *cnode, + gpointer data) +{ + FolderItem *item = FOLDER_ITEM(gnode->data); + gchar *name; + GdkPixmap *xpm, *openxpm; + GdkBitmap *mask, *openmask; + + switch (item->stype) { + case F_INBOX: + name = _("Inbox"); + break; + case F_OUTBOX: + name = _("Sent"); + break; + case F_QUEUE: + name = _("Queue"); + break; + case F_TRASH: + name = _("Trash"); + break; + case F_DRAFT: + name = _("Drafts"); + break; + default: + name = item->name; + + if (!item->parent) { + switch (FOLDER_TYPE(item->folder)) { + case F_MH: + Xstrcat_a(name, name, " (MH)", ); break; + case F_IMAP: + Xstrcat_a(name, name, " (IMAP4)", ); break; + case F_NEWS: + Xstrcat_a(name, name, " (News)", ); break; + default: + break; + } + } + } + + if (item->no_select) { + GdkColor color_noselect = {0, COLOR_DIM, COLOR_DIM, COLOR_DIM}; + xpm = openxpm = foldernoselectxpm; + mask = openmask = foldernoselectxpmmask; + gtk_ctree_node_set_foreground(ctree, cnode, &color_noselect); + } else { + xpm = folderxpm; + mask = folderxpmmask; + openxpm = folderopenxpm; + openmask = folderopenxpmmask; + } + + gtk_ctree_node_set_row_data(ctree, cnode, item); + gtk_ctree_set_node_info(ctree, cnode, name, + FOLDER_SPACING, + xpm, mask, openxpm, openmask, + FALSE, FALSE); + + return TRUE; +} + +static void foldersel_expand_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + if (GTK_CTREE_ROW(node)->children) + gtk_ctree_expand(ctree, node); +} + +#define SET_SPECIAL_FOLDER(item) \ +{ \ + if (item) { \ + GtkCTreeNode *node_, *parent, *sibling; \ + \ + node_ = gtk_ctree_find_by_row_data \ + (GTK_CTREE(ctree), node, item); \ + if (!node_) \ + g_warning("%s not found.\n", item->path); \ + else { \ + parent = GTK_CTREE_ROW(node_)->parent; \ + if (prev && parent == GTK_CTREE_ROW(prev)->parent) \ + sibling = GTK_CTREE_ROW(prev)->sibling; \ + else \ + sibling = GTK_CTREE_ROW(parent)->children; \ + if (node_ != sibling) \ + gtk_ctree_move(GTK_CTREE(ctree), \ + node_, parent, sibling); \ + } \ + \ + prev = node_; \ + } \ +} + +static void foldersel_set_tree(Folder *cur_folder, FolderSelectionType type) +{ + Folder *folder; + GtkCTreeNode *node; + GList *list; + + list = folder_get_list(); + + gtk_clist_freeze(GTK_CLIST(ctree)); + + for (; list != NULL; list = list->next) { + GtkCTreeNode *prev = NULL; + + folder = FOLDER(list->data); + g_return_if_fail(folder != NULL); + + if (type != FOLDER_SEL_ALL) { + if (FOLDER_TYPE(folder) == F_NEWS) + continue; + } + + node = gtk_ctree_insert_gnode(GTK_CTREE(ctree), NULL, NULL, + folder->node, + foldersel_gnode_func, + NULL); + gtk_ctree_sort_recursive(GTK_CTREE(ctree), node); + SET_SPECIAL_FOLDER(folder->inbox); + SET_SPECIAL_FOLDER(folder->outbox); + SET_SPECIAL_FOLDER(folder->draft); + SET_SPECIAL_FOLDER(folder->queue); + SET_SPECIAL_FOLDER(folder->trash); + gtk_ctree_pre_recursive(GTK_CTREE(ctree), node, + foldersel_expand_func, + NULL); + } + + gtk_clist_thaw(GTK_CLIST(ctree)); +} + +static void foldersel_selected(GtkCList *clist, gint row, gint column, + GdkEvent *event, gpointer data) +{ + GdkEventButton *ev = (GdkEventButton *)event; + + selected_item = gtk_clist_get_row_data(clist, row); + if (selected_item && selected_item->path && !selected_item->no_select) { + gchar *id; + id = folder_item_get_identifier(selected_item); + gtk_entry_set_text(GTK_ENTRY(entry), id); + g_free(id); + } else + gtk_entry_set_text(GTK_ENTRY(entry), ""); + + if (ev && GDK_2BUTTON_PRESS == ev->type) + gtk_button_clicked(GTK_BUTTON(ok_button)); +} + +static void foldersel_ok(GtkButton *button, gpointer data) +{ + finished = TRUE; +} + +static void foldersel_cancel(GtkButton *button, gpointer data) +{ + cancelled = TRUE; + finished = TRUE; +} + +static void foldersel_new_folder(GtkButton *button, gpointer data) +{ + FolderItem *new_item; + gchar *new_folder; + gchar *disp_name; + gchar *p; + gchar *text[1] = {NULL}; + GtkCTreeNode *selected_node; + GtkCTreeNode *node; + gint row; + + if (!selected_item || FOLDER_TYPE(selected_item->folder) == F_NEWS) + return; + selected_node = gtk_ctree_find_by_row_data(GTK_CTREE(ctree), NULL, + selected_item); + if (!selected_node) return; + + new_folder = input_dialog(_("New folder"), + _("Input the name of new folder:"), + _("NewFolder")); + if (!new_folder) return; + AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); + + p = strchr(new_folder, G_DIR_SEPARATOR); + if ((p && FOLDER_TYPE(selected_item->folder) != F_IMAP) || + (p && FOLDER_TYPE(selected_item->folder) == F_IMAP && + *(p + 1) != '\0')) { + alertpanel_error(_("`%c' can't be included in folder name."), + G_DIR_SEPARATOR); + return; + } + + disp_name = trim_string(new_folder, 32); + AUTORELEASE_STR(disp_name, {g_free(disp_name); return;}); + + /* find whether the directory already exists */ + if (folder_find_child_item_by_name(selected_item, new_folder)) { + alertpanel_error(_("The folder `%s' already exists."), + disp_name); + return; + } + + new_item = selected_item->folder->klass->create_folder + (selected_item->folder, selected_item, new_folder); + if (!new_item) { + alertpanel_error(_("Can't create the folder `%s'."), disp_name); + return; + } + + text[0] = new_item->name; + node = gtk_ctree_insert_node(GTK_CTREE(ctree), selected_node, + NULL, text, FOLDER_SPACING, + folderxpm, folderxpmmask, + folderopenxpm, folderopenxpmmask, + FALSE, FALSE); + gtk_ctree_expand(GTK_CTREE(ctree), selected_node); + gtk_ctree_node_set_row_data(GTK_CTREE(ctree), node, new_item); + gtk_ctree_sort_recursive(GTK_CTREE(ctree), selected_node); + + row = gtkut_ctree_get_nth_from_node(GTK_CTREE(ctree), node); + gtk_clist_select_row(GTK_CLIST(ctree), row, -1); + gtkut_clist_set_focus_row(GTK_CLIST(ctree), row); + gtk_ctree_node_moveto(GTK_CTREE(ctree), node, -1, 0.5, 0); + + folderview_append_item(new_item); + folder_write_list(); +} + +static void foldersel_activated(void) +{ + gtk_button_clicked(GTK_BUTTON(ok_button)); +} + +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + foldersel_cancel(NULL, NULL); + return TRUE; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) + foldersel_cancel(NULL, NULL); + return FALSE; +} + +static gint foldersel_clist_compare(GtkCList *clist, + gconstpointer ptr1, gconstpointer ptr2) +{ + FolderItem *item1 = ((GtkCListRow *)ptr1)->data; + FolderItem *item2 = ((GtkCListRow *)ptr2)->data; + + if (!item1->name) + return (item2->name != NULL); + if (!item2->name) + return -1; + + return g_strcasecmp(item1->name, item2->name); +} diff --git a/src/foldersel.h b/src/foldersel.h new file mode 100644 index 00000000..802bb50e --- /dev/null +++ b/src/foldersel.h @@ -0,0 +1,39 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __FOLDERSEL_H__ +#define __FOLDERSEL_H__ + +#include +#include + +#include "folder.h" + +typedef enum +{ + FOLDER_SEL_ALL, + FOLDER_SEL_MOVE, + FOLDER_SEL_COPY +} FolderSelectionType; + +FolderItem *foldersel_folder_sel(Folder *cur_folder, + FolderSelectionType type, + const gchar *default_folder); + +#endif /* __FOLDERSEL_H__ */ diff --git a/src/folderview.c b/src/folderview.c new file mode 100644 index 00000000..56320c8f --- /dev/null +++ b/src/folderview.c @@ -0,0 +1,2378 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "mainwindow.h" +#include "folderview.h" +#include "summaryview.h" +#include "summary_search.h" +#include "inputdialog.h" +#include "grouplistdialog.h" +#include "manage_window.h" +#include "alertpanel.h" +#include "menu.h" +#include "stock_pixmap.h" +#include "statusbar.h" +#include "procmsg.h" +#include "utils.h" +#include "gtkutils.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "prefs_filter.h" +#include "prefs_folder_item.h" +#include "account.h" +#include "folder.h" +#include "inc.h" + +typedef enum +{ + COL_FOLDER = 0, + COL_NEW = 1, + COL_UNREAD = 2, + COL_TOTAL = 3 +} FolderColumnPos; + +#define N_FOLDER_COLS 4 +#define COL_FOLDER_WIDTH 150 +#define COL_NUM_WIDTH 32 + +#define STATUSBAR_PUSH(mainwin, str) \ +{ \ + gtk_statusbar_push(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->folderview_cid, str); \ + gtkut_widget_wait_for_draw(mainwin->hbox_stat); \ +} + +#define STATUSBAR_POP(mainwin) \ +{ \ + gtk_statusbar_pop(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->folderview_cid); \ +} + +static GList *folderview_list = NULL; + +static GtkStyle *bold_style; +static GtkStyle *bold_color_style; + +static GdkPixmap *inboxxpm; +static GdkBitmap *inboxxpmmask; +static GdkPixmap *outboxxpm; +static GdkBitmap *outboxxpmmask; +static GdkPixmap *folderxpm; +static GdkBitmap *folderxpmmask; +static GdkPixmap *folderopenxpm; +static GdkBitmap *folderopenxpmmask; +static GdkPixmap *foldernoselectxpm; +static GdkBitmap *foldernoselectxpmmask; +static GdkPixmap *trashxpm; +static GdkBitmap *trashxpmmask; + +static void folderview_select_node (FolderView *folderview, + GtkCTreeNode *node); +static void folderview_set_folders (FolderView *folderview); +static void folderview_sort_folders (FolderView *folderview, + GtkCTreeNode *root, + Folder *folder); +static void folderview_append_folder (FolderView *folderview, + Folder *folder); +static void folderview_update_node (FolderView *folderview, + GtkCTreeNode *node); + +static gint folderview_clist_compare (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); + +/* callback functions */ +static gboolean folderview_button_pressed (GtkWidget *ctree, + GdkEventButton *event, + FolderView *folderview); +static gboolean folderview_button_released (GtkWidget *ctree, + GdkEventButton *event, + FolderView *folderview); + +static gboolean folderview_key_pressed (GtkWidget *widget, + GdkEventKey *event, + FolderView *folderview); +static void folderview_selected (GtkCTree *ctree, + GtkCTreeNode *row, + gint column, + FolderView *folderview); +static void folderview_tree_expanded (GtkCTree *ctree, + GtkCTreeNode *node, + FolderView *folderview); +static void folderview_tree_collapsed (GtkCTree *ctree, + GtkCTreeNode *node, + FolderView *folderview); +static void folderview_popup_close (GtkMenuShell *menu_shell, + FolderView *folderview); +static void folderview_col_resized (GtkCList *clist, + gint column, + gint width, + FolderView *folderview); + +static void folderview_download_cb (FolderView *folderview, + guint action, + GtkWidget *widget); + +static void folderview_update_tree_cb (FolderView *folderview, + guint action, + GtkWidget *widget); + +static void folderview_new_folder_cb (FolderView *folderview, + guint action, + GtkWidget *widget); +static void folderview_rename_folder_cb (FolderView *folderview, + guint action, + GtkWidget *widget); +static void folderview_delete_folder_cb (FolderView *folderview, + guint action, + GtkWidget *widget); +static void folderview_empty_trash_cb (FolderView *folderview, + guint action, + GtkWidget *widget); +static void folderview_remove_mailbox_cb(FolderView *folderview, + guint action, + GtkWidget *widget); + +static void folderview_rm_imap_server_cb (FolderView *folderview, + guint action, + GtkWidget *widget); + +static void folderview_new_news_group_cb(FolderView *folderview, + guint action, + GtkWidget *widget); +static void folderview_rm_news_group_cb (FolderView *folderview, + guint action, + GtkWidget *widget); +static void folderview_rm_news_server_cb(FolderView *folderview, + guint action, + GtkWidget *widget); + +static void folderview_search_cb (FolderView *folderview, + guint action, + GtkWidget *widget); + +static void folderview_property_cb (FolderView *folderview, + guint action, + GtkWidget *widget); + +static gboolean folderview_drag_motion_cb(GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + FolderView *folderview); +static void folderview_drag_leave_cb (GtkWidget *widget, + GdkDragContext *context, + guint time, + FolderView *folderview); +static void folderview_drag_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + FolderView *folderview); + +static GtkItemFactoryEntry folderview_mail_popup_entries[] = +{ + {N_("/Create _new folder..."), NULL, folderview_new_folder_cb, 0, NULL}, + {N_("/_Rename folder..."), NULL, folderview_rename_folder_cb, 0, NULL}, + {N_("/_Delete folder"), NULL, folderview_delete_folder_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/Empty _trash"), NULL, folderview_empty_trash_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Check for new messages"), + NULL, folderview_update_tree_cb, 0, NULL}, + {N_("/R_ebuild folder tree"), NULL, folderview_update_tree_cb, 1, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Search messages..."), NULL, folderview_search_cb, 0, NULL}, + {N_("/_Properties..."), NULL, folderview_property_cb, 0, NULL} +}; + +static GtkItemFactoryEntry folderview_imap_popup_entries[] = +{ + {N_("/Create _new folder..."), NULL, folderview_new_folder_cb, 0, NULL}, + {N_("/_Rename folder..."), NULL, folderview_rename_folder_cb, 0, NULL}, + {N_("/_Delete folder"), NULL, folderview_delete_folder_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/Empty _trash"), NULL, folderview_empty_trash_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/Down_load"), NULL, folderview_download_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Check for new messages"), + NULL, folderview_update_tree_cb, 0, NULL}, + {N_("/R_ebuild folder tree"), NULL, folderview_update_tree_cb, 1, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Search messages..."), NULL, folderview_search_cb, 0, NULL}, + {N_("/_Properties..."), NULL, folderview_property_cb, 0, NULL} +}; + +static GtkItemFactoryEntry folderview_news_popup_entries[] = +{ + {N_("/Su_bscribe to newsgroup..."), + NULL, folderview_new_news_group_cb, 0, NULL}, + {N_("/_Remove newsgroup"), NULL, folderview_rm_news_group_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/Down_load"), NULL, folderview_download_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Check for new messages"), + NULL, folderview_update_tree_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Search messages..."), NULL, folderview_search_cb, 0, NULL}, + {N_("/_Properties..."), NULL, folderview_property_cb, 0, NULL} +}; + + +FolderView *folderview_create(void) +{ + FolderView *folderview; + GtkWidget *scrolledwin; + GtkWidget *ctree; + gchar *titles[N_FOLDER_COLS]; + GtkWidget *mail_popup; + GtkWidget *news_popup; + GtkWidget *imap_popup; + GtkItemFactory *mail_factory; + GtkItemFactory *news_factory; + GtkItemFactory *imap_factory; + gint n_entries; + gint i; + + debug_print(_("Creating folder view...\n")); + folderview = g_new0(FolderView, 1); + + titles[COL_FOLDER] = _("Folder"); + titles[COL_NEW] = _("New"); + titles[COL_UNREAD] = _("Unread"); + titles[COL_TOTAL] = _("#"); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy + (GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + prefs_common.folderview_vscrollbar_policy); + gtk_widget_set_size_request(scrolledwin, + prefs_common.folderview_width, + prefs_common.folderview_height); + + ctree = gtk_ctree_new_with_titles(N_FOLDER_COLS, COL_FOLDER, titles); + gtk_container_add(GTK_CONTAINER(scrolledwin), ctree); + gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE); + gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_NEW, + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_UNREAD, + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_TOTAL, + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_width(GTK_CLIST(ctree), COL_FOLDER, + prefs_common.folder_col_folder); + gtk_clist_set_column_width(GTK_CLIST(ctree), COL_NEW, + prefs_common.folder_col_new); + gtk_clist_set_column_width(GTK_CLIST(ctree), COL_UNREAD, + prefs_common.folder_col_unread); + gtk_clist_set_column_width(GTK_CLIST(ctree), COL_TOTAL, + prefs_common.folder_col_total); + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED); + gtk_ctree_set_expander_style(GTK_CTREE(ctree), + GTK_CTREE_EXPANDER_SQUARE); + gtk_ctree_set_indent(GTK_CTREE(ctree), CTREE_INDENT); + gtk_clist_set_compare_func(GTK_CLIST(ctree), folderview_clist_compare); + + /* don't let title buttons take key focus */ + for (i = 0; i < N_FOLDER_COLS; i++) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button, + GTK_CAN_FOCUS); + + /* popup menu */ + n_entries = sizeof(folderview_mail_popup_entries) / + sizeof(folderview_mail_popup_entries[0]); + mail_popup = menu_create_items(folderview_mail_popup_entries, + n_entries, + "", &mail_factory, + folderview); + n_entries = sizeof(folderview_imap_popup_entries) / + sizeof(folderview_imap_popup_entries[0]); + imap_popup = menu_create_items(folderview_imap_popup_entries, + n_entries, + "", &imap_factory, + folderview); + n_entries = sizeof(folderview_news_popup_entries) / + sizeof(folderview_news_popup_entries[0]); + news_popup = menu_create_items(folderview_news_popup_entries, + n_entries, + "", &news_factory, + folderview); + + g_signal_connect(G_OBJECT(ctree), "key_press_event", + G_CALLBACK(folderview_key_pressed), + folderview); + g_signal_connect(G_OBJECT(ctree), "button_press_event", + G_CALLBACK(folderview_button_pressed), + folderview); + g_signal_connect(G_OBJECT(ctree), "button_release_event", + G_CALLBACK(folderview_button_released), + folderview); + g_signal_connect(G_OBJECT(ctree), "tree_select_row", + G_CALLBACK(folderview_selected), folderview); + + g_signal_connect_after(G_OBJECT(ctree), "tree_expand", + G_CALLBACK(folderview_tree_expanded), + folderview); + g_signal_connect_after(G_OBJECT(ctree), "tree_collapse", + G_CALLBACK(folderview_tree_collapsed), + folderview); + + g_signal_connect(G_OBJECT(ctree), "resize_column", + G_CALLBACK(folderview_col_resized), folderview); + + g_signal_connect(G_OBJECT(mail_popup), "selection_done", + G_CALLBACK(folderview_popup_close), folderview); + g_signal_connect(G_OBJECT(imap_popup), "selection_done", + G_CALLBACK(folderview_popup_close), folderview); + g_signal_connect(G_OBJECT(news_popup), "selection_done", + G_CALLBACK(folderview_popup_close), folderview); + + /* drop callback */ + gtk_drag_dest_set(ctree, GTK_DEST_DEFAULT_ALL & + ~GTK_DEST_DEFAULT_HIGHLIGHT, + summary_drag_types, 1, + GDK_ACTION_MOVE | GDK_ACTION_COPY); + g_signal_connect(G_OBJECT(ctree), "drag_motion", + G_CALLBACK(folderview_drag_motion_cb), folderview); + g_signal_connect(G_OBJECT(ctree), "drag_leave", + G_CALLBACK(folderview_drag_leave_cb), folderview); + g_signal_connect(G_OBJECT(ctree), "drag_data_received", + G_CALLBACK(folderview_drag_received_cb), folderview); + + folderview->scrolledwin = scrolledwin; + folderview->ctree = ctree; + folderview->mail_popup = mail_popup; + folderview->mail_factory = mail_factory; + folderview->imap_popup = imap_popup; + folderview->imap_factory = imap_factory; + folderview->news_popup = news_popup; + folderview->news_factory = news_factory; + + gtk_widget_show_all(scrolledwin); + + folderview_list = g_list_append(folderview_list, folderview); + + return folderview; +} + +void folderview_init(FolderView *folderview) +{ + GtkWidget *ctree = folderview->ctree; + + gtk_widget_realize(ctree); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_INBOX, &inboxxpm, &inboxxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_OUTBOX, + &outboxxpm, &outboxxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_DIR_CLOSE, + &folderxpm, &folderxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_DIR_OPEN, + &folderopenxpm, &folderopenxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_DIR_NOSELECT, + &foldernoselectxpm, &foldernoselectxpmmask); + stock_pixmap_gdk(ctree, STOCK_PIXMAP_TRASH, &trashxpm, &trashxpmmask); + + if (!bold_style) { + PangoFontDescription *font_desc; + bold_style = gtk_style_copy(gtk_widget_get_style(ctree)); + font_desc = pango_font_description_from_string + (prefs_common.boldfont); + if (font_desc) { + if (bold_style->font_desc) + pango_font_description_free + (bold_style->font_desc); + bold_style->font_desc = font_desc; + } + bold_color_style = gtk_style_copy(bold_style); + bold_color_style->fg[GTK_STATE_NORMAL] = folderview->color_new; + } +} + +void folderview_set(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + MainWindow *mainwin = folderview->mainwin; + + debug_print(_("Setting folder info...\n")); + STATUSBAR_PUSH(mainwin, _("Setting folder info...")); + + main_window_cursor_wait(mainwin); + + folderview->selected = NULL; + folderview->opened = NULL; + + gtk_clist_freeze(GTK_CLIST(ctree)); + gtk_clist_clear(GTK_CLIST(ctree)); + gtk_clist_thaw(GTK_CLIST(ctree)); + gtk_clist_freeze(GTK_CLIST(ctree)); + + folderview_set_folders(folderview); + + gtk_clist_thaw(GTK_CLIST(ctree)); + main_window_cursor_normal(mainwin); + STATUSBAR_POP(mainwin); +} + +void folderview_set_all(void) +{ + GList *list; + + for (list = folderview_list; list != NULL; list = list->next) + folderview_set((FolderView *)list->data); +} + +void folderview_select(FolderView *folderview, FolderItem *item) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *node; + + if (!item) return; + + node = gtk_ctree_find_by_row_data(ctree, NULL, item); + if (node) folderview_select_node(folderview, node); +} + +static void folderview_select_node(FolderView *folderview, GtkCTreeNode *node) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + + g_return_if_fail(node != NULL); + + folderview->open_folder = TRUE; + gtkut_ctree_set_focus_row(ctree, node); + gtk_ctree_select(ctree, node); + if (folderview->summaryview->folder_item && + folderview->summaryview->folder_item->total > 0) + gtk_widget_grab_focus(folderview->summaryview->ctree); + else + gtk_widget_grab_focus(folderview->ctree); + + gtkut_ctree_expand_parent_all(ctree, node); +} + +void folderview_unselect(FolderView *folderview) +{ + if (folderview->opened && !GTK_CTREE_ROW(folderview->opened)->children) + gtk_ctree_collapse + (GTK_CTREE(folderview->ctree), folderview->opened); + + folderview->selected = folderview->opened = NULL; +} + +static GtkCTreeNode *folderview_find_next_unread(GtkCTree *ctree, + GtkCTreeNode *node) +{ + FolderItem *item; + + if (node) + node = gtkut_ctree_node_next(ctree, node); + else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + for (; node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + item = gtk_ctree_node_get_row_data(ctree, node); + if (item && item->unread > 0 && item->stype != F_TRASH) + return node; + } + + return NULL; +} + +void folderview_select_next_unread(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *node = NULL; + + if ((node = folderview_find_next_unread(ctree, folderview->opened)) + != NULL) { + folderview_select_node(folderview, node); + return; + } + + if (!folderview->opened || + folderview->opened == GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list)) + return; + /* search again from the first node */ + if ((node = folderview_find_next_unread(ctree, NULL)) != NULL) + folderview_select_node(folderview, node); +} + +FolderItem *folderview_get_selected_item(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + + if (!folderview->selected) return NULL; + return gtk_ctree_node_get_row_data(ctree, folderview->selected); +} + +void folderview_update_msg_num(FolderView *folderview, GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + static GtkCTreeNode *prev_row = NULL; + FolderItem *item; + gint new, unread, total; + gchar *new_str, *unread_str, *total_str; + + if (!row) return; + + item = gtk_ctree_node_get_row_data(ctree, row); + if (!item) return; + + gtk_ctree_node_get_text(ctree, row, COL_NEW, &new_str); + gtk_ctree_node_get_text(ctree, row, COL_UNREAD, &unread_str); + gtk_ctree_node_get_text(ctree, row, COL_TOTAL, &total_str); + new = atoi(new_str); + unread = atoi(unread_str); + total = atoi(total_str); + + if (prev_row == row && + item->new == new && + item->unread == unread && + item->total == total) + return; + + prev_row = row; + + folderview_update_node(folderview, row); +} + +void folderview_append_item(FolderItem *item) +{ + FolderItem *parent; + GList *list; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(item->parent != NULL); + + parent = item->parent; + + for (list = folderview_list; list != NULL; list = list->next) { + FolderView *folderview = (FolderView *)list->data; + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *node, *child; + + node = gtk_ctree_find_by_row_data(ctree, NULL, parent); + if (node) { + child = gtk_ctree_find_by_row_data(ctree, node, item); + if (!child) { + gchar *text[N_FOLDER_COLS] = + {NULL, "0", "0", "0"}; + + gtk_clist_freeze(GTK_CLIST(ctree)); + + text[COL_FOLDER] = item->name; + child = gtk_ctree_insert_node + (ctree, node, NULL, text, + FOLDER_SPACING, + folderxpm, folderxpmmask, + folderopenxpm, folderopenxpmmask, + FALSE, FALSE); + gtk_ctree_node_set_row_data(ctree, child, item); + gtk_ctree_expand(ctree, node); + folderview_update_node(folderview, child); + folderview_sort_folders(folderview, node, + item->folder); + + gtk_clist_thaw(GTK_CLIST(ctree)); + } + } + } +} + +static void folderview_set_folders(FolderView *folderview) +{ + GList *list; + + list = folder_get_list(); + + for (; list != NULL; list = list->next) + folderview_append_folder(folderview, FOLDER(list->data)); +} + +static void folderview_scan_tree_func(Folder *folder, FolderItem *item, + gpointer data) +{ + GList *list; + gchar *rootpath; + + if (FOLDER_IS_LOCAL(folder)) + rootpath = LOCAL_FOLDER(folder)->rootpath; + else if (FOLDER_TYPE(folder) == F_IMAP && folder->account && + folder->account->recv_server) + rootpath = folder->account->recv_server; + else if (FOLDER_TYPE(folder) == F_NEWS && folder->account && + folder->account->nntp_server) + rootpath = folder->account->nntp_server; + else + return; + + for (list = folderview_list; list != NULL; list = list->next) { + FolderView *folderview = (FolderView *)list->data; + MainWindow *mainwin = folderview->mainwin; + gchar *str; + + if (item->path) + str = g_strdup_printf(_("Scanning folder %s%c%s ..."), + rootpath, G_DIR_SEPARATOR, + item->path); + else + str = g_strdup_printf(_("Scanning folder %s ..."), + rootpath); + + STATUSBAR_PUSH(mainwin, str); + STATUSBAR_POP(mainwin); + g_free(str); + } +} + +static GtkWidget *label_window_create(const gchar *str) +{ + GtkWidget *window; + GtkWidget *label; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 380, 60); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_title(GTK_WINDOW(window), str); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); + manage_window_set_transient(GTK_WINDOW(window)); + + label = gtk_label_new(str); + gtk_container_add(GTK_CONTAINER(window), label); + gtk_widget_show(label); + + gtk_widget_show_now(window); + + return window; +} + +static void folderview_rescan_tree(FolderView *folderview, Folder *folder) +{ + GtkWidget *window; + AlertValue avalue; + + g_return_if_fail(folder != NULL); + + if (!folder->klass->scan_tree) return; + + avalue = alertpanel + (_("Rebuild folder tree"), + _("The folder tree will be rebuilt. Continue?"), + _("Yes"), _("No"), NULL); + if (avalue != G_ALERTDEFAULT) return; + + if (!FOLDER_IS_LOCAL(folder) && + !main_window_toggle_online_if_offline(folderview->mainwin)) + return; + + inc_lock(); + window = label_window_create(_("Rebuilding folder tree...")); + + summary_show(folderview->summaryview, NULL, FALSE); + + folder_set_ui_func(folder, folderview_scan_tree_func, NULL); + if (folder->klass->scan_tree(folder) < 0) + alertpanel_error(_("Rebuilding of the folder tree failed.")); + folder_set_ui_func(folder, NULL, NULL); + + folder_write_list(); + folderview_set_all(); + statusbar_pop_all(); + + gtk_widget_destroy(window); + inc_unlock(); +} + +#if 0 +void folderview_rescan_all(void) +{ + GList *list; + GtkWidget *window; + + inc_lock(); + window = label_window_create(_("Rebuilding all folder trees...")); + + list = folder_get_list(); + for (; list != NULL; list = list->next) { + Folder *folder = list->data; + + if (!folder->klass->scan_tree) continue; + folder_set_ui_func(folder, folderview_scan_tree_func, NULL); + folder->klass->scan_tree(folder); + folder_set_ui_func(folder, NULL, NULL); + } + + folder_write_list(); + folderview_set_all(); + gtk_widget_destroy(window); + inc_unlock(); +} +#endif + +void folderview_check_new(Folder *folder) +{ + GList *list; + FolderItem *item; + FolderView *folderview; + GtkCTree *ctree; + GtkCTreeNode *node; + + for (list = folderview_list; list != NULL; list = list->next) { + folderview = (FolderView *)list->data; + ctree = GTK_CTREE(folderview->ctree); + + if (folder && !FOLDER_IS_LOCAL(folder)) { + if (!main_window_toggle_online_if_offline + (folderview->mainwin)) + return; + } + + inc_lock(); + main_window_lock(folderview->mainwin); + gtk_widget_set_sensitive(folderview->ctree, FALSE); + + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + item = gtk_ctree_node_get_row_data(ctree, node); + if (!item || !item->path || !item->folder) continue; + if (item->no_select) continue; + if (folder && folder != item->folder) continue; + if (!folder && !FOLDER_IS_LOCAL(item->folder)) continue; + + folderview_scan_tree_func(item->folder, item, NULL); + if (folder_item_scan(item) < 0) { + if (folder && !FOLDER_IS_LOCAL(folder)) + break; + } + folderview_update_node(folderview, node); + } + + gtk_widget_set_sensitive(folderview->ctree, TRUE); + main_window_unlock(folderview->mainwin); + inc_unlock(); + statusbar_pop_all(); + } + + folder_write_list(); +} + +void folderview_check_new_all(void) +{ + GList *list; + GtkWidget *window; + FolderView *folderview; + + folderview = (FolderView *)folderview_list->data; + + inc_lock(); + main_window_lock(folderview->mainwin); + window = label_window_create + (_("Checking for new messages in all folders...")); + + list = folder_get_list(); + for (; list != NULL; list = list->next) { + Folder *folder = list->data; + + folderview_check_new(folder); + } + + gtk_widget_destroy(window); + main_window_unlock(folderview->mainwin); + inc_unlock(); +} + +static gboolean folderview_search_new_recursive(GtkCTree *ctree, + GtkCTreeNode *node) +{ + FolderItem *item; + + if (node) { + item = gtk_ctree_node_get_row_data(ctree, node); + if (item) { + if (item->new > 0 || + (item->stype == F_QUEUE && item->total > 0)) + return TRUE; + } + node = GTK_CTREE_ROW(node)->children; + } else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + while (node) { + if (folderview_search_new_recursive(ctree, node) == TRUE) + return TRUE; + node = GTK_CTREE_ROW(node)->sibling; + } + + return FALSE; +} + +static gboolean folderview_have_new_children(FolderView *folderview, + GtkCTreeNode *node) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + + if (!node) + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + if (!node) + return FALSE; + + node = GTK_CTREE_ROW(node)->children; + + while (node) { + if (folderview_search_new_recursive(ctree, node) == TRUE) + return TRUE; + node = GTK_CTREE_ROW(node)->sibling; + } + + return FALSE; +} + +static gboolean folderview_search_unread_recursive(GtkCTree *ctree, + GtkCTreeNode *node) +{ + FolderItem *item; + + if (node) { + item = gtk_ctree_node_get_row_data(ctree, node); + if (item) { + if (item->unread > 0 || + (item->stype == F_QUEUE && item->total > 0)) + return TRUE; + } + node = GTK_CTREE_ROW(node)->children; + } else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + while (node) { + if (folderview_search_unread_recursive(ctree, node) == TRUE) + return TRUE; + node = GTK_CTREE_ROW(node)->sibling; + } + + return FALSE; +} + +static gboolean folderview_have_unread_children(FolderView *folderview, + GtkCTreeNode *node) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + + if (!node) + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + if (!node) + return FALSE; + + node = GTK_CTREE_ROW(node)->children; + + while (node) { + if (folderview_search_unread_recursive(ctree, node) == TRUE) + return TRUE; + node = GTK_CTREE_ROW(node)->sibling; + } + + return FALSE; +} + +static void folderview_update_node(FolderView *folderview, GtkCTreeNode *node) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkStyle *style = NULL; + FolderItem *item; + GdkPixmap *xpm, *openxpm; + GdkBitmap *mask, *openmask; + gchar *name; + gchar *str; + gboolean add_unread_mark; + gboolean use_bold, use_color; + + item = gtk_ctree_node_get_row_data(ctree, node); + g_return_if_fail(item != NULL); + + switch (item->stype) { + case F_INBOX: + xpm = openxpm = inboxxpm; + mask = openmask = inboxxpmmask; + name = g_strdup(FOLDER_IS_LOCAL(item->folder) && + !strcmp2(item->name, INBOX_DIR) ? _("Inbox") : + item->name); + break; + case F_OUTBOX: + xpm = openxpm = outboxxpm; + mask = openmask = outboxxpmmask; + name = g_strdup(FOLDER_IS_LOCAL(item->folder) && + !strcmp2(item->name, OUTBOX_DIR) ? _("Sent") : + item->name); + break; + case F_QUEUE: + xpm = openxpm = outboxxpm; + mask = openmask = outboxxpmmask; + name = g_strdup(FOLDER_IS_LOCAL(item->folder) && + !strcmp2(item->name, QUEUE_DIR) ? _("Queue") : + item->name); + break; + case F_TRASH: + xpm = openxpm = trashxpm; + mask = openmask = trashxpmmask; + name = g_strdup(FOLDER_IS_LOCAL(item->folder) && + !strcmp2(item->name, TRASH_DIR) ? _("Trash") : + item->name); + break; + case F_DRAFT: + xpm = folderxpm; + mask = folderxpmmask; + openxpm = folderopenxpm; + openmask = folderopenxpmmask; + name = g_strdup(FOLDER_IS_LOCAL(item->folder) && + !strcmp2(item->name, DRAFT_DIR) ? _("Drafts") : + item->name); + break; + default: + if (item->no_select) { + xpm = openxpm = foldernoselectxpm; + mask = openmask = foldernoselectxpmmask; + } else { + xpm = folderxpm; + mask = folderxpmmask; + openxpm = folderopenxpm; + openmask = folderopenxpmmask; + } + + if (!item->parent) { + switch (FOLDER_TYPE(item->folder)) { + case F_MH: + name = " (MH)"; break; + case F_IMAP: + name = " (IMAP4)"; break; + case F_NEWS: + name = " (News)"; break; + default: + name = ""; + } + name = g_strconcat(item->name, name, NULL); + } else { + if (FOLDER_TYPE(item->folder) == F_NEWS && + item->path && + !strcmp2(item->name, item->path)) + name = get_abbrev_newsgroup_name + (item->path, + prefs_common.ng_abbrev_len); + else + name = g_strdup(item->name); + } + } + + if (!GTK_CTREE_ROW(node)->expanded && + folderview_have_unread_children(folderview, node)) + add_unread_mark = TRUE; + else + add_unread_mark = FALSE; + + if (item->stype == F_QUEUE && item->total > 0 && + prefs_common.display_folder_unread) { + str = g_strdup_printf("%s (%d%s)", name, item->total, + add_unread_mark ? "+" : ""); + gtk_ctree_set_node_info(ctree, node, str, FOLDER_SPACING, + xpm, mask, openxpm, openmask, + FALSE, GTK_CTREE_ROW(node)->expanded); + g_free(str); + } else if ((item->unread > 0 || add_unread_mark) && + prefs_common.display_folder_unread) { + + if (item->unread > 0) + str = g_strdup_printf("%s (%d%s)", name, item->unread, + add_unread_mark ? "+" : ""); + else + str = g_strdup_printf("%s (+)", name); + gtk_ctree_set_node_info(ctree, node, str, FOLDER_SPACING, + xpm, mask, openxpm, openmask, + FALSE, GTK_CTREE_ROW(node)->expanded); + g_free(str); + } else + gtk_ctree_set_node_info(ctree, node, name, FOLDER_SPACING, + xpm, mask, openxpm, openmask, + FALSE, GTK_CTREE_ROW(node)->expanded); + g_free(name); + + if (!item->parent) { + gtk_ctree_node_set_text(ctree, node, COL_NEW, "-"); + gtk_ctree_node_set_text(ctree, node, COL_UNREAD, "-"); + gtk_ctree_node_set_text(ctree, node, COL_TOTAL, "-"); + } else { + gtk_ctree_node_set_text(ctree, node, COL_NEW, itos(item->new)); + gtk_ctree_node_set_text(ctree, node, COL_UNREAD, itos(item->unread)); + gtk_ctree_node_set_text(ctree, node, COL_TOTAL, itos(item->total)); + } + + if (item->stype == F_OUTBOX || item->stype == F_DRAFT || + item->stype == F_TRASH) { + use_bold = use_color = FALSE; + } else if (item->stype == F_QUEUE) { + /* highlight queue folder if there are any messages */ + use_bold = use_color = (item->total > 0); + } else { + /* if unread messages exist, print with bold font */ + use_bold = (item->unread > 0) || add_unread_mark; + /* if new messages exist, print with colored letter */ + use_color = + (item->new > 0) || + (add_unread_mark && + folderview_have_new_children(folderview, node)); + } + + gtk_ctree_node_set_foreground(ctree, node, NULL); + + if (item->no_select) + gtk_ctree_node_set_foreground(ctree, node, + &folderview->color_noselect); + else if (use_bold && use_color) + style = bold_color_style; + else if (use_bold) + style = bold_style; + else if (use_color) + gtk_ctree_node_set_foreground(ctree, node, + &folderview->color_new); + + gtk_ctree_node_set_row_style(ctree, node, style); + + item->updated = FALSE; + + if ((node = gtkut_ctree_find_collapsed_parent(ctree, node)) != NULL) + folderview_update_node(folderview, node); +} + +void folderview_update_item(FolderItem *item, gboolean update_summary) +{ + GList *list; + FolderView *folderview; + GtkCTree *ctree; + GtkCTreeNode *node; + + g_return_if_fail(item != NULL); + + for (list = folderview_list; list != NULL; list = list->next) { + folderview = (FolderView *)list->data; + ctree = GTK_CTREE(folderview->ctree); + + node = gtk_ctree_find_by_row_data(ctree, NULL, item); + if (node) { + folderview_update_node(folderview, node); + if (update_summary && folderview->opened == node) + summary_show(folderview->summaryview, + item, FALSE); + } + } +} + +static void folderview_update_item_foreach_func(gpointer key, gpointer val, + gpointer data) +{ + folderview_update_item((FolderItem *)key, GPOINTER_TO_INT(data)); +} + +void folderview_update_item_foreach(GHashTable *table, gboolean update_summary) +{ + g_hash_table_foreach(table, folderview_update_item_foreach_func, + GINT_TO_POINTER(update_summary)); +} + +static gboolean folderview_update_all_updated_func(GNode *node, gpointer data) +{ + FolderItem *item; + + item = FOLDER_ITEM(node->data); + if (item->updated) { + debug_print("folderview_update_all_updated(): '%s' is updated\n", item->path); + folderview_update_item(item, GPOINTER_TO_INT(data)); + } + + return FALSE; +} + +void folderview_update_all_updated(gboolean update_summary) +{ + GList *list; + Folder *folder; + + for (list = folder_get_list(); list != NULL; list = list->next) { + folder = (Folder *)list->data; + g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + folderview_update_all_updated_func, + GINT_TO_POINTER(update_summary)); + } +} + +static gboolean folderview_gnode_func(GtkCTree *ctree, guint depth, + GNode *gnode, GtkCTreeNode *cnode, + gpointer data) +{ + FolderView *folderview = (FolderView *)data; + FolderItem *item = FOLDER_ITEM(gnode->data); + + g_return_val_if_fail(item != NULL, FALSE); + + gtk_ctree_node_set_row_data(ctree, cnode, item); + folderview_update_node(folderview, cnode); + + return TRUE; +} + +static void folderview_expand_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + FolderView *folderview = (FolderView *)data; + FolderItem *item; + + if (GTK_CTREE_ROW(node)->children) { + item = gtk_ctree_node_get_row_data(ctree, node); + g_return_if_fail(item != NULL); + + if (!item->collapsed) + gtk_ctree_expand(ctree, node); + else + folderview_update_node(folderview, node); + } +} + +#define SET_SPECIAL_FOLDER(ctree, item) \ +{ \ + if (item) { \ + GtkCTreeNode *node, *parent, *sibling; \ + \ + node = gtk_ctree_find_by_row_data(ctree, root, item); \ + if (!node) \ + g_warning("%s not found.\n", item->path); \ + else { \ + parent = GTK_CTREE_ROW(node)->parent; \ + if (prev && parent == GTK_CTREE_ROW(prev)->parent) \ + sibling = GTK_CTREE_ROW(prev)->sibling; \ + else \ + sibling = GTK_CTREE_ROW(parent)->children; \ + while (sibling) { \ + FolderItem *tmp; \ + \ + tmp = gtk_ctree_node_get_row_data \ + (ctree, sibling); \ + if (tmp->stype != F_NORMAL) \ + sibling = GTK_CTREE_ROW(sibling)->sibling; \ + else \ + break; \ + } \ + if (node != sibling) \ + gtk_ctree_move(ctree, node, parent, sibling); \ + } \ + \ + prev = node; \ + } \ +} + +static void folderview_sort_folders(FolderView *folderview, GtkCTreeNode *root, + Folder *folder) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *prev = NULL; + + gtk_ctree_sort_recursive(ctree, root); + + if (GTK_CTREE_ROW(root)->parent) return; + + SET_SPECIAL_FOLDER(ctree, folder->inbox); + SET_SPECIAL_FOLDER(ctree, folder->outbox); + SET_SPECIAL_FOLDER(ctree, folder->draft); + SET_SPECIAL_FOLDER(ctree, folder->queue); + SET_SPECIAL_FOLDER(ctree, folder->trash); +} + +static void folderview_append_folder(FolderView *folderview, Folder *folder) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *root; + + g_return_if_fail(folder != NULL); + + root = gtk_ctree_insert_gnode(ctree, NULL, NULL, folder->node, + folderview_gnode_func, folderview); + gtk_ctree_pre_recursive(ctree, root, folderview_expand_func, + folderview); + folderview_sort_folders(folderview, root, folder); +} + +void folderview_new_folder(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + switch (FOLDER_TYPE(item->folder)) { + case F_MH: + case F_MBOX: + case F_MAILDIR: + case F_IMAP: + folderview_new_folder_cb(folderview, 0, NULL); + break; + case F_NEWS: + folderview_new_news_group_cb(folderview, 0, NULL); + break; + default: + break; + } +} + +void folderview_rename_folder(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (!item->path) return; + if (item->stype != F_NORMAL) return; + + switch (FOLDER_TYPE(item->folder)) { + case F_MH: + case F_MBOX: + case F_MAILDIR: + case F_IMAP: + folderview_rename_folder_cb(folderview, 0, NULL); + break; + case F_NEWS: + default: + break; + } +} + +void folderview_delete_folder(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (!item->path) return; + if (item->stype != F_NORMAL) return; + + switch (FOLDER_TYPE(item->folder)) { + case F_MH: + case F_MBOX: + case F_MAILDIR: + case F_IMAP: + folderview_delete_folder_cb(folderview, 0, NULL); + break; + case F_NEWS: + folderview_rm_news_group_cb(folderview, 0, NULL); + break; + default: + break; + } +} + +void folderview_check_new_selected(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (item->parent != NULL) return; + + folderview_check_new(item->folder); +} + +void folderview_remove_mailbox(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (item->parent != NULL) return; + + switch (FOLDER_TYPE(item->folder)) { + case F_MH: + case F_MBOX: + case F_MAILDIR: + folderview_remove_mailbox_cb(folderview, 0, NULL); + break; + case F_IMAP: + folderview_rm_imap_server_cb(folderview, 0, NULL); + break; + case F_NEWS: + folderview_rm_news_server_cb(folderview, 0, NULL); + break; + default: + break; + } +} + +void folderview_rebuild_tree(FolderView *folderview) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (item->parent != NULL) return; + + folderview_rescan_tree(folderview, item->folder); +} + + +/* callback functions */ + +static gboolean folderview_button_pressed(GtkWidget *ctree, + GdkEventButton *event, + FolderView *folderview) +{ + GtkCList *clist = GTK_CLIST(ctree); + gint prev_row = -1, row = -1, column = -1; + FolderItem *item; + Folder *folder; + GtkWidget *popup; + gboolean new_folder = FALSE; + gboolean rename_folder = FALSE; + gboolean delete_folder = FALSE; + gboolean empty_trash = FALSE; + gboolean download_msg = FALSE; + gboolean update_tree = FALSE; + gboolean rescan_tree = FALSE; + gboolean remove_tree = FALSE; + gboolean search_folder = FALSE; + gboolean folder_property = FALSE; + + if (!event) return FALSE; + + if (event->button == 1) { + folderview->open_folder = TRUE; + return FALSE; + } + + if (event->button == 2 || event->button == 3) { + /* right clicked */ + if (clist->selection) { + GtkCTreeNode *node; + + node = GTK_CTREE_NODE(clist->selection->data); + if (node) + prev_row = gtkut_ctree_get_nth_from_node + (GTK_CTREE(ctree), node); + } + + if (!gtk_clist_get_selection_info(clist, event->x, event->y, + &row, &column)) + return FALSE; + if (prev_row != row) { + gtk_clist_unselect_all(clist); + if (event->button == 2) + folderview_select_node + (folderview, + gtk_ctree_node_nth(GTK_CTREE(ctree), + row)); + else + gtk_clist_select_row(clist, row, column); + } + } + + if (event->button != 3) return FALSE; + + item = gtk_clist_get_row_data(clist, row); + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(item->folder != NULL, FALSE); + folder = item->folder; + + if (folderview->mainwin->lock_count == 0) { + new_folder = TRUE; + if (item->parent == NULL) { + update_tree = remove_tree = TRUE; + if (folder->account) + folder_property = TRUE; + } else { + folder_property = TRUE; + if (folderview->selected == folderview->opened) + search_folder = TRUE; + } + if (FOLDER_IS_LOCAL(folder) || FOLDER_TYPE(folder) == F_IMAP) { + if (item->parent == NULL) + update_tree = rescan_tree = TRUE; + else if (item->stype == F_NORMAL) + rename_folder = delete_folder = TRUE; + else if (item->stype == F_TRASH) + empty_trash = TRUE; + } else if (FOLDER_TYPE(folder) == F_NEWS) { + if (item->parent != NULL) + delete_folder = TRUE; + } + if (FOLDER_TYPE(folder) == F_IMAP || + FOLDER_TYPE(folder) == F_NEWS) { + if (item->parent != NULL && item->no_select == FALSE) + download_msg = TRUE; + } + } + +#define SET_SENS(factory, name, sens) \ + menu_set_sensitive(folderview->factory, name, sens) + + if (FOLDER_IS_LOCAL(folder)) { + popup = folderview->mail_popup; + menu_set_insensitive_all(GTK_MENU_SHELL(popup)); + SET_SENS(mail_factory, "/Create new folder...", new_folder); + SET_SENS(mail_factory, "/Rename folder...", rename_folder); + SET_SENS(mail_factory, "/Delete folder", delete_folder); + SET_SENS(mail_factory, "/Empty trash", empty_trash); + SET_SENS(mail_factory, "/Check for new messages", update_tree); + SET_SENS(mail_factory, "/Rebuild folder tree", rescan_tree); + SET_SENS(mail_factory, "/Search messages...", search_folder); + SET_SENS(mail_factory, "/Properties...", folder_property); + } else if (FOLDER_TYPE(folder) == F_IMAP) { + popup = folderview->imap_popup; + menu_set_insensitive_all(GTK_MENU_SHELL(popup)); + SET_SENS(imap_factory, "/Create new folder...", new_folder); + SET_SENS(imap_factory, "/Rename folder...", rename_folder); + SET_SENS(imap_factory, "/Delete folder", delete_folder); + SET_SENS(imap_factory, "/Empty trash", empty_trash); + SET_SENS(imap_factory, "/Download", download_msg); + SET_SENS(imap_factory, "/Check for new messages", update_tree); + SET_SENS(imap_factory, "/Rebuild folder tree", rescan_tree); + SET_SENS(imap_factory, "/Search messages...", search_folder); + SET_SENS(imap_factory, "/Properties...", folder_property); + } else if (FOLDER_TYPE(folder) == F_NEWS) { + popup = folderview->news_popup; + menu_set_insensitive_all(GTK_MENU_SHELL(popup)); + SET_SENS(news_factory, "/Subscribe to newsgroup...", new_folder); + SET_SENS(news_factory, "/Remove newsgroup", delete_folder); + SET_SENS(news_factory, "/Download", download_msg); + SET_SENS(news_factory, "/Check for new messages", update_tree); + SET_SENS(news_factory, "/Search messages...", search_folder); + SET_SENS(news_factory, "/Properties...", folder_property); + } else + return FALSE; + +#undef SET_SENS + + gtk_menu_popup(GTK_MENU(popup), NULL, NULL, NULL, NULL, + event->button, event->time); + + return FALSE; +} + +static gboolean folderview_button_released(GtkWidget *ctree, + GdkEventButton *event, + FolderView *folderview) +{ + if (!event) return FALSE; + + if (event->button == 1 && folderview->open_folder == FALSE && + folderview->opened != NULL) { + gtkut_ctree_set_focus_row(GTK_CTREE(ctree), + folderview->opened); + gtk_ctree_select(GTK_CTREE(ctree), folderview->opened); + } + + return FALSE; +} + +static gboolean folderview_key_pressed(GtkWidget *widget, GdkEventKey *event, + FolderView *folderview) +{ + if (!event) return FALSE; + + switch (event->keyval) { + case GDK_Return: + if (folderview->selected) { + folderview_select_node(folderview, + folderview->selected); + } + break; + case GDK_space: + if (folderview->selected) { + if (folderview->opened == folderview->selected && + (!folderview->summaryview->folder_item || + folderview->summaryview->folder_item->total == 0)) + folderview_select_next_unread(folderview); + else + folderview_select_node(folderview, + folderview->selected); + } + break; + default: + break; + } + + return FALSE; +} + +static void folderview_selected(GtkCTree *ctree, GtkCTreeNode *row, + gint column, FolderView *folderview) +{ + static gboolean can_select = TRUE; /* exclusive lock */ + gboolean opened; + FolderItem *item; + + folderview->selected = row; + + main_window_set_menu_sensitive(folderview->mainwin); + + if (folderview->opened == row) { + folderview->open_folder = FALSE; + return; + } + + if (!can_select || summary_is_locked(folderview->summaryview)) { + gtkut_ctree_set_focus_row(ctree, folderview->opened); + gtk_ctree_select(ctree, folderview->opened); + return; + } + + if (!folderview->open_folder) return; + + item = gtk_ctree_node_get_row_data(ctree, row); + if (!item) return; + + can_select = FALSE; + + if (item->path) + debug_print(_("Folder %s is selected\n"), item->path); + + if (!GTK_CTREE_ROW(row)->children) + gtk_ctree_expand(ctree, row); + if (folderview->opened && + !GTK_CTREE_ROW(folderview->opened)->children) + gtk_ctree_collapse(ctree, folderview->opened); + + /* ungrab the mouse event */ + if (GTK_WIDGET_HAS_GRAB(ctree)) { + gtk_grab_remove(GTK_WIDGET(ctree)); + if (gdk_pointer_is_grabbed()) + gdk_pointer_ungrab(GDK_CURRENT_TIME); + } + + opened = summary_show(folderview->summaryview, item, FALSE); + + if (!opened) { + gtkut_ctree_set_focus_row(ctree, folderview->opened); + gtk_ctree_select(ctree, folderview->opened); + } else { + folderview->opened = row; + if (gtk_ctree_node_is_visible(ctree, row) + != GTK_VISIBILITY_FULL) + gtk_ctree_node_moveto(ctree, row, -1, 0.5, 0); + } + + folderview->open_folder = FALSE; + can_select = TRUE; +} + +static void folderview_tree_expanded(GtkCTree *ctree, GtkCTreeNode *node, + FolderView *folderview) +{ + FolderItem *item; + + item = gtk_ctree_node_get_row_data(ctree, node); + g_return_if_fail(item != NULL); + item->collapsed = FALSE; + folderview_update_node(folderview, node); +} + +static void folderview_tree_collapsed(GtkCTree *ctree, GtkCTreeNode *node, + FolderView *folderview) +{ + FolderItem *item; + + item = gtk_ctree_node_get_row_data(ctree, node); + g_return_if_fail(item != NULL); + item->collapsed= TRUE; + folderview_update_node(folderview, node); +} + +static void folderview_popup_close(GtkMenuShell *menu_shell, + FolderView *folderview) +{ + if (!folderview->opened) return; + + gtkut_ctree_set_focus_row(GTK_CTREE(folderview->ctree), + folderview->opened); + gtk_ctree_select(GTK_CTREE(folderview->ctree), folderview->opened); +} + +static void folderview_col_resized(GtkCList *clist, gint column, gint width, + FolderView *folderview) +{ + switch (column) { + case COL_FOLDER: + prefs_common.folder_col_folder = width; + break; + case COL_NEW: + prefs_common.folder_col_new = width; + break; + case COL_UNREAD: + prefs_common.folder_col_unread = width; + break; + case COL_TOTAL: + prefs_common.folder_col_total = width; + break; + default: + break; + } +} + +static void folderview_download_func(Folder *folder, FolderItem *item, + gpointer data) +{ + GList *list; + + for (list = folderview_list; list != NULL; list = list->next) { + FolderView *folderview = (FolderView *)list->data; + MainWindow *mainwin = folderview->mainwin; + gchar *str; + + str = g_strdup_printf + (_("Downloading messages in %s ..."), item->path); + main_window_progress_set(mainwin, + GPOINTER_TO_INT(data), item->total); + STATUSBAR_PUSH(mainwin, str); + STATUSBAR_POP(mainwin); + g_free(str); + } +} + +static void folderview_download_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + MainWindow *mainwin = folderview->mainwin; + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if (!main_window_toggle_online_if_offline(folderview->mainwin)) + return; + + main_window_cursor_wait(mainwin); + inc_lock(); + main_window_lock(mainwin); + gtk_widget_set_sensitive(folderview->ctree, FALSE); + main_window_progress_on(mainwin); + GTK_EVENTS_FLUSH(); + folder_set_ui_func(item->folder, folderview_download_func, NULL); + if (folder_item_fetch_all_msg(item) < 0) { + gchar *name; + + name = trim_string(item->name, 32); + alertpanel_error(_("Error occurred while downloading messages in `%s'."), name); + g_free(name); + } + folder_set_ui_func(item->folder, NULL, NULL); + main_window_progress_off(mainwin); + gtk_widget_set_sensitive(folderview->ctree, TRUE); + main_window_unlock(mainwin); + inc_unlock(); + main_window_cursor_normal(mainwin); + statusbar_pop_all(); +} + +static void folderview_update_tree_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if (action == 0) + folderview_check_new(item->folder); + else + folderview_rescan_tree(folderview, item->folder); +} + +static void folderview_new_folder_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + FolderItem *new_item; + gchar *new_folder; + gchar *name; + gchar *p; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (FOLDER_TYPE(item->folder) == F_IMAP) + g_return_if_fail(item->folder->account != NULL); + + if (FOLDER_TYPE(item->folder) == F_IMAP) { + new_folder = input_dialog + (_("New folder"), + _("Input the name of new folder:\n" + "(if you want to create a folder to store subfolders,\n" + " append `/' at the end of the name)"), + _("NewFolder")); + } else { + new_folder = input_dialog(_("New folder"), + _("Input the name of new folder:"), + _("NewFolder")); + } + if (!new_folder) return; + AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); + + p = strchr(new_folder, G_DIR_SEPARATOR); + if ((p && FOLDER_TYPE(item->folder) != F_IMAP) || + (p && FOLDER_TYPE(item->folder) == F_IMAP && *(p + 1) != '\0')) { + alertpanel_error(_("`%c' can't be included in folder name."), + G_DIR_SEPARATOR); + return; + } + + name = trim_string(new_folder, 32); + AUTORELEASE_STR(name, {g_free(name); return;}); + + /* find whether the directory already exists */ + if (folder_find_child_item_by_name(item, new_folder)) { + alertpanel_error(_("The folder `%s' already exists."), name); + return; + } + + new_item = item->folder->klass->create_folder(item->folder, item, + new_folder); + if (!new_item) { + alertpanel_error(_("Can't create the folder `%s'."), name); + return; + } + + folderview_append_item(new_item); + folder_write_list(); +} + +static void folderview_rename_folder_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + gchar *new_folder; + gchar *name; + gchar *message; + gchar *old_path; + gchar *old_id; + gchar *new_id; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->path != NULL); + g_return_if_fail(item->folder != NULL); + + name = trim_string(item->name, 32); + message = g_strdup_printf(_("Input new name for `%s':"), name); + new_folder = input_dialog(_("Rename folder"), message, + g_basename(item->path)); + g_free(message); + g_free(name); + if (!new_folder) return; + AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); + + if (strchr(new_folder, G_DIR_SEPARATOR) != NULL) { + alertpanel_error(_("`%c' can't be included in folder name."), + G_DIR_SEPARATOR); + return; + } + + if (folder_find_child_item_by_name(item->parent, new_folder)) { + name = trim_string(new_folder, 32); + alertpanel_error(_("The folder `%s' already exists."), name); + g_free(name); + return; + } + + Xstrdup_a(old_path, item->path, {g_free(new_folder); return;}); + old_id = folder_item_get_identifier(item); + + if (item->folder->klass->rename_folder(item->folder, item, + new_folder) < 0) { + g_free(old_id); + return; + } + + if (folder_get_default_folder() == item->folder) + prefs_filter_rename_path(old_path, item->path); + new_id = folder_item_get_identifier(item); + prefs_filter_rename_path(old_id, new_id); + g_free(old_id); + g_free(new_id); + + gtk_clist_freeze(GTK_CLIST(ctree)); + + folderview_update_node(folderview, folderview->selected); + folderview_sort_folders(folderview, + GTK_CTREE_ROW(folderview->selected)->parent, + item->folder); + if (folderview->opened == folderview->selected || + gtk_ctree_is_ancestor(ctree, + folderview->selected, + folderview->opened)) { + GtkCTreeNode *node = folderview->opened; + folderview_unselect(folderview); + folderview_select_node(folderview, node); + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + + folder_write_list(); +} + +static void folderview_delete_folder_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + Folder *folder; + FolderItem *item; + gchar *message, *name; + AlertValue avalue; + gchar *old_path; + gchar *old_id; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->path != NULL); + g_return_if_fail(item->folder != NULL); + + folder = item->folder; + + name = trim_string(item->name, 32); + AUTORELEASE_STR(name, {g_free(name); return;}); + message = g_strdup_printf + (_("All folder(s) and message(s) under `%s' will be deleted.\n" + "Do you really want to delete?"), name); + avalue = alertpanel(_("Delete folder"), message, + _("Yes"), _("+No"), NULL); + g_free(message); + if (avalue != G_ALERTDEFAULT) return; + + Xstrdup_a(old_path, item->path, return); + old_id = folder_item_get_identifier(item); + + if (folderview->opened == folderview->selected || + gtk_ctree_is_ancestor(ctree, + folderview->selected, folderview->opened)) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + + if (folder->klass->remove_folder(folder, item) < 0) { + alertpanel_error(_("Can't remove the folder `%s'."), name); + g_free(old_id); + return; + } + + if (folder_get_default_folder() == folder) + prefs_filter_delete_path(old_path); + prefs_filter_delete_path(old_id); + g_free(old_id); + + gtk_ctree_remove_node(ctree, folderview->selected); + folder_write_list(); +} + +static void folderview_empty_trash_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + Folder *folder; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->path != NULL); + g_return_if_fail(item->folder != NULL); + + folder = item->folder; + + if (folder->trash != item) return; + if (item->stype != F_TRASH) return; + + if (alertpanel(_("Empty trash"), _("Empty all messages in trash?"), + _("Yes"), _("No"), NULL) != G_ALERTDEFAULT) + return; + + procmsg_empty_trash(folder->trash); + statusbar_pop_all(); + folderview_update_item(folder->trash, TRUE); + + if (folderview->opened == folderview->selected) + gtk_widget_grab_focus(folderview->ctree); +} + +static void folderview_remove_mailbox_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *node; + FolderItem *item; + gchar *name; + gchar *message; + AlertValue avalue; + + if (!folderview->selected) return; + node = folderview->selected; + item = gtk_ctree_node_get_row_data(ctree, node); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + if (item->parent) return; + + name = trim_string(item->folder->name, 32); + message = g_strdup_printf + (_("Really remove the mailbox `%s' ?\n" + "(The messages are NOT deleted from the disk)"), name); + avalue = alertpanel(_("Remove mailbox"), message, + _("Yes"), _("+No"), NULL); + g_free(message); + g_free(name); + if (avalue != G_ALERTDEFAULT) return; + + if (folderview->summaryview->folder_item && + folderview->summaryview->folder_item->folder == item->folder) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + folder_destroy(item->folder); + gtk_ctree_remove_node(ctree, node); + folder_write_list(); +} + +static void folderview_rm_imap_server_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + PrefsAccount *account; + gchar *name; + gchar *message; + AlertValue avalue; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP); + g_return_if_fail(item->folder->account != NULL); + + name = trim_string(item->folder->name, 32); + message = g_strdup_printf(_("Really delete IMAP4 account `%s'?"), name); + avalue = alertpanel(_("Delete IMAP4 account"), message, + _("Yes"), _("+No"), NULL); + g_free(message); + g_free(name); + + if (avalue != G_ALERTDEFAULT) return; + + if (folderview->summaryview->folder_item && + folderview->summaryview->folder_item->folder == item->folder) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + + account = item->folder->account; + folder_destroy(item->folder); + account_destroy(account); + gtk_ctree_remove_node(ctree, folderview->selected); + account_set_menu(); + main_window_reflect_prefs_all(); + folder_write_list(); +} + +static void folderview_new_news_group_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + GtkCTreeNode *servernode, *node; + Folder *folder; + FolderItem *item; + FolderItem *rootitem; + FolderItem *newitem; + GSList *new_subscr; + GSList *cur; + GNode *gnode; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + folder = item->folder; + g_return_if_fail(folder != NULL); + g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS); + g_return_if_fail(folder->account != NULL); + + if (GTK_CTREE_ROW(folderview->selected)->parent != NULL) + servernode = GTK_CTREE_ROW(folderview->selected)->parent; + else + servernode = folderview->selected; + + rootitem = gtk_ctree_node_get_row_data(ctree, servernode); + + new_subscr = grouplist_dialog(folder); + + /* remove unsubscribed newsgroups */ + for (gnode = folder->node->children; gnode != NULL; ) { + GNode *next = gnode->next; + + item = FOLDER_ITEM(gnode->data); + if (g_slist_find_custom(new_subscr, item->path, + (GCompareFunc)g_strcasecmp) != NULL) { + gnode = next; + continue; + } + + node = gtk_ctree_find_by_row_data(ctree, servernode, item); + if (!node) { + gnode = next; + continue; + } + + if (folderview->opened == node) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + + folder_item_remove(item); + gtk_ctree_remove_node(ctree, node); + + gnode = next; + } + + gtk_clist_freeze(GTK_CLIST(ctree)); + + /* add subscribed newsgroups */ + for (cur = new_subscr; cur != NULL; cur = cur->next) { + gchar *name = (gchar *)cur->data; + + if (folder_find_child_item_by_name(rootitem, name) != NULL) + continue; + + newitem = folder_item_new(name, name); + folder_item_append(rootitem, newitem); + folderview_append_item(newitem); + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + + slist_free_strings(new_subscr); + g_slist_free(new_subscr); + + folder_write_list(); +} + +static void folderview_rm_news_group_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + gchar *name; + gchar *message; + AlertValue avalue; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS); + g_return_if_fail(item->folder->account != NULL); + + name = trim_string_before(item->path, 32); + message = g_strdup_printf(_("Really delete newsgroup `%s'?"), name); + avalue = alertpanel(_("Delete newsgroup"), message, + _("Yes"), _("+No"), NULL); + g_free(message); + g_free(name); + if (avalue != G_ALERTDEFAULT) return; + + if (folderview->opened == folderview->selected) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + + folder_item_remove(item); + gtk_ctree_remove_node(ctree, folderview->selected); + folder_write_list(); +} + +static void folderview_rm_news_server_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + PrefsAccount *account; + gchar *name; + gchar *message; + AlertValue avalue; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS); + g_return_if_fail(item->folder->account != NULL); + + name = trim_string(item->folder->name, 32); + message = g_strdup_printf(_("Really delete news account `%s'?"), name); + avalue = alertpanel(_("Delete news account"), message, + _("Yes"), _("+No"), NULL); + g_free(message); + g_free(name); + + if (avalue != G_ALERTDEFAULT) return; + + if (folderview->summaryview->folder_item && + folderview->summaryview->folder_item->folder == item->folder) { + summary_clear_all(folderview->summaryview); + folderview->opened = NULL; + } + + account = item->folder->account; + folder_destroy(item->folder); + account_destroy(account); + gtk_ctree_remove_node(ctree, folderview->selected); + account_set_menu(); + main_window_reflect_prefs_all(); + folder_write_list(); +} + +static void folderview_search_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + summary_search(folderview->summaryview); +} + +static void folderview_property_cb(FolderView *folderview, guint action, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(folderview->ctree); + FolderItem *item; + + if (!folderview->selected) return; + + item = gtk_ctree_node_get_row_data(ctree, folderview->selected); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if (item->parent == NULL && item->folder->account) + account_open(item->folder->account); + else + prefs_folder_item_open(item); +} + +static void folderview_defer_expand_stop(FolderView *folderview) +{ + if (folderview->spring_timer > 0) { + gtk_timeout_remove(folderview->spring_timer); + folderview->spring_timer = 0; + } + folderview->spring_node = NULL; +} + +static gint folderview_defer_expand(gpointer data) +{ + FolderView *folderview = (FolderView *)data; + + if (folderview->spring_node) { + gtk_ctree_expand(GTK_CTREE(folderview->ctree), + folderview->spring_node); + } + folderview_defer_expand_stop(folderview); + + return FALSE; +} + +static gboolean folderview_drag_motion_cb(GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + FolderView *folderview) +{ + gint row, column; + FolderItem *item, *src_item; + GtkCTreeNode *node = NULL; + gboolean acceptable = FALSE; + + if (gtk_clist_get_selection_info + (GTK_CLIST(widget), x - 24, y - 24, &row, &column)) { + node = gtk_ctree_node_nth(GTK_CTREE(widget), row); + item = gtk_ctree_node_get_row_data(GTK_CTREE(widget), node); + src_item = folderview->summaryview->folder_item; + if (src_item && src_item != item) + acceptable = FOLDER_ITEM_CAN_ADD(item); + } + + if (node != folderview->spring_node) { + folderview_defer_expand_stop(folderview); + if (node && !GTK_CTREE_ROW(node)->expanded && + GTK_CTREE_ROW(node)->children) { + folderview->spring_timer = + gtk_timeout_add(1000, folderview_defer_expand, + folderview); + folderview->spring_node = node; + } + } + + if (acceptable) { + g_signal_handlers_block_by_func + (G_OBJECT(widget), + G_CALLBACK(folderview_selected), folderview); + gtk_ctree_select(GTK_CTREE(widget), node); + g_signal_handlers_unblock_by_func + (G_OBJECT(widget), + G_CALLBACK(folderview_selected), folderview); + if ((context->actions & GDK_ACTION_MOVE) != 0) + gdk_drag_status(context, GDK_ACTION_MOVE, time); + else if ((context->actions & GDK_ACTION_COPY) != 0) + gdk_drag_status(context, GDK_ACTION_COPY, time); + else if ((context->actions & GDK_ACTION_LINK) != 0) + gdk_drag_status(context, GDK_ACTION_LINK, time); + else + gdk_drag_status(context, 0, time); + } else { + gtk_ctree_select(GTK_CTREE(widget), folderview->opened); + gdk_drag_status(context, 0, time); + } + + return acceptable; +} + +static void folderview_drag_leave_cb(GtkWidget *widget, + GdkDragContext *context, + guint time, + FolderView *folderview) +{ + folderview_defer_expand_stop(folderview); + gtk_ctree_select(GTK_CTREE(widget), folderview->opened); +} + +static void folderview_drag_received_cb(GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + FolderView *folderview) +{ + gint row, column; + FolderItem *item, *src_item; + GtkCTreeNode *node; + + folderview_defer_expand_stop(folderview); + + if (gtk_clist_get_selection_info + (GTK_CLIST(widget), x - 24, y - 24, &row, &column) == 0) + return; + + node = gtk_ctree_node_nth(GTK_CTREE(widget), row); + item = gtk_ctree_node_get_row_data(GTK_CTREE(widget), node); + src_item = folderview->summaryview->folder_item; + if (FOLDER_ITEM_CAN_ADD(item) && src_item && src_item != item) { + if ((context->actions & GDK_ACTION_MOVE) != 0) { + summary_move_selected_to(folderview->summaryview, item); + gtk_drag_finish(context, TRUE, TRUE, time); + } else if ((context->actions & GDK_ACTION_COPY) != 0) { + summary_copy_selected_to(folderview->summaryview, item); + gtk_drag_finish(context, TRUE, TRUE, time); + } else + gtk_drag_finish(context, FALSE, FALSE, time); + } else + gtk_drag_finish(context, FALSE, FALSE, time); +} + +static gint folderview_clist_compare(GtkCList *clist, + gconstpointer ptr1, gconstpointer ptr2) +{ + FolderItem *item1 = ((GtkCListRow *)ptr1)->data; + FolderItem *item2 = ((GtkCListRow *)ptr2)->data; + + if (!item1->name) + return (item2->name != NULL); + if (!item2->name) + return -1; + + return g_strcasecmp(item1->name, item2->name); +} diff --git a/src/folderview.h b/src/folderview.h new file mode 100644 index 00000000..a56f4b39 --- /dev/null +++ b/src/folderview.h @@ -0,0 +1,96 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __FOLDERVIEW_H__ +#define __FOLDERVIEW_H__ + +#include +#include +#include + +#include "folder.h" + +typedef struct _FolderView FolderView; + +#include "mainwindow.h" +#include "summaryview.h" + +struct _FolderView +{ + GtkWidget *scrolledwin; + GtkWidget *ctree; + GtkWidget *mail_popup; + GtkWidget *imap_popup; + GtkWidget *news_popup; + + GtkItemFactory *mail_factory; + GtkItemFactory *imap_factory; + GtkItemFactory *news_factory; + + GtkCTreeNode *selected; + GtkCTreeNode *opened; + + GtkCTreeNode *spring_node; + guint spring_timer; + + gboolean open_folder; + + GdkColor color_new; + GdkColor color_noselect; + + MainWindow *mainwin; + SummaryView *summaryview; +}; + +FolderView *folderview_create (void); +void folderview_init (FolderView *folderview); + +void folderview_set (FolderView *folderview); +void folderview_set_all (void); + +void folderview_select (FolderView *folderview, + FolderItem *item); +void folderview_unselect (FolderView *folderview); +void folderview_select_next_unread (FolderView *folderview); + +FolderItem *folderview_get_selected_item(FolderView *folderview); + +void folderview_update_msg_num (FolderView *folderview, + GtkCTreeNode *row); + +void folderview_append_item (FolderItem *item); + +void folderview_check_new (Folder *folder); +void folderview_check_new_all (void); + +void folderview_update_item (FolderItem *item, + gboolean update_summary); +void folderview_update_item_foreach (GHashTable *table, + gboolean update_summary); +void folderview_update_all_updated (gboolean update_summary); + +void folderview_new_folder (FolderView *folderview); +void folderview_rename_folder (FolderView *folderview); +void folderview_delete_folder (FolderView *folderview); + +void folderview_check_new_selected (FolderView *folderview); +void folderview_remove_mailbox (FolderView *folderview); +void folderview_rebuild_tree (FolderView *folderview); + +#endif /* __FOLDERVIEW_H__ */ diff --git a/src/grouplistdialog.c b/src/grouplistdialog.c new file mode 100644 index 00000000..28977514 --- /dev/null +++ b/src/grouplistdialog.c @@ -0,0 +1,567 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "grouplistdialog.h" +#include "manage_window.h" +#include "gtkutils.h" +#include "utils.h" +#include "news.h" +#include "folder.h" +#include "alertpanel.h" +#include "recv.h" +#include "socket.h" + +#define GROUPLIST_DIALOG_WIDTH 450 +#define GROUPLIST_DIALOG_HEIGHT 400 +#define GROUPLIST_COL_NAME_WIDTH 250 + +static gboolean ack; +static gboolean locked; + +static GtkWidget *dialog; +static GtkWidget *entry; +static GtkWidget *ctree; +static GtkWidget *status_label; +static GtkWidget *ok_button; +static GSList *group_list; +static Folder *news_folder; + +static GSList *subscribed; + +static void grouplist_dialog_create (void); +static void grouplist_dialog_set_list (const gchar *pattern, + gboolean refresh); +static void grouplist_search (void); +static void grouplist_clear (void); +static gboolean grouplist_recv_func (SockInfo *sock, + gint count, + gint read_bytes, + gpointer data); + +static gint window_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static void ok_clicked (GtkWidget *widget, + gpointer data); +static void cancel_clicked (GtkWidget *widget, + gpointer data); +static void refresh_clicked (GtkWidget *widget, + gpointer data); +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void ctree_selected (GtkCTree *ctree, + GtkCTreeNode *node, + gint column, + gpointer data); +static void ctree_unselected (GtkCTree *ctree, + GtkCTreeNode *node, + gint column, + gpointer data); +static void entry_activated (GtkEditable *editable); +static void search_clicked (GtkWidget *widget, + gpointer data); + +GSList *grouplist_dialog(Folder *folder) +{ + GNode *node; + FolderItem *item; + + if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL; + + if (!dialog) + grouplist_dialog_create(); + + news_folder = folder; + + gtk_widget_show(dialog); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + manage_window_set_transient(GTK_WINDOW(dialog)); + gtk_widget_grab_focus(ok_button); + gtk_widget_grab_focus(ctree); + GTK_EVENTS_FLUSH(); + + subscribed = NULL; + for (node = folder->node->children; node != NULL; node = node->next) { + item = FOLDER_ITEM(node->data); + subscribed = g_slist_append(subscribed, g_strdup(item->path)); + } + + grouplist_dialog_set_list(NULL, TRUE); + + if (ack) gtk_main(); + + manage_window_focus_out(dialog, NULL, NULL); + gtk_widget_hide(dialog); + + if (!ack) { + slist_free_strings(subscribed); + g_slist_free(subscribed); + subscribed = NULL; + + for (node = folder->node->children; node != NULL; + node = node->next) { + item = FOLDER_ITEM(node->data); + subscribed = g_slist_append(subscribed, + g_strdup(item->path)); + } + } + + grouplist_clear(); + + return subscribed; +} + +static void grouplist_dialog_create(void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *msg_label; + GtkWidget *search_button; + GtkWidget *confirm_area; + GtkWidget *cancel_button; + GtkWidget *refresh_button; + GtkWidget *scrolledwin; + gchar *titles[3]; + gint i; + + dialog = gtk_dialog_new(); + gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, TRUE, FALSE); + gtk_widget_set_size_request(dialog, + GROUPLIST_DIALOG_WIDTH, + GROUPLIST_DIALOG_HEIGHT); + gtk_container_set_border_width + (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + gtk_window_set_title(GTK_WINDOW(dialog), _("Subscribe to newsgroup")); + g_signal_connect(G_OBJECT(dialog), "delete_event", + G_CALLBACK(window_deleted), NULL); + g_signal_connect(G_OBJECT(dialog), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(dialog); + + gtk_widget_realize(dialog); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + msg_label = gtk_label_new(_("Select newsgroups to subscribe.")); + gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + msg_label = gtk_label_new(_("Find groups:")); + gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0); + + entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); + g_signal_connect(G_OBJECT(entry), "activate", + G_CALLBACK(entry_activated), NULL); + + search_button = gtk_button_new_with_label(_(" Search ")); + gtk_box_pack_start(GTK_BOX(hbox), search_button, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(search_button), "clicked", + G_CALLBACK(search_clicked), NULL); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_box_pack_start(GTK_BOX (vbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + titles[0] = _("Newsgroup name"); + titles[1] = _("Messages"); + titles[2] = _("Type"); + ctree = gtk_ctree_new_with_titles(3, 0, titles); + gtk_container_add(GTK_CONTAINER(scrolledwin), ctree); + gtk_clist_set_column_width + (GTK_CLIST(ctree), 0, GROUPLIST_COL_NAME_WIDTH); + gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_MULTIPLE); + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED); + gtk_ctree_set_expander_style(GTK_CTREE(ctree), + GTK_CTREE_EXPANDER_SQUARE); + for (i = 0; i < 3; i++) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button, + GTK_CAN_FOCUS); + g_signal_connect(G_OBJECT(ctree), "tree_select_row", + G_CALLBACK(ctree_selected), NULL); + g_signal_connect(G_OBJECT(ctree), "tree_unselect_row", + G_CALLBACK(ctree_unselected), NULL); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + status_label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox), status_label, FALSE, FALSE, 0); + + gtkut_button_set_create(&confirm_area, + &ok_button, _("OK"), + &cancel_button, _("Cancel"), + &refresh_button, _("Refresh")); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), + confirm_area); + gtk_widget_grab_default(ok_button); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(ok_clicked), NULL); + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(cancel_clicked), NULL); + g_signal_connect(G_OBJECT(refresh_button), "clicked", + G_CALLBACK(refresh_clicked), NULL); + + gtk_widget_show_all(GTK_DIALOG(dialog)->vbox); +} + +static GHashTable *branch_node_table; + +static void grouplist_hash_init(void) +{ + branch_node_table = g_hash_table_new(g_str_hash, g_str_equal); +} + +static void grouplist_hash_done(void) +{ + hash_free_strings(branch_node_table); + g_hash_table_destroy(branch_node_table); +} + +static GtkCTreeNode *grouplist_hash_get_branch_node(const gchar *name) +{ + return g_hash_table_lookup(branch_node_table, name); +} + +static void grouplist_hash_set_branch_node(const gchar *name, + GtkCTreeNode *node) +{ + g_hash_table_insert(branch_node_table, g_strdup(name), node); +} + +static gchar *grouplist_get_parent_name(const gchar *name) +{ + gchar *p; + + p = strrchr(name, '.'); + if (!p) + return g_strdup(""); + return g_strndup(name, p - name); +} + +static GtkCTreeNode *grouplist_create_parent(const gchar *name, + const gchar *pattern) +{ + GtkCTreeNode *parent; + GtkCTreeNode *node; + gchar *cols[3]; + gchar *parent_name; + + if (*name == '\0') return NULL; + node = grouplist_hash_get_branch_node(name); + if (node != NULL) return node; + + cols[0] = (gchar *)name; + cols[1] = cols[2] = ""; + + parent_name = grouplist_get_parent_name(name); + parent = grouplist_create_parent(parent_name, pattern); + + node = parent ? GTK_CTREE_ROW(parent)->children + : GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node, + cols, 0, NULL, NULL, NULL, NULL, + FALSE, FALSE); + if (parent && fnmatch(pattern, parent_name, 0) != 0) + gtk_ctree_expand(GTK_CTREE(ctree), parent); + gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, FALSE); + + grouplist_hash_set_branch_node(name, node); + + g_free(parent_name); + + return node; +} + +static GtkCTreeNode *grouplist_create_branch(NewsGroupInfo *ginfo, + const gchar *pattern) +{ + GtkCTreeNode *node; + GtkCTreeNode *parent; + gchar *name = (gchar *)ginfo->name; + gchar *parent_name; + gchar *count_str; + gchar *cols[3]; + gint count; + + count = ginfo->last - ginfo->first; + if (count < 0) + count = 0; + count_str = itos(count); + + cols[0] = ginfo->name; + cols[1] = count_str; + if (ginfo->type == 'y') + cols[2] = ""; + else if (ginfo->type == 'm') + cols[2] = _("moderated"); + else if (ginfo->type == 'n') + cols[2] = _("readonly"); + else + cols[2] = _("unknown"); + + parent_name = grouplist_get_parent_name(name); + parent = grouplist_create_parent(parent_name, pattern); + node = grouplist_hash_get_branch_node(name); + if (node) { + gtk_ctree_set_node_info(GTK_CTREE(ctree), node, cols[0], 0, + NULL, NULL, NULL, NULL, FALSE, FALSE); + gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 1, cols[1]); + gtk_ctree_node_set_text(GTK_CTREE(ctree), node, 2, cols[2]); + } else { + node = parent ? GTK_CTREE_ROW(parent)->children + : GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node = gtk_ctree_insert_node(GTK_CTREE(ctree), parent, node, + cols, 0, NULL, NULL, NULL, NULL, + TRUE, FALSE); + if (parent && fnmatch(pattern, parent_name, 0) != 0) + gtk_ctree_expand(GTK_CTREE(ctree), parent); + } + gtk_ctree_node_set_selectable(GTK_CTREE(ctree), node, TRUE); + if (node) + gtk_ctree_node_set_row_data(GTK_CTREE(ctree), node, ginfo); + + g_free(parent_name); + + return node; +} + +static void grouplist_dialog_set_list(const gchar *pattern, gboolean refresh) +{ + GSList *cur; + GtkCTreeNode *node; + + if (locked) return; + locked = TRUE; + + if (!pattern || *pattern == '\0') + pattern = "*"; + + if (refresh) { + ack = TRUE; + grouplist_clear(); + recv_set_ui_func(grouplist_recv_func, NULL); + group_list = news_get_group_list(news_folder); + group_list = g_slist_reverse(group_list); + recv_set_ui_func(NULL, NULL); + if (group_list == NULL && ack == TRUE) { + alertpanel_error(_("Can't retrieve newsgroup list.")); + locked = FALSE; + return; + } + } else { + g_signal_handlers_block_by_func + (G_OBJECT(ctree), G_CALLBACK(ctree_unselected), + NULL); + gtk_clist_clear(GTK_CLIST(ctree)); + g_signal_handlers_unblock_by_func + (G_OBJECT(ctree), G_CALLBACK(ctree_unselected), + NULL); + } + gtk_entry_set_text(GTK_ENTRY(entry), pattern); + + grouplist_hash_init(); + + gtk_clist_freeze(GTK_CLIST(ctree)); + + g_signal_handlers_block_by_func(G_OBJECT(ctree), + G_CALLBACK(ctree_selected), NULL); + + for (cur = group_list; cur != NULL ; cur = cur->next) { + NewsGroupInfo *ginfo = (NewsGroupInfo *)cur->data; + + if (fnmatch(pattern, ginfo->name, 0) == 0) { + node = grouplist_create_branch(ginfo, pattern); + if (g_slist_find_custom(subscribed, ginfo->name, + (GCompareFunc)g_strcasecmp) + != NULL) + gtk_ctree_select(GTK_CTREE(ctree), node); + } + } + + g_signal_handlers_unblock_by_func(G_OBJECT(ctree), + G_CALLBACK(ctree_selected), NULL); + + gtk_clist_thaw(GTK_CLIST(ctree)); + + grouplist_hash_done(); + + gtk_label_set_text(GTK_LABEL(status_label), _("Done.")); + + locked = FALSE; +} + +static void grouplist_search(void) +{ + gchar *str; + + if (locked) return; + + str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1); + grouplist_dialog_set_list(str, FALSE); + g_free(str); +} + +static void grouplist_clear(void) +{ + g_signal_handlers_block_by_func(G_OBJECT(ctree), + G_CALLBACK(ctree_unselected), NULL); + gtk_clist_clear(GTK_CLIST(ctree)); + gtk_entry_set_text(GTK_ENTRY(entry), ""); + news_group_list_free(group_list); + group_list = NULL; + g_signal_handlers_unblock_by_func(G_OBJECT(ctree), + G_CALLBACK(ctree_unselected), NULL); +} + +static gboolean grouplist_recv_func(SockInfo *sock, gint count, gint read_bytes, + gpointer data) +{ + gchar buf[BUFFSIZE]; + + g_snprintf(buf, sizeof(buf), + _("%d newsgroups received (%s read)"), + count, to_human_readable(read_bytes)); + gtk_label_set_text(GTK_LABEL(status_label), buf); + GTK_EVENTS_FLUSH(); + if (ack == FALSE) + return FALSE; + else + return TRUE; +} + +static gint window_deleted(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + ack = FALSE; + if (gtk_main_level() > 1) + gtk_main_quit(); + + return TRUE; +} + +static void ok_clicked(GtkWidget *widget, gpointer data) +{ + ack = TRUE; + if (gtk_main_level() > 1) + gtk_main_quit(); +} + +static void cancel_clicked(GtkWidget *widget, gpointer data) +{ + ack = FALSE; + if (gtk_main_level() > 1) + gtk_main_quit(); +} + +static void refresh_clicked(GtkWidget *widget, gpointer data) +{ + gchar *str; + + if (locked) return; + + news_remove_group_list_cache(news_folder); + + str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1); + grouplist_dialog_set_list(str, TRUE); + g_free(str); +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + cancel_clicked(NULL, NULL); + return FALSE; +} + +static void ctree_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column, + gpointer data) +{ + NewsGroupInfo *ginfo; + + ginfo = gtk_ctree_node_get_row_data(ctree, node); + if (!ginfo) return; + + subscribed = g_slist_append(subscribed, g_strdup(ginfo->name)); +} + +static void ctree_unselected(GtkCTree *ctree, GtkCTreeNode *node, gint column, + gpointer data) +{ + NewsGroupInfo *ginfo; + GSList *list; + + ginfo = gtk_ctree_node_get_row_data(ctree, node); + if (!ginfo) return; + + list = g_slist_find_custom(subscribed, ginfo->name, + (GCompareFunc)g_strcasecmp); + if (list) { + g_free(list->data); + subscribed = g_slist_remove(subscribed, list->data); + } +} + +static void entry_activated(GtkEditable *editable) +{ + grouplist_search(); +} + +static void search_clicked(GtkWidget *widget, gpointer data) +{ + grouplist_search(); +} diff --git a/src/grouplistdialog.h b/src/grouplistdialog.h new file mode 100644 index 00000000..87433059 --- /dev/null +++ b/src/grouplistdialog.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __GROUPLISTDIALOG_H__ +#define __GROUPLISTDIALOG_H__ + +#include + +#include "folder.h" + +GSList *grouplist_dialog (Folder *folder); + +#endif /* __GROUPLISTDIALOG_H__ */ diff --git a/src/gtksctree.c b/src/gtksctree.c new file mode 100644 index 00000000..eddb52fc --- /dev/null +++ b/src/gtksctree.c @@ -0,0 +1,573 @@ +/* + * This program is based on gtkflist.c + */ + +#include "gtksctree.h" +#include "sylpheed-marshal.h" + +enum { + ROW_POPUP_MENU, + EMPTY_POPUP_MENU, + OPEN_ROW, + START_DRAG, + LAST_SIGNAL +}; + + +static void gtk_sctree_class_init (GtkSCTreeClass *class); +static void gtk_sctree_init (GtkSCTree *sctree); + +static gint gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event); +static gint gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event); +static gint gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event); +static void gtk_sctree_drag_begin (GtkWidget *widget, GdkDragContext *context); +static void gtk_sctree_drag_end (GtkWidget *widget, GdkDragContext *context); +static void gtk_sctree_drag_data_get (GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *data, guint info, guint time); +static void gtk_sctree_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time); +static gboolean gtk_sctree_drag_motion (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time); +static gboolean gtk_sctree_drag_drop (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time); +static void gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *data, + guint info, guint time); + +static void gtk_sctree_clear (GtkCList *clist); +static void gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node); + +static GtkCTreeClass *parent_class; + +static guint sctree_signals[LAST_SIGNAL]; + + +/** + * gtk_sctree_get_type: + * @void: + * + * Creates the GtkSCTree class and its type information + * + * Return value: The type ID for GtkSCTreeClass + **/ +GType +gtk_sctree_get_type (void) +{ + static GType sctree_type = 0; + + if (!sctree_type) { + GTypeInfo sctree_info = { + sizeof (GtkSCTreeClass), + + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + + (GClassInitFunc) gtk_sctree_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + + sizeof (GtkSCTree), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_sctree_init, + }; + + sctree_type = g_type_register_static (GTK_TYPE_CTREE, "GtkSCTree", &sctree_info, (GTypeFlags)0); + } + + return sctree_type; +} + +/* Standard class initialization function */ +static void +gtk_sctree_class_init (GtkSCTreeClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkCListClass *clist_class; + GtkCTreeClass *ctree_class; + + object_class = (GtkObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + clist_class = (GtkCListClass *) klass; + ctree_class = (GtkCTreeClass *) klass; + + parent_class = gtk_type_class (gtk_ctree_get_type ()); + + sctree_signals[ROW_POPUP_MENU] = + g_signal_new ("row_popup_menu", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, row_popup_menu), + NULL, NULL, + sylpheed_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + GDK_TYPE_EVENT); + sctree_signals[EMPTY_POPUP_MENU] = + g_signal_new ("empty_popup_menu", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, empty_popup_menu), + NULL, NULL, + sylpheed_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + GDK_TYPE_EVENT); + sctree_signals[OPEN_ROW] = + g_signal_new ("open_row", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, open_row), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + sctree_signals[START_DRAG] = + g_signal_new ("start_drag", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, start_drag), + NULL, NULL, + sylpheed_marshal_VOID__INT_POINTER, + G_TYPE_NONE, 2, + G_TYPE_INT, + GDK_TYPE_EVENT); + + /* gtk_object_class_add_signals (object_class, sctree_signals, LAST_SIGNAL); */ + + clist_class->clear = gtk_sctree_clear; + ctree_class->tree_collapse = gtk_sctree_collapse; + + widget_class->button_press_event = gtk_sctree_button_press; + widget_class->button_release_event = gtk_sctree_button_release; + widget_class->motion_notify_event = gtk_sctree_motion; + widget_class->drag_begin = gtk_sctree_drag_begin; + widget_class->drag_end = gtk_sctree_drag_end; + widget_class->drag_data_get = gtk_sctree_drag_data_get; + widget_class->drag_leave = gtk_sctree_drag_leave; + widget_class->drag_motion = gtk_sctree_drag_motion; + widget_class->drag_drop = gtk_sctree_drag_drop; + widget_class->drag_data_received = gtk_sctree_drag_data_received; +} + +/* Standard object initialization function */ +static void +gtk_sctree_init (GtkSCTree *sctree) +{ + sctree->anchor_row = NULL; + + /* GtkCTree does not specify pointer motion by default */ + gtk_widget_add_events (GTK_WIDGET (sctree), GDK_POINTER_MOTION_MASK); + gtk_widget_add_events (GTK_WIDGET (sctree), GDK_POINTER_MOTION_MASK); +} + +/* Get information the specified row is selected. */ + +static gboolean +row_is_selected(GtkSCTree *sctree, gint row) +{ + GtkCListRow *clist_row; + clist_row = g_list_nth (GTK_CLIST(sctree)->row_list, row)->data; + return clist_row ? clist_row->state == GTK_STATE_SELECTED : FALSE; +} + +/* Selects the rows between the anchor to the specified row, inclusive. */ +static void +select_range (GtkSCTree *sctree, gint row) +{ + gint prev_row; + gint min, max; + gint i; + + if (sctree->anchor_row == NULL) { + prev_row = row; + sctree->anchor_row = gtk_ctree_node_nth(GTK_CTREE(sctree), row); + } else + prev_row = g_list_position(GTK_CLIST(sctree)->row_list, + (GList *)sctree->anchor_row); + + if (row < prev_row) { + min = row; + max = prev_row; + } else { + min = prev_row; + max = row; + } + for (i = min; i <= max; i++) + gtk_clist_select_row (GTK_CLIST (sctree), i, -1); +} + +/* Handles row selection according to the specified modifier state */ +static void +select_row (GtkSCTree *sctree, gint row, gint col, guint state) +{ + gboolean range, additive; + g_return_if_fail (sctree != NULL); + g_return_if_fail (GTK_IS_SCTREE (sctree)); + + range = ((state & GDK_SHIFT_MASK) != 0) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE); + additive = ((state & GDK_CONTROL_MASK) != 0) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE); + + gtk_clist_freeze (GTK_CLIST (sctree)); + GTK_CLIST(sctree)->focus_row = row; + GTK_CLIST_GET_CLASS(sctree)->refresh(GTK_CLIST(sctree)); + if (!additive) + gtk_clist_unselect_all (GTK_CLIST (sctree)); + + if (!range) { + GtkCTreeNode *node; + + node = gtk_ctree_node_nth (GTK_CTREE(sctree), row); + + /*No need to manage overlapped list*/ + if (additive) { + if (row_is_selected(sctree, row)) + gtk_clist_unselect_row (GTK_CLIST (sctree), row, col); + else + g_signal_emit_by_name + (G_OBJECT (sctree), + "tree_select_row", node, col); + } else { + g_signal_emit_by_name + (G_OBJECT (sctree), + "tree_select_row", node, col); + } + sctree->anchor_row = node; + } else + select_range (sctree, row); + gtk_clist_thaw (GTK_CLIST (sctree)); +} + +/* Our handler for button_press events. We override all of GtkCList's broken + * behavior. + */ +static gint +gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event) +{ + GtkSCTree *sctree; + GtkCList *clist; + gboolean on_row; + gint row; + gint col; + gint retval; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sctree = GTK_SCTREE (widget); + clist = GTK_CLIST (widget); + retval = FALSE; + + if (event->window != clist->clist_window) + return (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event); + + on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col); + + if (on_row && !GTK_WIDGET_HAS_FOCUS(widget)) + gtk_widget_grab_focus (widget); + + if (gtk_ctree_is_hot_spot (GTK_CTREE(sctree), event->x, event->y)) { + gtk_ctree_toggle_expansion + (GTK_CTREE(sctree), + gtk_ctree_node_nth(GTK_CTREE(sctree), row)); + return TRUE; + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button == 1 || event->button == 2) { + if (event->button == 2) + event->state &= ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK); + if (on_row) { + /* Save the mouse info for DnD */ + sctree->dnd_press_button = event->button; + sctree->dnd_press_x = event->x; + sctree->dnd_press_y = event->y; + + /* Handle selection */ + if ((row_is_selected (sctree, row) + && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) + || ((event->state & GDK_CONTROL_MASK) + && !(event->state & GDK_SHIFT_MASK))) { + sctree->dnd_select_pending = TRUE; + sctree->dnd_select_pending_state = event->state; + sctree->dnd_select_pending_row = row; + } else + select_row (sctree, row, col, event->state); + } else + gtk_clist_unselect_all (clist); + + retval = TRUE; + } else if (event->button == 3) { + /* Emit *_popup_menu signal*/ + if (on_row) { + if (!row_is_selected(sctree,row)) + select_row (sctree, row, col, 0); + g_signal_emit (G_OBJECT (sctree), + sctree_signals[ROW_POPUP_MENU], + 0, event); + } else { + gtk_clist_unselect_all(clist); + g_signal_emit (G_OBJECT (sctree), + sctree_signals[EMPTY_POPUP_MENU], + 0, event); + } + retval = TRUE; + } + + break; + + case GDK_2BUTTON_PRESS: + if (event->button != 1) + break; + + sctree->dnd_select_pending = FALSE; + sctree->dnd_select_pending_state = 0; + + if (on_row) + g_signal_emit (G_OBJECT (sctree), + sctree_signals[OPEN_ROW], 0); + + retval = TRUE; + break; + + default: + break; + } + + return retval; +} + +/* Our handler for button_release events. We override all of GtkCList's broken + * behavior. + */ +static gint +gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event) +{ + GtkSCTree *sctree; + GtkCList *clist; + gint on_row; + gint row, col; + gint retval; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sctree = GTK_SCTREE (widget); + clist = GTK_CLIST (widget); + retval = FALSE; + + if (event->window != clist->clist_window) + return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event); + + on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col); + + if (!(event->button == 1 || event->button == 2)) + return FALSE; + + sctree->dnd_press_button = 0; + sctree->dnd_press_x = 0; + sctree->dnd_press_y = 0; + + if (on_row) { + if (sctree->dnd_select_pending) { + select_row (sctree, row, col, sctree->dnd_select_pending_state); + sctree->dnd_select_pending = FALSE; + sctree->dnd_select_pending_state = 0; + } + + retval = TRUE; + } + + return retval; +} + +/* Our handler for motion_notify events. We override all of GtkCList's broken + * behavior. + */ +static gint +gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event) +{ + GtkSCTree *sctree; + GtkCList *clist; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sctree = GTK_SCTREE (widget); + clist = GTK_CLIST (widget); + + if (event->window != clist->clist_window) + return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event); + + if (!((sctree->dnd_press_button == 1 && (event->state & GDK_BUTTON1_MASK)) + || (sctree->dnd_press_button == 2 && (event->state & GDK_BUTTON2_MASK)))) + return FALSE; + + /* This is the same threshold value that is used in gtkdnd.c */ + + if (MAX (ABS (sctree->dnd_press_x - event->x), + ABS (sctree->dnd_press_y - event->y)) <= 3) + return FALSE; + + /* Handle any pending selections */ + + if (sctree->dnd_select_pending) { + if (!row_is_selected(sctree,sctree->dnd_select_pending_row)) + select_row (sctree, + sctree->dnd_select_pending_row, + -1, + sctree->dnd_select_pending_state); + + sctree->dnd_select_pending = FALSE; + sctree->dnd_select_pending_state = 0; + } + + g_signal_emit (G_OBJECT (sctree), + sctree_signals[START_DRAG], + 0, + sctree->dnd_press_button, + event); + return TRUE; +} + +/* We override the drag_begin signal to do nothing */ +static void +gtk_sctree_drag_begin (GtkWidget *widget, GdkDragContext *context) +{ + /* nothing */ +} + +/* We override the drag_end signal to do nothing */ +static void +gtk_sctree_drag_end (GtkWidget *widget, GdkDragContext *context) +{ + /* nothing */ +} + +/* We override the drag_data_get signal to do nothing */ +static void +gtk_sctree_drag_data_get (GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *data, guint info, guint time) +{ + /* nothing */ +} + +/* We override the drag_leave signal to do nothing */ +static void +gtk_sctree_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time) +{ + /* nothing */ +} + +/* We override the drag_motion signal to do nothing */ +static gboolean +gtk_sctree_drag_motion (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time) +{ + return FALSE; +} + +/* We override the drag_drop signal to do nothing */ +static gboolean +gtk_sctree_drag_drop (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time) +{ + return FALSE; +} + +/* We override the drag_data_received signal to do nothing */ +static void +gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *data, + guint info, guint time) +{ + /* nothing */ +} + +/* Our handler for the clear signal of the clist. We have to reset the anchor + * to null. + */ +static void +gtk_sctree_clear (GtkCList *clist) +{ + GtkSCTree *sctree; + + g_return_if_fail (clist != NULL); + g_return_if_fail (GTK_IS_SCTREE (clist)); + + sctree = GTK_SCTREE (clist); + sctree->anchor_row = NULL; + + if (((GtkCListClass *)parent_class)->clear) + (* ((GtkCListClass *)parent_class)->clear) (clist); +} + +/* Our handler for the change_focus_row_expansion signal of the ctree. + We have to set the anchor to parent visible node. + */ +static void +gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node) +{ + g_return_if_fail (ctree != NULL); + g_return_if_fail (GTK_IS_SCTREE (ctree)); + + (* parent_class->tree_collapse) (ctree, node); + GTK_SCTREE(ctree)->anchor_row = + gtk_ctree_node_nth(ctree, GTK_CLIST(ctree)->focus_row); +} + +GtkWidget *gtk_sctree_new_with_titles (gint columns, gint tree_column, + gchar *titles[]) +{ +#if 0 + GtkSCTree* sctree; + + sctree = gtk_type_new (gtk_sctree_get_type ()); + gtk_ctree_construct (GTK_CTREE (sctree), columns, tree_column, titles); + gtk_clist_set_selection_mode(GTK_CLIST(sctree), GTK_SELECTION_EXTENDED); + + return GTK_WIDGET (sctree); +#else + GtkWidget *widget; + + g_return_val_if_fail (columns > 0, NULL); + g_return_val_if_fail (tree_column >= 0 && tree_column < columns, NULL); + + widget = gtk_widget_new (TYPE_GTK_SCTREE, + "n_columns", columns, + "tree_column", tree_column, + NULL); + if (titles) { + GtkCList *clist = GTK_CLIST (widget); + guint i; + + for (i = 0; i < columns; i++) + gtk_clist_set_column_title (clist, i, titles[i]); + gtk_clist_column_titles_show (clist); + } + + return widget; +#endif +} + +void gtk_sctree_select (GtkSCTree *sctree, GtkCTreeNode *node) +{ + select_row(sctree, + g_list_position(GTK_CLIST(sctree)->row_list, (GList *)node), + -1, 0); +} + +void gtk_sctree_unselect_all (GtkSCTree *sctree) +{ + gtk_clist_unselect_all(GTK_CLIST(sctree)); + sctree->anchor_row = NULL; +} + +void gtk_sctree_set_anchor_row (GtkSCTree *sctree, GtkCTreeNode *node) +{ + sctree->anchor_row = node; +} diff --git a/src/gtksctree.h b/src/gtksctree.h new file mode 100644 index 00000000..af836780 --- /dev/null +++ b/src/gtksctree.h @@ -0,0 +1,66 @@ +/* Mail Summary tree widget for Sylpheed */ + +#ifndef __GTK_SCTREE_H__ +#define __GTK_SCTREE_H__ + +#include +#include +#include + +/* This code is based on "gtkflist.{h,c}" from mc-4.5.42 .*/ + +#define TYPE_GTK_SCTREE (gtk_sctree_get_type ()) +#define GTK_SCTREE(obj) (GTK_CHECK_CAST ((obj), TYPE_GTK_SCTREE, GtkSCTree)) +#define GTK_SCTREE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TYPE_GTK_SCTREE, GtkSCTreeClass)) +#define GTK_IS_SCTREE(obj) (GTK_CHECK_TYPE ((obj), TYPE_GTK_SCTREE)) +#define GTK_IS_SCTREE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TYPE_GTK_SCTREE)) + + +typedef struct _GtkSCTree GtkSCTree; +typedef struct _GtkSCTreeClass GtkSCTreeClass; + +struct _GtkSCTree { + GtkCTree ctree; + + /* The anchor row for range selections */ + GtkCTreeNode *anchor_row; + + /* Mouse button and position saved on button press */ + gint dnd_press_button; + gint dnd_press_x, dnd_press_y; + + /* Delayed selection information */ + gint dnd_select_pending; + guint dnd_select_pending_state; + gint dnd_select_pending_row; +}; + +struct _GtkSCTreeClass { + GtkCTreeClass parent_class; + + /* Signal: invoke the popup menu for rows */ + void (* row_popup_menu) (GtkSCTree *sctree, GdkEventButton *event); + + /* Signal: invoke the popup menu for empty areas */ + void (* empty_popup_menu) (GtkSCTree *sctree, GdkEventButton *event); + + /* Signal: open the file in the selected row */ + void (* open_row) (GtkSCTree *sctree); + + /* Signal: initiate a drag and drop operation */ + void (* start_drag) (GtkSCTree *sctree, gint button, GdkEvent *event); +}; + + +GType gtk_sctree_get_type (void); +GtkWidget *gtk_sctree_new_with_titles (gint columns, + gint tree_column, + gchar *titles[]); +void gtk_sctree_select (GtkSCTree *sctree, + GtkCTreeNode *node); +void gtk_sctree_unselect_all (GtkSCTree *sctree); + +void gtk_sctree_set_anchor_row (GtkSCTree *sctree, + GtkCTreeNode *node); + +#endif /* __GTK_SCTREE_H__ */ diff --git a/src/gtkshruler.c b/src/gtkshruler.c new file mode 100644 index 00000000..1e350a58 --- /dev/null +++ b/src/gtkshruler.c @@ -0,0 +1,218 @@ +/* GtkSHRuler + * + * Copyright (C) 2000-2004 Alfons Hoogervorst & The Sylpheed Claws Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* I derived this class from hruler. S in HRuler could be read as + * Sylpheed (sylpheed.good-day.net), but also [S]ettable HRuler. + * I basically ripped apart the draw_ticks member of HRuler; it + * now draws the ticks at ruler->max_size. so gtk_ruler_set_range's + * last parameter has the distance between two ticks (which is + * the width of the fixed font character! + * + * -- Alfons + */ + +#include +#include +#include +#include +#include "gtkshruler.h" + +#define RULER_HEIGHT 14 +#define MINIMUM_INCR 5 +#define MAXIMUM_SUBDIVIDE 5 +#define MAXIMUM_SCALES 10 + +#define ROUND(x) ((int) ((x) + 0.5)) + +static void gtk_shruler_class_init (GtkSHRulerClass *klass); +static void gtk_shruler_init (GtkSHRuler *hruler); +#if 0 +static gint gtk_shruler_motion_notify (GtkWidget *widget, + GdkEventMotion *event); +#endif +static void gtk_shruler_draw_ticks (GtkRuler *ruler); +#if 0 +static void gtk_shruler_draw_pos (GtkRuler *ruler); +#endif + +GType +gtk_shruler_get_type(void) +{ + static GType shruler_type = 0; + + if ( !shruler_type ) { + static const GTypeInfo shruler_info = { + sizeof (GtkSHRulerClass), + + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + + (GClassInitFunc) gtk_shruler_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + + sizeof (GtkSHRuler), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_shruler_init, + }; + /* inherit from GtkHRuler */ + shruler_type = g_type_register_static ( GTK_TYPE_HRULER, "GtkSHRuler", &shruler_info, (GTypeFlags) 0 ); + } + return shruler_type; +} + +static void +gtk_shruler_class_init(GtkSHRulerClass * klass) +{ + GtkWidgetClass * widget_class; + GtkRulerClass * hruler_class; + + widget_class = (GtkWidgetClass*) klass; + hruler_class = (GtkRulerClass*) klass; + + /* just neglect motion notify events */ + widget_class->motion_notify_event = NULL /* gtk_shruler_motion_notify */; + + /* we want the old ruler draw ticks... */ + /* ruler_class->draw_ticks = gtk_hruler_draw_ticks; */ + hruler_class->draw_ticks = gtk_shruler_draw_ticks; + + /* unimplemented draw pos */ + hruler_class->draw_pos = NULL; +/* + hruler_class->draw_pos = gtk_shruler_draw_pos; +*/ +} + +static void +gtk_shruler_init (GtkSHRuler * shruler) +{ + GtkWidget * widget; + + widget = GTK_WIDGET (shruler); + widget->requisition.width = widget->style->xthickness * 2 + 1; + widget->requisition.height = widget->style->ythickness * 2 + RULER_HEIGHT; +} + + +GtkWidget* +gtk_shruler_new(void) +{ + return GTK_WIDGET( g_object_new( gtk_shruler_get_type(), NULL ) ); +} + +#if 0 +static gint +gtk_shruler_motion_notify(GtkWidget *widget, + GdkEventMotion *event) +{ + /* I could have perhaps set this to NULL */ + return FALSE; +} +#endif + +static void +gtk_shruler_draw_ticks(GtkRuler *ruler) +{ + GtkWidget *widget; + GdkGC *gc, *bg_gc; + GdkFont *font; + gint i; + gint width, height; + gint xthickness; + gint ythickness; + gint pos; + + g_return_if_fail (ruler != NULL); + g_return_if_fail (GTK_IS_HRULER (ruler)); + + if (!GTK_WIDGET_DRAWABLE (ruler)) + return; + + widget = GTK_WIDGET (ruler); + + gc = widget->style->fg_gc[GTK_STATE_NORMAL]; + bg_gc = widget->style->bg_gc[GTK_STATE_NORMAL]; + font = gtk_style_get_font(widget->style); + + xthickness = widget->style->xthickness; + ythickness = widget->style->ythickness; + + width = widget->allocation.width; + height = widget->allocation.height - ythickness * 2; + + gtk_paint_box (widget->style, ruler->backing_store, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, widget, "hruler", + 0, 0, + widget->allocation.width, widget->allocation.height); + +#if 0 + gdk_draw_line (ruler->backing_store, gc, + xthickness, + height + ythickness, + widget->allocation.width - xthickness, + height + ythickness); +#endif + + /* assume ruler->max_size has the char width */ + /* i is increment of char_width, pos is label number */ + for ( i = 0, pos = 0; i < widget->allocation.width - xthickness; i += ruler->max_size, pos++ ) { + int length = ythickness / 2; + + if ( pos % 10 == 0 ) length = ( 4 * ythickness ); + else if (pos % 5 == 0 ) length = ( 2 * ythickness ); + + gdk_draw_line(ruler->backing_store, gc, + i, height + ythickness, + i, height - length); + + if ( pos % 10 == 0 ) { + char buf[8]; + /* draw label */ + sprintf(buf, "%d", (int) pos); + gdk_draw_string(ruler->backing_store, font, gc, + i + 2, ythickness + font->ascent - 1, + buf); + } + } +} + +/* gtk_ruler_set_pos() - does not work yet, need to reimplement + * gtk_ruler_draw_pos(). */ +void +gtk_shruler_set_pos(GtkSHRuler * ruler, gfloat pos) +{ + GtkRuler * ruler_; + g_return_if_fail( ruler != NULL ); + + ruler_ = GTK_RULER(ruler); + + if ( pos < ruler_->lower ) + pos = ruler_->lower; + if ( pos > ruler_->upper ) + pos = ruler_->upper; + + ruler_->position = pos; + + /* Make sure the ruler has been allocated already */ + if ( ruler_->backing_store != NULL ) + gtk_ruler_draw_pos(ruler_); +} diff --git a/src/gtkshruler.h b/src/gtkshruler.h new file mode 100644 index 00000000..bf7e0cde --- /dev/null +++ b/src/gtkshruler.h @@ -0,0 +1,63 @@ +/* GTKSHRuler + * Copyright (C) 2000-2004 Alfons Hoogervorst & The Sylpheed Claws Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_SHRULER_H__ +#define __GTK_SHRULER_H__ + + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GTK_SHRULER(obj) (GTK_CHECK_CAST ((obj), gtk_shruler_get_type (), GtkSHRuler)) +#define GTK_SHRULER_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), gtk_shruler_get_type (), GtkSHRulerClass)) +#define GTK_IS_SHRULER(obj) (GTK_CHECK_TYPE ((obj), gtk_shruler_get_type ())) + + +typedef struct _GtkSHRuler GtkSHRuler; +typedef struct _GtkSHRulerClass GtkSHRulerClass; + +struct _GtkSHRuler +{ + GtkHRuler ruler; +}; + +struct _GtkSHRulerClass +{ + GtkHRulerClass parent_class; +}; + + +GType gtk_shruler_get_type (void); +GtkWidget* gtk_shruler_new (void); +void gtk_shruler_set_pos (GtkSHRuler *ruler, + gfloat pos); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_SHRULER_H__ */ diff --git a/src/gtkutils.c b/src/gtkutils.c new file mode 100644 index 00000000..196b46ee --- /dev/null +++ b/src/gtkutils.c @@ -0,0 +1,686 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (HAVE_WCTYPE_H && HAVE_WCHAR_H) +# include +# include +#endif + +#include "intl.h" +#include "gtkutils.h" +#include "utils.h" +#include "gtksctree.h" +#include "codeconv.h" +#include "menu.h" + +#warning FIXME_GTK2 +gboolean gtkut_get_font_size(GtkWidget *widget, gint *width, gint *height) +{ + PangoLayout *layout; + const gchar *str = "Abcdef"; + + g_return_val_if_fail(GTK_IS_WIDGET(widget), FALSE); + + layout = gtk_widget_create_pango_layout(widget, str); + g_return_val_if_fail(layout, FALSE); + pango_layout_get_pixel_size(layout, width, height); + if (width) + *width = *width / g_utf8_strlen(str, -1); + g_object_unref(layout); + + return TRUE; +} + +void gtkut_convert_int_to_gdk_color(gint rgbvalue, GdkColor *color) +{ + g_return_if_fail(color != NULL); + + color->pixel = 0L; + color->red = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0) * 65535.0); + color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >> 8) / 255.0) * 65535.0); + color->blue = (int) (((gdouble) (rgbvalue & 0x0000ff) / 255.0) * 65535.0); +} + +void gtkut_button_set_create(GtkWidget **bbox, + GtkWidget **button1, const gchar *label1, + GtkWidget **button2, const gchar *label2, + GtkWidget **button3, const gchar *label3) +{ + g_return_if_fail(bbox != NULL); + g_return_if_fail(button1 != NULL); + + *bbox = gtk_hbutton_box_new(); + gtk_button_box_set_layout(GTK_BUTTON_BOX(*bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing(GTK_BOX(*bbox), 5); + + *button1 = gtk_button_new_with_label(label1); + GTK_WIDGET_SET_FLAGS(*button1, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(*bbox), *button1, TRUE, TRUE, 0); + gtk_widget_show(*button1); + + if (button2) { + *button2 = gtk_button_new_with_label(label2); + GTK_WIDGET_SET_FLAGS(*button2, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(*bbox), *button2, TRUE, TRUE, 0); + gtk_widget_show(*button2); + } + + if (button3) { + *button3 = gtk_button_new_with_label(label3); + GTK_WIDGET_SET_FLAGS(*button3, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(*bbox), *button3, TRUE, TRUE, 0); + gtk_widget_show(*button3); + } +} + +static void combo_button_size_request(GtkWidget *widget, + GtkRequisition *requisition, + gpointer data) +{ + ComboButton *combo = (ComboButton *)data; + + if (combo->arrow->allocation.height != requisition->height) + gtk_widget_set_size_request(combo->arrow, + -1, requisition->height); +} + +static void combo_button_enter(GtkWidget *widget, gpointer data) +{ + ComboButton *combo = (ComboButton *)data; + + if (GTK_WIDGET_STATE(combo->arrow) != GTK_STATE_PRELIGHT) { + gtk_widget_set_state(combo->arrow, GTK_STATE_PRELIGHT); + gtk_widget_queue_draw(combo->arrow); + } + if (GTK_WIDGET_STATE(combo->button) != GTK_STATE_PRELIGHT) { + gtk_widget_set_state(combo->button, GTK_STATE_PRELIGHT); + gtk_widget_queue_draw(combo->button); + } +} + +static void combo_button_leave(GtkWidget *widget, gpointer data) +{ + ComboButton *combo = (ComboButton *)data; + + if (GTK_WIDGET_STATE(combo->arrow) != GTK_STATE_NORMAL) { + gtk_widget_set_state(combo->arrow, GTK_STATE_NORMAL); + gtk_widget_queue_draw(combo->arrow); + } + if (GTK_WIDGET_STATE(combo->button) != GTK_STATE_NORMAL) { + gtk_widget_set_state(combo->button, GTK_STATE_NORMAL); + gtk_widget_queue_draw(combo->button); + } +} + +static gint combo_button_arrow_pressed(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + ComboButton *combo = (ComboButton *)data; + + if (!event) return FALSE; + + gtk_menu_popup(GTK_MENU(combo->menu), NULL, NULL, + menu_button_position, combo->button, + event->button, event->time); + + return TRUE; +} + +static void combo_button_destroy(GtkWidget *widget, gpointer data) +{ + ComboButton *combo = (ComboButton *)data; + + gtk_object_destroy(GTK_OBJECT(combo->factory)); + g_free(combo); +} + +ComboButton *gtkut_combo_button_create(GtkWidget *button, + GtkItemFactoryEntry *entries, + gint n_entries, const gchar *path, + gpointer data) +{ + ComboButton *combo; + GtkWidget *arrow; + + combo = g_new0(ComboButton, 1); + + combo->arrow = gtk_button_new(); + arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(combo->arrow), arrow); + GTK_WIDGET_UNSET_FLAGS(combo->arrow, GTK_CAN_FOCUS); + gtk_widget_show_all(combo->arrow); + + combo->button = button; + combo->menu = menu_create_items(entries, n_entries, path, + &combo->factory, data); + combo->data = data; + + g_signal_connect(G_OBJECT(combo->button), "size_request", + G_CALLBACK(combo_button_size_request), combo); + g_signal_connect(G_OBJECT(combo->button), "enter", + G_CALLBACK(combo_button_enter), combo); + g_signal_connect(G_OBJECT(combo->button), "leave", + G_CALLBACK(combo_button_leave), combo); + g_signal_connect(G_OBJECT(combo->arrow), "enter", + G_CALLBACK(combo_button_enter), combo); + g_signal_connect(G_OBJECT(combo->arrow), "leave", + G_CALLBACK(combo_button_leave), combo); + g_signal_connect(G_OBJECT(combo->arrow), "button_press_event", + G_CALLBACK(combo_button_arrow_pressed), combo); + g_signal_connect(G_OBJECT(combo->arrow), "destroy", + G_CALLBACK(combo_button_destroy), combo); + + return combo; +} + +#define CELL_SPACING 1 +#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \ + (((row) + 1) * CELL_SPACING) + \ + (clist)->voffset) +#define ROW_FROM_YPIXEL(clist, y) (((y) - (clist)->voffset) / \ + ((clist)->row_height + CELL_SPACING)) + +void gtkut_ctree_node_move_if_on_the_edge(GtkCTree *ctree, GtkCTreeNode *node) +{ + GtkCList *clist = GTK_CLIST(ctree); + gint row; + GtkVisibility row_visibility, prev_row_visibility, next_row_visibility; + + g_return_if_fail(ctree != NULL); + g_return_if_fail(node != NULL); + + row = g_list_position(clist->row_list, (GList *)node); + if (row < 0 || row >= clist->rows || clist->row_height == 0) return; + row_visibility = gtk_clist_row_is_visible(clist, row); + prev_row_visibility = gtk_clist_row_is_visible(clist, row - 1); + next_row_visibility = gtk_clist_row_is_visible(clist, row + 1); + + if (row_visibility == GTK_VISIBILITY_NONE) { + gtk_clist_moveto(clist, row, -1, 0.5, 0); + return; + } + if (row_visibility == GTK_VISIBILITY_FULL && + prev_row_visibility == GTK_VISIBILITY_FULL && + next_row_visibility == GTK_VISIBILITY_FULL) + return; + if (prev_row_visibility != GTK_VISIBILITY_FULL && + next_row_visibility != GTK_VISIBILITY_FULL) + return; + + if (prev_row_visibility != GTK_VISIBILITY_FULL) { + gtk_clist_moveto(clist, row, -1, 0.2, 0); + return; + } + if (next_row_visibility != GTK_VISIBILITY_FULL) { + gtk_clist_moveto(clist, row, -1, 0.8, 0); + return; + } +} + +#undef CELL_SPACING +#undef ROW_TOP_YPIXEL +#undef ROW_FROM_YPIXEL + +gint gtkut_ctree_get_nth_from_node(GtkCTree *ctree, GtkCTreeNode *node) +{ + g_return_val_if_fail(ctree != NULL, -1); + g_return_val_if_fail(node != NULL, -1); + + return g_list_position(GTK_CLIST(ctree)->row_list, (GList *)node); +} + +/* get the next node, including the invisible one */ +GtkCTreeNode *gtkut_ctree_node_next(GtkCTree *ctree, GtkCTreeNode *node) +{ + GtkCTreeNode *parent; + + if (!node) return NULL; + + if (GTK_CTREE_ROW(node)->children) + return GTK_CTREE_ROW(node)->children; + + if (GTK_CTREE_ROW(node)->sibling) + return GTK_CTREE_ROW(node)->sibling; + + for (parent = GTK_CTREE_ROW(node)->parent; parent != NULL; + parent = GTK_CTREE_ROW(parent)->parent) { + if (GTK_CTREE_ROW(parent)->sibling) + return GTK_CTREE_ROW(parent)->sibling; + } + + return NULL; +} + +/* get the previous node, including the invisible one */ +GtkCTreeNode *gtkut_ctree_node_prev(GtkCTree *ctree, GtkCTreeNode *node) +{ + GtkCTreeNode *prev; + GtkCTreeNode *child; + + if (!node) return NULL; + + prev = GTK_CTREE_NODE_PREV(node); + if (prev == GTK_CTREE_ROW(node)->parent) + return prev; + + child = prev; + while (GTK_CTREE_ROW(child)->children != NULL) { + child = GTK_CTREE_ROW(child)->children; + while (GTK_CTREE_ROW(child)->sibling != NULL) + child = GTK_CTREE_ROW(child)->sibling; + } + + return child; +} + +gboolean gtkut_ctree_node_is_selected(GtkCTree *ctree, GtkCTreeNode *node) +{ + GtkCList *clist = GTK_CLIST(ctree); + GList *cur; + + for (cur = clist->selection; cur != NULL; cur = cur->next) { + if (node == GTK_CTREE_NODE(cur->data)) + return TRUE; + } + + return FALSE; +} + +GtkCTreeNode *gtkut_ctree_find_collapsed_parent(GtkCTree *ctree, + GtkCTreeNode *node) +{ + if (!node) return NULL; + + while ((node = GTK_CTREE_ROW(node)->parent) != NULL) { + if (!GTK_CTREE_ROW(node)->expanded) + return node; + } + + return NULL; +} + +void gtkut_ctree_expand_parent_all(GtkCTree *ctree, GtkCTreeNode *node) +{ + while ((node = gtkut_ctree_find_collapsed_parent(ctree, node)) != NULL) + gtk_ctree_expand(ctree, node); +} + +void gtkut_ctree_set_focus_row(GtkCTree *ctree, GtkCTreeNode *node) +{ + gtkut_clist_set_focus_row(GTK_CLIST(ctree), + gtkut_ctree_get_nth_from_node(ctree, node)); +} + +void gtkut_clist_set_focus_row(GtkCList *clist, gint row) +{ + clist->focus_row = row; + GTKUT_CTREE_REFRESH(clist); +} + +void gtkut_combo_set_items(GtkCombo *combo, const gchar *str1, ...) +{ + va_list args; + gchar *s; + GList *combo_items = NULL; + + g_return_if_fail(str1 != NULL); + + combo_items = g_list_append(combo_items, (gpointer)str1); + va_start(args, str1); + s = va_arg(args, gchar*); + while (s) { + combo_items = g_list_append(combo_items, (gpointer)s); + s = va_arg(args, gchar*); + } + va_end(args); + + gtk_combo_set_popdown_strings(combo, combo_items); + + g_list_free(combo_items); +} + +gchar *gtkut_editable_get_selection(GtkEditable *editable) +{ + guint start_pos, end_pos; + gboolean found; + + g_return_val_if_fail(GTK_IS_EDITABLE(editable), NULL); + + found = gtk_editable_get_selection_bounds(editable, + &start_pos, &end_pos); + if (found) + return gtk_editable_get_chars(editable, start_pos, end_pos); + else + return NULL; +} + +void gtkut_editable_disable_im(GtkEditable *editable) +{ + g_return_if_fail(editable != NULL); + +#if USE_XIM + if (editable->ic) { + gdk_ic_destroy(editable->ic); + editable->ic = NULL; + } + if (editable->ic_attr) { + gdk_ic_attr_destroy(editable->ic_attr); + editable->ic_attr = NULL; + } +#endif +} + +/* + * Walk through the widget tree and disclaim the selection from all currently + * realized GtkEditable widgets. + */ +static void gtkut_check_before_remove(GtkWidget *widget, gpointer unused) +{ + g_return_if_fail(widget != NULL); + + if (!GTK_WIDGET_REALIZED(widget)) + return; /* all nested widgets must be unrealized too */ + if (GTK_IS_CONTAINER(widget)) + gtk_container_forall(GTK_CONTAINER(widget), + gtkut_check_before_remove, NULL); +#if 0 + if (GTK_IS_EDITABLE(widget)) + gtk_editable_claim_selection(GTK_EDITABLE(widget), + FALSE, GDK_CURRENT_TIME); +#endif +} + +/* + * Wrapper around gtk_container_remove to work around a bug in GtkText and + * GtkEntry (in all GTK+ versions up to and including at least 1.2.10). + * + * The problem is that unrealizing a GtkText or GtkEntry widget which has the + * active selection completely messes up selection handling, leading to + * non-working selections and crashes. Removing a widget from its container + * implies unrealizing it and all its child widgets; this triggers the bug if + * the removed widget or any of its children is GtkText or GtkEntry. As a + * workaround, this function walks through the widget subtree before removing + * and disclaims the selection from all GtkEditable widgets found. + * + * A similar workaround may be needed for gtk_widget_reparent(); currently it + * is not necessary because Sylpheed does not use gtk_widget_reparent() for + * GtkEditable widgets or containers holding such widgets. + */ +void gtkut_container_remove(GtkContainer *container, GtkWidget *widget) +{ + gtkut_check_before_remove(widget, NULL); + gtk_container_remove(container, widget); +} + +#warning FIXME_GTK2 +gboolean gtkut_text_buffer_match_string(GtkTextBuffer *textbuf, gint pos, + gunichar *wcs, gint len, + gboolean case_sens) +{ + GtkTextIter start_iter, end_iter; + gchar *utf8str; + gint match_count = 0; + + gtk_text_buffer_get_iter_at_offset(textbuf, &start_iter, pos); + gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter, pos + len); + + utf8str = gtk_text_buffer_get_text(textbuf, &start_iter, &end_iter, FALSE); + if (!utf8str) return FALSE; + + if ((gint)g_utf8_strlen(utf8str, -1) != len) { + g_free(utf8str); + return FALSE; + } + + for (; match_count < len; pos++, match_count++) { + gchar *ptr; + gunichar ch; + + ptr = g_utf8_offset_to_pointer(utf8str, match_count); + if (!ptr) break; + ch = g_utf8_get_char(ptr); + + if (case_sens) { + if (ch != wcs[match_count]) + break; + } else { + if (g_unichar_tolower(ch) != + g_unichar_tolower(wcs[match_count])) + break; + } + } + + g_free(utf8str); + + if (match_count == len) + return TRUE; + else + return FALSE; +} + +guint gtkut_text_buffer_str_compare_n(GtkTextBuffer *textbuf, + guint pos1, guint pos2, + guint len, guint text_len) +{ + guint i; + + for (i = 0; i < len && pos1 + i < text_len && pos2 + i < text_len; i++) { + GtkTextIter start_iter, end_iter; + gchar *utf8str1, *utf8str2; + + gtk_text_buffer_get_iter_at_offset(textbuf, &start_iter, + pos1 + i); + gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter, + pos1 + i + 1); + utf8str1 = gtk_text_buffer_get_text(textbuf, + &start_iter, + &end_iter, + FALSE); + + gtk_text_buffer_get_iter_at_offset(textbuf, &start_iter, + pos2 + i); + gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter, + pos2 + i + 1); + utf8str2 = gtk_text_buffer_get_text(textbuf, + &start_iter, + &end_iter, + FALSE); + + if (!utf8str1 || !utf8str2 || strcmp(utf8str1, utf8str2) != 0) { + g_free(utf8str1); + g_free(utf8str2); + break; + } + + g_free(utf8str1); + g_free(utf8str2); + } + + return i; +} + +guint gtkut_text_buffer_str_compare(GtkTextBuffer *textbuf, + guint start_pos, guint text_len, + const gchar *str) +{ + gunichar *wcs; + guint len = 0; + glong items_read = 0, items_written = 0; + gboolean result; + GError *error = NULL; + + if (!str) return 0; + + wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error); + if (error != NULL) { + g_warning("An error occured while converting a string from UTF-8 to UCS-4: %s\n", error->message); + g_error_free(error); + } + if (!wcs || items_written <= 0) return 0; + len = (guint)items_written; + + if (len > text_len - start_pos) + result = FALSE; + else + result = gtkut_text_buffer_match_string(textbuf, start_pos, + wcs, len, TRUE); + + g_free(wcs); + + return result ? len : 0; +} + +gboolean gtkut_text_buffer_is_uri_string(GtkTextBuffer *textbuf, + guint start_pos, guint text_len) +{ + if (gtkut_text_buffer_str_compare + (textbuf, start_pos, text_len, "http://") || + gtkut_text_buffer_str_compare + (textbuf, start_pos, text_len, "ftp://") || + gtkut_text_buffer_str_compare + (textbuf, start_pos, text_len, "https://") || + gtkut_text_buffer_str_compare + (textbuf, start_pos, text_len, "www.")) + return TRUE; + + return FALSE; +} + +gchar *gtkut_text_view_get_selection(GtkTextView *textview) +{ + GtkTextBuffer *buffer; + GtkTextIter start_iter, end_iter; + gboolean found; + + g_return_val_if_fail(GTK_IS_TEXT_VIEW(textview), NULL); + + buffer = gtk_text_view_get_buffer(textview); + found = gtk_text_buffer_get_selection_bounds(buffer, + &start_iter, &end_iter); + if (found) + return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, + FALSE); + else + return NULL; +} + +void gtkut_window_popup(GtkWidget *window) +{ + gint x, y, sx, sy, new_x, new_y; + + g_return_if_fail(window != NULL); + g_return_if_fail(window->window != NULL); + + sx = gdk_screen_width(); + sy = gdk_screen_height(); + + gdk_window_get_origin(window->window, &x, &y); + new_x = x % sx; if (new_x < 0) new_x = 0; + new_y = y % sy; if (new_y < 0) new_y = 0; + if (new_x != x || new_y != y) + gdk_window_move(window->window, new_x, new_y); + + gdk_window_raise(window->window); + gdk_window_show(window->window); +} + +void gtkut_widget_get_uposition(GtkWidget *widget, gint *px, gint *py) +{ + gint x, y; + gint sx, sy; + + g_return_if_fail(widget != NULL); + g_return_if_fail(widget->window != NULL); + + sx = gdk_screen_width(); + sy = gdk_screen_height(); + + /* gdk_window_get_root_origin ever return *rootwindow*'s position */ + gdk_window_get_root_origin(widget->window, &x, &y); + + x %= sx; if (x < 0) x = 0; + y %= sy; if (y < 0) y = 0; + *px = x; + *py = y; +} + +#warning FIXME_GTK2 +void gtkut_widget_wait_for_draw(GtkWidget *widget) +{ + if (!GTK_WIDGET_VISIBLE(widget) || !GTK_WIDGET_MAPPED(widget)) return; + + while (gtk_events_pending()) + gtk_main_iteration(); +} + +static void gtkut_clist_bindings_add(GtkWidget *clist) +{ + GtkBindingSet *binding_set; + + binding_set = gtk_binding_set_by_class(GTK_CLIST_GET_CLASS(clist)); + + gtk_binding_entry_add_signal(binding_set, GDK_n, GDK_CONTROL_MASK, + "scroll_vertical", 2, + G_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD, + G_TYPE_FLOAT, 0.0); + gtk_binding_entry_add_signal(binding_set, GDK_p, GDK_CONTROL_MASK, + "scroll_vertical", 2, + G_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD, + G_TYPE_FLOAT, 0.0); +} + +void gtkut_widget_init(void) +{ + GtkWidget *clist; + + clist = gtk_clist_new(1); + g_object_ref(G_OBJECT(clist)); + gtk_object_sink(GTK_OBJECT(clist)); + gtkut_clist_bindings_add(clist); + g_object_unref(G_OBJECT(clist)); + + clist = gtk_ctree_new(1, 0); + g_object_ref(G_OBJECT(clist)); + gtk_object_sink(GTK_OBJECT(clist)); + gtkut_clist_bindings_add(clist); + g_object_unref(G_OBJECT(clist)); + + clist = gtk_sctree_new_with_titles(1, 0, NULL); + g_object_ref(G_OBJECT(clist)); + gtk_object_sink(GTK_OBJECT(clist)); + gtkut_clist_bindings_add(clist); + g_object_unref(G_OBJECT(clist)); +} diff --git a/src/gtkutils.h b/src/gtkutils.h new file mode 100644 index 00000000..23e7af51 --- /dev/null +++ b/src/gtkutils.h @@ -0,0 +1,166 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __GTKUTILS_H__ +#define __GTKUTILS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_WCHAR_H +# include +#endif + +typedef struct _ComboButton ComboButton; + +struct _ComboButton +{ + GtkWidget *arrow; + GtkWidget *button; + GtkWidget *menu; + GtkItemFactory *factory; + gpointer data; +}; + +#define GTK_EVENTS_FLUSH() \ +{ \ + while (gtk_events_pending()) \ + gtk_main_iteration(); \ +} + +#define PIXMAP_CREATE(widget, pixmap, mask, xpm_d) \ +{ \ + if (!pixmap) { \ + GtkStyle *style = gtk_widget_get_style(widget); \ + pixmap = gdk_pixmap_create_from_xpm_d \ + (widget->window, &mask, \ + &style->bg[GTK_STATE_NORMAL], xpm_d); \ + } \ +} + +#define GTK_WIDGET_PTR(wid) (*(GtkWidget **)wid) + +#define GTKUT_CTREE_NODE_SET_ROW_DATA(node, d) \ +{ \ + GTK_CTREE_ROW(node)->row.data = d; \ +} + +#define GTKUT_CTREE_NODE_GET_ROW_DATA(node) \ + (GTK_CTREE_ROW(node)->row.data) + +#define GTKUT_CTREE_REFRESH(clist) \ + GTK_CLIST_GET_CLASS(clist)->refresh(clist) + +gboolean gtkut_get_font_size (GtkWidget *widget, + gint *width, + gint *height); + +GdkFont *gtkut_font_load (const gchar *fontset_name); +GdkFont *gtkut_font_load_from_fontset (const gchar *fontset_name); + +void gtkut_convert_int_to_gdk_color (gint rgbvalue, + GdkColor *color); + +void gtkut_button_set_create (GtkWidget **bbox, + GtkWidget **button1, + const gchar *label1, + GtkWidget **button2, + const gchar *label2, + GtkWidget **button3, + const gchar *label3); + +ComboButton *gtkut_combo_button_create (GtkWidget *button, + GtkItemFactoryEntry *entries, + gint n_entries, + const gchar *path, + gpointer data); + +void gtkut_ctree_node_move_if_on_the_edge + (GtkCTree *ctree, + GtkCTreeNode *node); +gint gtkut_ctree_get_nth_from_node (GtkCTree *ctree, + GtkCTreeNode *node); +GtkCTreeNode *gtkut_ctree_node_next (GtkCTree *ctree, + GtkCTreeNode *node); +GtkCTreeNode *gtkut_ctree_node_prev (GtkCTree *ctree, + GtkCTreeNode *node); +gboolean gtkut_ctree_node_is_selected (GtkCTree *ctree, + GtkCTreeNode *node); +GtkCTreeNode *gtkut_ctree_find_collapsed_parent + (GtkCTree *ctree, + GtkCTreeNode *node); +void gtkut_ctree_expand_parent_all (GtkCTree *ctree, + GtkCTreeNode *node); +void gtkut_ctree_set_focus_row (GtkCTree *ctree, + GtkCTreeNode *node); + +void gtkut_clist_set_focus_row (GtkCList *clist, + gint row); + +void gtkut_combo_set_items (GtkCombo *combo, + const gchar *str1, ...); + +gchar *gtkut_editable_get_selection (GtkEditable *editable); +void gtkut_editable_disable_im (GtkEditable *editable); + +void gtkut_container_remove (GtkContainer *container, + GtkWidget *widget); + +gboolean gtkut_text_buffer_match_string (GtkTextBuffer *text, + gint pos, + gunichar *wcs, + gint len, + gboolean case_sens); +guint gtkut_text_buffer_str_compare_n (GtkTextBuffer *text, + guint pos1, + guint pos2, + guint len, + guint text_len); +guint gtkut_text_buffer_str_compare (GtkTextBuffer *text, + guint start_pos, + guint text_len, + const gchar *str); +gboolean gtkut_text_buffer_is_uri_string(GtkTextBuffer *text, + guint start_pos, + guint text_len); + +gchar *gtkut_text_view_get_selection (GtkTextView *textview); + +void gtkut_window_popup (GtkWidget *window); + +void gtkut_widget_get_uposition (GtkWidget *widget, + gint *px, + gint *py); +//void gtkut_widget_disable_theme_engine (GtkWidget *widget); +void gtkut_widget_wait_for_draw (GtkWidget *widget); +void gtkut_widget_init (void); + +#endif /* __GTKUTILS_H__ */ diff --git a/src/headerview.c b/src/headerview.c new file mode 100644 index 00000000..2a6d7369 --- /dev/null +++ b/src/headerview.c @@ -0,0 +1,348 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_LIBCOMPFACE +# include +#endif + +#include "intl.h" +#include "main.h" +#include "headerview.h" +#include "prefs_common.h" +#include "codeconv.h" +#include "gtkutils.h" +#include "utils.h" + +#define TR(str) (prefs_common.trans_hdr ? gettext(str) : str) + +#if 0 + _("From:"); + _("To:"); + _("Newsgroups:"); + _("Subject:"); +#endif + +#if HAVE_LIBCOMPFACE +#define XPM_XFACE_HEIGHT (HEIGHT + 3) /* 3 = 1 header + 2 colors */ + +static gchar *xpm_xface[XPM_XFACE_HEIGHT]; + +static void headerview_show_xface (HeaderView *headerview, + MsgInfo *msginfo); +static gint create_xpm_from_xface (gchar *xpm[], + const gchar *xface); +#endif + +HeaderView *headerview_create(void) +{ + HeaderView *headerview; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *hbox1; + GtkWidget *hbox2; + GtkWidget *from_header_label; + GtkWidget *from_body_label; + GtkWidget *to_header_label; + GtkWidget *to_body_label; + GtkWidget *ng_header_label; + GtkWidget *ng_body_label; + GtkWidget *subject_header_label; + GtkWidget *subject_body_label; + + debug_print(_("Creating header view...\n")); + headerview = g_new0(HeaderView, 1); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 2); + vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + + hbox1 = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0); + hbox2 = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 0); + + from_header_label = gtk_label_new(TR("From:")); + from_body_label = gtk_label_new(""); + to_header_label = gtk_label_new(TR("To:")); + to_body_label = gtk_label_new(""); + ng_header_label = gtk_label_new(TR("Newsgroups:")); + ng_body_label = gtk_label_new(""); + subject_header_label = gtk_label_new(TR("Subject:")); + subject_body_label = gtk_label_new(""); + + gtk_label_set_selectable(GTK_LABEL(from_body_label), TRUE); + gtk_label_set_selectable(GTK_LABEL(to_body_label), TRUE); + gtk_label_set_selectable(GTK_LABEL(ng_body_label), TRUE); + gtk_label_set_selectable(GTK_LABEL(subject_body_label), TRUE); + + gtk_box_pack_start(GTK_BOX(hbox1), from_header_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox1), from_body_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox1), to_header_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox1), to_body_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox1), ng_header_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox1), ng_body_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox2), subject_header_label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox2), subject_body_label, FALSE, FALSE, 0); + + headerview->hbox = hbox; + headerview->from_header_label = from_header_label; + headerview->from_body_label = from_body_label; + headerview->to_header_label = to_header_label; + headerview->to_body_label = to_body_label; + headerview->ng_header_label = ng_header_label; + headerview->ng_body_label = ng_body_label; + headerview->subject_header_label = subject_header_label; + headerview->subject_body_label = subject_body_label; + headerview->image = NULL; + + gtk_widget_show_all(hbox); + + return headerview; +} + +void headerview_init(HeaderView *headerview) +{ + static PangoFontDescription *boldfont = NULL; + + if (!boldfont && prefs_common.boldfont) + boldfont = pango_font_description_from_string + (prefs_common.boldfont); + +#define SET_FONT_STYLE(wid) \ +{ \ + if (boldfont) \ + gtk_widget_modify_font(headerview->wid, boldfont); \ +} + + SET_FONT_STYLE(from_header_label); + SET_FONT_STYLE(to_header_label); + SET_FONT_STYLE(ng_header_label); + SET_FONT_STYLE(subject_header_label); + + headerview_clear(headerview); + headerview_set_visibility(headerview, prefs_common.display_header_pane); + +#if HAVE_LIBCOMPFACE + { + gint i; + + for (i = 0; i < XPM_XFACE_HEIGHT; i++) { + xpm_xface[i] = g_malloc(WIDTH + 1); + *xpm_xface[i] = '\0'; + } + } +#endif +} + +void headerview_show(HeaderView *headerview, MsgInfo *msginfo) +{ + headerview_clear(headerview); + + gtk_label_set_text(GTK_LABEL(headerview->from_body_label), + msginfo->from ? msginfo->from : _("(No From)")); + if (msginfo->to) { + gtk_label_set_text(GTK_LABEL(headerview->to_body_label), + msginfo->to); + gtk_widget_show(headerview->to_header_label); + gtk_widget_show(headerview->to_body_label); + } + if (msginfo->newsgroups) { + gtk_label_set_text(GTK_LABEL(headerview->ng_body_label), + msginfo->newsgroups); + gtk_widget_show(headerview->ng_header_label); + gtk_widget_show(headerview->ng_body_label); + } + gtk_label_set_text(GTK_LABEL(headerview->subject_body_label), + msginfo->subject ? msginfo->subject + : _("(No Subject)")); + +#if HAVE_LIBCOMPFACE + headerview_show_xface(headerview, msginfo); +#endif +} + +#if HAVE_LIBCOMPFACE +static void headerview_show_xface(HeaderView *headerview, MsgInfo *msginfo) +{ + gchar xface[2048]; + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkWidget *hbox = headerview->hbox; + + if (!msginfo->xface || strlen(msginfo->xface) < 5) { + if (headerview->image && + GTK_WIDGET_VISIBLE(headerview->image)) { + gtk_widget_hide(headerview->image); + gtk_widget_queue_resize(hbox); + } + return; + } + if (!GTK_WIDGET_VISIBLE(headerview->hbox)) return; + + strncpy(xface, msginfo->xface, sizeof(xface)); + + if (uncompface(xface) < 0) { + g_warning("uncompface failed\n"); + if (headerview->image) + gtk_widget_hide(headerview->image); + return; + } + + create_xpm_from_xface(xpm_xface, xface); + + pixmap = gdk_pixmap_create_from_xpm_d + (hbox->window, &mask, &hbox->style->white, xpm_xface); + + if (!headerview->image) { + GtkWidget *image; + + image = gtk_image_new_from_pixmap(pixmap, mask); + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); + gtk_widget_show(image); + headerview->image = image; + } else { + gtk_image_set_from_pixmap(GTK_IMAGE(headerview->image), + pixmap, mask); + gtk_widget_show(headerview->image); + } + + gdk_pixmap_unref(pixmap); +} +#endif + +void headerview_clear(HeaderView *headerview) +{ + gtk_label_set_text(GTK_LABEL(headerview->from_body_label), ""); + gtk_label_set_text(GTK_LABEL(headerview->to_body_label), ""); + gtk_label_set_text(GTK_LABEL(headerview->ng_body_label), ""); + gtk_label_set_text(GTK_LABEL(headerview->subject_body_label), ""); + gtk_widget_hide(headerview->to_header_label); + gtk_widget_hide(headerview->to_body_label); + gtk_widget_hide(headerview->ng_header_label); + gtk_widget_hide(headerview->ng_body_label); + + if (headerview->image && GTK_WIDGET_VISIBLE(headerview->image)) { + gtk_widget_hide(headerview->image); + gtk_widget_queue_resize(headerview->hbox); + } +} + +void headerview_set_visibility(HeaderView *headerview, gboolean visibility) +{ + if (visibility) + gtk_widget_show(headerview->hbox); + else + gtk_widget_hide(headerview->hbox); +} + +void headerview_destroy(HeaderView *headerview) +{ + g_free(headerview); +} + +#if HAVE_LIBCOMPFACE +static gint create_xpm_from_xface(gchar *xpm[], const gchar *xface) +{ + static gchar *bit_pattern[] = { + "....", + "...#", + "..#.", + "..##", + ".#..", + ".#.#", + ".##.", + ".###", + "#...", + "#..#", + "#.#.", + "#.##", + "##..", + "##.#", + "###.", + "####" + }; + + static gchar *xface_header = "48 48 2 1"; + static gchar *xface_black = "# c #000000"; + static gchar *xface_white = ". c #ffffff"; + + gint i, line = 0; + const guchar *p; + gchar buf[WIDTH * 4 + 1]; /* 4 = strlen("0x0000") */ + + p = xface; + + strcpy(xpm[line++], xface_header); + strcpy(xpm[line++], xface_black); + strcpy(xpm[line++], xface_white); + + for (i = 0; i < HEIGHT; i++) { + gint col; + + buf[0] = '\0'; + + for (col = 0; col < 3; col++) { + gint figure; + + p += 2; /* skip '0x' */ + + for (figure = 0; figure < 4; figure++) { + gint n = 0; + + if ('0' <= *p && *p <= '9') { + n = *p - '0'; + } else if ('a' <= *p && *p <= 'f') { + n = *p - 'a' + 10; + } else if ('A' <= *p && *p <= 'F') { + n = *p - 'A' + 10; + } + + strcat(buf, bit_pattern[n]); + p++; /* skip ',' */ + } + + p++; /* skip '\n' */ + } + + strcpy(xpm[line++], buf); + p++; + } + + return 0; +} +#endif diff --git a/src/headerview.h b/src/headerview.h new file mode 100644 index 00000000..db7d810b --- /dev/null +++ b/src/headerview.h @@ -0,0 +1,55 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __HEADERVIEW_H__ +#define __HEADERVIEW_H__ + +#include +#include + +#include "procmsg.h" + +typedef struct _HeaderView HeaderView; + +struct _HeaderView +{ + GtkWidget *hbox; + + GtkWidget *from_header_label; + GtkWidget *from_body_label; + GtkWidget *to_header_label; + GtkWidget *to_body_label; + GtkWidget *ng_header_label; + GtkWidget *ng_body_label; + GtkWidget *subject_header_label; + GtkWidget *subject_body_label; + + GtkWidget *image; +}; + +HeaderView *headerview_create (void); +void headerview_init (HeaderView *headerview); +void headerview_show (HeaderView *headerview, + MsgInfo *msginfo); +void headerview_clear (HeaderView *headerview); +void headerview_set_visibility (HeaderView *headerview, + gboolean visibility); +void headerview_destroy (HeaderView *headerview); + +#endif /* __HEADERVIEW_H__ */ diff --git a/src/html.c b/src/html.c new file mode 100644 index 00000000..2c706c94 --- /dev/null +++ b/src/html.c @@ -0,0 +1,777 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include +#include +#include + +#include "html.h" +#include "codeconv.h" +#include "utils.h" + +#define HTMLBUFSIZE 8192 +#define HR_STR "------------------------------------------------" + +typedef struct _HTMLSymbol HTMLSymbol; + +struct _HTMLSymbol +{ + gchar *const key; + gchar *const val; +}; + +static HTMLSymbol symbol_list[] = { + {"<" , "<"}, + {">" , ">"}, + {"&" , "&"}, + {""" , "\""}, + {" " , " "}, + {"™" , "(TM)"}, + + {"™", "(TM)"}, +}; + +static HTMLSymbol ascii_symbol_list[] = { + {"¡" , "^!"}, + {"¦", "|"}, + {"©" , "(C)"}, + {"«" , "<<"}, + {"®" , "(R)"}, + + {"²" , "^2"}, + {"³" , "^3"}, + {"´" , "'"}, + {"¸" , ","}, + {"¹" , "^1"}, + {"»" , ">>"}, + {"¼", "1/4"}, + {"½", "1/2"}, + {"¾", "3/4"}, + {"¿", "^?"}, + + {"À", "A`"}, + {"Á", "A'"}, + {"Â" , "A^"}, + {"Ã", "A~"}, + {"Æ" , "AE"}, + {"È", "E`"}, + {"É", "E'"}, + {"Ê" , "E^"}, + {"Ì", "I`"}, + {"Í", "I'"}, + {"Î" , "I^"}, + + {"Ñ", "N~"}, + {"Ò", "O`"}, + {"Ó", "O'"}, + {"Ô" , "O^"}, + {"Õ", "O~"}, + {"Ù", "U`"}, + {"Ú", "U'"}, + {"Û" , "U^"}, + {"Ý", "Y'"}, + + {"à", "a`"}, + {"á", "a'"}, + {"â" , "a^"}, + {"ã", "a~"}, + {"æ" , "ae"}, + {"è", "e`"}, + {"é", "e'"}, + {"ê" , "e^"}, + {"ì", "i`"}, + {"í", "i'"}, + {"î" , "i^"}, + + {"ñ", "n~"}, + {"ò", "o`"}, + {"ó", "o'"}, + {"ô" , "o^"}, + {"õ", "o~"}, + {"ù", "u`"}, + {"ú", "u'"}, + {"û" , "u^"}, + {"ý", "y'"}, +}; + +static HTMLSymbol eucjp_symbol_list[] = { + {"¡" , "^!"}, + {"¢" , "\xa1\xf1"}, + {"£" , "\xa1\xf2"}, + {"¥" , "\xa1\xef"}, + {"¦", "|"}, + {"§" , "\xa1\xf8"}, + {"¨" , "\xa1\xaf"}, + {"©" , "(C)"}, + {"«" , "<<"}, + {"®" , "(R)"}, + + {"°" , "\xa1\xeb"}, + {"±", "\xa1\xde"}, + {"²" , "^2"}, + {"³" , "^3"}, + {"´" , "'"}, + {"µ" , "\xa6\xcc"}, + {"¶" , "\xa2\xf9"}, + {"·", "\xa1\xa6"}, + {"¸" , ","}, + {"¹" , "^1"}, + {"»" , ">>"}, + {"¼", "1/4"}, + {"½", "1/2"}, + {"¾", "3/4"}, + {"¿", "^?"}, + + {"À", "A`"}, + {"Á", "A'"}, + {"Â" , "A^"}, + {"Ã", "A~"}, + {"Ä" , "A\xa1\xaf"}, + {"Å" , "A\xa1\xeb"}, + {"Æ" , "AE"}, + {"È", "E`"}, + {"É", "E'"}, + {"Ê" , "E^"}, + {"Ë" , "E\xa1\xaf"}, + {"Ì", "I`"}, + {"Í", "I'"}, + {"Î" , "I^"}, + {"Ï" , "I\xa1\xaf"}, + + {"Ñ", "N~"}, + {"Ò", "O`"}, + {"Ó", "O'"}, + {"Ô" , "O^"}, + {"Õ", "O~"}, + {"Ö" , "O\xa1\xaf"}, + {"×" , "\xa1\xdf"}, + {"Ù", "U`"}, + {"Ú", "U'"}, + {"Û" , "U^"}, + {"Ü" , "U\xa1\xaf"}, + {"Ý", "Y'"}, + + {"à", "a`"}, + {"á", "a'"}, + {"â" , "a^"}, + {"ã", "a~"}, + {"ä" , "a\xa1\xaf"}, + {"å" , "a\xa1\xeb"}, + {"æ" , "ae"}, + {"è", "e`"}, + {"é", "e'"}, + {"ê" , "e^"}, + {"ë" , "e\xa1\xaf"}, + {"ì", "i`"}, + {"í", "i'"}, + {"î" , "i^"}, + {"ï" , "i\xa1\xaf"}, + + {"ð" , "\xa2\xdf"}, + {"ñ", "n~"}, + {"ò", "o`"}, + {"ó", "o'"}, + {"ô" , "o^"}, + {"õ", "o~"}, + {"ö" , "o\xa1\xaf"}, + {"÷", "\xa1\xe0"}, + {"ù", "u`"}, + {"ú", "u'"}, + {"û" , "u^"}, + {"ü" , "u\xa1\xaf"}, + {"ý", "y'"}, + {"ÿ" , "y\xa1\xaf"}, +}; + +static HTMLSymbol latin_symbol_list[] = { + {"¡" , "\xa1"}, + {"¢" , "\xa2"}, + {"£" , "\xa3"}, + {"¤", "\xa4"}, + {"¥" , "\xa5"}, + {"¦", "\xa6"}, + {"§" , "\xa7"}, + {"¨" , "\xa8"}, + {"©" , "\xa9"}, + {"ª" , "\xaa"}, + {"«" , "\xab"}, + {"¬" , "\xac"}, + {"­" , "\xad"}, + {"®" , "\xae"}, + {"¯" , "\xaf"}, + + {"°" , "\xb0"}, + {"±", "\xb1"}, + {"²" , "\xb2"}, + {"³" , "\xb3"}, + {"´" , "\xb4"}, + {"µ" , "\xb5"}, + {"¶" , "\xb6"}, + {"·", "\xb7"}, + {"¸" , "\xb8"}, + {"¹" , "\xb9"}, + {"º" , "\xba"}, + {"»" , "\xbb"}, + {"¼", "\xbc"}, + {"½", "\xbd"}, + {"¾", "\xbe"}, + {"¿", "\xbf"}, + + {"À", "\xc0"}, + {"Á", "\xc1"}, + {"Â" , "\xc2"}, + {"Ã", "\xc3"}, + {"Ä" , "\xc4"}, + {"Å" , "\xc5"}, + {"Æ" , "\xc6"}, + {"Ç", "\xc7"}, + {"È", "\xc8"}, + {"É", "\xc9"}, + {"Ê" , "\xca"}, + {"Ë" , "\xcb"}, + {"Ì", "\xcc"}, + {"Í", "\xcd"}, + {"Î" , "\xce"}, + {"Ï" , "\xcf"}, + + {"Ð" , "\xd0"}, + {"Ñ", "\xd1"}, + {"Ò", "\xd2"}, + {"Ó", "\xd3"}, + {"Ô" , "\xd4"}, + {"Õ", "\xd5"}, + {"Ö" , "\xd6"}, + {"×" , "\xd7"}, + {"Ø", "\xd8"}, + {"Ù", "\xd9"}, + {"Ú", "\xda"}, + {"Û" , "\xdb"}, + {"Ü" , "\xdc"}, + {"Ý", "\xdd"}, + {"Þ" , "\xde"}, + {"ß" , "\xdf"}, + + {"à", "\xe0"}, + {"á", "\xe1"}, + {"â" , "\xe2"}, + {"ã", "\xe3"}, + {"ä" , "\xe4"}, + {"å" , "\xe5"}, + {"æ" , "\xe6"}, + {"ç", "\xe7"}, + {"è", "\xe8"}, + {"é", "\xe9"}, + {"ê" , "\xea"}, + {"ë" , "\xeb"}, + {"ì", "\xec"}, + {"í", "\xed"}, + {"î" , "\xee"}, + {"ï" , "\xef"}, + + {"ð" , "\xf0"}, + {"ñ", "\xf1"}, + {"ò", "\xf2"}, + {"ó", "\xf3"}, + {"ô" , "\xf4"}, + {"õ", "\xf5"}, + {"ö" , "\xf6"}, + {"÷", "\xf7"}, + {"ø", "\xf8"}, + {"ù", "\xf9"}, + {"ú", "\xfa"}, + {"û" , "\xfb"}, + {"ü" , "\xfc"}, + {"ý", "\xfd"}, + {"þ" , "\xfe"}, + {"ÿ" , "\xff"}, +}; + +static GHashTable *default_symbol_table; +static GHashTable *eucjp_symbol_table; +static GHashTable *latin_symbol_table; + +static HTMLState html_read_line (HTMLParser *parser); +static void html_append_char (HTMLParser *parser, + gchar ch); +static void html_append_str (HTMLParser *parser, + const gchar *str, + gint len); +static HTMLState html_parse_tag (HTMLParser *parser); +static void html_parse_special (HTMLParser *parser); +static void html_get_parenthesis (HTMLParser *parser, + gchar *buf, + gint len); + + +HTMLParser *html_parser_new(FILE *fp, CodeConverter *conv) +{ + HTMLParser *parser; + + g_return_val_if_fail(fp != NULL, NULL); + g_return_val_if_fail(conv != NULL, NULL); + + parser = g_new0(HTMLParser, 1); + parser->fp = fp; + parser->conv = conv; + parser->str = g_string_new(NULL); + parser->buf = g_string_new(NULL); + parser->bufp = parser->buf->str; + parser->state = HTML_NORMAL; + parser->href = NULL; + parser->newline = TRUE; + parser->empty_line = TRUE; + parser->space = FALSE; + parser->pre = FALSE; + +#define SYMBOL_TABLE_ADD(table, list) \ +{ \ + gint i; \ + \ + for (i = 0; i < sizeof(list) / sizeof(list[0]); i++) \ + g_hash_table_insert(table, list[i].key, list[i].val); \ +} + + if (!default_symbol_table) { + default_symbol_table = + g_hash_table_new(g_str_hash, g_str_equal); + SYMBOL_TABLE_ADD(default_symbol_table, symbol_list); + SYMBOL_TABLE_ADD(default_symbol_table, ascii_symbol_list); + } + if (!eucjp_symbol_table) { + eucjp_symbol_table = + g_hash_table_new(g_str_hash, g_str_equal); + SYMBOL_TABLE_ADD(eucjp_symbol_table, symbol_list); + SYMBOL_TABLE_ADD(eucjp_symbol_table, eucjp_symbol_list); + } + if (!latin_symbol_table) { + latin_symbol_table = + g_hash_table_new(g_str_hash, g_str_equal); + SYMBOL_TABLE_ADD(latin_symbol_table, symbol_list); + SYMBOL_TABLE_ADD(latin_symbol_table, latin_symbol_list); + } + +#undef SYMBOL_TABLE_ADD + + if (conv->charset == C_ISO_8859_1) + parser->symbol_table = latin_symbol_table; + else if ((conv->charset == C_ISO_2022_JP || + conv->charset == C_ISO_2022_JP_2 || + conv->charset == C_EUC_JP || + conv->charset == C_SHIFT_JIS) && + conv_get_locale_charset() == C_EUC_JP) + parser->symbol_table = eucjp_symbol_table; + else + parser->symbol_table = default_symbol_table; + + return parser; +} + +void html_parser_destroy(HTMLParser *parser) +{ + g_string_free(parser->str, TRUE); + g_string_free(parser->buf, TRUE); + g_free(parser->href); + g_free(parser); +} + +gchar *html_parse(HTMLParser *parser) +{ + parser->state = HTML_NORMAL; + g_string_truncate(parser->str, 0); + + if (*parser->bufp == '\0') { + g_string_truncate(parser->buf, 0); + parser->bufp = parser->buf->str; + if (html_read_line(parser) == HTML_EOF) + return NULL; + } + + while (*parser->bufp != '\0') { + switch (*parser->bufp) { + case '<': + if (parser->str->len == 0) + html_parse_tag(parser); + else + return parser->str->str; + break; + case '&': + html_parse_special(parser); + break; + case ' ': + case '\t': + case '\r': + case '\n': + if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n') + parser->bufp++; + + if (!parser->pre) { + if (!parser->newline) + parser->space = TRUE; + + parser->bufp++; + break; + } + /* fallthrough */ + default: + html_append_char(parser, *parser->bufp++); + } + } + + return parser->str->str; +} + +static HTMLState html_read_line(HTMLParser *parser) +{ + gchar buf[HTMLBUFSIZE]; + gchar buf2[HTMLBUFSIZE]; + gint index; + + if (fgets(buf, sizeof(buf), parser->fp) == NULL) { + parser->state = HTML_EOF; + return HTML_EOF; + } + + if (conv_convert(parser->conv, buf2, sizeof(buf2), buf) < 0) { + index = parser->bufp - parser->buf->str; + + conv_localetodisp(buf2, sizeof(buf2), buf); + g_string_append(parser->buf, buf2); + + parser->bufp = parser->buf->str + index; + + return HTML_CONV_FAILED; + } + + index = parser->bufp - parser->buf->str; + + g_string_append(parser->buf, buf2); + + parser->bufp = parser->buf->str + index; + + return HTML_NORMAL; +} + +static void html_append_char(HTMLParser *parser, gchar ch) +{ + GString *str = parser->str; + + if (!parser->pre && parser->space) { + g_string_append_c(str, ' '); + parser->space = FALSE; + } + + g_string_append_c(str, ch); + + parser->empty_line = FALSE; + if (ch == '\n') { + parser->newline = TRUE; + if (str->len > 1 && str->str[str->len - 2] == '\n') + parser->empty_line = TRUE; + } else + parser->newline = FALSE; +} + +static void html_append_str(HTMLParser *parser, const gchar *str, gint len) +{ + GString *string = parser->str; + + if (!parser->pre && parser->space) { + g_string_append_c(string, ' '); + parser->space = FALSE; + } + + if (len == 0) return; + if (len < 0) + g_string_append(string, str); + else { + gchar *s; + Xstrndup_a(s, str, len, return); + g_string_append(string, s); + } + + parser->empty_line = FALSE; + if (string->len > 0 && string->str[string->len - 1] == '\n') { + parser->newline = TRUE; + if (string->len > 1 && string->str[string->len - 2] == '\n') + parser->empty_line = TRUE; + } else + parser->newline = FALSE; +} + +static HTMLTag *html_get_tag(const gchar *str) +{ + HTMLTag *tag; + gchar *tmp; + guchar *tmpp; + + g_return_val_if_fail(str != NULL, NULL); + + if (*str == '\0' || *str == '!') return NULL; + + Xstrdup_a(tmp, str, return NULL); + + tag = g_new0(HTMLTag, 1); + + for (tmpp = tmp; *tmpp != '\0' && !isspace(*tmpp); tmpp++) + ; + + if (*tmpp == '\0') { + g_strdown(tmp); + tag->name = g_strdup(tmp); + return tag; + } else { + *tmpp++ = '\0'; + g_strdown(tmp); + tag->name = g_strdup(tmp); + } + + while (*tmpp != '\0') { + HTMLAttr *attr; + gchar *attr_name; + gchar *attr_value; + gchar *p; + gchar quote; + + while (isspace(*tmpp)) tmpp++; + attr_name = tmpp; + + while (*tmpp != '\0' && !isspace(*tmpp) && *tmpp != '=') tmpp++; + if (*tmpp != '\0' && *tmpp != '=') { + *tmpp++ = '\0'; + while (isspace(*tmpp)) tmpp++; + } + + if (*tmpp == '=') { + *tmpp++ = '\0'; + while (isspace(*tmpp)) tmpp++; + + if (*tmpp == '"' || *tmpp == '\'') { + /* name="value" */ + quote = *tmpp; + tmpp++; + attr_value = tmpp; + if ((p = strchr(attr_value, quote)) == NULL) { + g_warning("html_get_tag(): syntax error in tag: '%s'\n", str); + return tag; + } + tmpp = p; + *tmpp++ = '\0'; + while (isspace(*tmpp)) tmpp++; + } else { + /* name=value */ + attr_value = tmpp; + while (*tmpp != '\0' && !isspace(*tmpp)) tmpp++; + if (*tmpp != '\0') + *tmpp++ = '\0'; + } + } else + attr_value = ""; + + g_strchomp(attr_name); + g_strdown(attr_name); + attr = g_new(HTMLAttr, 1); + attr->name = g_strdup(attr_name); + attr->value = g_strdup(attr_value); + tag->attr = g_list_append(tag->attr, attr); + } + + return tag; +} + +static void html_free_tag(HTMLTag *tag) +{ + if (!tag) return; + + g_free(tag->name); + while (tag->attr != NULL) { + HTMLAttr *attr = (HTMLAttr *)tag->attr->data; + g_free(attr->name); + g_free(attr->value); + g_free(attr); + tag->attr = g_list_remove(tag->attr, tag->attr->data); + } + g_free(tag); +} + +static HTMLState html_parse_tag(HTMLParser *parser) +{ + gchar buf[HTMLBUFSIZE]; + HTMLTag *tag; + + html_get_parenthesis(parser, buf, sizeof(buf)); + + tag = html_get_tag(buf); + + parser->state = HTML_UNKNOWN; + if (!tag) return HTML_UNKNOWN; + + if (!strcmp(tag->name, "br")) { + parser->space = FALSE; + html_append_char(parser, '\n'); + parser->state = HTML_BR; + } else if (!strcmp(tag->name, "a")) { + if (tag->attr && tag->attr->data && + !strcmp(((HTMLAttr *)tag->attr->data)->name, "href")) { + g_free(parser->href); + parser->href = + g_strdup(((HTMLAttr *)tag->attr->data)->value); + parser->state = HTML_HREF; + } + } else if (!strcmp(tag->name, "/a")) { + g_free(parser->href); + parser->href = NULL; + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "p")) { + parser->space = FALSE; + if (!parser->empty_line) { + parser->space = FALSE; + if (!parser->newline) html_append_char(parser, '\n'); + html_append_char(parser, '\n'); + } + parser->state = HTML_PAR; + } else if (!strcmp(tag->name, "pre")) { + parser->pre = TRUE; + parser->state = HTML_PRE; + } else if (!strcmp(tag->name, "/pre")) { + parser->pre = FALSE; + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "hr")) { + if (!parser->newline) { + parser->space = FALSE; + html_append_char(parser, '\n'); + } + html_append_str(parser, HR_STR "\n", -1); + parser->state = HTML_HR; + } else if (!strcmp(tag->name, "div") || + !strcmp(tag->name, "ul") || + !strcmp(tag->name, "li") || + !strcmp(tag->name, "table") || + !strcmp(tag->name, "tr") || + (tag->name[0] == 'h' && isdigit((guchar)tag->name[1]))) { + if (!parser->newline) { + parser->space = FALSE; + html_append_char(parser, '\n'); + } + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "/table") || + (tag->name[0] == '/' && + tag->name[1] == 'h' && + isdigit((guchar)tag->name[1]))) { + if (!parser->empty_line) { + parser->space = FALSE; + if (!parser->newline) html_append_char(parser, '\n'); + html_append_char(parser, '\n'); + } + parser->state = HTML_NORMAL; + } else if (!strcmp(tag->name, "/div") || + !strcmp(tag->name, "/ul") || + !strcmp(tag->name, "/li")) { + if (!parser->newline) { + parser->space = FALSE; + html_append_char(parser, '\n'); + } + parser->state = HTML_NORMAL; + } + + html_free_tag(tag); + + return parser->state; +} + +static void html_parse_special(HTMLParser *parser) +{ + gchar symbol_name[9]; + gint n; + const gchar *val; + + parser->state = HTML_UNKNOWN; + g_return_if_fail(*parser->bufp == '&'); + + /* &foo; */ + for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++) + ; + if (n > 7 || parser->bufp[n] != ';') { + /* output literal `&' */ + html_append_char(parser, *parser->bufp++); + parser->state = HTML_NORMAL; + return; + } + strncpy2(symbol_name, parser->bufp, n + 2); + parser->bufp += n + 1; + + if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name)) + != NULL) { + html_append_str(parser, val, -1); + parser->state = HTML_NORMAL; + return; + } else if (symbol_name[1] == '#' && isdigit((guchar)symbol_name[2])) { + gint ch; + + ch = atoi(symbol_name + 2); + if ((ch > 0 && ch <= 127) || + (ch >= 128 && ch <= 255 && + parser->conv->charset == C_ISO_8859_1)) { + html_append_char(parser, ch); + parser->state = HTML_NORMAL; + return; + } + } + + html_append_str(parser, symbol_name, -1); +} + +static void html_get_parenthesis(HTMLParser *parser, gchar *buf, gint len) +{ + gchar *p; + + buf[0] = '\0'; + g_return_if_fail(*parser->bufp == '<'); + + /* ignore comment / CSS / script stuff */ + if (!strncmp(parser->bufp, "")) == NULL) + if (html_read_line(parser) == HTML_EOF) return; + parser->bufp = p + 3; + return; + } + if (!g_strncasecmp(parser->bufp, "bufp += 6; + while ((p = strcasestr(parser->bufp, "")) == NULL) + if (html_read_line(parser) == HTML_EOF) return; + parser->bufp = p + 8; + return; + } + if (!g_strncasecmp(parser->bufp, "bufp += 7; + while ((p = strcasestr(parser->bufp, "")) == NULL) + if (html_read_line(parser) == HTML_EOF) return; + parser->bufp = p + 9; + return; + } + + parser->bufp++; + while ((p = strchr(parser->bufp, '>')) == NULL) + if (html_read_line(parser) == HTML_EOF) return; + + strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len)); + g_strstrip(buf); + parser->bufp = p + 1; +} diff --git a/src/html.h b/src/html.h new file mode 100644 index 00000000..7267c175 --- /dev/null +++ b/src/html.h @@ -0,0 +1,87 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __HTML_H__ +#define __HTML_H__ + +#include +#include + +#include "codeconv.h" + +typedef enum +{ + HTML_NORMAL, + HTML_PAR, + HTML_BR, + HTML_HR, + HTML_HREF, + HTML_IMG, + HTML_FONT, + HTML_PRE, + HTML_UNKNOWN, + HTML_CONV_FAILED, + HTML_ERR, + HTML_EOF +} HTMLState; + +typedef struct _HTMLParser HTMLParser; +typedef struct _HTMLAttr HTMLAttr; +typedef struct _HTMLTag HTMLTag; + +struct _HTMLParser +{ + FILE *fp; + CodeConverter *conv; + + GHashTable *symbol_table; + + GString *str; + GString *buf; + + gchar *bufp; + + HTMLState state; + + gchar *href; + + gboolean newline; + gboolean empty_line; + gboolean space; + gboolean pre; +}; + +struct _HTMLAttr +{ + gchar *name; + gchar *value; +}; + +struct _HTMLTag +{ + gchar *name; + GList *attr; +}; + +HTMLParser *html_parser_new (FILE *fp, + CodeConverter *conv); +void html_parser_destroy (HTMLParser *parser); +gchar *html_parse (HTMLParser *parser); + +#endif /* __HTML_H__ */ diff --git a/src/imageview.c b/src/imageview.c new file mode 100644 index 00000000..c1dd68c0 --- /dev/null +++ b/src/imageview.c @@ -0,0 +1,251 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#if HAVE_GDK_PIXBUF +# include +#endif /* HAVE_GDK_PIXBUF */ + +#include "intl.h" +#include "mainwindow.h" +#include "prefs_common.h" +#include "procmime.h" +#include "imageview.h" +#include "utils.h" + +static void get_resized_size (gint w, + gint h, + gint aw, + gint ah, + gint *sw, + gint *sh); + +static gint button_press_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static void size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + gpointer data); + +ImageView *imageview_create(void) +{ + ImageView *imageview; + GtkWidget *scrolledwin; + + debug_print(_("Creating image view...\n")); + imageview = g_new0(ImageView, 1); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_widget_set_size_request + (scrolledwin, prefs_common.mainview_width, -1); + + g_signal_connect(G_OBJECT(scrolledwin), "button_press_event", + G_CALLBACK(button_press_cb), imageview); + g_signal_connect(G_OBJECT(scrolledwin), "size_allocate", + G_CALLBACK(size_allocate_cb), imageview); + + gtk_widget_show_all(scrolledwin); + + imageview->scrolledwin = scrolledwin; + imageview->image = NULL; + imageview->image_data = NULL; + imageview->resize = FALSE; + imageview->resizing = FALSE; + + return imageview; +} + +void imageview_init(ImageView *imageview) +{ +} + +#if HAVE_GDK_PIXBUF +void imageview_show_image(ImageView *imageview, MimeInfo *mimeinfo, + const gchar *file, gboolean resize) +{ + GdkPixbuf *pixbuf; + gint avail_width; + gint avail_height; + gint new_width; + gint new_height; + GError *error = NULL; + + g_return_if_fail(imageview != NULL); + + if (file) { + imageview_clear(imageview); + pixbuf = gdk_pixbuf_new_from_file(file, &error); + imageview->image_data = pixbuf; + } else { + pixbuf = (GdkPixbuf *)imageview->image_data; + } + + if (error != NULL) { + g_warning("%s\n", error->message); + g_error_free(error); + } + + if (!pixbuf) { + g_warning(_("Can't load the image.")); + return; + } + + imageview->resize = resize; + + if (resize) { + GdkPixbuf *pixbuf_scaled; + + avail_width = imageview->scrolledwin->parent->allocation.width; + avail_height = imageview->scrolledwin->parent->allocation.height; + if (avail_width > 8) avail_width -= 8; + if (avail_height > 8) avail_height -= 8; + + get_resized_size(gdk_pixbuf_get_width(pixbuf), + gdk_pixbuf_get_height(pixbuf), + avail_width, avail_height, + &new_width, &new_height); + + pixbuf_scaled = gdk_pixbuf_scale_simple + (pixbuf, new_width, new_height, GDK_INTERP_BILINEAR); + pixbuf = pixbuf_scaled; + } else + g_object_ref(pixbuf); + + if (!imageview->image) { + imageview->image = gtk_image_new_from_pixbuf(pixbuf); + + gtk_scrolled_window_add_with_viewport + (GTK_SCROLLED_WINDOW(imageview->scrolledwin), + imageview->image); + } else + gtk_image_set_from_pixbuf(GTK_IMAGE(imageview->image), pixbuf); + + gdk_pixbuf_unref(pixbuf); + + gtk_widget_show(imageview->image); +} +#else +void imageview_show_image(ImageView *imageview, MimeInfo *mimeinfo, + const gchar *file, gboolean resize) +{ +} +#endif /* HAVE_GDK_PIXBUF */ + +void imageview_clear(ImageView *imageview) +{ + GtkAdjustment *hadj, *vadj; + + if (imageview->image) + gtk_image_set_from_pixmap(GTK_IMAGE(imageview->image), + NULL, NULL); + hadj = gtk_scrolled_window_get_hadjustment + (GTK_SCROLLED_WINDOW(imageview->scrolledwin)); + gtk_adjustment_set_value(hadj, 0.0); + vadj = gtk_scrolled_window_get_vadjustment + (GTK_SCROLLED_WINDOW(imageview->scrolledwin)); + gtk_adjustment_set_value(vadj, 0.0); + + if (imageview->image_data) { +#if HAVE_GDK_PIXBUF + gdk_pixbuf_unref((GdkPixbuf *)imageview->image_data); +#endif + imageview->image_data = NULL; + } +} + +void imageview_destroy(ImageView *imageview) +{ + imageview_clear(imageview); + g_free(imageview); +} + +static void get_resized_size(gint w, gint h, gint aw, gint ah, + gint *sw, gint *sh) +{ + gfloat wratio = 1.0; + gfloat hratio = 1.0; + gfloat ratio = 1.0; + + if (w <= aw && h <= ah) { + *sw = w; + *sh = h; + return; + } + + if (w > aw) + wratio = (gfloat)aw / (gfloat)w; + if (h > ah) + hratio = (gfloat)ah / (gfloat)h; + + ratio = (wratio > hratio) ? hratio : wratio; + + *sw = (gint)(w * ratio); + *sh = (gint)(h * ratio); + + /* restrict minimum size */ + if (*sw < 16 || *sh < 16) { + wratio = 16.0 / (gfloat)w; + hratio = 16.0 / (gfloat)h; + ratio = (wratio > hratio) ? wratio : hratio; + if (ratio >= 1.0) { + *sw = w; + *sh = h; + } else { + *sw = (gint)(w * ratio); + *sh = (gint)(h * ratio); + } + } +} + +static gint button_press_cb(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + ImageView *imageview = (ImageView *)data; + + if (event->button == 1 && imageview->image) { + imageview_show_image(imageview, NULL, NULL, !imageview->resize); + return TRUE; + } + return FALSE; +} + +static void size_allocate_cb(GtkWidget *widget,GtkAllocation *allocation, + gpointer data) +{ + ImageView *imageview = (ImageView *)data; + + if (imageview->resize) { + if (imageview->resizing) { + imageview->resizing = FALSE; + return; + } + imageview_show_image(imageview, NULL, NULL, TRUE); + imageview->resizing = TRUE; + } +} diff --git a/src/imageview.h b/src/imageview.h new file mode 100644 index 00000000..52269e3e --- /dev/null +++ b/src/imageview.h @@ -0,0 +1,52 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __IMAGEVIEW_H__ +#define __IMAGEVIEW_H__ + +#include +#include + +typedef struct _ImageView ImageView; + +#include "messageview.h" +#include "procmime.h" + +struct _ImageView +{ + GtkWidget *scrolledwin; + GtkWidget *image; + + gpointer image_data; + gboolean resize; + gboolean resizing; + + MessageView *messageview; +}; + +ImageView *imageview_create (void); +void imageview_init (ImageView *imageview); +void imageview_show_image (ImageView *imageview, + MimeInfo *mimeinfo, + const gchar *file, + gboolean resize); +void imageview_clear (ImageView *imageview); +void imageview_destroy (ImageView *imageview); + +#endif /* __IMAGEVIEW_H__ */ diff --git a/src/imap.c b/src/imap.c new file mode 100644 index 00000000..d038162f --- /dev/null +++ b/src/imap.c @@ -0,0 +1,3976 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_ICONV +# include +#endif + +#include "intl.h" +#include "imap.h" +#include "socket.h" +#include "ssl.h" +#include "recv.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "prefs_account.h" +#include "codeconv.h" +#include "md5.h" +#include "base64.h" +#include "utils.h" +#include "prefs_common.h" +#include "inputdialog.h" + +#define IMAP4_PORT 143 +#if USE_SSL +#define IMAPS_PORT 993 +#endif + +#define IMAP_CMD_LIMIT 1000 + +#define QUOTE_IF_REQUIRED(out, str) \ +{ \ + if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \ + gchar *__tmp; \ + gint len; \ + \ + len = strlen(str) + 3; \ + Xalloca(__tmp, len, return IMAP_ERROR); \ + g_snprintf(__tmp, len, "\"%s\"", str); \ + out = __tmp; \ + } else { \ + Xstrdup_a(out, str, return IMAP_ERROR); \ + } \ +} + +static GList *session_list = NULL; + +static void imap_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static Folder *imap_folder_new (const gchar *name, + const gchar *path); +static void imap_folder_destroy (Folder *folder); + +static Session *imap_session_new (PrefsAccount *account); +static void imap_session_destroy (Session *session); +/* static void imap_session_destroy_all (void); */ + +static gint imap_search_flags (IMAPSession *session, + GArray **uids, + GHashTable **flags_table); +static gint imap_fetch_flags (IMAPSession *session, + GArray **uids, + GHashTable **flags_table); + +static GSList *imap_get_msg_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static gchar *imap_fetch_msg (Folder *folder, + FolderItem *item, + gint uid); +static MsgInfo *imap_get_msginfo (Folder *folder, + FolderItem *item, + gint uid); +static gint imap_add_msg (Folder *folder, + FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); +static gint imap_add_msgs (Folder *folder, + FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); + +static gint imap_move_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint imap_move_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); +static gint imap_copy_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint imap_copy_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); + +static gint imap_remove_msg (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); +static gint imap_remove_msgs (Folder *folder, + FolderItem *item, + GSList *msglist); +static gint imap_remove_all_msg (Folder *folder, + FolderItem *item); + +static gboolean imap_is_msg_changed (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); + +static gint imap_close (Folder *folder, + FolderItem *item); + +static gint imap_scan_folder (Folder *folder, + FolderItem *item); +static gint imap_scan_tree (Folder *folder); + +static gint imap_create_tree (Folder *folder); + +static FolderItem *imap_create_folder (Folder *folder, + FolderItem *parent, + const gchar *name); +static gint imap_rename_folder (Folder *folder, + FolderItem *item, + const gchar *name); +static gint imap_remove_folder (Folder *folder, + FolderItem *item); + +static IMAPSession *imap_session_get (Folder *folder); + +static gint imap_greeting (IMAPSession *session); +static gint imap_auth (IMAPSession *session, + const gchar *user, + const gchar *pass, + IMAPAuthType type); + +static gint imap_scan_tree_recursive (IMAPSession *session, + FolderItem *item); +static GSList *imap_parse_list (IMAPSession *session, + const gchar *real_path, + gchar *separator); + +static void imap_create_missing_folders (Folder *folder); +static FolderItem *imap_create_special_folder + (Folder *folder, + SpecialFolderItemType stype, + const gchar *name); + +static gint imap_do_copy_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist, + gboolean remove_source); +static gint imap_remove_msgs_by_seq_set (Folder *folder, + FolderItem *item, + GSList *seq_list); + +static GSList *imap_get_uncached_messages (IMAPSession *session, + FolderItem *item, + guint32 first_uid, + guint32 last_uid, + gboolean update_count); +static void imap_delete_cached_message (FolderItem *item, + guint32 uid); +static GSList *imap_delete_cached_messages (GSList *mlist, + FolderItem *item, + guint32 first_uid, + guint32 last_uid); +static void imap_delete_all_cached_messages (FolderItem *item); + +#if USE_SSL +static SockInfo *imap_open (const gchar *server, + gushort port, + SSLType ssl_type); +#else +static SockInfo *imap_open (const gchar *server, + gushort port); +#endif + +static gint imap_msg_list_change_perm_flags (GSList *msglist, + MsgPermFlags flags, + gboolean is_set); +static gchar *imap_get_flag_str (IMAPFlags flags); +static gint imap_set_message_flags (IMAPSession *session, + const gchar *seq_set, + IMAPFlags flags, + gboolean is_set); +static gint imap_select (IMAPSession *session, + IMAPFolder *folder, + const gchar *path, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_status (IMAPSession *session, + IMAPFolder *folder, + const gchar *path, + gint *messages, + gint *recent, + guint32 *uid_next, + guint32 *uid_validity, + gint *unseen); + +static void imap_parse_namespace (IMAPSession *session, + IMAPFolder *folder); +static void imap_get_namespace_by_list (IMAPSession *session, + IMAPFolder *folder); +static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder, + const gchar *path); +static gchar imap_get_path_separator (IMAPFolder *folder, + const gchar *path); +static gchar *imap_get_real_path (IMAPFolder *folder, + const gchar *path); + +static gchar *imap_parse_atom (IMAPSession *session, + gchar *src, + gchar *dest, + gint dest_len, + GString *str); +static MsgFlags imap_parse_flags (const gchar *flag_str); +static IMAPFlags imap_parse_imap_flags (const gchar *flag_str); +static MsgInfo *imap_parse_envelope (IMAPSession *session, + FolderItem *item, + GString *line_str); + +static gboolean imap_has_capability (IMAPSession *session, + const gchar *capability); +static void imap_capability_free (IMAPSession *session); + +/* low-level IMAP4rev1 commands */ +static gint imap_cmd_capability (IMAPSession *session); +static gint imap_cmd_authenticate + (IMAPSession *session, + const gchar *user, + const gchar *pass, + IMAPAuthType type); +static gint imap_cmd_login (IMAPSession *session, + const gchar *user, + const gchar *pass); +static gint imap_cmd_logout (IMAPSession *session); +static gint imap_cmd_noop (IMAPSession *session); +#if USE_SSL +static gint imap_cmd_starttls (IMAPSession *session); +#endif +static gint imap_cmd_namespace (IMAPSession *session, + gchar **ns_str); +static gint imap_cmd_list (IMAPSession *session, + const gchar *ref, + const gchar *mailbox, + GPtrArray *argbuf); +static gint imap_cmd_do_select (IMAPSession *session, + const gchar *folder, + gboolean examine, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_cmd_select (IMAPSession *session, + const gchar *folder, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_cmd_examine (IMAPSession *session, + const gchar *folder, + gint *exists, + gint *recent, + gint *unseen, + guint32 *uid_validity); +static gint imap_cmd_create (IMAPSession *session, + const gchar *folder); +static gint imap_cmd_rename (IMAPSession *session, + const gchar *oldfolder, + const gchar *newfolder); +static gint imap_cmd_delete (IMAPSession *session, + const gchar *folder); +static gint imap_cmd_envelope (IMAPSession *session, + const gchar *seq_set); +static gint imap_cmd_search (IMAPSession *session, + const gchar *criteria, + GArray **result); +static gint imap_cmd_fetch (IMAPSession *session, + guint32 uid, + const gchar *filename); +static gint imap_cmd_append (IMAPSession *session, + const gchar *destfolder, + const gchar *file, + IMAPFlags flags, + guint32 *new_uid); +static gint imap_cmd_copy (IMAPSession *session, + const gchar *seq_set, + const gchar *destfolder); +static gint imap_cmd_store (IMAPSession *session, + const gchar *seq_set, + const gchar *sub_cmd); +static gint imap_cmd_expunge (IMAPSession *session); +static gint imap_cmd_close (IMAPSession *session); + +static gint imap_cmd_ok (IMAPSession *session, + GPtrArray *argbuf); +static void imap_cmd_gen_send (IMAPSession *session, + const gchar *format, ...); +static gint imap_cmd_gen_recv (IMAPSession *session, + gchar **ret); + +/* misc utility functions */ +static gchar *strchr_cpy (const gchar *src, + gchar ch, + gchar *dest, + gint len); +static gchar *get_quoted (const gchar *src, + gchar ch, + gchar *dest, + gint len); +static gchar *search_array_contain_str (GPtrArray *array, + gchar *str); +static gchar *search_array_str (GPtrArray *array, + gchar *str); +static void imap_path_separator_subst (gchar *str, + gchar separator); + +static gchar *imap_modified_utf7_to_locale (const gchar *mutf7_str); +static gchar *imap_locale_to_modified_utf7 (const gchar *from); + +static GSList *imap_get_seq_set_from_msglist (GSList *msglist); +static void imap_seq_set_free (GSList *seq_list); + +static GHashTable *imap_get_uid_table (GArray *array); + +static gboolean imap_rename_folder_func (GNode *node, + gpointer data); + +static FolderClass imap_class = +{ + F_IMAP, + + imap_folder_new, + imap_folder_destroy, + + imap_scan_tree, + imap_create_tree, + + imap_get_msg_list, + imap_fetch_msg, + imap_get_msginfo, + imap_add_msg, + imap_add_msgs, + imap_move_msg, + imap_move_msgs, + imap_copy_msg, + imap_copy_msgs, + imap_remove_msg, + imap_remove_msgs, + imap_remove_all_msg, + imap_is_msg_changed, + imap_close, + imap_scan_folder, + + imap_create_folder, + imap_rename_folder, + imap_remove_folder +}; + + +FolderClass *imap_get_class(void) +{ + return &imap_class; +} + +static Folder *imap_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(IMAPFolder, 1); + imap_folder_init(folder, name, path); + + return folder; +} + +static void imap_folder_destroy(Folder *folder) +{ + gchar *dir; + + dir = folder_get_path(folder); + if (is_dir_exist(dir)) + remove_dir_recursive(dir); + g_free(dir); + + folder_remote_folder_destroy(REMOTE_FOLDER(folder)); +} + +static void imap_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder->klass = imap_get_class(); + folder_remote_folder_init(folder, name, path); +} + +static IMAPSession *imap_session_get(Folder *folder) +{ + RemoteFolder *rfolder = REMOTE_FOLDER(folder); + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + if (!prefs_common.online_mode) + return NULL; + + if (!rfolder->session) { + rfolder->session = imap_session_new(folder->account); + if (rfolder->session) + imap_parse_namespace(IMAP_SESSION(rfolder->session), + IMAP_FOLDER(folder)); + return IMAP_SESSION(rfolder->session); + } + + if (time(NULL) - rfolder->session->last_access_time < + SESSION_TIMEOUT_INTERVAL) { + return IMAP_SESSION(rfolder->session); + } + + if (imap_cmd_noop(IMAP_SESSION(rfolder->session)) != IMAP_SUCCESS) { + log_warning(_("IMAP4 connection to %s has been" + " disconnected. Reconnecting...\n"), + folder->account->recv_server); + session_destroy(rfolder->session); + rfolder->session = imap_session_new(folder->account); + if (rfolder->session) + imap_parse_namespace(IMAP_SESSION(rfolder->session), + IMAP_FOLDER(folder)); + } + + return IMAP_SESSION(rfolder->session); +} + +static gint imap_greeting(IMAPSession *session) +{ + gchar *greeting; + gint ok; + + if ((ok = imap_cmd_gen_recv(session, &greeting)) != IMAP_SUCCESS) + return ok; + + if (greeting[0] != '*' || greeting[1] != ' ') + ok = IMAP_ERROR; + else if (!strncmp(greeting + 2, "OK", 2)) + ok = IMAP_SUCCESS; + else if (!strncmp(greeting + 2, "PREAUTH", 7)) { + session->authenticated = TRUE; + ok = IMAP_SUCCESS; + } else + ok = IMAP_ERROR; + + g_free(greeting); + return ok; +} + +static gint imap_auth(IMAPSession *session, const gchar *user, + const gchar *pass, IMAPAuthType type) +{ + gint ok; + + if (type == 0 || type == IMAP_AUTH_LOGIN) + ok = imap_cmd_login(session, user, pass); + else + ok = imap_cmd_authenticate(session, user, pass, type); + + if (ok == IMAP_SUCCESS) + session->authenticated = TRUE; + + return ok; +} + +static Session *imap_session_new(PrefsAccount *account) +{ + IMAPSession *session; + SockInfo *sock; + gchar *pass; + gushort port; + + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(account->recv_server != NULL, NULL); + g_return_val_if_fail(account->userid != NULL, NULL); + + pass = account->passwd; + if (!pass) { + gchar *tmp_pass; + tmp_pass = input_dialog_query_password(account->recv_server, + account->userid); + if (!tmp_pass) + return NULL; + Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return NULL;}); + g_free(tmp_pass); + } + +#if USE_SSL + port = account->set_imapport ? account->imapport + : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT; +#else + port = account->set_imapport ? account->imapport : IMAP4_PORT; +#endif + + log_message(_("creating IMAP4 connection to %s:%d ...\n"), + account->recv_server, port); + +#if USE_SSL + if ((sock = imap_open(account->recv_server, port, + account->ssl_imap)) == NULL) +#else + if ((sock = imap_open(account->recv_server, port)) == NULL) +#endif + return NULL; + + session = g_new0(IMAPSession, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_IMAP; + SESSION(session)->server = g_strdup(account->recv_server); + SESSION(session)->sock = sock; + SESSION(session)->last_access_time = time(NULL); + SESSION(session)->data = NULL; + + SESSION(session)->destroy = imap_session_destroy; + + session->authenticated = FALSE; + session->mbox = NULL; + session->cmd_count = 0; + + session_list = g_list_append(session_list, session); + + if (imap_greeting(session) != IMAP_SUCCESS) { + session_destroy(SESSION(session)); + return NULL; + } + + if (imap_cmd_capability(session) != IMAP_SUCCESS) { + session_destroy(SESSION(session)); + return NULL; + } + if (imap_has_capability(session, "UIDPLUS")) + session->uidplus = TRUE; + +#if USE_SSL + if (account->ssl_imap == SSL_STARTTLS && + imap_has_capability(session, "STARTTLS")) { + gint ok; + + ok = imap_cmd_starttls(session); + if (ok != IMAP_SUCCESS) { + log_warning(_("Can't start TLS session.\n")); + session_destroy(SESSION(session)); + return NULL; + } + if (!ssl_init_socket_with_method(sock, SSL_METHOD_TLSv1)) { + session_destroy(SESSION(session)); + return NULL; + } + } +#endif + + if (!session->authenticated && + imap_auth(session, account->userid, pass, account->imap_auth_type) + != IMAP_SUCCESS) { + imap_cmd_logout(session); + session_destroy(SESSION(session)); + return NULL; + } + + return SESSION(session); +} + +static void imap_session_destroy(Session *session) +{ + imap_capability_free(IMAP_SESSION(session)); + g_free(IMAP_SESSION(session)->mbox); + session_list = g_list_remove(session_list, session); +} + +#if 0 +static void imap_session_destroy_all(void) +{ + while (session_list != NULL) { + IMAPSession *session = (IMAPSession *)session_list->data; + + imap_cmd_logout(session); + session_destroy(SESSION(session)); + } +} +#endif + +#define THROW goto catch + +static gint imap_search_flags(IMAPSession *session, GArray **uids, + GHashTable **flags_table) +{ + gint ok; + gint i; + GArray *flag_uids; + GHashTable *unseen_table; + GHashTable *flagged_table; + GHashTable *answered_table; + guint32 uid; + IMAPFlags flags; + + ok = imap_cmd_search(session, "ALL", uids); + if (ok != IMAP_SUCCESS) return ok; + + ok = imap_cmd_search(session, "UNSEEN", &flag_uids); + if (ok != IMAP_SUCCESS) { + g_array_free(*uids, TRUE); + return ok; + } + unseen_table = imap_get_uid_table(flag_uids); + g_array_free(flag_uids, TRUE); + ok = imap_cmd_search(session, "FLAGGED", &flag_uids); + if (ok != IMAP_SUCCESS) { + g_hash_table_destroy(unseen_table); + g_array_free(*uids, TRUE); + return ok; + } + flagged_table = imap_get_uid_table(flag_uids); + g_array_free(flag_uids, TRUE); + ok = imap_cmd_search(session, "ANSWERED", &flag_uids); + if (ok != IMAP_SUCCESS) { + g_hash_table_destroy(flagged_table); + g_hash_table_destroy(unseen_table); + g_array_free(*uids, TRUE); + return ok; + } + answered_table = imap_get_uid_table(flag_uids); + g_array_free(flag_uids, TRUE); + + *flags_table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < (*uids)->len; i++) { + uid = g_array_index(*uids, guint32, i); + flags = IMAP_FLAG_DRAFT; + if (!g_hash_table_lookup(unseen_table, GUINT_TO_POINTER(uid))) + flags |= IMAP_FLAG_SEEN; + if (g_hash_table_lookup(flagged_table, GUINT_TO_POINTER(uid))) + flags |= IMAP_FLAG_FLAGGED; + if (g_hash_table_lookup(answered_table, GUINT_TO_POINTER(uid))) + flags |= IMAP_FLAG_ANSWERED; + g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid), + GINT_TO_POINTER(flags)); + } + + g_hash_table_destroy(answered_table); + g_hash_table_destroy(flagged_table); + g_hash_table_destroy(unseen_table); + + return IMAP_SUCCESS; +} + +static gint imap_fetch_flags(IMAPSession *session, GArray **uids, + GHashTable **flags_table) +{ + gint ok; + gchar *tmp; + gchar *cur_pos; + gchar buf[IMAPBUFSIZE]; + guint32 uid; + IMAPFlags flags; + + imap_cmd_gen_send(session, "UID FETCH 1:* (UID FLAGS)"); + + *uids = g_array_new(FALSE, FALSE, sizeof(guint32)); + *flags_table = g_hash_table_new(NULL, g_direct_equal); + + while ((ok = imap_cmd_gen_recv(session, &tmp)) == IMAP_SUCCESS) { + if (tmp[0] != '*' || tmp[1] != ' ') { + g_free(tmp); + break; + } + cur_pos = tmp + 2; + +#define PARSE_ONE_ELEMENT(ch) \ +{ \ + cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \ + if (cur_pos == NULL) { \ + g_warning("cur_pos == NULL\n"); \ + g_free(tmp); \ + g_hash_table_destroy(*flags_table); \ + g_array_free(*uids, TRUE); \ + return IMAP_ERROR; \ + } \ +} + + PARSE_ONE_ELEMENT(' '); + PARSE_ONE_ELEMENT(' '); + if (strcmp(buf, "FETCH") != 0) { + g_free(tmp); + continue; + } + if (*cur_pos != '(') { + g_free(tmp); + continue; + } + cur_pos++; + uid = 0; + flags = 0; + + while (*cur_pos != '\0' && *cur_pos != ')') { + while (*cur_pos == ' ') cur_pos++; + + if (!strncmp(cur_pos, "UID ", 4)) { + cur_pos += 4; + uid = strtoul(cur_pos, &cur_pos, 10); + } else if (!strncmp(cur_pos, "FLAGS ", 6)) { + cur_pos += 6; + if (*cur_pos != '(') { + g_warning("*cur_pos != '('\n"); + break; + } + cur_pos++; + PARSE_ONE_ELEMENT(')'); + flags = imap_parse_imap_flags(buf); + flags |= IMAP_FLAG_DRAFT; + } else { + g_warning("invalid FETCH response: %s\n", cur_pos); + break; + } + } + +#undef PARSE_ONE_ELEMENT + + if (uid > 0) { + g_array_append_val(*uids, uid); + g_hash_table_insert(*flags_table, GUINT_TO_POINTER(uid), + GINT_TO_POINTER(flags)); + } + + g_free(tmp); + } + + if (ok != IMAP_SUCCESS) { + g_hash_table_destroy(*flags_table); + g_array_free(*uids, TRUE); + } + + return ok; +} + +static GSList *imap_get_msg_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + GSList *mlist = NULL; + IMAPSession *session; + gint ok, exists = 0, recent = 0, unseen = 0; + guint32 uid_validity = 0; + guint32 first_uid = 0, last_uid = 0; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + item->new = item->unread = item->total = 0; + + session = imap_session_get(folder); + + if (!session) { + mlist = procmsg_read_cache(item, FALSE); + item->last_num = procmsg_get_last_num_in_msg_list(mlist); + procmsg_set_flags(mlist, item); + return mlist; + } + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + &exists, &recent, &unseen, &uid_validity); + if (ok != IMAP_SUCCESS) THROW; + + if (exists == 0) { + imap_delete_all_cached_messages(item); + return NULL; + } + + /* invalidate current cache if UIDVALIDITY has been changed */ + if (item->mtime != uid_validity) { + debug_print("imap_get_msg_list: " + "UIDVALIDITY has been changed.\n"); + use_cache = FALSE; + } + + if (use_cache) { + GArray *uids; + GHashTable *msg_table; + GHashTable *flags_table; + guint32 cache_last; + guint32 begin = 0; + GSList *cur, *next = NULL; + MsgInfo *msginfo; + IMAPFlags imap_flags; + + /* get cache data */ + mlist = procmsg_read_cache(item, FALSE); + procmsg_set_flags(mlist, item); + cache_last = procmsg_get_last_num_in_msg_list(mlist); + + /* get all UID list and flags */ + ok = imap_search_flags(session, &uids, &flags_table); + if (ok != IMAP_SUCCESS) { + if (ok == IMAP_SOCKET || ok == IMAP_IOERR) THROW; + ok = imap_fetch_flags(session, &uids, &flags_table); + if (ok != IMAP_SUCCESS) THROW; + } + + if (uids->len > 0) { + first_uid = g_array_index(uids, guint32, 0); + last_uid = g_array_index(uids, guint32, uids->len - 1); + } else { + g_array_free(uids, TRUE); + g_hash_table_destroy(flags_table); + THROW; + } + + /* sync message flags with server */ + for (cur = mlist; cur != NULL; cur = next) { + msginfo = (MsgInfo *)cur->data; + next = cur->next; + imap_flags = GPOINTER_TO_INT(g_hash_table_lookup + (flags_table, + GUINT_TO_POINTER(msginfo->msgnum))); + + if (imap_flags == 0) { + debug_print("imap_get_msg_list: " + "message %u has been deleted.\n", + msginfo->msgnum); + imap_delete_cached_message + (item, msginfo->msgnum); + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + item->total--; + mlist = g_slist_remove(mlist, msginfo); + procmsg_msginfo_free(msginfo); + continue; + } + + if (!IMAP_IS_SEEN(imap_flags)) { + if (!MSG_IS_UNREAD(msginfo->flags)) { + item->unread++; + MSG_SET_PERM_FLAGS(msginfo->flags, + MSG_UNREAD); + } + } else { + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_NEW|MSG_UNREAD); + } + + if (IMAP_IS_FLAGGED(imap_flags)) { + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_MARKED); + } else { + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_MARKED); + } + if (IMAP_IS_ANSWERED(imap_flags)) { + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_REPLIED); + } else { + MSG_UNSET_PERM_FLAGS(msginfo->flags, + MSG_REPLIED); + } + } + + /* check for the first new message */ + msg_table = procmsg_msg_hash_table_create(mlist); + if (msg_table == NULL) + begin = first_uid; + else { + gint i; + + for (i = 0; i < uids->len; i++) { + guint32 uid; + + uid = g_array_index(uids, guint32, i); + if (g_hash_table_lookup + (msg_table, GUINT_TO_POINTER(uid)) + == NULL) { + debug_print("imap_get_msg_list: " + "first new UID: %u\n", uid); + begin = uid; + break; + } + } + g_hash_table_destroy(msg_table); + } + + g_array_free(uids, TRUE); + g_hash_table_destroy(flags_table); + + /* remove ununsed caches */ + if (first_uid > 0 && last_uid > 0) { + mlist = imap_delete_cached_messages + (mlist, item, 0, first_uid - 1); + mlist = imap_delete_cached_messages + (mlist, item, begin > 0 ? begin : last_uid + 1, + UINT_MAX); + } + + if (begin > 0 && begin <= last_uid) { + GSList *newlist; + newlist = imap_get_uncached_messages(session, item, + begin, last_uid, + TRUE); + mlist = g_slist_concat(mlist, newlist); + } + } else { + imap_delete_all_cached_messages(item); + mlist = imap_get_uncached_messages(session, item, 0, 0, TRUE); + last_uid = procmsg_get_last_num_in_msg_list(mlist); + } + + item->mtime = uid_validity; + + mlist = procmsg_sort_msg_list(mlist, item->sort_key, item->sort_type); + + item->last_num = last_uid; + +catch: + return mlist; +} + +#undef THROW + +static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid) +{ + gchar *path, *filename; + IMAPSession *session; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + path = folder_item_get_path(item); + if (!is_dir_exist(path)) + make_dir_hier(path); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL); + g_free(path); + + if (is_file_exist(filename)) { + debug_print("message %d has been already cached.\n", uid); + return filename; + } + + session = imap_session_get(folder); + if (!session) { + g_free(filename); + return NULL; + } + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) { + g_warning("can't select mailbox %s\n", item->path); + g_free(filename); + return NULL; + } + + debug_print("getting message %d...\n", uid); + ok = imap_cmd_fetch(session, (guint32)uid, filename); + + if (ok != IMAP_SUCCESS) { + g_warning("can't fetch message %d\n", uid); + g_free(filename); + return NULL; + } + + return filename; +} + +static MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid) +{ + IMAPSession *session; + GSList *list; + MsgInfo *msginfo = NULL; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + session = imap_session_get(folder); + g_return_val_if_fail(session != NULL, NULL); + + list = imap_get_uncached_messages(session, item, uid, uid, FALSE); + if (list) { + msginfo = (MsgInfo *)list->data; + list->data = NULL; + } + procmsg_msg_list_free(list); + + return msginfo; +} + +static gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file, + MsgFlags *flags, gboolean remove_source) +{ + GSList file_list; + MsgFileInfo fileinfo; + + g_return_val_if_fail(file != NULL, -1); + + fileinfo.file = (gchar *)file; + fileinfo.flags = flags; + file_list.data = &fileinfo; + file_list.next = NULL; + + return imap_add_msgs(folder, dest, &file_list, remove_source, NULL); +} + +static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, + gboolean remove_source, gint *first) +{ + gchar *destdir; + IMAPSession *session; + gint messages, recent, unseen; + guint32 uid_next, uid_validity; + guint32 last_uid = 0; + GSList *cur; + MsgFileInfo *fileinfo; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_status(session, IMAP_FOLDER(folder), dest->path, + &messages, &recent, &uid_next, &uid_validity, &unseen); + if (ok != IMAP_SUCCESS) { + g_warning("can't append messages\n"); + return -1; + } + + destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path); + + if (!session->uidplus) + last_uid = uid_next - 1; + if (first) + *first = uid_next; + + for (cur = file_list; cur != NULL; cur = cur->next) { + IMAPFlags iflags = 0; + guint32 new_uid = 0; + + fileinfo = (MsgFileInfo *)cur->data; + + if (fileinfo->flags) { + if (MSG_IS_MARKED(*fileinfo->flags)) + iflags |= IMAP_FLAG_FLAGGED; + if (MSG_IS_REPLIED(*fileinfo->flags)) + iflags |= IMAP_FLAG_ANSWERED; + if (!MSG_IS_UNREAD(*fileinfo->flags)) + iflags |= IMAP_FLAG_SEEN; + } + + if (dest->stype == F_OUTBOX || + dest->stype == F_QUEUE || + dest->stype == F_DRAFT || + dest->stype == F_TRASH) + iflags |= IMAP_FLAG_SEEN; + + ok = imap_cmd_append(session, destdir, fileinfo->file, iflags, + &new_uid); + + if (ok != IMAP_SUCCESS) { + g_warning("can't append message %s\n", fileinfo->file); + g_free(destdir); + return -1; + } + + if (!session->uidplus) + last_uid++; + else if (last_uid < new_uid) + last_uid = new_uid; + + dest->last_num = last_uid; + dest->total++; + dest->updated = TRUE; + + if (fileinfo->flags) { + if (MSG_IS_UNREAD(*fileinfo->flags)) + dest->unread++; + } else + dest->unread++; + } + + g_free(destdir); + + if (remove_source) { + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + if (unlink(fileinfo->file) < 0) + FILE_OP_ERROR(fileinfo->file, "unlink"); + } + } + + return last_uid; +} + +static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist, + gboolean remove_source) +{ + FolderItem *src; + gchar *destdir; + GSList *seq_list, *cur; + MsgInfo *msginfo; + IMAPSession *session; + gint ok = IMAP_SUCCESS; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + msginfo = (MsgInfo *)msglist->data; + + src = msginfo->folder; + if (src == dest) { + g_warning("the src folder is identical to the dest.\n"); + return -1; + } + + ok = imap_select(session, IMAP_FOLDER(folder), src->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path); + + seq_list = imap_get_seq_set_from_msglist(msglist); + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + if (remove_source) + debug_print("Moving message %s%c[%s] to %s ...\n", + src->path, G_DIR_SEPARATOR, + seq_set, destdir); + else + debug_print("Copying message %s%c[%s] to %s ...\n", + src->path, G_DIR_SEPARATOR, + seq_set, destdir); + + ok = imap_cmd_copy(session, seq_set, destdir); + if (ok != IMAP_SUCCESS) { + imap_seq_set_free(seq_list); + return -1; + } + } + + dest->updated = TRUE; + + if (remove_source) { + imap_remove_msgs_by_seq_set(folder, src, seq_list); + if (ok != IMAP_SUCCESS) { + imap_seq_set_free(seq_list); + return ok; + } + } + + imap_seq_set_free(seq_list); + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + dest->total++; + if (MSG_IS_NEW(msginfo->flags)) + dest->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + dest->unread++; + + if (remove_source) { + src->total--; + if (MSG_IS_NEW(msginfo->flags)) + src->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + src->unread--; + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + } + } + + g_free(destdir); + + if (ok == IMAP_SUCCESS) + return 0; + else + return -1; +} + +static gint imap_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_move_msgs(folder, dest, &msglist); +} + +static gint imap_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + MsgInfo *msginfo; + GSList *file_list; + gint ret = 0; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + msginfo = (MsgInfo *)msglist->data; + g_return_val_if_fail(msginfo->folder != NULL, -1); + + if (folder == msginfo->folder->folder) + return imap_do_copy_msgs(folder, dest, msglist, TRUE); + + file_list = procmsg_get_message_file_list(msglist); + g_return_val_if_fail(file_list != NULL, -1); + + ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL); + + procmsg_message_file_list_free(file_list); + + if (ret != -1) + ret = folder_item_remove_msgs(msginfo->folder, msglist); + + return ret; +} + +static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_copy_msgs(folder, dest, &msglist); +} + +static gint imap_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + MsgInfo *msginfo; + GSList *file_list; + gint ret; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + msginfo = (MsgInfo *)msglist->data; + g_return_val_if_fail(msginfo->folder != NULL, -1); + + if (folder == msginfo->folder->folder) + return imap_do_copy_msgs(folder, dest, msglist, FALSE); + + file_list = procmsg_get_message_file_list(msglist); + g_return_val_if_fail(file_list != NULL, -1); + + ret = imap_add_msgs(folder, dest, file_list, FALSE, NULL); + + procmsg_message_file_list_free(file_list); + + return ret; +} + +static gint imap_remove_msgs_by_seq_set(Folder *folder, FolderItem *item, + GSList *seq_list) +{ + gint ok; + IMAPSession *session; + GSList *cur; + + g_return_val_if_fail(seq_list != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + ok = imap_set_message_flags(session, seq_set, IMAP_FLAG_DELETED, + TRUE); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't set deleted flags: %s\n"), + seq_set); + return ok; + } + } + + ok = imap_cmd_expunge(session); + if (ok != IMAP_SUCCESS) + log_warning(_("can't expunge\n")); + + item->updated = TRUE; + + return ok; +} + +static gint imap_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_remove_msgs(folder, item, &msglist); +} + +static gint imap_remove_msgs(Folder *folder, FolderItem *item, GSList *msglist) +{ + gint ok; + IMAPSession *session; + GSList *seq_list, *cur; + gchar *dir; + gboolean dir_exist; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + seq_list = imap_get_seq_set_from_msglist(msglist); + ok = imap_remove_msgs_by_seq_set(folder, item, seq_list); + imap_seq_set_free(seq_list); + if (ok != IMAP_SUCCESS) + return ok; + + dir = folder_item_get_path(item); + dir_exist = is_dir_exist(dir); + for (cur = msglist; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + guint32 uid = msginfo->msgnum; + + if (dir_exist) + remove_numbered_files(dir, uid, uid); + item->total--; + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + } + g_free(dir); + + return IMAP_SUCCESS; +} + +static gint imap_remove_all_msg(Folder *folder, FolderItem *item) +{ + gint ok; + IMAPSession *session; + gchar *dir; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), item->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + imap_cmd_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)"); + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't set deleted flags: 1:*\n")); + return ok; + } + + ok = imap_cmd_expunge(session); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't expunge\n")); + return ok; + } + + item->new = item->unread = item->total = 0; + item->updated = TRUE; + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) + remove_all_numbered_files(dir); + g_free(dir); + + return IMAP_SUCCESS; +} + +static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, + MsgInfo *msginfo) +{ + /* TODO: properly implement this method */ + return FALSE; +} + +static gint imap_close(Folder *folder, FolderItem *item) +{ + gint ok; + IMAPSession *session; + + g_return_val_if_fail(folder != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + if (session->mbox) { + if (strcmp2(session->mbox, item->path) != 0) return -1; + + ok = imap_cmd_close(session); + if (ok != IMAP_SUCCESS) + log_warning(_("can't close folder\n")); + + g_free(session->mbox); + session->mbox = NULL; + + return ok; + } else + return 0; +} + +static gint imap_scan_folder(Folder *folder, FolderItem *item) +{ + IMAPSession *session; + gint messages, recent, unseen; + guint32 uid_next, uid_validity; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_status(session, IMAP_FOLDER(folder), item->path, + &messages, &recent, &uid_next, &uid_validity, &unseen); + if (ok != IMAP_SUCCESS) return -1; + + item->new = unseen > 0 ? recent : 0; + item->unread = unseen; + item->total = messages; + item->last_num = (messages > 0 && uid_next > 0) ? uid_next - 1 : 0; + /* item->mtime = uid_validity; */ + item->updated = TRUE; + + return 0; +} + +static gint imap_scan_tree(Folder *folder) +{ + FolderItem *item = NULL; + IMAPSession *session; + gchar *root_folder = NULL; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(folder->account != NULL, -1); + + session = imap_session_get(folder); + if (!session) { + if (!folder->node) { + folder_tree_destroy(folder); + item = folder_item_new(folder->name, NULL); + item->folder = folder; + folder->node = item->node = g_node_new(item); + } + return -1; + } + + if (folder->account->imap_dir && *folder->account->imap_dir) { + gchar *real_path; + GPtrArray *argbuf; + gint ok; + + Xstrdup_a(root_folder, folder->account->imap_dir, return -1); + extract_quote(root_folder, '"'); + subst_char(root_folder, + imap_get_path_separator(IMAP_FOLDER(folder), + root_folder), + '/'); + strtailchomp(root_folder, '/'); + real_path = imap_get_real_path + (IMAP_FOLDER(folder), root_folder); + debug_print("IMAP root directory: %s\n", real_path); + + /* check if root directory exist */ + argbuf = g_ptr_array_new(); + ok = imap_cmd_list(session, NULL, real_path, argbuf); + if (ok != IMAP_SUCCESS || + search_array_str(argbuf, "LIST ") == NULL) { + log_warning(_("root folder %s not exist\n"), real_path); + g_ptr_array_free(argbuf, TRUE); + g_free(real_path); + return -1; + } + g_ptr_array_free(argbuf, TRUE); + g_free(real_path); + } + + if (folder->node) + item = FOLDER_ITEM(folder->node->data); + if (!item || ((item->path || root_folder) && + strcmp2(item->path, root_folder) != 0)) { + folder_tree_destroy(folder); + item = folder_item_new(folder->name, root_folder); + item->folder = folder; + folder->node = item->node = g_node_new(item); + } + + imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data)); + imap_create_missing_folders(folder); + + return 0; +} + +static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item) +{ + Folder *folder; + IMAPFolder *imapfolder; + FolderItem *new_item; + GSList *item_list, *cur; + GNode *node; + gchar *real_path; + gchar *wildcard_path; + gchar separator; + gchar wildcard[3]; + + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->folder != NULL, -1); + g_return_val_if_fail(item->no_sub == FALSE, -1); + + folder = item->folder; + imapfolder = IMAP_FOLDER(folder); + + separator = imap_get_path_separator(imapfolder, item->path); + + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data); + + if (item->path) { + wildcard[0] = separator; + wildcard[1] = '%'; + wildcard[2] = '\0'; + real_path = imap_get_real_path(imapfolder, item->path); + } else { + wildcard[0] = '%'; + wildcard[1] = '\0'; + real_path = g_strdup(""); + } + + Xstrcat_a(wildcard_path, real_path, wildcard, + {g_free(real_path); return IMAP_ERROR;}); + QUOTE_IF_REQUIRED(wildcard_path, wildcard_path); + + imap_cmd_gen_send(session, "LIST \"\" %s", wildcard_path); + + strtailchomp(real_path, separator); + item_list = imap_parse_list(session, real_path, NULL); + g_free(real_path); + + node = item->node->children; + while (node != NULL) { + FolderItem *old_item = FOLDER_ITEM(node->data); + GNode *next = node->next; + + new_item = NULL; + + for (cur = item_list; cur != NULL; cur = cur->next) { + FolderItem *cur_item = FOLDER_ITEM(cur->data); + if (!strcmp2(old_item->path, cur_item->path)) { + new_item = cur_item; + break; + } + } + if (!new_item) { + debug_print("folder '%s' not found. removing...\n", + old_item->path); + folder_item_remove(old_item); + } else { + old_item->no_sub = new_item->no_sub; + old_item->no_select = new_item->no_select; + if (old_item->no_select == TRUE) + old_item->new = old_item->unread = + old_item->total = 0; + if (old_item->no_sub == TRUE && node->children) { + debug_print("folder '%s' doesn't have " + "subfolders. removing...\n", + old_item->path); + folder_item_remove_children(old_item); + } + } + + node = next; + } + + for (cur = item_list; cur != NULL; cur = cur->next) { + FolderItem *cur_item = FOLDER_ITEM(cur->data); + new_item = NULL; + for (node = item->node->children; node != NULL; + node = node->next) { + if (!strcmp2(FOLDER_ITEM(node->data)->path, + cur_item->path)) { + new_item = FOLDER_ITEM(node->data); + folder_item_destroy(cur_item); + cur_item = NULL; + break; + } + } + if (!new_item) { + new_item = cur_item; + debug_print("new folder '%s' found.\n", new_item->path); + folder_item_append(item, new_item); + } + + if (!strcmp(new_item->path, "INBOX")) { + new_item->stype = F_INBOX; + folder->inbox = new_item; + } else if (!item->parent || item->stype == F_INBOX) { + const gchar *base; + + base = g_basename(new_item->path); + + if (!folder->outbox && !strcasecmp(base, "Sent")) { + new_item->stype = F_OUTBOX; + folder->outbox = new_item; + } else if (!folder->draft && !strcasecmp(base, "Drafts")) { + new_item->stype = F_DRAFT; + folder->draft = new_item; + } else if (!folder->queue && !strcasecmp(base, "Queue")) { + new_item->stype = F_QUEUE; + folder->queue = new_item; + } else if (!folder->trash && !strcasecmp(base, "Trash")) { + new_item->stype = F_TRASH; + folder->trash = new_item; + } + } + + if (new_item->no_select == FALSE) + imap_scan_folder(folder, new_item); + if (new_item->no_sub == FALSE) + imap_scan_tree_recursive(session, new_item); + } + + g_slist_free(item_list); + + return IMAP_SUCCESS; +} + +static GSList *imap_parse_list(IMAPSession *session, const gchar *real_path, + gchar *separator) +{ + gchar buf[IMAPBUFSIZE]; + gchar flags[256]; + gchar separator_str[16]; + gchar *p; + const gchar *name; + gchar *loc_name, *loc_path; + GSList *item_list = NULL; + GString *str; + FolderItem *new_item; + + debug_print("getting list of %s ...\n", + *real_path ? real_path : "\"\""); + + str = g_string_new(NULL); + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) { + log_warning(_("error occurred while getting LIST.\n")); + break; + } + strretchomp(buf); + if (buf[0] != '*' || buf[1] != ' ') { + log_print("IMAP4< %s\n", buf); + if (sscanf(buf, "%*d %16s", buf) < 1 || + strcmp(buf, "OK") != 0) + log_warning(_("error occurred while getting LIST.\n")); + + break; + } + debug_print("IMAP4< %s\n", buf); + + g_string_assign(str, buf); + p = str->str + 2; + if (strncmp(p, "LIST ", 5) != 0) continue; + p += 5; + + if (*p != '(') continue; + p++; + p = strchr_cpy(p, ')', flags, sizeof(flags)); + if (!p) continue; + while (*p == ' ') p++; + + p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str)); + if (!p) continue; + extract_quote(separator_str, '"'); + if (!strcmp(separator_str, "NIL")) + separator_str[0] = '\0'; + if (separator) + *separator = separator_str[0]; + + buf[0] = '\0'; + while (*p == ' ') p++; + if (*p == '{' || *p == '"') + p = imap_parse_atom(session, p, buf, sizeof(buf), str); + else + strncpy2(buf, p, sizeof(buf)); + strtailchomp(buf, separator_str[0]); + if (buf[0] == '\0') continue; + if (!strcmp(buf, real_path)) continue; + + if (separator_str[0] != '\0') + subst_char(buf, separator_str[0], '/'); + name = g_basename(buf); + if (name[0] == '.') continue; + + loc_name = imap_modified_utf7_to_locale(name); + loc_path = imap_modified_utf7_to_locale(buf); + new_item = folder_item_new(loc_name, loc_path); + if (strcasestr(flags, "\\Noinferiors") != NULL) + new_item->no_sub = TRUE; + if (strcmp(buf, "INBOX") != 0 && + strcasestr(flags, "\\Noselect") != NULL) + new_item->no_select = TRUE; + + item_list = g_slist_append(item_list, new_item); + + debug_print("folder '%s' found.\n", loc_path); + g_free(loc_path); + g_free(loc_name); + } + + g_string_free(str, TRUE); + + return item_list; +} + +static gint imap_create_tree(Folder *folder) +{ + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(folder->node != NULL, -1); + g_return_val_if_fail(folder->node->data != NULL, -1); + g_return_val_if_fail(folder->account != NULL, -1); + + imap_scan_tree(folder); + imap_create_missing_folders(folder); + + return 0; +} + +static void imap_create_missing_folders(Folder *folder) +{ + g_return_if_fail(folder != NULL); + + if (!folder->inbox) + folder->inbox = imap_create_special_folder + (folder, F_INBOX, "INBOX"); +#if 0 + if (!folder->outbox) + folder->outbox = imap_create_special_folder + (folder, F_OUTBOX, "Sent"); + if (!folder->draft) + folder->draft = imap_create_special_folder + (folder, F_DRAFT, "Drafts"); + if (!folder->queue) + folder->queue = imap_create_special_folder + (folder, F_QUEUE, "Queue"); +#endif + if (!folder->trash) + folder->trash = imap_create_special_folder + (folder, F_TRASH, "Trash"); +} + +static FolderItem *imap_create_special_folder(Folder *folder, + SpecialFolderItemType stype, + const gchar *name) +{ + FolderItem *item; + FolderItem *new_item; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(folder->node != NULL, NULL); + g_return_val_if_fail(folder->node->data != NULL, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + item = FOLDER_ITEM(folder->node->data); + new_item = imap_create_folder(folder, item, name); + + if (!new_item) { + g_warning(_("Can't create '%s'\n"), name); + if (!folder->inbox) return NULL; + + new_item = imap_create_folder(folder, folder->inbox, name); + if (!new_item) + g_warning(_("Can't create '%s' under INBOX\n"), name); + else + new_item->stype = stype; + } else + new_item->stype = stype; + + return new_item; +} + +static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent, + const gchar *name) +{ + gchar *dirpath, *imap_path; + IMAPSession *session; + FolderItem *new_item; + gchar separator; + gchar *new_name; + const gchar *p; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + session = imap_session_get(folder); + if (!session) return NULL; + + if (!parent->parent && strcmp(name, "INBOX") == 0) + dirpath = g_strdup(name); + else if (parent->path) + dirpath = g_strconcat(parent->path, "/", name, NULL); + else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0') + dirpath = g_strdup(name); + else if (folder->account->imap_dir && *folder->account->imap_dir) { + gchar *imap_dir; + + Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL); + strtailchomp(imap_dir, '/'); + dirpath = g_strconcat(imap_dir, "/", name, NULL); + } else + dirpath = g_strdup(name); + + /* keep trailing directory separator to create a folder that contains + sub folder */ + imap_path = imap_locale_to_modified_utf7(dirpath); + strtailchomp(dirpath, '/'); + Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;}); + strtailchomp(new_name, '/'); + separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path); + imap_path_separator_subst(imap_path, separator); + subst_char(new_name, '/', separator); + + if (strcmp(name, "INBOX") != 0) { + GPtrArray *argbuf; + gint i; + gboolean exist = FALSE; + + argbuf = g_ptr_array_new(); + ok = imap_cmd_list(session, NULL, imap_path, argbuf); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't create mailbox: LIST failed\n")); + g_free(imap_path); + g_free(dirpath); + g_ptr_array_free(argbuf, TRUE); + return NULL; + } + + for (i = 0; i < argbuf->len; i++) { + gchar *str; + str = g_ptr_array_index(argbuf, i); + if (!strncmp(str, "LIST ", 5)) { + exist = TRUE; + break; + } + } + g_ptr_array_free(argbuf, TRUE); + + if (!exist) { + ok = imap_cmd_create(session, imap_path); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't create mailbox\n")); + g_free(imap_path); + g_free(dirpath); + return NULL; + } + } + } + + new_item = folder_item_new(new_name, dirpath); + folder_item_append(parent, new_item); + g_free(imap_path); + g_free(dirpath); + + dirpath = folder_item_get_path(new_item); + if (!is_dir_exist(dirpath)) + make_dir_hier(dirpath); + g_free(dirpath); + + return new_item; +} + +static gint imap_rename_folder(Folder *folder, FolderItem *item, + const gchar *name) +{ + gchar *dirpath; + gchar *newpath; + gchar *real_oldpath; + gchar *real_newpath; + gchar *paths[2]; + gchar *old_cache_dir; + gchar *new_cache_dir; + IMAPSession *session; + gchar separator; + gint ok; + gint exists, recent, unseen; + guint32 uid_validity; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->path != NULL, -1); + g_return_val_if_fail(name != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path); + + g_free(session->mbox); + session->mbox = NULL; + ok = imap_cmd_examine(session, "INBOX", + &exists, &recent, &unseen, &uid_validity); + if (ok != IMAP_SUCCESS) { + g_free(real_oldpath); + return -1; + } + + separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path); + if (strchr(item->path, G_DIR_SEPARATOR)) { + dirpath = g_dirname(item->path); + newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL); + g_free(dirpath); + } else + newpath = g_strdup(name); + + real_newpath = imap_locale_to_modified_utf7(newpath); + imap_path_separator_subst(real_newpath, separator); + + ok = imap_cmd_rename(session, real_oldpath, real_newpath); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't rename mailbox: %s to %s\n"), + real_oldpath, real_newpath); + g_free(real_oldpath); + g_free(newpath); + g_free(real_newpath); + return -1; + } + + g_free(item->name); + item->name = g_strdup(name); + + old_cache_dir = folder_item_get_path(item); + + paths[0] = g_strdup(item->path); + paths[1] = newpath; + g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + imap_rename_folder_func, paths); + + if (is_dir_exist(old_cache_dir)) { + new_cache_dir = folder_item_get_path(item); + if (rename(old_cache_dir, new_cache_dir) < 0) { + FILE_OP_ERROR(old_cache_dir, "rename"); + } + g_free(new_cache_dir); + } + + g_free(old_cache_dir); + g_free(paths[0]); + g_free(newpath); + g_free(real_oldpath); + g_free(real_newpath); + + return 0; +} + +static gint imap_remove_folder(Folder *folder, FolderItem *item) +{ + gint ok; + IMAPSession *session; + gchar *path; + gchar *cache_dir; + gint exists, recent, unseen; + guint32 uid_validity; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->path != NULL, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + path = imap_get_real_path(IMAP_FOLDER(folder), item->path); + + ok = imap_cmd_examine(session, "INBOX", + &exists, &recent, &unseen, &uid_validity); + if (ok != IMAP_SUCCESS) { + g_free(path); + return -1; + } + + ok = imap_cmd_delete(session, path); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't delete mailbox\n")); + g_free(path); + return -1; + } + + g_free(path); + cache_dir = folder_item_get_path(item); + if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0) + g_warning("can't remove directory '%s'\n", cache_dir); + g_free(cache_dir); + folder_item_remove(item); + + return 0; +} + +static GSList *imap_get_uncached_messages(IMAPSession *session, + FolderItem *item, + guint32 first_uid, guint32 last_uid, + gboolean update_count) +{ + gchar *tmp; + GSList *newlist = NULL; + GSList *llast = NULL; + GString *str; + MsgInfo *msginfo; + gchar seq_set[22]; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, NULL); + g_return_val_if_fail(first_uid <= last_uid, NULL); + + if (first_uid == 0 && last_uid == 0) + strcpy(seq_set, "1:*"); + else + g_snprintf(seq_set, sizeof(seq_set), "%u:%u", + first_uid, last_uid); + if (imap_cmd_envelope(session, seq_set) != IMAP_SUCCESS) { + log_warning(_("can't get envelope\n")); + return NULL; + } + + str = g_string_new(NULL); + + for (;;) { + if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) { + log_warning(_("error occurred while getting envelope.\n")); + g_string_free(str, TRUE); + return newlist; + } + strretchomp(tmp); + if (tmp[0] != '*' || tmp[1] != ' ') { + log_print("IMAP4< %s\n", tmp); + g_free(tmp); + break; + } + if (strstr(tmp, "FETCH") == NULL) { + log_print("IMAP4< %s\n", tmp); + g_free(tmp); + continue; + } + log_print("IMAP4< %s\n", tmp); + g_string_assign(str, tmp); + g_free(tmp); + + msginfo = imap_parse_envelope(session, item, str); + if (!msginfo) { + log_warning(_("can't parse envelope: %s\n"), str->str); + continue; + } + if (update_count) { + if (MSG_IS_NEW(msginfo->flags)) + item->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread++; + } + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT); + } + + msginfo->folder = item; + + if (!newlist) + llast = newlist = g_slist_append(newlist, msginfo); + else { + llast = g_slist_append(llast, msginfo); + llast = llast->next; + } + + if (update_count) + item->total++; + } + + g_string_free(str, TRUE); + + session_set_access_time(SESSION(session)); + + return newlist; +} + +static void imap_delete_cached_message(FolderItem *item, guint32 uid) +{ + gchar *dir; + gchar *file; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP); + + dir = folder_item_get_path(item); + file = g_strdup_printf("%s%c%u", dir, G_DIR_SEPARATOR, uid); + + debug_print("Deleting cached message: %s\n", file); + + unlink(file); + + g_free(file); + g_free(dir); +} + +static GSList *imap_delete_cached_messages(GSList *mlist, FolderItem *item, + guint32 first_uid, guint32 last_uid) +{ + GSList *cur, *next; + MsgInfo *msginfo; + gchar *dir; + + g_return_val_if_fail(item != NULL, mlist); + g_return_val_if_fail(item->folder != NULL, mlist); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, mlist); + + if (first_uid == 0 && last_uid == 0) + return mlist; + + debug_print("Deleting cached messages %u - %u ... ", + first_uid, last_uid); + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) + remove_numbered_files(dir, first_uid, last_uid); + g_free(dir); + + for (cur = mlist; cur != NULL; ) { + next = cur->next; + + msginfo = (MsgInfo *)cur->data; + if (msginfo != NULL && first_uid <= msginfo->msgnum && + msginfo->msgnum <= last_uid) { + procmsg_msginfo_free(msginfo); + mlist = g_slist_remove(mlist, msginfo); + } + + cur = next; + } + + debug_print("done.\n"); + + return mlist; +} + +static void imap_delete_all_cached_messages(FolderItem *item) +{ + gchar *dir; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP); + + debug_print("Deleting all cached messages... "); + + dir = folder_item_get_path(item); + if (is_dir_exist(dir)) + remove_all_numbered_files(dir); + g_free(dir); + + debug_print("done.\n"); +} + +#if USE_SSL +static SockInfo *imap_open(const gchar *server, gushort port, + SSLType ssl_type) +#else +static SockInfo *imap_open(const gchar *server, gushort port) +#endif +{ + SockInfo *sock; + + if ((sock = sock_connect(server, port)) == NULL) { + log_warning(_("Can't connect to IMAP4 server: %s:%d\n"), + server, port); + return NULL; + } + +#if USE_SSL + if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) { + log_warning(_("Can't establish IMAP4 session with: %s:%d\n"), + server, port); + sock_close(sock); + return NULL; + } +#endif + + return sock; +} + +static GList *imap_parse_namespace_str(gchar *str) +{ + guchar *p = str; + gchar *name; + gchar *separator; + IMAPNameSpace *namespace; + GList *ns_list = NULL; + + while (*p != '\0') { + /* parse ("#foo" "/") */ + + while (*p && *p != '(') p++; + if (*p == '\0') break; + p++; + + while (*p && *p != '"') p++; + if (*p == '\0') break; + p++; + name = p; + + while (*p && *p != '"') p++; + if (*p == '\0') break; + *p = '\0'; + p++; + + while (*p && isspace(*p)) p++; + if (*p == '\0') break; + if (strncmp(p, "NIL", 3) == 0) + separator = NULL; + else if (*p == '"') { + p++; + separator = p; + while (*p && *p != '"') p++; + if (*p == '\0') break; + *p = '\0'; + p++; + } else break; + + while (*p && *p != ')') p++; + if (*p == '\0') break; + p++; + + namespace = g_new(IMAPNameSpace, 1); + namespace->name = g_strdup(name); + namespace->separator = separator ? separator[0] : '\0'; + ns_list = g_list_append(ns_list, namespace); + } + + return ns_list; +} + +static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder) +{ + gchar *ns_str; + gchar **str_array; + + g_return_if_fail(session != NULL); + g_return_if_fail(folder != NULL); + + if (folder->ns_personal != NULL || + folder->ns_others != NULL || + folder->ns_shared != NULL) + return; + + if (imap_cmd_namespace(session, &ns_str) != IMAP_SUCCESS) { + log_warning(_("can't get namespace\n")); + imap_get_namespace_by_list(session, folder); + return; + } + + str_array = strsplit_parenthesis(ns_str, '(', ')', 3); + if (str_array[0]) + folder->ns_personal = imap_parse_namespace_str(str_array[0]); + if (str_array[0] && str_array[1]) + folder->ns_others = imap_parse_namespace_str(str_array[1]); + if (str_array[0] && str_array[1] && str_array[2]) + folder->ns_shared = imap_parse_namespace_str(str_array[2]); + g_strfreev(str_array); + g_free(ns_str); +} + +static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder) +{ + GSList *item_list, *cur; + gchar separator = '\0'; + IMAPNameSpace *namespace; + + g_return_if_fail(session != NULL); + g_return_if_fail(folder != NULL); + + if (folder->ns_personal != NULL || + folder->ns_others != NULL || + folder->ns_shared != NULL) + return; + + imap_cmd_gen_send(session, "LIST \"\" \"\""); + item_list = imap_parse_list(session, "", &separator); + for (cur = item_list; cur != NULL; cur = cur->next) + folder_item_destroy(FOLDER_ITEM(cur->data)); + g_slist_free(item_list); + + namespace = g_new(IMAPNameSpace, 1); + namespace->name = g_strdup(""); + namespace->separator = separator; + folder->ns_personal = g_list_append(NULL, namespace); +} + +static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list, + const gchar *path) +{ + IMAPNameSpace *namespace = NULL; + gchar *tmp_path, *name; + + if (!path) path = ""; + + for (; ns_list != NULL; ns_list = ns_list->next) { + IMAPNameSpace *tmp_ns = ns_list->data; + + Xstrcat_a(tmp_path, path, "/", return namespace); + Xstrdup_a(name, tmp_ns->name, return namespace); + if (tmp_ns->separator && tmp_ns->separator != '/') { + subst_char(tmp_path, tmp_ns->separator, '/'); + subst_char(name, tmp_ns->separator, '/'); + } + if (strncmp(tmp_path, name, strlen(name)) == 0) + namespace = tmp_ns; + } + + return namespace; +} + +static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder, + const gchar *path) +{ + IMAPNameSpace *namespace; + + g_return_val_if_fail(folder != NULL, NULL); + + namespace = imap_find_namespace_from_list(folder->ns_personal, path); + if (namespace) return namespace; + namespace = imap_find_namespace_from_list(folder->ns_others, path); + if (namespace) return namespace; + namespace = imap_find_namespace_from_list(folder->ns_shared, path); + if (namespace) return namespace; + + return NULL; +} + +static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path) +{ + IMAPNameSpace *namespace; + gchar separator = '/'; + + namespace = imap_find_namespace(folder, path); + if (namespace && namespace->separator) + separator = namespace->separator; + + return separator; +} + +static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path) +{ + gchar *real_path; + gchar separator; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(path != NULL, NULL); + + real_path = imap_locale_to_modified_utf7(path); + separator = imap_get_path_separator(folder, path); + imap_path_separator_subst(real_path, separator); + + return real_path; +} + +static gchar *imap_parse_atom(IMAPSession *session, gchar *src, + gchar *dest, gint dest_len, GString *str) +{ + gchar *cur_pos = src; + gchar *nextline; + + g_return_val_if_fail(str != NULL, cur_pos); + + /* read the next line if the current response buffer is empty */ + while (isspace(*(guchar *)cur_pos)) cur_pos++; + while (*cur_pos == '\0') { + if ((nextline = sock_getline(SESSION(session)->sock)) == NULL) + return cur_pos; + g_string_assign(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + /* log_print("IMAP4< %s\n", nextline); */ + debug_print("IMAP4< %s\n", nextline); + g_free(nextline); + + while (isspace(*(guchar *)cur_pos)) cur_pos++; + } + + if (!strncmp(cur_pos, "NIL", 3)) { + *dest = '\0'; + cur_pos += 3; + } else if (*cur_pos == '\"') { + gchar *p; + + p = get_quoted(cur_pos, '\"', dest, dest_len); + cur_pos = p ? p : cur_pos + 2; + } else if (*cur_pos == '{') { + gchar buf[32]; + gint len; + gint line_len = 0; + + cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf)); + len = atoi(buf); + g_return_val_if_fail(len >= 0, cur_pos); + + g_string_truncate(str, 0); + cur_pos = str->str; + + do { + if ((nextline = sock_getline(SESSION(session)->sock)) + == NULL) + return cur_pos; + line_len += strlen(nextline); + g_string_append(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + /* log_print("IMAP4< %s\n", nextline); */ + debug_print("IMAP4< %s\n", nextline); + g_free(nextline); + } while (line_len < len); + + memcpy(dest, cur_pos, MIN(len, dest_len - 1)); + dest[MIN(len, dest_len - 1)] = '\0'; + cur_pos += len; + } + + return cur_pos; +} + +static gchar *imap_get_header(IMAPSession *session, gchar *cur_pos, + gchar **headers, GString *str) +{ + gchar *nextline; + gchar buf[32]; + gint len; + gint block_len = 0; + + *headers = NULL; + + g_return_val_if_fail(str != NULL, cur_pos); + + while (isspace(*(guchar *)cur_pos)) cur_pos++; + + g_return_val_if_fail(*cur_pos == '{', cur_pos); + + cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf)); + len = atoi(buf); + g_return_val_if_fail(len >= 0, cur_pos); + + g_string_truncate(str, 0); + cur_pos = str->str; + + do { + if ((nextline = sock_getline(SESSION(session)->sock)) == NULL) + return cur_pos; + block_len += strlen(nextline); + g_string_append(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + /* debug_print("IMAP4< %s\n", nextline); */ + g_free(nextline); + } while (block_len < len); + + debug_print("IMAP4< [contents of RFC822.HEADER]\n"); + + *headers = g_strndup(cur_pos, len); + cur_pos += len; + + while (isspace(*(guchar *)cur_pos)) cur_pos++; + while (*cur_pos == '\0') { + if ((nextline = sock_getline(SESSION(session)->sock)) == NULL) + return cur_pos; + g_string_assign(str, nextline); + cur_pos = str->str; + strretchomp(nextline); + debug_print("IMAP4< %s\n", nextline); + g_free(nextline); + + while (isspace(*(guchar *)cur_pos)) cur_pos++; + } + + return cur_pos; +} + +static MsgFlags imap_parse_flags(const gchar *flag_str) +{ + const gchar *p = flag_str; + MsgFlags flags = {0, 0}; + + flags.perm_flags = MSG_UNREAD; + + while ((p = strchr(p, '\\')) != NULL) { + p++; + + if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) { + MSG_SET_PERM_FLAGS(flags, MSG_NEW); + } else if (g_strncasecmp(p, "Seen", 4) == 0) { + MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD); + } else if (g_strncasecmp(p, "Deleted", 7) == 0) { + MSG_SET_PERM_FLAGS(flags, MSG_DELETED); + } else if (g_strncasecmp(p, "Flagged", 7) == 0) { + MSG_SET_PERM_FLAGS(flags, MSG_MARKED); + } else if (g_strncasecmp(p, "Answered", 8) == 0) { + MSG_SET_PERM_FLAGS(flags, MSG_REPLIED); + } + } + + return flags; +} + +static IMAPFlags imap_parse_imap_flags(const gchar *flag_str) +{ + const gchar *p = flag_str; + IMAPFlags flags = 0; + + while ((p = strchr(p, '\\')) != NULL) { + p++; + + if (g_strncasecmp(p, "Seen", 4) == 0) { + flags |= IMAP_FLAG_SEEN; + } else if (g_strncasecmp(p, "Deleted", 7) == 0) { + flags |= IMAP_FLAG_DELETED; + } else if (g_strncasecmp(p, "Flagged", 7) == 0) { + flags |= IMAP_FLAG_FLAGGED; + } else if (g_strncasecmp(p, "Answered", 8) == 0) { + flags |= IMAP_FLAG_ANSWERED; + } + } + + return flags; +} + +static MsgInfo *imap_parse_envelope(IMAPSession *session, FolderItem *item, + GString *line_str) +{ + gchar buf[IMAPBUFSIZE]; + MsgInfo *msginfo = NULL; + gchar *cur_pos; + gint msgnum; + guint32 uid = 0; + size_t size = 0; + MsgFlags flags = {0, 0}, imap_flags = {0, 0}; + + g_return_val_if_fail(line_str != NULL, NULL); + g_return_val_if_fail(line_str->str[0] == '*' && + line_str->str[1] == ' ', NULL); + + MSG_SET_TMP_FLAGS(flags, MSG_IMAP); + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(flags, MSG_DRAFT); + } + + cur_pos = line_str->str + 2; + +#define PARSE_ONE_ELEMENT(ch) \ +{ \ + cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \ + if (cur_pos == NULL) { \ + g_warning("cur_pos == NULL\n"); \ + procmsg_msginfo_free(msginfo); \ + return NULL; \ + } \ +} + + PARSE_ONE_ELEMENT(' '); + msgnum = atoi(buf); + + PARSE_ONE_ELEMENT(' '); + g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL); + + g_return_val_if_fail(*cur_pos == '(', NULL); + cur_pos++; + + while (*cur_pos != '\0' && *cur_pos != ')') { + while (*cur_pos == ' ') cur_pos++; + + if (!strncmp(cur_pos, "UID ", 4)) { + cur_pos += 4; + uid = strtoul(cur_pos, &cur_pos, 10); + } else if (!strncmp(cur_pos, "FLAGS ", 6)) { + cur_pos += 6; + if (*cur_pos != '(') { + g_warning("*cur_pos != '('\n"); + procmsg_msginfo_free(msginfo); + return NULL; + } + cur_pos++; + PARSE_ONE_ELEMENT(')'); + imap_flags = imap_parse_flags(buf); + } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) { + cur_pos += 12; + size = strtol(cur_pos, &cur_pos, 10); + } else if (!strncmp(cur_pos, "RFC822.HEADER ", 14)) { + gchar *headers; + + cur_pos += 14; + cur_pos = imap_get_header(session, cur_pos, &headers, + line_str); + msginfo = procheader_parse_str(headers, flags, FALSE); + g_free(headers); + } else { + g_warning("invalid FETCH response: %s\n", cur_pos); + break; + } + } + +#undef PARSE_ONE_ELEMENT + + if (msginfo) { + msginfo->msgnum = uid; + msginfo->size = size; + msginfo->flags.tmp_flags |= imap_flags.tmp_flags; + msginfo->flags.perm_flags = imap_flags.perm_flags; + } + + return msginfo; +} + +static gint imap_msg_list_change_perm_flags(GSList *msglist, MsgPermFlags flags, + gboolean is_set) +{ + Folder *folder; + IMAPSession *session; + IMAPFlags iflags = 0; + MsgInfo *msginfo; + GSList *seq_list, *cur; + gint ok = IMAP_SUCCESS; + + if (msglist == NULL) return IMAP_SUCCESS; + + msginfo = (MsgInfo *)msglist->data; + g_return_val_if_fail(msginfo != NULL, -1); + + g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1); + g_return_val_if_fail(msginfo->folder != NULL, -1); + g_return_val_if_fail(msginfo->folder->folder != NULL, -1); + + folder = msginfo->folder->folder; + g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1); + + session = imap_session_get(folder); + if (!session) return -1; + + ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path, + NULL, NULL, NULL, NULL); + if (ok != IMAP_SUCCESS) + return ok; + + seq_list = imap_get_seq_set_from_msglist(msglist); + + if (flags & MSG_MARKED) iflags |= IMAP_FLAG_FLAGGED; + if (flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED; + + for (cur = seq_list; cur != NULL; cur = cur->next) { + gchar *seq_set = (gchar *)cur->data; + + if (iflags) { + ok = imap_set_message_flags(session, seq_set, iflags, + is_set); + if (ok != IMAP_SUCCESS) break; + } + + if (flags & MSG_UNREAD) { + ok = imap_set_message_flags(session, seq_set, + IMAP_FLAG_SEEN, !is_set); + if (ok != IMAP_SUCCESS) break; + } + } + + imap_seq_set_free(seq_list); + + return ok; +} + +gint imap_msg_set_perm_flags(MsgInfo *msginfo, MsgPermFlags flags) +{ + GSList msglist; + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_msg_list_change_perm_flags(&msglist, flags, TRUE); +} + +gint imap_msg_unset_perm_flags(MsgInfo *msginfo, MsgPermFlags flags) +{ + GSList msglist; + + msglist.data = msginfo; + msglist.next = NULL; + + return imap_msg_list_change_perm_flags(&msglist, flags, FALSE); +} + +gint imap_msg_list_set_perm_flags(GSList *msglist, MsgPermFlags flags) +{ + return imap_msg_list_change_perm_flags(msglist, flags, TRUE); +} + +gint imap_msg_list_unset_perm_flags(GSList *msglist, MsgPermFlags flags) +{ + return imap_msg_list_change_perm_flags(msglist, flags, FALSE); +} + +static gchar *imap_get_flag_str(IMAPFlags flags) +{ + GString *str; + gchar *ret; + + str = g_string_new(NULL); + + if (IMAP_IS_SEEN(flags)) g_string_append(str, "\\Seen "); + if (IMAP_IS_ANSWERED(flags)) g_string_append(str, "\\Answered "); + if (IMAP_IS_FLAGGED(flags)) g_string_append(str, "\\Flagged "); + if (IMAP_IS_DELETED(flags)) g_string_append(str, "\\Deleted "); + if (IMAP_IS_DRAFT(flags)) g_string_append(str, "\\Draft"); + + if (str->len > 0 && str->str[str->len - 1] == ' ') + g_string_truncate(str, str->len - 1); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +static gint imap_set_message_flags(IMAPSession *session, + const gchar *seq_set, + IMAPFlags flags, + gboolean is_set) +{ + gchar *cmd; + gchar *flag_str; + gint ok; + + flag_str = imap_get_flag_str(flags); + cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (", + flag_str, ")", NULL); + g_free(flag_str); + + ok = imap_cmd_store(session, seq_set, cmd); + g_free(cmd); + + return ok; +} + +static gint imap_select(IMAPSession *session, IMAPFolder *folder, + const gchar *path, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + gchar *real_path; + gint ok; + gint exists_, recent_, unseen_, uid_validity_; + + if (!exists || !recent || !unseen || !uid_validity) { + if (session->mbox && strcmp(session->mbox, path) == 0) + return IMAP_SUCCESS; + exists = &exists_; + recent = &recent_; + unseen = &unseen_; + uid_validity = &uid_validity_; + } + + g_free(session->mbox); + session->mbox = NULL; + + real_path = imap_get_real_path(folder, path); + ok = imap_cmd_select(session, real_path, + exists, recent, unseen, uid_validity); + if (ok != IMAP_SUCCESS) + log_warning(_("can't select folder: %s\n"), real_path); + else + session->mbox = g_strdup(path); + g_free(real_path); + + return ok; +} + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_status(IMAPSession *session, IMAPFolder *folder, + const gchar *path, + gint *messages, gint *recent, + guint32 *uid_next, guint32 *uid_validity, + gint *unseen) +{ + gchar *real_path; + gchar *real_path_; + gint ok; + GPtrArray *argbuf = NULL; + gchar *str; + + if (messages && recent && uid_next && uid_validity && unseen) { + *messages = *recent = *uid_next = *uid_validity = *unseen = 0; + argbuf = g_ptr_array_new(); + } + + real_path = imap_get_real_path(folder, path); + QUOTE_IF_REQUIRED(real_path_, real_path); + imap_cmd_gen_send(session, "STATUS %s " + "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)", + real_path_); + + ok = imap_cmd_ok(session, argbuf); + if (ok != IMAP_SUCCESS || !argbuf) THROW(ok); + + str = search_array_str(argbuf, "STATUS"); + if (!str) THROW(IMAP_ERROR); + + str = strchr(str, '('); + if (!str) THROW(IMAP_ERROR); + str++; + while (*str != '\0' && *str != ')') { + while (*str == ' ') str++; + + if (!strncmp(str, "MESSAGES ", 9)) { + str += 9; + *messages = strtol(str, &str, 10); + } else if (!strncmp(str, "RECENT ", 7)) { + str += 7; + *recent = strtol(str, &str, 10); + } else if (!strncmp(str, "UIDNEXT ", 8)) { + str += 8; + *uid_next = strtoul(str, &str, 10); + } else if (!strncmp(str, "UIDVALIDITY ", 12)) { + str += 12; + *uid_validity = strtoul(str, &str, 10); + } else if (!strncmp(str, "UNSEEN ", 7)) { + str += 7; + *unseen = strtol(str, &str, 10); + } else { + g_warning("invalid STATUS response: %s\n", str); + break; + } + } + +catch: + g_free(real_path); + if (argbuf) { + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + } + + return ok; +} + +#undef THROW + +static gboolean imap_has_capability(IMAPSession *session, + const gchar *capability) +{ + gchar **p; + + for (p = session->capability; *p != NULL; ++p) { + if (!g_strcasecmp(*p, capability)) + return TRUE; + } + + return FALSE; +} + +static void imap_capability_free(IMAPSession *session) +{ + g_strfreev(session->capability); + session->capability = NULL; +} + + +/* low-level IMAP4rev1 commands */ + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_capability(IMAPSession *session) +{ + gint ok; + GPtrArray *argbuf; + gchar *capability; + + argbuf = g_ptr_array_new(); + + imap_cmd_gen_send(session, "CAPABILITY"); + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok); + + capability = search_array_str(argbuf, "CAPABILITY "); + if (!capability) THROW(IMAP_ERROR); + + capability += strlen("CAPABILITY "); + + IMAP_SESSION(session)->capability = g_strsplit(capability, " ", -1); + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +#undef THROW + +static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user, + const gchar *pass, IMAPAuthType type) +{ + gchar *auth_type; + gint ok; + gchar *buf = NULL; + gchar *challenge; + gint challenge_len; + gchar hexdigest[33]; + gchar *response; + gchar *response64; + + g_return_val_if_fail(type == IMAP_AUTH_CRAM_MD5, IMAP_ERROR); + + auth_type = "CRAM-MD5"; + + imap_cmd_gen_send(session, "AUTHENTICATE %s", auth_type); + ok = imap_cmd_gen_recv(session, &buf); + if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') { + g_free(buf); + return IMAP_ERROR; + } + + challenge = g_malloc(strlen(buf + 2) + 1); + challenge_len = base64_decode(challenge, buf + 2, -1); + challenge[challenge_len] = '\0'; + g_free(buf); + log_print("IMAP< [Decoded: %s]\n", challenge); + + md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass)); + g_free(challenge); + + response = g_strdup_printf("%s %s", user, hexdigest); + log_print("IMAP> [Encoded: %s]\n", response); + response64 = g_malloc((strlen(response) + 3) * 2 + 1); + base64_encode(response64, response, strlen(response)); + g_free(response); + + log_print("IMAP> %s\n", response64); + sock_puts(SESSION(session)->sock, response64); + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) + log_warning(_("IMAP4 authentication failed.\n")); + + return ok; +} + +static gint imap_cmd_login(IMAPSession *session, + const gchar *user, const gchar *pass) +{ + gchar *user_, *pass_; + gint ok; + + QUOTE_IF_REQUIRED(user_, user); + QUOTE_IF_REQUIRED(pass_, pass); + imap_cmd_gen_send(session, "LOGIN %s %s", user_, pass_); + + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) + log_warning(_("IMAP4 login failed.\n")); + + return ok; +} + +static gint imap_cmd_logout(IMAPSession *session) +{ + imap_cmd_gen_send(session, "LOGOUT"); + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_noop(IMAPSession *session) +{ + imap_cmd_gen_send(session, "NOOP"); + return imap_cmd_ok(session, NULL); +} + +#if USE_SSL +static gint imap_cmd_starttls(IMAPSession *session) +{ + imap_cmd_gen_send(session, "STARTTLS"); + return imap_cmd_ok(session, NULL); +} +#endif + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str) +{ + gint ok; + GPtrArray *argbuf; + gchar *str; + + argbuf = g_ptr_array_new(); + + imap_cmd_gen_send(session, "NAMESPACE"); + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok); + + str = search_array_str(argbuf, "NAMESPACE"); + if (!str) THROW(IMAP_ERROR); + + *ns_str = g_strdup(str); + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +#undef THROW + +static gint imap_cmd_list(IMAPSession *session, const gchar *ref, + const gchar *mailbox, GPtrArray *argbuf) +{ + gchar *ref_, *mailbox_; + + if (!ref) ref = "\"\""; + if (!mailbox) mailbox = "\"\""; + + QUOTE_IF_REQUIRED(ref_, ref); + QUOTE_IF_REQUIRED(mailbox_, mailbox); + imap_cmd_gen_send(session, "LIST %s %s", ref_, mailbox_); + + return imap_cmd_ok(session, argbuf); +} + +#define THROW goto catch + +static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder, + gboolean examine, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + gint ok; + gchar *resp_str; + GPtrArray *argbuf; + gchar *select_cmd; + gchar *folder_; + guint uid_validity_; + + *exists = *recent = *unseen = *uid_validity = 0; + argbuf = g_ptr_array_new(); + + if (examine) + select_cmd = "EXAMINE"; + else + select_cmd = "SELECT"; + + QUOTE_IF_REQUIRED(folder_, folder); + imap_cmd_gen_send(session, "%s %s", select_cmd, folder_); + + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW; + + resp_str = search_array_contain_str(argbuf, "EXISTS"); + if (resp_str) { + if (sscanf(resp_str,"%d EXISTS", exists) != 1) { + g_warning("imap_cmd_select(): invalid EXISTS line.\n"); + THROW; + } + } + + resp_str = search_array_contain_str(argbuf, "RECENT"); + if (resp_str) { + if (sscanf(resp_str, "%d RECENT", recent) != 1) { + g_warning("imap_cmd_select(): invalid RECENT line.\n"); + THROW; + } + } + + resp_str = search_array_contain_str(argbuf, "UIDVALIDITY"); + if (resp_str) { + if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", &uid_validity_) + != 1) { + g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n"); + THROW; + } + *uid_validity = uid_validity_; + } + + resp_str = search_array_contain_str(argbuf, "UNSEEN"); + if (resp_str) { + if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) { + g_warning("imap_cmd_select(): invalid UNSEEN line.\n"); + THROW; + } + } + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +static gint imap_cmd_select(IMAPSession *session, const gchar *folder, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + return imap_cmd_do_select(session, folder, FALSE, + exists, recent, unseen, uid_validity); +} + +static gint imap_cmd_examine(IMAPSession *session, const gchar *folder, + gint *exists, gint *recent, gint *unseen, + guint32 *uid_validity) +{ + return imap_cmd_do_select(session, folder, TRUE, + exists, recent, unseen, uid_validity); +} + +#undef THROW + +static gint imap_cmd_create(IMAPSession *session, const gchar *folder) +{ + gchar *folder_; + + QUOTE_IF_REQUIRED(folder_, folder); + imap_cmd_gen_send(session, "CREATE %s", folder_); + + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder, + const gchar *new_folder) +{ + gchar *old_folder_, *new_folder_; + + QUOTE_IF_REQUIRED(old_folder_, old_folder); + QUOTE_IF_REQUIRED(new_folder_, new_folder); + imap_cmd_gen_send(session, "RENAME %s %s", old_folder_, new_folder_); + + return imap_cmd_ok(session, NULL); +} + +static gint imap_cmd_delete(IMAPSession *session, const gchar *folder) +{ + gchar *folder_; + + QUOTE_IF_REQUIRED(folder_, folder); + imap_cmd_gen_send(session, "DELETE %s", folder_); + + return imap_cmd_ok(session, NULL); +} + +#define THROW(err) { ok = err; goto catch; } + +static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, + GArray **result) +{ + gint ok; + GPtrArray *argbuf; + GArray *array; + gchar *str; + gchar *p, *ep; + guint32 uid; + + g_return_val_if_fail(criteria != NULL, IMAP_ERROR); + g_return_val_if_fail(result != NULL, IMAP_ERROR); + + argbuf = g_ptr_array_new(); + + imap_cmd_gen_send(session, "UID SEARCH %s", criteria); + if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok); + + str = search_array_str(argbuf, "SEARCH"); + if (!str) THROW(IMAP_ERROR); + + array = g_array_new(FALSE, FALSE, sizeof(guint32)); + + p = str + strlen("SEARCH"); + + while (*p != '\0') { + uid = strtoul(p, &ep, 10); + if (p < ep && uid > 0) { + g_array_append_val(array, uid); + p = ep; + } else + break; + } + + *result = array; + +catch: + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + + return ok; +} + +static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, + const gchar *filename) +{ + gint ok; + gchar *buf; + gchar *cur_pos; + gchar size_str[32]; + glong size_num; + + g_return_val_if_fail(filename != NULL, IMAP_ERROR); + + imap_cmd_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid); + + while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) { + if (buf[0] != '*' || buf[1] != ' ') { + g_free(buf); + return IMAP_ERROR; + } + if (strstr(buf, "FETCH") != NULL) break; + g_free(buf); + } + if (ok != IMAP_SUCCESS) + return ok; + +#define RETURN_ERROR_IF_FAIL(cond) \ + if (!(cond)) { \ + g_free(buf); \ + return IMAP_ERROR; \ + } + + cur_pos = strchr(buf, '{'); + RETURN_ERROR_IF_FAIL(cur_pos != NULL); + cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str)); + RETURN_ERROR_IF_FAIL(cur_pos != NULL); + size_num = atol(size_str); + RETURN_ERROR_IF_FAIL(size_num >= 0); + + RETURN_ERROR_IF_FAIL(*cur_pos == '\0'); + +#undef RETURN_ERROR_IF_FAIL + + g_free(buf); + + if (recv_bytes_write_to_file(SESSION(session)->sock, + size_num, filename) != 0) + return IMAP_ERROR; + + if (imap_cmd_gen_recv(session, &buf) != IMAP_SUCCESS) + return IMAP_ERROR; + + if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') { + g_free(buf); + return IMAP_ERROR; + } + g_free(buf); + + ok = imap_cmd_ok(session, NULL); + + return ok; +} + +static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder, + const gchar *file, IMAPFlags flags, + guint32 *new_uid) +{ + gint ok; + gint size; + gchar *destfolder_; + gchar *flag_str; + guint new_uid_; + gchar *ret = NULL; + gchar buf[BUFFSIZE]; + FILE *fp; + GPtrArray *argbuf; + gchar *resp_str; + + g_return_val_if_fail(file != NULL, IMAP_ERROR); + + size = get_file_size_as_crlf(file); + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + QUOTE_IF_REQUIRED(destfolder_, destfolder); + flag_str = imap_get_flag_str(flags); + imap_cmd_gen_send(session, "APPEND %s (%s) {%d}", + destfolder_, flag_str, size); + g_free(flag_str); + + ok = imap_cmd_gen_recv(session, &ret); + if (ok != IMAP_SUCCESS || ret[0] != '+' || ret[1] != ' ') { + log_warning(_("can't append %s to %s\n"), file, destfolder_); + g_free(ret); + fclose(fp); + return IMAP_ERROR; + } + g_free(ret); + + log_print("IMAP4> %s\n", _("(sending file...)")); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (sock_puts(SESSION(session)->sock, buf) < 0) { + fclose(fp); + return -1; + } + } + + if (ferror(fp)) { + FILE_OP_ERROR(file, "fgets"); + fclose(fp); + return -1; + } + + sock_puts(SESSION(session)->sock, ""); + + fclose(fp); + + if (new_uid != NULL) + *new_uid = 0; + + if (new_uid != NULL && session->uidplus) { + argbuf = g_ptr_array_new(); + + ok = imap_cmd_ok(session, argbuf); + if (ok != IMAP_SUCCESS) + log_warning(_("can't append message to %s\n"), + destfolder_); + else if (argbuf->len > 0) { + resp_str = g_ptr_array_index(argbuf, argbuf->len - 1); + if (resp_str && + sscanf(resp_str, "%*u OK [APPENDUID %*u %u]", + &new_uid_) == 1) { + *new_uid = new_uid_; + } + } + + ptr_array_free_strings(argbuf); + g_ptr_array_free(argbuf, TRUE); + } else + ok = imap_cmd_ok(session, NULL); + + return ok; +} + +static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set, + const gchar *destfolder) +{ + gint ok; + gchar *destfolder_; + + g_return_val_if_fail(destfolder != NULL, IMAP_ERROR); + + QUOTE_IF_REQUIRED(destfolder_, destfolder); + imap_cmd_gen_send(session, "UID COPY %s %s", seq_set, destfolder_); + + ok = imap_cmd_ok(session, NULL); + if (ok != IMAP_SUCCESS) { + log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_); + return -1; + } + + return ok; +} + +gint imap_cmd_envelope(IMAPSession *session, const gchar *seq_set) +{ + imap_cmd_gen_send + (session, "UID FETCH %s (UID FLAGS RFC822.SIZE RFC822.HEADER)", + seq_set); + + return IMAP_SUCCESS; +} + +static gint imap_cmd_store(IMAPSession *session, const gchar *seq_set, + const gchar *sub_cmd) +{ + gint ok; + + imap_cmd_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd); + + if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) { + log_warning(_("error while imap command: STORE %s %s\n"), + seq_set, sub_cmd); + return ok; + } + + return IMAP_SUCCESS; +} + +static gint imap_cmd_expunge(IMAPSession *session) +{ + gint ok; + + imap_cmd_gen_send(session, "EXPUNGE"); + if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) { + log_warning(_("error while imap command: EXPUNGE\n")); + return ok; + } + + return IMAP_SUCCESS; +} + +static gint imap_cmd_close(IMAPSession *session) +{ + gint ok; + + imap_cmd_gen_send(session, "CLOSE"); + if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) + log_warning(_("error while imap command: CLOSE\n")); + + return ok; +} + +static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf) +{ + gint ok; + gchar *buf; + gint cmd_num; + gchar cmd_status[IMAPBUFSIZE + 1]; + + while ((ok = imap_cmd_gen_recv(session, &buf)) == IMAP_SUCCESS) { + if (buf[0] == '*' && buf[1] == ' ') { + if (argbuf) { + g_memmove(buf, buf + 2, strlen(buf + 2) + 1); + g_ptr_array_add(argbuf, buf); + } else + g_free(buf); + continue; + } + + if (sscanf(buf, "%d %" Xstr(IMAPBUFSIZE) "s", + &cmd_num, cmd_status) < 2) { + g_free(buf); + return IMAP_ERROR; + } else if (cmd_num == session->cmd_count && + !strcmp(cmd_status, "OK")) { + if (argbuf) + g_ptr_array_add(argbuf, buf); + else + g_free(buf); + return IMAP_SUCCESS; + } else { + g_free(buf); + return IMAP_ERROR; + } + } + + return ok; +} + +static void imap_cmd_gen_send(IMAPSession *session, const gchar *format, ...) +{ + gchar buf[IMAPBUFSIZE]; + gchar tmp[IMAPBUFSIZE]; + gchar *p; + va_list args; + + va_start(args, format); + g_vsnprintf(tmp, sizeof(tmp), format, args); + va_end(args); + + session->cmd_count++; + + g_snprintf(buf, sizeof(buf), "%d %s\r\n", session->cmd_count, tmp); + if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) { + *p = '\0'; + log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp); + } else + log_print("IMAP4> %d %s\n", session->cmd_count, tmp); + + sock_write_all(SESSION(session)->sock, buf, strlen(buf)); +} + +static gint imap_cmd_gen_recv(IMAPSession *session, gchar **ret) +{ + if ((*ret = sock_getline(SESSION(session)->sock)) == NULL) + return IMAP_SOCKET; + + strretchomp(*ret); + + log_print("IMAP4< %s\n", *ret); + + session_set_access_time(SESSION(session)); + + return IMAP_SUCCESS; +} + + +/* misc utility functions */ + +static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len) +{ + gchar *tmp; + + dest[0] = '\0'; + tmp = strchr(src, ch); + if (!tmp) + return NULL; + + memcpy(dest, src, MIN(tmp - src, len - 1)); + dest[MIN(tmp - src, len - 1)] = '\0'; + + return tmp + 1; +} + +static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len) +{ + const gchar *p = src; + gint n = 0; + + g_return_val_if_fail(*p == ch, NULL); + + *dest = '\0'; + p++; + + while (*p != '\0' && *p != ch) { + if (n < len - 1) { + if (*p == '\\' && *(p + 1) != '\0') + p++; + *dest++ = *p++; + } else + p++; + n++; + } + + *dest = '\0'; + return (gchar *)(*p == ch ? p + 1 : p); +} + +static gchar *search_array_contain_str(GPtrArray *array, gchar *str) +{ + gint i; + + for (i = 0; i < array->len; i++) { + gchar *tmp; + + tmp = g_ptr_array_index(array, i); + if (strstr(tmp, str) != NULL) + return tmp; + } + + return NULL; +} + +static gchar *search_array_str(GPtrArray *array, gchar *str) +{ + gint i; + gint len; + + len = strlen(str); + + for (i = 0; i < array->len; i++) { + gchar *tmp; + + tmp = g_ptr_array_index(array, i); + if (!strncmp(tmp, str, len)) + return tmp; + } + + return NULL; +} + +static void imap_path_separator_subst(gchar *str, gchar separator) +{ + gchar *p; + gboolean in_escape = FALSE; + + if (!separator || separator == '/') return; + + for (p = str; *p != '\0'; p++) { + if (*p == '/' && !in_escape) + *p = separator; + else if (*p == '&' && *(p + 1) != '-' && !in_escape) + in_escape = TRUE; + else if (*p == '-' && in_escape) + in_escape = FALSE; + } +} + +static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str) +{ +#if !HAVE_ICONV + const gchar *from_p; + gchar *to, *to_p; + + to = g_malloc(strlen(mutf7_str) + 1); + to_p = to; + + for (from_p = mutf7_str; *from_p != '\0'; from_p++) { + if (*from_p == '&' && *(from_p + 1) == '-') { + *to_p++ = '&'; + from_p++; + } else + *to_p++ = *from_p; + } + *to_p = '\0'; + + return to; +#else + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + GString *norm_utf7; + gchar *norm_utf7_p; + size_t norm_utf7_len; + const gchar *p; + gchar *to_str, *to_p; + size_t to_len; + gboolean in_escape = FALSE; + + if (!iconv_ok) return g_strdup(mutf7_str); + + if (cd == (iconv_t)-1) { + cd = iconv_open(conv_get_locale_charset_str(), "UTF-7"); + if (cd == (iconv_t)-1) { + g_warning("iconv cannot convert UTF-7 to %s\n", + conv_get_locale_charset_str()); + iconv_ok = FALSE; + return g_strdup(mutf7_str); + } + } + + norm_utf7 = g_string_new(NULL); + + for (p = mutf7_str; *p != '\0'; p++) { + /* replace: '&' -> '+', + "&-" -> '&', + escaped ',' -> '/' */ + if (!in_escape && *p == '&') { + if (*(p + 1) != '-') { + g_string_append_c(norm_utf7, '+'); + in_escape = TRUE; + } else { + g_string_append_c(norm_utf7, '&'); + p++; + } + } else if (in_escape && *p == ',') { + g_string_append_c(norm_utf7, '/'); + } else if (in_escape && *p == '-') { + g_string_append_c(norm_utf7, '-'); + in_escape = FALSE; + } else { + g_string_append_c(norm_utf7, *p); + } + } + + norm_utf7_p = norm_utf7->str; + norm_utf7_len = norm_utf7->len; + to_len = strlen(mutf7_str) * 5; + to_p = to_str = g_malloc(to_len + 1); + + if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len, + &to_p, &to_len) == -1) { + g_warning(_("iconv cannot convert UTF-7 to %s\n"), + conv_get_locale_charset_str()); + g_string_free(norm_utf7, TRUE); + g_free(to_str); + return g_strdup(mutf7_str); + } + + /* second iconv() call for flushing */ + iconv(cd, NULL, NULL, &to_p, &to_len); + g_string_free(norm_utf7, TRUE); + *to_p = '\0'; + + return to_str; +#endif /* !HAVE_ICONV */ +} + +static gchar *imap_locale_to_modified_utf7(const gchar *from) +{ +#if !HAVE_ICONV + const gchar *from_p; + gchar *to, *to_p; + + to = g_malloc(strlen(from) * 2 + 1); + to_p = to; + + for (from_p = from; *from_p != '\0'; from_p++) { + if (*from_p == '&') { + *to_p++ = '&'; + *to_p++ = '-'; + } else + *to_p++ = *from_p; + } + *to_p = '\0'; + + return to; +#else + static iconv_t cd = (iconv_t)-1; + static gboolean iconv_ok = TRUE; + gchar *norm_utf7, *norm_utf7_p; + size_t from_len, norm_utf7_len; + GString *to_str; + gchar *from_tmp, *to, *p; + gboolean in_escape = FALSE; + + if (!iconv_ok) return g_strdup(from); + + if (cd == (iconv_t)-1) { + cd = iconv_open("UTF-7", conv_get_locale_charset_str()); + if (cd == (iconv_t)-1) { + g_warning(_("iconv cannot convert %s to UTF-7\n"), + conv_get_locale_charset_str()); + iconv_ok = FALSE; + return g_strdup(from); + } + } + + Xstrdup_a(from_tmp, from, return g_strdup(from)); + from_len = strlen(from); + norm_utf7_len = from_len * 5; + Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from)); + norm_utf7_p = norm_utf7; + +#define IS_PRINT(ch) (isprint(ch) && isascii(ch)) + + while (from_len > 0) { + if (*from_tmp == '+') { + *norm_utf7_p++ = '+'; + *norm_utf7_p++ = '-'; + norm_utf7_len -= 2; + from_tmp++; + from_len--; + } else if (IS_PRINT(*(guchar *)from_tmp)) { + /* printable ascii char */ + *norm_utf7_p = *from_tmp; + norm_utf7_p++; + norm_utf7_len--; + from_tmp++; + from_len--; + } else { + size_t mb_len = 0, conv_len = 0; + + /* unprintable char: convert to UTF-7 */ + p = from_tmp; + while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) { + mb_len = mblen(p, MB_LEN_MAX); + if (mb_len <= 0) { + g_warning("wrong multibyte sequence\n"); + return g_strdup(from); + } + conv_len += mb_len; + p += mb_len; + } + + from_len -= conv_len; + if (iconv(cd, (ICONV_CONST gchar **)&from_tmp, + &conv_len, + &norm_utf7_p, &norm_utf7_len) == -1) { + g_warning("iconv cannot convert %s to UTF-7\n", + conv_get_locale_charset_str()); + return g_strdup(from); + } + + /* second iconv() call for flushing */ + iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len); + } + } + +#undef IS_PRINT + + *norm_utf7_p = '\0'; + to_str = g_string_new(NULL); + for (p = norm_utf7; p < norm_utf7_p; p++) { + /* replace: '&' -> "&-", + '+' -> '&', + "+-" -> '+', + BASE64 '/' -> ',' */ + if (!in_escape && *p == '&') { + g_string_append(to_str, "&-"); + } else if (!in_escape && *p == '+') { + if (*(p + 1) == '-') { + g_string_append_c(to_str, '+'); + p++; + } else { + g_string_append_c(to_str, '&'); + in_escape = TRUE; + } + } else if (in_escape && *p == '/') { + g_string_append_c(to_str, ','); + } else if (in_escape && *p == '-') { + g_string_append_c(to_str, '-'); + in_escape = FALSE; + } else { + g_string_append_c(to_str, *p); + } + } + + if (in_escape) { + in_escape = FALSE; + g_string_append_c(to_str, '-'); + } + + to = to_str->str; + g_string_free(to_str, FALSE); + + return to; +#endif /* !HAVE_ICONV */ +} + +static GSList *imap_get_seq_set_from_msglist(GSList *msglist) +{ + GString *str; + GSList *sorted_list, *cur; + guint first, last, next; + gchar *ret_str; + GSList *ret_list = NULL; + + if (msglist == NULL) + return NULL; + + str = g_string_sized_new(256); + + sorted_list = g_slist_copy(msglist); + sorted_list = procmsg_sort_msg_list(sorted_list, SORT_BY_NUMBER, + SORT_ASCENDING); + + first = ((MsgInfo *)sorted_list->data)->msgnum; + + for (cur = sorted_list; cur != NULL; cur = cur->next) { + last = ((MsgInfo *)cur->data)->msgnum; + if (cur->next) + next = ((MsgInfo *)cur->next->data)->msgnum; + else + next = 0; + + if (last + 1 != next || next == 0) { + if (str->len > 0) + g_string_append_c(str, ','); + if (first == last) + g_string_sprintfa(str, "%u", first); + else + g_string_sprintfa(str, "%u:%u", first, last); + + first = next; + + if (str->len > IMAP_CMD_LIMIT) { + ret_str = g_strdup(str->str); + ret_list = g_slist_append(ret_list, ret_str); + g_string_truncate(str, 0); + } + } + } + + if (str->len > 0) { + ret_str = g_strdup(str->str); + ret_list = g_slist_append(ret_list, ret_str); + } + + g_slist_free(sorted_list); + g_string_free(str, TRUE); + + return ret_list; +} + +static void imap_seq_set_free(GSList *seq_list) +{ + slist_free_strings(seq_list); + g_slist_free(seq_list); +} + +static GHashTable *imap_get_uid_table(GArray *array) +{ + GHashTable *table; + gint i; + guint32 uid; + + g_return_val_if_fail(array != NULL, NULL); + + table = g_hash_table_new(NULL, g_direct_equal); + + for (i = 0; i < array->len; i++) { + uid = g_array_index(array, guint32, i); + g_hash_table_insert(table, GUINT_TO_POINTER(uid), + GINT_TO_POINTER(i + 1)); + } + + return table; +} + +static gboolean imap_rename_folder_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gchar **paths = data; + const gchar *oldpath = paths[0]; + const gchar *newpath = paths[1]; + gchar *base; + gchar *new_itempath; + gint oldpathlen; + + oldpathlen = strlen(oldpath); + if (strncmp(oldpath, item->path, oldpathlen) != 0) { + g_warning("path doesn't match: %s, %s\n", oldpath, item->path); + return TRUE; + } + + base = item->path + oldpathlen; + while (*base == G_DIR_SEPARATOR) base++; + if (*base == '\0') + new_itempath = g_strdup(newpath); + else + new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base, + NULL); + g_free(item->path); + item->path = new_itempath; + + return FALSE; +} diff --git a/src/imap.h b/src/imap.h new file mode 100644 index 00000000..c726c875 --- /dev/null +++ b/src/imap.h @@ -0,0 +1,114 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __IMAP_H__ +#define __IMAP_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "folder.h" +#include "session.h" +#include "procmsg.h" + +typedef struct _IMAPFolder IMAPFolder; +typedef struct _IMAPSession IMAPSession; +typedef struct _IMAPNameSpace IMAPNameSpace; + +#define IMAP_FOLDER(obj) ((IMAPFolder *)obj) +#define IMAP_SESSION(obj) ((IMAPSession *)obj) + +#include "prefs_account.h" + +typedef enum +{ + IMAP_AUTH_LOGIN = 1 << 0, + IMAP_AUTH_CRAM_MD5 = 1 << 1 +} IMAPAuthType; + +struct _IMAPFolder +{ + RemoteFolder rfolder; + + /* list of IMAPNameSpace */ + GList *ns_personal; + GList *ns_others; + GList *ns_shared; +}; + +struct _IMAPSession +{ + Session session; + + gboolean authenticated; + + gchar **capability; + gboolean uidplus; + + gchar *mbox; + guint cmd_count; +}; + +struct _IMAPNameSpace +{ + gchar *name; + gchar separator; +}; + +#define IMAP_SUCCESS 0 +#define IMAP_SOCKET 2 +#define IMAP_AUTHFAIL 3 +#define IMAP_PROTOCOL 4 +#define IMAP_SYNTAX 5 +#define IMAP_IOERR 6 +#define IMAP_ERROR 7 + +#define IMAPBUFSIZE 8192 + +typedef enum +{ + IMAP_FLAG_SEEN = 1 << 0, + IMAP_FLAG_ANSWERED = 1 << 1, + IMAP_FLAG_FLAGGED = 1 << 2, + IMAP_FLAG_DELETED = 1 << 3, + IMAP_FLAG_DRAFT = 1 << 4 +} IMAPFlags; + +#define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0) +#define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0) +#define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0) +#define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0) +#define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0) + +FolderClass *imap_get_class (void); + +gint imap_msg_set_perm_flags (MsgInfo *msginfo, + MsgPermFlags flags); +gint imap_msg_unset_perm_flags (MsgInfo *msginfo, + MsgPermFlags flags); +gint imap_msg_list_set_perm_flags (GSList *msglist, + MsgPermFlags flags); +gint imap_msg_list_unset_perm_flags (GSList *msglist, + MsgPermFlags flags); + +#endif /* __IMAP_H__ */ diff --git a/src/import.c b/src/import.c new file mode 100644 index 00000000..9bf2a883 --- /dev/null +++ b/src/import.c @@ -0,0 +1,268 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "inc.h" +#include "mbox.h" +#include "folderview.h" +#include "filesel.h" +#include "foldersel.h" +#include "gtkutils.h" +#include "manage_window.h" +#include "folder.h" + +static GtkWidget *window; +static GtkWidget *file_entry; +static GtkWidget *dest_entry; +static GtkWidget *file_button; +static GtkWidget *dest_button; +static GtkWidget *ok_button; +static GtkWidget *cancel_button; +static gboolean import_ack; + +static void import_create(void); +static void import_ok_cb(GtkWidget *widget, gpointer data); +static void import_cancel_cb(GtkWidget *widget, gpointer data); +static void import_filesel_cb(GtkWidget *widget, gpointer data); +static void import_destsel_cb(GtkWidget *widget, gpointer data); +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data); +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data); + +gint import_mbox(FolderItem *default_dest) +{ + gint ok = 0; + gchar *dest_id = NULL; + + if (!window) + import_create(); + else + gtk_widget_show(window); + + gtk_entry_set_text(GTK_ENTRY(file_entry), ""); + if (default_dest && default_dest->path) + dest_id = folder_item_get_identifier(default_dest); + + if (dest_id) { + gtk_entry_set_text(GTK_ENTRY(dest_entry), dest_id); + g_free(dest_id); + } else + gtk_entry_set_text(GTK_ENTRY(dest_entry), ""); + gtk_widget_grab_focus(file_entry); + + manage_window_set_transient(GTK_WINDOW(window)); + + gtk_main(); + + if (import_ack) { + const gchar *utf8filename, *destdir; + FolderItem *dest; + + utf8filename = gtk_entry_get_text(GTK_ENTRY(file_entry)); + destdir = gtk_entry_get_text(GTK_ENTRY(dest_entry)); + if (utf8filename && *utf8filename) { + gchar *filename; + + filename = g_filename_from_utf8 + (utf8filename, -1, NULL, NULL, NULL); + if (!filename) { + g_warning("faild to convert character set\n"); + filename = g_strdup(utf8filename); + } + + if (!destdir || !*destdir) { + dest = folder_find_item_from_path(INBOX_DIR); + } else + dest = folder_find_item_from_identifier + (destdir); + + if (!dest) { + g_warning("Can't find the folder.\n"); + } else { + ok = proc_mbox(dest, filename, NULL); + folder_item_scan(dest); + folderview_update_item(dest, TRUE); + } + + g_free(filename); + } + } + + gtk_widget_hide(window); + + return ok; +} + +static void import_create(void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *desc_label; + GtkWidget *table; + GtkWidget *file_label; + GtkWidget *dest_label; + GtkWidget *confirm_area; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Import")); + gtk_container_set_border_width(GTK_CONTAINER(window), 5); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(delete_event), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + vbox = gtk_vbox_new(FALSE, 4); + gtk_container_add(GTK_CONTAINER(window), vbox); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 4); + + desc_label = gtk_label_new + (_("Specify target mbox file and destination folder.")); + gtk_box_pack_start(GTK_BOX(hbox), desc_label, FALSE, FALSE, 0); + + table = gtk_table_new(2, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(table), 8); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + gtk_widget_set_size_request(table, 420, -1); + + file_label = gtk_label_new(_("Importing file:")); + gtk_table_attach(GTK_TABLE(table), file_label, 0, 1, 0, 1, + GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); + gtk_misc_set_alignment(GTK_MISC(file_label), 1, 0.5); + + dest_label = gtk_label_new(_("Destination dir:")); + gtk_table_attach(GTK_TABLE(table), dest_label, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); + gtk_misc_set_alignment(GTK_MISC(dest_label), 1, 0.5); + + file_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), file_entry, 1, 2, 0, 1, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + dest_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), dest_entry, 1, 2, 1, 2, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + file_button = gtk_button_new_with_label(_(" Select... ")); + gtk_table_attach(GTK_TABLE(table), file_button, 2, 3, 0, 1, + 0, 0, 0, 0); + g_signal_connect(G_OBJECT(file_button), "clicked", + G_CALLBACK(import_filesel_cb), NULL); + + dest_button = gtk_button_new_with_label(_(" Select... ")); + gtk_table_attach(GTK_TABLE(table), dest_button, 2, 3, 1, 2, + 0, 0, 0, 0); + g_signal_connect(G_OBJECT(dest_button), "clicked", + G_CALLBACK(import_destsel_cb), NULL); + + gtkut_button_set_create(&confirm_area, + &ok_button, _("OK"), + &cancel_button, _("Cancel"), + NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_button); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(import_ok_cb), NULL); + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(import_cancel_cb), NULL); + + gtk_widget_show_all(window); +} + +static void import_ok_cb(GtkWidget *widget, gpointer data) +{ + import_ack = TRUE; + if (gtk_main_level() > 1) + gtk_main_quit(); +} + +static void import_cancel_cb(GtkWidget *widget, gpointer data) +{ + import_ack = FALSE; + if (gtk_main_level() > 1) + gtk_main_quit(); +} + +static void import_filesel_cb(GtkWidget *widget, gpointer data) +{ + const gchar *filename; + gchar *utf8_filename; + + filename = filesel_select_file(_("Select importing file"), NULL); + if (!filename) return; + + utf8_filename = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + if (!utf8_filename) { + g_warning("import_filesel_cb(): faild to convert characer set."); + utf8_filename = g_strdup(filename); + } + gtk_entry_set_text(GTK_ENTRY(file_entry), utf8_filename); + g_free(utf8_filename); +} + +static void import_destsel_cb(GtkWidget *widget, gpointer data) +{ + FolderItem *dest; + + dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL); + if (dest && dest->path) + gtk_entry_set_text(GTK_ENTRY(dest_entry), dest->path); +} + +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + import_cancel_cb(NULL, NULL); + return TRUE; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) + import_cancel_cb(NULL, NULL); + return FALSE; +} diff --git a/src/import.h b/src/import.h new file mode 100644 index 00000000..64d8421f --- /dev/null +++ b/src/import.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __IMPORT_H__ +#define __IMPORT_H__ + +#include + +#include "folder.h" + +gint import_mbox(FolderItem *default_dest); + +#endif /* __IMPORT_H__ */ diff --git a/src/importldif.c b/src/importldif.c new file mode 100644 index 00000000..b7bfa5a0 --- /dev/null +++ b/src/importldif.c @@ -0,0 +1,846 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Edit VCard address book data. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "addrbook.h" +#include "addressbook.h" +#include "addressitem.h" +#include "gtkutils.h" +#include "stock_pixmap.h" +#include "prefs_common.h" +#include "manage_window.h" +#include "mgutils.h" +#include "ldif.h" +#include "utils.h" + +#define IMPORTLDIF_GUESS_NAME "LDIF Import" + +#define PAGE_FILE_INFO 0 +#define PAGE_ATTRIBUTES 1 +#define PAGE_FINISH 2 + +#define IMPORTLDIF_WIDTH 380 +#define IMPORTLDIF_HEIGHT 300 + +#define FIELDS_N_COLS 3 +#define FIELDS_COL_WIDTH_SELECT 10 +#define FIELDS_COL_WIDTH_FIELD 140 +#define FIELDS_COL_WIDTH_ATTRIB 140 + +typedef enum { + FIELD_COL_SELECT = 0, + FIELD_COL_FIELD = 1, + FIELD_COL_ATTRIB = 2 +} ImpLdif_FieldColPos; + +static struct _ImpLdif_Dlg { + GtkWidget *window; + GtkWidget *notebook; + GtkWidget *file_entry; + GtkWidget *name_entry; + GtkWidget *clist_field; + GtkWidget *name_ldif; + GtkWidget *name_attrib; + GtkWidget *check_select; + GtkWidget *labelBook; + GtkWidget *labelFile; + GtkWidget *labelRecords; + GtkWidget *btnPrev; + GtkWidget *btnNext; + GtkWidget *btnCancel; + GtkWidget *statusbar; + gint status_cid; + gint rowIndSelect; + gint rowCount; + gchar *nameBook; + gchar *fileName; + gboolean cancelled; +} impldif_dlg; + +static struct _AddressFileSelection _imp_ldif_file_selector_; +static AddressBookFile *_importedBook_; +static AddressIndex *_imp_addressIndex_; +static LdifFile *_ldifFile_ = NULL; + +static GdkPixmap *markxpm; +static GdkBitmap *markxpmmask; + +static void imp_ldif_status_show( gchar *msg ) { + if( impldif_dlg.statusbar != NULL ) { + gtk_statusbar_pop( GTK_STATUSBAR(impldif_dlg.statusbar), impldif_dlg.status_cid ); + if( msg ) { + gtk_statusbar_push( GTK_STATUSBAR(impldif_dlg.statusbar), impldif_dlg.status_cid, msg ); + } + } +} + +static void imp_ldif_message( void ) { + gchar *sMsg = NULL; + gint pageNum; + + pageNum = gtk_notebook_get_current_page( GTK_NOTEBOOK(impldif_dlg.notebook) ); + if( pageNum == PAGE_FILE_INFO ) { + sMsg = _( "Please specify address book name and file to import." ); + } + else if( pageNum == PAGE_ATTRIBUTES ) { + sMsg = _( "Select and rename LDIF field names to import." ); + } + else if( pageNum == PAGE_FINISH ) { + sMsg = _( "File imported." ); + } + imp_ldif_status_show( sMsg ); +} + +static gchar *imp_ldif_guess_file( AddressBookFile *abf ) { + gchar *newFile = NULL; + GList *fileList = NULL; + gint fileNum = 1; + fileList = addrbook_get_bookfile_list( abf ); + if( fileList ) { + fileNum = 1 + abf->maxValue; + } + newFile = addrbook_gen_new_file_name( fileNum ); + g_list_free( fileList ); + fileList = NULL; + return newFile; +} + +static void imp_ldif_update_row( GtkCList *clist ) { + Ldif_FieldRec *rec; + gchar *text[ FIELDS_N_COLS ]; + gint row; + + if( impldif_dlg.rowIndSelect < 0 ) return; + row = impldif_dlg.rowIndSelect; + + rec = gtk_clist_get_row_data( clist, row ); + text[ FIELD_COL_SELECT ] = ""; + text[ FIELD_COL_FIELD ] = rec->tagName; + text[ FIELD_COL_ATTRIB ] = rec->userName; + + gtk_clist_freeze( clist ); + gtk_clist_remove( clist, row ); + if( row == impldif_dlg.rowCount - 1 ) { + gtk_clist_append( clist, text ); + } + else { + gtk_clist_insert( clist, row, text ); + } + if( rec->selected ) + gtk_clist_set_pixmap( clist, row, FIELD_COL_SELECT, markxpm, markxpmmask ); + + gtk_clist_set_row_data( clist, row, rec ); + gtk_clist_thaw( clist ); +} + +static void imp_ldif_load_fields( LdifFile *ldf ) { + GtkCList *clist = GTK_CLIST(impldif_dlg.clist_field); + GList *node, *list; + gchar *text[ FIELDS_N_COLS ]; + + impldif_dlg.rowIndSelect = -1; + impldif_dlg.rowCount = 0; + if( ! ldf->accessFlag ) return; + gtk_clist_clear( clist ); + list = ldif_get_fieldlist( ldf ); + node = list; + while( node ) { + Ldif_FieldRec *rec = node->data; + gint row; + + if( ! rec->reserved ) { + text[ FIELD_COL_SELECT ] = ""; + text[ FIELD_COL_FIELD ] = rec->tagName; + text[ FIELD_COL_ATTRIB ] = rec->userName; + row = gtk_clist_append( clist, text ); + gtk_clist_set_row_data( clist, row, rec ); + if( rec->selected ) + gtk_clist_set_pixmap( clist, row, FIELD_COL_SELECT, markxpm, markxpmmask ); + impldif_dlg.rowCount++; + } + node = g_list_next( node ); + } + g_list_free( list ); + list = NULL; + ldif_set_accessed( ldf, FALSE ); +} + +static void imp_ldif_field_list_selected( GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data ) { + Ldif_FieldRec *rec = gtk_clist_get_row_data( clist, row ); + + impldif_dlg.rowIndSelect = row; + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.name_attrib), "" ); + if( rec ) { + gtk_label_set_text( GTK_LABEL(impldif_dlg.name_ldif), rec->tagName ); + if( rec->userName ) + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.name_attrib), rec->userName ); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON( impldif_dlg.check_select), + rec->selected ); + } + gtk_widget_grab_focus(impldif_dlg.name_attrib); +} + +static gboolean imp_ldif_field_list_toggle( GtkCList *clist, GdkEventButton *event, gpointer data ) { + if( ! event ) return FALSE; + if( impldif_dlg.rowIndSelect < 0 ) return FALSE; + if( event->button == 1 ) { + if( event->type == GDK_2BUTTON_PRESS ) { + Ldif_FieldRec *rec = gtk_clist_get_row_data( clist, impldif_dlg.rowIndSelect ); + if( rec ) { + rec->selected = ! rec->selected; + imp_ldif_update_row( clist ); + } + } + } + return FALSE; +} + +static void imp_ldif_modify_pressed( GtkWidget *widget, gpointer data ) { + GtkCList *clist = GTK_CLIST(impldif_dlg.clist_field); + Ldif_FieldRec *rec; + gint row; + + if( impldif_dlg.rowIndSelect < 0 ) return; + row = impldif_dlg.rowIndSelect; + rec = gtk_clist_get_row_data( clist, impldif_dlg.rowIndSelect ); + + g_free( rec->userName ); + rec->userName = gtk_editable_get_chars( GTK_EDITABLE(impldif_dlg.name_attrib), 0, -1 ); + rec->selected = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( impldif_dlg.check_select) ); + imp_ldif_update_row( clist ); + gtk_clist_select_row( clist, row, 0 ); + gtk_label_set_text( GTK_LABEL(impldif_dlg.name_ldif), "" ); + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.name_attrib), "" ); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( impldif_dlg.check_select), FALSE ); +} + +/* +* Move off fields page. +* return: TRUE if OK to move off page. +*/ +static gboolean imp_ldif_field_move() { + gboolean retVal = FALSE; + gchar *newFile; + AddressBookFile *abf = NULL; + + if( _importedBook_ ) { + addrbook_free_book( _importedBook_ ); + } + + abf = addrbook_create_book(); + addrbook_set_path( abf, _imp_addressIndex_->filePath ); + addrbook_set_name( abf, impldif_dlg.nameBook ); + newFile = imp_ldif_guess_file( abf ); + addrbook_set_file( abf, newFile ); + g_free( newFile ); + + /* Import data into file */ + if( ldif_import_data( _ldifFile_, abf->addressCache ) == MGU_SUCCESS ) { + addrbook_save_data( abf ); + abf->dirtyFlag = TRUE; + _importedBook_ = abf; + retVal = TRUE; + } + else { + addrbook_free_book( abf ); + } + + return retVal; +} + +/* +* Move off fields page. +* return: TRUE if OK to move off page. +*/ +static gboolean imp_ldif_file_move() { + gboolean retVal = FALSE; + gchar *sName; + gchar *sFile; + gchar *sMsg = NULL; + gboolean errFlag = FALSE; + + sFile = gtk_editable_get_chars( GTK_EDITABLE(impldif_dlg.file_entry), 0, -1 ); + g_strchug( sFile ); g_strchomp( sFile ); + + sName = gtk_editable_get_chars( GTK_EDITABLE(impldif_dlg.name_entry), 0, -1 ); + g_strchug( sName ); g_strchomp( sName ); + + g_free( impldif_dlg.nameBook ); + g_free( impldif_dlg.fileName ); + impldif_dlg.nameBook = sName; + impldif_dlg.fileName = sFile; + + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.file_entry), sFile ); + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.name_entry), sName ); + + if( *sFile == '\0'|| strlen( sFile ) < 1 ) { + sMsg = _( "Please select a file." ); + gtk_widget_grab_focus(impldif_dlg.file_entry); + errFlag = TRUE; + } + + if( *sName == '\0'|| strlen( sName ) < 1 ) { + if( ! errFlag ) sMsg = _( "Address book name must be supplied." ); + gtk_widget_grab_focus(impldif_dlg.name_entry); + errFlag = TRUE; + } + + if( ! errFlag ) { + /* Read attribute list */ + ldif_set_file( _ldifFile_, sFile ); + if( ldif_read_tags( _ldifFile_ ) == MGU_SUCCESS ) { + /* Load fields */ + /* ldif_print_file( _ldifFile_, stdout ); */ + imp_ldif_load_fields( _ldifFile_ ); + retVal = TRUE; + } + else { + sMsg = _( "Error reading LDIF fields." ); + } + } + imp_ldif_status_show( sMsg ); + + return retVal; +} + +/* + * Display finish page. + */ +static void imp_ldif_finish_show() { + gchar *sMsg; + gchar *name; + + name = gtk_editable_get_chars( GTK_EDITABLE(impldif_dlg.name_entry), 0, -1 ); + gtk_label_set_text( GTK_LABEL(impldif_dlg.labelBook), name ); + g_free( name ); + gtk_label_set_text( GTK_LABEL(impldif_dlg.labelFile), _ldifFile_->path ); + gtk_label_set_text( GTK_LABEL(impldif_dlg.labelRecords), itos( _ldifFile_->importCount ) ); + gtk_widget_set_sensitive( impldif_dlg.btnPrev, FALSE ); + gtk_widget_set_sensitive( impldif_dlg.btnNext, FALSE ); + if( _ldifFile_->retVal == MGU_SUCCESS ) { + sMsg = _( "LDIF file imported successfully." ); + } + else { + sMsg = mgu_error2string( _ldifFile_->retVal ); + } + imp_ldif_status_show( sMsg ); + gtk_widget_grab_focus(impldif_dlg.btnCancel); +} + +static void imp_ldif_prev( GtkWidget *widget ) { + gint pageNum; + + pageNum = gtk_notebook_get_current_page( GTK_NOTEBOOK(impldif_dlg.notebook) ); + if( pageNum == PAGE_ATTRIBUTES ) { + /* Goto file page stuff */ + gtk_notebook_set_current_page( + GTK_NOTEBOOK(impldif_dlg.notebook), PAGE_FILE_INFO ); + gtk_widget_set_sensitive( impldif_dlg.btnPrev, FALSE ); + } + imp_ldif_message(); +} + +static void imp_ldif_next( GtkWidget *widget ) { + gint pageNum; + + pageNum = gtk_notebook_get_current_page( GTK_NOTEBOOK(impldif_dlg.notebook) ); + if( pageNum == PAGE_FILE_INFO ) { + /* Goto attributes stuff */ + if( imp_ldif_file_move() ) { + gtk_notebook_set_current_page( + GTK_NOTEBOOK(impldif_dlg.notebook), PAGE_ATTRIBUTES ); + imp_ldif_message(); + gtk_widget_set_sensitive( impldif_dlg.btnPrev, TRUE ); + } + else { + gtk_widget_set_sensitive( impldif_dlg.btnPrev, FALSE ); + } + } + else if( pageNum == PAGE_ATTRIBUTES ) { + /* Goto finish stuff */ + if( imp_ldif_field_move() ) { + gtk_notebook_set_current_page( + GTK_NOTEBOOK(impldif_dlg.notebook), PAGE_FINISH ); + imp_ldif_finish_show(); + } + } +} + +static void imp_ldif_cancel( GtkWidget *widget, gpointer data ) { + gint pageNum; + + pageNum = gtk_notebook_get_current_page( GTK_NOTEBOOK(impldif_dlg.notebook) ); + if( pageNum != PAGE_FINISH ) { + impldif_dlg.cancelled = TRUE; + } + gtk_main_quit(); +} + +static void imp_ldif_file_ok( GtkWidget *widget, gpointer data ) { + const gchar *sFile; + AddressFileSelection *afs; + GtkWidget *fileSel; + + afs = ( AddressFileSelection * ) data; + fileSel = afs->fileSelector; + sFile = gtk_file_selection_get_filename( GTK_FILE_SELECTION(fileSel) ); + + afs->cancelled = FALSE; + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.file_entry), sFile ); + gtk_widget_hide( afs->fileSelector ); + gtk_grab_remove( afs->fileSelector ); + gtk_widget_grab_focus( impldif_dlg.file_entry ); +} + +static void imp_ldif_file_cancel( GtkWidget *widget, gpointer data ) { + AddressFileSelection *afs = ( AddressFileSelection * ) data; + afs->cancelled = TRUE; + gtk_widget_hide( afs->fileSelector ); + gtk_grab_remove( afs->fileSelector ); + gtk_widget_grab_focus( impldif_dlg.file_entry ); +} + +static void imp_ldif_file_select_create( AddressFileSelection *afs ) { + GtkWidget *fileSelector; + + fileSelector = gtk_file_selection_new( _("Select LDIF File") ); + gtk_file_selection_hide_fileop_buttons( GTK_FILE_SELECTION(fileSelector) ); + g_signal_connect( G_OBJECT (GTK_FILE_SELECTION(fileSelector)->ok_button), + "clicked", G_CALLBACK (imp_ldif_file_ok), ( gpointer ) afs ); + g_signal_connect( G_OBJECT (GTK_FILE_SELECTION(fileSelector)->cancel_button), + "clicked", G_CALLBACK (imp_ldif_file_cancel), ( gpointer ) afs ); + afs->fileSelector = fileSelector; + afs->cancelled = TRUE; +} + +static void imp_ldif_file_select( void ) { + gchar *sFile; + if( ! _imp_ldif_file_selector_.fileSelector ) + imp_ldif_file_select_create( & _imp_ldif_file_selector_ ); + + sFile = gtk_editable_get_chars( GTK_EDITABLE(impldif_dlg.file_entry), 0, -1 ); + gtk_file_selection_set_filename( + GTK_FILE_SELECTION( _imp_ldif_file_selector_.fileSelector ), + sFile ); + g_free( sFile ); + gtk_widget_show( _imp_ldif_file_selector_.fileSelector ); + gtk_grab_add( _imp_ldif_file_selector_.fileSelector ); +} + +static gint imp_ldif_delete_event( GtkWidget *widget, GdkEventAny *event, gpointer data ) { + imp_ldif_cancel( widget, data ); + return TRUE; +} + +static gboolean imp_ldif_key_pressed( GtkWidget *widget, GdkEventKey *event, gpointer data ) { + if (event && event->keyval == GDK_Escape) { + imp_ldif_cancel( widget, data ); + } + return FALSE; +} + +static void imp_ldif_page_file( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *file_entry; + GtkWidget *name_entry; + GtkWidget *file_btn; + gint top; + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add( GTK_CONTAINER( impldif_dlg.notebook ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( impldif_dlg.notebook ), + gtk_notebook_get_nth_page( + GTK_NOTEBOOK( impldif_dlg.notebook ), pageNum ), + label ); + + table = gtk_table_new(2, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8 ); + + /* First row */ + top = 0; + label = gtk_label_new(_("Address Book")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + name_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), name_entry, 1, 2, top, (top + 1), + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Second row */ + top = 1; + label = gtk_label_new(_("File Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + file_entry = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), file_entry, 1, 2, top, (top + 1), + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + file_btn = gtk_button_new_with_label( _(" ... ")); + gtk_table_attach(GTK_TABLE(table), file_btn, 2, 3, top, (top + 1), + GTK_FILL, 0, 3, 0); + + gtk_widget_show_all(vbox); + + /* Button handler */ + g_signal_connect(G_OBJECT(file_btn), "clicked", + G_CALLBACK(imp_ldif_file_select), NULL); + + impldif_dlg.file_entry = file_entry; + impldif_dlg.name_entry = name_entry; +} + +static void imp_ldif_page_fields( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *vboxt; + GtkWidget *vboxb; + GtkWidget *buttonMod; + + GtkWidget *table; + GtkWidget *label; + GtkWidget *clist_swin; + GtkWidget *clist_field; + GtkWidget *name_ldif; + GtkWidget *name_attrib; + GtkWidget *check_select; + gint top; + + gchar *titles[ FIELDS_N_COLS ]; + gint i; + + titles[ FIELD_COL_SELECT ] = _("S"); + titles[ FIELD_COL_FIELD ] = _("LDIF Field"); + titles[ FIELD_COL_ATTRIB ] = _("Attribute Name"); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add( GTK_CONTAINER( impldif_dlg.notebook ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), 4 ); + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( impldif_dlg.notebook ), + gtk_notebook_get_nth_page(GTK_NOTEBOOK( impldif_dlg.notebook ), pageNum ), + label ); + + /* Upper area - Field list */ + vboxt = gtk_vbox_new( FALSE, 4 ); + gtk_container_add( GTK_CONTAINER( vbox ), vboxt ); + + clist_swin = gtk_scrolled_window_new( NULL, NULL ); + gtk_container_add( GTK_CONTAINER(vboxt), clist_swin ); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + + clist_field = gtk_clist_new_with_titles( FIELDS_N_COLS, titles ); + gtk_container_add( GTK_CONTAINER(clist_swin), clist_field ); + gtk_clist_set_selection_mode( GTK_CLIST(clist_field), GTK_SELECTION_BROWSE ); + gtk_clist_set_column_width( + GTK_CLIST(clist_field), FIELD_COL_SELECT, FIELDS_COL_WIDTH_SELECT ); + gtk_clist_set_column_width( + GTK_CLIST(clist_field), FIELD_COL_FIELD, FIELDS_COL_WIDTH_FIELD ); + gtk_clist_set_column_width( + GTK_CLIST(clist_field), FIELD_COL_ATTRIB, FIELDS_COL_WIDTH_ATTRIB ); + + for( i = 0; i < FIELDS_N_COLS; i++ ) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist_field)->column[i].button, GTK_CAN_FOCUS); + + /* Lower area - Edit area */ + vboxb = gtk_vbox_new( FALSE, 4 ); + gtk_box_pack_end(GTK_BOX(vbox), vboxb, FALSE, FALSE, 2); + + /* Data entry area */ + table = gtk_table_new( 3, 3, FALSE); + gtk_box_pack_start(GTK_BOX(vboxb), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 4); + gtk_table_set_col_spacings(GTK_TABLE(table), 4); + + /* First row */ + top = 0; + label = gtk_label_new(_("LDIF Field")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + name_ldif = gtk_label_new( "" ); + gtk_misc_set_alignment(GTK_MISC(name_ldif), 0.01, 0.5); + gtk_table_attach(GTK_TABLE(table), name_ldif, 1, 3, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Second row */ + ++top; + label = gtk_label_new(_("Attribute")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + name_attrib = gtk_entry_new(); + gtk_table_attach(GTK_TABLE(table), name_attrib, 1, 3, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + /* Next row */ + ++top; + label = gtk_label_new(_("Select")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + check_select = gtk_check_button_new(); + gtk_table_attach(GTK_TABLE(table), check_select, 1, 2, top, (top + 1), GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + + buttonMod = gtk_button_new_with_label( _("Modify")); + gtk_table_attach(GTK_TABLE(table), buttonMod, 2, 3, top, (top + 1), GTK_FILL, 0, 3, 0); + + gtk_widget_show_all(vbox); + + /* Event handlers */ + g_signal_connect( G_OBJECT(clist_field), "select_row", + G_CALLBACK(imp_ldif_field_list_selected), NULL ); + g_signal_connect( G_OBJECT(clist_field), "button_press_event", + G_CALLBACK(imp_ldif_field_list_toggle), NULL ); + g_signal_connect( G_OBJECT(buttonMod), "clicked", + G_CALLBACK(imp_ldif_modify_pressed), NULL ); + + impldif_dlg.clist_field = clist_field; + impldif_dlg.name_ldif = name_ldif; + impldif_dlg.name_attrib = name_attrib; + impldif_dlg.check_select = check_select; +} + +static void imp_ldif_page_finish( gint pageNum, gchar *pageLbl ) { + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkWidget *labelBook; + GtkWidget *labelFile; + GtkWidget *labelRecs; + gint top; + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add( GTK_CONTAINER( impldif_dlg.notebook ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), BORDER_WIDTH ); + + label = gtk_label_new( pageLbl ); + gtk_widget_show( label ); + gtk_notebook_set_tab_label( + GTK_NOTEBOOK( impldif_dlg.notebook ), + gtk_notebook_get_nth_page( GTK_NOTEBOOK( impldif_dlg.notebook ), pageNum ), label ); + + table = gtk_table_new(3, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width( GTK_CONTAINER(table), 8 ); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8 ); + + /* First row */ + top = 0; + label = gtk_label_new(_("Address Book :")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + labelBook = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), labelBook, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(labelBook), 0, 0.5); + + /* Second row */ + top++; + label = gtk_label_new(_("File Name :")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + labelFile = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), labelFile, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(labelFile), 0, 0.5); + + /* Third row */ + top++; + label = gtk_label_new(_("Records :")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + labelRecs = gtk_label_new(""); + gtk_table_attach(GTK_TABLE(table), labelRecs, 1, 2, top, (top + 1), GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(labelRecs), 0, 0.5); + + impldif_dlg.labelBook = labelBook; + impldif_dlg.labelFile = labelFile; + impldif_dlg.labelRecords = labelRecs; +} + +static void imp_ldif_dialog_create() { + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *vnbox; + GtkWidget *notebook; + GtkWidget *hbbox; + GtkWidget *btnPrev; + GtkWidget *btnNext; + GtkWidget *btnCancel; + GtkWidget *hsbox; + GtkWidget *statusbar; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, IMPORTLDIF_WIDTH, IMPORTLDIF_HEIGHT ); + gtk_container_set_border_width( GTK_CONTAINER(window), 0 ); + gtk_window_set_title( GTK_WINDOW(window), _("Import LDIF file into Address Book") ); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(imp_ldif_delete_event), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(imp_ldif_key_pressed), NULL); + + vbox = gtk_vbox_new(FALSE, 4); + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + vnbox = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width(GTK_CONTAINER(vnbox), 4); + gtk_widget_show(vnbox); + gtk_box_pack_start(GTK_BOX(vbox), vnbox, TRUE, TRUE, 0); + + /* Notebook */ + notebook = gtk_notebook_new(); + gtk_notebook_set_show_tabs( GTK_NOTEBOOK(notebook), FALSE ); + gtk_widget_show(notebook); + gtk_box_pack_start(GTK_BOX(vnbox), notebook, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(notebook), 6); + + /* Status line */ + hsbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hsbox, FALSE, FALSE, BORDER_WIDTH); + statusbar = gtk_statusbar_new(); + gtk_box_pack_start(GTK_BOX(hsbox), statusbar, TRUE, TRUE, BORDER_WIDTH); + + /* Button panel */ + gtkut_button_set_create(&hbbox, &btnPrev, _( "Prev" ), + &btnNext, _( "Next" ), + &btnCancel, _( "Cancel" ) ); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbbox), 2); + gtk_widget_grab_default(btnNext); + + /* Button handlers */ + g_signal_connect(G_OBJECT(btnPrev), "clicked", + G_CALLBACK(imp_ldif_prev), NULL); + g_signal_connect(G_OBJECT(btnNext), "clicked", + G_CALLBACK(imp_ldif_next), NULL); + g_signal_connect(G_OBJECT(btnCancel), "clicked", + G_CALLBACK(imp_ldif_cancel), NULL); + + gtk_widget_show_all(vbox); + + impldif_dlg.window = window; + impldif_dlg.notebook = notebook; + impldif_dlg.btnPrev = btnPrev; + impldif_dlg.btnNext = btnNext; + impldif_dlg.btnCancel = btnCancel; + impldif_dlg.statusbar = statusbar; + impldif_dlg.status_cid = gtk_statusbar_get_context_id( + GTK_STATUSBAR(statusbar), "Import LDIF Dialog" ); + +} + +static void imp_ldif_create() { + imp_ldif_dialog_create(); + imp_ldif_page_file( PAGE_FILE_INFO, _( "File Info" ) ); + imp_ldif_page_fields( PAGE_ATTRIBUTES, _( "Attributes" ) ); + imp_ldif_page_finish( PAGE_FINISH, _( "Finish" ) ); + gtk_widget_show_all( impldif_dlg.window ); +} + +AddressBookFile *addressbook_imp_ldif( AddressIndex *addrIndex ) { + _importedBook_ = NULL; + _imp_addressIndex_ = addrIndex; + + if( ! impldif_dlg.window ) + imp_ldif_create(); + impldif_dlg.cancelled = FALSE; + gtk_widget_show(impldif_dlg.window); + manage_window_set_transient(GTK_WINDOW(impldif_dlg.window)); + gtk_widget_grab_default(impldif_dlg.btnNext); + + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.name_entry), IMPORTLDIF_GUESS_NAME ); + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.file_entry), "" ); + gtk_label_set_text( GTK_LABEL(impldif_dlg.name_ldif), "" ); + gtk_entry_set_text( GTK_ENTRY(impldif_dlg.name_attrib), "" ); + gtk_clist_clear( GTK_CLIST(impldif_dlg.clist_field) ); + gtk_notebook_set_current_page( GTK_NOTEBOOK(impldif_dlg.notebook), PAGE_FILE_INFO ); + gtk_widget_set_sensitive( impldif_dlg.btnPrev, FALSE ); + gtk_widget_set_sensitive( impldif_dlg.btnNext, TRUE ); + stock_pixmap_gdk( impldif_dlg.window, STOCK_PIXMAP_MARK, + &markxpm, &markxpmmask ); + imp_ldif_message(); + gtk_widget_grab_focus(impldif_dlg.file_entry); + + impldif_dlg.rowIndSelect = -1; + impldif_dlg.rowCount = 0; + g_free( impldif_dlg.nameBook ); + g_free( impldif_dlg.fileName ); + impldif_dlg.nameBook = NULL; + impldif_dlg.fileName = NULL; + + _ldifFile_ = ldif_create(); + gtk_main(); + gtk_widget_hide(impldif_dlg.window); + ldif_free( _ldifFile_ ); + _ldifFile_ = NULL; + _imp_addressIndex_ = NULL; + + g_free( impldif_dlg.nameBook ); + g_free( impldif_dlg.fileName ); + impldif_dlg.nameBook = NULL; + impldif_dlg.fileName = NULL; + + if( impldif_dlg.cancelled == TRUE ) return NULL; + return _importedBook_; +} + +/* +* End of Source. +*/ + diff --git a/src/importldif.h b/src/importldif.h new file mode 100644 index 00000000..5e648b34 --- /dev/null +++ b/src/importldif.h @@ -0,0 +1,34 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Import LDIF data. + */ + +#ifndef __IMPORT_LDIF_H__ +#define __IMPORT_LDIF_H__ + +/* Function prototypes */ +AddressBookFile *addressbook_imp_ldif( AddressIndex *addrIndex ); + +#endif /* __IMPORT_LDIF_H__ */ + +/* +* End of Source. +*/ diff --git a/src/inc.c b/src/inc.c new file mode 100644 index 00000000..d8ab5132 --- /dev/null +++ b/src/inc.c @@ -0,0 +1,1337 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "inc.h" +#include "mainwindow.h" +#include "folderview.h" +#include "summaryview.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "account.h" +#include "procmsg.h" +#include "socket.h" +#include "ssl.h" +#include "pop.h" +#include "recv.h" +#include "mbox.h" +#include "utils.h" +#include "gtkutils.h" +#include "statusbar.h" +#include "manage_window.h" +#include "stock_pixmap.h" +#include "progressdialog.h" +#include "inputdialog.h" +#include "alertpanel.h" +#include "filter.h" +#include "folder.h" + +static GList *inc_dialog_list = NULL; + +static guint inc_lock_count = 0; + +static GdkPixmap *currentxpm; +static GdkBitmap *currentxpmmask; +static GdkPixmap *errorxpm; +static GdkBitmap *errorxpmmask; +static GdkPixmap *okxpm; +static GdkBitmap *okxpmmask; + +#define MSGBUFSIZE 8192 + +static void inc_finished (MainWindow *mainwin, + gboolean new_messages); +static gint inc_account_mail_real (MainWindow *mainwin, + PrefsAccount *account); + +static IncProgressDialog *inc_progress_dialog_create + (gboolean autocheck); +static void inc_progress_dialog_set_list(IncProgressDialog *inc_dialog); +static void inc_progress_dialog_destroy (IncProgressDialog *inc_dialog); + +static IncSession *inc_session_new (PrefsAccount *account); +static void inc_session_destroy (IncSession *session); +static gint inc_start (IncProgressDialog *inc_dialog); +static IncState inc_pop3_session_do (IncSession *session); + +static void inc_progress_dialog_update (IncProgressDialog *inc_dialog, + IncSession *inc_session); + +static void inc_progress_dialog_set_label + (IncProgressDialog *inc_dialog, + IncSession *inc_session); +static void inc_progress_dialog_set_progress + (IncProgressDialog *inc_dialog, + IncSession *inc_session); + +static void inc_update_folderview (IncProgressDialog *inc_dialog, + IncSession *inc_session); + +static void inc_progress_dialog_update_periodic + (IncProgressDialog *inc_dialog, + IncSession *inc_session); +static void inc_update_folderview_periodic + (IncProgressDialog *inc_dialog, + IncSession *inc_session); + +static gint inc_recv_data_progressive (Session *session, + guint cur_len, + guint total_len, + gpointer data); +static gint inc_recv_data_finished (Session *session, + guint len, + gpointer data); +static gint inc_recv_message (Session *session, + const gchar *msg, + gpointer data); +static gint inc_drop_message (Pop3Session *session, + const gchar *file); + +static void inc_put_error (IncState istate, + const gchar *msg); + +static void inc_cancel_cb (GtkWidget *widget, + gpointer data); +static gint inc_dialog_delete_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); + +static gint inc_spool (void); +static gint get_spool (FolderItem *dest, + const gchar *mbox); + +static void inc_autocheck_timer_set_interval (guint interval); +static gint inc_autocheck_func (gpointer data); + +/** + * inc_finished: + * @mainwin: Main window. + * @new_messages: TRUE if some messages have been received. + * + * Update the folder view and the summary view after receiving + * messages. If @new_messages is FALSE, this function avoids unneeded + * updating. + **/ +static void inc_finished(MainWindow *mainwin, gboolean new_messages) +{ + FolderItem *item; + + if (prefs_common.scan_all_after_inc) + folderview_check_new(NULL); + + if (!new_messages && !prefs_common.scan_all_after_inc) return; + + if (prefs_common.open_inbox_on_inc) { + item = cur_account && cur_account->inbox + ? folder_find_item_from_identifier(cur_account->inbox) + : folder_get_default_inbox(); + folderview_unselect(mainwin->folderview); + folderview_select(mainwin->folderview, item); + } else if (prefs_common.scan_all_after_inc) { + item = mainwin->summaryview->folder_item; + folderview_update_item(item, TRUE); + } +} + +void inc_mail(MainWindow *mainwin) +{ + gint new_msgs = 0; + + if (inc_lock_count) return; + + if (!main_window_toggle_online_if_offline(mainwin)) + return; + + inc_autocheck_timer_remove(); + summary_write_cache(mainwin->summaryview); + main_window_lock(mainwin); + + if (prefs_common.use_extinc && prefs_common.extinc_cmd) { + /* external incorporating program */ + if (execute_command_line(prefs_common.extinc_cmd, FALSE) != 0) { + main_window_unlock(mainwin); + inc_autocheck_timer_set(); + return; + } + + if (prefs_common.inc_local) + new_msgs = inc_spool(); + } else { + if (prefs_common.inc_local) { + new_msgs = inc_spool(); + if (new_msgs < 0) + new_msgs = 0; + } + + new_msgs += inc_account_mail_real(mainwin, cur_account); + } + + inc_finished(mainwin, new_msgs > 0); + main_window_unlock(mainwin); + inc_autocheck_timer_set(); +} + +static gint inc_account_mail_real(MainWindow *mainwin, PrefsAccount *account) +{ + IncProgressDialog *inc_dialog; + IncSession *session; + + if (account->protocol == A_IMAP4 || account->protocol == A_NNTP) { + FolderItem *item = mainwin->summaryview->folder_item; + + folderview_check_new(FOLDER(account->folder)); + if (!prefs_common.scan_all_after_inc && item != NULL && + FOLDER(account->folder) == item->folder) + folderview_update_item(item, TRUE); + return 1; + } + + session = inc_session_new(account); + if (!session) return 0; + + inc_dialog = inc_progress_dialog_create(FALSE); + inc_dialog->queue_list = g_list_append(inc_dialog->queue_list, session); + inc_dialog->mainwin = mainwin; + inc_progress_dialog_set_list(inc_dialog); + + main_window_set_toolbar_sensitive(mainwin); + main_window_set_menu_sensitive(mainwin); + + return inc_start(inc_dialog); +} + +gint inc_account_mail(MainWindow *mainwin, PrefsAccount *account) +{ + gint new_msgs; + + if (inc_lock_count) return 0; + + if (!main_window_toggle_online_if_offline(mainwin)) + return 0; + + inc_autocheck_timer_remove(); + summary_write_cache(mainwin->summaryview); + main_window_lock(mainwin); + + new_msgs = inc_account_mail_real(mainwin, account); + + inc_finished(mainwin, new_msgs > 0); + main_window_unlock(mainwin); + inc_autocheck_timer_set(); + + return new_msgs; +} + +void inc_all_account_mail(MainWindow *mainwin, gboolean autocheck) +{ + GList *list, *queue_list = NULL; + IncProgressDialog *inc_dialog; + gint new_msgs = 0; + + if (inc_lock_count) return; + + if (!main_window_toggle_online_if_offline(mainwin)) + return; + + inc_autocheck_timer_remove(); + summary_write_cache(mainwin->summaryview); + main_window_lock(mainwin); + + if (prefs_common.inc_local) { + new_msgs = inc_spool(); + if (new_msgs < 0) + new_msgs = 0; + } + + /* check IMAP4 / News folders */ + for (list = account_get_list(); list != NULL; list = list->next) { + PrefsAccount *account = list->data; + if ((account->protocol == A_IMAP4 || + account->protocol == A_NNTP) && account->recv_at_getall) { + FolderItem *item = mainwin->summaryview->folder_item; + + folderview_check_new(FOLDER(account->folder)); + if (!prefs_common.scan_all_after_inc && item != NULL && + FOLDER(account->folder) == item->folder) + folderview_update_item(item, TRUE); + } + } + + /* check POP3 accounts */ + for (list = account_get_list(); list != NULL; list = list->next) { + IncSession *session; + PrefsAccount *account = list->data; + + if (account->recv_at_getall) { + session = inc_session_new(account); + if (session) + queue_list = g_list_append(queue_list, session); + } + } + + if (queue_list) { + inc_dialog = inc_progress_dialog_create(autocheck); + inc_dialog->queue_list = queue_list; + inc_dialog->mainwin = mainwin; + inc_progress_dialog_set_list(inc_dialog); + + main_window_set_toolbar_sensitive(mainwin); + main_window_set_menu_sensitive(mainwin); + + new_msgs += inc_start(inc_dialog); + } + + inc_finished(mainwin, new_msgs > 0); + main_window_unlock(mainwin); + inc_autocheck_timer_set(); +} + +static IncProgressDialog *inc_progress_dialog_create(gboolean autocheck) +{ + IncProgressDialog *dialog; + ProgressDialog *progress; + + dialog = g_new0(IncProgressDialog, 1); + + progress = progress_dialog_create(); + gtk_window_set_title(GTK_WINDOW(progress->window), + _("Retrieving new messages")); + g_signal_connect(G_OBJECT(progress->cancel_btn), "clicked", + G_CALLBACK(inc_cancel_cb), dialog); + g_signal_connect(G_OBJECT(progress->window), "delete_event", + G_CALLBACK(inc_dialog_delete_cb), dialog); + /* manage_window_set_transient(GTK_WINDOW(progress->window)); */ + + progress_dialog_set_value(progress, 0.0); + + stock_pixmap_gdk(progress->clist, STOCK_PIXMAP_COMPLETE, + &okxpm, &okxpmmask); + stock_pixmap_gdk(progress->clist, STOCK_PIXMAP_CONTINUE, + ¤txpm, ¤txpmmask); + stock_pixmap_gdk(progress->clist, STOCK_PIXMAP_ERROR, + &errorxpm, &errorxpmmask); + + if (prefs_common.recv_dialog_mode == RECV_DIALOG_ALWAYS || + (prefs_common.recv_dialog_mode == RECV_DIALOG_MANUAL && + !autocheck)) { + dialog->show_dialog = TRUE; + gtk_widget_show_now(progress->window); + } + + dialog->dialog = progress; + gettimeofday(&dialog->progress_tv, NULL); + gettimeofday(&dialog->folder_tv, NULL); + dialog->queue_list = NULL; + dialog->cur_row = 0; + + inc_dialog_list = g_list_append(inc_dialog_list, dialog); + + return dialog; +} + +static void inc_progress_dialog_set_list(IncProgressDialog *inc_dialog) +{ + GList *list; + + for (list = inc_dialog->queue_list; list != NULL; list = list->next) { + IncSession *session = list->data; + Pop3Session *pop3_session = POP3_SESSION(session->session); + gchar *text[3]; + + session->data = inc_dialog; + + text[0] = NULL; + text[1] = pop3_session->ac_prefs->account_name; + text[2] = _("Standby"); + gtk_clist_append(GTK_CLIST(inc_dialog->dialog->clist), text); + } +} + +static void inc_progress_dialog_clear(IncProgressDialog *inc_dialog) +{ + progress_dialog_set_value(inc_dialog->dialog, 0.0); + progress_dialog_set_label(inc_dialog->dialog, ""); + main_window_progress_off(inc_dialog->mainwin); +} + +static void inc_progress_dialog_destroy(IncProgressDialog *inc_dialog) +{ + g_return_if_fail(inc_dialog != NULL); + + inc_dialog_list = g_list_remove(inc_dialog_list, inc_dialog); + + main_window_progress_off(inc_dialog->mainwin); + progress_dialog_destroy(inc_dialog->dialog); + + g_free(inc_dialog); +} + +static IncSession *inc_session_new(PrefsAccount *account) +{ + IncSession *session; + + g_return_val_if_fail(account != NULL, NULL); + + if (account->protocol != A_POP3) + return NULL; + if (!account->recv_server || !account->userid) + return NULL; + + session = g_new0(IncSession, 1); + + session->session = pop3_session_new(account); + session->session->data = session; + POP3_SESSION(session->session)->drop_message = inc_drop_message; + session_set_recv_message_notify(session->session, + inc_recv_message, session); + session_set_recv_data_progressive_notify(session->session, + inc_recv_data_progressive, + session); + session_set_recv_data_notify(session->session, + inc_recv_data_finished, session); + + session->folder_table = g_hash_table_new(NULL, NULL); + session->tmp_folder_table = g_hash_table_new(NULL, NULL); + + return session; +} + +static void inc_session_destroy(IncSession *session) +{ + g_return_if_fail(session != NULL); + + session_destroy(session->session); + g_hash_table_destroy(session->folder_table); + g_hash_table_destroy(session->tmp_folder_table); + g_free(session); +} + +static gint inc_start(IncProgressDialog *inc_dialog) +{ + IncSession *session; + GtkCList *clist = GTK_CLIST(inc_dialog->dialog->clist); + GList *qlist; + Pop3Session *pop3_session; + IncState inc_state; + gint error_num = 0; + gint new_msgs = 0; + gchar *msg; + gchar *fin_msg; + + qlist = inc_dialog->queue_list; + while (qlist != NULL) { + GList *next = qlist->next; + + session = qlist->data; + pop3_session = POP3_SESSION(session->session); + pop3_session->user = g_strdup(pop3_session->ac_prefs->userid); + if (pop3_session->ac_prefs->passwd) + pop3_session->pass = + g_strdup(pop3_session->ac_prefs->passwd); + else if (pop3_session->ac_prefs->tmp_pass) + pop3_session->pass = + g_strdup(pop3_session->ac_prefs->tmp_pass); + else { + gchar *pass; + + if (inc_dialog->show_dialog) + manage_window_focus_in + (inc_dialog->dialog->window, + NULL, NULL); + + pass = input_dialog_query_password + (pop3_session->ac_prefs->recv_server, + pop3_session->user); + + if (inc_dialog->show_dialog) + manage_window_focus_out + (inc_dialog->dialog->window, + NULL, NULL); + + if (pass) { + pop3_session->ac_prefs->tmp_pass = + g_strdup(pass); + pop3_session->pass = pass; + } + } + + qlist = next; + } + +#define SET_PIXMAP_AND_TEXT(xpm, xpmmask, str) \ +{ \ + gtk_clist_set_pixmap(clist, inc_dialog->cur_row, 0, xpm, xpmmask); \ + gtk_clist_set_text(clist, inc_dialog->cur_row, 2, str); \ +} + + for (; inc_dialog->queue_list != NULL; inc_dialog->cur_row++) { + session = inc_dialog->queue_list->data; + pop3_session = POP3_SESSION(session->session); + + if (pop3_session->pass == NULL) { + SET_PIXMAP_AND_TEXT(okxpm, okxpmmask, _("Cancelled")); + inc_session_destroy(session); + inc_dialog->queue_list = + g_list_remove(inc_dialog->queue_list, session); + continue; + } + + inc_progress_dialog_clear(inc_dialog); + gtk_clist_moveto(clist, inc_dialog->cur_row, -1, 1.0, 0.0); + + SET_PIXMAP_AND_TEXT(currentxpm, currentxpmmask, + _("Retrieving")); + + /* begin POP3 session */ + inc_state = inc_pop3_session_do(session); + + switch (inc_state) { + case INC_SUCCESS: + if (pop3_session->cur_total_num > 0) + msg = g_strdup_printf + (_("Done (%d message(s) (%s) received)"), + pop3_session->cur_total_num, + to_human_readable(pop3_session->cur_total_recv_bytes)); + else + msg = g_strdup_printf(_("Done (no new messages)")); + SET_PIXMAP_AND_TEXT(okxpm, okxpmmask, msg); + g_free(msg); + break; + case INC_CONNECT_ERROR: + SET_PIXMAP_AND_TEXT(errorxpm, errorxpmmask, + _("Connection failed")); + break; + case INC_AUTH_FAILED: + SET_PIXMAP_AND_TEXT(errorxpm, errorxpmmask, + _("Auth failed")); + break; + case INC_LOCKED: + SET_PIXMAP_AND_TEXT(errorxpm, errorxpmmask, + _("Locked")); + break; + case INC_ERROR: + case INC_NO_SPACE: + case INC_IO_ERROR: + case INC_SOCKET_ERROR: + case INC_EOF: + SET_PIXMAP_AND_TEXT(errorxpm, errorxpmmask, _("Error")); + break; + case INC_TIMEOUT: + SET_PIXMAP_AND_TEXT(errorxpm, errorxpmmask, _("Timeout")); + break; + case INC_CANCEL: + SET_PIXMAP_AND_TEXT(okxpm, okxpmmask, _("Cancelled")); + break; + default: + break; + } + + new_msgs += pop3_session->cur_total_num; + + if (!prefs_common.scan_all_after_inc) { + folder_item_scan_foreach(session->folder_table); + folderview_update_item_foreach + (session->folder_table, + !prefs_common.open_inbox_on_inc); + } + + if (pop3_session->error_val == PS_AUTHFAIL && + pop3_session->ac_prefs->tmp_pass) { + g_free(pop3_session->ac_prefs->tmp_pass); + pop3_session->ac_prefs->tmp_pass = NULL; + } + + pop3_write_uidl_list(pop3_session); + + if (inc_state != INC_SUCCESS && inc_state != INC_CANCEL) { + error_num++; + if (inc_dialog->show_dialog) + manage_window_focus_in + (inc_dialog->dialog->window, + NULL, NULL); + inc_put_error(inc_state, pop3_session->error_msg); + if (inc_dialog->show_dialog) + manage_window_focus_out + (inc_dialog->dialog->window, + NULL, NULL); + if (inc_state == INC_NO_SPACE || + inc_state == INC_IO_ERROR) + break; + } + + inc_session_destroy(session); + inc_dialog->queue_list = + g_list_remove(inc_dialog->queue_list, session); + } + +#undef SET_PIXMAP_AND_TEXT + + if (new_msgs > 0) + fin_msg = g_strdup_printf(_("Finished (%d new message(s))"), + new_msgs); + else + fin_msg = g_strdup_printf(_("Finished (no new messages)")); + + progress_dialog_set_label(inc_dialog->dialog, fin_msg); + +#if 0 + if (error_num && !prefs_common.no_recv_err_panel) { + if (inc_dialog->show_dialog) + manage_window_focus_in(inc_dialog->dialog->window, + NULL, NULL); + alertpanel_error(_("Some errors occurred while getting mail.")); + if (inc_dialog->show_dialog) + manage_window_focus_out(inc_dialog->dialog->window, + NULL, NULL); + } +#endif + + while (inc_dialog->queue_list != NULL) { + session = inc_dialog->queue_list->data; + inc_session_destroy(session); + inc_dialog->queue_list = + g_list_remove(inc_dialog->queue_list, session); + } + + if (prefs_common.close_recv_dialog || !inc_dialog->show_dialog) + inc_progress_dialog_destroy(inc_dialog); + else { + gtk_window_set_title(GTK_WINDOW(inc_dialog->dialog->window), + fin_msg); + gtk_label_set_text(GTK_LABEL(GTK_BIN(inc_dialog->dialog->cancel_btn)->child), + _("Close")); + } + + g_free(fin_msg); + + return new_msgs; +} + +static IncState inc_pop3_session_do(IncSession *session) +{ + Pop3Session *pop3_session = POP3_SESSION(session->session); + IncProgressDialog *inc_dialog = (IncProgressDialog *)session->data; + gchar *server; + gushort port; + gchar *buf; + + debug_print(_("getting new messages of account %s...\n"), + pop3_session->ac_prefs->account_name); + + buf = g_strdup_printf(_("%s: Retrieving new messages"), + pop3_session->ac_prefs->recv_server); + gtk_window_set_title(GTK_WINDOW(inc_dialog->dialog->window), buf); + g_free(buf); + + server = pop3_session->ac_prefs->recv_server; +#if USE_SSL + port = pop3_session->ac_prefs->set_popport ? + pop3_session->ac_prefs->popport : + pop3_session->ac_prefs->ssl_pop == SSL_TUNNEL ? 995 : 110; + SESSION(pop3_session)->ssl_type = pop3_session->ac_prefs->ssl_pop; + if (pop3_session->ac_prefs->ssl_pop != SSL_NONE) + SESSION(pop3_session)->nonblocking = + pop3_session->ac_prefs->use_nonblocking_ssl; +#else + port = pop3_session->ac_prefs->set_popport ? + pop3_session->ac_prefs->popport : 110; +#endif + + buf = g_strdup_printf(_("Connecting to POP3 server: %s..."), server); + log_message("%s\n", buf); + progress_dialog_set_label(inc_dialog->dialog, buf); + g_free(buf); + + session_set_timeout(SESSION(pop3_session), + prefs_common.io_timeout_secs * 1000); + + if (session_connect(SESSION(pop3_session), server, port) < 0) { + log_warning(_("Can't connect to POP3 server: %s:%d\n"), + server, port); + session->inc_state = INC_CONNECT_ERROR; + statusbar_pop_all(); + return INC_CONNECT_ERROR; + } + + while (session_is_connected(SESSION(pop3_session)) && + session->inc_state != INC_CANCEL) + gtk_main_iteration(); + + if (session->inc_state == INC_SUCCESS) { + switch (pop3_session->error_val) { + case PS_SUCCESS: + switch (SESSION(pop3_session)->state) { + case SESSION_ERROR: + if (pop3_session->state == POP3_READY) + session->inc_state = INC_CONNECT_ERROR; + else + session->inc_state = INC_ERROR; + break; + case SESSION_EOF: + session->inc_state = INC_EOF; + break; + case SESSION_TIMEOUT: + session->inc_state = INC_TIMEOUT; + break; + default: + session->inc_state = INC_SUCCESS; + break; + } + break; + case PS_AUTHFAIL: + session->inc_state = INC_AUTH_FAILED; + break; + case PS_IOERR: + session->inc_state = INC_IO_ERROR; + break; + case PS_SOCKET: + session->inc_state = INC_SOCKET_ERROR; + break; + case PS_LOCKBUSY: + session->inc_state = INC_LOCKED; + break; + default: + session->inc_state = INC_ERROR; + break; + } + } + + session_disconnect(SESSION(pop3_session)); + statusbar_pop_all(); + + return session->inc_state; +} + +static void inc_progress_dialog_update(IncProgressDialog *inc_dialog, + IncSession *inc_session) +{ + inc_progress_dialog_set_label(inc_dialog, inc_session); + inc_progress_dialog_set_progress(inc_dialog, inc_session); +} + +static void inc_progress_dialog_set_label(IncProgressDialog *inc_dialog, + IncSession *inc_session) +{ + ProgressDialog *dialog = inc_dialog->dialog; + Pop3Session *session; + + g_return_if_fail(inc_session != NULL); + + session = POP3_SESSION(inc_session->session); + + switch (session->state) { + case POP3_GREETING: + break; + case POP3_GETAUTH_USER: + case POP3_GETAUTH_PASS: + case POP3_GETAUTH_APOP: + progress_dialog_set_label(dialog, _("Authenticating...")); + statusbar_print_all(_("Retrieving messages from %s..."), + SESSION(session)->server); + break; + case POP3_GETRANGE_STAT: + progress_dialog_set_label + (dialog, _("Getting the number of new messages (STAT)...")); + break; + case POP3_GETRANGE_LAST: + progress_dialog_set_label + (dialog, _("Getting the number of new messages (LAST)...")); + break; + case POP3_GETRANGE_UIDL: + progress_dialog_set_label + (dialog, _("Getting the number of new messages (UIDL)...")); + break; + case POP3_GETSIZE_LIST: + progress_dialog_set_label + (dialog, _("Getting the size of messages (LIST)...")); + break; + case POP3_RETR: + case POP3_RETR_RECV: + break; + case POP3_DELETE: +#if 0 + if (session->msg[session->cur_msg].recv_time < + session->current_time) { + gchar buf[MSGBUFSIZE]; + g_snprintf(buf, sizeof(buf), _("Deleting message %d"), + session->cur_msg); + progress_dialog_set_label(dialog, buf); + } +#endif + break; + case POP3_LOGOUT: + progress_dialog_set_label(dialog, _("Quitting")); + break; + default: + break; + } +} + +static void inc_progress_dialog_set_progress(IncProgressDialog *inc_dialog, + IncSession *inc_session) +{ + gchar buf[MSGBUFSIZE]; + Pop3Session *pop3_session = POP3_SESSION(inc_session->session); + gchar *total_size_str; + gint cur_total; + gint total; + + if (!pop3_session->new_msg_exist) return; + + cur_total = inc_session->cur_total_bytes; + total = pop3_session->total_bytes; + if (pop3_session->state == POP3_RETR || + pop3_session->state == POP3_RETR_RECV || + pop3_session->state == POP3_DELETE) { + Xstrdup_a(total_size_str, to_human_readable(total), return); + g_snprintf(buf, sizeof(buf), + _("Retrieving message (%d / %d) (%s / %s)"), + pop3_session->cur_msg, pop3_session->count, + to_human_readable(cur_total), total_size_str); + progress_dialog_set_label(inc_dialog->dialog, buf); + } + + progress_dialog_set_percentage + (inc_dialog->dialog,(gfloat)cur_total / (gfloat)total); + + gtk_progress_set_show_text + (GTK_PROGRESS(inc_dialog->mainwin->progressbar), TRUE); + g_snprintf(buf, sizeof(buf), "%d / %d", + pop3_session->cur_msg, pop3_session->count); + gtk_progress_set_format_string + (GTK_PROGRESS(inc_dialog->mainwin->progressbar), buf); + gtk_progress_bar_update + (GTK_PROGRESS_BAR(inc_dialog->mainwin->progressbar), + (gfloat)cur_total / (gfloat)total); + + if (pop3_session->cur_total_num > 0) { + g_snprintf(buf, sizeof(buf), + _("Retrieving (%d message(s) (%s) received)"), + pop3_session->cur_total_num, + to_human_readable + (pop3_session->cur_total_recv_bytes)); + gtk_clist_set_text(GTK_CLIST(inc_dialog->dialog->clist), + inc_dialog->cur_row, 2, buf); + } +} + +static gboolean hash_remove_func(gpointer key, gpointer value, gpointer data) +{ + return TRUE; +} + +static void inc_update_folderview(IncProgressDialog *inc_dialog, + IncSession *inc_session) +{ + if (g_hash_table_size(inc_session->tmp_folder_table) > 0) { + folderview_update_item_foreach(inc_session->tmp_folder_table, + FALSE); + g_hash_table_foreach_remove(inc_session->tmp_folder_table, + hash_remove_func, NULL); + } +} + +static void inc_progress_dialog_update_periodic(IncProgressDialog *inc_dialog, + IncSession *inc_session) +{ + struct timeval tv_cur; + struct timeval tv_result; + gint msec; + + gettimeofday(&tv_cur, NULL); + + tv_result.tv_sec = tv_cur.tv_sec - inc_dialog->progress_tv.tv_sec; + tv_result.tv_usec = tv_cur.tv_usec - inc_dialog->progress_tv.tv_usec; + if (tv_result.tv_usec < 0) { + tv_result.tv_sec--; + tv_result.tv_usec += 1000000; + } + + msec = tv_result.tv_sec * 1000 + tv_result.tv_usec / 1000; + if (msec > PROGRESS_UPDATE_INTERVAL) { + inc_progress_dialog_update(inc_dialog, inc_session); + inc_dialog->progress_tv.tv_sec = tv_cur.tv_sec; + inc_dialog->progress_tv.tv_usec = tv_cur.tv_usec; + } +} + +static void inc_update_folderview_periodic(IncProgressDialog *inc_dialog, + IncSession *inc_session) +{ + struct timeval tv_cur; + struct timeval tv_result; + gint msec; + + gettimeofday(&tv_cur, NULL); + + tv_result.tv_sec = tv_cur.tv_sec - inc_dialog->folder_tv.tv_sec; + tv_result.tv_usec = tv_cur.tv_usec - inc_dialog->folder_tv.tv_usec; + if (tv_result.tv_usec < 0) { + tv_result.tv_sec--; + tv_result.tv_usec += 1000000; + } + + msec = tv_result.tv_sec * 1000 + tv_result.tv_usec / 1000; + if (msec > FOLDER_UPDATE_INTERVAL) { + inc_update_folderview(inc_dialog, inc_session); + inc_dialog->folder_tv.tv_sec = tv_cur.tv_sec; + inc_dialog->folder_tv.tv_usec = tv_cur.tv_usec; + } +} + +static gint inc_recv_data_progressive(Session *session, guint cur_len, + guint total_len, gpointer data) +{ + IncSession *inc_session = (IncSession *)data; + Pop3Session *pop3_session = POP3_SESSION(session); + IncProgressDialog *inc_dialog; + gint cur_total; + + g_return_val_if_fail(inc_session != NULL, -1); + + if (pop3_session->state != POP3_RETR && + pop3_session->state != POP3_RETR_RECV && + pop3_session->state != POP3_DELETE && + pop3_session->state != POP3_LOGOUT) return 0; + + if (!pop3_session->new_msg_exist) return 0; + + cur_total = pop3_session->cur_total_bytes + cur_len; + if (cur_total > pop3_session->total_bytes) + cur_total = pop3_session->total_bytes; + inc_session->cur_total_bytes = cur_total; + + inc_dialog = (IncProgressDialog *)inc_session->data; + inc_progress_dialog_update_periodic(inc_dialog, inc_session); + inc_update_folderview_periodic(inc_dialog, inc_session); + + return 0; +} + +static gint inc_recv_data_finished(Session *session, guint len, gpointer data) +{ + IncSession *inc_session = (IncSession *)data; + IncProgressDialog *inc_dialog; + + g_return_val_if_fail(inc_session != NULL, -1); + + inc_dialog = (IncProgressDialog *)inc_session->data; + + inc_recv_data_progressive(session, 0, 0, inc_session); + + if (POP3_SESSION(session)->state == POP3_LOGOUT) { + inc_progress_dialog_update(inc_dialog, inc_session); + inc_update_folderview(inc_dialog, inc_session); + } + + return 0; +} + +static gint inc_recv_message(Session *session, const gchar *msg, gpointer data) +{ + IncSession *inc_session = (IncSession *)data; + IncProgressDialog *inc_dialog; + + g_return_val_if_fail(inc_session != NULL, -1); + + inc_dialog = (IncProgressDialog *)inc_session->data; + + switch (POP3_SESSION(session)->state) { + case POP3_GETAUTH_USER: + case POP3_GETAUTH_PASS: + case POP3_GETAUTH_APOP: + case POP3_GETRANGE_STAT: + case POP3_GETRANGE_LAST: + case POP3_GETRANGE_UIDL: + case POP3_GETSIZE_LIST: + inc_progress_dialog_update(inc_dialog, inc_session); + break; + case POP3_RETR: + inc_recv_data_progressive(session, 0, 0, inc_session); + break; + case POP3_LOGOUT: + inc_progress_dialog_update(inc_dialog, inc_session); + inc_update_folderview(inc_dialog, inc_session); + break; + default: + break; + } + + return 0; +} + +static gint inc_drop_message(Pop3Session *session, const gchar *file) +{ + FolderItem *inbox; + GSList *cur; + FilterInfo *fltinfo; + IncSession *inc_session = (IncSession *)(SESSION(session)->data); + gint val; + + g_return_val_if_fail(inc_session != NULL, DROP_ERROR); + + if (session->ac_prefs->inbox) { + inbox = folder_find_item_from_identifier + (session->ac_prefs->inbox); + if (!inbox) + inbox = folder_get_default_inbox(); + } else + inbox = folder_get_default_inbox(); + if (!inbox) + return DROP_ERROR; + + fltinfo = filter_info_new(); + fltinfo->account = session->ac_prefs; + fltinfo->flags.perm_flags = MSG_NEW|MSG_UNREAD; + fltinfo->flags.tmp_flags = MSG_RECEIVED; + + if (session->ac_prefs->filter_on_recv) + filter_apply(prefs_common.fltlist, file, fltinfo); + + if (!fltinfo->drop_done) { + if (folder_item_add_msg + (inbox, file, &fltinfo->flags, FALSE) < 0) { + filter_info_free(fltinfo); + return DROP_ERROR; + } + fltinfo->dest_list = g_slist_append(fltinfo->dest_list, inbox); + } + + for (cur = fltinfo->dest_list; cur != NULL; cur = cur->next) { + FolderItem *drop_folder = (FolderItem *)cur->data; + + val = GPOINTER_TO_INT(g_hash_table_lookup + (inc_session->folder_table, drop_folder)); + if (val == 0) { + /* force updating */ + if (FOLDER_IS_LOCAL(drop_folder->folder)) + drop_folder->mtime = 0; + g_hash_table_insert(inc_session->folder_table, drop_folder, + GINT_TO_POINTER(1)); + } + g_hash_table_insert(inc_session->tmp_folder_table, drop_folder, + GINT_TO_POINTER(1)); + } + + if (fltinfo->actions[FLT_ACTION_NOT_RECEIVE] == TRUE) + val = DROP_DONT_RECEIVE; + else if (fltinfo->actions[FLT_ACTION_DELETE] == TRUE) + val = DROP_DELETE; + else + val = DROP_OK; + + filter_info_free(fltinfo); + + return val; +} + +static void inc_put_error(IncState istate, const gchar *msg) +{ + gchar *log_msg = NULL; + gchar *err_msg = NULL; + gboolean fatal_error = FALSE; + + switch (istate) { + case INC_CONNECT_ERROR: + log_msg = _("Connection failed."); + if (prefs_common.no_recv_err_panel) + break; + err_msg = g_strdup(log_msg); + break; + case INC_ERROR: + log_msg = _("Error occurred while processing mail."); + if (prefs_common.no_recv_err_panel) + break; + if (msg) + err_msg = g_strdup_printf + (_("Error occurred while processing mail:\n%s"), + msg); + else + err_msg = g_strdup(log_msg); + break; + case INC_NO_SPACE: + log_msg = _("No disk space left."); + err_msg = g_strdup(log_msg); + fatal_error = TRUE; + break; + case INC_IO_ERROR: + log_msg = _("Can't write file."); + err_msg = g_strdup(log_msg); + fatal_error = TRUE; + break; + case INC_SOCKET_ERROR: + log_msg = _("Socket error."); + if (prefs_common.no_recv_err_panel) + break; + err_msg = g_strdup(log_msg); + break; + case INC_EOF: + log_msg = _("Connection closed by the remote host."); + if (prefs_common.no_recv_err_panel) + break; + err_msg = g_strdup(log_msg); + break; + case INC_LOCKED: + log_msg = _("Mailbox is locked."); + if (prefs_common.no_recv_err_panel) + break; + if (msg) + err_msg = g_strdup_printf(_("Mailbox is locked:\n%s"), + msg); + else + err_msg = g_strdup(log_msg); + break; + case INC_AUTH_FAILED: + log_msg = _("Authentication failed."); + if (prefs_common.no_recv_err_panel) + break; + if (msg) + err_msg = g_strdup_printf + (_("Authentication failed:\n%s"), msg); + else + err_msg = g_strdup(log_msg); + break; + case INC_TIMEOUT: + log_msg = _("Session timed out."); + if (prefs_common.no_recv_err_panel) + break; + err_msg = g_strdup(log_msg); + break; + default: + break; + } + + if (log_msg) { + if (fatal_error) + log_error("%s\n", log_msg); + else + log_warning("%s\n", log_msg); + } + if (err_msg) { + alertpanel_error(err_msg); + g_free(err_msg); + } +} + +static void inc_cancel(IncProgressDialog *dialog) +{ + IncSession *session; + + g_return_if_fail(dialog != NULL); + + if (dialog->queue_list == NULL) { + inc_progress_dialog_destroy(dialog); + return; + } + + session = dialog->queue_list->data; + + session->inc_state = INC_CANCEL; + + log_message(_("Incorporation cancelled\n")); +} + +gboolean inc_is_active(void) +{ + return (inc_dialog_list != NULL); +} + +void inc_cancel_all(void) +{ + GList *cur; + + for (cur = inc_dialog_list; cur != NULL; cur = cur->next) + inc_cancel((IncProgressDialog *)cur->data); +} + +static void inc_cancel_cb(GtkWidget *widget, gpointer data) +{ + inc_cancel((IncProgressDialog *)data); +} + +static gint inc_dialog_delete_cb(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + IncProgressDialog *dialog = (IncProgressDialog *)data; + + if (dialog->queue_list == NULL) + inc_progress_dialog_destroy(dialog); + + return TRUE; +} + +static gint inc_spool(void) +{ + gchar *spool_path; + gchar *mbox; + gint msgs; + + spool_path = prefs_common.spool_path + ? prefs_common.spool_path : DEFAULT_SPOOL_PATH; + if (is_file_exist(spool_path)) + mbox = g_strdup(spool_path); + else if (is_dir_exist(spool_path)) + mbox = g_strconcat(spool_path, G_DIR_SEPARATOR_S, + g_get_user_name(), NULL); + else { + debug_print("%s: local mailbox not found.\n", spool_path); + return -1; + } + + msgs = get_spool(folder_get_default_inbox(), mbox); + g_free(mbox); + + return msgs; +} + +static gint get_spool(FolderItem *dest, const gchar *mbox) +{ + gint msgs, size; + gint lockfd; + gchar tmp_mbox[MAXPATHLEN + 1]; + GHashTable *folder_table = NULL; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(mbox != NULL, -1); + + if (!is_file_exist(mbox) || (size = get_file_size(mbox)) == 0) { + debug_print("%s: no messages in local mailbox.\n", mbox); + return 0; + } else if (size < 0) + return -1; + + if ((lockfd = lock_mbox(mbox, LOCK_FLOCK)) < 0) + return -1; + + g_snprintf(tmp_mbox, sizeof(tmp_mbox), "%s%ctmpmbox.%p", + get_tmp_dir(), G_DIR_SEPARATOR, mbox); + + if (copy_mbox(mbox, tmp_mbox) < 0) { + unlock_mbox(mbox, lockfd, LOCK_FLOCK); + return -1; + } + + debug_print(_("Getting new messages from %s into %s...\n"), + mbox, dest->path); + + if (prefs_common.filter_on_inc) + folder_table = g_hash_table_new(NULL, NULL); + msgs = proc_mbox(dest, tmp_mbox, folder_table); + + unlink(tmp_mbox); + if (msgs >= 0) empty_mbox(mbox); + unlock_mbox(mbox, lockfd, LOCK_FLOCK); + + if (folder_table) { + if (!prefs_common.scan_all_after_inc) { + folder_item_scan_foreach(folder_table); + folderview_update_item_foreach + (folder_table, !prefs_common.open_inbox_on_inc); + } + g_hash_table_destroy(folder_table); + } else if (!prefs_common.scan_all_after_inc) { + folder_item_scan(dest); + folderview_update_item(dest, TRUE); + } + + return msgs; +} + +void inc_lock(void) +{ + inc_lock_count++; +} + +void inc_unlock(void) +{ + if (inc_lock_count > 0) + inc_lock_count--; +} + +static guint autocheck_timer = 0; +static gpointer autocheck_data = NULL; + +void inc_autocheck_timer_init(MainWindow *mainwin) +{ + autocheck_data = mainwin; + inc_autocheck_timer_set(); +} + +static void inc_autocheck_timer_set_interval(guint interval) +{ + inc_autocheck_timer_remove(); + + if (prefs_common.autochk_newmail && autocheck_data) { + autocheck_timer = gtk_timeout_add + (interval, inc_autocheck_func, autocheck_data); + debug_print("added timer = %d\n", autocheck_timer); + } +} + +void inc_autocheck_timer_set(void) +{ + inc_autocheck_timer_set_interval(prefs_common.autochk_itv * 60000); +} + +void inc_autocheck_timer_remove(void) +{ + if (autocheck_timer) { + debug_print("removed timer = %d\n", autocheck_timer); + gtk_timeout_remove(autocheck_timer); + autocheck_timer = 0; + } +} + +static gint inc_autocheck_func(gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + if (inc_lock_count) { + debug_print("autocheck is locked.\n"); + inc_autocheck_timer_set_interval(1000); + return FALSE; + } + + inc_all_account_mail(mainwin, TRUE); + + return FALSE; +} diff --git a/src/inc.h b/src/inc.h new file mode 100644 index 00000000..8c2b406c --- /dev/null +++ b/src/inc.h @@ -0,0 +1,103 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __INC_H__ +#define __INC_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "mainwindow.h" +#include "progressdialog.h" +#include "prefs_account.h" +#include "session.h" +#include "pop.h" + +typedef struct _IncProgressDialog IncProgressDialog; +typedef struct _IncSession IncSession; + +typedef enum +{ + INC_SUCCESS, + INC_CONNECT_ERROR, + INC_AUTH_FAILED, + INC_LOCKED, + INC_ERROR, + INC_NO_SPACE, + INC_IO_ERROR, + INC_SOCKET_ERROR, + INC_EOF, + INC_TIMEOUT, + INC_CANCEL +} IncState; + +struct _IncProgressDialog +{ + ProgressDialog *dialog; + + MainWindow *mainwin; + + gboolean show_dialog; + + struct timeval progress_tv; + struct timeval folder_tv; + + GList *queue_list; /* list of IncSession */ + gint cur_row; +}; + +struct _IncSession +{ + Session *session; + IncState inc_state; + + GHashTable *folder_table; /* table of destination folders */ + GHashTable *tmp_folder_table; /* for progressive update */ + + gint cur_total_bytes; + + gpointer data; +}; + +#define TIMEOUT_ITV 200 + +void inc_mail (MainWindow *mainwin); +gint inc_account_mail (MainWindow *mainwin, + PrefsAccount *account); +void inc_all_account_mail (MainWindow *mainwin, + gboolean autocheck); +void inc_progress_update (Pop3Session *session); + +gboolean inc_is_active (void); + +void inc_cancel_all (void); + +void inc_lock (void); +void inc_unlock (void); + +void inc_autocheck_timer_init (MainWindow *mainwin); +void inc_autocheck_timer_set (void); +void inc_autocheck_timer_remove (void); + +#endif /* __INC_H__ */ diff --git a/src/inputdialog.c b/src/inputdialog.c new file mode 100644 index 00000000..6e5473c4 --- /dev/null +++ b/src/inputdialog.c @@ -0,0 +1,332 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "inputdialog.h" +#include "manage_window.h" +#include "inc.h" +#include "gtkutils.h" +#include "utils.h" + +#define INPUT_DIALOG_WIDTH 420 + +typedef enum +{ + INPUT_DIALOG_NORMAL, + INPUT_DIALOG_INVISIBLE, + INPUT_DIALOG_COMBO +} InputDialogType; + +static gboolean ack; +static gboolean fin; + +static InputDialogType type; + +static GtkWidget *dialog; +static GtkWidget *msg_label; +static GtkWidget *entry; +static GtkWidget *combo; +static GtkWidget *ok_button; + +static void input_dialog_create (void); +static gchar *input_dialog_open (const gchar *title, + const gchar *message, + const gchar *default_string); +static void input_dialog_set (const gchar *title, + const gchar *message, + const gchar *default_string); + +static void ok_clicked (GtkWidget *widget, + gpointer data); +static void cancel_clicked (GtkWidget *widget, + gpointer data); +static gint delete_event (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void entry_activated (GtkEditable *editable); +static void combo_activated (GtkEditable *editable); + + +gchar *input_dialog(const gchar *title, const gchar *message, + const gchar *default_string) +{ + if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL; + + if (!dialog) + input_dialog_create(); + + type = INPUT_DIALOG_NORMAL; + gtk_widget_hide(combo); + gtk_widget_show(entry); + gtk_entry_set_visibility(GTK_ENTRY(entry), TRUE); + + return input_dialog_open(title, message, default_string); +} + +gchar *input_dialog_with_invisible(const gchar *title, const gchar *message, + const gchar *default_string) +{ + if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL; + + if (!dialog) + input_dialog_create(); + + type = INPUT_DIALOG_INVISIBLE; + gtk_widget_hide(combo); + gtk_widget_show(entry); + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + + return input_dialog_open(title, message, default_string); +} + +gchar *input_dialog_combo(const gchar *title, const gchar *message, + const gchar *default_string, GList *list, + gboolean case_sensitive) +{ + if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL; + + if (!dialog) + input_dialog_create(); + + type = INPUT_DIALOG_COMBO; + gtk_widget_hide(entry); + gtk_widget_show(combo); + + if (!list) { + GList empty_list; + + empty_list.data = (gpointer)""; + empty_list.next = NULL; + empty_list.prev = NULL; + gtk_combo_set_popdown_strings(GTK_COMBO(combo), &empty_list); + } else + gtk_combo_set_popdown_strings(GTK_COMBO(combo), list); + + gtk_combo_set_case_sensitive(GTK_COMBO(combo), case_sensitive); + + return input_dialog_open(title, message, default_string); +} + +gchar *input_dialog_query_password(const gchar *server, const gchar *user) +{ + gchar *message; + gchar *pass; + + message = g_strdup_printf(_("Input password for %s on %s:"), + user, server); + pass = input_dialog_with_invisible(_("Input password"), message, NULL); + g_free(message); + + return pass; +} + +static void input_dialog_create(void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *confirm_area; + GtkWidget *cancel_button; + + dialog = gtk_dialog_new(); + gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE); + gtk_widget_set_size_request(dialog, INPUT_DIALOG_WIDTH, -1); + gtk_container_set_border_width + (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + g_signal_connect(G_OBJECT(dialog), "delete_event", + G_CALLBACK(delete_event), NULL); + g_signal_connect(G_OBJECT(dialog), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(dialog); + + gtk_widget_realize(dialog); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + msg_label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox), msg_label, FALSE, FALSE, 0); + gtk_label_set_justify(GTK_LABEL(msg_label), GTK_JUSTIFY_LEFT); + + entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(entry), "activate", + G_CALLBACK(entry_activated), NULL); + + combo = gtk_combo_new(); + gtk_box_pack_start(GTK_BOX(vbox), combo, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "activate", + G_CALLBACK(combo_activated), NULL); + + gtkut_button_set_create(&confirm_area, + &ok_button, _("OK"), + &cancel_button, _("Cancel"), + NULL, NULL); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), + confirm_area); + gtk_widget_grab_default(ok_button); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(ok_clicked), NULL); + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(cancel_clicked), NULL); + + + gtk_widget_show_all(GTK_DIALOG(dialog)->vbox); +} + +static gchar *input_dialog_open(const gchar *title, const gchar *message, + const gchar *default_string) +{ + gchar *str; + + if (dialog && GTK_WIDGET_VISIBLE(dialog)) return NULL; + + if (!dialog) + input_dialog_create(); + + input_dialog_set(title, message, default_string); + gtk_widget_show(dialog); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + manage_window_set_transient(GTK_WINDOW(dialog)); + + ack = fin = FALSE; + + inc_lock(); + + while (fin == FALSE) + gtk_main_iteration(); + + manage_window_focus_out(dialog, NULL, NULL); + gtk_widget_hide(dialog); + + if (ack) { + GtkEditable *editable; + + if (type == INPUT_DIALOG_COMBO) + editable = GTK_EDITABLE(GTK_COMBO(combo)->entry); + else + editable = GTK_EDITABLE(entry); + + str = gtk_editable_get_chars(editable, 0, -1); + if (str && *str == '\0') { + g_free(str); + str = NULL; + } + } else + str = NULL; + + GTK_EVENTS_FLUSH(); + + inc_unlock(); + + debug_print("return string = %s\n", str ? str : "(none)"); + return str; +} + +static void input_dialog_set(const gchar *title, const gchar *message, + const gchar *default_string) +{ + GtkWidget *entry_; + + if (type == INPUT_DIALOG_COMBO) + entry_ = GTK_COMBO(combo)->entry; + else + entry_ = entry; + + gtk_window_set_title(GTK_WINDOW(dialog), title); + gtk_label_set_text(GTK_LABEL(msg_label), message); + if (default_string && *default_string) { + gtk_entry_set_text(GTK_ENTRY(entry_), default_string); + gtk_entry_set_position(GTK_ENTRY(entry_), 0); + gtk_entry_select_region(GTK_ENTRY(entry_), 0, -1); + } else + gtk_entry_set_text(GTK_ENTRY(entry_), ""); + + gtk_widget_grab_focus(ok_button); + gtk_widget_grab_focus(entry_); +} + +static void ok_clicked(GtkWidget *widget, gpointer data) +{ + ack = TRUE; + fin = TRUE; +} + +static void cancel_clicked(GtkWidget *widget, gpointer data) +{ + ack = FALSE; + fin = TRUE; +} + +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + ack = FALSE; + fin = TRUE; + + return TRUE; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) { + ack = FALSE; + fin = TRUE; + } + + return FALSE; +} + +static void entry_activated(GtkEditable *editable) +{ + ack = TRUE; + fin = TRUE; +} + +static void combo_activated(GtkEditable *editable) +{ + ack = TRUE; + fin = TRUE; +} diff --git a/src/inputdialog.h b/src/inputdialog.h new file mode 100644 index 00000000..0e9175a8 --- /dev/null +++ b/src/inputdialog.h @@ -0,0 +1,39 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __INPUTDIALOG_H__ +#define __INPUTDIALOG_H__ + +#include + +gchar *input_dialog (const gchar *title, + const gchar *message, + const gchar *default_string); +gchar *input_dialog_with_invisible (const gchar *title, + const gchar *message, + const gchar *default_string); +gchar *input_dialog_combo (const gchar *title, + const gchar *message, + const gchar *default_string, + GList *list, + gboolean case_sensitive); +gchar *input_dialog_query_password (const gchar *server, + const gchar *user); + +#endif /* __INPUTDIALOG_H__ */ diff --git a/src/intl.h b/src/intl.h new file mode 100644 index 00000000..3cb08bf7 --- /dev/null +++ b/src/intl.h @@ -0,0 +1,22 @@ +#ifndef __INTL_H__ +#define __INTL_H__ + +#ifdef ENABLE_NLS +# include +# define _(String) gettext(String) +# ifdef gettext_noop +# define N_(String) gettext_noop(String) +# else +# define N_(String) (String) +# endif /* gettext_noop */ +#else +# define _(String) (String) +# define N_(String) (String) +# define textdomain(String) (String) +# define gettext(String) (String) +# define dgettext(Domain,String) (String) +# define dcgettext(Domain,String,Type) (String) +# define bindtextdomain(Domain,Directory) (Domain) +#endif /* ENABLE_NLS */ + +#endif /* __INTL_H__ */ diff --git a/src/jpilot.c b/src/jpilot.c new file mode 100644 index 00000000..99f41c0d --- /dev/null +++ b/src/jpilot.c @@ -0,0 +1,1732 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Functions necessary to access JPilot database files. + * JPilot is Copyright(c) by Judd Montgomery. + * Visit http://www.jpilot.org for more details. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_JPILOT + +#include +#include +#include +#include +#include +#include +/* #include */ +#include + +#ifdef HAVE_LIBPISOCK_PI_ARGS_H +# include +# include +# include +#else +# include +# include +# include +#endif + +#include "mgutils.h" +#include "addritem.h" +#include "addrcache.h" +#include "jpilot.h" +#include "codeconv.h" + +#define JPILOT_DBHOME_DIR ".jpilot" +#define JPILOT_DBHOME_FILE "AddressDB.pdb" +#define PILOT_LINK_LIB_NAME "libpisock.so" + +#define IND_LABEL_LASTNAME 0 /* Index of last name in address data */ +#define IND_LABEL_FIRSTNAME 1 /* Index of first name in address data */ +#define IND_PHONE_EMAIL 4 /* Index of E-Mail address in phone labels */ +#define OFFSET_PHONE_LABEL 3 /* Offset to phone data in address data */ +#define IND_CUSTOM_LABEL 14 /* Offset to custom label names */ +#define NUM_CUSTOM_LABEL 4 /* Number of custom labels */ + +/* Shamelessly copied from JPilot (libplugin.h) */ +typedef struct { + unsigned char db_name[32]; + unsigned char flags[2]; + unsigned char version[2]; + unsigned char creation_time[4]; + unsigned char modification_time[4]; + unsigned char backup_time[4]; + unsigned char modification_number[4]; + unsigned char app_info_offset[4]; + unsigned char sort_info_offset[4]; + unsigned char type[4];/*Database ID */ + unsigned char creator_id[4];/*Application ID */ + unsigned char unique_id_seed[4]; + unsigned char next_record_list_id[4]; + unsigned char number_of_records[2]; +} RawDBHeader; + +/* Shamelessly copied from JPilot (libplugin.h) */ +typedef struct { + char db_name[32]; + unsigned int flags; + unsigned int version; + time_t creation_time; + time_t modification_time; + time_t backup_time; + unsigned int modification_number; + unsigned int app_info_offset; + unsigned int sort_info_offset; + char type[5];/*Database ID */ + char creator_id[5];/*Application ID */ + char unique_id_seed[5]; + unsigned int next_record_list_id; + unsigned int number_of_records; +} DBHeader; + +/* Shamelessly copied from JPilot (libplugin.h) */ +typedef struct { + unsigned char Offset[4]; /*4 bytes offset from BOF to record */ + unsigned char attrib; + unsigned char unique_ID[3]; +} record_header; + +/* Shamelessly copied from JPilot (libplugin.h) */ +typedef struct mem_rec_header_s { + unsigned int rec_num; + unsigned int offset; + unsigned int unique_id; + unsigned char attrib; + struct mem_rec_header_s *next; +} mem_rec_header; + +/* Shamelessly copied from JPilot (libplugin.h) */ +#define SPENT_PC_RECORD_BIT 256 + +typedef enum { + PALM_REC = 100L, + MODIFIED_PALM_REC = 101L, + DELETED_PALM_REC = 102L, + NEW_PC_REC = 103L, + DELETED_PC_REC = SPENT_PC_RECORD_BIT + 104L, + DELETED_DELETED_PALM_REC = SPENT_PC_RECORD_BIT + 105L +} PCRecType; + +/* Shamelessly copied from JPilot (libplugin.h) */ +typedef struct { + PCRecType rt; + unsigned int unique_id; + unsigned char attrib; + void *buf; + int size; +} buf_rec; + +/* Shamelessly copied from JPilot (libplugin.h) */ +typedef struct { + unsigned long header_len; + unsigned long header_version; + unsigned long rec_len; + unsigned long unique_id; + unsigned long rt; /* Record Type */ + unsigned char attrib; +} PC3RecordHeader; + +enum { + FAMILY_LAST = 0, + FAMILY_FIRST = 1 +} name_order; + +gboolean convert_charcode; + +/* +* Create new pilot file object. +*/ +JPilotFile *jpilot_create() { + JPilotFile *pilotFile; + pilotFile = g_new0( JPilotFile, 1 ); + pilotFile->name = NULL; + pilotFile->file = NULL; + pilotFile->path = NULL; + pilotFile->addressCache = addrcache_create(); + pilotFile->readMetadata = FALSE; + pilotFile->customLabels = NULL; + pilotFile->labelInd = NULL; + pilotFile->retVal = MGU_SUCCESS; + pilotFile->accessFlag = FALSE; + pilotFile->havePC3 = FALSE; + pilotFile->pc3ModifyTime = 0; + return pilotFile; +} + +/* +* Create new pilot file object for specified file. +*/ +JPilotFile *jpilot_create_path( const gchar *path ) { + JPilotFile *pilotFile; + pilotFile = jpilot_create(); + jpilot_set_file( pilotFile, path ); + return pilotFile; +} + +/* +* Properties... +*/ +void jpilot_set_name( JPilotFile* pilotFile, const gchar *value ) { + g_return_if_fail( pilotFile != NULL ); + pilotFile->name = mgu_replace_string( pilotFile->name, value ); +} +void jpilot_set_file( JPilotFile* pilotFile, const gchar *value ) { + g_return_if_fail( pilotFile != NULL ); + addrcache_refresh( pilotFile->addressCache ); + pilotFile->readMetadata = FALSE; + pilotFile->path = mgu_replace_string( pilotFile->path, value ); +} +void jpilot_set_accessed( JPilotFile *pilotFile, const gboolean value ) { + g_return_if_fail( pilotFile != NULL ); + pilotFile->accessFlag = value; +} + +gint jpilot_get_status( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, -1 ); + return pilotFile->retVal; +} +ItemFolder *jpilot_get_root_folder( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, NULL ); + return addrcache_get_root_folder( pilotFile->addressCache ); +} +gchar *jpilot_get_name( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, NULL ); + return pilotFile->name; +} + +/* +* Test whether file was read. +* Return: TRUE if file was read. +*/ +gboolean jpilot_get_read_flag( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, FALSE ); + return pilotFile->addressCache->dataRead; +} + +/* +* Free up custom label list. +*/ +void jpilot_clear_custom_labels( JPilotFile *pilotFile ) { + GList *node; + + g_return_if_fail( pilotFile != NULL ); + + /* Release custom labels */ + mgu_free_dlist( pilotFile->customLabels ); + pilotFile->customLabels = NULL; + + /* Release indexes */ + node = pilotFile->labelInd; + while( node ) { + node->data = NULL; + node = g_list_next( node ); + } + g_list_free( pilotFile->labelInd ); + pilotFile->labelInd = NULL; + + /* Force a fresh read */ + addrcache_refresh( pilotFile->addressCache ); +} + +/* +* Append a custom label, representing an E-Mail address field to the +* custom label list. +*/ +void jpilot_add_custom_label( JPilotFile *pilotFile, const gchar *labelName ) { + g_return_if_fail( pilotFile != NULL ); + + if( labelName ) { + gchar *labelCopy = g_strdup( labelName ); + g_strstrip( labelCopy ); + if( *labelCopy == '\0' ) { + g_free( labelCopy ); + } + else { + pilotFile->customLabels = g_list_append( pilotFile->customLabels, labelCopy ); + /* Force a fresh read */ + addrcache_refresh( pilotFile->addressCache ); + } + } +} + +/* +* Get list of custom labels. +* Return: List of labels. Must use g_free() when done. +*/ +GList *jpilot_get_custom_labels( JPilotFile *pilotFile ) { + GList *retVal = NULL; + GList *node; + + g_return_val_if_fail( pilotFile != NULL, NULL ); + + node = pilotFile->customLabels; + while( node ) { + retVal = g_list_append( retVal, g_strdup( node->data ) ); + node = g_list_next( node ); + } + return retVal; +} + +/* +* Return filespec of PC3 file corresponding to JPilot PDB file. +* Note: Filespec should be g_free() when done. +*/ +static gchar *jpilot_get_pc3_file( JPilotFile *pilotFile ) { + gchar *fileSpec, *r; + gint i, len, pos; + + if( pilotFile == NULL ) return NULL; + if( pilotFile->path == NULL ) return NULL; + + fileSpec = g_strdup( pilotFile->path ); + len = strlen( fileSpec ); + pos = -1; + r = NULL; + for( i = len; i > 0; i-- ) { + if( *(fileSpec + i) == '.' ) { + pos = i + 1; + r = fileSpec + pos; + break; + } + } + if( r ) { + if( len - pos == 3 ) { + *r++ = 'p'; *r++ = 'c'; *r = '3'; + return fileSpec; + } + } + g_free( fileSpec ); + return NULL; +} + +/* +* Save PC3 file time to cache. +* return: TRUE if time marked. +*/ +static gboolean jpilot_mark_files( JPilotFile *pilotFile ) { + gboolean retVal = FALSE; + struct stat filestat; + gchar *pcFile; + + /* Mark PDB file cache */ + retVal = addrcache_mark_file( pilotFile->addressCache, pilotFile->path ); + + /* Now mark PC3 file */ + pilotFile->havePC3 = FALSE; + pilotFile->pc3ModifyTime = 0; + pcFile = jpilot_get_pc3_file( pilotFile ); + if( pcFile == NULL ) return retVal; + if( 0 == lstat( pcFile, &filestat ) ) { + pilotFile->havePC3 = TRUE; + pilotFile->pc3ModifyTime = filestat.st_mtime; + retVal = TRUE; + } + g_free( pcFile ); + return retVal; +} + +/* +* Check whether JPilot PDB or PC3 file has changed by comparing +* with cached data. +* return: TRUE if file has changed. +*/ +static gboolean jpilot_check_files( JPilotFile *pilotFile ) { + gboolean retVal = TRUE; + struct stat filestat; + gchar *pcFile; + + /* Check main file */ + if( addrcache_check_file( pilotFile->addressCache, pilotFile->path ) ) + return TRUE; + + /* Test PC3 file */ + if( ! pilotFile->havePC3 ) return FALSE; + pcFile = jpilot_get_pc3_file( pilotFile ); + if( pcFile == NULL ) return FALSE; + + if( 0 == lstat( pcFile, &filestat ) ) { + if( filestat.st_mtime == pilotFile->pc3ModifyTime ) retVal = FALSE; + } + g_free( pcFile ); + return retVal; +} + +/* +* Test whether file was modified since last access. +* Return: TRUE if file was modified. +*/ +gboolean jpilot_get_modified( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, FALSE ); + return jpilot_check_files( pilotFile ); +} +gboolean jpilot_get_accessed( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, FALSE ); + return pilotFile->accessFlag; +} + +/* +* Free up pilot file object by releasing internal memory. +*/ +void jpilot_free( JPilotFile *pilotFile ) { + g_return_if_fail( pilotFile != NULL ); + + /* Free internal stuff */ + g_free( pilotFile->path ); + + /* Release custom labels */ + jpilot_clear_custom_labels( pilotFile ); + + /* Clear cache */ + addrcache_clear( pilotFile->addressCache ); + addrcache_free( pilotFile->addressCache ); + pilotFile->addressCache = NULL; + pilotFile->readMetadata = FALSE; + pilotFile->accessFlag = FALSE; + pilotFile->havePC3 = FALSE; + pilotFile->pc3ModifyTime = 0; + + /* Now release file object */ + g_free( pilotFile ); +} + +/* +* Refresh internal variables to force a file read. +*/ +void jpilot_force_refresh( JPilotFile *pilotFile ) { + addrcache_refresh( pilotFile->addressCache ); +} + +/* +* Print object to specified stream. +*/ +void jpilot_print_file( JPilotFile *pilotFile, FILE *stream ) { + GList *node; + + g_return_if_fail( pilotFile != NULL ); + + fprintf( stream, "JPilotFile:\n" ); + fprintf( stream, "file spec: '%s'\n", pilotFile->path ); + fprintf( stream, " metadata: %s\n", pilotFile->readMetadata ? "yes" : "no" ); + fprintf( stream, " ret val: %d\n", pilotFile->retVal ); + + node = pilotFile->customLabels; + while( node ) { + fprintf( stream, " c label: %s\n", (gchar *)node->data ); + node = g_list_next( node ); + } + + node = pilotFile->labelInd; + while( node ) { + fprintf( stream, " labelind: %d\n", GPOINTER_TO_INT(node->data) ); + node = g_list_next( node ); + } + + addrcache_print( pilotFile->addressCache, stream ); + fprintf( stream, " ret val: %d\n", pilotFile->retVal ); + fprintf( stream, " have pc3: %s\n", pilotFile->havePC3 ? "yes" : "no" ); + fprintf( stream, " pc3 time: %lu\n", pilotFile->pc3ModifyTime ); + addritem_print_item_folder( pilotFile->addressCache->rootFolder, stream ); +} + +/* +* Print summary of object to specified stream. +*/ +void jpilot_print_short( JPilotFile *pilotFile, FILE *stream ) { + GList *node; + g_return_if_fail( pilotFile != NULL ); + fprintf( stream, "JPilotFile:\n" ); + fprintf( stream, "file spec: '%s'\n", pilotFile->path ); + fprintf( stream, " metadata: %s\n", pilotFile->readMetadata ? "yes" : "no" ); + fprintf( stream, " ret val: %d\n", pilotFile->retVal ); + + node = pilotFile->customLabels; + while( node ) { + fprintf( stream, " c label: %s\n", (gchar *)node->data ); + node = g_list_next( node ); + } + + node = pilotFile->labelInd; + while( node ) { + fprintf( stream, " labelind: %d\n", GPOINTER_TO_INT(node->data) ); + node = g_list_next( node ); + } + addrcache_print( pilotFile->addressCache, stream ); + fprintf( stream, " have pc3: %s\n", pilotFile->havePC3 ? "yes" : "no" ); + fprintf( stream, " pc3 time: %lu\n", pilotFile->pc3ModifyTime ); +} + +/* Shamelessly copied from JPilot (libplugin.c) */ +static unsigned int bytes_to_bin(unsigned char *bytes, unsigned int num_bytes) { +unsigned int i, n; + n=0; + for (i=0;idb_name, rdbh->db_name, 31); + dbh->db_name[31] = '\0'; + dbh->flags = bytes_to_bin(rdbh->flags, 2); + dbh->version = bytes_to_bin(rdbh->version, 2); + temp = bytes_to_bin(rdbh->creation_time, 4); + dbh->creation_time = pilot_time_to_unix_time(temp); + temp = bytes_to_bin(rdbh->modification_time, 4); + dbh->modification_time = pilot_time_to_unix_time(temp); + temp = bytes_to_bin(rdbh->backup_time, 4); + dbh->backup_time = pilot_time_to_unix_time(temp); + dbh->modification_number = bytes_to_bin(rdbh->modification_number, 4); + dbh->app_info_offset = bytes_to_bin(rdbh->app_info_offset, 4); + dbh->sort_info_offset = bytes_to_bin(rdbh->sort_info_offset, 4); + strncpy(dbh->type, rdbh->type, 4); + dbh->type[4] = '\0'; + strncpy(dbh->creator_id, rdbh->creator_id, 4); + dbh->creator_id[4] = '\0'; + strncpy(dbh->unique_id_seed, rdbh->unique_id_seed, 4); + dbh->unique_id_seed[4] = '\0'; + dbh->next_record_list_id = bytes_to_bin(rdbh->next_record_list_id, 4); + dbh->number_of_records = bytes_to_bin(rdbh->number_of_records, 2); + return 0; +} + +/* Shamelessly copied from JPilot (libplugin.c) */ +/* returns 1 if found */ +/* 0 if eof */ +static int find_next_offset( mem_rec_header *mem_rh, long fpos, + unsigned int *next_offset, unsigned char *attrib, unsigned int *unique_id ) +{ + mem_rec_header *temp_mem_rh; + unsigned char found = 0; + unsigned long found_at; + + found_at=0xFFFFFF; + for (temp_mem_rh=mem_rh; temp_mem_rh; temp_mem_rh = temp_mem_rh->next) { + if ((temp_mem_rh->offset > fpos) && (temp_mem_rh->offset < found_at)) { + found_at = temp_mem_rh->offset; + /* *attrib = temp_mem_rh->attrib; */ + /* *unique_id = temp_mem_rh->unique_id; */ + } + if ((temp_mem_rh->offset == fpos)) { + found = 1; + *attrib = temp_mem_rh->attrib; + *unique_id = temp_mem_rh->unique_id; + } + } + *next_offset = found_at; + return found; +} + +/* Shamelessly copied from JPilot (libplugin.c) */ +static void free_mem_rec_header(mem_rec_header **mem_rh) { + mem_rec_header *h, *next_h; + for (h=*mem_rh; h; h=next_h) { + next_h=h->next; + free(h); + } + *mem_rh = NULL; +} + +#if 0 +/* Shamelessly copied from JPilot (libplugin.c) */ +static int jpilot_free_db_list( GList **br_list ) { + GList *temp_list, *first; + buf_rec *br; + + /* Go to first entry in the list */ + first=NULL; + for( temp_list = *br_list; temp_list; temp_list = temp_list->prev ) { + first = temp_list; + } + for (temp_list = first; temp_list; temp_list = temp_list->next) { + if (temp_list->data) { + br=temp_list->data; + if (br->buf) { + free(br->buf); + temp_list->data=NULL; + } + free(br); + } + } + g_list_free(*br_list); + *br_list=NULL; + return 0; +} +#endif + +/* Shamelessly copied from JPilot (libplugin.c) */ +/* Read file size */ +static int jpilot_get_info_size( FILE *in, int *size ) { + RawDBHeader rdbh; + DBHeader dbh; + unsigned int offset; + record_header rh; + + fseek(in, 0, SEEK_SET); + fread(&rdbh, sizeof(RawDBHeader), 1, in); + if (feof(in)) { + return MGU_EOF; + } + + raw_header_to_header(&rdbh, &dbh); + if (dbh.app_info_offset==0) { + *size=0; + return MGU_SUCCESS; + } + if (dbh.sort_info_offset!=0) { + *size = dbh.sort_info_offset - dbh.app_info_offset; + return MGU_SUCCESS; + } + if (dbh.number_of_records==0) { + fseek(in, 0, SEEK_END); + *size=ftell(in) - dbh.app_info_offset; + return MGU_SUCCESS; + } + + fread(&rh, sizeof(record_header), 1, in); + offset = ((rh.Offset[0]*256+rh.Offset[1])*256+rh.Offset[2])*256+rh.Offset[3]; + *size=offset - dbh.app_info_offset; + + return MGU_SUCCESS; +} + +/* + * Read address file into address list. Based on JPilot's + * libplugin.c (jp_get_app_info) + */ +static gint jpilot_get_file_info( JPilotFile *pilotFile, unsigned char **buf, int *buf_size ) { + FILE *in; + int num; + unsigned int rec_size; + RawDBHeader rdbh; + DBHeader dbh; + + if( ( !buf_size ) || ( ! buf ) ) { + return MGU_BAD_ARGS; + } + + *buf = NULL; + *buf_size=0; + + if( pilotFile->path ) { + in = fopen( pilotFile->path, "rb" ); + if( !in ) { + return MGU_OPEN_FILE; + } + } + else { + return MGU_NO_FILE; + } + + num = fread( &rdbh, sizeof( RawDBHeader ), 1, in ); + if( num != 1 ) { + if( ferror(in) ) { + fclose(in); + return MGU_ERROR_READ; + } + } + if (feof(in)) { + fclose(in); + return MGU_EOF; + } + + /* Convert header into something recognizable */ + raw_header_to_header(&rdbh, &dbh); + + num = jpilot_get_info_size(in, &rec_size); + if (num) { + fclose(in); + return MGU_ERROR_READ; + } + + fseek(in, dbh.app_info_offset, SEEK_SET); + *buf = ( char * ) malloc(rec_size); + if (!(*buf)) { + fclose(in); + return MGU_OO_MEMORY; + } + num = fread(*buf, rec_size, 1, in); + if (num != 1) { + if (ferror(in)) { + fclose(in); + free(*buf); + return MGU_ERROR_READ; + } + } + fclose(in); + + *buf_size = rec_size; + + return MGU_SUCCESS; +} + +/* Shamelessly copied from JPilot (libplugin.c) */ +static int unpack_header(PC3RecordHeader *header, unsigned char *packed_header) { + unsigned char *p; + unsigned long l; + + p = packed_header; + + memcpy(&l, p, sizeof(l)); + header->header_len=ntohl(l); + p+=sizeof(l); + + memcpy(&l, p, sizeof(l)); + header->header_version=ntohl(l); + p+=sizeof(l); + + memcpy(&l, p, sizeof(l)); + header->rec_len=ntohl(l); + p+=sizeof(l); + + memcpy(&l, p, sizeof(l)); + header->unique_id=ntohl(l); + p+=sizeof(l); + + memcpy(&l, p, sizeof(l)); + header->rt=ntohl(l); + p+=sizeof(l); + + memcpy(&(header->attrib), p, sizeof(unsigned char)); + p+=sizeof(unsigned char); + + return 0; +} + +/* Shamelessly copied from JPilot (libplugin.c) */ +static int read_header(FILE *pc_in, PC3RecordHeader *header) { + unsigned long l, len; + unsigned char packed_header[256]; + int num; + + num = fread(&l, sizeof(l), 1, pc_in); + if (feof(pc_in)) { + return -1; + } + if (num!=1) { + return num; + } + memcpy(packed_header, &l, sizeof(l)); + len=ntohl(l); + if (len > 255) { + return -1; + } + num = fread(packed_header+sizeof(l), len-sizeof(l), 1, pc_in); + if (feof(pc_in)) { + return -1; + } + if (num!=1) { + return num; + } + unpack_header(header, packed_header); + return 1; +} + +/* Read next record from PC3 file. Based on JPilot's + * pc_read_next_rec (libplugin.c) */ +static gint jpilot_read_next_pc( FILE *in, buf_rec *br ) { + PC3RecordHeader header; + int rec_len, num; + char *record; + + if(feof(in)) { + return MGU_EOF; + } + num = read_header(in, &header); + if (num < 1) { + if (ferror(in)) { + return MGU_ERROR_READ; + } + if (feof(in)) { + return MGU_EOF; + } + } + rec_len = header.rec_len; + record = malloc(rec_len); + if (!record) { + return MGU_OO_MEMORY; + } + num = fread(record, rec_len, 1, in); + if (num != 1) { + if (ferror(in)) { + free(record); + return MGU_ERROR_READ; + } + } + br->rt = header.rt; + br->unique_id = header.unique_id; + br->attrib = header.attrib; + br->buf = record; + br->size = rec_len; + + return MGU_SUCCESS; +} + +/* + * Read address file into a linked list. Based on JPilot's + * jp_read_DB_files (from libplugin.c) + */ +static gint jpilot_read_db_files( JPilotFile *pilotFile, GList **records ) { + FILE *in, *pc_in; + char *buf; + GList *temp_list; + int num_records, recs_returned, i, num, r; + unsigned int offset, prev_offset, next_offset, rec_size; + int out_of_order; + long fpos; /*file position indicator */ + unsigned char attrib; + unsigned int unique_id; + mem_rec_header *mem_rh, *temp_mem_rh, *last_mem_rh; + record_header rh; + RawDBHeader rdbh; + DBHeader dbh; + buf_rec *temp_br; + gchar *pcFile; + + mem_rh = last_mem_rh = NULL; + *records = NULL; + recs_returned = 0; + + if( pilotFile->path == NULL ) { + return MGU_BAD_ARGS; + } + + in = fopen( pilotFile->path, "rb" ); + if (!in) { + return MGU_OPEN_FILE; + } + + /* Read the database header */ + num = fread(&rdbh, sizeof(RawDBHeader), 1, in); + if (num != 1) { + if (ferror(in)) { + fclose(in); + return MGU_ERROR_READ; + } + if (feof(in)) { + return MGU_EOF; + } + } + raw_header_to_header(&rdbh, &dbh); + + /* Read each record entry header */ + num_records = dbh.number_of_records; + out_of_order = 0; + prev_offset = 0; + + for (i = 1; i < num_records + 1; i++) { + num = fread(&rh, sizeof(record_header), 1, in); + if (num != 1) { + if (ferror(in)) { + break; + } + if (feof(in)) { + return MGU_EOF; + } + } + + offset = ((rh.Offset[0]*256+rh.Offset[1])*256+rh.Offset[2])*256+rh.Offset[3]; + if (offset < prev_offset) { + out_of_order = 1; + } + prev_offset = offset; + temp_mem_rh = (mem_rec_header *)malloc(sizeof(mem_rec_header)); + if (!temp_mem_rh) { + break; + } + temp_mem_rh->next = NULL; + temp_mem_rh->rec_num = i; + temp_mem_rh->offset = offset; + temp_mem_rh->attrib = rh.attrib; + temp_mem_rh->unique_id = (rh.unique_ID[0]*256+rh.unique_ID[1])*256+rh.unique_ID[2]; + if (mem_rh == NULL) { + mem_rh = temp_mem_rh; + last_mem_rh = temp_mem_rh; + } else { + last_mem_rh->next = temp_mem_rh; + last_mem_rh = temp_mem_rh; + } + } + + temp_mem_rh = mem_rh; + + if (num_records) { + if (out_of_order) { + find_next_offset(mem_rh, 0, &next_offset, &attrib, &unique_id); + } else { + if (mem_rh) { + next_offset = mem_rh->offset; + attrib = mem_rh->attrib; + unique_id = mem_rh->unique_id; + } + } + fseek(in, next_offset, SEEK_SET); + while(!feof(in)) { + fpos = ftell(in); + if (out_of_order) { + find_next_offset(mem_rh, fpos, &next_offset, &attrib, &unique_id); + } else { + next_offset = 0xFFFFFF; + if (temp_mem_rh) { + attrib = temp_mem_rh->attrib; + unique_id = temp_mem_rh->unique_id; + if (temp_mem_rh->next) { + temp_mem_rh = temp_mem_rh->next; + next_offset = temp_mem_rh->offset; + } + } + } + rec_size = next_offset - fpos; + buf = malloc(rec_size); + if (!buf) break; + num = fread(buf, rec_size, 1, in); + if ((num != 1)) { + if (ferror(in)) { + free(buf); + break; + } + } + + temp_br = malloc(sizeof(buf_rec)); + if (!temp_br) { + break; + } + temp_br->rt = PALM_REC; + temp_br->unique_id = unique_id; + temp_br->attrib = attrib; + temp_br->buf = buf; + temp_br->size = rec_size; + + *records = g_list_append(*records, temp_br); + + recs_returned++; + } + } + fclose(in); + free_mem_rec_header(&mem_rh); + + /* Read the PC3 file, if present */ + pcFile = jpilot_get_pc3_file( pilotFile ); + if( pcFile == NULL ) return MGU_SUCCESS; + pc_in = fopen( pcFile, "rb"); + g_free( pcFile ); + + if( pc_in == NULL ) { + return MGU_SUCCESS; + } + + while( ! feof( pc_in ) ) { + temp_br = malloc(sizeof(buf_rec)); + if (!temp_br) { + break; + } + r = jpilot_read_next_pc( pc_in, temp_br ); + if ( r != MGU_SUCCESS ) { + free(temp_br); + break; + } + if ((temp_br->rt!=DELETED_PC_REC) + &&(temp_br->rt!=DELETED_PALM_REC) + &&(temp_br->rt!=MODIFIED_PALM_REC) + &&(temp_br->rt!=DELETED_DELETED_PALM_REC)) { + *records = g_list_append(*records, temp_br); + recs_returned++; + } + if ((temp_br->rt==DELETED_PALM_REC) || (temp_br->rt==MODIFIED_PALM_REC)) { + temp_list=*records; + if (*records) { + while(temp_list->next) { + temp_list=temp_list->next; + } + } + for (; temp_list; temp_list=temp_list->prev) { + if (((buf_rec *)temp_list->data)->unique_id == temp_br->unique_id) { + ((buf_rec *)temp_list->data)->rt = temp_br->rt; + } + } + } + } + fclose(pc_in); + + return MGU_SUCCESS; +} + +#define FULLNAME_BUFSIZE 256 +#define EMAIL_BUFSIZE 256 +/* + * Unpack address, building new data inside cache. + */ +static void jpilot_load_address( JPilotFile *pilotFile, buf_rec *buf, ItemFolder *folderInd[] ) { + struct Address addr; + gchar **addrEnt; + gint num, k; + gint cat_id = 0; + guint unique_id; + guchar attrib; + gchar fullName[ FULLNAME_BUFSIZE ]; + gchar bufEMail[ EMAIL_BUFSIZE ]; + ItemPerson *person; + ItemEMail *email; + gint *indPhoneLbl; + gchar *labelEntry; + GList *node; + gchar* extID; + struct AddressAppInfo *ai; + gchar **firstName = NULL; + gchar **lastName = NULL; + + /* Retrieve address */ + num = unpack_Address( & addr, buf->buf, buf->size ); + if( num > 0 ) { + addrEnt = addr.entry; + attrib = buf->attrib; + unique_id = buf->unique_id; + cat_id = attrib & 0x0F; + + *fullName = *bufEMail = '\0'; + + if( addrEnt[ IND_LABEL_FIRSTNAME ] ) { + firstName = g_strsplit( addrEnt[ IND_LABEL_FIRSTNAME ], "\01", 2 ); + } + if( addrEnt[ IND_LABEL_LASTNAME ] ) { + lastName = g_strsplit( addrEnt[ IND_LABEL_LASTNAME ], "\01", 2 ); + } + + if( name_order == FAMILY_LAST ) { + g_snprintf( fullName, FULLNAME_BUFSIZE, "%s %s", + firstName ? firstName[0] : "", + lastName ? lastName[0] : "" ); + } + else { + g_snprintf( fullName, FULLNAME_BUFSIZE, "%s %s", + lastName ? lastName[0] : "", + firstName ? firstName[0] : "" ); + } + + if( firstName ) { + g_strfreev( firstName ); + } + if( lastName ) { + g_strfreev( lastName ); + } + + g_strstrip( fullName ); + + if( convert_charcode ) { + gchar *nameConv; + nameConv = g_strdup( fullName ); + conv_sjistoeuc( fullName, FULLNAME_BUFSIZE, nameConv ); + g_free( nameConv ); + } + + person = addritem_create_item_person(); + addritem_person_set_common_name( person, fullName ); + addritem_person_set_first_name( person, addrEnt[ IND_LABEL_FIRSTNAME ] ); + addritem_person_set_last_name( person, addrEnt[ IND_LABEL_LASTNAME ] ); + addrcache_id_person( pilotFile->addressCache, person ); + + extID = g_strdup_printf( "%d", unique_id ); + addritem_person_set_external_id( person, extID ); + g_free( extID ); + extID = NULL; + + /* Pointer to address metadata. */ + ai = & pilotFile->addrInfo; + + /* Add entry for each email address listed under phone labels. */ + indPhoneLbl = addr.phoneLabel; + for( k = 0; k < JPILOT_NUM_ADDR_PHONE; k++ ) { + gint ind; + + ind = indPhoneLbl[k]; + /* + * fprintf( stdout, "%d : %d : %20s : %s\n", k, ind, + * ai->phoneLabels[ind], addrEnt[3+k] ); + */ + if( indPhoneLbl[k] == IND_PHONE_EMAIL ) { + labelEntry = addrEnt[ OFFSET_PHONE_LABEL + k ]; + if( labelEntry ) { + strcpy( bufEMail, labelEntry ); + g_strchug( bufEMail ); + g_strchomp( bufEMail ); + + email = addritem_create_item_email(); + addritem_email_set_address( email, bufEMail ); + addrcache_id_email( pilotFile->addressCache, email ); + addrcache_person_add_email + ( pilotFile->addressCache, person, email ); + } + } + } + + /* Add entry for each custom label */ + node = pilotFile->labelInd; + while( node ) { + gchar convertBuff[ JPILOT_LEN_LABEL ]; + gint ind; + + ind = GPOINTER_TO_INT( node->data ); + if( ind > -1 ) { + /* + * fprintf( stdout, "%d : %20s : %s\n", ind, ai->labels[ind], + * addrEnt[ind] ); + */ + labelEntry = addrEnt[ind]; + if( labelEntry ) { + strcpy( bufEMail, labelEntry ); + g_strchug( bufEMail ); + g_strchomp( bufEMail ); + + email = addritem_create_item_email(); + addritem_email_set_address( email, bufEMail ); + + if( convert_charcode ) { + conv_sjistoeuc( convertBuff, JPILOT_LEN_LABEL, ai->labels[ind] ); + addritem_email_set_remarks( email, convertBuff ); + } + else { + addritem_email_set_remarks( email, ai->labels[ind] ); + } + + addrcache_id_email( pilotFile->addressCache, email ); + addrcache_person_add_email + ( pilotFile->addressCache, person, email ); + } + } + + node = g_list_next( node ); + } + + if( person->listEMail ) { + if( cat_id > -1 && cat_id < JPILOT_NUM_CATEG ) { + /* Add to specified category */ + addrcache_folder_add_person + ( pilotFile->addressCache, folderInd[cat_id], person ); + } + else { + /* Add to root folder */ + addrcache_add_person( pilotFile->addressCache, person ); + } + } + else { + addritem_free_item_person( person ); + person = NULL; + } + } +} + +/* + * Free up address list. + */ +static void jpilot_free_addrlist( GList *records ) { + GList *node; + buf_rec *br; + + node = records; + while( node ) { + br = node->data; + free( br ); + node->data = NULL; + node = g_list_next( node ); + } + + /* Free up list */ + g_list_free( records ); +} + +/* + * Read address file into address cache. + */ +static gint jpilot_read_file( JPilotFile *pilotFile ) { + gint retVal, i; + GList *records = NULL; + GList *node; + buf_rec *br; + ItemFolder *folderInd[ JPILOT_NUM_CATEG ]; + + retVal = jpilot_read_db_files( pilotFile, &records ); + if( retVal != MGU_SUCCESS ) { + jpilot_free_addrlist( records ); + return retVal; + } + + /* Build array of pointers to categories */ + i = 0; + node = addrcache_get_list_folder( pilotFile->addressCache ); + while( node ) { + if( i < JPILOT_NUM_CATEG ) { + folderInd[i] = node->data; + } + node = g_list_next( node ); + i++; + } + + /* Load all addresses, free up old stuff as we go */ + node = records; + while( node ) { + br = node->data; + if( ( br->rt != DELETED_PC_REC ) && + ( br->rt != DELETED_PALM_REC ) && + ( br->rt != MODIFIED_PALM_REC ) && + ( br->rt != DELETED_DELETED_PALM_REC ) ) { + jpilot_load_address( pilotFile, br, folderInd ); + } + free( br ); + node->data = NULL; + node = g_list_next( node ); + } + + /* Free up list */ + g_list_free( records ); + + return retVal; +} + + +/* +* Read metadata from file. +*/ +static gint jpilot_read_metadata( JPilotFile *pilotFile ) { + gint retVal; + unsigned int rec_size; + unsigned char *buf; + int num; + + g_return_val_if_fail( pilotFile != NULL, -1 ); + + pilotFile->readMetadata = FALSE; + addrcache_clear( pilotFile->addressCache ); + + /* Read file info */ + retVal = jpilot_get_file_info( pilotFile, &buf, &rec_size); + if( retVal != MGU_SUCCESS ) { + pilotFile->retVal = retVal; + return pilotFile->retVal; + } + + num = unpack_AddressAppInfo( &pilotFile->addrInfo, buf, rec_size ); + if( buf ) { + free(buf); + } + if( num <= 0 ) { + pilotFile->retVal = MGU_ERROR_READ; + return pilotFile->retVal; + } + + pilotFile->readMetadata = TRUE; + pilotFile->retVal = MGU_SUCCESS; + return pilotFile->retVal; +} + +/* +* Setup labels and indexes from metadata. +* Return: TRUE is setup successfully. +*/ +static gboolean jpilot_setup_labels( JPilotFile *pilotFile ) { + gboolean retVal = FALSE; + struct AddressAppInfo *ai; + GList *node; + + g_return_val_if_fail( pilotFile != NULL, -1 ); + + /* Release indexes */ + node = pilotFile->labelInd; + while( node ) { + node->data = NULL; + node = g_list_next( node ); + } + pilotFile->labelInd = NULL; + + if( pilotFile->readMetadata ) { + ai = & pilotFile->addrInfo; + node = pilotFile->customLabels; + while( node ) { + gchar *lbl = node->data; + gint ind = -1; + gint i; + for( i = 0; i < JPILOT_NUM_LABELS; i++ ) { + gchar *labelName = ai->labels[i]; + gchar convertBuff[ JPILOT_LEN_LABEL ]; + + if( convert_charcode ) { + conv_sjistoeuc( convertBuff, JPILOT_LEN_LABEL, labelName ); + labelName = convertBuff; + } + + if( g_strcasecmp( labelName, lbl ) == 0 ) { + ind = i; + break; + } + } + pilotFile->labelInd = g_list_append( pilotFile->labelInd, GINT_TO_POINTER(ind) ); + node = g_list_next( node ); + } + retVal = TRUE; + } + return retVal; +} + +/* +* Load list with character strings of label names. +*/ +GList *jpilot_load_label( JPilotFile *pilotFile, GList *labelList ) { + int i; + + g_return_val_if_fail( pilotFile != NULL, NULL ); + + if( pilotFile->readMetadata ) { + struct AddressAppInfo *ai = & pilotFile->addrInfo; + for( i = 0; i < JPILOT_NUM_LABELS; i++ ) { + gchar *labelName = ai->labels[i]; + gchar convertBuff[JPILOT_LEN_LABEL]; + + if( labelName ) { + if( convert_charcode ) { + conv_sjistoeuc( convertBuff, JPILOT_LEN_LABEL, labelName ); + labelName = convertBuff; + } + labelList = g_list_append( labelList, g_strdup( labelName ) ); + } + else { + labelList = g_list_append( labelList, g_strdup( "" ) ); + } + } + } + return labelList; +} + +/* +* Return category name for specified category ID. +* Enter: Category ID. +* Return: Name, or empty string if not invalid ID. Name should be g_free() when done. +*/ +gchar *jpilot_get_category_name( JPilotFile *pilotFile, gint catID ) { + gchar *catName = NULL; + + g_return_val_if_fail( pilotFile != NULL, NULL ); + + if( pilotFile->readMetadata ) { + struct AddressAppInfo *ai = & pilotFile->addrInfo; + struct CategoryAppInfo *cat = & ai->category; + if( catID < 0 || catID > JPILOT_NUM_CATEG ) { + } + else { + catName = g_strdup( cat->name[catID] ); + } + } + if( ! catName ) catName = g_strdup( "" ); + return catName; +} + +/* +* Load list with character strings of phone label names. +*/ +GList *jpilot_load_phone_label( JPilotFile *pilotFile, GList *labelList ) { + gint i; + + g_return_val_if_fail( pilotFile != NULL, NULL ); + + if( pilotFile->readMetadata ) { + struct AddressAppInfo *ai = & pilotFile->addrInfo; + for( i = 0; i < JPILOT_NUM_PHONELABELS; i++ ) { + gchar *labelName = ai->phoneLabels[i]; + if( labelName ) { + labelList = g_list_append( labelList, g_strdup( labelName ) ); + } + else { + labelList = g_list_append( labelList, g_strdup( "" ) ); + } + } + } + return labelList; +} + +/* +* Load list with character strings of label names. Only none blank names +* are loaded. +*/ +GList *jpilot_load_custom_label( JPilotFile *pilotFile, GList *labelList ) { + gint i; + char convertBuff[JPILOT_LEN_LABEL]; + + g_return_val_if_fail( pilotFile != NULL, NULL ); + + if( pilotFile->readMetadata ) { + struct AddressAppInfo *ai = & pilotFile->addrInfo; + for( i = 0; i < NUM_CUSTOM_LABEL; i++ ) { + gchar *labelName = ai->labels[i+IND_CUSTOM_LABEL]; + if( labelName ) { + g_strchomp( labelName ); + g_strchug( labelName ); + if( *labelName != '\0' ) { + if( convert_charcode ) { + conv_sjistoeuc( convertBuff, JPILOT_LEN_LABEL, labelName ); + labelName = convertBuff; + } + labelList = g_list_append( labelList, g_strdup( labelName ) ); + } + } + } + } + return labelList; +} + +/* +* Load list with character strings of category names. +*/ +GList *jpilot_get_category_list( JPilotFile *pilotFile ) { + GList *catList = NULL; + gint i; + + g_return_val_if_fail( pilotFile != NULL, NULL ); + + if( pilotFile->readMetadata ) { + struct AddressAppInfo *ai = & pilotFile->addrInfo; + struct CategoryAppInfo *cat = & ai->category; + for( i = 0; i < JPILOT_NUM_CATEG; i++ ) { + gchar *catName = cat->name[i]; + if( catName ) { + catList = g_list_append( catList, g_strdup( catName ) ); + } + else { + catList = g_list_append( catList, g_strdup( "" ) ); + } + } + } + return catList; +} + +/* +* Build folder for each category. +*/ +static void jpilot_build_category_list( JPilotFile *pilotFile ) { + struct AddressAppInfo *ai = & pilotFile->addrInfo; + struct CategoryAppInfo *cat = & ai->category; + gint i; + + for( i = 0; i < JPILOT_NUM_CATEG; i++ ) { + ItemFolder *folder = addritem_create_item_folder(); + + if( convert_charcode ) { + gchar catName[ JPILOT_LEN_CATEG ]; + conv_sjistoeuc( catName, JPILOT_LEN_CATEG, cat->name[i] ); + addritem_folder_set_name( folder, catName ); + } + else { + addritem_folder_set_name( folder, cat->name[i] ); + } + + addrcache_id_folder( pilotFile->addressCache, folder ); + addrcache_add_folder( pilotFile->addressCache, folder ); + } +} + +/* +* Remove empty folders (categories). +*/ +static void jpilot_remove_empty( JPilotFile *pilotFile ) { + GList *listFolder; + GList *remList; + GList *node; + gint i = 0; + + listFolder = addrcache_get_list_folder( pilotFile->addressCache ); + node = listFolder; + remList = NULL; + while( node ) { + ItemFolder *folder = node->data; + if( ADDRITEM_NAME(folder) == NULL || *ADDRITEM_NAME(folder) == '\0' ) { + if( folder->listPerson ) { + /* Give name to folder */ + gchar name[20]; + sprintf( name, "? %d", i ); + addritem_folder_set_name( folder, name ); + } + else { + /* Mark for removal */ + remList = g_list_append( remList, folder ); + } + } + node = g_list_next( node ); + i++; + } + node = remList; + while( node ) { + ItemFolder *folder = node->data; + addrcache_remove_folder( pilotFile->addressCache, folder ); + node = g_list_next( node ); + } + g_list_free( remList ); +} + +/* +* ============================================================================================ +* Read file into list. Main entry point +* Return: TRUE if file read successfully. +* ============================================================================================ +*/ +gint jpilot_read_data( JPilotFile *pilotFile ) { + const gchar *cur_locale; + + name_order = FAMILY_LAST; + convert_charcode = FALSE; + + cur_locale = conv_get_current_locale(); + + if( g_strncasecmp( cur_locale, "ja", 2 ) == 0 ) { + name_order = FAMILY_FIRST; + } + + if( conv_get_locale_charset() == C_EUC_JP ) { + convert_charcode = TRUE; + } + + g_return_val_if_fail( pilotFile != NULL, -1 ); + + pilotFile->retVal = MGU_SUCCESS; + pilotFile->accessFlag = FALSE; + + if( jpilot_check_files( pilotFile ) ) { + addrcache_clear( pilotFile->addressCache ); + jpilot_read_metadata( pilotFile ); + if( pilotFile->retVal == MGU_SUCCESS ) { + jpilot_setup_labels( pilotFile ); + jpilot_build_category_list( pilotFile ); + pilotFile->retVal = jpilot_read_file( pilotFile ); + if( pilotFile->retVal == MGU_SUCCESS ) { + jpilot_remove_empty( pilotFile ); + jpilot_mark_files( pilotFile ); + pilotFile->addressCache->modified = FALSE; + pilotFile->addressCache->dataRead = TRUE; + } + } + } + return pilotFile->retVal; +} + +/* +* Return link list of persons. +*/ +GList *jpilot_get_list_person( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, NULL ); + return addrcache_get_list_person( pilotFile->addressCache ); +} + +/* +* Return link list of folders. This is always NULL since there are +* no folders in GnomeCard. +* Return: NULL. +*/ +GList *jpilot_get_list_folder( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, NULL ); + return addrcache_get_list_folder( pilotFile->addressCache ); +} + +/* +* Return link list of all persons. Note that the list contains references +* to items. Do *NOT* attempt to use the addrbook_free_xxx() functions... +* this will destroy the addressbook data! +* Return: List of items, or NULL if none. +*/ +GList *jpilot_get_all_persons( JPilotFile *pilotFile ) { + g_return_val_if_fail( pilotFile != NULL, NULL ); + return addrcache_get_all_persons( pilotFile->addressCache ); +} + +/* +* Check label list for specified label. +*/ +gint jpilot_check_label( struct AddressAppInfo *ai, gchar *lblCheck ) { + gint i; + gchar *lblName; + + if( lblCheck == NULL ) return -1; + if( strlen( lblCheck ) < 1 ) return -1; + for( i = 0; i < JPILOT_NUM_LABELS; i++ ) { + lblName = ai->labels[i]; + if( lblName ) { + if( strlen( lblName ) ) { + if( g_strcasecmp( lblName, lblCheck ) == 0 ) return i; + } + } + } + return -2; +} + +/* +* Validate that all parameters specified. +* Return: TRUE if data is good. +*/ +gboolean jpilot_validate( const JPilotFile *pilotFile ) { + gboolean retVal; + + g_return_val_if_fail( pilotFile != NULL, FALSE ); + + retVal = TRUE; + if( pilotFile->path ) { + if( strlen( pilotFile->path ) < 1 ) retVal = FALSE; + } + else { + retVal = FALSE; + } + if( pilotFile->name ) { + if( strlen( pilotFile->name ) < 1 ) retVal = FALSE; + } + else { + retVal = FALSE; + } + return retVal; +} + +#define WORK_BUFLEN 1024 + +/* +* Attempt to find a valid JPilot file. +* Return: Filename, or home directory if not found, or empty string if +* no home. Filename should be g_free() when done. +*/ +gchar *jpilot_find_pilotdb( void ) { + gchar *homedir; + gchar str[ WORK_BUFLEN ]; + gint len; + FILE *fp; + + homedir = g_get_home_dir(); + if( ! homedir ) return g_strdup( "" ); + + strcpy( str, homedir ); + len = strlen( str ); + if( len > 0 ) { + if( str[ len-1 ] != G_DIR_SEPARATOR ) { + str[ len ] = G_DIR_SEPARATOR; + str[ ++len ] = '\0'; + } + } + strcat( str, JPILOT_DBHOME_DIR ); + strcat( str, G_DIR_SEPARATOR_S ); + strcat( str, JPILOT_DBHOME_FILE ); + + /* Attempt to open */ + if( ( fp = fopen( str, "rb" ) ) != NULL ) { + fclose( fp ); + } + else { + /* Truncate filename */ + str[ len ] = '\0'; + } + return g_strdup( str ); +} + +/* +* Attempt to read file, testing for valid JPilot format. +* Return: TRUE if file appears to be valid format. +*/ +gint jpilot_test_read_file( const gchar *fileSpec ) { + JPilotFile *pilotFile; + gint retVal; + + if( fileSpec ) { + pilotFile = jpilot_create_path( fileSpec ); + retVal = jpilot_read_metadata( pilotFile ); + jpilot_free( pilotFile ); + pilotFile = NULL; + } + else { + retVal = MGU_NO_FILE; + } + return retVal; +} + +/* +* Check whether label is in custom labels. +* Return: TRUE if found. +*/ +gboolean jpilot_test_custom_label( JPilotFile *pilotFile, const gchar *labelName ) { + gboolean retVal; + GList *node; + + g_return_val_if_fail( pilotFile != NULL, FALSE ); + + retVal = FALSE; + if( labelName ) { + node = pilotFile->customLabels; + while( node ) { + if( g_strcasecmp( labelName, node->data ) == 0 ) { + retVal = TRUE; + break; + } + node = g_list_next( node ); + } + } + return retVal; +} + +/* +* Test whether pilot link library installed. +* Return: TRUE if library available. +*/ +#if 0 +gboolean jpilot_test_pilot_lib( void ) { + void *handle, *fun; + + handle = dlopen( PILOT_LINK_LIB_NAME, RTLD_LAZY ); + if( ! handle ) { + return FALSE; + } + + /* Test for symbols we need */ + fun = dlsym( handle, "unpack_Address" ); + if( ! fun ) { + dlclose( handle ); + return FALSE; + } + + fun = dlsym( handle, "unpack_AddressAppInfo" ); + if( ! fun ) { + dlclose( handle ); + return FALSE; + } + dlclose( handle ); + return TRUE; +} +#endif /* 0 */ + +#endif /* USE_JPILOT */ + +/* +* End of Source. +*/ diff --git a/src/jpilot.h b/src/jpilot.h new file mode 100644 index 00000000..b1ddada3 --- /dev/null +++ b/src/jpilot.h @@ -0,0 +1,116 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Definitions for accessing JPilot database files. + * JPilot is Copyright(c) by Judd Montgomery. + * Visit http://www.jpilot.org for more details. + */ + +#ifndef __JPILOT_H__ +#define __JPILOT_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_JPILOT + +#include +#include + +#ifdef HAVE_LIBPISOCK_PI_ADDRESS_H +# include +#else +# include +#endif + +#include "addritem.h" +#include "addrcache.h" + +typedef struct _JPilotFile JPilotFile; + +struct _JPilotFile { + gchar *name; + FILE *file; + gchar *path; + AddressCache *addressCache; + struct AddressAppInfo addrInfo; + gboolean readMetadata; + GList *customLabels; + GList *labelInd; + gint retVal; + gboolean accessFlag; + gboolean havePC3; + time_t pc3ModifyTime; +}; + +/* Limits */ +#define JPILOT_NUM_LABELS 22 /* Number of labels */ +#define JPILOT_NUM_PHONELABELS 8 /* Number of phone number labels */ +#define JPILOT_NUM_CATEG 16 /* Number of categories */ +#define JPILOT_LEN_LABEL 15 /* Max length of label */ +#define JPILOT_LEN_CATEG 15 /* Max length of category */ +#define JPILOT_NUM_ADDR_PHONE 5 /* Number of phone entries a person + can have */ + +/* Function prototypes */ +JPilotFile *jpilot_create ( void ); +JPilotFile *jpilot_create_path ( const gchar *path ); +void jpilot_set_name ( JPilotFile* pilotFile, const gchar *value ); +void jpilot_set_file ( JPilotFile* pilotFile, const gchar *value ); +void jpilot_free ( JPilotFile *pilotFile ); +gint jpilot_get_status ( JPilotFile *pilotFile ); +gboolean jpilot_get_modified ( JPilotFile *pilotFile ); +gboolean jpilot_get_accessed ( JPilotFile *pilotFile ); +void jpilot_set_accessed ( JPilotFile *pilotFile, const gboolean value ); +gboolean jpilot_get_read_flag ( JPilotFile *pilotFile ); +ItemFolder *jpilot_get_root_folder ( JPilotFile *pilotFile ); +gchar *jpilot_get_name ( JPilotFile *pilotFile ); + +void jpilot_force_refresh ( JPilotFile *pilotFile ); +void jpilot_print_file ( JPilotFile *jpilotFile, FILE *stream ); +void jpilot_print_short ( JPilotFile *pilotFile, FILE *stream ); +gint jpilot_read_data ( JPilotFile *pilotFile ); +GList *jpilot_get_list_person ( JPilotFile *pilotFile ); +GList *jpilot_get_list_folder ( JPilotFile *pilotFile ); +GList *jpilot_get_all_persons ( JPilotFile *pilotFile ); + +GList *jpilot_load_label ( JPilotFile *pilotFile, GList *labelList ); +GList *jpilot_get_category_list ( JPilotFile *pilotFile ); +gchar *jpilot_get_category_name ( JPilotFile *pilotFile, gint catID ); +GList *jpilot_load_phone_label ( JPilotFile *pilotFile, GList *labelList ); +GList *jpilot_load_custom_label ( JPilotFile *pilotFile, GList *labelList ); + +gboolean jpilot_validate ( const JPilotFile *pilotFile ); +gchar *jpilot_find_pilotdb ( void ); + +gint jpilot_test_read_file ( const gchar *fileSpec ); + +void jpilot_clear_custom_labels ( JPilotFile *pilotFile ); +void jpilot_add_custom_label ( JPilotFile *pilotFile, const gchar *labelName ); +GList *jpilot_get_custom_labels ( JPilotFile *pilotFile ); +gboolean jpilot_test_custom_label ( JPilotFile *pilotFile, const gchar *labelName ); +/* gboolean jpilot_test_pilot_lib ( void ); */ + +gint jpilot_read_modified ( JPilotFile *pilotFile ); + +#endif /* USE_JPILOT */ + +#endif /* __JPILOT_H__ */ diff --git a/src/ldif.c b/src/ldif.c new file mode 100644 index 00000000..cae15b10 --- /dev/null +++ b/src/ldif.c @@ -0,0 +1,933 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Functions necessary to access LDIF files (LDAP Data Interchange Format + * files). + */ + +#include +#include +#include + +#include "mgutils.h" +#include "ldif.h" +#include "addritem.h" +#include "addrcache.h" + +#include "base64.h" + +/* +* Create new object. +*/ +LdifFile *ldif_create() { + LdifFile *ldifFile; + ldifFile = g_new0( LdifFile, 1 ); + ldifFile->path = NULL; + ldifFile->file = NULL; + ldifFile->bufptr = ldifFile->buffer; + ldifFile->hashFields = g_hash_table_new( g_str_hash, g_str_equal ); + ldifFile->tempList = NULL; + ldifFile->dirtyFlag = TRUE; + ldifFile->accessFlag = FALSE; + ldifFile->retVal = MGU_SUCCESS; + ldifFile->cbProgress = NULL; + ldifFile->importCount = 0; + return ldifFile; +} + +/* +* Properties... +*/ +void ldif_set_file( LdifFile *ldifFile, const gchar *value ) { + g_return_if_fail( ldifFile != NULL ); + + if( ldifFile->path ) { + if( strcmp( ldifFile->path, value ) != 0 ) + ldifFile->dirtyFlag = TRUE; + } + else { + ldifFile->dirtyFlag = TRUE; + } + ldifFile->path = mgu_replace_string( ldifFile->path, value ); + g_strstrip( ldifFile->path ); + ldifFile->importCount = 0; +} +void ldif_set_accessed( LdifFile *ldifFile, const gboolean value ) { + g_return_if_fail( ldifFile != NULL ); + ldifFile->accessFlag = value; +} + +/* +* Register a callback function. When called, the function will be passed +* the following arguments: +* LdifFile object, +* File size (long), +* Current position (long) +* This can be used for a progress indicator. +*/ +void ldif_set_callback( LdifFile *ldifFile, void *func ) { + ldifFile->cbProgress = func; +} + +/* +* Create field record object. +*/ +static Ldif_FieldRec *ldif_create_fieldrec( gchar *field ) { + Ldif_FieldRec *rec = g_new0( Ldif_FieldRec, 1 ); + rec->tagName = g_strdup( field ); + rec->userName = NULL; + rec->reserved = FALSE; + rec->selected = FALSE; + return rec; +} + +/* +* Free field record object. +*/ +static void ldif_free_fieldrec( Ldif_FieldRec *rec ) { + if( rec ) { + g_free( rec->tagName ); + g_free( rec->userName ); + rec->tagName = NULL; + rec->userName = NULL; + rec->reserved = FALSE; + rec->selected = FALSE; + g_free( rec ); + } +} + +/* +* Free hash table entry visitor function. +*/ +static gint ldif_hash_free_vis( gpointer key, gpointer value, gpointer data ) { + ldif_free_fieldrec( ( Ldif_FieldRec * ) value ); + value = NULL; + key = NULL; + return -1; +} + +/* +* Free up object by releasing internal memory. +*/ +void ldif_free( LdifFile *ldifFile ) { + g_return_if_fail( ldifFile != NULL ); + + /* Close file */ + if( ldifFile->file ) fclose( ldifFile->file ); + + /* Free internal stuff */ + g_free( ldifFile->path ); + + /* Free field list */ + g_hash_table_foreach_remove( ldifFile->hashFields, ldif_hash_free_vis, NULL ); + g_hash_table_destroy( ldifFile->hashFields ); + ldifFile->hashFields = NULL; + + /* Clear pointers */ + ldifFile->file = NULL; + ldifFile->path = NULL; + ldifFile->retVal = MGU_SUCCESS; + ldifFile->tempList = NULL; + ldifFile->dirtyFlag = FALSE; + ldifFile->accessFlag = FALSE; + ldifFile->cbProgress = NULL; + + /* Now release file object */ + g_free( ldifFile ); +} + +/* +* Display field record. +*/ +void ldif_print_fieldrec( Ldif_FieldRec *rec, FILE *stream ) { + fprintf( stream, "\ttag:\t%s", rec->reserved ? "yes" : "no" ); + fprintf( stream, "\t%s", rec->selected ? "yes" : "no" ); + fprintf( stream, "\t:%s:\t:%s:\n", rec->userName, rec->tagName ); +} + +/* +* Display field record. + * +*/ +static void ldif_print_file_vis( gpointer key, gpointer value, gpointer data ) { + Ldif_FieldRec *rec = value; + FILE *stream = data; + ldif_print_fieldrec( rec, stream ); +} + +/* +* Display object to specified stream. +*/ +void ldif_print_file( LdifFile *ldifFile, FILE *stream ) { + g_return_if_fail( ldifFile != NULL ); + fprintf( stream, "LDIF File:\n" ); + fprintf( stream, "file spec: '%s'\n", ldifFile->path ); + fprintf( stream, " ret val: %d\n", ldifFile->retVal ); + fprintf( stream, " fields: {\n" ); + g_hash_table_foreach( ldifFile->hashFields, ldif_print_file_vis, stream ); + fprintf( stream, "} ---\n" ); +} + +/* +* Open file for read. +* return: TRUE if file opened successfully. +*/ +static gint ldif_open_file( LdifFile* ldifFile ) { + /* printf( "Opening file\n" ); */ + if( ldifFile->path ) { + ldifFile->file = fopen( ldifFile->path, "rb" ); + if( ! ldifFile->file ) { + /* printf( "can't open %s\n", ldifFile->path ); */ + ldifFile->retVal = MGU_OPEN_FILE; + return ldifFile->retVal; + } + } + else { + /* printf( "file not specified\n" ); */ + ldifFile->retVal = MGU_NO_FILE; + return ldifFile->retVal; + } + + /* Setup a buffer area */ + ldifFile->buffer[0] = '\0'; + ldifFile->bufptr = ldifFile->buffer; + ldifFile->retVal = MGU_SUCCESS; + return ldifFile->retVal; +} + +/* +* Close file. +*/ +static void ldif_close_file( LdifFile *ldifFile ) { + g_return_if_fail( ldifFile != NULL ); + if( ldifFile->file ) fclose( ldifFile->file ); + ldifFile->file = NULL; +} + +/* +* Read line of text from file. +* Return: ptr to buffer where line starts. +*/ +static gchar *ldif_get_line( LdifFile *ldifFile ) { + gchar buf[ LDIFBUFSIZE ]; + gint ch; + gchar *ptr; + + if( feof( ldifFile->file ) ) return NULL; + + ptr = buf; + while( TRUE ) { + *ptr = '\0'; + ch = fgetc( ldifFile->file ); + if( ch == '\0' || ch == EOF ) { + if( *buf == '\0' ) return NULL; + break; + } +#if HAVE_DOSISH_SYSTEM +#else + if( ch == '\r' ) continue; +#endif + if( ch == '\n' ) break; + *ptr = ch; + ptr++; + } + + /* Return a copy of buffer */ + return g_strdup( buf ); +} + +/* +* Parse tag name from line buffer. +* Enter: line Buffer. +* flag64 Base-64 encoder flag. +* Return: Buffer containing the tag name, or NULL if no delimiter char found. +* If a double delimiter (::) is found, flag64 is set. +*/ +static gchar *ldif_get_tagname( char* line, gboolean *flag64 ) { + gint len = 0; + gchar *tag = NULL; + gchar *lptr = line; + gchar *sptr = NULL; + + while( *lptr++ ) { + /* Check for language tag */ + if( *lptr == LDIF_LANG_TAG ) { + if( sptr == NULL ) sptr = lptr; + } + + /* Check for delimiter */ + if( *lptr == LDIF_SEP_TAG ) { + if( sptr ) { + len = sptr - line; + } + else { + len = lptr - line; + } + + /* Base-64 encoding? */ + if( * ++lptr == LDIF_SEP_TAG ) *flag64 = TRUE; + + tag = g_strndup( line, len+1 ); + tag[ len ] = '\0'; + g_strdown( tag ); + return tag; + } + } + return tag; +} + +/* +* Parse tag value from line buffer. +* Enter: line Buffer. +* Return: Buffer containing the tag value. Empty string is returned if +* no delimiter char found. +*/ +static gchar *ldif_get_tagvalue( gchar* line ) { + gchar *value = NULL; + gchar *start = NULL; + gchar *lptr; + gint len = 0; + + for( lptr = line; *lptr; lptr++ ) { + if( *lptr == LDIF_SEP_TAG ) { + if( ! start ) + start = lptr + 1; + } + } + if( start ) { + if( *start == LDIF_SEP_TAG ) start++; + len = lptr - start; + value = g_strndup( start, len+1 ); + g_strstrip( value ); + } + else { + /* Ensure that we get an empty string */ + value = g_strndup( "", 1 ); + } + value[ len ] = '\0'; + return value; +} + +#if 0 +/* +* Dump linked lists of character strings (for debug). +*/ +static void ldif_dump_lists( GSList *listName, GSList *listAddr, GSList *listRem, GSList *listID, FILE *stream ) { + fprintf( stream, "dump name\n" ); + fprintf( stream, "------------\n" ); + mgu_print_list( listName, stdout ); + fprintf( stream, "dump address\n" ); + fprintf( stream, "------------\n" ); + mgu_print_list( listAddr, stdout ); + fprintf( stream, "dump remarks\n" ); + fprintf( stdout, "------------\n" ); + mgu_print_list( listRem, stdout ); + fprintf( stream, "dump id\n" ); + fprintf( stdout, "------------\n" ); + mgu_print_list( listID, stdout ); +} +#endif + +/* +* Parsed address data. +*/ +typedef struct _Ldif_ParsedRec_ Ldif_ParsedRec; +struct _Ldif_ParsedRec_ { + GSList *listCName; + GSList *listFName; + GSList *listLName; + GSList *listNName; + GSList *listAddress; + GSList *listID; + GSList *userAttr; +}; + +/* +* User attribute data. +*/ +typedef struct _Ldif_UserAttr_ Ldif_UserAttr; +struct _Ldif_UserAttr_ { + gchar *name; + gchar *value; +}; + +/* +* Build an address list entry and append to list of address items. Name is formatted +* as " ". +*/ +static void ldif_build_items( LdifFile *ldifFile, Ldif_ParsedRec *rec, AddressCache *cache ) { + GSList *nodeFirst; + GSList *nodeAddress; + GSList *nodeAttr; + gchar *firstName = NULL, *lastName = NULL, *fullName = NULL, *nickName = NULL; + gint iLen = 0, iLenT = 0; + ItemPerson *person; + ItemEMail *email; + + nodeAddress = rec->listAddress; + if( nodeAddress == NULL ) return; + + /* Find longest first name in list */ + nodeFirst = rec->listFName; + while( nodeFirst ) { + if( firstName == NULL ) { + firstName = nodeFirst->data; + iLen = strlen( firstName ); + } + else { + if( ( iLenT = strlen( nodeFirst->data ) ) > iLen ) { + firstName = nodeFirst->data; + iLen = iLenT; + } + } + nodeFirst = g_slist_next( nodeFirst ); + } + + /* Format name */ + if( rec->listLName ) { + lastName = rec->listLName->data; + } + + if( firstName ) { + if( lastName ) { + fullName = g_strdup_printf( "%s %s", firstName, lastName ); + } + else { + fullName = g_strdup_printf( "%s", firstName ); + } + } + else { + if( lastName ) { + fullName = g_strdup_printf( "%s", lastName ); + } + } + if( fullName ) { + g_strchug( fullName ); g_strchomp( fullName ); + } + + if( rec->listNName ) { + nickName = rec->listNName->data; + } + + person = addritem_create_item_person(); + addritem_person_set_common_name( person, fullName ); + addritem_person_set_first_name( person, firstName ); + addritem_person_set_last_name( person, lastName ); + addritem_person_set_nick_name( person, nickName ); + addrcache_id_person( cache, person ); + addrcache_add_person( cache, person ); + ++ldifFile->importCount; + + /* Add address item */ + while( nodeAddress ) { + email = addritem_create_item_email(); + addritem_email_set_address( email, nodeAddress->data ); + addrcache_id_email( cache, email ); + addrcache_person_add_email( cache, person, email ); + nodeAddress = g_slist_next( nodeAddress ); + } + g_free( fullName ); + fullName = firstName = lastName = NULL; + + /* Add user attributes */ + nodeAttr = rec->userAttr; + while( nodeAttr ) { + Ldif_UserAttr *attr = nodeAttr->data; + UserAttribute *attrib = addritem_create_attribute(); + addritem_attrib_set_name( attrib, attr->name ); + addritem_attrib_set_value( attrib, attr->value ); + addritem_person_add_attribute( person, attrib ); + nodeAttr = g_slist_next( nodeAttr ); + } + nodeAttr = NULL; +} + +/* +* Add selected field as user attribute. +*/ +static void ldif_add_user_attr( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue, GHashTable *hashField ) { + Ldif_FieldRec *fld = NULL; + Ldif_UserAttr *attr = NULL; + gchar *name; + + fld = g_hash_table_lookup( hashField, tagName ); + if( fld ) { + if( fld->reserved ) return; + if( ! fld->selected ) return; + + name = fld->tagName; + if( fld->userName ) { + name = fld->userName; + } + attr = g_new0( Ldif_UserAttr, 1 ); + attr->name = g_strdup( name ); + attr->value = g_strdup( tagValue ); + rec->userAttr = g_slist_append( rec->userAttr, attr ); + } +} + +/* +* Add value to parsed data. +*/ +static void ldif_add_value( Ldif_ParsedRec *rec, gchar *tagName, gchar *tagValue, GHashTable *hashField ) { + gchar *nm, *val; + + nm = g_strdup( tagName ); + g_strdown( nm ); + if( tagValue ) { + val = g_strdup( tagValue ); + } + else { + val = g_strdup( "" ); + } + g_strstrip( val ); + if( g_strcasecmp( nm, LDIF_TAG_COMMONNAME ) == 0 ) { + rec->listCName = g_slist_append( rec->listCName, val ); + } + else if( g_strcasecmp( nm, LDIF_TAG_FIRSTNAME ) == 0 ) { + rec->listFName = g_slist_append( rec->listFName, val ); + } + else if( g_strcasecmp( nm, LDIF_TAG_LASTNAME ) == 0 ) { + rec->listLName = g_slist_append( rec->listLName, val ); + } + else if( g_strcasecmp( nm, LDIF_TAG_NICKNAME ) == 0 ) { + rec->listNName = g_slist_append( rec->listNName, val ); + } + else if( g_strcasecmp( nm, LDIF_TAG_EMAIL ) == 0 ) { + rec->listAddress = g_slist_append( rec->listAddress, val ); + } + else { + /* Add field as user attribute */ + ldif_add_user_attr( rec, tagName, tagValue, hashField ); + } + g_free( nm ); +} + +/* +* Clear parsed data. +*/ +static void ldif_clear_rec( Ldif_ParsedRec *rec ) { + GSList *list; + + /* Free up user attributes */ + list = rec->userAttr; + while( list ) { + Ldif_UserAttr *attr = list->data; + g_free( attr->name ); + g_free( attr->value ); + g_free( attr ); + list = g_slist_next( list ); + } + g_slist_free( rec->userAttr ); + + g_slist_free( rec->listCName ); + g_slist_free( rec->listFName ); + g_slist_free( rec->listLName ); + g_slist_free( rec->listNName ); + g_slist_free( rec->listAddress ); + g_slist_free( rec->listID ); + + rec->userAttr = NULL; + rec->listCName = NULL; + rec->listFName = NULL; + rec->listLName = NULL; + rec->listNName = NULL; + rec->listAddress = NULL; + rec->listID = NULL; +} + +#if 0 +/* +* Print parsed data. +*/ +static void ldif_print_record( Ldif_ParsedRec *rec, FILE *stream ) { + GSList *list; + + fprintf( stream, "LDIF Parsed Record:\n" ); + fprintf( stream, "common name:" ); + mgu_print_list( rec->listCName, stream ); + if( ! rec->listCName ) fprintf( stream, "\n" ); + fprintf( stream, "first name:" ); + mgu_print_list( rec->listFName, stream ); + if( ! rec->listFName ) fprintf( stream, "\n" ); + fprintf( stream, "last name:" ); + mgu_print_list( rec->listLName, stream ); + if( ! rec->listLName ) fprintf( stream, "\n" ); + fprintf( stream, "nick name:" ); + mgu_print_list( rec->listNName, stream ); + if( ! rec->listNName ) fprintf( stream, "\n" ); + fprintf( stream, "address:" ); + mgu_print_list( rec->listAddress, stream ); + if( ! rec->listAddress ) fprintf( stream, "\n" ); + fprintf( stream, "id:" ); + mgu_print_list( rec->listID, stream ); + if( ! rec->listID ) fprintf( stream, "\n" ); + + list = rec->userAttr; + while( list ) { + Ldif_UserAttr *attr = list->data; + fprintf( stream, "n/v:\t%s:\t:%s:\n", attr->name, attr->value ); + list = g_slist_next( list ); + } + list = NULL; +} + +static void ldif_dump_b64( gchar *buf ) { + Base64Decoder *decoder = NULL; + gchar outBuf[8192]; + gint len; + + printf( "base-64 : inbuf : %s\n", buf ); + decoder = base64_decoder_new(); + len = base64_decoder_decode( decoder, buf, outBuf ); + if (len < 0) { + printf( "base-64 : Bad BASE64 content\n" ); + } + else { + outBuf[len] = '\0'; + printf( "base-64 : %d : %s\n\n", len, outBuf ); + } + base64_decoder_free( decoder ); + decoder = NULL; +} +#endif + +/* +* Read file data into address cache. +* Note that one LDIF record identifies one entity uniquely with the +* distinguished name (dn) tag. Each person can have multiple E-Mail +* addresses. Also, each person can have many common name (cn) tags. +*/ +static void ldif_read_file( LdifFile *ldifFile, AddressCache *cache ) { + gchar *tagName = NULL, *tagValue = NULL; + gchar *lastTag = NULL, *fullValue = NULL; + GSList *listValue = NULL; + gboolean flagEOF = FALSE, flagEOR = FALSE; + gboolean flag64 = FALSE, last64 = FALSE; + Ldif_ParsedRec *rec; + long posEnd = 0L; + long posCur = 0L; + GHashTable *hashField; + + hashField = ldifFile->hashFields; + rec = g_new0( Ldif_ParsedRec, 1 ); + ldif_clear_rec( rec ); + + /* Find EOF for progress indicator */ + fseek( ldifFile->file, 0L, SEEK_END ); + posEnd = ftell( ldifFile->file ); + fseek( ldifFile->file, 0L, SEEK_SET ); + + while( ! flagEOF ) { + gchar *line = ldif_get_line( ldifFile ); + + posCur = ftell( ldifFile->file ); + if( ldifFile->cbProgress ) { + /* Call progress indicator */ + ( ldifFile->cbProgress ) ( ldifFile, & posEnd, & posCur ); + } + + flag64 = FALSE; + if( line == NULL ) { + flagEOF = flagEOR = TRUE; + } + else if( *line == '\0' ) { + flagEOR = TRUE; + } + + if( flagEOR ) { + /* EOR, Output address data */ + if( lastTag ) { + /* Save record */ + fullValue = mgu_list_coalesce( listValue ); + + /* Base-64 encoded data */ + /* + if( last64 ) { + ldif_dump_b64( fullValue ); + } + */ + + ldif_add_value( rec, lastTag, fullValue, hashField ); + /* ldif_print_record( rec, stdout ); */ + ldif_build_items( ldifFile, rec, cache ); + ldif_clear_rec( rec ); + g_free( lastTag ); + mgu_free_list( listValue ); + lastTag = NULL; + listValue = NULL; + last64 = FALSE; + } + } + if( line ) { + flagEOR = FALSE; + if( *line == ' ' ) { + /* Continuation line */ + listValue = g_slist_append( listValue, g_strdup( line+1 ) ); + } + else if( *line == '=' ) { + /* Base-64 encoded continuation field */ + listValue = g_slist_append( listValue, g_strdup( line ) ); + } + else { + /* Parse line */ + tagName = ldif_get_tagname( line, &flag64 ); + if( tagName ) { + tagValue = ldif_get_tagvalue( line ); + if( tagValue ) { + if( lastTag ) { + /* Save data */ + fullValue = mgu_list_coalesce( listValue ); + /* Base-64 encoded data */ + /* + if( last64 ) { + ldif_dump_b64( fullValue ); + } + */ + + ldif_add_value( rec, lastTag, fullValue, hashField ); + g_free( lastTag ); + mgu_free_list( listValue ); + lastTag = NULL; + listValue = NULL; + last64 = FALSE; + } + + lastTag = g_strdup( tagName ); + listValue = g_slist_append( listValue, g_strdup( tagValue ) ); + g_free( tagValue ); + last64 = flag64; + } + g_free( tagName ); + } + } + } + g_free( line ); + } + + /* Release data */ + ldif_clear_rec( rec ); + g_free( rec ); + g_free( lastTag ); + mgu_free_list( listValue ); +} + +/* +* Add list of field names to hash table. +*/ +static void ldif_hash_add_list( GHashTable *table, GSList *list ) { + GSList *node = list; + + /* mgu_print_list( list, stdout ); */ + while( node ) { + gchar *tag = node->data; + if( ! g_hash_table_lookup( table, tag ) ) { + Ldif_FieldRec *rec = NULL; + gchar *key = g_strdup( tag ); + + rec = ldif_create_fieldrec( tag ); + if( g_strcasecmp( tag, LDIF_TAG_COMMONNAME ) == 0 ) { + rec->reserved = TRUE; + } + else if( g_strcasecmp( tag, LDIF_TAG_FIRSTNAME ) == 0 ) { + rec->reserved = TRUE; + } + else if( g_strcasecmp( tag, LDIF_TAG_LASTNAME ) == 0 ) { + rec->reserved = TRUE; + } + else if( g_strcasecmp( tag, LDIF_TAG_NICKNAME ) == 0 ) { + rec->reserved = TRUE; + } + else if( g_strcasecmp( tag, LDIF_TAG_EMAIL ) == 0 ) { + rec->reserved = TRUE; + } + g_hash_table_insert( table, key, rec ); + } + node = g_slist_next( node ); + } +} + +/* +* Sorted list comparison function. +*/ +static int ldif_field_compare( gconstpointer ptr1, gconstpointer ptr2 ) { + const Ldif_FieldRec *rec1 = ptr1; + const Ldif_FieldRec *rec2 = ptr2; + return g_strcasecmp( rec1->tagName, rec2->tagName ); +} + +/* +* Append hash table entry to list - visitor function. +*/ +static void ldif_hash2list_vis( gpointer key, gpointer value, gpointer data ) { + LdifFile *ldf = data; + ldf->tempList = g_list_insert_sorted( ldf->tempList, value, ldif_field_compare ); +} + +/* +* Read tag names for file data. +*/ +static void ldif_read_tag_list( LdifFile *ldifFile ) { + gchar *tagName = NULL; + GSList *listTags = NULL; + gboolean flagEOF = FALSE, flagEOR = FALSE, flagMail = FALSE; + gboolean flag64 = FALSE; + long posEnd = 0L; + long posCur = 0L; + + /* Clear hash table */ + g_hash_table_foreach_remove( ldifFile->hashFields, ldif_hash_free_vis, NULL ); + + /* Find EOF for progress indicator */ + fseek( ldifFile->file, 0L, SEEK_END ); + posEnd = ftell( ldifFile->file ); + fseek( ldifFile->file, 0L, SEEK_SET ); + + /* Process file */ + while( ! flagEOF ) { + gchar *line = ldif_get_line( ldifFile ); + + posCur = ftell( ldifFile->file ); + if( ldifFile->cbProgress ) { + /* Call progress indicator */ + ( ldifFile->cbProgress ) ( ldifFile, & posEnd, & posCur ); + } + + flag64 = FALSE; + if( line == NULL ) { + flagEOF = flagEOR = TRUE; + } + else if( *line == '\0' ) { + flagEOR = TRUE; + } + + if( flagEOR ) { + /* EOR, Output address data */ + /* Save field list to hash table */ + if( flagMail ) { + ldif_hash_add_list( ldifFile->hashFields, listTags ); + } + mgu_free_list( listTags ); + listTags = NULL; + flagMail = FALSE; + } + if( line ) { + flagEOR = FALSE; + if( *line == ' ' ) { + /* Continuation line */ + } + else if( *line == '=' ) { + /* Base-64 encoded continuation field */ + } + else { + /* Parse line */ + tagName = ldif_get_tagname( line, &flag64 ); + if( tagName ) { + /* Add tag to list */ + listTags = g_slist_append( listTags, tagName ); + if( g_strcasecmp( tagName, LDIF_TAG_EMAIL ) == 0 ) { + flagMail = TRUE; + } + } + } + } + g_free( line ); + } + + /* Release data */ + mgu_free_list( listTags ); + listTags = NULL; +} + +/* +* ============================================================================ +* Read file into list. Main entry point +* Enter: ldifFile LDIF control data. +* cache Address cache to load. +* Return: Status code. +* ============================================================================ +*/ +gint ldif_import_data( LdifFile *ldifFile, AddressCache *cache ) { + g_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS ); + ldifFile->retVal = MGU_SUCCESS; + addrcache_clear( cache ); + cache->dataRead = FALSE; + ldif_open_file( ldifFile ); + if( ldifFile->retVal == MGU_SUCCESS ) { + /* Read data into the cache */ + ldif_read_file( ldifFile, cache ); + ldif_close_file( ldifFile ); + + /* Mark cache */ + cache->modified = FALSE; + cache->dataRead = TRUE; + } + return ldifFile->retVal; +} + +/* +* ============================================================================ +* Process entire file reading list of unique fields. List of fields may be +* accessed with the ldif_get_fieldlist() function. +* Enter: ldifFile LDIF control data. +* Return: Status code. +* ============================================================================ +*/ +gint ldif_read_tags( LdifFile *ldifFile ) { + g_return_val_if_fail( ldifFile != NULL, MGU_BAD_ARGS ); + ldifFile->retVal = MGU_SUCCESS; + if( ldifFile->dirtyFlag ) { + ldif_open_file( ldifFile ); + if( ldifFile->retVal == MGU_SUCCESS ) { + /* Read data into the cache */ + ldif_read_tag_list( ldifFile ); + ldif_close_file( ldifFile ); + ldifFile->dirtyFlag = FALSE; + ldifFile->accessFlag = TRUE; + } + } + return ldifFile->retVal; +} + +/* +* Return list of fields for LDIF file. +* Enter: ldifFile LdifFile object. +* Return: Linked list of Ldif_FieldRec objects. This list may be g_free'd. +* Note that the objects in the list should not be freed since they refer to +* objects inside the internal cache. These objects will be freed when +* LDIF file object is freed. +*/ +GList *ldif_get_fieldlist( LdifFile *ldifFile ) { + GList *list = NULL; + + g_return_val_if_fail( ldifFile != NULL, NULL ); + if( ldifFile->hashFields ) { + ldifFile->tempList = NULL; + g_hash_table_foreach( ldifFile->hashFields, ldif_hash2list_vis, ldifFile ); + list = ldifFile->tempList; + ldifFile->tempList = NULL; + } + return list; +} + +/* +* End of Source. +*/ + diff --git a/src/ldif.h b/src/ldif.h new file mode 100644 index 00000000..5ec2643c --- /dev/null +++ b/src/ldif.h @@ -0,0 +1,119 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Definitions necessary to access LDIF files (LDAP Data Interchange Format + * files). These files are used to load LDAP servers and to interchange data + * between servers. They are also used by several E-Mail client programs and + * other programs as a means of interchange address book data. + */ + +#ifndef __LDIF_H__ +#define __LDIF_H__ + +#include +#include + +#include "addrcache.h" + +#define LDIFBUFSIZE 2048 + +/* Reserved tag names - for address book import */ +#define LDIF_TAG_COMMONNAME "cn" +#define LDIF_TAG_FIRSTNAME "givenname" +#define LDIF_TAG_LASTNAME "sn" +#define LDIF_TAG_NICKNAME "xmozillanickname" +#define LDIF_TAG_EMAIL "mail" + +#define LDIF_SEP_TAG ':' +#define LDIF_LANG_TAG ';' + +/* +// Typical LDIF entry (similar to that generated by Netscape): +// +// dn: uid=axel, dc=axel, dc=com +// cn: Axel Rose +// sn: Rose +// givenname: Arnold +// xmozillanickname: Axel +// mail: axel@axelrose.com +// mail: axelrose@aol.com +// mail: axel@netscape.net +// mail: axel@hotmail.com +// uid: axelrose +// o: The Company +// locality: Denver +// st: CO +// streetaddress: 777 Lexington Avenue +// postalcode: 80298 +// countryname: USA +// telephonenumber: 303-555-1234 +// homephone: 303-555-2345 +// cellphone: 303-555-3456 +// homeurl: http://www.axelrose.com +// objectclass: top +// objectclass: person +// +// Note that first entry is always dn. An empty line defines end of +// record. Note that attribute names are case insensitive. There may +// also be several occurrences of an attribute, for example, as +// illustrated for "mail" and "objectclass" attributes. LDIF files +// can also use binary data using base-64 encoding. +// +*/ + +/* LDIF file object */ +typedef struct _LdifFile LdifFile; +struct _LdifFile { + FILE *file; + gchar *path; + gchar *bufptr; + gchar buffer[ LDIFBUFSIZE ]; + gint retVal; + GHashTable *hashFields; + GList *tempList; + gboolean dirtyFlag; + gboolean accessFlag; + void (*cbProgress)( void *, void *, void * ); + gint importCount; +}; + +/* Field list structure */ +typedef struct _Ldif_FieldRec_ Ldif_FieldRec; +struct _Ldif_FieldRec_ { + gchar *tagName; + gchar *userName; + gboolean reserved; + gboolean selected; +}; + +/* Function prototypes */ +LdifFile *ldif_create ( void ); +void ldif_set_file ( LdifFile* ldifFile, const gchar *value ); +void ldif_set_accessed ( LdifFile* ldifFile, const gboolean value ); +void ldif_set_callback ( LdifFile *ldifFile, void *func ); +void ldif_free ( LdifFile *ldifFile ); +void ldif_print_fieldrec ( Ldif_FieldRec *rec, FILE *stream ); +void ldif_print_file ( LdifFile *ldifFile, FILE *stream ); +gint ldif_import_data ( LdifFile *ldifFile, AddressCache *cache ); +gint ldif_read_tags ( LdifFile *ldifFile ); +GList *ldif_get_fieldlist ( LdifFile *ldifFile ); + +#endif /* __LDIF_H__ */ + diff --git a/src/logwindow.c b/src/logwindow.c new file mode 100644 index 00000000..ffda7fd1 --- /dev/null +++ b/src/logwindow.c @@ -0,0 +1,221 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "logwindow.h" +#include "utils.h" +#include "gtkutils.h" + +#define MAX_LINES 500 +#define TRIM_LINES 25 + +static LogWindow *logwindow; + +static void hide_cb (GtkWidget *widget, + LogWindow *logwin); +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + LogWindow *logwin); + +LogWindow *log_window_create(void) +{ + LogWindow *logwin; + GtkWidget *window; + GtkWidget *scrolledwin; + GtkWidget *text; + GtkTextBuffer *buffer; + GtkTextIter iter; + + debug_print("Creating log window...\n"); + logwin = g_new0(LogWindow, 1); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Protocol log")); + gtk_window_set_wmclass(GTK_WINDOW(window), "log_window", "Sylpheed"); + gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE); + gtk_widget_set_size_request(window, 520, 400); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), logwin); + g_signal_connect(G_OBJECT(window), "hide", + G_CALLBACK(hide_cb), logwin); + gtk_widget_realize(window); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_container_add(GTK_CONTAINER(window), scrolledwin); + gtk_widget_show(scrolledwin); + + text = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE); + gtk_container_add(GTK_CONTAINER(scrolledwin), text); + gtk_widget_show(text); + + logwin->window = window; + logwin->scrolledwin = scrolledwin; + logwin->text = text; + logwin->lines = 0; + + logwindow = logwin; + + return logwin; +} + +void log_window_init(LogWindow *logwin) +{ + GtkTextBuffer *buffer; + GdkColormap *colormap; + GdkColor color[3] = + {{0, 0, 0xafff, 0}, {0, 0xefff, 0, 0}, {0, 0xefff, 0, 0}}; + gboolean success[3]; + gint i; + + //gtkut_widget_disable_theme_engine(logwin->text); + + logwin->msg_color = color[0]; + logwin->warn_color = color[1]; + logwin->error_color = color[2]; + + colormap = gdk_window_get_colormap(logwin->window->window); + gdk_colormap_alloc_colors(colormap, color, 3, FALSE, TRUE, success); + + for (i = 0; i < 3; i++) { + if (success[i] == FALSE) { + GtkStyle *style; + + g_warning("LogWindow: color allocation failed\n"); + style = gtk_widget_get_style(logwin->window); + logwin->msg_color = logwin->warn_color = + logwin->error_color = style->black; + break; + } + } + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(logwin->text)); + gtk_text_buffer_create_tag(buffer, "message", + "foreground-gdk", &logwindow->msg_color, + NULL); + gtk_text_buffer_create_tag(buffer, "warn", + "foreground-gdk", &logwindow->warn_color, + NULL); + gtk_text_buffer_create_tag(buffer, "error", + "foreground-gdk", &logwindow->error_color, + NULL); +} + +void log_window_show(LogWindow *logwin) +{ + GtkTextView *text = GTK_TEXT_VIEW(logwin->text); + GtkTextBuffer *buffer; + GtkTextMark *mark; + + buffer = gtk_text_view_get_buffer(text); + + gtk_widget_hide(logwin->window); + + mark = gtk_text_buffer_get_mark(buffer, "end"); + gtk_text_view_scroll_mark_onscreen(text, mark); + + gtk_widget_show(logwin->window); +} + +void log_window_append(const gchar *str, LogType type) +{ + GtkTextView *text; + GtkTextBuffer *buffer; + GtkTextMark *mark; + GtkTextIter iter; + GdkColor *color = NULL; + gchar *head = NULL; + const gchar *tag; + + g_return_if_fail(logwindow != NULL); + + text = GTK_TEXT_VIEW(logwindow->text); + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); + + switch (type) { + case LOG_MSG: + color = &logwindow->msg_color; + tag = "message"; + head = "* "; + break; + case LOG_WARN: + color = &logwindow->warn_color; + tag = "warn"; + head = "** "; + break; + case LOG_ERROR: + color = &logwindow->error_color; + tag = "error"; + head = "*** "; + break; + default: + tag = NULL; + break; + } + + if (logwindow->lines == MAX_LINES) { + // + } + + if (head) + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, head, -1, tag, NULL); + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, str, -1, tag, NULL); + + mark = gtk_text_buffer_get_mark(buffer, "end"); + if (GTK_WIDGET_VISIBLE(text)) + gtk_text_view_scroll_mark_onscreen(text, mark); + + logwindow->lines++; +} + +static void hide_cb(GtkWidget *widget, LogWindow *logwin) +{ +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + LogWindow *logwin) +{ + if (event && event->keyval == GDK_Escape) + gtk_widget_hide(logwin->window); + return FALSE; +} diff --git a/src/logwindow.h b/src/logwindow.h new file mode 100644 index 00000000..97a9159f --- /dev/null +++ b/src/logwindow.h @@ -0,0 +1,55 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __LOGWINDOW_H__ +#define __LOGWINDOW_H__ + +#include +#include + +typedef struct _LogWindow LogWindow; + +typedef enum +{ + LOG_NORMAL, + LOG_MSG, + LOG_WARN, + LOG_ERROR +} LogType; + +struct _LogWindow +{ + GtkWidget *window; + GtkWidget *scrolledwin; + GtkWidget *text; + + GdkColor msg_color; + GdkColor warn_color; + GdkColor error_color; + + gint lines; +}; + +LogWindow *log_window_create(void); +void log_window_init(LogWindow *logwin); +void log_window_show(LogWindow *logwin); + +void log_window_append(const gchar *str, LogType type); + +#endif /* __LOGWINDOW_H__ */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..0f46186a --- /dev/null +++ b/src/main.c @@ -0,0 +1,721 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include + +#if HAVE_GDK_IMLIB +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_LOCALE_H +# include +#endif + +#if USE_GPGME +# include +#endif + +#include "intl.h" +#include "main.h" +#include "mainwindow.h" +#include "folderview.h" +#include "summaryview.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "prefs_actions.h" +#include "prefs_display_header.h" +#include "account.h" +#include "procmsg.h" +#include "filter.h" +#include "inc.h" +#include "import.h" +#include "manage_window.h" +#include "alertpanel.h" +#include "statusbar.h" +#include "addressbook.h" +#include "compose.h" +#include "folder.h" +#include "setup.h" +#include "utils.h" +#include "gtkutils.h" +#include "socket.h" + +#if USE_GPGME +# include "rfc2015.h" +#endif +#if USE_SSL +# include "ssl.h" +#endif + +#include "version.h" + +gchar *prog_version; +gchar *startup_dir; +gboolean debug_mode = FALSE; + +static gint lock_socket = -1; +static gint lock_socket_tag = 0; + +static struct RemoteCmd { + gboolean receive; + gboolean receive_all; + gboolean compose; + const gchar *compose_mailto; + GPtrArray *attach_files; + gboolean status; + gboolean status_full; + GPtrArray *status_folders; + GPtrArray *status_full_folders; + gboolean send; +} cmd; + +static void parse_cmd_opt(int argc, char *argv[]); + +#if USE_GPGME +static void idle_function_for_gpgme(void); +#endif /* USE_GPGME */ + +static gint prohibit_duplicate_launch (void); +static gint lock_socket_remove (void); +static void lock_socket_input_cb (gpointer data, + gint source, + GdkInputCondition condition); +static gchar *get_socket_name (void); + +static void open_compose_new (const gchar *address, + GPtrArray *attach_files); + +static void send_queue (void); + +#if 0 +/* for gettext */ +_("File `%s' already exists.\n" + "Can't create folder.") +#endif + +#define MAKE_DIR_IF_NOT_EXIST(dir) \ +{ \ + if (!is_dir_exist(dir)) { \ + if (is_file_exist(dir)) { \ + alertpanel_warning \ + (_("File `%s' already exists.\n" \ + "Can't create folder."), \ + dir); \ + return 1; \ + } \ + if (make_dir(dir) < 0) \ + return 1; \ + } \ +} + +int main(int argc, char *argv[]) +{ + gchar *userrc; + MainWindow *mainwin; + FolderView *folderview; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, CS_UTF_8); + textdomain(PACKAGE); + + prog_version = PROG_VERSION; + startup_dir = g_get_current_dir(); + + parse_cmd_opt(argc, argv); + + /* check and create unix domain socket for remote operation */ + lock_socket = prohibit_duplicate_launch(); + if (lock_socket < 0) return 0; + + if (cmd.status || cmd.status_full) { + puts("0 Sylpheed not running."); + lock_socket_remove(); + return 0; + } + + gtk_set_locale(); + gtk_init(&argc, &argv); + + gdk_rgb_init(); + gtk_widget_set_default_colormap(gdk_rgb_get_cmap()); + gtk_widget_set_default_visual(gdk_rgb_get_visual()); + +#if USE_THREADS || USE_LDAP + g_thread_init(NULL); + if (!g_thread_supported()) + g_error(_("g_thread is not supported by glib.\n")); +#endif + +#if HAVE_GDK_IMLIB + gdk_imlib_init(); + gtk_widget_push_visual(gdk_imlib_get_visual()); + gtk_widget_push_colormap(gdk_imlib_get_colormap()); +#endif + +#if USE_SSL + ssl_init(); +#endif + + srandom((gint)time(NULL)); + + /* parse gtkrc files */ + userrc = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".gtkrc-2.0", + NULL); + gtk_rc_parse(userrc); + g_free(userrc); + userrc = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".gtk", + G_DIR_SEPARATOR_S, "gtkrc-2.0", NULL); + gtk_rc_parse(userrc); + g_free(userrc); + userrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "gtkrc", NULL); + gtk_rc_parse(userrc); + g_free(userrc); + + userrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MENU_RC, NULL); + gtk_accel_map_load(userrc); + g_free(userrc); + + CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), 1); + + /* backup if old rc file exists */ + if (is_file_exist(RC_DIR)) { + if (rename(RC_DIR, RC_DIR ".bak") < 0) + FILE_OP_ERROR(RC_DIR, "rename"); + } + MAKE_DIR_IF_NOT_EXIST(RC_DIR); + MAKE_DIR_IF_NOT_EXIST(get_imap_cache_dir()); + MAKE_DIR_IF_NOT_EXIST(get_news_cache_dir()); + MAKE_DIR_IF_NOT_EXIST(get_mime_tmp_dir()); + MAKE_DIR_IF_NOT_EXIST(get_tmp_dir()); + MAKE_DIR_IF_NOT_EXIST(RC_DIR G_DIR_SEPARATOR_S "uidl"); + + if (is_file_exist(RC_DIR G_DIR_SEPARATOR_S "sylpheed.log")) { + if (rename(RC_DIR G_DIR_SEPARATOR_S "sylpheed.log", + RC_DIR G_DIR_SEPARATOR_S "sylpheed.log.bak") < 0) + FILE_OP_ERROR("sylpheed.log", "rename"); + } + set_log_file(RC_DIR G_DIR_SEPARATOR_S "sylpheed.log"); + + prefs_common_read_config(); + +#if USE_GPGME + if (gpgme_check_engine()) { /* Also does some gpgme init */ + rfc2015_disable_all(); + debug_print("gpgme_engine_version:\n%s\n", + gpgme_get_engine_info()); + + if (prefs_common.gpg_warning) { + AlertValue val; + + val = alertpanel_message_with_disable + (_("Warning"), + _("GnuPG is not installed properly, or its version is too old.\n" + "OpenPGP support disabled."), + ALERT_WARNING); + if (val & G_ALERTDISABLE) + prefs_common.gpg_warning = FALSE; + } + } + gpgme_register_idle(idle_function_for_gpgme); +#endif + + sock_set_io_timeout(prefs_common.io_timeout_secs); + + prefs_filter_read_config(); + prefs_actions_read_config(); + prefs_display_header_read_config(); + + gtkut_widget_init(); + + mainwin = main_window_create + (prefs_common.sep_folder | prefs_common.sep_msg << 1); + folderview = mainwin->folderview; + + /* register the callback of unix domain socket input */ + lock_socket_tag = gdk_input_add(lock_socket, + GDK_INPUT_READ | GDK_INPUT_EXCEPTION, + lock_socket_input_cb, + mainwin); + + account_read_config_all(); + + if (folder_read_list() < 0) { + setup(mainwin); + folder_write_list(); + } + if (!account_get_list()) { + account_edit_open(); + account_add(); + } + + account_set_missing_folder(); + folder_set_missing_folders(); + folderview_set(folderview); + + addressbook_read_file(); + + inc_autocheck_timer_init(mainwin); + + /* ignore SIGPIPE signal for preventing sudden death of program */ + signal(SIGPIPE, SIG_IGN); + + if (cmd.receive_all) + inc_all_account_mail(mainwin, FALSE); + else if (prefs_common.chk_on_startup) + inc_all_account_mail(mainwin, TRUE); + else if (cmd.receive) + inc_mail(mainwin); + else + gtk_widget_grab_focus(folderview->ctree); + + if (cmd.compose) + open_compose_new(cmd.compose_mailto, cmd.attach_files); + if (cmd.attach_files) { + ptr_array_free_strings(cmd.attach_files); + g_ptr_array_free(cmd.attach_files, TRUE); + cmd.attach_files = NULL; + } + if (cmd.send) + send_queue(); + if (cmd.status_folders) { + g_ptr_array_free(cmd.status_folders, TRUE); + cmd.status_folders = NULL; + } + if (cmd.status_full_folders) { + g_ptr_array_free(cmd.status_full_folders, TRUE); + cmd.status_full_folders = NULL; + } + + gtk_main(); + + return 0; +} + +static void parse_cmd_opt(int argc, char *argv[]) +{ + gint i; + + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], "--debug", 7)) + debug_mode = TRUE; + else if (!strncmp(argv[i], "--receive-all", 13)) + cmd.receive_all = TRUE; + else if (!strncmp(argv[i], "--receive", 9)) + cmd.receive = TRUE; + else if (!strncmp(argv[i], "--compose", 9)) { + const gchar *p = argv[i + 1]; + + cmd.compose = TRUE; + cmd.compose_mailto = NULL; + if (p && *p != '\0' && *p != '-') { + if (!strncmp(p, "mailto:", 7)) + cmd.compose_mailto = p + 7; + else + cmd.compose_mailto = p; + i++; + } + } else if (!strncmp(argv[i], "--attach", 8)) { + const gchar *p = argv[i + 1]; + gchar *file; + + while (p && *p != '\0' && *p != '-') { + if (!cmd.attach_files) + cmd.attach_files = g_ptr_array_new(); + if (*p != G_DIR_SEPARATOR) + file = g_strconcat(startup_dir, + G_DIR_SEPARATOR_S, + p, NULL); + else + file = g_strdup(p); + g_ptr_array_add(cmd.attach_files, file); + i++; + p = argv[i + 1]; + } + } else if (!strncmp(argv[i], "--send", 6)) { + cmd.send = TRUE; + } else if (!strncmp(argv[i], "--version", 9)) { + puts("Sylpheed version " VERSION); + exit(0); + } else if (!strncmp(argv[i], "--status-full", 13)) { + const gchar *p = argv[i + 1]; + + cmd.status_full = TRUE; + while (p && *p != '\0' && *p != '-') { + if (!cmd.status_full_folders) + cmd.status_full_folders = + g_ptr_array_new(); + g_ptr_array_add(cmd.status_full_folders, + g_strdup(p)); + i++; + p = argv[i + 1]; + } + } else if (!strncmp(argv[i], "--status", 8)) { + const gchar *p = argv[i + 1]; + + cmd.status = TRUE; + while (p && *p != '\0' && *p != '-') { + if (!cmd.status_folders) + cmd.status_folders = g_ptr_array_new(); + g_ptr_array_add(cmd.status_folders, + g_strdup(p)); + i++; + p = argv[i + 1]; + } + } else if (!strncmp(argv[i], "--help", 6)) { + g_print(_("Usage: %s [OPTION]...\n"), + g_basename(argv[0])); + + puts(_(" --compose [address] open composition window")); + puts(_(" --attach file1 [file2]...\n" + " open composition window with specified files\n" + " attached")); + puts(_(" --receive receive new messages")); + puts(_(" --receive-all receive new messages of all accounts")); + puts(_(" --send send all queued messages")); + puts(_(" --status [folder]... show the total number of messages")); + puts(_(" --status-full [folder]...\n" + " show the status of each folder")); + puts(_(" --debug debug mode")); + puts(_(" --help display this help and exit")); + puts(_(" --version output version information and exit")); + + exit(1); + } + } + + if (cmd.attach_files && cmd.compose == FALSE) { + cmd.compose = TRUE; + cmd.compose_mailto = NULL; + } +} + +static gint get_queued_message_num(void) +{ + FolderItem *queue; + + queue = folder_get_default_queue(); + if (!queue) return -1; + + folder_item_scan(queue); + return queue->total; +} + +void app_will_exit(GtkWidget *widget, gpointer data) +{ + MainWindow *mainwin = data; + gchar *filename; + + if (compose_get_compose_list()) { + if (alertpanel(_("Notice"), + _("Composing message exists. Really quit?"), + _("OK"), _("Cancel"), NULL) != G_ALERTDEFAULT) + return; + manage_window_focus_in(mainwin->window, NULL, NULL); + } + + if (prefs_common.warn_queued_on_exit && get_queued_message_num() > 0) { + if (alertpanel(_("Queued messages"), + _("Some unsent messages are queued. Exit now?"), + _("OK"), _("Cancel"), NULL) != G_ALERTDEFAULT) + return; + manage_window_focus_in(mainwin->window, NULL, NULL); + } + + inc_autocheck_timer_remove(); + + if (prefs_common.clean_on_exit) + main_window_empty_trash(mainwin, prefs_common.ask_on_clean); + + /* save all state before exiting */ + folder_write_list(); + summary_write_cache(mainwin->summaryview); + + main_window_get_size(mainwin); + main_window_get_position(mainwin); + prefs_common_write_config(); + prefs_filter_write_config(); + account_write_config_all(); + addressbook_export_to_file(); + + filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MENU_RC, NULL); + gtk_accel_map_save(filename); + g_free(filename); + + /* delete temporary files */ + remove_all_files(get_mime_tmp_dir()); + + close_log_file(); + lock_socket_remove(); + +#if USE_SSL + ssl_done(); +#endif + + gtk_main_quit(); +} + +#if USE_GPGME +static void idle_function_for_gpgme(void) +{ + while (gtk_events_pending()) + gtk_main_iteration(); +} +#endif /* USE_GPGME */ + +static gchar *get_socket_name(void) +{ + static gchar *filename = NULL; + + if (filename == NULL) { + filename = g_strdup_printf("%s%csylpheed-%d", + g_get_tmp_dir(), G_DIR_SEPARATOR, + getuid()); + } + + return filename; +} + +static gint prohibit_duplicate_launch(void) +{ + gint uxsock; + gchar *path; + + path = get_socket_name(); + uxsock = fd_connect_unix(path); + if (uxsock < 0) { + unlink(path); + return fd_open_unix(path); + } + + /* remote command mode */ + + debug_print(_("another Sylpheed is already running.\n")); + + if (cmd.receive_all) + fd_write_all(uxsock, "receive_all\n", 12); + else if (cmd.receive) + fd_write_all(uxsock, "receive\n", 8); + else if (cmd.compose && cmd.attach_files) { + gchar *str, *compose_str; + gint i; + + if (cmd.compose_mailto) + compose_str = g_strdup_printf("compose_attach %s\n", + cmd.compose_mailto); + else + compose_str = g_strdup("compose_attach\n"); + + fd_write_all(uxsock, compose_str, strlen(compose_str)); + g_free(compose_str); + + for (i = 0; i < cmd.attach_files->len; i++) { + str = g_ptr_array_index(cmd.attach_files, i); + fd_write_all(uxsock, str, strlen(str)); + fd_write_all(uxsock, "\n", 1); + } + + fd_write_all(uxsock, ".\n", 2); + } else if (cmd.compose) { + gchar *compose_str; + + if (cmd.compose_mailto) + compose_str = g_strdup_printf + ("compose %s\n", cmd.compose_mailto); + else + compose_str = g_strdup("compose\n"); + + fd_write_all(uxsock, compose_str, strlen(compose_str)); + g_free(compose_str); + } else if (cmd.send) { + fd_write_all(uxsock, "send\n", 5); + } else if (cmd.status || cmd.status_full) { + gchar buf[BUFFSIZE]; + gint i; + const gchar *command; + GPtrArray *folders; + gchar *folder; + + command = cmd.status_full ? "status-full\n" : "status\n"; + folders = cmd.status_full ? cmd.status_full_folders : + cmd.status_folders; + + fd_write_all(uxsock, command, strlen(command)); + for (i = 0; folders && i < folders->len; ++i) { + folder = g_ptr_array_index(folders, i); + fd_write_all(uxsock, folder, strlen(folder)); + fd_write_all(uxsock, "\n", 1); + } + fd_write_all(uxsock, ".\n", 2); + for (;;) { + fd_gets(uxsock, buf, sizeof(buf)); + if (!strncmp(buf, ".\n", 2)) break; + fputs(buf, stdout); + } + } else + fd_write_all(uxsock, "popup\n", 6); + + fd_close(uxsock); + return -1; +} + +static gint lock_socket_remove(void) +{ + gchar *filename; + + if (lock_socket < 0) return -1; + + if (lock_socket_tag > 0) + gdk_input_remove(lock_socket_tag); + fd_close(lock_socket); + filename = get_socket_name(); + unlink(filename); + + return 0; +} + +static GPtrArray *get_folder_item_list(gint sock) +{ + gchar buf[BUFFSIZE]; + FolderItem *item; + GPtrArray *folders = NULL; + + for (;;) { + fd_gets(sock, buf, sizeof(buf)); + if (!strncmp(buf, ".\n", 2)) break; + strretchomp(buf); + if (!folders) folders = g_ptr_array_new(); + item = folder_find_item_from_identifier(buf); + if (item) + g_ptr_array_add(folders, item); + else + g_warning("no such folder: %s\n", buf); + } + + return folders; +} + +static void lock_socket_input_cb(gpointer data, + gint source, + GdkInputCondition condition) +{ + MainWindow *mainwin = (MainWindow *)data; + gint sock; + gchar buf[BUFFSIZE]; + + sock = fd_accept(source); + fd_gets(sock, buf, sizeof(buf)); + + if (!strncmp(buf, "popup", 5)) { + main_window_popup(mainwin); + } else if (!strncmp(buf, "receive_all", 11)) { + main_window_popup(mainwin); + inc_all_account_mail(mainwin, FALSE); + } else if (!strncmp(buf, "receive", 7)) { + main_window_popup(mainwin); + inc_mail(mainwin); + } else if (!strncmp(buf, "compose_attach", 14)) { + GPtrArray *files; + gchar *mailto; + + mailto = g_strdup(buf + strlen("compose_attach") + 1); + files = g_ptr_array_new(); + while (fd_gets(sock, buf, sizeof(buf)) > 0) { + if (buf[0] == '.' && buf[1] == '\n') break; + strretchomp(buf); + g_ptr_array_add(files, g_strdup(buf)); + } + open_compose_new(mailto, files); + ptr_array_free_strings(files); + g_ptr_array_free(files, TRUE); + g_free(mailto); + } else if (!strncmp(buf, "compose", 7)) { + open_compose_new(buf + strlen("compose") + 1, NULL); + } else if (!strncmp(buf, "send", 4)) { + send_queue(); + } else if (!strncmp(buf, "status-full", 11) || + !strncmp(buf, "status", 6)) { + gchar *status; + GPtrArray *folders; + + folders = get_folder_item_list(sock); + status = folder_get_status + (folders, !strncmp(buf, "status-full", 11)); + fd_write_all(sock, status, strlen(status)); + fd_write_all(sock, ".\n", 2); + g_free(status); + if (folders) g_ptr_array_free(folders, TRUE); + } + + fd_close(sock); +} + +static void open_compose_new(const gchar *address, GPtrArray *attach_files) +{ + gchar *addr = NULL; + + if (address) { + Xstrdup_a(addr, address, return); + g_strstrip(addr); + } + + compose_new(NULL, NULL, addr, attach_files); +} + +static void send_queue(void) +{ + GList *list; + + if (!main_window_toggle_online_if_offline(main_window_get())) + return; + + for (list = folder_get_list(); list != NULL; list = list->next) { + Folder *folder = list->data; + + if (folder->queue) { + gint ret; + + ret = procmsg_send_queue(folder->queue, + prefs_common.savemsg); + statusbar_pop_all(); + if (ret > 0) + folder_item_scan(folder->queue); + } + } + + folderview_update_all_updated(TRUE); +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 00000000..0fb8c56f --- /dev/null +++ b/src/main.h @@ -0,0 +1,32 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include +#include + +extern gchar *prog_version; +extern gchar *startup_dir; +extern gboolean debug_mode; + +void app_will_exit (GtkWidget *widget, gpointer data); + +#endif /* __MAIN_H__ */ diff --git a/src/mainwindow.c b/src/mainwindow.c new file mode 100644 index 00000000..72d05fa2 --- /dev/null +++ b/src/mainwindow.c @@ -0,0 +1,3174 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "mainwindow.h" +#include "folderview.h" +#include "foldersel.h" +#include "summaryview.h" +#include "summary_search.h" +#include "messageview.h" +#include "message_search.h" +#include "headerview.h" +#include "menu.h" +#include "stock_pixmap.h" +#include "folder.h" +#include "inc.h" +#include "compose.h" +#include "procmsg.h" +#include "import.h" +#include "export.h" +#include "prefs_common.h" +#include "prefs_filter.h" +#include "prefs_actions.h" +#include "prefs_account.h" +#include "prefs_summary_column.h" +#include "prefs_template.h" +#include "action.h" +#include "account.h" +#include "addressbook.h" +#include "logwindow.h" +#include "manage_window.h" +#include "alertpanel.h" +#include "statusbar.h" +#include "inputdialog.h" +#include "utils.h" +#include "gtkutils.h" +#include "codeconv.h" +#include "about.h" +#include "manual.h" +#include "version.h" + +#define AC_LABEL_WIDTH 240 + +#define STATUSBAR_PUSH(mainwin, str) \ +{ \ + gtk_statusbar_push(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->mainwin_cid, str); \ + gtkut_widget_wait_for_draw(mainwin->hbox_stat); \ +} + +#define STATUSBAR_POP(mainwin) \ +{ \ + gtk_statusbar_pop(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->mainwin_cid); \ +} + +/* list of all instantiated MainWindow */ +static GList *mainwin_list = NULL; + +static GdkCursor *watch_cursor; + +static void main_window_menu_callback_block (MainWindow *mainwin); +static void main_window_menu_callback_unblock (MainWindow *mainwin); + +static void main_window_show_cur_account (MainWindow *mainwin); + +static void main_window_set_widgets (MainWindow *mainwin, + SeparateType type); +static void main_window_toolbar_create (MainWindow *mainwin, + GtkWidget *container); + +/* callback functions */ +static void toolbar_inc_cb (GtkWidget *widget, + gpointer data); +static void toolbar_inc_all_cb (GtkWidget *widget, + gpointer data); +static void toolbar_send_cb (GtkWidget *widget, + gpointer data); + +static void toolbar_compose_cb (GtkWidget *widget, + gpointer data); +static void toolbar_reply_cb (GtkWidget *widget, + gpointer data); +static void toolbar_reply_to_all_cb (GtkWidget *widget, + gpointer data); +static void toolbar_forward_cb (GtkWidget *widget, + gpointer data); + +static void toolbar_delete_cb (GtkWidget *widget, + gpointer data); +static void toolbar_exec_cb (GtkWidget *widget, + gpointer data); + +static void toolbar_next_unread_cb (GtkWidget *widget, + gpointer data); + +#if 0 +static void toolbar_prefs_cb (GtkWidget *widget, + gpointer data); +static void toolbar_account_cb (GtkWidget *widget, + gpointer data); + +static void toolbar_account_button_pressed (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +#endif + +static void toolbar_child_attached (GtkWidget *widget, + GtkWidget *child, + gpointer data); +static void toolbar_child_detached (GtkWidget *widget, + GtkWidget *child, + gpointer data); + +static void online_switch_clicked (GtkWidget *widget, + gpointer data); +static void ac_label_button_pressed (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static void ac_menu_popup_closed (GtkMenuShell *menu_shell, + gpointer data); + +static gint main_window_close_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gint folder_window_close_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gint message_window_close_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); + +static void main_window_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + gpointer data); +static void folder_window_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + gpointer data); +static void message_window_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + gpointer data); + +static void new_folder_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void rename_folder_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void delete_folder_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void update_folderview_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void add_mailbox_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void remove_mailbox_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void rebuild_tree_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void import_mbox_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void export_mbox_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void empty_trash_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void save_as_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void print_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void toggle_offline_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void app_exit_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void search_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void toggle_folder_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void toggle_message_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void toggle_toolbar_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void toggle_statusbar_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void separate_widget_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void addressbook_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void log_window_show_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void inc_mail_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void inc_all_account_mail_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void inc_cancel_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void send_queue_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void compose_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void reply_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void open_msg_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void view_source_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void show_all_header_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void move_to_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void copy_to_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void delete_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void mark_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void unmark_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void mark_as_unread_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void mark_as_read_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void mark_all_read_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void reedit_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void add_address_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void set_charset_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void thread_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void expand_threads_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void collapse_threads_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void set_display_item_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void sort_summary_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void sort_summary_type_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void attract_by_subject_cb(MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void delete_duplicated_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void filter_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void execute_summary_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void update_summary_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void prev_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void next_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void prev_unread_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void next_unread_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prev_new_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void next_new_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prev_marked_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void next_marked_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prev_labeled_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void next_labeled_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void goto_folder_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void copy_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void allsel_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void select_thread_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void create_filter_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void prefs_common_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prefs_filter_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prefs_template_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prefs_actions_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void prefs_account_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void new_account_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void account_selector_menu_cb (GtkMenuItem *menuitem, + gpointer data); +static void account_receive_menu_cb (GtkMenuItem *menuitem, + gpointer data); + +static void manual_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); +static void faq_open_cb (MainWindow *mainwin, + guint action, + GtkWidget *widget); + +static void scan_tree_func (Folder *folder, + FolderItem *item, + gpointer data); + +static GtkItemFactoryEntry mainwin_entries[] = +{ + {N_("/_File"), NULL, NULL, 0, ""}, + {N_("/_File/_Folder"), NULL, NULL, 0, ""}, + {N_("/_File/_Folder/Create _new folder..."), + NULL, new_folder_cb, 0, NULL}, + {N_("/_File/_Folder/_Rename folder..."),NULL, rename_folder_cb, 0, NULL}, + {N_("/_File/_Folder/_Delete folder"), NULL, delete_folder_cb, 0, NULL}, + {N_("/_File/_Mailbox"), NULL, NULL, 0, ""}, + {N_("/_File/_Mailbox/Add _mailbox..."), NULL, add_mailbox_cb, 0, NULL}, + {N_("/_File/_Mailbox/_Remove mailbox"), NULL, remove_mailbox_cb, 0, NULL}, + {N_("/_File/_Mailbox/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Mailbox/_Check for new messages"), + NULL, update_folderview_cb, 0, NULL}, + {N_("/_File/_Mailbox/Check for new messages in _all mailboxes"), + NULL, update_folderview_cb, 1, NULL}, + {N_("/_File/_Mailbox/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Mailbox/R_ebuild folder tree"), + NULL, rebuild_tree_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Import mbox file..."), NULL, import_mbox_cb, 0, NULL}, + {N_("/_File/_Export to mbox file..."), NULL, export_mbox_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/Empty all _trash"), NULL, empty_trash_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Save as..."), "S", save_as_cb, 0, NULL}, + {N_("/_File/_Print..."), NULL, print_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Work offline"), NULL, toggle_offline_cb, 0, ""}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + /* {N_("/_File/_Close"), "W", app_exit_cb, 0, NULL}, */ + {N_("/_File/E_xit"), "Q", app_exit_cb, 0, NULL}, + + {N_("/_Edit"), NULL, NULL, 0, ""}, + {N_("/_Edit/_Copy"), "C", copy_cb, 0, NULL}, + {N_("/_Edit/Select _all"), "A", allsel_cb, 0, NULL}, + {N_("/_Edit/Select _thread"), NULL, select_thread_cb, 0, NULL}, + {N_("/_Edit/---"), NULL, NULL, 0, ""}, + {N_("/_Edit/_Find in current message..."), + "F", search_cb, 0, NULL}, + {N_("/_Edit/_Search messages..."), "F", search_cb, 1, NULL}, + + {N_("/_View"), NULL, NULL, 0, ""}, + {N_("/_View/Show or hi_de"), NULL, NULL, 0, ""}, + {N_("/_View/Show or hi_de/_Folder tree"), + NULL, toggle_folder_cb, 0, ""}, + {N_("/_View/Show or hi_de/_Message view"), + "V", toggle_message_cb, 0, ""}, + {N_("/_View/Show or hi_de/_Toolbar"), + NULL, NULL, 0, ""}, + {N_("/_View/Show or hi_de/_Toolbar/Icon _and text"), + NULL, toggle_toolbar_cb, TOOLBAR_BOTH, ""}, + {N_("/_View/Show or hi_de/_Toolbar/_Icon"), + NULL, toggle_toolbar_cb, TOOLBAR_ICON, "/View/Show or hide/Toolbar/Icon and text"}, + {N_("/_View/Show or hi_de/_Toolbar/_Text"), + NULL, toggle_toolbar_cb, TOOLBAR_TEXT, "/View/Show or hide/Toolbar/Icon and text"}, + {N_("/_View/Show or hi_de/_Toolbar/_None"), + NULL, toggle_toolbar_cb, TOOLBAR_NONE, "/View/Show or hide/Toolbar/Icon and text"}, + {N_("/_View/Show or hi_de/Status _bar"), + NULL, toggle_statusbar_cb, 0, ""}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/Separate f_older tree"), NULL, separate_widget_cb, SEPARATE_FOLDER, ""}, + {N_("/_View/Separate m_essage view"), NULL, separate_widget_cb, SEPARATE_MESSAGE, ""}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Sort"), NULL, NULL, 0, ""}, + {N_("/_View/_Sort/by _number"), NULL, sort_summary_cb, SORT_BY_NUMBER, ""}, + {N_("/_View/_Sort/by s_ize"), NULL, sort_summary_cb, SORT_BY_SIZE, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _date"), NULL, sort_summary_cb, SORT_BY_DATE, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _from"), NULL, sort_summary_cb, SORT_BY_FROM, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _recipient"), NULL, sort_summary_cb, SORT_BY_TO, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _subject"), NULL, sort_summary_cb, SORT_BY_SUBJECT, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _color label"), + NULL, sort_summary_cb, SORT_BY_LABEL, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _mark"), NULL, sort_summary_cb, SORT_BY_MARK, "/View/Sort/by number"}, + {N_("/_View/_Sort/by _unread"), NULL, sort_summary_cb, SORT_BY_UNREAD, "/View/Sort/by number"}, + {N_("/_View/_Sort/by a_ttachment"), + NULL, sort_summary_cb, SORT_BY_MIME, "/View/Sort/by number"}, + {N_("/_View/_Sort/D_on't sort"), NULL, sort_summary_cb, SORT_BY_NONE, "/View/Sort/by number"}, + {N_("/_View/_Sort/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Sort/Ascending"), NULL, sort_summary_type_cb, SORT_ASCENDING, ""}, + {N_("/_View/_Sort/Descending"), NULL, sort_summary_type_cb, SORT_DESCENDING, "/View/Sort/Ascending"}, + {N_("/_View/_Sort/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Sort/_Attract by subject"), + NULL, attract_by_subject_cb, 0, NULL}, + {N_("/_View/Th_read view"), "T", thread_cb, 0, ""}, + {N_("/_View/E_xpand all threads"), NULL, expand_threads_cb, 0, NULL}, + {N_("/_View/Co_llapse all threads"), NULL, collapse_threads_cb, 0, NULL}, + {N_("/_View/Set display _item..."), NULL, set_display_item_cb, 0, NULL}, + + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to/_Prev message"), "P", prev_cb, 0, NULL}, + {N_("/_View/_Go to/_Next message"), "N", next_cb, 0, NULL}, + {N_("/_View/_Go to/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to/P_rev unread message"), + "P", prev_unread_cb, 0, NULL}, + {N_("/_View/_Go to/N_ext unread message"), + "N", next_unread_cb, 0, NULL}, + {N_("/_View/_Go to/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to/Prev ne_w message"), NULL, prev_new_cb, 0, NULL}, + {N_("/_View/_Go to/Ne_xt new message"), NULL, next_new_cb, 0, NULL}, + {N_("/_View/_Go to/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to/Prev _marked message"), + NULL, prev_marked_cb, 0, NULL}, + {N_("/_View/_Go to/Next m_arked message"), + NULL, next_marked_cb, 0, NULL}, + {N_("/_View/_Go to/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to/Prev _labeled message"), + NULL, prev_labeled_cb, 0, NULL}, + {N_("/_View/_Go to/Next la_beled message"), + NULL, next_labeled_cb, 0, NULL}, + {N_("/_View/_Go to/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Go to/Other _folder..."), "G", goto_folder_cb, 0, NULL}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + +#define CODESET_SEPARATOR \ + {N_("/_View/_Code set/---"), NULL, NULL, 0, ""} +#define CODESET_ACTION(action) \ + NULL, set_charset_cb, action, "/View/Code set/Auto detect" + + {N_("/_View/_Code set"), NULL, NULL, 0, ""}, + {N_("/_View/_Code set/_Auto detect"), + NULL, set_charset_cb, C_AUTO, ""}, + {N_("/_View/_Code set/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Code set/7bit ascii (US-ASC_II)"), + CODESET_ACTION(C_US_ASCII)}, + +#if HAVE_ICONV + {N_("/_View/_Code set/Unicode (_UTF-8)"), + CODESET_ACTION(C_UTF_8)}, + CODESET_SEPARATOR, +#endif + {N_("/_View/_Code set/Western European (ISO-8859-_1)"), + CODESET_ACTION(C_ISO_8859_1)}, + {N_("/_View/_Code set/Western European (ISO-8859-15)"), + CODESET_ACTION(C_ISO_8859_15)}, + CODESET_SEPARATOR, +#if HAVE_ICONV + {N_("/_View/_Code set/Central European (ISO-8859-_2)"), + CODESET_ACTION(C_ISO_8859_2)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/_Baltic (ISO-8859-13)"), + CODESET_ACTION(C_ISO_8859_13)}, + {N_("/_View/_Code set/Baltic (ISO-8859-_4)"), + CODESET_ACTION(C_ISO_8859_4)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Greek (ISO-8859-_7)"), + CODESET_ACTION(C_ISO_8859_7)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Turkish (ISO-8859-_9)"), + CODESET_ACTION(C_ISO_8859_9)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Cyrillic (ISO-8859-_5)"), + CODESET_ACTION(C_ISO_8859_5)}, + {N_("/_View/_Code set/Cyrillic (KOI8-_R)"), + CODESET_ACTION(C_KOI8_R)}, + {N_("/_View/_Code set/Cyrillic (KOI8-U)"), + CODESET_ACTION(C_KOI8_U)}, + {N_("/_View/_Code set/Cyrillic (Windows-1251)"), + CODESET_ACTION(C_CP1251)}, + CODESET_SEPARATOR, +#endif + {N_("/_View/_Code set/Japanese (ISO-2022-_JP)"), + CODESET_ACTION(C_ISO_2022_JP)}, +#if HAVE_ICONV + {N_("/_View/_Code set/Japanese (ISO-2022-JP-2)"), + CODESET_ACTION(C_ISO_2022_JP_2)}, +#endif + {N_("/_View/_Code set/Japanese (_EUC-JP)"), + CODESET_ACTION(C_EUC_JP)}, + {N_("/_View/_Code set/Japanese (_Shift__JIS)"), + CODESET_ACTION(C_SHIFT_JIS)}, +#if HAVE_ICONV + CODESET_SEPARATOR, + {N_("/_View/_Code set/Simplified Chinese (_GB2312)"), + CODESET_ACTION(C_GB2312)}, + {N_("/_View/_Code set/Traditional Chinese (_Big5)"), + CODESET_ACTION(C_BIG5)}, + {N_("/_View/_Code set/Traditional Chinese (EUC-_TW)"), + CODESET_ACTION(C_EUC_TW)}, + {N_("/_View/_Code set/Chinese (ISO-2022-_CN)"), + CODESET_ACTION(C_ISO_2022_CN)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Korean (EUC-_KR)"), + CODESET_ACTION(C_EUC_KR)}, + {N_("/_View/_Code set/Korean (ISO-2022-KR)"), + CODESET_ACTION(C_ISO_2022_KR)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Thai (TIS-620)"), + CODESET_ACTION(C_TIS_620)}, + {N_("/_View/_Code set/Thai (Windows-874)"), + CODESET_ACTION(C_WINDOWS_874)}, +#endif + +#undef CODESET_SEPARATOR +#undef CODESET_ACTION + + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/Open in new _window"), "N", open_msg_cb, 0, NULL}, + {N_("/_View/Mess_age source"), "U", view_source_cb, 0, NULL}, + {N_("/_View/Show all _header"), "H", show_all_header_cb, 0, ""}, + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/_Update summary"), "U", update_summary_cb, 0, NULL}, + + {N_("/_Message"), NULL, NULL, 0, ""}, + {N_("/_Message/Recei_ve"), NULL, NULL, 0, ""}, + {N_("/_Message/Recei_ve/Get from _current account"), + "I", inc_mail_cb, 0, NULL}, + {N_("/_Message/Recei_ve/Get from _all accounts"), + "I", inc_all_account_mail_cb, 0, NULL}, + {N_("/_Message/Recei_ve/Cancel receivin_g"), + NULL, inc_cancel_cb, 0, NULL}, + {N_("/_Message/Recei_ve/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Send queued messages"), NULL, send_queue_cb, 0, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/Compose _new message"), "M", compose_cb, 0, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Reply"), "R", reply_cb, COMPOSE_REPLY, NULL}, + {N_("/_Message/Repl_y to"), NULL, NULL, 0, ""}, + {N_("/_Message/Repl_y to/_all"), "R", reply_cb, COMPOSE_REPLY_TO_ALL, NULL}, + {N_("/_Message/Repl_y to/_sender"), NULL, reply_cb, COMPOSE_REPLY_TO_SENDER, NULL}, + {N_("/_Message/Repl_y to/mailing _list"), + "L", reply_cb, COMPOSE_REPLY_TO_LIST, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Forward"), "F", reply_cb, COMPOSE_FORWARD, NULL}, + {N_("/_Message/For_ward as attachment"), + "F", reply_cb, COMPOSE_FORWARD_AS_ATTACH, NULL}, + {N_("/_Message/Redirec_t"), NULL, reply_cb, COMPOSE_REDIRECT, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/M_ove..."), "O", move_to_cb, 0, NULL}, + {N_("/_Message/_Copy..."), "O", copy_to_cb, 0, NULL}, + {N_("/_Message/_Delete"), "D", delete_cb, 0, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Mark"), NULL, NULL, 0, ""}, + {N_("/_Message/_Mark/_Mark"), "asterisk", mark_cb, 0, NULL}, + {N_("/_Message/_Mark/_Unmark"), "U", unmark_cb, 0, NULL}, + {N_("/_Message/_Mark/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Mark/Mark as unr_ead"), "exclam", mark_as_unread_cb, 0, NULL}, + {N_("/_Message/_Mark/Mark as rea_d"), + NULL, mark_as_read_cb, 0, NULL}, + {N_("/_Message/_Mark/Mark all _read"), NULL, mark_all_read_cb, 0, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/Re-_edit"), NULL, reedit_cb, 0, NULL}, + + {N_("/_Tools"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Address book"), "A", addressbook_open_cb, 0, NULL}, + {N_("/_Tools/Add sender to address boo_k"), + NULL, add_address_cb, 0, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Filter all messages in folder"), + NULL, filter_cb, 0, NULL}, + {N_("/_Tools/Filter _selected messages"), + NULL, filter_cb, 1, NULL}, + {N_("/_Tools/_Create filter rule"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Create filter rule/_Automatically"), + NULL, create_filter_cb, FILTER_BY_AUTO, NULL}, + {N_("/_Tools/_Create filter rule/by _From"), + NULL, create_filter_cb, FILTER_BY_FROM, NULL}, + {N_("/_Tools/_Create filter rule/by _To"), + NULL, create_filter_cb, FILTER_BY_TO, NULL}, + {N_("/_Tools/_Create filter rule/by _Subject"), + NULL, create_filter_cb, FILTER_BY_SUBJECT, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/Actio_ns"), NULL, NULL, 0, ""}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/Delete du_plicated messages"), + NULL, delete_duplicated_cb, 0, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/E_xecute"), "X", execute_summary_cb, 0, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Log window"), "L", log_window_show_cb, 0, NULL}, + + {N_("/_Configuration"), NULL, NULL, 0, ""}, + {N_("/_Configuration/_Common preferences..."), + NULL, prefs_common_open_cb, 0, NULL}, + {N_("/_Configuration/_Filter setting..."), + NULL, prefs_filter_open_cb, 0, NULL}, + {N_("/_Configuration/_Template..."), NULL, prefs_template_open_cb, 0, NULL}, + {N_("/_Configuration/_Actions..."), NULL, prefs_actions_open_cb, 0, NULL}, + {N_("/_Configuration/---"), NULL, NULL, 0, ""}, + {N_("/_Configuration/_Preferences for current account..."), + NULL, prefs_account_open_cb, 0, NULL}, + {N_("/_Configuration/Create _new account..."), + NULL, new_account_cb, 0, NULL}, + {N_("/_Configuration/_Edit accounts..."), + NULL, account_edit_open, 0, NULL}, + {N_("/_Configuration/C_hange current account"), + NULL, NULL, 0, ""}, + + {N_("/_Help"), NULL, NULL, 0, ""}, + {N_("/_Help/_Manual"), NULL, NULL, 0, ""}, + {N_("/_Help/_Manual/_English"), NULL, manual_open_cb, MANUAL_LANG_EN, NULL}, + {N_("/_Help/_Manual/_Japanese"), NULL, manual_open_cb, MANUAL_LANG_JA, NULL}, + {N_("/_Help/_FAQ"), NULL, NULL, 0, ""}, + {N_("/_Help/_FAQ/_English"), NULL, faq_open_cb, MANUAL_LANG_EN, NULL}, + {N_("/_Help/_FAQ/_German"), NULL, faq_open_cb, MANUAL_LANG_DE, NULL}, + {N_("/_Help/_FAQ/_Spanish"), NULL, faq_open_cb, MANUAL_LANG_ES, NULL}, + {N_("/_Help/_FAQ/_French"), NULL, faq_open_cb, MANUAL_LANG_FR, NULL}, + {N_("/_Help/_FAQ/_Italian"), NULL, faq_open_cb, MANUAL_LANG_IT, NULL}, + {N_("/_Help/---"), NULL, NULL, 0, ""}, + {N_("/_Help/_About"), NULL, about_show, 0, NULL} +}; + +MainWindow *main_window_create(SeparateType type) +{ + MainWindow *mainwin; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *handlebox; + GtkWidget *vbox_body; + GtkWidget *hbox_stat; + GtkWidget *statusbar; + GtkWidget *progressbar; + GtkWidget *statuslabel; + GtkWidget *online_hbox; + GtkWidget *online_switch; + GtkWidget *online_pixmap; + GtkWidget *offline_pixmap; + GtkTooltips *online_tip; + GtkWidget *ac_button; + GtkWidget *ac_label; + + FolderView *folderview; + SummaryView *summaryview; + MessageView *messageview; + GdkColormap *colormap; + GdkColor color[3]; + gboolean success[3]; + guint n_menu_entries; + GtkItemFactory *ifactory; + GtkWidget *ac_menu; + GtkWidget *menuitem; + gint i; + + static GdkGeometry geometry; + + debug_print(_("Creating main window...\n")); + mainwin = g_new0(MainWindow, 1); + + /* main window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), PROG_VERSION); + gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE); + gtk_window_set_wmclass(GTK_WINDOW(window), "main_window", "Sylpheed"); + + if (!geometry.min_height) { + geometry.min_width = 320; + geometry.min_height = 200; + } + gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry, + GDK_HINT_MIN_SIZE); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(main_window_close_cb), mainwin); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + gtk_widget_realize(window); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + /* menu bar */ + n_menu_entries = sizeof(mainwin_entries) / sizeof(mainwin_entries[0]); + menubar = menubar_create(window, mainwin_entries, + n_menu_entries, "
", mainwin); + gtk_widget_show(menubar); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + ifactory = gtk_item_factory_from_widget(menubar); + + handlebox = gtk_handle_box_new(); + gtk_widget_show(handlebox); + gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(handlebox), "child_attached", + G_CALLBACK(toolbar_child_attached), mainwin); + g_signal_connect(G_OBJECT(handlebox), "child_detached", + G_CALLBACK(toolbar_child_detached), mainwin); + + main_window_toolbar_create(mainwin, handlebox); + + /* vbox that contains body */ + vbox_body = gtk_vbox_new(FALSE, BORDER_WIDTH); + gtk_widget_show(vbox_body); + gtk_container_set_border_width(GTK_CONTAINER(vbox_body), BORDER_WIDTH); + gtk_box_pack_start(GTK_BOX(vbox), vbox_body, TRUE, TRUE, 0); + + hbox_stat = gtk_hbox_new(FALSE, 2); + gtk_box_pack_end(GTK_BOX(vbox_body), hbox_stat, FALSE, FALSE, 0); + + statusbar = statusbar_create(); + gtk_box_pack_start(GTK_BOX(hbox_stat), statusbar, TRUE, TRUE, 0); + + progressbar = gtk_progress_bar_new(); + gtk_widget_set_size_request(progressbar, 120, 1); + gtk_box_pack_start(GTK_BOX(hbox_stat), progressbar, FALSE, FALSE, 0); + + statuslabel = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox_stat), statuslabel, FALSE, FALSE, 0); + + online_hbox = gtk_hbox_new(FALSE, 0); + + online_pixmap = stock_pixmap_widget(hbox_stat, STOCK_PIXMAP_ONLINE); + offline_pixmap = stock_pixmap_widget(hbox_stat, STOCK_PIXMAP_OFFLINE); + gtk_box_pack_start(GTK_BOX(online_hbox), online_pixmap, + FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(online_hbox), offline_pixmap, + FALSE, FALSE, 0); + + online_switch = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(online_switch), GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS(online_switch, GTK_CAN_FOCUS); + gtk_container_add(GTK_CONTAINER(online_switch), online_hbox); + g_signal_connect(G_OBJECT(online_switch), "clicked", + G_CALLBACK(online_switch_clicked), mainwin); + gtk_box_pack_start(GTK_BOX(hbox_stat), online_switch, FALSE, FALSE, 0); + + online_tip = gtk_tooltips_new(); + + ac_button = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(ac_button), GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS(ac_button, GTK_CAN_FOCUS); + gtk_widget_set_size_request(ac_button, -1, 1); + gtk_box_pack_end(GTK_BOX(hbox_stat), ac_button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(ac_button), "button_press_event", + G_CALLBACK(ac_label_button_pressed), mainwin); + + ac_label = gtk_label_new(""); + gtk_container_add(GTK_CONTAINER(ac_button), ac_label); + + gtk_widget_show_all(hbox_stat); + + /* create views */ + mainwin->folderview = folderview = folderview_create(); + mainwin->summaryview = summaryview = summary_create(); + mainwin->messageview = messageview = messageview_create(); + mainwin->logwin = log_window_create(); + + folderview->mainwin = mainwin; + folderview->summaryview = summaryview; + + summaryview->mainwin = mainwin; + summaryview->folderview = folderview; + summaryview->messageview = messageview; + summaryview->window = window; + + messageview->statusbar = statusbar; + messageview->mainwin = mainwin; + + mainwin->window = window; + mainwin->vbox = vbox; + mainwin->menubar = menubar; + mainwin->menu_factory = ifactory; + mainwin->handlebox = handlebox; + mainwin->vbox_body = vbox_body; + mainwin->hbox_stat = hbox_stat; + mainwin->statusbar = statusbar; + mainwin->progressbar = progressbar; + mainwin->statuslabel = statuslabel; + mainwin->online_switch = online_switch; + mainwin->online_pixmap = online_pixmap; + mainwin->offline_pixmap = offline_pixmap; + mainwin->online_tip = online_tip; + mainwin->ac_button = ac_button; + mainwin->ac_label = ac_label; + + /* set context IDs for status bar */ + mainwin->mainwin_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR(statusbar), "Main Window"); + mainwin->folderview_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR(statusbar), "Folder View"); + mainwin->summaryview_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR(statusbar), "Summary View"); + mainwin->messageview_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR(statusbar), "Message View"); + + messageview->statusbar_cid = mainwin->messageview_cid; + + /* allocate colors for summary view and folder view */ + summaryview->color_marked.red = summaryview->color_marked.green = 0; + summaryview->color_marked.blue = (guint16)65535; + + summaryview->color_dim.red = summaryview->color_dim.green = + summaryview->color_dim.blue = COLOR_DIM; + + folderview->color_new.red = (guint16)55000; + folderview->color_new.green = folderview->color_new.blue = 15000; + + folderview->color_noselect.red = folderview->color_noselect.green = + folderview->color_noselect.blue = COLOR_DIM; + + color[0] = summaryview->color_marked; + color[1] = summaryview->color_dim; + color[2] = folderview->color_new; + + colormap = gdk_window_get_colormap(window->window); + gdk_colormap_alloc_colors(colormap, color, 3, FALSE, TRUE, success); + for (i = 0; i < 3; i++) { + if (success[i] == FALSE) + g_warning(_("MainWindow: color allocation %d failed\n"), i); + } + + messageview->visible = prefs_common.msgview_visible; + + main_window_set_widgets(mainwin, type); + + g_signal_connect(G_OBJECT(window), "size_allocate", + G_CALLBACK(main_window_size_allocate_cb), mainwin); + + /* set menu items */ + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Code set/Auto detect"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); + + switch (prefs_common.toolbar_style) { + case TOOLBAR_NONE: + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Toolbar/None"); + break; + case TOOLBAR_ICON: + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Toolbar/Icon"); + break; + case TOOLBAR_TEXT: + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Toolbar/Text"); + break; + case TOOLBAR_BOTH: + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Toolbar/Icon and text"); + } + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE); + + gtk_widget_hide(mainwin->hbox_stat); + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Status bar"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + prefs_common.show_statusbar); + + /* set account selection menu */ + ac_menu = gtk_item_factory_get_widget + (ifactory, "/Configuration/Change current account"); + g_signal_connect(G_OBJECT(ac_menu), "selection_done", + G_CALLBACK(ac_menu_popup_closed), mainwin); + mainwin->ac_menu = ac_menu; + + main_window_set_toolbar_sensitive(mainwin); + + /* create actions menu */ + action_update_mainwin_menu(ifactory, mainwin); + + /* initialize online switch */ + prefs_common.online_mode = !prefs_common.online_mode; + online_switch_clicked(online_switch, mainwin); + + /* show main window */ + gtk_widget_show(mainwin->window); + + /* initialize views */ + folderview_init(folderview); + summary_init(summaryview); + messageview_init(messageview); + log_window_init(mainwin->logwin); + + mainwin->lock_count = 0; + mainwin->menu_lock_count = 0; + mainwin->cursor_count = 0; + + if (!watch_cursor) + watch_cursor = gdk_cursor_new(GDK_WATCH); + + mainwin_list = g_list_append(mainwin_list, mainwin); + + debug_print(_("done.\n")); + + return mainwin; +} + +void main_window_cursor_wait(MainWindow *mainwin) +{ + + if (mainwin->cursor_count == 0) + gdk_window_set_cursor(mainwin->window->window, watch_cursor); + + mainwin->cursor_count++; + + gdk_flush(); +} + +void main_window_cursor_normal(MainWindow *mainwin) +{ + if (mainwin->cursor_count) + mainwin->cursor_count--; + + if (mainwin->cursor_count == 0) + gdk_window_set_cursor(mainwin->window->window, NULL); + + gdk_flush(); +} + +/* lock / unlock the user-interface */ +void main_window_lock(MainWindow *mainwin) +{ + if (mainwin->lock_count == 0) + gtk_widget_set_sensitive(mainwin->ac_button, FALSE); + + mainwin->lock_count++; + + main_window_set_menu_sensitive(mainwin); + main_window_set_toolbar_sensitive(mainwin); +} + +void main_window_unlock(MainWindow *mainwin) +{ + if (mainwin->lock_count) + mainwin->lock_count--; + + main_window_set_menu_sensitive(mainwin); + main_window_set_toolbar_sensitive(mainwin); + + if (mainwin->lock_count == 0) + gtk_widget_set_sensitive(mainwin->ac_button, TRUE); +} + +static void main_window_menu_callback_block(MainWindow *mainwin) +{ + mainwin->menu_lock_count++; +} + +static void main_window_menu_callback_unblock(MainWindow *mainwin) +{ + if (mainwin->menu_lock_count) + mainwin->menu_lock_count--; +} + +void main_window_reflect_prefs_all(void) +{ + GList *cur; + MainWindow *mainwin; + + for (cur = mainwin_list; cur != NULL; cur = cur->next) { + mainwin = (MainWindow *)cur->data; + + main_window_show_cur_account(mainwin); + main_window_set_menu_sensitive(mainwin); + main_window_set_toolbar_sensitive(mainwin); + + if (prefs_common.immediate_exec) + gtk_widget_hide(mainwin->exec_btn); + else + gtk_widget_show(mainwin->exec_btn); + + summary_redisplay_msg(mainwin->summaryview); + headerview_set_visibility(mainwin->messageview->headerview, + prefs_common.display_header_pane); + } +} + +void main_window_set_summary_column(void) +{ + GList *cur; + MainWindow *mainwin; + + for (cur = mainwin_list; cur != NULL; cur = cur->next) { + mainwin = (MainWindow *)cur->data; + summary_set_column_order(mainwin->summaryview); + } +} + +static void main_window_set_account_selector_menu(MainWindow *mainwin, + GList *account_list) +{ + GList *cur_ac, *cur_item; + GtkWidget *menuitem; + PrefsAccount *ac_prefs; + + /* destroy all previous menu item */ + cur_item = GTK_MENU_SHELL(mainwin->ac_menu)->children; + while (cur_item != NULL) { + GList *next = cur_item->next; + gtk_widget_destroy(GTK_WIDGET(cur_item->data)); + cur_item = next; + } + + for (cur_ac = account_list; cur_ac != NULL; cur_ac = cur_ac->next) { + ac_prefs = (PrefsAccount *)cur_ac->data; + + menuitem = gtk_menu_item_new_with_label + (ac_prefs->account_name + ? ac_prefs->account_name : _("Untitled")); + gtk_widget_show(menuitem); + gtk_menu_append(GTK_MENU(mainwin->ac_menu), menuitem); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(account_selector_menu_cb), + ac_prefs); + } +} + +static void main_window_set_account_receive_menu(MainWindow *mainwin, + GList *account_list) +{ + GList *cur_ac, *cur_item; + GtkWidget *menu; + GtkWidget *menuitem; + PrefsAccount *ac_prefs; + + menu = gtk_item_factory_get_widget(mainwin->menu_factory, + "/Message/Receive"); + + /* search for separator */ + for (cur_item = GTK_MENU_SHELL(menu)->children; cur_item != NULL; + cur_item = cur_item->next) { + if (GTK_BIN(cur_item->data)->child == NULL) { + cur_item = cur_item->next; + break; + } + } + + /* destroy all previous menu item */ + while (cur_item != NULL) { + GList *next = cur_item->next; + gtk_widget_destroy(GTK_WIDGET(cur_item->data)); + cur_item = next; + } + + for (cur_ac = account_list; cur_ac != NULL; cur_ac = cur_ac->next) { + ac_prefs = (PrefsAccount *)cur_ac->data; + + menuitem = gtk_menu_item_new_with_label + (ac_prefs->account_name ? ac_prefs->account_name + : _("Untitled")); + gtk_widget_show(menuitem); + gtk_menu_append(GTK_MENU(menu), menuitem); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(account_receive_menu_cb), + ac_prefs); + } +} + +void main_window_set_account_menu(GList *account_list) +{ + GList *cur; + MainWindow *mainwin; + + for (cur = mainwin_list; cur != NULL; cur = cur->next) { + mainwin = (MainWindow *)cur->data; + main_window_set_account_selector_menu(mainwin, account_list); + main_window_set_account_receive_menu(mainwin, account_list); + } +} + +static void main_window_show_cur_account(MainWindow *mainwin) +{ + gchar *buf; + gchar *ac_name; + + ac_name = g_strdup(cur_account + ? (cur_account->account_name + ? cur_account->account_name : _("Untitled")) + : _("none")); + + if (cur_account) + buf = g_strdup_printf("%s - %s", ac_name, PROG_VERSION); + else + buf = g_strdup(PROG_VERSION); + gtk_window_set_title(GTK_WINDOW(mainwin->window), buf); + g_free(buf); + + gtk_label_set_text(GTK_LABEL(mainwin->ac_label), ac_name); + gtk_widget_queue_resize(mainwin->ac_button); + + g_free(ac_name); +} + +MainWindow *main_window_get(void) +{ + return (MainWindow *)mainwin_list->data; +} + +GtkWidget *main_window_get_folder_window(MainWindow *mainwin) +{ + switch (mainwin->type) { + case SEPARATE_FOLDER: + return mainwin->win.sep_folder.folderwin; + case SEPARATE_BOTH: + return mainwin->win.sep_both.folderwin; + default: + return NULL; + } +} + +GtkWidget *main_window_get_message_window(MainWindow *mainwin) +{ + switch (mainwin->type) { + case SEPARATE_MESSAGE: + return mainwin->win.sep_message.messagewin; + case SEPARATE_BOTH: + return mainwin->win.sep_both.messagewin; + default: + return NULL; + } +} + +void main_window_separation_change(MainWindow *mainwin, SeparateType type) +{ + GtkWidget *folder_wid = GTK_WIDGET_PTR(mainwin->folderview); + GtkWidget *summary_wid = GTK_WIDGET_PTR(mainwin->summaryview); + GtkWidget *message_wid = GTK_WIDGET_PTR(mainwin->messageview); + + debug_print(_("Changing window separation type from %d to %d\n"), + mainwin->type, type); + + if (mainwin->type == type) return; + + /* remove widgets from those containers */ + gtk_widget_ref(folder_wid); + gtk_widget_ref(summary_wid); + gtk_widget_ref(message_wid); + gtkut_container_remove + (GTK_CONTAINER(folder_wid->parent), folder_wid); + gtkut_container_remove + (GTK_CONTAINER(summary_wid->parent), summary_wid); + gtkut_container_remove + (GTK_CONTAINER(message_wid->parent), message_wid); + + /* clean containers */ + switch (mainwin->type) { + case SEPARATE_NONE: + gtk_widget_destroy(mainwin->win.sep_none.hpaned); + break; + case SEPARATE_FOLDER: + gtk_widget_destroy(mainwin->win.sep_folder.vpaned); + gtk_widget_destroy(mainwin->win.sep_folder.folderwin); + break; + case SEPARATE_MESSAGE: + gtk_widget_destroy(mainwin->win.sep_message.hpaned); + gtk_widget_destroy(mainwin->win.sep_message.messagewin); + break; + case SEPARATE_BOTH: + gtk_widget_destroy(mainwin->win.sep_both.messagewin); + gtk_widget_destroy(mainwin->win.sep_both.folderwin); + break; + } + + gtk_widget_hide(mainwin->window); + main_window_set_widgets(mainwin, type); + gtk_widget_show(mainwin->window); + + gtk_widget_unref(folder_wid); + gtk_widget_unref(summary_wid); + gtk_widget_unref(message_wid); +} + +void main_window_toggle_message_view(MainWindow *mainwin) +{ + SummaryView *summaryview = mainwin->summaryview; + union CompositeWin *cwin = &mainwin->win; + GtkWidget *vpaned = NULL; + GtkWidget *container = NULL; + GtkWidget *msgwin = NULL; + + switch (mainwin->type) { + case SEPARATE_NONE: + vpaned = cwin->sep_none.vpaned; + container = cwin->sep_none.hpaned; + break; + case SEPARATE_FOLDER: + vpaned = cwin->sep_folder.vpaned; + container = mainwin->vbox_body; + break; + case SEPARATE_MESSAGE: + msgwin = mainwin->win.sep_message.messagewin; + break; + case SEPARATE_BOTH: + msgwin = mainwin->win.sep_both.messagewin; + break; + } + + if (msgwin) { + if (GTK_WIDGET_VISIBLE(msgwin)) { + gtk_widget_hide(msgwin); + mainwin->messageview->visible = FALSE; + summaryview->displayed = NULL; + } else { + gtk_widget_show(msgwin); + mainwin->messageview->visible = TRUE; + } + } else if (vpaned->parent != NULL) { + mainwin->messageview->visible = FALSE; + summaryview->displayed = NULL; + gtk_widget_ref(vpaned); + gtkut_container_remove(GTK_CONTAINER(container), vpaned); + gtk_widget_reparent(GTK_WIDGET_PTR(summaryview), container); + gtk_arrow_set(GTK_ARROW(summaryview->toggle_arrow), + GTK_ARROW_UP, GTK_SHADOW_OUT); + } else { + mainwin->messageview->visible = TRUE; + gtk_widget_reparent(GTK_WIDGET_PTR(summaryview), vpaned); + gtk_container_add(GTK_CONTAINER(container), vpaned); + gtk_widget_unref(vpaned); + gtk_arrow_set(GTK_ARROW(summaryview->toggle_arrow), + GTK_ARROW_DOWN, GTK_SHADOW_OUT); + } + + if (mainwin->messageview->visible == FALSE) + messageview_clear(mainwin->messageview); + + main_window_set_menu_sensitive(mainwin); + + prefs_common.msgview_visible = mainwin->messageview->visible; + + gtk_widget_grab_focus(summaryview->ctree); +} + +void main_window_get_size(MainWindow *mainwin) +{ + GtkAllocation *allocation; + + allocation = &(GTK_WIDGET_PTR(mainwin->summaryview)->allocation); + + if (allocation->width > 1 && allocation->height > 1) { + prefs_common.summaryview_width = allocation->width; + + if ((mainwin->type == SEPARATE_NONE || + mainwin->type == SEPARATE_FOLDER) && + messageview_is_visible(mainwin->messageview)) + prefs_common.summaryview_height = allocation->height; + + prefs_common.mainview_width = allocation->width; + } + + allocation = &mainwin->window->allocation; + if (allocation->width > 1 && allocation->height > 1) { + prefs_common.mainview_height = allocation->height; + prefs_common.mainwin_width = allocation->width; + prefs_common.mainwin_height = allocation->height; + } + + allocation = &(GTK_WIDGET_PTR(mainwin->folderview)->allocation); + if (allocation->width > 1 && allocation->height > 1) { + prefs_common.folderview_width = allocation->width; + prefs_common.folderview_height = allocation->height; + } + + allocation = &(GTK_WIDGET_PTR(mainwin->messageview)->allocation); + if (allocation->width > 1 && allocation->height > 1) { + prefs_common.msgview_width = allocation->width; + prefs_common.msgview_height = allocation->height; + } + +#if 0 + debug_print("summaryview size: %d x %d\n", + prefs_common.summaryview_width, + prefs_common.summaryview_height); + debug_print("folderview size: %d x %d\n", + prefs_common.folderview_width, + prefs_common.folderview_height); + debug_print("messageview size: %d x %d\n", + prefs_common.msgview_width, + prefs_common.msgview_height); +#endif +} + +void main_window_get_position(MainWindow *mainwin) +{ + gint x, y; + GtkWidget *window; + + gtkut_widget_get_uposition(mainwin->window, &x, &y); + + prefs_common.mainview_x = x; + prefs_common.mainview_y = y; + prefs_common.mainwin_x = x; + prefs_common.mainwin_y = y; + + debug_print("main window position: %d, %d\n", x, y); + + window = main_window_get_folder_window(mainwin); + if (window) { + gtkut_widget_get_uposition(window, &x, &y); + prefs_common.folderwin_x = x; + prefs_common.folderwin_y = y; + debug_print("folder window position: %d, %d\n", x, y); + } + window = main_window_get_message_window(mainwin); + if (window) { + gtkut_widget_get_uposition(window, &x, &y); + prefs_common.main_msgwin_x = x; + prefs_common.main_msgwin_y = y; + debug_print("message window position: %d, %d\n", x, y); + } +} + +void main_window_progress_on(MainWindow *mainwin) +{ + gtk_progress_set_show_text(GTK_PROGRESS(mainwin->progressbar), TRUE); + gtk_progress_set_format_string(GTK_PROGRESS(mainwin->progressbar), ""); +} + +void main_window_progress_off(MainWindow *mainwin) +{ + gtk_progress_set_show_text(GTK_PROGRESS(mainwin->progressbar), FALSE); + gtk_progress_bar_update(GTK_PROGRESS_BAR(mainwin->progressbar), 0.0); + gtk_progress_set_format_string(GTK_PROGRESS(mainwin->progressbar), ""); +} + +void main_window_progress_set(MainWindow *mainwin, gint cur, gint total) +{ + gchar buf[32]; + + g_snprintf(buf, sizeof(buf), "%d / %d", cur, total); + gtk_progress_set_format_string(GTK_PROGRESS(mainwin->progressbar), buf); + gtk_progress_bar_update(GTK_PROGRESS_BAR(mainwin->progressbar), + (cur == 0 && total == 0) ? 0 : + (gfloat)cur / (gfloat)total); +} + +void main_window_toggle_online(MainWindow *mainwin, gboolean online) +{ + if (prefs_common.online_mode != online) + online_switch_clicked(mainwin->online_switch, mainwin); +} + +gboolean main_window_toggle_online_if_offline(MainWindow *mainwin) +{ + if (!prefs_common.online_mode) { + if (alertpanel(_("Offline"), + _("You are offline. Go online?"), + _("Yes"), _("No"), NULL) == G_ALERTDEFAULT) + main_window_toggle_online(mainwin, TRUE); + } + + return prefs_common.online_mode; +} + +void main_window_empty_trash(MainWindow *mainwin, gboolean confirm) +{ + GList *list; + + if (confirm) { + if (alertpanel(_("Empty all trash"), + _("Empty messages in all trash?"), + _("Yes"), _("No"), NULL) != G_ALERTDEFAULT) + return; + manage_window_focus_in(mainwin->window, NULL, NULL); + } + + procmsg_empty_all_trash(); + statusbar_pop_all(); + + for (list = folder_get_list(); list != NULL; list = list->next) { + Folder *folder; + + folder = list->data; + if (folder->trash) + folderview_update_item(folder->trash, TRUE); + } + + if (mainwin->summaryview->folder_item && + mainwin->summaryview->folder_item->stype == F_TRASH) + gtk_widget_grab_focus(mainwin->folderview->ctree); +} + +void main_window_add_mailbox(MainWindow *mainwin) +{ + gchar *path; + Folder *folder; + + path = input_dialog(_("Add mailbox"), + _("Input the location of mailbox.\n" + "If the existing mailbox is specified, it will be\n" + "scanned automatically."), + "Mail"); + if (!path) return; + if (folder_find_from_path(path)) { + alertpanel_error(_("The mailbox `%s' already exists."), path); + g_free(path); + return; + } + if (!strcmp(path, "Mail")) + folder = folder_new(F_MH, _("Mailbox"), path); + else + folder = folder_new(F_MH, g_basename(path), path); + g_free(path); + + if (folder->klass->create_tree(folder) < 0) { + alertpanel_error(_("Creation of the mailbox failed.\n" + "Maybe some files already exist, or you don't have the permission to write there.")); + folder_destroy(folder); + return; + } + + folder_add(folder); + folder_set_ui_func(folder, scan_tree_func, mainwin); + folder->klass->scan_tree(folder); + folder_set_ui_func(folder, NULL, NULL); + + folderview_set(mainwin->folderview); +} + +typedef enum +{ + M_UNLOCKED = 1 << 0, + M_MSG_EXIST = 1 << 1, + M_TARGET_EXIST = 1 << 2, + M_SINGLE_TARGET_EXIST = 1 << 3, + M_EXEC = 1 << 4, + M_ALLOW_REEDIT = 1 << 5, + M_HAVE_ACCOUNT = 1 << 6, + M_THREADED = 1 << 7, + M_UNTHREADED = 1 << 8, + M_ALLOW_DELETE = 1 << 9, + M_INC_ACTIVE = 1 << 10, + + M_FOLDER_NEWOK = 1 << 11, + M_FOLDER_RENOK = 1 << 12, + M_FOLDER_DELOK = 1 << 13, + M_MBOX_ADDOK = 1 << 14, + M_MBOX_RMOK = 1 << 15, + M_MBOX_CHKOK = 1 << 16, + M_MBOX_CHKALLOK = 1 << 17, + M_MBOX_REBUILDOK = 1 << 18 +} SensitiveCond; + +static SensitiveCond main_window_get_current_state(MainWindow *mainwin) +{ + SensitiveCond state = 0; + SummarySelection selection; + FolderItem *item = mainwin->summaryview->folder_item; + + selection = summary_get_selection_type(mainwin->summaryview); + + if (mainwin->lock_count == 0) + state |= M_UNLOCKED; + if (selection != SUMMARY_NONE) + state |= M_MSG_EXIST; + if (item && item->path && item->parent && !item->no_select) { + state |= M_EXEC; + if (item->threaded) + state |= M_THREADED; + else + state |= M_UNTHREADED; + if (FOLDER_TYPE(item->folder) != F_NEWS) + state |= M_ALLOW_DELETE; + } + if (selection == SUMMARY_SELECTED_SINGLE || + selection == SUMMARY_SELECTED_MULTIPLE) + state |= M_TARGET_EXIST; + if (selection == SUMMARY_SELECTED_SINGLE) + state |= M_SINGLE_TARGET_EXIST; + if (selection == SUMMARY_SELECTED_SINGLE && + (item && + (item->stype == F_OUTBOX || item->stype == F_DRAFT || + item->stype == F_QUEUE))) + state |= M_ALLOW_REEDIT; + if (cur_account) + state |= M_HAVE_ACCOUNT; + + if (inc_is_active()) + state |= M_INC_ACTIVE; + + item = folderview_get_selected_item(mainwin->folderview); + if (item) { + state |= M_FOLDER_NEWOK; + if (item->parent == NULL) { + state |= M_MBOX_RMOK; + state |= M_MBOX_CHKOK; + } + if (FOLDER_IS_LOCAL(item->folder) || + FOLDER_TYPE(item->folder) == F_IMAP) { + if (item->parent == NULL) + state |= M_MBOX_REBUILDOK; + else if (item->stype == F_NORMAL) { + state |= M_FOLDER_RENOK; + state |= M_FOLDER_DELOK; + } + } else if (FOLDER_TYPE(item->folder) == F_NEWS) { + if (item->parent != NULL) + state |= M_FOLDER_DELOK; + } + } + state |= M_MBOX_ADDOK; + state |= M_MBOX_CHKALLOK; + + return state; +} + +void main_window_set_toolbar_sensitive(MainWindow *mainwin) +{ + SensitiveCond state; + gboolean sensitive; + gint i = 0; + + struct { + GtkWidget *widget; + SensitiveCond cond; + } entry[12]; + +#define SET_WIDGET_COND(w, c) \ +{ \ + entry[i].widget = w; \ + entry[i].cond = c; \ + i++; \ +} + + SET_WIDGET_COND(mainwin->get_btn, M_HAVE_ACCOUNT|M_UNLOCKED); + SET_WIDGET_COND(mainwin->getall_btn, M_HAVE_ACCOUNT|M_UNLOCKED); + SET_WIDGET_COND(mainwin->compose_btn, M_HAVE_ACCOUNT); + SET_WIDGET_COND(mainwin->reply_btn, + M_HAVE_ACCOUNT|M_SINGLE_TARGET_EXIST); + SET_WIDGET_COND(GTK_WIDGET_PTR(mainwin->reply_combo), + M_HAVE_ACCOUNT|M_SINGLE_TARGET_EXIST); + SET_WIDGET_COND(mainwin->replyall_btn, + M_HAVE_ACCOUNT|M_SINGLE_TARGET_EXIST); + SET_WIDGET_COND(mainwin->fwd_btn, M_HAVE_ACCOUNT|M_TARGET_EXIST); + SET_WIDGET_COND(GTK_WIDGET_PTR(mainwin->fwd_combo), + M_HAVE_ACCOUNT|M_TARGET_EXIST); +#if 0 + SET_WIDGET_COND(mainwin->prefs_btn, M_UNLOCKED); + SET_WIDGET_COND(mainwin->account_btn, M_UNLOCKED); +#endif + SET_WIDGET_COND(mainwin->next_btn, M_MSG_EXIST); + SET_WIDGET_COND(mainwin->delete_btn, + M_TARGET_EXIST|M_ALLOW_DELETE); + SET_WIDGET_COND(mainwin->exec_btn, M_MSG_EXIST|M_EXEC); + SET_WIDGET_COND(NULL, 0); + +#undef SET_WIDGET_COND + + state = main_window_get_current_state(mainwin); + + for (i = 0; entry[i].widget != NULL; i++) { + sensitive = ((entry[i].cond & state) == entry[i].cond); + gtk_widget_set_sensitive(entry[i].widget, sensitive); + } +} + +void main_window_set_menu_sensitive(MainWindow *mainwin) +{ + GtkItemFactory *ifactory = mainwin->menu_factory; + SensitiveCond state; + gboolean sensitive; + GtkWidget *menu; + GtkWidget *menuitem; + FolderItem *item; + gchar *menu_path; + gint i; + GList *cur_item; + + static const struct { + gchar *const entry; + SensitiveCond cond; + } entry[] = { + {"/File/Folder/Create new folder...", M_UNLOCKED|M_FOLDER_NEWOK}, + {"/File/Folder/Rename folder..." , M_UNLOCKED|M_FOLDER_RENOK}, + {"/File/Folder/Delete folder" , M_UNLOCKED|M_FOLDER_DELOK}, + {"/File/Mailbox/Add mailbox..." , M_UNLOCKED|M_MBOX_ADDOK}, + {"/File/Mailbox/Remove mailbox" , M_UNLOCKED|M_MBOX_RMOK}, + {"/File/Mailbox/Check for new messages" + , M_UNLOCKED|M_MBOX_CHKOK}, + {"/File/Mailbox/Check for new messages in all mailboxes" + , M_UNLOCKED|M_MBOX_CHKALLOK}, + {"/File/Mailbox/Rebuild folder tree", M_UNLOCKED|M_MBOX_REBUILDOK}, + {"/File/Import mbox file..." , M_UNLOCKED}, + {"/File/Export to mbox file..." , M_UNLOCKED}, + {"/File/Empty all trash" , M_UNLOCKED}, + + {"/File/Save as..." , M_SINGLE_TARGET_EXIST}, + {"/File/Print..." , M_TARGET_EXIST}, + {"/File/Work offline", M_UNLOCKED}, + /* {"/File/Close" , M_UNLOCKED}, */ + {"/File/Exit" , M_UNLOCKED}, + + {"/Edit/Select thread" , M_SINGLE_TARGET_EXIST}, + + {"/View/Sort" , M_EXEC}, + {"/View/Thread view" , M_EXEC}, + {"/View/Expand all threads" , M_MSG_EXIST}, + {"/View/Collapse all threads" , M_MSG_EXIST}, + {"/View/Go to/Prev message" , M_MSG_EXIST}, + {"/View/Go to/Next message" , M_MSG_EXIST}, + {"/View/Go to/Prev unread message" , M_MSG_EXIST}, + {"/View/Go to/Next unread message" , M_MSG_EXIST}, + {"/View/Go to/Prev new message" , M_MSG_EXIST}, + {"/View/Go to/Next new message" , M_MSG_EXIST}, + {"/View/Go to/Prev marked message" , M_MSG_EXIST}, + {"/View/Go to/Next marked message" , M_MSG_EXIST}, + {"/View/Go to/Prev labeled message", M_MSG_EXIST}, + {"/View/Go to/Next labeled message", M_MSG_EXIST}, + {"/View/Open in new window" , M_SINGLE_TARGET_EXIST}, + {"/View/Show all header" , M_SINGLE_TARGET_EXIST}, + {"/View/Message source" , M_SINGLE_TARGET_EXIST}, + + {"/Message/Receive/Get from current account" + , M_HAVE_ACCOUNT|M_UNLOCKED}, + {"/Message/Receive/Get from all accounts" + , M_HAVE_ACCOUNT|M_UNLOCKED}, + {"/Message/Receive/Cancel receiving" + , M_INC_ACTIVE}, + + {"/Message/Compose new message" , M_HAVE_ACCOUNT}, + {"/Message/Reply" , M_HAVE_ACCOUNT|M_SINGLE_TARGET_EXIST}, + {"/Message/Reply to" , M_HAVE_ACCOUNT|M_SINGLE_TARGET_EXIST}, + {"/Message/Forward" , M_HAVE_ACCOUNT|M_TARGET_EXIST}, + {"/Message/Forward as attachment", M_HAVE_ACCOUNT|M_TARGET_EXIST}, + {"/Message/Redirect" , M_HAVE_ACCOUNT|M_SINGLE_TARGET_EXIST}, + {"/Message/Move..." , M_TARGET_EXIST|M_ALLOW_DELETE}, + {"/Message/Copy..." , M_TARGET_EXIST|M_EXEC}, + {"/Message/Delete" , M_TARGET_EXIST|M_ALLOW_DELETE}, + {"/Message/Mark" , M_TARGET_EXIST}, + {"/Message/Re-edit" , M_HAVE_ACCOUNT|M_ALLOW_REEDIT}, + + {"/Tools/Add sender to address book" , M_SINGLE_TARGET_EXIST}, + {"/Tools/Filter all messages in folder", M_MSG_EXIST|M_EXEC}, + {"/Tools/Filter selected messages" , M_TARGET_EXIST|M_EXEC}, + {"/Tools/Create filter rule" , M_SINGLE_TARGET_EXIST|M_UNLOCKED}, + {"/Tools/Actions" , M_TARGET_EXIST|M_UNLOCKED}, + {"/Tools/Execute" , M_MSG_EXIST|M_EXEC}, + {"/Tools/Delete duplicated messages" , M_MSG_EXIST|M_ALLOW_DELETE}, + + {"/Configuration", M_UNLOCKED}, + + {NULL, 0} + }; + + state = main_window_get_current_state(mainwin); + + for (i = 0; entry[i].entry != NULL; i++) { + sensitive = ((entry[i].cond & state) == entry[i].cond); + menu_set_sensitive(ifactory, entry[i].entry, sensitive); + } + + menu = gtk_item_factory_get_widget(ifactory, "/Message/Receive"); + + /* search for separator */ + for (cur_item = GTK_MENU_SHELL(menu)->children; cur_item != NULL; + cur_item = cur_item->next) { + if (GTK_BIN(cur_item->data)->child == NULL) { + cur_item = cur_item->next; + break; + } + } + + for (; cur_item != NULL; cur_item = cur_item->next) { + gtk_widget_set_sensitive(GTK_WIDGET(cur_item->data), + (M_UNLOCKED & state) != 0); + } + + main_window_menu_callback_block(mainwin); + +#define SET_CHECK_MENU_ACTIVE(path, active) \ +{ \ + menuitem = gtk_item_factory_get_widget(ifactory, path); \ + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), active); \ +} + + SET_CHECK_MENU_ACTIVE("/View/Show or hide/Message view", + messageview_is_visible(mainwin->messageview)); + + item = mainwin->summaryview->folder_item; + menu_path = "/View/Sort/Don't sort"; + if (item) { + switch (item->sort_key) { + case SORT_BY_NUMBER: + menu_path = "/View/Sort/by number"; break; + case SORT_BY_SIZE: + menu_path = "/View/Sort/by size"; break; + case SORT_BY_DATE: + menu_path = "/View/Sort/by date"; break; + case SORT_BY_FROM: + menu_path = "/View/Sort/by from"; break; + case SORT_BY_TO: + menu_path = "/View/Sort/by recipient"; break; + case SORT_BY_SUBJECT: + menu_path = "/View/Sort/by subject"; break; + case SORT_BY_LABEL: + menu_path = "/View/Sort/by color label"; break; + case SORT_BY_MARK: + menu_path = "/View/Sort/by mark"; break; + case SORT_BY_UNREAD: + menu_path = "/View/Sort/by unread"; break; + case SORT_BY_MIME: + menu_path = "/View/Sort/by attachment"; break; + case SORT_BY_NONE: + default: + menu_path = "/View/Sort/Don't sort"; break; + } + } + SET_CHECK_MENU_ACTIVE(menu_path, TRUE); + + if (!item || item->sort_type == SORT_ASCENDING) { + SET_CHECK_MENU_ACTIVE("/View/Sort/Ascending", TRUE); + } else { + SET_CHECK_MENU_ACTIVE("/View/Sort/Descending", TRUE); + } + + if (item && item->sort_key != SORT_BY_NONE) { + menu_set_sensitive(ifactory, "/View/Sort/Ascending", TRUE); + menu_set_sensitive(ifactory, "/View/Sort/Descending", TRUE); + } else { + menu_set_sensitive(ifactory, "/View/Sort/Ascending", FALSE); + menu_set_sensitive(ifactory, "/View/Sort/Descending", FALSE); + } + + SET_CHECK_MENU_ACTIVE("/View/Show all header", + mainwin->messageview->textview->show_all_headers); + SET_CHECK_MENU_ACTIVE("/View/Thread view", (state & M_THREADED) != 0); + +#undef SET_CHECK_MENU_ACTIVE + + main_window_menu_callback_unblock(mainwin); +} + +void main_window_popup(MainWindow *mainwin) +{ + gtkut_window_popup(mainwin->window); + + switch (mainwin->type) { + case SEPARATE_FOLDER: + gtkut_window_popup(mainwin->win.sep_folder.folderwin); + break; + case SEPARATE_MESSAGE: + gtkut_window_popup(mainwin->win.sep_message.messagewin); + break; + case SEPARATE_BOTH: + gtkut_window_popup(mainwin->win.sep_both.folderwin); + gtkut_window_popup(mainwin->win.sep_both.messagewin); + break; + default: + break; + } +} + +static void main_window_set_widgets(MainWindow *mainwin, SeparateType type) +{ + GtkWidget *folderwin = NULL; + GtkWidget *messagewin = NULL; + GtkWidget *hpaned; + GtkWidget *vpaned; + GtkWidget *vbox_body = mainwin->vbox_body; + GtkItemFactory *ifactory = mainwin->menu_factory; + GtkWidget *menuitem; + + debug_print("Setting widgets... "); + + /* create separated window(s) if needed */ + if (type & SEPARATE_FOLDER) { + folderwin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(folderwin), + _("Sylpheed - Folder View")); + gtk_window_set_wmclass(GTK_WINDOW(folderwin), + "folder_view", "Sylpheed"); + gtk_window_set_policy(GTK_WINDOW(folderwin), + TRUE, TRUE, FALSE); + gtk_widget_set_uposition(folderwin, prefs_common.folderwin_x, + prefs_common.folderwin_y); + gtk_container_set_border_width(GTK_CONTAINER(folderwin), + BORDER_WIDTH); + g_signal_connect(G_OBJECT(folderwin), "delete_event", + G_CALLBACK(folder_window_close_cb), mainwin); + gtk_container_add(GTK_CONTAINER(folderwin), + GTK_WIDGET_PTR(mainwin->folderview)); + gtk_widget_realize(folderwin); + if (prefs_common.folderview_visible) + gtk_widget_show(folderwin); + } + if (type & SEPARATE_MESSAGE) { + messagewin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(messagewin), + _("Sylpheed - Message View")); + gtk_window_set_wmclass(GTK_WINDOW(messagewin), + "message_view", "Sylpheed"); + gtk_window_set_policy(GTK_WINDOW(messagewin), + TRUE, TRUE, FALSE); + gtk_widget_set_uposition(messagewin, prefs_common.main_msgwin_x, + prefs_common.main_msgwin_y); + gtk_container_set_border_width(GTK_CONTAINER(messagewin), + BORDER_WIDTH); + g_signal_connect(G_OBJECT(messagewin), "delete_event", + G_CALLBACK(message_window_close_cb), mainwin); + gtk_container_add(GTK_CONTAINER(messagewin), + GTK_WIDGET_PTR(mainwin->messageview)); + gtk_widget_realize(messagewin); + if (messageview_is_visible(mainwin->messageview)) + gtk_widget_show(messagewin); + } + + gtk_widget_set_size_request(GTK_WIDGET_PTR(mainwin->folderview), + prefs_common.folderview_width, + prefs_common.folderview_height); + gtk_widget_set_size_request(GTK_WIDGET_PTR(mainwin->summaryview), + prefs_common.summaryview_width, + prefs_common.summaryview_height); + gtk_widget_set_size_request(GTK_WIDGET_PTR(mainwin->messageview), + prefs_common.msgview_width, + prefs_common.msgview_height); + + switch (type) { + case SEPARATE_NONE: + hpaned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox_body), hpaned, TRUE, TRUE, 0); + gtk_paned_add1(GTK_PANED(hpaned), + GTK_WIDGET_PTR(mainwin->folderview)); + gtk_widget_show(hpaned); + gtk_widget_queue_resize(hpaned); + + vpaned = gtk_vpaned_new(); + if (messageview_is_visible(mainwin->messageview)) { + gtk_paned_add2(GTK_PANED(hpaned), vpaned); + gtk_paned_add1(GTK_PANED(vpaned), + GTK_WIDGET_PTR(mainwin->summaryview)); + } else { + gtk_paned_add2(GTK_PANED(hpaned), + GTK_WIDGET_PTR(mainwin->summaryview)); + gtk_widget_ref(vpaned); + } + gtk_paned_add2(GTK_PANED(vpaned), + GTK_WIDGET_PTR(mainwin->messageview)); + gtk_widget_show(vpaned); + gtk_widget_queue_resize(vpaned); + + mainwin->win.sep_none.hpaned = hpaned; + mainwin->win.sep_none.vpaned = vpaned; + break; + case SEPARATE_FOLDER: + vpaned = gtk_vpaned_new(); + if (messageview_is_visible(mainwin->messageview)) { + gtk_box_pack_start(GTK_BOX(vbox_body), vpaned, + TRUE, TRUE, 0); + gtk_paned_add1(GTK_PANED(vpaned), + GTK_WIDGET_PTR(mainwin->summaryview)); + } else { + gtk_box_pack_start(GTK_BOX(vbox_body), + GTK_WIDGET_PTR(mainwin->summaryview), + TRUE, TRUE, 0); + gtk_widget_ref(vpaned); + } + gtk_paned_add2(GTK_PANED(vpaned), + GTK_WIDGET_PTR(mainwin->messageview)); + gtk_widget_show(vpaned); + gtk_widget_queue_resize(vpaned); + + mainwin->win.sep_folder.folderwin = folderwin; + mainwin->win.sep_folder.vpaned = vpaned; + + break; + case SEPARATE_MESSAGE: + hpaned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox_body), hpaned, TRUE, TRUE, 0); + gtk_paned_add1(GTK_PANED(hpaned), + GTK_WIDGET_PTR(mainwin->folderview)); + gtk_paned_add2(GTK_PANED(hpaned), + GTK_WIDGET_PTR(mainwin->summaryview)); + gtk_widget_show(hpaned); + gtk_widget_queue_resize(hpaned); + + mainwin->win.sep_message.messagewin = messagewin; + mainwin->win.sep_message.hpaned = hpaned; + + break; + case SEPARATE_BOTH: + gtk_box_pack_start(GTK_BOX(vbox_body), + GTK_WIDGET_PTR(mainwin->summaryview), + TRUE, TRUE, 0); + + mainwin->win.sep_both.folderwin = folderwin; + mainwin->win.sep_both.messagewin = messagewin; + + break; + } + + gtk_widget_set_uposition(mainwin->window, + prefs_common.mainwin_x, + prefs_common.mainwin_y); + + gtk_widget_queue_resize(vbox_body); + gtk_widget_queue_resize(mainwin->vbox); + gtk_widget_queue_resize(mainwin->window); + + mainwin->type = type; + + /* toggle menu state */ + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Folder tree"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + (type & SEPARATE_FOLDER) == 0 ? TRUE : + prefs_common.folderview_visible); + gtk_widget_set_sensitive(menuitem, ((type & SEPARATE_FOLDER) != 0)); + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Show or hide/Message view"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + (type & SEPARATE_MESSAGE) == 0 ? TRUE : + prefs_common.msgview_visible); + + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Separate folder tree"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + ((type & SEPARATE_FOLDER) != 0)); + menuitem = gtk_item_factory_get_item + (ifactory, "/View/Separate message view"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + ((type & SEPARATE_MESSAGE) != 0)); + + if (folderwin) { + g_signal_connect + (G_OBJECT(folderwin), "size_allocate", + G_CALLBACK(folder_window_size_allocate_cb), mainwin); + } + if (messagewin) { + g_signal_connect + (G_OBJECT(messagewin), "size_allocate", + G_CALLBACK(message_window_size_allocate_cb), mainwin); + } + + debug_print("done.\n"); +} + +static GtkItemFactoryEntry reply_entries[] = +{ + {N_("/_Reply"), NULL, reply_cb, COMPOSE_REPLY, NULL}, + {N_("/Reply to _all"), NULL, reply_cb, COMPOSE_REPLY_TO_ALL, NULL}, + {N_("/Reply to _sender"), NULL, reply_cb, COMPOSE_REPLY_TO_SENDER, NULL}, + {N_("/Reply to mailing _list"), NULL, reply_cb, COMPOSE_REPLY_TO_LIST, NULL} +}; + +static GtkItemFactoryEntry forward_entries[] = +{ + {N_("/_Forward"), NULL, reply_cb, COMPOSE_FORWARD, NULL}, + {N_("/For_ward as attachment"), NULL, reply_cb, COMPOSE_FORWARD_AS_ATTACH, NULL}, + {N_("/Redirec_t"), NULL, reply_cb, COMPOSE_REDIRECT, NULL} +}; + +static void main_window_toolbar_create(MainWindow *mainwin, + GtkWidget *container) +{ + GtkWidget *toolbar; + GtkWidget *icon_wid; + GtkWidget *get_btn; + GtkWidget *getall_btn; + GtkWidget *send_btn; + GtkWidget *compose_btn; + GtkWidget *reply_btn; + ComboButton *reply_combo; + GtkWidget *replyall_btn; + GtkWidget *fwd_btn; + ComboButton *fwd_combo; +#if 0 + GtkWidget *prefs_btn; + GtkWidget *account_btn; +#endif + GtkWidget *next_btn; + GtkWidget *delete_btn; + GtkWidget *exec_btn; + + gint n_entries; + + toolbar = gtk_toolbar_new(); + gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar), + GTK_ORIENTATION_HORIZONTAL); + gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH); + gtk_container_add(GTK_CONTAINER(container), toolbar); + gtk_container_set_border_width(GTK_CONTAINER(container), 2); + //gtk_toolbar_set_button_relief(GTK_TOOLBAR(toolbar), GTK_RELIEF_NONE); + //gtk_toolbar_set_space_style(GTK_TOOLBAR(toolbar), + // GTK_TOOLBAR_SPACE_LINE); + gtk_widget_set_size_request(toolbar, 1, -1); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_RECEIVE); + get_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Get"), + _("Incorporate new mail"), + "Get", + icon_wid, + G_CALLBACK(toolbar_inc_cb), + mainwin); + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_RECEIVE_ALL); + getall_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Get all"), + _("Incorporate new mail of all accounts"), + "Get all", + icon_wid, + G_CALLBACK(toolbar_inc_all_cb), + mainwin); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_SEND); + send_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Send"), + _("Send queued message(s)"), + "Send", + icon_wid, + G_CALLBACK(toolbar_send_cb), + mainwin); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_COMPOSE); + compose_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Compose"), + _("Compose new message"), + "New", + icon_wid, + G_CALLBACK(toolbar_compose_cb), + mainwin); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_REPLY); + reply_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Reply"), + _("Reply to the message"), + "Reply", + icon_wid, + G_CALLBACK(toolbar_reply_cb), + mainwin); + + n_entries = sizeof(reply_entries) / sizeof(reply_entries[0]); + reply_combo = gtkut_combo_button_create(reply_btn, + reply_entries, n_entries, + "", mainwin); + gtk_button_set_relief(GTK_BUTTON(reply_combo->arrow), GTK_RELIEF_NONE); + gtk_toolbar_append_widget(GTK_TOOLBAR(toolbar), + GTK_WIDGET_PTR(reply_combo), + _("Reply to the message"), "Reply"); + + icon_wid = stock_pixmap_widget + (container, STOCK_PIXMAP_MAIL_REPLY_TO_ALL); + replyall_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Reply all"), + _("Reply to all"), + "Reply to all", + icon_wid, + G_CALLBACK(toolbar_reply_to_all_cb), + mainwin); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_MAIL_FORWARD); + fwd_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Forward"), + _("Forward the message"), + "Fwd", + icon_wid, + G_CALLBACK(toolbar_forward_cb), + mainwin); + + n_entries = sizeof(forward_entries) / sizeof(forward_entries[0]); + fwd_combo = gtkut_combo_button_create(fwd_btn, + forward_entries, n_entries, + "", mainwin); + gtk_button_set_relief(GTK_BUTTON(fwd_combo->arrow), GTK_RELIEF_NONE); + gtk_toolbar_append_widget(GTK_TOOLBAR(toolbar), + GTK_WIDGET_PTR(fwd_combo), + _("Forward the message"), "Fwd"); + + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_CLOSE); + delete_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Delete"), + _("Delete the message"), + "Delete", + icon_wid, + G_CALLBACK(toolbar_delete_cb), + mainwin); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_EXEC); + exec_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Execute"), + _("Execute marked process"), + "Execute", + icon_wid, + G_CALLBACK(toolbar_exec_cb), + mainwin); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_DOWN_ARROW); + next_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Next"), + _("Next unread message"), + "Next unread", + icon_wid, + G_CALLBACK(toolbar_next_unread_cb), + mainwin); + +#if 0 + gtk_toolbar_append_space(GTK_TOOLBAR(toolbar)); + + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_PREFERENCES); + prefs_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Prefs"), + _("Common preferences"), + "Prefs", + icon_wid, + G_CALLBACK(toolbar_prefs_cb), + mainwin); + icon_wid = stock_pixmap_widget(container, STOCK_PIXMAP_PROPERTIES); + account_btn = gtk_toolbar_append_item(GTK_TOOLBAR(toolbar), + _("Account"), + _("Account setting"), + "Account", + icon_wid, + G_CALLBACK(toolbar_account_cb), + mainwin); + g_signal_connect(G_OBJECT(account_btn), "button_press_event", + G_CALLBACK(toolbar_account_button_pressed), mainwin); +#endif + + mainwin->toolbar = toolbar; + mainwin->get_btn = get_btn; + mainwin->getall_btn = getall_btn; + mainwin->compose_btn = compose_btn; + mainwin->reply_btn = reply_btn; + mainwin->reply_combo = reply_combo; + mainwin->replyall_btn = replyall_btn; + mainwin->fwd_btn = fwd_btn; + mainwin->fwd_combo = fwd_combo; + mainwin->send_btn = send_btn; +#if 0 + mainwin->prefs_btn = prefs_btn; + mainwin->account_btn = account_btn; +#endif + mainwin->next_btn = next_btn; + mainwin->delete_btn = delete_btn; + mainwin->exec_btn = exec_btn; + + gtk_widget_show_all(toolbar); +} + +/* callback functions */ + +static void toolbar_inc_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + inc_mail_cb(mainwin, 0, NULL); +} + +static void toolbar_inc_all_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + inc_all_account_mail_cb(mainwin, 0, NULL); +} + +static void toolbar_send_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + send_queue_cb(mainwin, 0, NULL); +} + +static void toolbar_compose_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + compose_cb(mainwin, 0, NULL); +} + +static void toolbar_reply_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + if (prefs_common.default_reply_list) + reply_cb(mainwin, COMPOSE_REPLY_TO_LIST, NULL); + else + reply_cb(mainwin, COMPOSE_REPLY, NULL); +} + +static void toolbar_reply_to_all_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + reply_cb(mainwin, COMPOSE_REPLY_TO_ALL, NULL); +} + +static void toolbar_forward_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + reply_cb(mainwin, COMPOSE_FORWARD, NULL); +} + +static void toolbar_delete_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + summary_delete(mainwin->summaryview); +} + +static void toolbar_exec_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + summary_execute(mainwin->summaryview); +} + +static void toolbar_next_unread_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + next_unread_cb(mainwin, 0, NULL); +} + +#if 0 +static void toolbar_prefs_cb (GtkWidget *widget, + gpointer data) +{ + prefs_common_open(); +} + +static void toolbar_account_cb (GtkWidget *widget, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + prefs_account_open_cb(mainwin, 0, NULL); +} + +static void toolbar_account_button_pressed(GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + if (!event) return; + if (event->button != 3) return; + + gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NORMAL); + g_object_set_data(G_OBJECT(mainwin->ac_menu), "menu_button", widget); + + gtk_menu_popup(GTK_MENU(mainwin->ac_menu), NULL, NULL, + menu_button_position, widget, + event->button, event->time); +} +#endif + +static void toolbar_child_attached(GtkWidget *widget, GtkWidget *child, + gpointer data) +{ + gtk_widget_set_size_request(child, 1, -1); +} + +static void toolbar_child_detached(GtkWidget *widget, GtkWidget *child, + gpointer data) +{ + gtk_widget_set_size_request(child, -1, -1); +} + +static void online_switch_clicked(GtkWidget *widget, gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + GtkWidget *menuitem; + + menuitem = gtk_item_factory_get_item(mainwin->menu_factory, + "/File/Work offline"); + + if (prefs_common.online_mode == TRUE) { + prefs_common.online_mode = FALSE; + gtk_widget_hide(mainwin->online_pixmap); + gtk_widget_show(mainwin->offline_pixmap); + gtk_tooltips_set_tip + (mainwin->online_tip, mainwin->online_switch, + _("You are offline. Click the icon to go online."), + NULL); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + TRUE); + inc_autocheck_timer_remove(); + } else { + prefs_common.online_mode = TRUE; + gtk_widget_hide(mainwin->offline_pixmap); + gtk_widget_show(mainwin->online_pixmap); + gtk_tooltips_set_tip + (mainwin->online_tip, mainwin->online_switch, + _("You are online. Click the icon to go offline."), + NULL); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), + FALSE); + inc_autocheck_timer_set(); + } +} + +static void ac_label_button_pressed(GtkWidget *widget, GdkEventButton *event, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + if (!event) return; + + gtk_button_set_relief(GTK_BUTTON(widget), GTK_RELIEF_NORMAL); + g_object_set_data(G_OBJECT(mainwin->ac_menu), "menu_button", widget); + + gtk_menu_popup(GTK_MENU(mainwin->ac_menu), NULL, NULL, + menu_button_position, widget, + event->button, event->time); +} + +static void ac_menu_popup_closed(GtkMenuShell *menu_shell, gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + GtkWidget *button; + + button = g_object_get_data(G_OBJECT(menu_shell), "menu_button"); + if (!button) return; + gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + g_object_set_data(G_OBJECT(mainwin->ac_menu), "menu_button", NULL); + manage_window_focus_in(mainwin->window, NULL, NULL); +} + +static gint main_window_close_cb(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + if (mainwin->lock_count == 0) + app_exit_cb(data, 0, widget); + + return TRUE; +} + +static gint folder_window_close_cb(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + GtkWidget *menuitem; + + menuitem = gtk_item_factory_get_item + (mainwin->menu_factory, "/View/Show or hide/Folder tree"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), FALSE); + + return TRUE; +} + +static gint message_window_close_cb(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + GtkWidget *menuitem; + + menuitem = gtk_item_factory_get_item + (mainwin->menu_factory, "/View/Show or hide/Message view"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), FALSE); + + return TRUE; +} + +static void main_window_size_allocate_cb(GtkWidget *widget, + GtkAllocation *allocation, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + main_window_get_size(mainwin); +} + +static void folder_window_size_allocate_cb(GtkWidget *widget, + GtkAllocation *allocation, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + main_window_get_size(mainwin); +} + +static void message_window_size_allocate_cb(GtkWidget *widget, + GtkAllocation *allocation, + gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + + main_window_get_size(mainwin); +} + +static void new_folder_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + folderview_new_folder(mainwin->folderview); +} + +static void rename_folder_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + folderview_rename_folder(mainwin->folderview); +} + +static void delete_folder_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + folderview_delete_folder(mainwin->folderview); +} + +static void add_mailbox_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + main_window_add_mailbox(mainwin); +} + +static void remove_mailbox_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + folderview_remove_mailbox(mainwin->folderview); +} + +static void update_folderview_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + if (action == 0) + folderview_check_new_selected(mainwin->folderview); + else + folderview_check_new_all(); +} + +static void rebuild_tree_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + folderview_rebuild_tree(mainwin->folderview); +} + +static void import_mbox_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + import_mbox(mainwin->summaryview->folder_item); +} + +static void export_mbox_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + export_mbox(mainwin->summaryview->folder_item); +} + +static void empty_trash_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + main_window_empty_trash(mainwin, TRUE); +} + +static void save_as_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_save_as(mainwin->summaryview); +} + +static void print_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_print(mainwin->summaryview); +} + +static void toggle_offline_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + main_window_toggle_online + (mainwin, !GTK_CHECK_MENU_ITEM(widget)->active); +} + +static void app_exit_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + if (prefs_common.confirm_on_exit) { + if (alertpanel(_("Exit"), _("Exit this program?"), + _("OK"), _("Cancel"), NULL) != G_ALERTDEFAULT) + return; + manage_window_focus_in(mainwin->window, NULL, NULL); + } + + app_will_exit(widget, mainwin); +} + +static void search_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + if (action == 1) + summary_search(mainwin->summaryview); + else + message_search(mainwin->messageview); +} + +static void toggle_folder_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + gboolean active; + + active = GTK_CHECK_MENU_ITEM(widget)->active; + + switch (mainwin->type) { + case SEPARATE_NONE: + case SEPARATE_MESSAGE: +#if 0 + if (active) + gtk_widget_show(GTK_WIDGET_PTR(mainwin->folderview)); + else + gtk_widget_hide(GTK_WIDGET_PTR(mainwin->folderview)); +#endif + break; + case SEPARATE_FOLDER: + if (active) + gtk_widget_show(mainwin->win.sep_folder.folderwin); + else + gtk_widget_hide(mainwin->win.sep_folder.folderwin); + break; + case SEPARATE_BOTH: + if (active) + gtk_widget_show(mainwin->win.sep_both.folderwin); + else + gtk_widget_hide(mainwin->win.sep_both.folderwin); + break; + } + + prefs_common.folderview_visible = active; +} + +static void toggle_message_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + gboolean active; + + active = GTK_CHECK_MENU_ITEM(widget)->active; + + if (active != messageview_is_visible(mainwin->messageview)) + summary_toggle_view(mainwin->summaryview); +} + +static void toggle_toolbar_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + switch ((ToolbarStyle)action) { + case TOOLBAR_NONE: + gtk_widget_hide(mainwin->handlebox); + case TOOLBAR_ICON: + gtk_toolbar_set_style(GTK_TOOLBAR(mainwin->toolbar), + GTK_TOOLBAR_ICONS); + break; + case TOOLBAR_TEXT: + gtk_toolbar_set_style(GTK_TOOLBAR(mainwin->toolbar), + GTK_TOOLBAR_TEXT); + break; + case TOOLBAR_BOTH: + gtk_toolbar_set_style(GTK_TOOLBAR(mainwin->toolbar), + GTK_TOOLBAR_BOTH); + break; + } + + if (action != TOOLBAR_NONE) { + gtk_widget_show(mainwin->handlebox); + gtk_widget_queue_resize(mainwin->handlebox); + } + + mainwin->toolbar_style = (ToolbarStyle)action; + prefs_common.toolbar_style = (ToolbarStyle)action; +} + +static void toggle_statusbar_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + if (GTK_CHECK_MENU_ITEM(widget)->active) { + gtk_widget_show(mainwin->hbox_stat); + prefs_common.show_statusbar = TRUE; + } else { + gtk_widget_hide(mainwin->hbox_stat); + prefs_common.show_statusbar = FALSE; + } +} + +static void separate_widget_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + SeparateType type; + + if (GTK_CHECK_MENU_ITEM(widget)->active) + type = mainwin->type | action; + else + type = mainwin->type & ~action; + + main_window_separation_change(mainwin, type); + + prefs_common.sep_folder = (type & SEPARATE_FOLDER) != 0; + prefs_common.sep_msg = (type & SEPARATE_MESSAGE) != 0; +} + +static void addressbook_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + addressbook_open(NULL); +} + +static void log_window_show_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + log_window_show(mainwin->logwin); +} + +static void inc_mail_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + inc_mail(mainwin); +} + +static void inc_all_account_mail_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + inc_all_account_mail(mainwin, FALSE); +} + +static void inc_cancel_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + inc_cancel_all(); +} + +static void send_queue_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + GList *list; + + if (!main_window_toggle_online_if_offline(mainwin)) + return; + + for (list = folder_get_list(); list != NULL; list = list->next) { + Folder *folder = list->data; + + if (folder->queue) { + gint ret; + + ret = procmsg_send_queue(folder->queue, + prefs_common.savemsg); + statusbar_pop_all(); + if (ret > 0) + folder_item_scan(folder->queue); + } + } + + folderview_update_all_updated(TRUE); +} + +static void compose_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + PrefsAccount *ac = NULL; + FolderItem *item = mainwin->summaryview->folder_item; + + if (item) { + ac = account_find_from_item(item); + if (ac && ac->protocol == A_NNTP && + FOLDER_TYPE(item->folder) == F_NEWS) { + compose_new(ac, item, item->path, NULL); + return; + } + } + + compose_new(ac, item, NULL, NULL); +} + +static void reply_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_reply(mainwin->summaryview, (ComposeMode)action); +} + +static void move_to_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_move_to(mainwin->summaryview); +} + +static void copy_to_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_copy_to(mainwin->summaryview); +} + +static void delete_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_delete(mainwin->summaryview); +} + +static void open_msg_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_open_msg(mainwin->summaryview); +} + +static void view_source_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_view_source(mainwin->summaryview); +} + +static void show_all_header_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + if (mainwin->menu_lock_count) return; + summary_display_msg_selected(mainwin->summaryview, + GTK_CHECK_MENU_ITEM(widget)->active); +} + +static void mark_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_mark(mainwin->summaryview); +} + +static void unmark_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_unmark(mainwin->summaryview); +} + +static void mark_as_unread_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_mark_as_unread(mainwin->summaryview); +} + +static void mark_as_read_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_mark_as_read(mainwin->summaryview); +} + +static void mark_all_read_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_mark_all_read(mainwin->summaryview); +} + +static void reedit_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_reedit(mainwin->summaryview); +} + +static void add_address_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_add_address(mainwin->summaryview); +} + +static void set_charset_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + const gchar *str; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + str = conv_get_charset_str((CharSet)action); + g_free(prefs_common.force_charset); + prefs_common.force_charset = str ? g_strdup(str) : NULL; + + summary_redisplay_msg(mainwin->summaryview); + + debug_print("forced charset: %s\n", + str ? str : "Auto-Detect"); + } +} + +static void thread_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + if (mainwin->menu_lock_count) return; + if (!mainwin->summaryview->folder_item) return; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + summary_thread_build(mainwin->summaryview); + mainwin->summaryview->folder_item->threaded = TRUE; + } else { + summary_unthread(mainwin->summaryview); + mainwin->summaryview->folder_item->threaded = FALSE; + } +} + +static void expand_threads_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_expand_threads(mainwin->summaryview); +} + +static void collapse_threads_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_collapse_threads(mainwin->summaryview); +} + +static void set_display_item_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + prefs_summary_column_open(); +} + +static void sort_summary_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + FolderItem *item = mainwin->summaryview->folder_item; + GtkWidget *menuitem; + + if (mainwin->menu_lock_count) return; + + if (GTK_CHECK_MENU_ITEM(widget)->active && item) { + menuitem = gtk_item_factory_get_item + (mainwin->menu_factory, "/View/Sort/Ascending"); + summary_sort(mainwin->summaryview, (FolderSortKey)action, + GTK_CHECK_MENU_ITEM(menuitem)->active + ? SORT_ASCENDING : SORT_DESCENDING); + } +} + +static void sort_summary_type_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + FolderItem *item = mainwin->summaryview->folder_item; + + if (mainwin->menu_lock_count) return; + + if (GTK_CHECK_MENU_ITEM(widget)->active && item) + summary_sort(mainwin->summaryview, + item->sort_key, (FolderSortType)action); +} + +static void attract_by_subject_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_attract_by_subject(mainwin->summaryview); +} + +static void delete_duplicated_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_delete_duplicated(mainwin->summaryview); +} + +static void filter_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_filter(mainwin->summaryview, (gboolean)action); +} + +static void execute_summary_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_execute(mainwin->summaryview); +} + +static void update_summary_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + FolderItem *fitem; + FolderView *folderview = mainwin->folderview; + + if (!mainwin->summaryview->folder_item) return; + if (!folderview->opened) return; + + fitem = gtk_ctree_node_get_row_data(GTK_CTREE(folderview->ctree), + folderview->opened); + if (!fitem) return; + + summary_show(mainwin->summaryview, fitem, TRUE); +} + +static void prev_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_step(mainwin->summaryview, GTK_SCROLL_STEP_BACKWARD); +} + +static void next_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_step(mainwin->summaryview, GTK_SCROLL_STEP_FORWARD); +} + +static void prev_unread_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_prev_unread(mainwin->summaryview); +} + +static void next_unread_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_next_unread(mainwin->summaryview); +} + +static void prev_new_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_select_prev_new(mainwin->summaryview); +} + +static void next_new_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + summary_select_next_new(mainwin->summaryview); +} + +static void prev_marked_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_prev_marked(mainwin->summaryview); +} + +static void next_marked_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_next_marked(mainwin->summaryview); +} + +static void prev_labeled_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_prev_labeled(mainwin->summaryview); +} + +static void next_labeled_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_next_labeled(mainwin->summaryview); +} + +static void goto_folder_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + FolderItem *to_folder; + + to_folder = foldersel_folder_sel(NULL, FOLDER_SEL_ALL, NULL); + + if (to_folder) + folderview_select(mainwin->folderview, to_folder); +} + +static void copy_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + messageview_copy_clipboard(mainwin->messageview); +} + +static void allsel_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + MessageView *msgview = mainwin->messageview; + + if (GTK_WIDGET_HAS_FOCUS(mainwin->summaryview->ctree)) + summary_select_all(mainwin->summaryview); + else if (messageview_is_visible(msgview) && + (GTK_WIDGET_HAS_FOCUS(msgview->textview->text) || + GTK_WIDGET_HAS_FOCUS(msgview->mimeview->textview->text))) + messageview_select_all(msgview); +} + +static void select_thread_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_select_thread(mainwin->summaryview); +} + +static void create_filter_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + summary_filter_open(mainwin->summaryview, (PrefsFilterType)action); +} + +static void prefs_common_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + prefs_common_open(); +} + +static void prefs_filter_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + prefs_filter_open(NULL, NULL); +} + +static void prefs_template_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + prefs_template_open(); +} + +static void prefs_actions_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + prefs_actions_open(mainwin); +} + +static void prefs_account_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + if (!cur_account) { + new_account_cb(mainwin, 0, widget); + } else { + account_open(cur_account); + } +} + +static void new_account_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + account_edit_open(); + if (!compose_get_compose_list()) account_add(); +} + +static void account_selector_menu_cb(GtkMenuItem *menuitem, gpointer data) +{ + cur_account = (PrefsAccount *)data; + main_window_reflect_prefs_all(); +} + +static void account_receive_menu_cb(GtkMenuItem *menuitem, gpointer data) +{ + MainWindow *mainwin = (MainWindow *)mainwin_list->data; + PrefsAccount *account = (PrefsAccount *)data; + + inc_account_mail(mainwin, account); +} + +static void manual_open_cb(MainWindow *mainwin, guint action, + GtkWidget *widget) +{ + manual_open((ManualLang)action); +} + +static void faq_open_cb(MainWindow *mainwin, guint action, GtkWidget *widget) +{ + faq_open((ManualLang)action); +} + +static void scan_tree_func(Folder *folder, FolderItem *item, gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + gchar *str; + + if (item->path) + str = g_strdup_printf(_("Scanning folder %s%c%s ..."), + LOCAL_FOLDER(folder)->rootpath, + G_DIR_SEPARATOR, + item->path); + else + str = g_strdup_printf(_("Scanning folder %s ..."), + LOCAL_FOLDER(folder)->rootpath); + + STATUSBAR_PUSH(mainwin, str); + STATUSBAR_POP(mainwin); + g_free(str); +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 00000000..2993f0ce --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,177 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MAINWINDOW_H__ +#define __MAINWINDOW_H__ + +#include +#include +#include +#include + +typedef struct _MainWindow MainWindow; + +#include "folderview.h" +#include "summaryview.h" +#include "headerview.h" +#include "messageview.h" +#include "logwindow.h" +#include "gtkutils.h" + +typedef enum +{ + SEPARATE_NONE = 0, + SEPARATE_FOLDER = 1 << 0, + SEPARATE_MESSAGE = 1 << 1, + SEPARATE_BOTH = (SEPARATE_FOLDER | SEPARATE_MESSAGE) +} SeparateType; + +typedef enum +{ + TOOLBAR_NONE = 0, + TOOLBAR_ICON = 1, + TOOLBAR_TEXT = 2, + TOOLBAR_BOTH = 3 +} ToolbarStyle; + +struct _MainWindow +{ + SeparateType type; + + union CompositeWin { + struct + { + GtkWidget *hpaned; + GtkWidget *vpaned; + } sep_none; + struct { + GtkWidget *folderwin; + GtkWidget *vpaned; + } sep_folder; + struct { + GtkWidget *messagewin; + GtkWidget *hpaned; + } sep_message; + struct { + GtkWidget *folderwin; + GtkWidget *messagewin; + } sep_both; + } win; + + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *menubar; + + GtkItemFactory *menu_factory; + + /* toolbar */ + GtkWidget *handlebox; + GtkWidget *toolbar; + GtkWidget *get_btn; + GtkWidget *getall_btn; + GtkWidget *compose_btn; + GtkWidget *reply_btn; + ComboButton *reply_combo; + GtkWidget *replyall_btn; + GtkWidget *fwd_btn; + ComboButton *fwd_combo; + GtkWidget *send_btn; + GtkWidget *prefs_btn; + GtkWidget *account_btn; + GtkWidget *next_btn; + GtkWidget *delete_btn; + GtkWidget *exec_btn; + + /* body */ + GtkWidget *vbox_body; + GtkWidget *hbox_stat; + GtkWidget *statusbar; + GtkWidget *progressbar; + GtkWidget *statuslabel; + GtkWidget *online_switch; + GtkWidget *online_pixmap; + GtkWidget *offline_pixmap; + GtkTooltips *online_tip; + GtkWidget *ac_button; + GtkWidget *ac_label; + GtkWidget *ac_menu; + + /* context IDs for status bar */ + gint mainwin_cid; + gint folderview_cid; + gint summaryview_cid; + gint messageview_cid; + + ToolbarStyle toolbar_style; + + guint lock_count; + guint menu_lock_count; + guint cursor_count; + + FolderView *folderview; + SummaryView *summaryview; + MessageView *messageview; + LogWindow *logwin; +}; + +MainWindow *main_window_create (SeparateType type); + +void main_window_cursor_wait (MainWindow *mainwin); +void main_window_cursor_normal (MainWindow *mainwin); + +void main_window_lock (MainWindow *mainwin); +void main_window_unlock (MainWindow *mainwin); + +void main_window_reflect_prefs_all (void); +void main_window_set_summary_column (void); +void main_window_set_account_menu (GList *account_list); + +MainWindow *main_window_get (void); + +GtkWidget *main_window_get_folder_window (MainWindow *mainwin); +GtkWidget *main_window_get_message_window (MainWindow *mainwin); + +void main_window_separation_change (MainWindow *mainwin, + SeparateType type); + +void main_window_toggle_message_view (MainWindow *mainwin); + +void main_window_get_size (MainWindow *mainwin); +void main_window_get_position (MainWindow *mainwin); + +void main_window_progress_on (MainWindow *mainwin); +void main_window_progress_off (MainWindow *mainwin); +void main_window_progress_set (MainWindow *mainwin, + gint cur, + gint total); + +void main_window_toggle_online (MainWindow *mainwin, + gboolean online); +gboolean main_window_toggle_online_if_offline (MainWindow *mainwin); + +void main_window_empty_trash (MainWindow *mainwin, + gboolean confirm); +void main_window_add_mailbox (MainWindow *mainwin); + +void main_window_set_toolbar_sensitive (MainWindow *mainwin); +void main_window_set_menu_sensitive (MainWindow *mainwin); + +void main_window_popup (MainWindow *mainwin); + +#endif /* __MAINWINDOW_H__ */ diff --git a/src/manage_window.c b/src/manage_window.c new file mode 100644 index 00000000..e001c68d --- /dev/null +++ b/src/manage_window.c @@ -0,0 +1,92 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include +#include + +#include "manage_window.h" +#include "utils.h" + +static GtkWidget *focus_window; + +gint manage_window_focus_in(GtkWidget *widget, GdkEventFocus *event, + gpointer data) +{ + /* debug_print("Focus in event: window: %p\n", widget); */ + + focus_window = widget; + + return FALSE; +} + +gint manage_window_focus_out(GtkWidget *widget, GdkEventFocus *event, + gpointer data) +{ + /* debug_print("Focused window: %p\n", focus_window); */ + /* debug_print("Focus out event: window: %p\n", widget); */ + + if (focus_window == widget) + focus_window = NULL; + + return FALSE; +} + +gint manage_window_unmap(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + /* debug_print("unmap event: %p\n", widget); */ + + if (focus_window == widget) + focus_window = NULL; + + return FALSE; +} + +gint manage_window_delete(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + /* debug_print("delete event: %p\n", widget); */ + + if (focus_window == widget) + focus_window = NULL; + + return FALSE; +} + +void manage_window_destroy(GtkWidget *widget, gpointer data) +{ + /* debug_print("destroy event: %p\n", widget); */ + + if (focus_window == widget) + focus_window = NULL; +} + +void manage_window_set_transient(GtkWindow *window) +{ + /* debug_print("manage_window_set_transient(): window = %p, focus_window = %p\n", + window, focus_window); */ + + if (window && focus_window) + gtk_window_set_transient_for(window, GTK_WINDOW(focus_window)); +} + +GtkWidget *manage_window_get_focus_window(void) +{ + return focus_window; +} diff --git a/src/manage_window.h b/src/manage_window.h new file mode 100644 index 00000000..64d48617 --- /dev/null +++ b/src/manage_window.h @@ -0,0 +1,58 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MANAGE_WINDOW_H__ +#define __MANAGE_WINDOW_H__ + +#include +#include +#include + +#define MANAGE_WINDOW_SIGNALS_CONNECT(window) \ +{ \ + g_signal_connect(G_OBJECT(window), "focus_in_event", \ + G_CALLBACK(manage_window_focus_in), NULL); \ + g_signal_connect(G_OBJECT(window), "focus_out_event", \ + G_CALLBACK(manage_window_focus_out), NULL); \ + g_signal_connect(G_OBJECT(window), "unmap_event", \ + G_CALLBACK(manage_window_unmap), NULL); \ + g_signal_connect(G_OBJECT(window), "destroy", \ + G_CALLBACK(manage_window_destroy), NULL); \ +} + +gint manage_window_focus_in (GtkWidget *widget, + GdkEventFocus *event, + gpointer data); +gint manage_window_focus_out (GtkWidget *widget, + GdkEventFocus *event, + gpointer data); +gint manage_window_unmap (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +gint manage_window_delete (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +void manage_window_destroy (GtkWidget *widget, + gpointer data); + +void manage_window_set_transient (GtkWindow *window); + +GtkWidget *manage_window_get_focus_window (void); + +#endif /* __MANAGE_WINDOW_H__ */ diff --git a/src/manual.c b/src/manual.c new file mode 100644 index 00000000..8afc4bdb --- /dev/null +++ b/src/manual.c @@ -0,0 +1,85 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include + +#include "prefs_common.h" +#include "manual.h" +#include "utils.h" + +static gchar *get_lang_str(ManualLang lang); + +static gchar *get_lang_str(ManualLang lang) +{ + switch (lang) { + case MANUAL_LANG_DE: + return "de"; + case MANUAL_LANG_EN: + return "en"; + case MANUAL_LANG_ES: + return "es"; + case MANUAL_LANG_FR: + return "fr"; + case MANUAL_LANG_IT: + return "it"; + case MANUAL_LANG_JA: + return "ja"; + default: + return NULL; + } +} + +void manual_open(ManualLang lang) +{ + gchar *lang_str; + gchar *file_uri; + + lang_str = get_lang_str(lang); + if (!lang_str) return; + + file_uri = g_strconcat("file://", MANUALDIR, + G_DIR_SEPARATOR_S, lang_str, G_DIR_SEPARATOR_S, + MANUAL_HTML_INDEX, NULL); + debug_print("Opening manual: %s\n", file_uri); + open_uri(file_uri, prefs_common.uri_cmd); + g_free(file_uri); +} + +void faq_open(ManualLang lang) +{ + gchar *lang_str; + gchar *file_uri; + + lang_str = get_lang_str(lang); + if (!lang_str) return; + + file_uri = g_strconcat("file://", FAQDIR, + G_DIR_SEPARATOR_S, lang_str, G_DIR_SEPARATOR_S, + FAQ_HTML_INDEX, NULL); + debug_print("Opening FAQ: %s\n", file_uri); + open_uri(file_uri, prefs_common.uri_cmd); + g_free(file_uri); +} diff --git a/src/manual.h b/src/manual.h new file mode 100644 index 00000000..6ae384f3 --- /dev/null +++ b/src/manual.h @@ -0,0 +1,36 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MANUAL_H__ +#define __MANUAL_H__ + +typedef enum +{ + MANUAL_LANG_DE, + MANUAL_LANG_EN, + MANUAL_LANG_ES, + MANUAL_LANG_FR, + MANUAL_LANG_IT, + MANUAL_LANG_JA, +} ManualLang; + +void manual_open(ManualLang lang); +void faq_open (ManualLang lang); + +#endif /* __MANUAL_H__ */ diff --git a/src/mbox.c b/src/mbox.c new file mode 100644 index 00000000..aaf9d663 --- /dev/null +++ b/src/mbox.c @@ -0,0 +1,455 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "mbox.h" +#include "procmsg.h" +#include "folder.h" +#include "filter.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "account.h" +#include "utils.h" + +#define MSGBUFSIZE 8192 + +#define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \ +{ \ + if (fputs(s, tmp_fp) == EOF) { \ + g_warning(_("can't write to temporary file\n")); \ + fclose(tmp_fp); \ + fclose(mbox_fp); \ + unlink(tmp_file); \ + g_free(tmp_file); \ + return -1; \ + } \ +} + +gint proc_mbox(FolderItem *dest, const gchar *mbox, GHashTable *folder_table) +{ + FILE *mbox_fp; + gchar buf[MSGBUFSIZE], from_line[MSGBUFSIZE]; + gchar *tmp_file; + gint msgs = 0; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(mbox != NULL, -1); + + debug_print(_("Getting messages from %s into %s...\n"), mbox, dest->path); + + if ((mbox_fp = fopen(mbox, "rb")) == NULL) { + FILE_OP_ERROR(mbox, "fopen"); + return -1; + } + + /* ignore empty lines on the head */ + do { + if (fgets(buf, sizeof(buf), mbox_fp) == NULL) { + g_warning(_("can't read mbox file.\n")); + fclose(mbox_fp); + return -1; + } + } while (buf[0] == '\n' || buf[0] == '\r'); + + if (strncmp(buf, "From ", 5) != 0) { + g_warning(_("invalid mbox format: %s\n"), mbox); + fclose(mbox_fp); + return -1; + } + + strcpy(from_line, buf); + if (fgets(buf, sizeof(buf), mbox_fp) == NULL) { + g_warning(_("malformed mbox: %s\n"), mbox); + fclose(mbox_fp); + return -1; + } + + tmp_file = get_tmp_file(); + + do { + FILE *tmp_fp; + GSList *cur; + gchar *startp, *endp, *rpath; + gint empty_line; + gboolean is_next_msg = FALSE; + FilterInfo *fltinfo; + + if ((tmp_fp = fopen(tmp_file, "wb")) == NULL) { + FILE_OP_ERROR(tmp_file, "fopen"); + g_warning(_("can't open temporary file\n")); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + if (change_file_mode_rw(tmp_fp, tmp_file) < 0) + FILE_OP_ERROR(tmp_file, "chmod"); + + /* convert unix From into Return-Path */ + startp = from_line + 5; + endp = strchr(startp, ' '); + if (endp == NULL) + rpath = g_strdup(startp); + else + rpath = g_strndup(startp, endp - startp); + g_strstrip(rpath); + g_snprintf(from_line, sizeof(from_line), + "Return-Path: %s\n", rpath); + g_free(rpath); + + FPUTS_TO_TMP_ABORT_IF_FAIL(from_line); + FPUTS_TO_TMP_ABORT_IF_FAIL(buf); + from_line[0] = '\0'; + + empty_line = 0; + + while (fgets(buf, sizeof(buf), mbox_fp) != NULL) { + if (buf[0] == '\n' || buf[0] == '\r') { + empty_line++; + buf[0] = '\0'; + continue; + } + + /* From separator */ + while (!strncmp(buf, "From ", 5)) { + strcpy(from_line, buf); + if (fgets(buf, sizeof(buf), mbox_fp) == NULL) { + buf[0] = '\0'; + break; + } + + if (is_header_line(buf)) { + is_next_msg = TRUE; + break; + } else if (!strncmp(buf, "From ", 5)) { + continue; + } else if (!strncmp(buf, ">From ", 6)) { + g_memmove(buf, buf + 1, strlen(buf)); + is_next_msg = TRUE; + break; + } else { + g_warning(_("unescaped From found:\n%s"), + from_line); + break; + } + } + if (is_next_msg) break; + + if (empty_line > 0) { + while (empty_line--) + FPUTS_TO_TMP_ABORT_IF_FAIL("\n"); + empty_line = 0; + } + + if (from_line[0] != '\0') { + FPUTS_TO_TMP_ABORT_IF_FAIL(from_line); + from_line[0] = '\0'; + } + + if (buf[0] != '\0') { + if (!strncmp(buf, ">From ", 6)) { + FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1); + } else + FPUTS_TO_TMP_ABORT_IF_FAIL(buf); + + buf[0] = '\0'; + } + } + + if (empty_line > 0) { + while (--empty_line) + FPUTS_TO_TMP_ABORT_IF_FAIL("\n"); + } + + if (fclose(tmp_fp) == EOF) { + FILE_OP_ERROR(tmp_file, "fclose"); + g_warning(_("can't write to temporary file\n")); + unlink(tmp_file); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + + fltinfo = filter_info_new(); + fltinfo->flags.perm_flags = MSG_NEW|MSG_UNREAD; + fltinfo->flags.tmp_flags = MSG_RECEIVED; + + if (folder_table) + filter_apply(prefs_common.fltlist, tmp_file, fltinfo); + + if (fltinfo->actions[FLT_ACTION_MOVE] == FALSE && + fltinfo->actions[FLT_ACTION_DELETE] == FALSE) { + if (folder_item_add_msg(dest, tmp_file, &fltinfo->flags, + FALSE) < 0) { + filter_info_free(fltinfo); + unlink(tmp_file); + g_free(tmp_file); + fclose(mbox_fp); + return -1; + } + fltinfo->dest_list = g_slist_append(fltinfo->dest_list, + dest); + } + + for (cur = fltinfo->dest_list; cur != NULL; cur = cur->next) { + FolderItem *drop_folder = (FolderItem *)cur->data; + gint val = 0; + + if (folder_table) { + val = GPOINTER_TO_INT(g_hash_table_lookup + (folder_table, + drop_folder)); + } + if (val == 0) { + /* force updating */ + if (FOLDER_IS_LOCAL(drop_folder->folder)) + drop_folder->mtime = 0; + if (folder_table) { + g_hash_table_insert(folder_table, + drop_folder, + GINT_TO_POINTER(1)); + } + } + } + + filter_info_free(fltinfo); + unlink(tmp_file); + + msgs++; + } while (from_line[0] != '\0'); + + g_free(tmp_file); + fclose(mbox_fp); + debug_print(_("%d messages found.\n"), msgs); + + return msgs; +} + +gint lock_mbox(const gchar *base, LockType type) +{ + gint retval = 0; + + if (type == LOCK_FILE) { + gchar *lockfile, *locklink; + gint retry = 0; + FILE *lockfp; + + lockfile = g_strdup_printf("%s.%d", base, getpid()); + if ((lockfp = fopen(lockfile, "wb")) == NULL) { + FILE_OP_ERROR(lockfile, "fopen"); + g_warning(_("can't create lock file %s\n"), lockfile); + g_warning(_("use 'flock' instead of 'file' if possible.\n")); + g_free(lockfile); + return -1; + } + + fprintf(lockfp, "%d\n", getpid()); + fclose(lockfp); + + locklink = g_strconcat(base, ".lock", NULL); + while (link(lockfile, locklink) < 0) { + FILE_OP_ERROR(lockfile, "link"); + if (retry >= 5) { + g_warning(_("can't create %s\n"), lockfile); + unlink(lockfile); + g_free(lockfile); + return -1; + } + if (retry == 0) + g_warning(_("mailbox is owned by another" + " process, waiting...\n")); + retry++; + sleep(5); + } + unlink(lockfile); + g_free(lockfile); + } else if (type == LOCK_FLOCK) { + gint lockfd; + +#if HAVE_FLOCK + if ((lockfd = open(base, O_RDONLY)) < 0) { +#else + if ((lockfd = open(base, O_RDWR)) < 0) { +#endif + FILE_OP_ERROR(base, "open"); + return -1; + } +#if HAVE_FLOCK + if (flock(lockfd, LOCK_EX|LOCK_NB) < 0) { + perror("flock"); +#else +#if HAVE_LOCKF + if (lockf(lockfd, F_TLOCK, 0) < 0) { + perror("lockf"); +#else + { +#endif +#endif /* HAVE_FLOCK */ + g_warning(_("can't lock %s\n"), base); + if (close(lockfd) < 0) + perror("close"); + return -1; + } + retval = lockfd; + } else { + g_warning(_("invalid lock type\n")); + return -1; + } + + return retval; +} + +gint unlock_mbox(const gchar *base, gint fd, LockType type) +{ + if (type == LOCK_FILE) { + gchar *lockfile; + + lockfile = g_strconcat(base, ".lock", NULL); + if (unlink(lockfile) < 0) { + FILE_OP_ERROR(lockfile, "unlink"); + g_free(lockfile); + return -1; + } + g_free(lockfile); + + return 0; + } else if (type == LOCK_FLOCK) { +#if HAVE_FLOCK + if (flock(fd, LOCK_UN) < 0) { + perror("flock"); +#else +#if HAVE_LOCKF + if (lockf(fd, F_ULOCK, 0) < 0) { + perror("lockf"); +#else + { +#endif +#endif /* HAVE_FLOCK */ + g_warning(_("can't unlock %s\n"), base); + if (close(fd) < 0) + perror("close"); + return -1; + } + + if (close(fd) < 0) { + perror("close"); + return -1; + } + + return 0; + } + + g_warning(_("invalid lock type\n")); + return -1; +} + +gint copy_mbox(const gchar *src, const gchar *dest) +{ + return copy_file(src, dest, TRUE); +} + +void empty_mbox(const gchar *mbox) +{ + if (truncate(mbox, 0) < 0) { + FILE *fp; + + FILE_OP_ERROR(mbox, "truncate"); + if ((fp = fopen(mbox, "wb")) == NULL) { + FILE_OP_ERROR(mbox, "fopen"); + g_warning(_("can't truncate mailbox to zero.\n")); + return; + } + fclose(fp); + } +} + +/* read all messages in SRC, and store them into one MBOX file. */ +gint export_to_mbox(FolderItem *src, const gchar *mbox) +{ + GSList *mlist; + GSList *cur; + MsgInfo *msginfo; + FILE *msg_fp; + FILE *mbox_fp; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(src != NULL, -1); + g_return_val_if_fail(src->folder != NULL, -1); + g_return_val_if_fail(mbox != NULL, -1); + + debug_print(_("Exporting messages from %s into %s...\n"), + src->path, mbox); + + if ((mbox_fp = fopen(mbox, "wb")) == NULL) { + FILE_OP_ERROR(mbox, "fopen"); + return -1; + } + + mlist = folder_item_get_msg_list(src, TRUE); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + msg_fp = procmsg_open_message(msginfo); + if (!msg_fp) { + procmsg_msginfo_free(msginfo); + continue; + } + + strncpy2(buf, + msginfo->from ? msginfo->from : + cur_account && cur_account->address ? + cur_account->address : "unknown", + sizeof(buf)); + extract_address(buf); + + fprintf(mbox_fp, "From %s %s", + buf, ctime(&msginfo->date_t)); + + while (fgets(buf, sizeof(buf), msg_fp) != NULL) { + if (!strncmp(buf, "From ", 5)) + fputc('>', mbox_fp); + fputs(buf, mbox_fp); + } + fputc('\n', mbox_fp); + + fclose(msg_fp); + procmsg_msginfo_free(msginfo); + } + + g_slist_free(mlist); + + fclose(mbox_fp); + + return 0; +} diff --git a/src/mbox.h b/src/mbox.h new file mode 100644 index 00000000..3b210f0a --- /dev/null +++ b/src/mbox.h @@ -0,0 +1,47 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MBOX_H__ +#define __MBOX_H__ + +#include + +#include "folder.h" + +typedef enum { + LOCK_FILE, + LOCK_FLOCK +} LockType; + +gint proc_mbox (FolderItem *dest, + const gchar *mbox, + GHashTable *folder_table); +gint lock_mbox (const gchar *base, + LockType type); +gint unlock_mbox (const gchar *base, + gint fd, + LockType type); +gint copy_mbox (const gchar *src, + const gchar *dest); +void empty_mbox (const gchar *mbox); + +gint export_to_mbox (FolderItem *src, + const gchar *mbox); + +#endif /* __MBOX_H__ */ diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 00000000..54585971 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,433 @@ +/* md5.c - MD5 Message-Digest Algorithm + * Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc. + * + * according to the definition of MD5 in RFC 1321 from April 1992. + * NOTE: This is *not* the same file as the one from glibc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + */ +/* Written by Ulrich Drepper , 1995. */ +/* heavily modified for GnuPG by */ +/* modified again for Sylpheed by 2001-02-11 */ + + +/* Test values: + * "" D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E + * "a" 0C C1 75 B9 C0 F1 B6 A8 31 C3 99 E2 69 77 26 61 + * "abc 90 01 50 98 3C D2 4F B0 D6 96 3F 7D 28 E1 7F 72 + * "message digest" F9 6B 69 7D 7C B7 93 8D 52 5A 2F 31 AA F1 61 D0 + */ + +#include +#include +#include +#include +#include + +#include "utils.h" +#include "md5.h" + + +/**************** + * Rotate a 32 bit integer by n bytes + */ +#if defined(__GNUC__) && defined(__i386__) +static inline u32 +rol( u32 x, int n) +{ + __asm__("roll %%cl,%0" + :"=r" (x) + :"0" (x),"c" (n)); + return x; +} +#else +#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) +#endif + + +void +md5_init(MD5_CONTEXT *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->nblocks = 0; + ctx->count = 0; + ctx->finalized = 0; +} + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + + +/**************** + * transform n*64 bytes + */ +static void +transform(MD5_CONTEXT *ctx, const unsigned char *data) +{ + u32 correct_words[16]; + u32 A = ctx->A; + u32 B = ctx->B; + u32 C = ctx->C; + u32 D = ctx->D; + u32 *cwp = correct_words; + +#ifdef BIG_ENDIAN_HOST + { + int i; + unsigned char *p2, *p1; + + for (i = 0, p1 = data, p2 = (unsigned char*)correct_words; + i < 16; i++, p2 += 4) { + p2[3] = *p1++; + p2[2] = *p1++; + p2[1] = *p1++; + p2[0] = *p1++; + } + } +#else + memcpy(correct_words, data, 64); +#endif + + +#define OP(a, b, c, d, s, T) \ + do { \ + a += FF (b, c, d) + (*cwp++) + T; \ + a = rol(a, s); \ + a += b; \ + } while (0) + + /* Before we start, one word about the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + + /* Round 1. */ + OP (A, B, C, D, 7, 0xd76aa478); + OP (D, A, B, C, 12, 0xe8c7b756); + OP (C, D, A, B, 17, 0x242070db); + OP (B, C, D, A, 22, 0xc1bdceee); + OP (A, B, C, D, 7, 0xf57c0faf); + OP (D, A, B, C, 12, 0x4787c62a); + OP (C, D, A, B, 17, 0xa8304613); + OP (B, C, D, A, 22, 0xfd469501); + OP (A, B, C, D, 7, 0x698098d8); + OP (D, A, B, C, 12, 0x8b44f7af); + OP (C, D, A, B, 17, 0xffff5bb1); + OP (B, C, D, A, 22, 0x895cd7be); + OP (A, B, C, D, 7, 0x6b901122); + OP (D, A, B, C, 12, 0xfd987193); + OP (C, D, A, B, 17, 0xa679438e); + OP (B, C, D, A, 22, 0x49b40821); + +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do { \ + a += f (b, c, d) + correct_words[k] + T; \ + a = rol(a, s); \ + a += b; \ + } while (0) + + /* Round 2. */ + OP (FG, A, B, C, D, 1, 5, 0xf61e2562); + OP (FG, D, A, B, C, 6, 9, 0xc040b340); + OP (FG, C, D, A, B, 11, 14, 0x265e5a51); + OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP (FG, A, B, C, D, 5, 5, 0xd62f105d); + OP (FG, D, A, B, C, 10, 9, 0x02441453); + OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP (FG, D, A, B, C, 14, 9, 0xc33707d6); + OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP (FG, B, C, D, A, 8, 20, 0x455a14ed); + OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP (FG, C, D, A, B, 7, 14, 0x676f02d9); + OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); + + /* Round 3. */ + OP (FH, A, B, C, D, 5, 4, 0xfffa3942); + OP (FH, D, A, B, C, 8, 11, 0x8771f681); + OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP (FH, B, C, D, A, 14, 23, 0xfde5380c); + OP (FH, A, B, C, D, 1, 4, 0xa4beea44); + OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP (FH, B, C, D, A, 6, 23, 0x04881d05); + OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); + + /* Round 4. */ + OP (FI, A, B, C, D, 0, 6, 0xf4292244); + OP (FI, D, A, B, C, 7, 10, 0x432aff97); + OP (FI, C, D, A, B, 14, 15, 0xab9423a7); + OP (FI, B, C, D, A, 5, 21, 0xfc93a039); + OP (FI, A, B, C, D, 12, 6, 0x655b59c3); + OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP (FI, C, D, A, B, 10, 15, 0xffeff47d); + OP (FI, B, C, D, A, 1, 21, 0x85845dd1); + OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP (FI, C, D, A, B, 6, 15, 0xa3014314); + OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP (FI, A, B, C, D, 4, 6, 0xf7537e82); + OP (FI, D, A, B, C, 11, 10, 0xbd3af235); + OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP (FI, B, C, D, A, 9, 21, 0xeb86d391); + + /* Put checksum in context given as argument. */ + ctx->A += A; + ctx->B += B; + ctx->C += C; + ctx->D += D; +} + + + +/* The routine updates the message-digest context to + * account for the presence of each of the characters inBuf[0..inLen-1] + * in the message whose digest is being computed. + */ +void +md5_update(MD5_CONTEXT *hd, const unsigned char *inbuf, size_t inlen) +{ + if (hd->count == 64) { /* flush the buffer */ + transform( hd, hd->buf ); + hd->count = 0; + hd->nblocks++; + } + if (!inbuf) + return; + if (hd->count) { + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; + md5_update(hd, NULL, 0); + if (!inlen) + return; + } + + while (inlen >= 64) { + transform(hd, inbuf); + hd->count = 0; + hd->nblocks++; + inlen -= 64; + inbuf += 64; + } + + for (; inlen && hd->count < 64; inlen--) + hd->buf[hd->count++] = *inbuf++; +} + + + +/* The routine final terminates the message-digest computation and + * ends with the desired message digest in mdContext->digest[0...15]. + * The handle is prepared for a new MD5 cycle. + * Returns 16 bytes representing the digest. + */ + +static void +do_final(MD5_CONTEXT *hd) +{ + u32 t, msb, lsb; + unsigned char *p; + + md5_update(hd, NULL, 0); /* flush */ + + msb = 0; + t = hd->nblocks; + if ((lsb = t << 6) < t) /* multiply by 64 to make a byte count */ + msb++; + msb += t >> 26; + t = lsb; + if ((lsb = t + hd->count) < t) /* add the count */ + msb++; + t = lsb; + if ((lsb = t << 3) < t) /* multiply by 8 to make a bit count */ + msb++; + msb += t >> 29; + + if (hd->count < 56) { /* enough room */ + hd->buf[hd->count++] = 0x80; /* pad */ + while(hd->count < 56) + hd->buf[hd->count++] = 0; /* pad */ + } else { /* need one extra block */ + hd->buf[hd->count++] = 0x80; /* pad character */ + while (hd->count < 64) + hd->buf[hd->count++] = 0; + md5_update(hd, NULL, 0); /* flush */ + memset(hd->buf, 0, 56); /* fill next block with zeroes */ + } + + /* append the 64 bit count */ + hd->buf[56] = lsb ; + hd->buf[57] = lsb >> 8; + hd->buf[58] = lsb >> 16; + hd->buf[59] = lsb >> 24; + hd->buf[60] = msb ; + hd->buf[61] = msb >> 8; + hd->buf[62] = msb >> 16; + hd->buf[63] = msb >> 24; + transform(hd, hd->buf); + + p = hd->buf; +#ifdef BIG_ENDIAN_HOST +#define X(a) do { *p++ = hd->a ; *p++ = hd->a >> 8; \ + *p++ = hd->a >> 16; *p++ = hd->a >> 24; } while(0) +#else /* little endian */ + /*#define X(a) do { *(u32*)p = hd->##a ; p += 4; } while(0)*/ + /* Unixware's cpp doesn't like the above construct so we do it his way: + * (reported by Allan Clark) */ +#define X(a) do { *(u32*)p = (*hd).a ; p += 4; } while(0) +#endif + X(A); + X(B); + X(C); + X(D); +#undef X + hd->finalized = 1; +} + +void +md5_final(unsigned char *digest, MD5_CONTEXT *ctx) +{ + if (!ctx->finalized) + do_final(ctx); + memcpy(digest, ctx->buf, 16); +} + +/* + * Creates a MD5 digest in hex fomrat (lowercase letters) from the + * string S. hextdigest but be buffer of at lease 33 bytes! + */ +void +md5_hex_digest(char *hexdigest, const unsigned char *s) +{ + int i; + MD5_CONTEXT context; + unsigned char digest[16]; + + md5_init(&context); + md5_update(&context, s, strlen(s)); + md5_final(digest, &context); + + for (i = 0; i < 16; i++) + sprintf(hexdigest + 2 * i, "%02x", digest[i]); +} + + +/* +** Function: md5_hmac +** taken from the file rfc2104.txt +** written by Martin Schaaf +*/ +void +md5_hmac(unsigned char *digest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len) +{ + MD5_CONTEXT context; + unsigned char k_ipad[64]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[64]; /* outer padding - + * key XORd with opad + */ + /* unsigned char tk[16]; */ + int i; + + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + if (key_len > 64) { + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + MD5_CONTEXT tctx; + + md5_init(&tctx); + md5_update(&tctx, key, key_len); + md5_final(k_ipad, &tctx); + md5_final(k_opad, &tctx); + } else { + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* + * perform inner MD5 + */ + md5_init(&context); /* init context for 1st + * pass */ + md5_update(&context, k_ipad, 64); /* start with inner pad */ + md5_update(&context, text, text_len); /* then text of datagram */ + md5_final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + md5_init(&context); /* init context for 2nd + * pass */ + md5_update(&context, k_opad, 64); /* start with outer pad */ + md5_update(&context, digest, 16); /* then results of 1st + * hash */ + md5_final(digest, &context); /* finish up 2nd pass */ +} + + +void +md5_hex_hmac(char *hexdigest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len) +{ + unsigned char digest[16]; + int i; + + md5_hmac(digest, text, text_len, key, key_len); + for (i = 0; i < 16; i++) + sprintf(hexdigest + 2 * i, "%02x", digest[i]); +} diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 00000000..84894b2c --- /dev/null +++ b/src/md5.h @@ -0,0 +1,49 @@ +/* md5.h - MD5 Message-Digest Algorithm + * Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc. + * + * according to the definition of MD5 in RFC 1321 from April 1992. + * NOTE: This is *not* the same file as the one from glibc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + */ + +#ifndef _MD5_HDR_ +#define _MD5_HDR_ + +#include "utils.h" + +typedef struct { /* Hmm, should be private */ + u32 A,B,C,D; + u32 nblocks; + unsigned char buf[64]; + int count; + int finalized; +} MD5_CONTEXT; + +void md5_init(MD5_CONTEXT *ctx); +void md5_update(MD5_CONTEXT *hd, const unsigned char *inbuf, size_t inlen); +void md5_final(unsigned char *digest, MD5_CONTEXT *ctx); + +void md5_hex_digest(char *hexdigest, const unsigned char *s); + +void md5_hmac(unsigned char *digest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len); +void md5_hex_hmac(char *hexdigest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len); + +#endif /* _MD5_HDR_ */ + diff --git a/src/menu.c b/src/menu.c new file mode 100644 index 00000000..82c40cd5 --- /dev/null +++ b/src/menu.c @@ -0,0 +1,262 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "menu.h" +#include "utils.h" + +static gchar *menu_translate(const gchar *path, gpointer data); + +GtkWidget *menubar_create(GtkWidget *window, GtkItemFactoryEntry *entries, + guint n_entries, const gchar *path, gpointer data) +{ + GtkItemFactory *factory; + + factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, path, NULL); + gtk_item_factory_set_translate_func(factory, menu_translate, + NULL, NULL); + gtk_item_factory_create_items(factory, n_entries, entries, data); + gtk_window_add_accel_group(GTK_WINDOW(window), factory->accel_group); + + return gtk_item_factory_get_widget(factory, path); +} + +GtkWidget *menu_create_items(GtkItemFactoryEntry *entries, + guint n_entries, const gchar *path, + GtkItemFactory **factory, gpointer data) +{ + *factory = gtk_item_factory_new(GTK_TYPE_MENU, path, NULL); + gtk_item_factory_set_translate_func(*factory, menu_translate, + NULL, NULL); + gtk_item_factory_create_items(*factory, n_entries, entries, data); + + return gtk_item_factory_get_widget(*factory, path); +} + +static gchar *menu_translate(const gchar *path, gpointer data) +{ + gchar *retval; + + retval = gettext(path); + + return retval; +} + +#warning FIXME_GTK2 +#if 0 +static void factory_print_func(gpointer data, gchar *string) +{ + GString *out_str = data; + + g_string_append(out_str, string); + g_string_append_c(out_str, '\n'); +} + +GString *menu_factory_get_rc(const gchar *path) +{ + GString *string; + GtkPatternSpec *pspec; + gchar pattern[256]; + + pspec = g_new(GtkPatternSpec, 1); + g_snprintf(pattern, sizeof(pattern), "%s*", path); + gtk_pattern_spec_init(pspec, pattern); + string = g_string_new(""); + gtk_item_factory_dump_items(pspec, FALSE, factory_print_func, + string); + gtk_pattern_spec_free_segs(pspec); + + return string; +} + +void menu_factory_clear_rc(const gchar *rc_str) +{ + GString *string; + gchar *p; + gchar *start, *end; + guint pos = 0; + + string = g_string_new(rc_str); + while ((p = strstr(string->str + pos, "(menu-path \"")) != NULL) { + pos = p + 12 - string->str; + p = strchr(p + 12, '"'); + if (!p) continue; + start = strchr(p + 1, '"'); + if (!start) continue; + end = strchr(start + 1, '"'); + if (!end) continue; + pos = start + 1 - string->str; + if (end > start + 1) + g_string_erase(string, pos, end - (start + 1)); + } + + gtk_item_factory_parse_rc_string(string->str); + g_string_free(string, TRUE); +} + +void menu_factory_copy_rc(const gchar *src_path, const gchar *dest_path) +{ + GString *string; + gint src_path_len; + gint dest_path_len; + gchar *p; + guint pos = 0; + + string = menu_factory_get_rc(src_path); + src_path_len = strlen(src_path); + dest_path_len = strlen(dest_path); + + while ((p = strstr(string->str + pos, src_path)) != NULL) { + pos = p - string->str; + g_string_erase(string, pos, src_path_len); + g_string_insert(string, pos, dest_path); + pos += dest_path_len; + } + + pos = 0; + while ((p = strchr(string->str + pos, ';')) != NULL) { + pos = p - string->str; + if (pos == 0 || *(p - 1) == '\n') + g_string_erase(string, pos, 1); + } + + menu_factory_clear_rc(string->str); + gtk_item_factory_parse_rc_string(string->str); + g_string_free(string, TRUE); +} +#endif + +void menu_set_sensitive(GtkItemFactory *ifactory, const gchar *path, + gboolean sensitive) +{ + GtkWidget *widget; + + g_return_if_fail(ifactory != NULL); + + widget = gtk_item_factory_get_item(ifactory, path); + gtk_widget_set_sensitive(widget, sensitive); +} + +void menu_set_sensitive_all(GtkMenuShell *menu_shell, gboolean sensitive) +{ + GList *cur; + + for (cur = menu_shell->children; cur != NULL; cur = cur->next) + gtk_widget_set_sensitive(GTK_WIDGET(cur->data), sensitive); +} + +void menu_set_active(GtkItemFactory *ifactory, const gchar *path, + gboolean is_active) +{ + GtkWidget *widget; + + g_return_if_fail(ifactory != NULL); + + widget = gtk_item_factory_get_item(ifactory, path); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), is_active); +} + +void menu_button_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, + gpointer user_data) +{ + GtkWidget *button; + GtkRequisition requisition; + gint button_xpos, button_ypos; + gint xpos, ypos; + gint width, height; + gint scr_width, scr_height; + + g_return_if_fail(x != NULL && y != NULL); + g_return_if_fail(GTK_IS_BUTTON(user_data)); + + button = GTK_WIDGET(user_data); + + gtk_widget_get_child_requisition(GTK_WIDGET(menu), &requisition); + width = requisition.width; + height = requisition.height; + gdk_window_get_origin(button->window, &button_xpos, &button_ypos); + + xpos = button_xpos + button->allocation.x; + ypos = button_ypos + button->allocation.y + button->requisition.height; + + scr_width = gdk_screen_width(); + scr_height = gdk_screen_height(); + + if (xpos + width > scr_width) + xpos -= (xpos + width) - scr_width; + if (ypos + height > scr_height) + ypos = button->requisition.height + height; + if (xpos < 0) + xpos = 0; + if (ypos < 0) + ypos = 0; + + *x = xpos; + *y = ypos; +} + +gint menu_find_option_menu_index(GtkOptionMenu *optmenu, gpointer data, + GCompareFunc func) +{ + GtkWidget *menu; + GtkWidget *menuitem; + gpointer menu_data; + GList *cur; + gint n; + + menu = gtk_option_menu_get_menu(optmenu); + + for (cur = GTK_MENU_SHELL(menu)->children, n = 0; + cur != NULL; cur = cur->next, n++) { + menuitem = GTK_WIDGET(cur->data); + menu_data = g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID); + if (func) { + if (func(menu_data, data) == 0) + return n; + } else if (menu_data == data) + return n; + } + + return -1; +} + +gint menu_get_option_menu_active_index(GtkOptionMenu *optmenu) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + + return GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); +} diff --git a/src/menu.h b/src/menu.h new file mode 100644 index 00000000..eead2819 --- /dev/null +++ b/src/menu.h @@ -0,0 +1,90 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MENU_H__ +#define __MENU_H__ + +#include +#include +#include +#include +#include +#include + +#define MENU_VAL_ID "Sylpheed::Menu::ValueID" + +#define MENUITEM_ADD(menu, menuitem, label, data) \ +{ \ + if (label) \ + menuitem = gtk_menu_item_new_with_label(label); \ + else { \ + menuitem = gtk_menu_item_new(); \ + gtk_widget_set_sensitive(menuitem, FALSE); \ + } \ + gtk_widget_show(menuitem); \ + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); \ + if (data) \ + g_object_set_data(G_OBJECT(menuitem), \ + MENU_VAL_ID, \ + GINT_TO_POINTER(data)); \ +} + +#define menu_set_insensitive_all(menu_shell) \ + menu_set_sensitive_all(menu_shell, FALSE); + +GtkWidget *menubar_create (GtkWidget *window, + GtkItemFactoryEntry *entries, + guint n_entries, + const gchar *path, + gpointer data); +GtkWidget *menu_create_items (GtkItemFactoryEntry *entries, + guint n_entries, + const gchar *path, + GtkItemFactory **factory, + gpointer data); + +GString *menu_factory_get_rc (const gchar *path); +void menu_factory_clear_rc (const gchar *rc_str); +void menu_factory_copy_rc (const gchar *src_path, + const gchar *dest_path); + +void menu_set_sensitive (GtkItemFactory *ifactory, + const gchar *path, + gboolean sensitive); +void menu_set_sensitive_all (GtkMenuShell *menu_shell, + gboolean sensitive); + +void menu_set_active (GtkItemFactory *ifactory, + const gchar *path, + gboolean is_active); + +void menu_button_position (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data); + +gint menu_find_option_menu_index(GtkOptionMenu *optmenu, + gpointer data, + GCompareFunc func); + +gint menu_get_option_menu_active_index + (GtkOptionMenu *optmenu); + +#endif /* __MENU_H__ */ diff --git a/src/message_search.c b/src/message_search.c new file mode 100644 index 00000000..e49daca1 --- /dev/null +++ b/src/message_search.c @@ -0,0 +1,226 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "message_search.h" +#include "messageview.h" +#include "utils.h" +#include "gtkutils.h" +#include "manage_window.h" +#include "alertpanel.h" + +static GtkWidget *window; +static GtkWidget *body_entry; +static GtkWidget *case_checkbtn; +static GtkWidget *backward_checkbtn; +static GtkWidget *search_btn; +static GtkWidget *clear_btn; +static GtkWidget *close_btn; + +static void message_search_create(MessageView *summaryview); +static void message_search_execute(GtkButton *button, gpointer data); +static void message_search_clear(GtkButton *button, gpointer data); +static void body_activated(void); +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data); + +void message_search(MessageView *messageview) +{ + if (!window) + message_search_create(messageview); + else + gtk_widget_hide(window); + + gtk_widget_grab_focus(search_btn); + gtk_widget_grab_focus(body_entry); + gtk_widget_show(window); +} + +static void message_search_create(MessageView *messageview) +{ + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *body_label; + GtkWidget *checkbtn_hbox; + GtkWidget *confirm_area; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), + _("Find in current message")); + gtk_widget_set_size_request (window, 450, -1); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (window), vbox1); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); + + body_label = gtk_label_new (_("Find text:")); + gtk_widget_show (body_label); + gtk_box_pack_start (GTK_BOX (hbox1), body_label, FALSE, FALSE, 0); + + body_entry = gtk_entry_new (); + gtk_widget_show (body_entry); + gtk_box_pack_start (GTK_BOX (hbox1), body_entry, TRUE, TRUE, 0); + g_signal_connect(G_OBJECT(body_entry), "activate", + G_CALLBACK(body_activated), messageview); + + checkbtn_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (checkbtn_hbox); + gtk_box_pack_start (GTK_BOX (vbox1), checkbtn_hbox, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (checkbtn_hbox), 8); + + case_checkbtn = gtk_check_button_new_with_label (_("Case sensitive")); + gtk_widget_show (case_checkbtn); + gtk_box_pack_start (GTK_BOX (checkbtn_hbox), case_checkbtn, + FALSE, FALSE, 0); + + backward_checkbtn = + gtk_check_button_new_with_label (_("Backward search")); + gtk_widget_show (backward_checkbtn); + gtk_box_pack_start (GTK_BOX (checkbtn_hbox), backward_checkbtn, + FALSE, FALSE, 0); + + gtkut_button_set_create(&confirm_area, + &search_btn, _("Search"), + &clear_btn, _("Clear"), + &close_btn, _("Close")); + gtk_widget_show (confirm_area); + gtk_box_pack_start (GTK_BOX (vbox1), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(search_btn); + + g_signal_connect(G_OBJECT(search_btn), "clicked", + G_CALLBACK(message_search_execute), messageview); + g_signal_connect(G_OBJECT(clear_btn), "clicked", + G_CALLBACK(message_search_clear), messageview); + g_signal_connect_closure + (G_OBJECT(close_btn), "clicked", + g_cclosure_new_swap(G_CALLBACK(gtk_widget_hide), + window, NULL), + FALSE); +} + +static void message_search_execute(GtkButton *button, gpointer data) +{ + MessageView *messageview = data; + gboolean case_sens; + gboolean backward; + gboolean all_searched = FALSE; + const gchar *body_str; + + body_str = gtk_entry_get_text(GTK_ENTRY(body_entry)); + if (*body_str == '\0') return; + + case_sens = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(case_checkbtn)); + backward = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(backward_checkbtn)); + + for (;;) { + gchar *str; + AlertValue val; + + if (backward) { + if (messageview_search_string_backward + (messageview, body_str, case_sens) == TRUE) + break; + } else { + if (messageview_search_string + (messageview, body_str, case_sens) == TRUE) + break; + } + + if (all_searched) { + alertpanel_message + (_("Search failed"), + _("Search string not found."), + ALERT_WARNING); + break; + } + + all_searched = TRUE; + + if (backward) + str = _("Beginning of message reached; " + "continue from end?"); + else + str = _("End of message reached; " + "continue from beginning?"); + + val = alertpanel(_("Search finished"), str, + _("Yes"), _("No"), NULL); + if (G_ALERTDEFAULT == val) { + manage_window_focus_in(window, NULL, NULL); + messageview_set_position(messageview, + backward ? -1 : 0); + } else + break; + } +} + +static void message_search_clear(GtkButton *button, gpointer data) +{ + gtk_editable_delete_text(GTK_EDITABLE(body_entry), 0, -1); +} + +static void body_activated(void) +{ + gtk_button_clicked(GTK_BUTTON(search_btn)); +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + gtk_widget_hide(window); + return FALSE; +} diff --git a/src/message_search.h b/src/message_search.h new file mode 100644 index 00000000..1ec45ede --- /dev/null +++ b/src/message_search.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MESSAGE_SEARCH_H__ +#define __MESSAGE_SEARCH_H__ + +#include + +#include "messageview.h" + +void message_search (MessageView *messageview); + +#endif /* __MESSAGE_SEARCH_H__ */ diff --git a/src/messageview.c b/src/messageview.c new file mode 100644 index 00000000..307ead0c --- /dev/null +++ b/src/messageview.c @@ -0,0 +1,877 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "messageview.h" +#include "message_search.h" +#include "headerview.h" +#include "textview.h" +#include "imageview.h" +#include "mimeview.h" +#include "menu.h" +#include "about.h" +#include "filesel.h" +#include "sourcewindow.h" +#include "addressbook.h" +#include "alertpanel.h" +#include "inputdialog.h" +#include "manage_window.h" +#include "procmsg.h" +#include "procheader.h" +#include "procmime.h" +#include "account.h" +#include "action.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "prefs_filter.h" +#include "gtkutils.h" +#include "utils.h" +#include "rfc2015.h" + +static GList *messageview_list = NULL; + +static void messageview_change_view_type(MessageView *messageview, + MessageType type); +static void messageview_destroy_cb (GtkWidget *widget, + MessageView *messageview); +static void messageview_size_allocate_cb(GtkWidget *widget, + GtkAllocation *allocation); +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + MessageView *messageview); + +static void save_as_cb (gpointer data, + guint action, + GtkWidget *widget); +static void print_cb (gpointer data, + guint action, + GtkWidget *widget); +static void close_cb (gpointer data, + guint action, + GtkWidget *widget); +static void copy_cb (gpointer data, + guint action, + GtkWidget *widget); +static void allsel_cb (gpointer data, + guint action, + GtkWidget *widget); +static void search_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void set_charset_cb (gpointer data, + guint action, + GtkWidget *widget); +static void view_source_cb (gpointer data, + guint action, + GtkWidget *widget); +static void show_all_header_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void compose_cb (gpointer data, + guint action, + GtkWidget *widget); +static void reply_cb (gpointer data, + guint action, + GtkWidget *widget); +static void reedit_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void addressbook_open_cb (gpointer data, + guint action, + GtkWidget *widget); +static void add_address_cb (gpointer data, + guint action, + GtkWidget *widget); +static void create_filter_cb (gpointer data, + guint action, + GtkWidget *widget); + +static void about_cb (gpointer data, + guint action, + GtkWidget *widget); + +static GtkItemFactoryEntry msgview_entries[] = +{ + {N_("/_File"), NULL, NULL, 0, ""}, + {N_("/_File/_Save as..."), NULL, save_as_cb, 0, NULL}, + {N_("/_File/_Print..."), NULL, print_cb, 0, NULL}, + {N_("/_File/---"), NULL, NULL, 0, ""}, + {N_("/_File/_Close"), NULL, close_cb, 0, NULL}, + + {N_("/_Edit"), NULL, NULL, 0, ""}, + {N_("/_Edit/_Copy"), NULL, copy_cb, 0, NULL}, + {N_("/_Edit/Select _all"), NULL, allsel_cb, 0, NULL}, + {N_("/_Edit/---"), NULL, NULL, 0, ""}, + {N_("/_Edit/_Find in current message..."), + NULL, search_cb, 0, NULL}, + + {N_("/_View"), NULL, NULL, 0, ""}, + +#define CODESET_SEPARATOR \ + {N_("/_View/_Code set/---"), NULL, NULL, 0, ""} +#define CODESET_ACTION(action) \ + NULL, set_charset_cb, action, "/View/Code set/Auto detect" + + {N_("/_View/_Code set"), NULL, NULL, 0, ""}, + {N_("/_View/_Code set/_Auto detect"), + NULL, set_charset_cb, C_AUTO, ""}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/7bit ascii (US-ASC_II)"), + CODESET_ACTION(C_US_ASCII)}, + +#if HAVE_ICONV + {N_("/_View/_Code set/Unicode (_UTF-8)"), + CODESET_ACTION(C_UTF_8)}, + CODESET_SEPARATOR, +#endif + {N_("/_View/_Code set/Western European (ISO-8859-_1)"), + CODESET_ACTION(C_ISO_8859_1)}, + {N_("/_View/_Code set/Western European (ISO-8859-15)"), + CODESET_ACTION(C_ISO_8859_15)}, + CODESET_SEPARATOR, +#if HAVE_ICONV + {N_("/_View/_Code set/Central European (ISO-8859-_2)"), + CODESET_ACTION(C_ISO_8859_2)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/_Baltic (ISO-8859-13)"), + CODESET_ACTION(C_ISO_8859_13)}, + {N_("/_View/_Code set/Baltic (ISO-8859-_4)"), + CODESET_ACTION(C_ISO_8859_4)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Greek (ISO-8859-_7)"), + CODESET_ACTION(C_ISO_8859_7)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Turkish (ISO-8859-_9)"), + CODESET_ACTION(C_ISO_8859_9)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Cyrillic (ISO-8859-_5)"), + CODESET_ACTION(C_ISO_8859_5)}, + {N_("/_View/_Code set/Cyrillic (KOI8-_R)"), + CODESET_ACTION(C_KOI8_R)}, + {N_("/_View/_Code set/Cyrillic (KOI8-U)"), + CODESET_ACTION(C_KOI8_U)}, + {N_("/_View/_Code set/Cyrillic (Windows-1251)"), + CODESET_ACTION(C_CP1251)}, + CODESET_SEPARATOR, +#endif + {N_("/_View/_Code set/Japanese (ISO-2022-_JP)"), + CODESET_ACTION(C_ISO_2022_JP)}, +#if HAVE_ICONV + {N_("/_View/_Code set/Japanese (ISO-2022-JP-2)"), + CODESET_ACTION(C_ISO_2022_JP_2)}, +#endif + {N_("/_View/_Code set/Japanese (_EUC-JP)"), + CODESET_ACTION(C_EUC_JP)}, + {N_("/_View/_Code set/Japanese (_Shift__JIS)"), + CODESET_ACTION(C_SHIFT_JIS)}, +#if HAVE_ICONV + CODESET_SEPARATOR, + {N_("/_View/_Code set/Simplified Chinese (_GB2312)"), + CODESET_ACTION(C_GB2312)}, + {N_("/_View/_Code set/Traditional Chinese (_Big5)"), + CODESET_ACTION(C_BIG5)}, + {N_("/_View/_Code set/Traditional Chinese (EUC-_TW)"), + CODESET_ACTION(C_EUC_TW)}, + {N_("/_View/_Code set/Chinese (ISO-2022-_CN)"), + CODESET_ACTION(C_ISO_2022_CN)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Korean (EUC-_KR)"), + CODESET_ACTION(C_EUC_KR)}, + {N_("/_View/_Code set/Korean (ISO-2022-KR)"), + CODESET_ACTION(C_ISO_2022_KR)}, + CODESET_SEPARATOR, + {N_("/_View/_Code set/Thai (TIS-620)"), + CODESET_ACTION(C_TIS_620)}, + {N_("/_View/_Code set/Thai (Windows-874)"), + CODESET_ACTION(C_WINDOWS_874)}, +#endif + +#undef CODESET_SEPARATOR +#undef CODESET_ACTION + + {N_("/_View/---"), NULL, NULL, 0, ""}, + {N_("/_View/Mess_age source"), NULL, view_source_cb, 0, NULL}, + {N_("/_View/Show all _header"), NULL, show_all_header_cb, 0, ""}, + + {N_("/_Message"), NULL, NULL, 0, ""}, + {N_("/_Message/Compose _new message"), + NULL, compose_cb, 0, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Reply"), NULL, reply_cb, COMPOSE_REPLY, NULL}, + {N_("/_Message/Repl_y to/_all"), + NULL, reply_cb, COMPOSE_REPLY_TO_ALL, NULL}, + {N_("/_Message/Repl_y to/_sender"), + NULL, reply_cb, COMPOSE_REPLY_TO_SENDER, NULL}, + {N_("/_Message/Repl_y to/mailing _list"), + NULL, reply_cb, COMPOSE_REPLY_TO_LIST, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/_Forward"), NULL, reply_cb, COMPOSE_FORWARD, NULL}, + {N_("/_Message/For_ward as attachment"), + NULL, reply_cb, COMPOSE_FORWARD_AS_ATTACH, NULL}, + {N_("/_Message/Redirec_t"), NULL, reply_cb, COMPOSE_REDIRECT, NULL}, + {N_("/_Message/---"), NULL, NULL, 0, ""}, + {N_("/_Message/Re-_edit"), NULL, reedit_cb, 0, NULL}, + + {N_("/_Tools"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Address book"), NULL, addressbook_open_cb, 0, NULL}, + {N_("/_Tools/Add sender to address boo_k"), + NULL, add_address_cb, 0, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/_Create filter rule"), + NULL, NULL, 0, ""}, + {N_("/_Tools/_Create filter rule/_Automatically"), + NULL, create_filter_cb, FILTER_BY_AUTO, NULL}, + {N_("/_Tools/_Create filter rule/by _From"), + NULL, create_filter_cb, FILTER_BY_FROM, NULL}, + {N_("/_Tools/_Create filter rule/by _To"), + NULL, create_filter_cb, FILTER_BY_TO, NULL}, + {N_("/_Tools/_Create filter rule/by _Subject"), + NULL, create_filter_cb, FILTER_BY_SUBJECT, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, ""}, + {N_("/_Tools/Actio_ns"), NULL, NULL, 0, ""}, + + {N_("/_Help"), NULL, NULL, 0, ""}, + {N_("/_Help/_About"), NULL, about_cb, 0, NULL} +}; + + +MessageView *messageview_create(void) +{ + MessageView *messageview; + GtkWidget *vbox; + HeaderView *headerview; + TextView *textview; + MimeView *mimeview; + + debug_print(_("Creating message view...\n")); + messageview = g_new0(MessageView, 1); + + messageview->type = MVIEW_TEXT; + + headerview = headerview_create(); + + textview = textview_create(); + textview->messageview = messageview; + + mimeview = mimeview_create(); + mimeview->textview = textview_create(); + mimeview->textview->messageview = messageview; + mimeview->imageview = imageview_create(); + mimeview->imageview->messageview = messageview; + mimeview->messageview = messageview; + + vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET_PTR(headerview), + FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET_PTR(textview), + TRUE, TRUE, 0); + gtk_widget_show(vbox); + + /* to remove without destroyed */ + gtk_widget_ref(GTK_WIDGET_PTR(textview)); + gtk_widget_ref(GTK_WIDGET_PTR(mimeview)); + gtk_widget_ref(GTK_WIDGET_PTR(mimeview->textview)); + gtk_widget_ref(GTK_WIDGET_PTR(mimeview->imageview)); + + messageview->vbox = vbox; + messageview->new_window = FALSE; + messageview->window = NULL; + messageview->window_vbox = NULL; + messageview->headerview = headerview; + messageview->textview = textview; + messageview->mimeview = mimeview; + + messageview->statusbar = NULL; + messageview->statusbar_cid = 0; + + return messageview; +} + +MessageView *messageview_create_with_new_window(void) +{ + MessageView *msgview; + GtkWidget *window; + GtkWidget *window_vbox; + GtkWidget *body_vbox; + GtkWidget *menubar; + GtkItemFactory *ifactory; + GtkWidget *statusbar; + guint n_menu_entries; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Sylpheed - Message View")); + gtk_window_set_wmclass(GTK_WINDOW(window), "message_view", "Sylpheed"); + gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE); + gtk_widget_set_size_request(window, prefs_common.msgwin_width, + prefs_common.msgwin_height); + + msgview = messageview_create(); + + window_vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), window_vbox); + + g_signal_connect(G_OBJECT(window), "size_allocate", + G_CALLBACK(messageview_size_allocate_cb), + msgview); + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(messageview_destroy_cb), msgview); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), msgview); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + n_menu_entries = sizeof(msgview_entries) / sizeof (msgview_entries[0]); + menubar = menubar_create(window, msgview_entries, n_menu_entries, + "", msgview); +#warning FIXME_GTK2 +#if 0 + menu_factory_copy_rc("
", ""); +#endif + gtk_box_pack_start(GTK_BOX(window_vbox), menubar, FALSE, TRUE, 0); + + body_vbox = gtk_vbox_new(FALSE, BORDER_WIDTH); + gtk_container_set_border_width(GTK_CONTAINER(body_vbox), BORDER_WIDTH); + gtk_box_pack_start(GTK_BOX(window_vbox), body_vbox, TRUE, TRUE, 0); + + gtk_box_pack_start(GTK_BOX(body_vbox), GTK_WIDGET_PTR(msgview), + TRUE, TRUE, 0); + gtk_widget_grab_focus(msgview->textview->text); + + statusbar = gtk_statusbar_new(); + gtk_box_pack_end(GTK_BOX(body_vbox), statusbar, FALSE, FALSE, 0); + msgview->statusbar = statusbar; + msgview->statusbar_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR(statusbar), "Message View"); + + gtk_widget_show_all(window); + + msgview->new_window = TRUE; + msgview->window = window; + msgview->window_vbox = window_vbox; + msgview->body_vbox = body_vbox; + msgview->visible = TRUE; + + messageview_init(msgview); + + ifactory = gtk_item_factory_from_widget(menubar); + action_update_msgview_menu(ifactory, msgview); + + messageview_list = g_list_append(messageview_list, msgview); + + return msgview; +} + +void messageview_init(MessageView *messageview) +{ + headerview_init(messageview->headerview); + textview_init(messageview->textview); + mimeview_init(messageview->mimeview); + /* messageview_set_font(messageview); */ +} + +GList *messageview_get_window_list(void) +{ + return messageview_list; +} + +gint messageview_show(MessageView *messageview, MsgInfo *msginfo, + gboolean all_headers) +{ + gchar *file; + MimeInfo *mimeinfo; + + g_return_val_if_fail(msginfo != NULL, -1); + + mimeinfo = procmime_scan_message(msginfo); + if (!mimeinfo) { + messageview_change_view_type(messageview, MVIEW_TEXT); + textview_show_error(messageview->textview); + return -1; + } + + file = procmsg_get_message_file_path(msginfo); + if (!file) { + g_warning("can't get message file path.\n"); + procmime_mimeinfo_free_all(mimeinfo); + messageview_change_view_type(messageview, MVIEW_TEXT); + textview_show_error(messageview->textview); + return -1; + } + + if (messageview->msginfo != msginfo) { + procmsg_msginfo_free(messageview->msginfo); + messageview->msginfo = procmsg_msginfo_get_full_info(msginfo); + } + headerview_show(messageview->headerview, messageview->msginfo); + + textview_set_all_headers(messageview->textview, all_headers); + textview_set_all_headers(messageview->mimeview->textview, all_headers); + + if (mimeinfo->mime_type != MIME_TEXT && + mimeinfo->mime_type != MIME_TEXT_HTML) { + messageview_change_view_type(messageview, MVIEW_MIME); + mimeview_show_message(messageview->mimeview, mimeinfo, file); + } else { + messageview_change_view_type(messageview, MVIEW_TEXT); + textview_show_message(messageview->textview, mimeinfo, file); + procmime_mimeinfo_free_all(mimeinfo); + } + + g_free(file); + + return 0; +} + +static void messageview_change_view_type(MessageView *messageview, + MessageType type) +{ + TextView *textview = messageview->textview; + MimeView *mimeview = messageview->mimeview; + + if (messageview->type == type) return; + + if (type == MVIEW_MIME) { + gtkut_container_remove + (GTK_CONTAINER(GTK_WIDGET_PTR(messageview)), + GTK_WIDGET_PTR(textview)); + gtk_box_pack_start(GTK_BOX(messageview->vbox), + GTK_WIDGET_PTR(mimeview), TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(mimeview->vbox), + GTK_WIDGET_PTR(textview)); + } else if (type == MVIEW_TEXT) { + gtkut_container_remove + (GTK_CONTAINER(GTK_WIDGET_PTR(messageview)), + GTK_WIDGET_PTR(mimeview)); + mimeview_clear(mimeview); + + if (mimeview->vbox == GTK_WIDGET_PTR(textview)->parent) + gtkut_container_remove(GTK_CONTAINER(mimeview->vbox), + GTK_WIDGET_PTR(textview)); + + gtk_box_pack_start(GTK_BOX(messageview->vbox), + GTK_WIDGET_PTR(textview), TRUE, TRUE, 0); + } else + return; + + messageview->type = type; +} + +void messageview_clear(MessageView *messageview) +{ + procmsg_msginfo_free(messageview->msginfo); + messageview->msginfo = NULL; + messageview_change_view_type(messageview, MVIEW_TEXT); + headerview_clear(messageview->headerview); + textview_clear(messageview->textview); + mimeview_clear(messageview->mimeview); +} + +void messageview_destroy(MessageView *messageview) +{ + GtkWidget *textview = GTK_WIDGET_PTR(messageview->textview); + GtkWidget *imageview = GTK_WIDGET_PTR(messageview->mimeview->imageview); + GtkWidget *mimeview = GTK_WIDGET_PTR(messageview->mimeview); + + messageview_list = g_list_remove(messageview_list, messageview); + + headerview_destroy(messageview->headerview); + textview_destroy(messageview->textview); + mimeview_destroy(messageview->mimeview); + + procmsg_msginfo_free(messageview->msginfo); + + g_free(messageview); + + gtk_widget_unref(textview); + gtk_widget_unref(imageview); + gtk_widget_unref(mimeview); +} + +void messageview_quote_color_set(void) +{ +} + +void messageview_set_font(MessageView *messageview) +{ + textview_set_font(messageview->textview, NULL); +} + +TextView *messageview_get_current_textview(MessageView *messageview) +{ + TextView *text = NULL; + + if (messageview->type == MVIEW_TEXT) + text = messageview->textview; + else if (messageview->type == MVIEW_MIME) { + if (gtk_notebook_get_current_page + (GTK_NOTEBOOK(messageview->mimeview->notebook)) == 0) + text = messageview->textview; + else if (messageview->mimeview->type == MIMEVIEW_TEXT) + text = messageview->mimeview->textview; + } + + return text; +} + +MimeInfo *messageview_get_selected_mime_part(MessageView *messageview) +{ + if (messageview->type == MVIEW_MIME) + return mimeview_get_selected_part(messageview->mimeview); + + return NULL; +} + +void messageview_copy_clipboard(MessageView *messageview) +{ + TextView *text; + + text = messageview_get_current_textview(messageview); + if (text) { + GtkTextView *textview = GTK_TEXT_VIEW(text->text); + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + buffer = gtk_text_view_get_buffer(textview); + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_text_buffer_copy_clipboard(buffer, clipboard); + } +} + +void messageview_select_all(MessageView *messageview) +{ + TextView *text; + + text = messageview_get_current_textview(messageview); + if (text) + gtk_editable_select_region(GTK_EDITABLE(text->text), 0, -1); +} + +void messageview_set_position(MessageView *messageview, gint pos) +{ + textview_set_position(messageview->textview, pos); +} + +gboolean messageview_search_string(MessageView *messageview, const gchar *str, + gboolean case_sens) +{ + return textview_search_string(messageview->textview, str, case_sens); + return FALSE; +} + +gboolean messageview_search_string_backward(MessageView *messageview, + const gchar *str, + gboolean case_sens) +{ + return textview_search_string_backward(messageview->textview, + str, case_sens); + return FALSE; +} + +gboolean messageview_is_visible(MessageView *messageview) +{ + return messageview->visible; +} + +void messageview_save_as(MessageView *messageview) +{ + gchar *filename = NULL; + MsgInfo *msginfo; + gchar *src, *dest; + + if (!messageview->msginfo) return; + msginfo = messageview->msginfo; + + if (msginfo->subject) { + Xstrdup_a(filename, msginfo->subject, return); + subst_for_filename(filename); + } + dest = filesel_select_file(_("Save as"), filename); + if (!dest) return; + if (is_file_exist(dest)) { + AlertValue aval; + + aval = alertpanel(_("Overwrite"), + _("Overwrite existing file?"), + _("OK"), _("Cancel"), NULL); + if (G_ALERTDEFAULT != aval) return; + } + + src = procmsg_get_message_file(msginfo); + if (copy_file(src, dest, TRUE) < 0) { + alertpanel_error(_("Can't save the file `%s'."), + g_basename(dest)); + } + g_free(src); +} + +static void messageview_destroy_cb(GtkWidget *widget, MessageView *messageview) +{ + messageview_destroy(messageview); +} + +static void messageview_size_allocate_cb(GtkWidget *widget, + GtkAllocation *allocation) +{ + g_return_if_fail(allocation != NULL); + + prefs_common.msgwin_width = allocation->width; + prefs_common.msgwin_height = allocation->height; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + MessageView *messageview) +{ + if (event && event->keyval == GDK_Escape && messageview->window) + gtk_widget_destroy(messageview->window); + return FALSE; +} + +static void save_as_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + messageview_save_as(messageview); +} + +static void print_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + gchar *cmdline; + gchar *p; + + if (!messageview->msginfo) return; + + cmdline = input_dialog(_("Print"), + _("Enter the print command line:\n" + "(`%s' will be replaced with file name)"), + prefs_common.print_cmd); + if (!cmdline) return; + if (!(p = strchr(cmdline, '%')) || *(p + 1) != 's' || + strchr(p + 2, '%')) { + alertpanel_error(_("Print command line is invalid:\n`%s'"), + cmdline); + g_free(cmdline); + return; + } + + procmsg_print_message(messageview->msginfo, cmdline); + g_free(cmdline); +} + +static void close_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + gtk_widget_destroy(messageview->window); +} + +static void copy_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + messageview_copy_clipboard(messageview); +} + +static void allsel_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + messageview_select_all(messageview); +} + +static void search_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + message_search(messageview); +} + +static void set_charset_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + const gchar *charset; + + if (GTK_CHECK_MENU_ITEM(widget)->active) { + charset = conv_get_charset_str((CharSet)action); + g_free(messageview->forced_charset); + messageview->forced_charset = g_strdup(charset); + messageview_show(messageview, messageview->msginfo, FALSE); + } +} + +static void view_source_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + SourceWindow *srcwin; + + if (!messageview->msginfo) return; + + srcwin = source_window_create(); + source_window_show_msg(srcwin, messageview->msginfo); + source_window_show(srcwin); +} + +static void show_all_header_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + MsgInfo *msginfo = messageview->msginfo; + + if (!msginfo) return; + messageview->msginfo = NULL; + messageview_show(messageview, msginfo, + GTK_CHECK_MENU_ITEM(widget)->active); + procmsg_msginfo_free(msginfo); +} + +static void compose_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + PrefsAccount *ac = NULL; + FolderItem *item = NULL; + + if (messageview->msginfo) + item = messageview->msginfo->folder; + + if (item) { + ac = account_find_from_item(item); + if (ac && ac->protocol == A_NNTP && + FOLDER_TYPE(item->folder) == F_NEWS) { + compose_new(ac, item, item->path, NULL); + return; + } + } + + compose_new(ac, item, NULL, NULL); +} + +static void reply_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + GSList *mlist = NULL; + MsgInfo *msginfo; + gchar *text = NULL; + ComposeMode mode = (ComposeMode)action; + + msginfo = messageview->msginfo; + mlist = g_slist_append(NULL, msginfo); + + text = gtkut_editable_get_selection + (GTK_EDITABLE(messageview->textview->text)); + if (text && *text == '\0') { + g_free(text); + text = NULL; + } + + if (!COMPOSE_QUOTE_MODE(mode)) + mode |= prefs_common.reply_with_quote + ? COMPOSE_WITH_QUOTE : COMPOSE_WITHOUT_QUOTE; + + switch (COMPOSE_MODE(mode)) { + case COMPOSE_REPLY: + case COMPOSE_REPLY_TO_SENDER: + case COMPOSE_REPLY_TO_ALL: + case COMPOSE_REPLY_TO_LIST: + compose_reply(msginfo, msginfo->folder, mode, text); + break; + case COMPOSE_FORWARD: + compose_forward(mlist, msginfo->folder, FALSE, text); + break; + case COMPOSE_FORWARD_AS_ATTACH: + compose_forward(mlist, msginfo->folder, TRUE, NULL); + break; + case COMPOSE_REDIRECT: + compose_redirect(msginfo, msginfo->folder); + break; + default: + g_warning("messageview.c: reply_cb(): invalid mode: %d\n", + mode); + } + + /* summary_set_marks_selected(summaryview); */ + g_free(text); + g_slist_free(mlist); +} + +static void reedit_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + MsgInfo *msginfo; + + if (!messageview->msginfo) return; + msginfo = messageview->msginfo; + if (!msginfo->folder) return; + if (msginfo->folder->stype != F_OUTBOX && + msginfo->folder->stype != F_DRAFT && + msginfo->folder->stype != F_QUEUE) return; + + compose_reedit(msginfo); +} + +static void addressbook_open_cb(gpointer data, guint action, GtkWidget *widget) +{ + addressbook_open(NULL); +} + +static void add_address_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + MsgInfo *msginfo; + gchar *from; + + if (!messageview->msginfo) return; + msginfo = messageview->msginfo; + Xstrdup_a(from, msginfo->from, return); + eliminate_address_comment(from); + extract_address(from); + addressbook_add_contact(msginfo->fromname, from, NULL); +} + +static void create_filter_cb(gpointer data, guint action, GtkWidget *widget) +{ + MessageView *messageview = (MessageView *)data; + gchar *header = NULL; + gchar *key = NULL; + + if (!messageview->msginfo) return; + + procmsg_get_filter_keyword(messageview->msginfo, &header, &key, + (PrefsFilterType)action); + prefs_filter_open(messageview->msginfo, header); + + g_free(header); + g_free(key); +} + +static void about_cb(gpointer data, guint action, GtkWidget *widget) +{ + about_show(); +} diff --git a/src/messageview.h b/src/messageview.h new file mode 100644 index 00000000..99c71631 --- /dev/null +++ b/src/messageview.h @@ -0,0 +1,96 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MESSAGEVIEW_H__ +#define __MESSAGEVIEW_H__ + +#include +#include + +typedef struct _MessageView MessageView; + +#include "mainwindow.h" +#include "headerview.h" +#include "textview.h" +#include "mimeview.h" +#include "procmsg.h" +#include "procmime.h" + +typedef enum +{ + MVIEW_TEXT, + MVIEW_MIME +} MessageType; + +struct _MessageView +{ + GtkWidget *vbox; + + MessageType type; + gboolean new_window; + GtkWidget *window; + GtkWidget *window_vbox; + GtkWidget *body_vbox; + + HeaderView *headerview; + TextView *textview; + MimeView *mimeview; + + GtkWidget *statusbar; + gint statusbar_cid; + + MainWindow *mainwin; + + MsgInfo *msginfo; + + gchar *forced_charset; + + gboolean visible; +}; + +MessageView *messageview_create (void); +MessageView *messageview_create_with_new_window (void); +void messageview_init (MessageView *messageview); +gint messageview_show (MessageView *messageview, + MsgInfo *msginfo, + gboolean all_headers); +void messageview_clear (MessageView *messageview); +void messageview_destroy (MessageView *messageview); + +void messageview_quote_color_set (void); +void messageview_set_font (MessageView *messageview); + +TextView *messageview_get_current_textview (MessageView *messageview); +MimeInfo *messageview_get_selected_mime_part (MessageView *messageview); + +void messageview_copy_clipboard (MessageView *messageview); +void messageview_select_all (MessageView *messageview); +void messageview_set_position (MessageView *messageview, + gint pos); + +gboolean messageview_search_string (MessageView *messageview, + const gchar *str, + gboolean case_sens); +gboolean messageview_search_string_backward (MessageView *messageview, + const gchar *str, + gboolean case_sens); + +gboolean messageview_is_visible (MessageView *messageview); + +#endif /* __MESSAGEVIEW_H__ */ diff --git a/src/mgutils.c b/src/mgutils.c new file mode 100644 index 00000000..45ba8f4d --- /dev/null +++ b/src/mgutils.c @@ -0,0 +1,220 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * General functions for create common address book entries. + */ + +#include +#include +#include + +#include "mgutils.h" + +/* +* Dump linked list of character strings (for debug). +*/ +void mgu_print_list( GSList *list, FILE *stream ) { + GSList *node = list; + while( node ) { + fprintf( stream, "\t- >%s<\n", (gchar *)node->data ); + node = g_slist_next( node ); + } +} + +/* +* Dump linked list of character strings (for debug). +*/ +void mgu_print_dlist( GList *list, FILE *stream ) { + GList *node = list; + while( node ) { + fprintf( stream, "\t- >%s<\n", (gchar *)node->data ); + node = g_list_next( node ); + } +} + +/* +* Free linked list of character strings. +*/ +void mgu_free_list( GSList *list ) { + GSList *node = list; + while( node ) { + g_free( node->data ); + node->data = NULL; + node = g_slist_next( node ); + } + g_slist_free( list ); +} + +/* +* Free linked list of character strings. +*/ +void mgu_free_dlist( GList *list ) { + GList *node = list; + while( node ) { + g_free( node->data ); + node->data = NULL; + node = g_list_next( node ); + } + g_list_free( list ); +} + +/* +* Coalesce linked list of characaters into one long string. +*/ +gchar *mgu_list_coalesce( GSList *list ) { + gchar *str = NULL; + gchar *buf = NULL; + gchar *start = NULL; + GSList *node = NULL; + gint len; + + if( ! list ) return NULL; + + /* Calculate maximum length of text */ + len = 0; + node = list; + while( node ) { + str = node->data; + len += 1 + strlen( str ); + node = g_slist_next( node ); + } + + /* Create new buffer. */ + buf = g_new0( gchar, len+1 ); + start = buf; + node = list; + while( node ) { + str = node->data; + len = strlen( str ); + strcpy( start, str ); + start += len; + node = g_slist_next( node ); + } + return buf; +} + +struct mgu_error_entry { + gint e_code; + gchar *e_reason; +}; + +static const struct mgu_error_entry mgu_error_list[] = { + { MGU_SUCCESS, "Success" }, + { MGU_BAD_ARGS, "Bad arguments" }, + { MGU_NO_FILE, "File not specified" }, + { MGU_OPEN_FILE, "Error opening file" }, + { MGU_ERROR_READ, "Error reading file" }, + { MGU_EOF, "End of file encountered" }, + { MGU_OO_MEMORY, "Error allocating memory" }, + { MGU_BAD_FORMAT, "Bad file format" }, + { MGU_LDAP_CONNECT, "Error connecting to LDAP server" }, + { MGU_LDAP_INIT, "Error initializing LDAP" }, + { MGU_LDAP_BIND, "Error binding to LDAP server" }, + { MGU_LDAP_SEARCH, "Error searching LDAP database" }, + { MGU_LDAP_TIMEOUT, "Timeout performing LDAP operation" }, + { MGU_LDAP_CRITERIA, "Error in LDAP search criteria" }, + { MGU_LDAP_CRITERIA, "Error in LDAP search criteria" }, + { MGU_LDAP_NOENTRIES, "No LDAP entries found for search criteria" }, + { MGU_ERROR_WRITE, "Error writing to file" }, + { MGU_OPEN_DIRECTORY, "Error opening directory" }, + { MGU_NO_PATH, "No path specified" }, + { -999, NULL } +}; + +static const struct mgu_error_entry *mgu_error_find( gint err ) { + gint i; + for ( i = 0; mgu_error_list[i].e_code != -999; i++ ) { + if ( err == mgu_error_list[i].e_code ) + return & mgu_error_list[i]; + } + return NULL; +} + +/* +* Return error message for specified error code. +*/ +gchar *mgu_error2string( gint err ) { + const struct mgu_error_entry *e; + e = mgu_error_find( err ); + return ( e != NULL ) ? e->e_reason : "Unknown error"; +} + +/* +* Replace existing string with new string. +*/ +gchar *mgu_replace_string( gchar *str, const gchar *value ) { + if( str ) g_free( str ); + if( value ) { + str = g_strdup( value ); + g_strstrip( str ); + } + else { + str = NULL; + } + return str; +} + +/* +* Clear a linked list by setting node data pointers to NULL. Note that +* items are not freed. +*/ +void mgu_clear_slist( GSList *list ) { + GSList *node = list; + while( node ) { + node->data = NULL; + node = g_slist_next( node ); + } +} + +/* +* Clear a linked list by setting node data pointers to NULL. Note that +* items are not freed. +*/ +void mgu_clear_list( GList *list ) { + GList *node = list; + while( node ) { + node->data = NULL; + node = g_list_next( node ); + } +} + +/* +* Test and reformat an email address. +* Enter: address. +* Return: Address, or NULL if address is empty. +* Note: Leading and trailing white space is removed. +*/ +gchar *mgu_email_check_empty( gchar *address ) { + gchar *retVal = NULL; + if( address ) { + retVal = g_strdup( address ); + retVal = g_strchug( retVal ); + retVal = g_strchomp( retVal ); + if( *retVal == '\0' ) { + g_free( retVal ); + retVal = NULL; + } + } + return retVal; +} + +/* +* End of Source. +*/ diff --git a/src/mgutils.h b/src/mgutils.h new file mode 100644 index 00000000..626bf75c --- /dev/null +++ b/src/mgutils.h @@ -0,0 +1,62 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * General definitions for common address book entries. + */ + +#ifndef __MGUTILS_H__ +#define __MGUTILS_H__ + +#include +#include + +/* Error codes */ +#define MGU_SUCCESS 0 +#define MGU_BAD_ARGS -1 +#define MGU_NO_FILE -2 +#define MGU_OPEN_FILE -3 +#define MGU_ERROR_READ -4 +#define MGU_EOF -5 +#define MGU_OO_MEMORY -6 +#define MGU_BAD_FORMAT -7 +#define MGU_LDAP_CONNECT -8 +#define MGU_LDAP_INIT -9 +#define MGU_LDAP_BIND -10 +#define MGU_LDAP_SEARCH -11 +#define MGU_LDAP_TIMEOUT -12 +#define MGU_LDAP_CRITERIA -13 +#define MGU_LDAP_NOENTRIES -14 +#define MGU_ERROR_WRITE -15 +#define MGU_OPEN_DIRECTORY -16 +#define MGU_NO_PATH -17 + +/* Function prototypes */ +void mgu_print_list ( GSList *list, FILE *stream ); +void mgu_print_dlist ( GList *list, FILE *stream ); +void mgu_free_list ( GSList *list ); +void mgu_free_dlist ( GList *list ); +gchar *mgu_list_coalesce ( GSList *list ); +gchar *mgu_error2string ( gint err ); +gchar *mgu_replace_string ( gchar *str, const gchar *value ); +void mgu_clear_slist ( GSList *list ); +void mgu_clear_list ( GList *list ); +gchar *mgu_email_check_empty ( gchar *address ); + +#endif /* __MGUTILS_H__ */ diff --git a/src/mh.c b/src/mh.c new file mode 100644 index 00000000..7c7e5bd3 --- /dev/null +++ b/src/mh.c @@ -0,0 +1,1282 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include + +#undef MEASURE_TIME + +#ifdef MEASURE_TIME +# include +#endif + +#include "intl.h" +#include "folder.h" +#include "mh.h" +#include "procmsg.h" +#include "procheader.h" +#include "utils.h" + +static void mh_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static Folder *mh_folder_new (const gchar *name, + const gchar *path); +static void mh_folder_destroy (Folder *folder); + +static GSList *mh_get_msg_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static gchar *mh_fetch_msg (Folder *folder, + FolderItem *item, + gint num); +static MsgInfo *mh_get_msginfo (Folder *folder, + FolderItem *item, + gint num); +static gint mh_add_msg (Folder *folder, + FolderItem *dest, + const gchar *file, + MsgFlags *flags, + gboolean remove_source); +static gint mh_add_msgs (Folder *folder, + FolderItem *dest, + GSList *file_list, + gboolean remove_source, + gint *first); +static gint mh_move_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint mh_move_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); +static gint mh_copy_msg (Folder *folder, + FolderItem *dest, + MsgInfo *msginfo); +static gint mh_copy_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); +static gint mh_remove_msg (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); +static gint mh_remove_all_msg (Folder *folder, + FolderItem *item); +static gboolean mh_is_msg_changed (Folder *folder, + FolderItem *item, + MsgInfo *msginfo); +static gint mh_close (Folder *folder, + FolderItem *item); + +static gint mh_scan_folder_full (Folder *folder, + FolderItem *item, + gboolean count_sum); +static gint mh_scan_folder (Folder *folder, + FolderItem *item); +static gint mh_scan_tree (Folder *folder); + +static gint mh_create_tree (Folder *folder); +static FolderItem *mh_create_folder (Folder *folder, + FolderItem *parent, + const gchar *name); +static gint mh_rename_folder (Folder *folder, + FolderItem *item, + const gchar *name); +static gint mh_remove_folder (Folder *folder, + FolderItem *item); + +static gchar *mh_get_new_msg_filename (FolderItem *dest); + +static gint mh_do_move_msgs (Folder *folder, + FolderItem *dest, + GSList *msglist); + +static time_t mh_get_mtime (FolderItem *item); +static GSList *mh_get_uncached_msgs (GHashTable *msg_table, + FolderItem *item); +static MsgInfo *mh_parse_msg (const gchar *file, + FolderItem *item); +static void mh_remove_missing_folder_items (Folder *folder); +static void mh_scan_tree_recursive (FolderItem *item); + +static gboolean mh_rename_folder_func (GNode *node, + gpointer data); + +static FolderClass mh_class = +{ + F_MH, + + mh_folder_new, + mh_folder_destroy, + + mh_scan_tree, + mh_create_tree, + + mh_get_msg_list, + mh_fetch_msg, + mh_get_msginfo, + mh_add_msg, + mh_add_msgs, + mh_move_msg, + mh_move_msgs, + mh_copy_msg, + mh_copy_msgs, + mh_remove_msg, + NULL, + mh_remove_all_msg, + mh_is_msg_changed, + mh_close, + mh_scan_folder, + + mh_create_folder, + mh_rename_folder, + mh_remove_folder, +}; + + +FolderClass *mh_get_class(void) +{ + return &mh_class; +} + +static Folder *mh_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(MHFolder, 1); + mh_folder_init(folder, name, path); + + return folder; +} + +static void mh_folder_destroy(Folder *folder) +{ + folder_local_folder_destroy(LOCAL_FOLDER(folder)); +} + +static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path) +{ + folder->klass = mh_get_class(); + folder_local_folder_init(folder, name, path); +} + +static GSList *mh_get_msg_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + GSList *mlist; + GHashTable *msg_table; + time_t cur_mtime; +#ifdef MEASURE_TIME + struct timeval tv_before, tv_after, tv_result; + + gettimeofday(&tv_before, NULL); +#endif + + g_return_val_if_fail(item != NULL, NULL); + + cur_mtime = mh_get_mtime(item); + + if (use_cache && item->mtime == cur_mtime) { + debug_print("Folder is not modified.\n"); + mlist = procmsg_read_cache(item, FALSE); + if (!mlist) + mlist = mh_get_uncached_msgs(NULL, item); + } else if (use_cache) { + GSList *newlist; + + mlist = procmsg_read_cache(item, TRUE); + msg_table = procmsg_msg_hash_table_create(mlist); + + newlist = mh_get_uncached_msgs(msg_table, item); + if (msg_table) + g_hash_table_destroy(msg_table); + + mlist = g_slist_concat(mlist, newlist); + } else + mlist = mh_get_uncached_msgs(NULL, item); + + item->mtime = cur_mtime; + + procmsg_set_flags(mlist, item); + + mlist = procmsg_sort_msg_list(mlist, item->sort_key, item->sort_type); + +#ifdef MEASURE_TIME + gettimeofday(&tv_after, NULL); + + timersub(&tv_after, &tv_before, &tv_result); + g_print("mh_get_msg_list: %s: elapsed time: %ld.%06ld sec\n", + item->path, tv_result.tv_sec, tv_result.tv_usec); +#endif + + return mlist; +} + +static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, gint num) +{ + gchar *path; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(num > 0, NULL); + + if (item->last_num < 0 || num > item->last_num) { + mh_scan_folder(folder, item); + if (item->last_num < 0) return NULL; + } + + g_return_val_if_fail(num <= item->last_num, NULL); + + path = folder_item_get_path(item); + file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL); + g_free(path); + if (!is_file_exist(file)) { + g_free(file); + return NULL; + } + + return file; +} + +static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num) +{ + MsgInfo *msginfo; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(num > 0, NULL); + + file = mh_fetch_msg(folder, item, num); + if (!file) return NULL; + + msginfo = mh_parse_msg(file, item); + if (msginfo) + msginfo->msgnum = num; + + g_free(file); + + return msginfo; +} + +static gchar *mh_get_new_msg_filename(FolderItem *dest) +{ + gchar *destfile; + gchar *destpath; + + destpath = folder_item_get_path(dest); + g_return_val_if_fail(destpath != NULL, NULL); + + if (!is_dir_exist(destpath)) + make_dir_hier(destpath); + + for (;;) { + destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR, + dest->last_num + 1); + if (is_file_entry_exist(destfile)) { + dest->last_num++; + g_free(destfile); + } else + break; + } + + g_free(destpath); + + return destfile; +} + +#define SET_DEST_MSG_FLAGS(fp, dest, n, fl) \ +{ \ + MsgInfo newmsginfo; \ + \ + newmsginfo.msgnum = n; \ + newmsginfo.flags = fl; \ + if (dest->stype == F_OUTBOX || \ + dest->stype == F_QUEUE || \ + dest->stype == F_DRAFT || \ + dest->stype == F_TRASH) \ + MSG_UNSET_PERM_FLAGS(newmsginfo.flags, \ + MSG_NEW|MSG_UNREAD|MSG_DELETED); \ + \ + if (fp) \ + procmsg_write_flags(&newmsginfo, fp); \ + else if (dest->opened) \ + procmsg_add_flags(dest, n, newmsginfo.flags); \ +} + +static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file, + MsgFlags *flags, gboolean remove_source) +{ + GSList file_list; + MsgFileInfo fileinfo; + + g_return_val_if_fail(file != NULL, -1); + + fileinfo.file = (gchar *)file; + fileinfo.flags = flags; + file_list.data = &fileinfo; + file_list.next = NULL; + + return mh_add_msgs(folder, dest, &file_list, remove_source, NULL); +} + +static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, + gboolean remove_source, gint *first) +{ + gchar *destfile; + GSList *cur; + MsgFileInfo *fileinfo; + gint first_ = 0; + FILE *fp; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(file_list != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + if ((((MsgFileInfo *)file_list->data)->flags == NULL && + file_list->next == NULL) || dest->opened) + fp = NULL; + else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL) + g_warning("Can't open mark file.\n"); + + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + + destfile = mh_get_new_msg_filename(dest); + if (destfile == NULL) return -1; + if (first_ == 0 || first_ > dest->last_num + 1) + first_ = dest->last_num + 1; + + if (link(fileinfo->file, destfile) < 0) { + if (copy_file(fileinfo->file, destfile, TRUE) < 0) { + g_warning(_("can't copy message %s to %s\n"), + fileinfo->file, destfile); + g_free(destfile); + return -1; + } + } + + g_free(destfile); + dest->last_num++; + dest->total++; + dest->updated = TRUE; + + if (fileinfo->flags) { + if (MSG_IS_RECEIVED(*fileinfo->flags)) { + if (dest->unmarked_num == 0) + dest->new = 0; + dest->unmarked_num++; + procmsg_add_mark_queue(dest, dest->last_num, + *fileinfo->flags); + } else { + SET_DEST_MSG_FLAGS(fp, dest, dest->last_num, + *fileinfo->flags); + } + if (MSG_IS_NEW(*fileinfo->flags)) + dest->new++; + if (MSG_IS_UNREAD(*fileinfo->flags)) + dest->unread++; + } else { + if (dest->unmarked_num == 0) + dest->new = 0; + dest->unmarked_num++; + dest->new++; + dest->unread++; + } + } + + if (fp) fclose(fp); + + if (first) + *first = first_; + + if (remove_source) { + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + if (unlink(fileinfo->file) < 0) + FILE_OP_ERROR(fileinfo->file, "unlink"); + } + } + + return dest->last_num; +} + +static gint mh_do_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + FolderItem *src; + gchar *srcfile; + gchar *destfile; + FILE *fp; + GSList *cur; + MsgInfo *msginfo; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + if (dest->opened) + fp = NULL; + else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL) + g_warning(_("Can't open mark file.\n")); + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + src = msginfo->folder; + + if (src == dest) { + g_warning(_("the src folder is identical to the dest.\n")); + continue; + } + debug_print("Moving message %s%c%d to %s ...\n", + src->path, G_DIR_SEPARATOR, msginfo->msgnum, + dest->path); + + destfile = mh_get_new_msg_filename(dest); + if (!destfile) break; + srcfile = procmsg_get_message_file(msginfo); + + if (move_file(srcfile, destfile, FALSE) < 0) { + g_free(srcfile); + g_free(destfile); + break; + } + + g_free(srcfile); + g_free(destfile); + src->total--; + src->updated = TRUE; + dest->last_num++; + dest->total++; + dest->updated = TRUE; + + if (fp) { + SET_DEST_MSG_FLAGS(fp, dest, dest->last_num, + msginfo->flags); + } + + if (MSG_IS_NEW(msginfo->flags)) { + src->new--; + dest->new++; + } + if (MSG_IS_UNREAD(msginfo->flags)) { + src->unread--; + dest->unread++; + } + + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + } + + if (fp) fclose(fp); + + return dest->last_num; +} + +static gint mh_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return mh_move_msgs(folder, dest, &msglist); +} + +static gint mh_move_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + MsgInfo *msginfo; + GSList *file_list; + gint ret = 0; + gint first; + + msginfo = (MsgInfo *)msglist->data; + if (folder == msginfo->folder->folder) + return mh_do_move_msgs(folder, dest, msglist); + + file_list = procmsg_get_message_file_list(msglist); + g_return_val_if_fail(file_list != NULL, -1); + + ret = mh_add_msgs(folder, dest, file_list, FALSE, &first); + + procmsg_message_file_list_free(file_list); + + if (ret != -1) + ret = folder_item_remove_msgs(msginfo->folder, msglist); + + return ret; +} + +static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) +{ + GSList msglist; + + g_return_val_if_fail(msginfo != NULL, -1); + + msglist.data = msginfo; + msglist.next = NULL; + + return mh_copy_msgs(folder, dest, &msglist); +} + +static gint mh_copy_msgs(Folder *folder, FolderItem *dest, GSList *msglist) +{ + gchar *srcfile; + gchar *destfile; + FILE *fp; + GSList *cur; + MsgInfo *msginfo; + + g_return_val_if_fail(dest != NULL, -1); + g_return_val_if_fail(msglist != NULL, -1); + + if (dest->last_num < 0) { + mh_scan_folder(folder, dest); + if (dest->last_num < 0) return -1; + } + + if (dest->opened) + fp = NULL; + else if ((fp = procmsg_open_mark_file(dest, DATA_APPEND)) == NULL) + g_warning(_("Can't open mark file.\n")); + + for (cur = msglist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (msginfo->folder == dest) { + g_warning(_("the src folder is identical to the dest.\n")); + continue; + } + debug_print(_("Copying message %s%c%d to %s ...\n"), + msginfo->folder->path, G_DIR_SEPARATOR, + msginfo->msgnum, dest->path); + + destfile = mh_get_new_msg_filename(dest); + if (!destfile) break; + srcfile = procmsg_get_message_file(msginfo); + + if (copy_file(srcfile, destfile, TRUE) < 0) { + FILE_OP_ERROR(srcfile, "copy"); + g_free(srcfile); + g_free(destfile); + break; + } + + g_free(srcfile); + g_free(destfile); + dest->last_num++; + dest->total++; + dest->updated = TRUE; + + if (fp) { + SET_DEST_MSG_FLAGS(fp, dest, dest->last_num, + msginfo->flags); + } + + if (MSG_IS_NEW(msginfo->flags)) + dest->new++; + if (MSG_IS_UNREAD(msginfo->flags)) + dest->unread++; + } + + if (fp) fclose(fp); + + return dest->last_num; +} + +static gint mh_remove_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo) +{ + gchar *file; + + g_return_val_if_fail(item != NULL, -1); + + file = mh_fetch_msg(folder, item, msginfo->msgnum); + g_return_val_if_fail(file != NULL, -1); + + if (unlink(file) < 0) { + FILE_OP_ERROR(file, "unlink"); + g_free(file); + return -1; + } + g_free(file); + + item->total--; + item->updated = TRUE; + if (MSG_IS_NEW(msginfo->flags)) + item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + item->unread--; + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_INVALID); + + if (msginfo->msgnum == item->last_num) + item->last_num = mh_scan_folder_full(folder, item, FALSE); + + return 0; +} + +static gint mh_remove_all_msg(Folder *folder, FolderItem *item) +{ + gchar *path; + gint val; + + g_return_val_if_fail(item != NULL, -1); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, -1); + val = remove_all_numbered_files(path); + g_free(path); + if (val == 0) { + item->new = item->unread = item->total = 0; + item->last_num = 0; + item->updated = TRUE; + } + + return val; +} + +static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item, + MsgInfo *msginfo) +{ + struct stat s; + + if (stat(itos(msginfo->msgnum), &s) < 0 || + msginfo->size != s.st_size || + msginfo->mtime != s.st_mtime) + return TRUE; + + return FALSE; +} + +static gint mh_close(Folder *folder, FolderItem *item) +{ + return 0; +} + +static gint mh_scan_folder_full(Folder *folder, FolderItem *item, + gboolean count_sum) +{ + gchar *path; + DIR *dp; + struct dirent *d; + gint max = 0; + gint num; + gint n_msg = 0; + + g_return_val_if_fail(item != NULL, -1); + + debug_print("mh_scan_folder(): Scanning %s ...\n", item->path); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, -1); + if (change_dir(path) < 0) { + g_free(path); + return -1; + } + g_free(path); + + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(item->path, "opendir"); + return -1; + } + + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data); + + while ((d = readdir(dp)) != NULL) { + if ((num = to_number(d->d_name)) >= 0 && + dirent_is_regular_file(d)) { + n_msg++; + if (max < num) + max = num; + } + } + + closedir(dp); + + if (n_msg == 0) + item->new = item->unread = item->total = 0; + else if (count_sum) { + gint new, unread, total, min, max_; + + procmsg_get_mark_sum + (item, &new, &unread, &total, &min, &max_, 0); + + if (n_msg > total) { + item->unmarked_num = new = n_msg - total; + unread += n_msg - total; + } else + item->unmarked_num = 0; + + item->new = new; + item->unread = unread; + item->total = n_msg; + } + + item->updated = TRUE; + + debug_print(_("Last number in dir %s = %d\n"), item->path, max); + item->last_num = max; + + return 0; +} + +static gint mh_scan_folder(Folder *folder, FolderItem *item) +{ + return mh_scan_folder_full(folder, item, TRUE); +} + +static gint mh_scan_tree(Folder *folder) +{ + FolderItem *item; + gchar *rootpath; + + g_return_val_if_fail(folder != NULL, -1); + + if (!folder->node) { + item = folder_item_new(folder->name, NULL); + item->folder = folder; + folder->node = item->node = g_node_new(item); + } else + item = FOLDER_ITEM(folder->node->data); + + rootpath = folder_item_get_path(item); + if (change_dir(rootpath) < 0) { + g_free(rootpath); + return -1; + } + g_free(rootpath); + + mh_create_tree(folder); + mh_remove_missing_folder_items(folder); + mh_scan_tree_recursive(item); + + return 0; +} + +#define MAKE_DIR_IF_NOT_EXIST(dir) \ +{ \ + if (!is_dir_exist(dir)) { \ + if (is_file_exist(dir)) { \ + g_warning(_("File `%s' already exists.\n" \ + "Can't create folder."), dir); \ + return -1; \ + } \ + if (make_dir(dir) < 0) \ + return -1; \ + } \ +} + +static gint mh_create_tree(Folder *folder) +{ + gchar *rootpath; + + g_return_val_if_fail(folder != NULL, -1); + + CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), -1); + rootpath = LOCAL_FOLDER(folder)->rootpath; + MAKE_DIR_IF_NOT_EXIST(rootpath); + CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1); + MAKE_DIR_IF_NOT_EXIST(INBOX_DIR); + MAKE_DIR_IF_NOT_EXIST(OUTBOX_DIR); + MAKE_DIR_IF_NOT_EXIST(QUEUE_DIR); + MAKE_DIR_IF_NOT_EXIST(DRAFT_DIR); + MAKE_DIR_IF_NOT_EXIST(TRASH_DIR); + + return 0; +} + +#undef MAKE_DIR_IF_NOT_EXIST + +static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent, + const gchar *name) +{ + gchar *path; + gchar *fs_name; + gchar *fullpath; + FolderItem *new_item; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + path = folder_item_get_path(parent); + fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL); + fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, + fs_name ? fs_name : name, NULL); + g_free(fs_name); + g_free(path); + + if (make_dir(fullpath) < 0) { + g_free(fullpath); + return NULL; + } + + g_free(fullpath); + + if (parent->path) + path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name, + NULL); + else + path = g_strdup(name); + new_item = folder_item_new(name, path); + folder_item_append(parent, new_item); + g_free(path); + + return new_item; +} + +static gint mh_rename_folder(Folder *folder, FolderItem *item, + const gchar *name) +{ + gchar *fs_name; + gchar *oldpath; + gchar *dirname; + gchar *newpath; + gchar *paths[2]; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->path != NULL, -1); + g_return_val_if_fail(name != NULL, -1); + + oldpath = folder_item_get_path(item); + dirname = g_dirname(oldpath); + fs_name = g_filename_from_utf8(name, -1, NULL, NULL, NULL); + newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, + fs_name ? fs_name : name, NULL); + g_free(fs_name); + g_free(dirname); + + if (rename(oldpath, newpath) < 0) { + FILE_OP_ERROR(oldpath, "rename"); + g_free(oldpath); + g_free(newpath); + return -1; + } + + g_free(oldpath); + g_free(newpath); + + if (strchr(item->path, G_DIR_SEPARATOR) != NULL) { + dirname = g_dirname(item->path); + newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL); + g_free(dirname); + } else + newpath = g_strdup(name); + + g_free(item->name); + item->name = g_strdup(name); + + paths[0] = g_strdup(item->path); + paths[1] = newpath; + g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + mh_rename_folder_func, paths); + + g_free(paths[0]); + g_free(paths[1]); + return 0; +} + +static gint mh_remove_folder(Folder *folder, FolderItem *item) +{ + gchar *path; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + g_return_val_if_fail(item->path != NULL, -1); + + path = folder_item_get_path(item); + if (remove_dir_recursive(path) < 0) { + g_warning("can't remove directory `%s'\n", path); + g_free(path); + return -1; + } + + g_free(path); + folder_item_remove(item); + return 0; +} + + +static time_t mh_get_mtime(FolderItem *item) +{ + gchar *path; + struct stat s; + + path = folder_item_get_path(item); + if (stat(path, &s) < 0) { + FILE_OP_ERROR(path, "stat"); + return -1; + } else { + return MAX(s.st_mtime, s.st_ctime); + } +} + +static GSList *mh_get_uncached_msgs(GHashTable *msg_table, FolderItem *item) +{ + gchar *path; + DIR *dp; + struct dirent *d; + GSList *newlist = NULL; + GSList *last = NULL; + MsgInfo *msginfo; + gint n_newmsg = 0; + gint num; + + g_return_val_if_fail(item != NULL, NULL); + + path = folder_item_get_path(item); + g_return_val_if_fail(path != NULL, NULL); + if (change_dir(path) < 0) { + g_free(path); + return NULL; + } + g_free(path); + + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(item->path, "opendir"); + return NULL; + } + + debug_print("Searching uncached messages...\n"); + + if (msg_table) { + while ((d = readdir(dp)) != NULL) { + if ((num = to_number(d->d_name)) < 0) continue; + + msginfo = g_hash_table_lookup + (msg_table, GUINT_TO_POINTER(num)); + + if (!msginfo) { + /* not found in the cache (uncached message) */ + msginfo = mh_parse_msg(d->d_name, item); + if (!msginfo) continue; + + if (!newlist) + last = newlist = + g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + n_newmsg++; + } + } + } else { + /* discard all previous cache */ + while ((d = readdir(dp)) != NULL) { + if (to_number(d->d_name) < 0) continue; + + msginfo = mh_parse_msg(d->d_name, item); + if (!msginfo) continue; + + if (!newlist) + last = newlist = g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + n_newmsg++; + } + } + + closedir(dp); + + if (n_newmsg) + debug_print("%d uncached message(s) found.\n", n_newmsg); + else + debug_print("done.\n"); + + /* sort new messages in numerical order */ + if (newlist && item->sort_key == SORT_BY_NONE) { + debug_print("Sorting uncached messages in numerical order...\n"); + newlist = g_slist_sort + (newlist, (GCompareFunc)procmsg_cmp_msgnum_for_sort); + debug_print("done.\n"); + } + + return newlist; +} + +static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item) +{ + MsgInfo *msginfo; + MsgFlags flags; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(file != NULL, NULL); + + flags.perm_flags = MSG_NEW|MSG_UNREAD; + flags.tmp_flags = 0; + + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(flags, MSG_DRAFT); + } + + msginfo = procheader_parse_file(file, flags, FALSE); + if (!msginfo) return NULL; + + msginfo->msgnum = atoi(file); + msginfo->folder = item; + + return msginfo; +} + +#if 0 +static gboolean mh_is_maildir_one(const gchar *path, const gchar *dir) +{ + gchar *entry; + gboolean result; + + entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir, NULL); + result = is_dir_exist(entry); + g_free(entry); + + return result; +} + +/* + * check whether PATH is a Maildir style mailbox. + * This is the case if the 3 subdir: new, cur, tmp are existing. + * This functon assumes that entry is an directory + */ +static gboolean mh_is_maildir(const gchar *path) +{ + return mh_is_maildir_one(path, "new") && + mh_is_maildir_one(path, "cur") && + mh_is_maildir_one(path, "tmp"); +} +#endif + +static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data) +{ + FolderItem *item; + gchar *path; + + g_return_val_if_fail(node->data != NULL, FALSE); + + if (G_NODE_IS_ROOT(node)) + return FALSE; + + item = FOLDER_ITEM(node->data); + + path = folder_item_get_path(item); + if (!is_dir_exist(path)) { + debug_print("folder '%s' not found. removing...\n", path); + folder_item_remove(item); + } + g_free(path); + + return FALSE; +} + +static void mh_remove_missing_folder_items(Folder *folder) +{ + g_return_if_fail(folder != NULL); + + debug_print("searching missing folders...\n"); + + g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, + mh_remove_missing_folder_items_func, folder); +} + +static void mh_scan_tree_recursive(FolderItem *item) +{ + Folder *folder; + DIR *dp; + struct dirent *d; + struct stat s; + gchar *fs_path; + gchar *entry; + gchar *utf8entry; + gchar *utf8name; + gint n_msg = 0; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + folder = item->folder; + + fs_path = item->path ? + g_filename_from_utf8(item->path, -1, NULL, NULL, NULL) + : g_strdup("."); + if (!fs_path) + fs_path = g_strdup(item->path); + dp = opendir(fs_path); + if (!dp) { + FILE_OP_ERROR(fs_path, "opendir"); + g_free(fs_path); + return; + } + g_free(fs_path); + + debug_print("scanning %s ...\n", + item->path ? item->path + : LOCAL_FOLDER(item->folder)->rootpath); + if (folder->ui_func) + folder->ui_func(folder, item, folder->ui_func_data); + + while ((d = readdir(dp)) != NULL) { + if (d->d_name[0] == '.') continue; + + utf8name = g_filename_to_utf8(d->d_name, -1, NULL, NULL, NULL); + if (!utf8name) + utf8name = g_strdup(d->d_name); + + if (item->path) + utf8entry = g_strconcat(item->path, G_DIR_SEPARATOR_S, + utf8name, NULL); + else + utf8entry = g_strdup(utf8name); + entry = g_filename_from_utf8(utf8entry, -1, NULL, NULL, NULL); + if (!entry) + entry = g_strdup(utf8entry); + + if ( +#ifdef HAVE_DIRENT_D_TYPE + d->d_type == DT_DIR || + (d->d_type == DT_UNKNOWN && +#endif + stat(entry, &s) == 0 && S_ISDIR(s.st_mode) +#ifdef HAVE_DIRENT_D_TYPE + ) +#endif + ) { + FolderItem *new_item = NULL; + GNode *node; + +#if 0 + if (mh_is_maildir(entry)) { + g_free(entry); + g_free(utf8entry); + g_free(utf8name); + continue; + } +#endif + + node = item->node; + for (node = node->children; node != NULL; node = node->next) { + FolderItem *cur_item = FOLDER_ITEM(node->data); + if (!strcmp2(cur_item->path, utf8entry)) { + new_item = cur_item; + break; + } + } + if (!new_item) { + debug_print("new folder '%s' found.\n", entry); + new_item = folder_item_new(utf8name, utf8entry); + folder_item_append(item, new_item); + } + + if (!item->path) { + if (!folder->inbox && + !strcmp(d->d_name, INBOX_DIR)) { + new_item->stype = F_INBOX; + folder->inbox = new_item; + } else if (!folder->outbox && + !strcmp(d->d_name, OUTBOX_DIR)) { + new_item->stype = F_OUTBOX; + folder->outbox = new_item; + } else if (!folder->draft && + !strcmp(d->d_name, DRAFT_DIR)) { + new_item->stype = F_DRAFT; + folder->draft = new_item; + } else if (!folder->queue && + !strcmp(d->d_name, QUEUE_DIR)) { + new_item->stype = F_QUEUE; + folder->queue = new_item; + } else if (!folder->trash && + !strcmp(d->d_name, TRASH_DIR)) { + new_item->stype = F_TRASH; + folder->trash = new_item; + } + } + + mh_scan_tree_recursive(new_item); + } else if (to_number(d->d_name) != -1) n_msg++; + + g_free(entry); + g_free(utf8entry); + g_free(utf8name); + } + + closedir(dp); + + if (item->path) { + gint new, unread, total, min, max; + + procmsg_get_mark_sum + (item, &new, &unread, &total, &min, &max, 0); + if (n_msg > total) { + new += n_msg - total; + unread += n_msg - total; + } + item->new = new; + item->unread = unread; + item->total = n_msg; + item->updated = TRUE; + } +} + +static gboolean mh_rename_folder_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gchar **paths = data; + const gchar *oldpath = paths[0]; + const gchar *newpath = paths[1]; + gchar *base; + gchar *new_itempath; + gint oldpathlen; + + oldpathlen = strlen(oldpath); + if (strncmp(oldpath, item->path, oldpathlen) != 0) { + g_warning("path doesn't match: %s, %s\n", oldpath, item->path); + return TRUE; + } + + base = item->path + oldpathlen; + while (*base == G_DIR_SEPARATOR) base++; + if (*base == '\0') + new_itempath = g_strdup(newpath); + else + new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base, + NULL); + g_free(item->path); + item->path = new_itempath; + + return FALSE; +} diff --git a/src/mh.h b/src/mh.h new file mode 100644 index 00000000..160259c1 --- /dev/null +++ b/src/mh.h @@ -0,0 +1,38 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MH_H__ +#define __MH_H__ + +#include + +#include "folder.h" + +typedef struct _MHFolder MHFolder; + +#define MH_FOLDER(obj) ((MHFolder *)obj) + +struct _MHFolder +{ + LocalFolder lfolder; +}; + +FolderClass *mh_get_class (void); + +#endif /* __MH_H__ */ diff --git a/src/mimeview.c b/src/mimeview.c new file mode 100644 index 00000000..3d36c4f8 --- /dev/null +++ b/src/mimeview.c @@ -0,0 +1,998 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "mimeview.h" +#include "textview.h" +#include "imageview.h" +#include "procmime.h" +#include "summaryview.h" +#include "menu.h" +#include "filesel.h" +#include "alertpanel.h" +#include "inputdialog.h" +#include "utils.h" +#include "gtkutils.h" +#include "prefs_common.h" +#include "rfc2015.h" + +typedef enum +{ + COL_MIMETYPE = 0, + COL_SIZE = 1, + COL_NAME = 2 +} MimeViewColumnPos; + +#define N_MIMEVIEW_COLS 3 + +static void mimeview_set_multipart_tree (MimeView *mimeview, + MimeInfo *mimeinfo, + GtkCTreeNode *parent); +static GtkCTreeNode *mimeview_append_part (MimeView *mimeview, + MimeInfo *partinfo, + GtkCTreeNode *parent); +static void mimeview_show_message_part (MimeView *mimeview, + MimeInfo *partinfo); +static void mimeview_show_image_part (MimeView *mimeview, + MimeInfo *partinfo); +static void mimeview_change_view_type (MimeView *mimeview, + MimeViewType type); + +static void mimeview_selected (GtkCTree *ctree, + GtkCTreeNode *node, + gint column, + MimeView *mimeview); +static void mimeview_start_drag (GtkWidget *widget, + gint button, + GdkEvent *event, + MimeView *mimeview); +static gint mimeview_button_pressed (GtkWidget *widget, + GdkEventButton *event, + MimeView *mimeview); +static gint mimeview_key_pressed (GtkWidget *widget, + GdkEventKey *event, + MimeView *mimeview); + +static void mimeview_drag_data_get (GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *selection_data, + guint info, + guint time, + MimeView *mimeview); + +static void mimeview_display_as_text (MimeView *mimeview); +static void mimeview_save_as (MimeView *mimeview); +static void mimeview_launch (MimeView *mimeview); +static void mimeview_open_with (MimeView *mimeview); +static void mimeview_view_file (const gchar *filename, + MimeInfo *partinfo, + const gchar *cmdline); +#if USE_GPGME +static void mimeview_check_signature (MimeView *mimeview); +#endif + +static GtkItemFactoryEntry mimeview_popup_entries[] = +{ + {N_("/_Open"), NULL, mimeview_launch, 0, NULL}, + {N_("/Open _with..."), NULL, mimeview_open_with, 0, NULL}, + {N_("/_Display as text"), NULL, mimeview_display_as_text, 0, NULL}, + {N_("/_Save as..."), NULL, mimeview_save_as, 0, NULL} +#if USE_GPGME + , + {N_("/_Check signature"), NULL, mimeview_check_signature, 0, NULL} +#endif +}; + +static GtkTargetEntry mimeview_mime_types[] = +{ + {"text/uri-list", 0, 0} +}; + +MimeView *mimeview_create(void) +{ + MimeView *mimeview; + + GtkWidget *notebook; + GtkWidget *vbox; + GtkWidget *paned; + GtkWidget *scrolledwin; + GtkWidget *ctree; + GtkWidget *mime_vbox; + GtkWidget *popupmenu; + GtkItemFactory *popupfactory; + gchar *titles[N_MIMEVIEW_COLS]; + gint n_entries; + gint i; + + debug_print(_("Creating MIME view...\n")); + mimeview = g_new0(MimeView, 1); + + titles[COL_MIMETYPE] = _("MIME Type"); + titles[COL_SIZE] = _("Size"); + titles[COL_NAME] = _("Name"); + + notebook = gtk_notebook_new(); + gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(notebook), vbox); + gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(notebook), vbox, + _("Text")); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scrolledwin, -1, 80); + + ctree = gtk_sctree_new_with_titles(N_MIMEVIEW_COLS, 0, titles); + gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE); + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_NONE); + gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_SIZE, + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_width(GTK_CLIST(ctree), COL_MIMETYPE, 240); + gtk_clist_set_column_width(GTK_CLIST(ctree), COL_SIZE, 64); + for (i = 0; i < N_MIMEVIEW_COLS; i++) + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button, + GTK_CAN_FOCUS); + gtk_container_add(GTK_CONTAINER(scrolledwin), ctree); + + g_signal_connect(G_OBJECT(ctree), "tree_select_row", + G_CALLBACK(mimeview_selected), mimeview); + g_signal_connect(G_OBJECT(ctree), "button_press_event", + G_CALLBACK(mimeview_button_pressed), mimeview); + g_signal_connect(G_OBJECT(ctree), "key_press_event", + G_CALLBACK(mimeview_key_pressed), mimeview); + g_signal_connect(G_OBJECT (ctree),"start_drag", + G_CALLBACK (mimeview_start_drag), mimeview); + g_signal_connect(G_OBJECT(ctree), "drag_data_get", + G_CALLBACK(mimeview_drag_data_get), mimeview); + + mime_vbox = gtk_vbox_new(FALSE, 0); + + paned = gtk_vpaned_new(); + gtk_paned_add1(GTK_PANED(paned), scrolledwin); + gtk_paned_add2(GTK_PANED(paned), mime_vbox); + gtk_container_add(GTK_CONTAINER(notebook), paned); + gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(notebook), paned, + _("Attachments")); + + gtk_widget_show_all(notebook); + + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 0); + + n_entries = sizeof(mimeview_popup_entries) / + sizeof(mimeview_popup_entries[0]); + popupmenu = menu_create_items(mimeview_popup_entries, n_entries, + "", &popupfactory, mimeview); + + mimeview->notebook = notebook; + mimeview->vbox = vbox; + mimeview->paned = paned; + mimeview->scrolledwin = scrolledwin; + mimeview->ctree = ctree; + mimeview->mime_vbox = mime_vbox; + mimeview->popupmenu = popupmenu; + mimeview->popupfactory = popupfactory; + mimeview->type = -1; + + return mimeview; +} + +void mimeview_init(MimeView *mimeview) +{ + textview_init(mimeview->textview); + imageview_init(mimeview->imageview); +} + +/* + * Check whether the message is OpenPGP signed + */ +#if USE_GPGME +static gboolean mimeview_is_signed(MimeView *mimeview) +{ + MimeInfo *partinfo; + + debug_print("mimeview_is signed of %p\n", mimeview); + + if (!mimeview) return FALSE; + if (!mimeview->opened) return FALSE; + + debug_print("mimeview_is_signed: open\n" ); + + if (!mimeview->file) return FALSE; + + debug_print("mimeview_is_signed: file\n" ); + + partinfo = mimeview_get_selected_part(mimeview); + g_return_val_if_fail(partinfo != NULL, FALSE); + + /* walk the tree and see whether there is a signature somewhere */ + do { + if (rfc2015_has_signature(partinfo)) + return TRUE; + } while ((partinfo = partinfo->parent) != NULL); + + debug_print("mimeview_is_signed: FALSE\n" ); + + return FALSE; +} + +static void set_unchecked_signature(MimeInfo *mimeinfo) +{ + MimeInfo *sig_partinfo; + + sig_partinfo = rfc2015_find_signature(mimeinfo); + if (sig_partinfo == NULL) return; + + g_free(sig_partinfo->sigstatus); + sig_partinfo->sigstatus = + g_strdup(_("Select \"Check signature\" to check")); + + g_free(sig_partinfo->sigstatus_full); + sig_partinfo->sigstatus_full = NULL; +} +#endif /* USE_GPGME */ + +void mimeview_show_message(MimeView *mimeview, MimeInfo *mimeinfo, + const gchar *file) +{ + GtkCTree *ctree = GTK_CTREE(mimeview->ctree); + GtkCTreeNode *node; + + mimeview_clear(mimeview); + textview_clear(mimeview->messageview->textview); + + g_return_if_fail(file != NULL); + g_return_if_fail(mimeinfo != NULL); + + mimeview->mimeinfo = mimeinfo; + + mimeview->file = g_strdup(file); + +#if USE_GPGME + if (prefs_common.auto_check_signatures) { + FILE *fp; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return; + } + rfc2015_check_signature(mimeinfo, fp); + fclose(fp); + } else + set_unchecked_signature(mimeinfo); +#endif + + g_signal_handlers_block_by_func + (G_OBJECT(ctree), G_CALLBACK(mimeview_selected), mimeview); + + mimeview_set_multipart_tree(mimeview, mimeinfo, NULL); + + g_signal_handlers_unblock_by_func + (G_OBJECT(ctree), G_CALLBACK(mimeview_selected), mimeview); + + /* search first text part */ + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node != NULL; node = GTK_CTREE_NODE_NEXT(node)) { + MimeInfo *partinfo; + + partinfo = gtk_ctree_node_get_row_data(ctree, node); + if (partinfo && + (partinfo->mime_type == MIME_TEXT || + partinfo->mime_type == MIME_TEXT_HTML)) + break; + } + textview_show_message(mimeview->messageview->textview, mimeinfo, file); + + if (!node) + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + if (node) { + gtk_ctree_select(ctree, node); + gtkut_ctree_set_focus_row(ctree, node); + gtk_widget_grab_focus(mimeview->ctree); + } +} + +void mimeview_clear(MimeView *mimeview) +{ + GtkCList *clist = GTK_CLIST(mimeview->ctree); + + procmime_mimeinfo_free_all(mimeview->mimeinfo); + mimeview->mimeinfo = NULL; + + gtk_clist_clear(clist); + textview_clear(mimeview->textview); + imageview_clear(mimeview->imageview); + + mimeview->opened = NULL; + + g_free(mimeview->file); + mimeview->file = NULL; + + /* gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->notebook), 0); */ +} + +void mimeview_destroy(MimeView *mimeview) +{ + textview_destroy(mimeview->textview); + imageview_destroy(mimeview->imageview); + procmime_mimeinfo_free_all(mimeview->mimeinfo); + g_free(mimeview->file); + g_free(mimeview); +} + +MimeInfo *mimeview_get_selected_part(MimeView *mimeview) +{ + if (gtk_notebook_get_current_page + (GTK_NOTEBOOK(mimeview->notebook)) == 0) + return NULL; + + return gtk_ctree_node_get_row_data + (GTK_CTREE(mimeview->ctree), mimeview->opened); +} + +static void mimeview_set_multipart_tree(MimeView *mimeview, + MimeInfo *mimeinfo, + GtkCTreeNode *parent) +{ + GtkCTreeNode *node; + + g_return_if_fail(mimeinfo != NULL); + + if (mimeinfo->children) + mimeinfo = mimeinfo->children; + + while (mimeinfo != NULL) { + node = mimeview_append_part(mimeview, mimeinfo, parent); + + if (mimeinfo->children) + mimeview_set_multipart_tree(mimeview, mimeinfo, node); + else if (mimeinfo->sub && + mimeinfo->sub->mime_type != MIME_TEXT && + mimeinfo->sub->mime_type != MIME_TEXT_HTML) + mimeview_set_multipart_tree(mimeview, mimeinfo->sub, + node); + mimeinfo = mimeinfo->next; + } +} + +static gchar *get_part_name(MimeInfo *partinfo) +{ +#if USE_GPGME + if (partinfo->sigstatus) + return partinfo->sigstatus; + else +#endif + if (partinfo->name) + return partinfo->name; + else if (partinfo->filename) + return partinfo->filename; + else + return ""; +} + +static GtkCTreeNode *mimeview_append_part(MimeView *mimeview, + MimeInfo *partinfo, + GtkCTreeNode *parent) +{ + GtkCTree *ctree = GTK_CTREE(mimeview->ctree); + GtkCTreeNode *node; + gchar *str[N_MIMEVIEW_COLS]; + + str[COL_MIMETYPE] = + partinfo->content_type ? partinfo->content_type : ""; + str[COL_SIZE] = to_human_readable(partinfo->size); + str[COL_NAME] = get_part_name(partinfo); + + node = gtk_ctree_insert_node(ctree, parent, NULL, str, 0, + NULL, NULL, NULL, NULL, + FALSE, TRUE); + gtk_ctree_node_set_row_data(ctree, node, partinfo); + + return node; +} + +static void mimeview_show_message_part(MimeView *mimeview, MimeInfo *partinfo) +{ + FILE *fp; + const gchar *fname; +#if USE_GPGME + MimeInfo *pi; +#endif + + if (!partinfo) return; + +#if USE_GPGME + for (pi = partinfo; pi && !pi->plaintextfile ; pi = pi->parent) + ; + fname = pi ? pi->plaintextfile : mimeview->file; +#else + fname = mimeview->file; +#endif /* USE_GPGME */ + if (!fname) return; + + if ((fp = fopen(fname, "rb")) == NULL) { + FILE_OP_ERROR(fname, "fopen"); + return; + } + + if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) { + FILE_OP_ERROR(mimeview->file, "fseek"); + fclose(fp); + return; + } + + mimeview_change_view_type(mimeview, MIMEVIEW_TEXT); + textview_show_part(mimeview->textview, partinfo, fp); + + fclose(fp); +} + +static void mimeview_show_image_part(MimeView *mimeview, MimeInfo *partinfo) +{ + gchar *filename; + + if (!partinfo) return; + + filename = procmime_get_tmp_file_name(partinfo); + + if (procmime_get_part(filename, mimeview->file, partinfo) < 0) + alertpanel_error + (_("Can't get the part of multipart message.")); + else { + mimeview_change_view_type(mimeview, MIMEVIEW_IMAGE); + imageview_show_image(mimeview->imageview, partinfo, filename, + prefs_common.resize_image); + unlink(filename); + } + + g_free(filename); +} + +static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type) +{ + TextView *textview = mimeview->textview; + ImageView *imageview = mimeview->imageview; + GList *children; + + if (mimeview->type == type) return; + + children = gtk_container_get_children + (GTK_CONTAINER(mimeview->mime_vbox)); + if (children) { + gtkut_container_remove(GTK_CONTAINER(mimeview->mime_vbox), + GTK_WIDGET(children->data)); + g_list_free(children); + } + + switch (type) { + case MIMEVIEW_IMAGE: + gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox), + GTK_WIDGET_PTR(imageview)); + break; + case MIMEVIEW_TEXT: + gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox), + GTK_WIDGET_PTR(textview)); + break; + default: + return; + } + + mimeview->type = type; +} + +static void mimeview_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column, + MimeView *mimeview) +{ + MimeInfo *partinfo; + + if (mimeview->opened == node) return; + mimeview->opened = node; + gtk_ctree_node_moveto(ctree, node, -1, 0.5, 0); + + partinfo = gtk_ctree_node_get_row_data(ctree, node); + if (!partinfo) return; + + /* ungrab the mouse event */ + if (GTK_WIDGET_HAS_GRAB(ctree)) { + gtk_grab_remove(GTK_WIDGET(ctree)); + if (gdk_pointer_is_grabbed()) + gdk_pointer_ungrab(GDK_CURRENT_TIME); + } + + switch (partinfo->mime_type) { + case MIME_TEXT: + case MIME_TEXT_HTML: + case MIME_MESSAGE_RFC822: + case MIME_MULTIPART: + mimeview_show_message_part(mimeview, partinfo); + break; +#if (HAVE_GDK_PIXBUF || HAVE_GDK_IMLIB) + case MIME_IMAGE: + mimeview_show_image_part(mimeview, partinfo); + break; +#endif + default: + mimeview_change_view_type(mimeview, MIMEVIEW_TEXT); +#if USE_GPGME + if (g_strcasecmp(partinfo->content_type, + "application/pgp-signature") == 0) + textview_show_signature_part(mimeview->textview, + partinfo); + else +#endif + textview_show_mime_part(mimeview->textview, partinfo); + break; + } +} + +static void mimeview_start_drag(GtkWidget *widget, gint button, + GdkEvent *event, MimeView *mimeview) +{ + GtkTargetList *list; + GdkDragContext *context; + MimeInfo *partinfo; + + g_return_if_fail(mimeview != NULL); + + partinfo = mimeview_get_selected_part(mimeview); + if (partinfo->filename == NULL && partinfo->name == NULL) return; + + list = gtk_target_list_new(mimeview_mime_types, 1); + context = gtk_drag_begin(widget, list, + GDK_ACTION_COPY, button, event); + gtk_drag_set_icon_default(context); +} + +static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event, + MimeView *mimeview) +{ + GtkCList *clist = GTK_CLIST(widget); + MimeInfo *partinfo; + gint row, column; + + if (!event) return FALSE; + + if (event->button == 2 || event->button == 3) { + if (!gtk_clist_get_selection_info(clist, event->x, event->y, + &row, &column)) + return FALSE; + gtk_clist_unselect_all(clist); + gtk_clist_select_row(clist, row, column); + gtkut_clist_set_focus_row(clist, row); + } + + if (event->button == 2 || + (event->button == 1 && event->type == GDK_2BUTTON_PRESS)) { + /* call external program for image, audio or html */ + mimeview_launch(mimeview); + } else if (event->button == 3) { + partinfo = mimeview_get_selected_part(mimeview); + if (partinfo && (partinfo->mime_type == MIME_TEXT || + partinfo->mime_type == MIME_TEXT_HTML || + partinfo->mime_type == MIME_MESSAGE_RFC822 || + partinfo->mime_type == MIME_IMAGE || + partinfo->mime_type == MIME_MULTIPART)) + menu_set_sensitive(mimeview->popupfactory, + "/Display as text", FALSE); + else + menu_set_sensitive(mimeview->popupfactory, + "/Display as text", TRUE); + if (partinfo && + partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM) + menu_set_sensitive(mimeview->popupfactory, + "/Open", FALSE); + else + menu_set_sensitive(mimeview->popupfactory, + "/Open", TRUE); +#if USE_GPGME + menu_set_sensitive(mimeview->popupfactory, + "/Check signature", + mimeview_is_signed(mimeview)); +#endif + + gtk_menu_popup(GTK_MENU(mimeview->popupmenu), + NULL, NULL, NULL, NULL, + event->button, event->time); + } + +#warning FIXME_GTK2 Is it correct? + return FALSE; +} + +void mimeview_pass_key_press_event(MimeView *mimeview, GdkEventKey *event) +{ + mimeview_key_pressed(mimeview->ctree, event, mimeview); +} + +#define BREAK_ON_MODIFIER_KEY() \ + if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break + +#warning FIXME_GTK2 +#if 0 +#define KEY_PRESS_EVENT_STOP() \ + if (gtk_signal_n_emissions_by_name \ + (G_OBJECT(ctree), "key_press_event") > 0) { \ + gtk_signal_emit_stop_by_name(G_OBJECT(ctree), \ + "key_press_event"); \ + } +#else +#define KEY_PRESS_EVENT_STOP() \ + g_signal_stop_emission_by_name(G_OBJECT(ctree), "key_press_event"); +#endif + +static gint mimeview_key_pressed(GtkWidget *widget, GdkEventKey *event, + MimeView *mimeview) +{ + SummaryView *summaryview; + GtkCTree *ctree = GTK_CTREE(widget); + GtkCTreeNode *node; + + if (!event) return FALSE; + if (!mimeview->opened) return FALSE; + + switch (event->keyval) { + case GDK_space: + if (textview_scroll_page(mimeview->textview, FALSE)) + return TRUE; + + node = GTK_CTREE_NODE_NEXT(mimeview->opened); + if (node) { + gtk_sctree_unselect_all(GTK_SCTREE(ctree)); + gtk_sctree_select(GTK_SCTREE(ctree), node); + return TRUE; + } + break; + case GDK_BackSpace: + textview_scroll_page(mimeview->textview, TRUE); + return TRUE; + case GDK_Return: + textview_scroll_one_line(mimeview->textview, + (event->state & GDK_MOD1_MASK) != 0); + return TRUE; + case GDK_n: + case GDK_N: + BREAK_ON_MODIFIER_KEY(); + if (!GTK_CTREE_NODE_NEXT(mimeview->opened)) break; + KEY_PRESS_EVENT_STOP(); + + g_signal_emit_by_name(G_OBJECT(ctree), "scroll_vertical", + GTK_SCROLL_STEP_FORWARD, 0.0); + return TRUE; + case GDK_p: + case GDK_P: + BREAK_ON_MODIFIER_KEY(); + if (!GTK_CTREE_NODE_PREV(mimeview->opened)) break; + KEY_PRESS_EVENT_STOP(); + + g_signal_emit_by_name(G_OBJECT(ctree), "scroll_vertical", + GTK_SCROLL_STEP_BACKWARD, 0.0); + return TRUE; + case GDK_y: + BREAK_ON_MODIFIER_KEY(); + KEY_PRESS_EVENT_STOP(); + mimeview_save_as(mimeview); + return TRUE; + case GDK_t: + BREAK_ON_MODIFIER_KEY(); + KEY_PRESS_EVENT_STOP(); + mimeview_display_as_text(mimeview); + return TRUE; + case GDK_l: + BREAK_ON_MODIFIER_KEY(); + KEY_PRESS_EVENT_STOP(); + mimeview_launch(mimeview); + return TRUE; + default: + break; + } + + if (!mimeview->messageview->mainwin) return FALSE; + summaryview = mimeview->messageview->mainwin->summaryview; + summary_pass_key_press_event(summaryview, event); + return TRUE; +} + +static void mimeview_drag_data_get(GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *selection_data, + guint info, + guint time, + MimeView *mimeview) +{ + gchar *filename, *uriname; + const gchar *bname; + MimeInfo *partinfo; + + if (!mimeview->opened) return; + if (!mimeview->file) return; + + partinfo = mimeview_get_selected_part(mimeview); + if (!partinfo) return; + if (!partinfo->filename && !partinfo->name) return; + + filename = partinfo->filename ? partinfo->filename : partinfo->name; + bname = g_basename(filename); + if (*bname == '\0') return; + + filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S, + bname, NULL); + + if (procmime_get_part(filename, mimeview->file, partinfo) < 0) + alertpanel_error + (_("Can't save the part of multipart message.")); + + uriname = g_strconcat("file://", filename, NULL); + gtk_selection_data_set(selection_data, selection_data->target, 8, + uriname, strlen(uriname)); + + g_free(uriname); + g_free(filename); +} + +static void mimeview_display_as_text(MimeView *mimeview) +{ + MimeInfo *partinfo; + + if (!mimeview->opened) return; + + partinfo = mimeview_get_selected_part(mimeview); + g_return_if_fail(partinfo != NULL); + mimeview_show_message_part(mimeview, partinfo); +} + +static void mimeview_save_as(MimeView *mimeview) +{ + gchar *filename; + gchar *defname = NULL; + MimeInfo *partinfo; + + if (!mimeview->opened) return; + if (!mimeview->file) return; + + partinfo = mimeview_get_selected_part(mimeview); + g_return_if_fail(partinfo != NULL); + + if (partinfo->filename) + defname = partinfo->filename; + else if (partinfo->name) { + Xstrdup_a(defname, partinfo->name, return); + subst_for_filename(defname); + } + + filename = filesel_select_file(_("Save as"), defname); + if (!filename) return; + if (is_file_exist(filename)) { + AlertValue aval; + + aval = alertpanel(_("Overwrite"), + _("Overwrite existing file?"), + _("OK"), _("Cancel"), NULL); + if (G_ALERTDEFAULT != aval) return; + } + + if (procmime_get_part(filename, mimeview->file, partinfo) < 0) + alertpanel_error + (_("Can't save the part of multipart message.")); +} + +static void mimeview_launch(MimeView *mimeview) +{ + MimeInfo *partinfo; + gchar *filename; + + if (!mimeview->opened) return; + if (!mimeview->file) return; + + partinfo = mimeview_get_selected_part(mimeview); + g_return_if_fail(partinfo != NULL); + + filename = procmime_get_tmp_file_name(partinfo); + + if (procmime_get_part(filename, mimeview->file, partinfo) < 0) + alertpanel_error + (_("Can't save the part of multipart message.")); + else + mimeview_view_file(filename, partinfo, NULL); + + g_free(filename); +} + +static void mimeview_open_with(MimeView *mimeview) +{ + MimeInfo *partinfo; + gchar *filename; + gchar *cmd; + + if (!mimeview->opened) return; + if (!mimeview->file) return; + + partinfo = mimeview_get_selected_part(mimeview); + g_return_if_fail(partinfo != NULL); + + filename = procmime_get_tmp_file_name(partinfo); + + if (procmime_get_part(filename, mimeview->file, partinfo) < 0) { + alertpanel_error + (_("Can't save the part of multipart message.")); + g_free(filename); + return; + } + + if (!prefs_common.mime_open_cmd_history) + prefs_common.mime_open_cmd_history = + add_history(NULL, prefs_common.mime_open_cmd); + + cmd = input_dialog_combo + (_("Open with"), + _("Enter the command line to open file:\n" + "(`%s' will be replaced with file name)"), + prefs_common.mime_open_cmd, + prefs_common.mime_open_cmd_history, + TRUE); + if (cmd) { + mimeview_view_file(filename, partinfo, cmd); + g_free(prefs_common.mime_open_cmd); + prefs_common.mime_open_cmd = cmd; + prefs_common.mime_open_cmd_history = + add_history(prefs_common.mime_open_cmd_history, cmd); + } + + g_free(filename); +} + +static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo, + const gchar *cmdline) +{ + static gchar *default_image_cmdline = "display '%s'"; + static gchar *default_audio_cmdline = "play '%s'"; + static gchar *default_html_cmdline = DEFAULT_BROWSER_CMD; + static gchar *mime_cmdline = "metamail -d -b -x -c %s '%s'"; + gchar buf[1024]; + gchar m_buf[1024]; + const gchar *cmd; + const gchar *def_cmd; + const gchar *p; + + if (cmdline) { + cmd = cmdline; + def_cmd = NULL; + } else if (MIME_APPLICATION_OCTET_STREAM == partinfo->mime_type) { + return; + } else if (MIME_IMAGE == partinfo->mime_type) { + cmd = prefs_common.mime_image_viewer; + def_cmd = default_image_cmdline; + } else if (MIME_AUDIO == partinfo->mime_type) { + cmd = prefs_common.mime_audio_player; + def_cmd = default_audio_cmdline; + } else if (MIME_TEXT_HTML == partinfo->mime_type) { + cmd = prefs_common.uri_cmd; + def_cmd = default_html_cmdline; + } else { + g_snprintf(m_buf, sizeof(m_buf), mime_cmdline, + partinfo->content_type, "%s"); + cmd = m_buf; + def_cmd = NULL; + } + + if (cmd && (p = strchr(cmd, '%')) && *(p + 1) == 's' && + !strchr(p + 2, '%')) + g_snprintf(buf, sizeof(buf), cmd, filename); + else { + if (cmd) + g_warning(_("MIME viewer command line is invalid: `%s'"), cmd); + if (def_cmd) + g_snprintf(buf, sizeof(buf), def_cmd, filename); + else + return; + } + + execute_command_line(buf, TRUE); +} + +#if USE_GPGME +static void update_node_name(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + MimeInfo *partinfo; + gchar *part_name; + + partinfo = gtk_ctree_node_get_row_data(ctree, node); + g_return_if_fail(partinfo != NULL); + + part_name = get_part_name(partinfo); + gtk_ctree_node_set_text(ctree, node, COL_NAME, part_name); +} + +static void mimeview_update_names(MimeView *mimeview) +{ + GtkCTree *ctree = GTK_CTREE(mimeview->ctree); + + gtk_ctree_pre_recursive(ctree, NULL, update_node_name, NULL); +} + +static void mimeview_update_signature_info(MimeView *mimeview) +{ + MimeInfo *partinfo; + + if (!mimeview) return; + if (!mimeview->opened) return; + + partinfo = mimeview_get_selected_part(mimeview); + if (!partinfo) return; + + if (g_strcasecmp(partinfo->content_type, + "application/pgp-signature") == 0) { + mimeview_change_view_type(mimeview, MIMEVIEW_TEXT); + textview_show_signature_part(mimeview->textview, partinfo); + } +} + +static void mimeview_check_signature(MimeView *mimeview) +{ + MimeInfo *mimeinfo; + FILE *fp; + + g_return_if_fail (mimeview_is_signed(mimeview)); + + mimeinfo = mimeview_get_selected_part(mimeview); + g_return_if_fail(mimeinfo != NULL); + g_return_if_fail(mimeview->file != NULL); + + while (mimeinfo->parent) + mimeinfo = mimeinfo->parent; + + if ((fp = fopen(mimeview->file, "rb")) == NULL) { + FILE_OP_ERROR(mimeview->file, "fopen"); + return; + } + + rfc2015_check_signature(mimeinfo, fp); + fclose(fp); + + mimeview_update_names(mimeview); + mimeview_update_signature_info(mimeview); + + textview_show_message(mimeview->messageview->textview, mimeinfo, + mimeview->file); +} +#endif /* USE_GPGME */ diff --git a/src/mimeview.h b/src/mimeview.h new file mode 100644 index 00000000..e2dfb121 --- /dev/null +++ b/src/mimeview.h @@ -0,0 +1,81 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __MIMEVIEW_H__ +#define __MIMEVIEW_H__ + +#include +#include +#include +#include + +typedef struct _MimeView MimeView; + +#include "textview.h" +#include "imageview.h" +#include "messageview.h" +#include "procmime.h" + +typedef enum +{ + MIMEVIEW_TEXT, + MIMEVIEW_IMAGE +} MimeViewType; + +struct _MimeView +{ + GtkWidget *notebook; + GtkWidget *vbox; + + GtkWidget *paned; + GtkWidget *scrolledwin; + GtkWidget *ctree; + GtkWidget *mime_vbox; + + MimeViewType type; + + GtkWidget *popupmenu; + GtkItemFactory *popupfactory; + + GtkCTreeNode *opened; + + TextView *textview; + ImageView *imageview; + + MessageView *messageview; + + MimeInfo *mimeinfo; + + gchar *file; +}; + +MimeView *mimeview_create (void); +void mimeview_init (MimeView *mimeview); +void mimeview_show_message (MimeView *mimeview, + MimeInfo *mimeinfo, + const gchar *file); +void mimeview_clear (MimeView *mimeview); +void mimeview_destroy (MimeView *mimeview); + +MimeInfo *mimeview_get_selected_part (MimeView *mimeview); + +void mimeview_pass_key_press_event (MimeView *mimeview, + GdkEventKey *event); + +#endif /* __MIMEVIEW_H__ */ diff --git a/src/news.c b/src/news.c new file mode 100644 index 00000000..521acec9 --- /dev/null +++ b/src/news.c @@ -0,0 +1,1056 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "news.h" +#include "nntp.h" +#include "socket.h" +#include "recv.h" +#include "procmsg.h" +#include "procheader.h" +#include "folder.h" +#include "session.h" +#include "codeconv.h" +#include "utils.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "inputdialog.h" +#include "alertpanel.h" +#if USE_SSL +# include "ssl.h" +#endif + +#define NNTP_PORT 119 +#if USE_SSL +#define NNTPS_PORT 563 +#endif + +static void news_folder_init (Folder *folder, + const gchar *name, + const gchar *path); + +static Folder *news_folder_new (const gchar *name, + const gchar *folder); +static void news_folder_destroy (Folder *folder); + +static GSList *news_get_article_list (Folder *folder, + FolderItem *item, + gboolean use_cache); +static gchar *news_fetch_msg (Folder *folder, + FolderItem *item, + gint num); +static MsgInfo *news_get_msginfo (Folder *folder, + FolderItem *item, + gint num); + +static gint news_close (Folder *folder, + FolderItem *item); + +static gint news_scan_group (Folder *folder, + FolderItem *item); + +#if USE_SSL +static Session *news_session_new (const gchar *server, + gushort port, + const gchar *userid, + const gchar *passwd, + SSLType ssl_type); +#else +static Session *news_session_new (const gchar *server, + gushort port, + const gchar *userid, + const gchar *passwd); +#endif + +static gint news_get_article_cmd (NNTPSession *session, + const gchar *cmd, + gint num, + gchar *filename); +static gint news_get_article (NNTPSession *session, + gint num, + gchar *filename); +#if 0 +static gint news_get_header (NNTPSession *session, + gint num, + gchar *filename); +#endif + +static gint news_select_group (NNTPSession *session, + const gchar *group, + gint *num, + gint *first, + gint *last); +static GSList *news_get_uncached_articles(NNTPSession *session, + FolderItem *item, + gint cache_last, + gint *rfirst, + gint *rlast); +static MsgInfo *news_parse_xover (const gchar *xover_str); +static gchar *news_parse_xhdr (const gchar *xhdr_str, + MsgInfo *msginfo); +static GSList *news_delete_old_articles (GSList *alist, + FolderItem *item, + gint first); +static void news_delete_all_articles (FolderItem *item); +static void news_delete_expired_caches (GSList *alist, + FolderItem *item); + +static FolderClass news_class = +{ + F_NEWS, + + news_folder_new, + news_folder_destroy, + + NULL, + NULL, + + news_get_article_list, + news_fetch_msg, + news_get_msginfo, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + news_close, + news_scan_group, + + NULL, + NULL, + NULL +}; + + +FolderClass *news_get_class(void) +{ + return &news_class; +} + +static Folder *news_folder_new(const gchar *name, const gchar *path) +{ + Folder *folder; + + folder = (Folder *)g_new0(NewsFolder, 1); + news_folder_init(folder, name, path); + + return folder; +} + +static void news_folder_destroy(Folder *folder) +{ + gchar *dir; + + dir = folder_get_path(folder); + if (is_dir_exist(dir)) + remove_dir_recursive(dir); + g_free(dir); + + folder_remote_folder_destroy(REMOTE_FOLDER(folder)); +} + +static void news_folder_init(Folder *folder, const gchar *name, + const gchar *path) +{ + folder->klass = news_get_class(); + folder_remote_folder_init(folder, name, path); +} + +#if USE_SSL +static Session *news_session_new(const gchar *server, gushort port, + const gchar *userid, const gchar *passwd, + SSLType ssl_type) +#else +static Session *news_session_new(const gchar *server, gushort port, + const gchar *userid, const gchar *passwd) +#endif +{ + gchar buf[NNTPBUFSIZE]; + Session *session; + + g_return_val_if_fail(server != NULL, NULL); + + log_message(_("creating NNTP connection to %s:%d ...\n"), server, port); + +#if USE_SSL + session = nntp_session_new(server, port, buf, userid, passwd, ssl_type); +#else + session = nntp_session_new(server, port, buf, userid, passwd); +#endif + + return session; +} + +static Session *news_session_new_for_folder(Folder *folder) +{ + Session *session; + PrefsAccount *ac; + const gchar *userid = NULL; + gchar *passwd = NULL; + gushort port; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + ac = folder->account; + if (ac->use_nntp_auth && ac->userid && ac->userid[0]) { + userid = ac->userid; + if (ac->passwd && ac->passwd[0]) + passwd = g_strdup(ac->passwd); + else + passwd = input_dialog_query_password(ac->nntp_server, + userid); + } + +#if USE_SSL + port = ac->set_nntpport ? ac->nntpport + : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT; + session = news_session_new(ac->nntp_server, port, userid, passwd, + ac->ssl_nntp); +#else + port = ac->set_nntpport ? ac->nntpport : NNTP_PORT; + session = news_session_new(ac->nntp_server, port, userid, passwd); +#endif + + g_free(passwd); + + return session; +} + +static NNTPSession *news_session_get(Folder *folder) +{ + RemoteFolder *rfolder = REMOTE_FOLDER(folder); + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL); + g_return_val_if_fail(folder->account != NULL, NULL); + + if (!prefs_common.online_mode) + return NULL; + + if (!rfolder->session) { + rfolder->session = news_session_new_for_folder(folder); + return NNTP_SESSION(rfolder->session); + } + + if (time(NULL) - rfolder->session->last_access_time < + SESSION_TIMEOUT_INTERVAL) { + return NNTP_SESSION(rfolder->session); + } + + if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE) + != NN_SUCCESS) { + log_warning(_("NNTP connection to %s:%d has been" + " disconnected. Reconnecting...\n"), + folder->account->nntp_server, + folder->account->set_nntpport ? + folder->account->nntpport : NNTP_PORT); + session_destroy(rfolder->session); + rfolder->session = news_session_new_for_folder(folder); + } + + if (rfolder->session) + session_set_access_time(rfolder->session); + + return NNTP_SESSION(rfolder->session); +} + +static GSList *news_get_article_list(Folder *folder, FolderItem *item, + gboolean use_cache) +{ + GSList *alist; + NNTPSession *session; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL); + + session = news_session_get(folder); + + if (!session) { + alist = procmsg_read_cache(item, FALSE); + item->last_num = procmsg_get_last_num_in_msg_list(alist); + } else if (use_cache) { + GSList *newlist; + gint cache_last; + gint first, last; + + alist = procmsg_read_cache(item, FALSE); + + cache_last = procmsg_get_last_num_in_msg_list(alist); + newlist = news_get_uncached_articles + (session, item, cache_last, &first, &last); + if (first == 0 && last == 0) { + news_delete_all_articles(item); + procmsg_msg_list_free(alist); + alist = NULL; + } else { + alist = news_delete_old_articles(alist, item, first); + news_delete_expired_caches(alist, item); + } + + alist = g_slist_concat(alist, newlist); + + item->last_num = last; + } else { + gint last; + + alist = news_get_uncached_articles + (session, item, 0, NULL, &last); + news_delete_all_articles(item); + item->last_num = last; + } + + procmsg_set_flags(alist, item); + + alist = procmsg_sort_msg_list(alist, item->sort_key, item->sort_type); + + return alist; +} + +static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num) +{ + gchar *path, *filename; + NNTPSession *session; + gint ok; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + path = folder_item_get_path(item); + if (!is_dir_exist(path)) + make_dir_hier(path); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL); + g_free(path); + + if (is_file_exist(filename)) { + debug_print(_("article %d has been already cached.\n"), num); + return filename; + } + + session = news_session_get(folder); + if (!session) { + g_free(filename); + return NULL; + } + + ok = news_select_group(session, item->path, NULL, NULL, NULL); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + g_free(filename); + return NULL; + } + + debug_print(_("getting article %d...\n"), num); + ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session), + num, filename); + if (ok != NN_SUCCESS) { + g_warning(_("can't read article %d\n"), num); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + g_free(filename); + return NULL; + } + + return filename; +} + +static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num) +{ + MsgInfo *msginfo; + MsgFlags flags = {0, 0}; + gchar *file; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + file = news_fetch_msg(folder, item, num); + if (!file) return NULL; + + msginfo = procheader_parse_file(file, flags, FALSE); + + g_free(file); + + return msginfo; +} + +static gint news_close(Folder *folder, FolderItem *item) +{ + return 0; +} + +static gint news_scan_group(Folder *folder, FolderItem *item) +{ + NNTPSession *session; + gint num = 0, first = 0, last = 0; + gint new = 0, unread = 0, total = 0; + gint min = 0, max = 0; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(item != NULL, -1); + + session = news_session_get(folder); + if (!session) return -1; + + ok = news_select_group(session, item->path, &num, &first, &last); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + return -1; + } + + if (num == 0) { + item->new = item->unread = item->total = item->last_num = 0; + return 0; + } + + procmsg_get_mark_sum(item, &new, &unread, &total, &min, &max, first); + + if (max < first || last < min) + new = unread = total = num; + else { + if (min < first) + min = first; + + if (last < max) + max = last; + else if (max < last) { + new += last - max; + unread += last - max; + } + + if (new > num) new = num; + if (unread > num) unread = num; + } + + item->new = new; + item->unread = unread; + item->total = num; + item->last_num = last; + + return 0; +} + +static NewsGroupInfo *news_group_info_new(const gchar *name, + gint first, gint last, gchar type) +{ + NewsGroupInfo *ginfo; + + ginfo = g_new(NewsGroupInfo, 1); + ginfo->name = g_strdup(name); + ginfo->first = first; + ginfo->last = last; + ginfo->type = type; + + return ginfo; +} + +static void news_group_info_free(NewsGroupInfo *ginfo) +{ + g_free(ginfo->name); + g_free(ginfo); +} + +static gint news_group_info_compare(NewsGroupInfo *ginfo1, + NewsGroupInfo *ginfo2) +{ + return g_strcasecmp(ginfo1->name, ginfo2->name); +} + +GSList *news_get_group_list(Folder *folder) +{ + gchar *path, *filename; + FILE *fp; + GSList *list = NULL; + GSList *last = NULL; + gchar buf[NNTPBUFSIZE]; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, NULL); + + path = folder_item_get_path(FOLDER_ITEM(folder->node->data)); + if (!is_dir_exist(path)) + make_dir_hier(path); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL); + g_free(path); + + if ((fp = fopen(filename, "rb")) == NULL) { + NNTPSession *session; + gint ok; + + session = news_session_get(folder); + if (!session) { + g_free(filename); + return NULL; + } + + ok = nntp_list(session); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + g_free(filename); + return NULL; + } + if (recv_write_to_file(SESSION(session)->sock, filename) < 0) { + log_warning(_("can't retrieve newsgroup list\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + g_free(filename); + return NULL; + } + + if ((fp = fopen(filename, "rb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + g_free(filename); + return NULL; + } + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + gchar *p = buf; + gchar *name; + gint last_num; + gint first_num; + gchar type; + NewsGroupInfo *ginfo; + + p = strchr(p, ' '); + if (!p) continue; + *p = '\0'; + p++; + name = buf; + + if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3) + continue; + + ginfo = news_group_info_new(name, first_num, last_num, type); + + if (!last) + last = list = g_slist_append(NULL, ginfo); + else { + last = g_slist_append(last, ginfo); + last = last->next; + } + } + + fclose(fp); + g_free(filename); + + list = g_slist_sort(list, (GCompareFunc)news_group_info_compare); + + return list; +} + +void news_group_list_free(GSList *group_list) +{ + GSList *cur; + + if (!group_list) return; + + for (cur = group_list; cur != NULL; cur = cur->next) + news_group_info_free((NewsGroupInfo *)cur->data); + g_slist_free(group_list); +} + +void news_remove_group_list_cache(Folder *folder) +{ + gchar *path, *filename; + + g_return_if_fail(folder != NULL); + g_return_if_fail(FOLDER_TYPE(folder) == F_NEWS); + + path = folder_item_get_path(FOLDER_ITEM(folder->node->data)); + filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL); + g_free(path); + + if (is_file_exist(filename)) { + if (remove(filename) < 0) + FILE_OP_ERROR(filename, "remove"); + } + g_free(filename); +} + +gint news_post(Folder *folder, const gchar *file) +{ + FILE *fp; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1); + g_return_val_if_fail(file != NULL, -1); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + ok = news_post_stream(folder, fp); + + fclose(fp); + + return ok; +} + +gint news_post_stream(Folder *folder, FILE *fp) +{ + NNTPSession *session; + gint ok; + + g_return_val_if_fail(folder != NULL, -1); + g_return_val_if_fail(FOLDER_TYPE(folder) == F_NEWS, -1); + g_return_val_if_fail(fp != NULL, -1); + + session = news_session_get(folder); + if (!session) return -1; + + ok = nntp_post(session, fp); + if (ok != NN_SUCCESS) { + log_warning(_("can't post article.\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(folder)->session = NULL; + } + return -1; + } + + return 0; +} + +static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd, + gint num, gchar *filename) +{ + gchar *msgid; + gint ok; + + ok = nntp_get_article(session, cmd, num, &msgid); + if (ok != NN_SUCCESS) + return ok; + + debug_print("Message-Id = %s, num = %d\n", msgid, num); + g_free(msgid); + + ok = recv_write_to_file(SESSION(session)->sock, filename); + if (ok < 0) { + log_warning(_("can't retrieve article %d\n"), num); + if (ok == -2) + return NN_SOCKET; + else + return NN_IOERR; + } + + return NN_SUCCESS; +} + +static gint news_get_article(NNTPSession *session, gint num, gchar *filename) +{ + return news_get_article_cmd(session, "ARTICLE", num, filename); +} + +#if 0 +static gint news_get_header(NNTPSession *session, gint num, gchar *filename) +{ + return news_get_article_cmd(session, "HEAD", num, filename); +} +#endif + +/** + * news_select_group: + * @session: Active NNTP session. + * @group: Newsgroup name. + * @num: Estimated number of articles. + * @first: First article number. + * @last: Last article number. + * + * Select newsgroup @group with the GROUP command if it is not already + * selected in @session, or article numbers need to be returned. + * + * Return value: NNTP result code. + **/ +static gint news_select_group(NNTPSession *session, const gchar *group, + gint *num, gint *first, gint *last) +{ + gint ok; + gint num_, first_, last_; + + if (!num || !first || !last) { + if (session->group && g_strcasecmp(session->group, group) == 0) + return NN_SUCCESS; + num = &num_; + first = &first_; + last = &last_; + } + + g_free(session->group); + session->group = NULL; + + ok = nntp_group(session, group, num, first, last); + if (ok == NN_SUCCESS) + session->group = g_strdup(group); + else + log_warning(_("can't select group: %s\n"), group); + + return ok; +} + +static GSList *news_get_uncached_articles(NNTPSession *session, + FolderItem *item, gint cache_last, + gint *rfirst, gint *rlast) +{ + gint ok; + gint num = 0, first = 0, last = 0, begin = 0, end = 0; + gchar buf[NNTPBUFSIZE]; + GSList *newlist = NULL; + GSList *llast = NULL; + MsgInfo *msginfo; + + if (rfirst) *rfirst = -1; + if (rlast) *rlast = -1; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, NULL); + + ok = news_select_group(session, item->path, &num, &first, &last); + if (ok != NN_SUCCESS) { + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return NULL; + } + + /* calculate getting overview range */ + if (first > last) { + log_warning(_("invalid article range: %d - %d\n"), + first, last); + return NULL; + } + + if (rfirst) *rfirst = first; + if (rlast) *rlast = last; + + if (cache_last < first) + begin = first; + else if (last < cache_last) + begin = first; + else if (last == cache_last) { + debug_print(_("no new articles.\n")); + return NULL; + } else + begin = cache_last + 1; + end = last; + + if (prefs_common.max_articles > 0 && + end - begin + 1 > prefs_common.max_articles) + begin = end - prefs_common.max_articles + 1; + + log_message(_("getting xover %d - %d in %s...\n"), + begin, end, item->path); + ok = nntp_xover(session, begin, end); + if (ok != NN_SUCCESS) { + log_warning(_("can't get xover\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return NULL; + } + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { + log_warning(_("error occurred while getting xover.\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + return newlist; + } + + if (buf[0] == '.' && buf[1] == '\r') break; + + msginfo = news_parse_xover(buf); + if (!msginfo) { + log_warning(_("invalid xover line: %s\n"), buf); + continue; + } + + msginfo->folder = item; + msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD; + msginfo->flags.tmp_flags = MSG_NEWS; + msginfo->newsgroups = g_strdup(item->path); + + if (!newlist) + llast = newlist = g_slist_append(newlist, msginfo); + else { + llast = g_slist_append(llast, msginfo); + llast = llast->next; + } + } + + ok = nntp_xhdr(session, "to", begin, end); + if (ok != NN_SUCCESS) { + log_warning(_("can't get xhdr\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return newlist; + } + + llast = newlist; + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { + log_warning(_("error occurred while getting xhdr.\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + return newlist; + } + + if (buf[0] == '.' && buf[1] == '\r') break; + if (!llast) { + g_warning("llast == NULL\n"); + continue; + } + + msginfo = (MsgInfo *)llast->data; + msginfo->to = news_parse_xhdr(buf, msginfo); + + llast = llast->next; + } + + ok = nntp_xhdr(session, "cc", begin, end); + if (ok != NN_SUCCESS) { + log_warning(_("can't get xhdr\n")); + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + } + return newlist; + } + + llast = newlist; + + for (;;) { + if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { + log_warning(_("error occurred while getting xhdr.\n")); + session_destroy(SESSION(session)); + REMOTE_FOLDER(item->folder)->session = NULL; + return newlist; + } + + if (buf[0] == '.' && buf[1] == '\r') break; + if (!llast) { + g_warning("llast == NULL\n"); + continue; + } + + msginfo = (MsgInfo *)llast->data; + msginfo->cc = news_parse_xhdr(buf, msginfo); + + llast = llast->next; + } + + session_set_access_time(SESSION(session)); + + return newlist; +} + +#define PARSE_ONE_PARAM(p, srcp) \ +{ \ + p = strchr(srcp, '\t'); \ + if (!p) return NULL; \ + else \ + *p++ = '\0'; \ +} + +static MsgInfo *news_parse_xover(const gchar *xover_str) +{ + MsgInfo *msginfo; + gchar buf[NNTPBUFSIZE]; + gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp; + gchar *p; + gint num, size_int, line_int; + gchar *xover_buf; + + Xstrdup_a(xover_buf, xover_str, return NULL); + + PARSE_ONE_PARAM(subject, xover_buf); + PARSE_ONE_PARAM(sender, subject); + PARSE_ONE_PARAM(date, sender); + PARSE_ONE_PARAM(msgid, date); + PARSE_ONE_PARAM(ref, msgid); + PARSE_ONE_PARAM(size, ref); + PARSE_ONE_PARAM(line, size); + + tmp = strchr(line, '\t'); + if (!tmp) tmp = strchr(line, '\r'); + if (!tmp) tmp = strchr(line, '\n'); + if (tmp) *tmp = '\0'; + + num = atoi(xover_str); + size_int = atoi(size); + line_int = atoi(line); + + /* set MsgInfo */ + msginfo = g_new0(MsgInfo, 1); + msginfo->msgnum = num; + msginfo->size = size_int; + + msginfo->date = g_strdup(date); + msginfo->date_t = procheader_date_parse(NULL, date, 0); + + conv_unmime_header(buf, sizeof(buf), sender, NULL); + msginfo->from = g_strdup(buf); + msginfo->fromname = procheader_get_fromname(buf); + + conv_unmime_header(buf, sizeof(buf), subject, NULL); + msginfo->subject = g_strdup(buf); + + extract_parenthesis(msgid, '<', '>'); + remove_space(msgid); + if (*msgid != '\0') + msginfo->msgid = g_strdup(msgid); + + eliminate_parenthesis(ref, '(', ')'); + if ((p = strrchr(ref, '<')) != NULL) { + extract_parenthesis(p, '<', '>'); + remove_space(p); + if (*p != '\0') + msginfo->inreplyto = g_strdup(p); + } + + return msginfo; +} + +static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo) +{ + gchar *p; + gchar *tmp; + gint num; + + p = strchr(xhdr_str, ' '); + if (!p) + return NULL; + else + p++; + + num = atoi(xhdr_str); + if (msginfo->msgnum != num) return NULL; + + tmp = strchr(p, '\r'); + if (!tmp) tmp = strchr(p, '\n'); + + if (tmp) + return g_strndup(p, tmp - p); + else + return g_strdup(p); +} + +static GSList *news_delete_old_articles(GSList *alist, FolderItem *item, + gint first) +{ + GSList *cur, *next; + MsgInfo *msginfo; + gchar *dir; + + g_return_val_if_fail(item != NULL, alist); + g_return_val_if_fail(item->folder != NULL, alist); + g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_NEWS, alist); + + if (first < 2) return alist; + + debug_print("Deleting cached articles 1 - %d ...\n", first - 1); + + dir = folder_item_get_path(item); + remove_numbered_files(dir, 1, first - 1); + g_free(dir); + + for (cur = alist; cur != NULL; ) { + next = cur->next; + + msginfo = (MsgInfo *)cur->data; + if (msginfo && msginfo->msgnum < first) { + procmsg_msginfo_free(msginfo); + alist = g_slist_remove(alist, msginfo); + } + + cur = next; + } + + return alist; +} + +static void news_delete_all_articles(FolderItem *item) +{ + gchar *dir; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS); + + debug_print("Deleting all cached articles...\n"); + + dir = folder_item_get_path(item); + remove_all_numbered_files(dir); + g_free(dir); +} + +static void news_delete_expired_caches(GSList *alist, FolderItem *item) +{ + gchar *dir; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + g_return_if_fail(FOLDER_TYPE(item->folder) == F_NEWS); + + debug_print("Deleting expired cached articles...\n"); + + dir = folder_item_get_path(item); + remove_expired_files(dir, 24 * 7); + g_free(dir); +} diff --git a/src/news.h b/src/news.h new file mode 100644 index 00000000..31628113 --- /dev/null +++ b/src/news.h @@ -0,0 +1,59 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __NEWS_H__ +#define __NEWS_H__ + +#include +#include + +#include "folder.h" + +typedef struct _NewsFolder NewsFolder; +typedef struct _NewsGroupInfo NewsGroupInfo; + +#define NEWS_FOLDER(obj) ((NewsFolder *)obj) + +struct _NewsFolder +{ + RemoteFolder rfolder; + + gboolean use_auth; +}; + +struct _NewsGroupInfo +{ + gchar *name; + guint first; + guint last; + gchar type; +}; + +FolderClass *news_get_class (void); + +GSList *news_get_group_list (Folder *folder); +void news_group_list_free (GSList *group_list); +void news_remove_group_list_cache (Folder *folder); + +gint news_post (Folder *folder, + const gchar *file); +gint news_post_stream (Folder *folder, + FILE *fp); + +#endif /* __NEWS_H__ */ diff --git a/src/nntp.c b/src/nntp.c new file mode 100644 index 00000000..54335ce9 --- /dev/null +++ b/src/nntp.c @@ -0,0 +1,431 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "intl.h" +#include "nntp.h" +#include "socket.h" +#include "utils.h" +#if USE_SSL +# include "ssl.h" +#endif + +static gint verbose = 1; + +static void nntp_session_destroy(Session *session); + +static gint nntp_ok (SockInfo *sock, + gchar *argbuf); + +static gint nntp_gen_send (SockInfo *sock, + const gchar *format, + ...); +static gint nntp_gen_recv (SockInfo *sock, + gchar *buf, + gint size); +static gint nntp_gen_command (NNTPSession *session, + gchar *argbuf, + const gchar *format, + ...); + + +#if USE_SSL +Session *nntp_session_new(const gchar *server, gushort port, gchar *buf, + const gchar *userid, const gchar *passwd, + SSLType ssl_type) +#else +Session *nntp_session_new(const gchar *server, gushort port, gchar *buf, + const gchar *userid, const gchar *passwd) +#endif +{ + NNTPSession *session; + SockInfo *sock; + + if ((sock = sock_connect(server, port)) == NULL) { + log_warning(_("Can't connect to NNTP server: %s:%d\n"), + server, port); + return NULL; + } + +#if USE_SSL + if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) { + sock_close(sock); + return NULL; + } +#endif + + if (nntp_ok(sock, buf) != NN_SUCCESS) { + sock_close(sock); + return NULL; + } + + session = g_new0(NNTPSession, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_NEWS; + SESSION(session)->server = g_strdup(server); + SESSION(session)->sock = sock; + SESSION(session)->last_access_time = time(NULL); + SESSION(session)->data = NULL; + + SESSION(session)->destroy = nntp_session_destroy; + + session->group = NULL; + + if (userid && passwd) { + gint ok; + + session->userid = g_strdup(userid); + session->passwd = g_strdup(passwd); + + ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid); + if (ok != NN_SUCCESS) { + session_destroy(SESSION(session)); + return NULL; + } + ok = nntp_ok(sock, NULL); + if (ok == NN_AUTHCONT) { + ok = nntp_gen_send(sock, "AUTHINFO PASS %s", + session->passwd); + if (ok != NN_SUCCESS) { + session_destroy(SESSION(session)); + return NULL; + } + ok = nntp_ok(sock, NULL); + if (ok != NN_SUCCESS) + session->auth_failed = TRUE; + } + if (ok == NN_SOCKET) { + session_destroy(SESSION(session)); + return NULL; + } + } + + session_set_access_time(SESSION(session)); + + return SESSION(session); +} + +static void nntp_session_destroy(Session *session) +{ + NNTPSession *nntp_session = NNTP_SESSION(session); + + g_return_if_fail(session != NULL); + + g_free(nntp_session->group); + g_free(nntp_session->userid); + g_free(nntp_session->passwd); +} + +gint nntp_group(NNTPSession *session, const gchar *group, + gint *num, gint *first, gint *last) +{ + gint ok; + gint resp; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "GROUP %s", group); + + if (ok != NN_SUCCESS && ok != NN_SOCKET && ok != NN_AUTHREQ) { + ok = nntp_mode(session, FALSE); + if (ok == NN_SUCCESS) + ok = nntp_gen_command(session, buf, "GROUP %s", group); + } + + if (ok != NN_SUCCESS) + return ok; + + if (sscanf(buf, "%d %d %d %d", &resp, num, first, last) + != 4) { + log_warning(_("protocol error: %s\n"), buf); + return NN_PROTOCOL; + } + + return NN_SUCCESS; +} + +gint nntp_get_article(NNTPSession *session, const gchar *cmd, gint num, + gchar **msgid) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + if (num > 0) + ok = nntp_gen_command(session, buf, "%s %d", cmd, num); + else + ok = nntp_gen_command(session, buf, cmd); + + if (ok != NN_SUCCESS) + return ok; + + extract_parenthesis(buf, '<', '>'); + if (buf[0] == '\0') { + log_warning(_("protocol error\n")); + *msgid = g_strdup("0"); + } else + *msgid = g_strdup(buf); + + return NN_SUCCESS; +} + +gint nntp_article(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "ARTICLE", num, msgid); +} + +gint nntp_body(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "BODY", num, msgid); +} + +gint nntp_head(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "HEAD", num, msgid); +} + +gint nntp_stat(NNTPSession *session, gint num, gchar **msgid) +{ + return nntp_get_article(session, "STAT", num, msgid); +} + +gint nntp_next(NNTPSession *session, gint *num, gchar **msgid) +{ + gint ok; + gint resp; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "NEXT"); + + if (ok != NN_SUCCESS) + return ok; + + if (sscanf(buf, "%d %d", &resp, num) != 2) { + log_warning(_("protocol error: %s\n"), buf); + return NN_PROTOCOL; + } + + extract_parenthesis(buf, '<', '>'); + if (buf[0] == '\0') { + log_warning(_("protocol error\n")); + return NN_PROTOCOL; + } + *msgid = g_strdup(buf); + + return NN_SUCCESS; +} + +gint nntp_xover(NNTPSession *session, gint first, gint last) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "XOVER %d-%d", first, last); + if (ok != NN_SUCCESS) + return ok; + + return NN_SUCCESS; +} + +gint nntp_xhdr(NNTPSession *session, const gchar *header, gint first, gint last) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + ok = nntp_gen_command(session, buf, "XHDR %s %d-%d", + header, first, last); + if (ok != NN_SUCCESS) + return ok; + + return NN_SUCCESS; +} + +gint nntp_list(NNTPSession *session) +{ + return nntp_gen_command(session, NULL, "LIST"); +} + +gint nntp_post(NNTPSession *session, FILE *fp) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + gchar *msg; + + ok = nntp_gen_command(session, buf, "POST"); + if (ok != NN_SUCCESS) + return ok; + + msg = get_outgoing_rfc2822_str(fp); + if (sock_write_all(SESSION(session)->sock, msg, strlen(msg)) < 0) { + log_warning(_("Error occurred while posting\n")); + g_free(msg); + return NN_SOCKET; + } + g_free(msg); + + sock_write_all(SESSION(session)->sock, ".\r\n", 3); + if ((ok = nntp_ok(SESSION(session)->sock, buf)) != NN_SUCCESS) + return ok; + + session_set_access_time(SESSION(session)); + + return NN_SUCCESS; +} + +gint nntp_newgroups(NNTPSession *session) +{ + return NN_SUCCESS; +} + +gint nntp_newnews(NNTPSession *session) +{ + return NN_SUCCESS; +} + +gint nntp_mode(NNTPSession *session, gboolean stream) +{ + gint ok; + + ok = nntp_gen_command(session, NULL, "MODE %s", + stream ? "STREAM" : "READER"); + + return ok; +} + +static gint nntp_ok(SockInfo *sock, gchar *argbuf) +{ + gint ok; + gchar buf[NNTPBUFSIZE]; + + if ((ok = nntp_gen_recv(sock, buf, sizeof(buf))) == NN_SUCCESS) { + if (strlen(buf) < 3) + return NN_ERROR; + + if ((buf[0] == '1' || buf[0] == '2' || buf[0] == '3') && + (buf[3] == ' ' || buf[3] == '\0')) { + if (argbuf) + strcpy(argbuf, buf); + + if (!strncmp(buf, "381", 3)) + return NN_AUTHCONT; + + return NN_SUCCESS; + } else if (!strncmp(buf, "480", 3)) + return NN_AUTHREQ; + else + return NN_ERROR; + } + + return ok; +} + +static gint nntp_gen_send(SockInfo *sock, const gchar *format, ...) +{ + gchar buf[NNTPBUFSIZE]; + va_list args; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + if (verbose) { + if (!g_strncasecmp(buf, "AUTHINFO PASS", 13)) + log_print("NNTP> AUTHINFO PASS ********\n"); + else + log_print("NNTP> %s\n", buf); + } + + strcat(buf, "\r\n"); + if (sock_write_all(sock, buf, strlen(buf)) < 0) { + log_warning(_("Error occurred while sending command\n")); + return NN_SOCKET; + } + + return NN_SUCCESS; +} + +static gint nntp_gen_recv(SockInfo *sock, gchar *buf, gint size) +{ + if (sock_gets(sock, buf, size) == -1) + return NN_SOCKET; + + strretchomp(buf); + + if (verbose) + log_print("NNTP< %s\n", buf); + + return NN_SUCCESS; +} + +static gint nntp_gen_command(NNTPSession *session, gchar *argbuf, + const gchar *format, ...) +{ + gchar buf[NNTPBUFSIZE]; + va_list args; + gint ok; + SockInfo *sock; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + sock = SESSION(session)->sock; + ok = nntp_gen_send(sock, "%s", buf); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, argbuf); + if (ok == NN_AUTHREQ) { + if (!session->userid || !session->passwd) { + session->auth_failed = TRUE; + return ok; + } + + ok = nntp_gen_send(sock, "AUTHINFO USER %s", session->userid); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, NULL); + if (ok == NN_AUTHCONT) { + ok = nntp_gen_send(sock, "AUTHINFO PASS %s", + session->passwd); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, NULL); + } + if (ok != NN_SUCCESS) { + session->auth_failed = TRUE; + return ok; + } + + ok = nntp_gen_send(sock, "%s", buf); + if (ok != NN_SUCCESS) + return ok; + ok = nntp_ok(sock, argbuf); + } + + session_set_access_time(SESSION(session)); + + return ok; +} diff --git a/src/nntp.h b/src/nntp.h new file mode 100644 index 00000000..46992e42 --- /dev/null +++ b/src/nntp.h @@ -0,0 +1,109 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __NNTP_H__ +#define __NNTP_H__ + +#include "session.h" +#if USE_SSL +# include "ssl.h" +#endif + +typedef struct _NNTPSession NNTPSession; + +#define NNTP_SESSION(obj) ((NNTPSession *)obj) + +struct _NNTPSession +{ + Session session; + + gchar *group; + + gchar *userid; + gchar *passwd; + gboolean auth_failed; +}; + +#define NN_SUCCESS 0 +#define NN_SOCKET 2 +#define NN_AUTHFAIL 3 +#define NN_PROTOCOL 4 +#define NN_SYNTAX 5 +#define NN_IOERR 6 +#define NN_ERROR 7 +#define NN_AUTHREQ 8 +#define NN_AUTHCONT 9 + +#define NNTPBUFSIZE 8192 + +#if USE_SSL +Session *nntp_session_new (const gchar *server, + gushort port, + gchar *buf, + const gchar *userid, + const gchar *passwd, + SSLType ssl_type); +#else +Session *nntp_session_new (const gchar *server, + gushort port, + gchar *buf, + const gchar *userid, + const gchar *passwd); +#endif + +gint nntp_group (NNTPSession *session, + const gchar *group, + gint *num, + gint *first, + gint *last); +gint nntp_get_article (NNTPSession *session, + const gchar *cmd, + gint num, + gchar **msgid); +gint nntp_article (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_body (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_head (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_stat (NNTPSession *session, + gint num, + gchar **msgid); +gint nntp_next (NNTPSession *session, + gint *num, + gchar **msgid); +gint nntp_xover (NNTPSession *session, + gint first, + gint last); +gint nntp_xhdr (NNTPSession *session, + const gchar *header, + gint first, + gint last); +gint nntp_list (NNTPSession *session); +gint nntp_post (NNTPSession *session, + FILE *fp); +gint nntp_newgroups (NNTPSession *session); +gint nntp_newnews (NNTPSession *session); +gint nntp_mode (NNTPSession *sessio, + gboolean stream); + +#endif /* __NNTP_H__ */ diff --git a/src/passphrase.c b/src/passphrase.c new file mode 100644 index 00000000..445db517 --- /dev/null +++ b/src/passphrase.c @@ -0,0 +1,342 @@ +/* passphrase.c - GTK+ based passphrase callback + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if USE_GPGME + +#include +#include +#include +#include +#include +#ifdef GDK_WINDOWING_X11 +# include /* GDK_DISPLAY() */ +#endif /* GDK_WINDOWING_X11 */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "passphrase.h" +#include "prefs_common.h" +#include "manage_window.h" +#include "utils.h" + +static int grab_all = 0; + +static gboolean pass_ack; +static gchar *last_pass = NULL; + +static void passphrase_ok_cb(GtkWidget *widget, gpointer data); +static void passphrase_cancel_cb(GtkWidget *widget, gpointer data); +static gint passphrase_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data); +static gboolean passphrase_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data); +static gchar* passphrase_mbox (const gchar *desc); + + +static GtkWidget *create_description (const gchar *desc); + +void +gpgmegtk_set_passphrase_grab (gint yes) +{ + grab_all = yes; +} + +static gchar* +passphrase_mbox (const gchar *desc) +{ + gchar *the_passphrase = NULL; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *pass_label; + GtkWidget *confirm_box; + GtkWidget *window; + GtkWidget *pass_entry; + GtkWidget *ok_button; + GtkWidget *cancel_button; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Passphrase")); + gtk_widget_set_size_request(window, 450, -1); + gtk_container_set_border_width(GTK_CONTAINER(window), 4); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(passphrase_deleted), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(passphrase_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + manage_window_set_transient(GTK_WINDOW(window)); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + + if (desc) { + GtkWidget *label; + label = create_description (desc); + gtk_box_pack_start (GTK_BOX(vbox), label, TRUE, TRUE, 0); + } + + table = gtk_table_new(2, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(table), 8); + gtk_table_set_row_spacings(GTK_TABLE(table), 12); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + + pass_label = gtk_label_new(""); + gtk_table_attach (GTK_TABLE(table), pass_label, 0, 1, 0, 1, + GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); + gtk_misc_set_alignment (GTK_MISC (pass_label), 1, 0.5); + + pass_entry = gtk_entry_new(); + gtk_table_attach (GTK_TABLE(table), pass_entry, 1, 2, 0, 1, + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); + gtk_entry_set_visibility (GTK_ENTRY(pass_entry), FALSE); + gtk_widget_grab_focus (pass_entry); + + + confirm_box = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX(confirm_box), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX(confirm_box), 5); + + ok_button = gtk_button_new_with_label (_("OK")); + GTK_WIDGET_SET_FLAGS (ok_button, GTK_CAN_DEFAULT); + gtk_box_pack_start (GTK_BOX(confirm_box), ok_button, TRUE, TRUE, 0); + + cancel_button = gtk_button_new_with_label (_("Cancel")); + GTK_WIDGET_SET_FLAGS (cancel_button, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(confirm_box), cancel_button, TRUE, TRUE, 0); + + gtk_box_pack_end(GTK_BOX(vbox), confirm_box, FALSE, FALSE, 0); + gtk_widget_grab_default (ok_button); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(passphrase_ok_cb), NULL); + g_signal_connect(G_OBJECT(pass_entry), "activate", + G_CALLBACK(passphrase_ok_cb), NULL); + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(passphrase_cancel_cb), NULL); + + if (grab_all) + g_object_set (G_OBJECT(window), "type", GTK_WINDOW_POPUP, NULL); + gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); + if (grab_all) + gtk_window_set_policy (GTK_WINDOW(window), FALSE, FALSE, TRUE); + + gtk_widget_show_all(window); + + /* don't use XIM on entering passphrase */ + gtkut_editable_disable_im(GTK_EDITABLE(pass_entry)); + + if (grab_all) { +#ifdef GDK_WINDOWING_X11 + //XGrabServer(GDK_DISPLAY()); +#endif /* GDK_WINDOWING_X11 */ + if ( gdk_pointer_grab ( window->window, TRUE, 0, + NULL, NULL, GDK_CURRENT_TIME)) { +#ifdef GDK_WINDOWING_X11 + //XUngrabServer ( GDK_DISPLAY() ); +#endif /* GDK_WINDOWING_X11 */ + g_warning ("OOPS: Could not grab mouse\n"); + gtk_widget_destroy (window); + return NULL; + } + if ( gdk_keyboard_grab( window->window, FALSE, GDK_CURRENT_TIME )) { + gdk_pointer_ungrab (GDK_CURRENT_TIME); +#ifdef GDK_WINDOWING_X11 + //XUngrabServer ( GDK_DISPLAY() ); +#endif /* GDK_WINDOWING_X11 */ + g_warning ("OOPS: Could not grab keyboard\n"); + gtk_widget_destroy (window); + return NULL; + } + } + + gtk_main(); + + if (grab_all) { +#ifdef GDK_WINDOWING_X11 + //XUngrabServer (GDK_DISPLAY()); +#endif /* GDK_WINDOWING_X11 */ + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + gdk_flush(); + } + + manage_window_focus_out(window, NULL, NULL); + + if (pass_ack) { + const gchar *entry_text; + entry_text = gtk_entry_get_text(GTK_ENTRY(pass_entry)); + if (entry_text) /* Hmmm: Do we really need this? */ + the_passphrase = g_strdup (entry_text); + } + gtk_widget_destroy (window); + + return the_passphrase; +} + + +static void +passphrase_ok_cb(GtkWidget *widget, gpointer data) +{ + pass_ack = TRUE; + gtk_main_quit(); +} + +static void +passphrase_cancel_cb(GtkWidget *widget, gpointer data) +{ + pass_ack = FALSE; + gtk_main_quit(); +} + + +static gint +passphrase_deleted(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + passphrase_cancel_cb(NULL, NULL); + return TRUE; +} + + +static gboolean +passphrase_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) + passphrase_cancel_cb(NULL, NULL); + return FALSE; +} + +static gint +linelen (const gchar *s) +{ + gint i; + + for (i = 0; *s && *s != '\n'; s++, i++) + ; + + return i; +} + +static GtkWidget * +create_description (const gchar *desc) +{ + const gchar *cmd = NULL, *uid = NULL, *info = NULL; + gchar *buf; + GtkWidget *label; + + cmd = desc; + uid = strchr (cmd, '\n'); + if (uid) { + info = strchr (++uid, '\n'); + if (info ) + info++; + } + + if (!uid) + uid = _("[no user id]"); + if (!info) + info = ""; + + buf = g_strdup_printf (_("%sPlease enter the passphrase for:\n\n" + " %.*s \n" + "(%.*s)\n"), + !strncmp (cmd, "TRY_AGAIN", 9 ) ? + _("Bad passphrase! Try again...\n\n") : "", + linelen (uid), uid, linelen (info), info); + + label = gtk_label_new (buf); + g_free (buf); + + return label; +} + +static int free_passphrase(gpointer _unused) +{ + if (last_pass != NULL) { + munlock(last_pass, strlen(last_pass)); + g_free(last_pass); + last_pass = NULL; + debug_print("%% passphrase removed"); + } + + return FALSE; +} + +const char* +gpgmegtk_passphrase_cb (void *opaque, const char *desc, void **r_hd) +{ + struct passphrase_cb_info_s *info = opaque; + GpgmeCtx ctx = info ? info->c : NULL; + const char *pass; + + if (!desc) { + /* FIXME: cleanup by looking at *r_hd */ + return NULL; + } + if (prefs_common.store_passphrase && last_pass != NULL && + strncmp(desc, "TRY_AGAIN", 9) != 0) + return g_strdup(last_pass); + + gpgmegtk_set_passphrase_grab (prefs_common.passphrase_grab); + debug_print ("%% requesting passphrase for `%s': ", desc); + pass = passphrase_mbox (desc); + gpgmegtk_free_passphrase(); + if (!pass) { + debug_print ("%% cancel passphrase entry"); + gpgme_cancel (ctx); + } + else { + if (prefs_common.store_passphrase) { + last_pass = g_strdup(pass); + if (mlock(last_pass, strlen(last_pass)) == -1) + debug_print("%% locking passphrase failed"); + + if (prefs_common.store_passphrase_timeout > 0) { + gtk_timeout_add(prefs_common.store_passphrase_timeout*60*1000, + free_passphrase, NULL); + } + } + debug_print ("%% sending passphrase"); + } + + return pass; +} + +void gpgmegtk_free_passphrase() +{ + (void)free_passphrase(NULL); // could be inline +} + +#endif /* USE_GPGME */ diff --git a/src/passphrase.h b/src/passphrase.h new file mode 100644 index 00000000..e33301d9 --- /dev/null +++ b/src/passphrase.h @@ -0,0 +1,34 @@ +/* passphrase.h - GTK+ based passphrase callback + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef GPGMEGTK_PASSPHRASE_H +#define GPGMEGTK_PASSPHRASE_H + +#include +#include + +struct passphrase_cb_info_s { + GpgmeCtx c; + int did_it; +}; + +void gpgmegtk_set_passphrase_grab (gint yesno); +const char* gpgmegtk_passphrase_cb(void *opaque, const char *desc, void **r_hd); +void gpgmegtk_free_passphrase(); + +#endif /* GPGMEGTK_PASSPHRASE_H */ diff --git a/src/pixmaps/address.xpm b/src/pixmaps/address.xpm new file mode 100644 index 00000000..4105fb46 --- /dev/null +++ b/src/pixmaps/address.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char * address_xpm[] = { +"16 16 4 1", +" c None", +". c #071FF9", +"+ c #FFFFFF", +"@ c #000000", +" ", +" ", +" ...............", +" .+++.+++++.+++.", +" .++...+++...++.", +" .+++++++++++++.", +" .+++++++++++++.", +" .+++@+@@@@+@@+.", +" .+++++++++++++.", +" .+++@@+@+@@@@+.", +" .+++++++++++++.", +" .+++@@+@@@+@@+.", +" .+++++++++++++.", +" ...............", +" ", +" "}; diff --git a/src/pixmaps/book.xpm b/src/pixmaps/book.xpm new file mode 100644 index 00000000..5bd96517 --- /dev/null +++ b/src/pixmaps/book.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char * book_xpm[] = { +"16 16 4 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"@ c #BE22A6", +" ", +" ... ... ", +" .+++. .+++. ", +" .+++++. .+++++.", +" .++++++.++++++.", +" .++.+++.+++.++.", +" .++++.+.+.++++.", +" .++++++.+++.++.", +" .++.+++.+.++++.", +" .++++.+.+++.++.", +" .++++++.+.++++.", +" .++++++.++++++.", +" .+...++.++...+.", +" ..@@@.+.+.@@@..", +" .@ @...@ @.", +" @ @.@ @"}; diff --git a/src/pixmaps/category.xpm b/src/pixmaps/category.xpm new file mode 100644 index 00000000..fd16880f --- /dev/null +++ b/src/pixmaps/category.xpm @@ -0,0 +1,35 @@ +/* XPM */ +static char * category_xpm[] = { +"16 16 16 1", +" c None", +". c #000000", +"+ c #2C7AD4", +"@ c #3B98C5", +"# c #2D5AD3", +"$ c #01DFFF", +"% c #BFEDE2", +"& c #12BDEE", +"* c #B8EADF", +"= c #2ABAD6", +"- c #039FFD", +"; c #155DEB", +"> c #271BD9", +", c #3F18C1", +"' c #1F1CE1", +") c #3719C9", +" ", +" ", +" .... ", +" .+@+@. ", +" .+#+#@+...... ", +" .$$%$%&*%$==$.", +" .=@&@&@=+=+-;.", +" .$&@&@=+=+-+#.", +" .=@&@=+=+-+->.", +" .$&@=+=+-+-;#.", +" .=@=+=+-+-;-,.", +" .$=+=+-+-;-;>.", +" .=';';';';)>,.", +" ............ ", +" ", +" "}; diff --git a/src/pixmaps/checkbox_off.xpm b/src/pixmaps/checkbox_off.xpm new file mode 100644 index 00000000..1b926b3b --- /dev/null +++ b/src/pixmaps/checkbox_off.xpm @@ -0,0 +1,20 @@ +/* XPM */ +static char * checkbox_off_xpm[] = { +"13 13 4 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"@ c #DFDFDF", +" ", +" ........... ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .+++++++++@ ", +" .@@@@@@@@@@ ", +" "}; diff --git a/src/pixmaps/checkbox_on.xpm b/src/pixmaps/checkbox_on.xpm new file mode 100644 index 00000000..e4fe1c7f --- /dev/null +++ b/src/pixmaps/checkbox_on.xpm @@ -0,0 +1,20 @@ +/* XPM */ +static char * checkbox_on_xpm[] = { +"13 13 4 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"@ c #DFDFDF", +" ", +" ........... ", +" .+++++++++@ ", +" .+++++++.+@ ", +" .++++++..+@ ", +" .+.+++...+@ ", +" .+..+...++@ ", +" .+.....+++@ ", +" .++...++++@ ", +" .+++.+++++@ ", +" .+++++++++@ ", +" .@@@@@@@@@@ ", +" "}; diff --git a/src/pixmaps/clip.xpm b/src/pixmaps/clip.xpm new file mode 100644 index 00000000..c425d203 --- /dev/null +++ b/src/pixmaps/clip.xpm @@ -0,0 +1,17 @@ +/* XPM */ +static char * clip_xpm[] = { +"6 12 2 1", +" c None", +". c #000000", +" ... ", +" . .", +" . .", +".. ..", +".. ..", +".. ..", +".. ..", +". .. .", +". .", +". .", +" .... ", +" "}; diff --git a/src/pixmaps/complete.xpm b/src/pixmaps/complete.xpm new file mode 100644 index 00000000..69a9ad47 --- /dev/null +++ b/src/pixmaps/complete.xpm @@ -0,0 +1,17 @@ +/* XPM */ +static char * complete_xpm[] = { +"10 10 4 1", +" c None", +". c #000000", +"+ c #708C58", +"@ c #88AC84", +" ..", +" .+.", +" .++.", +".. .+++.", +".@. .+++. ", +".@@.+++. ", +".@@+++. ", +" .@++. ", +" .@. ", +" . "}; diff --git a/src/pixmaps/continue.xpm b/src/pixmaps/continue.xpm new file mode 100644 index 00000000..99b3c7f9 --- /dev/null +++ b/src/pixmaps/continue.xpm @@ -0,0 +1,42 @@ +/* XPM */ +static char * continue_xpm[] = { +"10 10 29 1", +" c None", +". c #000000", +"+ c #B1C7AC", +"@ c #F6F9F6", +"# c #E1EADF", +"$ c #F7F9F6", +"% c #F0F4EF", +"& c #ECF2EB", +"* c #E5EDE4", +"= c #F4F7F4", +"- c #EFF3EE", +"; c #E4ECE3", +"> c #E0E9DE", +", c #8EB184", +"' c #85AB7D", +") c #84AA7C", +"! c #82A678", +"~ c #769C6C", +"{ c #577550", +"] c #2E4429", +"^ c #8CB182", +"/ c #83A879", +"( c #7DA171", +"_ c #608756", +": c #30462B", +"< c #7CA273", +"[ c #52744A", +"} c #2D4526", +"| c #31472C", +".. ", +".+.. ", +".@#+.. ", +".$%&*+.. ", +".=-&;>#+..", +".,')!~{]..", +".^/(_:.. ", +".<[}.. ", +".|.. ", +".. "}; diff --git a/src/pixmaps/deleted.xpm b/src/pixmaps/deleted.xpm new file mode 100644 index 00000000..988364b8 --- /dev/null +++ b/src/pixmaps/deleted.xpm @@ -0,0 +1,15 @@ +/* XPM */ +static char * deleted_xpm[] = { +"10 10 2 1", +" c None", +". c #999999", +" ", +" . .. ", +" .. ... ", +" .... ", +" .. ", +" .... ", +" ..... ", +" .. ... ", +" . . ", +" "}; diff --git a/src/pixmaps/dir-close.xpm b/src/pixmaps/dir-close.xpm new file mode 100644 index 00000000..f6092fb3 --- /dev/null +++ b/src/pixmaps/dir-close.xpm @@ -0,0 +1,100 @@ +/* XPM */ +static char * dir_close_xpm[] = { +"16 16 81 1", +" c None", +". c #000000", +"+ c #79A0D4", +"@ c #92B8E3", +"# c #7599D1", +"$ c #8CB3DF", +"% c #8AB0DF", +"& c #9BBEE6", +"* c #93B7E4", +"= c #8BB1DF", +"- c #789ED7", +"; c #E6F2FA", +"> c #E2EFF9", +", c #E0EEF9", +"' c #DFEDF8", +") c #DAE9F6", +"! c #CBDFF4", +"~ c #C7DDF3", +"{ c #C0D8F1", +"] c #AFCCED", +"^ c #DCECF8", +"/ c #D1E6F5", +"( c #CBE2F4", +"_ c #CBE1F4", +": c #C9DFF3", +"< c #BFD8F0", +"[ c #B2D0ED", +"} c #ACCAED", +"| c #A0C3E9", +"1 c #8AAEDF", +"2 c #DAEAF6", +"3 c #C1DAF0", +"4 c #BFD9F0", +"5 c #BCD7F0", +"6 c #B5D1ED", +"7 c #ABCAEC", +"8 c #A2C4EA", +"9 c #9DBEE8", +"0 c #8CB0E0", +"a c #7397D3", +"b c #D8E9F6", +"c c #CAE0F3", +"d c #BBD6EF", +"e c #B0CFED", +"f c #A8C8EB", +"g c #9FC0E8", +"h c #95B9E6", +"i c #81A6DC", +"j c #6788CC", +"k c #D8EAF6", +"l c #98BBE6", +"m c #8CB0E2", +"n c #799DD8", +"o c #6484C4", +"p c #C5DCF1", +"q c #B8D4EE", +"r c #AECDEC", +"s c #A6C8EA", +"t c #9BBDE8", +"u c #80A3DD", +"v c #7095D2", +"w c #5E7DBE", +"x c #D1E5F5", +"y c #A0C2E7", +"z c #97BAE6", +"A c #91B4E3", +"B c #8AAEE1", +"C c #83A5DE", +"D c #6F92D1", +"E c #6384C7", +"F c #5874B3", +"G c #C0D9F1", +"H c #94B7E3", +"I c #7DA0DA", +"J c #7B9ED8", +"K c #7B9FD9", +"L c #779BD8", +"M c #7398D5", +"N c #6E8ED0", +"O c #6585C7", +"P c #536BA3", +" ", +" ", +" .... ", +" .+@@#. ", +" .$%&*=-...... ", +" .;>,',',)!~{].", +" .^/(_(_:<[}|1.", +" .2_344567890a.", +" .bc45def8ghij.", +" .kc c #F0F7FC", +", c #EFF6FC", +"' c #EFF6FB", +") c #ECF4FA", +"! c #E5EFF9", +"~ c #E3EEF9", +"{ c #DFEBF8", +"] c #D7E5F6", +"^ c #EDF5FB", +"/ c #E8F2FA", +"( c #E5F0F9", +"_ c #E4EFF9", +": c #DFEBF7", +"< c #D8E7F6", +"[ c #D5E4F6", +"} c #CFE1F4", +"| c #C4D6EF", +"1 c #E0ECF7", +"2 c #DFECF7", +"3 c #DDEBF7", +"4 c #DAE8F6", +"5 c #D5E4F5", +"6 c #D0E1F4", +"7 c #CEDEF3", +"8 c #C5D7EF", +"9 c #B9CBE9", +"0 c #EBF4FA", +"a c #DDEAF7", +"b c #D7E7F6", +"c c #D3E3F5", +"d c #CFDFF3", +"e c #CADCF2", +"f c #C0D2ED", +"g c #B3C3E5", +"h c #CBDDF2", +"i c #C5D7F0", +"j c #BCCEEB", +"k c #B1C1E1", +"l c #E2EDF8", +"m c #DBE9F6", +"n c #D6E6F5", +"o c #D2E3F4", +"p c #CDDEF3", +"q c #BFD1EE", +"r c #B7CAE8", +"s c #AEBEDE", +"t c #CFE0F3", +"u c #CBDCF2", +"v c #C8D9F1", +"w c #C4D6F0", +"x c #C1D2EE", +"y c #B7C8E8", +"z c #B1C1E3", +"A c #ABB9D9", +"B c #DFECF8", +"C c #BECFEC", +"D c #BDCEEB", +"E c #BDCFEC", +"F c #BBCDEB", +"G c #B9CBEA", +"H c #B6C6E7", +"I c #B2C2E3", +"J c #A9B5D1", +" ", +" ", +" .... ", +" .+@@#. ", +" .$%&*=-...... ", +" .;>,',',)!~{].", +" .^/((((_:<[}|.", +" .)(1223456789.", +" .0_23abc6defg.", +" .0_:abc6dhijk.", +" .0lmno}peiqrs.", +" ./4tu*vwxjyzA.", +" .B*CDEFGHIsAJ.", +" ............ ", +" ", +" "}; diff --git a/src/pixmaps/dir-open.xpm b/src/pixmaps/dir-open.xpm new file mode 100644 index 00000000..19fd6cd6 --- /dev/null +++ b/src/pixmaps/dir-open.xpm @@ -0,0 +1,83 @@ +/* XPM */ +static char * dir_open_xpm[] = { +"16 16 64 1", +" c None", +". c #000000", +"+ c #7EA4D8", +"@ c #93B7E5", +"# c #97BAE6", +"$ c #779CD5", +"% c #83A9DE", +"& c #8EB5E4", +"* c #8DB4E3", +"= c #7FA4DC", +"- c #7297D4", +"; c #9BBCE8", +"> c #87AEE2", +", c #8CB1E4", +"' c #8BAFE2", +") c #7FA3DE", +"! c #8DB2E3", +"~ c #7DA1DD", +"{ c #7598D9", +"] c #789DDA", +"^ c #80A4DF", +"/ c #4F679E", +"( c #5874B3", +"_ c #6485CA", +": c #DFEDF7", +"< c #DBECF7", +"[ c #D5E8F6", +"} c #D4E7F5", +"| c #D2E7F5", +"1 c #CBE0F3", +"2 c #A0C3E4", +"3 c #485D8C", +"4 c #546DA8", +"5 c #BFD9EE", +"6 c #C0D9EF", +"7 c #C2DBF1", +"8 c #BED9F1", +"9 c #BDD9F0", +"0 c #B7D3EE", +"a c #8CB3DC", +"b c #465883", +"c c #4C6293", +"d c #B4D2EE", +"e c #B3D1ED", +"f c #AECDEB", +"g c #93B9E0", +"h c #6185BD", +"i c #475B8A", +"j c #AFCFED", +"k c #AFCEEC", +"l c #A0C4E7", +"m c #8EB7DD", +"n c #455684", +"o c #A6C8EA", +"p c #A5C7E9", +"q c #A3C6E9", +"r c #85ABDA", +"s c #5C7CB6", +"t c #98BCE5", +"u c #97BCE6", +"v c #93B9E4", +"w c #8CB4E0", +"x c #8BB2DF", +"y c #759ED5", +" ", +" ", +" .... ", +" .+@#$. ", +" .$%&*=-...... ", +" .;>,>')!~{{]^.", +" .........../(_.", +".:<[}}}}}|12.34.", +".5678989890a.bc.", +" .5ddeeeeefgh.i.", +" .5jjkkkkkflm.n.", +" .dopppppqlrs..", +" .tuvvvvvvwxy..", +" ............ ", +" ", +" "}; diff --git a/src/pixmaps/error.xpm b/src/pixmaps/error.xpm new file mode 100644 index 00000000..e70ac53e --- /dev/null +++ b/src/pixmaps/error.xpm @@ -0,0 +1,45 @@ +/* XPM */ +static char * error_xpm[] = { +"10 10 32 1", +" c None", +". c #0B0402", +"+ c #331516", +"@ c #261B1C", +"# c #272120", +"$ c #3C1D1D", +"% c #4B2727", +"& c #462F2F", +"* c #443A3B", +"= c #573435", +"- c #573E3E", +"; c #524647", +"> c #734647", +", c #805353", +"' c #786D6D", +") c #826F72", +"! c #A16A6D", +"~ c #947273", +"{ c #9B7376", +"] c #877D7D", +"^ c #B07E7E", +"/ c #A48887", +"( c #AF9291", +"_ c #BB8D8F", +": c #AF9FA1", +"< c #C4A0A1", +"[ c #C2A5A4", +"} c #C3B4B5", +"| c #D0B4B6", +"1 c #D6B2B3", +"2 c #E1D7D6", +"3 c #EFE2E2", +" #*;;&+ ", +" ;:222|~% ", +"@:3}23:1,+", +"*2}#]]@/_%", +";23]..'|<=", +"-22]..)1_=", +"&[:@')+{!%", +"+~1(|1{^>+", +" ={__^!,% ", +" $===$+ "}; diff --git a/src/pixmaps/forwarded.xpm b/src/pixmaps/forwarded.xpm new file mode 100644 index 00000000..a3cabada --- /dev/null +++ b/src/pixmaps/forwarded.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char * forwarded_xpm[] = { +"10 10 10 1", +" c None", +". c #000000", +"+ c #B39C82", +"@ c #BCA488", +"# c #867561", +"$ c #8F7D68", +"% c #98856E", +"& c #A18D75", +"* c #AA947B", +"= c #C5AC8F", +" ", +" .. ", +" .+. ", +"......+@. ", +".#$%&*+@=.", +".#$%&*+@=.", +"......+@. ", +" .+. ", +" .. ", +" "}; diff --git a/src/pixmaps/group.xpm b/src/pixmaps/group.xpm new file mode 100644 index 00000000..0735a3cc --- /dev/null +++ b/src/pixmaps/group.xpm @@ -0,0 +1,97 @@ +/* XPM */ +static char * group_xpm[] = { +"16 16 78 1", +" c None", +". c #000000", +"+ c #83D47A", +"@ c #9EE393", +"# c #7CD175", +"$ c #99DF8D", +"% c #95DF8B", +"& c #A6E69C", +"* c #9DE494", +"= c #80D779", +"- c #EBFAE6", +"; c #E8F9E3", +"> c #E7F9E1", +", c #E6F8E0", +"' c #E0F6DA", +") c #D3F4CC", +"! c #CEF3C7", +"~ c #C9F1C1", +"{ c #B8EDAF", +"] c #E3F8DC", +"^ c #DAF5D1", +"/ c #D6F4CC", +"( c #D5F4CC", +"_ c #D1F3C9", +": c #C9F0C0", +"< c #BEEDB3", +"[ c #B6EDAD", +"} c #ABE9A1", +"| c #93DF8B", +"1 c #E1F6DA", +"2 c #CCF0C2", +"3 c #CAF0C0", +"4 c #C6F0BC", +"5 c #BEEDB5", +"6 c #B5ECAC", +"7 c #ACEAA2", +"8 c #A7E89E", +"9 c #94E08C", +"0 c #78D373", +"a c #DFF6D8", +"b c #D4F3CB", +"c c #C5EFBB", +"d c #BCEDB1", +"e c #B2EBA9", +"f c #A9E8A0", +"g c #A0E696", +"h c #88DC82", +"i c #68CC68", +"j c #E0F6D8", +"k c #A1E698", +"l c #94E28C", +"m c #7FD87A", +"n c #64C464", +"o c #CDF1C5", +"p c #C2EEB8", +"q c #B8ECAE", +"r c #B1EAA6", +"s c #A5E89C", +"t c #86DD81", +"u c #75D270", +"v c #5EBE5E", +"w c #ABE7A1", +"x c #99E391", +"y c #93E18B", +"z c #88DE84", +"A c #71D16F", +"B c #63C763", +"C c #59B35B", +"D c #CAF1C1", +"E c #9EE395", +"F c #82DA7E", +"G c #80D87C", +"H c #80D97B", +"I c #7CD878", +"J c #78D573", +"K c #6ED06E", +"L c #65C765", +"M c #53A355", +" ", +" ", +" .... ", +" .+@@#. ", +" .$%&*%=...... ", +" .-;>,>,>')!~{.", +" .]^/(/(_:<[}|.", +" .1(2334567890.", +" .ab34cde7fghi.", +" .jb:cde7fklmn.", +" .aopqr}sgltuv.", +" .^5wk*xyzmABC.", +" .DEFGHIJKLvCM.", +" ............ ", +" ", +" "}; diff --git a/src/pixmaps/inbox.xpm b/src/pixmaps/inbox.xpm new file mode 100644 index 00000000..321776e4 --- /dev/null +++ b/src/pixmaps/inbox.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char * inbox_xpm[] = { +"16 16 6 1", +" c None", +". c #020204", +"+ c #A9A9AA", +"@ c #F3F3F1", +"# c #636364", +"$ c #920A0C", +" $ ", +" $$$ . ", +" $$$ ..+. ", +" $$$+$+@. ", +" ..$$$$#@@. ", +"..+++$$$+#@@. ", +".@.+$$$$@@#@@. ", +".@@.++@@@@@#@@. ", +".@@@.++@@@@@#@+.", +" .@@@.+++@@@@#@.", +" .@@@.++@@@@+#.", +" .@@@.++@++++.", +" .@@+#++++.. ", +" .@@.++.. ", +" .@... ", +" .. "}; diff --git a/src/pixmaps/interface.xpm b/src/pixmaps/interface.xpm new file mode 100644 index 00000000..8a4158ec --- /dev/null +++ b/src/pixmaps/interface.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char * interface_xpm[] = { +"16 16 5 1", +" c None", +". c #B6B6B6", +"+ c #000000", +"@ c #726B6B", +"# c #FEF925", +" . ", +" + @@", +" + @ ", +" + @ ", +" + +++++ @ ", +" + +#.#.+ @ ", +" + ++++.#.#.+ @ ", +" + +#.#.#+ @ ", +" + +.#.#.+@ ", +" + +#.#.#+ ", +" + ++++.#.#.+ ", +" + +#.#.+ ", +" + +++++ ", +" + ", +" + ", +" "}; diff --git a/src/pixmaps/jpilot.xpm b/src/pixmaps/jpilot.xpm new file mode 100644 index 00000000..b72225cf --- /dev/null +++ b/src/pixmaps/jpilot.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char * jpilot_xpm[] = { +"16 16 6 1", +" c None", +". c #000000", +"+ c #848587", +"@ c #34991E", +"# c #F8FAF6", +"$ c #97989B", +" ", +" ........... ", +" .+++++++++. ", +" .+@@@@@@@+. ", +" .+@@@@@@@+. ", +" .+@@@@@@@+. ", +" .+@@@@@@@+. ", +" .+@@@@@@@+. ", +" .+@@@@@@@+. ", +" .+@@@@@@@+. ", +" .+++++++++. ", +" .++++#++++. ", +" .+#+#$#+#+. ", +" .++++#++++. ", +" ........... ", +" "}; diff --git a/src/pixmaps/ldap.xpm b/src/pixmaps/ldap.xpm new file mode 100644 index 00000000..c1e61e44 --- /dev/null +++ b/src/pixmaps/ldap.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char * ldap_xpm[] = { +"16 16 6 1", +" c None", +". c #000000", +"+ c #E3DAB1", +"@ c #606060", +"# c #A0A0A0", +"$ c #F6210A", +" ", +" . ........ ", +" . .++++++. ", +" . .@#@#@#. ", +" . .#@#@#@. ", +" . .++++++. ", +" @.@ .++++++. ", +" @......++++++. ", +" @.@ .++++++. ", +" . .++++++. ", +" . .++++++. ", +" . .+++$$+. ", +" . .+++$$+. ", +" . .++++++. ", +" . ........ ", +" "}; diff --git a/src/pixmaps/linewrap.xpm b/src/pixmaps/linewrap.xpm new file mode 100644 index 00000000..bce4af2b --- /dev/null +++ b/src/pixmaps/linewrap.xpm @@ -0,0 +1,29 @@ +/* XPM */ +static char * linewrap_xpm[] = { +"24 24 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" ", +" ", +" ", +" ............ . ", +" . . ", +" ............ .... ", +" . ", +" ............ ", +" ", +" ..... ", +" ", +" ............ . ", +" . . ", +" ............ .... ", +" . ", +" ..... ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/mail.xpm b/src/pixmaps/mail.xpm new file mode 100644 index 00000000..bf4766c0 --- /dev/null +++ b/src/pixmaps/mail.xpm @@ -0,0 +1,41 @@ +/* XPM */ +static char * mail_xpm[] = { +"13 10 28 1", +" c None", +". c #000000", +"+ c #474747", +"@ c #F5F5F5", +"# c #323232", +"$ c #F4F4F4", +"% c #C1C1C1", +"& c #EAEAEA", +"* c #EBEBEB", +"= c #AAAAAA", +"- c #3D3D3D", +"; c #EFEFEF", +"> c #F3F3F3", +", c #3F3F3F", +"' c #464646", +") c #BDBDBD", +"! c #D6D6D6", +"~ c #A0A0A0", +"{ c #F2F2F2", +"] c #BABABA", +"^ c #E9E9E9", +"/ c #8C8C8C", +"( c #BFBFBF", +"_ c #DADADA", +": c #CDCDCD", +"< c #A7A7A7", +"[ c #D3D3D3", +"} c #282828", +" ........... ", +".+@@@@@@@@@#.", +".@+@@@$@$$+%.", +".@&+@@$@$+*=.", +".@@@+@@@-;**.", +".>@;+,@'+@>).", +".@$+@@+@>+!~.", +".@+$@@{$>]#^.", +".+~~/(]_:<[}.", +" ........... "}; diff --git a/src/pixmaps/mark.xpm b/src/pixmaps/mark.xpm new file mode 100644 index 00000000..de17d9c9 --- /dev/null +++ b/src/pixmaps/mark.xpm @@ -0,0 +1,16 @@ +/* XPM */ +static char * mark_xpm[] = { +"10 10 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +" ..", +" .+.", +" .++.", +".. .+++.", +".+. .+++. ", +".++.+++. ", +".+++++. ", +" .+++. ", +" .+. ", +" . "}; diff --git a/src/pixmaps/new.xpm b/src/pixmaps/new.xpm new file mode 100644 index 00000000..74992613 --- /dev/null +++ b/src/pixmaps/new.xpm @@ -0,0 +1,62 @@ +/* XPM */ +static char * new_xpm[] = { +"13 10 49 1", +" c None", +". c #000000", +"+ c #D52828", +"@ c #F5CECD", +"# c #F5CFCE", +"$ c #F4CECB", +"% c #F4CDCB", +"& c #9C1D1D", +"* c #F4CDC9", +"= c #F3C8C2", +"- c #F3C8C4", +"; c #F4CAC7", +"> c #E5837C", +", c #EFB6AE", +"' c #F3CCC6", +") c #F3CAC2", +"! c #F3CBC6", +"~ c #F3CBC4", +"{ c #EFB7B0", +"] c #E06F63", +"^ c #F5CECE", +"/ c #F0BFB7", +"( c #F3C7C2", +"_ c #F0C0B5", +": c #F3CCC4", +"< c #F2C6BF", +"[ c #E37E70", +"} c #F4CDC7", +"| c #F3CBC2", +"1 c #F2CAC1", +"2 c #F1C6BA", +"3 c #E89789", +"4 c #DE6255", +"5 c #F3CAC4", +"6 c #F0C3B7", +"7 c #F2C7BF", +"8 c #E27A69", +"9 c #A61F1F", +"0 c #EEB4AD", +"a c #DF685C", +"b c #DE6559", +"c c #DA5345", +"d c #E37F6E", +"e c #E17968", +"f c #E89C8D", +"g c #E58C7E", +"h c #DF6A5A", +"i c #E8968B", +"j c #771616", +" ........... ", +".+@#@$%@@@%&.", +".@+$*#=$-;+>.", +".%,+*')!~+{].", +".^%*+$'*+/{{.", +".(@_++:++)<[.", +".@=+}|+12+34.", +".@+5'~677890.", +".+abcdefghij.", +" ........... "}; diff --git a/src/pixmaps/offline.xpm b/src/pixmaps/offline.xpm new file mode 100644 index 00000000..baa36a49 --- /dev/null +++ b/src/pixmaps/offline.xpm @@ -0,0 +1,228 @@ +/* XPM */ +static char * offline_xpm[] = { +"26 12 213 2", +" c None", +". c #FFFFFF", +"+ c #FFFFCC", +"@ c #FFFF99", +"# c #FFFF66", +"$ c #FFFF33", +"% c #FFFF00", +"& c #FFCCFF", +"* c #FFCCCC", +"= c #FFCC99", +"- c #FFCC66", +"; c #FFCC33", +"> c #FFCC00", +", c #FF99FF", +"' c #FF99CC", +") c #FF9999", +"! c #FF9966", +"~ c #FF9933", +"{ c #FF9900", +"] c #FF66FF", +"^ c #FF66CC", +"/ c #FF6699", +"( c #FF6666", +"_ c #FF6633", +": c #FF6600", +"< c #FF33FF", +"[ c #FF33CC", +"} c #FF3399", +"| c #FF3366", +"1 c #FF3333", +"2 c #FF3300", +"3 c #FF00FF", +"4 c #FF00CC", +"5 c #FF0099", +"6 c #FF0066", +"7 c #FF0033", +"8 c #FF0000", +"9 c #CCFFFF", +"0 c #CCFFCC", +"a c #CCFF99", +"b c #CCFF66", +"c c #CCFF33", +"d c #CCFF00", +"e c #CCCCFF", +"f c #CCCCCC", +"g c #CCCC99", +"h c #CCCC66", +"i c #CCCC33", +"j c #CCCC00", +"k c #CC99FF", +"l c #CC99CC", +"m c #CC9999", +"n c #CC9966", +"o c #CC9933", +"p c #CC9900", +"q c #CC66FF", +"r c #CC66CC", +"s c #CC6699", +"t c #CC6666", +"u c #CC6633", +"v c #CC6600", +"w c #CC33FF", +"x c #CC33CC", +"y c #CC3399", +"z c #CC3366", +"A c #CC3333", +"B c #CC3300", +"C c #CC00FF", +"D c #CC00CC", +"E c #CC0099", +"F c #CC0066", +"G c #CC0033", +"H c #CC0000", +"I c #99FFFF", +"J c #99FFCC", +"K c #99FF99", +"L c #99FF66", +"M c #99FF33", +"N c #99FF00", +"O c #99CCFF", +"P c #99CCCC", +"Q c #99CC99", +"R c #99CC66", +"S c #99CC33", +"T c #99CC00", +"U c #9999FF", +"V c #9999CC", +"W c #999999", +"X c #999966", +"Y c #999933", +"Z c #999900", +"` c #9966FF", +" . c #9966CC", +".. c #996699", +"+. c #996666", +"@. c #996633", +"#. c #996600", +"$. c #9933FF", +"%. c #9933CC", +"&. c #993399", +"*. c #993366", +"=. c #993333", +"-. c #993300", +";. c #9900FF", +">. c #9900CC", +",. c #990099", +"'. c #990066", +"). c #990033", +"!. c #990000", +"~. c #66FFFF", +"{. c #66FFCC", +"]. c #66FF99", +"^. c #66FF66", +"/. c #66FF33", +"(. c #66FF00", +"_. c #66CCFF", +":. c #66CCCC", +"<. c #66CC99", +"[. c #66CC66", +"}. c #66CC33", +"|. c #66CC00", +"1. c #6699FF", +"2. c #6699CC", +"3. c #669999", +"4. c #669966", +"5. c #669933", +"6. c #669900", +"7. c #6666FF", +"8. c #6666CC", +"9. c #666699", +"0. c #666666", +"a. c #666633", +"b. c #666600", +"c. c #6633FF", +"d. c #6633CC", +"e. c #663399", +"f. c #663366", +"g. c #663333", +"h. c #663300", +"i. c #6600FF", +"j. c #6600CC", +"k. c #660099", +"l. c #660066", +"m. c #660033", +"n. c #660000", +"o. c #33FFFF", +"p. c #33FFCC", +"q. c #33FF99", +"r. c #33FF66", +"s. c #33FF33", +"t. c #33FF00", +"u. c #33CCFF", +"v. c #33CCCC", +"w. c #33CC99", +"x. c #33CC66", +"y. c #33CC33", +"z. c #33CC00", +"A. c #3399FF", +"B. c #3399CC", +"C. c #339999", +"D. c #339966", +"E. c #339933", +"F. c #339900", +"G. c #3366FF", +"H. c #3366CC", +"I. c #336699", +"J. c #336666", +"K. c #336633", +"L. c #336600", +"M. c #3333FF", +"N. c #3333CC", +"O. c #333399", +"P. c #333366", +"Q. c #333333", +"R. c #333300", +"S. c #3300FF", +"T. c #3300CC", +"U. c #330099", +"V. c #330066", +"W. c #330033", +"X. c #330000", +"Y. c #00FFFF", +"Z. c #00FFCC", +"`. c #00FF99", +" + c #00FF66", +".+ c #00FF33", +"++ c #00FF00", +"@+ c #00CCFF", +"#+ c #00CCCC", +"$+ c #00CC99", +"%+ c #00CC66", +"&+ c #00CC33", +"*+ c #00CC00", +"=+ c #0099FF", +"-+ c #0099CC", +";+ c #009999", +">+ c #009966", +",+ c #009933", +"'+ c #009900", +")+ c #0066FF", +"!+ c #0066CC", +"~+ c #006699", +"{+ c #006666", +"]+ c #006633", +"^+ c #006600", +"/+ c #0033FF", +"(+ c #0033CC", +"_+ c #003399", +":+ c #003366", +"<+ c #003333", +"[+ c #003300", +"}+ c #0000FF", +"|+ c #0000CC", +" ", +" P.P.P.P.P. ", +" P.. e |+e |+ P.P.P.P. ", +" P.e U P.8.|+ P.. U P. ", +" P.P.U U P.e |+ P.|+e U P.P. ", +"P.P.P.P.P.e P.U U P.8.|+ |+U U P.e P.P.P.P.", +"U U U U P.U P.U U P.e |+ P.|+U U P.U P.8.8.8.", +"P.P.P.P.P.8.P.U U P.8.|+ |+U U P.8.P.P.P.P.", +" P.P.U U P.e |+ P.|+U U P.P. ", +" P.U U P.P.|+ P.8.8.P. ", +" P.8.8.P.W P. P.P.P.P. ", +" P.P.P.P.P. "}; diff --git a/src/pixmaps/online.xpm b/src/pixmaps/online.xpm new file mode 100644 index 00000000..ed508651 --- /dev/null +++ b/src/pixmaps/online.xpm @@ -0,0 +1,232 @@ +/* XPM */ +static char * online_xpm[] = { +"26 12 217 2", +" c None", +". c #FFFFFF", +"+ c #FFFFCC", +"@ c #FFFF99", +"# c #FFFF66", +"$ c #FFFF33", +"% c #FFFF00", +"& c #FFCCFF", +"* c #FFCCCC", +"= c #FFCC99", +"- c #FFCC66", +"; c #FFCC33", +"> c #FFCC00", +", c #FF99FF", +"' c #FF99CC", +") c #FF9999", +"! c #FF9966", +"~ c #FF9933", +"{ c #FF9900", +"] c #FF66FF", +"^ c #FF66CC", +"/ c #FF6699", +"( c #FF6666", +"_ c #FF6633", +": c #FF6600", +"< c #FF33FF", +"[ c #FF33CC", +"} c #FF3399", +"| c #FF3366", +"1 c #FF3333", +"2 c #FF3300", +"3 c #FF00FF", +"4 c #FF00CC", +"5 c #FF0099", +"6 c #FF0066", +"7 c #FF0033", +"8 c #FF0000", +"9 c #CCFFFF", +"0 c #CCFFCC", +"a c #CCFF99", +"b c #CCFF66", +"c c #CCFF33", +"d c #CCFF00", +"e c #CCCCFF", +"f c #CCCCCC", +"g c #CCCC99", +"h c #CCCC66", +"i c #CCCC33", +"j c #CCCC00", +"k c #CC99FF", +"l c #CC99CC", +"m c #CC9999", +"n c #CC9966", +"o c #CC9933", +"p c #CC9900", +"q c #CC66FF", +"r c #CC66CC", +"s c #CC6699", +"t c #CC6666", +"u c #CC6633", +"v c #CC6600", +"w c #CC33FF", +"x c #CC33CC", +"y c #CC3399", +"z c #CC3366", +"A c #CC3333", +"B c #CC3300", +"C c #CC00FF", +"D c #CC00CC", +"E c #CC0099", +"F c #CC0066", +"G c #CC0033", +"H c #CC0000", +"I c #99FFFF", +"J c #99FFCC", +"K c #99FF99", +"L c #99FF66", +"M c #99FF33", +"N c #99FF00", +"O c #99CCFF", +"P c #99CCCC", +"Q c #99CC99", +"R c #99CC66", +"S c #99CC33", +"T c #99CC00", +"U c #9999FF", +"V c #9999CC", +"W c #999999", +"X c #999966", +"Y c #999933", +"Z c #999900", +"` c #9966FF", +" . c #9966CC", +".. c #996699", +"+. c #996666", +"@. c #996633", +"#. c #996600", +"$. c #9933FF", +"%. c #9933CC", +"&. c #993399", +"*. c #993366", +"=. c #993333", +"-. c #993300", +";. c #9900FF", +">. c #9900CC", +",. c #990099", +"'. c #990066", +"). c #990033", +"!. c #990000", +"~. c #66FFFF", +"{. c #66FFCC", +"]. c #66FF99", +"^. c #66FF66", +"/. c #66FF33", +"(. c #66FF00", +"_. c #66CCFF", +":. c #66CCCC", +"<. c #66CC99", +"[. c #66CC66", +"}. c #66CC33", +"|. c #66CC00", +"1. c #6699FF", +"2. c #6699CC", +"3. c #669999", +"4. c #669966", +"5. c #669933", +"6. c #669900", +"7. c #6666FF", +"8. c #6666CC", +"9. c #666699", +"0. c #666666", +"a. c #666633", +"b. c #666600", +"c. c #6633FF", +"d. c #6633CC", +"e. c #663399", +"f. c #663366", +"g. c #663333", +"h. c #663300", +"i. c #6600FF", +"j. c #6600CC", +"k. c #660099", +"l. c #660066", +"m. c #660033", +"n. c #660000", +"o. c #33FFFF", +"p. c #33FFCC", +"q. c #33FF99", +"r. c #33FF66", +"s. c #33FF33", +"t. c #33FF00", +"u. c #33CCFF", +"v. c #33CCCC", +"w. c #33CC99", +"x. c #33CC66", +"y. c #33CC33", +"z. c #33CC00", +"A. c #3399FF", +"B. c #3399CC", +"C. c #339999", +"D. c #339966", +"E. c #339933", +"F. c #339900", +"G. c #3366FF", +"H. c #3366CC", +"I. c #336699", +"J. c #336666", +"K. c #336633", +"L. c #336600", +"M. c #3333FF", +"N. c #3333CC", +"O. c #333399", +"P. c #333366", +"Q. c #333333", +"R. c #333300", +"S. c #3300FF", +"T. c #3300CC", +"U. c #330099", +"V. c #330066", +"W. c #330033", +"X. c #330000", +"Y. c #00FFFF", +"Z. c #00FFCC", +"`. c #00FF99", +" + c #00FF66", +".+ c #00FF33", +"++ c #00FF00", +"@+ c #00CCFF", +"#+ c #00CCCC", +"$+ c #00CC99", +"%+ c #00CC66", +"&+ c #00CC33", +"*+ c #00CC00", +"=+ c #0099FF", +"-+ c #0099CC", +";+ c #009999", +">+ c #009966", +",+ c #009933", +"'+ c #009900", +")+ c #0066FF", +"!+ c #0066CC", +"~+ c #006699", +"{+ c #006666", +"]+ c #006633", +"^+ c #006600", +"/+ c #0033FF", +"(+ c #0033CC", +"_+ c #003399", +":+ c #003366", +"<+ c #003333", +"[+ c #003300", +"}+ c #0000FF", +"|+ c #0000CC", +"1+ c #000099", +"2+ c #000066", +"3+ c #000033", +"4+ c #000000", +" ", +" 8.P.P. ", +" 8.. e P.8.8.P. ", +" 8.U e }+e . e P. ", +" P.P.U e }+U U U P.P. ", +"8.8.8.8.8.8.8.8.8.e P.U e }+U U U P.e 4+P.P.P.P.P.P.", +"e e U e U U U U P.U P.U e }+U U U P.U P.U U U U U U ", +"P.P.P.P.P.P.P.P.P.8.P.U e }+U U U P.8.P.P.P.P.P.P.P.", +" P.P.U e }+8.8.8.P.P. ", +" P.U 8.}+8.8.8.P. ", +" 4+8.8.P.P.P.P. ", +" P.P.P. "}; diff --git a/src/pixmaps/outbox.xpm b/src/pixmaps/outbox.xpm new file mode 100644 index 00000000..70944f13 --- /dev/null +++ b/src/pixmaps/outbox.xpm @@ -0,0 +1,27 @@ +/* XPM */ +static char * outbox_xpm[] = { +"16 16 8 1", +" c None", +". c #020204", +"+ c #99999B", +"@ c #E5E5E3", +"# c #024A6C", +"$ c #626263", +"% c #FDFDFB", +"& c #B4B4B4", +" ", +" . ####", +" ..+. ###", +" ..+++%. ####", +" ..++++$%%### #", +"..++++$$&$### ", +".%.+$$&@@###%. ", +".%%.+&@@@@#$%%. ", +".%%%.&&@@@@@$%+.", +" .%%%.&&@@@@@$@.", +" .%%%.&&@@@@&$.", +" .%%%.&@@&&&+.", +" .%%+$&&&+.. ", +" .%@.&+.. ", +" .%... ", +" .. "}; diff --git a/src/pixmaps/replied.xpm b/src/pixmaps/replied.xpm new file mode 100644 index 00000000..e16ab028 --- /dev/null +++ b/src/pixmaps/replied.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char * replied_xpm[] = { +"10 10 11 1", +" c None", +". c #000000", +"+ c #E0D8B0", +"@ c #B39C82", +"# c #BCA488", +"$ c #C5AC8F", +"% c #AA947B", +"& c #A18D75", +"* c #98856E", +"= c #8F7D68", +"- c #867561", +" . ", +" .. .+. ", +" .@..+++.", +" .#@......", +".$#@%&*=-.", +".$#@%&*=. ", +" .#@.... ", +" .@. ", +" .. ", +" "}; diff --git a/src/pixmaps/stock_add_16.xpm b/src/pixmaps/stock_add_16.xpm new file mode 100644 index 00000000..f068b153 --- /dev/null +++ b/src/pixmaps/stock_add_16.xpm @@ -0,0 +1,35 @@ +/* XPM */ +static char * stock_add_16_xpm[] = { +"16 16 16 1", +" c None", +". c #000100", +"+ c #4D6076", +"@ c #5C6E85", +"# c #5F7289", +"$ c #607794", +"% c #6B829F", +"& c #7689A1", +"* c #7890AD", +"= c #859CB9", +"- c #939EAC", +"; c #93A6BE", +"> c #A0AFC2", +", c #A9B7C8", +"' c #B9C4D2", +") c #C9D4DF", +" ... ", +" .)'-. ", +" .'*#. ", +" .'*#. ", +" .'*#. ", +" .....'*@..... ", +".,>>>>;*=,'''-. ", +".)***********$. ", +".&####**%#####. ", +" .....=*$..... ", +" .'*#. ", +" .'*#. ", +" .'*#. ", +" .>#+. ", +" ... ", +" "}; diff --git a/src/pixmaps/stock_close.xpm b/src/pixmaps/stock_close.xpm new file mode 100644 index 00000000..88c28568 --- /dev/null +++ b/src/pixmaps/stock_close.xpm @@ -0,0 +1,29 @@ +/* XPM */ +static char * stock_close_xpm[] = { +"24 24 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" ", +" ", +" ", +" . .. ", +" .. .... ", +" .. ... ", +" ..... ", +" ... ", +" .... ", +" ...... ", +" .. .... ", +" .. .... ", +" . .. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_dialog_error_48.xpm b/src/pixmaps/stock_dialog_error_48.xpm new file mode 100644 index 00000000..521bf278 --- /dev/null +++ b/src/pixmaps/stock_dialog_error_48.xpm @@ -0,0 +1,115 @@ +/* XPM */ +static char * stock_dialog_error_48_xpm[] = { +"48 48 64 1", +" c None", +". c #590304", +"+ c #371515", +"@ c #750002", +"# c #471514", +"$ c #6C0904", +"% c #800100", +"& c #58150E", +"* c #66140D", +"= c #950702", +"- c #A40204", +"; c #5D231F", +"> c #522824", +", c #463736", +"' c #A90E08", +") c #662C1F", +"! c #8B1F0C", +"~ c #AF1604", +"{ c #A91C0A", +"] c #6C3530", +"^ c #543F3D", +"/ c #B9160A", +"( c #B31C07", +"_ c #683B35", +": c #863022", +"< c #B12411", +"[ c #C01F10", +"} c #C52514", +"| c #774840", +"1 c #BF2E14", +"2 c #CA2B10", +"3 c #6E5149", +"4 c #D13216", +"5 c #CB371A", +"6 c #795D58", +"7 c #D8391B", +"8 c #835C54", +"9 c #706562", +"0 c #95584D", +"a c #BE463E", +"b c #DD3D1E", +"c c #DF3F18", +"d c #D8482A", +"e c #886C68", +"f c #CE513C", +"g c #936D66", +"h c #C65951", +"i c #AF6C5B", +"j c #C56456", +"k c #E55F3F", +"l c #A6786F", +"m c #D46F63", +"n c #A6837A", +"o c #E66D50", +"p c #C37F6F", +"q c #E5725B", +"r c #C08877", +"s c #E5836D", +"t c #C0C2BE", +"u c #DADCD9", +"v c #E2E4E1", +"w c #EBEDEA", +"x c #F0F2EF", +"y c #F4F6F3", +" ", +" ", +" ", +" ", +" ", +" l8|||||8g ", +" l||8imssmmj00]| ", +" l38psokdd777bddf:)| ", +" 38pskbb777774444251:&g ", +" r3lskccbc777444444222}<*| ", +" n3pqbccb7777744444}22}2}1$_ ", +" r3rkbcc77777444444222}2}}[($| ", +" 3roccc77774444442}22}}}}[1[<$e ", +" 6locbbb7777444444222}}2}1[[[[{. ", +" n8scbb777774444442}2}}}}[[[[(/(=_ ", +" 3pbbb777744444452222}}2[[[[(///'$ ", +" neocbb777444444}2}2}}}}[[[[[/((//=_ ", +" 6pbb77777444422222}2}}[1[[[/(/((/{* ", +" 6ob777774444422}2}21[[[[[[/(/((/~'%e ", +" ngb777511111<111<<<<<<{{{{{{{{'(<~~=_ ", +" gi77771twwvvvvvvvvvuvuuuvuuuuv<~~''{) ", +" 6j77775uyyyyyyyyyyyyyxxxxxxxwya'~~'{* ", +" 6f74445uyyyyyyyyyyxxxxxxxwxwwyh''''-$ ", +" 6d74445uyyyyyyyxyxxxxxwwwwwwwyh''''-@ ", +" 8574442uyyyyyxxxxxxxwwwwwwwwwxh~''-=$ ", +" 8144441uyyyxxxxxwxwwwwwwwwwwwyh'''-=* ", +" 8<44425vyyyyyyyyyyxyxxxxxxxxxxa'''=%; ", +" g:444}5fqmsmmmmmmmmmhmhjhhhhha'''-=@> ", +" :44222}}}}1[[[(//((((~'~'''''----%$^ ", +" )1222}21}[[[([(/(((//~'~~'''---{=%.9 ", +" 8!2}}2}[[1[([(/((//~~~~'''''----%@+ ", +" *12}1}[[[([/(//((~'~''''''--{-=%$, ", +" |!2}}[1[[[(/(/((/~~'''''-----=%@# ", +" &<[1[1[1//(/((~~''''''------=%., ", +" g*<[[[//(////~'~~''''------=%$+ ", +" |$((//(///(~''~''''{-----=%$+ ", +" _$ c #908252", +", c #838587", +"' c #A49356", +") c #A5976D", +"! c #96989E", +"~ c #979B97", +"{ c #BBA763", +"] c #A5A7AF", +"^ c #A7AAA7", +"/ c #B3B196", +"( c #C2B077", +"_ c #BFB38B", +": c #C9B56C", +"< c #B4B3BB", +"[ c #B9B9B6", +"} c #D9C776", +"| c #C1C5D3", +"1 c #D2C6A7", +"2 c #C7C7C1", +"3 c #D4C79E", +"4 c #D6C895", +"5 c #C7C6CA", +"6 c #CAC9B9", +"7 c #CCCAB4", +"8 c #DBCB91", +"9 c #D2D6D0", +"0 c #D5D5E0", +"a c #EBDA91", +"b c #D2DAF0", +"c c #D9DADE", +"d c #EDDEA3", +"e c #D8DCEC", +"f c #D3DFED", +"g c #D5E0E8", +"h c #E8DEC0", +"i c #DEDBED", +"j c #EFE3B6", +"k c #D2EAE9", +"l c #D8E7EA", +"m c #DCE5EE", +"n c #E0E3F3", +"o c #F0E5C5", +"p c #E9E4E2", +"q c #E5E6F1", +"r c #E6EAEB", +"s c #F4EBD2", +"t c #EBECF7", +"u c #F1EEF3", +"v c #F6F3F8", +"w c #F9F6EA", +"x c #FBFAFE", +"y c #FEFFFC", +" ", +" 9[]~!~!^[ ", +" [!]50qne0+ ", +" + ", +" ^soj43_({{'14>+ ", +" ]ajwywwsohh4{-+ ", +" ^j8jjd8}:::{'*+ ", +" + ", +" [-8swwoojh3('.$ ", +" %(84d8:::{'#. ", +" +%)))>-*##. ", +" @+,;+.. ", +" $==+. ", +" &@@ ", +" ", +" "}; diff --git a/src/pixmaps/stock_dialog_question_48.xpm b/src/pixmaps/stock_dialog_question_48.xpm new file mode 100644 index 00000000..45b24a9e --- /dev/null +++ b/src/pixmaps/stock_dialog_question_48.xpm @@ -0,0 +1,115 @@ +/* XPM */ +static char * stock_dialog_question_48_xpm[] = { +"48 48 64 1", +" c None", +". c #161715", +"+ c #1B1C1A", +"@ c #272826", +"# c #382621", +"$ c #492A23", +"% c #4E291F", +"& c #333432", +"* c #572A20", +"= c #5B3429", +"- c #423E3C", +"; c #643125", +"> c #573730", +", c #773B2D", +"' c #724136", +") c #4F4F4C", +"! c #7F3C30", +"~ c #664740", +"{ c #6D453C", +"] c #85402F", +"^ c #8C4635", +"/ c #964936", +"( c #5D5F5C", +"_ c #78554C", +": c #6E5953", +"< c #8D4E3F", +"[ c #875244", +"} c #A14F3C", +"| c #6B6B68", +"1 c #AA5341", +"2 c #7F6963", +"3 c #856760", +"4 c #AC5B46", +"5 c #767572", +"6 c #A06558", +"7 c #B1604E", +"8 c #967169", +"9 c #B36855", +"0 c #907B78", +"a c #868582", +"b c #B87262", +"c c #A17F77", +"d c #B67D70", +"e c #969592", +"f c #AE9089", +"g c #AC9693", +"h c #C28E82", +"i c #A3A4A1", +"j c #B79F96", +"k c #C9A59C", +"l c #B4B6B3", +"m c #C8AFA7", +"n c #C2C3C0", +"o c #D7BDB5", +"p c #C9CBC8", +"q c #D1D3D0", +"r c #DECEC9", +"s c #D9DBD8", +"t c #DFE1DE", +"u c #E4E6E3", +"v c #E8EAE7", +"w c #ECEEEB", +"x c #F1F3F0", +"y c #F9FBF8", +" ", +" ", +" ", +" 8__>%*=>>===%___ ", +" 8~=~__666bbb977444^^^,;*=~ ", +" f={'6db94444711111111}1111}/,;%~ ", +" 8>[6bdb4}<]],,]]]^^///}11111111}/,*> ", +" {{6b97/]],]68fgjnnnnnnjmohh1111111}/,* ", +" {[bb4/]][30nquwxyyyxxxxxxxxxxrod11111}/% ", +" _[b9}]'3itxyyyxxxxxyxxxxxxxxxxxxxxob111}/% ", +" >b7},8awyyxxxyxxxxxxxxxxxxxxxxxxwxwxrb1}/! ", +" c_4},5lyxyxxxxxxxyyyyyyyxxxxwxxwwwwwwww71}]~ ", +" _84][axxxxxxxxxxyxl)@++.@sxxwwwwwwwwwwvk11]{ ", +" _d4]8nxxxxxxxxxxya+.+@&@.+lwwwwwwvwvwvvo11/* ", +" 3d4,jpxxxxxwxxxyi&.@(|(&&+@twvvwvwvvvvvo41}% ", +" 3d7,kpxxxwxxwxwy(++(wwwq-++(tvvvvvvvvvwk41^~ ", +" 364,dpwwxxwwwwwy(+.5vwwx(++(qvvvvvvuuvwh11]~ ", +" 8[7]64]6nvvvvvvvvvuuuuwxe).+|itttvttttuv911]> ", +" 3'7/!guvvvvvuuuuvuwxq)+.)etttttttttvo911,: ", +" =b1,cnvuuuuuuuuutxt(&.&eptttttttstvd71/* ", +" =64^[isuuuuttttttxi-.+eittttsttsstr911]> ", +" 2{91,hivtttvtttttx5@.)astssssssssuhb1/;: ", +" %61^[iptttttttttwa+..assssssssstrb71,$ ", +" _{41,fitttttsttsst|@@isssssssssvhb1};) ", +" %61/,}111111111111/;$ ", +" 2*^}11111111},*> ", +" :;^/}11}/]=> ", +" :~*,;$>~ ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_dialog_warning_48.xpm b/src/pixmaps/stock_dialog_warning_48.xpm new file mode 100644 index 00000000..d5b7f02a --- /dev/null +++ b/src/pixmaps/stock_dialog_warning_48.xpm @@ -0,0 +1,83 @@ +/* XPM */ +static char * stock_dialog_warning_48_xpm[] = { +"48 48 32 1", +" c None", +". c #000100", +"+ c #1C1406", +"@ c #211806", +"# c #1B1C1A", +"$ c #2A2A26", +"% c #372A0D", +"& c #3E3E3A", +"* c #533E13", +"= c #565755", +"- c #70561F", +"; c #715F3A", +"> c #7E611A", +", c #717270", +"' c #967122", +") c #967B44", +"! c #8B8D8A", +"~ c #B58A2A", +"{ c #B6944A", +"] c #B09762", +"^ c #CC9C2D", +"/ c #CDA547", +"( c #DEA833", +"_ c #EAB039", +": c #DCBB70", +"< c #F5B939", +"[ c #FABC34", +"} c #F9C44F", +"| c #F9CD6A", +"1 c #FCD177", +"2 c #F8D586", +"3 c #F4D695", +" ", +" ", +" ", +" !=...#= ", +" ,.........#! ", +" .............# ", +" &................ ", +" #......+;{{*.......= ", +" #.....+)221||~-......= ", +" #.....)22}}<<[(^^%.....= ", +" #....+:2|<<<[[<[_^(*.....! ", +" =....+2|<[<[<[<[[[[(~'..... ", +" ,....+:|<<[<}|||<<[<[_~-....# ", +" .....]|<<[[<1322}[<[<[_~*....& ", +" #....)|}<[<[|3;..-<[<[[<^~+....! ", +" !....%2}<[<[<2;....-_<[<[<^'..... ", +" .....:|<[<[[}3......(<<[<[<~*....& ", +" ,....;1[[<[<<|3......^_[<[[<_~+.... ", +" ....$2}<<[<[[|3......~_<[[<[[^'....& ", +" ,....]}<[[[<[<|1......~<[<[<[<<^%.... ", +" ....$1<[<<[[[<|1......^<[<<[[<[_'....& ", +" !....]}[[<[<<[<}1%....%^<[[[<<[<[(%.... ", +" #...@2<<[[[[[<[}|-....-([<<[[<[<[(~....= ", +" ....;|[<[<<<[<[<|~....~(<[[<[<[[<<^%.... ", +" ,....:}<[<[[[<[<<}|+..+^<<[<<[[<<<[('.... ", +" #...%1<[[<<<<[[[[<}>..-^[[<[[[<[[<[(~....= ", +" ....)|[[<[[[[[<<<[<<**^<<<[<[<[<<[<[_*.... ", +" ....:}<[<[<<[<[[[<}}}(<<[[[<[[<[[<[<<'.... ", +",...+2<<[<[[[<[<[<}|222}[<[<[<<[<[[<[<(.... ", +"=...*|[[<[<[<[[<[<|:%.%~<[[<[[[<[<<[<[_*...= ", +"=...)|<<[[<[<[<[<<2%...%^_<[<<<[[[<[[<_*...= ", +"=...{}[[<<[<[<[[<<1.....^_[[[[[<<[<[<[_'...# ", +"=...)}<<[[<[<[<[<<|%...%~<<[<[<[[<[<[[(>.... ", +"=...%}[[<[<[<[[<[[(_<[[<[<<[[<[<[[<[<[[[<[[<[_^''%...., ", +" $.....+-~(<[<[[<[<[[<[[<[<<<[[<_^~>%...... ", +" #.......%>'__<<<<<<<<<<(_((((~>%........, ", +" #..........%*->>~^^^^^''>**...........= ", +" =..................................#= ", +" ,#.............................$=! ", +" =#......................#$=, ", +" &&$..........$$$== ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_down_arrow.xpm b/src/pixmaps/stock_down_arrow.xpm new file mode 100644 index 00000000..e6adb7db --- /dev/null +++ b/src/pixmaps/stock_down_arrow.xpm @@ -0,0 +1,100 @@ +/* XPM */ +static char * stock_down_arrow_xpm[] = { +"24 24 73 1", +" c None", +". c #000000", +"+ c #607C53", +"@ c #688559", +"# c #729063", +"$ c #739364", +"% c #739264", +"& c #BEC2BB", +"* c #BEC2BA", +"= c #BFC3BB", +"- c #BDC1B8", +"; c #BBBFB7", +"> c #B9BEB5", +", c #718E62", +"' c #89A67C", +") c #8BAB7E", +"! c #90AE83", +"~ c #8FAE81", +"{ c #E7E7E7", +"] c #EAE9E9", +"^ c #ECEBEB", +"/ c #E5E4E3", +"( c #D9D9D7", +"_ c #69875B", +": c #8AA67F", +"< c #8DAD7F", +"[ c #8AAC7A", +"} c #8AAD7A", +"| c #F4F0F4", +"1 c #F5F1F5", +"2 c #F2EFF2", +"3 c #EFECEF", +"4 c #E2E1E1", +"5 c #7A956D", +"6 c #8EAA81", +"7 c #8BAD7B", +"8 c #8BAE7B", +"9 c #F6F2F6", +"0 c #F1EDF0", +"a c #ECEAEB", +"b c #859F76", +"c c #90AE81", +"d c #91B182", +"e c #F3EFF2", +"f c #EFECEE", +"g c #E4E3E3", +"h c #759267", +"i c #92AE85", +"j c #95B487", +"k c #F3EFF3", +"l c #F0EEF0", +"m c #EBEAEA", +"n c #010101", +"o c #678458", +"p c #91AA85", +"q c #99B48C", +"r c #F0ECF0", +"s c #EEECEE", +"t c #E3E2E2", +"u c #8CA480", +"v c #9DB591", +"w c #EAE9EA", +"x c #E9E8E8", +"y c #030303", +"z c #6A875B", +"A c #97B08D", +"B c #E2E0E0", +"C c #92AB87", +"D c #DCDDDA", +"E c #060606", +"F c #94A989", +"G c #D6D9D5", +"H c #1D1D1D", +" ", +" ", +" ", +" ", +" ", +" .............. ", +" .+@#$%%&*=-;>. ", +" .,')!~{]^/(. ", +" ._:<[}|1234. ", +" .56789|0a. ", +" .@bcd9efg. ", +" .hijklmn ", +" .opqrst. ", +" .uvwxy ", +" .zAtB. ", +" .CD. ", +" EFGH ", +" .. ", +" .. ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_exec.xpm b/src/pixmaps/stock_exec.xpm new file mode 100644 index 00000000..777b17d6 --- /dev/null +++ b/src/pixmaps/stock_exec.xpm @@ -0,0 +1,107 @@ +/* XPM */ +static char * stock_exec_xpm[] = { +"24 24 80 1", +" c None", +". c #000000", +"+ c #B2A97E", +"@ c #B6AD81", +"# c #7A7356", +"$ c #B0A77C", +"% c #B5AC80", +"& c #BAB184", +"* c #BBB284", +"= c #B6AD80", +"- c #ADA57B", +"; c #B7AE81", +"> c #BDB486", +", c #837C5C", +"' c #B4AC80", +") c #7C7557", +"! c #C0B687", +"~ c #BEB586", +"{ c #B1A87D", +"] c #B9B082", +"^ c #C0B788", +"/ c #AFA67B", +"( c #AFA77C", +"_ c #BAB183", +": c #BBB285", +"< c #BCB385", +"[ c #C4BB8B", +"} c #C6BC8C", +"| c #C7BD8D", +"1 c #C7BE8D", +"2 c #C8BF8E", +"3 c #C8BE8D", +"4 c #C5BC8B", +"5 c #C1B788", +"6 c #B9B083", +"7 c #CDC391", +"8 c #D0C693", +"9 c #D1C794", +"0 c #D3C995", +"a c #CDC491", +"b c #C2B889", +"c c #CAC18F", +"d c #D5CB97", +"e c #D6CC97", +"f c #D6CC98", +"g c #D7CD98", +"h c #CEC491", +"i c #C2B98A", +"j c #CBC18F", +"k c #C1B889", +"l c #8A8261", +"m c #D4CA96", +"n c #C0B787", +"o c #80785A", +"p c #B9B183", +"q c #827C5B", +"r c #CFC592", +"s c #C7BD8C", +"t c #C3BA8A", +"u c #CAC08F", +"v c #D0C793", +"w c #7C7657", +"x c #D2C894", +"y c #D2C995", +"z c #CCC290", +"A c #C4BA8A", +"B c #D1C793", +"C c #CEC592", +"D c #C5BC8C", +"E c #CFC693", +"F c #C1B888", +"G c #BEB486", +"H c #C8BF8D", +"I c #C9C08F", +"J c #C9BF8E", +"K c #C5BB8B", +"L c #B2A97D", +"M c #C2B989", +"N c #BFB687", +"O c #B3AA7E", +" ", +" ", +" ", +" .... ", +" .+.@#. ", +" .$%&*=-. ", +" ..;>,$.. ", +" .-')!~@. ... ", +" ..{]~^.. ./({. . ", +" .#._#._..&:<..@. ", +" . ..>[}|1232456. ", +" .2789009a2. ", +" .bc9defge0h.. ", +" .]ij9ekl1gm83n. ", +" .*ic9dopqgmrs~. ", +" . c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #A9A9A6", +"M c #626260", +"N c #ECEBE4", +"O c #EBEAE3", +"P c #E9E7E0", +"Q c #E8E6DF", +"R c #E7E5DD", +"S c #777671", +"T c #93918C", +"U c #BEBDB8", +"V c #989793", +"W c #ECEAE4", +"X c #E8E6DE", +"Y c #E6E4DB", +"Z c #E4E3DA", +"` c #75746F", +" . c #91908A", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #CCCBC4", +"=. c #A3A29D", +"-. c #B6B5B2", +";. c #BCBCB7", +">. c #CDCCC6", +",. c #959490", +"'. c #ECEBE5", +"). c #61615E", +"!. c #E9E8E0", +"~. c #CECDC7", +"{. c #797875", +"]. c #969590", +"^. c #CFCEC8", +"/. c #AEADA8", +"(. c #585754", +"_. c #7B7A76", +" ", +" ", +" ", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" . A * B ( ; C D E u F G H I J K . ", +" . L / ( _ : M t N O l P Q R S T . ", +" . # _ g U V W 0 l P X R Y Z ` .. ", +" . ; g ..+.@.G x y #.$.%.&.*.=.. . ", +" . -.t ;.F G x y #.K >.,.. . . ", +" . '.).G !.Q ~.H . . . ", +" . {.].^./.. . . ", +" . (._.. . ", +" . . ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_mail_attach.xpm b/src/pixmaps/stock_mail_attach.xpm new file mode 100644 index 00000000..dc14e6d3 --- /dev/null +++ b/src/pixmaps/stock_mail_attach.xpm @@ -0,0 +1,134 @@ +/* XPM */ +static char * stock_mail_attach_xpm[] = { +"24 24 107 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F6F6F3", +"{ c #F5F5F2", +"] c #F2F1ED", +"^ c #F1F0EC", +"/ c #F0EFEA", +"( c #EFEEE9", +"_ c #4F4F4D", +": c #BDBCB8", +"< c #A7A6A3", +"[ c #C6C6C4", +"} c #7C7C7B", +"| c #525251", +"1 c #F5F4F1", +"2 c #F4F3F0", +"3 c #999894", +"4 c #62625F", +"5 c #BCBCB6", +"6 c #EBE9E3", +"7 c #838381", +"8 c #D7D6D4", +"9 c #A8A8A6", +"0 c #7E7D7C", +"a c #DAD9D5", +"b c #EFEEE8", +"c c #EEEDE7", +"d c #4F4E4C", +"e c #BCBBB6", +"f c #EBE9E2", +"g c #EAE8E1", +"h c #F6F5F2", +"i c #B5B4B1", +"j c #9B9A97", +"k c #92918E", +"l c #EEEDE8", +"m c #EDECE6", +"n c #4E4E4C", +"o c #797976", +"p c #797874", +"q c #E9E8E1", +"r c #E8E7DF", +"s c #B4B3AF", +"t c #D0D0CD", +"u c #BEBDB9", +"v c #7A7A77", +"w c #979691", +"x c #EAE9E2", +"y c #959590", +"z c #787773", +"A c #B8B7B0", +"B c #E6E4DC", +"C c #A9A9A6", +"D c #626260", +"E c #ECEBE4", +"F c #EBEAE3", +"G c #E9E7E0", +"H c #E8E6DF", +"I c #E7E5DD", +"J c #777671", +"K c #93918C", +"L c #BEBDB8", +"M c #989793", +"N c #ECEAE4", +"O c #E8E6DE", +"P c #E6E4DB", +"Q c #E4E3DA", +"R c #75746F", +"S c #91908A", +"T c #EEECE7", +"U c #62615F", +"V c #EBEAE4", +"W c #E7E6DE", +"X c #E6E5DC", +"Y c #E5E4DB", +"Z c #E4E2DA", +"` c #CCCBC4", +" . c #A3A29D", +".. c #B6B5B2", +"+. c #BCBCB7", +"@. c #CDCCC6", +"#. c #959490", +"$. c #ECEBE5", +"%. c #61615E", +"&. c #E9E8E0", +"*. c #CECDC7", +"=. c #797875", +"-. c #969590", +";. c #CFCEC8", +">. c #AEADA8", +",. c #585754", +"'. c #7B7A76", +" ", +" . . . . ", +" . . . ", +" . . . . ", +" . . . . ", +" . . . . . . . . ", +" . . . . . . . + @ # $ . ", +" . . . . . % . & * = - ; > , . ", +" . ' ) ! . ~ { . * ] ^ / ( _ : < . ", +" . [ } | . 1 2 . ] # / ( 3 4 5 6 . ", +" . 7 8 9 . 0 a . # / b c d e f g . ", +" . h 1 . i j . k l m n o p q r s . ", +" . t * . ^ ; . u v n w x y z A B . ", +" . C ] ^ . . D m E F g G H I J K . ", +" . # / b L M N 6 g G O I P Q R S . ", +" . ; b T U V x q r W X Y Z ` .. . ", +" . ..m +.w x q r W B @.#.. . . ", +" . $.%.x &.H *.y . . . ", +" . =.-.;.>.. . . ", +" . ,.'.. . ", +" . . ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_mail_compose.xpm b/src/pixmaps/stock_mail_compose.xpm new file mode 100644 index 00000000..77da55df --- /dev/null +++ b/src/pixmaps/stock_mail_compose.xpm @@ -0,0 +1,144 @@ +/* XPM */ +static char * stock_mail_compose_xpm[] = { +"24 24 117 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #D8BE6A", +"M c #A9A9A6", +"N c #626260", +"O c #ECEBE4", +"P c #EBEAE3", +"Q c #E9E7E0", +"R c #E8E6DF", +"S c #E7E5DD", +"T c #777671", +"U c #93918C", +"V c #8E7D45", +"W c #BEBDB8", +"X c #989793", +"Y c #ECEAE4", +"Z c #E8E6DE", +"` c #E6E4DB", +" . c #E4E3DA", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #B6B5B2", +"=. c #BCBCB7", +"-. c #CDCCC6", +";. c #959490", +">. c #ECEBE5", +",. c #61615E", +"'. c #E9E8E0", +"). c #CECDC7", +"!. c #797875", +"~. c #969590", +"{. c #CFCEC8", +"]. c #AEADA8", +"^. c #585754", +"/. c #7B7A76", +"(. c #AD8E30", +"_. c #756020", +":. c #060605", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . . ", +" . A * B ( ; C D E u F G H I J K . . L . ", +" . M / ( _ : N t O P l Q R S T U . L V . ", +" . # _ g W X Y 0 l Q Z S ` .. L V . ", +" . ; g ..+.@.G x y #.$.%.&.. L V . ", +" . *.t =.F G x y #.K -.;.. L V . ", +" . >.,.G '.R ).H . . . L V . ", +" . !.~.{.].. . . . L V . ", +" . ^./.. . . L V . ", +" . . . L V . ", +" (._.. . ", +" :.. ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_mail_forward.xpm b/src/pixmaps/stock_mail_forward.xpm new file mode 100644 index 00000000..5d12c8f7 --- /dev/null +++ b/src/pixmaps/stock_mail_forward.xpm @@ -0,0 +1,153 @@ +/* XPM */ +static char * stock_mail_forward_xpm[] = { +"24 24 126 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #A9A9A6", +"M c #626260", +"N c #ECEBE4", +"O c #EBEAE3", +"P c #E9E7E0", +"Q c #E8E6DF", +"R c #E7E5DD", +"S c #777671", +"T c #93918C", +"U c #BEBDB8", +"V c #989793", +"W c #ECEAE4", +"X c #E8E6DE", +"Y c #E6E4DB", +"Z c #E4E3DA", +"` c #75746F", +" . c #91908A", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #CCCBC4", +"=. c #A3A29D", +"-. c #B6B5B2", +";. c #BCBCB7", +">. c #CDCCC6", +",. c #959490", +"'. c #ECEBE5", +"). c #61615E", +"!. c #E9E8E0", +"~. c #CECDC7", +"{. c #797875", +"]. c #969590", +"^. c #CFCEC8", +"/. c #AEADA8", +"(. c #585754", +"_. c #7B7A76", +":. c #B39C82", +"<. c #BCA488", +"[. c #746554", +"}. c #7D6D5B", +"|. c #867561", +"1. c #8F7D68", +"2. c #98856E", +"3. c #A18D75", +"4. c #AA947B", +"5. c #C5AC8F", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" . A * B ( ; C D E u F G H I J K . ", +" . L / ( _ : M t N O l P Q R S T . ", +" . # _ g U V W 0 l P X R Y Z ` .. ", +" . ; g ..+.@.G x y #.$.%.&.*.=.. . ", +" . -.t ;.F G x y #.K >.,.. . . ", +" . '.).G !.Q ~.H . . . ", +" . {.].^./.. . . ", +" . (._.. . . . ", +" . . . :.. ", +" . . . . . . . . :.<.. ", +" . [.}.|.1.2.3.4.:.<.5.. ", +" . [.}.|.1.2.3.4.:.<.5.. ", +" . . . . . . . . :.<.. ", +" . :.. ", +" . . "}; diff --git a/src/pixmaps/stock_mail_receive.xpm b/src/pixmaps/stock_mail_receive.xpm new file mode 100644 index 00000000..5ad326eb --- /dev/null +++ b/src/pixmaps/stock_mail_receive.xpm @@ -0,0 +1,175 @@ +/* XPM */ +static char * stock_mail_receive_xpm[] = { +"24 24 148 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #A9A9A6", +"M c #626260", +"N c #ECEBE4", +"O c #EBEAE3", +"P c #E9E7E0", +"Q c #E8E6DF", +"R c #E7E5DD", +"S c #777671", +"T c #93918C", +"U c #BEBDB8", +"V c #989793", +"W c #ECEAE4", +"X c #E8E6DE", +"Y c #E6E4DB", +"Z c #E4E3DA", +"` c #75746F", +" . c #91908A", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #CCCBC4", +"=. c #A3A29D", +"-. c #B6B5B2", +";. c #BCBCB7", +">. c #CDCCC6", +",. c #959490", +"'. c #ECEBE5", +"). c #61615E", +"!. c #E9E8E0", +"~. c #CECDC7", +"{. c #7C6D5A", +"]. c #797875", +"^. c #969590", +"/. c #CFCEC8", +"(. c #AEADA8", +"_. c #8A7A65", +":. c #8F7D68", +"<. c #93816B", +"[. c #585754", +"}. c #7B7A76", +"|. c #9D8A72", +"1. c #A18E76", +"2. c #A69179", +"3. c #AB977D", +"4. c #B09A80", +"5. c #B49E83", +"6. c #B8A286", +"7. c #BDA689", +"8. c #BEA78A", +"9. c #C2AB8E", +"0. c #C7AE91", +"a. c #CBB294", +"b. c #CFB697", +"c. c #8F7E68", +"d. c #CCB395", +"e. c #D1B798", +"f. c #D5BB9B", +"g. c #D9BF9E", +"h. c #DEC3A1", +"i. c #E2C6A5", +"j. c #E6CAA8", +"k. c #A79379", +"l. c #897963", +"m. c #E8CBA9", +"n. c #ECCFAC", +"o. c #F0D3AF", +"p. c #93826B", +"q. c #D7BC9C", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" . A * B ( ; C D E u F G H I J K . ", +" . L / ( _ : M t N O l P Q R S T . ", +" . # _ g U V W 0 l P X R Y Z ` .. ", +" . ; g ..+.@.G x y #.$.%.&.*.=.. . ", +" . -.t ;.F G x y #.K >.,.. . . ", +" . '.).G !.Q ~.H . . . . {.. ", +" . ].^./.(.. . . . _.:.<.. ", +" . [.}.. . . |.1.2.. ", +" . . . 3.4.5.6.7.. ", +" . . . 8.9.0.a.b.. . . ", +" . c.d.e.f.g.h.i.j.k.. ", +" . . l.m.n.o.p.. . ", +" . . q.. . ", +" . ", +" "}; diff --git a/src/pixmaps/stock_mail_receive_all.xpm b/src/pixmaps/stock_mail_receive_all.xpm new file mode 100644 index 00000000..c74d97bc --- /dev/null +++ b/src/pixmaps/stock_mail_receive_all.xpm @@ -0,0 +1,181 @@ +/* XPM */ +static char * stock_mail_receive_all_xpm[] = { +"24 24 154 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #A9A9A6", +"M c #626260", +"N c #ECEBE4", +"O c #EBEAE3", +"P c #E9E7E0", +"Q c #E8E6DF", +"R c #E7E5DD", +"S c #777671", +"T c #93918C", +"U c #BEBDB8", +"V c #989793", +"W c #ECEAE4", +"X c #E8E6DE", +"Y c #E6E4DB", +"Z c #E4E3DA", +"` c #75746F", +" . c #91908A", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #CCCBC4", +"=. c #A3A29D", +"-. c #B6B5B2", +";. c #0C0C0B", +">. c #090909", +",. c #070707", +"'. c #CDCCC6", +"). c #959490", +"!. c #0C0B0B", +"~. c #71685B", +"{. c #0B0B0B", +"]. c #E9E8E0", +"^. c #CECDC7", +"/. c #7C6D5A", +"(. c #897965", +"_. c #8F7E6A", +":. c #96846F", +"<. c #403F3D", +"[. c #8A7A65", +"}. c #8F7D68", +"|. c #93816B", +"1. c #998770", +"2. c #9F8C76", +"3. c #9D8972", +"4. c #9D8A72", +"5. c #A18E76", +"6. c #A69179", +"7. c #AB977D", +"8. c #A79279", +"9. c #AA957C", +"0. c #B8A286", +"a. c #BDA689", +"b. c #B09A80", +"c. c #B49E83", +"d. c #BEA78A", +"e. c #C2AB8E", +"f. c #C7AE91", +"g. c #CBB294", +"h. c #CFB697", +"i. c #8F7E68", +"j. c #CCB395", +"k. c #D1B798", +"l. c #D5BB9B", +"m. c #D9BF9E", +"n. c #DEC3A1", +"o. c #E2C6A5", +"p. c #E6CAA8", +"q. c #A79379", +"r. c #897963", +"s. c #E8CBA9", +"t. c #ECCFAC", +"u. c #F0D3AF", +"v. c #93826B", +"w. c #D7BC9C", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" . A * B ( ; C D E u F G H I J K . ", +" . L / ( _ : M t N O l P Q R S T . ", +" . # _ g U V W 0 l P X R Y Z ` .. ", +" . ; g ..+.@.G x y #.$.%.&.*.=.. . ", +" . -.;.>.,.G x y #.K '.).. . . ", +" . !.~.{.].Q ^.H . . . . /.. ", +" . (._.:.<.. . . . [.}.|.. ", +" . 1.2.3.. . 4.5.6.. ", +" . 7.8.9.0.a.. . 7.b.c.0.a.. ", +" . . . d.e.f.g.h.. . . . . d.e.f.g.h.. . . ", +" . i.j.k.l.m.n.o.p.q.. i.j.k.l.m.n.o.p.q.. ", +" . . r.s.t.u.v.. . . . r.s.t.u.v.. . ", +" . . w.. . . . w.. . ", +" . . ", +" "}; diff --git a/src/pixmaps/stock_mail_reply.xpm b/src/pixmaps/stock_mail_reply.xpm new file mode 100644 index 00000000..94905b81 --- /dev/null +++ b/src/pixmaps/stock_mail_reply.xpm @@ -0,0 +1,154 @@ +/* XPM */ +static char * stock_mail_reply_xpm[] = { +"24 24 127 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #A9A9A6", +"M c #626260", +"N c #ECEBE4", +"O c #EBEAE3", +"P c #E9E7E0", +"Q c #E8E6DF", +"R c #E7E5DD", +"S c #777671", +"T c #93918C", +"U c #BEBDB8", +"V c #989793", +"W c #ECEAE4", +"X c #E8E6DE", +"Y c #E6E4DB", +"Z c #E4E3DA", +"` c #75746F", +" . c #91908A", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #CCCBC4", +"=. c #A3A29D", +"-. c #B6B5B2", +";. c #BCBCB7", +">. c #CDCCC6", +",. c #959490", +"'. c #ECEBE5", +"). c #61615E", +"!. c #E9E8E0", +"~. c #CECDC7", +"{. c #797875", +"]. c #969590", +"^. c #CFCEC8", +"/. c #AEADA8", +"(. c #E0D8B0", +"_. c #585754", +":. c #7B7A76", +"<. c #B39C82", +"[. c #BCA488", +"}. c #C5AC8F", +"|. c #AA947B", +"1. c #A18D75", +"2. c #98856E", +"3. c #8F7D68", +"4. c #867561", +"5. c #7D6D5B", +"6. c #746554", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" . A * B ( ; C D E u F G H I J K . ", +" . L / ( _ : M t N O l P Q R S T . ", +" . # _ g U V W 0 l P X R Y Z ` .. ", +" . ; g ..+.@.G x y #.$.%.&.*.=.. . ", +" . -.t ;.F G x y #.K >.,.. . . ", +" . '.).G !.Q ~.H . . . . ", +" . {.].^./.. . . . (.. ", +" . _.:.. . . . . (.(.(.. ", +" . . . <.. . (.(.(.. ", +" . [.<.. . . . . . . . ", +" . }.[.<.|.1.2.3.4.5.6.. ", +" . }.[.<.|.1.2.3.4.5.. ", +" . [.<.. . . . . . ", +" . <.. ", +" . . "}; diff --git a/src/pixmaps/stock_mail_reply_to_all.xpm b/src/pixmaps/stock_mail_reply_to_all.xpm new file mode 100644 index 00000000..6086a860 --- /dev/null +++ b/src/pixmaps/stock_mail_reply_to_all.xpm @@ -0,0 +1,126 @@ +/* XPM */ +static char * stock_mail_reply_to_all_xpm[] = { +"24 24 99 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #E0D8B0", +"b c #838381", +"c c #D7D6D4", +"d c #A8A8A6", +"e c #515150", +"f c #7E7D7C", +"g c #DAD9D5", +"h c #EFEEE8", +"i c #EEEDE7", +"j c #EBE9E2", +"k c #F6F5F2", +"l c #F4F3EF", +"m c #B5B4B1", +"n c #9B9A97", +"o c #646361", +"p c #92918E", +"q c #EEEDE8", +"r c #EDECE6", +"s c #B39C82", +"t c #E9E8E1", +"u c #E8E7DF", +"v c #D0D0CD", +"w c #F2F2ED", +"x c #BFBEBA", +"y c #BEBDB9", +"z c #7A7A77", +"A c #BCA488", +"B c #A9A9A6", +"C c #626260", +"D c #C5AC8F", +"E c #AA947B", +"F c #A18D75", +"G c #98856E", +"H c #8F7D68", +"I c #867561", +"J c #7D6D5B", +"K c #746554", +"L c #BEBDB8", +"M c #989793", +"N c #ECEAE4", +"O c #EEECE7", +"P c #62615F", +"Q c #EBEAE4", +"R c #EAE9E2", +"S c #B6B5B2", +"T c #BCBCB7", +"U c #979691", +"V c #E7E6DE", +"W c #ECEBE5", +"X c #61615E", +"Y c #E9E8E0", +"Z c #E8E6DF", +"` c #CECDC7", +" . c #959590", +".. c #797875", +"+. c #969590", +"@. c #CFCEC8", +"#. c #AEADA8", +"$. c #585754", +"%. c #7B7A76", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . a . ", +" . b c d e f g / # _ h i . . j . a a a . ", +" . k 4 l m n o p q r . s . t u . a a a . ", +" . v * w ( ; x y z . A s . . . . . . . . ", +" . B / ( _ : C r . D A s E F G H I J K . ", +" . # _ h L M N . D A s E F G H I J . ", +" . ; h O P Q R t . A s . . . . . . ", +" . S r T U R t u V . s . . . . ", +" . W X R Y Z ` .. . . . ", +" . ..+.@.#.. . . . a . ", +" . $.%.. . . . . a a a . ", +" . . . s . . a a a . ", +" . A s . . . . . . . . ", +" . D A s E F G H I J K . ", +" . D A s E F G H I J . ", +" . A s . . . . . . ", +" . s . ", +" . . "}; diff --git a/src/pixmaps/stock_mail_send.xpm b/src/pixmaps/stock_mail_send.xpm new file mode 100644 index 00000000..f53bfa8e --- /dev/null +++ b/src/pixmaps/stock_mail_send.xpm @@ -0,0 +1,162 @@ +/* XPM */ +static char * stock_mail_send_xpm[] = { +"24 24 135 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #D0D0CD", +"B c #F2F2ED", +"C c #BFBEBA", +"D c #BEBDB9", +"E c #7A7A77", +"F c #979691", +"G c #EAE9E2", +"H c #959590", +"I c #787773", +"J c #B8B7B0", +"K c #E6E4DC", +"L c #A9A9A6", +"M c #626260", +"N c #ECEBE4", +"O c #EBEAE3", +"P c #E9E7E0", +"Q c #E8E6DF", +"R c #E7E5DD", +"S c #777671", +"T c #93918C", +"U c #BEBDB8", +"V c #989793", +"W c #ECEAE4", +"X c #E8E6DE", +"Y c #E6E4DB", +"Z c #E4E3DA", +"` c #75746F", +" . c #91908A", +".. c #EEECE7", +"+. c #62615F", +"@. c #EBEAE4", +"#. c #E7E6DE", +"$. c #E6E5DC", +"%. c #E5E4DB", +"&. c #E4E2DA", +"*. c #CCCBC4", +"=. c #A3A29D", +"-. c #B6B5B2", +";. c #BCBCB7", +">. c #CDCCC6", +",. c #959490", +"'. c #ECEBE5", +"). c #61615E", +"!. c #E9E8E0", +"~. c #CECDC7", +"{. c #797875", +"]. c #969590", +"^. c #CFCEC8", +"/. c #AEADA8", +"(. c #282828", +"_. c #6D675E", +":. c #786F64", +"<. c #585754", +"[. c #7B7A76", +"}. c #736A5B", +"|. c #908570", +"1. c #928875", +"2. c #665C4E", +"3. c #8C816F", +"4. c #9B907F", +"5. c #0F0E0B", +"6. c #AB9A87", +"7. c #A8967F", +"8. c #998873", +"9. c #B6A38A", +"0. c #8C7C69", +"a. c #C8B298", +"b. c #B7A38A", +"c. c #DAC2A5", +"d. c #B49E84", +" ", +" ", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" . A * B ( ; C D E u F G H I J K . ", +" . L / ( _ : M t N O l P Q R S T . ", +" . # _ g U V W 0 l P X R Y Z ` .. ", +" . ; g ..+.@.G x y #.$.%.&.*.=.. . ", +" . -.t ;.F G x y #.K >.,.. . . ", +" . '.).G !.Q ~.H . . . . . ", +" . {.].^./.. . . (._.:.. ", +" . <.[.. . . }.|.1.2.. ", +" . . . . . 3.4.. 5.. ", +" . 6.7.. ", +" . 8.9.9.0.. ", +" . a.a.a.a.. ", +" . b.c.c.c.c.d.. ", +" . . . . . . . . ", +" "}; diff --git a/src/pixmaps/stock_mail_send_queue.xpm b/src/pixmaps/stock_mail_send_queue.xpm new file mode 100644 index 00000000..97e40138 --- /dev/null +++ b/src/pixmaps/stock_mail_send_queue.xpm @@ -0,0 +1,244 @@ +/* XPM */ +static char * stock_mail_send_queue_xpm[] = { +"24 24 217 2", +" c None", +". c #000000", +"+ c #7B7B78", +"@ c #C7C7C3", +"# c #F1F0EB", +"$ c #BFBFBC", +"% c #5F5E5E", +"& c #E3E3DF", +"* c #F3F3EF", +"= c #F2F2EE", +"- c #F1F1EC", +"; c #F0EFEB", +"> c #D2D2CD", +", c #969592", +"' c #616060", +") c #B5B5B4", +"! c #F8F8F6", +"~ c #F7F7F5", +"{ c #F6F6F3", +"] c #F5F5F2", +"^ c #F4F4F0", +"/ c #F2F1ED", +"( c #F1F0EC", +"_ c #F0EFEA", +": c #EFEEE9", +"< c #4F4F4D", +"[ c #BDBCB8", +"} c #A7A6A3", +"| c #C6C6C4", +"1 c #7C7C7B", +"2 c #525251", +"3 c #DBDBD9", +"4 c #F5F4F1", +"5 c #F4F3F0", +"6 c #F3F2EE", +"7 c #999894", +"8 c #62625F", +"9 c #BCBCB6", +"0 c #EBE9E3", +"a c #838381", +"b c #D7D6D4", +"c c #A8A8A6", +"d c #515150", +"e c #7E7D7C", +"f c #DAD9D5", +"g c #EFEEE8", +"h c #EEEDE7", +"i c #4F4E4C", +"j c #BCBBB6", +"k c #EBE9E2", +"l c #EAE8E1", +"m c #F6F5F2", +"n c #F4F3EF", +"o c #B5B4B1", +"p c #9B9A97", +"q c #646361", +"r c #92918E", +"s c #EEEDE8", +"t c #EDECE6", +"u c #4E4E4C", +"v c #797976", +"w c #797874", +"x c #E9E8E1", +"y c #E8E7DF", +"z c #B4B3AF", +"A c #3F3F3F", +"B c #0E0E0E", +"C c #1E1E1D", +"D c #6B6B69", +"E c #BFBEBA", +"F c #BEBDB9", +"G c #7A7A77", +"H c #979691", +"I c #EAE9E2", +"J c #959590", +"K c #787773", +"L c #B8B7B0", +"M c #E6E4DC", +"N c #101010", +"O c #D5D5D5", +"P c #FFFFFF", +"Q c #B1B1B1", +"R c #1D1D1D", +"S c #626260", +"T c #ECEBE4", +"U c #EBEAE3", +"V c #E9E7E0", +"W c #E8E6DF", +"X c #E7E5DD", +"Y c #777671", +"Z c #93918C", +"` c #ECEAE4", +" . c #E8E6DE", +".. c #E6E4DB", +"+. c #E4E3DA", +"@. c #75746F", +"#. c #91908A", +"$. c #EBEBE9", +"%. c #B4B5B0", +"&. c #A0A29B", +"*. c #9FA099", +"=. c #AAACA5", +"-. c #C6C7C2", +";. c #F5F6F4", +">. c #E7E6DE", +",. c #E6E5DC", +"'. c #E5E4DB", +"). c #E4E2DA", +"!. c #CCCBC4", +"~. c #A3A29D", +"{. c #D1D2CF", +"]. c #979890", +"^. c #9FA199", +"/. c #A8A9A1", +"(. c #ADAFA6", +"_. c #B2B3AB", +":. c #B6B8B0", +"<. c #BABCB4", +"[. c #11110F", +"}. c #1D1D1C", +"|. c #CDCCC6", +"1. c #959490", +"2. c #ECECEA", +"3. c #989991", +"4. c #A1A39B", +"5. c #B0B1A9", +"6. c #BBBCB5", +"7. c #BFC0B8", +"8. c #C2C4BC", +"9. c #C1C2BA", +"0. c #D1D1D0", +"a. c #DEDEDE", +"b. c #9C9E96", +"c. c #C5C7BF", +"d. c #CBCCC5", +"e. c #CDCEC6", +"f. c #C5C7BE", +"g. c #DEDFDA", +"h. c #939393", +"i. c #2F2F2F", +"j. c #282828", +"k. c #6D675E", +"l. c #786F64", +"m. c #9E9F98", +"n. c #A1A29A", +"o. c #B5B7AF", +"p. c #CFD1C8", +"q. c #D0D2CA", +"r. c #CCCEC6", +"s. c #D1D3CB", +"t. c #736A5B", +"u. c #908570", +"v. c #928875", +"w. c #665C4E", +"x. c #9A9B93", +"y. c #A6A8A0", +"z. c #C8CAC1", +"A. c #D6D7D0", +"B. c #D5D7CF", +"C. c #D4D5CD", +"D. c #D3D5CD", +"E. c #8C816F", +"F. c #9B907F", +"G. c #0F0E0B", +"H. c #A5A69F", +"I. c #A8A9A2", +"J. c #BABBB3", +"K. c #C9CAC2", +"L. c #94958E", +"M. c #D6D8D0", +"N. c #D7D9D1", +"O. c #DBDCD5", +"P. c #AB9A87", +"Q. c #A8967F", +"R. c #BFC0BB", +"S. c #A8AAA2", +"T. c #C4C5BD", +"U. c #B0B2AA", +"V. c #D7D8D0", +"W. c #E6E7E2", +"X. c #998873", +"Y. c #B6A38A", +"Z. c #8C7C69", +"`. c #F2F2F1", +" + c #ADAEA6", +".+ c #B1B3AB", +"++ c #BCBDB5", +"@+ c #C6C8C0", +"#+ c #CFD0C9", +"$+ c #D4D6CE", +"%+ c #D5D6CE", +"&+ c #D6D7CF", +"*+ c #C8B298", +"=+ c #E4E4E1", +"-+ c #B3B4AC", +";+ c #B7B8B0", +">+ c #BCBEB6", +",+ c #C4C6BE", +"'+ c #C9CBC3", +")+ c #CBCCC4", +"!+ c #D0D1C9", +"~+ c #D4D5D2", +"{+ c #B7A38A", +"]+ c #DAC2A5", +"^+ c #B49E84", +"/+ c #B9B9B9", +"(+ c #F5F5F4", +"_+ c #CFD0CB", +":+ c #C0C2BB", +"<+ c #C0C2BA", +"[+ c #C8C9C2", +"}+ c #D9DAD5", +"|+ c #D9D9D8", +"1+ c #D3D3D3", +"2+ c #0D0D0D", +"3+ c #7A7A7A", +"4+ c #343434", +" . . . . ", +" . . . . . + @ # $ . ", +" . . . . . % $ & * = - ; > , . ", +" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ", +" . | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ", +" . a b c d e f / # _ g h i j k l . ", +" . m 4 n o p q r s t u v w x y z . ", +" A B . C D ( ; E F G u H I J K L M . ", +" N . O P P O Q . R S t T U l V W X Y Z . ", +" . O P P P P P P O Q . ` 0 l V .X ..+.@.#.. ", +" . O P $.%.&.*.=.-.;.O O . x y >.,.'.).!.~.. . ", +"N Q P {.].^./.(._.:.<.[.O Q }.>.M |.1.. . . ", +". O 2.3.4.5.6.7.8.9.. . 0.O . J . . . . . ", +"Q a.%.b.. . c.d.e.. . f.g.O h.i. j.k.l.. ", +"O P m.n.o.. . p.. . q.r.s.O h.N . t.u.v.w.. ", +"a.P x.y.6.z.. . . A.B.C.D.O h.. . . . E.F.. G.. ", +"O P H.I.J.K.L.. 3.M.N.N.O.O h.N . P.Q.. ", +"Q a.R.S.o.T.r.U.V.M.N.N.W.O h.A . X.Y.Y.Z.. ", +". O `. +.+++@+#+$+%+&+N.0.O . . *+*+*+*+. ", +"N Q a.=+-+;+>+,+'+)+!+~+O Q N . {+]+]+]+]+^+. ", +" . /+a.(+_+:+<+[+}+|+O Q . . . . . . . . . ", +" 1+. /+a.a.P P a.O Q h.. ", +" 2+. Q /+/+Q 3+. N ", +" 4+2+. . A "}; diff --git a/src/pixmaps/stock_paste.xpm b/src/pixmaps/stock_paste.xpm new file mode 100644 index 00000000..81072cf8 --- /dev/null +++ b/src/pixmaps/stock_paste.xpm @@ -0,0 +1,132 @@ +/* XPM */ +static char * stock_paste_xpm[] = { +"24 24 105 2", +" c None", +". c #000000", +"+ c #CFCDBE", +"@ c #CFCCBD", +"# c #CFCBBC", +"$ c #D0CFBE", +"% c #CFCEBE", +"& c #CECABB", +"* c #D1CFBE", +"= c #7E7E74", +"- c #2F2F2B", +"; c #595855", +"> c #A09F9C", +", c #CCC9BB", +"' c #595956", +") c #DDDDDA", +"! c #ACABA5", +"~ c #7E7C72", +"{ c #3E3C38", +"] c #CDC9BB", +"^ c #CCC9BA", +"/ c #4D4D4B", +"( c #7E7C73", +"_ c #CDCABB", +": c #CBC9BA", +"< c #CAC8B9", +"[ c #73736E", +"} c #4D4C46", +"| c #A4A195", +"1 c #CAC7B8", +"2 c #CFCDBD", +"3 c #CFCCBC", +"4 c #CFCBBB", +"5 c #CAC9B9", +"6 c #CAC8B8", +"7 c #C9C6B8", +"8 c #D1D1BC", +"9 c #919182", +"0 c #CBC9B9", +"a c #272727", +"b c #AEAEA8", +"c c #B7B7A5", +"d c #D4D4BE", +"e c #D8D8C2", +"f c #EAEAE2", +"g c #707070", +"h c #B3B3AC", +"i c #D7D7C1", +"j c #DBDBC4", +"k c #ACAC9B", +"l c #DDDDD9", +"m c #42423A", +"n c #A5A595", +"o c #DADAC3", +"p c #DEDEC7", +"q c #E2E2CA", +"r c #C9C7B8", +"s c #C9C5B6", +"t c #616156", +"u c #A5A594", +"v c #D9D9C3", +"w c #DDDDC6", +"x c #E1E1C9", +"y c #E4E4CD", +"z c #B4B4A1", +"A c #C7C5B5", +"B c #C5C1B1", +"C c #C0BEAB", +"D c #BBBBA7", +"E c #DCDCC5", +"F c #E0E0C9", +"G c #E3E3CC", +"H c #E7E7CF", +"I c #EBEBD3", +"J c #A3A391", +"K c #C3C0AF", +"L c #C3BFAE", +"M c #C2BEAE", +"N c #C1BDAC", +"O c #BAB8A5", +"P c #929284", +"Q c #DFDFC8", +"R c #E2E2CB", +"S c #E6E6CF", +"T c #EAEAD2", +"U c #EEEED5", +"V c #F2F2D9", +"W c #C0BBAA", +"X c #BCB9A6", +"Y c #B8B4A1", +"Z c #B7B3A0", +"` c #E5E5CE", +" . c #E9E9D1", +".. c #EDEDD5", +"+. c #F1F1D8", +"@. c #F5F5DC", +"#. c #A7A796", +"$. c #B9B5A1", +"%. c #B8B4A0", +"&. c #AEAE9C", +"*. c #E8E8D0", +"=. c #ECECD4", +"-. c #F0F0D7", +";. c #A6A696", +">. c #9F9F8F", +" ", +" ", +" ", +" . . . ", +" . . + @ # . . ", +" . . $ % @ # & . . . . . . ", +" . . * $ = - ; > & , . . . . . . ", +" . * * * ' ) ! ~ { ] ^ . . ", +" . * * $ / ! ( { _ , : < . ", +" . $ % [ } | _ , : < 1 . . . ", +" . % 2 3 4 & ] : 5 6 7 . . 8 9 . ", +" . @ # & _ ^ 0 6 . a b c d e . ", +" . # & _ , : < 1 . f g h i j k . ", +" . & , : 5 1 7 . l m n o p q . ", +" . ] ^ 5 6 r s . t u v w x y z . ", +" . : 1 A B C . D e E F G H I J . ", +" . K L M N O . P j Q R S T U V . ", +" . W X Y Z . . p q ` ...+.@.#.. ", +" . $.%.. . . &.y *.=.-.;.. . ", +" . . . H I J . . ", +" . >.. . ", +" . ", +" ", +" "}; diff --git a/src/pixmaps/stock_preferences.xpm b/src/pixmaps/stock_preferences.xpm new file mode 100644 index 00000000..d68760dc --- /dev/null +++ b/src/pixmaps/stock_preferences.xpm @@ -0,0 +1,80 @@ +/* XPM */ +static char * stock_preferences_xpm[] = { +"24 24 53 1", +" c None", +". c #000000", +"+ c #E5E5E5", +"@ c #CECECE", +"# c #CDCDCD", +"$ c #DCCB94", +"% c #DCDCDC", +"& c #C7C7C7", +"* c #8E7D45", +"= c #CFCFCF", +"- c #C1C1C1", +"; c #AB5959", +"> c #D9D9D9", +", c #878787", +"' c #DBDBDB", +") c #D4D4D4", +"! c #A84F4F", +"~ c #A7A7A7", +"{ c #9B9B9B", +"] c #F5F5F5", +"^ c #F3F3F3", +"/ c #A65757", +"( c #6E6E6E", +"_ c #DACACA", +": c #AC5757", +"< c #C48B8B", +"[ c #AA6C6C", +"} c #E9DDDD", +"| c #A65353", +"1 c #C6C6C6", +"2 c #D4C38D", +"3 c #E4E4E4", +"4 c #D6D6D0", +"5 c #C0C0BB", +"6 c #828279", +"7 c #AD8E30", +"8 c #756020", +"9 c #C5C5BF", +"0 c #A7A79A", +"a c #818174", +"b c #DFE1E1", +"c c #D6E1E0", +"d c #060605", +"e c #A3A395", +"f c #C8C8C8", +"g c #8B8B7D", +"h c #7E7E71", +"i c #B2B9B6", +"j c #D9D9D2", +"k c #D9D9D3", +"l c #DADAD4", +"m c #DADAD9", +"n c #DADADA", +" ", +" ", +" ", +" .. ", +" ..+@. . ", +" ..#+++. .$. ", +" ...%+++++&. .$*. ", +" ..#++++=+-++. .$*. ", +" .%++++;+>+=,+'.$*. ", +" .)++++!~+{,++.$*. ", +" .++]^/+(+++.$*. ", +" .%_:<[++++.$*.>. ", +" .+}|/+++.$*.=+1. ", +" .)+++++.2*.>3456. ", +" .+++]78..90000a. ", +" .%+bcd.ee0000f. ", +" .+0ghijkl+m.. ", +" .)+++++++f. ", +" .++++++.. ", +" .%+++f. ", +" .+n.. ", +" .. ", +" ", +" "}; diff --git a/src/pixmaps/stock_properties.xpm b/src/pixmaps/stock_properties.xpm new file mode 100644 index 00000000..c13d7ae8 --- /dev/null +++ b/src/pixmaps/stock_properties.xpm @@ -0,0 +1,140 @@ +/* XPM */ +static char * stock_properties_xpm[] = { +"24 24 113 2", +" c None", +". c #000000", +"+ c #ADAD9C", +"@ c #959585", +"# c #DCDCC5", +"$ c #DFDFC8", +"% c #242424", +"& c #A7A796", +"* c #D7D7C1", +"= c #D9D9C3", +"- c #DCDCC4", +"; c #DDDDC7", +"> c #E1E1CA", +", c #D8BE6A", +"' c #A6A694", +") c #D6D6BF", +"! c #D8D8C2", +"~ c #DBDBC4", +"{ c #A2A291", +"] c #E3E3CB", +"^ c #B2B29F", +"/ c #8E7D45", +"( c #7C7C7C", +"_ c #646464", +": c #D5D5BD", +"< c #D5D5BF", +"[ c #D7D7C0", +"} c #9E9E8D", +"| c #ABAB98", +"1 c #E1E1C9", +"2 c #E5E5CD", +"3 c #E9E9D1", +"4 c #343434", +"5 c #E7E7E7", +"6 c #373736", +"7 c #B8B8A6", +"8 c #D3D3BC", +"9 c #909081", +"0 c #9F9F8E", +"a c #DDDDC5", +"b c #E0E0C8", +"c c #A2A292", +"d c #ECECD4", +"e c #EEEED5", +"f c #ECECEC", +"g c #EDEDED", +"h c #3D3D37", +"i c #8A8A7A", +"j c #E6E6CE", +"k c #9A9A8A", +"l c #EAEAD2", +"m c #EEEEE5", +"n c #3C3C3C", +"o c #8F8F80", +"p c #D0D0B9", +"q c #999988", +"r c #ECECD3", +"s c #EFEFD6", +"t c #EEEEE6", +"u c #505050", +"v c #929282", +"w c #D1D1B9", +"x c #969686", +"y c #E7E7CF", +"z c #EBEBD2", +"A c #F5F5DC", +"B c #D2D2BC", +"C c #E9E9D0", +"D c #EDEDD4", +"E c #E3E3DC", +"F c #797973", +"G c #D8D8C1", +"H c #919182", +"I c #949484", +"J c #EAEAD1", +"K c #D9D9CC", +"L c #BFBFB0", +"M c #ACACA5", +"N c #9F9F93", +"O c #98988F", +"P c #E4E4CC", +"Q c #AD8E30", +"R c #756020", +"S c #C4C4B0", +"T c #9D9D8D", +"U c #818174", +"V c #DFDFC7", +"W c #E2E2CA", +"X c #060605", +"Y c #9B9B8B", +"Z c #9C9C8C", +"` c #BEBEAE", +" . c #8B8B7D", +".. c #7E7E71", +"+. c #C0C0AC", +"@. c #C2C2AE", +"#. c #C3C3AF", +"$. c #DADAC3", +"%. c #4E4E4E", +"&. c #E8E8D0", +"*. c #A0A090", +"=. c #F2F2D9", +"-. c #F3F3DA", +";. c #F4F4DB", +">. c #828274", +",. c #EBEBD3", +"'. c #F1F1D8", +"). c #F0F0D7", +"!. c #A5A594", +"~. c #BBBBA8", +"{. c #CDCDB8", +"]. c #4D4D45", +"^. c #A6A696", +" ", +" ", +" . ", +" . . + . ", +" . . @ # $ % . ", +" . . & * = - ; > . . , . ", +" . . ' ) * * ! ~ { ] ^ . . , / . ", +" . ( _ ) : < [ } | 1 2 3 4 . , / . ", +" . 5 6 7 8 9 0 a b 2 c d e . , / . ", +" . f g h i < ~ b j k l e . , / . ", +" . m m n o p b q q r s . , / . ", +" . t u v w - x y z e . , / . A . ", +" . . x B ~ x ] C D . , / . A A E . ", +" . F G H I > y J . , / . A K L M N . ", +" . O ~ # $ P y Q R . . S T T T T U . ", +" . V b W 2 3 X . Y Z T T T T . . ", +" . ` 2 2 T ...+.@.#.S A $.. ", +" %.&.*.l D s =.-.;.=.>.. ", +" . ,.d D s '.=.=.$.. ", +" . s ).'.=.3 !.. ", +" . ~.=.-.{.].. ", +" . -.^.. ", +" . . ", +" "}; diff --git a/src/pixmaps/stock_remove_16.xpm b/src/pixmaps/stock_remove_16.xpm new file mode 100644 index 00000000..a1903d6a --- /dev/null +++ b/src/pixmaps/stock_remove_16.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static char * stock_remove_16_xpm[] = { +"16 16 9 1", +" c None", +". c #000000", +"+ c #5E738B", +"@ c #617891", +"# c #7589A0", +"$ c #7590AE", +"% c #93A0AD", +"& c #ADBCCE", +"* c #C4CEDC", +" ", +" ", +" ", +" ", +" ", +" ............. ", +".&***********%. ", +".*$$$$$$$$$$$@. ", +".#++++++++++++. ", +" ............. ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_search.xpm b/src/pixmaps/stock_search.xpm new file mode 100644 index 00000000..645eff5c --- /dev/null +++ b/src/pixmaps/stock_search.xpm @@ -0,0 +1,155 @@ +/* XPM */ +static char * stock_search_xpm[] = { +"24 24 128 2", +" c None", +". c #000000", +"+ c #ADAD9C", +"@ c #959585", +"# c #DCDCC5", +"$ c #DFDFC8", +"% c #242424", +"& c #A7A796", +"* c #D7D7C1", +"= c #D9D9C3", +"- c #DCDCC4", +"; c #DDDDC7", +"> c #E1E1CA", +", c #A6A694", +"' c #D6D6BF", +") c #D8D8C2", +"! c #DBDBC4", +"~ c #DFDFC7", +"{ c #E3E3CB", +"] c #B2B29F", +"^ c #7C7C7C", +"/ c #646464", +"( c #D5D5BD", +"_ c #D5D5BF", +": c #D7D7C0", +"< c #DADAC3", +"[ c #DEDEC6", +"} c #E1E1C9", +"| c #E5E5CD", +"1 c #E9E9D1", +"2 c #343434", +"3 c #E7E7E7", +"4 c #373736", +"5 c #B8B8A6", +"6 c #D3D3BC", +"7 c #D9D9C2", +"8 c #DDDDC5", +"9 c #E0E0C8", +"0 c #E8E8D0", +"a c #ECECD4", +"b c #EEEED5", +"c c #ECECEC", +"d c #EDEDED", +"e c #3D3D37", +"f c #CECEB7", +"g c #E6E6CE", +"h c #EAEAD2", +"i c #F1F1D8", +"j c #A5A594", +"k c #EEEEE5", +"l c #3C3C3C", +"m c #8F8F80", +"n c #D0D0B9", +"o c #B4B4A0", +"p c #46463E", +"q c #090908", +"r c #4A4A42", +"s c #C1C1AD", +"t c #F2F2D9", +"u c #F3F3DA", +"v c #EEEEE6", +"w c #505050", +"x c #929282", +"y c #D1D1B9", +"z c #B0B09D", +"A c #33332D", +"B c #9D9D8D", +"C c #CFCFB9", +"D c #C4C4AF", +"E c #8D8D7F", +"F c #34342F", +"G c #C3C3AF", +"H c #F4F4DB", +"I c #F5F5DC", +"J c #969686", +"K c #D2D2BC", +"L c #45453E", +"M c #9C9C8C", +"N c #E2E2D0", +"O c #EDEDE5", +"P c #C0C0AC", +"Q c #828274", +"R c #4B4B43", +"S c #BEBEAB", +"T c #797973", +"U c #D8D8C1", +"V c #DDDDC6", +"W c #080807", +"X c #FBFBFA", +"Y c #C3C3AE", +"Z c #B5B5A2", +"` c #A6A695", +" . c #959586", +".. c #98988F", +"+. c #080808", +"@. c #CACAB5", +"#. c #DDDDD0", +"$. c #B7B7A4", +"%. c #AAAA98", +"&. c #9B9B8B", +"*. c #8C8C7D", +"=. c #818174", +"-. c #E2E2CA", +";. c #46463F", +">. c #929283", +",. c #BABAA7", +"'. c #ADAD9B", +"). c #9F9F8E", +"!. c #909081", +"~. c #727266", +"{. c #4B4B44", +"]. c #BEBEAE", +"^. c #33332E", +"/. c #878779", +"(. c #A0A090", +"_. c #737367", +":. c #4E4E4E", +"<. c #BEBEAA", +"[. c #404040", +"}. c #6F6F6F", +"|. c #EBEBD3", +"1. c #EDEDD4", +"2. c #EFEFD6", +"3. c #F0F0D7", +"4. c #BBBBA8", +"5. c #CDCDB8", +"6. c #4D4D45", +"7. c #A6A696", +" ", +" ", +" . ", +" . . + . ", +" . . @ # $ % ", +" . . & * = - ; > . ", +" . . , ' * * ) ! ~ { ] . ", +" . ^ / ' ( _ : < [ } | 1 2 ", +" . 3 4 5 6 * 7 8 9 | 0 a b . ", +" . c d e f _ ! 9 g 0 h b i j . ", +" . k k l m n o p q q r s t u . ", +" . v w x y z A B C D E F G H I . ", +" . . J K ! L M N O P ] Q R I I S . ", +" . T U 7 V W _ X Y Z ` .q I I I ) . ", +" . ..! # $ +.@.#.$.%.&.*.q I I I =.. ", +" . ~ 9 -.;.>.,.'.).!.~.{.I I . . ", +" . ].| | ,.^./.(.>._.. . < . ", +" :.0 1 h <.r q q [.}.. . ", +" . |.a 1.2.i t t < . . . . ", +" . 2.3.i t 1 j . . . . ", +" . 4.t u 5.6.. . . . ", +" . u 7.. . . ", +" . . ", +" "}; diff --git a/src/pixmaps/stock_trash.xpm b/src/pixmaps/stock_trash.xpm new file mode 100644 index 00000000..11c9ebcc --- /dev/null +++ b/src/pixmaps/stock_trash.xpm @@ -0,0 +1,112 @@ +/* XPM */ +static char * stock_trash_xpm[] = { +"24 24 85 1", +" c None", +". c #000000", +"+ c #252525", +"@ c #A8BA9E", +"# c #B4C1AB", +"$ c #E1E7DD", +"% c #838A7D", +"& c #909F86", +"* c #484E43", +"= c #A0AD94", +"- c #9EAB91", +"; c #B6C4AD", +"> c #BFCCB7", +", c #A7B69B", +"' c #BEC9B5", +") c #656F5E", +"! c #2B2E28", +"~ c #353931", +"{ c #242622", +"] c #9BAA8F", +"^ c #9AA78E", +"/ c #98A58C", +"( c #96A58D", +"_ c #D7DED2", +": c #A4B398", +"< c #A2B196", +"[ c #A0B095", +"} c #97A38A", +"| c #7C9674", +"1 c #B0C0AB", +"2 c #CED6C8", +"3 c #DAE1D6", +"4 c #BBC4B1", +"5 c #5B6B57", +"6 c #637354", +"7 c #748C6B", +"8 c #94AA8D", +"9 c #8FA58A", +"0 c #6F8668", +"a c #667961", +"b c #5D6E58", +"c c #4D5A4B", +"d c #485245", +"e c #738B6E", +"f c #687B63", +"g c #5E6F5A", +"h c #576452", +"i c #495345", +"j c #4F5B4B", +"k c #3F453D", +"l c #687D63", +"m c #B9C6B4", +"n c #65785F", +"o c #9BAE97", +"p c #525F4F", +"q c #728A6D", +"r c #4F5C4C", +"s c #6E8468", +"t c #4D5849", +"u c #9BAD96", +"v c #6E8368", +"w c #080808", +"x c #6E8367", +"y c #B7C6B4", +"z c #9AAD96", +"A c #525F4E", +"B c #4D5848", +"C c #687A63", +"D c #B7C5B4", +"E c #6D8367", +"F c #4C5848", +"G c #809A78", +"H c #A0B29A", +"I c #60725C", +"J c #728A6B", +"K c #4F5B4C", +"L c #586853", +"M c #677A61", +"N c #7D9775", +"O c #7A9472", +"P c #788F71", +"Q c #748D6E", +"R c #6F8568", +"S c #6D8267", +"T c #697D64", +" ", +" ", +" ", +" ", +" ........ ", +" +.@#$%&*=-.. ", +" .;>,')!~{-]^/. ", +" .(_':<[=-]^/}. ", +" .|1234=-]^/|5. ", +" .67899||0abcd. ", +" .+effghijk+. ", +" .l++++++++f. ", +" .lmnopqrstf. ", +" .lmnupqrvtf.www ", +" .lmnupqrxtf.wwww ", +" .lynzAqrxBC.wwww ", +" .lDnzAqrEFC.wwww ", +" .GHnzIJKELM.www ", +" .NOPQJRST.ww ", +" ........w ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/stock_up_arrow.xpm b/src/pixmaps/stock_up_arrow.xpm new file mode 100644 index 00000000..bb5c3195 --- /dev/null +++ b/src/pixmaps/stock_up_arrow.xpm @@ -0,0 +1,100 @@ +/* XPM */ +static char * stock_up_arrow_xpm[] = { +"24 24 73 1", +" c None", +". c #000000", +"+ c #1D1D1D", +"@ c #D6D9D5", +"# c #94A989", +"$ c #060606", +"% c #DCDDDA", +"& c #92AB87", +"* c #E2E0E0", +"= c #E3E2E2", +"- c #97B08D", +"; c #6A875B", +"> c #030303", +", c #E9E8E8", +"' c #EAE9EA", +") c #9DB591", +"! c #8CA480", +"~ c #EEECEE", +"{ c #F0ECF0", +"] c #99B48C", +"^ c #91AA85", +"/ c #678458", +"( c #010101", +"_ c #EBEAEA", +": c #F0EEF0", +"< c #F3EFF3", +"[ c #95B487", +"} c #92AE85", +"| c #759267", +"1 c #E4E3E3", +"2 c #EFECEE", +"3 c #F3EFF2", +"4 c #F6F2F6", +"5 c #91B182", +"6 c #90AE81", +"7 c #859F76", +"8 c #688559", +"9 c #ECEAEB", +"0 c #F1EDF0", +"a c #F4F0F4", +"b c #8BAE7B", +"c c #8BAD7B", +"d c #8EAA81", +"e c #7A956D", +"f c #E2E1E1", +"g c #EFECEF", +"h c #F2EFF2", +"i c #F5F1F5", +"j c #8AAD7A", +"k c #8AAC7A", +"l c #8DAD7F", +"m c #8AA67F", +"n c #69875B", +"o c #D9D9D7", +"p c #E5E4E3", +"q c #ECEBEB", +"r c #EAE9E9", +"s c #E7E7E7", +"t c #8FAE81", +"u c #90AE83", +"v c #8BAB7E", +"w c #89A67C", +"x c #718E62", +"y c #B9BEB5", +"z c #BBBFB7", +"A c #BDC1B8", +"B c #BFC3BB", +"C c #BEC2BA", +"D c #BEC2BB", +"E c #739264", +"F c #739364", +"G c #729063", +"H c #607C53", +" ", +" ", +" ", +" ", +" ", +" .. ", +" .. ", +" +@#$ ", +" .%&. ", +" .*=-;. ", +" >,')!. ", +" .=~{]^/. ", +" (_:<[}|. ", +" .12345678. ", +" .90a4bcde. ", +" .fghiajklmn. ", +" .opqrstuvwx. ", +" .yzABCDEEFG8H. ", +" .............. ", +" ", +" ", +" ", +" ", +" "}; diff --git a/src/pixmaps/sylpheed-logo.xpm b/src/pixmaps/sylpheed-logo.xpm new file mode 100644 index 00000000..d727c392 --- /dev/null +++ b/src/pixmaps/sylpheed-logo.xpm @@ -0,0 +1,53 @@ +/* XPM */ +static char * sylpheed_logo_xpm[] = { +"128 40 10 1", +" c None", +". c #969696", +"+ c #808080", +"@ c #404040", +"# c #565656", +"$ c #ABABAB", +"% c #000000", +"& c #161616", +"* c #2B2B2B", +"= c #6B6B6B", +" .+@@@#++$ $ ", +" $#%%%%%%%%%%%&@@@@@&$ ", +" #%%*@@@@%%%%%%%%%&@+ ", +" $&@. ..", +" *+ + += +%$", +" =# $@* $=%= $#*%@ ", +" @ .*%%+ .&%%& $%%. ", +" @ $%& %%= +%* ", +" + =%= .%& @%+ ", +" + ++@++ *%$ *%= %& ", +" # =#+.+#%&= .%# .%& =%= ", +" #$ $+ $&%@ *%. *%= *% ", +" # $ $&%# .%* $%%$ .%# ", +" $# .$ =%%. *%. #%# *%$ ", +" ==++==$ $%%@ $%* $%%$ .%# ", +" %%% $$ $ #%= .+. #%@ +. +. $+$*%. ", +" %%% #%@ =%# $%& $*@ =&%%%+ $%%. $=&%# .=@@&%= .=@@&%= +#=. .%* ", +" %%% .@%%. &%. #%= +&%@ $&%@@&%@ #%* $##.&%= +$ $&%= +$ $&%= $#*. *%. ", +" +%%% $..%* =%* $%& .+ @%+$*= @%@ $%%.=#$ $%% $# +%% $# +%% +&+ .%* ", +" ++++$ =%%@ *%. &%. #%= %&$#$ %%= #%&#$ #%# $*$ %% $*$ %% #&$ *%= ", +" =%%%%%%*= *%%+ .%* =%* $%%$ +%#= .%%$ &%@ %%$ *= =%# *= =%# @%$ +%& ", +" @%&=+++#*%&= .%%& *%. &%+ #%# @%@$ #%@ =%* =%# =% =*#$ =% =*#$ +%= $&%= ", +"+%&$ +@&. *%%+ =%* =%& &%$ %%. $%%$ &%. &%$ ** .=#. ** .=#. $%* @%& ", +"+%$ $## #%%# &%+ &%= =%# +%@ *%+ =%* #%# %@ $$ %@ $$ #%= #&%# ", +".@ +# +%%@ =%& $&%& &%. *%$ @%+ &%. $%%$ %& %& $%%$ #+%%$ ", +" #$ $# @%%+ &%= .#&%= =%* $%# @%+ =%* @%@ %%. %%. =%% @+*%# $ ", +" #$ #%%*$ +%& +=.%& &%.$= #%. $**$ &%. .%%.$+ #%& +$ #%& +$ @%%$ =&+$%%$+. ", +" =#$ .#&%@= +%* +@+ *%= +%%@# $%* $#*. +%* @%%@+ $&%&+$.==$ $&%&+$.==$ #%%*@%&$ +%%*$ ", +" +##@+@@@&@@=$ + $#%&#$ .%& +%&+ .+@%##@=. @%. @%*$ $@%%%*. $@%%%*. $&%%*= +%# ", +" + *%= + +%*+$ ", +" . .%& .%= ", +" *%= *& ", +" .%& .%= ", +" .#@*@#=%%# *& ", +" .*%=$ .%&$+$ .%# ", +" .%&$ &%. &%$ ", +" *%. @%+ =%# ", +" %% @&+ &%$ ", +" @%+ =&#$ =%* ", +" +=@=+ $%%. "}; diff --git a/src/pixmaps/tb_address_book.xpm b/src/pixmaps/tb_address_book.xpm new file mode 100644 index 00000000..289be56f --- /dev/null +++ b/src/pixmaps/tb_address_book.xpm @@ -0,0 +1,56 @@ +/* XPM */ +static char * tb_address_book_xpm[] = { +"24 24 29 1", +" c None", +". c #000000", +"+ c #4B6772", +"@ c #70929F", +"# c #668B99", +"$ c #5E808D", +"% c #B6B6B5", +"& c #52707B", +"* c #23393F", +"= c #A6A7A4", +"- c #888D82", +"; c #577782", +"> c #5B7D8A", +", c #9E6769", +"' c #3C4035", +") c #54594B", +"! c #4E6A75", +"~ c #91948E", +"{ c #AE8182", +"] c #E1D5D2", +"^ c #F1F0EC", +"/ c #AAAAA7", +"( c #636361", +"_ c #C2C2C0", +": c #14090A", +"< c #85A3AE", +"[ c #CCCDC4", +"} c #7697A3", +"| c #999D91", +" ", +" ", +" ", +" ", +" .......... ", +" .+@#$$$$$$. ", +" .@#$$$$$$.% ", +" .&@$$*$$$$.% ", +" .@#$**$$$.%% ", +" .@#$*$*$$$.%=. ", +" .&@$****$$.%%-. ", +" .@#*$$$*$$.%=. ", +" .;@$$$$$*$.%%-. ", +" .>#$$$$$$$.%=. ", +" .,...')!$$.%%~. ", +" .{]^^^/(...%=. ", +" .,]^^^^^^^_%~. ", +" :..<[^^^^_=. ", +" ...}[^|-. ", +" ...$. ", +" .. ", +" ", +" ", +" "}; diff --git a/src/pixmaps/trash.xpm b/src/pixmaps/trash.xpm new file mode 100644 index 00000000..a201902b --- /dev/null +++ b/src/pixmaps/trash.xpm @@ -0,0 +1,29 @@ +/* XPM */ +static char * trash_xpm[] = { +"16 16 10 1", +" c None", +". c #000000", +"+ c #A1B7E7", +"@ c #7B92CE", +"# c #3B4766", +"$ c #272A42", +"% c #1B1C2E", +"& c #5D6CAD", +"* c #D2DEFB", +"= c #52617C", +" ", +" ...... ", +" ..++@#@@.. ", +".@+++$%@@@&. ", +".&+**@@@@&=. ", +".=&@@&&==##. ", +" .%&==##$%. ", +" .=%%%%%%=. ", +" .=+#@#&#=.... ", +" .=+#@#&#=..... ", +" .=+#@#&#=..... ", +" .&+#@#&#=.... ", +" .&&&=&=... ", +" ....... ", +" ", +" "}; diff --git a/src/pixmaps/unread.xpm b/src/pixmaps/unread.xpm new file mode 100644 index 00000000..b3c31e4f --- /dev/null +++ b/src/pixmaps/unread.xpm @@ -0,0 +1,50 @@ +/* XPM */ +static char * unread_xpm[] = { +"13 10 37 1", +" c None", +". c #000000", +"+ c #3D81C1", +"@ c #D4E3F0", +"# c #D5E3F1", +"$ c #2A5984", +"% c #D1E0EF", +"& c #D1E1EF", +"* c #D2E2F0", +"= c #94B9DC", +"- c #C0D5EA", +"; c #D4E2F0", +"> c #C2D5EA", +", c #80AAD6", +"' c #346FA6", +") c #C9D9ED", +"! c #CFDEEF", +"~ c #C9DBED", +"{ c #3673AC", +"] c #3B7EBD", +"^ c #D2E1F0", +"/ c #8EB0DA", +"( c #A6C1E2", +"_ c #77A0D3", +": c #CCDCEE", +"< c #D1DFEF", +"[ c #8CAFDA", +"} c #295883", +"| c #BFD4E9", +"1 c #7AA9D4", +"2 c #6B9CCF", +"3 c #91B4DB", +"4 c #ABC4E3", +"5 c #9DBCDF", +"6 c #7DA6D5", +"7 c #A4C0E0", +"8 c #1E4060", +" ........... ", +".+@##@@##@@$.", +".@+@@#%@&*+=.", +".@-+#@*;*+>,.", +".#@@+#@#')>>.", +".!#~+{@]+^!/.", +".#%+@;+^!+(_.", +".@+&@;: +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "pop.h" +#include "md5.h" +#include "prefs_account.h" +#include "utils.h" +#include "recv.h" + +static gint pop3_greeting_recv (Pop3Session *session, + const gchar *msg); +static gint pop3_getauth_user_send (Pop3Session *session); +static gint pop3_getauth_pass_send (Pop3Session *session); +static gint pop3_getauth_apop_send (Pop3Session *session); +#if USE_SSL +static gint pop3_stls_send (Pop3Session *session); +static gint pop3_stls_recv (Pop3Session *session); +#endif +static gint pop3_getrange_stat_send (Pop3Session *session); +static gint pop3_getrange_stat_recv (Pop3Session *session, + const gchar *msg); +static gint pop3_getrange_last_send (Pop3Session *session); +static gint pop3_getrange_last_recv (Pop3Session *session, + const gchar *msg); +static gint pop3_getrange_uidl_send (Pop3Session *session); +static gint pop3_getrange_uidl_recv (Pop3Session *session, + const gchar *data, + guint len); +static gint pop3_getsize_list_send (Pop3Session *session); +static gint pop3_getsize_list_recv (Pop3Session *session, + const gchar *data, + guint len); +static gint pop3_retr_send (Pop3Session *session); +static gint pop3_retr_recv (Pop3Session *session, + const gchar *data, + guint len); +static gint pop3_delete_send (Pop3Session *session); +static gint pop3_delete_recv (Pop3Session *session); +static gint pop3_logout_send (Pop3Session *session); + +static void pop3_gen_send (Pop3Session *session, + const gchar *format, ...); + +static void pop3_session_destroy (Session *session); + +static gint pop3_write_msg_to_file (const gchar *file, + const gchar *data, + guint len); + +static Pop3State pop3_lookup_next (Pop3Session *session); +static Pop3ErrorValue pop3_ok (Pop3Session *session, + const gchar *msg); + +static gint pop3_session_recv_msg (Session *session, + const gchar *msg); +static gint pop3_session_recv_data_finished (Session *session, + guchar *data, + guint len); + + +static gint pop3_greeting_recv(Pop3Session *session, const gchar *msg) +{ + session->state = POP3_GREETING; + + session->greeting = g_strdup(msg); + return PS_SUCCESS; +} + +#if USE_SSL +static gint pop3_stls_send(Pop3Session *session) +{ + session->state = POP3_STLS; + pop3_gen_send(session, "STLS"); + return PS_SUCCESS; +} + +static gint pop3_stls_recv(Pop3Session *session) +{ + if (session_start_tls(SESSION(session)) < 0) { + session->error_val = PS_SOCKET; + return -1; + } + return PS_SUCCESS; +} +#endif /* USE_SSL */ + +static gint pop3_getauth_user_send(Pop3Session *session) +{ + g_return_val_if_fail(session->user != NULL, -1); + + session->state = POP3_GETAUTH_USER; + pop3_gen_send(session, "USER %s", session->user); + return PS_SUCCESS; +} + +static gint pop3_getauth_pass_send(Pop3Session *session) +{ + g_return_val_if_fail(session->pass != NULL, -1); + + session->state = POP3_GETAUTH_PASS; + pop3_gen_send(session, "PASS %s", session->pass); + return PS_SUCCESS; +} + +static gint pop3_getauth_apop_send(Pop3Session *session) +{ + gchar *start, *end; + gchar *apop_str; + gchar md5sum[33]; + + g_return_val_if_fail(session->user != NULL, -1); + g_return_val_if_fail(session->pass != NULL, -1); + + session->state = POP3_GETAUTH_APOP; + + if ((start = strchr(session->greeting, '<')) == NULL) { + log_warning(_("Required APOP timestamp not found " + "in greeting\n")); + session->error_val = PS_PROTOCOL; + return -1; + } + + if ((end = strchr(start, '>')) == NULL || end == start + 1) { + log_warning(_("Timestamp syntax error in greeting\n")); + session->error_val = PS_PROTOCOL; + return -1; + } + + *(end + 1) = '\0'; + + apop_str = g_strconcat(start, session->pass, NULL); + md5_hex_digest(md5sum, apop_str); + g_free(apop_str); + + pop3_gen_send(session, "APOP %s %s", session->user, md5sum); + + return PS_SUCCESS; +} + +static gint pop3_getrange_stat_send(Pop3Session *session) +{ + session->state = POP3_GETRANGE_STAT; + pop3_gen_send(session, "STAT"); + return PS_SUCCESS; +} + +static gint pop3_getrange_stat_recv(Pop3Session *session, const gchar *msg) +{ + if (sscanf(msg, "%d %d", &session->count, &session->total_bytes) != 2) { + log_warning(_("POP3 protocol error\n")); + session->error_val = PS_PROTOCOL; + return -1; + } else { + if (session->count == 0) { + session->uidl_is_valid = TRUE; + } else { + session->msg = g_new0(Pop3MsgInfo, session->count + 1); + session->cur_msg = 1; + } + } + + return PS_SUCCESS; +} + +static gint pop3_getrange_last_send(Pop3Session *session) +{ + session->state = POP3_GETRANGE_LAST; + pop3_gen_send(session, "LAST"); + return PS_SUCCESS; +} + +static gint pop3_getrange_last_recv(Pop3Session *session, const gchar *msg) +{ + gint last; + + if (sscanf(msg, "%d", &last) == 0) { + log_warning(_("POP3 protocol error\n")); + session->error_val = PS_PROTOCOL; + return -1; + } else { + if (session->count > last) { + session->new_msg_exist = TRUE; + session->cur_msg = last + 1; + } else + session->cur_msg = 0; + } + + return PS_SUCCESS; +} + +static gint pop3_getrange_uidl_send(Pop3Session *session) +{ + session->state = POP3_GETRANGE_UIDL; + pop3_gen_send(session, "UIDL"); + return PS_SUCCESS; +} + +static gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *data, + guint len) +{ + gchar id[IDLEN + 1]; + gchar buf[POPBUFSIZE]; + gint buf_len; + gint num; + time_t recv_time; + const gchar *p = data; + const gchar *lastp = data + len; + const gchar *newline; + + while (p < lastp) { + if ((newline = memchr(p, '\r', lastp - p)) == NULL) + return -1; + buf_len = MIN(newline - p, sizeof(buf) - 1); + memcpy(buf, p, buf_len); + buf[buf_len] = '\0'; + + p = newline + 1; + if (p < lastp && *p == '\n') p++; + + if (sscanf(buf, "%d %" Xstr(IDLEN) "s", &num, id) != 2 || + num <= 0 || num > session->count) { + log_warning(_("invalid UIDL response: %s\n"), buf); + continue; + } + + session->msg[num].uidl = g_strdup(id); + + recv_time = (time_t)g_hash_table_lookup(session->uidl_table, id); + session->msg[num].recv_time = recv_time; + + if (!session->ac_prefs->getall && recv_time != RECV_TIME_NONE) + session->msg[num].received = TRUE; + + if (!session->new_msg_exist && + (session->ac_prefs->getall || recv_time == RECV_TIME_NONE || + session->ac_prefs->rmmail)) { + session->cur_msg = num; + session->new_msg_exist = TRUE; + } + } + + session->uidl_is_valid = TRUE; + return PS_SUCCESS; +} + +static gint pop3_getsize_list_send(Pop3Session *session) +{ + session->state = POP3_GETSIZE_LIST; + pop3_gen_send(session, "LIST"); + return PS_SUCCESS; +} + +static gint pop3_getsize_list_recv(Pop3Session *session, const gchar *data, + guint len) +{ + gchar buf[POPBUFSIZE]; + gint buf_len; + guint num, size; + const gchar *p = data; + const gchar *lastp = data + len; + const gchar *newline; + + while (p < lastp) { + if ((newline = memchr(p, '\r', lastp - p)) == NULL) + return -1; + buf_len = MIN(newline - p, sizeof(buf) - 1); + memcpy(buf, p, buf_len); + buf[buf_len] = '\0'; + + p = newline + 1; + if (p < lastp && *p == '\n') p++; + + if (sscanf(buf, "%u %u", &num, &size) != 2) { + session->error_val = PS_PROTOCOL; + return -1; + } + + if (num > 0 && num <= session->count) + session->msg[num].size = size; + if (num > 0 && num < session->cur_msg) + session->cur_total_bytes += size; + } + + return PS_SUCCESS; +} + +static gint pop3_retr_send(Pop3Session *session) +{ + session->state = POP3_RETR; + pop3_gen_send(session, "RETR %d", session->cur_msg); + return PS_SUCCESS; +} + +static gint pop3_retr_recv(Pop3Session *session, const gchar *data, guint len) +{ + gchar *file; + gint drop_ok; + + file = get_tmp_file(); + if (pop3_write_msg_to_file(file, data, len) < 0) { + g_free(file); + session->error_val = PS_IOERR; + return -1; + } + + drop_ok = session->drop_message(session, file); + unlink(file); + g_free(file); + if (drop_ok < 0) { + session->error_val = PS_IOERR; + return -1; + } + + session->cur_total_bytes += session->msg[session->cur_msg].size; + session->cur_total_recv_bytes += session->msg[session->cur_msg].size; + session->cur_total_num++; + + session->msg[session->cur_msg].received = TRUE; + session->msg[session->cur_msg].recv_time = + drop_ok == DROP_DONT_RECEIVE ? RECV_TIME_KEEP + : drop_ok == DROP_DELETE ? RECV_TIME_DELETE + : session->current_time; + + return PS_SUCCESS; +} + +static gint pop3_delete_send(Pop3Session *session) +{ + session->state = POP3_DELETE; + pop3_gen_send(session, "DELE %d", session->cur_msg); + return PS_SUCCESS; +} + +static gint pop3_delete_recv(Pop3Session *session) +{ + session->msg[session->cur_msg].deleted = TRUE; + return PS_SUCCESS; +} + +static gint pop3_logout_send(Pop3Session *session) +{ + session->state = POP3_LOGOUT; + pop3_gen_send(session, "QUIT"); + return PS_SUCCESS; +} + +static void pop3_gen_send(Pop3Session *session, const gchar *format, ...) +{ + gchar buf[POPBUFSIZE + 1]; + va_list args; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf) - 2, format, args); + va_end(args); + + if (!strncasecmp(buf, "PASS ", 5)) + log_print("POP3> PASS ********\n"); + else + log_print("POP3> %s\n", buf); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); +} + +Session *pop3_session_new(PrefsAccount *account) +{ + Pop3Session *session; + + g_return_val_if_fail(account != NULL, NULL); + + session = g_new0(Pop3Session, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_POP3; + + SESSION(session)->recv_msg = pop3_session_recv_msg; + SESSION(session)->recv_data_finished = pop3_session_recv_data_finished; + SESSION(session)->send_data_finished = NULL; + + SESSION(session)->destroy = pop3_session_destroy; + + session->state = POP3_READY; + session->ac_prefs = account; + session->uidl_table = pop3_get_uidl_table(account); + session->current_time = time(NULL); + session->error_val = PS_SUCCESS; + session->error_msg = NULL; + + return SESSION(session); +} + +static void pop3_session_destroy(Session *session) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + gint n; + + g_return_if_fail(session != NULL); + + for (n = 1; n <= pop3_session->count; n++) + g_free(pop3_session->msg[n].uidl); + g_free(pop3_session->msg); + + if (pop3_session->uidl_table) { + hash_free_strings(pop3_session->uidl_table); + g_hash_table_destroy(pop3_session->uidl_table); + } + + g_free(pop3_session->greeting); + g_free(pop3_session->user); + g_free(pop3_session->pass); + g_free(pop3_session->error_msg); +} + +GHashTable *pop3_get_uidl_table(PrefsAccount *ac_prefs) +{ + GHashTable *table; + gchar *path; + FILE *fp; + gchar buf[POPBUFSIZE]; + gchar uidl[POPBUFSIZE]; + time_t recv_time; + time_t now; + + table = g_hash_table_new(g_str_hash, g_str_equal); + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "uidl", G_DIR_SEPARATOR_S, ac_prefs->recv_server, + "-", ac_prefs->userid, NULL); + if ((fp = fopen(path, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(path, "fopen"); + g_free(path); + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "uidl-", ac_prefs->recv_server, + "-", ac_prefs->userid, NULL); + if ((fp = fopen(path, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(path, "fopen"); + g_free(path); + return table; + } + } + g_free(path); + + now = time(NULL); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + recv_time = RECV_TIME_NONE; + if (sscanf(buf, "%s\t%ld", uidl, &recv_time) != 2) { + if (sscanf(buf, "%s", uidl) != 1) + continue; + else + recv_time = now; + } + if (recv_time == RECV_TIME_NONE) + recv_time = RECV_TIME_RECEIVED; + g_hash_table_insert(table, g_strdup(uidl), + GINT_TO_POINTER(recv_time)); + } + + fclose(fp); + return table; +} + +gint pop3_write_uidl_list(Pop3Session *session) +{ + gchar *path; + FILE *fp; + Pop3MsgInfo *msg; + gint n; + + if (!session->uidl_is_valid) return 0; + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + "uidl", G_DIR_SEPARATOR_S, + session->ac_prefs->recv_server, + "-", session->ac_prefs->userid, NULL); + if ((fp = fopen(path, "wb")) == NULL) { + FILE_OP_ERROR(path, "fopen"); + g_free(path); + return -1; + } + + for (n = 1; n <= session->count; n++) { + msg = &session->msg[n]; + if (msg->uidl && msg->received && !msg->deleted) + fprintf(fp, "%s\t%ld\n", msg->uidl, msg->recv_time); + } + + if (fclose(fp) == EOF) FILE_OP_ERROR(path, "fclose"); + g_free(path); + + return 0; +} + +static gint pop3_write_msg_to_file(const gchar *file, const gchar *data, + guint len) +{ + FILE *fp; + const gchar *prev, *cur; + + g_return_val_if_fail(file != NULL, -1); + + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + if (change_file_mode_rw(fp, file) < 0) + FILE_OP_ERROR(file, "chmod"); + + /* +------------------+----------------+--------------------------+ * + * ^data ^prev ^cur data+len-1^ */ + + prev = data; + while ((cur = (gchar *)my_memmem(prev, len - (prev - data), "\r\n", 2)) + != NULL) { + if ((cur > prev && fwrite(prev, cur - prev, 1, fp) < 1) || + fputc('\n', fp) == EOF) { + FILE_OP_ERROR(file, "fwrite"); + g_warning("can't write to file: %s\n", file); + fclose(fp); + unlink(file); + return -1; + } + + if (cur == data + len - 1) { + prev = cur + 1; + break; + } + + if (*(cur + 1) == '\n') + prev = cur + 2; + else + prev = cur + 1; + + if (prev - data < len - 1 && *prev == '.' && *(prev + 1) == '.') + prev++; + + if (prev - data >= len) + break; + } + + if (prev - data < len && + fwrite(prev, len - (prev - data), 1, fp) < 1) { + FILE_OP_ERROR(file, "fwrite"); + g_warning("can't write to file: %s\n", file); + fclose(fp); + unlink(file); + return -1; + } + if (data[len - 1] != '\r' && data[len - 1] != '\n') { + if (fputc('\n', fp) == EOF) { + FILE_OP_ERROR(file, "fputc"); + g_warning("can't write to file: %s\n", file); + fclose(fp); + unlink(file); + return -1; + } + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + unlink(file); + return -1; + } + + return 0; +} + +static Pop3State pop3_lookup_next(Pop3Session *session) +{ + Pop3MsgInfo *msg; + PrefsAccount *ac = session->ac_prefs; + gint size; + gboolean size_limit_over; + + for (;;) { + msg = &session->msg[session->cur_msg]; + size = msg->size; + size_limit_over = + (ac->enable_size_limit && + ac->size_limit > 0 && + size > ac->size_limit * 1024); + + if (msg->recv_time == RECV_TIME_DELETE || + (ac->rmmail && + msg->recv_time != RECV_TIME_NONE && + msg->recv_time != RECV_TIME_KEEP && + session->current_time - msg->recv_time >= + ac->msg_leave_time * 24 * 60 * 60)) { + log_print(_("POP3: Deleting expired message %d\n"), + session->cur_msg); + pop3_delete_send(session); + return POP3_DELETE; + } + + if (size_limit_over) + log_print + (_("POP3: Skipping message %d (%d bytes)\n"), + session->cur_msg, size); + + if (size == 0 || msg->received || size_limit_over) { + session->cur_total_bytes += size; + if (session->cur_msg == session->count) { + pop3_logout_send(session); + return POP3_LOGOUT; + } else + session->cur_msg++; + } else + break; + } + + pop3_retr_send(session); + return POP3_RETR; +} + +static Pop3ErrorValue pop3_ok(Pop3Session *session, const gchar *msg) +{ + Pop3ErrorValue ok; + + log_print("POP3< %s\n", msg); + + if (!strncmp(msg, "+OK", 3)) + ok = PS_SUCCESS; + else if (!strncmp(msg, "-ERR", 4)) { + if (strstr(msg + 4, "lock") || + strstr(msg + 4, "Lock") || + strstr(msg + 4, "LOCK") || + strstr(msg + 4, "wait")) { + log_warning(_("mailbox is locked\n")); + ok = PS_LOCKBUSY; + } else if (strcasestr(msg + 4, "timeout")) { + log_warning(_("session timeout\n")); + ok = PS_ERROR; + } else { + switch (session->state) { +#if USE_SSL + case POP3_STLS: + log_warning(_("can't start TLS session\n")); + ok = PS_ERROR; + break; +#endif + case POP3_GETAUTH_USER: + case POP3_GETAUTH_PASS: + case POP3_GETAUTH_APOP: + log_warning(_("error occurred on authentication\n")); + ok = PS_AUTHFAIL; + break; + case POP3_GETRANGE_LAST: + case POP3_GETRANGE_UIDL: + log_warning(_("command not supported\n")); + ok = PS_NOTSUPPORTED; + break; + default: + log_warning(_("error occurred on POP3 session\n")); + ok = PS_ERROR; + } + } + + g_free(session->error_msg); + session->error_msg = g_strdup(msg); + fprintf(stderr, "POP3: %s\n", msg); + } else + ok = PS_PROTOCOL; + + session->error_val = ok; + return ok; +} + +static gint pop3_session_recv_msg(Session *session, const gchar *msg) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + Pop3ErrorValue val = PS_SUCCESS; + const guchar *body; + + body = msg; + if (pop3_session->state != POP3_GETRANGE_UIDL_RECV && + pop3_session->state != POP3_GETSIZE_LIST_RECV) { + val = pop3_ok(pop3_session, msg); + if (val != PS_SUCCESS) { + if (val != PS_NOTSUPPORTED) { + pop3_session->state = POP3_ERROR; + return -1; + } + } + + if (*body == '+' || *body == '-') + body++; + while (isalpha(*body)) + body++; + while (isspace(*body)) + body++; + } + + switch (pop3_session->state) { + case POP3_READY: + case POP3_GREETING: + pop3_greeting_recv(pop3_session, body); +#if USE_SSL + if (pop3_session->ac_prefs->ssl_pop == SSL_STARTTLS) + pop3_stls_send(pop3_session); + else +#endif + if (pop3_session->ac_prefs->use_apop_auth) + pop3_getauth_apop_send(pop3_session); + else + pop3_getauth_user_send(pop3_session); + break; +#if USE_SSL + case POP3_STLS: + if (pop3_stls_recv(pop3_session) != PS_SUCCESS) + return -1; + if (pop3_session->ac_prefs->use_apop_auth) + pop3_getauth_apop_send(pop3_session); + else + pop3_getauth_user_send(pop3_session); + break; +#endif + case POP3_GETAUTH_USER: + pop3_getauth_pass_send(pop3_session); + break; + case POP3_GETAUTH_PASS: + case POP3_GETAUTH_APOP: + pop3_getrange_stat_send(pop3_session); + break; + case POP3_GETRANGE_STAT: + if (pop3_getrange_stat_recv(pop3_session, body) < 0) + return -1; + if (pop3_session->count > 0) + pop3_getrange_uidl_send(pop3_session); + else + pop3_logout_send(pop3_session); + break; + case POP3_GETRANGE_LAST: + if (val == PS_NOTSUPPORTED) + pop3_session->error_val = PS_SUCCESS; + else if (pop3_getrange_last_recv(pop3_session, body) < 0) + return -1; + if (pop3_session->cur_msg > 0) + pop3_getsize_list_send(pop3_session); + else + pop3_logout_send(pop3_session); + break; + case POP3_GETRANGE_UIDL: + if (val == PS_NOTSUPPORTED) { + pop3_session->error_val = PS_SUCCESS; + pop3_getrange_last_send(pop3_session); + } else { + pop3_session->state = POP3_GETRANGE_UIDL_RECV; + session_recv_data(session, 0, ".\r\n"); + } + break; + case POP3_GETSIZE_LIST: + pop3_session->state = POP3_GETSIZE_LIST_RECV; + session_recv_data(session, 0, ".\r\n"); + break; + case POP3_RETR: + pop3_session->state = POP3_RETR_RECV; + session_recv_data(session, 0, ".\r\n"); + break; + case POP3_DELETE: + pop3_delete_recv(pop3_session); + if (pop3_session->cur_msg == pop3_session->count) + pop3_logout_send(pop3_session); + else { + pop3_session->cur_msg++; + if (pop3_lookup_next(pop3_session) == POP3_ERROR) + return -1; + } + break; + case POP3_LOGOUT: + session_disconnect(session); + break; + case POP3_ERROR: + default: + return -1; + } + + return 0; +} + +static gint pop3_session_recv_data_finished(Session *session, guchar *data, + guint len) +{ + Pop3Session *pop3_session = POP3_SESSION(session); + Pop3ErrorValue val = PS_SUCCESS; + + switch (pop3_session->state) { + case POP3_GETRANGE_UIDL_RECV: + val = pop3_getrange_uidl_recv(pop3_session, data, len); + if (val == PS_SUCCESS) { + if (pop3_session->new_msg_exist) + pop3_getsize_list_send(pop3_session); + else + pop3_logout_send(pop3_session); + } else + return -1; + break; + case POP3_GETSIZE_LIST_RECV: + val = pop3_getsize_list_recv(pop3_session, data, len); + if (val == PS_SUCCESS) { + if (pop3_lookup_next(pop3_session) == POP3_ERROR) + return -1; + } else + return -1; + break; + case POP3_RETR_RECV: + if (pop3_retr_recv(pop3_session, data, len) < 0) + return -1; + + if (pop3_session->msg[pop3_session->cur_msg].recv_time + == RECV_TIME_DELETE || + (pop3_session->ac_prefs->rmmail && + pop3_session->ac_prefs->msg_leave_time == 0 && + pop3_session->msg[pop3_session->cur_msg].recv_time + != RECV_TIME_KEEP)) + pop3_delete_send(pop3_session); + else if (pop3_session->cur_msg == pop3_session->count) + pop3_logout_send(pop3_session); + else { + pop3_session->cur_msg++; + if (pop3_lookup_next(pop3_session) == POP3_ERROR) + return -1; + } + break; + case POP3_ERROR: + default: + return -1; + } + + return 0; +} diff --git a/src/pop.h b/src/pop.h new file mode 100644 index 00000000..515bc61b --- /dev/null +++ b/src/pop.h @@ -0,0 +1,153 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __POP_H__ +#define __POP_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "session.h" +#include "prefs_account.h" + +typedef struct _Pop3MsgInfo Pop3MsgInfo; +typedef struct _Pop3Session Pop3Session; + +#define POP3_SESSION(obj) ((Pop3Session *)obj) + +typedef enum { + POP3_READY, + POP3_GREETING, +#if USE_SSL + POP3_STLS, +#endif + POP3_GETAUTH_USER, + POP3_GETAUTH_PASS, + POP3_GETAUTH_APOP, + POP3_GETRANGE_STAT, + POP3_GETRANGE_LAST, + POP3_GETRANGE_UIDL, + POP3_GETRANGE_UIDL_RECV, + POP3_GETSIZE_LIST, + POP3_GETSIZE_LIST_RECV, + POP3_RETR, + POP3_RETR_RECV, + POP3_DELETE, + POP3_LOGOUT, + POP3_ERROR, + + N_POP3_STATE +} Pop3State; + +typedef enum { + PS_SUCCESS = 0, /* command successful */ + PS_NOMAIL = 1, /* no mail available */ + PS_SOCKET = 2, /* socket I/O woes */ + PS_AUTHFAIL = 3, /* user authorization failed */ + PS_PROTOCOL = 4, /* protocol violation */ + PS_SYNTAX = 5, /* command-line syntax error */ + PS_IOERR = 6, /* file I/O error */ + PS_ERROR = 7, /* protocol error */ + PS_EXCLUDE = 8, /* client-side exclusion error */ + PS_LOCKBUSY = 9, /* server responded lock busy */ + PS_SMTP = 10, /* SMTP error */ + PS_DNS = 11, /* fatal DNS error */ + PS_BSMTP = 12, /* output batch could not be opened */ + PS_MAXFETCH = 13, /* poll ended by fetch limit */ + + PS_NOTSUPPORTED = 14, /* command not supported */ + + /* leave space for more codes */ + + PS_CONTINUE = 128 /* more responses may follow */ +} Pop3ErrorValue; + +typedef enum { + RECV_TIME_NONE = 0, + RECV_TIME_RECEIVED = 1, + RECV_TIME_KEEP = 2, + RECV_TIME_DELETE = 3 +} RecvTime; + +typedef enum { + DROP_OK = 0, + DROP_DONT_RECEIVE = 1, + DROP_DELETE = 2, + DROP_ERROR = -1 +} Pop3DropValue; + +struct _Pop3MsgInfo +{ + gint size; + gchar *uidl; + time_t recv_time; + guint received : 1; + guint deleted : 1; +}; + +struct _Pop3Session +{ + Session session; + + Pop3State state; + + PrefsAccount *ac_prefs; + + gchar *greeting; + gchar *user; + gchar *pass; + gint count; + gint total_bytes; + gint cur_msg; + gint cur_total_num; + gint cur_total_bytes; + gint cur_total_recv_bytes; + + Pop3MsgInfo *msg; + + GHashTable *uidl_table; + + gboolean new_msg_exist; + gboolean uidl_is_valid; + + time_t current_time; + + Pop3ErrorValue error_val; + gchar *error_msg; + + gpointer data; + + /* virtual method to drop message */ + gint (*drop_message) (Pop3Session *session, + const gchar *file); +}; + +#define POPBUFSIZE 512 +/* #define IDLEN 128 */ +#define IDLEN POPBUFSIZE + +Session *pop3_session_new (PrefsAccount *account); +GHashTable *pop3_get_uidl_table (PrefsAccount *account); +gint pop3_write_uidl_list (Pop3Session *session); + +#endif /* __POP_H__ */ diff --git a/src/prefs.c b/src/prefs.c new file mode 100644 index 00000000..d9972b57 --- /dev/null +++ b/src/prefs.c @@ -0,0 +1,817 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "prefs.h" +#include "codeconv.h" +#include "utils.h" +#include "gtkutils.h" + +typedef enum +{ + DUMMY_PARAM +} DummyEnum; + +void prefs_read_config(PrefParam *param, const gchar *label, + const gchar *rcfile) +{ + FILE *fp; + gchar buf[PREFSBUFSIZE]; + gchar *rcpath; + gchar *block_label; + + g_return_if_fail(param != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(rcfile != NULL); + + debug_print(_("Reading configuration...\n")); + + prefs_set_default(param); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL); + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + return; + } + g_free(rcpath); + + block_label = g_strdup_printf("[%s]", label); + + /* search aiming block */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + gint val; + + val = strncmp(buf, block_label, strlen(block_label)); + if (val == 0) { + debug_print(_("Found %s\n"), block_label); + break; + } + } + g_free(block_label); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + /* reached next block */ + if (buf[0] == '[') break; + + prefs_config_parse_one_line(param, buf); + } + + debug_print(_("Finished reading configuration.\n")); + fclose(fp); +} + +void prefs_config_parse_one_line(PrefParam *param, const gchar *buf) +{ + gint i; + gint name_len; + const gchar *value; + + for (i = 0; param[i].name != NULL; i++) { + name_len = strlen(param[i].name); + if (strncasecmp(buf, param[i].name, name_len)) + continue; + if (buf[name_len] != '=') + continue; + value = buf + name_len + 1; + /* debug_print("%s = %s\n", param[i].name, value); */ + + switch (param[i].type) { + case P_STRING: + g_free(*((gchar **)param[i].data)); + *((gchar **)param[i].data) = + *value ? g_strdup(value) : NULL; + break; + case P_INT: + *((gint *)param[i].data) = + (gint)atoi(value); + break; + case P_BOOL: + *((gboolean *)param[i].data) = + (*value == '0' || *value == '\0') + ? FALSE : TRUE; + break; + case P_ENUM: + *((DummyEnum *)param[i].data) = + (DummyEnum)atoi(value); + break; + case P_USHORT: + *((gushort *)param[i].data) = + (gushort)atoi(value); + break; + default: + break; + } + } +} + +#define TRY(func) \ +if (!(func)) \ +{ \ + g_warning(_("failed to write configuration to file\n")); \ + if (orig_fp) fclose(orig_fp); \ + prefs_file_close_revert(pfile); \ + g_free(rcpath); \ + g_free(block_label); \ + return; \ +} \ + +void prefs_write_config(PrefParam *param, const gchar *label, + const gchar *rcfile) +{ + FILE *orig_fp; + PrefFile *pfile; + gchar *rcpath; + gchar buf[PREFSBUFSIZE]; + gchar *block_label = NULL; + gboolean block_matched = FALSE; + + g_return_if_fail(param != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(rcfile != NULL); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL); + if ((orig_fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + } + + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning(_("failed to write configuration to file\n")); + if (orig_fp) fclose(orig_fp); + g_free(rcpath); + return; + } + + block_label = g_strdup_printf("[%s]", label); + + /* search aiming block */ + if (orig_fp) { + while (fgets(buf, sizeof(buf), orig_fp) != NULL) { + gint val; + + val = strncmp(buf, block_label, strlen(block_label)); + if (val == 0) { + debug_print(_("Found %s\n"), block_label); + block_matched = TRUE; + break; + } else + TRY(fputs(buf, pfile->fp) != EOF); + } + } + + TRY(fprintf(pfile->fp, "%s\n", block_label) > 0); + g_free(block_label); + block_label = NULL; + + /* write all param data to file */ + TRY(prefs_file_write_param(pfile, param) == 0); + + if (block_matched) { + while (fgets(buf, sizeof(buf), orig_fp) != NULL) { + /* next block */ + if (buf[0] == '[') { + TRY(fputc('\n', pfile->fp) != EOF && + fputs(buf, pfile->fp) != EOF); + break; + } + } + while (fgets(buf, sizeof(buf), orig_fp) != NULL) + TRY(fputs(buf, pfile->fp) != EOF); + } + + if (orig_fp) fclose(orig_fp); + if (prefs_file_close(pfile) < 0) + g_warning(_("failed to write configuration to file\n")); + g_free(rcpath); + + debug_print(_("Configuration is saved.\n")); +} + +gint prefs_file_write_param(PrefFile *pfile, PrefParam *param) +{ + gint i; + gchar buf[PREFSBUFSIZE]; + + for (i = 0; param[i].name != NULL; i++) { + switch (param[i].type) { + case P_STRING: + g_snprintf(buf, sizeof(buf), "%s=%s\n", param[i].name, + *((gchar **)param[i].data) ? + *((gchar **)param[i].data) : ""); + break; + case P_INT: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gint *)param[i].data)); + break; + case P_BOOL: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gboolean *)param[i].data)); + break; + case P_ENUM: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((DummyEnum *)param[i].data)); + break; + case P_USHORT: + g_snprintf(buf, sizeof(buf), "%s=%d\n", param[i].name, + *((gushort *)param[i].data)); + break; + default: + buf[0] = '\0'; + } + + if (buf[0] != '\0') { + if (fputs(buf, pfile->fp) == EOF) { + perror("fputs"); + return -1; + } + } + } + + return 0; +} + +PrefFile *prefs_file_open(const gchar *path) +{ + PrefFile *pfile; + gchar *tmppath; + FILE *fp; + + g_return_val_if_fail(path != NULL, NULL); + + tmppath = g_strconcat(path, ".tmp", NULL); + if ((fp = fopen(tmppath, "wb")) == NULL) { + FILE_OP_ERROR(tmppath, "fopen"); + g_free(tmppath); + return NULL; + } + + if (change_file_mode_rw(fp, tmppath) < 0) + FILE_OP_ERROR(tmppath, "chmod"); + + g_free(tmppath); + + pfile = g_new(PrefFile, 1); + pfile->fp = fp; + pfile->path = g_strdup(path); + + return pfile; +} + +gint prefs_file_close(PrefFile *pfile) +{ + FILE *fp; + gchar *path; + gchar *tmppath; + gchar *bakpath = NULL; + + g_return_val_if_fail(pfile != NULL, -1); + + fp = pfile->fp; + path = pfile->path; + g_free(pfile); + + tmppath = g_strconcat(path, ".tmp", NULL); + if (fclose(fp) == EOF) { + FILE_OP_ERROR(tmppath, "fclose"); + unlink(tmppath); + g_free(path); + g_free(tmppath); + return -1; + } + + if (is_file_exist(path)) { + bakpath = g_strconcat(path, ".bak", NULL); + if (rename(path, bakpath) < 0) { + FILE_OP_ERROR(path, "rename"); + unlink(tmppath); + g_free(path); + g_free(tmppath); + g_free(bakpath); + return -1; + } + } + + if (rename(tmppath, path) < 0) { + FILE_OP_ERROR(tmppath, "rename"); + unlink(tmppath); + g_free(path); + g_free(tmppath); + g_free(bakpath); + return -1; + } + + g_free(path); + g_free(tmppath); + g_free(bakpath); + return 0; +} + +gint prefs_file_close_revert(PrefFile *pfile) +{ + gchar *tmppath; + + g_return_val_if_fail(pfile != NULL, -1); + + tmppath = g_strconcat(pfile->path, ".tmp", NULL); + fclose(pfile->fp); + if (unlink(tmppath) < 0) FILE_OP_ERROR(tmppath, "unlink"); + g_free(tmppath); + g_free(pfile->path); + g_free(pfile); + + return 0; +} + +void prefs_set_default(PrefParam *param) +{ + gint i; + + g_return_if_fail(param != NULL); + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].data) continue; + + switch (param[i].type) { + case P_STRING: + if (param[i].defval != NULL) { + if (!strncasecmp(param[i].defval, "ENV_", 4)) { + const gchar *envstr; + gchar *tmp = NULL; + + envstr = g_getenv(param[i].defval + 4); + if (envstr) { + tmp = conv_codeset_strdup + (envstr, + conv_get_locale_charset_str(), + CS_UTF_8); + if (!tmp) { + g_warning("failed to convert character set."); + tmp = g_strdup(envstr); + } + } + *((gchar **)param[i].data) = tmp; + } else if (param[i].defval[0] == '~') + *((gchar **)param[i].data) = + g_strconcat(get_home_dir(), + param[i].defval + 1, + NULL); + else if (param[i].defval[0] != '\0') + *((gchar **)param[i].data) = + g_strdup(param[i].defval); + else + *((gchar **)param[i].data) = NULL; + } else + *((gchar **)param[i].data) = NULL; + break; + case P_INT: + if (param[i].defval != NULL) + *((gint *)param[i].data) = + (gint)atoi(param[i].defval); + else + *((gint *)param[i].data) = 0; + break; + case P_BOOL: + if (param[i].defval != NULL) { + if (!strcasecmp(param[i].defval, "TRUE")) + *((gboolean *)param[i].data) = TRUE; + else + *((gboolean *)param[i].data) = + atoi(param[i].defval) ? TRUE : FALSE; + } else + *((gboolean *)param[i].data) = FALSE; + break; + case P_ENUM: + if (param[i].defval != NULL) + *((DummyEnum*)param[i].data) = + (DummyEnum)atoi(param[i].defval); + else + *((DummyEnum *)param[i].data) = 0; + break; + case P_USHORT: + if (param[i].defval != NULL) + *((gushort *)param[i].data) = + (gushort)atoi(param[i].defval); + else + *((gushort *)param[i].data) = 0; + break; + default: + break; + } + } +} + +void prefs_free(PrefParam *param) +{ + gint i; + + g_return_if_fail(param != NULL); + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].data) continue; + + switch (param[i].type) { + case P_STRING: + g_free(*((gchar **)param[i].data)); + break; + default: + break; + } + } +} + +void prefs_dialog_create(PrefsDialog *dialog) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *notebook; + + GtkWidget *confirm_area; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *apply_btn; + + g_return_if_fail(dialog != NULL); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + gtk_window_set_policy (GTK_WINDOW(window), FALSE, TRUE, FALSE); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show(vbox); + gtk_container_add (GTK_CONTAINER (window), vbox); + + notebook = gtk_notebook_new (); + gtk_widget_show(notebook); + gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (notebook), 2); + /* GTK_WIDGET_UNSET_FLAGS (notebook, GTK_CAN_FOCUS); */ + gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE); + + gtkut_button_set_create(&confirm_area, + &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), + &apply_btn, _("Apply")); + gtk_widget_show(confirm_area); + gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + dialog->window = window; + dialog->notebook = notebook; + dialog->ok_btn = ok_btn; + dialog->cancel_btn = cancel_btn; + dialog->apply_btn = apply_btn; +} + +void prefs_dialog_destroy(PrefsDialog *dialog) +{ + gtk_widget_destroy(dialog->window); + dialog->window = NULL; + dialog->notebook = NULL; + dialog->ok_btn = NULL; + dialog->cancel_btn = NULL; + dialog->apply_btn = NULL; +} + +void prefs_button_toggled(GtkToggleButton *toggle_btn, GtkWidget *widget) +{ + gboolean is_active; + + is_active = gtk_toggle_button_get_active(toggle_btn); + gtk_widget_set_sensitive(widget, is_active); +} + +void prefs_set_dialog(PrefParam *param) +{ + gint i; + + for (i = 0; param[i].name != NULL; i++) { + if (param[i].widget_set_func) + param[i].widget_set_func(¶m[i]); + } +} + +void prefs_set_data_from_dialog(PrefParam *param) +{ + gint i; + + for (i = 0; param[i].name != NULL; i++) { + if (param[i].data_set_func) + param[i].data_set_func(¶m[i]); + } +} + +void prefs_set_dialog_to_default(PrefParam *param) +{ + gint i; + PrefParam tmpparam; + gchar *str_data = NULL; + gint int_data; + gushort ushort_data; + gboolean bool_data; + DummyEnum enum_data; + + for (i = 0; param[i].name != NULL; i++) { + if (!param[i].widget_set_func) continue; + + tmpparam = param[i]; + + switch (tmpparam.type) { + case P_STRING: +#warning FIXME_GTK2 + if (tmpparam.defval) { + if (!strncasecmp(tmpparam.defval, "ENV_", 4)) { + str_data = g_strdup(g_getenv(param[i].defval + 4)); + tmpparam.data = &str_data; + break; + } else if (tmpparam.defval[0] == '~') { + str_data = + g_strconcat(get_home_dir(), + param[i].defval + 1, + NULL); + tmpparam.data = &str_data; + break; + } + } + tmpparam.data = &tmpparam.defval; + break; + case P_INT: + if (tmpparam.defval) + int_data = atoi(tmpparam.defval); + else + int_data = 0; + tmpparam.data = &int_data; + break; + case P_USHORT: + if (tmpparam.defval) + ushort_data = atoi(tmpparam.defval); + else + ushort_data = 0; + tmpparam.data = &ushort_data; + break; + case P_BOOL: + if (tmpparam.defval) { + if (!strcasecmp(tmpparam.defval, "TRUE")) + bool_data = TRUE; + else + bool_data = atoi(tmpparam.defval) + ? TRUE : FALSE; + } else + bool_data = FALSE; + tmpparam.data = &bool_data; + break; + case P_ENUM: + if (tmpparam.defval) + enum_data = (DummyEnum)atoi(tmpparam.defval); + else + enum_data = 0; + tmpparam.data = &enum_data; + break; + case P_OTHER: + break; + } + tmpparam.widget_set_func(&tmpparam); + g_free(str_data); + str_data = NULL; + } +} + +void prefs_set_data_from_entry(PrefParam *pparam) +{ + gchar **str; + const gchar *entry_str; + + g_return_if_fail(*pparam->widget != NULL); + + entry_str = gtk_entry_get_text(GTK_ENTRY(*pparam->widget)); + + switch (pparam->type) { + case P_STRING: + str = (gchar **)pparam->data; + g_free(*str); + *str = entry_str[0] ? g_strdup(entry_str) : NULL; + break; + case P_USHORT: + *((gushort *)pparam->data) = atoi(entry_str); + break; + case P_INT: + *((gint *)pparam->data) = atoi(entry_str); + break; + default: + g_warning("Invalid PrefType for GtkEntry widget: %d\n", + pparam->type); + } +} + +void prefs_set_entry(PrefParam *pparam) +{ + gchar **str; + + g_return_if_fail(*pparam->widget != NULL); + + switch (pparam->type) { + case P_STRING: + str = (gchar **)pparam->data; + gtk_entry_set_text(GTK_ENTRY(*pparam->widget), + *str ? *str : ""); + break; + case P_INT: + gtk_entry_set_text(GTK_ENTRY(*pparam->widget), + itos(*((gint *)pparam->data))); + break; + case P_USHORT: + gtk_entry_set_text(GTK_ENTRY(*pparam->widget), + itos(*((gushort *)pparam->data))); + break; + default: + g_warning("Invalid PrefType for GtkEntry widget: %d\n", + pparam->type); + } +} + +void prefs_set_data_from_text(PrefParam *pparam) +{ + gchar **str; + gchar *text = NULL, *tp = NULL; + gchar *tmp, *tmpp; + + g_return_if_fail(*pparam->widget != NULL); + g_return_if_fail(GTK_IS_EDITABLE(*pparam->widget) || + GTK_IS_TEXT_VIEW(*pparam->widget)); + + switch (pparam->type) { + case P_STRING: + str = (gchar **)pparam->data; + g_free(*str); + if (GTK_IS_EDITABLE(*pparam->widget)) { + tp = text = gtk_editable_get_chars + (GTK_EDITABLE(*pparam->widget), 0, -1); + } else if (GTK_IS_TEXT_VIEW(*pparam->widget)) { + GtkTextView *textview = GTK_TEXT_VIEW(*pparam->widget); + GtkTextBuffer *buffer; + GtkTextIter start, end; + + buffer = gtk_text_view_get_buffer(textview); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_iter_at_offset(buffer, &end, -1); + tp = text = gtk_text_buffer_get_text + (buffer, &start, &end, FALSE); + } + + g_return_if_fail(tp != NULL && text != NULL); + + if (text[0] == '\0') { + *str = NULL; + g_free(text); + break; + } + + Xalloca(tmpp = tmp, strlen(text) * 2 + 1, + { *str = NULL; break; }); + while (*tp) { + if (*tp == '\n') { + *tmpp++ = '\\'; + *tmpp++ = 'n'; + tp++; + } else + *tmpp++ = *tp++; + } + *tmpp = '\0'; + *str = g_strdup(tmp); + g_free(text); + break; + default: + g_warning("Invalid PrefType for GtkTextView widget: %d\n", + pparam->type); + } +} + +void prefs_set_text(PrefParam *pparam) +{ + gchar *buf, *sp, *bufp; + gchar **str; + GtkTextView *text; + GtkTextBuffer *buffer; + GtkTextIter iter; + + g_return_if_fail(*pparam->widget != NULL); + + switch (pparam->type) { + case P_STRING: + str = (gchar **)pparam->data; + if (*str) { + bufp = buf = alloca(strlen(*str) + 1); + if (!buf) buf = ""; + else { + sp = *str; + while (*sp) { + if (*sp == '\\' && *(sp + 1) == 'n') { + *bufp++ = '\n'; + sp += 2; + } else + *bufp++ = *sp++; + } + *bufp = '\0'; + } + } else + buf = ""; + + text = GTK_TEXT_VIEW(*pparam->widget); + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_set_text(buffer, "", 0); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_insert(buffer, &iter, buf, -1); + break; + default: + g_warning("Invalid PrefType for GtkTextView widget: %d\n", + pparam->type); + } +} + +void prefs_set_data_from_toggle(PrefParam *pparam) +{ + g_return_if_fail(pparam->type == P_BOOL); + g_return_if_fail(*pparam->widget != NULL); + + *((gboolean *)pparam->data) = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(*pparam->widget)); +} + +void prefs_set_toggle(PrefParam *pparam) +{ + g_return_if_fail(pparam->type == P_BOOL); + g_return_if_fail(*pparam->widget != NULL); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(*pparam->widget), + *((gboolean *)pparam->data)); +} + +void prefs_set_data_from_spinbtn(PrefParam *pparam) +{ + g_return_if_fail(*pparam->widget != NULL); + + switch (pparam->type) { + case P_INT: + *((gint *)pparam->data) = + gtk_spin_button_get_value_as_int + (GTK_SPIN_BUTTON(*pparam->widget)); + break; + case P_USHORT: + *((gushort *)pparam->data) = + (gushort)gtk_spin_button_get_value_as_int + (GTK_SPIN_BUTTON(*pparam->widget)); + break; + default: + g_warning("Invalid PrefType for GtkSpinButton widget: %d\n", + pparam->type); + } +} + +void prefs_set_spinbtn(PrefParam *pparam) +{ + g_return_if_fail(*pparam->widget != NULL); + + switch (pparam->type) { + case P_INT: + gtk_spin_button_set_value(GTK_SPIN_BUTTON(*pparam->widget), + (gfloat)*((gint *)pparam->data)); + break; + case P_USHORT: + gtk_spin_button_set_value(GTK_SPIN_BUTTON(*pparam->widget), + (gfloat)*((gushort *)pparam->data)); + break; + default: + g_warning("Invalid PrefType for GtkSpinButton widget: %d\n", + pparam->type); + } +} diff --git a/src/prefs.h b/src/prefs.h new file mode 100644 index 00000000..987269b0 --- /dev/null +++ b/src/prefs.h @@ -0,0 +1,169 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_H__ +#define __PREFS_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _PrefParam PrefParam; +typedef struct _PrefFile PrefFile; +typedef struct _PrefsDialog PrefsDialog; + +#include "account.h" + +#define VSPACING 10 +#define VSPACING_NARROW 4 +#define VSPACING_NARROW_2 2 +#define VBOX_BORDER 16 +#define DEFAULT_ENTRY_WIDTH 80 +#define PREFSBUFSIZE 1024 + +typedef enum +{ + P_STRING, + P_INT, + P_BOOL, + P_ENUM, + P_USHORT, + P_OTHER +} PrefType; + +typedef void (*DataSetFunc) (PrefParam *pparam); +typedef void (*WidgetSetFunc) (PrefParam *pparam); + +struct _PrefParam { + gchar *name; + gchar *defval; + gpointer data; + PrefType type; + GtkWidget **widget; + DataSetFunc data_set_func; + WidgetSetFunc widget_set_func; +}; + +struct _PrefFile { + FILE *fp; + gchar *path; +}; + +struct _PrefsDialog +{ + GtkWidget *window; + GtkWidget *notebook; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *apply_btn; +}; + +#define SET_NOTEBOOK_LABEL(notebook, str, page_num) \ +{ \ + GtkWidget *label; \ + \ + label = gtk_label_new (str); \ + gtk_widget_show (label); \ + gtk_notebook_set_tab_label \ + (GTK_NOTEBOOK (notebook), \ + gtk_notebook_get_nth_page \ + (GTK_NOTEBOOK (notebook), page_num), \ + label); \ +} + +#define PACK_CHECK_BUTTON(box, chkbtn, label) \ +{ \ + chkbtn = gtk_check_button_new_with_label(label); \ + gtk_widget_show(chkbtn); \ + gtk_box_pack_start(GTK_BOX(box), chkbtn, FALSE, TRUE, 0); \ +} + +#define PACK_END_CHECK_BUTTON(box, chkbtn, label) \ +{ \ + chkbtn = gtk_check_button_new_with_label(label); \ + gtk_widget_show(chkbtn); \ + gtk_box_pack_end(GTK_BOX(box), chkbtn, FALSE, TRUE, 0); \ +} + +#define PACK_FRAME(box, frame, label) \ +{ \ + frame = gtk_frame_new(label); \ + gtk_widget_show(frame); \ + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); \ + gtk_frame_set_label_align(GTK_FRAME(frame), 0.01, 0.5); \ +} + +#define PACK_VSPACER(box, vbox, spacing) \ +{ \ + vbox = gtk_vbox_new(FALSE, 0); \ + gtk_widget_show(vbox); \ + gtk_box_pack_start(GTK_BOX(box), vbox, FALSE, TRUE, spacing); \ +} + +#define SET_TOGGLE_SENSITIVITY(togglewid, targetwid) \ +{ \ + gtk_widget_set_sensitive(targetwid, FALSE); \ + g_signal_connect(G_OBJECT(togglewid), "toggled", \ + G_CALLBACK(prefs_button_toggled), targetwid); \ +} + +void prefs_read_config (PrefParam *param, + const gchar *label, + const gchar *rcfile); +void prefs_config_parse_one_line(PrefParam *param, + const gchar *buf); +void prefs_write_config (PrefParam *param, + const gchar *label, + const gchar *rcfile); + +PrefFile *prefs_file_open (const gchar *path); +gint prefs_file_write_param (PrefFile *pfile, + PrefParam *param); +gint prefs_file_close (PrefFile *pfile); +gint prefs_file_close_revert (PrefFile *pfile); + +void prefs_set_default (PrefParam *param); +void prefs_free (PrefParam *param); + +void prefs_dialog_create (PrefsDialog *dialog); +void prefs_dialog_destroy (PrefsDialog *dialog); + +void prefs_button_toggled (GtkToggleButton *toggle_btn, + GtkWidget *widget); + +void prefs_set_dialog (PrefParam *param); +void prefs_set_data_from_dialog (PrefParam *param); +void prefs_set_dialog_to_default(PrefParam *param); + +void prefs_set_data_from_entry (PrefParam *pparam); +void prefs_set_entry (PrefParam *pparam); +void prefs_set_data_from_text (PrefParam *pparam); +void prefs_set_text (PrefParam *pparam); +void prefs_set_data_from_toggle (PrefParam *pparam); +void prefs_set_toggle (PrefParam *pparam); +void prefs_set_data_from_spinbtn(PrefParam *pparam); +void prefs_set_spinbtn (PrefParam *pparam); + +#endif /* __PREFS_H__ */ diff --git a/src/prefs_account.c b/src/prefs_account.c new file mode 100644 index 00000000..bcad14b9 --- /dev/null +++ b/src/prefs_account.c @@ -0,0 +1,2295 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "prefs.h" +#include "prefs_account.h" +#include "prefs_customheader.h" +#include "account.h" +#include "mainwindow.h" +#include "manage_window.h" +#include "foldersel.h" +#include "inc.h" +#include "menu.h" +#include "gtkutils.h" +#include "utils.h" +#include "alertpanel.h" +#include "smtp.h" +#include "imap.h" + +static gboolean cancelled; + +static PrefsAccount tmp_ac_prefs; + +static PrefsDialog dialog; + +static struct Basic { + GtkWidget *acname_entry; + GtkWidget *default_chkbtn; + + GtkWidget *name_entry; + GtkWidget *addr_entry; + GtkWidget *org_entry; + + GtkWidget *serv_frame; + GtkWidget *serv_table; + GtkWidget *protocol_optmenu; + GtkWidget *recvserv_label; + GtkWidget *smtpserv_label; + GtkWidget *nntpserv_label; + GtkWidget *recvserv_entry; + GtkWidget *smtpserv_entry; + GtkWidget *nntpserv_entry; + GtkWidget *nntpauth_chkbtn; + GtkWidget *uid_label; + GtkWidget *pass_label; + GtkWidget *uid_entry; + GtkWidget *pass_entry; +} basic; + +static struct Receive { + GtkWidget *pop3_frame; + GtkWidget *use_apop_chkbtn; + GtkWidget *rmmail_chkbtn; + GtkWidget *leave_time_entry; + GtkWidget *getall_chkbtn; + GtkWidget *size_limit_chkbtn; + GtkWidget *size_limit_entry; + GtkWidget *filter_on_recv_chkbtn; + GtkWidget *inbox_label; + GtkWidget *inbox_entry; + GtkWidget *inbox_btn; + + GtkWidget *imap_frame; + GtkWidget *imap_auth_type_optmenu; + + GtkWidget *recvatgetall_chkbtn; +} receive; + +static struct Send { + GtkWidget *date_chkbtn; + GtkWidget *msgid_chkbtn; + + GtkWidget *customhdr_chkbtn; + + GtkWidget *smtp_auth_chkbtn; + GtkWidget *smtp_auth_type_optmenu; + GtkWidget *smtp_uid_entry; + GtkWidget *smtp_pass_entry; + /* GtkWidget *pop_bfr_smtp_chkbtn; */ +} p_send; + +static struct Compose { + GtkWidget *sigfile_radiobtn; + GtkWidget *sigpath_entry; + + GtkWidget *autocc_chkbtn; + GtkWidget *autocc_entry; + GtkWidget *autobcc_chkbtn; + GtkWidget *autobcc_entry; + GtkWidget *autoreplyto_chkbtn; + GtkWidget *autoreplyto_entry; +} compose; + +#if USE_GPGME +static struct Privacy { + GtkWidget *default_encrypt_chkbtn; + GtkWidget *default_sign_chkbtn; + GtkWidget *ascii_armored_chkbtn; + GtkWidget *clearsign_chkbtn; + GtkWidget *defaultkey_radiobtn; + GtkWidget *emailkey_radiobtn; + GtkWidget *customkey_radiobtn; + GtkWidget *customkey_entry; +} privacy; +#endif /* USE_GPGME */ + +#if USE_SSL +static struct SSLPrefs { + GtkWidget *pop_frame; + GtkWidget *pop_nossl_radiobtn; + GtkWidget *pop_ssltunnel_radiobtn; + GtkWidget *pop_starttls_radiobtn; + + GtkWidget *imap_frame; + GtkWidget *imap_nossl_radiobtn; + GtkWidget *imap_ssltunnel_radiobtn; + GtkWidget *imap_starttls_radiobtn; + + GtkWidget *nntp_frame; + GtkWidget *nntp_nossl_radiobtn; + GtkWidget *nntp_ssltunnel_radiobtn; + + GtkWidget *send_frame; + GtkWidget *smtp_nossl_radiobtn; + GtkWidget *smtp_ssltunnel_radiobtn; + GtkWidget *smtp_starttls_radiobtn; + + GtkWidget *use_nonblocking_ssl_chkbtn; +} ssl; +#endif /* USE_SSL */ + +static struct Advanced { + GtkWidget *smtpport_chkbtn; + GtkWidget *smtpport_entry; + GtkWidget *popport_hbox; + GtkWidget *popport_chkbtn; + GtkWidget *popport_entry; + GtkWidget *imapport_hbox; + GtkWidget *imapport_chkbtn; + GtkWidget *imapport_entry; + GtkWidget *nntpport_hbox; + GtkWidget *nntpport_chkbtn; + GtkWidget *nntpport_entry; + GtkWidget *domain_chkbtn; + GtkWidget *domain_entry; + + GtkWidget *imap_frame; + GtkWidget *imapdir_entry; + + GtkWidget *sent_folder_chkbtn; + GtkWidget *sent_folder_entry; + GtkWidget *draft_folder_chkbtn; + GtkWidget *draft_folder_entry; + GtkWidget *trash_folder_chkbtn; + GtkWidget *trash_folder_entry; +} advanced; + +static void prefs_account_protocol_set_data_from_optmenu(PrefParam *pparam); +static void prefs_account_protocol_set_optmenu (PrefParam *pparam); +static void prefs_account_protocol_activated (GtkMenuItem *menuitem); + +static void prefs_account_imap_auth_type_set_data_from_optmenu + (PrefParam *pparam); +static void prefs_account_imap_auth_type_set_optmenu (PrefParam *pparam); +static void prefs_account_smtp_auth_type_set_data_from_optmenu + (PrefParam *pparam); +static void prefs_account_smtp_auth_type_set_optmenu (PrefParam *pparam); + +static void prefs_account_enum_set_data_from_radiobtn (PrefParam *pparam); +static void prefs_account_enum_set_radiobtn (PrefParam *pparam); + +#if USE_GPGME +static void prefs_account_ascii_armored_warning (GtkWidget *widget); +#endif /* USE_GPGME */ + +static PrefParam param[] = { + /* Basic */ + {"account_name", NULL, &tmp_ac_prefs.account_name, P_STRING, + &basic.acname_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"is_default", "FALSE", &tmp_ac_prefs.is_default, P_BOOL, + &basic.default_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"name", NULL, &tmp_ac_prefs.name, P_STRING, + &basic.name_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"address", NULL, &tmp_ac_prefs.address, P_STRING, + &basic.addr_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"organization", NULL, &tmp_ac_prefs.organization, P_STRING, + &basic.org_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"protocol", NULL, &tmp_ac_prefs.protocol, P_ENUM, + &basic.protocol_optmenu, + prefs_account_protocol_set_data_from_optmenu, + prefs_account_protocol_set_optmenu}, + + {"receive_server", NULL, &tmp_ac_prefs.recv_server, P_STRING, + &basic.recvserv_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"smtp_server", NULL, &tmp_ac_prefs.smtp_server, P_STRING, + &basic.smtpserv_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"nntp_server", NULL, &tmp_ac_prefs.nntp_server, P_STRING, + &basic.nntpserv_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"use_nntp_auth", "FALSE", &tmp_ac_prefs.use_nntp_auth, P_BOOL, + &basic.nntpauth_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"user_id", "ENV_USER", &tmp_ac_prefs.userid, P_STRING, + &basic.uid_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"password", NULL, &tmp_ac_prefs.passwd, P_STRING, + &basic.pass_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"inbox", "inbox", &tmp_ac_prefs.inbox, P_STRING, + &receive.inbox_entry, prefs_set_data_from_entry, prefs_set_entry}, + + /* Receive */ + {"use_apop_auth", "FALSE", &tmp_ac_prefs.use_apop_auth, P_BOOL, + &receive.use_apop_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"remove_mail", "TRUE", &tmp_ac_prefs.rmmail, P_BOOL, + &receive.rmmail_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"message_leave_time", "0", &tmp_ac_prefs.msg_leave_time, P_INT, + &receive.leave_time_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"get_all_mail", "FALSE", &tmp_ac_prefs.getall, P_BOOL, + &receive.getall_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"enable_size_limit", "FALSE", &tmp_ac_prefs.enable_size_limit, P_BOOL, + &receive.size_limit_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"size_limit", "1024", &tmp_ac_prefs.size_limit, P_INT, + &receive.size_limit_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"filter_on_receive", "TRUE", &tmp_ac_prefs.filter_on_recv, P_BOOL, + &receive.filter_on_recv_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"imap_auth_method", "0", &tmp_ac_prefs.imap_auth_type, P_ENUM, + &receive.imap_auth_type_optmenu, + prefs_account_imap_auth_type_set_data_from_optmenu, + prefs_account_imap_auth_type_set_optmenu}, + + {"receive_at_get_all", "TRUE", &tmp_ac_prefs.recv_at_getall, P_BOOL, + &receive.recvatgetall_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + /* Send */ + {"add_date", "TRUE", &tmp_ac_prefs.add_date, P_BOOL, + &p_send.date_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"generate_msgid", "TRUE", &tmp_ac_prefs.gen_msgid, P_BOOL, + &p_send.msgid_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"add_custom_header", "FALSE", &tmp_ac_prefs.add_customhdr, P_BOOL, + &p_send.customhdr_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"use_smtp_auth", "FALSE", &tmp_ac_prefs.use_smtp_auth, P_BOOL, + &p_send.smtp_auth_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"smtp_auth_method", "0", &tmp_ac_prefs.smtp_auth_type, P_ENUM, + &p_send.smtp_auth_type_optmenu, + prefs_account_smtp_auth_type_set_data_from_optmenu, + prefs_account_smtp_auth_type_set_optmenu}, + + {"smtp_user_id", NULL, &tmp_ac_prefs.smtp_userid, P_STRING, + &p_send.smtp_uid_entry, prefs_set_data_from_entry, prefs_set_entry}, + {"smtp_password", NULL, &tmp_ac_prefs.smtp_passwd, P_STRING, + &p_send.smtp_pass_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"pop_before_smtp", "FALSE", &tmp_ac_prefs.pop_before_smtp, P_BOOL, + NULL, NULL, NULL}, +#if 0 + &p_send.pop_bfr_smtp_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, +#endif + + /* Compose */ + {"signature_type", "0", &tmp_ac_prefs.sig_type, P_ENUM, + &compose.sigfile_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + {"signature_path", "~/"DEFAULT_SIGNATURE, &tmp_ac_prefs.sig_path, P_STRING, + &compose.sigpath_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_autocc", "FALSE", &tmp_ac_prefs.set_autocc, P_BOOL, + &compose.autocc_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"auto_cc", NULL, &tmp_ac_prefs.auto_cc, P_STRING, + &compose.autocc_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_autobcc", "FALSE", &tmp_ac_prefs.set_autobcc, P_BOOL, + &compose.autobcc_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"auto_bcc", NULL, &tmp_ac_prefs.auto_bcc, P_STRING, + &compose.autobcc_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_autoreplyto", "FALSE", &tmp_ac_prefs.set_autoreplyto, P_BOOL, + &compose.autoreplyto_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"auto_replyto", NULL, &tmp_ac_prefs.auto_replyto, P_STRING, + &compose.autoreplyto_entry, + prefs_set_data_from_entry, prefs_set_entry}, + +#if USE_GPGME + /* Privacy */ + {"default_encrypt", "FALSE", &tmp_ac_prefs.default_encrypt, P_BOOL, + &privacy.default_encrypt_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"default_sign", "FALSE", &tmp_ac_prefs.default_sign, P_BOOL, + &privacy.default_sign_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"ascii_armored", "FALSE", &tmp_ac_prefs.ascii_armored, P_BOOL, + &privacy.ascii_armored_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"clearsign", "FALSE", &tmp_ac_prefs.clearsign, P_BOOL, + &privacy.clearsign_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"sign_key", NULL, &tmp_ac_prefs.sign_key, P_ENUM, + &privacy.defaultkey_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + {"sign_key_id", NULL, &tmp_ac_prefs.sign_key_id, P_STRING, + &privacy.customkey_entry, + prefs_set_data_from_entry, prefs_set_entry}, +#endif /* USE_GPGME */ + +#if USE_SSL + /* SSL */ + {"ssl_pop", "0", &tmp_ac_prefs.ssl_pop, P_ENUM, + &ssl.pop_nossl_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + {"ssl_imap", "0", &tmp_ac_prefs.ssl_imap, P_ENUM, + &ssl.imap_nossl_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + {"ssl_nntp", "0", &tmp_ac_prefs.ssl_nntp, P_ENUM, + &ssl.nntp_nossl_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + {"ssl_smtp", "0", &tmp_ac_prefs.ssl_smtp, P_ENUM, + &ssl.smtp_nossl_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + + {"use_nonblocking_ssl", "1", &tmp_ac_prefs.use_nonblocking_ssl, P_BOOL, + &ssl.use_nonblocking_ssl_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, +#endif /* USE_SSL */ + + /* Advanced */ + {"set_smtpport", "FALSE", &tmp_ac_prefs.set_smtpport, P_BOOL, + &advanced.smtpport_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"smtp_port", "25", &tmp_ac_prefs.smtpport, P_USHORT, + &advanced.smtpport_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_popport", "FALSE", &tmp_ac_prefs.set_popport, P_BOOL, + &advanced.popport_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"pop_port", "110", &tmp_ac_prefs.popport, P_USHORT, + &advanced.popport_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_imapport", "FALSE", &tmp_ac_prefs.set_imapport, P_BOOL, + &advanced.imapport_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"imap_port", "143", &tmp_ac_prefs.imapport, P_USHORT, + &advanced.imapport_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_nntpport", "FALSE", &tmp_ac_prefs.set_nntpport, P_BOOL, + &advanced.nntpport_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"nntp_port", "119", &tmp_ac_prefs.nntpport, P_USHORT, + &advanced.nntpport_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_domain", "FALSE", &tmp_ac_prefs.set_domain, P_BOOL, + &advanced.domain_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"domain", NULL, &tmp_ac_prefs.domain, P_STRING, + &advanced.domain_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"imap_directory", NULL, &tmp_ac_prefs.imap_dir, P_STRING, + &advanced.imapdir_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"set_sent_folder", "FALSE", &tmp_ac_prefs.set_sent_folder, P_BOOL, + &advanced.sent_folder_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"sent_folder", NULL, &tmp_ac_prefs.sent_folder, P_STRING, + &advanced.sent_folder_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_draft_folder", "FALSE", &tmp_ac_prefs.set_draft_folder, P_BOOL, + &advanced.draft_folder_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"draft_folder", NULL, &tmp_ac_prefs.draft_folder, P_STRING, + &advanced.draft_folder_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"set_trash_folder", "FALSE", &tmp_ac_prefs.set_trash_folder, P_BOOL, + &advanced.trash_folder_chkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"trash_folder", NULL, &tmp_ac_prefs.trash_folder, P_STRING, + &advanced.trash_folder_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} +}; + +static gint prefs_account_get_new_id (void); + +static void prefs_account_create (void); +static void prefs_account_basic_create (void); +static void prefs_account_receive_create (void); +static void prefs_account_send_create (void); +static void prefs_account_compose_create (void); +#if USE_GPGME +static void prefs_account_privacy_create (void); +#endif /* USE_GPGME */ +#if USE_SSL +static void prefs_account_ssl_create (void); +#endif /* USE_SSL */ +static void prefs_account_advanced_create (void); + +static void prefs_account_select_folder_cb (GtkWidget *widget, + gpointer data); +static void prefs_account_edit_custom_header (void); + +static gint prefs_account_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_account_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_account_ok (void); +static gint prefs_account_apply (void); +static void prefs_account_cancel (void); + +PrefsAccount *prefs_account_new(void) +{ + PrefsAccount *ac_prefs; + + ac_prefs = g_new0(PrefsAccount, 1); + memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount)); + prefs_set_default(param); + *ac_prefs = tmp_ac_prefs; + ac_prefs->account_id = prefs_account_get_new_id(); + + return ac_prefs; +} + +void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label) +{ + const guchar *p = label; + gint id; + + g_return_if_fail(ac_prefs != NULL); + g_return_if_fail(label != NULL); + + memset(&tmp_ac_prefs, 0, sizeof(PrefsAccount)); + prefs_read_config(param, label, ACCOUNT_RC); + *ac_prefs = tmp_ac_prefs; + while (*p && !isdigit(*p)) p++; + id = atoi(p); + if (id < 0) g_warning("wrong account id: %d\n", id); + ac_prefs->account_id = id; + + if (ac_prefs->protocol == A_APOP) { + debug_print("converting protocol A_APOP to new prefs.\n"); + ac_prefs->protocol = A_POP3; + ac_prefs->use_apop_auth = TRUE; + } + + prefs_custom_header_read_config(ac_prefs); +} + +void prefs_account_write_config_all(GList *account_list) +{ + GList *cur; + gchar *rcpath; + PrefFile *pfile; + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACCOUNT_RC, NULL); + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_free(rcpath); + return; + } + g_free(rcpath); + + for (cur = account_list; cur != NULL; cur = cur->next) { + tmp_ac_prefs = *(PrefsAccount *)cur->data; + if (fprintf(pfile->fp, "[Account: %d]\n", + tmp_ac_prefs.account_id) <= 0 || + prefs_file_write_param(pfile, param) < 0) { + g_warning(_("failed to write configuration to file\n")); + prefs_file_close_revert(pfile); + return; + } + if (cur->next) { + if (fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputc"); + prefs_file_close_revert(pfile); + return; + } + } + } + + if (prefs_file_close(pfile) < 0) + g_warning(_("failed to write configuration to file\n")); +} + +void prefs_account_free(PrefsAccount *ac_prefs) +{ + if (!ac_prefs) return; + + tmp_ac_prefs = *ac_prefs; + prefs_free(param); +} + +static gint prefs_account_get_new_id(void) +{ + GList *ac_list; + PrefsAccount *ac; + static gint last_id = 0; + + for (ac_list = account_get_list(); ac_list != NULL; + ac_list = ac_list->next) { + ac = (PrefsAccount *)ac_list->data; + if (last_id < ac->account_id) + last_id = ac->account_id; + } + + return last_id + 1; +} + +PrefsAccount *prefs_account_open(PrefsAccount *ac_prefs) +{ + gboolean new_account = FALSE; + + debug_print(_("Opening account preferences window...\n")); + + inc_lock(); + + cancelled = FALSE; + + if (!ac_prefs) { + ac_prefs = prefs_account_new(); + new_account = TRUE; + } + + if (!dialog.window) { + prefs_account_create(); + } + + manage_window_set_transient(GTK_WINDOW(dialog.window)); + gtk_notebook_set_current_page(GTK_NOTEBOOK(dialog.notebook), 0); + gtk_widget_grab_focus(dialog.ok_btn); + + tmp_ac_prefs = *ac_prefs; + + if (new_account) { + PrefsAccount *def_ac; + gchar *buf; + + prefs_set_dialog_to_default(param); + buf = g_strdup_printf(_("Account%d"), ac_prefs->account_id); + gtk_entry_set_text(GTK_ENTRY(basic.acname_entry), buf); + g_free(buf); + def_ac = account_get_default(); + if (def_ac) { + gtk_entry_set_text(GTK_ENTRY(basic.name_entry), + def_ac->name ? def_ac->name : ""); + gtk_entry_set_text(GTK_ENTRY(basic.addr_entry), + def_ac->address ? def_ac->address : ""); + gtk_entry_set_text(GTK_ENTRY(basic.org_entry), + def_ac->organization ? def_ac->organization : ""); + } + menu_set_sensitive_all + (GTK_MENU_SHELL + (gtk_option_menu_get_menu + (GTK_OPTION_MENU + (basic.protocol_optmenu))), + TRUE); + gtk_window_set_title(GTK_WINDOW(dialog.window), + _("Preferences for new account")); + gtk_widget_hide(dialog.apply_btn); + } else { + prefs_set_dialog(param); + gtk_window_set_title(GTK_WINDOW(dialog.window), + _("Account preferences")); + gtk_widget_show(dialog.apply_btn); + } + + gtk_widget_show(dialog.window); + gtk_main(); + gtk_widget_hide(dialog.window); + + inc_unlock(); + + if (cancelled && new_account) { + g_free(ac_prefs); + return NULL; + } else { + *ac_prefs = tmp_ac_prefs; + return ac_prefs; + } +} + +static void prefs_account_create(void) +{ + gint page = 0; + + debug_print(_("Creating account preferences window...\n")); + + /* create dialog */ + prefs_dialog_create(&dialog); + g_signal_connect(G_OBJECT(dialog.window), "delete_event", + G_CALLBACK(prefs_account_deleted), NULL); + g_signal_connect(G_OBJECT(dialog.window), "key_press_event", + G_CALLBACK(prefs_account_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(dialog.window); + + g_signal_connect(G_OBJECT(dialog.ok_btn), "clicked", + G_CALLBACK(prefs_account_ok), NULL); + g_signal_connect(G_OBJECT(dialog.apply_btn), "clicked", + G_CALLBACK(prefs_account_apply), NULL); + g_signal_connect(G_OBJECT(dialog.cancel_btn), "clicked", + G_CALLBACK(prefs_account_cancel), NULL); + + prefs_account_basic_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Basic"), page++); + prefs_account_receive_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Receive"), page++); + prefs_account_send_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Send"), page++); + prefs_account_compose_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Compose"), page++); +#if USE_GPGME + prefs_account_privacy_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++); +#endif /* USE_GPGME */ +#if USE_SSL + prefs_account_ssl_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("SSL"), page++); +#endif /* USE_SSL */ + prefs_account_advanced_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Advanced"), page++); +} + +#define SET_ACTIVATE(menuitem) \ +{ \ + g_signal_connect(G_OBJECT(menuitem), "activate", \ + G_CALLBACK(prefs_account_protocol_activated), NULL); \ +} + +static void prefs_account_basic_create(void) +{ + GtkWidget *vbox1; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *acname_entry; + GtkWidget *default_chkbtn; + GtkWidget *frame1; + GtkWidget *table1; + GtkWidget *name_entry; + GtkWidget *addr_entry; + GtkWidget *org_entry; + + GtkWidget *serv_frame; + GtkWidget *vbox2; + GtkWidget *optmenu; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *serv_table; + GtkWidget *recvserv_label; + GtkWidget *smtpserv_label; + GtkWidget *nntpserv_label; + GtkWidget *recvserv_entry; + GtkWidget *smtpserv_entry; + GtkWidget *nntpserv_entry; + GtkWidget *nntpauth_chkbtn; + GtkWidget *uid_label; + GtkWidget *pass_label; + GtkWidget *uid_entry; + GtkWidget *pass_entry; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox1), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Name of this account")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + acname_entry = gtk_entry_new (); + gtk_widget_show (acname_entry); + gtk_widget_set_size_request (acname_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start (GTK_BOX (hbox), acname_entry, TRUE, TRUE, 0); + + default_chkbtn = gtk_check_button_new_with_label (_("Set as default")); + gtk_widget_show (default_chkbtn); + gtk_box_pack_end (GTK_BOX (hbox), default_chkbtn, FALSE, FALSE, 0); + + PACK_FRAME (vbox1, frame1, _("Personal information")); + + table1 = gtk_table_new (3, 2, FALSE); + gtk_widget_show (table1); + gtk_container_add (GTK_CONTAINER (frame1), table1); + gtk_container_set_border_width (GTK_CONTAINER (table1), 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), VSPACING_NARROW); + gtk_table_set_col_spacings (GTK_TABLE (table1), 8); + + label = gtk_label_new (_("Full name")); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + label = gtk_label_new (_("Mail address")); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + label = gtk_label_new (_("Organization")); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 0, 1, 2, 3, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + name_entry = gtk_entry_new (); + gtk_widget_show (name_entry); + gtk_table_attach (GTK_TABLE (table1), name_entry, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + addr_entry = gtk_entry_new (); + gtk_widget_show (addr_entry); + gtk_table_attach (GTK_TABLE (table1), addr_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + org_entry = gtk_entry_new (); + gtk_widget_show (org_entry); + gtk_table_attach (GTK_TABLE (table1), org_entry, 1, 2, 2, 3, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + PACK_FRAME (vbox1, serv_frame, _("Server information")); + + vbox2 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (serv_frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (_("Protocol")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + optmenu = gtk_option_menu_new (); + gtk_widget_show (optmenu); + gtk_box_pack_start (GTK_BOX (hbox), optmenu, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new (); + + MENUITEM_ADD (optmenu_menu, menuitem, _("POP3"), A_POP3); + SET_ACTIVATE (menuitem); + MENUITEM_ADD (optmenu_menu, menuitem, _("IMAP4"), A_IMAP4); + SET_ACTIVATE (menuitem); + MENUITEM_ADD (optmenu_menu, menuitem, _("News (NNTP)"), A_NNTP); + SET_ACTIVATE (menuitem); + MENUITEM_ADD (optmenu_menu, menuitem, _("None (local)"), A_LOCAL); + SET_ACTIVATE (menuitem); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (optmenu), optmenu_menu); + + serv_table = gtk_table_new (6, 4, FALSE); + gtk_widget_show (serv_table); + gtk_box_pack_start (GTK_BOX (vbox2), serv_table, FALSE, FALSE, 0); + gtk_table_set_row_spacings (GTK_TABLE (serv_table), VSPACING_NARROW); + gtk_table_set_row_spacing (GTK_TABLE (serv_table), 3, 0); + gtk_table_set_col_spacings (GTK_TABLE (serv_table), 8); + + nntpauth_chkbtn = gtk_check_button_new_with_label + (_("This server requires authentication")); + gtk_widget_show (nntpauth_chkbtn); + gtk_table_attach (GTK_TABLE (serv_table), nntpauth_chkbtn, 0, 4, 4, 5, + GTK_FILL, 0, 0, 0); + + nntpserv_entry = gtk_entry_new (); + gtk_widget_show (nntpserv_entry); + gtk_table_attach (GTK_TABLE (serv_table), nntpserv_entry, 1, 4, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + gtk_table_set_row_spacing (GTK_TABLE (serv_table), 0, 0); + + recvserv_entry = gtk_entry_new (); + gtk_widget_show (recvserv_entry); + gtk_table_attach (GTK_TABLE (serv_table), recvserv_entry, 1, 4, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + smtpserv_entry = gtk_entry_new (); + gtk_widget_show (smtpserv_entry); + gtk_table_attach (GTK_TABLE (serv_table), smtpserv_entry, 1, 4, 2, 3, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + uid_entry = gtk_entry_new (); + gtk_widget_show (uid_entry); + gtk_widget_set_size_request (uid_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_table_attach (GTK_TABLE (serv_table), uid_entry, 1, 2, 5, 6, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + pass_entry = gtk_entry_new (); + gtk_widget_show (pass_entry); + gtk_widget_set_size_request (pass_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_table_attach (GTK_TABLE (serv_table), pass_entry, 3, 4, 5, 6, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + gtk_entry_set_visibility (GTK_ENTRY (pass_entry), FALSE); + + nntpserv_label = gtk_label_new (_("News server")); + gtk_widget_show (nntpserv_label); + gtk_table_attach (GTK_TABLE (serv_table), nntpserv_label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (nntpserv_label), 1, 0.5); + + recvserv_label = gtk_label_new (_("Server for receiving")); + gtk_widget_show (recvserv_label); + gtk_table_attach (GTK_TABLE (serv_table), recvserv_label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (recvserv_label), 1, 0.5); + + smtpserv_label = gtk_label_new (_("SMTP server (send)")); + gtk_widget_show (smtpserv_label); + gtk_table_attach (GTK_TABLE (serv_table), smtpserv_label, 0, 1, 2, 3, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (smtpserv_label), 1, 0.5); + gtk_table_set_row_spacing (GTK_TABLE (serv_table), 2, 0); + + uid_label = gtk_label_new (_("User ID")); + gtk_widget_show (uid_label); + gtk_table_attach (GTK_TABLE (serv_table), uid_label, 0, 1, 5, 6, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (uid_label), 1, 0.5); + + pass_label = gtk_label_new (_("Password")); + gtk_widget_show (pass_label); + gtk_table_attach (GTK_TABLE (serv_table), pass_label, 2, 3, 5, 6, + 0, 0, 0, 0); + + SET_TOGGLE_SENSITIVITY (nntpauth_chkbtn, uid_label); + SET_TOGGLE_SENSITIVITY (nntpauth_chkbtn, pass_label); + SET_TOGGLE_SENSITIVITY (nntpauth_chkbtn, uid_entry); + SET_TOGGLE_SENSITIVITY (nntpauth_chkbtn, pass_entry); + + basic.acname_entry = acname_entry; + basic.default_chkbtn = default_chkbtn; + + basic.name_entry = name_entry; + basic.addr_entry = addr_entry; + basic.org_entry = org_entry; + + basic.serv_frame = serv_frame; + basic.serv_table = serv_table; + basic.protocol_optmenu = optmenu; + basic.recvserv_label = recvserv_label; + basic.recvserv_entry = recvserv_entry; + basic.smtpserv_label = smtpserv_label; + basic.smtpserv_entry = smtpserv_entry; + basic.nntpserv_label = nntpserv_label; + basic.nntpserv_entry = nntpserv_entry; + basic.nntpauth_chkbtn = nntpauth_chkbtn; + basic.uid_label = uid_label; + basic.pass_label = pass_label; + basic.uid_entry = uid_entry; + basic.pass_entry = pass_entry; +} + +static void prefs_account_receive_create(void) +{ + GtkWidget *vbox1; + GtkWidget *frame1; + GtkWidget *vbox2; + GtkWidget *use_apop_chkbtn; + GtkWidget *rmmail_chkbtn; + GtkWidget *hbox_spc; + GtkWidget *leave_time_label; + GtkWidget *leave_time_entry; + GtkWidget *getall_chkbtn; + GtkWidget *hbox1; + GtkWidget *size_limit_chkbtn; + GtkWidget *size_limit_entry; + GtkWidget *label; + GtkWidget *filter_on_recv_chkbtn; + GtkWidget *vbox3; + GtkWidget *inbox_label; + GtkWidget *inbox_entry; + GtkWidget *inbox_btn; + GtkWidget *imap_frame; + GtkWidget *optmenu; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *recvatgetall_chkbtn; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME (vbox1, frame1, _("POP3")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame1), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + PACK_CHECK_BUTTON (vbox2, use_apop_chkbtn, + _("Use secure authentication (APOP)")); + + PACK_CHECK_BUTTON (vbox2, rmmail_chkbtn, + _("Remove messages on server when received")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + leave_time_label = gtk_label_new (_("Remove after")); + gtk_widget_show (leave_time_label); + gtk_box_pack_start (GTK_BOX (hbox1), leave_time_label, FALSE, FALSE, 0); + + leave_time_entry = gtk_entry_new (); + gtk_widget_show (leave_time_entry); + gtk_widget_set_size_request (leave_time_entry, 64, -1); + gtk_box_pack_start (GTK_BOX (hbox1), leave_time_entry, FALSE, FALSE, 0); + + leave_time_label = gtk_label_new (_("days")); + gtk_widget_show (leave_time_label); + gtk_box_pack_start (GTK_BOX (hbox1), leave_time_label, FALSE, FALSE, 0); + + SET_TOGGLE_SENSITIVITY (rmmail_chkbtn, hbox1); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW_2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + leave_time_label = gtk_label_new (_("(0 days: remove immediately)")); + gtk_widget_show (leave_time_label); + gtk_box_pack_start (GTK_BOX (hbox1), leave_time_label, FALSE, FALSE, 0); + + SET_TOGGLE_SENSITIVITY (rmmail_chkbtn, hbox1); + + PACK_CHECK_BUTTON (vbox2, getall_chkbtn, + _("Download all messages on server")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox1, size_limit_chkbtn, _("Receive size limit")); + + size_limit_entry = gtk_entry_new (); + gtk_widget_show (size_limit_entry); + gtk_widget_set_size_request (size_limit_entry, 64, -1); + gtk_box_pack_start (GTK_BOX (hbox1), size_limit_entry, FALSE, FALSE, 0); + + label = gtk_label_new (_("KB")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + + SET_TOGGLE_SENSITIVITY (size_limit_chkbtn, size_limit_entry); + + PACK_CHECK_BUTTON (vbox2, filter_on_recv_chkbtn, + _("Filter messages on receiving")); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW_2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + inbox_label = gtk_label_new (_("Default inbox")); + gtk_widget_show (inbox_label); + gtk_box_pack_start (GTK_BOX (hbox1), inbox_label, FALSE, FALSE, 0); + + inbox_entry = gtk_entry_new (); + gtk_widget_show (inbox_entry); + gtk_widget_set_size_request (inbox_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start (GTK_BOX (hbox1), inbox_entry, TRUE, TRUE, 0); + + inbox_btn = gtk_button_new_with_label (_(" Select... ")); + gtk_widget_show (inbox_btn); + gtk_box_pack_start (GTK_BOX (hbox1), inbox_btn, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (inbox_btn), "clicked", + G_CALLBACK (prefs_account_select_folder_cb), + inbox_entry); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW_2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + label = gtk_label_new + (_("(Unfiltered messages will be stored in this folder)")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + PACK_FRAME (vbox1, imap_frame, _("IMAP4")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (imap_frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + label = gtk_label_new (_("Authentication method")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + + optmenu = gtk_option_menu_new (); + gtk_widget_show (optmenu); + gtk_box_pack_start (GTK_BOX (hbox1), optmenu, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new (); + + MENUITEM_ADD (optmenu_menu, menuitem, _("Automatic"), 0); + MENUITEM_ADD (optmenu_menu, menuitem, "LOGIN", IMAP_AUTH_LOGIN); + MENUITEM_ADD (optmenu_menu, menuitem, "CRAM-MD5", IMAP_AUTH_CRAM_MD5); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (optmenu), optmenu_menu); + + PACK_CHECK_BUTTON + (vbox1, recvatgetall_chkbtn, + _("`Get all' checks for new messages on this account")); + + receive.pop3_frame = frame1; + receive.use_apop_chkbtn = use_apop_chkbtn; + receive.rmmail_chkbtn = rmmail_chkbtn; + receive.leave_time_entry = leave_time_entry; + receive.getall_chkbtn = getall_chkbtn; + receive.size_limit_chkbtn = size_limit_chkbtn; + receive.size_limit_entry = size_limit_entry; + receive.filter_on_recv_chkbtn = filter_on_recv_chkbtn; + receive.inbox_label = inbox_label; + receive.inbox_entry = inbox_entry; + receive.inbox_btn = inbox_btn; + + receive.imap_frame = imap_frame; + receive.imap_auth_type_optmenu = optmenu; + + receive.recvatgetall_chkbtn = recvatgetall_chkbtn; +} + +static void prefs_account_send_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *frame; + GtkWidget *date_chkbtn; + GtkWidget *msgid_chkbtn; + GtkWidget *hbox; + GtkWidget *customhdr_chkbtn; + GtkWidget *customhdr_edit_btn; + GtkWidget *vbox3; + GtkWidget *smtp_auth_chkbtn; + GtkWidget *optmenu; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *vbox4; + GtkWidget *hbox_spc; + GtkWidget *label; + GtkWidget *smtp_uid_entry; + GtkWidget *smtp_pass_entry; + GtkWidget *vbox_spc; + /* GtkWidget *pop_bfr_smtp_chkbtn; */ + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME (vbox1, frame, _("Header")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + PACK_CHECK_BUTTON (vbox2, date_chkbtn, _("Add Date header field")); + PACK_CHECK_BUTTON (vbox2, msgid_chkbtn, _("Generate Message-ID")); + + hbox = gtk_hbox_new (FALSE, 12); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox, customhdr_chkbtn, + _("Add user-defined header")); + + customhdr_edit_btn = gtk_button_new_with_label (_(" Edit... ")); + gtk_widget_show (customhdr_edit_btn); + gtk_box_pack_start (GTK_BOX (hbox), customhdr_edit_btn, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (customhdr_edit_btn), "clicked", + G_CALLBACK (prefs_account_edit_custom_header), + NULL); + + SET_TOGGLE_SENSITIVITY (customhdr_chkbtn, customhdr_edit_btn); + + PACK_FRAME (vbox1, frame, _("Authentication")); + + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox3); + gtk_container_add (GTK_CONTAINER (frame), vbox3); + gtk_container_set_border_width (GTK_CONTAINER (vbox3), 8); + + PACK_CHECK_BUTTON (vbox3, smtp_auth_chkbtn, + _("SMTP Authentication (SMTP AUTH)")); + + vbox4 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox4); + gtk_box_pack_start (GTK_BOX (vbox3), vbox4, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox4), hbox, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + label = gtk_label_new (_("Authentication method")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + optmenu = gtk_option_menu_new (); + gtk_widget_show (optmenu); + gtk_box_pack_start (GTK_BOX (hbox), optmenu, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new (); + + MENUITEM_ADD (optmenu_menu, menuitem, _("Automatic"), 0); + MENUITEM_ADD (optmenu_menu, menuitem, "LOGIN", SMTPAUTH_LOGIN); + MENUITEM_ADD (optmenu_menu, menuitem, "CRAM-MD5", SMTPAUTH_CRAM_MD5); + MENUITEM_ADD (optmenu_menu, menuitem, "DIGEST-MD5", SMTPAUTH_DIGEST_MD5); + gtk_widget_set_sensitive (menuitem, FALSE); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (optmenu), optmenu_menu); + + PACK_VSPACER(vbox4, vbox_spc, VSPACING_NARROW_2); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox4), hbox, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + label = gtk_label_new (_("User ID")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + smtp_uid_entry = gtk_entry_new (); + gtk_widget_show (smtp_uid_entry); + gtk_widget_set_size_request (smtp_uid_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start (GTK_BOX (hbox), smtp_uid_entry, TRUE, TRUE, 0); + + label = gtk_label_new (_("Password")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + smtp_pass_entry = gtk_entry_new (); + gtk_widget_show (smtp_pass_entry); + gtk_widget_set_size_request (smtp_pass_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start (GTK_BOX (hbox), smtp_pass_entry, TRUE, TRUE, 0); + gtk_entry_set_visibility (GTK_ENTRY (smtp_pass_entry), FALSE); + + PACK_VSPACER(vbox4, vbox_spc, VSPACING_NARROW_2); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox4), hbox, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + label = gtk_label_new + (_("If you leave these entries empty, the same\n" + "user ID and password as receiving will be used.")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + SET_TOGGLE_SENSITIVITY (smtp_auth_chkbtn, vbox4); + +#if 0 + PACK_CHECK_BUTTON (vbox3, pop_bfr_smtp_chkbtn, + _("Authenticate with POP3 before sending")); + gtk_widget_set_sensitive(pop_bfr_smtp_chkbtn, FALSE); +#endif + + p_send.date_chkbtn = date_chkbtn; + p_send.msgid_chkbtn = msgid_chkbtn; + p_send.customhdr_chkbtn = customhdr_chkbtn; + + p_send.smtp_auth_chkbtn = smtp_auth_chkbtn; + p_send.smtp_auth_type_optmenu = optmenu; + p_send.smtp_uid_entry = smtp_uid_entry; + p_send.smtp_pass_entry = smtp_pass_entry; + /* p_send.pop_bfr_smtp_chkbtn = pop_bfr_smtp_chkbtn; */ +} + +static void prefs_account_compose_create(void) +{ + GtkWidget *vbox1; + GtkWidget *sig_vbox; + GtkWidget *sig_hbox; + GtkWidget *sigfile_radiobtn; + GtkWidget *sigcmd_radiobtn; + GtkWidget *sigpath_entry; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *autocc_chkbtn; + GtkWidget *autocc_entry; + GtkWidget *autobcc_chkbtn; + GtkWidget *autobcc_entry; + GtkWidget *autoreplyto_chkbtn; + GtkWidget *autoreplyto_entry; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME (vbox1, frame, _("Signature")); + + sig_vbox = gtk_vbox_new (FALSE, VSPACING_NARROW_2); + gtk_widget_show (sig_vbox); + gtk_container_add (GTK_CONTAINER (frame), sig_vbox); + gtk_container_set_border_width (GTK_CONTAINER (sig_vbox), 8); + + sig_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (sig_hbox); + gtk_box_pack_start (GTK_BOX (sig_vbox), sig_hbox, FALSE, FALSE, 0); + + sigfile_radiobtn = gtk_radio_button_new_with_label (NULL, _("File")); + gtk_widget_show (sigfile_radiobtn); + gtk_box_pack_start (GTK_BOX (sig_hbox), sigfile_radiobtn, + FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (sigfile_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER (SIG_FILE)); + + sigcmd_radiobtn = gtk_radio_button_new_with_label_from_widget + (GTK_RADIO_BUTTON(sigfile_radiobtn), _("Command output")); + gtk_widget_show (sigcmd_radiobtn); + gtk_box_pack_start (GTK_BOX (sig_hbox), sigcmd_radiobtn, + FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (sigcmd_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER (SIG_COMMAND)); + + sigpath_entry = gtk_entry_new (); + gtk_widget_show (sigpath_entry); + gtk_box_pack_start (GTK_BOX (sig_vbox), sigpath_entry, TRUE, TRUE, 0); + + PACK_FRAME (vbox1, frame, _("Automatically set the following addresses")); + + table = gtk_table_new (3, 2, FALSE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_container_set_border_width (GTK_CONTAINER (table), 8); + gtk_table_set_row_spacings (GTK_TABLE (table), VSPACING_NARROW_2); + gtk_table_set_col_spacings (GTK_TABLE (table), 8); + + autocc_chkbtn = gtk_check_button_new_with_label (_("Cc")); + gtk_widget_show (autocc_chkbtn); + gtk_table_attach (GTK_TABLE (table), autocc_chkbtn, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + + autocc_entry = gtk_entry_new (); + gtk_widget_show (autocc_entry); + gtk_table_attach (GTK_TABLE (table), autocc_entry, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + SET_TOGGLE_SENSITIVITY (autocc_chkbtn, autocc_entry); + + autobcc_chkbtn = gtk_check_button_new_with_label (_("Bcc")); + gtk_widget_show (autobcc_chkbtn); + gtk_table_attach (GTK_TABLE (table), autobcc_chkbtn, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + + autobcc_entry = gtk_entry_new (); + gtk_widget_show (autobcc_entry); + gtk_table_attach (GTK_TABLE (table), autobcc_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + SET_TOGGLE_SENSITIVITY (autobcc_chkbtn, autobcc_entry); + + autoreplyto_chkbtn = gtk_check_button_new_with_label (_("Reply-To")); + gtk_widget_show (autoreplyto_chkbtn); + gtk_table_attach (GTK_TABLE (table), autoreplyto_chkbtn, 0, 1, 2, 3, + GTK_FILL, 0, 0, 0); + + autoreplyto_entry = gtk_entry_new (); + gtk_widget_show (autoreplyto_entry); + gtk_table_attach (GTK_TABLE (table), autoreplyto_entry, 1, 2, 2, 3, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + SET_TOGGLE_SENSITIVITY (autoreplyto_chkbtn, autoreplyto_entry); + + + compose.sigfile_radiobtn = sigfile_radiobtn; + compose.sigpath_entry = sigpath_entry; + + compose.autocc_chkbtn = autocc_chkbtn; + compose.autocc_entry = autocc_entry; + compose.autobcc_chkbtn = autobcc_chkbtn; + compose.autobcc_entry = autobcc_entry; + compose.autoreplyto_chkbtn = autoreplyto_chkbtn; + compose.autoreplyto_entry = autoreplyto_entry; +} + +#if USE_GPGME +static void prefs_account_privacy_create(void) +{ + GtkWidget *vbox1; + GtkWidget *frame1; + GtkWidget *vbox2; + GtkWidget *hbox1; + GtkWidget *label; + GtkWidget *default_encrypt_chkbtn; + GtkWidget *default_sign_chkbtn; + GtkWidget *ascii_armored_chkbtn; + GtkWidget *clearsign_chkbtn; + GtkWidget *defaultkey_radiobtn; + GtkWidget *emailkey_radiobtn; + GtkWidget *customkey_radiobtn; + GtkWidget *customkey_entry; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (vbox2, default_encrypt_chkbtn, + _("Encrypt message by default")); + PACK_CHECK_BUTTON (vbox2, default_sign_chkbtn, + _("Sign message by default")); + PACK_CHECK_BUTTON (vbox2, ascii_armored_chkbtn, + _("Use ASCII-armored format for encryption")); + PACK_CHECK_BUTTON (vbox2, clearsign_chkbtn, + _("Use clear text signature")); + g_signal_connect (G_OBJECT (ascii_armored_chkbtn), "toggled", + G_CALLBACK (prefs_account_ascii_armored_warning), + NULL); + + PACK_FRAME (vbox1, frame1, _("Sign key")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame1), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + defaultkey_radiobtn = gtk_radio_button_new_with_label + (NULL, _("Use default GnuPG key")); + gtk_widget_show (defaultkey_radiobtn); + gtk_box_pack_start (GTK_BOX (vbox2), defaultkey_radiobtn, + FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (defaultkey_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER (SIGN_KEY_DEFAULT)); + + emailkey_radiobtn = gtk_radio_button_new_with_label_from_widget + (GTK_RADIO_BUTTON (defaultkey_radiobtn), + _("Select key by your email address")); + gtk_widget_show (emailkey_radiobtn); + gtk_box_pack_start (GTK_BOX (vbox2), emailkey_radiobtn, + FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (emailkey_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER (SIGN_KEY_BY_FROM)); + + customkey_radiobtn = gtk_radio_button_new_with_label_from_widget + (GTK_RADIO_BUTTON (defaultkey_radiobtn), + _("Specify key manually")); + gtk_widget_show (customkey_radiobtn); + gtk_box_pack_start (GTK_BOX (vbox2), customkey_radiobtn, + FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (customkey_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER (SIGN_KEY_CUSTOM)); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + label = gtk_label_new (""); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + gtk_widget_set_size_request (label, 16, -1); + + label = gtk_label_new (_("User or key ID:")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + + customkey_entry = gtk_entry_new (); + gtk_widget_show (customkey_entry); + gtk_box_pack_start (GTK_BOX (hbox1), customkey_entry, + TRUE, TRUE, 0); + + SET_TOGGLE_SENSITIVITY (customkey_radiobtn, customkey_entry); + + privacy.default_encrypt_chkbtn = default_encrypt_chkbtn; + privacy.default_sign_chkbtn = default_sign_chkbtn; + privacy.ascii_armored_chkbtn = ascii_armored_chkbtn; + privacy.clearsign_chkbtn = clearsign_chkbtn; + privacy.defaultkey_radiobtn = defaultkey_radiobtn; + privacy.emailkey_radiobtn = emailkey_radiobtn; + privacy.customkey_radiobtn = customkey_radiobtn; + privacy.customkey_entry = customkey_entry; +} +#endif /* USE_GPGME */ + +#if USE_SSL + +#define CREATE_RADIO_BUTTON(box, btn, btn_p, label, data) \ +{ \ + btn = gtk_radio_button_new_with_label_from_widget \ + (GTK_RADIO_BUTTON (btn_p), label); \ + gtk_widget_show (btn); \ + gtk_box_pack_start (GTK_BOX (box), btn, FALSE, FALSE, 0); \ + g_object_set_data (G_OBJECT (btn), MENU_VAL_ID, \ + GINT_TO_POINTER (data)); \ +} + +#define CREATE_RADIO_BUTTONS(box, \ + btn1, btn1_label, btn1_data, \ + btn2, btn2_label, btn2_data, \ + btn3, btn3_label, btn3_data) \ +{ \ + btn1 = gtk_radio_button_new_with_label(NULL, btn1_label); \ + gtk_widget_show (btn1); \ + gtk_box_pack_start (GTK_BOX (box), btn1, FALSE, FALSE, 0); \ + g_object_set_data (G_OBJECT (btn1), MENU_VAL_ID, \ + GINT_TO_POINTER (btn1_data)); \ + \ + CREATE_RADIO_BUTTON(box, btn2, btn1, btn2_label, btn2_data); \ + CREATE_RADIO_BUTTON(box, btn3, btn1, btn3_label, btn3_data); \ +} + +static void prefs_account_ssl_create(void) +{ + GtkWidget *vbox1; + + GtkWidget *pop_frame; + GtkWidget *vbox2; + GtkWidget *pop_nossl_radiobtn; + GtkWidget *pop_ssltunnel_radiobtn; + GtkWidget *pop_starttls_radiobtn; + + GtkWidget *imap_frame; + GtkWidget *vbox3; + GtkWidget *imap_nossl_radiobtn; + GtkWidget *imap_ssltunnel_radiobtn; + GtkWidget *imap_starttls_radiobtn; + + GtkWidget *nntp_frame; + GtkWidget *vbox4; + GtkWidget *nntp_nossl_radiobtn; + GtkWidget *nntp_ssltunnel_radiobtn; + + GtkWidget *send_frame; + GtkWidget *vbox5; + GtkWidget *smtp_nossl_radiobtn; + GtkWidget *smtp_ssltunnel_radiobtn; + GtkWidget *smtp_starttls_radiobtn; + + GtkWidget *vbox6; + GtkWidget *use_nonblocking_ssl_chkbtn; + GtkWidget *hbox; + GtkWidget *hbox_spc; + GtkWidget *label; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME (vbox1, pop_frame, _("POP3")); + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (pop_frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + CREATE_RADIO_BUTTONS(vbox2, + pop_nossl_radiobtn, + _("Don't use SSL"), + SSL_NONE, + pop_ssltunnel_radiobtn, + _("Use SSL for POP3 connection"), + SSL_TUNNEL, + pop_starttls_radiobtn, + _("Use STARTTLS command to start SSL session"), + SSL_STARTTLS); + + PACK_FRAME (vbox1, imap_frame, _("IMAP4")); + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox3); + gtk_container_add (GTK_CONTAINER (imap_frame), vbox3); + gtk_container_set_border_width (GTK_CONTAINER (vbox3), 8); + + CREATE_RADIO_BUTTONS(vbox3, + imap_nossl_radiobtn, + _("Don't use SSL"), + SSL_NONE, + imap_ssltunnel_radiobtn, + _("Use SSL for IMAP4 connection"), + SSL_TUNNEL, + imap_starttls_radiobtn, + _("Use STARTTLS command to start SSL session"), + SSL_STARTTLS); + + PACK_FRAME (vbox1, nntp_frame, _("NNTP")); + vbox4 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox4); + gtk_container_add (GTK_CONTAINER (nntp_frame), vbox4); + gtk_container_set_border_width (GTK_CONTAINER (vbox4), 8); + + nntp_nossl_radiobtn = + gtk_radio_button_new_with_label (NULL, _("Don't use SSL")); + gtk_widget_show (nntp_nossl_radiobtn); + gtk_box_pack_start (GTK_BOX (vbox4), nntp_nossl_radiobtn, + FALSE, FALSE, 0); + g_object_set_data (G_OBJECT (nntp_nossl_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER (SSL_NONE)); + + CREATE_RADIO_BUTTON(vbox4, nntp_ssltunnel_radiobtn, nntp_nossl_radiobtn, + _("Use SSL for NNTP connection"), SSL_TUNNEL); + + PACK_FRAME (vbox1, send_frame, _("Send (SMTP)")); + vbox5 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox5); + gtk_container_add (GTK_CONTAINER (send_frame), vbox5); + gtk_container_set_border_width (GTK_CONTAINER (vbox5), 8); + + CREATE_RADIO_BUTTONS(vbox5, + smtp_nossl_radiobtn, + _("Don't use SSL"), + SSL_NONE, + smtp_ssltunnel_radiobtn, + _("Use SSL for SMTP connection"), + SSL_TUNNEL, + smtp_starttls_radiobtn, + _("Use STARTTLS command to start SSL session"), + SSL_STARTTLS); + + vbox6 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox6); + gtk_box_pack_start (GTK_BOX (vbox1), vbox6, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON(vbox6, use_nonblocking_ssl_chkbtn, + _("Use non-blocking SSL")); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox6), hbox, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 16, -1); + + label = gtk_label_new + (_("(Turn this off if you have problems in SSL connection)")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + ssl.pop_frame = pop_frame; + ssl.pop_nossl_radiobtn = pop_nossl_radiobtn; + ssl.pop_ssltunnel_radiobtn = pop_ssltunnel_radiobtn; + ssl.pop_starttls_radiobtn = pop_starttls_radiobtn; + + ssl.imap_frame = imap_frame; + ssl.imap_nossl_radiobtn = imap_nossl_radiobtn; + ssl.imap_ssltunnel_radiobtn = imap_ssltunnel_radiobtn; + ssl.imap_starttls_radiobtn = imap_starttls_radiobtn; + + ssl.nntp_frame = nntp_frame; + ssl.nntp_nossl_radiobtn = nntp_nossl_radiobtn; + ssl.nntp_ssltunnel_radiobtn = nntp_ssltunnel_radiobtn; + + ssl.send_frame = send_frame; + ssl.smtp_nossl_radiobtn = smtp_nossl_radiobtn; + ssl.smtp_ssltunnel_radiobtn = smtp_ssltunnel_radiobtn; + ssl.smtp_starttls_radiobtn = smtp_starttls_radiobtn; + + ssl.use_nonblocking_ssl_chkbtn = use_nonblocking_ssl_chkbtn; +} + +#undef CREATE_RADIO_BUTTONS +#undef CREATE_RADIO_BUTTON + +#endif /* USE_SSL */ + +static void prefs_account_advanced_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *hbox1; + GtkWidget *checkbtn_smtpport; + GtkWidget *entry_smtpport; + GtkWidget *hbox_popport; + GtkWidget *checkbtn_popport; + GtkWidget *entry_popport; + GtkWidget *hbox_imapport; + GtkWidget *checkbtn_imapport; + GtkWidget *entry_imapport; + GtkWidget *hbox_nntpport; + GtkWidget *checkbtn_nntpport; + GtkWidget *entry_nntpport; + GtkWidget *checkbtn_domain; + GtkWidget *entry_domain; + GtkWidget *imap_frame; + GtkWidget *imapdir_label; + GtkWidget *imapdir_entry; + GtkWidget *folder_frame; + GtkWidget *vbox3; + GtkWidget *table; + GtkWidget *sent_folder_chkbtn; + GtkWidget *sent_folder_entry; + GtkWidget *draft_folder_chkbtn; + GtkWidget *draft_folder_entry; + GtkWidget *trash_folder_chkbtn; + GtkWidget *trash_folder_entry; + +#define PACK_HBOX(hbox) \ +{ \ + hbox = gtk_hbox_new (FALSE, 8); \ + gtk_widget_show (hbox); \ + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); \ +} + +#define PACK_PORT_ENTRY(box, entry) \ +{ \ + entry = gtk_entry_new (); \ + gtk_entry_set_max_length (GTK_ENTRY(entry), 5); \ + gtk_widget_show (entry); \ + gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0); \ + gtk_widget_set_size_request (entry, 64, -1); \ +} + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + vbox2 = gtk_vbox_new (FALSE, VSPACING_NARROW_2); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + PACK_HBOX (hbox1); + PACK_CHECK_BUTTON (hbox1, checkbtn_smtpport, _("Specify SMTP port")); + PACK_PORT_ENTRY (hbox1, entry_smtpport); + SET_TOGGLE_SENSITIVITY (checkbtn_smtpport, entry_smtpport); + + PACK_HBOX (hbox_popport); + PACK_CHECK_BUTTON (hbox_popport, checkbtn_popport, + _("Specify POP3 port")); + PACK_PORT_ENTRY (hbox_popport, entry_popport); + SET_TOGGLE_SENSITIVITY (checkbtn_popport, entry_popport); + + PACK_HBOX (hbox_imapport); + PACK_CHECK_BUTTON (hbox_imapport, checkbtn_imapport, + _("Specify IMAP4 port")); + PACK_PORT_ENTRY (hbox_imapport, entry_imapport); + SET_TOGGLE_SENSITIVITY (checkbtn_imapport, entry_imapport); + + PACK_HBOX (hbox_nntpport); + PACK_CHECK_BUTTON (hbox_nntpport, checkbtn_nntpport, + _("Specify NNTP port")); + PACK_PORT_ENTRY (hbox_nntpport, entry_nntpport); + SET_TOGGLE_SENSITIVITY (checkbtn_nntpport, entry_nntpport); + + PACK_HBOX (hbox1); + PACK_CHECK_BUTTON (hbox1, checkbtn_domain, _("Specify domain name")); + + entry_domain = gtk_entry_new (); + gtk_widget_show (entry_domain); + gtk_box_pack_start (GTK_BOX (hbox1), entry_domain, TRUE, TRUE, 0); + SET_TOGGLE_SENSITIVITY (checkbtn_domain, entry_domain); + + PACK_FRAME (vbox1, imap_frame, _("IMAP4")); + + vbox3 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox3); + gtk_container_add (GTK_CONTAINER (imap_frame), vbox3); + gtk_container_set_border_width (GTK_CONTAINER (vbox3), 8); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox3), hbox1, FALSE, FALSE, 0); + + imapdir_label = gtk_label_new (_("IMAP server directory")); + gtk_widget_show (imapdir_label); + gtk_box_pack_start (GTK_BOX (hbox1), imapdir_label, FALSE, FALSE, 0); + + imapdir_entry = gtk_entry_new(); + gtk_widget_show (imapdir_entry); + gtk_box_pack_start (GTK_BOX (hbox1), imapdir_entry, TRUE, TRUE, 0); + +#undef PACK_HBOX +#undef PACK_PORT_ENTRY + + /* special folder setting (maybe these options are redundant) */ + + PACK_FRAME (vbox1, folder_frame, _("Folder")); + + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox3); + gtk_container_add (GTK_CONTAINER (folder_frame), vbox3); + gtk_container_set_border_width (GTK_CONTAINER (vbox3), 8); + + table = gtk_table_new (3, 3, FALSE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (vbox3), table); + gtk_table_set_row_spacings (GTK_TABLE (table), VSPACING_NARROW_2); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + +#define SET_CHECK_BTN_AND_ENTRY(label, chkbtn, entry, n) \ +{ \ + GtkWidget *button; \ + \ + chkbtn = gtk_check_button_new_with_label (label); \ + gtk_widget_show (chkbtn); \ + gtk_table_attach (GTK_TABLE (table), chkbtn, \ + 0, 1, n, n + 1, GTK_FILL, 0, 0, 0); \ + \ + entry = gtk_entry_new (); \ + gtk_widget_show (entry); \ + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, n, n + 1, \ + GTK_EXPAND | GTK_SHRINK | GTK_FILL, \ + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); \ + \ + button = gtk_button_new_with_label (_(" ... ")); \ + gtk_widget_show (button); \ + gtk_table_attach (GTK_TABLE (table), button, \ + 2, 3, n, n + 1, GTK_FILL, 0, 0, 0); \ + g_signal_connect \ + (G_OBJECT (button), "clicked", \ + G_CALLBACK (prefs_account_select_folder_cb), \ + entry); \ + \ + SET_TOGGLE_SENSITIVITY (chkbtn, entry); \ + SET_TOGGLE_SENSITIVITY (chkbtn, button); \ +} + + SET_CHECK_BTN_AND_ENTRY(_("Put sent messages in"), + sent_folder_chkbtn, sent_folder_entry, 0); + SET_CHECK_BTN_AND_ENTRY(_("Put draft messages in"), + draft_folder_chkbtn, draft_folder_entry, 1); + SET_CHECK_BTN_AND_ENTRY(_("Put deleted messages in"), + trash_folder_chkbtn, trash_folder_entry, 2); + + advanced.smtpport_chkbtn = checkbtn_smtpport; + advanced.smtpport_entry = entry_smtpport; + advanced.popport_hbox = hbox_popport; + advanced.popport_chkbtn = checkbtn_popport; + advanced.popport_entry = entry_popport; + advanced.imapport_hbox = hbox_imapport; + advanced.imapport_chkbtn = checkbtn_imapport; + advanced.imapport_entry = entry_imapport; + advanced.nntpport_hbox = hbox_nntpport; + advanced.nntpport_chkbtn = checkbtn_nntpport; + advanced.nntpport_entry = entry_nntpport; + advanced.domain_chkbtn = checkbtn_domain; + advanced.domain_entry = entry_domain; + + advanced.imap_frame = imap_frame; + advanced.imapdir_entry = imapdir_entry; + + advanced.sent_folder_chkbtn = sent_folder_chkbtn; + advanced.sent_folder_entry = sent_folder_entry; + advanced.draft_folder_chkbtn = draft_folder_chkbtn; + advanced.draft_folder_entry = draft_folder_entry; + advanced.trash_folder_chkbtn = trash_folder_chkbtn; + advanced.trash_folder_entry = trash_folder_entry; +} + +static gint prefs_account_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_account_cancel(); + return TRUE; +} + +static gboolean prefs_account_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_account_cancel(); + return FALSE; +} + +static void prefs_account_ok(void) +{ + if (prefs_account_apply() == 0) + gtk_main_quit(); +} + +static gint prefs_account_apply(void) +{ + RecvProtocol protocol; + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(basic.protocol_optmenu)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + protocol = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); + + if (*gtk_entry_get_text(GTK_ENTRY(basic.acname_entry)) == '\0') { + alertpanel_error(_("Account name is not entered.")); + return -1; + } + if (*gtk_entry_get_text(GTK_ENTRY(basic.addr_entry)) == '\0') { + alertpanel_error(_("Mail address is not entered.")); + return -1; + } + if ((protocol == A_POP3 || protocol == A_LOCAL) && + *gtk_entry_get_text(GTK_ENTRY(basic.smtpserv_entry)) == '\0') { + alertpanel_error(_("SMTP server is not entered.")); + return -1; + } + if ((protocol == A_POP3 || protocol == A_IMAP4) && + *gtk_entry_get_text(GTK_ENTRY(basic.uid_entry)) == '\0') { + alertpanel_error(_("User ID is not entered.")); + return -1; + } + if (protocol == A_POP3 && + *gtk_entry_get_text(GTK_ENTRY(basic.recvserv_entry)) == '\0') { + alertpanel_error(_("POP3 server is not entered.")); + return -1; + } + if (protocol == A_IMAP4 && + *gtk_entry_get_text(GTK_ENTRY(basic.recvserv_entry)) == '\0') { + alertpanel_error(_("IMAP4 server is not entered.")); + return -1; + } + if (protocol == A_NNTP && + *gtk_entry_get_text(GTK_ENTRY(basic.nntpserv_entry)) == '\0') { + alertpanel_error(_("NNTP server is not entered.")); + return -1; + } + + prefs_set_data_from_dialog(param); + return 0; +} + +static void prefs_account_cancel(void) +{ + cancelled = TRUE; + gtk_main_quit(); +} + +static void prefs_account_select_folder_cb(GtkWidget *widget, gpointer data) +{ + FolderItem *item; + gchar *id; + + item = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL); + if (item && item->path) { + id = folder_item_get_identifier(item); + if (id) { + gtk_entry_set_text(GTK_ENTRY(data), id); + g_free(id); + } + } +} + +static void prefs_account_edit_custom_header(void) +{ + prefs_custom_header_open(&tmp_ac_prefs); +} + +static void prefs_account_enum_set_data_from_radiobtn(PrefParam *pparam) +{ + GtkRadioButton *radiobtn; + GSList *group; + + radiobtn = GTK_RADIO_BUTTON (*pparam->widget); + group = gtk_radio_button_get_group (radiobtn); + while (group != NULL) { + GtkToggleButton *btn = GTK_TOGGLE_BUTTON (group->data); + if (gtk_toggle_button_get_active (btn)) { + *((gint *)pparam->data) = GPOINTER_TO_INT + (g_object_get_data (G_OBJECT (btn), + MENU_VAL_ID)); + break; + } + group = group->next; + } +} + +static void prefs_account_enum_set_radiobtn(PrefParam *pparam) +{ + GtkRadioButton *radiobtn; + GSList *group; + gpointer data; + + data = GINT_TO_POINTER (*((gint *)pparam->data)); + radiobtn = GTK_RADIO_BUTTON (*pparam->widget); + group = gtk_radio_button_get_group (radiobtn); + while (group != NULL) { + GtkToggleButton *btn = GTK_TOGGLE_BUTTON (group->data); + gpointer data1; + + data1 = g_object_get_data (G_OBJECT (btn), MENU_VAL_ID); + if (data1 == data) { + gtk_toggle_button_set_active (btn, TRUE); + break; + } + group = group->next; + } +} + + +#if USE_GPGME +static void prefs_account_ascii_armored_warning(GtkWidget *widget) +{ + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) && + gtk_notebook_get_current_page(GTK_NOTEBOOK(dialog.notebook)) > 0) + alertpanel_warning + (_("It's not recommended to use the old style ASCII-armored\n" + "mode for encrypted messages. It doesn't comply with the\n" + "RFC 3156 - MIME Security with OpenPGP.")); +} +#endif /* USE_GPGME */ + +static void prefs_account_protocol_set_data_from_optmenu(PrefParam *pparam) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(*pparam->widget)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + *((RecvProtocol *)pparam->data) = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); +} + +static void prefs_account_protocol_set_optmenu(PrefParam *pparam) +{ + RecvProtocol protocol; + GtkOptionMenu *optmenu = GTK_OPTION_MENU(*pparam->widget); + GtkWidget *menu; + GtkWidget *menuitem; + gint index; + + protocol = *((RecvProtocol *)pparam->data); + index = menu_find_option_menu_index + (optmenu, GINT_TO_POINTER(protocol), NULL); + if (index < 0) return; + gtk_option_menu_set_history(optmenu, index); + + menu = gtk_option_menu_get_menu(optmenu); + menu_set_insensitive_all(GTK_MENU_SHELL(menu)); + + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + gtk_widget_set_sensitive(menuitem, TRUE); + gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); +} + +static void prefs_account_imap_auth_type_set_data_from_optmenu(PrefParam *pparam) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(*pparam->widget)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + *((RecvProtocol *)pparam->data) = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); +} + +static void prefs_account_imap_auth_type_set_optmenu(PrefParam *pparam) +{ + IMAPAuthType type = *((IMAPAuthType *)pparam->data); + GtkOptionMenu *optmenu = GTK_OPTION_MENU(*pparam->widget); + GtkWidget *menu; + GtkWidget *menuitem; + + switch (type) { + case IMAP_AUTH_LOGIN: + gtk_option_menu_set_history(optmenu, 1); + break; + case IMAP_AUTH_CRAM_MD5: + gtk_option_menu_set_history(optmenu, 2); + break; + case 0: + default: + gtk_option_menu_set_history(optmenu, 0); + } + + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); +} + +static void prefs_account_smtp_auth_type_set_data_from_optmenu(PrefParam *pparam) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(*pparam->widget)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + *((RecvProtocol *)pparam->data) = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); +} + +static void prefs_account_smtp_auth_type_set_optmenu(PrefParam *pparam) +{ + SMTPAuthType type = *((SMTPAuthType *)pparam->data); + GtkOptionMenu *optmenu = GTK_OPTION_MENU(*pparam->widget); + GtkWidget *menu; + GtkWidget *menuitem; + + switch (type) { + case SMTPAUTH_LOGIN: + gtk_option_menu_set_history(optmenu, 1); + break; + case SMTPAUTH_CRAM_MD5: + gtk_option_menu_set_history(optmenu, 2); + break; + case SMTPAUTH_DIGEST_MD5: + gtk_option_menu_set_history(optmenu, 3); + break; + case 0: + default: + gtk_option_menu_set_history(optmenu, 0); + } + + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); +} + +static void prefs_account_protocol_activated(GtkMenuItem *menuitem) +{ + RecvProtocol protocol; + gboolean active; + + protocol = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); + + switch(protocol) { + case A_NNTP: + gtk_widget_show(basic.nntpserv_label); + gtk_widget_show(basic.nntpserv_entry); + gtk_widget_show(basic.nntpauth_chkbtn); + gtk_widget_hide(basic.recvserv_label); + gtk_widget_hide(basic.recvserv_entry); + gtk_widget_hide(basic.smtpserv_label); + gtk_widget_hide(basic.smtpserv_entry); + active = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(basic.nntpauth_chkbtn)); + gtk_widget_set_sensitive(basic.uid_label, active); + gtk_widget_set_sensitive(basic.pass_label, active); + gtk_widget_set_sensitive(basic.uid_entry, active); + gtk_widget_set_sensitive(basic.pass_entry, active); + gtk_widget_hide(receive.pop3_frame); + gtk_widget_hide(receive.imap_frame); + gtk_widget_set_sensitive(receive.recvatgetall_chkbtn, TRUE); + + if (!tmp_ac_prefs.account_name) { + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(receive.recvatgetall_chkbtn), + FALSE); + } + +#if USE_SSL + gtk_widget_hide(ssl.pop_frame); + gtk_widget_hide(ssl.imap_frame); + gtk_widget_show(ssl.nntp_frame); + gtk_widget_hide(ssl.send_frame); +#endif + gtk_widget_hide(advanced.popport_hbox); + gtk_widget_hide(advanced.imapport_hbox); + gtk_widget_show(advanced.nntpport_hbox); + gtk_widget_hide(advanced.imap_frame); + break; + case A_LOCAL: + gtk_widget_hide(basic.nntpserv_label); + gtk_widget_hide(basic.nntpserv_entry); + gtk_widget_hide(basic.nntpauth_chkbtn); + gtk_widget_set_sensitive(basic.recvserv_label, FALSE); + gtk_widget_set_sensitive(basic.recvserv_entry, FALSE); + gtk_widget_show(basic.recvserv_label); + gtk_widget_show(basic.recvserv_entry); + gtk_widget_show(basic.smtpserv_label); + gtk_widget_show(basic.smtpserv_entry); + gtk_widget_set_sensitive(basic.uid_label, FALSE); + gtk_widget_set_sensitive(basic.pass_label, FALSE); + gtk_widget_set_sensitive(basic.uid_entry, FALSE); + gtk_widget_set_sensitive(basic.pass_entry, FALSE); + gtk_widget_hide(receive.pop3_frame); + gtk_widget_hide(receive.imap_frame); + gtk_widget_set_sensitive(receive.recvatgetall_chkbtn, FALSE); + + if (!tmp_ac_prefs.account_name) { + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(receive.recvatgetall_chkbtn), + TRUE); + } + +#if USE_SSL + gtk_widget_hide(ssl.pop_frame); + gtk_widget_hide(ssl.imap_frame); + gtk_widget_hide(ssl.nntp_frame); + gtk_widget_show(ssl.send_frame); +#endif + gtk_widget_hide(advanced.popport_hbox); + gtk_widget_hide(advanced.imapport_hbox); + gtk_widget_hide(advanced.nntpport_hbox); + gtk_widget_hide(advanced.imap_frame); + break; + case A_IMAP4: + gtk_widget_hide(basic.nntpserv_label); + gtk_widget_hide(basic.nntpserv_entry); + gtk_widget_hide(basic.nntpauth_chkbtn); + gtk_widget_set_sensitive(basic.recvserv_label, TRUE); + gtk_widget_set_sensitive(basic.recvserv_entry, TRUE); + gtk_widget_show(basic.recvserv_label); + gtk_widget_show(basic.recvserv_entry); + gtk_widget_show(basic.smtpserv_label); + gtk_widget_show(basic.smtpserv_entry); + gtk_widget_set_sensitive(basic.uid_label, TRUE); + gtk_widget_set_sensitive(basic.pass_label, TRUE); + gtk_widget_set_sensitive(basic.uid_entry, TRUE); + gtk_widget_set_sensitive(basic.pass_entry, TRUE); + gtk_widget_hide(receive.pop3_frame); + gtk_widget_show(receive.imap_frame); + gtk_widget_set_sensitive(receive.recvatgetall_chkbtn, TRUE); + + if (!tmp_ac_prefs.account_name) { + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(receive.recvatgetall_chkbtn), + FALSE); + } + +#if USE_SSL + gtk_widget_hide(ssl.pop_frame); + gtk_widget_show(ssl.imap_frame); + gtk_widget_hide(ssl.nntp_frame); + gtk_widget_show(ssl.send_frame); +#endif + gtk_widget_hide(advanced.popport_hbox); + gtk_widget_show(advanced.imapport_hbox); + gtk_widget_hide(advanced.nntpport_hbox); + gtk_widget_show(advanced.imap_frame); + break; + case A_POP3: + default: + gtk_widget_hide(basic.nntpserv_label); + gtk_widget_hide(basic.nntpserv_entry); + gtk_widget_hide(basic.nntpauth_chkbtn); + gtk_widget_set_sensitive(basic.recvserv_label, TRUE); + gtk_widget_set_sensitive(basic.recvserv_entry, TRUE); + gtk_widget_show(basic.recvserv_label); + gtk_widget_show(basic.recvserv_entry); + gtk_widget_show(basic.smtpserv_label); + gtk_widget_show(basic.smtpserv_entry); + gtk_widget_set_sensitive(basic.uid_label, TRUE); + gtk_widget_set_sensitive(basic.pass_label, TRUE); + gtk_widget_set_sensitive(basic.uid_entry, TRUE); + gtk_widget_set_sensitive(basic.pass_entry, TRUE); + gtk_widget_show(receive.pop3_frame); + gtk_widget_hide(receive.imap_frame); + gtk_widget_set_sensitive(receive.recvatgetall_chkbtn, TRUE); + + if (!tmp_ac_prefs.account_name) { + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(receive.recvatgetall_chkbtn), + TRUE); + } + +#if USE_SSL + gtk_widget_show(ssl.pop_frame); + gtk_widget_hide(ssl.imap_frame); + gtk_widget_hide(ssl.nntp_frame); + gtk_widget_show(ssl.send_frame); +#endif + gtk_widget_show(advanced.popport_hbox); + gtk_widget_hide(advanced.imapport_hbox); + gtk_widget_hide(advanced.nntpport_hbox); + gtk_widget_hide(advanced.imap_frame); + break; + } + + gtk_widget_queue_resize(basic.serv_frame); +} diff --git a/src/prefs_account.h b/src/prefs_account.h new file mode 100644 index 00000000..efa659f8 --- /dev/null +++ b/src/prefs_account.h @@ -0,0 +1,175 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_ACCOUNT_H__ +#define __PREFS_ACCOUNT_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +typedef struct _PrefsAccount PrefsAccount; + +#include "folder.h" +#include "smtp.h" + +typedef enum { + A_POP3, + A_APOP, /* deprecated */ + A_RPOP, /* deprecated */ + A_IMAP4, + A_NNTP, + A_LOCAL +} RecvProtocol; + +typedef enum { + SIG_FILE, + SIG_COMMAND, + SIG_DIRECT +} SigType; + +#if USE_GPGME +typedef enum { + SIGN_KEY_DEFAULT, + SIGN_KEY_BY_FROM, + SIGN_KEY_CUSTOM +} SignKeyType; +#endif /* USE_GPGME */ + +struct _PrefsAccount +{ + gchar *account_name; + + /* Personal info */ + gchar *name; + gchar *address; + gchar *organization; + + /* Server info */ + RecvProtocol protocol; + gchar *recv_server; + gchar *smtp_server; + gchar *nntp_server; + gboolean use_nntp_auth; + gchar *userid; + gchar *passwd; + +#if USE_SSL + /* SSL */ + SSLType ssl_pop; + SSLType ssl_imap; + SSLType ssl_nntp; + SSLType ssl_smtp; + gboolean use_nonblocking_ssl; +#endif /* USE_SSL */ + + /* Temporarily preserved password */ + gchar *tmp_pass; + + /* Receive */ + gboolean use_apop_auth; + gboolean rmmail; + gint msg_leave_time; + gboolean getall; + gboolean recv_at_getall; + gboolean enable_size_limit; + gint size_limit; + gboolean filter_on_recv; + gchar *inbox; + + gint imap_auth_type; + + /* Send */ + gboolean add_date; + gboolean gen_msgid; + gboolean add_customhdr; + gboolean use_smtp_auth; + SMTPAuthType smtp_auth_type; + gchar *smtp_userid; + gchar *smtp_passwd; + + /* Temporarily preserved password */ + gchar *tmp_smtp_pass; + + gboolean pop_before_smtp; + + GSList *customhdr_list; + + /* Compose */ + SigType sig_type; + gchar *sig_path; + gboolean set_autocc; + gchar *auto_cc; + gboolean set_autobcc; + gchar *auto_bcc; + gboolean set_autoreplyto; + gchar *auto_replyto; + +#if USE_GPGME + /* Privacy */ + gboolean default_encrypt; + gboolean default_sign; + gboolean ascii_armored; + gboolean clearsign; + SignKeyType sign_key; + gchar *sign_key_id; +#endif /* USE_GPGME */ + + /* Advanced */ + gboolean set_smtpport; + gushort smtpport; + gboolean set_popport; + gushort popport; + gboolean set_imapport; + gushort imapport; + gboolean set_nntpport; + gushort nntpport; + gboolean set_domain; + gchar *domain; + + gchar *imap_dir; + + gboolean set_sent_folder; + gchar *sent_folder; + gboolean set_draft_folder; + gchar *draft_folder; + gboolean set_trash_folder; + gchar *trash_folder; + + /* Default or not */ + gboolean is_default; + /* Unique account ID */ + gint account_id; + + RemoteFolder *folder; +}; + +PrefsAccount *prefs_account_new (void); + +void prefs_account_read_config (PrefsAccount *ac_prefs, + const gchar *label); +void prefs_account_write_config_all (GList *account_list); + +void prefs_account_free (PrefsAccount *ac_prefs); + +PrefsAccount *prefs_account_open (PrefsAccount *ac_prefs); + +#endif /* __PREFS_ACCOUNT_H__ */ diff --git a/src/prefs_actions.c b/src/prefs_actions.c new file mode 100644 index 00000000..22e7c4e2 --- /dev/null +++ b/src/prefs_actions.c @@ -0,0 +1,666 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto & The Sylpheed Claws Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "prefs.h" +#include "inc.h" +#include "utils.h" +#include "gtkutils.h" +#include "manage_window.h" +#include "mainwindow.h" +#include "prefs_common.h" +#include "alertpanel.h" +#include "prefs_actions.h" +#include "action.h" + +static struct Actions +{ + GtkWidget *window; + + GtkWidget *ok_btn; + + GtkWidget *name_entry; + GtkWidget *cmd_entry; + + GtkWidget *actions_clist; +} actions; + +/* widget creating functions */ +static void prefs_actions_create (MainWindow *mainwin); +static void prefs_actions_set_dialog (void); +static gint prefs_actions_clist_set_row (gint row); + +/* callback functions */ +static void prefs_actions_help_cb (GtkWidget *w, + gpointer data); +static void prefs_actions_register_cb (GtkWidget *w, + gpointer data); +static void prefs_actions_substitute_cb (GtkWidget *w, + gpointer data); +static void prefs_actions_delete_cb (GtkWidget *w, + gpointer data); +static void prefs_actions_up (GtkWidget *w, + gpointer data); +static void prefs_actions_down (GtkWidget *w, + gpointer data); +static void prefs_actions_select (GtkCList *clist, + gint row, + gint column, + GdkEvent *event); +static void prefs_actions_row_move (GtkCList *clist, + gint source_row, + gint dest_row); +static gint prefs_actions_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer *data); +static gboolean prefs_actions_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_actions_cancel (GtkWidget *w, + gpointer data); +static void prefs_actions_ok (GtkWidget *w, + gpointer data); + + +void prefs_actions_open(MainWindow *mainwin) +{ + inc_lock(); + + if (!actions.window) + prefs_actions_create(mainwin); + + manage_window_set_transient(GTK_WINDOW(actions.window)); + gtk_widget_grab_focus(actions.ok_btn); + + prefs_actions_set_dialog(); + + gtk_widget_show(actions.window); +} + +static void prefs_actions_create(MainWindow *mainwin) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *confirm_area; + + GtkWidget *vbox1; + + GtkWidget *entry_vbox; + GtkWidget *hbox; + GtkWidget *name_label; + GtkWidget *name_entry; + GtkWidget *cmd_label; + GtkWidget *cmd_entry; + + GtkWidget *reg_hbox; + GtkWidget *btn_hbox; + GtkWidget *arrow; + GtkWidget *reg_btn; + GtkWidget *subst_btn; + GtkWidget *del_btn; + + GtkWidget *cond_hbox; + GtkWidget *cond_scrolledwin; + GtkWidget *cond_clist; + + GtkWidget *help_vbox; + GtkWidget *help_label; + GtkWidget *help_toggle; + + GtkWidget *btn_vbox; + GtkWidget *up_btn; + GtkWidget *down_btn; + + gchar *title[1]; + + debug_print("Creating actions configuration window...\n"); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_container_set_border_width(GTK_CONTAINER (window), 8); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE); + gtk_window_set_default_size(GTK_WINDOW(window), 400, -1); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_widget_show(confirm_area); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + gtk_window_set_title(GTK_WINDOW(window), _("Actions configuration")); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_actions_deleted), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_actions_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(prefs_actions_ok), mainwin); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(prefs_actions_cancel), NULL); + + vbox1 = gtk_vbox_new(FALSE, 8); + gtk_widget_show(vbox1); + gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2); + + entry_vbox = gtk_vbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(vbox1), entry_vbox, FALSE, FALSE, 0); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(entry_vbox), hbox, FALSE, FALSE, 0); + + name_label = gtk_label_new(_("Menu name:")); + gtk_box_pack_start(GTK_BOX(hbox), name_label, FALSE, FALSE, 0); + + name_entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(hbox), name_entry, TRUE, TRUE, 0); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(entry_vbox), hbox, TRUE, TRUE, 0); + + cmd_label = gtk_label_new(_("Command line:")); + gtk_box_pack_start(GTK_BOX(hbox), cmd_label, FALSE, FALSE, 0); + + cmd_entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(hbox), cmd_entry, TRUE, TRUE, 0); + + gtk_widget_show_all(entry_vbox); + + help_vbox = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox1), help_vbox, FALSE, FALSE, 0); + + help_label = gtk_label_new + (_("Menu name:\n" + " Use / in menu name to make submenus.\n" + "Command line:\n" + " Begin with:\n" + " | to send message body or selection to command\n" + " > to send user provided text to command\n" + " * to send user provided hidden text to command\n" + " End with:\n" + " | to replace message body or selection with command output\n" + " > to insert command's output without replacing old text\n" + " & to run command asynchronously\n" + " Use:\n" + " %f for message file name\n" + " %F for the list of the file names of selected messages\n" + " %p for the selected message part\n" + " %u for a user provided argument\n" + " %h for a user provided hidden argument\n" + " %s for the text selection")); + gtk_misc_set_alignment(GTK_MISC(help_label), 0, 0.5); + gtk_label_set_justify(GTK_LABEL(help_label), GTK_JUSTIFY_LEFT); + gtk_widget_show(help_label); + gtk_box_pack_start(GTK_BOX(help_vbox), help_label, FALSE, FALSE, 0); + gtk_widget_hide(help_vbox); + + /* register / substitute / delete */ + + reg_hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(reg_hbox); + gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0); + + arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_widget_show(arrow); + gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0); + gtk_widget_set_size_request(arrow, -1, 16); + + btn_hbox = gtk_hbox_new(TRUE, 4); + gtk_widget_show(btn_hbox); + gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0); + + reg_btn = gtk_button_new_with_label(_("Add")); + gtk_widget_show(reg_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(reg_btn), "clicked", + G_CALLBACK(prefs_actions_register_cb), NULL); + + subst_btn = gtk_button_new_with_label(_(" Replace ")); + gtk_widget_show(subst_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(subst_btn), "clicked", + G_CALLBACK(prefs_actions_substitute_cb), NULL); + + del_btn = gtk_button_new_with_label(_("Delete")); + gtk_widget_show(del_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(prefs_actions_delete_cb), NULL); + + help_toggle = gtk_toggle_button_new_with_label(_(" Syntax help ")); + gtk_widget_show(help_toggle); + gtk_box_pack_end(GTK_BOX(reg_hbox), help_toggle, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(help_toggle), "toggled", + G_CALLBACK(prefs_actions_help_cb), help_vbox); + + cond_hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(cond_hbox); + gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0); + + cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(cond_scrolledwin); + gtk_widget_set_size_request(cond_scrolledwin, -1, 150); + gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin, + TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Registered actions"); + cond_clist = gtk_clist_new_with_titles(1, title); + gtk_widget_show(cond_clist); + gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_clist); + gtk_clist_set_column_width(GTK_CLIST (cond_clist), 0, 80); + gtk_clist_set_selection_mode(GTK_CLIST (cond_clist), + GTK_SELECTION_BROWSE); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(cond_clist)->column[0].button, + GTK_CAN_FOCUS); + g_signal_connect(G_OBJECT(cond_clist), "select_row", + G_CALLBACK(prefs_actions_select), NULL); + g_signal_connect_after(G_OBJECT(cond_clist), "row_move", + G_CALLBACK(prefs_actions_row_move), NULL); + + btn_vbox = gtk_vbox_new(FALSE, 8); + gtk_widget_show(btn_vbox); + gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0); + + up_btn = gtk_button_new_with_label(_("Up")); + gtk_widget_show(up_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(up_btn), "clicked", + G_CALLBACK(prefs_actions_up), NULL); + + down_btn = gtk_button_new_with_label(_("Down")); + gtk_widget_show(down_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(down_btn), "clicked", + G_CALLBACK(prefs_actions_down), NULL); + + gtk_widget_show(window); + + actions.window = window; + actions.ok_btn = ok_btn; + + actions.name_entry = name_entry; + actions.cmd_entry = cmd_entry; + + actions.actions_clist = cond_clist; +} + +static void prefs_actions_help_cb(GtkWidget *w, gpointer data) +{ + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) + gtk_widget_show(GTK_WIDGET(data)); + else + gtk_widget_hide(GTK_WIDGET(data)); +} + +void prefs_actions_read_config(void) +{ + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + gchar *act; + + debug_print("Reading actions configurations...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL); + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + return; + } + g_free(rcpath); + + while (prefs_common.actions_list != NULL) { + act = (gchar *)prefs_common.actions_list->data; + prefs_common.actions_list = + g_slist_remove(prefs_common.actions_list, act); + g_free(act); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + g_strchomp(buf); + act = strstr(buf, ": "); + if (act && act[2] && + action_get_type(&act[2]) != ACTION_ERROR) + prefs_common.actions_list = + g_slist_append(prefs_common.actions_list, + g_strdup(buf)); + } + fclose(fp); +} + +void prefs_actions_write_config(void) +{ + gchar *rcpath; + PrefFile *pfile; + GSList *cur; + + debug_print("Writing actions configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL); + if ((pfile= prefs_file_open(rcpath)) == NULL) { + g_warning("failed to write configuration to file\n"); + g_free(rcpath); + return; + } + + for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) { + gchar *act = (gchar *)cur->data; + if (fputs(act, pfile->fp) == EOF || + fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputs || fputc"); + prefs_file_close_revert(pfile); + g_free(rcpath); + return; + } + } + + g_free(rcpath); + + if (prefs_file_close(pfile) < 0) { + g_warning("failed to write configuration to file\n"); + return; + } +} + +static void prefs_actions_set_dialog(void) +{ + GtkCList *clist = GTK_CLIST(actions.actions_clist); + GSList *cur; + gchar *action_str[1]; + gint row; + + gtk_clist_freeze(clist); + gtk_clist_clear(clist); + + action_str[0] = _("(New)"); + row = gtk_clist_append(clist, action_str); + gtk_clist_set_row_data(clist, row, NULL); + + for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) { + gchar *action[1]; + + action[0] = (gchar *)cur->data; + row = gtk_clist_append(clist, action); + gtk_clist_set_row_data(clist, row, action[0]); + } + + gtk_clist_thaw(clist); +} + +static void prefs_actions_set_list(void) +{ + gint row = 1; + gchar *action; + + g_slist_free(prefs_common.actions_list); + prefs_common.actions_list = NULL; + + while ((action = (gchar *)gtk_clist_get_row_data + (GTK_CLIST(actions.actions_clist), row)) != NULL) { + prefs_common.actions_list = + g_slist_append(prefs_common.actions_list, action); + row++; + } +} + +#define GET_ENTRY(entry) \ + entry_text = gtk_entry_get_text(GTK_ENTRY(entry)) + +static gint prefs_actions_clist_set_row(gint row) +{ + GtkCList *clist = GTK_CLIST(actions.actions_clist); + const gchar *entry_text; + gint len; + gchar action[PREFSBUFSIZE]; + gchar *buf[1]; + + g_return_val_if_fail(row != 0, -1); + + GET_ENTRY(actions.name_entry); + if (entry_text[0] == '\0') { + alertpanel_error(_("Menu name is not set.")); + return -1; + } + + if (strchr(entry_text, ':')) { + alertpanel_error(_("Colon ':' is not allowed in the menu name.")); + return -1; + } + + strncpy(action, entry_text, PREFSBUFSIZE - 1); + g_strstrip(action); + + /* Keep space for the ': ' delimiter */ + len = strlen(action) + 2; + if (len >= PREFSBUFSIZE - 1) { + alertpanel_error(_("Menu name is too long.")); + return -1; + } + + strcat(action, ": "); + + GET_ENTRY(actions.cmd_entry); + + if (entry_text[0] == '\0') { + alertpanel_error(_("Command line not set.")); + return -1; + } + + if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) { + alertpanel_error(_("Menu name and command are too long.")); + return -1; + } + + if (action_get_type(entry_text) == ACTION_ERROR) { + alertpanel_error(_("The command\n%s\nhas a syntax error."), + entry_text); + return -1; + } + + strcat(action, entry_text); + + buf[0] = action; + if (row < 0) + row = gtk_clist_append(clist, buf); + else { + gchar *old_action; + gtk_clist_set_text(clist, row, 0, action); + old_action = (gchar *) gtk_clist_get_row_data(clist, row); + if (old_action) + g_free(old_action); + } + + buf[0] = g_strdup(action); + + gtk_clist_set_row_data(clist, row, buf[0]); + + prefs_actions_set_list(); + + return 0; +} + +/* callback functions */ + +static void prefs_actions_register_cb(GtkWidget *w, gpointer data) +{ + prefs_actions_clist_set_row(-1); +} + +static void prefs_actions_substitute_cb(GtkWidget *w, gpointer data) +{ + GtkCList *clist = GTK_CLIST(actions.actions_clist); + gchar *action; + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row == 0) return; + + action = gtk_clist_get_row_data(clist, row); + if (!action) return; + + prefs_actions_clist_set_row(row); +} + +static void prefs_actions_delete_cb(GtkWidget *w, gpointer data) +{ + GtkCList *clist = GTK_CLIST(actions.actions_clist); + gchar *action; + gint row; + + if (!clist->selection) return; + row = GPOINTER_TO_INT(clist->selection->data); + if (row == 0) return; + + if (alertpanel(_("Delete action"), + _("Do you really want to delete this action?"), + _("Yes"), _("No"), NULL) == G_ALERTALTERNATE) + return; + + action = gtk_clist_get_row_data(clist, row); + g_free(action); + gtk_clist_remove(clist, row); + prefs_common.actions_list = g_slist_remove(prefs_common.actions_list, + action); +} + +static void prefs_actions_up(GtkWidget *w, gpointer data) +{ + GtkCList *clist = GTK_CLIST(actions.actions_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 1) + gtk_clist_row_move(clist, row, row - 1); +} + +static void prefs_actions_down(GtkWidget *w, gpointer data) +{ + GtkCList *clist = GTK_CLIST(actions.actions_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0 && row < clist->rows - 1) + gtk_clist_row_move(clist, row, row + 1); +} + +#define ENTRY_SET_TEXT(entry, str) \ + gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "") + +static void prefs_actions_select(GtkCList *clist, gint row, gint column, + GdkEvent *event) +{ + gchar *action; + gchar *cmd; + gchar buf[PREFSBUFSIZE]; + action = gtk_clist_get_row_data(clist, row); + + if (!action) { + ENTRY_SET_TEXT(actions.name_entry, ""); + ENTRY_SET_TEXT(actions.cmd_entry, ""); + return; + } + + strncpy(buf, action, PREFSBUFSIZE - 1); + buf[PREFSBUFSIZE - 1] = 0x00; + cmd = strstr(buf, ": "); + + if (cmd && cmd[2]) + ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]); + else + return; + + *cmd = 0x00; + ENTRY_SET_TEXT(actions.name_entry, buf); +} + +static void prefs_actions_row_move(GtkCList *clist, + gint source_row, gint dest_row) +{ + prefs_actions_set_list(); + if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) { + gtk_clist_moveto(clist, dest_row, -1, + source_row < dest_row ? 1.0 : 0.0, 0.0); + } +} + +static gint prefs_actions_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer *data) +{ + prefs_actions_cancel(widget, data); + return TRUE; +} + +static gboolean prefs_actions_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_actions_cancel(widget, data); + return FALSE; +} + +static void prefs_actions_cancel(GtkWidget *w, gpointer data) +{ + prefs_actions_read_config(); + gtk_widget_hide(actions.window); + inc_unlock(); +} + +static void prefs_actions_ok(GtkWidget *widget, gpointer data) +{ + GtkItemFactory *ifactory; + MainWindow *mainwin = (MainWindow *)data; + + prefs_actions_write_config(); + ifactory = gtk_item_factory_from_widget(mainwin->menubar); + action_update_mainwin_menu(ifactory, mainwin); + gtk_widget_hide(actions.window); + inc_unlock(); +} + diff --git a/src/prefs_actions.h b/src/prefs_actions.h new file mode 100644 index 00000000..f20f6230 --- /dev/null +++ b/src/prefs_actions.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto & The Sylpheed Claws Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_ACTIONS_H__ +#define __PREFS_ACTIONS_H__ + +#include "mainwindow.h" + +void prefs_actions_read_config (void); +void prefs_actions_write_config (void); +void prefs_actions_open (MainWindow *mainwin); + +#endif /* __PREFS_ACTIONS_H__ */ diff --git a/src/prefs_common.c b/src/prefs_common.c new file mode 100644 index 00000000..51c2e6ba --- /dev/null +++ b/src/prefs_common.c @@ -0,0 +1,3545 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "prefs.h" +#include "prefs_common.h" +#include "prefs_display_header.h" +#include "prefs_summary_column.h" +#include "mainwindow.h" +#include "summaryview.h" +#include "messageview.h" +#include "manage_window.h" +#include "inc.h" +#include "menu.h" +#include "codeconv.h" +#include "utils.h" +#include "gtkutils.h" +#include "alertpanel.h" +#include "folder.h" +#include "socket.h" + +PrefsCommon prefs_common; + +static PrefsDialog dialog; + +static struct Receive { + GtkWidget *checkbtn_incext; + GtkWidget *entry_incext; + GtkWidget *button_incext; + + GtkWidget *checkbtn_local; + GtkWidget *checkbtn_filter_on_inc; + GtkWidget *entry_spool; + + GtkWidget *checkbtn_autochk; + GtkWidget *spinbtn_autochk; + GtkObject *spinbtn_autochk_adj; + + GtkWidget *checkbtn_chkonstartup; + GtkWidget *checkbtn_scan_after_inc; + + GtkWidget *spinbtn_maxarticle; + GtkObject *spinbtn_maxarticle_adj; +} receive; + +static struct Send { + GtkWidget *checkbtn_extsend; + GtkWidget *entry_extsend; + GtkWidget *button_extsend; + + GtkWidget *checkbtn_savemsg; + + GtkWidget *optmenu_charset; + GtkWidget *optmenu_encoding_method; +} p_send; + +static struct Compose { + GtkWidget *checkbtn_autosig; + GtkWidget *entry_sigsep; + + GtkWidget *checkbtn_autoextedit; + GtkWidget *spinbtn_undolevel; + GtkObject *spinbtn_undolevel_adj; + GtkWidget *spinbtn_linewrap; + GtkObject *spinbtn_linewrap_adj; + GtkWidget *checkbtn_wrapquote; + GtkWidget *checkbtn_autowrap; + GtkWidget *checkbtn_wrapatsend; + + GtkWidget *checkbtn_reply_account_autosel; + GtkWidget *checkbtn_quote; + GtkWidget *checkbtn_default_reply_list; +} compose; + +static struct Quote { + GtkWidget *entry_quotemark; + GtkWidget *text_quotefmt; + + GtkWidget *entry_fw_quotemark; + GtkWidget *text_fw_quotefmt; +} quote; + +static struct Display { + GtkWidget *entry_textfont; + GtkWidget *button_textfont; + + GtkWidget *chkbtn_folder_unread; + GtkWidget *entry_ng_abbrev_len; + GtkWidget *spinbtn_ng_abbrev_len; + GtkObject *spinbtn_ng_abbrev_len_adj; + + GtkWidget *chkbtn_transhdr; + + GtkWidget *chkbtn_swapfrom; + GtkWidget *chkbtn_expand_thread; + GtkWidget *entry_datefmt; +} display; + +static struct Message { + GtkWidget *chkbtn_enablecol; + GtkWidget *button_edit_col; + GtkWidget *chkbtn_mbalnum; + GtkWidget *chkbtn_disphdrpane; + GtkWidget *chkbtn_disphdr; + GtkWidget *spinbtn_linespc; + GtkObject *spinbtn_linespc_adj; + GtkWidget *chkbtn_headspc; + + GtkWidget *chkbtn_smoothscroll; + GtkWidget *spinbtn_scrollstep; + GtkObject *spinbtn_scrollstep_adj; + GtkWidget *chkbtn_halfpage; + + GtkWidget *chkbtn_resize_image; +} message; + +#if USE_GPGME +static struct Privacy { + GtkWidget *checkbtn_auto_check_signatures; + GtkWidget *checkbtn_gpg_signature_popup; + GtkWidget *checkbtn_store_passphrase; + GtkWidget *spinbtn_store_passphrase; + GtkObject *spinbtn_store_passphrase_adj; + GtkWidget *checkbtn_passphrase_grab; + GtkWidget *checkbtn_gpg_warning; +} privacy; +#endif + +static struct Interface { + GtkWidget *checkbtn_always_show_msg; + GtkWidget *checkbtn_openunread; + GtkWidget *checkbtn_mark_as_read_on_newwin; + GtkWidget *checkbtn_openinbox; + GtkWidget *checkbtn_immedexec; + GtkWidget *optmenu_recvdialog; + GtkWidget *checkbtn_no_recv_err_panel; + GtkWidget *checkbtn_close_recv_dialog; +} interface; + +static struct Other { + GtkWidget *uri_combo; + GtkWidget *uri_entry; + GtkWidget *printcmd_entry; + GtkWidget *exteditor_combo; + GtkWidget *exteditor_entry; + + GtkWidget *checkbtn_addaddrbyclick; + GtkWidget *checkbtn_confonexit; + GtkWidget *checkbtn_cleanonexit; + GtkWidget *checkbtn_askonclean; + GtkWidget *checkbtn_warnqueued; + + GtkWidget *spinbtn_iotimeout; + GtkObject *spinbtn_iotimeout_adj; +} other; + +static struct MessageColorButtons { + GtkWidget *quote_level1_btn; + GtkWidget *quote_level2_btn; + GtkWidget *quote_level3_btn; + GtkWidget *uri_btn; +} color_buttons; + +static struct KeybindDialog { + GtkWidget *window; + GtkWidget *combo; +} keybind; + +static GtkWidget *quote_desc_win; +static GtkWidget *font_sel_win; +static GtkWidget *quote_color_win; +static GtkWidget *color_dialog; + +static void prefs_common_charset_set_data_from_optmenu (PrefParam *pparam); +static void prefs_common_charset_set_optmenu (PrefParam *pparam); +static void prefs_common_encoding_set_data_from_optmenu (PrefParam *pparam); +static void prefs_common_encoding_set_optmenu (PrefParam *pparam); +static void prefs_common_recv_dialog_set_data_from_optmenu (PrefParam *pparam); +static void prefs_common_recv_dialog_set_optmenu (PrefParam *pparam); + +/* + parameter name, default value, pointer to the prefs variable, data type, + pointer to the widget pointer, + pointer to the function for data setting, + pointer to the function for widget setting + */ + +static PrefParam param[] = { + /* Receive */ + {"use_ext_inc", "FALSE", &prefs_common.use_extinc, P_BOOL, + &receive.checkbtn_incext, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"ext_inc_path", DEFAULT_INC_PATH, &prefs_common.extinc_cmd, P_STRING, + &receive.entry_incext, + prefs_set_data_from_entry, prefs_set_entry}, + + {"inc_local", "FALSE", &prefs_common.inc_local, P_BOOL, + &receive.checkbtn_local, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"filter_on_inc_local", "TRUE", &prefs_common.filter_on_inc, P_BOOL, + &receive.checkbtn_filter_on_inc, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"spool_path", DEFAULT_SPOOL_PATH, &prefs_common.spool_path, P_STRING, + &receive.entry_spool, + prefs_set_data_from_entry, prefs_set_entry}, + + {"autochk_newmail", "FALSE", &prefs_common.autochk_newmail, P_BOOL, + &receive.checkbtn_autochk, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"autochk_interval", "10", &prefs_common.autochk_itv, P_INT, + &receive.spinbtn_autochk, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + {"check_on_startup", "FALSE", &prefs_common.chk_on_startup, P_BOOL, + &receive.checkbtn_chkonstartup, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"scan_all_after_inc", "FALSE", &prefs_common.scan_all_after_inc, + P_BOOL, &receive.checkbtn_scan_after_inc, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"max_news_articles", "300", &prefs_common.max_articles, P_INT, + &receive.spinbtn_maxarticle, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + + /* Send */ + {"use_ext_sendmail", "FALSE", &prefs_common.use_extsend, P_BOOL, + &p_send.checkbtn_extsend, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"ext_sendmail_cmd", DEFAULT_SENDMAIL_CMD, + &prefs_common.extsend_cmd, P_STRING, + &p_send.entry_extsend, prefs_set_data_from_entry, prefs_set_entry}, + {"save_message", "TRUE", &prefs_common.savemsg, P_BOOL, + &p_send.checkbtn_savemsg, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"outgoing_charset", CS_AUTO, &prefs_common.outgoing_charset, P_STRING, + &p_send.optmenu_charset, + prefs_common_charset_set_data_from_optmenu, + prefs_common_charset_set_optmenu}, + {"encoding_method", "0", &prefs_common.encoding_method, P_ENUM, + &p_send.optmenu_encoding_method, + prefs_common_encoding_set_data_from_optmenu, + prefs_common_encoding_set_optmenu}, + + {"allow_jisx0201_kana", "FALSE", &prefs_common.allow_jisx0201_kana, + P_BOOL, NULL, NULL, NULL}, + + /* Compose */ + {"auto_signature", "TRUE", &prefs_common.auto_sig, P_BOOL, + &compose.checkbtn_autosig, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"signature_separator", "-- ", &prefs_common.sig_sep, P_STRING, + &compose.entry_sigsep, prefs_set_data_from_entry, prefs_set_entry}, + + {"auto_ext_editor", "FALSE", &prefs_common.auto_exteditor, P_BOOL, + &compose.checkbtn_autoextedit, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"undo_level", "50", &prefs_common.undolevels, P_INT, + &compose.spinbtn_undolevel, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + + {"linewrap_length", "72", &prefs_common.linewrap_len, P_INT, + &compose.spinbtn_linewrap, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + {"linewrap_quotation", "FALSE", &prefs_common.linewrap_quote, P_BOOL, + &compose.checkbtn_wrapquote, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"linewrap_auto", "FALSE", &prefs_common.autowrap, P_BOOL, + &compose.checkbtn_autowrap, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"linewrap_before_sending", "FALSE", + &prefs_common.linewrap_at_send, P_BOOL, + &compose.checkbtn_wrapatsend, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"reply_with_quote", "TRUE", &prefs_common.reply_with_quote, P_BOOL, + &compose.checkbtn_quote, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"reply_account_autoselect", "TRUE", + &prefs_common.reply_account_autosel, P_BOOL, + &compose.checkbtn_reply_account_autosel, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"default_reply_list", "TRUE", &prefs_common.default_reply_list, P_BOOL, + &compose.checkbtn_default_reply_list, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"show_ruler", "TRUE", &prefs_common.show_ruler, P_BOOL, + NULL, NULL, NULL}, + + /* Quote */ + {"reply_quote_mark", "> ", &prefs_common.quotemark, P_STRING, + "e.entry_quotemark, prefs_set_data_from_entry, prefs_set_entry}, + {"reply_quote_format", "On %d\\n%f wrote:\\n\\n%Q", + &prefs_common.quotefmt, P_STRING, "e.text_quotefmt, + prefs_set_data_from_text, prefs_set_text}, + + {"forward_quote_mark", "> ", &prefs_common.fw_quotemark, P_STRING, + "e.entry_fw_quotemark, + prefs_set_data_from_entry, prefs_set_entry}, + {"forward_quote_format", + "\\n\\nBegin forwarded message:\\n\\n" + "?d{Date: %d\\n}?f{From: %f\\n}?t{To: %t\\n}?c{Cc: %c\\n}" + "?n{Newsgroups: %n\\n}?s{Subject: %s\\n}\\n\\n%M", + &prefs_common.fw_quotefmt, P_STRING, "e.text_fw_quotefmt, + prefs_set_data_from_text, prefs_set_text}, + + /* Display */ + {"widget_font", NULL, &prefs_common.widgetfont, P_STRING, + NULL, NULL, NULL}, + {"message_font", DEFAULT_MESSAGE_FONT, &prefs_common.textfont, P_STRING, + &display.entry_textfont, prefs_set_data_from_entry, prefs_set_entry}, + {"normal_font", DEFAULT_NORMAL_FONT, &prefs_common.normalfont, P_STRING, + NULL, NULL, NULL}, + {"bold_font", DEFAULT_BOLD_FONT, &prefs_common.boldfont, P_STRING, + NULL, NULL, NULL}, + {"small_font", DEFAULT_SMALL_FONT, &prefs_common.smallfont, P_STRING, + NULL, NULL, NULL}, + {"title_font", DEFAULT_TITLE_FONT, &prefs_common.titlefont, P_STRING, + NULL, NULL, NULL}, + + {"display_folder_unread_num", "TRUE", + &prefs_common.display_folder_unread, P_BOOL, + &display.chkbtn_folder_unread, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"newsgroup_abbrev_len", "16", + &prefs_common.ng_abbrev_len, P_INT, + &display.spinbtn_ng_abbrev_len, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + + {"translate_header", "TRUE", &prefs_common.trans_hdr, P_BOOL, + &display.chkbtn_transhdr, + prefs_set_data_from_toggle, prefs_set_toggle}, + + /* Display: Summary View */ + {"enable_swap_from", "FALSE", &prefs_common.swap_from, P_BOOL, + &display.chkbtn_swapfrom, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"date_format", "%y/%m/%d(%a) %H:%M", &prefs_common.date_format, + P_STRING, &display.entry_datefmt, + prefs_set_data_from_entry, prefs_set_entry}, + {"expand_thread", "TRUE", &prefs_common.expand_thread, P_BOOL, + &display.chkbtn_expand_thread, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"enable_hscrollbar", "TRUE", &prefs_common.enable_hscrollbar, P_BOOL, + NULL, NULL, NULL}, + {"bold_unread", "TRUE", &prefs_common.bold_unread, P_BOOL, + NULL, NULL, NULL}, + + {"enable_thread", "TRUE", &prefs_common.enable_thread, P_BOOL, + NULL, NULL, NULL}, + {"toolbar_style", "3", &prefs_common.toolbar_style, P_ENUM, + NULL, NULL, NULL}, + {"show_statusbar", "TRUE", &prefs_common.show_statusbar, P_BOOL, + NULL, NULL, NULL}, + + {"folderview_vscrollbar_policy", "0", + &prefs_common.folderview_vscrollbar_policy, P_ENUM, + NULL, NULL, NULL}, + + {"summary_col_show_mark", "TRUE", + &prefs_common.summary_col_visible[S_COL_MARK], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_unread", "TRUE", + &prefs_common.summary_col_visible[S_COL_UNREAD], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_mime", "TRUE", + &prefs_common.summary_col_visible[S_COL_MIME], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_subject", "TRUE", + &prefs_common.summary_col_visible[S_COL_SUBJECT], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_from", "TRUE", + &prefs_common.summary_col_visible[S_COL_FROM], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_date", "TRUE", + &prefs_common.summary_col_visible[S_COL_DATE], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_size", "TRUE", + &prefs_common.summary_col_visible[S_COL_SIZE], P_BOOL, NULL, NULL, NULL}, + {"summary_col_show_number", "FALSE", + &prefs_common.summary_col_visible[S_COL_NUMBER], P_BOOL, NULL, NULL, NULL}, + + {"summary_col_pos_mark", "0", + &prefs_common.summary_col_pos[S_COL_MARK], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_unread", "1", + &prefs_common.summary_col_pos[S_COL_UNREAD], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_mime", "2", + &prefs_common.summary_col_pos[S_COL_MIME], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_subject", "3", + &prefs_common.summary_col_pos[S_COL_SUBJECT], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_from", "4", + &prefs_common.summary_col_pos[S_COL_FROM], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_date", "5", + &prefs_common.summary_col_pos[S_COL_DATE], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_size", "6", + &prefs_common.summary_col_pos[S_COL_SIZE], P_INT, NULL, NULL, NULL}, + {"summary_col_pos_number", "7", + &prefs_common.summary_col_pos[S_COL_NUMBER], P_INT, NULL, NULL, NULL}, + + {"summary_col_size_mark", "10", + &prefs_common.summary_col_size[S_COL_MARK], P_INT, NULL, NULL, NULL}, + {"summary_col_size_unread", "13", + &prefs_common.summary_col_size[S_COL_UNREAD], P_INT, NULL, NULL, NULL}, + {"summary_col_size_mime", "10", + &prefs_common.summary_col_size[S_COL_MIME], P_INT, NULL, NULL, NULL}, + {"summary_col_size_subject", "200", + &prefs_common.summary_col_size[S_COL_SUBJECT], P_INT, NULL, NULL, NULL}, + {"summary_col_size_from", "120", + &prefs_common.summary_col_size[S_COL_FROM], P_INT, NULL, NULL, NULL}, + {"summary_col_size_date", "118", + &prefs_common.summary_col_size[S_COL_DATE], P_INT, NULL, NULL, NULL}, + {"summary_col_size_size", "45", + &prefs_common.summary_col_size[S_COL_SIZE], P_INT, NULL, NULL, NULL}, + {"summary_col_size_number", "40", + &prefs_common.summary_col_size[S_COL_NUMBER], P_INT, NULL, NULL, NULL}, + + /* Widget size */ + {"folderwin_x", "16", &prefs_common.folderwin_x, P_INT, + NULL, NULL, NULL}, + {"folderwin_y", "16", &prefs_common.folderwin_y, P_INT, + NULL, NULL, NULL}, + {"folderview_width", "179", &prefs_common.folderview_width, P_INT, + NULL, NULL, NULL}, + {"folderview_height", "450", &prefs_common.folderview_height, P_INT, + NULL, NULL, NULL}, + {"folderview_visible", "TRUE", &prefs_common.folderview_visible, P_BOOL, + NULL, NULL, NULL}, + + {"folder_col_folder", "150", &prefs_common.folder_col_folder, P_INT, + NULL, NULL, NULL}, + {"folder_col_new", "32", &prefs_common.folder_col_new, P_INT, + NULL, NULL, NULL}, + {"folder_col_unread", "32", &prefs_common.folder_col_unread, P_INT, + NULL, NULL, NULL}, + {"folder_col_total", "32", &prefs_common.folder_col_total, P_INT, + NULL, NULL, NULL}, + + {"summaryview_width", "600", &prefs_common.summaryview_width, P_INT, + NULL, NULL, NULL}, + {"summaryview_height", "157", &prefs_common.summaryview_height, P_INT, + NULL, NULL, NULL}, + + {"main_messagewin_x", "256", &prefs_common.main_msgwin_x, P_INT, + NULL, NULL, NULL}, + {"main_messagewin_y", "210", &prefs_common.main_msgwin_y, P_INT, + NULL, NULL, NULL}, + {"messageview_width", "600", &prefs_common.msgview_width, P_INT, + NULL, NULL, NULL}, + {"messageview_height", "300", &prefs_common.msgview_height, P_INT, + NULL, NULL, NULL}, + {"messageview_visible", "TRUE", &prefs_common.msgview_visible, P_BOOL, + NULL, NULL, NULL}, + + {"mainview_x", "64", &prefs_common.mainview_x, P_INT, + NULL, NULL, NULL}, + {"mainview_y", "64", &prefs_common.mainview_y, P_INT, + NULL, NULL, NULL}, + {"mainview_width", "600", &prefs_common.mainview_width, P_INT, + NULL, NULL, NULL}, + {"mainview_height", "600", &prefs_common.mainview_height, P_INT, + NULL, NULL, NULL}, + {"mainwin_x", "64", &prefs_common.mainwin_x, P_INT, + NULL, NULL, NULL}, + {"mainwin_y", "64", &prefs_common.mainwin_y, P_INT, + NULL, NULL, NULL}, + {"mainwin_width", "800", &prefs_common.mainwin_width, P_INT, + NULL, NULL, NULL}, + {"mainwin_height", "600", &prefs_common.mainwin_height, P_INT, + NULL, NULL, NULL}, + {"messagewin_width", "600", &prefs_common.msgwin_width, P_INT, + NULL, NULL, NULL}, + {"messagewin_height", "540", &prefs_common.msgwin_height, P_INT, + NULL, NULL, NULL}, + {"sourcewin_width", "600", &prefs_common.sourcewin_width, P_INT, + NULL, NULL, NULL}, + {"sourcewin_height", "500", &prefs_common.sourcewin_height, P_INT, + NULL, NULL, NULL}, + {"compose_width", "600", &prefs_common.compose_width, P_INT, + NULL, NULL, NULL}, + {"compose_height", "560", &prefs_common.compose_height, P_INT, + NULL, NULL, NULL}, + + /* Message */ + {"enable_color", "TRUE", &prefs_common.enable_color, P_BOOL, + &message.chkbtn_enablecol, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"quote_level1_color", "179", &prefs_common.quote_level1_col, P_INT, + NULL, NULL, NULL}, + {"quote_level2_color", "179", &prefs_common.quote_level2_col, P_INT, + NULL, NULL, NULL}, + {"quote_level3_color", "179", &prefs_common.quote_level3_col, P_INT, + NULL, NULL, NULL}, + {"uri_color", "32512", &prefs_common.uri_col, P_INT, + NULL, NULL, NULL}, + {"signature_color", "0", &prefs_common.sig_col, P_USHORT, + NULL, NULL, NULL}, + {"recycle_quote_colors", "FALSE", &prefs_common.recycle_quote_colors, + P_BOOL, NULL, NULL, NULL}, + + {"convert_mb_alnum", "FALSE", &prefs_common.conv_mb_alnum, P_BOOL, + &message.chkbtn_mbalnum, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"display_header_pane", "TRUE", &prefs_common.display_header_pane, + P_BOOL, &message.chkbtn_disphdrpane, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"display_header", "TRUE", &prefs_common.display_header, P_BOOL, + &message.chkbtn_disphdr, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"line_space", "2", &prefs_common.line_space, P_INT, + &message.spinbtn_linespc, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + {"enable_head_space", "FALSE", &prefs_common.head_space, P_BOOL, + &message.chkbtn_headspc, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"enable_smooth_scroll", "FALSE", + &prefs_common.enable_smooth_scroll, P_BOOL, + &message.chkbtn_smoothscroll, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"scroll_step", "1", &prefs_common.scroll_step, P_INT, + &message.spinbtn_scrollstep, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + {"scroll_half_page", "FALSE", &prefs_common.scroll_halfpage, P_BOOL, + &message.chkbtn_halfpage, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"resize_image", "TRUE", &prefs_common.resize_image, P_BOOL, + &message.chkbtn_resize_image, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"show_other_header", "FALSE", &prefs_common.show_other_header, P_BOOL, + NULL, NULL, NULL}, + + /* MIME viewer */ + {"mime_image_viewer", "display '%s'", + &prefs_common.mime_image_viewer, P_STRING, NULL, NULL, NULL}, + {"mime_audio_player", "play '%s'", + &prefs_common.mime_audio_player, P_STRING, NULL, NULL, NULL}, + {"mime_open_command", "gedit '%s'", + &prefs_common.mime_open_cmd, P_STRING, NULL, NULL, NULL}, + +#if USE_GPGME + /* Privacy */ + {"auto_check_signatures", "TRUE", + &prefs_common.auto_check_signatures, P_BOOL, + &privacy.checkbtn_auto_check_signatures, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"gpg_signature_popup", "FALSE", + &prefs_common.gpg_signature_popup, P_BOOL, + &privacy.checkbtn_gpg_signature_popup, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL, + &privacy.checkbtn_store_passphrase, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"store_passphrase_timeout", "0", + &prefs_common.store_passphrase_timeout, P_INT, + &privacy.spinbtn_store_passphrase, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, +#ifndef __MINGW32__ + {"passphrase_grab", "FALSE", &prefs_common.passphrase_grab, P_BOOL, + &privacy.checkbtn_passphrase_grab, + prefs_set_data_from_toggle, prefs_set_toggle}, +#endif /* __MINGW32__ */ + {"gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL, + &privacy.checkbtn_gpg_warning, + prefs_set_data_from_toggle, prefs_set_toggle}, +#endif /* USE_GPGME */ + + /* Interface */ + {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL, + NULL, NULL, NULL}, + {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL, + NULL, NULL, NULL}, + + {"emulate_emacs", "FALSE", &prefs_common.emulate_emacs, P_BOOL, + NULL, NULL, NULL}, + {"always_show_message_when_selected", "FALSE", + &prefs_common.always_show_msg, + P_BOOL, &interface.checkbtn_always_show_msg, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"open_unread_on_enter", "FALSE", &prefs_common.open_unread_on_enter, + P_BOOL, &interface.checkbtn_openunread, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"mark_as_read_on_new_window", "FALSE", + &prefs_common.mark_as_read_on_new_window, + P_BOOL, &interface.checkbtn_mark_as_read_on_newwin, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"open_inbox_on_inc", "FALSE", &prefs_common.open_inbox_on_inc, + P_BOOL, &interface.checkbtn_openinbox, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"immediate_execution", "TRUE", &prefs_common.immediate_exec, P_BOOL, + &interface.checkbtn_immedexec, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"receive_dialog_mode", "1", &prefs_common.recv_dialog_mode, P_ENUM, + &interface.optmenu_recvdialog, + prefs_common_recv_dialog_set_data_from_optmenu, + prefs_common_recv_dialog_set_optmenu}, + {"no_receive_error_panel", "FALSE", &prefs_common.no_recv_err_panel, + P_BOOL, &interface.checkbtn_no_recv_err_panel, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"close_receive_dialog", "TRUE", &prefs_common.close_recv_dialog, + P_BOOL, &interface.checkbtn_close_recv_dialog, + prefs_set_data_from_toggle, prefs_set_toggle}, + + /* Other */ + {"uri_open_command", DEFAULT_BROWSER_CMD, + &prefs_common.uri_cmd, P_STRING, + &other.uri_entry, prefs_set_data_from_entry, prefs_set_entry}, + {"print_command", "lpr %s", &prefs_common.print_cmd, P_STRING, + &other.printcmd_entry, prefs_set_data_from_entry, prefs_set_entry}, + {"ext_editor_command", "gedit %s", + &prefs_common.ext_editor_cmd, P_STRING, + &other.exteditor_entry, prefs_set_data_from_entry, prefs_set_entry}, + + {"add_address_by_click", "FALSE", &prefs_common.add_address_by_click, + P_BOOL, &other.checkbtn_addaddrbyclick, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"confirm_on_exit", "FALSE", &prefs_common.confirm_on_exit, P_BOOL, + &other.checkbtn_confonexit, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"clean_trash_on_exit", "FALSE", &prefs_common.clean_on_exit, P_BOOL, + &other.checkbtn_cleanonexit, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"ask_on_cleaning", "TRUE", &prefs_common.ask_on_clean, P_BOOL, + &other.checkbtn_askonclean, + prefs_set_data_from_toggle, prefs_set_toggle}, + {"warn_queued_on_exit", "TRUE", &prefs_common.warn_queued_on_exit, + P_BOOL, &other.checkbtn_warnqueued, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"io_timeout_secs", "60", &prefs_common.io_timeout_secs, + P_INT, &other.spinbtn_iotimeout, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + + {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} +}; + +/* widget creating functions */ +static void prefs_common_create (void); +static void prefs_receive_create (void); +static void prefs_send_create (void); +static void prefs_compose_create (void); +static void prefs_quote_create (void); +static void prefs_display_create (void); +static void prefs_message_create (void); +#if USE_GPGME +static void prefs_privacy_create (void); +#endif +static void prefs_interface_create (void); +static void prefs_other_create (void); + +static void date_format_ok_btn_clicked (GtkButton *button, + GtkWidget **widget); +static void date_format_cancel_btn_clicked (GtkButton *button, + GtkWidget **widget); +static gboolean date_format_key_pressed (GtkWidget *keywidget, + GdkEventKey *event, + GtkWidget **widget); +static gboolean date_format_on_delete (GtkWidget *dialogwidget, + GdkEventAny *event, + GtkWidget **widget); +static void date_format_entry_on_change (GtkEditable *editable, + GtkLabel *example); +static void date_format_select_row (GtkWidget *date_format_list, + gint row, + gint column, + GdkEventButton *event, + GtkWidget *date_format); +static GtkWidget *date_format_create (GtkButton *button, + void *data); + +static void prefs_quote_description_create (void); +static gboolean prefs_quote_description_key_pressed + (GtkWidget *widget, + GdkEventKey *event, + gpointer data); + +static void prefs_quote_colors_dialog (void); +static void prefs_quote_colors_dialog_create (void); +static gboolean prefs_quote_colors_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void quote_color_set_dialog (GtkWidget *widget, + gpointer data); +static void quote_colors_set_dialog_ok (GtkWidget *widget, + gpointer data); +static void quote_colors_set_dialog_cancel (GtkWidget *widget, + gpointer data); +static gboolean quote_colors_set_dialog_key_pressed + (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void set_button_bg_color (GtkWidget *widget, + gint color); +static void prefs_enable_message_color_toggled (void); +static void prefs_recycle_colors_toggled (GtkWidget *widget); + +static void prefs_font_select (GtkButton *button); +static gboolean prefs_font_selection_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_font_selection_ok (GtkButton *button); + +static void prefs_keybind_select (void); +static gint prefs_keybind_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_keybind_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_keybind_cancel (void); +static void prefs_keybind_apply_clicked (GtkWidget *widget); + +static gint prefs_common_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_common_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_common_ok (void); +static void prefs_common_apply (void); +static void prefs_common_cancel (void); + +void prefs_common_read_config(void) +{ + FILE *fp; + gchar *path; + gchar buf[PREFSBUFSIZE]; + + prefs_read_config(param, "Common", COMMON_RC); + + prefs_common.online_mode = TRUE; + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY, + NULL); + if ((fp = fopen(path, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(path, "fopen"); + g_free(path); + return; + } + g_free(path); + while (fgets(buf, sizeof(buf), fp) != NULL) { + g_strstrip(buf); + if (buf[0] == '\0') continue; + prefs_common.mime_open_cmd_history = + add_history(prefs_common.mime_open_cmd_history, buf); + } + fclose(fp); + + prefs_common.mime_open_cmd_history = + g_list_reverse(prefs_common.mime_open_cmd_history); +} + +void prefs_common_write_config(void) +{ + GList *cur; + FILE *fp; + gchar *path; + + prefs_write_config(param, "Common", COMMON_RC); + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMAND_HISTORY, + NULL); + if ((fp = fopen(path, "wb")) == NULL) { + FILE_OP_ERROR(path, "fopen"); + g_free(path); + return; + } + + for (cur = prefs_common.mime_open_cmd_history; + cur != NULL; cur = cur->next) { + fputs((gchar *)cur->data, fp); + fputc('\n', fp); + } + + fclose(fp); + g_free(path); +} + +void prefs_common_open(void) +{ + inc_lock(); + + if (!dialog.window) { + prefs_common_create(); + } + + manage_window_set_transient(GTK_WINDOW(dialog.window)); + gtk_notebook_set_current_page(GTK_NOTEBOOK(dialog.notebook), 0); + gtk_widget_grab_focus(dialog.ok_btn); + + prefs_set_dialog(param); + + gtk_widget_show(dialog.window); +} + +static void prefs_common_create(void) +{ + gint page = 0; + + debug_print(_("Creating common preferences window...\n")); + + prefs_dialog_create(&dialog); + gtk_window_set_title (GTK_WINDOW(dialog.window), + _("Common Preferences")); + g_signal_connect (G_OBJECT(dialog.window), "delete_event", + G_CALLBACK(prefs_common_deleted), NULL); + g_signal_connect (G_OBJECT(dialog.window), "key_press_event", + G_CALLBACK(prefs_common_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(dialog.window); + + g_signal_connect (G_OBJECT(dialog.ok_btn), "clicked", + G_CALLBACK(prefs_common_ok), NULL); + g_signal_connect (G_OBJECT(dialog.apply_btn), "clicked", + G_CALLBACK(prefs_common_apply), NULL); + g_signal_connect (G_OBJECT(dialog.cancel_btn), "clicked", + G_CALLBACK(prefs_common_cancel), NULL); + + /* create all widgets on notebook */ + prefs_receive_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Receive"), page++); + prefs_send_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Send"), page++); + prefs_compose_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Compose"), page++); + prefs_quote_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Quote"), page++); + prefs_display_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Display"), page++); + prefs_message_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Message"), page++); +#if USE_GPGME + prefs_privacy_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++); +#endif + prefs_interface_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Interface"), page++); + prefs_other_create(); + SET_NOTEBOOK_LABEL(dialog.notebook, _("Other"), page++); + + gtk_widget_show_all(dialog.window); +} + +static void prefs_receive_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *frame_incext; + GtkWidget *checkbtn_incext; + GtkWidget *hbox; + GtkWidget *label_incext; + GtkWidget *entry_incext; + /* GtkWidget *button_incext; */ + + GtkWidget *frame_spool; + GtkWidget *checkbtn_local; + GtkWidget *checkbtn_filter_on_inc; + GtkWidget *label_spool; + GtkWidget *entry_spool; + + GtkWidget *hbox_autochk; + GtkWidget *checkbtn_autochk; + GtkWidget *label_autochk1; + GtkObject *spinbtn_autochk_adj; + GtkWidget *spinbtn_autochk; + GtkWidget *label_autochk2; + GtkWidget *checkbtn_chkonstartup; + GtkWidget *checkbtn_scan_after_inc; + + GtkWidget *frame_news; + GtkWidget *label_maxarticle; + GtkWidget *spinbtn_maxarticle; + GtkObject *spinbtn_maxarticle_adj; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME(vbox1, frame_incext, _("External program")); + + vbox2 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame_incext), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + /* Use of external incorporation program */ + PACK_CHECK_BUTTON (vbox2, checkbtn_incext, + _("Use external program for incorporation")); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + SET_TOGGLE_SENSITIVITY (checkbtn_incext, hbox); + + label_incext = gtk_label_new (_("Command")); + gtk_widget_show (label_incext); + gtk_box_pack_start (GTK_BOX (hbox), label_incext, FALSE, FALSE, 0); + + entry_incext = gtk_entry_new (); + gtk_widget_show (entry_incext); + gtk_box_pack_start (GTK_BOX (hbox), entry_incext, TRUE, TRUE, 0); + +#if 0 + button_incext = gtk_button_new_with_label ("... "); + gtk_widget_show (button_incext); + gtk_box_pack_start (GTK_BOX (hbox), button_incext, FALSE, FALSE, 0); +#endif + + PACK_FRAME(vbox1, frame_spool, _("Local spool")); + + vbox2 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame_spool), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + hbox = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox, checkbtn_local, _("Incorporate from spool")); + PACK_CHECK_BUTTON (hbox, checkbtn_filter_on_inc, + _("Filter on incorporation")); + SET_TOGGLE_SENSITIVITY (checkbtn_local, checkbtn_filter_on_inc); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + SET_TOGGLE_SENSITIVITY (checkbtn_local, hbox); + + label_spool = gtk_label_new (_("Spool path")); + gtk_widget_show (label_spool); + gtk_box_pack_start (GTK_BOX (hbox), label_spool, FALSE, FALSE, 0); + + entry_spool = gtk_entry_new (); + gtk_widget_show (entry_spool); + gtk_box_pack_start (GTK_BOX (hbox), entry_spool, TRUE, TRUE, 0); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + /* Auto-checking */ + hbox_autochk = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox_autochk); + gtk_box_pack_start (GTK_BOX (vbox2), hbox_autochk, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox_autochk, checkbtn_autochk, + _("Auto-check new mail")); + + label_autochk1 = gtk_label_new (_("every")); + gtk_widget_show (label_autochk1); + gtk_box_pack_start (GTK_BOX (hbox_autochk), label_autochk1, FALSE, FALSE, 0); + + spinbtn_autochk_adj = gtk_adjustment_new (5, 1, 100, 1, 10, 10); + spinbtn_autochk = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_autochk_adj), 1, 0); + gtk_widget_show (spinbtn_autochk); + gtk_box_pack_start (GTK_BOX (hbox_autochk), spinbtn_autochk, FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_autochk, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_autochk), TRUE); + + label_autochk2 = gtk_label_new (_("minute(s)")); + gtk_widget_show (label_autochk2); + gtk_box_pack_start (GTK_BOX (hbox_autochk), label_autochk2, FALSE, FALSE, 0); + + SET_TOGGLE_SENSITIVITY(checkbtn_autochk, label_autochk1); + SET_TOGGLE_SENSITIVITY(checkbtn_autochk, spinbtn_autochk); + SET_TOGGLE_SENSITIVITY(checkbtn_autochk, label_autochk2); + + PACK_CHECK_BUTTON (vbox2, checkbtn_chkonstartup, + _("Check new mail on startup")); + PACK_CHECK_BUTTON (vbox2, checkbtn_scan_after_inc, + _("Update all local folders after incorporation")); + + PACK_FRAME(vbox1, frame_news, _("News")); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_container_add (GTK_CONTAINER (frame_news), hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 8); + + label_maxarticle = gtk_label_new + (_("Maximum number of articles to download\n" + "(unlimited if 0 is specified)")); + gtk_widget_show (label_maxarticle); + gtk_box_pack_start (GTK_BOX (hbox), label_maxarticle, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label_maxarticle), GTK_JUSTIFY_LEFT); + + spinbtn_maxarticle_adj = + gtk_adjustment_new (300, 0, 10000, 10, 100, 100); + spinbtn_maxarticle = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_maxarticle_adj), 10, 0); + gtk_widget_show (spinbtn_maxarticle); + gtk_box_pack_start (GTK_BOX (hbox), spinbtn_maxarticle, + FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_maxarticle, 64, -1); + gtk_spin_button_set_numeric + (GTK_SPIN_BUTTON (spinbtn_maxarticle), TRUE); + + receive.checkbtn_incext = checkbtn_incext; + receive.entry_incext = entry_incext; + /* receive.button_incext = button_incext; */ + + receive.checkbtn_local = checkbtn_local; + receive.checkbtn_filter_on_inc = checkbtn_filter_on_inc; + receive.entry_spool = entry_spool; + + receive.checkbtn_autochk = checkbtn_autochk; + receive.spinbtn_autochk = spinbtn_autochk; + receive.spinbtn_autochk_adj = spinbtn_autochk_adj; + + receive.checkbtn_chkonstartup = checkbtn_chkonstartup; + receive.checkbtn_scan_after_inc = checkbtn_scan_after_inc; + + receive.spinbtn_maxarticle = spinbtn_maxarticle; + receive.spinbtn_maxarticle_adj = spinbtn_maxarticle_adj; +} + +static void prefs_send_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *frame_extsend; + GtkWidget *vbox_extsend; + GtkWidget *checkbtn_extsend; + GtkWidget *hbox1; + GtkWidget *label_extsend; + GtkWidget *entry_extsend; + /* GtkWidget *button_extsend; */ + GtkWidget *checkbtn_savemsg; + GtkWidget *label_outcharset; + GtkWidget *optmenu_charset; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *label_charset_desc; + GtkWidget *optmenu_encoding; + GtkWidget *label_encoding; + GtkWidget *label_encoding_desc; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME(vbox1, frame_extsend, _("External program")); + + vbox_extsend = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox_extsend); + gtk_container_add (GTK_CONTAINER (frame_extsend), vbox_extsend); + gtk_container_set_border_width (GTK_CONTAINER (vbox_extsend), 8); + + PACK_CHECK_BUTTON (vbox_extsend, checkbtn_extsend, + _("Use external program for sending")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox_extsend), hbox1, FALSE, FALSE, 0); + SET_TOGGLE_SENSITIVITY(checkbtn_extsend, hbox1); + + label_extsend = gtk_label_new (_("Command")); + gtk_widget_show (label_extsend); + gtk_box_pack_start (GTK_BOX (hbox1), label_extsend, FALSE, FALSE, 0); + + entry_extsend = gtk_entry_new (); + gtk_widget_show (entry_extsend); + gtk_box_pack_start (GTK_BOX (hbox1), entry_extsend, TRUE, TRUE, 0); + +#if 0 + button_extsend = gtk_button_new_with_label ("... "); + gtk_widget_show (button_extsend); + gtk_box_pack_start (GTK_BOX (hbox1), button_extsend, FALSE, FALSE, 0); +#endif + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (vbox2, checkbtn_savemsg, + _("Save sent messages to outbox")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + label_outcharset = gtk_label_new (_("Outgoing codeset")); + gtk_widget_show (label_outcharset); + gtk_box_pack_start (GTK_BOX (hbox1), label_outcharset, FALSE, FALSE, 0); + + optmenu_charset = gtk_option_menu_new (); + gtk_widget_show (optmenu_charset); + gtk_box_pack_start (GTK_BOX (hbox1), optmenu_charset, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new (); + +#define SET_MENUITEM(str, data) \ +{ \ + MENUITEM_ADD(optmenu_menu, menuitem, str, data); \ +} + + SET_MENUITEM(_("Automatic (Recommended)"), CS_AUTO); + SET_MENUITEM(_("7bit ascii (US-ASCII)"), CS_US_ASCII); +#if HAVE_ICONV + SET_MENUITEM(_("Unicode (UTF-8)"), CS_UTF_8); +#endif + SET_MENUITEM(_("Western European (ISO-8859-1)"), CS_ISO_8859_1); + SET_MENUITEM(_("Western European (ISO-8859-15)"), CS_ISO_8859_15); + SET_MENUITEM(_("Central European (ISO-8859-2)"), CS_ISO_8859_2); + SET_MENUITEM(_("Baltic (ISO-8859-13)"), CS_ISO_8859_13); + SET_MENUITEM(_("Baltic (ISO-8859-4)"), CS_ISO_8859_4); + SET_MENUITEM(_("Greek (ISO-8859-7)"), CS_ISO_8859_7); + SET_MENUITEM(_("Turkish (ISO-8859-9)"), CS_ISO_8859_9); +#if HAVE_ICONV + SET_MENUITEM(_("Cyrillic (ISO-8859-5)"), CS_ISO_8859_5); +#endif + SET_MENUITEM(_("Cyrillic (KOI8-R)"), CS_KOI8_R); +#if HAVE_ICONV + SET_MENUITEM(_("Cyrillic (Windows-1251)"), CS_WINDOWS_1251); + SET_MENUITEM(_("Cyrillic (KOI8-U)"), CS_KOI8_U); +#endif + SET_MENUITEM(_("Japanese (ISO-2022-JP)"), CS_ISO_2022_JP); +#if 0 + SET_MENUITEM(_("Japanese (EUC-JP)"), CS_EUC_JP); + SET_MENUITEM(_("Japanese (Shift_JIS)"), CS_SHIFT_JIS); +#endif /* 0 */ + SET_MENUITEM(_("Simplified Chinese (GB2312)"), CS_GB2312); + SET_MENUITEM(_("Traditional Chinese (Big5)"), CS_BIG5); +#if 0 + SET_MENUITEM(_("Traditional Chinese (EUC-TW)"), CS_EUC_TW); + SET_MENUITEM(_("Chinese (ISO-2022-CN)"), CS_ISO_2022_CN); +#endif /* 0 */ + SET_MENUITEM(_("Korean (EUC-KR)"), CS_EUC_KR); + SET_MENUITEM(_("Thai (TIS-620)"), CS_TIS_620); + SET_MENUITEM(_("Thai (Windows-874)"), CS_WINDOWS_874); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (optmenu_charset), + optmenu_menu); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + label_charset_desc = gtk_label_new + (_("If `Automatic' is selected, the optimal encoding\n" + "for the current locale will be used.")); + gtk_widget_show (label_charset_desc); + gtk_box_pack_start (GTK_BOX (hbox1), label_charset_desc, + FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label_charset_desc), + GTK_JUSTIFY_LEFT); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + label_encoding = gtk_label_new (_("Transfer encoding")); + gtk_widget_show (label_encoding); + gtk_box_pack_start (GTK_BOX (hbox1), label_encoding, FALSE, FALSE, 0); + + optmenu_encoding = gtk_option_menu_new (); + gtk_widget_show (optmenu_encoding); + gtk_box_pack_start (GTK_BOX (hbox1), optmenu_encoding, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new (); + + SET_MENUITEM(_("Automatic"), CTE_AUTO); + SET_MENUITEM("base64", CTE_BASE64); + SET_MENUITEM("quoted-printable", CTE_QUOTED_PRINTABLE); + SET_MENUITEM("8bit", CTE_8BIT); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (optmenu_encoding), + optmenu_menu); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + label_encoding_desc = gtk_label_new + (_("Specify Content-Transfer-Encoding used when\n" + "message body contains non-ASCII characters.")); + gtk_widget_show (label_encoding_desc); + gtk_box_pack_start (GTK_BOX (hbox1), label_encoding_desc, + FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label_encoding_desc), + GTK_JUSTIFY_LEFT); + + p_send.checkbtn_extsend = checkbtn_extsend; + p_send.entry_extsend = entry_extsend; + /* p_send.button_extsend = button_extsend; */ + + p_send.checkbtn_savemsg = checkbtn_savemsg; + + p_send.optmenu_charset = optmenu_charset; + p_send.optmenu_encoding_method = optmenu_encoding; +} + +static void prefs_compose_create(void) +{ + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *hbox2; + + GtkWidget *frame_sig; + GtkWidget *checkbtn_autosig; + GtkWidget *label_sigsep; + GtkWidget *entry_sigsep; + + GtkWidget *frame_editor; + GtkWidget *vbox2; + GtkWidget *checkbtn_autoextedit; + GtkWidget *vbox3; + GtkWidget *hbox3; + GtkWidget *hbox4; + GtkWidget *label_undolevel; + GtkObject *spinbtn_undolevel_adj; + GtkWidget *spinbtn_undolevel; + GtkWidget *label_linewrap; + GtkObject *spinbtn_linewrap_adj; + GtkWidget *spinbtn_linewrap; + GtkWidget *checkbtn_wrapquote; + GtkWidget *checkbtn_autowrap; + GtkWidget *checkbtn_wrapatsend; + + GtkWidget *frame_reply; + GtkWidget *checkbtn_reply_account_autosel; + GtkWidget *checkbtn_quote; + GtkWidget *checkbtn_default_reply_list; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + /* signature */ + + PACK_FRAME(vbox1, frame_sig, _("Signature")); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_container_add (GTK_CONTAINER (frame_sig), hbox1); + gtk_container_set_border_width (GTK_CONTAINER (hbox1), 8); + + hbox2 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (hbox1), hbox2, FALSE, FALSE, 0); + + label_sigsep = gtk_label_new (_("Signature separator")); + gtk_widget_show (label_sigsep); + gtk_box_pack_start (GTK_BOX (hbox2), label_sigsep, FALSE, FALSE, 0); + + entry_sigsep = gtk_entry_new (); + gtk_widget_show (entry_sigsep); + gtk_box_pack_start (GTK_BOX (hbox2), entry_sigsep, FALSE, FALSE, 0); + gtk_widget_set_size_request (entry_sigsep, 64, -1); + + PACK_CHECK_BUTTON (hbox1, checkbtn_autosig, _("Insert automatically")); + + PACK_FRAME (vbox1, frame_editor, _("Editor")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame_editor), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + PACK_CHECK_BUTTON (vbox2, checkbtn_autoextedit, + _("Automatically launch the external editor")); + + PACK_VSPACER (vbox2, vbox3, VSPACING_NARROW_2); + + /* undo */ + + hbox3 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox3); + gtk_box_pack_start (GTK_BOX (vbox2), hbox3, FALSE, FALSE, 0); + + label_undolevel = gtk_label_new (_("Undo level")); + gtk_widget_show (label_undolevel); + gtk_box_pack_start (GTK_BOX (hbox3), label_undolevel, FALSE, FALSE, 0); + + spinbtn_undolevel_adj = gtk_adjustment_new (50, 0, 100, 1, 10, 10); + spinbtn_undolevel = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_undolevel_adj), 1, 0); + gtk_widget_show (spinbtn_undolevel); + gtk_box_pack_start (GTK_BOX (hbox3), spinbtn_undolevel, FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_undolevel, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_undolevel), TRUE); + + PACK_VSPACER (vbox2, vbox3, VSPACING_NARROW_2); + + /* line-wrapping */ + + hbox3 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox3); + gtk_box_pack_start (GTK_BOX (vbox2), hbox3, FALSE, FALSE, 0); + + label_linewrap = gtk_label_new (_("Wrap messages at")); + gtk_widget_show (label_linewrap); + gtk_box_pack_start (GTK_BOX (hbox3), label_linewrap, FALSE, FALSE, 0); + + spinbtn_linewrap_adj = gtk_adjustment_new (72, 20, 1024, 1, 10, 10); + spinbtn_linewrap = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_linewrap_adj), 1, 0); + gtk_widget_show (spinbtn_linewrap); + gtk_box_pack_start (GTK_BOX (hbox3), spinbtn_linewrap, FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_linewrap, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_linewrap), TRUE); + + label_linewrap = gtk_label_new (_("characters")); + gtk_widget_show (label_linewrap); + gtk_box_pack_start (GTK_BOX (hbox3), label_linewrap, FALSE, FALSE, 0); + + PACK_VSPACER (vbox2, vbox3, VSPACING_NARROW_2); + + hbox4 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox4); + gtk_box_pack_start (GTK_BOX (vbox2), hbox4, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox4, checkbtn_wrapquote, _("Wrap quotation")); + + hbox4 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox4); + gtk_box_pack_start (GTK_BOX (vbox2), hbox4, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox4, checkbtn_autowrap, _("Wrap on input")); + PACK_CHECK_BUTTON + (hbox4, checkbtn_wrapatsend, _("Wrap before sending")); + + PACK_FRAME(vbox1, frame_reply, _("Reply")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame_reply), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + PACK_CHECK_BUTTON (vbox2, checkbtn_reply_account_autosel, + _("Automatically select account for replies")); + PACK_CHECK_BUTTON (vbox2, checkbtn_quote, + _("Quote message when replying")); + PACK_CHECK_BUTTON (vbox2, checkbtn_default_reply_list, + _("Reply button invokes mailing list reply")); + + compose.checkbtn_autosig = checkbtn_autosig; + compose.entry_sigsep = entry_sigsep; + + compose.checkbtn_autoextedit = checkbtn_autoextedit; + + compose.spinbtn_undolevel = spinbtn_undolevel; + compose.spinbtn_undolevel_adj = spinbtn_undolevel_adj; + + compose.spinbtn_linewrap = spinbtn_linewrap; + compose.spinbtn_linewrap_adj = spinbtn_linewrap_adj; + compose.checkbtn_wrapquote = checkbtn_wrapquote; + compose.checkbtn_autowrap = checkbtn_autowrap; + compose.checkbtn_wrapatsend = checkbtn_wrapatsend; + + compose.checkbtn_quote = checkbtn_quote; + compose.checkbtn_reply_account_autosel = + checkbtn_reply_account_autosel; + compose.checkbtn_default_reply_list = checkbtn_default_reply_list; +} + +static void prefs_quote_create(void) +{ + GtkWidget *vbox1; + GtkWidget *frame_quote; + GtkWidget *vbox_quote; + GtkWidget *hbox1; + GtkWidget *hbox2; + GtkWidget *label_quotemark; + GtkWidget *entry_quotemark; + GtkWidget *scrolledwin_quotefmt; + GtkWidget *text_quotefmt; + + GtkWidget *entry_fw_quotemark; + GtkWidget *text_fw_quotefmt; + + GtkWidget *btn_quotedesc; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + /* reply */ + + PACK_FRAME (vbox1, frame_quote, _("Reply format")); + + vbox_quote = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox_quote); + gtk_container_add (GTK_CONTAINER (frame_quote), vbox_quote); + gtk_container_set_border_width (GTK_CONTAINER (vbox_quote), 8); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox_quote), hbox1, FALSE, FALSE, 0); + + hbox2 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (hbox1), hbox2, FALSE, FALSE, 0); + + label_quotemark = gtk_label_new (_("Quotation mark")); + gtk_widget_show (label_quotemark); + gtk_box_pack_start (GTK_BOX (hbox2), label_quotemark, FALSE, FALSE, 0); + + entry_quotemark = gtk_entry_new (); + gtk_widget_show (entry_quotemark); + gtk_box_pack_start (GTK_BOX (hbox2), entry_quotemark, FALSE, FALSE, 0); + gtk_widget_set_size_request (entry_quotemark, 64, -1); + + scrolledwin_quotefmt = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwin_quotefmt); + gtk_box_pack_start (GTK_BOX (vbox_quote), scrolledwin_quotefmt, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy + (GTK_SCROLLED_WINDOW (scrolledwin_quotefmt), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + text_quotefmt = gtk_text_view_new (); + gtk_widget_show (text_quotefmt); + gtk_container_add(GTK_CONTAINER(scrolledwin_quotefmt), text_quotefmt); + gtk_text_view_set_editable (GTK_TEXT_VIEW (text_quotefmt), TRUE); + gtk_widget_set_size_request(text_quotefmt, -1, 60); + + /* forward */ + + PACK_FRAME (vbox1, frame_quote, _("Forward format")); + + vbox_quote = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_widget_show (vbox_quote); + gtk_container_add (GTK_CONTAINER (frame_quote), vbox_quote); + gtk_container_set_border_width (GTK_CONTAINER (vbox_quote), 8); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox_quote), hbox1, FALSE, FALSE, 0); + + hbox2 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (hbox1), hbox2, FALSE, FALSE, 0); + + label_quotemark = gtk_label_new (_("Quotation mark")); + gtk_widget_show (label_quotemark); + gtk_box_pack_start (GTK_BOX (hbox2), label_quotemark, FALSE, FALSE, 0); + + entry_fw_quotemark = gtk_entry_new (); + gtk_widget_show (entry_fw_quotemark); + gtk_box_pack_start (GTK_BOX (hbox2), entry_fw_quotemark, + FALSE, FALSE, 0); + gtk_widget_set_size_request (entry_fw_quotemark, 64, -1); + + scrolledwin_quotefmt = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwin_quotefmt); + gtk_box_pack_start (GTK_BOX (vbox_quote), scrolledwin_quotefmt, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy + (GTK_SCROLLED_WINDOW (scrolledwin_quotefmt), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + text_fw_quotefmt = gtk_text_view_new (); + gtk_widget_show (text_fw_quotefmt); + gtk_container_add(GTK_CONTAINER(scrolledwin_quotefmt), + text_fw_quotefmt); + gtk_text_view_set_editable (GTK_TEXT_VIEW (text_fw_quotefmt), TRUE); + gtk_widget_set_size_request (text_fw_quotefmt, -1, 60); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + btn_quotedesc = + gtk_button_new_with_label (_(" Description of symbols ")); + gtk_widget_show (btn_quotedesc); + gtk_box_pack_start (GTK_BOX (hbox1), btn_quotedesc, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(btn_quotedesc), "clicked", + G_CALLBACK(prefs_quote_description), NULL); + + quote.entry_quotemark = entry_quotemark; + quote.text_quotefmt = text_quotefmt; + quote.entry_fw_quotemark = entry_fw_quotemark; + quote.text_fw_quotefmt = text_fw_quotefmt; +} + +static void prefs_display_create(void) +{ + GtkWidget *vbox1; + GtkWidget *frame_font; + GtkWidget *table1; + GtkWidget *label_textfont; + GtkWidget *entry_textfont; + GtkWidget *button_textfont; + GtkWidget *chkbtn_transhdr; + GtkWidget *chkbtn_folder_unread; + GtkWidget *hbox1; + GtkWidget *label_ng_abbrev; + GtkWidget *spinbtn_ng_abbrev_len; + GtkObject *spinbtn_ng_abbrev_len_adj; + GtkWidget *frame_summary; + GtkWidget *vbox2; + GtkWidget *chkbtn_swapfrom; + GtkWidget *chkbtn_expand_thread; + GtkWidget *vbox3; + GtkWidget *label_datefmt; + GtkWidget *button_datefmt; + GtkWidget *entry_datefmt; + GtkWidget *button_dispitem; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME(vbox1, frame_font, _("Font")); + + table1 = gtk_table_new (1, 3, FALSE); + gtk_widget_show (table1); + gtk_container_add (GTK_CONTAINER (frame_font), table1); + gtk_container_set_border_width (GTK_CONTAINER (table1), 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), 8); + gtk_table_set_col_spacings (GTK_TABLE (table1), 8); + + label_textfont = gtk_label_new (_("Text")); + gtk_widget_show (label_textfont); + gtk_table_attach (GTK_TABLE (table1), label_textfont, 0, 1, 0, 1, + GTK_FILL, (GTK_EXPAND | GTK_FILL), 0, 0); + + entry_textfont = gtk_entry_new (); + gtk_widget_show (entry_textfont); + gtk_table_attach (GTK_TABLE (table1), entry_textfont, 1, 2, 0, 1, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + + button_textfont = gtk_button_new_with_label ("... "); + gtk_widget_show (button_textfont); + gtk_table_attach (GTK_TABLE (table1), button_textfont, 2, 3, 0, 1, + 0, 0, 0, 0); + g_signal_connect (G_OBJECT (button_textfont), "clicked", + G_CALLBACK (prefs_font_select), NULL); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, TRUE, 0); + + PACK_CHECK_BUTTON + (vbox2, chkbtn_transhdr, + _("Translate header name (such as `From:', `Subject:')")); + + PACK_CHECK_BUTTON (vbox2, chkbtn_folder_unread, + _("Display unread number next to folder name")); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW_2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, TRUE, 0); + + label_ng_abbrev = gtk_label_new + (_("Abbreviate newsgroups longer than")); + gtk_widget_show (label_ng_abbrev); + gtk_box_pack_start (GTK_BOX (hbox1), label_ng_abbrev, FALSE, FALSE, 0); + + spinbtn_ng_abbrev_len_adj = gtk_adjustment_new (16, 0, 999, 1, 10, 10); + spinbtn_ng_abbrev_len = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_ng_abbrev_len_adj), 1, 0); + gtk_widget_show (spinbtn_ng_abbrev_len); + gtk_box_pack_start (GTK_BOX (hbox1), spinbtn_ng_abbrev_len, + FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_ng_abbrev_len, 56, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_ng_abbrev_len), + TRUE); + + label_ng_abbrev = gtk_label_new + (_("letters")); + gtk_widget_show (label_ng_abbrev); + gtk_box_pack_start (GTK_BOX (hbox1), label_ng_abbrev, FALSE, FALSE, 0); + + /* ---- Summary ---- */ + + PACK_FRAME(vbox1, frame_summary, _("Summary View")); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame_summary), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + PACK_CHECK_BUTTON + (vbox2, chkbtn_swapfrom, + _("Display recipient on `From' column if sender is yourself")); + PACK_CHECK_BUTTON + (vbox2, chkbtn_expand_thread, _("Expand threads")); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW_2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, TRUE, 0); + + label_datefmt = gtk_label_new (_("Date format")); + gtk_widget_show (label_datefmt); + gtk_box_pack_start (GTK_BOX (hbox1), label_datefmt, FALSE, FALSE, 0); + + entry_datefmt = gtk_entry_new (); + gtk_widget_show (entry_datefmt); + gtk_box_pack_start (GTK_BOX (hbox1), entry_datefmt, TRUE, TRUE, 0); + + button_datefmt = gtk_button_new_with_label ("... "); + gtk_widget_show (button_datefmt); + gtk_box_pack_start (GTK_BOX (hbox1), button_datefmt, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (button_datefmt), "clicked", + G_CALLBACK (date_format_create), NULL); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, TRUE, 0); + + button_dispitem = gtk_button_new_with_label + (_(" Set display item of summary... ")); + gtk_widget_show (button_dispitem); + gtk_box_pack_start (GTK_BOX (hbox1), button_dispitem, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (button_dispitem), "clicked", + G_CALLBACK (prefs_summary_column_open), NULL); + + display.entry_textfont = entry_textfont; + display.button_textfont = button_textfont; + + display.chkbtn_transhdr = chkbtn_transhdr; + display.chkbtn_folder_unread = chkbtn_folder_unread; + display.spinbtn_ng_abbrev_len = spinbtn_ng_abbrev_len; + display.spinbtn_ng_abbrev_len_adj = spinbtn_ng_abbrev_len_adj; + + display.chkbtn_swapfrom = chkbtn_swapfrom; + display.chkbtn_expand_thread = chkbtn_expand_thread; + display.entry_datefmt = entry_datefmt; +} + +static void prefs_message_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *vbox3; + GtkWidget *hbox1; + GtkWidget *chkbtn_enablecol; + GtkWidget *button_edit_col; + GtkWidget *chkbtn_mbalnum; + GtkWidget *chkbtn_disphdrpane; + GtkWidget *chkbtn_disphdr; + GtkWidget *button_edit_disphdr; + GtkWidget *hbox_linespc; + GtkWidget *label_linespc; + GtkObject *spinbtn_linespc_adj; + GtkWidget *spinbtn_linespc; + GtkWidget *chkbtn_headspc; + + GtkWidget *frame_scr; + GtkWidget *vbox_scr; + GtkWidget *chkbtn_smoothscroll; + GtkWidget *hbox_scr; + GtkWidget *label_scr; + GtkObject *spinbtn_scrollstep_adj; + GtkWidget *spinbtn_scrollstep; + GtkWidget *chkbtn_halfpage; + + GtkWidget *chkbtn_resize_image; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, TRUE, 0); + + PACK_CHECK_BUTTON (hbox1, chkbtn_enablecol, + _("Enable coloration of message")); + g_signal_connect(G_OBJECT(chkbtn_enablecol), "toggled", + G_CALLBACK(prefs_enable_message_color_toggled), NULL); + + button_edit_col = gtk_button_new_with_label (_(" Edit... ")); + gtk_widget_show (button_edit_col); + gtk_box_pack_end (GTK_BOX (hbox1), button_edit_col, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (button_edit_col), "clicked", + G_CALLBACK (prefs_quote_colors_dialog), NULL); + + SET_TOGGLE_SENSITIVITY(chkbtn_enablecol, button_edit_col); + + PACK_CHECK_BUTTON + (vbox2, chkbtn_mbalnum, + _("Display multi-byte alphabet and numeric as\n" + "ASCII character (Japanese only)")); + gtk_label_set_justify (GTK_LABEL (GTK_BIN(chkbtn_mbalnum)->child), + GTK_JUSTIFY_LEFT); + + PACK_CHECK_BUTTON(vbox2, chkbtn_disphdrpane, + _("Display header pane above message view")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, TRUE, 0); + + PACK_CHECK_BUTTON(hbox1, chkbtn_disphdr, + _("Display short headers on message view")); + + button_edit_disphdr = gtk_button_new_with_label (_(" Edit... ")); + gtk_widget_show (button_edit_disphdr); + gtk_box_pack_end (GTK_BOX (hbox1), button_edit_disphdr, + FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (button_edit_disphdr), "clicked", + G_CALLBACK (prefs_display_header_open), NULL); + + SET_TOGGLE_SENSITIVITY(chkbtn_disphdr, button_edit_disphdr); + + PACK_VSPACER(vbox2, vbox3, VSPACING_NARROW_2); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, TRUE, 0); + + hbox_linespc = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (hbox1), hbox_linespc, FALSE, TRUE, 0); + + label_linespc = gtk_label_new (_("Line space")); + gtk_widget_show (label_linespc); + gtk_box_pack_start (GTK_BOX (hbox_linespc), label_linespc, + FALSE, FALSE, 0); + + spinbtn_linespc_adj = gtk_adjustment_new (2, 0, 16, 1, 1, 16); + spinbtn_linespc = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_linespc_adj), 1, 0); + gtk_widget_show (spinbtn_linespc); + gtk_box_pack_start (GTK_BOX (hbox_linespc), spinbtn_linespc, + FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_linespc, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_linespc), TRUE); + + label_linespc = gtk_label_new (_("pixel(s)")); + gtk_widget_show (label_linespc); + gtk_box_pack_start (GTK_BOX (hbox_linespc), label_linespc, + FALSE, FALSE, 0); + + PACK_CHECK_BUTTON(hbox1, chkbtn_headspc, _("Leave space on head")); + + PACK_FRAME(vbox1, frame_scr, _("Scroll")); + + vbox_scr = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox_scr); + gtk_container_add (GTK_CONTAINER (frame_scr), vbox_scr); + gtk_container_set_border_width (GTK_CONTAINER (vbox_scr), 8); + + PACK_CHECK_BUTTON(vbox_scr, chkbtn_halfpage, _("Half page")); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox_scr), hbox1, FALSE, TRUE, 0); + + PACK_CHECK_BUTTON(hbox1, chkbtn_smoothscroll, _("Smooth scroll")); + + hbox_scr = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox_scr); + gtk_box_pack_start (GTK_BOX (hbox1), hbox_scr, FALSE, FALSE, 0); + + label_scr = gtk_label_new (_("Step")); + gtk_widget_show (label_scr); + gtk_box_pack_start (GTK_BOX (hbox_scr), label_scr, FALSE, FALSE, 0); + + spinbtn_scrollstep_adj = gtk_adjustment_new (1, 1, 100, 1, 10, 10); + spinbtn_scrollstep = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_scrollstep_adj), 1, 0); + gtk_widget_show (spinbtn_scrollstep); + gtk_box_pack_start (GTK_BOX (hbox_scr), spinbtn_scrollstep, + FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_scrollstep, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_scrollstep), + TRUE); + + label_scr = gtk_label_new (_("pixel(s)")); + gtk_widget_show (label_scr); + gtk_box_pack_start (GTK_BOX (hbox_scr), label_scr, FALSE, FALSE, 0); + + SET_TOGGLE_SENSITIVITY (chkbtn_smoothscroll, hbox_scr) + + PACK_CHECK_BUTTON(vbox1, chkbtn_resize_image, + _("Resize attached large images to fit in the window")); + + message.chkbtn_enablecol = chkbtn_enablecol; + message.button_edit_col = button_edit_col; + message.chkbtn_mbalnum = chkbtn_mbalnum; + message.chkbtn_disphdrpane = chkbtn_disphdrpane; + message.chkbtn_disphdr = chkbtn_disphdr; + message.spinbtn_linespc = spinbtn_linespc; + message.chkbtn_headspc = chkbtn_headspc; + + message.chkbtn_smoothscroll = chkbtn_smoothscroll; + message.spinbtn_scrollstep = spinbtn_scrollstep; + message.spinbtn_scrollstep_adj = spinbtn_scrollstep_adj; + message.chkbtn_halfpage = chkbtn_halfpage; + + message.chkbtn_resize_image = chkbtn_resize_image; +} + +#if USE_GPGME +static void prefs_privacy_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *vbox3; + GtkWidget *hbox1; + GtkWidget *hbox_spc; + GtkWidget *label; + GtkWidget *checkbtn_auto_check_signatures; + GtkWidget *checkbtn_gpg_signature_popup; + GtkWidget *checkbtn_store_passphrase; + GtkObject *spinbtn_store_passphrase_adj; + GtkWidget *spinbtn_store_passphrase; + GtkWidget *checkbtn_passphrase_grab; + GtkWidget *checkbtn_gpg_warning; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (vbox2, checkbtn_auto_check_signatures, + _("Automatically check signatures")); + + PACK_CHECK_BUTTON (vbox2, checkbtn_gpg_signature_popup, + _("Show signature check result in a popup window")); + + PACK_CHECK_BUTTON (vbox2, checkbtn_store_passphrase, + _("Store passphrase in memory temporarily")); + + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox3); + gtk_box_pack_start (GTK_BOX (vbox2), vbox3, FALSE, FALSE, 0); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox3), hbox1, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + label = gtk_label_new (_("Expired after")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + + spinbtn_store_passphrase_adj = gtk_adjustment_new (0, 0, 1440, 1, 5, 5); + spinbtn_store_passphrase = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_store_passphrase_adj), 1, 0); + gtk_widget_show (spinbtn_store_passphrase); + gtk_box_pack_start (GTK_BOX (hbox1), spinbtn_store_passphrase, FALSE, FALSE, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_store_passphrase), + TRUE); + gtk_widget_set_size_request (spinbtn_store_passphrase, 64, -1); + + label = gtk_label_new (_("minute(s) ")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox3), hbox1, FALSE, FALSE, 0); + + hbox_spc = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox_spc); + gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0); + gtk_widget_set_size_request (hbox_spc, 12, -1); + + label = gtk_label_new (_("(Setting to '0' will store the passphrase\n" + " for the whole session)")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + SET_TOGGLE_SENSITIVITY (checkbtn_store_passphrase, vbox3); + +#ifndef __MINGW32__ + PACK_CHECK_BUTTON (vbox2, checkbtn_passphrase_grab, + _("Grab input while entering a passphrase")); +#endif + + PACK_CHECK_BUTTON + (vbox2, checkbtn_gpg_warning, + _("Display warning on startup if GnuPG doesn't work")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + privacy.checkbtn_auto_check_signatures + = checkbtn_auto_check_signatures; + privacy.checkbtn_gpg_signature_popup + = checkbtn_gpg_signature_popup; + privacy.checkbtn_store_passphrase = checkbtn_store_passphrase; + privacy.spinbtn_store_passphrase = spinbtn_store_passphrase; + privacy.spinbtn_store_passphrase_adj = spinbtn_store_passphrase_adj; + privacy.checkbtn_passphrase_grab = checkbtn_passphrase_grab; + privacy.checkbtn_gpg_warning = checkbtn_gpg_warning; +} +#endif /* USE_GPGME */ + +static void prefs_interface_create(void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *vbox3; + GtkWidget *checkbtn_always_show_msg; + GtkWidget *checkbtn_openunread; + GtkWidget *checkbtn_mark_as_read_on_newwin; + GtkWidget *checkbtn_openinbox; + GtkWidget *checkbtn_immedexec; + GtkWidget *frame_recv; + GtkWidget *vbox_recv; + GtkWidget *hbox1; + GtkWidget *label; + GtkWidget *optmenu_recvdialog; + GtkWidget *menu; + GtkWidget *menuitem; + GtkWidget *checkbtn_no_recv_err_panel; + GtkWidget *checkbtn_close_recv_dialog; + + GtkWidget *button_keybind; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON + (vbox2, checkbtn_always_show_msg, + _("Always open messages in summary when selected")); + + PACK_CHECK_BUTTON + (vbox2, checkbtn_openunread, + _("Open first unread message when entering a folder")); + + PACK_CHECK_BUTTON + (vbox2, checkbtn_mark_as_read_on_newwin, + _("Only mark message as read when opened in new window")); + + PACK_CHECK_BUTTON + (vbox2, checkbtn_openinbox, + _("Go to inbox after receiving new mail")); + + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox3); + gtk_box_pack_start (GTK_BOX (vbox2), vbox3, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON + (vbox3, checkbtn_immedexec, + _("Execute immediately when moving or deleting messages")); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox3), hbox1, FALSE, FALSE, 0); + + label = gtk_label_new + (_("(Messages will be marked until execution\n" + " if this is turned off)")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 8); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + PACK_FRAME (vbox1, frame_recv, _("Receive dialog")); + vbox_recv = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox_recv); + gtk_container_add (GTK_CONTAINER (frame_recv), vbox_recv); + gtk_container_set_border_width (GTK_CONTAINER (vbox_recv), 8); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox_recv), hbox1, FALSE, FALSE, 0); + + label = gtk_label_new (_("Show receive dialog")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + + optmenu_recvdialog = gtk_option_menu_new (); + gtk_widget_show (optmenu_recvdialog); + gtk_box_pack_start (GTK_BOX (hbox1), optmenu_recvdialog, + FALSE, FALSE, 0); + + menu = gtk_menu_new (); + MENUITEM_ADD (menu, menuitem, _("Always"), RECV_DIALOG_ALWAYS); + MENUITEM_ADD (menu, menuitem, _("Only on manual receiving"), + RECV_DIALOG_MANUAL); + MENUITEM_ADD (menu, menuitem, _("Never"), RECV_DIALOG_NEVER); + + gtk_option_menu_set_menu (GTK_OPTION_MENU (optmenu_recvdialog), menu); + + PACK_CHECK_BUTTON (vbox_recv, checkbtn_no_recv_err_panel, + _("Don't popup error dialog on receive error")); + + PACK_CHECK_BUTTON (vbox_recv, checkbtn_close_recv_dialog, + _("Close receive dialog when finished")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + button_keybind = gtk_button_new_with_label (_(" Set key bindings... ")); + gtk_widget_show (button_keybind); + gtk_box_pack_start (GTK_BOX (hbox1), button_keybind, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (button_keybind), "clicked", + G_CALLBACK (prefs_keybind_select), NULL); + + interface.checkbtn_always_show_msg = checkbtn_always_show_msg; + interface.checkbtn_openunread = checkbtn_openunread; + interface.checkbtn_mark_as_read_on_newwin + = checkbtn_mark_as_read_on_newwin; + interface.checkbtn_openinbox = checkbtn_openinbox; + interface.checkbtn_immedexec = checkbtn_immedexec; + interface.optmenu_recvdialog = optmenu_recvdialog; + interface.checkbtn_no_recv_err_panel = checkbtn_no_recv_err_panel; + interface.checkbtn_close_recv_dialog = checkbtn_close_recv_dialog; +} + +static void prefs_other_create(void) +{ + GtkWidget *vbox1; + GtkWidget *ext_frame; + GtkWidget *ext_table; + GtkWidget *hbox1; + + GtkWidget *uri_label; + GtkWidget *uri_combo; + GtkWidget *uri_entry; + + GtkWidget *printcmd_label; + GtkWidget *printcmd_entry; + + GtkWidget *exteditor_label; + GtkWidget *exteditor_combo; + GtkWidget *exteditor_entry; + + GtkWidget *frame_addr; + GtkWidget *vbox_addr; + GtkWidget *checkbtn_addaddrbyclick; + + GtkWidget *frame_exit; + GtkWidget *vbox_exit; + GtkWidget *checkbtn_confonexit; + GtkWidget *checkbtn_cleanonexit; + GtkWidget *checkbtn_askonclean; + GtkWidget *checkbtn_warnqueued; + + GtkWidget *label_iotimeout; + GtkWidget *spinbtn_iotimeout; + GtkObject *spinbtn_iotimeout_adj; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + PACK_FRAME(vbox1, ext_frame, + _("External commands (%s will be replaced with file name / URI)")); + + ext_table = gtk_table_new (3, 2, FALSE); + gtk_widget_show (ext_table); + gtk_container_add (GTK_CONTAINER (ext_frame), ext_table); + gtk_container_set_border_width (GTK_CONTAINER (ext_table), 8); + gtk_table_set_row_spacings (GTK_TABLE (ext_table), VSPACING_NARROW); + gtk_table_set_col_spacings (GTK_TABLE (ext_table), 8); + + uri_label = gtk_label_new (_("Web browser")); + gtk_widget_show(uri_label); + gtk_table_attach (GTK_TABLE (ext_table), uri_label, 0, 1, 0, 1, + GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_misc_set_alignment (GTK_MISC (uri_label), 1, 0.5); + + uri_combo = gtk_combo_new (); + gtk_widget_show (uri_combo); + gtk_table_attach (GTK_TABLE (ext_table), uri_combo, 1, 2, 0, 1, + GTK_EXPAND | GTK_FILL, 0, 0, 0); + gtkut_combo_set_items (GTK_COMBO (uri_combo), + DEFAULT_BROWSER_CMD, + "mozilla-firefox '%s'", + "mozilla -remote 'openURL(%s,new-window)'", + "mozilla '%s'", + "netscape -remote 'openURL(%s,new-window)'", + "netscape '%s'", + "gnome-moz-remote --newwin '%s'", + "rxvt -e w3m '%s'", + "rxvt -e lynx '%s'", + NULL); + uri_entry = GTK_COMBO (uri_combo)->entry; + + printcmd_label = gtk_label_new (_("Print")); + gtk_widget_show (printcmd_label); + gtk_table_attach (GTK_TABLE (ext_table), printcmd_label, 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_misc_set_alignment (GTK_MISC (printcmd_label), 1, 0.5); + + printcmd_entry = gtk_entry_new (); + gtk_widget_show (printcmd_entry); + gtk_table_attach (GTK_TABLE (ext_table), printcmd_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_FILL, 0, 0, 0); + + exteditor_label = gtk_label_new (_("Editor")); + gtk_widget_show (exteditor_label); + gtk_table_attach (GTK_TABLE (ext_table), exteditor_label, 0, 1, 2, 3, + GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_misc_set_alignment (GTK_MISC (exteditor_label), 1, 0.5); + + exteditor_combo = gtk_combo_new (); + gtk_widget_show (exteditor_combo); + gtk_table_attach (GTK_TABLE (ext_table), exteditor_combo, 1, 2, 2, 3, + GTK_EXPAND | GTK_FILL, 0, 0, 0); + gtkut_combo_set_items (GTK_COMBO (exteditor_combo), + "gedit %s", + "kedit %s", + "mgedit --no-fork %s", + "emacs %s", + "xemacs %s", + "kterm -e jed %s", + "kterm -e vi %s", + NULL); + exteditor_entry = GTK_COMBO (exteditor_combo)->entry; + + PACK_FRAME (vbox1, frame_addr, _("Address book")); + + vbox_addr = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox_addr); + gtk_container_add (GTK_CONTAINER (frame_addr), vbox_addr); + gtk_container_set_border_width (GTK_CONTAINER (vbox_addr), 8); + + PACK_CHECK_BUTTON + (vbox_addr, checkbtn_addaddrbyclick, + _("Add address to destination when double-clicked")); + + PACK_FRAME (vbox1, frame_exit, _("On exit")); + + vbox_exit = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox_exit); + gtk_container_add (GTK_CONTAINER (frame_exit), vbox_exit); + gtk_container_set_border_width (GTK_CONTAINER (vbox_exit), 8); + + PACK_CHECK_BUTTON (vbox_exit, checkbtn_confonexit, + _("Confirm on exit")); + + hbox1 = gtk_hbox_new (FALSE, 32); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox_exit), hbox1, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (hbox1, checkbtn_cleanonexit, + _("Empty trash on exit")); + PACK_CHECK_BUTTON (hbox1, checkbtn_askonclean, + _("Ask before emptying")); + SET_TOGGLE_SENSITIVITY (checkbtn_cleanonexit, checkbtn_askonclean); + + PACK_CHECK_BUTTON (vbox_exit, checkbtn_warnqueued, + _("Warn if there are queued messages")); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + label_iotimeout = gtk_label_new (_("Socket I/O timeout:")); + gtk_widget_show (label_iotimeout); + gtk_box_pack_start (GTK_BOX (hbox1), label_iotimeout, FALSE, FALSE, 0); + + spinbtn_iotimeout_adj = gtk_adjustment_new (60, 0, 1000, 1, 10, 10); + spinbtn_iotimeout = gtk_spin_button_new + (GTK_ADJUSTMENT (spinbtn_iotimeout_adj), 1, 0); + gtk_widget_show (spinbtn_iotimeout); + gtk_box_pack_start (GTK_BOX (hbox1), spinbtn_iotimeout, + FALSE, FALSE, 0); + gtk_widget_set_size_request (spinbtn_iotimeout, 64, -1); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbtn_iotimeout), TRUE); + + label_iotimeout = gtk_label_new (_("second(s)")); + gtk_widget_show (label_iotimeout); + gtk_box_pack_start (GTK_BOX (hbox1), label_iotimeout, FALSE, FALSE, 0); + + other.uri_combo = uri_combo; + other.uri_entry = uri_entry; + other.printcmd_entry = printcmd_entry; + + other.exteditor_combo = exteditor_combo; + other.exteditor_entry = exteditor_entry; + + other.checkbtn_addaddrbyclick = checkbtn_addaddrbyclick; + + other.checkbtn_confonexit = checkbtn_confonexit; + other.checkbtn_cleanonexit = checkbtn_cleanonexit; + other.checkbtn_askonclean = checkbtn_askonclean; + other.checkbtn_warnqueued = checkbtn_warnqueued; + + other.spinbtn_iotimeout = spinbtn_iotimeout; + other.spinbtn_iotimeout_adj = spinbtn_iotimeout_adj; +} + +static void date_format_ok_btn_clicked(GtkButton *button, GtkWidget **widget) +{ + GtkWidget *datefmt_sample = NULL; + gchar *text; + + g_return_if_fail(widget != NULL); + g_return_if_fail(*widget != NULL); + g_return_if_fail(display.entry_datefmt != NULL); + + datefmt_sample = GTK_WIDGET(g_object_get_data + (G_OBJECT(*widget), "datefmt_sample")); + g_return_if_fail(datefmt_sample != NULL); + + text = gtk_editable_get_chars(GTK_EDITABLE(datefmt_sample), 0, -1); + g_free(prefs_common.date_format); + prefs_common.date_format = text; + gtk_entry_set_text(GTK_ENTRY(display.entry_datefmt), text); + + gtk_widget_destroy(*widget); + *widget = NULL; +} + +static void date_format_cancel_btn_clicked(GtkButton *button, + GtkWidget **widget) +{ + g_return_if_fail(widget != NULL); + g_return_if_fail(*widget != NULL); + + gtk_widget_destroy(*widget); + *widget = NULL; +} + +static gboolean date_format_key_pressed(GtkWidget *keywidget, + GdkEventKey *event, + GtkWidget **widget) +{ + if (event && event->keyval == GDK_Escape) + date_format_cancel_btn_clicked(NULL, widget); + return FALSE; +} + +static gboolean date_format_on_delete(GtkWidget *dialogwidget, + GdkEventAny *event, GtkWidget **widget) +{ + g_return_val_if_fail(widget != NULL, FALSE); + g_return_val_if_fail(*widget != NULL, FALSE); + + *widget = NULL; + return FALSE; +} + +static void date_format_entry_on_change(GtkEditable *editable, + GtkLabel *example) +{ + time_t cur_time; + struct tm *cal_time; + gchar buffer[100]; + gchar *text; + + cur_time = time(NULL); + cal_time = localtime(&cur_time); + buffer[0] = 0; + text = gtk_editable_get_chars(editable, 0, -1); + if (text) + strftime(buffer, sizeof buffer, text, cal_time); + g_free(text); + + text = conv_codeset_strdup(buffer, conv_get_locale_charset_str(), + CS_UTF_8); + if (!text) + text = g_strdup(buffer); + gtk_label_set_text(example, text); + g_free(text); +} + +static void date_format_select_row(GtkWidget *date_format_list, gint row, + gint column, GdkEventButton *event, + GtkWidget *date_format) +{ + gint cur_pos; + gchar *format; + const gchar *old_format; + gchar *new_format; + GtkWidget *datefmt_sample; + + /* only on double click */ + if (!event || event->type != GDK_2BUTTON_PRESS) return; + + datefmt_sample = GTK_WIDGET(g_object_get_data + (G_OBJECT(date_format), "datefmt_sample")); + + g_return_if_fail(date_format_list != NULL); + g_return_if_fail(date_format != NULL); + g_return_if_fail(datefmt_sample != NULL); + + /* get format from clist */ + gtk_clist_get_text(GTK_CLIST(date_format_list), row, 0, &format); + + cur_pos = gtk_editable_get_position(GTK_EDITABLE(datefmt_sample)); + old_format = gtk_entry_get_text(GTK_ENTRY(datefmt_sample)); + + /* insert the format into the text entry */ + new_format = g_malloc(strlen(old_format) + 3); + + strncpy(new_format, old_format, cur_pos); + new_format[cur_pos] = '\0'; + strcat(new_format, format); + strcat(new_format, &old_format[cur_pos]); + + gtk_entry_set_text(GTK_ENTRY(datefmt_sample), new_format); + gtk_editable_set_position(GTK_EDITABLE(datefmt_sample), cur_pos + 2); + + g_free(new_format); +} + +static GtkWidget *date_format_create(GtkButton *button, void *data) +{ + static GtkWidget *datefmt_win = NULL; + GtkWidget *vbox1; + GtkWidget *scrolledwindow1; + GtkWidget *datefmt_clist; + GtkWidget *table; + GtkWidget *label1; + GtkWidget *label2; + GtkWidget *label3; + GtkWidget *confirm_area; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *datefmt_entry; + + struct { + gchar *fmt; + gchar *txt; + } time_format[] = { + { "%a", NULL }, + { "%A", NULL }, + { "%b", NULL }, + { "%B", NULL }, + { "%c", NULL }, + { "%C", NULL }, + { "%d", NULL }, + { "%H", NULL }, + { "%I", NULL }, + { "%j", NULL }, + { "%m", NULL }, + { "%M", NULL }, + { "%p", NULL }, + { "%S", NULL }, + { "%w", NULL }, + { "%x", NULL }, + { "%y", NULL }, + { "%Y", NULL }, + { "%Z", NULL } + }; + + gchar *titles[2]; + gint i; + const gint TIME_FORMAT_ELEMS = + sizeof time_format / sizeof time_format[0]; + + time_format[0].txt = _("the full abbreviated weekday name"); + time_format[1].txt = _("the full weekday name"); + time_format[2].txt = _("the abbreviated month name"); + time_format[3].txt = _("the full month name"); + time_format[4].txt = _("the preferred date and time for the current locale"); + time_format[5].txt = _("the century number (year/100)"); + time_format[6].txt = _("the day of the month as a decimal number"); + time_format[7].txt = _("the hour as a decimal number using a 24-hour clock"); + time_format[8].txt = _("the hour as a decimal number using a 12-hour clock"); + time_format[9].txt = _("the day of the year as a decimal number"); + time_format[10].txt = _("the month as a decimal number"); + time_format[11].txt = _("the minute as a decimal number"); + time_format[12].txt = _("either AM or PM"); + time_format[13].txt = _("the second as a decimal number"); + time_format[14].txt = _("the day of the week as a decimal number"); + time_format[15].txt = _("the preferred date for the current locale"); + time_format[16].txt = _("the last two digits of a year"); + time_format[17].txt = _("the year as a decimal number"); + time_format[18].txt = _("the time zone or name or abbreviation"); + + if (datefmt_win) return datefmt_win; + + datefmt_win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(datefmt_win), 8); + gtk_window_set_title(GTK_WINDOW(datefmt_win), _("Date format")); + gtk_window_set_position(GTK_WINDOW(datefmt_win), GTK_WIN_POS_CENTER); + gtk_widget_set_size_request(datefmt_win, 440, 280); + + vbox1 = gtk_vbox_new(FALSE, 10); + gtk_widget_show(vbox1); + gtk_container_add(GTK_CONTAINER(datefmt_win), vbox1); + + scrolledwindow1 = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy + (GTK_SCROLLED_WINDOW(scrolledwindow1), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_show(scrolledwindow1); + gtk_box_pack_start(GTK_BOX(vbox1), scrolledwindow1, TRUE, TRUE, 0); + + titles[0] = _("Specifier"); + titles[1] = _("Description"); + datefmt_clist = gtk_clist_new_with_titles(2, titles); + gtk_widget_show(datefmt_clist); + gtk_container_add(GTK_CONTAINER(scrolledwindow1), datefmt_clist); + /* gtk_clist_set_column_width(GTK_CLIST(datefmt_clist), 0, 80); */ + gtk_clist_set_selection_mode(GTK_CLIST(datefmt_clist), + GTK_SELECTION_BROWSE); + + for (i = 0; i < TIME_FORMAT_ELEMS; i++) { + gchar *text[2]; + /* phoney casting necessary because of gtk... */ + text[0] = (gchar *)time_format[i].fmt; + text[1] = (gchar *)time_format[i].txt; + gtk_clist_append(GTK_CLIST(datefmt_clist), text); + } + + table = gtk_table_new(2, 2, FALSE); + gtk_widget_show(table); + gtk_box_pack_start(GTK_BOX(vbox1), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 4); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + label1 = gtk_label_new(_("Date format")); + gtk_widget_show(label1); + gtk_table_attach(GTK_TABLE(table), label1, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_label_set_justify(GTK_LABEL(label1), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5); + + datefmt_entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(datefmt_entry), 256); + gtk_widget_show(datefmt_entry); + gtk_table_attach(GTK_TABLE(table), datefmt_entry, 1, 2, 0, 1, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + + /* we need the "sample" entry box; add it as data so callbacks can + * get the entry box */ + g_object_set_data(G_OBJECT(datefmt_win), "datefmt_sample", + datefmt_entry); + + label2 = gtk_label_new(_("Example")); + gtk_widget_show(label2); + gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_label_set_justify(GTK_LABEL(label2), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5); + + label3 = gtk_label_new(""); + gtk_widget_show(label3); + gtk_table_attach(GTK_TABLE(table), label3, 1, 2, 1, 2, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + gtk_label_set_justify(GTK_LABEL(label3), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + + gtk_box_pack_start(GTK_BOX(vbox1), confirm_area, FALSE, FALSE, 0); + gtk_widget_show(confirm_area); + gtk_widget_grab_default(ok_btn); + + /* set the current format */ + gtk_entry_set_text(GTK_ENTRY(datefmt_entry), prefs_common.date_format); + date_format_entry_on_change(GTK_EDITABLE(datefmt_entry), + GTK_LABEL(label3)); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(date_format_ok_btn_clicked), &datefmt_win); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(date_format_cancel_btn_clicked), + &datefmt_win); + g_signal_connect(G_OBJECT(datefmt_win), "key_press_event", + G_CALLBACK(date_format_key_pressed), &datefmt_win); + g_signal_connect(G_OBJECT(datefmt_win), "delete_event", + G_CALLBACK(date_format_on_delete), &datefmt_win); + g_signal_connect(G_OBJECT(datefmt_entry), "changed", + G_CALLBACK(date_format_entry_on_change), label3); + g_signal_connect(G_OBJECT(datefmt_clist), "select_row", + G_CALLBACK(date_format_select_row), datefmt_win); + + gtk_window_set_position(GTK_WINDOW(datefmt_win), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(datefmt_win), TRUE); + + gtk_widget_show(datefmt_win); + manage_window_set_transient(GTK_WINDOW(datefmt_win)); + + gtk_widget_grab_focus(ok_btn); + + return datefmt_win; +} + +void prefs_quote_colors_dialog(void) +{ + if (!quote_color_win) + prefs_quote_colors_dialog_create(); + gtk_widget_show(quote_color_win); + manage_window_set_transient(GTK_WINDOW(quote_color_win)); + + gtk_main(); + gtk_widget_hide(quote_color_win); + + textview_update_message_colors(); + main_window_reflect_prefs_all(); +} + +static void prefs_quote_colors_dialog_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *quotelevel1_label; + GtkWidget *quotelevel2_label; + GtkWidget *quotelevel3_label; + GtkWidget *uri_label; + GtkWidget *hbbox; + GtkWidget *ok_btn; + GtkWidget *recycle_colors_btn; + GtkWidget *frame_colors; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window), 2); + gtk_window_set_title(GTK_WINDOW(window), _("Set message colors")); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); + + vbox = gtk_vbox_new (FALSE, VSPACING); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 8); + PACK_FRAME(vbox, frame_colors, _("Colors")); + + table = gtk_table_new (4, 2, FALSE); + gtk_container_add (GTK_CONTAINER (frame_colors), table); + gtk_container_set_border_width (GTK_CONTAINER (table), 8); + gtk_table_set_row_spacings (GTK_TABLE (table), 2); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + + color_buttons.quote_level1_btn = gtk_button_new(); + gtk_table_attach (GTK_TABLE (table), color_buttons.quote_level1_btn, + 0, 1, 0, 1, 0, 0, 0, 0); + gtk_widget_set_size_request (color_buttons.quote_level1_btn, 40, 30); + gtk_container_set_border_width + (GTK_CONTAINER (color_buttons.quote_level1_btn), 5); + + color_buttons.quote_level2_btn = gtk_button_new(); + gtk_table_attach (GTK_TABLE (table), color_buttons.quote_level2_btn, + 0, 1, 1, 2, 0, 0, 0, 0); + gtk_widget_set_size_request (color_buttons.quote_level2_btn, 40, 30); + gtk_container_set_border_width (GTK_CONTAINER (color_buttons.quote_level2_btn), 5); + + color_buttons.quote_level3_btn = gtk_button_new_with_label (""); + gtk_table_attach (GTK_TABLE (table), color_buttons.quote_level3_btn, + 0, 1, 2, 3, 0, 0, 0, 0); + gtk_widget_set_size_request (color_buttons.quote_level3_btn, 40, 30); + gtk_container_set_border_width + (GTK_CONTAINER (color_buttons.quote_level3_btn), 5); + + color_buttons.uri_btn = gtk_button_new_with_label (""); + gtk_table_attach (GTK_TABLE (table), color_buttons.uri_btn, + 0, 1, 3, 4, 0, 0, 0, 0); + gtk_widget_set_size_request (color_buttons.uri_btn, 40, 30); + gtk_container_set_border_width (GTK_CONTAINER (color_buttons.uri_btn), 5); + + quotelevel1_label = gtk_label_new (_("Quoted Text - First Level")); + gtk_table_attach (GTK_TABLE (table), quotelevel1_label, 1, 2, 0, 1, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (quotelevel1_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (quotelevel1_label), 0, 0.5); + + quotelevel2_label = gtk_label_new (_("Quoted Text - Second Level")); + gtk_table_attach (GTK_TABLE (table), quotelevel2_label, 1, 2, 1, 2, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (quotelevel2_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (quotelevel2_label), 0, 0.5); + + quotelevel3_label = gtk_label_new (_("Quoted Text - Third Level")); + gtk_table_attach (GTK_TABLE (table), quotelevel3_label, 1, 2, 2, 3, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (quotelevel3_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (quotelevel3_label), 0, 0.5); + + uri_label = gtk_label_new (_("URI link")); + gtk_table_attach (GTK_TABLE (table), uri_label, 1, 2, 3, 4, + (GTK_EXPAND | GTK_FILL), 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (uri_label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (uri_label), 0, 0.5); + + PACK_CHECK_BUTTON (vbox, recycle_colors_btn, + _("Recycle quote colors")); + + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + NULL, NULL, NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + + gtk_widget_grab_default(ok_btn); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_quote_colors_key_pressed), NULL); + + g_signal_connect(G_OBJECT(color_buttons.quote_level1_btn), "clicked", + G_CALLBACK(quote_color_set_dialog), "LEVEL1"); + g_signal_connect(G_OBJECT(color_buttons.quote_level2_btn), "clicked", + G_CALLBACK(quote_color_set_dialog), "LEVEL2"); + g_signal_connect(G_OBJECT(color_buttons.quote_level3_btn), "clicked", + G_CALLBACK(quote_color_set_dialog), "LEVEL3"); + g_signal_connect(G_OBJECT(color_buttons.uri_btn), "clicked", + G_CALLBACK(quote_color_set_dialog), "URI"); + g_signal_connect(G_OBJECT(recycle_colors_btn), "toggled", + G_CALLBACK(prefs_recycle_colors_toggled), NULL); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(gtk_main_quit), NULL); + + /* show message button colors and recycle options */ + set_button_bg_color(color_buttons.quote_level1_btn, + prefs_common.quote_level1_col); + set_button_bg_color(color_buttons.quote_level2_btn, + prefs_common.quote_level2_col); + set_button_bg_color(color_buttons.quote_level3_btn, + prefs_common.quote_level3_col); + set_button_bg_color(color_buttons.uri_btn, + prefs_common.uri_col); + gtk_toggle_button_set_active((GtkToggleButton *)recycle_colors_btn, + prefs_common.recycle_quote_colors); + + gtk_widget_show_all(vbox); + quote_color_win = window; +} + +static gboolean prefs_quote_colors_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + gtk_main_quit(); + return FALSE; +} + +static void quote_color_set_dialog(GtkWidget *widget, gpointer data) +{ + gchar *type = (gchar *)data; + gchar *title = NULL; + gdouble color[4] = {0.0, 0.0, 0.0, 0.0}; + gint rgbvalue = 0; + GtkColorSelectionDialog *dialog; + + if(g_strcasecmp(type, "LEVEL1") == 0) { + title = _("Pick color for quotation level 1"); + rgbvalue = prefs_common.quote_level1_col; + } else if(g_strcasecmp(type, "LEVEL2") == 0) { + title = _("Pick color for quotation level 2"); + rgbvalue = prefs_common.quote_level2_col; + } else if(g_strcasecmp(type, "LEVEL3") == 0) { + title = _("Pick color for quotation level 3"); + rgbvalue = prefs_common.quote_level3_col; + } else if(g_strcasecmp(type, "URI") == 0) { + title = _("Pick color for URI"); + rgbvalue = prefs_common.uri_col; + } else { /* Should never be called */ + g_warning("Unrecognized datatype '%s' in quote_color_set_dialog\n", type); + return; + } + + color_dialog = gtk_color_selection_dialog_new(title); + gtk_window_set_position(GTK_WINDOW(color_dialog), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(color_dialog), TRUE); + gtk_window_set_policy(GTK_WINDOW(color_dialog), FALSE, FALSE, FALSE); + manage_window_set_transient(GTK_WINDOW(color_dialog)); + + g_signal_connect(G_OBJECT(GTK_COLOR_SELECTION_DIALOG(color_dialog)->ok_button), + "clicked", G_CALLBACK(quote_colors_set_dialog_ok), data); + g_signal_connect(G_OBJECT(GTK_COLOR_SELECTION_DIALOG(color_dialog)->cancel_button), + "clicked", G_CALLBACK(quote_colors_set_dialog_cancel), data); + g_signal_connect(G_OBJECT(color_dialog), "key_press_event", + G_CALLBACK(quote_colors_set_dialog_key_pressed), data); + + /* preselect the previous color in the color selection dialog */ + color[0] = (gdouble) ((rgbvalue & 0xff0000) >> 16) / 255.0; + color[1] = (gdouble) ((rgbvalue & 0x00ff00) >> 8) / 255.0; + color[2] = (gdouble) (rgbvalue & 0x0000ff) / 255.0; + dialog = GTK_COLOR_SELECTION_DIALOG(color_dialog); + gtk_color_selection_set_color + (GTK_COLOR_SELECTION(dialog->colorsel), color); + + gtk_widget_show(color_dialog); +} + +static void quote_colors_set_dialog_ok(GtkWidget *widget, gpointer data) +{ + GtkColorSelection *colorsel = (GtkColorSelection *) + ((GtkColorSelectionDialog *)color_dialog)->colorsel; + gdouble color[4]; + gint red, green, blue, rgbvalue; + gchar *type = (gchar *)data; + + gtk_color_selection_get_color(colorsel, color); + + red = (gint) (color[0] * 255.0); + green = (gint) (color[1] * 255.0); + blue = (gint) (color[2] * 255.0); + rgbvalue = (gint) ((red * 0x10000) | (green * 0x100) | blue); + +#if 0 + fprintf(stderr, "redc = %f, greenc = %f, bluec = %f\n", color[0], color[1], color[2]); + fprintf(stderr, "red = %d, green = %d, blue = %d\n", red, green, blue); + fprintf(stderr, "Color is %x\n", rgbvalue); +#endif + + if (g_strcasecmp(type, "LEVEL1") == 0) { + prefs_common.quote_level1_col = rgbvalue; + set_button_bg_color(color_buttons.quote_level1_btn, rgbvalue); + } else if (g_strcasecmp(type, "LEVEL2") == 0) { + prefs_common.quote_level2_col = rgbvalue; + set_button_bg_color(color_buttons.quote_level2_btn, rgbvalue); + } else if (g_strcasecmp(type, "LEVEL3") == 0) { + prefs_common.quote_level3_col = rgbvalue; + set_button_bg_color(color_buttons.quote_level3_btn, rgbvalue); + } else if (g_strcasecmp(type, "URI") == 0) { + prefs_common.uri_col = rgbvalue; + set_button_bg_color(color_buttons.uri_btn, rgbvalue); + } else + fprintf( stderr, "Unrecognized datatype '%s' in quote_color_set_dialog_ok\n", type ); + + gtk_widget_destroy(color_dialog); +} + +static void quote_colors_set_dialog_cancel(GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy(color_dialog); +} + +static gboolean quote_colors_set_dialog_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + gtk_widget_destroy(color_dialog); + return FALSE; +} + +static void set_button_bg_color(GtkWidget *widget, gint rgbvalue) +{ + GtkStyle *newstyle; + GdkColor color; + + gtkut_convert_int_to_gdk_color(rgbvalue, &color); + newstyle = gtk_style_copy(gtk_widget_get_default_style()); + newstyle->bg[GTK_STATE_NORMAL] = color; + newstyle->bg[GTK_STATE_PRELIGHT] = color; + newstyle->bg[GTK_STATE_ACTIVE] = color; + + gtk_widget_set_style(GTK_WIDGET(widget), newstyle); +} + +static void prefs_enable_message_color_toggled(void) +{ + gboolean is_active; + + is_active = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(message.chkbtn_enablecol)); + gtk_widget_set_sensitive(message.button_edit_col, is_active); + prefs_common.enable_color = is_active; +} + +static void prefs_recycle_colors_toggled(GtkWidget *widget) +{ + gboolean is_active; + + is_active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + prefs_common.recycle_quote_colors = is_active; +} + +void prefs_quote_description(void) +{ + if (!quote_desc_win) + prefs_quote_description_create(); + + manage_window_set_transient(GTK_WINDOW(quote_desc_win)); + gtk_widget_show(quote_desc_win); + gtk_main(); + gtk_widget_hide(quote_desc_win); +} + +static void prefs_quote_description_create(void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *vbox2; + GtkWidget *label; + GtkWidget *hbbox; + GtkWidget *ok_btn; + + quote_desc_win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(quote_desc_win), + _("Description of symbols")); + gtk_container_set_border_width(GTK_CONTAINER(quote_desc_win), 8); + gtk_window_set_position(GTK_WINDOW(quote_desc_win), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(quote_desc_win), TRUE); + gtk_window_set_policy(GTK_WINDOW(quote_desc_win), FALSE, FALSE, FALSE); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(quote_desc_win), vbox); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); + + vbox2 = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0); + +#define PACK_LABEL() \ + gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0); \ + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); \ + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + label = gtk_label_new + ("%d\n" /* date */ + "%f\n" /* from */ + "%N\n" /* full name of sender */ + "%F\n" /* first name of sender */ + "%I\n" /* initial of sender */ + "%s\n" /* subject */ + "%t\n" /* to */ + "%c\n" /* cc */ + "%n\n" /* newsgroups */ + "%i"); /* message id */ + PACK_LABEL(); + + label = gtk_label_new + ("?x{expr}"); /* condition */ + PACK_LABEL(); + + label = gtk_label_new + ("%M\n" /* message body */ + "%Q\n" /* quoted message body */ + "%m\n" /* message body without signature */ + "%q\n" /* quoted message body without signature */ + "%%"); /* literal percent */ + PACK_LABEL(); + + label = gtk_label_new + ("\\\\\n" /* literal backslash */ + "\\?\n" /* literal question mark */ + "\\{\n" /* literal opening curly brace */ + "\\}"); /* literal closing curly brace */ + PACK_LABEL(); + + vbox2 = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0); + + label = gtk_label_new + (_("Date\n" + "From\n" + "Full Name of Sender\n" + "First Name of Sender\n" + "Initial of Sender\n" + "Subject\n" + "To\n" + "Cc\n" + "Newsgroups\n" + "Message-ID")); + PACK_LABEL(); + + label = gtk_label_new + (_("If x is set, displays expr")); + PACK_LABEL(); + + label = gtk_label_new + (_("Message body\n" + "Quoted message body\n" + "Message body without signature\n" + "Quoted message body without signature\n" + "Literal %")); + PACK_LABEL(); + + label = gtk_label_new + (_("Literal backslash\n" + "Literal question mark\n" + "Literal opening curly brace\n" + "Literal closing curly brace")); + PACK_LABEL(); + +#undef PACK_LABEL + + gtkut_button_set_create(&hbbox, &ok_btn, _("OK"), + NULL, NULL, NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); + + gtk_widget_grab_default(ok_btn); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect + (G_OBJECT(quote_desc_win), "key_press_event", + G_CALLBACK(prefs_quote_description_key_pressed), NULL); + g_signal_connect(G_OBJECT(quote_desc_win), "delete_event", + G_CALLBACK(gtk_main_quit), NULL); + + gtk_widget_show_all(vbox); +} + +static gboolean prefs_quote_description_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + gtk_main_quit(); + return FALSE; +} + +static void prefs_font_select(GtkButton *button) +{ + if (!font_sel_win) { + font_sel_win = gtk_font_selection_dialog_new + (_("Font selection")); + gtk_window_set_position(GTK_WINDOW(font_sel_win), + GTK_WIN_POS_CENTER); + g_signal_connect(G_OBJECT(font_sel_win), "delete_event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect + (G_OBJECT(font_sel_win), "key_press_event", + G_CALLBACK(prefs_font_selection_key_pressed), NULL); + g_signal_connect + (G_OBJECT(GTK_FONT_SELECTION_DIALOG(font_sel_win)->ok_button), + "clicked", + G_CALLBACK(prefs_font_selection_ok), NULL); + g_signal_connect_closure + (G_OBJECT(GTK_FONT_SELECTION_DIALOG(font_sel_win)->cancel_button), + "clicked", + g_cclosure_new_swap(G_CALLBACK(gtk_widget_hide_on_delete), + font_sel_win, NULL), + FALSE); + } + + manage_window_set_transient(GTK_WINDOW(font_sel_win)); + gtk_window_set_modal(GTK_WINDOW(font_sel_win), TRUE); + gtk_widget_grab_focus + (GTK_FONT_SELECTION_DIALOG(font_sel_win)->ok_button); + gtk_widget_show(font_sel_win); +} + +static gboolean prefs_font_selection_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + gtk_widget_hide(font_sel_win); + return FALSE; +} + +static void prefs_font_selection_ok(GtkButton *button) +{ + gchar *fontname; + + fontname = gtk_font_selection_dialog_get_font_name + (GTK_FONT_SELECTION_DIALOG(font_sel_win)); + + if (fontname) { + gtk_entry_set_text(GTK_ENTRY(display.entry_textfont), fontname); + g_free(fontname); + } + + gtk_widget_hide(font_sel_win); +} + +static void prefs_keybind_select(void) +{ + GtkWidget *window; + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *confirm_area; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + gtk_window_set_title (GTK_WINDOW (window), _("Key bindings")); + gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + gtk_window_set_policy (GTK_WINDOW (window), FALSE, FALSE, FALSE); + manage_window_set_transient (GTK_WINDOW (window)); + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_container_add (GTK_CONTAINER (window), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + label = gtk_label_new + (_("Select the preset of key bindings.\n" + "You can also modify each menu's shortcuts by pressing\n" + "any key(s) when placing the mouse pointer on the item.")); + gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + combo = gtk_combo_new (); + gtk_box_pack_start (GTK_BOX (hbox1), combo, TRUE, TRUE, 0); + gtkut_combo_set_items (GTK_COMBO (combo), + _("Default"), + "Mew / Wanderlust", + "Mutt", + _("Old Sylpheed"), + NULL); + gtk_editable_set_editable + (GTK_EDITABLE (GTK_COMBO (combo)->entry), FALSE); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + gtkut_button_set_create (&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end (GTK_BOX (hbox1), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default (ok_btn); + + MANAGE_WINDOW_SIGNALS_CONNECT(window); + g_signal_connect (G_OBJECT (window), "delete_event", + G_CALLBACK (prefs_keybind_deleted), NULL); + g_signal_connect (G_OBJECT (window), "key_press_event", + G_CALLBACK (prefs_keybind_key_pressed), NULL); + g_signal_connect (G_OBJECT (ok_btn), "clicked", + G_CALLBACK (prefs_keybind_apply_clicked), NULL); + g_signal_connect (G_OBJECT (cancel_btn), "clicked", + G_CALLBACK (prefs_keybind_cancel), NULL); + + gtk_widget_show_all(window); + + keybind.window = window; + keybind.combo = combo; +} + +static gboolean prefs_keybind_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_keybind_cancel(); + return FALSE; +} + +static gint prefs_keybind_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_keybind_cancel(); + return TRUE; +} + +static void prefs_keybind_cancel(void) +{ + gtk_widget_destroy(keybind.window); + keybind.window = NULL; + keybind.combo = NULL; +} + +struct KeyBind { + const gchar *accel_path; + const gchar *accel_key; +}; + +static void prefs_keybind_apply(struct KeyBind keybind[], gint num) +{ + gint i; + guint key; + GdkModifierType mods; + + for (i = 0; i < num; i++) { + const gchar *accel_key = + keybind[i].accel_key ? keybind[i].accel_key : ""; + gtk_accelerator_parse(accel_key, &key, &mods); + gtk_accel_map_change_entry(keybind[i].accel_path, + key, mods, TRUE); + } +} + +static void prefs_keybind_apply_clicked(GtkWidget *widget) +{ + GtkEntry *entry = GTK_ENTRY(GTK_COMBO(keybind.combo)->entry); + const gchar *text; + struct KeyBind *menurc; + gint n_menurc; + + static struct KeyBind default_menurc[] = { + {"
/File/Empty all trash", ""}, + {"
/File/Save as...", "S"}, + {"
/File/Print...", ""}, + {"
/File/Exit", "Q"}, + + {"
/Edit/Copy", "C"}, + {"
/Edit/Select all", "A"}, + {"
/Edit/Find in current message...", "F"}, + {"
/Edit/Search messages...", "F"}, + + {"
/View/Show or hide/Message view", "V"}, + {"
/View/Thread view", "T"}, + {"
/View/Go to/Prev message", "P"}, + {"
/View/Go to/Next message", "N"}, + {"
/View/Go to/Prev unread message", "P"}, + {"
/View/Go to/Next unread message", "N"}, + {"
/View/Go to/Other folder...", "G"}, + {"
/View/Open in new window", "N"}, + {"
/View/View source", "U"}, + {"
/View/Show all header", "H"}, + {"
/View/Update", "U"}, + + {"
/Message/Receive/Get new mail", "I"}, + {"
/Message/Receive/Get from all accounts", "I"}, + {"
/Message/Compose new message", "M"}, + {"
/Message/Reply", "R"}, + {"
/Message/Reply to/all", "R"}, + {"
/Message/Reply to/sender", ""}, + {"
/Message/Reply to/mailing list", "L"}, + {"
/Message/Forward", "F"}, + {"
/Message/Forward as attachment", ""}, + {"
/Message/Move...", "O"}, + {"
/Message/Copy...", "O"}, + {"
/Message/Delete", "D"}, + {"
/Message/Mark/Mark", "asterisk"}, + {"
/Message/Mark/Unmark", "U"}, + {"
/Message/Mark/Mark as unread", "exclam"}, + {"
/Message/Mark/Mark as read", ""}, + + {"
/Tools/Address book", "A"}, + {"
/Tools/Execute", "X"}, + {"
/Tools/Log window", "L"}, + + {"/File/Close", "W"}, + {"/Edit/Select all", "A"}, + {"/Edit/Advanced/Move a word backward", ""}, + {"/Edit/Advanced/Move a word forward", ""}, + {"/Edit/Advanced/Move to beginning of line", ""}, + {"/Edit/Advanced/Delete a word backward", ""}, + {"/Edit/Advanced/Delete a word forward", ""}, + }; + + static struct KeyBind mew_wl_menurc[] = { + {"
/File/Empty all trash", "D"}, + {"
/File/Save as...", "Y"}, + {"
/File/Print...", "numbersign"}, + {"
/File/Exit", "Q"}, + + {"
/Edit/Copy", "C"}, + {"
/Edit/Select all", "A"}, + {"
/Edit/Find in current message...", "F"}, + {"
/Edit/Search messages...", "S"}, + + {"
/View/Show or hide/Message view", "V"}, + {"
/View/Thread view", "T"}, + {"
/View/Go to/Prev message", "P"}, + {"
/View/Go to/Next message", "N"}, + {"
/View/Go to/Prev unread message", "P"}, + {"
/View/Go to/Next unread message", "N"}, + {"
/View/Go to/Other folder...", "G"}, + {"
/View/Open in new window", "N"}, + {"
/View/View source", "U"}, + {"
/View/Show all header", "H"}, + {"
/View/Update", "S"}, + + {"
/Message/Receive/Get new mail", "I"}, + {"
/Message/Receive/Get from all accounts", "I"}, + {"
/Message/Compose new message", "W"}, + {"
/Message/Reply", "R"}, + {"
/Message/Reply to/all", "A"}, + {"
/Message/Reply to/sender", ""}, + {"
/Message/Reply to/mailing list", "L"}, + {"
/Message/Forward", "F"}, + {"
/Message/Forward as attachment", "F"}, + {"
/Message/Move...", "O"}, + {"
/Message/Copy...", "O"}, + {"
/Message/Delete", "D"}, + {"
/Message/Mark/Mark", "asterisk"}, + {"
/Message/Mark/Unmark", "U"}, + {"
/Message/Mark/Mark as unread", "exclam"}, + {"
/Message/Mark/Mark as read", "R"}, + + {"
/Tools/Address book", "A"}, + {"
/Tools/Execute", "X"}, + {"
/Tools/Log window", "L"}, + + {"/File/Close", "W"}, + {"/Edit/Select all", ""}, + {"/Edit/Advanced/Move a word backward", "B"}, + {"/Edit/Advanced/Move a word forward", "F"}, + {"/Edit/Advanced/Move to beginning of line", "A"}, + {"/Edit/Advanced/Delete a word backward", "W"}, + {"/Edit/Advanced/Delete a word forward", "D"}, + }; + + static struct KeyBind mutt_menurc[] = { + {"
/File/Empty all trash", ""}, + {"
/File/Save as...", "S"}, + {"
/File/Print...", "P"}, + {"
/File/Exit", "Q"}, + + {"
/Edit/Copy", "C"}, + {"
/Edit/Select all", "A"}, + {"
/Edit/Find in current message...", "F"}, + {"
/Edit/Search messages...", "slash"}, + + {"
/View/Show or hide/Message view", "V"}, + {"
/View/Thread view", "T"}, + {"
/View/Go to/Prev message", ""}, + {"
/View/Go to/Next message", ""}, + {"
/View/Go to/Prev unread message", ""}, + {"
/View/Go to/Next unread message", ""}, + {"
/View/Go to/Other folder...", "C"}, + {"
/View/Open in new window", "N"}, + {"
/View/View source", "U"}, + {"
/View/Show all header", "H"}, + {"
/View/Update", "U"}, + + {"
/Message/Receive/Get new mail", "I"}, + {"
/Message/Receive/Get from all accounts", "I"}, + {"
/Message/Compose new message", "M"}, + {"
/Message/Reply", "R"}, + {"
/Message/Reply to/all", "G"}, + {"
/Message/Reply to/sender", ""}, + {"
/Message/Reply to/mailing list", "L"}, + {"
/Message/Forward", "F"}, + {"
/Message/Forward as attachment", ""}, + {"
/Message/Move...", "O"}, + {"
/Message/Copy...", "C"}, + {"
/Message/Delete", "D"}, + {"
/Message/Mark/Mark", "F"}, + {"
/Message/Mark/Unmark", "U"}, + {"
/Message/Mark/Mark as unread", "N"}, + {"
/Message/Mark/Mark as read", ""}, + + {"
/Tools/Address book", "A"}, + {"
/Tools/Execute", "X"}, + {"
/Tools/Log window", "L"}, + + {"/File/Close", "W"}, + {"/Edit/Select all", ""}, + {"/Edit/Advanced/Move a word backward", "B"}, + {"/Edit/Advanced/Move a word forward", "F"}, + {"/Edit/Advanced/Move to beginning of line", "A"}, + {"/Edit/Advanced/Delete a word backward", "W"}, + {"/Edit/Advanced/Delete a word forward", "D"}, + }; + + static struct KeyBind old_sylpheed_menurc[] = { + {"
/File/Empty all trash", ""}, + {"
/File/Save as...", ""}, + {"
/File/Print...", "P"}, + {"
/File/Exit", "Q"}, + + {"
/Edit/Copy", "C"}, + {"
/Edit/Select all", "A"}, + {"
/Edit/Find in current message...", "F"}, + {"
/Edit/Search messages...", "S"}, + + {"
/View/Show or hide/Message view", "V"}, + {"
/View/Thread view", "T"}, + {"
/View/Go to/Prev message", "P"}, + {"
/View/Go to/Next message", "N"}, + {"
/View/Go to/Prev unread message", "P"}, + {"
/View/Go to/Next unread message", "N"}, + {"
/View/Go to/Other folder...", "G"}, + {"
/View/Open in new window", "N"}, + {"
/View/View source", "U"}, + {"
/View/Show all header", "H"}, + {"
/View/Update", "U"}, + + {"
/Message/Receive/Get new mail", "I"}, + {"
/Message/Receive/Get from all accounts", "I"}, + {"
/Message/Compose new message", "N"}, + {"
/Message/Reply", "R"}, + {"
/Message/Reply to/all", "R"}, + {"
/Message/Reply to/sender", "R"}, + {"
/Message/Reply to/mailing list", "L"}, + {"
/Message/Forward", "F"}, + {"
/Message/Forward as attachment", "F"}, + {"
/Message/Move...", "O"}, + {"
/Message/Copy...", ""}, + {"
/Message/Delete", "D"}, + {"
/Message/Mark/Mark", "asterisk"}, + {"
/Message/Mark/Unmark", "U"}, + {"
/Message/Mark/Mark as unread", "exclam"}, + {"
/Message/Mark/Mark as read", ""}, + + {"
/Tools/Address book", "A"}, + {"
/Tools/Execute", "X"}, + {"
/Tools/Log window", "L"}, + + {"/File/Close", "W"}, + {"/Edit/Select all", ""}, + {"/Edit/Advanced/Move a word backward", "B"}, + {"/Edit/Advanced/Move a word forward", "F"}, + {"/Edit/Advanced/Move to beginning of line", "A"}, + {"/Edit/Advanced/Delete a word backward", "W"}, + {"/Edit/Advanced/Delete a word forward", "D"}, + }; + + static struct KeyBind empty_menurc[] = { + {"
/File/Empty all trash", ""}, + {"
/File/Save as...", ""}, + {"
/File/Print...", ""}, + {"
/File/Exit", ""}, + + {"
/Edit/Copy", ""}, + {"
/Edit/Select all", ""}, + {"
/Edit/Find in current message...", ""}, + {"
/Edit/Search messages...", ""}, + + {"
/View/Show or hide/Message view", ""}, + {"
/View/Thread view", ""}, + {"
/View/Go to/Prev message", ""}, + {"
/View/Go to/Next message", ""}, + {"
/View/Go to/Prev unread message", ""}, + {"
/View/Go to/Next unread message", ""}, + {"
/View/Go to/Other folder...", ""}, + {"
/View/Open in new window", ""}, + {"
/View/View source", ""}, + {"
/View/Show all header", ""}, + {"
/View/Update", ""}, + + {"
/Message/Receive/Get new mail", ""}, + {"
/Message/Receive/Get from all accounts", ""}, + {"
/Message/Compose new message", ""}, + {"
/Message/Reply", ""}, + {"
/Message/Reply to/all", ""}, + {"
/Message/Reply to/sender", ""}, + {"
/Message/Reply to/mailing list", ""}, + {"
/Message/Forward", ""}, + {"
/Message/Forward as attachment", ""}, + {"
/Message/Move...", ""}, + {"
/Message/Copy...", ""}, + {"
/Message/Delete", ""}, + {"
/Message/Mark/Mark", ""}, + {"
/Message/Mark/Unmark", ""}, + {"
/Message/Mark/Mark as unread", ""}, + {"
/Message/Mark/Mark as read", ""}, + + {"
/Tools/Address book", ""}, + {"
/Tools/Execute", ""}, + {"
/Tools/Log window", ""}, + + {"/File/Close", ""}, + {"/Edit/Select all", ""}, + {"/Edit/Advanced/Move a word backward", ""}, + {"/Edit/Advanced/Move a word forward", ""}, + {"/Edit/Advanced/Move to beginning of line", ""}, + {"/Edit/Advanced/Delete a word backward", ""}, + {"/Edit/Advanced/Delete a word forward", ""}, + }; + + text = gtk_entry_get_text(entry); + + if (!strcmp(text, _("Default"))) { + menurc = default_menurc; + n_menurc = G_N_ELEMENTS(default_menurc); + } else if (!strcmp(text, "Mew / Wanderlust")) { + menurc = mew_wl_menurc; + n_menurc = G_N_ELEMENTS(mew_wl_menurc); + } else if (!strcmp(text, "Mutt")) { + menurc = mutt_menurc; + n_menurc = G_N_ELEMENTS(mutt_menurc); + } else if (!strcmp(text, _("Old Sylpheed"))) { + menurc = old_sylpheed_menurc; + n_menurc = G_N_ELEMENTS(old_sylpheed_menurc); + } else + return; + + prefs_keybind_apply(empty_menurc, G_N_ELEMENTS(empty_menurc)); + prefs_keybind_apply(menurc, n_menurc); + + gtk_widget_destroy(keybind.window); + keybind.window = NULL; + keybind.combo = NULL; +} + +static void prefs_common_charset_set_data_from_optmenu(PrefParam *pparam) +{ + GtkWidget *menu; + GtkWidget *menuitem; + gchar *charset; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(*pparam->widget)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + charset = g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID); + g_free(*((gchar **)pparam->data)); + *((gchar **)pparam->data) = g_strdup(charset); +} + +static void prefs_common_charset_set_optmenu(PrefParam *pparam) +{ + GtkOptionMenu *optmenu = GTK_OPTION_MENU(*pparam->widget); + gint index; + + g_return_if_fail(optmenu != NULL); + g_return_if_fail(*((gchar **)pparam->data) != NULL); + + index = menu_find_option_menu_index(optmenu, *((gchar **)pparam->data), + (GCompareFunc)strcmp); + if (index >= 0) + gtk_option_menu_set_history(optmenu, index); + else { + gtk_option_menu_set_history(optmenu, 0); + prefs_common_charset_set_data_from_optmenu(pparam); + } +} + +static void prefs_common_encoding_set_data_from_optmenu(PrefParam *pparam) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(*pparam->widget)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + *((TransferEncodingMethod *)pparam->data) = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); +} + +static void prefs_common_encoding_set_optmenu(PrefParam *pparam) +{ + TransferEncodingMethod method = + *((TransferEncodingMethod *)pparam->data); + GtkOptionMenu *optmenu = GTK_OPTION_MENU(*pparam->widget); + gint index; + + g_return_if_fail(optmenu != NULL); + + index = menu_find_option_menu_index(optmenu, GINT_TO_POINTER(method), + NULL); + if (index >= 0) + gtk_option_menu_set_history(optmenu, index); + else { + gtk_option_menu_set_history(optmenu, 0); + prefs_common_encoding_set_data_from_optmenu(pparam); + } +} + +static void prefs_common_recv_dialog_set_data_from_optmenu(PrefParam *pparam) +{ + GtkWidget *menu; + GtkWidget *menuitem; + + menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(*pparam->widget)); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + *((RecvDialogMode *)pparam->data) = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); +} + +static void prefs_common_recv_dialog_set_optmenu(PrefParam *pparam) +{ + RecvDialogMode mode = *((RecvDialogMode *)pparam->data); + GtkOptionMenu *optmenu = GTK_OPTION_MENU(*pparam->widget); + GtkWidget *menu; + GtkWidget *menuitem; + gint index; + + index = menu_find_option_menu_index(optmenu, GINT_TO_POINTER(mode), + NULL); + if (index >= 0) + gtk_option_menu_set_history(optmenu, index); + else { + gtk_option_menu_set_history(optmenu, 0); + prefs_common_recv_dialog_set_data_from_optmenu(pparam); + } + + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); +} + +static gint prefs_common_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_common_cancel(); + return TRUE; +} + +static gboolean prefs_common_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_common_cancel(); + return FALSE; +} + +static void prefs_common_ok(void) +{ + prefs_common_apply(); + gtk_widget_hide(dialog.window); + if (quote_desc_win && GTK_WIDGET_VISIBLE(quote_desc_win)) + gtk_widget_hide(quote_desc_win); + + inc_unlock(); +} + +static void prefs_common_apply(void) +{ + prefs_set_data_from_dialog(param); + main_window_reflect_prefs_all(); + sock_set_io_timeout(prefs_common.io_timeout_secs); + prefs_common_write_config(); + + inc_autocheck_timer_remove(); + inc_autocheck_timer_set(); +} + +static void prefs_common_cancel(void) +{ + gtk_widget_hide(dialog.window); + inc_unlock(); +} diff --git a/src/prefs_common.h b/src/prefs_common.h new file mode 100644 index 00000000..4b1c6101 --- /dev/null +++ b/src/prefs_common.h @@ -0,0 +1,246 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_COMMON_H__ +#define __PREFS_COMMON_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "mainwindow.h" +#include "summaryview.h" +#include "codeconv.h" +#include "textview.h" + +typedef struct _PrefsCommon PrefsCommon; + +typedef enum { + RECV_DIALOG_ALWAYS, + RECV_DIALOG_MANUAL, + RECV_DIALOG_NEVER +} RecvDialogMode; + +typedef enum { + CTE_AUTO, + CTE_BASE64, + CTE_QUOTED_PRINTABLE, + CTE_8BIT +} TransferEncodingMethod; + +struct _PrefsCommon +{ + /* Receive */ + gboolean use_extinc; + gchar *extinc_cmd; + gboolean inc_local; + gboolean filter_on_inc; + gchar *spool_path; + gboolean scan_all_after_inc; + gboolean autochk_newmail; + gint autochk_itv; + gboolean chk_on_startup; + gint max_articles; + + /* Send */ + gboolean use_extsend; + gchar *extsend_cmd; + gboolean savemsg; + gchar *outgoing_charset; + TransferEncodingMethod encoding_method; + + gboolean allow_jisx0201_kana; + + /* Compose */ + gboolean auto_sig; + gchar *sig_sep; + gint undolevels; + gint linewrap_len; + gboolean linewrap_quote; + gboolean autowrap; + gboolean linewrap_at_send; + gboolean auto_exteditor; + gboolean reply_account_autosel; + gboolean default_reply_list; + gboolean show_ruler; + + /* Quote */ + gboolean reply_with_quote; + gchar *quotemark; + gchar *quotefmt; + gchar *fw_quotemark; + gchar *fw_quotefmt; + + /* Display */ + gchar *widgetfont; + gchar *textfont; + gchar *normalfont; + gchar *boldfont; + gchar *smallfont; + gchar *titlefont; + + gboolean trans_hdr; + gboolean display_folder_unread; + gint ng_abbrev_len; + + gboolean swap_from; + gboolean expand_thread; + gchar *date_format; + + gboolean enable_hscrollbar; + gboolean bold_unread; + gboolean enable_thread; + + ToolbarStyle toolbar_style; + gboolean show_statusbar; + + gint folderview_vscrollbar_policy; + + /* Summary columns visibility, position and size */ + gboolean summary_col_visible[N_SUMMARY_COLS]; + gint summary_col_pos[N_SUMMARY_COLS]; + gint summary_col_size[N_SUMMARY_COLS]; + + /* Widget visibility, position and size */ + gint folderwin_x; + gint folderwin_y; + gint folderview_width; + gint folderview_height; + gboolean folderview_visible; + + gint folder_col_folder; + gint folder_col_new; + gint folder_col_unread; + gint folder_col_total; + + gint summaryview_width; + gint summaryview_height; + + gint main_msgwin_x; + gint main_msgwin_y; + gint msgview_width; + gint msgview_height; + gboolean msgview_visible; + + gint mainview_x; + gint mainview_y; + gint mainview_width; + gint mainview_height; + gint mainwin_x; + gint mainwin_y; + gint mainwin_width; + gint mainwin_height; + + gint msgwin_width; + gint msgwin_height; + + gint sourcewin_width; + gint sourcewin_height; + + gint compose_width; + gint compose_height; + + /* Message */ + gboolean enable_color; + gint quote_level1_col; + gint quote_level2_col; + gint quote_level3_col; + gint uri_col; + gushort sig_col; + gboolean recycle_quote_colors; + gboolean conv_mb_alnum; + gboolean display_header_pane; + gboolean display_header; + gboolean head_space; + gint line_space; + gboolean enable_smooth_scroll; + gint scroll_step; + gboolean scroll_halfpage; + + gboolean resize_image; + + gchar *force_charset; + + gboolean show_other_header; + GSList *disphdr_list; + + /* MIME viewer */ + gchar *mime_image_viewer; + gchar *mime_audio_player; + gchar *mime_open_cmd; + + GList *mime_open_cmd_history; + +#if USE_GPGME + /* Privacy */ + gboolean auto_check_signatures; + gboolean gpg_signature_popup; + gboolean store_passphrase; + gint store_passphrase_timeout; + gboolean passphrase_grab; + gboolean gpg_warning; +#endif /* USE_GPGME */ + + /* Interface */ + gboolean sep_folder; + gboolean sep_msg; + gboolean emulate_emacs; + gboolean always_show_msg; + gboolean open_unread_on_enter; + gboolean mark_as_read_on_new_window; + gboolean open_inbox_on_inc; + gboolean immediate_exec; + RecvDialogMode recv_dialog_mode; + gboolean close_recv_dialog; + gboolean no_recv_err_panel; + gboolean add_address_by_click; + + /* Other */ + gchar *uri_cmd; + gchar *print_cmd; + gchar *ext_editor_cmd; + + gboolean confirm_on_exit; + gboolean clean_on_exit; + gboolean ask_on_clean; + gboolean warn_queued_on_exit; + + gint io_timeout_secs; + + /* Filtering */ + GSList *fltlist; + + /* Actions */ + GSList *actions_list; + + /* Online / Offline */ + gboolean online_mode; +}; + +extern PrefsCommon prefs_common; + +void prefs_common_read_config (void); +void prefs_common_write_config (void); +void prefs_common_open (void); + +void prefs_quote_description (void); + +#endif /* __PREFS_COMMON_H__ */ diff --git a/src/prefs_customheader.c b/src/prefs_customheader.c new file mode 100644 index 00000000..f7b1d5df --- /dev/null +++ b/src/prefs_customheader.c @@ -0,0 +1,623 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "prefs.h" +#include "prefs_customheader.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "mainwindow.h" +#include "foldersel.h" +#include "manage_window.h" +#include "customheader.h" +#include "folder.h" +#include "utils.h" +#include "gtkutils.h" +#include "alertpanel.h" + +static struct CustomHdr { + GtkWidget *window; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + GtkWidget *hdr_combo; + GtkWidget *hdr_entry; + GtkWidget *val_entry; + GtkWidget *customhdr_clist; +} customhdr; + +/* widget creating functions */ +static void prefs_custom_header_create (void); + +static void prefs_custom_header_set_dialog (PrefsAccount *ac); +static void prefs_custom_header_set_list (PrefsAccount *ac); +static gint prefs_custom_header_clist_set_row (PrefsAccount *ac, + gint row); + +/* callback functions */ +static void prefs_custom_header_add_cb (void); +static void prefs_custom_header_delete_cb (void); +static void prefs_custom_header_up (void); +static void prefs_custom_header_down (void); +static void prefs_custom_header_select (GtkCList *clist, + gint row, + gint column, + GdkEvent *event); + +static void prefs_custom_header_row_moved (GtkCList *clist, + gint source_row, + gint dest_row, + gpointer data); + +static gboolean prefs_custom_header_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_custom_header_ok (void); +static void prefs_custom_header_cancel (void); +static gint prefs_custom_header_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); + +static PrefsAccount *cur_ac = NULL; + +void prefs_custom_header_open(PrefsAccount *ac) +{ + if (!customhdr.window) { + prefs_custom_header_create(); + } + + manage_window_set_transient(GTK_WINDOW(customhdr.window)); + gtk_widget_grab_focus(customhdr.ok_btn); + + prefs_custom_header_set_dialog(ac); + + cur_ac = ac; + + gtk_widget_show(customhdr.window); +} + +static void prefs_custom_header_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + GtkWidget *confirm_area; + + GtkWidget *vbox1; + + GtkWidget *table1; + GtkWidget *hdr_label; + GtkWidget *hdr_combo; + GtkWidget *val_label; + GtkWidget *val_entry; + + GtkWidget *reg_hbox; + GtkWidget *btn_hbox; + GtkWidget *arrow; + GtkWidget *add_btn; + GtkWidget *del_btn; + + GtkWidget *ch_hbox; + GtkWidget *ch_scrolledwin; + GtkWidget *customhdr_clist; + + GtkWidget *btn_vbox; + GtkWidget *up_btn; + GtkWidget *down_btn; + + gchar *title[1]; + + debug_print("Creating custom header setting window...\n"); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (window), vbox); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_widget_show (confirm_area); + gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default (ok_btn); + + gtk_window_set_title (GTK_WINDOW(window), _("Custom header setting")); + MANAGE_WINDOW_SIGNALS_CONNECT (window); + g_signal_connect (G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_custom_header_deleted), + NULL); + g_signal_connect (G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_custom_header_key_pressed), + NULL); + g_signal_connect (G_OBJECT(ok_btn), "clicked", + G_CALLBACK(prefs_custom_header_ok), NULL); + g_signal_connect (G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(prefs_custom_header_cancel), NULL); + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2); + + table1 = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vbox1), table1, + FALSE, FALSE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table1), 8); + gtk_table_set_col_spacings (GTK_TABLE (table1), 8); + + hdr_label = gtk_label_new (_("Header")); + gtk_widget_show (hdr_label); + gtk_table_attach (GTK_TABLE (table1), hdr_label, 0, 1, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (hdr_label), 0, 0.5); + + hdr_combo = gtk_combo_new (); + gtk_widget_show (hdr_combo); + gtk_table_attach (GTK_TABLE (table1), hdr_combo, 0, 1, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 0, 0, 0); + gtk_widget_set_size_request (hdr_combo, 150, -1); + gtkut_combo_set_items (GTK_COMBO (hdr_combo), + "User-Agent", "X-Face", "X-Operating-System", + NULL); + + val_label = gtk_label_new (_("Value")); + gtk_widget_show (val_label); + gtk_table_attach (GTK_TABLE (table1), val_label, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (val_label), 0, 0.5); + + val_entry = gtk_entry_new (); + gtk_widget_show (val_entry); + gtk_table_attach (GTK_TABLE (table1), val_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + 0, 0, 0); + gtk_widget_set_size_request (val_entry, 200, -1); + + /* add / delete */ + + reg_hbox = gtk_hbox_new (FALSE, 4); + gtk_widget_show (reg_hbox); + gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_widget_show (arrow); + gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0); + gtk_widget_set_size_request (arrow, -1, 16); + + btn_hbox = gtk_hbox_new (TRUE, 4); + gtk_widget_show (btn_hbox); + gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0); + + add_btn = gtk_button_new_with_label (_("Add")); + gtk_widget_show (add_btn); + gtk_box_pack_start (GTK_BOX (btn_hbox), add_btn, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (add_btn), "clicked", + G_CALLBACK (prefs_custom_header_add_cb), NULL); + + del_btn = gtk_button_new_with_label (_(" Delete ")); + gtk_widget_show (del_btn); + gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (del_btn), "clicked", + G_CALLBACK (prefs_custom_header_delete_cb), NULL); + + + ch_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (ch_hbox); + gtk_box_pack_start (GTK_BOX (vbox1), ch_hbox, TRUE, TRUE, 0); + + ch_scrolledwin = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (ch_scrolledwin, -1, 200); + gtk_widget_show (ch_scrolledwin); + gtk_box_pack_start (GTK_BOX (ch_hbox), ch_scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ch_scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Custom headers"); + customhdr_clist = gtk_clist_new_with_titles(1, title); + gtk_widget_show (customhdr_clist); + gtk_container_add (GTK_CONTAINER (ch_scrolledwin), customhdr_clist); + gtk_clist_set_column_width (GTK_CLIST (customhdr_clist), 0, 80); + gtk_clist_set_selection_mode (GTK_CLIST (customhdr_clist), + GTK_SELECTION_BROWSE); + gtk_clist_set_reorderable (GTK_CLIST (customhdr_clist), TRUE); + gtk_clist_set_use_drag_icons (GTK_CLIST (customhdr_clist), FALSE); + GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (customhdr_clist)->column[0].button, + GTK_CAN_FOCUS); + g_signal_connect (G_OBJECT (customhdr_clist), "select_row", + G_CALLBACK (prefs_custom_header_select), NULL); + g_signal_connect_after + (G_OBJECT (customhdr_clist), "row_move", + G_CALLBACK (prefs_custom_header_row_moved), NULL); + + btn_vbox = gtk_vbox_new (FALSE, 8); + gtk_widget_show (btn_vbox); + gtk_box_pack_start (GTK_BOX (ch_hbox), btn_vbox, FALSE, FALSE, 0); + + up_btn = gtk_button_new_with_label (_("Up")); + gtk_widget_show (up_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (up_btn), "clicked", + G_CALLBACK (prefs_custom_header_up), NULL); + + down_btn = gtk_button_new_with_label (_("Down")); + gtk_widget_show (down_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (down_btn), "clicked", + G_CALLBACK (prefs_custom_header_down), NULL); + + gtk_widget_show_all(window); + + customhdr.window = window; + customhdr.ok_btn = ok_btn; + customhdr.cancel_btn = cancel_btn; + + customhdr.hdr_combo = hdr_combo; + customhdr.hdr_entry = GTK_COMBO (hdr_combo)->entry; + customhdr.val_entry = val_entry; + + customhdr.customhdr_clist = customhdr_clist; +} + +void prefs_custom_header_read_config(PrefsAccount *ac) +{ + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + CustomHeader *ch; + + debug_print("Reading custom header configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + CUSTOM_HEADER_RC, NULL); + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + ac->customhdr_list = NULL; + return; + } + g_free(rcpath); + + /* remove all previous headers list */ + while (ac->customhdr_list != NULL) { + ch = (CustomHeader *)ac->customhdr_list->data; + custom_header_free(ch); + ac->customhdr_list = g_slist_remove(ac->customhdr_list, ch); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + ch = custom_header_read_str(buf); + if (ch) { + if (ch->account_id == ac->account_id) { + ac->customhdr_list = + g_slist_append(ac->customhdr_list, ch); + } else + custom_header_free(ch); + } + } + + fclose(fp); +} + +void prefs_custom_header_write_config(PrefsAccount *ac) +{ + gchar *rcpath; + PrefFile *pfile; + GSList *cur; + gchar buf[PREFSBUFSIZE]; + FILE * fp; + CustomHeader *ch; + + GSList *all_hdrs = NULL; + + debug_print("Writing custom header configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + CUSTOM_HEADER_RC, NULL); + + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + } else { + all_hdrs = NULL; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + ch = custom_header_read_str(buf); + if (ch) { + if (ch->account_id != ac->account_id) + all_hdrs = + g_slist_append(all_hdrs, ch); + else + custom_header_free(ch); + } + } + + fclose(fp); + } + + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning(_("failed to write configuration to file\n")); + g_free(rcpath); + return; + } + + for (cur = all_hdrs; cur != NULL; cur = cur->next) { + CustomHeader *hdr = (CustomHeader *)cur->data; + gchar *chstr; + + chstr = custom_header_get_str(hdr); + if (fputs(chstr, pfile->fp) == EOF || + fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputs || fputc"); + prefs_file_close_revert(pfile); + g_free(rcpath); + g_free(chstr); + return; + } + g_free(chstr); + } + + for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) { + CustomHeader *hdr = (CustomHeader *)cur->data; + gchar *chstr; + + chstr = custom_header_get_str(hdr); + if (fputs(chstr, pfile->fp) == EOF || + fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputs || fputc"); + prefs_file_close_revert(pfile); + g_free(rcpath); + g_free(chstr); + return; + } + g_free(chstr); + } + + g_free(rcpath); + + while (all_hdrs != NULL) { + ch = (CustomHeader *)all_hdrs->data; + custom_header_free(ch); + all_hdrs = g_slist_remove(all_hdrs, ch); + } + + if (prefs_file_close(pfile) < 0) { + g_warning(_("failed to write configuration to file\n")); + return; + } +} + +static void prefs_custom_header_set_dialog(PrefsAccount *ac) +{ + GtkCList *clist = GTK_CLIST(customhdr.customhdr_clist); + GSList *cur; + gchar *ch_str[1]; + gint row; + + gtk_clist_freeze(clist); + gtk_clist_clear(clist); + + for (cur = ac->customhdr_list; cur != NULL; cur = cur->next) { + CustomHeader *ch = (CustomHeader *)cur->data; + + ch_str[0] = g_strdup_printf("%s: %s", ch->name, + ch->value ? ch->value : ""); + row = gtk_clist_append(clist, ch_str); + gtk_clist_set_row_data(clist, row, ch); + + g_free(ch_str[0]); + } + + gtk_clist_thaw(clist); +} + +static void prefs_custom_header_set_list(PrefsAccount *ac) +{ + gint row = 0; + CustomHeader *ch; + + g_slist_free(ac->customhdr_list); + ac->customhdr_list = NULL; + + while ((ch = gtk_clist_get_row_data + (GTK_CLIST(customhdr.customhdr_clist), row)) != NULL) { + ac->customhdr_list = g_slist_append(ac->customhdr_list, ch); + row++; + } +} + +static gint prefs_custom_header_clist_set_row(PrefsAccount *ac, gint row) +{ + GtkCList *clist = GTK_CLIST(customhdr.customhdr_clist); + CustomHeader *ch; + const gchar *entry_text; + gchar *ch_str[1]; + + entry_text = gtk_entry_get_text(GTK_ENTRY(customhdr.hdr_entry)); + if (entry_text[0] == '\0') { + alertpanel_error(_("Header name is not set.")); + return -1; + } + + ch = g_new0(CustomHeader, 1); + + ch->account_id = ac->account_id; + + ch->name = g_strdup(entry_text); + unfold_line(ch->name); + g_strstrip(ch->name); + gtk_entry_set_text(GTK_ENTRY(customhdr.hdr_entry), ch->name); + + entry_text = gtk_entry_get_text(GTK_ENTRY(customhdr.val_entry)); + if (entry_text[0] != '\0') { + ch->value = g_strdup(entry_text); + unfold_line(ch->value); + g_strstrip(ch->value); + gtk_entry_set_text(GTK_ENTRY(customhdr.val_entry), ch->value); + } + + ch_str[0] = g_strdup_printf("%s: %s", ch->name, + ch->value ? ch->value : ""); + + if (row < 0) + row = gtk_clist_append(clist, ch_str); + else { + CustomHeader *tmp_ch; + + gtk_clist_set_text(clist, row, 0, ch_str[0]); + tmp_ch = gtk_clist_get_row_data(clist, row); + if (tmp_ch) + custom_header_free(tmp_ch); + } + + gtk_clist_set_row_data(clist, row, ch); + + g_free(ch_str[0]); + + prefs_custom_header_set_list(cur_ac); + + return row; +} + +static void prefs_custom_header_add_cb(void) +{ + prefs_custom_header_clist_set_row(cur_ac, -1); +} + +static void prefs_custom_header_delete_cb(void) +{ + GtkCList *clist = GTK_CLIST(customhdr.customhdr_clist); + CustomHeader *ch; + gint row; + + if (!clist->selection) return; + row = GPOINTER_TO_INT(clist->selection->data); + + if (alertpanel(_("Delete header"), + _("Do you really want to delete this header?"), + _("Yes"), _("No"), NULL) != G_ALERTDEFAULT) + return; + + ch = gtk_clist_get_row_data(clist, row); + custom_header_free(ch); + gtk_clist_remove(clist, row); + cur_ac->customhdr_list = g_slist_remove(cur_ac->customhdr_list, ch); +} + +static void prefs_custom_header_up(void) +{ + GtkCList *clist = GTK_CLIST(customhdr.customhdr_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0) + gtk_clist_row_move(clist, row, row - 1); +} + +static void prefs_custom_header_down(void) +{ + GtkCList *clist = GTK_CLIST(customhdr.customhdr_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row >= 0 && row < clist->rows - 1) + gtk_clist_row_move(clist, row, row + 1); +} + +#define ENTRY_SET_TEXT(entry, str) \ + gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "") + +static void prefs_custom_header_select(GtkCList *clist, gint row, gint column, + GdkEvent *event) +{ + CustomHeader *ch; + CustomHeader default_ch = { 0, "", NULL }; + + ch = gtk_clist_get_row_data(clist, row); + if (!ch) ch = &default_ch; + + ENTRY_SET_TEXT(customhdr.hdr_entry, ch->name); + ENTRY_SET_TEXT(customhdr.val_entry, ch->value); +} + +#undef ENTRY_SET_TEXT + +static void prefs_custom_header_row_moved(GtkCList *clist, gint source_row, + gint dest_row, gpointer data) +{ + prefs_custom_header_set_list(cur_ac); +} + +static gboolean prefs_custom_header_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_custom_header_cancel(); + return FALSE; +} + +static void prefs_custom_header_ok(void) +{ + prefs_custom_header_write_config(cur_ac); + gtk_widget_hide(customhdr.window); +} + +static void prefs_custom_header_cancel(void) +{ + prefs_custom_header_read_config(cur_ac); + gtk_widget_hide(customhdr.window); +} + +static gint prefs_custom_header_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_custom_header_cancel(); + return TRUE; +} diff --git a/src/prefs_customheader.h b/src/prefs_customheader.h new file mode 100644 index 00000000..c72e29d2 --- /dev/null +++ b/src/prefs_customheader.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_CUSTOMHEADER_H__ +#define __PREFS_CUSTOMHEADER_H__ + +#include "prefs_account.h" + +void prefs_custom_header_read_config (PrefsAccount *ac); +void prefs_custom_header_write_config (PrefsAccount *ac); +void prefs_custom_header_open (PrefsAccount *ac); + +#endif /* __PREFS_CUSTOMHEADER_H__ */ diff --git a/src/prefs_display_header.c b/src/prefs_display_header.c new file mode 100644 index 00000000..8239b7de --- /dev/null +++ b/src/prefs_display_header.c @@ -0,0 +1,631 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "prefs.h" +#include "prefs_display_header.h" +#include "prefs_common.h" +#include "manage_window.h" +#include "alertpanel.h" +#include "displayheader.h" +#include "utils.h" +#include "gtkutils.h" + +static struct DisplayHeader { + GtkWidget *window; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + GtkWidget *hdr_combo; + GtkWidget *hdr_entry; + GtkWidget *key_check; + GtkWidget *headers_clist; + GtkWidget *hidden_headers_clist; + + GtkWidget *other_headers; +} dispheader; + +/* widget creating functions */ +static void prefs_display_header_create (void); + +static void prefs_display_header_set_dialog (void); +static void prefs_display_header_set_list (void); +static gint prefs_display_header_clist_set_row (gboolean hidden); + +/* callback functions */ +static void prefs_display_header_register_cb (GtkButton *btn, + gpointer hidden_data); +static void prefs_display_header_delete_cb (GtkButton *btn, + gpointer clist_data); +static void prefs_display_header_up (void); +static void prefs_display_header_down (void); + +static void prefs_display_header_row_moved (GtkCList *clist, + gint source_row, + gint dest_row, + gpointer data); + +static gboolean prefs_display_header_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_display_header_ok (void); +static void prefs_display_header_cancel (void); +static gint prefs_display_header_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); + +static gchar *defaults[] = +{ + "From", + "To", + "Cc", + "Reply-To", + "Newsgroups", + "Followup-To", + "Subject", + "Date", + "Sender", + "Organization", + "X-Mailer", + "X-Newsreader", + "User-Agent", + "-Received", + "-Message-Id", + "-In-Reply-To", + "-References", + "-Mime-Version", + "-Content-Type", + "-Content-Transfer-Encoding", + "-X-UIDL", + "-Precedence", + "-Status", + "-Priority", + "-X-Face" +}; + +static void prefs_display_header_set_default(void) +{ + gint i; + DisplayHeaderProp *dp; + + for(i = 0; i < sizeof(defaults) / sizeof(defaults[0]); i++) { + dp = display_header_prop_read_str(defaults[i]); + prefs_common.disphdr_list = + g_slist_append(prefs_common.disphdr_list, dp); + } +} + +void prefs_display_header_open(void) +{ + if (!dispheader.window) { + prefs_display_header_create(); + } + + manage_window_set_transient(GTK_WINDOW(dispheader.window)); + gtk_widget_grab_focus(dispheader.ok_btn); + + prefs_display_header_set_dialog(); + + gtk_widget_show(dispheader.window); +} + +static void prefs_display_header_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *btn_hbox; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *confirm_area; + + GtkWidget *vbox1; + + GtkWidget *hbox1; + GtkWidget *hdr_label; + GtkWidget *hdr_combo; + + GtkWidget *btn_vbox; + GtkWidget *reg_btn; + GtkWidget *del_btn; + GtkWidget *up_btn; + GtkWidget *down_btn; + + GtkWidget *clist_hbox; + GtkWidget *clist_hbox1; + GtkWidget *clist_hbox2; + GtkWidget *clist_scrolledwin; + GtkWidget *headers_clist; + GtkWidget *hidden_headers_clist; + + GtkWidget *checkbtn_other_headers; + + gchar *title[1]; + + debug_print(_("Creating display header setting window...\n")); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (window), vbox); + + btn_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (btn_hbox); + gtk_box_pack_end (GTK_BOX (vbox), btn_hbox, FALSE, FALSE, 0); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_widget_show (confirm_area); + gtk_box_pack_end (GTK_BOX(btn_hbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default (ok_btn); + + gtk_window_set_title (GTK_WINDOW(window), + _("Display header setting")); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + g_signal_connect (G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_display_header_deleted), NULL); + g_signal_connect (G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_display_header_key_pressed), NULL); + g_signal_connect (G_OBJECT(ok_btn), "clicked", + G_CALLBACK(prefs_display_header_ok), NULL); + g_signal_connect (G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(prefs_display_header_cancel), NULL); + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_widget_show (vbox1); + gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2); + + hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, TRUE, 0); + + hdr_label = gtk_label_new (_("Header name")); + gtk_widget_show (hdr_label); + gtk_box_pack_start (GTK_BOX (hbox1), hdr_label, FALSE, FALSE, 0); + + hdr_combo = gtk_combo_new (); + gtk_widget_show (hdr_combo); + gtk_box_pack_start (GTK_BOX (hbox1), hdr_combo, TRUE, TRUE, 0); + gtk_widget_set_size_request (hdr_combo, 150, -1); + gtkut_combo_set_items (GTK_COMBO (hdr_combo), + "From", "To", "Cc", "Subject", "Date", + "Reply-To", "Sender", "User-Agent", "X-Mailer", + NULL); + + clist_hbox = gtk_hbox_new (FALSE, 10); + gtk_widget_show (clist_hbox); + gtk_box_pack_start (GTK_BOX (vbox1), clist_hbox, TRUE, TRUE, 0); + + /* display headers list */ + + clist_hbox1 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (clist_hbox1); + gtk_box_pack_start (GTK_BOX (clist_hbox), clist_hbox1, TRUE, TRUE, 0); + + clist_scrolledwin = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (clist_scrolledwin, 200, 210); + gtk_widget_show (clist_scrolledwin); + gtk_box_pack_start (GTK_BOX (clist_hbox1), clist_scrolledwin, + TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (clist_scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Displayed Headers"); + headers_clist = gtk_clist_new_with_titles(1, title); + gtk_widget_show (headers_clist); + gtk_container_add (GTK_CONTAINER (clist_scrolledwin), headers_clist); + gtk_clist_set_selection_mode (GTK_CLIST (headers_clist), + GTK_SELECTION_BROWSE); + gtk_clist_set_reorderable (GTK_CLIST (headers_clist), TRUE); + gtk_clist_set_use_drag_icons (GTK_CLIST (headers_clist), FALSE); + GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (headers_clist)->column[0].button, + GTK_CAN_FOCUS); + g_signal_connect_after + (G_OBJECT (headers_clist), "row_move", + G_CALLBACK (prefs_display_header_row_moved), NULL); + + btn_vbox = gtk_vbox_new (FALSE, 8); + gtk_widget_show (btn_vbox); + gtk_box_pack_start (GTK_BOX (clist_hbox1), btn_vbox, FALSE, FALSE, 0); + + reg_btn = gtk_button_new_with_label (_("Add")); + gtk_widget_show (reg_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), reg_btn, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (reg_btn), "clicked", + G_CALLBACK (prefs_display_header_register_cb), + GINT_TO_POINTER(FALSE)); + del_btn = gtk_button_new_with_label (_("Delete")); + gtk_widget_show (del_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), del_btn, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (del_btn), "clicked", + G_CALLBACK (prefs_display_header_delete_cb), + headers_clist); + + up_btn = gtk_button_new_with_label (_("Up")); + gtk_widget_show (up_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (up_btn), "clicked", + G_CALLBACK (prefs_display_header_up), NULL); + + down_btn = gtk_button_new_with_label (_("Down")); + gtk_widget_show (down_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (down_btn), "clicked", + G_CALLBACK (prefs_display_header_down), NULL); + + /* hidden headers list */ + + clist_hbox2 = gtk_hbox_new (FALSE, 8); + gtk_widget_show (clist_hbox2); + gtk_box_pack_start (GTK_BOX (clist_hbox), clist_hbox2, TRUE, TRUE, 0); + + clist_scrolledwin = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (clist_scrolledwin, 200, 210); + gtk_widget_show (clist_scrolledwin); + gtk_box_pack_start (GTK_BOX (clist_hbox2), clist_scrolledwin, + TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (clist_scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Hidden headers"); + hidden_headers_clist = gtk_clist_new_with_titles(1, title); + gtk_widget_show (hidden_headers_clist); + gtk_container_add (GTK_CONTAINER (clist_scrolledwin), + hidden_headers_clist); + gtk_clist_set_selection_mode (GTK_CLIST (hidden_headers_clist), + GTK_SELECTION_BROWSE); + gtk_clist_set_auto_sort(GTK_CLIST (hidden_headers_clist), TRUE); + GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (hidden_headers_clist)-> + column[0].button, GTK_CAN_FOCUS); + + btn_vbox = gtk_vbox_new (FALSE, 8); + gtk_widget_show (btn_vbox); + gtk_box_pack_start (GTK_BOX (clist_hbox2), btn_vbox, FALSE, FALSE, 0); + + reg_btn = gtk_button_new_with_label (_("Add")); + gtk_widget_show (reg_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), reg_btn, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (reg_btn), "clicked", + G_CALLBACK (prefs_display_header_register_cb), + GINT_TO_POINTER (TRUE)); + del_btn = gtk_button_new_with_label (_("Delete")); + gtk_widget_show (del_btn); + gtk_box_pack_start (GTK_BOX (btn_vbox), del_btn, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (del_btn), "clicked", + G_CALLBACK (prefs_display_header_delete_cb), + hidden_headers_clist); + + PACK_CHECK_BUTTON (btn_hbox, checkbtn_other_headers, + _("Show all unspecified headers")); + SET_TOGGLE_SENSITIVITY (checkbtn_other_headers, clist_hbox2); + + gtk_widget_show_all(window); + + dispheader.window = window; + dispheader.ok_btn = ok_btn; + dispheader.cancel_btn = cancel_btn; + + dispheader.hdr_combo = hdr_combo; + dispheader.hdr_entry = GTK_COMBO (hdr_combo)->entry; + + dispheader.headers_clist = headers_clist; + dispheader.hidden_headers_clist = hidden_headers_clist; + + dispheader.other_headers = checkbtn_other_headers; +} + +void prefs_display_header_read_config(void) +{ + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + DisplayHeaderProp *dp; + + debug_print(_("Reading configuration for displaying headers...\n")); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + DISPLAY_HEADER_RC, NULL); + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + prefs_common.disphdr_list = NULL; + prefs_display_header_set_default(); + return; + } + g_free(rcpath); + + /* remove all previous headers list */ + while (prefs_common.disphdr_list != NULL) { + dp = (DisplayHeaderProp *)prefs_common.disphdr_list->data; + display_header_prop_free(dp); + prefs_common.disphdr_list = + g_slist_remove(prefs_common.disphdr_list, dp); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + g_strchomp(buf); + dp = display_header_prop_read_str(buf); + if (dp) + prefs_common.disphdr_list = + g_slist_append(prefs_common.disphdr_list, dp); + } + + fclose(fp); +} + +void prefs_display_header_write_config(void) +{ + gchar *rcpath; + PrefFile *pfile; + GSList *cur; + + debug_print(_("Writing configuration for displaying headers...\n")); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + DISPLAY_HEADER_RC, NULL); + + if ((pfile = prefs_file_open(rcpath)) == NULL) { + g_warning(_("failed to write configuration to file\n")); + g_free(rcpath); + return; + } + + for (cur = prefs_common.disphdr_list; cur != NULL; + cur = cur->next) { + DisplayHeaderProp *dp = (DisplayHeaderProp *)cur->data; + gchar *dpstr; + + dpstr = display_header_prop_get_str(dp); + if (fputs(dpstr, pfile->fp) == EOF || + fputc('\n', pfile->fp) == EOF) { + FILE_OP_ERROR(rcpath, "fputs || fputc"); + prefs_file_close_revert(pfile); + g_free(rcpath); + g_free(dpstr); + return; + } + g_free(dpstr); + } + + g_free(rcpath); + + if (prefs_file_close(pfile) < 0) { + g_warning(_("failed to write configuration to file\n")); + return; + } +} + +static void prefs_display_header_set_dialog(void) +{ + GtkCList *clist = GTK_CLIST(dispheader.headers_clist); + GtkCList *hidden_clist = GTK_CLIST(dispheader.hidden_headers_clist); + GSList *cur; + gchar *dp_str[1]; + gint row; + + gtk_clist_freeze(clist); + gtk_clist_freeze(hidden_clist); + + gtk_clist_clear(clist); + gtk_clist_clear(hidden_clist); + + for (cur = prefs_common.disphdr_list; cur != NULL; + cur = cur->next) { + DisplayHeaderProp *dp = (DisplayHeaderProp *)cur->data; + + dp_str[0] = dp->name; + + if (dp->hidden) { + row = gtk_clist_append(hidden_clist, dp_str); + gtk_clist_set_row_data(hidden_clist, row, dp); + } else { + row = gtk_clist_append(clist, dp_str); + gtk_clist_set_row_data(clist, row, dp); + } + } + + gtk_clist_thaw(hidden_clist); + gtk_clist_thaw(clist); + + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(dispheader.other_headers), + prefs_common.show_other_header); +} + +static void prefs_display_header_set_list() +{ + gint row = 0; + DisplayHeaderProp *dp; + + g_slist_free(prefs_common.disphdr_list); + prefs_common.disphdr_list = NULL; + + while ((dp = gtk_clist_get_row_data + (GTK_CLIST(dispheader.headers_clist), row)) != NULL) { + prefs_common.disphdr_list = + g_slist_append(prefs_common.disphdr_list, dp); + row++; + } + + row = 0; + while ((dp = gtk_clist_get_row_data + (GTK_CLIST(dispheader.hidden_headers_clist), row)) != NULL) { + prefs_common.disphdr_list = + g_slist_append(prefs_common.disphdr_list, dp); + row++; + } +} + +static gint prefs_display_header_find_header(GtkCList *clist, + const gchar *header) +{ + gint row = 0; + DisplayHeaderProp *dp; + + while ((dp = gtk_clist_get_row_data(clist, row)) != NULL) { + if (g_strcasecmp(dp->name, header) == 0) + return row; + row++; + } + + return -1; +} + +static gint prefs_display_header_clist_set_row(gboolean hidden) +{ + GtkCList *clist; + DisplayHeaderProp *dp; + const gchar *entry_text; + gchar *dp_str[1]; + gint row; + + entry_text = gtk_entry_get_text(GTK_ENTRY(dispheader.hdr_entry)); + if (entry_text[0] == '\0') { + alertpanel_error(_("Header name is not set.")); + return -1; + } + + if (hidden) + clist = GTK_CLIST(dispheader.hidden_headers_clist); + else + clist = GTK_CLIST(dispheader.headers_clist); + + if (prefs_display_header_find_header(clist, entry_text) != -1) { + alertpanel_error(_("This header is already in the list.")); + return -1; + } + + dp = g_new0(DisplayHeaderProp, 1); + + dp->name = g_strdup(entry_text); + dp->hidden = hidden; + + dp_str[0] = dp->name; + row = gtk_clist_append(clist, dp_str); + gtk_clist_set_row_data(clist, row, dp); + + prefs_display_header_set_list(); + + return row; +} + +static void prefs_display_header_register_cb(GtkButton *btn, + gpointer hidden_data) +{ + prefs_display_header_clist_set_row(GPOINTER_TO_INT(hidden_data)); +} + +static void prefs_display_header_delete_cb(GtkButton *btn, gpointer clist_data) +{ + GtkCList *clist = GTK_CLIST(clist_data); + DisplayHeaderProp *dp; + gint row; + + if (!clist->selection) return; + row = GPOINTER_TO_INT(clist->selection->data); + + dp = gtk_clist_get_row_data(clist, row); + display_header_prop_free(dp); + gtk_clist_remove(clist, row); + prefs_common.disphdr_list = + g_slist_remove(prefs_common.disphdr_list, dp); +} + +static void prefs_display_header_up(void) +{ + GtkCList *clist = GTK_CLIST(dispheader.headers_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0) + gtk_clist_row_move(clist, row, row - 1); +} + +static void prefs_display_header_down(void) +{ + GtkCList *clist = GTK_CLIST(dispheader.headers_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row >= 0 && row < clist->rows - 1) + gtk_clist_row_move(clist, row, row + 1); +} + +static void prefs_display_header_row_moved(GtkCList *clist, gint source_row, + gint dest_row, gpointer data) +{ + prefs_display_header_set_list(); +} + +static gboolean prefs_display_header_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_display_header_cancel(); + return FALSE; +} + +static void prefs_display_header_ok(void) +{ + prefs_common.show_other_header = + gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(dispheader.other_headers)); + prefs_display_header_write_config(); + gtk_widget_hide(dispheader.window); +} + +static void prefs_display_header_cancel(void) +{ + prefs_display_header_read_config(); + gtk_widget_hide(dispheader.window); +} + +static gint prefs_display_header_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_display_header_cancel(); + return TRUE; +} diff --git a/src/prefs_display_header.h b/src/prefs_display_header.h new file mode 100644 index 00000000..bf1f4f2d --- /dev/null +++ b/src/prefs_display_header.h @@ -0,0 +1,27 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_DISPLAY_HEADER_H__ +#define __PREFS_DISPLAY_HEADER_H__ + +void prefs_display_header_read_config (void); +void prefs_display_header_write_config (void); +void prefs_display_header_open (void); + +#endif /* __PREFS_DISPLAY_HEADER_H__ */ diff --git a/src/prefs_filter.c b/src/prefs_filter.c new file mode 100644 index 00000000..041aa4bf --- /dev/null +++ b/src/prefs_filter.c @@ -0,0 +1,841 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "prefs.h" +#include "prefs_filter.h" +#include "prefs_filter_edit.h" +#include "prefs_common.h" +#include "mainwindow.h" +#include "foldersel.h" +#include "manage_window.h" +#include "stock_pixmap.h" +#include "inc.h" +#include "procheader.h" +#include "menu.h" +#include "filter.h" +#include "utils.h" +#include "gtkutils.h" +#include "alertpanel.h" +#include "xml.h" + +static struct FilterRuleListWindow { + GtkWidget *window; + + GtkWidget *clist; + + GtkWidget *add_btn; + GtkWidget *edit_btn; + GtkWidget *copy_btn; + GtkWidget *del_btn; + + GSList *default_hdr_list; + GSList *user_hdr_list; + GSList *msg_hdr_list; + + GHashTable *msg_hdr_table; + + GtkWidget *close_btn; +} rule_list_window; + +static GdkPixmap *markxpm; +static GdkBitmap *markxpmmask; + +static void prefs_filter_create (void); + +//static void prefs_filter_read_old_config (void); + +static void prefs_filter_set_dialog (void); +static void prefs_filter_set_list_row (gint row, + FilterRule *rule, + gboolean move_view); + +static void prefs_filter_set_header_list (MsgInfo *msginfo); + +static void prefs_filter_write_user_header_list (void); + +static void prefs_filter_set_list (void); + +/* callback functions */ +static void prefs_filter_add_cb (void); +static void prefs_filter_edit_cb (void); +static void prefs_filter_copy_cb (void); +static void prefs_filter_delete_cb (void); +static void prefs_filter_top (void); +static void prefs_filter_up (void); +static void prefs_filter_down (void); +static void prefs_filter_bottom (void); + +static void prefs_filter_select (GtkCList *clist, + gint row, + gint column, + GdkEvent *event); +static void prefs_filter_row_move (GtkCList *clist, + gint source_row, + gint dest_row); + +static gint prefs_filter_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_filter_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_filter_close (void); + + +void prefs_filter_open(MsgInfo *msginfo, const gchar *header) +{ + inc_lock(); + + if (!rule_list_window.window) + prefs_filter_create(); + + prefs_filter_set_header_list(msginfo); + + manage_window_set_transient(GTK_WINDOW(rule_list_window.window)); + gtk_widget_grab_focus(rule_list_window.close_btn); + + prefs_filter_set_dialog(); + + gtk_widget_show(rule_list_window.window); + + if (msginfo) { + FilterRule *rule; + + rule = prefs_filter_edit_open(NULL, header); + + if (rule) { + prefs_filter_set_list_row(-1, rule, TRUE); + prefs_filter_set_list(); + } + } +} + +static void prefs_filter_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *close_btn; + GtkWidget *confirm_area; + + GtkWidget *hbox; + GtkWidget *scrolledwin; + GtkWidget *clist; + + GtkWidget *btn_vbox; + GtkWidget *spc_vbox; + GtkWidget *top_btn; + GtkWidget *up_btn; + GtkWidget *down_btn; + GtkWidget *bottom_btn; + + GtkWidget *btn_hbox; + GtkWidget *add_btn; + GtkWidget *edit_btn; + GtkWidget *copy_btn; + GtkWidget *del_btn; + + gchar *title[2]; + + debug_print("Creating filter setting window...\n"); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_widget_set_usize(window, 540, 360); + gtk_window_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + gtkut_button_set_create(&confirm_area, &close_btn, _("Close"), + NULL, NULL, NULL, NULL); + gtk_widget_show(confirm_area); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(close_btn); + + gtk_window_set_title(GTK_WINDOW(window), + _("Filter setting")); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_filter_deleted), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_filter_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT (window); + g_signal_connect(G_OBJECT(close_btn), "clicked", + G_CALLBACK(prefs_filter_close), NULL); + + /* Rule list */ + + hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(scrolledwin); + gtk_widget_set_usize(scrolledwin, -1, 150); + gtk_box_pack_start(GTK_BOX(hbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Enabled"); + title[1] = _("Name"); + clist = gtk_clist_new_with_titles(2, title); + gtk_widget_show(clist); + gtk_container_add (GTK_CONTAINER(scrolledwin), clist); + gtk_clist_set_column_width(GTK_CLIST(clist), 0, 64); + gtk_clist_set_column_justification(GTK_CLIST(clist), 0, + GTK_JUSTIFY_CENTER); + gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_BROWSE); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[0].button, + GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[1].button, + GTK_CAN_FOCUS); + g_signal_connect(G_OBJECT(clist), "select_row", + G_CALLBACK(prefs_filter_select), NULL); + g_signal_connect_after(G_OBJECT(clist), "row_move", + G_CALLBACK(prefs_filter_row_move), NULL); + + /* Up / Down */ + + btn_vbox = gtk_vbox_new (FALSE, 8); + gtk_widget_show(btn_vbox); + gtk_box_pack_start(GTK_BOX(hbox), btn_vbox, FALSE, FALSE, 0); + + top_btn = gtk_button_new_with_label(_("Top")); + gtk_widget_show(top_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), top_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(top_btn), "clicked", + G_CALLBACK(prefs_filter_top), NULL); + + PACK_VSPACER(btn_vbox, spc_vbox, VSPACING_NARROW_2); + + up_btn = gtk_button_new_with_label(_("Up")); + gtk_widget_show(up_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(up_btn), "clicked", + G_CALLBACK(prefs_filter_up), NULL); + + down_btn = gtk_button_new_with_label(_("Down")); + gtk_widget_show(down_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(down_btn), "clicked", + G_CALLBACK(prefs_filter_down), NULL); + + PACK_VSPACER(btn_vbox, spc_vbox, VSPACING_NARROW_2); + + bottom_btn = gtk_button_new_with_label(_("Bottom")); + gtk_widget_show(bottom_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), bottom_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(bottom_btn), "clicked", + G_CALLBACK(prefs_filter_bottom), NULL); + + /* add / edit / copy / delete */ + + hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + btn_hbox = gtk_hbox_new(TRUE, 4); + gtk_widget_show(btn_hbox); + gtk_box_pack_start(GTK_BOX(hbox), btn_hbox, FALSE, FALSE, 0); + + add_btn = gtk_button_new_with_label(_("Add")); + gtk_widget_show(add_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), add_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(add_btn), "clicked", + G_CALLBACK(prefs_filter_add_cb), NULL); + + edit_btn = gtk_button_new_with_label(_("Edit")); + gtk_widget_show(edit_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), edit_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(edit_btn), "clicked", + G_CALLBACK(prefs_filter_edit_cb), NULL); + + copy_btn = gtk_button_new_with_label(_("Copy")); + gtk_widget_show(copy_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), copy_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(copy_btn), "clicked", + G_CALLBACK(prefs_filter_copy_cb), NULL); + + del_btn = gtk_button_new_with_label(_(" Delete ")); + gtk_widget_show(del_btn); + gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(prefs_filter_delete_cb), NULL); + + gtk_widget_show_all(window); + + stock_pixmap_gdk(clist, STOCK_PIXMAP_MARK, &markxpm, &markxpmmask); + + rule_list_window.window = window; + rule_list_window.close_btn = close_btn; + + rule_list_window.clist = clist; + + rule_list_window.default_hdr_list = NULL; + rule_list_window.user_hdr_list = NULL; + rule_list_window.msg_hdr_list = NULL; + rule_list_window.msg_hdr_table = NULL; +} + +void prefs_filter_read_config(void) +{ + gchar *rcpath; + GNode *node; + FilterRule *rule; + + debug_print("Reading filter configuration...\n"); + + /* remove all previous filter list */ + while (prefs_common.fltlist != NULL) { + rule = (FilterRule *)prefs_common.fltlist->data; + filter_rule_free(rule); + prefs_common.fltlist = g_slist_remove(prefs_common.fltlist, + rule); + } + +#warning FIXME_GTK2 + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST, + NULL); + if (!is_file_exist(rcpath)) { + //prefs_filter_read_old_config(); + g_free(rcpath); + return; + } + + node = xml_parse_file(rcpath); + if (!node) { + g_warning("Can't parse %s\n", rcpath); + g_free(rcpath); + return; + } + g_free(rcpath); + + prefs_common.fltlist = filter_xml_node_to_filter_list(node); + + xml_free_tree(node); +} + +#if 0 +static void prefs_filter_read_old_config(void) +{ + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + FilterRule *rule; + + debug_print("Reading old filter configuration...\n"); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_RC, NULL); + if ((fp = fopen(rcpath, "rb")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + return; + } + g_free(rcpath); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + g_strchomp(buf); + rule = filter_read_str(buf); + if (rule) { + prefs_common.fltlist = + g_slist_append(prefs_common.fltlist, rule); + } + } + + fclose(fp); +} +#endif + +void prefs_filter_write_config(void) +{ + filter_write_config(prefs_common.fltlist); +} + +void prefs_filter_rename_path(const gchar *old_path, const gchar *new_path) +{ + GSList *cur; + + g_return_if_fail(old_path != NULL); + g_return_if_fail(new_path != NULL); + + for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + filter_rule_rename_dest_path(rule, old_path, new_path); + } + + filter_write_config(prefs_common.fltlist); +} + +void prefs_filter_delete_path(const gchar *path) +{ + GSList *cur; + GSList *next; + + g_return_if_fail(path != NULL); + + for (cur = prefs_common.fltlist; cur != NULL; cur = next) { + FilterRule *rule = (FilterRule *)cur->data; + next = cur->next; + + filter_rule_delete_action_by_dest_path(rule, path); + if (!rule->action_list) { + prefs_common.fltlist = + g_slist_remove(prefs_common.fltlist, rule); + filter_rule_free(rule); + } + } + + filter_write_config(prefs_common.fltlist); +} + +static void prefs_filter_set_dialog(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + GSList *cur; + + gtk_clist_freeze(clist); + gtk_clist_clear(clist); + + for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) { + FilterRule *rule = (FilterRule *)cur->data; + prefs_filter_set_list_row(-1, rule, FALSE); + } + + gtk_clist_thaw(clist); +} + +static void prefs_filter_set_list_row(gint row, FilterRule *rule, + gboolean move_view) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + gchar *cond_str[2] = {"", NULL}; + + if (!rule) + rule = gtk_clist_get_row_data(clist, row); + + g_return_if_fail(rule != NULL); + + if (rule->name && *rule->name) + cond_str[1] = g_strdup(rule->name); + else { + cond_str[1] = filter_get_str(rule); + } + + if (row < 0) + row = gtk_clist_append(clist, cond_str); + else { + FilterRule *prev_rule; + + prev_rule = gtk_clist_get_row_data(clist, row); + if (rule == prev_rule) + gtk_clist_set_text(clist, row, 1, cond_str[1]); + else if (prev_rule) { + gtk_clist_set_text(clist, row, 1, cond_str[1]); + filter_rule_free(prev_rule); + } else + row = gtk_clist_append(clist, cond_str); + } + + if (rule->enabled) + gtk_clist_set_pixmap(clist, row, 0, markxpm, markxpmmask); + else + gtk_clist_set_text(clist, row, 0, ""); + + gtk_clist_set_row_data(clist, row, rule); + g_free(cond_str[1]); + + if (move_view && + gtk_clist_row_is_visible(clist, row) != GTK_VISIBILITY_FULL) + gtk_clist_moveto(clist, row, -1, 0.5, 0.0); +} + +#define APPEND_HDR_LIST(hdr_list) \ + for (cur = hdr_list; cur != NULL; cur = cur->next) { \ + header = (Header *)cur->data; \ + \ + if (!g_hash_table_lookup(table, header->name)) { \ + g_hash_table_insert(table, header->name, header); \ + list = g_slist_append(list, header); \ + } \ + } + +GSList *prefs_filter_get_header_list(void) +{ + GSList *list = NULL; + GSList *cur; + GHashTable *table; + Header *header; + + table = g_hash_table_new(str_case_hash, str_case_equal); + + APPEND_HDR_LIST(rule_list_window.default_hdr_list) + APPEND_HDR_LIST(rule_list_window.user_hdr_list); + APPEND_HDR_LIST(rule_list_window.msg_hdr_list); + + g_hash_table_destroy(table); + + return list; +} + +#undef APPEND_HDR_LIST + +GSList *prefs_filter_get_user_header_list(void) +{ + return rule_list_window.user_hdr_list; +} + +gchar *prefs_filter_get_msg_header_field(const gchar *header_name) +{ + if (!rule_list_window.msg_hdr_table) + return NULL; + + return (gchar *)g_hash_table_lookup + (rule_list_window.msg_hdr_table, header_name); +} + +void prefs_filter_set_user_header_list(GSList *list) +{ + procheader_header_list_destroy(rule_list_window.user_hdr_list); + rule_list_window.user_hdr_list = list; +} + +void prefs_filter_set_msg_header_list(MsgInfo *msginfo) +{ + gchar *file; + GSList *cur; + GSList *next; + Header *header; + + if (rule_list_window.msg_hdr_table) { + g_hash_table_destroy(rule_list_window.msg_hdr_table); + rule_list_window.msg_hdr_table = NULL; + } + if (rule_list_window.msg_hdr_list) { + procheader_header_list_destroy(rule_list_window.msg_hdr_list); + rule_list_window.msg_hdr_list = NULL; + } + + if (!msginfo) + return; + + file = procmsg_get_message_file(msginfo); + g_return_if_fail(file != NULL); + + rule_list_window.msg_hdr_list = + procheader_get_header_list_from_file(file); + + g_free(file); + + rule_list_window.msg_hdr_table = + g_hash_table_new(str_case_hash, str_case_equal); + + for (cur = rule_list_window.msg_hdr_list; cur != NULL; + cur = next) { + next = cur->next; + header = (Header *)cur->data; + if (!g_strcasecmp(header->name, "Received") || + !g_strcasecmp(header->name, "Mime-Version") || + !g_strcasecmp(header->name, "X-UIDL")) { + procheader_header_free(header); + rule_list_window.msg_hdr_list = + g_slist_remove(rule_list_window.msg_hdr_list, + header); + continue; + } + if (!g_hash_table_lookup(rule_list_window.msg_hdr_table, + header->name)) { + g_hash_table_insert(rule_list_window.msg_hdr_table, + header->name, header->body); + } + } +} + +static void prefs_filter_set_header_list(MsgInfo *msginfo) +{ + GSList *list = NULL; + gchar *path; + FILE *fp; + + list = procheader_add_header_list(list, "From", NULL); + list = procheader_add_header_list(list, "To", NULL); + list = procheader_add_header_list(list, "Cc", NULL); + list = procheader_add_header_list(list, "Subject", NULL); + list = procheader_add_header_list(list, "Reply-To", NULL); + list = procheader_add_header_list(list, "List-Id", NULL); + list = procheader_add_header_list(list, "X-ML-Name", NULL); + + procheader_header_list_destroy(rule_list_window.default_hdr_list); + rule_list_window.default_hdr_list = list; + + list = NULL; + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_HEADER_RC, + NULL); + if ((fp = fopen(path, "rb")) != NULL) { + gchar buf[PREFSBUFSIZE]; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + g_strstrip(buf); + if (buf[0] == '\0') continue; + list = procheader_add_header_list(list, buf, NULL); + } + + fclose(fp); + } else + if (ENOENT != errno) FILE_OP_ERROR(path, "fopen"); + g_free(path); + + prefs_filter_set_user_header_list(list); + + prefs_filter_set_msg_header_list(msginfo); +} + +static void prefs_filter_write_user_header_list(void) +{ + gchar *path; + PrefFile *pfile; + GSList *cur; + + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_HEADER_RC, + NULL); + + if ((pfile = prefs_file_open(path)) == NULL) { + g_warning("failed to write filter user header list\n"); + g_free(path); + return; + } + g_free(path); + + for (cur = rule_list_window.user_hdr_list; cur != NULL; + cur = cur->next) { + Header *header = (Header *)cur->data; + fputs(header->name, pfile->fp); + fputc('\n', pfile->fp); + } + + if (prefs_file_close(pfile) < 0) + g_warning("failed to write filter user header list\n"); +} + +static void prefs_filter_set_list(void) +{ + gint row = 0; + FilterRule *rule; + + g_slist_free(prefs_common.fltlist); + prefs_common.fltlist = NULL; + + while ((rule = gtk_clist_get_row_data + (GTK_CLIST(rule_list_window.clist), row)) != NULL) { + prefs_common.fltlist = g_slist_append(prefs_common.fltlist, + rule); + row++; + } +} + +static void prefs_filter_add_cb(void) +{ + FilterRule *rule; + + rule = prefs_filter_edit_open(NULL, NULL); + + if (rule) { + prefs_filter_set_list_row(-1, rule, TRUE); + prefs_filter_set_list(); + } +} + +static void prefs_filter_edit_cb(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + FilterRule *rule, *new_rule; + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + + rule = gtk_clist_get_row_data(clist, row); + g_return_if_fail(rule != NULL); + + new_rule = prefs_filter_edit_open(rule, NULL); + + if (new_rule) { + prefs_filter_set_list_row(row, new_rule, TRUE); + prefs_filter_set_list(); + } +} + +static void prefs_filter_copy_cb(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + FilterRule *rule, *new_rule; + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + + rule = gtk_clist_get_row_data(clist, row); + g_return_if_fail(rule != NULL); + + new_rule = prefs_filter_edit_open(rule, NULL); + + if (new_rule) { + prefs_filter_set_list_row(-1, new_rule, TRUE); + prefs_filter_set_list(); + } +} + +static void prefs_filter_delete_cb(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + FilterRule *rule; + gint row; + + if (!clist->selection) return; + row = GPOINTER_TO_INT(clist->selection->data); + + if (alertpanel(_("Delete rule"), + _("Do you really want to delete this rule?"), + _("Yes"), _("No"), NULL) != G_ALERTDEFAULT) + return; + + rule = gtk_clist_get_row_data(clist, row); + filter_rule_free(rule); + gtk_clist_remove(clist, row); + prefs_common.fltlist = g_slist_remove(prefs_common.fltlist, rule); + if (!clist->selection) + gtk_clist_select_row(clist, row - 1, -1); +} + +static void prefs_filter_top(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0) + gtk_clist_row_move(clist, row, 0); +} + +static void prefs_filter_up(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0) + gtk_clist_row_move(clist, row, row - 1); +} + +static void prefs_filter_down(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row < clist->rows - 1) + gtk_clist_row_move(clist, row, row + 1); +} + +static void prefs_filter_bottom(void) +{ + GtkCList *clist = GTK_CLIST(rule_list_window.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row < clist->rows - 1) + gtk_clist_row_move(clist, row, clist->rows - 1); +} + +static void prefs_filter_select(GtkCList *clist, gint row, gint column, + GdkEvent *event) +{ + if (event && event->type == GDK_2BUTTON_PRESS) { + prefs_filter_edit_cb(); + return; + } + + if (column == 0) { + FilterRule *rule; + rule = gtk_clist_get_row_data(clist, row); + rule->enabled ^= TRUE; + prefs_filter_set_list_row(row, rule, FALSE); + } +} + +static void prefs_filter_row_move(GtkCList *clist, gint source_row, + gint dest_row) +{ + prefs_filter_set_list(); + if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) + gtk_clist_moveto(clist, dest_row, -1, 0.5, 0.0); +} + +static gint prefs_filter_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_filter_close(); + return TRUE; +} + +static gboolean prefs_filter_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_filter_close(); + return FALSE; +} + +static void prefs_filter_close(void) +{ + prefs_filter_set_msg_header_list(NULL); + prefs_filter_write_user_header_list(); + filter_write_config(prefs_common.fltlist); + gtk_widget_hide(rule_list_window.window); + gtk_clist_clear(GTK_CLIST(rule_list_window.clist)); + inc_unlock(); +} diff --git a/src/prefs_filter.h b/src/prefs_filter.h new file mode 100644 index 00000000..619676e9 --- /dev/null +++ b/src/prefs_filter.h @@ -0,0 +1,58 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_FILTER_H__ +#define __PREFS_FILTER_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "procmsg.h" + +typedef enum +{ + FILTER_BY_NONE, + FILTER_BY_AUTO, + FILTER_BY_FROM, + FILTER_BY_TO, + FILTER_BY_SUBJECT +} PrefsFilterType; + +void prefs_filter_read_config (void); +void prefs_filter_write_config (void); + +void prefs_filter_open (MsgInfo *msginfo, + const gchar *header); + +GSList *prefs_filter_get_header_list (void); +GSList *prefs_filter_get_user_header_list (void); + +gchar *prefs_filter_get_msg_header_field (const gchar *header_name); + +void prefs_filter_set_user_header_list (GSList *list); +void prefs_filter_set_msg_header_list (MsgInfo *msginfo); + +void prefs_filter_rename_path (const gchar *old_path, + const gchar *new_path); +void prefs_filter_delete_path (const gchar *path); + +#endif /* __PREFS_FILTER_H__ */ diff --git a/src/prefs_filter_edit.c b/src/prefs_filter_edit.c new file mode 100644 index 00000000..d1dc7d90 --- /dev/null +++ b/src/prefs_filter_edit.c @@ -0,0 +1,2035 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "prefs.h" +#include "prefs_filter.h" +#include "prefs_filter_edit.h" +#include "prefs_common.h" +#include "mainwindow.h" +#include "foldersel.h" +#include "colorlabel.h" +#include "manage_window.h" +#include "procheader.h" +#include "menu.h" +#include "filter.h" +#include "utils.h" +#include "gtkutils.h" +#include "stock_pixmap.h" +#include "alertpanel.h" +#include "folder.h" + +typedef enum +{ + PF_COND_HEADER, + PF_COND_TO_OR_CC, + PF_COND_ANY_HEADER, + PF_COND_BODY, + PF_COND_CMD_TEST, + PF_COND_SIZE, + PF_COND_AGE, + PF_COND_ACCOUNT, + PF_COND_EDIT_HEADER, + PF_COND_SEPARATOR, + PF_COND_NONE +} CondMenuType; + +typedef enum +{ + PF_MATCH_CONTAIN, + PF_MATCH_NOT_CONTAIN, + PF_MATCH_EQUAL, + PF_MATCH_NOT_EQUAL, + PF_MATCH_REGEX, + PF_MATCH_NOT_REGEX, + PF_MATCH_NONE +} MatchMenuType; + +typedef enum +{ + PF_SIZE_LARGER, + PF_SIZE_SMALLER +} SizeMatchType; + +typedef enum +{ + PF_AGE_LONGER, + PF_AGE_SHORTER +} AgeMatchType; + +typedef enum +{ + PF_ACTION_MOVE, + PF_ACTION_COPY, + PF_ACTION_NOT_RECEIVE, + PF_ACTION_DELETE, + PF_ACTION_EXEC, + PF_ACTION_EXEC_ASYNC, + PF_ACTION_MARK, + PF_ACTION_COLOR_LABEL, + PF_ACTION_MARK_READ, + PF_ACTION_FORWARD, + PF_ACTION_FORWARD_AS_ATTACHMENT, + PF_ACTION_REDIRECT, + PF_ACTION_STOP_EVAL, + PF_ACTION_SEPARATOR, + PF_ACTION_NONE +} ActionMenuType; + +static struct FilterRuleEditWindow { + GtkWidget *window; + + GtkWidget *name_entry; + GtkWidget *bool_op_optmenu; + + GtkWidget *cond_scrolled_win; + GtkWidget *cond_vbox; + GSList *cond_hbox_list; + + GtkWidget *action_scrolled_win; + GtkWidget *action_vbox; + GSList *action_hbox_list; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + GSList *hdr_list; + GSList *rule_hdr_list; + + FilterRule *new_rule; + gboolean edit_finished; +} rule_edit_window; + +static struct FilterEditHeaderListDialog { + GtkWidget *window; + GtkWidget *clist; + GtkWidget *entry; + + gboolean finished; + gboolean ok; +} edit_header_list_dialog; + +typedef struct _CondHBox { + GtkWidget *hbox; + + GtkWidget *cond_type_optmenu; + GtkWidget *match_type_optmenu; + GtkWidget *size_match_optmenu; + GtkWidget *age_match_optmenu; + GtkWidget *key_entry; + GtkWidget *spin_btn; + GtkWidget *label; + + GtkWidget *del_btn; + GtkWidget *add_btn; + + CondMenuType cur_type; + gchar *cur_header_name; +} CondHBox; + +typedef struct _ActionHBox { + GtkWidget *hbox; + + GtkWidget *action_type_optmenu; + GtkWidget *label; + GtkWidget *folder_entry; + GtkWidget *cmd_entry; + GtkWidget *address_entry; + GtkWidget *clabel_optmenu; + + GtkWidget *folder_sel_btn; + + GtkWidget *action_type_menu_items[PF_ACTION_NONE]; + + GtkWidget *del_btn; + GtkWidget *add_btn; +} ActionHBox; + +static void prefs_filter_edit_create (void); +static void prefs_filter_edit_clear (void); +static void prefs_filter_edit_rule_to_dialog (FilterRule *rule); +static void prefs_filter_edit_set_header_list (FilterRule *rule); +static void prefs_filter_edit_update_header_list(void); + +static CondHBox *prefs_filter_edit_cond_hbox_create (void); +static ActionHBox *prefs_filter_edit_action_hbox_create (void); +static void prefs_filter_edit_cond_hbox_set (CondHBox *hbox, + FilterCond *cond); +static void prefs_filter_edit_action_hbox_set (ActionHBox *hbox, + FilterAction *action); + +static void prefs_filter_edit_cond_hbox_select (CondHBox *hbox, + CondMenuType type, + const gchar *header_name); + +static void prefs_filter_edit_set_cond_hbox_widgets (CondHBox *hbox, + CondMenuType type); +static void prefs_filter_edit_set_action_hbox_widgets (ActionHBox *hbox, + ActionMenuType type); + +static void prefs_filter_edit_set_action_hbox_menu_sensitive + (ActionHBox *hbox, + ActionMenuType type, + gboolean sensitive); +static void prefs_filter_edit_set_action_hbox_menus_sensitive + (void); + +static void prefs_filter_edit_get_action_hbox_menus_selection + (gboolean *selection); +static ActionMenuType prefs_filter_edit_get_action_hbox_type + (ActionHBox *hbox); + +static void prefs_filter_edit_insert_cond_hbox (CondHBox *hbox, + gint pos); +static void prefs_filter_edit_insert_action_hbox(ActionHBox *hbox, + gint pos); +static void prefs_filter_edit_remove_cond_hbox (CondHBox *hbox); +static void prefs_filter_edit_remove_action_hbox(ActionHBox *hbox); + +static void prefs_filter_edit_add_rule_cond (FilterRule *rule); +static void prefs_filter_edit_add_rule_action (FilterRule *rule); + +static void prefs_filter_edit_set_cond_header_menu + (CondHBox *hbox); + +static void prefs_filter_edit_activate_cond_header + (const gchar *header); + +static void prefs_filter_edit_edit_header_list (void); + +static FilterRule *prefs_filter_edit_dialog_to_rule (void); + +/* callback functions */ +static gint prefs_filter_edit_deleted (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_filter_edit_key_pressed (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_filter_edit_ok (void); +static void prefs_filter_edit_cancel (void); + +static void prefs_filter_cond_activated_cb (GtkWidget *widget, + gpointer data); +static void prefs_filter_action_activated_cb (GtkWidget *widget, + gpointer data); + +static void prefs_filter_action_select_dest_cb (GtkWidget *widget, + gpointer data); + +static void prefs_filter_cond_del_cb (GtkWidget *widget, + gpointer data); +static void prefs_filter_cond_add_cb (GtkWidget *widget, + gpointer data); +static void prefs_filter_action_del_cb (GtkWidget *widget, + gpointer data); +static void prefs_filter_action_add_cb (GtkWidget *widget, + gpointer data); + + +FilterRule *prefs_filter_edit_open(FilterRule *rule, const gchar *header) +{ + FilterRule *new_rule; + + if (!rule_edit_window.window) + prefs_filter_edit_create(); + + manage_window_set_transient(GTK_WINDOW(rule_edit_window.window)); + + prefs_filter_edit_set_header_list(rule); + prefs_filter_edit_rule_to_dialog(rule); + if (header) + prefs_filter_edit_activate_cond_header(header); + gtk_widget_show(rule_edit_window.window); + + rule_edit_window.new_rule = NULL; + rule_edit_window.edit_finished = FALSE; + while (rule_edit_window.edit_finished == FALSE) + gtk_main_iteration(); + + prefs_filter_edit_clear(); + prefs_filter_set_msg_header_list(NULL); + gtk_widget_hide(rule_edit_window.window); + + new_rule = rule_edit_window.new_rule; + rule_edit_window.new_rule = NULL; + + if (new_rule) + debug_print("new rule created: %s\n", new_rule->name); + + return new_rule; +} + +static void prefs_filter_edit_create(void) +{ + GtkWidget *window; + + GtkWidget *vbox; + + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *name_entry; + + GtkWidget *bool_op_optmenu; + GtkWidget *menu; + GtkWidget *menuitem; + + GtkWidget *cond_scrolled_win; + GtkWidget *cond_vbox; + + GtkWidget *action_scrolled_win; + GtkWidget *action_vbox; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + GtkWidget *confirm_area; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_widget_set_size_request(window, 632, 405); + gtk_window_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + gtk_widget_realize(window); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_widget_show(confirm_area); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + gtk_window_set_title(GTK_WINDOW(window), + _("Filter rule")); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_filter_edit_deleted), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_filter_edit_key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT (window); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(prefs_filter_edit_ok), NULL); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(prefs_filter_edit_cancel), NULL); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new(_("Name:")); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + name_entry = gtk_entry_new(); + gtk_widget_show(name_entry); + gtk_box_pack_start(GTK_BOX(hbox), name_entry, TRUE, TRUE, 0); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + bool_op_optmenu = gtk_option_menu_new(); + gtk_widget_show(bool_op_optmenu); + gtk_box_pack_start(GTK_BOX(hbox), bool_op_optmenu, FALSE, FALSE, 0); + + menu = gtk_menu_new(); + MENUITEM_ADD(menu, menuitem, + _("If any of the following condition matches"), FLT_OR); + MENUITEM_ADD(menu, menuitem, + _("If all of the following conditions match"), FLT_AND); + gtk_option_menu_set_menu(GTK_OPTION_MENU(bool_op_optmenu), menu); + + cond_scrolled_win = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(cond_scrolled_win); + gtk_widget_set_size_request(cond_scrolled_win, -1, 125); + gtk_box_pack_start(GTK_BOX(vbox), cond_scrolled_win, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cond_scrolled_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + cond_vbox = gtk_vbox_new(FALSE, 2); + gtk_widget_show(cond_vbox); + gtk_container_set_border_width(GTK_CONTAINER(cond_vbox), 2); + gtk_scrolled_window_add_with_viewport + (GTK_SCROLLED_WINDOW(cond_scrolled_win), cond_vbox); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new(_("Perform the following actions:")); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + action_scrolled_win = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(action_scrolled_win); + gtk_box_pack_start(GTK_BOX(vbox), action_scrolled_win, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(action_scrolled_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + action_vbox = gtk_vbox_new(FALSE, 2); + gtk_widget_show(action_vbox); + gtk_container_set_border_width(GTK_CONTAINER(action_vbox), 2); + gtk_scrolled_window_add_with_viewport + (GTK_SCROLLED_WINDOW(action_scrolled_win), action_vbox); + + rule_edit_window.window = window; + rule_edit_window.name_entry = name_entry; + + rule_edit_window.bool_op_optmenu = bool_op_optmenu; + rule_edit_window.cond_scrolled_win = cond_scrolled_win; + rule_edit_window.cond_vbox = cond_vbox; + rule_edit_window.action_scrolled_win = action_scrolled_win; + rule_edit_window.action_vbox = action_vbox; + + rule_edit_window.ok_btn = ok_btn; + rule_edit_window.cancel_btn = cancel_btn; +} + +static void prefs_filter_edit_clear(void) +{ + while (rule_edit_window.cond_hbox_list) { + CondHBox *hbox = + (CondHBox *)rule_edit_window.cond_hbox_list->data; + prefs_filter_edit_remove_cond_hbox(hbox); + } + while (rule_edit_window.action_hbox_list) { + ActionHBox *hbox = + (ActionHBox *)rule_edit_window.action_hbox_list->data; + prefs_filter_edit_remove_action_hbox(hbox); + } + + g_slist_free(rule_edit_window.hdr_list); + rule_edit_window.hdr_list = NULL; + procheader_header_list_destroy(rule_edit_window.rule_hdr_list); + rule_edit_window.rule_hdr_list = NULL; +} + +static void prefs_filter_edit_rule_to_dialog(FilterRule *rule) +{ + gint index = 0; + static gint count = 1; + + if (rule && rule->name) + gtk_entry_set_text(GTK_ENTRY(rule_edit_window.name_entry), + rule->name); + else { + gchar rule_name[32]; + g_snprintf(rule_name, sizeof(rule_name), "Rule %d", count++); + gtk_entry_set_text(GTK_ENTRY(rule_edit_window.name_entry), + rule_name); + } + + if (rule) { + index = menu_find_option_menu_index + (GTK_OPTION_MENU(rule_edit_window.bool_op_optmenu), + GINT_TO_POINTER(rule->bool_op), NULL); + if (index < 0) + index = 0; + } + if (index >= 0) { + gtk_option_menu_set_history + (GTK_OPTION_MENU(rule_edit_window.bool_op_optmenu), + index); + } + + prefs_filter_edit_add_rule_cond(rule); + prefs_filter_edit_add_rule_action(rule); +} + +static void prefs_filter_edit_set_header_list(FilterRule *rule) +{ + GSList *list; + GSList *rule_hdr_list = NULL; + GSList *cur; + FilterCond *cond; + + g_slist_free(rule_edit_window.hdr_list); + rule_edit_window.hdr_list = NULL; + procheader_header_list_destroy(rule_edit_window.rule_hdr_list); + rule_edit_window.rule_hdr_list = NULL; + + list = prefs_filter_get_header_list(); + rule_edit_window.hdr_list = list; + + if (!rule) + return; + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + + if (cond->type == FLT_COND_HEADER && + procheader_find_header_list + (rule_hdr_list, cond->header_name) < 0) + rule_hdr_list = procheader_add_header_list + (rule_hdr_list, cond->header_name, NULL); + } + + rule_edit_window.rule_hdr_list = rule_hdr_list; + + rule_edit_window.hdr_list = + procheader_merge_header_list(list, rule_hdr_list); +} + +static void prefs_filter_edit_update_header_list(void) +{ + GSList *list; + + g_slist_free(rule_edit_window.hdr_list); + rule_edit_window.hdr_list = NULL; + + list = prefs_filter_get_header_list(); + rule_edit_window.hdr_list = list; + + rule_edit_window.hdr_list = + procheader_merge_header_list(list, + rule_edit_window.rule_hdr_list); +} + +static CondHBox *prefs_filter_edit_cond_hbox_create(void) +{ + CondHBox *cond_hbox; + GtkWidget *hbox; + GtkWidget *cond_type_optmenu; + GtkWidget *match_type_optmenu; + GtkWidget *size_match_optmenu; + GtkWidget *age_match_optmenu; + GtkWidget *menu; + GtkWidget *menuitem; + GtkWidget *key_entry; + GtkObject *spin_btn_adj; + GtkWidget *spin_btn; + GtkWidget *label; + GtkWidget *del_btn; + GtkWidget *add_btn; + GtkWidget *del_pixmap; + GtkWidget *add_pixmap; + + cond_hbox = g_new0(CondHBox, 1); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox); + + cond_type_optmenu = gtk_option_menu_new(); + gtk_widget_show(cond_type_optmenu); + gtk_box_pack_start(GTK_BOX(hbox), cond_type_optmenu, FALSE, FALSE, 0); + +#define COND_MENUITEM_ADD(str, action) \ +{ \ + MENUITEM_ADD(menu, menuitem, str, action); \ + g_signal_connect(G_OBJECT(menuitem), "activate", \ + G_CALLBACK(prefs_filter_cond_activated_cb), \ + cond_hbox); \ +} + + menu = gtk_menu_new(); + MENUITEM_ADD(menu, menuitem, NULL, PF_COND_SEPARATOR); + COND_MENUITEM_ADD(_("To or Cc"), PF_COND_TO_OR_CC); + COND_MENUITEM_ADD(_("Any header"), PF_COND_ANY_HEADER); + COND_MENUITEM_ADD(_("Edit header..."), PF_COND_EDIT_HEADER); + + MENUITEM_ADD(menu, menuitem, NULL, PF_COND_SEPARATOR); + COND_MENUITEM_ADD(_("Message body"), PF_COND_BODY); + COND_MENUITEM_ADD(_("Result of command"), PF_COND_CMD_TEST); + COND_MENUITEM_ADD(_("Size"), PF_COND_SIZE); + COND_MENUITEM_ADD(_("Age"), PF_COND_AGE); + /* COND_MENUITEM_ADD(_("Account"), PF_COND_ACCOUNT); */ + + gtk_option_menu_set_menu(GTK_OPTION_MENU(cond_type_optmenu), menu); + +#undef COND_MENUITEM_ADD + + match_type_optmenu = gtk_option_menu_new(); + gtk_widget_show(match_type_optmenu); + gtk_box_pack_start(GTK_BOX(hbox), match_type_optmenu, FALSE, FALSE, 0); + + menu = gtk_menu_new(); + MENUITEM_ADD(menu, menuitem, _("contains"), + PF_MATCH_CONTAIN); + MENUITEM_ADD(menu, menuitem, _("doesn't contain"), + PF_MATCH_NOT_CONTAIN); + MENUITEM_ADD(menu, menuitem, _("is"), + PF_MATCH_EQUAL); + MENUITEM_ADD(menu, menuitem, _("is not"), + PF_MATCH_NOT_EQUAL); + MENUITEM_ADD(menu, menuitem, _("match to regex"), + PF_MATCH_REGEX); + MENUITEM_ADD(menu, menuitem, _("doesn't match to regex"), + PF_MATCH_NOT_REGEX); + gtk_option_menu_set_menu(GTK_OPTION_MENU(match_type_optmenu), menu); + + size_match_optmenu = gtk_option_menu_new(); + gtk_box_pack_start(GTK_BOX(hbox), size_match_optmenu, FALSE, FALSE, 0); + + menu = gtk_menu_new(); + MENUITEM_ADD(menu, menuitem, _("is larger than"), PF_SIZE_LARGER); + MENUITEM_ADD(menu, menuitem, _("is smaller than"), PF_SIZE_SMALLER); + gtk_option_menu_set_menu(GTK_OPTION_MENU(size_match_optmenu), menu); + + age_match_optmenu = gtk_option_menu_new(); + gtk_box_pack_start(GTK_BOX(hbox), age_match_optmenu, FALSE, FALSE, 0); + + menu = gtk_menu_new(); + MENUITEM_ADD(menu, menuitem, _("is longer than"), PF_AGE_LONGER); + MENUITEM_ADD(menu, menuitem, _("is shorter than"), PF_AGE_SHORTER); + gtk_option_menu_set_menu(GTK_OPTION_MENU(age_match_optmenu), menu); + + key_entry = gtk_entry_new(); + gtk_widget_show(key_entry); + gtk_box_pack_start(GTK_BOX(hbox), key_entry, TRUE, TRUE, 0); + + spin_btn_adj = gtk_adjustment_new(0, 0, 99999, 1, 10, 100); + spin_btn = gtk_spin_button_new(GTK_ADJUSTMENT(spin_btn_adj), 1, 0); + gtk_box_pack_start(GTK_BOX(hbox), spin_btn, FALSE, FALSE, 0); + gtk_widget_set_size_request(spin_btn, 64, -1); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(spin_btn), TRUE); + + label = gtk_label_new(_("KB")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + del_pixmap = stock_pixmap_widget(rule_edit_window.window, + STOCK_PIXMAP_REMOVE); + gtk_widget_show(del_pixmap); + + del_btn = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(del_btn), del_pixmap); + gtk_widget_show(del_btn); + gtk_box_pack_end(GTK_BOX(hbox), del_btn, FALSE, FALSE, 0); + + add_pixmap = stock_pixmap_widget(rule_edit_window.window, + STOCK_PIXMAP_ADD); + gtk_widget_show(add_pixmap); + + add_btn = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(add_btn), add_pixmap); + gtk_widget_show(add_btn); + gtk_box_pack_end(GTK_BOX(hbox), add_btn, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(prefs_filter_cond_del_cb), cond_hbox); + g_signal_connect(G_OBJECT(add_btn), "clicked", + G_CALLBACK(prefs_filter_cond_add_cb), cond_hbox); + + cond_hbox->hbox = hbox; + cond_hbox->cond_type_optmenu = cond_type_optmenu; + cond_hbox->match_type_optmenu = match_type_optmenu; + cond_hbox->size_match_optmenu = size_match_optmenu; + cond_hbox->age_match_optmenu = age_match_optmenu; + cond_hbox->key_entry = key_entry; + cond_hbox->spin_btn = spin_btn; + cond_hbox->label = label; + cond_hbox->del_btn = del_btn; + cond_hbox->add_btn = add_btn; + cond_hbox->cur_type = PF_COND_HEADER; + cond_hbox->cur_header_name = NULL; + + prefs_filter_edit_set_cond_header_menu(cond_hbox); + gtk_option_menu_set_history(GTK_OPTION_MENU(cond_type_optmenu), 0); + + return cond_hbox; +} + +static ActionHBox *prefs_filter_edit_action_hbox_create(void) +{ + ActionHBox *action_hbox; + GtkWidget *hbox; + GtkWidget *action_type_optmenu; + GtkWidget *menu; + GtkWidget *menuitem; + GtkWidget *label; + GtkWidget *folder_entry; + GtkWidget *cmd_entry; + GtkWidget *address_entry; + GtkWidget *folder_pixmap; + GtkWidget *folder_sel_btn; + GtkWidget *clabel_optmenu; + GtkWidget *del_btn; + GtkWidget *add_btn; + GtkWidget *del_pixmap; + GtkWidget *add_pixmap; + + action_hbox = g_new0(ActionHBox, 1); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox); + + action_type_optmenu = gtk_option_menu_new(); + gtk_widget_show(action_type_optmenu); + gtk_box_pack_start(GTK_BOX(hbox), action_type_optmenu, FALSE, FALSE, 0); + + memset(action_hbox->action_type_menu_items, 0, + sizeof(action_hbox->action_type_menu_items)); + +#define ACTION_MENUITEM_ADD(str, action) \ +{ \ + MENUITEM_ADD(menu, menuitem, str, action); \ + action_hbox->action_type_menu_items[action] = menuitem; \ + g_signal_connect(G_OBJECT(menuitem), "activate", \ + G_CALLBACK(prefs_filter_action_activated_cb), \ + action_hbox); \ +} + + menu = gtk_menu_new(); + ACTION_MENUITEM_ADD(_("Move to"), PF_ACTION_MOVE); + ACTION_MENUITEM_ADD(_("Copy to"), PF_ACTION_COPY); + ACTION_MENUITEM_ADD(_("Don't receive"), PF_ACTION_NOT_RECEIVE); + ACTION_MENUITEM_ADD(_("Delete from server"), PF_ACTION_DELETE); + + MENUITEM_ADD(menu, menuitem, NULL, PF_ACTION_SEPARATOR); + ACTION_MENUITEM_ADD(_("Set mark"), PF_ACTION_MARK); + ACTION_MENUITEM_ADD(_("Set color"), PF_ACTION_COLOR_LABEL); + ACTION_MENUITEM_ADD(_("Mark as read"), PF_ACTION_MARK_READ); + +#if 0 + MENUITEM_ADD(menu, menuitem, NULL, PF_ACTION_SEPARATOR); + ACTION_MENUITEM_ADD(_("Forward"), PF_ACTION_FORWARD); + ACTION_MENUITEM_ADD(_("Forward as attachment"), PF_ACTION_FORWARD_AS_ATTACHMENT); + ACTION_MENUITEM_ADD(_("Redirect"), PF_ACTION_REDIRECT); +#endif + + MENUITEM_ADD(menu, menuitem, NULL, PF_ACTION_SEPARATOR); + ACTION_MENUITEM_ADD(_("Execute command"), PF_ACTION_EXEC); + + MENUITEM_ADD(menu, menuitem, NULL, PF_ACTION_SEPARATOR); + ACTION_MENUITEM_ADD(_("Stop rule evaluation"), PF_ACTION_STOP_EVAL); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(action_type_optmenu), menu); + +#undef ACTION_MENUITEM_ADD + + label = gtk_label_new(_("folder:")); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + folder_entry = gtk_entry_new(); + gtk_widget_show(folder_entry); + gtk_box_pack_start(GTK_BOX(hbox), folder_entry, TRUE, TRUE, 0); + + folder_pixmap = stock_pixmap_widget(rule_edit_window.window, + STOCK_PIXMAP_DIR_OPEN); + gtk_widget_show(folder_pixmap); + + folder_sel_btn = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(folder_sel_btn), folder_pixmap); + gtk_widget_show(folder_sel_btn); + gtk_box_pack_start(GTK_BOX(hbox), folder_sel_btn, FALSE, FALSE, 0); + + cmd_entry = gtk_entry_new(); + gtk_widget_show(cmd_entry); + gtk_box_pack_start(GTK_BOX(hbox), cmd_entry, TRUE, TRUE, 0); + + address_entry = gtk_entry_new(); + gtk_widget_show(address_entry); + gtk_box_pack_start(GTK_BOX(hbox), address_entry, TRUE, TRUE, 0); + + clabel_optmenu = gtk_option_menu_new(); + gtk_widget_show(clabel_optmenu); + gtk_box_pack_start(GTK_BOX(hbox), clabel_optmenu, FALSE, FALSE, 0); + + menu = colorlabel_create_color_menu(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(clabel_optmenu), menu); + + del_pixmap = stock_pixmap_widget(rule_edit_window.window, + STOCK_PIXMAP_REMOVE); + gtk_widget_show(del_pixmap); + + del_btn = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(del_btn), del_pixmap); + gtk_widget_show(del_btn); + gtk_box_pack_end(GTK_BOX(hbox), del_btn, FALSE, FALSE, 0); + + add_pixmap = stock_pixmap_widget(rule_edit_window.window, + STOCK_PIXMAP_ADD); + gtk_widget_show(add_pixmap); + + add_btn = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(add_btn), add_pixmap); + gtk_widget_show(add_btn); + gtk_box_pack_end(GTK_BOX(hbox), add_btn, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(folder_sel_btn), "clicked", + G_CALLBACK(prefs_filter_action_select_dest_cb), + action_hbox); + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(prefs_filter_action_del_cb), action_hbox); + g_signal_connect(G_OBJECT(add_btn), "clicked", + G_CALLBACK(prefs_filter_action_add_cb), action_hbox); + + action_hbox->hbox = hbox; + action_hbox->action_type_optmenu = action_type_optmenu; + action_hbox->label = label; + action_hbox->folder_entry = folder_entry; + action_hbox->cmd_entry = cmd_entry; + action_hbox->address_entry = address_entry; + action_hbox->folder_sel_btn = folder_sel_btn; + action_hbox->clabel_optmenu = clabel_optmenu; + action_hbox->del_btn = del_btn; + action_hbox->add_btn = add_btn; + + return action_hbox; +} + +static void prefs_filter_edit_cond_hbox_set(CondHBox *hbox, FilterCond *cond) +{ + GtkOptionMenu *cond_type_optmenu = + GTK_OPTION_MENU(hbox->cond_type_optmenu); + GtkOptionMenu *match_type_optmenu = + GTK_OPTION_MENU(hbox->match_type_optmenu); + gint cond_index = -1; + gint match_index = -1; + CondMenuType cond_type = PF_COND_NONE; + MatchMenuType match_type = PF_MATCH_NONE; + SizeMatchType size_type = PF_SIZE_LARGER; + AgeMatchType age_type = PF_AGE_LONGER; + + switch (cond->type) { + case FLT_COND_HEADER: + cond_type = PF_COND_HEADER; break; + case FLT_COND_TO_OR_CC: + cond_type = PF_COND_TO_OR_CC; break; + case FLT_COND_ANY_HEADER: + cond_type = PF_COND_ANY_HEADER; break; + case FLT_COND_BODY: + cond_type = PF_COND_BODY; break; + case FLT_COND_CMD_TEST: + cond_type = PF_COND_CMD_TEST; break; + case FLT_COND_SIZE_GREATER: + cond_type = PF_COND_SIZE; + if (FLT_IS_NOT_MATCH(cond->match_flag)) + size_type = PF_SIZE_SMALLER; + else + size_type = PF_SIZE_LARGER; + break; + case FLT_COND_AGE_GREATER: + cond_type = PF_COND_AGE; + if (FLT_IS_NOT_MATCH(cond->match_flag)) + age_type = PF_AGE_SHORTER; + else + age_type = PF_AGE_LONGER; + break; + case FLT_COND_ACCOUNT: + cond_type = PF_COND_ACCOUNT; break; + default: + break; + } + + switch (cond->type) { + case FLT_COND_HEADER: + case FLT_COND_TO_OR_CC: + case FLT_COND_ANY_HEADER: + case FLT_COND_BODY: + switch (cond->match_type) { + case FLT_CONTAIN: + if (FLT_IS_NOT_MATCH(cond->match_flag)) + match_type = PF_MATCH_NOT_CONTAIN; + else + match_type = PF_MATCH_CONTAIN; + break; + case FLT_EQUAL: + if (FLT_IS_NOT_MATCH(cond->match_flag)) + match_type = PF_MATCH_NOT_EQUAL; + else + match_type = PF_MATCH_EQUAL; + break; + case FLT_REGEX: + if (FLT_IS_NOT_MATCH(cond->match_flag)) + match_type = PF_MATCH_NOT_REGEX; + else + match_type = PF_MATCH_REGEX; + break; + } + break; + default: + break; + } + + if (cond_type == PF_COND_HEADER) + cond_index = procheader_find_header_list + (rule_edit_window.hdr_list, cond->header_name); + else + cond_index = menu_find_option_menu_index + (cond_type_optmenu, GINT_TO_POINTER(cond_type), NULL); + if (cond_index >= 0) { + GtkWidget *menuitem; + + if (cond_type == PF_COND_SIZE || cond_type == PF_COND_AGE) { + gtk_spin_button_set_value + (GTK_SPIN_BUTTON(hbox->spin_btn), + (gfloat)cond->int_value); + } else { + gtk_entry_set_text(GTK_ENTRY(hbox->key_entry), + cond->str_value ? + cond->str_value : ""); + } + gtk_option_menu_set_history(cond_type_optmenu, cond_index); + menuitem = gtk_menu_get_active + (GTK_MENU(gtk_option_menu_get_menu(cond_type_optmenu))); + gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); + } + + match_index = menu_find_option_menu_index + (match_type_optmenu, GINT_TO_POINTER(match_type), NULL); + if (match_index >= 0) + gtk_option_menu_set_history(match_type_optmenu, match_index); + if (cond_type == PF_COND_SIZE) + gtk_option_menu_set_history + (GTK_OPTION_MENU(hbox->size_match_optmenu), size_type); + else if (cond_type == PF_COND_AGE) + gtk_option_menu_set_history + (GTK_OPTION_MENU(hbox->age_match_optmenu), age_type); +} + +static void prefs_filter_edit_action_hbox_set(ActionHBox *hbox, + FilterAction *action) +{ + GtkOptionMenu *type_optmenu = GTK_OPTION_MENU(hbox->action_type_optmenu); + GtkWidget *menu; + ActionMenuType type = PF_ACTION_NONE; + + menu = gtk_option_menu_get_menu(type_optmenu); + + switch (action->type) { + case FLT_ACTION_MOVE: + type = PF_ACTION_MOVE; break; + case FLT_ACTION_COPY: + type = PF_ACTION_COPY; break; + case FLT_ACTION_NOT_RECEIVE: + type = PF_ACTION_NOT_RECEIVE; break; + case FLT_ACTION_DELETE: + type = PF_ACTION_DELETE; break; + case FLT_ACTION_EXEC: + type = PF_ACTION_EXEC; break; + case FLT_ACTION_MARK: + type = PF_ACTION_MARK; break; + case FLT_ACTION_COLOR_LABEL: + type = PF_ACTION_COLOR_LABEL; break; + case FLT_ACTION_MARK_READ: + type = PF_ACTION_MARK_READ; break; + case FLT_ACTION_STOP_EVAL: + type = PF_ACTION_STOP_EVAL; break; + default: + break; + } + + switch (type) { + case PF_ACTION_MOVE: + case PF_ACTION_COPY: + gtk_entry_set_text(GTK_ENTRY(hbox->folder_entry), + action->str_value ? action->str_value : ""); + break; + case PF_ACTION_EXEC: + gtk_entry_set_text(GTK_ENTRY(hbox->cmd_entry), + action->str_value ? action->str_value : ""); + break; + case PF_ACTION_COLOR_LABEL: + gtk_option_menu_set_history + (GTK_OPTION_MENU(hbox->clabel_optmenu), + action->int_value - 1); + break; + default: + break; + } + + prefs_filter_edit_set_action_hbox_widgets(hbox, type); +} + +static void prefs_filter_edit_cond_hbox_select(CondHBox *hbox, + CondMenuType type, + const gchar *header_name) +{ + gint index; + GtkOptionMenu *cond_type_optmenu = + GTK_OPTION_MENU(hbox->cond_type_optmenu); + GtkWidget *menu; + + if (type == PF_COND_HEADER) { + if (header_name) + index = procheader_find_header_list + (rule_edit_window.hdr_list, header_name); + else + index = 0; + } else + index = menu_find_option_menu_index + (cond_type_optmenu, GINT_TO_POINTER(type), NULL); + + if (index < 0) + index = 0; + + menu = gtk_option_menu_get_menu(cond_type_optmenu); + gtk_option_menu_set_history(cond_type_optmenu, index); +} + +static void prefs_filter_edit_set_cond_hbox_widgets(CondHBox *hbox, + CondMenuType type) +{ + switch (type) { + case PF_COND_HEADER: + case PF_COND_TO_OR_CC: + case PF_COND_ANY_HEADER: + case PF_COND_BODY: + gtk_widget_show(hbox->match_type_optmenu); + gtk_widget_hide(hbox->size_match_optmenu); + gtk_widget_hide(hbox->age_match_optmenu); + gtk_widget_show(hbox->key_entry); + gtk_widget_hide(hbox->spin_btn); + gtk_widget_hide(hbox->label); + break; + case PF_COND_CMD_TEST: + gtk_widget_hide(hbox->match_type_optmenu); + gtk_widget_hide(hbox->size_match_optmenu); + gtk_widget_hide(hbox->age_match_optmenu); + gtk_widget_show(hbox->key_entry); + gtk_widget_hide(hbox->spin_btn); + gtk_widget_hide(hbox->label); + break; + case PF_COND_SIZE: + gtk_widget_hide(hbox->match_type_optmenu); + gtk_widget_show(hbox->size_match_optmenu); + gtk_widget_hide(hbox->age_match_optmenu); + gtk_widget_hide(hbox->key_entry); + gtk_widget_show(hbox->spin_btn); + gtk_widget_show(hbox->label); + break; + case PF_COND_AGE: + gtk_widget_hide(hbox->match_type_optmenu); + gtk_widget_hide(hbox->size_match_optmenu); + gtk_widget_show(hbox->age_match_optmenu); + gtk_widget_hide(hbox->key_entry); + gtk_widget_show(hbox->spin_btn); + gtk_widget_hide(hbox->label); + break; + case PF_COND_ACCOUNT: + gtk_widget_hide(hbox->match_type_optmenu); + gtk_widget_hide(hbox->size_match_optmenu); + gtk_widget_hide(hbox->age_match_optmenu); + gtk_widget_hide(hbox->key_entry); + /* gtk_widget_show(hbox->account_optmenu); */ + gtk_widget_hide(hbox->spin_btn); + gtk_widget_hide(hbox->label); + break; + default: + break; + } +} + +static void prefs_filter_edit_set_action_hbox_widgets(ActionHBox *hbox, + ActionMenuType type) +{ + GtkOptionMenu *type_optmenu = GTK_OPTION_MENU(hbox->action_type_optmenu); + gint index; + + switch (type) { + case PF_ACTION_MOVE: + case PF_ACTION_COPY: + gtk_widget_show(hbox->label); + gtk_label_set_text(GTK_LABEL(hbox->label), _("folder:")); + gtk_widget_show(hbox->folder_entry); + gtk_widget_show(hbox->folder_sel_btn); + gtk_widget_hide(hbox->cmd_entry); + gtk_widget_hide(hbox->address_entry); + gtk_widget_hide(hbox->clabel_optmenu); + break; + case PF_ACTION_NOT_RECEIVE: + case PF_ACTION_DELETE: + case PF_ACTION_MARK: + case PF_ACTION_MARK_READ: + case PF_ACTION_STOP_EVAL: + gtk_widget_hide(hbox->label); + gtk_widget_hide(hbox->folder_entry); + gtk_widget_hide(hbox->folder_sel_btn); + gtk_widget_hide(hbox->cmd_entry); + gtk_widget_hide(hbox->address_entry); + gtk_widget_hide(hbox->clabel_optmenu); + break; + case PF_ACTION_EXEC: + case PF_ACTION_EXEC_ASYNC: + gtk_widget_hide(hbox->label); + gtk_widget_hide(hbox->folder_entry); + gtk_widget_hide(hbox->folder_sel_btn); + gtk_widget_show(hbox->cmd_entry); + gtk_widget_hide(hbox->address_entry); + gtk_widget_hide(hbox->clabel_optmenu); + break; + case PF_ACTION_COLOR_LABEL: + gtk_widget_hide(hbox->label); + gtk_widget_hide(hbox->folder_entry); + gtk_widget_hide(hbox->folder_sel_btn); + gtk_widget_hide(hbox->cmd_entry); + gtk_widget_hide(hbox->address_entry); + gtk_widget_show(hbox->clabel_optmenu); + break; + case PF_ACTION_FORWARD: + case PF_ACTION_FORWARD_AS_ATTACHMENT: + case PF_ACTION_REDIRECT: + gtk_widget_show(hbox->label); + gtk_label_set_text(GTK_LABEL(hbox->label), _("address:")); + gtk_widget_hide(hbox->folder_entry); + gtk_widget_hide(hbox->folder_sel_btn); + gtk_widget_hide(hbox->cmd_entry); + gtk_widget_show(hbox->address_entry); + gtk_widget_hide(hbox->clabel_optmenu); + break; + default: + break; + } + + index = menu_find_option_menu_index(type_optmenu, GINT_TO_POINTER(type), + NULL); + gtk_option_menu_set_history(type_optmenu, index); + prefs_filter_edit_set_action_hbox_menus_sensitive(); +} + +static void prefs_filter_edit_set_action_hbox_menu_sensitive + (ActionHBox *hbox, ActionMenuType type, gboolean sensitive) +{ + GtkWidget *menuitem; + + menuitem = hbox->action_type_menu_items[type]; + if (menuitem) + gtk_widget_set_sensitive(menuitem, sensitive); +} + +static void prefs_filter_edit_set_action_hbox_menus_sensitive(void) +{ + GSList *cur; + ActionHBox *cur_hbox; + ActionMenuType menu_type; + ActionMenuType cur_type; + gboolean action_menu_selection[PF_ACTION_NONE]; + gboolean action_menu_sensitive[PF_ACTION_NONE]; + + prefs_filter_edit_get_action_hbox_menus_selection + (action_menu_selection); + + for (cur = rule_edit_window.action_hbox_list; cur != NULL; + cur = cur->next) { + cur_hbox = (ActionHBox *)cur->data; + menu_type = prefs_filter_edit_get_action_hbox_type(cur_hbox); + for (cur_type = PF_ACTION_MOVE; cur_type < PF_ACTION_NONE; + cur_type++) + action_menu_sensitive[cur_type] = TRUE; + + for (cur_type = PF_ACTION_MOVE; cur_type < PF_ACTION_NONE; + cur_type++) { + switch (cur_type) { + case PF_ACTION_MOVE: + case PF_ACTION_NOT_RECEIVE: + case PF_ACTION_DELETE: + if (action_menu_selection[cur_type] == TRUE && + menu_type != cur_type) { + action_menu_sensitive[PF_ACTION_MOVE] = FALSE; + action_menu_sensitive[PF_ACTION_NOT_RECEIVE] = FALSE; + action_menu_sensitive[PF_ACTION_DELETE] = FALSE; + } + break; + case PF_ACTION_MARK: + case PF_ACTION_COLOR_LABEL: + case PF_ACTION_MARK_READ: + case PF_ACTION_STOP_EVAL: + if (action_menu_selection[cur_type] == TRUE && + menu_type != cur_type) + action_menu_sensitive[cur_type] = FALSE; + break; + default: + break; + } + } + + for (cur_type = PF_ACTION_MOVE; cur_type < PF_ACTION_NONE; + cur_type++) { + prefs_filter_edit_set_action_hbox_menu_sensitive + (cur_hbox, cur_type, + action_menu_sensitive[cur_type]); + } + } +} + +static void prefs_filter_edit_get_action_hbox_menus_selection(gboolean *selection) +{ + GSList *cur; + ActionHBox *cur_hbox; + ActionMenuType menu_type; + ActionMenuType cur_type; + + for (cur_type = PF_ACTION_MOVE; cur_type < PF_ACTION_NONE; cur_type++) + selection[cur_type] = FALSE; + + for (cur = rule_edit_window.action_hbox_list; cur != NULL; + cur = cur->next) { + cur_hbox = (ActionHBox *)cur->data; + menu_type = prefs_filter_edit_get_action_hbox_type(cur_hbox); + if (menu_type >= PF_ACTION_MOVE && menu_type < PF_ACTION_NONE) + selection[menu_type] = TRUE; + } +} + +static ActionMenuType prefs_filter_edit_get_action_hbox_type(ActionHBox *hbox) +{ + GtkWidget *menuitem; + ActionMenuType type; + + g_return_val_if_fail(hbox != NULL, PF_ACTION_NONE); + + menuitem = gtk_menu_get_active + (GTK_MENU(gtk_option_menu_get_menu + (GTK_OPTION_MENU(hbox->action_type_optmenu)))); + type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), + MENU_VAL_ID)); + + return type; +} + +static void prefs_filter_edit_insert_cond_hbox(CondHBox *hbox, gint pos) +{ + g_return_if_fail(hbox != NULL); + + if (!rule_edit_window.cond_hbox_list) { + gtk_widget_set_sensitive(hbox->del_btn, FALSE); + } else if (rule_edit_window.cond_hbox_list && + !rule_edit_window.cond_hbox_list->next) { + CondHBox *top_hbox = + (CondHBox *)rule_edit_window.cond_hbox_list->data; + gtk_widget_set_sensitive(top_hbox->del_btn, TRUE); + } + + gtk_box_pack_start(GTK_BOX(rule_edit_window.cond_vbox), + hbox->hbox, FALSE, FALSE, 0); + if (pos >= 0) { + gtk_box_reorder_child(GTK_BOX(rule_edit_window.cond_vbox), + hbox->hbox, pos); + } + + rule_edit_window.cond_hbox_list = + g_slist_insert(rule_edit_window.cond_hbox_list, hbox, pos); +} + +static void prefs_filter_edit_insert_action_hbox(ActionHBox *hbox, gint pos) +{ + g_return_if_fail(hbox != NULL); + + if (!rule_edit_window.action_hbox_list) { + gtk_widget_set_sensitive(hbox->del_btn, FALSE); + } else if (rule_edit_window.action_hbox_list && + !rule_edit_window.action_hbox_list->next) { + ActionHBox *top_hbox = + (ActionHBox *)rule_edit_window.action_hbox_list->data; + gtk_widget_set_sensitive(top_hbox->del_btn, TRUE); + } + + gtk_box_pack_start(GTK_BOX(rule_edit_window.action_vbox), + hbox->hbox, FALSE, FALSE, 0); + if (pos >= 0) { + gtk_box_reorder_child(GTK_BOX(rule_edit_window.action_vbox), + hbox->hbox, pos); + } + + rule_edit_window.action_hbox_list = + g_slist_insert(rule_edit_window.action_hbox_list, hbox, pos); +} + +static void prefs_filter_edit_remove_cond_hbox(CondHBox *hbox) +{ + g_return_if_fail(hbox != NULL); + g_return_if_fail(rule_edit_window.cond_hbox_list != NULL); + + rule_edit_window.cond_hbox_list = + g_slist_remove(rule_edit_window.cond_hbox_list, hbox); + gtk_widget_destroy(hbox->hbox); + g_free(hbox); + + if (rule_edit_window.cond_hbox_list && + !rule_edit_window.cond_hbox_list->next) { + hbox = (CondHBox *)rule_edit_window.cond_hbox_list->data; + gtk_widget_set_sensitive(hbox->del_btn, FALSE); + } +} + +static void prefs_filter_edit_remove_action_hbox(ActionHBox *hbox) +{ + g_return_if_fail(hbox != NULL); + g_return_if_fail(rule_edit_window.action_hbox_list != NULL); + + rule_edit_window.action_hbox_list = + g_slist_remove(rule_edit_window.action_hbox_list, hbox); + gtk_widget_destroy(hbox->hbox); + g_free(hbox); + + prefs_filter_edit_set_action_hbox_menus_sensitive(); + + if (rule_edit_window.action_hbox_list && + !rule_edit_window.action_hbox_list->next) { + hbox = (ActionHBox *)rule_edit_window.action_hbox_list->data; + gtk_widget_set_sensitive(hbox->del_btn, FALSE); + } +} + +static void prefs_filter_edit_add_rule_cond(FilterRule *rule) +{ + CondHBox *hbox; + GSList *cur; + FilterCond *cond; + + if (!rule || !rule->cond_list) { + hbox = prefs_filter_edit_cond_hbox_create(); + prefs_filter_edit_set_cond_hbox_widgets(hbox, PF_COND_HEADER); + prefs_filter_edit_insert_cond_hbox(hbox, -1); + return; + } + + for (cur = rule->cond_list; cur != NULL; cur = cur->next) { + cond = (FilterCond *)cur->data; + + hbox = prefs_filter_edit_cond_hbox_create(); + prefs_filter_edit_cond_hbox_set(hbox, cond); + prefs_filter_edit_insert_cond_hbox(hbox, -1); + } +} + +static void prefs_filter_edit_add_rule_action(FilterRule *rule) +{ + ActionHBox *hbox; + GSList *cur; + + if (!rule || !rule->action_list) { + hbox = prefs_filter_edit_action_hbox_create(); + prefs_filter_edit_insert_action_hbox(hbox, -1); + prefs_filter_edit_set_action_hbox_widgets(hbox, PF_ACTION_MOVE); + return; + } + + for (cur = rule->action_list; cur != NULL; cur = cur->next) { + FilterAction *action = (FilterAction *)cur->data; + + hbox = prefs_filter_edit_action_hbox_create(); + prefs_filter_edit_insert_action_hbox(hbox, -1); + prefs_filter_edit_action_hbox_set(hbox, action); + } +} + +static void prefs_filter_edit_set_cond_header_menu(CondHBox *hbox) +{ + GSList *cur; + GtkWidget *menu; + GtkWidget *menuitem; + gint pos = 0; + GList *child; + + menu = gtk_option_menu_get_menu + (GTK_OPTION_MENU(hbox->cond_type_optmenu)); + + /* destroy header items */ + child = GTK_MENU_SHELL(menu)->children; + while (child != NULL) { + GList *next = child->next; + menuitem = GTK_WIDGET(child->data); + if (!g_object_get_data(G_OBJECT(menuitem), "header_str")) + break; + gtk_widget_destroy(menuitem); + child = next; + } + + for (cur = rule_edit_window.hdr_list; cur != NULL; + cur = cur->next, pos++) { + Header *header = (Header *)cur->data; + + menuitem = gtk_menu_item_new_with_label(header->name); + gtk_widget_show(menuitem); + gtk_menu_insert(GTK_MENU(menu), menuitem, pos); + g_object_set_data(G_OBJECT(menuitem), MENU_VAL_ID, + GINT_TO_POINTER(PF_COND_HEADER)); + g_object_set_data(G_OBJECT(menuitem), "header_str", + header->name); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(prefs_filter_cond_activated_cb), + hbox); + } + + if (hbox->cur_type == PF_COND_HEADER) + prefs_filter_edit_cond_hbox_select + (hbox, hbox->cur_type, hbox->cur_header_name); +} + +static void prefs_filter_edit_activate_cond_header(const gchar *header) +{ + gint index; + CondHBox *hbox; + GtkWidget *menu; + GtkWidget *menuitem; + GList *cur; + gchar *menu_header; + + g_return_if_fail(header != NULL); + g_return_if_fail(rule_edit_window.cond_hbox_list != NULL); + + hbox = (CondHBox *)rule_edit_window.cond_hbox_list->data; + menu = gtk_option_menu_get_menu + (GTK_OPTION_MENU(hbox->cond_type_optmenu)); + + for (cur = GTK_MENU_SHELL(menu)->children, index = 0; + cur != NULL; cur = cur->next, index++) { + menuitem = GTK_WIDGET(cur->data); + menu_header = g_object_get_data(G_OBJECT(menuitem), + "header_str"); + if (!menu_header) + break; + if (!g_strcasecmp(menu_header, header)) { + gtk_option_menu_set_history + (GTK_OPTION_MENU(hbox->cond_type_optmenu), + index); + gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); + break; + } + } +} + +static gint edit_header_list_dialog_deleted(GtkWidget *widget, + GdkEventAny *event, gpointer data) +{ + edit_header_list_dialog.finished = TRUE; + return TRUE; +} + +static gboolean edit_header_list_dialog_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + edit_header_list_dialog.finished = TRUE; + return FALSE; +} + +static void edit_header_list_dialog_add(void) +{ + GtkCList *clist = GTK_CLIST(edit_header_list_dialog.clist); + const gchar *text; + gchar *ctext[1]; + gint row; + gchar *row_text; + + text = gtk_entry_get_text(GTK_ENTRY(edit_header_list_dialog.entry)); + if (text[0] == '\0') return; + + for (row = 0; gtk_clist_get_text(clist, row, 0, &row_text) != 0; + row++) { + if (g_strcasecmp(row_text, text) == 0) return; + } + + ctext[0] = (gchar *)text; + gtk_clist_append(clist, ctext); +} + +static void edit_header_list_dialog_delete(void) +{ + GtkCList *clist = GTK_CLIST(edit_header_list_dialog.clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + gtk_clist_remove(clist, row); +} + +static void edit_header_list_dialog_ok(void) +{ + edit_header_list_dialog.finished = TRUE; + edit_header_list_dialog.ok = TRUE; +} + +static void edit_header_list_dialog_cancel(void) +{ + edit_header_list_dialog.finished = TRUE; +} + +static void prefs_filter_edit_edit_header_list_dialog_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *hbox; + + GtkWidget *vbox2; + GtkWidget *scrwin; + GtkWidget *clist; + + GtkWidget *entry_hbox; + GtkWidget *label; + GtkWidget *entry; + + GtkWidget *btn_vbox; + GtkWidget *add_btn; + GtkWidget *del_btn; + + GtkWidget *confirm_area; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + gchar *title[1]; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_window_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + gtk_window_set_title(GTK_WINDOW(window), _("Edit header list")); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(edit_header_list_dialog_deleted), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(edit_header_list_dialog_key_pressed), NULL); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(window), vbox); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); + + vbox2 = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0); + + scrwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_size_request(scrwin, 120, 160); + gtk_box_pack_start(GTK_BOX(vbox2), scrwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Headers"); + clist = gtk_clist_new_with_titles(1, title); + gtk_container_add(GTK_CONTAINER(scrwin), clist); + gtk_clist_set_column_width(GTK_CLIST(clist), 0, 80); + gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_BROWSE); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[0].button, + GTK_CAN_FOCUS); + + entry_hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox), entry_hbox, FALSE, TRUE, 0); + + label = gtk_label_new(_("Header:")); + gtk_box_pack_start(GTK_BOX(entry_hbox), label, FALSE, FALSE, 0); + + entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(entry_hbox), entry, TRUE, TRUE, 0); + + btn_vbox = gtk_vbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(hbox), btn_vbox, FALSE, FALSE, 0); + + add_btn = gtk_button_new_with_label(_("Add")); + gtk_box_pack_start(GTK_BOX(btn_vbox), add_btn, FALSE, FALSE, 0); + + del_btn = gtk_button_new_with_label(_(" Delete ")); + gtk_box_pack_start(GTK_BOX(btn_vbox), del_btn, FALSE, FALSE, 0); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(add_btn), "clicked", + G_CALLBACK(edit_header_list_dialog_add), NULL); + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(edit_header_list_dialog_delete), NULL); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(edit_header_list_dialog_ok), NULL); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(edit_header_list_dialog_cancel), NULL); + + manage_window_set_transient(GTK_WINDOW(window)); + + gtk_widget_show_all(window); + + edit_header_list_dialog.window = window; + edit_header_list_dialog.clist = clist; + edit_header_list_dialog.entry = entry; + edit_header_list_dialog.finished = FALSE; + edit_header_list_dialog.ok = FALSE; +} + +static void prefs_filter_edit_edit_header_list_dialog_set(void) +{ + GtkCList *clist = GTK_CLIST(edit_header_list_dialog.clist); + GSList *list; + GSList *cur; + gchar *text[1]; + + gtk_clist_freeze(clist); + + list = prefs_filter_get_user_header_list(); + for (cur = list; cur != NULL; cur = cur->next) { + Header *header = (Header *)cur->data; + text[0] = header->name; + gtk_clist_append(clist, text); + } + + gtk_clist_thaw(clist); +} + +static GSList *prefs_filter_edit_edit_header_list_dialog_get(void) +{ + GtkCList *clist = GTK_CLIST(edit_header_list_dialog.clist); + gint row; + gchar *text; + GSList *list = NULL; + + for (row = 0; gtk_clist_get_text(clist, row, 0, &text) != 0; row++) + list = procheader_add_header_list(list, text, NULL); + + return list; +} + +static void prefs_filter_edit_edit_header_list(void) +{ + GSList *list; + GSList *cur; + + prefs_filter_edit_edit_header_list_dialog_create(); + prefs_filter_edit_edit_header_list_dialog_set(); + + while (edit_header_list_dialog.finished == FALSE) + gtk_main_iteration(); + + if (edit_header_list_dialog.ok == TRUE) { + list = prefs_filter_edit_edit_header_list_dialog_get(); + prefs_filter_set_user_header_list(list); + prefs_filter_edit_update_header_list(); + for (cur = rule_edit_window.cond_hbox_list; cur != NULL; + cur = cur->next) { + CondHBox *hbox = (CondHBox *)cur->data; + prefs_filter_edit_set_cond_header_menu(hbox); + } + } + + gtk_widget_destroy(edit_header_list_dialog.window); + edit_header_list_dialog.window = NULL; + edit_header_list_dialog.clist = NULL; + edit_header_list_dialog.entry = NULL; + edit_header_list_dialog.finished = FALSE; + edit_header_list_dialog.ok = FALSE; +} + +static FilterRule *prefs_filter_edit_dialog_to_rule(void) +{ + FilterRule *rule = NULL; + GSList *cur; + const gchar *rule_name; + FilterBoolOp bool_op = FLT_OR; + GSList *cond_list = NULL; + GSList *action_list = NULL; + GtkWidget *bool_op_menuitem; + gchar *error_msg = NULL; + + rule_name = gtk_entry_get_text(GTK_ENTRY(rule_edit_window.name_entry)); + if (!rule_name || *rule_name == '\0') { + error_msg = _("Rule name is not specified."); + goto error; + } + + bool_op_menuitem = gtk_menu_get_active + (GTK_MENU(gtk_option_menu_get_menu + (GTK_OPTION_MENU(rule_edit_window.bool_op_optmenu)))); + bool_op = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(bool_op_menuitem), MENU_VAL_ID)); + + for (cur = rule_edit_window.cond_hbox_list; cur != NULL; + cur = cur->next) { + CondHBox *hbox = (CondHBox *)cur->data; + GtkWidget *cond_type_menuitem; + CondMenuType cond_menu_type; + MatchMenuType match_menu_type; + const gchar *header_name; + const gchar *key_str; + gint int_value; + FilterCond *cond = NULL; + FilterMatchType match_type = FLT_CONTAIN; + FilterMatchFlag match_flag = 0; + SizeMatchType size_type; + AgeMatchType age_type; + + cond_type_menuitem = gtk_menu_get_active + (GTK_MENU(gtk_option_menu_get_menu + (GTK_OPTION_MENU(hbox->cond_type_optmenu)))); + cond_menu_type = GPOINTER_TO_INT + (g_object_get_data + (G_OBJECT(cond_type_menuitem), MENU_VAL_ID)); + + match_menu_type = menu_get_option_menu_active_index + (GTK_OPTION_MENU(hbox->match_type_optmenu)); + + key_str = gtk_entry_get_text(GTK_ENTRY(hbox->key_entry)); + + switch (match_menu_type) { + case PF_MATCH_CONTAIN: + match_type = FLT_CONTAIN; + break; + case PF_MATCH_NOT_CONTAIN: + match_type = FLT_CONTAIN; + match_flag |= FLT_NOT_MATCH; + break; + case PF_MATCH_EQUAL: + match_type = FLT_EQUAL; + break; + case PF_MATCH_NOT_EQUAL: + match_type = FLT_EQUAL; + match_flag |= FLT_NOT_MATCH; + break; + case PF_MATCH_REGEX: + match_type = FLT_REGEX; + break; + case PF_MATCH_NOT_REGEX: + match_type = FLT_REGEX; + match_flag |= FLT_NOT_MATCH; + break; + default: + break; + } + + switch (cond_menu_type) { + case PF_COND_HEADER: + header_name = g_object_get_data + (G_OBJECT(cond_type_menuitem), "header_str"); + cond = filter_cond_new(FLT_COND_HEADER, + match_type, match_flag, + header_name, key_str); + break; + case PF_COND_TO_OR_CC: + cond = filter_cond_new(FLT_COND_TO_OR_CC, match_type, + match_flag, NULL, key_str); + break; + case PF_COND_ANY_HEADER: + cond = filter_cond_new(FLT_COND_ANY_HEADER, match_type, + match_flag, NULL, key_str); + break; + case PF_COND_BODY: + cond = filter_cond_new(FLT_COND_BODY, match_type, + match_flag, NULL, key_str); + break; + case PF_COND_CMD_TEST: + if (key_str && *key_str) + cond = filter_cond_new(FLT_COND_CMD_TEST, + 0, 0, NULL, key_str); + else + error_msg = _("Command is not specified."); + break; + case PF_COND_SIZE: + size_type = menu_get_option_menu_active_index + (GTK_OPTION_MENU(hbox->size_match_optmenu)); + match_flag = size_type == PF_SIZE_LARGER + ? 0 : FLT_NOT_MATCH; + int_value = gtk_spin_button_get_value_as_int + (GTK_SPIN_BUTTON(hbox->spin_btn)); + cond = filter_cond_new(FLT_COND_SIZE_GREATER, + 0, match_flag, NULL, + itos(int_value)); + break; + case PF_COND_AGE: + age_type = menu_get_option_menu_active_index + (GTK_OPTION_MENU(hbox->age_match_optmenu)); + match_flag = age_type == PF_AGE_LONGER + ? 0 : FLT_NOT_MATCH; + int_value = gtk_spin_button_get_value_as_int + (GTK_SPIN_BUTTON(hbox->spin_btn)); + cond = filter_cond_new(FLT_COND_AGE_GREATER, + 0, match_flag, NULL, + itos(int_value)); + break; + case PF_COND_ACCOUNT: + case PF_COND_EDIT_HEADER: + default: + break; + } + + if (cond) + cond_list = g_slist_append(cond_list, cond); + else { + if (!error_msg) + error_msg = _("Invalid condition exists."); + goto error; + } + } + + for (cur = rule_edit_window.action_hbox_list; cur != NULL; + cur = cur->next) { + ActionHBox *hbox = (ActionHBox *)cur->data; + ActionMenuType action_menu_type; + const gchar *str; + guint color; + FilterAction *action = NULL; + + action_menu_type = prefs_filter_edit_get_action_hbox_type(hbox); + + switch (action_menu_type) { + case PF_ACTION_MOVE: + str = gtk_entry_get_text(GTK_ENTRY(hbox->folder_entry)); + if (str && *str) + action = filter_action_new(FLT_ACTION_MOVE, + str); + else + error_msg = _("Destination folder is not specified."); + break; + case PF_ACTION_COPY: + str = gtk_entry_get_text(GTK_ENTRY(hbox->folder_entry)); + if (str && *str) + action = filter_action_new(FLT_ACTION_COPY, + str); + else + error_msg = _("Destination folder is not specified."); + break; + case PF_ACTION_NOT_RECEIVE: + action = filter_action_new(FLT_ACTION_NOT_RECEIVE, + NULL); + break; + case PF_ACTION_DELETE: + action = filter_action_new(FLT_ACTION_DELETE, NULL); + break; + case PF_ACTION_EXEC: + str = gtk_entry_get_text(GTK_ENTRY(hbox->cmd_entry)); + if (str && *str) + action = filter_action_new(FLT_ACTION_EXEC, + str); + else + error_msg = _("Command is not specified."); + break; + case PF_ACTION_EXEC_ASYNC: + str = gtk_entry_get_text(GTK_ENTRY(hbox->cmd_entry)); + if (str && *str) + action = filter_action_new + (FLT_ACTION_EXEC_ASYNC, str); + else + error_msg = _("Command is not specified."); + break; + case PF_ACTION_MARK: + action = filter_action_new(FLT_ACTION_MARK, NULL); + break; + case PF_ACTION_COLOR_LABEL: + color = colorlabel_get_color_menu_active_item + (gtk_option_menu_get_menu + (GTK_OPTION_MENU(hbox->clabel_optmenu))); + action = filter_action_new(FLT_ACTION_COLOR_LABEL, + itos(color)); + break; + case PF_ACTION_MARK_READ: + action = filter_action_new(FLT_ACTION_MARK_READ, NULL); + break; + case PF_ACTION_FORWARD: + case PF_ACTION_FORWARD_AS_ATTACHMENT: + case PF_ACTION_REDIRECT: + break; + case PF_ACTION_STOP_EVAL: + action = filter_action_new(FLT_ACTION_STOP_EVAL, NULL); + break; + case PF_ACTION_SEPARATOR: + default: + break; + } + + if (action) + action_list = g_slist_append(action_list, action); + else { + if (!error_msg) + error_msg = _("Invalid action exists."); + goto error; + } + } + +error: + if (error_msg || !cond_list || !action_list) { + if (!error_msg) { + if (!cond_list) + error_msg = _("Condition not exist."); + else + error_msg = _("Action not exist."); + } + alertpanel_error("%s", error_msg); + if (cond_list) + filter_cond_list_free(cond_list); + if (action_list) + filter_action_list_free(action_list); + return NULL; + } + + rule = filter_rule_new(rule_name, bool_op, cond_list, action_list); + + return rule; +} + +/* callback functions */ + +static gint prefs_filter_edit_deleted(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_filter_edit_cancel(); + return TRUE; +} + +static gboolean prefs_filter_edit_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_filter_edit_cancel(); + return FALSE; +} + +static void prefs_filter_edit_ok(void) +{ + FilterRule *rule; + + rule = prefs_filter_edit_dialog_to_rule(); + if (rule) { + rule_edit_window.new_rule = rule; + rule_edit_window.edit_finished = TRUE; + } +} + +static void prefs_filter_edit_cancel(void) +{ + rule_edit_window.new_rule = NULL; + rule_edit_window.edit_finished = TRUE; +} + +static void prefs_filter_cond_activated_cb(GtkWidget *widget, gpointer data) +{ + CondHBox *hbox = (CondHBox *)data; + CondMenuType type; + + type = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(widget), MENU_VAL_ID)); + + if (type == PF_COND_EDIT_HEADER) { + prefs_filter_edit_edit_header_list(); + prefs_filter_edit_cond_hbox_select + (hbox, hbox->cur_type, hbox->cur_header_name); + } else { + hbox->cur_type = type; + g_free(hbox->cur_header_name); + hbox->cur_header_name = NULL; + + prefs_filter_edit_set_cond_hbox_widgets(hbox, type); + if (type == PF_COND_HEADER) { + gchar *header_name; + gchar *header_field; + + header_name = (gchar *)g_object_get_data + (G_OBJECT(widget), "header_str"); + header_field = prefs_filter_get_msg_header_field + (header_name); + if (header_field) + gtk_entry_set_text(GTK_ENTRY(hbox->key_entry), + header_field); + hbox->cur_header_name = g_strdup(header_name); + } + } +} + +static void prefs_filter_action_activated_cb(GtkWidget *widget, gpointer data) +{ + ActionHBox *hbox = (ActionHBox *)data; + ActionMenuType type; + + type = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(widget), MENU_VAL_ID)); + prefs_filter_edit_set_action_hbox_widgets(hbox, type); +} + +static void prefs_filter_action_select_dest_cb(GtkWidget *widget, gpointer data) +{ + ActionHBox *hbox = (ActionHBox *)data; + + FolderItem *dest; + gchar *id; + + dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL); + if (!dest || !dest->path) return; + + id = folder_item_get_identifier(dest); + if (id) { + gtk_entry_set_text(GTK_ENTRY(hbox->folder_entry), id); + g_free(id); + } +} + +static void prefs_filter_cond_del_cb(GtkWidget *widget, gpointer data) +{ + CondHBox *hbox = (CondHBox *)data; + + if (rule_edit_window.cond_hbox_list && + rule_edit_window.cond_hbox_list->next) + prefs_filter_edit_remove_cond_hbox(hbox); +} + +static void prefs_filter_cond_add_cb(GtkWidget *widget, gpointer data) +{ + CondHBox *hbox = (CondHBox *)data; + CondHBox *new_hbox; + gint index; + + index = g_slist_index(rule_edit_window.cond_hbox_list, hbox); + g_return_if_fail(index >= 0); + new_hbox = prefs_filter_edit_cond_hbox_create(); + prefs_filter_edit_set_cond_hbox_widgets(new_hbox, PF_COND_HEADER); + prefs_filter_edit_insert_cond_hbox(new_hbox, index + 1); +} + +static void prefs_filter_action_del_cb(GtkWidget *widget, gpointer data) +{ + ActionHBox *hbox = (ActionHBox *)data; + + if (rule_edit_window.action_hbox_list && + rule_edit_window.action_hbox_list->next) + prefs_filter_edit_remove_action_hbox(hbox); +} + +static void prefs_filter_action_add_cb(GtkWidget *widget, gpointer data) +{ + ActionHBox *hbox = (ActionHBox *)data; + ActionHBox *new_hbox; + gboolean action_menu_selection[PF_ACTION_NONE]; + gint index; + + prefs_filter_edit_get_action_hbox_menus_selection(action_menu_selection); + + index = g_slist_index(rule_edit_window.action_hbox_list, hbox); + g_return_if_fail(index >= 0); + new_hbox = prefs_filter_edit_action_hbox_create(); + prefs_filter_edit_insert_action_hbox(new_hbox, index + 1); + if (action_menu_selection[PF_ACTION_MOVE] == TRUE || + action_menu_selection[PF_ACTION_NOT_RECEIVE] == TRUE || + action_menu_selection[PF_ACTION_DELETE] == TRUE) + prefs_filter_edit_set_action_hbox_widgets(new_hbox, + PF_ACTION_COPY); + else + prefs_filter_edit_set_action_hbox_widgets(new_hbox, + PF_ACTION_MOVE); +} diff --git a/src/prefs_filter_edit.h b/src/prefs_filter_edit.h new file mode 100644 index 00000000..c7e91d2d --- /dev/null +++ b/src/prefs_filter_edit.h @@ -0,0 +1,28 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_FILTER_EDIT_H__ +#define __PREFS_FILTER_EDIT_H__ + +#include "filter.h" + +FilterRule *prefs_filter_edit_open (FilterRule *rule, + const gchar *header); + +#endif /* __PREFS_FILTER_EDIT_H__ */ diff --git a/src/prefs_folder_item.c b/src/prefs_folder_item.c new file mode 100644 index 00000000..183ccdc4 --- /dev/null +++ b/src/prefs_folder_item.c @@ -0,0 +1,579 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include + +#include "intl.h" +#include "folder.h" +#include "prefs.h" +#include "prefs_folder_item.h" +#include "prefs_account.h" +#include "account.h" +#include "manage_window.h" +#include "folderview.h" +#include "inc.h" +#include "menu.h" + +typedef struct _PrefsFolderItemDialog PrefsFolderItemDialog; + +struct _PrefsFolderItemDialog +{ + PrefsDialog *dialog; + FolderItem *item; + + /* General */ + GtkWidget *name_entry; + GtkWidget *path_entry; + GtkWidget *type_optmenu; + + GtkWidget *trim_summary_subj_chkbtn; + GtkWidget *trim_compose_subj_chkbtn; + + /* Compose */ + GtkWidget *account_optmenu; + GtkWidget *ac_apply_sub_chkbtn; + GtkWidget *to_entry; + GtkWidget *on_reply_chkbtn; + GtkWidget *cc_entry; + GtkWidget *bcc_entry; + GtkWidget *replyto_entry; +}; + +static PrefsFolderItemDialog *prefs_folder_item_create + (FolderItem *item); +static void prefs_folder_item_general_create + (PrefsFolderItemDialog *dialog); +static void prefs_folder_item_compose_create + (PrefsFolderItemDialog *dialog); +static void prefs_folder_item_set_dialog(PrefsFolderItemDialog *dialog); + +static void prefs_folder_item_ok_cb (GtkWidget *widget, + PrefsFolderItemDialog *dialog); +static void prefs_folder_item_apply_cb (GtkWidget *widget, + PrefsFolderItemDialog *dialog); +static void prefs_folder_item_cancel_cb (GtkWidget *widget, + PrefsFolderItemDialog *dialog); +static gint prefs_folder_item_delete_cb (GtkWidget *widget, + GdkEventAny *event, + PrefsFolderItemDialog *dialog); +static gboolean prefs_folder_item_key_press_cb + (GtkWidget *widget, + GdkEventKey *event, + PrefsFolderItemDialog *dialog); + +void prefs_folder_item_open(FolderItem *item) +{ + PrefsFolderItemDialog *dialog; + + g_return_if_fail(item != NULL); + + inc_lock(); + + dialog = prefs_folder_item_create(item); + + manage_window_set_transient(GTK_WINDOW(dialog->dialog->window)); + + prefs_folder_item_set_dialog(dialog); + + gtk_widget_show_all(dialog->dialog->window); +} + +PrefsFolderItemDialog *prefs_folder_item_create(FolderItem *item) +{ + PrefsFolderItemDialog *new_dialog; + PrefsDialog *dialog; + + new_dialog = g_new0(PrefsFolderItemDialog, 1); + + dialog = g_new0(PrefsDialog, 1); + prefs_dialog_create(dialog); + + gtk_window_set_title(GTK_WINDOW(dialog->window), _("Folder properties")); + gtk_widget_realize(dialog->window); + g_signal_connect(G_OBJECT(dialog->window), "delete_event", + G_CALLBACK(prefs_folder_item_delete_cb), new_dialog); + g_signal_connect(G_OBJECT(dialog->window), "key_press_event", + G_CALLBACK(prefs_folder_item_key_press_cb), new_dialog); + MANAGE_WINDOW_SIGNALS_CONNECT(dialog->window); + + g_signal_connect(G_OBJECT(dialog->ok_btn), "clicked", + G_CALLBACK(prefs_folder_item_ok_cb), new_dialog); + g_signal_connect(G_OBJECT(dialog->apply_btn), "clicked", + G_CALLBACK(prefs_folder_item_apply_cb), new_dialog); + g_signal_connect(G_OBJECT(dialog->cancel_btn), "clicked", + G_CALLBACK(prefs_folder_item_cancel_cb), new_dialog); + + new_dialog->dialog = dialog; + new_dialog->item = item; + + prefs_folder_item_general_create(new_dialog); + prefs_folder_item_compose_create(new_dialog); + + SET_NOTEBOOK_LABEL(dialog->notebook, _("General"), 0); + SET_NOTEBOOK_LABEL(dialog->notebook, _("Compose"), 1); + + return new_dialog; +} + +static void prefs_folder_item_general_create(PrefsFolderItemDialog *dialog) +{ + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *name_entry; + GtkWidget *path_entry; + GtkWidget *optmenu; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *vbox2; + GtkWidget *trim_summary_subj_chkbtn; + GtkWidget *trim_compose_subj_chkbtn; + GtkStyle *style; + + style = gtk_style_copy(gtk_widget_get_style(dialog->dialog->window)); + style->base[GTK_STATE_NORMAL] = style->bg[GTK_STATE_NORMAL]; + + vbox = gtk_vbox_new(FALSE, VSPACING); + gtk_container_add(GTK_CONTAINER(dialog->dialog->notebook), vbox); + gtk_container_set_border_width(GTK_CONTAINER (vbox), VBOX_BORDER); + + table = gtk_table_new(3, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 8); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + label = gtk_label_new(_("Name")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + name_entry = gtk_entry_new(); + gtk_editable_set_editable(GTK_EDITABLE(name_entry), FALSE); + gtk_widget_set_size_request(name_entry, 200, -1); + gtk_widget_set_style(name_entry, style); + gtk_table_attach(GTK_TABLE(table), name_entry, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + label = gtk_label_new(_("Path")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + path_entry = gtk_entry_new(); + gtk_editable_set_editable(GTK_EDITABLE(path_entry), FALSE); + gtk_widget_set_size_request(path_entry, 200, -1); + gtk_widget_set_style(path_entry, style); + gtk_table_attach(GTK_TABLE(table), path_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + label = gtk_label_new(_("Type")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, + GTK_FILL, 0, 0, 0); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(hbox); + gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 2, 3, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + optmenu = gtk_option_menu_new(); + gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new(); + + MENUITEM_ADD(optmenu_menu, menuitem, _("Normal"), F_NORMAL); + MENUITEM_ADD(optmenu_menu, menuitem, _("Inbox") , F_INBOX); + MENUITEM_ADD(optmenu_menu, menuitem, _("Sent") , F_OUTBOX); + MENUITEM_ADD(optmenu_menu, menuitem, _("Drafts"), F_DRAFT); + MENUITEM_ADD(optmenu_menu, menuitem, _("Queue") , F_QUEUE); + MENUITEM_ADD(optmenu_menu, menuitem, _("Trash") , F_TRASH); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), optmenu_menu); + + vbox2 = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON(vbox2, trim_summary_subj_chkbtn, + _("Don't display [...] or (...) at the beginning of subject in summary")); + PACK_CHECK_BUTTON(vbox2, trim_compose_subj_chkbtn, + _("Delete [...] or (...) at the beginning of subject on reply")); + + dialog->name_entry = name_entry; + dialog->path_entry = path_entry; + dialog->type_optmenu = optmenu; + dialog->trim_summary_subj_chkbtn = trim_summary_subj_chkbtn; + dialog->trim_compose_subj_chkbtn = trim_compose_subj_chkbtn; +} + +static void prefs_folder_item_compose_create(PrefsFolderItemDialog *dialog) +{ + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *account_vbox; + GtkWidget *table; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *optmenu; + GtkWidget *optmenu_menu; + GtkWidget *menuitem; + GtkWidget *ac_apply_sub_chkbtn; + GtkWidget *to_entry; + GtkWidget *on_reply_chkbtn; + GtkWidget *cc_entry; + GtkWidget *bcc_entry; + GtkWidget *replyto_entry; + GList *list; + + vbox = gtk_vbox_new(FALSE, VSPACING); + gtk_container_add(GTK_CONTAINER(dialog->dialog->notebook), vbox); + gtk_container_set_border_width(GTK_CONTAINER (vbox), VBOX_BORDER); + + PACK_FRAME(vbox, frame, _("Account")); + + account_vbox = gtk_vbox_new(FALSE, VSPACING_NARROW); + gtk_container_add(GTK_CONTAINER(frame), account_vbox); + gtk_container_set_border_width (GTK_CONTAINER (account_vbox), 8); + + table = gtk_table_new(1, 2, FALSE); + gtk_box_pack_start(GTK_BOX(account_vbox), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + label = gtk_label_new(_("Account")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(hbox); + gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + optmenu = gtk_option_menu_new(); + gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0); + + optmenu_menu = gtk_menu_new(); + + MENUITEM_ADD(optmenu_menu, menuitem, _("None"), -1); + + for (list = account_get_list(); list != NULL; list = list->next) { + gchar *text; + PrefsAccount *ac = list->data; + + text = g_strdup_printf("%s: %s", ac->account_name, ac->address); + MENUITEM_ADD(optmenu_menu, menuitem, text, ac->account_id); + } + + gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), optmenu_menu); + + PACK_CHECK_BUTTON(account_vbox, ac_apply_sub_chkbtn, + _("Apply to subfolders")); + + PACK_FRAME(vbox, frame, _("Automatically set the following addresses")); + + table = gtk_table_new(4, 2, FALSE); + gtk_container_add(GTK_CONTAINER(frame), table); + gtk_container_set_border_width (GTK_CONTAINER (table), 8); + gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + + label = gtk_label_new(_("To:")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(hbox); + gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 0, 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + to_entry = gtk_entry_new(); + gtk_widget_set_size_request(to_entry, 200, -1); + gtk_box_pack_start(GTK_BOX(hbox), to_entry, TRUE, TRUE, 0); + + PACK_CHECK_BUTTON(hbox, on_reply_chkbtn, _("use also on reply")); + + label = gtk_label_new(_("Cc:")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + cc_entry = gtk_entry_new(); + gtk_widget_set_size_request(cc_entry, 200, -1); + gtk_table_attach(GTK_TABLE(table), cc_entry, 1, 2, 1, 2, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + label = gtk_label_new(_("Bcc:")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + bcc_entry = gtk_entry_new(); + gtk_widget_set_size_request(bcc_entry, 200, -1); + gtk_table_attach(GTK_TABLE(table), bcc_entry, 1, 2, 2, 3, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + label = gtk_label_new(_("Reply-To:")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + replyto_entry = gtk_entry_new(); + gtk_widget_set_size_request(replyto_entry, 200, -1); + gtk_table_attach(GTK_TABLE(table), replyto_entry, 1, 2, 3, 4, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0); + + dialog->account_optmenu = optmenu; + dialog->ac_apply_sub_chkbtn = ac_apply_sub_chkbtn; + dialog->to_entry = to_entry; + dialog->on_reply_chkbtn = on_reply_chkbtn; + dialog->cc_entry = cc_entry; + dialog->bcc_entry = bcc_entry; + dialog->replyto_entry = replyto_entry; +} + +#define SET_ENTRY(entry, str) \ + gtk_entry_set_text(GTK_ENTRY(dialog->entry), \ + dialog->item->str ? dialog->item->str : "") + +static void prefs_folder_item_set_dialog(PrefsFolderItemDialog *dialog) +{ + GtkWidget *menu; + GtkWidget *menuitem; + GtkOptionMenu *optmenu; + gchar *id; + GList *cur; + SpecialFolderItemType type; + gint n; + guint index = 0; + + /* General */ + + SET_ENTRY(name_entry, name); + + id = folder_item_get_identifier(dialog->item); + gtk_entry_set_text(GTK_ENTRY(dialog->path_entry), id); + g_free(id); + + optmenu = GTK_OPTION_MENU(dialog->type_optmenu); + menu = gtk_option_menu_get_menu(optmenu); + for (cur = GTK_MENU_SHELL(menu)->children, n = 0; + cur != NULL; cur = cur->next, n++) { + menuitem = GTK_WIDGET(cur->data); + type = (SpecialFolderItemType) + g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID); + if (type != F_NORMAL && + FOLDER_TYPE(dialog->item->folder) == F_NEWS) + gtk_widget_set_sensitive(menuitem, FALSE); + if (dialog->item->stype == type) + index = n; + } + + gtk_option_menu_set_history(optmenu, index); + + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(dialog->trim_summary_subj_chkbtn), + dialog->item->trim_summary_subject); + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(dialog->trim_compose_subj_chkbtn), + dialog->item->trim_compose_subject); + + /* Compose */ + + index = 0; + optmenu = GTK_OPTION_MENU(dialog->account_optmenu); + if (dialog->item->account) { + index = menu_find_option_menu_index + (optmenu, + GINT_TO_POINTER(dialog->item->account->account_id), + NULL); + if (index < 0) + index = 0; + } + + gtk_option_menu_set_history(optmenu, index); + + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(dialog->ac_apply_sub_chkbtn), + dialog->item->ac_apply_sub); + + SET_ENTRY(to_entry, auto_to); + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON(dialog->on_reply_chkbtn), + dialog->item->use_auto_to_on_reply); + + SET_ENTRY(cc_entry, auto_cc); + SET_ENTRY(bcc_entry, auto_bcc); + SET_ENTRY(replyto_entry, auto_replyto); +} + +#undef SET_ENTRY + +void prefs_folder_item_destroy(PrefsFolderItemDialog *dialog) +{ + prefs_dialog_destroy(dialog->dialog); + g_free(dialog->dialog); + g_free(dialog); + + inc_unlock(); +} + +static void prefs_folder_item_ok_cb(GtkWidget *widget, + PrefsFolderItemDialog *dialog) +{ + prefs_folder_item_apply_cb(widget, dialog); + prefs_folder_item_destroy(dialog); +} + +#define SET_DATA_FROM_ENTRY(entry, str) \ +{ \ + entry_str = gtk_entry_get_text(GTK_ENTRY(dialog->entry)); \ + g_free(item->str); \ + item->str = (entry_str && *entry_str) ? g_strdup(entry_str) : NULL; \ +} + +static void prefs_folder_item_apply_cb(GtkWidget *widget, + PrefsFolderItemDialog *dialog) +{ + GtkWidget *menu; + GtkWidget *menuitem; + GtkOptionMenu *optmenu; + SpecialFolderItemType type; + FolderItem *item = dialog->item; + Folder *folder = item->folder; + FolderItem *prev_item = NULL; + gint account_id; + const gchar *entry_str; + + optmenu = GTK_OPTION_MENU(dialog->type_optmenu); + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + type = (SpecialFolderItemType) + g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID); + + if (item->stype != type) { + switch (type) { + case F_NORMAL: + break; + case F_INBOX: + if (folder->inbox) + folder->inbox->stype = F_NORMAL; + prev_item = folder->inbox; + folder->inbox = item; + break; + case F_OUTBOX: + if (folder->outbox) + folder->outbox->stype = F_NORMAL; + prev_item = folder->outbox; + folder->outbox = item; + break; + case F_DRAFT: + if (folder->draft) + folder->draft->stype = F_NORMAL; + prev_item = folder->draft; + folder->draft = item; + break; + case F_QUEUE: + if (folder->queue) + folder->queue->stype = F_NORMAL; + prev_item = folder->queue; + folder->queue = item; + break; + case F_TRASH: + if (folder->trash) + folder->trash->stype = F_NORMAL; + prev_item = folder->trash; + folder->trash = item; + break; + } + + item->stype = type; + + if (prev_item) + folderview_update_item(prev_item, FALSE); + folderview_update_item(item, FALSE); + } + + item->trim_summary_subject = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(dialog->trim_summary_subj_chkbtn)); + item->trim_compose_subject = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(dialog->trim_compose_subj_chkbtn)); + + /* account menu */ + optmenu = GTK_OPTION_MENU(dialog->account_optmenu); + menu = gtk_option_menu_get_menu(optmenu); + menuitem = gtk_menu_get_active(GTK_MENU(menu)); + account_id = GPOINTER_TO_INT + (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID)); + if (account_id >= 0) + item->account = account_find_from_id(account_id); + else + item->account = NULL; + + item->ac_apply_sub = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(dialog->ac_apply_sub_chkbtn)); + + SET_DATA_FROM_ENTRY(to_entry, auto_to); + item->use_auto_to_on_reply = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(dialog->on_reply_chkbtn)); + + SET_DATA_FROM_ENTRY(cc_entry, auto_cc); + SET_DATA_FROM_ENTRY(bcc_entry, auto_bcc); + SET_DATA_FROM_ENTRY(replyto_entry, auto_replyto); +} + +#undef SET_DATA_FROM_ENTRY + +static void prefs_folder_item_cancel_cb(GtkWidget *widget, + PrefsFolderItemDialog *dialog) +{ + prefs_folder_item_destroy(dialog); +} + +static gint prefs_folder_item_delete_cb(GtkWidget *widget, GdkEventAny *event, + PrefsFolderItemDialog *dialog) +{ + prefs_folder_item_destroy(dialog); + return TRUE; +} + +static gboolean prefs_folder_item_key_press_cb(GtkWidget *widget, + GdkEventKey *event, + PrefsFolderItemDialog *dialog) +{ + if (event && event->keyval == GDK_Escape) + prefs_folder_item_cancel_cb(widget, dialog); + return FALSE; +} diff --git a/src/prefs_folder_item.h b/src/prefs_folder_item.h new file mode 100644 index 00000000..d6daa873 --- /dev/null +++ b/src/prefs_folder_item.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_FOLDER_ITEM_H__ +#define __PREFS_FOLDER_ITEM_H__ + +#include + +#include "folder.h" + +void prefs_folder_item_open (FolderItem *item); + +#endif /* __PREFS_FOLDER_ITEM_H__ */ diff --git a/src/prefs_summary_column.c b/src/prefs_summary_column.c new file mode 100644 index 00000000..8fdf6be8 --- /dev/null +++ b/src/prefs_summary_column.c @@ -0,0 +1,537 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "prefs.h" +#include "prefs_common.h" +#include "prefs_summary_column.h" +#include "manage_window.h" +#include "summaryview.h" +#include "mainwindow.h" +#include "inc.h" +#include "gtkutils.h" +#include "utils.h" + +static struct _SummaryColumnDialog +{ + GtkWidget *window; + + GtkWidget *stock_clist; + GtkWidget *shown_clist; + + GtkWidget *add_btn; + GtkWidget *remove_btn; + GtkWidget *up_btn; + GtkWidget *down_btn; + + GtkWidget *default_btn; + + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + gboolean finished; +} summary_col; + +static const gchar *const col_name[N_SUMMARY_COLS] = { + N_("Mark"), /* S_COL_MARK */ + N_("Unread"), /* S_COL_UNREAD */ + N_("Attachment"), /* S_COL_MIME */ + N_("Subject"), /* S_COL_SUBJECT */ + N_("From"), /* S_COL_FROM */ + N_("Date"), /* S_COL_DATE */ + N_("Size"), /* S_COL_SIZE */ + N_("Number") /* S_COL_NUMBER */ +}; + +static SummaryColumnState default_state[N_SUMMARY_COLS] = { + { S_COL_MARK , TRUE }, + { S_COL_UNREAD , TRUE }, + { S_COL_MIME , TRUE }, + { S_COL_SUBJECT, TRUE }, + { S_COL_FROM , TRUE }, + { S_COL_DATE , TRUE }, + { S_COL_SIZE , TRUE }, + { S_COL_NUMBER , FALSE } +}; + +static void prefs_summary_column_create (void); + +static void prefs_summary_column_set_dialog (SummaryColumnState *state); +static void prefs_summary_column_set_view (void); + +/* callback functions */ +static void prefs_summary_column_add (void); +static void prefs_summary_column_remove (void); + +static void prefs_summary_column_up (void); +static void prefs_summary_column_down (void); + +static void prefs_summary_column_set_to_default (void); + +static void prefs_summary_column_ok (void); +static void prefs_summary_column_cancel (void); + +static gint prefs_summary_column_delete_event (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_summary_column_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data); + +void prefs_summary_column_open(void) +{ + inc_lock(); + + if (!summary_col.window) + prefs_summary_column_create(); + + manage_window_set_transient(GTK_WINDOW(summary_col.window)); + gtk_widget_grab_focus(summary_col.ok_btn); + + prefs_summary_column_set_dialog(NULL); + + gtk_widget_show(summary_col.window); + + summary_col.finished = FALSE; + while (summary_col.finished == FALSE) + gtk_main_iteration(); + + gtk_widget_hide(summary_col.window); + + inc_unlock(); +} + +static void prefs_summary_column_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + + GtkWidget *label_hbox; + GtkWidget *label; + + GtkWidget *vbox1; + + GtkWidget *hbox1; + GtkWidget *clist_hbox; + GtkWidget *scrolledwin; + GtkWidget *stock_clist; + GtkWidget *shown_clist; + + GtkWidget *btn_vbox; + GtkWidget *btn_vbox1; + GtkWidget *add_btn; + GtkWidget *remove_btn; + GtkWidget *up_btn; + GtkWidget *down_btn; + + GtkWidget *btn_hbox; + GtkWidget *default_btn; + GtkWidget *confirm_area; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + gchar *title[1]; + + debug_print(_("Creating summary column setting window...\n")); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); + gtk_window_set_title(GTK_WINDOW(window), + _("Summary display item setting")); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_summary_column_delete_event), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_summary_column_key_pressed), NULL); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(window), vbox); + + label_hbox = gtk_hbox_new(FALSE, 0); + gtk_widget_show(label_hbox); + gtk_box_pack_start(GTK_BOX(vbox), label_hbox, FALSE, FALSE, 4); + + label = gtk_label_new + (_("Select items to be displayed on the summary view. You can modify\n" + "the order by using the Up / Down button, or dragging the items.")); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(label_hbox), label, FALSE, FALSE, 4); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); + + vbox1 = gtk_vbox_new(FALSE, VSPACING); + gtk_widget_show(vbox1); + gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2); + + hbox1 = gtk_hbox_new(FALSE, 8); + gtk_widget_show(hbox1); + gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, TRUE, 0); + + clist_hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(clist_hbox); + gtk_box_pack_start(GTK_BOX(hbox1), clist_hbox, TRUE, TRUE, 0); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_size_request(scrolledwin, 180, 210); + gtk_widget_show(scrolledwin); + gtk_box_pack_start(GTK_BOX(clist_hbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Available items"); + stock_clist = gtk_clist_new_with_titles(1, title); + gtk_widget_show(stock_clist); + gtk_container_add(GTK_CONTAINER(scrolledwin), stock_clist); + gtk_clist_set_selection_mode(GTK_CLIST(stock_clist), + GTK_SELECTION_BROWSE); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(stock_clist)->column[0].button, + GTK_CAN_FOCUS); + + /* add/remove button */ + btn_vbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(btn_vbox); + gtk_box_pack_start(GTK_BOX(hbox1), btn_vbox, FALSE, FALSE, 0); + + btn_vbox1 = gtk_vbox_new(FALSE, 8); + gtk_widget_show(btn_vbox1); + gtk_box_pack_start(GTK_BOX(btn_vbox), btn_vbox1, TRUE, FALSE, 0); + + add_btn = gtk_button_new_with_label(_(" -> ")); + gtk_widget_show(add_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox1), add_btn, FALSE, FALSE, 0); + + remove_btn = gtk_button_new_with_label(_(" <- ")); + gtk_widget_show(remove_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox1), remove_btn, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(add_btn), "clicked", + G_CALLBACK(prefs_summary_column_add), NULL); + g_signal_connect(G_OBJECT(remove_btn), "clicked", + G_CALLBACK(prefs_summary_column_remove), NULL); + + clist_hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(clist_hbox); + gtk_box_pack_start(GTK_BOX(hbox1), clist_hbox, TRUE, TRUE, 0); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_set_size_request(scrolledwin, 180, 210); + gtk_widget_show(scrolledwin); + gtk_box_pack_start(GTK_BOX(clist_hbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Displayed items"); + shown_clist = gtk_clist_new_with_titles(1, title); + gtk_widget_show(shown_clist); + gtk_container_add(GTK_CONTAINER(scrolledwin), shown_clist); + gtk_clist_set_selection_mode(GTK_CLIST(shown_clist), + GTK_SELECTION_BROWSE); + gtk_clist_set_reorderable(GTK_CLIST(shown_clist), TRUE); + gtk_clist_set_use_drag_icons(GTK_CLIST(shown_clist), FALSE); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(shown_clist)->column[0].button, + GTK_CAN_FOCUS); + + /* up/down button */ + btn_vbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(btn_vbox); + gtk_box_pack_start(GTK_BOX(hbox1), btn_vbox, FALSE, FALSE, 0); + + btn_vbox1 = gtk_vbox_new(FALSE, 8); + gtk_widget_show(btn_vbox1); + gtk_box_pack_start(GTK_BOX(btn_vbox), btn_vbox1, TRUE, FALSE, 0); + + up_btn = gtk_button_new_with_label(_("Up")); + gtk_widget_show(up_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox1), up_btn, FALSE, FALSE, 0); + + down_btn = gtk_button_new_with_label(_("Down")); + gtk_widget_show(down_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox1), down_btn, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(up_btn), "clicked", + G_CALLBACK(prefs_summary_column_up), NULL); + g_signal_connect(G_OBJECT(down_btn), "clicked", + G_CALLBACK(prefs_summary_column_down), NULL); + + btn_hbox = gtk_hbox_new(FALSE, 8); + gtk_widget_show(btn_hbox); + gtk_box_pack_end(GTK_BOX(vbox), btn_hbox, FALSE, FALSE, 0); + + btn_vbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(btn_vbox); + gtk_box_pack_start(GTK_BOX(btn_hbox), btn_vbox, FALSE, FALSE, 0); + + default_btn = gtk_button_new_with_label(_(" Revert to default ")); + gtk_widget_show(default_btn); + gtk_box_pack_start(GTK_BOX(btn_vbox), default_btn, TRUE, FALSE, 0); + g_signal_connect(G_OBJECT(default_btn), "clicked", + G_CALLBACK(prefs_summary_column_set_to_default), NULL); + + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_widget_show(confirm_area); + gtk_box_pack_end(GTK_BOX(btn_hbox), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(prefs_summary_column_ok), NULL); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(prefs_summary_column_cancel), NULL); + + summary_col.window = window; + summary_col.stock_clist = stock_clist; + summary_col.shown_clist = shown_clist; + summary_col.add_btn = add_btn; + summary_col.remove_btn = remove_btn; + summary_col.up_btn = up_btn; + summary_col.down_btn = down_btn; + summary_col.ok_btn = ok_btn; + summary_col.cancel_btn = cancel_btn; +} + +SummaryColumnState *prefs_summary_column_get_config(void) +{ + static SummaryColumnState state[N_SUMMARY_COLS]; + SummaryColumnType type; + gint pos; + + for (pos = 0; pos < N_SUMMARY_COLS; pos++) + state[pos].type = -1; + + for (type = 0; type < N_SUMMARY_COLS; type++) { + pos = prefs_common.summary_col_pos[type]; + if (pos < 0 || pos >= N_SUMMARY_COLS || + state[pos].type != -1) { + g_warning("Wrong column position\n"); + prefs_summary_column_set_config(default_state); + return default_state; + } + + state[pos].type = type; + state[pos].visible = prefs_common.summary_col_visible[type]; + } + + return state; +} + +void prefs_summary_column_set_config(SummaryColumnState *state) +{ + SummaryColumnType type; + gint pos; + + for (pos = 0; pos < N_SUMMARY_COLS; pos++) { + type = state[pos].type; + prefs_common.summary_col_visible[type] = state[pos].visible; + prefs_common.summary_col_pos[type] = pos; + } +} + +static void prefs_summary_column_set_dialog(SummaryColumnState *state) +{ + GtkCList *stock_clist = GTK_CLIST(summary_col.stock_clist); + GtkCList *shown_clist = GTK_CLIST(summary_col.shown_clist); + gint pos; + SummaryColumnType type; + gchar *name; + + gtk_clist_clear(stock_clist); + gtk_clist_clear(shown_clist); + + if (!state) + state = prefs_summary_column_get_config(); + + for (pos = 0; pos < N_SUMMARY_COLS; pos++) { + gint row; + type = state[pos].type; + name = gettext(col_name[type]); + + if (state[pos].visible) { + row = gtk_clist_append(shown_clist, (gchar **)&name); + gtk_clist_set_row_data(shown_clist, row, + GINT_TO_POINTER(type)); + } else { + row = gtk_clist_append(stock_clist, (gchar **)&name); + gtk_clist_set_row_data(stock_clist, row, + GINT_TO_POINTER(type)); + } + } +} + +static void prefs_summary_column_set_view(void) +{ + GtkCList *stock_clist = GTK_CLIST(summary_col.stock_clist); + GtkCList *shown_clist = GTK_CLIST(summary_col.shown_clist); + SummaryColumnState state[N_SUMMARY_COLS]; + SummaryColumnType type; + gint row, pos = 0; + + g_return_if_fail + (stock_clist->rows + shown_clist->rows == N_SUMMARY_COLS); + + for (row = 0; row < stock_clist->rows; row++) { + type = GPOINTER_TO_INT + (gtk_clist_get_row_data(stock_clist, row)); + state[row].type = type; + state[row].visible = FALSE; + } + + pos = row; + for (row = 0; row < shown_clist->rows; row++) { + type = GPOINTER_TO_INT + (gtk_clist_get_row_data(shown_clist, row)); + state[pos + row].type = type; + state[pos + row].visible = TRUE; + } + + prefs_summary_column_set_config(state); + main_window_set_summary_column(); +} + +static void prefs_summary_column_add(void) +{ + GtkCList *stock_clist = GTK_CLIST(summary_col.stock_clist); + GtkCList *shown_clist = GTK_CLIST(summary_col.shown_clist); + gint row; + SummaryColumnType type; + gchar *name; + + if (!stock_clist->selection) return; + + row = GPOINTER_TO_INT(stock_clist->selection->data); + type = GPOINTER_TO_INT(gtk_clist_get_row_data(stock_clist, row)); + gtk_clist_remove(stock_clist, row); + if (stock_clist->rows == row) + gtk_clist_select_row(stock_clist, row - 1, -1); + + if (!shown_clist->selection) + row = 0; + else + row = GPOINTER_TO_INT(shown_clist->selection->data) + 1; + + name = gettext(col_name[type]); + row = gtk_clist_insert(shown_clist, row, (gchar **)&name); + gtk_clist_set_row_data(shown_clist, row, GINT_TO_POINTER(type)); + gtk_clist_select_row(shown_clist, row, -1); +} + +static void prefs_summary_column_remove(void) +{ + GtkCList *stock_clist = GTK_CLIST(summary_col.stock_clist); + GtkCList *shown_clist = GTK_CLIST(summary_col.shown_clist); + gint row; + SummaryColumnType type; + gchar *name; + + if (!shown_clist->selection) return; + + row = GPOINTER_TO_INT(shown_clist->selection->data); + type = GPOINTER_TO_INT(gtk_clist_get_row_data(shown_clist, row)); + gtk_clist_remove(shown_clist, row); + if (shown_clist->rows == row) + gtk_clist_select_row(shown_clist, row - 1, -1); + + if (!stock_clist->selection) + row = 0; + else + row = GPOINTER_TO_INT(stock_clist->selection->data) + 1; + + name = gettext(col_name[type]); + row = gtk_clist_insert(stock_clist, row, (gchar **)&name); + gtk_clist_set_row_data(stock_clist, row, GINT_TO_POINTER(type)); + gtk_clist_select_row(stock_clist, row, -1); +} + +static void prefs_summary_column_up(void) +{ + GtkCList *clist = GTK_CLIST(summary_col.shown_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row > 0) + gtk_clist_row_move(clist, row, row - 1); +} + +static void prefs_summary_column_down(void) +{ + GtkCList *clist = GTK_CLIST(summary_col.shown_clist); + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row >= 0 && row < clist->rows - 1) + gtk_clist_row_move(clist, row, row + 1); +} + +static void prefs_summary_column_set_to_default(void) +{ + prefs_summary_column_set_dialog(default_state); +} + +static void prefs_summary_column_ok(void) +{ + if (!summary_col.finished) { + summary_col.finished = TRUE; + prefs_summary_column_set_view(); + } +} + +static void prefs_summary_column_cancel(void) +{ + summary_col.finished = TRUE; +} + +static gint prefs_summary_column_delete_event(GtkWidget *widget, + GdkEventAny *event, + gpointer data) +{ + summary_col.finished = TRUE; + return TRUE; +} + +static gboolean prefs_summary_column_key_pressed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + summary_col.finished = TRUE; + return FALSE; +} diff --git a/src/prefs_summary_column.h b/src/prefs_summary_column.h new file mode 100644 index 00000000..3ea9101d --- /dev/null +++ b/src/prefs_summary_column.h @@ -0,0 +1,30 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_SUMMARY_COLUMN_H__ +#define __PREFS_SUMMARY_COLUMN_H__ + +#include "summaryview.h" + +void prefs_summary_column_open(void); + +SummaryColumnState *prefs_summary_column_get_config(void); +void prefs_summary_column_set_config(SummaryColumnState *state); + +#endif /* __PREFS_SUMMARY_COLUMN_H__ */ diff --git a/src/prefs_template.c b/src/prefs_template.c new file mode 100644 index 00000000..024378de --- /dev/null +++ b/src/prefs_template.c @@ -0,0 +1,533 @@ +/* + * Sylpheed templates subsystem + * Copyright (C) 2001 Alexander Barinov + * Copyright (C) 2001-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "template.h" +#include "main.h" +#include "inc.h" +#include "utils.h" +#include "gtkutils.h" +#include "alertpanel.h" +#include "manage_window.h" +#include "prefs_common.h" +#include "compose.h" +#include "addr_compl.h" +#include "quote_fmt.h" + +static struct Templates { + GtkWidget *window; + GtkWidget *ok_btn; + GtkWidget *clist_tmpls; + GtkWidget *entry_name; + GtkWidget *entry_to; + GtkWidget *entry_cc; + GtkWidget *entry_subject; + GtkWidget *text_value; +} templates; + +/* widget creating functions */ +static void prefs_template_window_create (void); +static void prefs_template_window_setup (void); +static void prefs_template_clear (void); + +static GSList *prefs_template_get_list (void); + +/* callbacks */ +static gint prefs_template_deleted_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static gboolean prefs_template_key_pressed_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static void prefs_template_cancel_cb (void); +static void prefs_template_ok_cb (void); +static void prefs_template_select_cb (GtkCList *clist, + gint row, + gint column, + GdkEvent *event); +static void prefs_template_register_cb (void); +static void prefs_template_substitute_cb (void); +static void prefs_template_delete_cb (void); + +/* Called from mainwindow.c */ +void prefs_template_open(void) +{ + inc_lock(); + + if (!templates.window) + prefs_template_window_create(); + + prefs_template_window_setup(); + gtk_widget_show(templates.window); +} + +#define ADD_ENTRY(entry, str, row) \ +{ \ + label1 = gtk_label_new(str); \ + gtk_widget_show(label1); \ + gtk_table_attach(GTK_TABLE(table), label1, 0, 1, row, (row + 1), \ + GTK_FILL, 0, 0, 0); \ + gtk_misc_set_alignment(GTK_MISC(label1), 1, 0.5); \ + \ + entry = gtk_entry_new(); \ + gtk_widget_show(entry); \ + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, row, (row + 1), \ + GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \ +} + +static void prefs_template_window_create(void) +{ + /* window structure ;) */ + GtkWidget *window; + GtkWidget *vpaned; + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *label1; + GtkWidget *entry_name; + GtkWidget *table; + GtkWidget *entry_to; + GtkWidget *entry_cc; + GtkWidget *entry_subject; + GtkWidget *scroll2; + GtkWidget *text_value; + GtkWidget *vbox2; + GtkWidget *hbox2; + GtkWidget *arrow1; + GtkWidget *hbox3; + GtkWidget *reg_btn; + GtkWidget *subst_btn; + GtkWidget *del_btn; + GtkWidget *desc_btn; + GtkWidget *scroll1; + GtkWidget *clist_tmpls; + GtkWidget *confirm_area; + GtkWidget *ok_btn; + GtkWidget *cancel_btn; + + gchar *title[1]; + + /* main window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + gtk_window_set_default_size(GTK_WINDOW(window), 400, -1); + + /* vpaned to separate template settings from templates list */ + vpaned = gtk_vpaned_new(); + gtk_widget_show(vpaned); + gtk_container_add(GTK_CONTAINER(window), vpaned); + + /* vbox to handle template name and content */ + vbox1 = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox1); + gtk_container_set_border_width(GTK_CONTAINER(vbox1), 8); + gtk_paned_pack1(GTK_PANED(vpaned), vbox1, FALSE, FALSE); + + hbox1 = gtk_hbox_new(FALSE, 8); + gtk_widget_show(hbox1); + gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, FALSE, 0); + + label1 = gtk_label_new(_("Template name")); + gtk_widget_show(label1); + gtk_box_pack_start(GTK_BOX(hbox1), label1, FALSE, FALSE, 0); + + entry_name = gtk_entry_new(); + gtk_widget_show(entry_name); + gtk_box_pack_start(GTK_BOX(hbox1), entry_name, TRUE, TRUE, 0); + + /* table for headers */ + table = gtk_table_new(3, 2, FALSE); + gtk_widget_show(table); + gtk_box_pack_start(GTK_BOX(vbox1), table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 4); + gtk_table_set_col_spacings(GTK_TABLE(table), 4); + + ADD_ENTRY(entry_to, _("To:"), 0); + address_completion_register_entry(GTK_ENTRY(entry_to)); + ADD_ENTRY(entry_cc, _("Cc:"), 1); + address_completion_register_entry(GTK_ENTRY(entry_cc)); + ADD_ENTRY(entry_subject, _("Subject:"), 2); + +#undef ADD_ENTRY + + /* template content */ + scroll2 = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(scroll2); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll2), + GTK_POLICY_NEVER, + GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(vbox1), scroll2, TRUE, TRUE, 0); + + text_value = gtk_text_view_new(); + gtk_widget_show(text_value); + gtk_widget_set_size_request(text_value, -1, 120); + gtk_container_add(GTK_CONTAINER(scroll2), text_value); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text_value), TRUE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_value), GTK_WRAP_WORD); + + /* vbox for buttons and templates list */ + vbox2 = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox2); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 8); + gtk_paned_pack2(GTK_PANED(vpaned), vbox2, TRUE, FALSE); + + /* register | substitute | delete */ + hbox2 = gtk_hbox_new(FALSE, 4); + gtk_widget_show(hbox2); + gtk_box_pack_start(GTK_BOX(vbox2), hbox2, FALSE, FALSE, 0); + + arrow1 = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_widget_show(arrow1); + gtk_box_pack_start(GTK_BOX(hbox2), arrow1, FALSE, FALSE, 0); + gtk_widget_set_size_request(arrow1, -1, 16); + + hbox3 = gtk_hbox_new(TRUE, 4); + gtk_widget_show(hbox3); + gtk_box_pack_start(GTK_BOX(hbox2), hbox3, FALSE, FALSE, 0); + + reg_btn = gtk_button_new_with_label(_("Register")); + gtk_widget_show(reg_btn); + gtk_box_pack_start(GTK_BOX(hbox3), reg_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT (reg_btn), "clicked", + G_CALLBACK (prefs_template_register_cb), NULL); + + subst_btn = gtk_button_new_with_label(_(" Substitute ")); + gtk_widget_show(subst_btn); + gtk_box_pack_start(GTK_BOX(hbox3), subst_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(subst_btn), "clicked", + G_CALLBACK(prefs_template_substitute_cb), NULL); + + del_btn = gtk_button_new_with_label(_("Delete")); + gtk_widget_show(del_btn); + gtk_box_pack_start(GTK_BOX(hbox3), del_btn, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(del_btn), "clicked", + G_CALLBACK(prefs_template_delete_cb), NULL); + + desc_btn = gtk_button_new_with_label(_(" Symbols ")); + gtk_widget_show(desc_btn); + gtk_box_pack_end(GTK_BOX(hbox2), desc_btn, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(desc_btn), "clicked", + G_CALLBACK(prefs_quote_description), NULL); + + /* templates list */ + scroll1 = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(scroll1); + gtk_box_pack_start(GTK_BOX(vbox2), scroll1, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll1), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + title[0] = _("Registered templates"); + clist_tmpls = gtk_clist_new_with_titles(1, title); + gtk_widget_show(clist_tmpls); + gtk_widget_set_size_request(scroll1, -1, 140); + gtk_container_add(GTK_CONTAINER(scroll1), clist_tmpls); + gtk_clist_set_column_width(GTK_CLIST(clist_tmpls), 0, 80); + gtk_clist_set_selection_mode(GTK_CLIST(clist_tmpls), + GTK_SELECTION_BROWSE); + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist_tmpls)->column[0].button, + GTK_CAN_FOCUS); + g_signal_connect(G_OBJECT (clist_tmpls), "select_row", + G_CALLBACK (prefs_template_select_cb), NULL); + + /* ok | cancel */ + gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"), + &cancel_btn, _("Cancel"), NULL, NULL); + gtk_widget_show(confirm_area); + gtk_box_pack_end(GTK_BOX(vbox2), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(ok_btn); + + gtk_window_set_title(GTK_WINDOW(window), _("Templates")); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(prefs_template_deleted_cb), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(prefs_template_key_pressed_cb), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + g_signal_connect(G_OBJECT(ok_btn), "clicked", + G_CALLBACK(prefs_template_ok_cb), NULL); + g_signal_connect(G_OBJECT(cancel_btn), "clicked", + G_CALLBACK(prefs_template_cancel_cb), NULL); + + address_completion_start(window); + + templates.window = window; + templates.ok_btn = ok_btn; + templates.clist_tmpls = clist_tmpls; + templates.entry_name = entry_name; + templates.entry_to = entry_to; + templates.entry_cc = entry_cc; + templates.entry_subject = entry_subject; + templates.text_value = text_value; +} + +static void prefs_template_window_setup(void) +{ + GtkCList *clist = GTK_CLIST(templates.clist_tmpls); + GSList *tmpl_list; + GSList *cur; + gchar *title[1]; + gint row; + Template *tmpl; + + manage_window_set_transient(GTK_WINDOW(templates.window)); + gtk_widget_grab_focus(templates.ok_btn); + + gtk_clist_freeze(clist); + gtk_clist_clear(clist); + + title[0] = _("(New)"); + row = gtk_clist_append(clist, title); + gtk_clist_set_row_data(clist, row, NULL); + + tmpl_list = template_read_config(); + + for (cur = tmpl_list; cur != NULL; cur = cur->next) { + tmpl = (Template *)cur->data; + title[0] = tmpl->name; + row = gtk_clist_append(clist, title); + gtk_clist_set_row_data(clist, row, tmpl); + } + + g_slist_free(tmpl_list); + + gtk_clist_thaw(clist); +} + +static void prefs_template_clear(void) +{ + Template *tmpl; + gint row = 1; + + while ((tmpl = gtk_clist_get_row_data + (GTK_CLIST(templates.clist_tmpls), row)) != NULL) { + template_free(tmpl); + row++; + } + + gtk_clist_clear(GTK_CLIST(templates.clist_tmpls)); +} + +static gint prefs_template_deleted_cb(GtkWidget *widget, GdkEventAny *event, + gpointer data) +{ + prefs_template_cancel_cb(); + return TRUE; +} + +static gboolean prefs_template_key_pressed_cb(GtkWidget *widget, + GdkEventKey *event, gpointer data) +{ + if (event && event->keyval == GDK_Escape) + prefs_template_cancel_cb(); + return FALSE; +} + +static void prefs_template_ok_cb(void) +{ + GSList *tmpl_list; + + tmpl_list = prefs_template_get_list(); + template_set_config(tmpl_list); + compose_reflect_prefs_all(); + gtk_clist_clear(GTK_CLIST(templates.clist_tmpls)); + gtk_widget_hide(templates.window); + inc_unlock(); +} + +static void prefs_template_cancel_cb(void) +{ + prefs_template_clear(); + gtk_widget_hide(templates.window); + inc_unlock(); +} + +static void prefs_template_select_cb(GtkCList *clist, gint row, gint column, + GdkEvent *event) +{ + Template *tmpl; + Template tmpl_def; + GtkTextBuffer *buffer; + GtkTextIter iter; + + tmpl_def.name = _("Template"); + tmpl_def.subject = ""; + tmpl_def.to = ""; + tmpl_def.cc = ""; + tmpl_def.value = ""; + + if (!(tmpl = gtk_clist_get_row_data(clist, row))) + tmpl = &tmpl_def; + + gtk_entry_set_text(GTK_ENTRY(templates.entry_name), tmpl->name); + gtk_entry_set_text(GTK_ENTRY(templates.entry_to), + tmpl->to ? tmpl->to : ""); + gtk_entry_set_text(GTK_ENTRY(templates.entry_cc), + tmpl->cc ? tmpl->cc : ""); + gtk_entry_set_text(GTK_ENTRY(templates.entry_subject), + tmpl->subject ? tmpl->subject : ""); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(templates.text_value)); + gtk_text_buffer_set_text(buffer, "", 0); + gtk_text_buffer_get_start_iter(buffer, &iter); + gtk_text_buffer_insert(buffer, &iter, tmpl->value, -1); +} + +static GSList *prefs_template_get_list(void) +{ + gint row = 1; + GSList *tmpl_list = NULL; + Template *tmpl; + + while ((tmpl = gtk_clist_get_row_data + (GTK_CLIST(templates.clist_tmpls), row)) != NULL) { + tmpl_list = g_slist_append(tmpl_list, tmpl); + row++; + } + + return tmpl_list; +} + +static gint prefs_template_clist_set_row(gint row) +{ + GtkCList *clist = GTK_CLIST(templates.clist_tmpls); + Template *tmpl; + Template *tmp_tmpl; + GtkTextBuffer *buffer; + GtkTextIter start, end; + gchar *name; + gchar *to; + gchar *cc; + gchar *subject; + gchar *value; + gchar *title[1]; + + g_return_val_if_fail(row != 0, -1); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(templates.text_value)); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_iter_at_offset(buffer, &end, -1); // end_iter? + value = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + + if (value && *value != '\0') { + gchar *parsed_buf; + MsgInfo dummyinfo; + + memset(&dummyinfo, 0, sizeof(MsgInfo)); + quote_fmt_init(&dummyinfo, NULL, NULL); + quote_fmt_scan_string(value); + quote_fmt_parse(); + parsed_buf = quote_fmt_get_buffer(); + if (!parsed_buf) { + alertpanel_error(_("Template format error.")); + g_free(value); + return -1; + } + } + + name = gtk_editable_get_chars(GTK_EDITABLE(templates.entry_name), + 0, -1); + subject = gtk_editable_get_chars(GTK_EDITABLE(templates.entry_subject), + 0, -1); + to = gtk_editable_get_chars(GTK_EDITABLE(templates.entry_to), 0, -1); + cc = gtk_editable_get_chars(GTK_EDITABLE(templates.entry_cc), 0, -1); + + if (subject && *subject == '\0') { + g_free(subject); + subject = NULL; + } + if (to && *to == '\0') { + g_free(to); + to = NULL; + } + + tmpl = g_new(Template, 1); + tmpl->name = name; + tmpl->to = to; + tmpl->cc = cc; + tmpl->subject = subject; + tmpl->value = value; + + title[0] = name; + + if (row < 0) { + row = gtk_clist_append(clist, title); + } else { + gtk_clist_set_text(clist, row, 0, name); + tmp_tmpl = gtk_clist_get_row_data(clist, row); + if (tmp_tmpl) + template_free(tmp_tmpl); + } + + gtk_clist_set_row_data(clist, row, tmpl); + return row; +} + +static void prefs_template_register_cb(void) +{ + prefs_template_clist_set_row(-1); +} + +static void prefs_template_substitute_cb(void) +{ + GtkCList *clist = GTK_CLIST(templates.clist_tmpls); + Template *tmpl; + gint row; + + if (!clist->selection) return; + + row = GPOINTER_TO_INT(clist->selection->data); + if (row == 0) return; + + tmpl = gtk_clist_get_row_data(clist, row); + if (!tmpl) return; + + prefs_template_clist_set_row(row); +} + +static void prefs_template_delete_cb(void) +{ + GtkCList *clist = GTK_CLIST(templates.clist_tmpls); + Template *tmpl; + gint row; + + if (!clist->selection) return; + row = GPOINTER_TO_INT(clist->selection->data); + if (row == 0) return; + + if (alertpanel(_("Delete template"), + _("Do you really want to delete this template?"), + _("Yes"), _("No"), NULL) == G_ALERTALTERNATE) + return; + + tmpl = gtk_clist_get_row_data(clist, row); + template_free(tmpl); + gtk_clist_remove(clist, row); +} diff --git a/src/prefs_template.h b/src/prefs_template.h new file mode 100644 index 00000000..c9e95516 --- /dev/null +++ b/src/prefs_template.h @@ -0,0 +1,25 @@ +/* + * Sylpheed templates subsystem + * Copyright (C) 2001 Alexander Barinov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PREFS_TEMPLATES_H__ +#define __PREFS_TEMPLATES_H__ + +void prefs_template_open(void); + +#endif /* __PREFS_TEMPLATES_H__ */ diff --git a/src/procheader.c b/src/procheader.c new file mode 100644 index 00000000..9a18bf31 --- /dev/null +++ b/src/procheader.c @@ -0,0 +1,764 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "procheader.h" +#include "procmsg.h" +#include "codeconv.h" +#include "prefs_common.h" +#include "utils.h" + +#define BUFFSIZE 8192 + +gint procheader_get_one_field(gchar *buf, gint len, FILE *fp, + HeaderEntry hentry[]) +{ + gint nexthead; + gint hnum = 0; + HeaderEntry *hp = NULL; + + if (hentry != NULL) { + /* skip non-required headers */ + do { + do { + if (fgets(buf, len, fp) == NULL) + return -1; + if (buf[0] == '\r' || buf[0] == '\n') + return -1; + } while (buf[0] == ' ' || buf[0] == '\t'); + + for (hp = hentry, hnum = 0; hp->name != NULL; + hp++, hnum++) { + if (!strncasecmp(hp->name, buf, + strlen(hp->name))) + break; + } + } while (hp->name == NULL); + } else { + if (fgets(buf, len, fp) == NULL) return -1; + if (buf[0] == '\r' || buf[0] == '\n') return -1; + } + + /* unfold the specified folded line */ + if (hp && hp->unfold) { + gboolean folded = FALSE; + gchar *bufp = buf + strlen(buf); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + while (1) { + nexthead = fgetc(fp); + + /* folded */ + if (nexthead == ' ' || nexthead == '\t') + folded = TRUE; + else if (nexthead == EOF) + break; + else if (folded == TRUE) { + if ((len - (bufp - buf)) <= 2) break; + + if (nexthead == '\n') { + folded = FALSE; + continue; + } + + /* replace return code on the tail end + with space */ + *bufp++ = ' '; + *bufp++ = nexthead; + *bufp = '\0'; + + /* concatenate next line */ + if (fgets(bufp, len - (bufp - buf), fp) + == NULL) break; + bufp += strlen(bufp); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + folded = FALSE; + } else { + ungetc(nexthead, fp); + break; + } + } + + return hnum; + } + + while (1) { + nexthead = fgetc(fp); + if (nexthead == ' ' || nexthead == '\t') { + size_t buflen = strlen(buf); + + /* concatenate next line */ + if ((len - buflen) > 2) { + gchar *p = buf + buflen; + + *p++ = nexthead; + *p = '\0'; + buflen++; + if (fgets(p, len - buflen, fp) == NULL) + break; + } else + break; + } else { + if (nexthead != EOF) + ungetc(nexthead, fp); + break; + } + } + + /* remove trailing return code */ + strretchomp(buf); + + return hnum; +} + +gchar *procheader_get_unfolded_line(gchar *buf, gint len, FILE *fp) +{ + gboolean folded = FALSE; + gint nexthead; + gchar *bufp; + + if (fgets(buf, len, fp) == NULL) return NULL; + if (buf[0] == '\r' || buf[0] == '\n') return NULL; + bufp = buf + strlen(buf); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + while (1) { + nexthead = fgetc(fp); + + /* folded */ + if (nexthead == ' ' || nexthead == '\t') + folded = TRUE; + else if (nexthead == EOF) + break; + else if (folded == TRUE) { + if ((len - (bufp - buf)) <= 2) break; + + if (nexthead == '\n') { + folded = FALSE; + continue; + } + + /* replace return code on the tail end + with space */ + *bufp++ = ' '; + *bufp++ = nexthead; + *bufp = '\0'; + + /* concatenate next line */ + if (fgets(bufp, len - (bufp - buf), fp) + == NULL) break; + bufp += strlen(bufp); + + for (; bufp > buf && + (*(bufp - 1) == '\n' || *(bufp - 1) == '\r'); + bufp--) + *(bufp - 1) = '\0'; + + folded = FALSE; + } else { + ungetc(nexthead, fp); + break; + } + } + + /* remove trailing return code */ + strretchomp(buf); + + return buf; +} + +GSList *procheader_get_header_list_from_file(const gchar *file) +{ + FILE *fp; + GSList *hlist; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + hlist = procheader_get_header_list(fp); + + fclose(fp); + return hlist; +} + +GSList *procheader_get_header_list(FILE *fp) +{ + gchar buf[BUFFSIZE], tmp[BUFFSIZE]; + gchar *p; + GSList *hlist = NULL; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + while (*p == ' ' || *p == '\t') p++; + conv_unmime_header(tmp, sizeof(tmp), p, NULL); + header->body = g_strdup(tmp); + + hlist = g_slist_append(hlist, header); + break; + } + } + } + + return hlist; +} + +GSList *procheader_add_header_list(GSList *hlist, const gchar *header_name, + const gchar *body) +{ + Header *header; + + g_return_val_if_fail(header_name != NULL, hlist); + + header = g_new(Header, 1); + header->name = g_strdup(header_name); + header->body = g_strdup(body); + + return g_slist_append(hlist, header); +} + +GSList *procheader_merge_header_list(GSList *hlist1, GSList *hlist2) +{ + GSList *cur; + + for (cur = hlist2; cur != NULL; cur = cur->next) { + Header *header = (Header *)cur->data; + if (procheader_find_header_list(hlist1, header->name) < 0) + hlist1 = g_slist_append(hlist1, header); + } + + return hlist1; +} + +gint procheader_find_header_list(GSList *hlist, const gchar *header_name) +{ + GSList *cur; + gint index = 0; + Header *header; + + g_return_val_if_fail(header_name != NULL, -1); + + for (cur = hlist; cur != NULL; cur = cur->next, index++) { + header = (Header *)cur->data; + if (g_strcasecmp(header->name, header_name) == 0) + return index; + } + + return -1; +} + +GPtrArray *procheader_get_header_array(FILE *fp) +{ + gchar buf[BUFFSIZE], tmp[BUFFSIZE]; + gchar *p; + GPtrArray *headers; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = g_ptr_array_new(); + + while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + while (*p == ' ' || *p == '\t') p++; + conv_unmime_header(tmp, sizeof(tmp), p, NULL); + header->body = g_strdup(tmp); + + g_ptr_array_add(headers, header); + break; + } + } + } + + return headers; +} + +GPtrArray *procheader_get_header_array_asis(FILE *fp) +{ + gchar buf[BUFFSIZE], tmp[BUFFSIZE]; + gchar *p; + GPtrArray *headers; + Header *header; + + g_return_val_if_fail(fp != NULL, NULL); + + headers = g_ptr_array_new(); + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (*buf == ':') continue; + for (p = buf; *p && *p != ' '; p++) { + if (*p == ':') { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf); + p++; + conv_unmime_header(tmp, sizeof(tmp), p, NULL); + header->body = g_strdup(tmp); + + g_ptr_array_add(headers, header); + break; + } + } + } + + return headers; +} + +void procheader_header_list_destroy(GSList *hlist) +{ + Header *header; + + while (hlist != NULL) { + header = hlist->data; + procheader_header_free(header); + hlist = g_slist_remove(hlist, header); + } +} + +void procheader_header_array_destroy(GPtrArray *harray) +{ + gint i; + Header *header; + + for (i = 0; i < harray->len; i++) { + header = g_ptr_array_index(harray, i); + procheader_header_free(header); + } + + g_ptr_array_free(harray, TRUE); +} + +void procheader_header_free(Header *header) +{ + if (!header) return; + + g_free(header->name); + g_free(header->body); + g_free(header); +} + +void procheader_get_header_fields(FILE *fp, HeaderEntry hentry[]) +{ + gchar buf[BUFFSIZE]; + HeaderEntry *hp; + gint hnum; + gchar *p; + + if (hentry == NULL) return; + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = hentry + hnum; + + p = buf + strlen(hp->name); + while (*p == ' ' || *p == '\t') p++; + + if (hp->body == NULL) + hp->body = g_strdup(p); + else if (!strcasecmp(hp->name, "To:") || + !strcasecmp(hp->name, "Cc:")) { + gchar *tp = hp->body; + hp->body = g_strconcat(tp, ", ", p, NULL); + g_free(tp); + } + } +} + +MsgInfo *procheader_parse_file(const gchar *file, MsgFlags flags, + gboolean full) +{ + struct stat s; + FILE *fp; + MsgInfo *msginfo; + + if (stat(file, &s) < 0) { + FILE_OP_ERROR(file, "stat"); + return NULL; + } + if (!S_ISREG(s.st_mode)) + return NULL; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + msginfo = procheader_parse_stream(fp, flags, full); + fclose(fp); + + if (msginfo) { + msginfo->size = s.st_size; + msginfo->mtime = s.st_mtime; + } + + return msginfo; +} + +MsgInfo *procheader_parse_str(const gchar *str, MsgFlags flags, gboolean full) +{ + FILE *fp; + MsgInfo *msginfo; + + if ((fp = str_open_as_stream(str)) == NULL) + return NULL; + + msginfo = procheader_parse_stream(fp, flags, full); + fclose(fp); + return msginfo; +} + +enum +{ + H_DATE = 0, + H_FROM = 1, + H_TO = 2, + H_NEWSGROUPS = 3, + H_SUBJECT = 4, + H_MSG_ID = 5, + H_REFERENCES = 6, + H_IN_REPLY_TO = 7, + H_CONTENT_TYPE = 8, + H_SEEN = 9, + H_CC = 10, + H_X_FACE = 11 +}; + +MsgInfo *procheader_parse_stream(FILE *fp, MsgFlags flags, gboolean full) +{ + static HeaderEntry hentry_full[] = {{"Date:", NULL, FALSE}, + {"From:", NULL, TRUE}, + {"To:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Subject:", NULL, TRUE}, + {"Message-Id:", NULL, FALSE}, + {"References:", NULL, FALSE}, + {"In-Reply-To:", NULL, FALSE}, + {"Content-Type:", NULL, FALSE}, + {"Seen:", NULL, FALSE}, + {"Cc:", NULL, TRUE}, + {"X-Face:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + static HeaderEntry hentry_short[] = {{"Date:", NULL, FALSE}, + {"From:", NULL, TRUE}, + {"To:", NULL, TRUE}, + {"Newsgroups:", NULL, TRUE}, + {"Subject:", NULL, TRUE}, + {"Message-Id:", NULL, FALSE}, + {"References:", NULL, FALSE}, + {"In-Reply-To:", NULL, FALSE}, + {"Content-Type:", NULL, FALSE}, + {"Seen:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + + MsgInfo *msginfo; + gchar buf[BUFFSIZE], tmp[BUFFSIZE]; + gchar *reference = NULL; + gchar *p; + gchar *hp; + HeaderEntry *hentry; + gint hnum; + + hentry = full ? hentry_full : hentry_short; + + if (MSG_IS_QUEUED(flags)) { + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + msginfo = g_new0(MsgInfo, 1); + msginfo->flags = flags; + msginfo->inreplyto = NULL; + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = buf + strlen(hentry[hnum].name); + while (*hp == ' ' || *hp == '\t') hp++; + + switch (hnum) { + case H_DATE: + if (msginfo->date) break; + msginfo->date_t = + procheader_date_parse(NULL, hp, 0); + msginfo->date = g_strdup(hp); + break; + case H_FROM: + if (msginfo->from) break; + conv_unmime_header(tmp, sizeof(tmp), hp, NULL); + msginfo->from = g_strdup(tmp); + msginfo->fromname = procheader_get_fromname(tmp); + break; + case H_TO: + conv_unmime_header(tmp, sizeof(tmp), hp, NULL); + if (msginfo->to) { + p = msginfo->to; + msginfo->to = + g_strconcat(p, ", ", tmp, NULL); + g_free(p); + } else + msginfo->to = g_strdup(tmp); + break; + case H_NEWSGROUPS: + if (msginfo->newsgroups) { + p = msginfo->newsgroups; + msginfo->newsgroups = + g_strconcat(p, ",", hp, NULL); + g_free(p); + } else + msginfo->newsgroups = g_strdup(buf + 12); + break; + case H_SUBJECT: + if (msginfo->subject) break; + conv_unmime_header(tmp, sizeof(tmp), hp, NULL); + msginfo->subject = g_strdup(tmp); + break; + case H_MSG_ID: + if (msginfo->msgid) break; + + extract_parenthesis(hp, '<', '>'); + remove_space(hp); + msginfo->msgid = g_strdup(hp); + break; + case H_REFERENCES: + case H_IN_REPLY_TO: + if (!reference) { + eliminate_parenthesis(hp, '(', ')'); + if ((p = strrchr(hp, '<')) != NULL && + strchr(p + 1, '>') != NULL) { + extract_parenthesis(p, '<', '>'); + remove_space(p); + if (*p != '\0') + reference = g_strdup(p); + } + } + break; + case H_CONTENT_TYPE: + if (!strncasecmp(hp, "multipart", 9)) + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MIME); + break; + case H_SEEN: + /* mnews Seen header */ + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW|MSG_UNREAD); + break; + case H_CC: + conv_unmime_header(tmp, sizeof(tmp), hp, NULL); + if (msginfo->cc) { + p = msginfo->cc; + msginfo->cc = + g_strconcat(p, ", ", tmp, NULL); + g_free(p); + } else + msginfo->cc = g_strdup(tmp); + break; + case H_X_FACE: + if (msginfo->xface) break; + msginfo->xface = g_strdup(hp); + break; + default: + break; + } + } + msginfo->inreplyto = reference; + + return msginfo; +} + +gchar *procheader_get_fromname(const gchar *str) +{ + gchar *tmp, *name; + + Xstrdup_a(tmp, str, return NULL); + + if (*tmp == '\"') { + extract_quote(tmp, '\"'); + g_strstrip(tmp); + } else if (strchr(tmp, '<')) { + eliminate_parenthesis(tmp, '<', '>'); + g_strstrip(tmp); + if (*tmp == '\0') { + strcpy(tmp, str); + extract_parenthesis(tmp, '<', '>'); + g_strstrip(tmp); + } + } else if (strchr(tmp, '(')) { + extract_parenthesis(tmp, '(', ')'); + g_strstrip(tmp); + } + + if (*tmp == '\0') + name = g_strdup(str); + else + name = g_strdup(tmp); + + return name; +} + +static gint procheader_scan_date_string(const gchar *str, + gchar *weekday, gint *day, + gchar *month, gint *year, + gint *hh, gint *mm, gint *ss, + gchar *zone) +{ + gint result; + + result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result == 8) return 0; + + result = sscanf(str, "%3s,%d %9s %d %2d:%2d:%2d %5s", + weekday, day, month, year, hh, mm, ss, zone); + if (result == 8) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d:%2d %5s", + day, month, year, hh, mm, ss, zone); + if (result == 7) return 0; + + *zone = '\0'; + result = sscanf(str, "%10s %d %9s %d %2d:%2d:%2d", + weekday, day, month, year, hh, mm, ss); + if (result == 7) return 0; + + *ss = 0; + result = sscanf(str, "%10s %d %9s %d %2d:%2d %5s", + weekday, day, month, year, hh, mm, zone); + if (result == 7) return 0; + + result = sscanf(str, "%d %9s %d %2d:%2d %5s", + day, month, year, hh, mm, zone); + if (result == 6) return 0; + + return -1; +} + +time_t procheader_date_parse(gchar *dest, const gchar *src, gint len) +{ + static gchar monthstr[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + gchar weekday[11]; + gint day; + gchar month[10]; + gint year; + gint hh, mm, ss; + gchar zone[6]; + GDateMonth dmonth = G_DATE_BAD_MONTH; + struct tm t; + gchar *p; + time_t timer; + time_t tz_offset; + + if (procheader_scan_date_string(src, weekday, &day, month, &year, + &hh, &mm, &ss, zone) < 0) { + g_warning("Invalid date: %s\n", src); + if (dest && len > 0) + strncpy2(dest, src, len); + return 0; + } + + /* Y2K compliant :) */ + if (year < 1000) { + if (year < 50) + year += 2000; + else + year += 1900; + } + + month[3] = '\0'; + for (p = monthstr; *p != '\0'; p += 3) { + if (!strncasecmp(p, month, 3)) { + dmonth = (gint)(p - monthstr) / 3 + 1; + break; + } + } + if (*p == '\0') + g_warning("Invalid month: %s\n", month); + + t.tm_sec = ss; + t.tm_min = mm; + t.tm_hour = hh; + t.tm_mday = day; + t.tm_mon = dmonth - 1; + t.tm_year = year - 1900; + t.tm_wday = 0; + t.tm_yday = 0; + t.tm_isdst = -1; + + timer = mktime(&t); + tz_offset = remote_tzoffset_sec(zone); + if (tz_offset != -1) + timer += tzoffset_sec(&timer) - tz_offset; + + if (dest) + procheader_date_get_localtime(dest, len, timer); + + return timer; +} + +void procheader_date_get_localtime(gchar *dest, gint len, const time_t timer) +{ + struct tm *lt; + gchar *default_format = "%y/%m/%d(%a) %H:%M"; + gchar *tmp; + + Xalloca(tmp, len + 1, dest[0] = '\0'; return;); + + lt = localtime(&timer); + + if (prefs_common.date_format) + strftime(tmp, len, prefs_common.date_format, lt); + else + strftime(tmp, len, default_format, lt); + + conv_localetodisp(dest, len, tmp); +} diff --git a/src/procheader.h b/src/procheader.h new file mode 100644 index 00000000..f03a97be --- /dev/null +++ b/src/procheader.h @@ -0,0 +1,91 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PROCHEADER_H__ +#define __PROCHEADER_H__ + +#include +#include +#include + +#include "procmsg.h" + +typedef struct _HeaderEntry HeaderEntry; +typedef struct _Header Header; + +struct _HeaderEntry +{ + gchar *name; + gchar *body; + gboolean unfold; +}; + +struct _Header +{ + gchar *name; + gchar *body; +}; + +gint procheader_get_one_field (gchar *buf, + gint len, + FILE *fp, + HeaderEntry hentry[]); +gchar *procheader_get_unfolded_line (gchar *buf, + gint len, + FILE *fp); + +GSList *procheader_get_header_list_from_file (const gchar *file); +GSList *procheader_get_header_list (FILE *fp); +GSList *procheader_add_header_list (GSList *hlist, + const gchar *header_name, + const gchar *body); +GSList *procheader_merge_header_list (GSList *hlist1, + GSList *hlist2); +gint procheader_find_header_list (GSList *hlist, + const gchar *header_name); +void procheader_header_list_destroy (GSList *hlist); + +GPtrArray *procheader_get_header_array (FILE *fp); +GPtrArray *procheader_get_header_array_asis (FILE *fp); +void procheader_header_array_destroy (GPtrArray *harray); + +void procheader_header_free (Header *header); + +void procheader_get_header_fields (FILE *fp, + HeaderEntry hentry[]); +MsgInfo *procheader_parse_file (const gchar *file, + MsgFlags flags, + gboolean full); +MsgInfo *procheader_parse_str (const gchar *str, + MsgFlags flags, + gboolean full); +MsgInfo *procheader_parse_stream (FILE *fp, + MsgFlags flags, + gboolean full); + +gchar *procheader_get_fromname (const gchar *str); + +time_t procheader_date_parse (gchar *dest, + const gchar *src, + gint len); +void procheader_date_get_localtime (gchar *dest, + gint len, + const time_t timer); + +#endif /* __PROCHEADER_H__ */ diff --git a/src/procmime.c b/src/procmime.c new file mode 100644 index 00000000..eb6225df --- /dev/null +++ b/src/procmime.c @@ -0,0 +1,1128 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "intl.h" +#include "procmime.h" +#include "procheader.h" +#include "base64.h" +#include "quoted-printable.h" +#include "uuencode.h" +#include "unmime.h" +#include "html.h" +#include "codeconv.h" +#include "utils.h" +#include "prefs_common.h" + +#if USE_GPGME +# include "rfc2015.h" +#endif + +static GHashTable *procmime_get_mime_type_table (void); +static GList *procmime_get_mime_type_list (const gchar *file); + + +MimeInfo *procmime_mimeinfo_new(void) +{ + MimeInfo *mimeinfo; + + mimeinfo = g_new0(MimeInfo, 1); + mimeinfo->mime_type = MIME_UNKNOWN; + mimeinfo->encoding_type = ENC_UNKNOWN; + + return mimeinfo; +} + +void procmime_mimeinfo_free_all(MimeInfo *mimeinfo) +{ + while (mimeinfo != NULL) { + MimeInfo *next; + + g_free(mimeinfo->encoding); + g_free(mimeinfo->content_type); + g_free(mimeinfo->charset); + g_free(mimeinfo->name); + g_free(mimeinfo->boundary); + g_free(mimeinfo->content_disposition); + g_free(mimeinfo->filename); +#if USE_GPGME + g_free(mimeinfo->plaintextfile); + g_free(mimeinfo->sigstatus); + g_free(mimeinfo->sigstatus_full); +#endif + + procmime_mimeinfo_free_all(mimeinfo->sub); + procmime_mimeinfo_free_all(mimeinfo->children); +#if USE_GPGME + procmime_mimeinfo_free_all(mimeinfo->plaintext); +#endif + + next = mimeinfo->next; + g_free(mimeinfo); + mimeinfo = next; + } +} + +MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo) +{ + MimeInfo *child = parent->children; + + if (!child) + parent->children = mimeinfo; + else { + while (child->next != NULL) + child = child->next; + + child->next = mimeinfo; + } + + mimeinfo->parent = parent; + mimeinfo->level = parent->level + 1; + + return mimeinfo; +} + +void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new) +{ + MimeInfo *parent = old->parent; + MimeInfo *child; + + g_return_if_fail(parent != NULL); + g_return_if_fail(new->next == NULL); + + for (child = parent->children; child && child != old; + child = child->next) + ; + if (!child) { + g_warning("oops: parent can't find it's own child"); + return; + } + procmime_mimeinfo_free_all(old); + + if (child == parent->children) { + new->next = parent->children->next; + parent->children = new; + } else { + new->next = child->next; + child = new; + } +} + +MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo) +{ + if (!mimeinfo) return NULL; + + if (mimeinfo->children) + return mimeinfo->children; + if (mimeinfo->sub) + return mimeinfo->sub; + if (mimeinfo->next) + return mimeinfo->next; + + if (mimeinfo->main) { + mimeinfo = mimeinfo->main; + if (mimeinfo->next) + return mimeinfo->next; + } + + for (mimeinfo = mimeinfo->parent; mimeinfo != NULL; + mimeinfo = mimeinfo->parent) { + if (mimeinfo->next) + return mimeinfo->next; + if (mimeinfo->main) { + mimeinfo = mimeinfo->main; + if (mimeinfo->next) + return mimeinfo->next; + } + } + + return NULL; +} + +#if 0 +void procmime_dump_mimeinfo(MimeInfo *mimeinfo) +{ + gint i; + + g_print("\n"); + + for (; mimeinfo != NULL; mimeinfo = procmime_mimeinfo_next(mimeinfo)) { + for (i = 0; i < mimeinfo->level; i++) + g_print(" "); + g_print("%s%s\n", mimeinfo->main ? "sub: " : "", + mimeinfo->content_type); + } +} +#endif + +MimeInfo *procmime_scan_message(MsgInfo *msginfo) +{ + FILE *fp; + MimeInfo *mimeinfo; + + g_return_val_if_fail(msginfo != NULL, NULL); + +#if USE_GPGME + if ((fp = procmsg_open_message_decrypted(msginfo, &mimeinfo)) == NULL) + return NULL; +#else + if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL; + mimeinfo = procmime_scan_mime_header(fp); +#endif + + if (mimeinfo) { + mimeinfo->size = get_left_file_size(fp); + if (mimeinfo->mime_type == MIME_MULTIPART || + mimeinfo->mime_type == MIME_MESSAGE_RFC822) + procmime_scan_multipart_message(mimeinfo, fp); + } + + fclose(fp); + + return mimeinfo; +} + +void procmime_scan_multipart_message(MimeInfo *mimeinfo, FILE *fp) +{ + gchar *p; + gchar *boundary; + gint boundary_len = 0; + gchar buf[BUFFSIZE]; + glong fpos, prev_fpos; + + g_return_if_fail(mimeinfo != NULL); + g_return_if_fail(mimeinfo->mime_type == MIME_MULTIPART || + mimeinfo->mime_type == MIME_MESSAGE_RFC822); + + if (mimeinfo->mime_type == MIME_MULTIPART) { + g_return_if_fail(mimeinfo->boundary != NULL); + g_return_if_fail(mimeinfo->sub == NULL); + } + g_return_if_fail(fp != NULL); + + boundary = mimeinfo->boundary; + + if (boundary) { + boundary_len = strlen(boundary); + + /* look for first boundary */ + while ((p = fgets(buf, sizeof(buf), fp)) != NULL) + if (IS_BOUNDARY(buf, boundary, boundary_len)) break; + if (!p) return; + } else if (mimeinfo->parent && mimeinfo->parent->boundary) { + boundary = mimeinfo->parent->boundary; + boundary_len = strlen(boundary); + } + + if ((fpos = ftell(fp)) < 0) { + perror("ftell"); + return; + } + + for (;;) { + MimeInfo *partinfo; + gboolean eom = FALSE; + gint len; + + prev_fpos = fpos; + debug_print("prev_fpos: %ld\n", fpos); + + if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + MimeInfo *sub; + + mimeinfo->sub = sub = procmime_scan_mime_header(fp); + if (!sub) break; + + sub->level = mimeinfo->level + 1; + sub->parent = mimeinfo->parent; + sub->main = mimeinfo; + + partinfo = sub; + } else { + partinfo = procmime_scan_mime_header(fp); + if (!partinfo) break; + procmime_mimeinfo_insert(mimeinfo, partinfo); + debug_print("content-type: %s\n", + partinfo->content_type); + } + + if (partinfo->mime_type == MIME_MULTIPART || + partinfo->mime_type == MIME_MESSAGE_RFC822) { + if (partinfo->level < 8) + procmime_scan_multipart_message(partinfo, fp); + } + + /* look for next boundary */ + buf[0] = '\0'; + while ((p = fgets(buf, sizeof(buf), fp)) != NULL) { + if (IS_BOUNDARY(buf, boundary, boundary_len)) { + if (buf[2 + boundary_len] == '-' && + buf[2 + boundary_len + 1] == '-') + eom = TRUE; + break; + } + } + if (p == NULL) { + /* broken MIME, or single part MIME message */ + buf[0] = '\0'; + eom = TRUE; + } + debug_print("boundary: %s\n", buf); + + fpos = ftell(fp); + debug_print("fpos: %ld\n", fpos); + + len = strlen(buf); + partinfo->size = fpos - prev_fpos - len; + debug_print("partinfo->size: %d\n", partinfo->size); + if (partinfo->sub && !partinfo->sub->sub && + !partinfo->sub->children) { + partinfo->sub->size = + fpos - partinfo->sub->fpos - strlen(buf); + debug_print("partinfo->sub->size: %d\n", + partinfo->sub->size); + } + + if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + if (len > 0 && fseek(fp, fpos - len, SEEK_SET) < 0) + perror("fseek"); + break; + } + + if (eom) break; + } +} + +void procmime_scan_encoding(MimeInfo *mimeinfo, const gchar *encoding) +{ + gchar *buf; + + Xstrdup_a(buf, encoding, return); + + g_free(mimeinfo->encoding); + + mimeinfo->encoding = g_strdup(g_strstrip(buf)); + if (!strcasecmp(buf, "7bit")) + mimeinfo->encoding_type = ENC_7BIT; + else if (!strcasecmp(buf, "8bit")) + mimeinfo->encoding_type = ENC_8BIT; + else if (!strcasecmp(buf, "quoted-printable")) + mimeinfo->encoding_type = ENC_QUOTED_PRINTABLE; + else if (!strcasecmp(buf, "base64")) + mimeinfo->encoding_type = ENC_BASE64; + else if (!strcasecmp(buf, "x-uuencode")) + mimeinfo->encoding_type = ENC_X_UUENCODE; + else + mimeinfo->encoding_type = ENC_UNKNOWN; + +} + +void procmime_scan_content_type(MimeInfo *mimeinfo, const gchar *content_type) +{ + gchar *delim, *p, *cnttype; + gchar *buf; + + if (conv_get_locale_charset() == C_EUC_JP && + strchr(content_type, '\033')) { + gint len; + len = strlen(content_type) * 2 + 1; + Xalloca(buf, len, return); + conv_jistoeuc(buf, len, content_type); + } else + Xstrdup_a(buf, content_type, return); + + g_free(mimeinfo->content_type); + g_free(mimeinfo->charset); + g_free(mimeinfo->name); + mimeinfo->content_type = NULL; + mimeinfo->charset = NULL; + mimeinfo->name = NULL; + + if ((delim = strchr(buf, ';'))) *delim = '\0'; + mimeinfo->content_type = cnttype = g_strdup(g_strstrip(buf)); + + mimeinfo->mime_type = procmime_scan_mime_type(cnttype); + + if (!delim) return; + p = delim + 1; + + for (;;) { + gchar *eq; + gchar *attr, *value; + + if ((delim = strchr(p, ';'))) *delim = '\0'; + + if (!(eq = strchr(p, '='))) break; + + *eq = '\0'; + attr = p; + g_strstrip(attr); + value = eq + 1; + g_strstrip(value); + + if (*value == '"') + extract_quote(value, '"'); + else { + eliminate_parenthesis(value, '(', ')'); + g_strstrip(value); + } + + if (*value) { + if (!strcasecmp(attr, "charset")) + mimeinfo->charset = g_strdup(value); + else if (!strcasecmp(attr, "name")) { + gchar *tmp; + size_t len; + + len = strlen(value) + 1; + Xalloca(tmp, len, return); + conv_unmime_header(tmp, len, value, NULL); + mimeinfo->name = g_strdup(tmp); + } else if (!strcasecmp(attr, "boundary")) + mimeinfo->boundary = g_strdup(value); + } + + if (!delim) break; + p = delim + 1; + } + + if (mimeinfo->mime_type == MIME_MULTIPART && !mimeinfo->boundary) + mimeinfo->mime_type = MIME_TEXT; +} + +void procmime_scan_content_disposition(MimeInfo *mimeinfo, + const gchar *content_disposition) +{ + gchar *delim, *p, *dispos; + gchar *buf; + + if (conv_get_locale_charset() == C_EUC_JP && + strchr(content_disposition, '\033')) { + gint len; + len = strlen(content_disposition) * 2 + 1; + Xalloca(buf, len, return); + conv_jistoeuc(buf, len, content_disposition); + } else + Xstrdup_a(buf, content_disposition, return); + + if ((delim = strchr(buf, ';'))) *delim = '\0'; + mimeinfo->content_disposition = dispos = g_strdup(g_strstrip(buf)); + + if (!delim) return; + p = delim + 1; + + for (;;) { + gchar *eq; + gchar *attr, *value; + + if ((delim = strchr(p, ';'))) *delim = '\0'; + + if (!(eq = strchr(p, '='))) break; + + *eq = '\0'; + attr = p; + g_strstrip(attr); + value = eq + 1; + g_strstrip(value); + + if (*value == '"') + extract_quote(value, '"'); + else { + eliminate_parenthesis(value, '(', ')'); + g_strstrip(value); + } + + if (*value) { + if (!strcasecmp(attr, "filename")) { + gchar *tmp; + size_t len; + + len = strlen(value) + 1; + Xalloca(tmp, len, return); + conv_unmime_header(tmp, len, value, NULL); + g_free(mimeinfo->filename); + mimeinfo->filename = g_strdup(tmp); + break; + } + } + + if (!delim) break; + p = delim + 1; + } +} + +enum +{ + H_CONTENT_TRANSFER_ENCODING = 0, + H_CONTENT_TYPE = 1, + H_CONTENT_DISPOSITION = 2 +}; + +MimeInfo *procmime_scan_mime_header(FILE *fp) +{ + static HeaderEntry hentry[] = {{"Content-Transfer-Encoding:", + NULL, FALSE}, + {"Content-Type:", NULL, TRUE}, + {"Content-Disposition:", + NULL, TRUE}, + {NULL, NULL, FALSE}}; + gchar buf[BUFFSIZE]; + gint hnum; + HeaderEntry *hp; + MimeInfo *mimeinfo; + + g_return_val_if_fail(fp != NULL, NULL); + + mimeinfo = procmime_mimeinfo_new(); + mimeinfo->mime_type = MIME_TEXT; + mimeinfo->encoding_type = ENC_7BIT; + mimeinfo->fpos = ftell(fp); + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) + != -1) { + hp = hentry + hnum; + + if (H_CONTENT_TRANSFER_ENCODING == hnum) { + procmime_scan_encoding + (mimeinfo, buf + strlen(hp->name)); + } else if (H_CONTENT_TYPE == hnum) { + procmime_scan_content_type + (mimeinfo, buf + strlen(hp->name)); + } else if (H_CONTENT_DISPOSITION == hnum) { + procmime_scan_content_disposition + (mimeinfo, buf + strlen(hp->name)); + } + } + + if (mimeinfo->mime_type == MIME_APPLICATION_OCTET_STREAM && + mimeinfo->name) { + const gchar *type; + type = procmime_get_mime_type(mimeinfo->name); + if (type) + mimeinfo->mime_type = procmime_scan_mime_type(type); + } + + if (!mimeinfo->content_type) + mimeinfo->content_type = g_strdup("text/plain"); + + return mimeinfo; +} + +FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo) +{ + gchar buf[BUFFSIZE]; + gchar *boundary = NULL; + gint boundary_len = 0; + gboolean tmp_file = FALSE; + + g_return_val_if_fail(infp != NULL, NULL); + g_return_val_if_fail(mimeinfo != NULL, NULL); + + if (!outfp) { + outfp = my_tmpfile(); + if (!outfp) { + perror("tmpfile"); + return NULL; + } + tmp_file = TRUE; + } + + if (mimeinfo->parent && mimeinfo->parent->boundary) { + boundary = mimeinfo->parent->boundary; + boundary_len = strlen(boundary); + } + + if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) { + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + gint len; + len = qp_decode_line(buf); + fwrite(buf, len, 1, outfp); + } + } else if (mimeinfo->encoding_type == ENC_BASE64) { + gchar outbuf[BUFFSIZE]; + gint len; + Base64Decoder *decoder; + gboolean uncanonicalize = FALSE; + FILE *tmpfp = outfp; + + if (mimeinfo->mime_type == MIME_TEXT || + mimeinfo->mime_type == MIME_TEXT_HTML || + mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + uncanonicalize = TRUE; + tmpfp = my_tmpfile(); + if (!tmpfp) { + perror("tmpfile"); + if (tmp_file) fclose(outfp); + return NULL; + } + } + + decoder = base64_decoder_new(); + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + len = base64_decoder_decode(decoder, buf, outbuf); + if (len < 0) { + g_warning("Bad BASE64 content\n"); + break; + } + fwrite(outbuf, sizeof(gchar), len, tmpfp); + } + base64_decoder_free(decoder); + + if (uncanonicalize) { + rewind(tmpfp); + while (fgets(buf, sizeof(buf), tmpfp) != NULL) { + strcrchomp(buf); + fputs(buf, outfp); + } + fclose(tmpfp); + } + } else if (mimeinfo->encoding_type == ENC_X_UUENCODE) { + gchar outbuf[BUFFSIZE]; + gint len; + gboolean flag = FALSE; + + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + if(!flag && strncmp(buf,"begin ", 6)) continue; + + if (flag) { + len = fromuutobits(outbuf, buf); + if (len <= 0) { + if (len < 0) + g_warning("Bad UUENCODE content(%d)\n", len); + break; + } + fwrite(outbuf, sizeof(gchar), len, outfp); + } else + flag = TRUE; + } + } else { + while (fgets(buf, sizeof(buf), infp) != NULL && + (!boundary || + !IS_BOUNDARY(buf, boundary, boundary_len))) { + fputs(buf, outfp); + } + } + + if (tmp_file) rewind(outfp); + return outfp; +} + +gint procmime_get_part(const gchar *outfile, const gchar *infile, + MimeInfo *mimeinfo) +{ + FILE *infp, *outfp; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(outfile != NULL, -1); + g_return_val_if_fail(infile != NULL, -1); + g_return_val_if_fail(mimeinfo != NULL, -1); + + if ((infp = fopen(infile, "rb")) == NULL) { + FILE_OP_ERROR(infile, "fopen"); + return -1; + } + if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) { + FILE_OP_ERROR(infile, "fseek"); + fclose(infp); + return -1; + } + if ((outfp = fopen(outfile, "wb")) == NULL) { + FILE_OP_ERROR(outfile, "fopen"); + fclose(infp); + return -1; + } + + while (fgets(buf, sizeof(buf), infp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + + procmime_decode_content(outfp, infp, mimeinfo); + + fclose(infp); + if (fclose(outfp) == EOF) { + FILE_OP_ERROR(outfile, "fclose"); + unlink(outfile); + return -1; + } + + return 0; +} + +FILE *procmime_get_text_content(MimeInfo *mimeinfo, FILE *infp) +{ + FILE *tmpfp, *outfp; + gchar *src_codeset; + gboolean conv_fail = FALSE; + gchar buf[BUFFSIZE]; + gchar *str; + + g_return_val_if_fail(mimeinfo != NULL, NULL); + g_return_val_if_fail(infp != NULL, NULL); + g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT || + mimeinfo->mime_type == MIME_TEXT_HTML, NULL); + + if (fseek(infp, mimeinfo->fpos, SEEK_SET) < 0) { + perror("fseek"); + return NULL; + } + + while (fgets(buf, sizeof(buf), infp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + + tmpfp = procmime_decode_content(NULL, infp, mimeinfo); + if (!tmpfp) + return NULL; + + if ((outfp = my_tmpfile()) == NULL) { + perror("tmpfile"); + fclose(tmpfp); + return NULL; + } + + src_codeset = prefs_common.force_charset + ? prefs_common.force_charset : mimeinfo->charset; + + if (mimeinfo->mime_type == MIME_TEXT) { + while (fgets(buf, sizeof(buf), tmpfp) != NULL) { + str = conv_codeset_strdup(buf, src_codeset, NULL); + if (str) { + fputs(str, outfp); + g_free(str); + } else { + conv_fail = TRUE; + fputs(buf, outfp); + } + } + } else if (mimeinfo->mime_type == MIME_TEXT_HTML) { + HTMLParser *parser; + CodeConverter *conv; + + conv = conv_code_converter_new(src_codeset); + parser = html_parser_new(tmpfp, conv); + while ((str = html_parse(parser)) != NULL) { + fputs(str, outfp); + } + html_parser_destroy(parser); + conv_code_converter_destroy(conv); + } + + if (conv_fail) + g_warning(_("procmime_get_text_content(): Code conversion failed.\n")); + + fclose(tmpfp); + rewind(outfp); + + return outfp; +} + +/* search the first text part of (multipart) MIME message, + decode, convert it and output to outfp. */ +FILE *procmime_get_first_text_content(MsgInfo *msginfo) +{ + FILE *infp, *outfp = NULL; + MimeInfo *mimeinfo, *partinfo; + + g_return_val_if_fail(msginfo != NULL, NULL); + + mimeinfo = procmime_scan_message(msginfo); + if (!mimeinfo) return NULL; + + if ((infp = procmsg_open_message(msginfo)) == NULL) { + procmime_mimeinfo_free_all(mimeinfo); + return NULL; + } + + partinfo = mimeinfo; + while (partinfo && partinfo->mime_type != MIME_TEXT) + partinfo = procmime_mimeinfo_next(partinfo); + if (!partinfo) { + partinfo = mimeinfo; + while (partinfo && partinfo->mime_type != MIME_TEXT_HTML) + partinfo = procmime_mimeinfo_next(partinfo); + } + + if (partinfo) + outfp = procmime_get_text_content(partinfo, infp); + + fclose(infp); + procmime_mimeinfo_free_all(mimeinfo); + + return outfp; +} + +gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename, + const gchar *str, StrFindFunc find_func) +{ + + FILE *infp, *outfp; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(mimeinfo != NULL, FALSE); + g_return_val_if_fail(mimeinfo->mime_type == MIME_TEXT || + mimeinfo->mime_type == MIME_TEXT_HTML, FALSE); + g_return_val_if_fail(str != NULL, FALSE); + g_return_val_if_fail(find_func != NULL, FALSE); + + if ((infp = fopen(filename, "rb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + return FALSE; + } + + outfp = procmime_get_text_content(mimeinfo, infp); + fclose(infp); + + if (!outfp) + return FALSE; + + while (fgets(buf, sizeof(buf), outfp) != NULL) { + strretchomp(buf); + if (find_func(buf, str)) { + fclose(outfp); + return TRUE; + } + } + + fclose(outfp); + + return FALSE; +} + +gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str, + StrFindFunc find_func) +{ + MimeInfo *mimeinfo; + MimeInfo *partinfo; + gchar *filename; + gboolean found = FALSE; + + g_return_val_if_fail(msginfo != NULL, FALSE); + g_return_val_if_fail(str != NULL, FALSE); + g_return_val_if_fail(find_func != NULL, FALSE); + + filename = procmsg_get_message_file(msginfo); + if (!filename) return FALSE; + mimeinfo = procmime_scan_message(msginfo); + + for (partinfo = mimeinfo; partinfo != NULL; + partinfo = procmime_mimeinfo_next(partinfo)) { + if (partinfo->mime_type == MIME_TEXT || + partinfo->mime_type == MIME_TEXT_HTML) { + if (procmime_find_string_part + (partinfo, filename, str, find_func) == TRUE) { + found = TRUE; + break; + } + } + } + + procmime_mimeinfo_free_all(mimeinfo); + g_free(filename); + + return found; +} + +gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo) +{ + static guint32 id = 0; + gchar *base; + gchar *filename; + gchar f_prefix[10]; + + g_return_val_if_fail(mimeinfo != NULL, NULL); + + g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++); + + if (MIME_TEXT_HTML == mimeinfo->mime_type) + base = "mimetmp.html"; + else { + const gchar *base_; + base_ = mimeinfo->filename ? mimeinfo->filename + : mimeinfo->name ? mimeinfo->name : "mimetmp"; + base_ = g_basename(base_); + if (*base_ == '\0') base_ = "mimetmp"; + Xstrdup_a(base, base_, return NULL); + subst_for_filename(base); + } + + filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S, + f_prefix, base, NULL); + + return filename; +} + +ContentType procmime_scan_mime_type(const gchar *mime_type) +{ + ContentType type; + + if (!strncasecmp(mime_type, "text/html", 9)) + type = MIME_TEXT_HTML; + else if (!strncasecmp(mime_type, "text/", 5)) + type = MIME_TEXT; + else if (!strncasecmp(mime_type, "message/rfc822", 14)) + type = MIME_MESSAGE_RFC822; + else if (!strncasecmp(mime_type, "message/", 8)) + type = MIME_TEXT; + else if (!strncasecmp(mime_type, "application/octet-stream", 24)) + type = MIME_APPLICATION_OCTET_STREAM; + else if (!strncasecmp(mime_type, "application/", 12)) + type = MIME_APPLICATION; + else if (!strncasecmp(mime_type, "multipart/", 10)) + type = MIME_MULTIPART; + else if (!strncasecmp(mime_type, "image/", 6)) + type = MIME_IMAGE; + else if (!strncasecmp(mime_type, "audio/", 6)) + type = MIME_AUDIO; + else if (!strcasecmp(mime_type, "text")) + type = MIME_TEXT; + else + type = MIME_UNKNOWN; + + return type; +} + +static GList *mime_type_list = NULL; + +gchar *procmime_get_mime_type(const gchar *filename) +{ + static GHashTable *mime_type_table = NULL; + MimeType *mime_type; + const gchar *p; + gchar *ext; + + if (!mime_type_table) { + mime_type_table = procmime_get_mime_type_table(); + if (!mime_type_table) return NULL; + } + + filename = g_basename(filename); + p = strrchr(filename, '.'); + if (!p) return NULL; + + Xstrdup_a(ext, p + 1, return NULL); + g_strdown(ext); + mime_type = g_hash_table_lookup(mime_type_table, ext); + if (mime_type) { + gchar *str; + + str = g_strconcat(mime_type->type, "/", mime_type->sub_type, + NULL); + return str; + } + + return NULL; +} + +static GHashTable *procmime_get_mime_type_table(void) +{ + GHashTable *table = NULL; + GList *cur; + MimeType *mime_type; + gchar **exts; + + if (!mime_type_list) { + GList *list; + gchar *dir; + + mime_type_list = + procmime_get_mime_type_list(SYSCONFDIR "/mime.types"); + if (!mime_type_list) { + list = procmime_get_mime_type_list("/etc/mime.types"); + mime_type_list = g_list_concat(mime_type_list, list); + } + dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, RC_DIR, + G_DIR_SEPARATOR_S, "mime.types", NULL); + list = procmime_get_mime_type_list(dir); + g_free(dir); + mime_type_list = g_list_concat(mime_type_list, list); + + if (!mime_type_list) { + g_warning("mime.types not found\n"); + return NULL; + } + } + + table = g_hash_table_new(g_str_hash, g_str_equal); + + for (cur = mime_type_list; cur != NULL; cur = cur->next) { + gint i; + gchar *key; + + mime_type = (MimeType *)cur->data; + + if (!mime_type->extension) continue; + + exts = g_strsplit(mime_type->extension, " ", 16); + for (i = 0; exts[i] != NULL; i++) { + /* make the key case insensitive */ + g_strdown(exts[i]); + /* use previously dup'd key on overwriting */ + if (g_hash_table_lookup(table, exts[i])) + key = exts[i]; + else + key = g_strdup(exts[i]); + g_hash_table_insert(table, key, mime_type); + } + g_strfreev(exts); + } + + return table; +} + +static GList *procmime_get_mime_type_list(const gchar *file) +{ + GList *list = NULL; + FILE *fp; + gchar buf[BUFFSIZE]; + guchar *p; + gchar *delim; + MimeType *mime_type; + + if ((fp = fopen(file, "rb")) == NULL) return NULL; + + debug_print("Reading %s ...\n", file); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + p = strchr(buf, '#'); + if (p) *p = '\0'; + g_strstrip(buf); + + p = buf; + while (*p && !isspace(*p)) p++; + if (*p) { + *p = '\0'; + p++; + } + delim = strchr(buf, '/'); + if (delim == NULL) continue; + *delim = '\0'; + + mime_type = g_new(MimeType, 1); + mime_type->type = g_strdup(buf); + mime_type->sub_type = g_strdup(delim + 1); + + while (*p && isspace(*p)) p++; + if (*p) + mime_type->extension = g_strdup(p); + else + mime_type->extension = NULL; + + list = g_list_append(list, mime_type); + } + + fclose(fp); + + if (!list) + g_warning("Can't read mime.types\n"); + + return list; +} + +EncodingType procmime_get_encoding_for_charset(const gchar *charset) +{ + if (!charset) + return ENC_8BIT; + else if (!strncasecmp(charset, "ISO-2022-", 9) || + !strcasecmp(charset, "US-ASCII")) + return ENC_7BIT; + else if (!strcasecmp(charset, "ISO-8859-5") || + !strncasecmp(charset, "KOI8-", 5) || + !strcasecmp(charset, "Windows-1251")) + return ENC_8BIT; + else if (!strncasecmp(charset, "ISO-8859-", 9)) + return ENC_QUOTED_PRINTABLE; + else + return ENC_8BIT; +} + +EncodingType procmime_get_encoding_for_text_file(const gchar *file) +{ + FILE *fp; + guchar buf[BUFFSIZE]; + size_t len; + size_t octet_chars = 0; + size_t total_len = 0; + gfloat octet_percentage; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return ENC_UNKNOWN; + } + + while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) { + guchar *p; + gint i; + + for (p = buf, i = 0; i < len; ++p, ++i) { + if (*p & 0x80) + ++octet_chars; + } + total_len += len; + } + + fclose(fp); + + if (total_len > 0) + octet_percentage = (gfloat)octet_chars / (gfloat)total_len; + else + octet_percentage = 0.0; + + debug_print("procmime_get_encoding_for_text_file(): " + "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len, + 100.0 * octet_percentage); + + if (octet_percentage > 0.20) { + debug_print("using BASE64\n"); + return ENC_BASE64; + } else if (octet_chars > 0) { + debug_print("using quoted-printable\n"); + return ENC_QUOTED_PRINTABLE; + } else { + debug_print("using 7bit\n"); + return ENC_7BIT; + } +} + +const gchar *procmime_get_encoding_str(EncodingType encoding) +{ + static const gchar *encoding_str[] = { + "7bit", "8bit", "quoted-printable", "base64", "x-uuencode", + NULL + }; + + if (encoding >= ENC_7BIT && encoding <= ENC_UNKNOWN) + return encoding_str[encoding]; + else + return NULL; +} diff --git a/src/procmime.h b/src/procmime.h new file mode 100644 index 00000000..c52d4bda --- /dev/null +++ b/src/procmime.h @@ -0,0 +1,176 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PROCMIME_H__ +#define __PROCMIME_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct _MimeType MimeType; +typedef struct _MimeInfo MimeInfo; + +#include "procmsg.h" +#include "utils.h" + +typedef enum +{ + ENC_7BIT, + ENC_8BIT, + ENC_QUOTED_PRINTABLE, + ENC_BASE64, + ENC_X_UUENCODE, + ENC_UNKNOWN +} EncodingType; + +typedef enum +{ + MIME_TEXT, + MIME_TEXT_HTML, + MIME_MESSAGE_RFC822, + MIME_APPLICATION, + MIME_APPLICATION_OCTET_STREAM, + MIME_MULTIPART, + MIME_IMAGE, + MIME_AUDIO, + MIME_UNKNOWN +} ContentType; + +struct _MimeType +{ + gchar *type; + gchar *sub_type; + + gchar *extension; +}; + +/* + * An example of MimeInfo structure: + * + * multipart/mixed root <-+ parent + * | + * multipart/alternative children <-+ parent + * | + * text/plain children --+ + * | + * text/html next <-+ + * + * message/rfc822 next <-+ main + * | + * sub (capsulated message) + * + * image/jpeg next + */ + +struct _MimeInfo +{ + gchar *encoding; + + EncodingType encoding_type; + ContentType mime_type; + + gchar *content_type; + gchar *charset; + gchar *name; + gchar *boundary; + + gchar *content_disposition; + gchar *filename; + + glong fpos; + guint size; + + MimeInfo *main; + MimeInfo *sub; + + MimeInfo *next; + MimeInfo *parent; + MimeInfo *children; + +#if USE_GPGME + MimeInfo *plaintext; + gchar *plaintextfile; + gchar *sigstatus; + gchar *sigstatus_full; +#endif + + gint level; +}; + +#define IS_BOUNDARY(s, bnd, len) \ + (bnd && s[0] == '-' && s[1] == '-' && !strncmp(s + 2, bnd, len)) + +/* MimeInfo handling */ + +MimeInfo *procmime_mimeinfo_new (void); +void procmime_mimeinfo_free_all (MimeInfo *mimeinfo); + +MimeInfo *procmime_mimeinfo_insert (MimeInfo *parent, + MimeInfo *mimeinfo); +void procmime_mimeinfo_replace (MimeInfo *old, + MimeInfo *new); + +MimeInfo *procmime_mimeinfo_next (MimeInfo *mimeinfo); + +MimeInfo *procmime_scan_message (MsgInfo *msginfo); +void procmime_scan_multipart_message (MimeInfo *mimeinfo, + FILE *fp); + +/* scan headers */ + +void procmime_scan_encoding (MimeInfo *mimeinfo, + const gchar *encoding); +void procmime_scan_content_type (MimeInfo *mimeinfo, + const gchar *content_type); +void procmime_scan_content_disposition (MimeInfo *mimeinfo, + const gchar *content_disposition); +MimeInfo *procmime_scan_mime_header (FILE *fp); + +FILE *procmime_decode_content (FILE *outfp, + FILE *infp, + MimeInfo *mimeinfo); +gint procmime_get_part (const gchar *outfile, + const gchar *infile, + MimeInfo *mimeinfo); +FILE *procmime_get_text_content (MimeInfo *mimeinfo, + FILE *infp); +FILE *procmime_get_first_text_content (MsgInfo *msginfo); + +gboolean procmime_find_string_part (MimeInfo *mimeinfo, + const gchar *filename, + const gchar *str, + StrFindFunc find_func); +gboolean procmime_find_string (MsgInfo *msginfo, + const gchar *str, + StrFindFunc find_func); + +gchar *procmime_get_tmp_file_name (MimeInfo *mimeinfo); + +ContentType procmime_scan_mime_type (const gchar *mime_type); +gchar *procmime_get_mime_type (const gchar *filename); + +EncodingType procmime_get_encoding_for_charset (const gchar *charset); +EncodingType procmime_get_encoding_for_text_file(const gchar *file); +const gchar *procmime_get_encoding_str (EncodingType encoding); + +#endif /* __PROCMIME_H__ */ diff --git a/src/procmsg.c b/src/procmsg.c new file mode 100644 index 00000000..0328d47a --- /dev/null +++ b/src/procmsg.c @@ -0,0 +1,1519 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "utils.h" +#include "procmsg.h" +#include "procheader.h" +#include "account.h" +#include "send_message.h" +#include "procmime.h" +#include "statusbar.h" +#include "prefs_filter.h" +#include "filter.h" +#include "folder.h" +#if USE_GPGME +# include "rfc2015.h" +#endif + +static void mark_sum_func (gpointer key, + gpointer value, + gpointer data); + +static GHashTable *procmsg_read_mark_file (FolderItem *item); +static void procmsg_write_mark_file (FolderItem *item, + GHashTable *mark_table); + +static FILE *procmsg_open_data_file (const gchar *file, + guint version, + DataOpenMode mode, + gchar *buf, + size_t buf_size); +static FILE *procmsg_open_cache_file_with_buffer(FolderItem *item, + DataOpenMode mode, + gchar *buf, + size_t buf_size); + +static gint procmsg_cmp_by_mark (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_unread (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_mime (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_label (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_number (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_size (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_date (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_from (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_to (gconstpointer a, + gconstpointer b); +static gint procmsg_cmp_by_subject (gconstpointer a, + gconstpointer b); + + +GHashTable *procmsg_msg_hash_table_create(GSList *mlist) +{ + GHashTable *msg_table; + + if (mlist == NULL) return NULL; + + msg_table = g_hash_table_new(NULL, g_direct_equal); + procmsg_msg_hash_table_append(msg_table, mlist); + + return msg_table; +} + +void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + + if (msg_table == NULL || mlist == NULL) return; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + g_hash_table_insert(msg_table, + GUINT_TO_POINTER(msginfo->msgnum), + msginfo); + } +} + +GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist) +{ + GHashTable *msg_table; + GSList *cur; + MsgInfo *msginfo; + + if (mlist == NULL) return NULL; + + msg_table = g_hash_table_new(NULL, g_direct_equal); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + g_hash_table_insert(msg_table, msginfo->to_folder, msginfo); + } + + return msg_table; +} + +static gint procmsg_read_cache_data_str(FILE *fp, gchar **str) +{ + gchar buf[BUFFSIZE]; + gint ret = 0; + guint32 len; + + if (fread(&len, sizeof(len), 1, fp) == 1) { + if (len > G_MAXINT) + ret = -1; + else { + gchar *tmp = NULL; + + while (len > 0) { + size_t size = MIN(len, BUFFSIZE - 1); + + if (fread(buf, size, 1, fp) != 1) { + ret = -1; + if (tmp) g_free(tmp); + *str = NULL; + break; + } + + buf[size] = '\0'; + if (tmp) { + *str = g_strconcat(tmp, buf, NULL); + g_free(tmp); + tmp = *str; + } else + tmp = *str = g_strdup(buf); + + len -= size; + } + } + } else + ret = -1; + + if (ret < 0) + g_warning("Cache data is corrupted\n"); + + return ret; +} + +#define READ_CACHE_DATA(data, fp) \ +{ \ + if (procmsg_read_cache_data_str(fp, &data) < 0) { \ + procmsg_msginfo_free(msginfo); \ + procmsg_msg_list_free(mlist); \ + mlist = NULL; \ + break; \ + } \ +} + +#define READ_CACHE_DATA_INT(n, fp) \ +{ \ + guint32 idata; \ + \ + if (fread(&idata, sizeof(idata), 1, fp) != 1) { \ + g_warning("Cache data is corrupted\n"); \ + procmsg_msginfo_free(msginfo); \ + procmsg_msg_list_free(mlist); \ + mlist = NULL; \ + break; \ + } else \ + n = idata; \ +} + +GSList *procmsg_read_cache(FolderItem *item, gboolean scan_file) +{ + GSList *mlist = NULL; + GSList *last = NULL; + FILE *fp; + MsgInfo *msginfo; + MsgFlags default_flags; + gchar file_buf[BUFFSIZE]; + guint32 num; + FolderType type; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->folder != NULL, NULL); + type = FOLDER_TYPE(item->folder); + + default_flags.perm_flags = MSG_NEW|MSG_UNREAD; + default_flags.tmp_flags = 0; + if (type == F_MH || type == F_IMAP) { + if (item->stype == F_QUEUE) { + MSG_SET_TMP_FLAGS(default_flags, MSG_QUEUED); + } else if (item->stype == F_DRAFT) { + MSG_SET_TMP_FLAGS(default_flags, MSG_DRAFT); + } + } + if (type == F_IMAP) { + MSG_SET_TMP_FLAGS(default_flags, MSG_IMAP); + } else if (type == F_NEWS) { + MSG_SET_TMP_FLAGS(default_flags, MSG_NEWS); + } + + if (type == F_MH) { + gchar *path; + + path = folder_item_get_path(item); + if (change_dir(path) < 0) { + g_free(path); + return NULL; + } + g_free(path); + } + + if ((fp = procmsg_open_cache_file_with_buffer + (item, DATA_READ, file_buf, sizeof(file_buf))) == NULL) + return NULL; + + debug_print("Reading summary cache..."); + + while (fread(&num, sizeof(num), 1, fp) == 1) { + msginfo = g_new0(MsgInfo, 1); + msginfo->msgnum = num; + READ_CACHE_DATA_INT(msginfo->size, fp); + READ_CACHE_DATA_INT(msginfo->mtime, fp); + READ_CACHE_DATA_INT(msginfo->date_t, fp); + READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp); + + READ_CACHE_DATA(msginfo->fromname, fp); + + READ_CACHE_DATA(msginfo->date, fp); + READ_CACHE_DATA(msginfo->from, fp); + READ_CACHE_DATA(msginfo->to, fp); + READ_CACHE_DATA(msginfo->newsgroups, fp); + READ_CACHE_DATA(msginfo->subject, fp); + READ_CACHE_DATA(msginfo->msgid, fp); + READ_CACHE_DATA(msginfo->inreplyto, fp); + + MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags); + MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags); + + /* if the message file doesn't exist or is changed, + don't add the data */ + if (type == F_MH && scan_file && + folder_item_is_msg_changed(item, msginfo)) + procmsg_msginfo_free(msginfo); + else { + msginfo->folder = item; + + if (!mlist) + last = mlist = g_slist_append(NULL, msginfo); + else { + last = g_slist_append(last, msginfo); + last = last->next; + } + } + } + + fclose(fp); + + debug_print("done.\n"); + + return mlist; +} + +#undef READ_CACHE_DATA +#undef READ_CACHE_DATA_INT + +static void mark_unset_new_func(gpointer key, gpointer value, gpointer data) +{ + MSG_UNSET_PERM_FLAGS(*((MsgFlags *)value), MSG_NEW); +} + +void procmsg_set_flags(GSList *mlist, FolderItem *item) +{ + GSList *cur; + gint new = 0, unread = 0, total = 0; + gint lastnum = 0; + gint unflagged = 0; + gboolean mark_queue_exist; + MsgInfo *msginfo; + GHashTable *mark_table; + MsgFlags *flags; + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + debug_print("Marking the messages...\n"); + + mark_queue_exist = (item->mark_queue != NULL); + mark_table = procmsg_read_mark_file(item); + if (!mark_table) { + item->new = item->unread = item->total = g_slist_length(mlist); + item->updated = TRUE; + return; + } + + /* unset new flags if new (unflagged) messages exist */ + if (!mark_queue_exist) { + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + flags = g_hash_table_lookup + (mark_table, GUINT_TO_POINTER(msginfo->msgnum)); + if (!flags) { + g_hash_table_foreach(mark_table, + mark_unset_new_func, NULL); + break; + } + } + } + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + + if (lastnum < msginfo->msgnum) + lastnum = msginfo->msgnum; + + flags = g_hash_table_lookup + (mark_table, GUINT_TO_POINTER(msginfo->msgnum)); + + if (flags != NULL) { + /* add the permanent flags only */ + msginfo->flags.perm_flags = flags->perm_flags; + if (MSG_IS_NEW(*flags)) + ++new; + if (MSG_IS_UNREAD(*flags)) + ++unread; + if (FOLDER_TYPE(item->folder) == F_IMAP) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_IMAP); + } else if (FOLDER_TYPE(item->folder) == F_NEWS) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_NEWS); + } + } else { + ++unflagged; + ++new; + ++unread; + } + + ++total; + } + + item->new = new; + item->unread = unread; + item->total = total; + item->unmarked_num = unflagged; + item->last_num = lastnum; + item->updated = TRUE; + + debug_print("new: %d unread: %d unflagged: %d total: %d\n", + new, unread, unflagged, total); + + hash_free_value_mem(mark_table); + g_hash_table_destroy(mark_table); +} + +static FolderSortType cmp_func_sort_type; + +GSList *procmsg_sort_msg_list(GSList *mlist, FolderSortKey sort_key, + FolderSortType sort_type) +{ + GCompareFunc cmp_func; + + switch (sort_key) { + case SORT_BY_MARK: + cmp_func = procmsg_cmp_by_mark; break; + case SORT_BY_UNREAD: + cmp_func = procmsg_cmp_by_unread; break; + case SORT_BY_MIME: + cmp_func = procmsg_cmp_by_mime; break; + case SORT_BY_LABEL: + cmp_func = procmsg_cmp_by_label; break; + case SORT_BY_NUMBER: + cmp_func = procmsg_cmp_by_number; break; + case SORT_BY_SIZE: + cmp_func = procmsg_cmp_by_size; break; + case SORT_BY_DATE: + cmp_func = procmsg_cmp_by_date; break; + case SORT_BY_FROM: + cmp_func = procmsg_cmp_by_from; break; + case SORT_BY_SUBJECT: + cmp_func = procmsg_cmp_by_subject; break; + case SORT_BY_TO: + cmp_func = procmsg_cmp_by_to; break; + default: + return mlist; + } + + cmp_func_sort_type = sort_type; + + mlist = g_slist_sort(mlist, cmp_func); + + return mlist; +} + +gint procmsg_get_last_num_in_msg_list(GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + gint last = 0; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (msginfo && msginfo->msgnum > last) + last = msginfo->msgnum; + } + + return last; +} + +void procmsg_msg_list_free(GSList *mlist) +{ + GSList *cur; + MsgInfo *msginfo; + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + procmsg_msginfo_free(msginfo); + } + g_slist_free(mlist); +} + +void procmsg_write_cache(MsgInfo *msginfo, FILE *fp) +{ + MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK; + + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(msginfo->size, fp); + WRITE_CACHE_DATA_INT(msginfo->mtime, fp); + WRITE_CACHE_DATA_INT(msginfo->date_t, fp); + WRITE_CACHE_DATA_INT(flags, fp); + + WRITE_CACHE_DATA(msginfo->fromname, fp); + + WRITE_CACHE_DATA(msginfo->date, fp); + WRITE_CACHE_DATA(msginfo->from, fp); + WRITE_CACHE_DATA(msginfo->to, fp); + WRITE_CACHE_DATA(msginfo->newsgroups, fp); + WRITE_CACHE_DATA(msginfo->subject, fp); + WRITE_CACHE_DATA(msginfo->msgid, fp); + WRITE_CACHE_DATA(msginfo->inreplyto, fp); +} + +void procmsg_write_flags(MsgInfo *msginfo, FILE *fp) +{ + MsgPermFlags flags = msginfo->flags.perm_flags; + + WRITE_CACHE_DATA_INT(msginfo->msgnum, fp); + WRITE_CACHE_DATA_INT(flags, fp); +} + +void procmsg_flush_mark_queue(FolderItem *item, FILE *fp) +{ + MsgInfo *flaginfo; + + g_return_if_fail(item != NULL); + g_return_if_fail(fp != NULL); + + if (item->mark_queue) + debug_print("flushing mark_queue...\n"); + + while (item->mark_queue != NULL) { + flaginfo = (MsgInfo *)item->mark_queue->data; + procmsg_write_flags(flaginfo, fp); + procmsg_msginfo_free(flaginfo); + item->mark_queue = g_slist_remove(item->mark_queue, flaginfo); + } +} + +void procmsg_add_mark_queue(FolderItem *item, gint num, MsgFlags flags) +{ + MsgInfo *queue_msginfo; + + queue_msginfo = g_new0(MsgInfo, 1); + queue_msginfo->msgnum = num; + queue_msginfo->flags = flags; + item->mark_queue = g_slist_append + (item->mark_queue, queue_msginfo); + return; +} + +void procmsg_add_flags(FolderItem *item, gint num, MsgFlags flags) +{ + FILE *fp; + MsgInfo msginfo; + + g_return_if_fail(item != NULL); + + if (item->opened) { + procmsg_add_mark_queue(item, num, flags); + return; + } + + if ((fp = procmsg_open_mark_file(item, DATA_APPEND)) == NULL) { + g_warning(_("can't open mark file\n")); + return; + } + + msginfo.msgnum = num; + msginfo.flags = flags; + + procmsg_write_flags(&msginfo, fp); + fclose(fp); +} + +struct MarkSum { + gint *new; + gint *unread; + gint *total; + gint *min; + gint *max; + gint first; +}; + +static void mark_sum_func(gpointer key, gpointer value, gpointer data) +{ + MsgFlags *flags = value; + gint num = GPOINTER_TO_INT(key); + struct MarkSum *marksum = data; + + if (marksum->first <= num) { + if (MSG_IS_NEW(*flags)) (*marksum->new)++; + if (MSG_IS_UNREAD(*flags)) (*marksum->unread)++; + if (num > *marksum->max) *marksum->max = num; + if (num < *marksum->min || *marksum->min == 0) *marksum->min = num; + (*marksum->total)++; + } + + g_free(flags); +} + +void procmsg_get_mark_sum(FolderItem *item, + gint *new, gint *unread, gint *total, + gint *min, gint *max, + gint first) +{ + GHashTable *mark_table; + struct MarkSum marksum; + + *new = *unread = *total = *min = *max = 0; + marksum.new = new; + marksum.unread = unread; + marksum.total = total; + marksum.min = min; + marksum.max = max; + marksum.first = first; + + mark_table = procmsg_read_mark_file(item); + + if (mark_table) { + g_hash_table_foreach(mark_table, mark_sum_func, &marksum); + g_hash_table_destroy(mark_table); + } +} + +static GHashTable *procmsg_read_mark_file(FolderItem *item) +{ + FILE *fp; + GHashTable *mark_table = NULL; + guint32 idata; + guint num; + MsgFlags *flags; + MsgPermFlags perm_flags; + GSList *cur; + + if ((fp = procmsg_open_mark_file(item, DATA_READ)) == NULL) + return NULL; + + mark_table = g_hash_table_new(NULL, g_direct_equal); + + while (fread(&idata, sizeof(idata), 1, fp) == 1) { + num = idata; + if (fread(&idata, sizeof(idata), 1, fp) != 1) break; + perm_flags = idata; + + flags = g_hash_table_lookup(mark_table, GUINT_TO_POINTER(num)); + if (flags != NULL) + g_free(flags); + + flags = g_new0(MsgFlags, 1); + flags->perm_flags = perm_flags; + + g_hash_table_insert(mark_table, GUINT_TO_POINTER(num), flags); + } + + fclose(fp); + + if (item->mark_queue) { + g_hash_table_foreach(mark_table, mark_unset_new_func, NULL); + } + + for (cur = item->mark_queue; cur != NULL; cur = cur->next) { + MsgInfo *msginfo = (MsgInfo *)cur->data; + + flags = g_hash_table_lookup(mark_table, + GUINT_TO_POINTER(msginfo->msgnum)); + if (flags != NULL) + g_free(flags); + + flags = g_new0(MsgFlags, 1); + flags->perm_flags = msginfo->flags.perm_flags; + + g_hash_table_insert(mark_table, + GUINT_TO_POINTER(msginfo->msgnum), flags); + + } + + if (item->mark_queue && !item->opened) { + procmsg_write_mark_file(item, mark_table); + procmsg_msg_list_free(item->mark_queue); + item->mark_queue = NULL; + } + + return mark_table; +} + +static void write_mark_func(gpointer key, gpointer value, gpointer data) +{ + MsgInfo msginfo; + + msginfo.msgnum = GPOINTER_TO_UINT(key); + msginfo.flags.perm_flags = ((MsgFlags *)value)->perm_flags; + procmsg_write_flags(&msginfo, (FILE *)data); +} + +static void procmsg_write_mark_file(FolderItem *item, GHashTable *mark_table) +{ + FILE *fp; + + fp = procmsg_open_mark_file(item, DATA_WRITE); + g_hash_table_foreach(mark_table, write_mark_func, fp); + fclose(fp); +} + +static FILE *procmsg_open_data_file(const gchar *file, guint version, + DataOpenMode mode, + gchar *buf, size_t buf_size) +{ + FILE *fp; + guint32 data_ver; + + g_return_val_if_fail(file != NULL, NULL); + + if (mode == DATA_WRITE) { + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + if (change_file_mode_rw(fp, file) < 0) + FILE_OP_ERROR(file, "chmod"); + + WRITE_CACHE_DATA_INT(version, fp); + return fp; + } + + /* check version */ + if ((fp = fopen(file, "rb")) == NULL) + debug_print("Mark/Cache file not found\n"); + else { + if (buf && buf_size > 0) + setvbuf(fp, buf, _IOFBF, buf_size); + if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 || + version != data_ver) { + debug_print("Mark/Cache version is different (%u != %u). " + "Discarding it.\n", data_ver, version); + fclose(fp); + fp = NULL; + } + } + + if (mode == DATA_READ) + return fp; + + if (fp) { + /* reopen with append mode */ + fclose(fp); + if ((fp = fopen(file, "ab")) == NULL) + FILE_OP_ERROR(file, "fopen"); + } else { + /* open with overwrite mode if mark file doesn't exist or + version is different */ + fp = procmsg_open_data_file(file, version, DATA_WRITE, buf, + buf_size); + } + + return fp; +} + +static FILE *procmsg_open_cache_file_with_buffer(FolderItem *item, + DataOpenMode mode, + gchar *buf, size_t buf_size) +{ + gchar *cachefile; + FILE *fp; + + cachefile = folder_item_get_cache_file(item); + fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, buf, + buf_size); + g_free(cachefile); + + return fp; +} + +FILE *procmsg_open_cache_file(FolderItem *item, DataOpenMode mode) +{ + gchar *cachefile; + FILE *fp; + + cachefile = folder_item_get_cache_file(item); + fp = procmsg_open_data_file(cachefile, CACHE_VERSION, mode, NULL, 0); + g_free(cachefile); + + return fp; +} + +FILE *procmsg_open_mark_file(FolderItem *item, DataOpenMode mode) +{ + gchar *markfile; + FILE *fp; + + markfile = folder_item_get_mark_file(item); + fp = procmsg_open_data_file(markfile, MARK_VERSION, mode, NULL, 0); + g_free(markfile); + + return fp; +} + +/* return the reversed thread tree */ +GNode *procmsg_get_thread_tree(GSList *mlist) +{ + GNode *root, *parent, *node, *next; + GHashTable *table; + MsgInfo *msginfo; + const gchar *msgid; + + root = g_node_new(NULL); + table = g_hash_table_new(g_str_hash, g_str_equal); + + for (; mlist != NULL; mlist = mlist->next) { + msginfo = (MsgInfo *)mlist->data; + parent = root; + + if (msginfo->inreplyto) { + parent = g_hash_table_lookup(table, msginfo->inreplyto); + if (parent == NULL) + parent = root; + } + node = g_node_insert_data_before + (parent, parent == root ? parent->children : NULL, + msginfo); + if ((msgid = msginfo->msgid) && + g_hash_table_lookup(table, msgid) == NULL) + g_hash_table_insert(table, (gchar *)msgid, node); + } + + /* complete the unfinished threads */ + for (node = root->children; node != NULL; ) { + next = node->next; + msginfo = (MsgInfo *)node->data; + if (msginfo->inreplyto) { + parent = g_hash_table_lookup(table, msginfo->inreplyto); + /* node should not be the parent, and node should not + be an ancestor of parent (circular reference) */ + if (parent && parent != node && + !g_node_is_ancestor(node, parent)) { + g_node_unlink(node); + g_node_insert_before + (parent, parent->children, node); + } + } + node = next; + } + + g_hash_table_destroy(table); + + return root; +} + +gint procmsg_move_messages(GSList *mlist) +{ + GSList *cur, *movelist = NULL; + MsgInfo *msginfo; + FolderItem *dest = NULL; + GHashTable *hash; + gint val = 0; + + if (!mlist) return 0; + + hash = procmsg_to_folder_hash_table_create(mlist); + folder_item_scan_foreach(hash); + g_hash_table_destroy(hash); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!dest) { + dest = msginfo->to_folder; + movelist = g_slist_append(movelist, msginfo); + } else if (dest == msginfo->to_folder) { + movelist = g_slist_append(movelist, msginfo); + } else { + val = folder_item_move_msgs(dest, movelist); + g_slist_free(movelist); + movelist = NULL; + if (val == -1) + return val; + dest = msginfo->to_folder; + movelist = g_slist_append(movelist, msginfo); + } + } + + if (movelist) { + val = folder_item_move_msgs(dest, movelist); + g_slist_free(movelist); + } + + return val == -1 ? -1 : 0; +} + +gint procmsg_copy_messages(GSList *mlist) +{ + GSList *cur, *copylist = NULL; + MsgInfo *msginfo; + FolderItem *dest = NULL; + GHashTable *hash; + gint val = 0; + + if (!mlist) return 0; + + hash = procmsg_to_folder_hash_table_create(mlist); + folder_item_scan_foreach(hash); + g_hash_table_destroy(hash); + + for (cur = mlist; cur != NULL; cur = cur->next) { + msginfo = (MsgInfo *)cur->data; + if (!dest) { + dest = msginfo->to_folder; + copylist = g_slist_append(copylist, msginfo); + } else if (dest == msginfo->to_folder) { + copylist = g_slist_append(copylist, msginfo); + } else { + val = folder_item_copy_msgs(dest, copylist); + g_slist_free(copylist); + copylist = NULL; + if (val == -1) + return val; + dest = msginfo->to_folder; + copylist = g_slist_append(copylist, msginfo); + } + } + + if (copylist) { + val = folder_item_copy_msgs(dest, copylist); + g_slist_free(copylist); + } + + return val == -1 ? -1 : 0; +} + +gchar *procmsg_get_message_file_path(MsgInfo *msginfo) +{ + gchar *path, *file; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->plaintext_file) + file = g_strdup(msginfo->plaintext_file); + else if (msginfo->file_path) + return g_strdup(msginfo->file_path); + else { + path = folder_item_get_path(msginfo->folder); + file = g_strconcat(path, G_DIR_SEPARATOR_S, + itos(msginfo->msgnum), NULL); + g_free(path); + } + + return file; +} + +gchar *procmsg_get_message_file(MsgInfo *msginfo) +{ + gchar *filename = NULL; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (msginfo->file_path) + return g_strdup(msginfo->file_path); + + filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum); + if (!filename) + debug_print(_("can't fetch message %d\n"), msginfo->msgnum); + + return filename; +} + +GSList *procmsg_get_message_file_list(GSList *mlist) +{ + GSList *file_list = NULL; + MsgInfo *msginfo; + MsgFileInfo *fileinfo; + gchar *file; + + while (mlist != NULL) { + msginfo = (MsgInfo *)mlist->data; + file = procmsg_get_message_file(msginfo); + if (!file) { + procmsg_message_file_list_free(file_list); + return NULL; + } + fileinfo = g_new(MsgFileInfo, 1); + fileinfo->file = file; + fileinfo->flags = g_new(MsgFlags, 1); + *fileinfo->flags = msginfo->flags; + file_list = g_slist_prepend(file_list, fileinfo); + mlist = mlist->next; + } + + file_list = g_slist_reverse(file_list); + + return file_list; +} + +void procmsg_message_file_list_free(GSList *file_list) +{ + GSList *cur; + MsgFileInfo *fileinfo; + + for (cur = file_list; cur != NULL; cur = cur->next) { + fileinfo = (MsgFileInfo *)cur->data; + g_free(fileinfo->file); + g_free(fileinfo->flags); + g_free(fileinfo); + } + + g_slist_free(file_list); +} + +FILE *procmsg_open_message(MsgInfo *msginfo) +{ + FILE *fp; + gchar *file; + + g_return_val_if_fail(msginfo != NULL, NULL); + + file = procmsg_get_message_file_path(msginfo); + g_return_val_if_fail(file != NULL, NULL); + + if (!is_file_exist(file)) { + g_free(file); + file = procmsg_get_message_file(msginfo); + if (!file) + return NULL; + } + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + g_free(file); + return NULL; + } + + g_free(file); + + if (MSG_IS_QUEUED(msginfo->flags)) { + gchar buf[BUFFSIZE]; + + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + return fp; +} + +#if USE_GPGME +FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo) +{ + FILE *fp; + MimeInfo *mimeinfo_; + glong fpos; + + g_return_val_if_fail(msginfo != NULL, NULL); + + if (mimeinfo) *mimeinfo = NULL; + + if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL; + + mimeinfo_ = procmime_scan_mime_header(fp); + if (!mimeinfo_) { + fclose(fp); + return NULL; + } + + if (!MSG_IS_ENCRYPTED(msginfo->flags) && + rfc2015_is_encrypted(mimeinfo_)) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED); + } + + if (MSG_IS_ENCRYPTED(msginfo->flags) && + !msginfo->plaintext_file && + !msginfo->decryption_failed) { + fpos = ftell(fp); + rfc2015_decrypt_message(msginfo, mimeinfo_, fp); + if (msginfo->plaintext_file && + !msginfo->decryption_failed) { + fclose(fp); + procmime_mimeinfo_free_all(mimeinfo_); + if ((fp = procmsg_open_message(msginfo)) == NULL) + return NULL; + mimeinfo_ = procmime_scan_mime_header(fp); + if (!mimeinfo_) { + fclose(fp); + return NULL; + } + } else { + if (fseek(fp, fpos, SEEK_SET) < 0) + perror("fseek"); + } + } + + if (mimeinfo) *mimeinfo = mimeinfo_; + return fp; +} +#endif + +gboolean procmsg_msg_exist(MsgInfo *msginfo) +{ + gchar *path; + gboolean ret; + + if (!msginfo) return FALSE; + + path = folder_item_get_path(msginfo->folder); + change_dir(path); + ret = !folder_item_is_msg_changed(msginfo->folder, msginfo); + g_free(path); + + return ret; +} + +void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key, + PrefsFilterType type) +{ + static HeaderEntry hentry[] = {{"List-Id:", NULL, TRUE}, + {"X-ML-Name:", NULL, TRUE}, + {"X-List:", NULL, TRUE}, + {"X-Mailing-list:", NULL, TRUE}, + {"X-Sequence:", NULL, TRUE}, + {NULL, NULL, FALSE}}; + enum + { + H_LIST_ID = 0, + H_X_ML_NAME = 1, + H_X_LIST = 2, + H_X_MAILING_LIST = 3, + H_X_SEQUENCE = 4 + }; + + FILE *fp; + + g_return_if_fail(msginfo != NULL); + g_return_if_fail(header != NULL); + g_return_if_fail(key != NULL); + + *header = NULL; + *key = NULL; + + switch (type) { + case FILTER_BY_NONE: + return; + case FILTER_BY_AUTO: + if ((fp = procmsg_open_message(msginfo)) == NULL) + return; + procheader_get_header_fields(fp, hentry); + fclose(fp); + +#define SET_FILTER_KEY(hstr, idx) \ +{ \ + *header = g_strdup(hstr); \ + *key = hentry[idx].body; \ + hentry[idx].body = NULL; \ +} + + if (hentry[H_LIST_ID].body != NULL) { + SET_FILTER_KEY("List-Id", H_LIST_ID); + extract_list_id_str(*key); + } else if (hentry[H_X_ML_NAME].body != NULL) { + SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME); + } else if (hentry[H_X_LIST].body != NULL) { + SET_FILTER_KEY("X-List", H_X_LIST); + } else if (hentry[H_X_MAILING_LIST].body != NULL) { + SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST); + } else if (hentry[H_X_SEQUENCE].body != NULL) { + guchar *p; + + SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE); + p = *key; + while (*p != '\0') { + while (*p != '\0' && !isspace(*p)) p++; + while (isspace(*p)) p++; + if (isdigit(*p)) { + *p = '\0'; + break; + } + } + g_strstrip(*key); + } else if (msginfo->subject) { + *header = g_strdup("Subject"); + *key = g_strdup(msginfo->subject); + } + +#undef SET_FILTER_KEY + + g_free(hentry[H_LIST_ID].body); + hentry[H_LIST_ID].body = NULL; + g_free(hentry[H_X_ML_NAME].body); + hentry[H_X_ML_NAME].body = NULL; + g_free(hentry[H_X_LIST].body); + hentry[H_X_LIST].body = NULL; + g_free(hentry[H_X_MAILING_LIST].body); + hentry[H_X_MAILING_LIST].body = NULL; + + break; + case FILTER_BY_FROM: + *header = g_strdup("From"); + *key = g_strdup(msginfo->from); + break; + case FILTER_BY_TO: + *header = g_strdup("To"); + *key = g_strdup(msginfo->to); + break; + case FILTER_BY_SUBJECT: + *header = g_strdup("Subject"); + *key = g_strdup(msginfo->subject); + break; + default: + break; + } +} + +void procmsg_empty_trash(FolderItem *trash) +{ + FILE *fp; + + if (trash && trash->total > 0) { + debug_print("Emptying messages in %s ...\n", trash->path); + + folder_item_remove_all_msg(trash); + fp = procmsg_open_cache_file(trash, DATA_WRITE); + if (fp) fclose(fp); + fp = procmsg_open_mark_file(trash, DATA_WRITE); + if (fp) fclose(fp); + } +} + +void procmsg_empty_all_trash(void) +{ + FolderItem *trash; + GList *cur; + + for (cur = folder_get_list(); cur != NULL; cur = cur->next) { + trash = FOLDER(cur->data)->trash; + procmsg_empty_trash(trash); + } +} + +gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs) +{ + gint ret = 0; + GSList *mlist = NULL; + GSList *cur; + + if (!queue) + queue = folder_get_default_queue(); + g_return_val_if_fail(queue != NULL, -1); + + mlist = folder_item_get_msg_list(queue, FALSE); + mlist = procmsg_sort_msg_list(mlist, SORT_BY_NUMBER, SORT_ASCENDING); + + for (cur = mlist; cur != NULL; cur = cur->next) { + gchar *file; + MsgInfo *msginfo = (MsgInfo *)cur->data; + + file = procmsg_get_message_file(msginfo); + if (file) { + QueueInfo *qinfo; + + qinfo = send_get_queue_info(file); + if (!qinfo || send_message_queue(qinfo) < 0) { + g_warning(_("Sending queued message %d failed.\n"), + msginfo->msgnum); + } else { + ret++; + if (save_msgs) { + FolderItem *outbox; + outbox = account_get_special_folder + (qinfo->ac, F_OUTBOX); + procmsg_save_to_outbox(outbox, file, + TRUE); + } + folder_item_remove_msg(queue, msginfo); + } + send_queue_info_free(qinfo); + g_free(file); + } + + procmsg_msginfo_free(msginfo); + } + + g_slist_free(mlist); + + queue->mtime = 0; + + return ret; +} + +gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file, + gboolean is_queued) +{ + gint num; + FILE *fp; + MsgFlags flag = {0, 0}; + + debug_print("saving sent message...\n"); + + if (!outbox) + outbox = folder_get_default_outbox(); + g_return_val_if_fail(outbox != NULL, -1); + + /* remove queueing headers */ + if (is_queued) { + gchar tmp[MAXPATHLEN + 1]; + gchar buf[BUFFSIZE]; + FILE *outfp; + + g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x", + get_rc_dir(), G_DIR_SEPARATOR, (guint)random()); + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + if ((outfp = fopen(tmp, "wb")) == NULL) { + FILE_OP_ERROR(tmp, "fopen"); + fclose(fp); + return -1; + } + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + while (fgets(buf, sizeof(buf), fp) != NULL) + fputs(buf, outfp); + fclose(outfp); + fclose(fp); + + folder_item_scan(outbox); + if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) { + g_warning("can't save message\n"); + unlink(tmp); + return -1; + } + } else { + folder_item_scan(outbox); + if ((num = folder_item_add_msg + (outbox, file, &flag, FALSE)) < 0) { + g_warning("can't save message\n"); + return -1; + } + } + + return 0; +} + +void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline) +{ + static const gchar *def_cmd = "lpr %s"; + static guint id = 0; + gchar *prtmp; + FILE *tmpfp, *prfp; + gchar buf[1024]; + gchar *p; + + g_return_if_fail(msginfo); + + if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) { + g_warning(_("Can't get text part\n")); + return; + } + + prtmp = g_strdup_printf("%s%cprinttmp.%08x", + get_mime_tmp_dir(), G_DIR_SEPARATOR, id++); + + if ((prfp = fopen(prtmp, "wb")) == NULL) { + FILE_OP_ERROR(prtmp, "fopen"); + g_free(prtmp); + fclose(tmpfp); + return; + } + + if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date); + if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from); + if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to); + if (msginfo->newsgroups) + fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups); + if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject); + fputc('\n', prfp); + + while (fgets(buf, sizeof(buf), tmpfp) != NULL) + fputs(buf, prfp); + + fclose(prfp); + fclose(tmpfp); + + if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' && + !strchr(p + 2, '%')) + g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp); + else { + if (cmdline) + g_warning(_("Print command line is invalid: `%s'\n"), + cmdline); + g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp); + } + + g_free(prtmp); + + g_strchomp(buf); + if (buf[strlen(buf) - 1] != '&') strcat(buf, "&"); + system(buf); +} + +MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo) +{ + MsgInfo *newmsginfo; + + if (msginfo == NULL) return NULL; + + newmsginfo = g_new0(MsgInfo, 1); + +#define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb +#define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \ + g_strdup(msginfo->mmb) : NULL + + MEMBCOPY(msgnum); + MEMBCOPY(size); + MEMBCOPY(mtime); + MEMBCOPY(date_t); + + MEMBCOPY(flags); + + MEMBDUP(fromname); + + MEMBDUP(date); + MEMBDUP(from); + MEMBDUP(to); + MEMBDUP(cc); + MEMBDUP(newsgroups); + MEMBDUP(subject); + MEMBDUP(msgid); + MEMBDUP(inreplyto); + + MEMBCOPY(folder); + MEMBCOPY(to_folder); + + MEMBDUP(xface); + + MEMBDUP(file_path); + + MEMBDUP(plaintext_file); + MEMBCOPY(decryption_failed); + + return newmsginfo; +} + +MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo) +{ + MsgInfo *full_msginfo; + gchar *file; + + if (msginfo == NULL) return NULL; + + file = procmsg_get_message_file(msginfo); + if (!file) { + g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n"); + return NULL; + } + + full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE); + g_free(file); + if (!full_msginfo) return NULL; + + full_msginfo->msgnum = msginfo->msgnum; + full_msginfo->size = msginfo->size; + full_msginfo->mtime = msginfo->mtime; + full_msginfo->folder = msginfo->folder; + full_msginfo->to_folder = msginfo->to_folder; + + full_msginfo->file_path = g_strdup(msginfo->file_path); + +#if USE_GPGME + full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file); + full_msginfo->decryption_failed = msginfo->decryption_failed; +#endif + + return full_msginfo; +} + +void procmsg_msginfo_free(MsgInfo *msginfo) +{ + if (msginfo == NULL) return; + + g_free(msginfo->xface); + + g_free(msginfo->fromname); + + g_free(msginfo->date); + g_free(msginfo->from); + g_free(msginfo->to); + g_free(msginfo->cc); + g_free(msginfo->newsgroups); + g_free(msginfo->subject); + g_free(msginfo->msgid); + g_free(msginfo->inreplyto); + + g_free(msginfo->file_path); + + g_free(msginfo->plaintext_file); + + g_free(msginfo); +} + +gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b) +{ + const MsgInfo *msginfo1 = a; + const MsgInfo *msginfo2 = b; + + if (!msginfo1) + return -1; + if (!msginfo2) + return -1; + + return msginfo1->msgnum - msginfo2->msgnum; +} + +#define CMP_FUNC_DEF(func_name, val) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + \ + if (!msginfo1 || !msginfo2) \ + return -1; \ + \ + return (val) * (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_mark, + MSG_IS_MARKED(msginfo1->flags) - MSG_IS_MARKED(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_unread, + MSG_IS_UNREAD(msginfo1->flags) - MSG_IS_UNREAD(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_mime, + MSG_IS_MIME(msginfo1->flags) - MSG_IS_MIME(msginfo2->flags)) +CMP_FUNC_DEF(procmsg_cmp_by_label, + MSG_GET_COLORLABEL(msginfo1->flags) - + MSG_GET_COLORLABEL(msginfo2->flags)) + +CMP_FUNC_DEF(procmsg_cmp_by_number, msginfo1->msgnum - msginfo2->msgnum) +CMP_FUNC_DEF(procmsg_cmp_by_size, msginfo1->size - msginfo2->size) +CMP_FUNC_DEF(procmsg_cmp_by_date, msginfo1->date_t - msginfo2->date_t) + +#undef CMP_FUNC_DEF +#define CMP_FUNC_DEF(func_name, var_name) \ +static gint func_name(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + \ + if (!msginfo1->var_name) \ + return (msginfo2->var_name != NULL); \ + if (!msginfo2->var_name) \ + return -1; \ + \ + return strcasecmp(msginfo1->var_name, msginfo2->var_name) * \ + (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} + +CMP_FUNC_DEF(procmsg_cmp_by_from, fromname) +CMP_FUNC_DEF(procmsg_cmp_by_to, to) + +#undef CMP_FUNC_DEF + +static gint procmsg_cmp_by_subject(gconstpointer a, gconstpointer b) \ +{ \ + const MsgInfo *msginfo1 = a; \ + const MsgInfo *msginfo2 = b; \ + \ + if (!msginfo1->subject) \ + return (msginfo2->subject != NULL); \ + if (!msginfo2->subject) \ + return -1; \ + \ + return subject_compare_for_sort \ + (msginfo1->subject, msginfo2->subject) * \ + (cmp_func_sort_type == SORT_ASCENDING ? 1 : -1); \ +} diff --git a/src/procmsg.h b/src/procmsg.h new file mode 100644 index 00000000..2a718baf --- /dev/null +++ b/src/procmsg.h @@ -0,0 +1,280 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PROCMSG_H__ +#define __PROCMSG_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +typedef struct _MsgInfo MsgInfo; +typedef struct _MsgFlags MsgFlags; +typedef struct _MsgFileInfo MsgFileInfo; + +#include "folder.h" +#include "procmime.h" +#include "prefs_filter.h" + +typedef enum +{ + DATA_READ, + DATA_WRITE, + DATA_APPEND +} DataOpenMode; + +#define MSG_NEW (1U << 0) +#define MSG_UNREAD (1U << 1) +#define MSG_MARKED (1U << 2) +#define MSG_DELETED (1U << 3) +#define MSG_REPLIED (1U << 4) +#define MSG_FORWARDED (1U << 5) + +#define MSG_CLABEL_SBIT (7) /* start bit of color label */ +#define MAKE_MSG_CLABEL(h, m, l) (((h) << (MSG_CLABEL_SBIT + 2)) | \ + ((m) << (MSG_CLABEL_SBIT + 1)) | \ + ((l) << (MSG_CLABEL_SBIT + 0))) + +#define MSG_CLABEL_NONE MAKE_MSG_CLABEL(0U, 0U, 0U) +#define MSG_CLABEL_1 MAKE_MSG_CLABEL(0U, 0U, 1U) +#define MSG_CLABEL_2 MAKE_MSG_CLABEL(0U, 1U, 0U) +#define MSG_CLABEL_3 MAKE_MSG_CLABEL(0U, 1U, 1U) +#define MSG_CLABEL_4 MAKE_MSG_CLABEL(1U, 0U, 0U) +#define MSG_CLABEL_5 MAKE_MSG_CLABEL(1U, 0U, 1U) +#define MSG_CLABEL_6 MAKE_MSG_CLABEL(1U, 1U, 0U) +#define MSG_CLABEL_7 MAKE_MSG_CLABEL(1U, 1U, 1U) + +#define MSG_CLABEL_ORANGE MSG_CLABEL_1 +#define MSG_CLABEL_RED MSG_CLABEL_2 +#define MSG_CLABEL_PINK MSG_CLABEL_3 +#define MSG_CLABEL_SKYBLUE MSG_CLABEL_4 +#define MSG_CLABEL_BLUE MSG_CLABEL_5 +#define MSG_CLABEL_GREEN MSG_CLABEL_6 +#define MSG_CLABEL_BROWN MSG_CLABEL_7 + +/* RESERVED */ +#define MSG_RESERVED (1U << 31) + +#define MSG_CLABEL_FLAG_MASK (MSG_CLABEL_7) + +typedef guint32 MsgPermFlags; + +#define MSG_MOVE (1U << 0) +#define MSG_COPY (1U << 1) +#define MSG_QUEUED (1U << 16) +#define MSG_DRAFT (1U << 17) +#define MSG_ENCRYPTED (1U << 18) +#define MSG_IMAP (1U << 19) +#define MSG_NEWS (1U << 20) +#define MSG_SIGNED (1U << 21) +#define MSG_MIME (1U << 29) +#define MSG_INVALID (1U << 30) +#define MSG_RECEIVED (1U << 31) + +#define MSG_CACHED_FLAG_MASK (MSG_MIME) + +typedef guint32 MsgTmpFlags; + +#define MSG_SET_FLAGS(msg, flags) { (msg) |= (flags); } +#define MSG_UNSET_FLAGS(msg, flags) { (msg) &= ~(flags); } +#define MSG_SET_PERM_FLAGS(msg, flags) \ + MSG_SET_FLAGS((msg).perm_flags, flags) +#define MSG_SET_TMP_FLAGS(msg, flags) \ + MSG_SET_FLAGS((msg).tmp_flags, flags) +#define MSG_UNSET_PERM_FLAGS(msg, flags) \ + MSG_UNSET_FLAGS((msg).perm_flags, flags) +#define MSG_UNSET_TMP_FLAGS(msg, flags) \ + MSG_UNSET_FLAGS((msg).tmp_flags, flags) + +#define MSG_IS_NEW(msg) (((msg).perm_flags & MSG_NEW) != 0) +#define MSG_IS_UNREAD(msg) (((msg).perm_flags & MSG_UNREAD) != 0) +#define MSG_IS_MARKED(msg) (((msg).perm_flags & MSG_MARKED) != 0) +#define MSG_IS_DELETED(msg) (((msg).perm_flags & MSG_DELETED) != 0) +#define MSG_IS_REPLIED(msg) (((msg).perm_flags & MSG_REPLIED) != 0) +#define MSG_IS_FORWARDED(msg) (((msg).perm_flags & MSG_FORWARDED) != 0) + +#define MSG_GET_COLORLABEL(msg) (((msg).perm_flags & MSG_CLABEL_FLAG_MASK)) +#define MSG_GET_COLORLABEL_VALUE(msg) (MSG_GET_COLORLABEL(msg) >> MSG_CLABEL_SBIT) +#define MSG_SET_COLORLABEL_VALUE(msg, val) \ + MSG_SET_PERM_FLAGS(msg, ((((guint)(val)) & 7) << MSG_CLABEL_SBIT)) + +#define MSG_IS_MOVE(msg) (((msg).tmp_flags & MSG_MOVE) != 0) +#define MSG_IS_COPY(msg) (((msg).tmp_flags & MSG_COPY) != 0) + +#define MSG_IS_QUEUED(msg) (((msg).tmp_flags & MSG_QUEUED) != 0) +#define MSG_IS_DRAFT(msg) (((msg).tmp_flags & MSG_DRAFT) != 0) +#define MSG_IS_ENCRYPTED(msg) (((msg).tmp_flags & MSG_ENCRYPTED) != 0) +#define MSG_IS_IMAP(msg) (((msg).tmp_flags & MSG_IMAP) != 0) +#define MSG_IS_NEWS(msg) (((msg).tmp_flags & MSG_NEWS) != 0) +#define MSG_IS_MIME(msg) (((msg).tmp_flags & MSG_MIME) != 0) +#define MSG_IS_INVALID(msg) (((msg).tmp_flags & MSG_INVALID) != 0) +#define MSG_IS_RECEIVED(msg) (((msg).tmp_flags & MSG_RECEIVED) != 0) + +#define WRITE_CACHE_DATA_INT(n, fp) \ +{ \ + guint32 idata; \ + \ + idata = (guint32)n; \ + fwrite(&idata, sizeof(idata), 1, fp); \ +} + +#define WRITE_CACHE_DATA(data, fp) \ +{ \ + size_t len; \ + \ + if (data == NULL) { \ + len = 0; \ + WRITE_CACHE_DATA_INT(len, fp); \ + } else { \ + len = strlen(data); \ + WRITE_CACHE_DATA_INT(len, fp); \ + if (len > 0) \ + fwrite(data, len, 1, fp); \ + } \ +} + +struct _MsgFlags +{ + MsgPermFlags perm_flags; + MsgTmpFlags tmp_flags; +}; + +struct _MsgInfo +{ + guint msgnum; + off_t size; + time_t mtime; + time_t date_t; + + MsgFlags flags; + + gchar *fromname; + + gchar *date; + gchar *from; + gchar *to; + gchar *cc; + gchar *newsgroups; + gchar *subject; + gchar *msgid; + gchar *inreplyto; + + FolderItem *folder; + FolderItem *to_folder; + + gchar *xface; + + /* used only for temporary messages */ + gchar *file_path; + + /* used only for encrypted messages */ + gchar *plaintext_file; + guint decryption_failed : 1; +}; + +struct _MsgFileInfo +{ + gchar *file; + MsgFlags *flags; +}; + +GHashTable *procmsg_msg_hash_table_create (GSList *mlist); +void procmsg_msg_hash_table_append (GHashTable *msg_table, + GSList *mlist); +GHashTable *procmsg_to_folder_hash_table_create (GSList *mlist); + +GSList *procmsg_read_cache (FolderItem *item, + gboolean scan_file); +void procmsg_set_flags (GSList *mlist, + FolderItem *item); +GSList *procmsg_sort_msg_list (GSList *mlist, + FolderSortKey sort_key, + FolderSortType sort_type); +gint procmsg_get_last_num_in_msg_list(GSList *mlist); +void procmsg_msg_list_free (GSList *mlist); +void procmsg_write_cache (MsgInfo *msginfo, + FILE *fp); +void procmsg_write_flags (MsgInfo *msginfo, + FILE *fp); +void procmsg_flush_mark_queue (FolderItem *item, + FILE *fp); +void procmsg_add_mark_queue (FolderItem *item, + gint num, + MsgFlags flags); +void procmsg_add_flags (FolderItem *item, + gint num, + MsgFlags flags); +void procmsg_get_mark_sum (FolderItem *item, + gint *new, + gint *unread, + gint *total, + gint *min, + gint *max, + gint first); +FILE *procmsg_open_cache_file (FolderItem *item, + DataOpenMode mode); +FILE *procmsg_open_mark_file (FolderItem *item, + DataOpenMode mode); + +GNode *procmsg_get_thread_tree (GSList *mlist); + +gint procmsg_move_messages (GSList *mlist); +gint procmsg_copy_messages (GSList *mlist); + +gchar *procmsg_get_message_file_path (MsgInfo *msginfo); +gchar *procmsg_get_message_file (MsgInfo *msginfo); +GSList *procmsg_get_message_file_list (GSList *mlist); +void procmsg_message_file_list_free (GSList *file_list); +FILE *procmsg_open_message (MsgInfo *msginfo); +#if USE_GPGME +FILE *procmsg_open_message_decrypted (MsgInfo *msginfo, + MimeInfo **mimeinfo); +#endif +gboolean procmsg_msg_exist (MsgInfo *msginfo); + +void procmsg_get_filter_keyword (MsgInfo *msginfo, + gchar **header, + gchar **key, + PrefsFilterType type); + +void procmsg_empty_trash (FolderItem *trash); +void procmsg_empty_all_trash (void); + +gint procmsg_send_queue (FolderItem *queue, + gboolean save_msgs); +gint procmsg_save_to_outbox (FolderItem *outbox, + const gchar *file, + gboolean is_queued); +void procmsg_print_message (MsgInfo *msginfo, + const gchar *cmdline); + +MsgInfo *procmsg_msginfo_copy (MsgInfo *msginfo); +MsgInfo *procmsg_msginfo_get_full_info (MsgInfo *msginfo); +void procmsg_msginfo_free (MsgInfo *msginfo); + +gint procmsg_cmp_msgnum_for_sort (gconstpointer a, + gconstpointer b); + +#endif /* __PROCMSG_H__ */ diff --git a/src/progressdialog.c b/src/progressdialog.c new file mode 100644 index 00000000..31295372 --- /dev/null +++ b/src/progressdialog.c @@ -0,0 +1,135 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "progressdialog.h" +#include "gtkutils.h" +#include "utils.h" + +ProgressDialog *progress_dialog_create(void) +{ + ProgressDialog *progress; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *cancel_btn; + GtkWidget *cancel_area; + GtkWidget *progressbar; + GtkWidget *scrolledwin; + GtkWidget *clist; + gchar *text[] = {NULL, NULL, NULL}; + + text[1] = _("Account"); + text[2] = _("Status"); + + debug_print(_("Creating progress dialog...\n")); + progress = g_new0(ProgressDialog, 1); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request(window, 460, -1); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE); + gtk_widget_realize(window); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 8); + gtk_widget_show(hbox); + + label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 8); + gtk_widget_show(label); + + gtkut_button_set_create(&cancel_area, &cancel_btn, _("Cancel"), + NULL, NULL, NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), cancel_area, FALSE, FALSE, 0); + gtk_widget_grab_default(cancel_btn); + gtk_widget_show_all(cancel_area); + + progressbar = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), progressbar, FALSE, FALSE, 0); + gtk_widget_show(progressbar); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(scrolledwin); + gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + clist = gtk_clist_new_with_titles(3, text); + gtk_widget_show(clist); + gtk_container_add(GTK_CONTAINER(scrolledwin), clist); + gtk_widget_set_size_request(clist, -1, 120); + gtk_clist_set_column_justification(GTK_CLIST(clist), 0, + GTK_JUSTIFY_CENTER); + gtk_clist_set_column_width(GTK_CLIST(clist), 0, 16); + gtk_clist_set_column_width(GTK_CLIST(clist), 1, 160); + + progress->window = window; + progress->label = label; + progress->cancel_btn = cancel_btn; + progress->progressbar = progressbar; + progress->clist = clist; + + return progress; +} + +void progress_dialog_set_label(ProgressDialog *progress, gchar *str) +{ + gtk_label_set_text(GTK_LABEL(progress->label), str); +} + +void progress_dialog_set_value(ProgressDialog *progress, gfloat value) +{ + gtk_progress_set_value(GTK_PROGRESS(progress->progressbar), value); +} + +void progress_dialog_set_percentage(ProgressDialog *progress, + gfloat percentage) +{ + gtk_progress_set_percentage(GTK_PROGRESS(progress->progressbar), + percentage); +} + +void progress_dialog_destroy(ProgressDialog *progress) +{ + if (progress) { + gtk_widget_destroy(progress->window); + g_free(progress); + } +} diff --git a/src/progressdialog.h b/src/progressdialog.h new file mode 100644 index 00000000..047524ef --- /dev/null +++ b/src/progressdialog.h @@ -0,0 +1,46 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __PROGRESS_H__ +#define __PROGRESS_H__ + +#include +#include + +typedef struct _ProgressDialog ProgressDialog; + +struct _ProgressDialog +{ + GtkWidget *window; + GtkWidget *label; + GtkWidget *cancel_btn; + GtkWidget *progressbar; + GtkWidget *clist; +}; + +ProgressDialog *progress_dialog_create (void); +void progress_dialog_set_label (ProgressDialog *progress, + gchar *str); +void progress_dialog_set_value (ProgressDialog *progress, + gfloat value); +void progress_dialog_set_percentage (ProgressDialog *progress, + gfloat percentage); +void progress_dialog_destroy (ProgressDialog *progress); + +#endif /* __PROGRESS_H__ */ diff --git a/src/quote_fmt.h b/src/quote_fmt.h new file mode 100644 index 00000000..c9c4c004 --- /dev/null +++ b/src/quote_fmt.h @@ -0,0 +1,13 @@ +#ifndef __QUOTE_FMT_H__ + +#define __QUOTE_FMT_H__ + +#define quote_fmt_parse quote_fmtparse + +gchar *quote_fmt_get_buffer(void); +void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str, + const gchar *my_body); +gint quote_fmtparse(void); +void quote_fmt_scan_string(const gchar *str); + +#endif /* __QUOTE_FMT_H__ */ diff --git a/src/quote_fmt_lex.h b/src/quote_fmt_lex.h new file mode 100644 index 00000000..585751a1 --- /dev/null +++ b/src/quote_fmt_lex.h @@ -0,0 +1,47 @@ +/* The following defines shamelessly stolen from GDB sources... */ + +/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc), + as well as gratuitiously global symbol names, so we can have multiple + yacc generated parsers in gdb. Note that these are only the variables + produced by yacc. If other parser generators (bison, byacc, etc) produce + additional global names that conflict at link time, then those parser + generators need to be fixed instead of adding those names to this list. */ + +#define yymaxdepth quote_fmtmaxdepth +#define yyparse quote_fmtparse +#define yylex quote_fmtlex +#define yyerror quote_fmterror +#define yylval quote_fmtlval +#define yychar quote_fmtchar +#define yydebug quote_fmtdebug +#define yypact quote_fmtpact +#define yyr1 quote_fmtr1 +#define yyr2 quote_fmtr2 +#define yydef quote_fmtdef +#define yychk quote_fmtchk +#define yypgo quote_fmtpgo +#define yyact quote_fmtact +#define yyexca quote_fmtexca +#define yyerrflag quote_fmterrflag +#define yynerrs quote_fmtnerrs +#define yyps quote_fmtps +#define yypv quote_fmtpv +#define yys quote_fmts +#define yy_yys quote_fmtyys +#define yystate quote_fmtstate +#define yytmp quote_fmttmp +#define yyv quote_fmtv +#define yy_yyv quote_fmtyyv +#define yyval quote_fmtval +#define yylloc quote_fmtlloc +#define yyreds quote_fmtreds /* With YYDEBUG defined */ +#define yytoks quote_fmttoks /* With YYDEBUG defined */ +#define yylhs quote_fmtyylhs +#define yylen quote_fmtyylen +#define yydefred quote_fmtyydefred +#define yydgoto quote_fmtyydgoto +#define yysindex quote_fmtyysindex +#define yyrindex quote_fmtyyrindex +#define yygindex quote_fmtyygindex +#define yytable quote_fmtyytable +#define yycheck quote_fmtyycheck diff --git a/src/quote_fmt_lex.l b/src/quote_fmt_lex.l new file mode 100644 index 00000000..7f904c17 --- /dev/null +++ b/src/quote_fmt_lex.l @@ -0,0 +1,46 @@ +%{ +#include "quote_fmt_lex.h" +#include "quote_fmt_parse.h" +%} + +%option prefix="quote_fmt" +%option outfile="lex.yy.c" + +%% + +"%d" /* date */ return SHOW_DATE; +"%f" /* from */ return SHOW_FROM; +"%N" /* full name */ return SHOW_FULLNAME; +"%F" /* first name */ return SHOW_FIRST_NAME; +"%I" /* initial of sender */ return SHOW_SENDER_INITIAL; +"%s" /* subject */ return SHOW_SUBJECT; +"%t" /* to */ return SHOW_TO; +"%c" /* cc */ return SHOW_CC; +"%n" /* newsgroups */ return SHOW_NEWSGROUPS; +"%i" /* message-id */ return SHOW_MESSAGEID; +"%r" /* references */ return SHOW_REFERENCES; +"%M" /* message */ return SHOW_MESSAGE; +"%Q" /* quoted message */ return SHOW_QUOTED_MESSAGE; +"%m" /* message with no signature */ return SHOW_MESSAGE_NO_SIGNATURE; +"%q" /* quoted message with no signature */ return SHOW_QUOTED_MESSAGE_NO_SIGNATURE; +"%%" /* % */ return SHOW_PERCENT; +"\\\\" /* \ */ return SHOW_BACKSLASH; +"\\t"|"\t" /* tab */ return SHOW_TAB; +"\\n"|"\n" /* return */ return SHOW_EOL; +"\\?" /* ? */ return SHOW_QUESTION_MARK; +"\\{" return SHOW_OPARENT; +"\\}" return SHOW_CPARENT; +"?d" /* query date */ return QUERY_DATE; +"?f" /* query from */ return QUERY_FROM; +"?N"|"?F"|"?I" /* query from name */ return QUERY_FULLNAME; +"?s" /* query subject */ return QUERY_SUBJECT; +"?t" /* query to */ return QUERY_TO; +"?c" /* query cc */ return QUERY_CC; +"?n" /* query newsgroups */ return QUERY_NEWSGROUPS; +"?i" /* query message-id */ return QUERY_MESSAGEID; +"?r" /* query references */ return QUERY_REFERENCES; +"{" return OPARENT; +"}" return CPARENT; +. { yylval.chr = yytext[0]; return CHARACTER; } + +%% diff --git a/src/quote_fmt_parse.y b/src/quote_fmt_parse.y new file mode 100644 index 00000000..c44edf01 --- /dev/null +++ b/src/quote_fmt_parse.y @@ -0,0 +1,459 @@ +%{ + +#include "defs.h" + +#include +#include + +#include "procmsg.h" +#include "procmime.h" +#include "utils.h" + +#include "quote_fmt.h" +#include "quote_fmt_lex.h" + +/* decl */ +/* +flex quote_fmt.l +bison -p quote_fmt quote_fmt.y +*/ + +int yylex(void); + +static MsgInfo *msginfo = NULL; +static gboolean *visible = NULL; +static gint maxsize = 0; +static gint stacksize = 0; + +static gchar *buffer = NULL; +static gint bufmax = 0; +static gint bufsize = 0; +static const gchar *quote_str = NULL; +static const gchar *body = NULL; +static gint error = 0; + +static void add_visibility(gboolean val) +{ + stacksize++; + if (maxsize < stacksize) { + maxsize += 128; + visible = g_realloc(visible, maxsize * sizeof(gboolean)); + if (visible == NULL) + maxsize = 0; + } + + visible[stacksize - 1] = val; +} + +static void remove_visibility(void) +{ + stacksize--; +} + +static void add_buffer(const gchar *s) +{ + gint len; + + len = strlen(s); + if (bufsize + len + 1 > bufmax) { + if (bufmax == 0) + bufmax = 128; + while (bufsize + len + 1 > bufmax) + bufmax *= 2; + buffer = g_realloc(buffer, bufmax); + } + strcpy(buffer + bufsize, s); + bufsize += len; +} + +#if 0 +static void flush_buffer(void) +{ + if (buffer != NULL) + *buffer = '\0'; + bufsize = 0; +} +#endif + +gchar *quote_fmt_get_buffer(void) +{ + if (error != 0) + return NULL; + else + return buffer; +} + +#define INSERT(buf) \ + if (stacksize != 0 && visible[stacksize - 1]) \ + add_buffer(buf) + +#define INSERT_CHARACTER(chr) \ + if (stacksize != 0 && visible[stacksize - 1]) { \ + gchar tmp[2]; \ + tmp[0] = (chr); \ + tmp[1] = '\0'; \ + add_buffer(tmp); \ + } + +void quote_fmt_init(MsgInfo *info, const gchar *my_quote_str, + const gchar *my_body) +{ + quote_str = my_quote_str; + body = my_body; + msginfo = info; + stacksize = 0; + add_visibility(TRUE); + if (buffer != NULL) + *buffer = 0; + bufsize = 0; + error = 0; +} + +void quote_fmterror(char *str) +{ + g_warning("Error: %s\n", str); + error = 1; +} + +int quote_fmtwrap(void) +{ + return 1; +} + +static int isseparator(int ch) +{ + return isspace(ch) || ch == '.' || ch == '-'; +} +%} + +%union { + char chr; +} + +%token SHOW_NEWSGROUPS +%token SHOW_DATE SHOW_FROM SHOW_FULLNAME SHOW_FIRST_NAME +%token SHOW_SENDER_INITIAL SHOW_SUBJECT SHOW_TO SHOW_MESSAGEID +%token SHOW_PERCENT SHOW_CC SHOW_REFERENCES SHOW_MESSAGE +%token SHOW_QUOTED_MESSAGE SHOW_BACKSLASH SHOW_TAB +%token SHOW_QUOTED_MESSAGE_NO_SIGNATURE SHOW_MESSAGE_NO_SIGNATURE +%token SHOW_EOL SHOW_QUESTION_MARK SHOW_OPARENT SHOW_CPARENT +%token QUERY_DATE QUERY_FROM +%token QUERY_FULLNAME QUERY_SUBJECT QUERY_TO QUERY_NEWSGROUPS +%token QUERY_MESSAGEID QUERY_CC QUERY_REFERENCES +%token OPARENT CPARENT +%token CHARACTER + +%start quote_fmt + +%token CHARACTER +%type character + +%% + +quote_fmt: + character_or_special_or_query_list; + +character_or_special_or_query_list: + character_or_special_or_query character_or_special_or_query_list + | character_or_special_or_query ; + +character_or_special_or_query: + special + | character + { + INSERT_CHARACTER($1); + } + | query ; + + +character: + CHARACTER + ; + +special: + SHOW_NEWSGROUPS + { + if (msginfo->newsgroups) + INSERT(msginfo->newsgroups); + } + | SHOW_DATE + { + if (msginfo->date) + INSERT(msginfo->date); + } + | SHOW_FROM + { + if (msginfo->from) + INSERT(msginfo->from); + } + | SHOW_FULLNAME + { + if (msginfo->fromname) + INSERT(msginfo->fromname); + } + | SHOW_FIRST_NAME + { + if (msginfo->fromname) { + guchar *p; + gchar *str; + + str = alloca(strlen(msginfo->fromname) + 1); + if (str != NULL) { + strcpy(str, msginfo->fromname); + p = str; + while (*p && !isspace(*p)) p++; + *p = '\0'; + INSERT(str); + } + } + } + | SHOW_SENDER_INITIAL + { +#define MAX_SENDER_INITIAL 20 + if (msginfo->fromname) { + gchar tmp[MAX_SENDER_INITIAL]; + guchar *p; + gchar *cur; + gint len = 0; + + p = msginfo->fromname; + cur = tmp; + while (*p) { + if (*p && isalnum(*p)) { + *cur = toupper(*p); + cur++; + len++; + if (len >= MAX_SENDER_INITIAL - 1) + break; + } else + break; + while (*p && !isseparator(*p)) p++; + while (*p && isseparator(*p)) p++; + } + *cur = '\0'; + INSERT(tmp); + } + } + | SHOW_SUBJECT + { + if (msginfo->subject) + INSERT(msginfo->subject); + } + | SHOW_TO + { + if (msginfo->to) + INSERT(msginfo->to); + } + | SHOW_MESSAGEID + { + if (msginfo->msgid) + INSERT(msginfo->msgid); + } + | SHOW_PERCENT + { + INSERT("%"); + } + | SHOW_CC + { + if (msginfo->cc) + INSERT(msginfo->cc); + } + | SHOW_REFERENCES + { + /* if (msginfo->references) + INSERT(msginfo->references); */ + } + | SHOW_MESSAGE + { + if (msginfo->folder || body) { + gchar buf[BUFFSIZE]; + FILE *fp; + + if (body) + fp = str_open_as_stream(body); + else + fp = procmime_get_first_text_content(msginfo); + + if (fp == NULL) + g_warning("Can't get text part\n"); + else { + while (fgets(buf, sizeof(buf), fp) != NULL) { + strcrchomp(buf); + INSERT(buf); + } + fclose(fp); + } + } + } + | SHOW_QUOTED_MESSAGE + { + if (msginfo->folder || body) { + gchar buf[BUFFSIZE]; + FILE *fp; + + if (body) + fp = str_open_as_stream(body); + else + fp = procmime_get_first_text_content(msginfo); + + if (fp == NULL) + g_warning("Can't get text part\n"); + else { + while (fgets(buf, sizeof(buf), fp) != NULL) { + strcrchomp(buf); + if (quote_str) + INSERT(quote_str); + INSERT(buf); + } + fclose(fp); + } + } + } + | SHOW_MESSAGE_NO_SIGNATURE + { + if (msginfo->folder || body) { + gchar buf[BUFFSIZE]; + FILE *fp; + + if (body) + fp = str_open_as_stream(body); + else + fp = procmime_get_first_text_content(msginfo); + + if (fp == NULL) + g_warning("Can't get text part\n"); + else { + while (fgets(buf, sizeof(buf), fp) != NULL) { + strcrchomp(buf); + if (strncmp(buf, "-- \n", 4) == 0) + break; + INSERT(buf); + } + fclose(fp); + } + } + } + | SHOW_QUOTED_MESSAGE_NO_SIGNATURE + { + if (msginfo->folder || body) { + gchar buf[BUFFSIZE]; + FILE *fp; + + if (body) + fp = str_open_as_stream(body); + else + fp = procmime_get_first_text_content(msginfo); + + if (fp == NULL) + g_warning("Can't get text part\n"); + else { + while (fgets(buf, sizeof(buf), fp) != NULL) { + strcrchomp(buf); + if (strncmp(buf, "-- \n", 4) == 0) + break; + if (quote_str) + INSERT(quote_str); + INSERT(buf); + } + fclose(fp); + } + } + } + | SHOW_BACKSLASH + { + INSERT("\\"); + } + | SHOW_TAB + { + INSERT("\t"); + } + | SHOW_EOL + { + INSERT("\n"); + } + | SHOW_QUESTION_MARK + { + INSERT("?"); + } + | SHOW_OPARENT + { + INSERT("{"); + } + | SHOW_CPARENT + { + INSERT("}"); + }; + +query: + QUERY_DATE + { + add_visibility(msginfo->date != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_FROM + { + add_visibility(msginfo->from != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_FULLNAME + { + add_visibility(msginfo->fromname != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_SUBJECT + { + add_visibility(msginfo->subject != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_TO + { + add_visibility(msginfo->to != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_NEWSGROUPS + { + add_visibility(msginfo->newsgroups != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_MESSAGEID + { + add_visibility(msginfo->msgid != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_CC + { + add_visibility(msginfo->cc != NULL); + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + } + | QUERY_REFERENCES + { + /* add_visibility(msginfo->references != NULL); */ + } + OPARENT quote_fmt CPARENT + { + remove_visibility(); + }; diff --git a/src/quoted-printable.c b/src/quoted-printable.c new file mode 100644 index 00000000..bf99a234 --- /dev/null +++ b/src/quoted-printable.c @@ -0,0 +1,231 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include + +static gboolean get_hex_value(guchar *out, gchar c1, gchar c2); +static void get_hex_str(gchar *out, guchar ch); + +#define MAX_LINELEN 76 + +#define IS_LBREAK(p) \ + (*(p) == '\0' || *(p) == '\n' || (*(p) == '\r' && *((p) + 1) == '\n')) + +#define SOFT_LBREAK_IF_REQUIRED(n) \ + if (len + (n) > MAX_LINELEN || \ + (len + (n) == MAX_LINELEN && (!IS_LBREAK(inp + 1)))) { \ + *outp++ = '='; \ + *outp++ = '\n'; \ + len = 0; \ + } + +void qp_encode_line(gchar *out, const guchar *in) +{ + const guchar *inp = in; + gchar *outp = out; + guchar ch; + gint len = 0; + + while (*inp != '\0') { + ch = *inp; + + if (IS_LBREAK(inp)) { + *outp++ = '\n'; + len = 0; + if (*inp == '\r') + inp++; + inp++; + } else if (ch == '\t' || ch == ' ') { + if (IS_LBREAK(inp + 1)) { + SOFT_LBREAK_IF_REQUIRED(3); + *outp++ = '='; + get_hex_str(outp, ch); + outp += 2; + len += 3; + inp++; + } else { + SOFT_LBREAK_IF_REQUIRED(1); + *outp++ = *inp++; + len++; + } + } else if ((ch >= 33 && ch <= 60) || (ch >= 62 && ch <= 126)) { + SOFT_LBREAK_IF_REQUIRED(1); + *outp++ = *inp++; + len++; + } else { + SOFT_LBREAK_IF_REQUIRED(3); + *outp++ = '='; + get_hex_str(outp, ch); + outp += 2; + len += 3; + inp++; + } + } + + if (len > 0) + *outp++ = '\n'; + + *outp = '\0'; +} + +gint qp_decode_line(gchar *str) +{ + gchar *inp = str, *outp = str; + + while (*inp != '\0') { + if (*inp == '=') { + if (inp[1] && inp[2] && + get_hex_value(outp, inp[1], inp[2]) == TRUE) { + inp += 3; + } else if (inp[1] == '\0' || isspace((guchar)inp[1])) { + /* soft line break */ + break; + } else { + /* broken QP string */ + *outp = *inp++; + } + } else { + *outp = *inp++; + } + outp++; + } + + *outp = '\0'; + + return outp - str; +} + +gint qp_decode_q_encoding(guchar *out, const gchar *in, gint inlen) +{ + const gchar *inp = in; + guchar *outp = out; + + if (inlen < 0) + inlen = G_MAXINT; + + while (inp - in < inlen && *inp != '\0') { + if (*inp == '=' && inp + 3 - in <= inlen) { + if (get_hex_value(outp, inp[1], inp[2]) == TRUE) { + inp += 3; + } else { + *outp = *inp++; + } + } else if (*inp == '_') { + *outp = ' '; + inp++; + } else { + *outp = *inp++; + } + outp++; + } + + *outp = '\0'; + + return outp - out; +} + +gint qp_get_q_encoding_len(const guchar *str) +{ + const guchar *inp = str; + gint len = 0; + + while (*inp != '\0') { + if (*inp == 0x20) + len++; + else if (*inp == '=' || *inp == '?' || *inp == '_' || + *inp < 32 || *inp > 127 || isspace(*inp)) + len += 3; + else + len++; + + inp++; + } + + return len; +} + +void qp_q_encode(gchar *out, const guchar *in) +{ + const guchar *inp = in; + gchar *outp = out; + + while (*inp != '\0') { + if (*inp == 0x20) + *outp++ = '_'; + else if (*inp == '=' || *inp == '?' || *inp == '_' || + *inp < 32 || *inp > 127 || isspace(*inp)) { + *outp++ = '='; + get_hex_str(outp, *inp); + outp += 2; + } else + *outp++ = *inp; + + inp++; + } + + *outp = '\0'; +} + +#define HEX_TO_INT(val, hex) \ +{ \ + gchar c = hex; \ + \ + if ('0' <= c && c <= '9') { \ + val = c - '0'; \ + } else if ('a' <= c && c <= 'f') { \ + val = c - 'a' + 10; \ + } else if ('A' <= c && c <= 'F') { \ + val = c - 'A' + 10; \ + } else { \ + val = -1; \ + } \ +} + +static gboolean get_hex_value(guchar *out, gchar c1, gchar c2) +{ + gint hi, lo; + + HEX_TO_INT(hi, c1); + HEX_TO_INT(lo, c2); + + if (hi == -1 || lo == -1) + return FALSE; + + *out = (hi << 4) + lo; + return TRUE; +} + +#define INT_TO_HEX(hex, val) \ +{ \ + if ((val) < 10) \ + hex = '0' + (val); \ + else \ + hex = 'A' + (val) - 10; \ +} + +static void get_hex_str(gchar *out, guchar ch) +{ + gchar hex; + + INT_TO_HEX(hex, ch >> 4); + *out++ = hex; + INT_TO_HEX(hex, ch & 0x0f); + *out++ = hex; +} diff --git a/src/quoted-printable.h b/src/quoted-printable.h new file mode 100644 index 00000000..e5abf4f7 --- /dev/null +++ b/src/quoted-printable.h @@ -0,0 +1,36 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __QUOTED_PRINTABLE_H__ +#define __QUOTED_PRINTABLE_H__ + +#include + +void qp_encode_line (gchar *out, + const guchar *in); +gint qp_decode_line (gchar *str); + +gint qp_decode_q_encoding (guchar *out, + const gchar *in, + gint inlen); +gint qp_get_q_encoding_len (const guchar *str); +void qp_q_encode (gchar *out, + const guchar *in); + +#endif /* __QUOTED_PRINTABLE_H__ */ diff --git a/src/recv.c b/src/recv.c new file mode 100644 index 00000000..46cf27be --- /dev/null +++ b/src/recv.c @@ -0,0 +1,227 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "intl.h" +#include "recv.h" +#include "socket.h" +#include "utils.h" + +static RecvUIFunc recv_ui_func; +static gpointer recv_ui_func_data; + +gint recv_write_to_file(SockInfo *sock, const gchar *filename) +{ + FILE *fp; + gint ret; + + g_return_val_if_fail(filename != NULL, -1); + + if ((fp = fopen(filename, "wb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + recv_write(sock, NULL); + return -1; + } + + if (change_file_mode_rw(fp, filename) < 0) + FILE_OP_ERROR(filename, "chmod"); + + if ((ret = recv_write(sock, fp)) < 0) { + fclose(fp); + unlink(filename); + return ret; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(filename, "fclose"); + unlink(filename); + return -1; + } + + return 0; +} + +gint recv_bytes_write_to_file(SockInfo *sock, glong size, const gchar *filename) +{ + FILE *fp; + gint ret; + + g_return_val_if_fail(filename != NULL, -1); + + if ((fp = fopen(filename, "wb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + recv_write(sock, NULL); + return -1; + } + + if (change_file_mode_rw(fp, filename) < 0) + FILE_OP_ERROR(filename, "chmod"); + + if ((ret = recv_bytes_write(sock, size, fp)) < 0) { + fclose(fp); + unlink(filename); + return ret; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(filename, "fclose"); + unlink(filename); + return -1; + } + + return 0; +} + +gint recv_write(SockInfo *sock, FILE *fp) +{ + gchar buf[BUFFSIZE]; + gint len; + gint count = 0; + gint bytes = 0; + struct timeval tv_prev, tv_cur; + + gettimeofday(&tv_prev, NULL); + + for (;;) { + if (sock_gets(sock, buf, sizeof(buf)) < 0) { + g_warning(_("error occurred while retrieving data.\n")); + return -2; + } + + len = strlen(buf); + if (len > 1 && buf[0] == '.' && buf[1] == '\r') { + if (recv_ui_func) + recv_ui_func(sock, count, bytes, + recv_ui_func_data); + break; + } + count++; + bytes += len; + + if (recv_ui_func) { + gettimeofday(&tv_cur, NULL); + /* if elapsed time from previous update is greater + than 50msec, update UI */ + if (tv_cur.tv_sec - tv_prev.tv_sec > 0 || + tv_cur.tv_usec - tv_prev.tv_usec > UI_REFRESH_INTERVAL) { + gboolean ret; + ret = recv_ui_func(sock, count, bytes, + recv_ui_func_data); + if (ret == FALSE) return -1; + gettimeofday(&tv_prev, NULL); + } + } + + if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { + buf[len - 2] = '\n'; + buf[len - 1] = '\0'; + len--; + } + + if (buf[0] == '.' && buf[1] == '.') + memmove(buf, buf + 1, len--); + + if (!strncmp(buf, ">From ", 6)) + memmove(buf, buf + 1, len--); + + if (fp && fputs(buf, fp) == EOF) { + perror("fputs"); + g_warning(_("Can't write to file.\n")); + fp = NULL; + } + } + + if (!fp) return -1; + + return 0; +} + +gint recv_bytes_write(SockInfo *sock, glong size, FILE *fp) +{ + gchar *buf; + glong count = 0; + gchar *prev, *cur; + + if (size == 0) + return 0; + + buf = g_malloc(size); + + do { + gint read_count; + + read_count = sock_read(sock, buf + count, size - count); + if (read_count < 0) { + g_free(buf); + return -2; + } + count += read_count; + } while (count < size); + + /* +------------------+----------------+--------------------------+ * + * ^buf ^prev ^cur buf+size-1^ */ + + prev = buf; + while ((cur = memchr(prev, '\r', size - (prev - buf))) != NULL) { + if (cur == buf + size - 1) break; + + if (fwrite(prev, sizeof(gchar), cur - prev, fp) == EOF || + fwrite("\n", sizeof(gchar), 1, fp) == EOF) { + perror("fwrite"); + g_warning(_("Can't write to file.\n")); + g_free(buf); + return -1; + } + + if (*(cur + 1) == '\n') + prev = cur + 2; + else + prev = cur + 1; + + if (prev - buf >= size) break; + } + + if (prev - buf < size && fwrite(buf, sizeof(gchar), + size - (prev - buf), fp) == EOF) { + perror("fwrite"); + g_warning(_("Can't write to file.\n")); + g_free(buf); + return -1; + } + + g_free(buf); + return 0; +} + +void recv_set_ui_func(RecvUIFunc func, gpointer data) +{ + recv_ui_func = func; + recv_ui_func_data = data; +} diff --git a/src/recv.h b/src/recv.h new file mode 100644 index 00000000..e73a78f2 --- /dev/null +++ b/src/recv.h @@ -0,0 +1,46 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __RECV_H__ +#define __RECV_H__ + +#include + +#include "socket.h" + +typedef gboolean (*RecvUIFunc) (SockInfo *sock, + gint count, + gint read_bytes, + gpointer data); + +gint recv_write_to_file (SockInfo *sock, + const gchar *filename); +gint recv_bytes_write_to_file (SockInfo *sock, + glong size, + const gchar *filename); +gint recv_write (SockInfo *sock, + FILE *fp); +gint recv_bytes_write (SockInfo *sock, + glong size, + FILE *fp); + +void recv_set_ui_func (RecvUIFunc func, + gpointer data); + +#endif /* __RECV_H__ */ diff --git a/src/rfc2015.c b/src/rfc2015.c new file mode 100644 index 00000000..9ef38a24 --- /dev/null +++ b/src/rfc2015.c @@ -0,0 +1,1395 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_GPGME + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include + +#include "intl.h" +#include "procmime.h" +#include "procheader.h" +#include "base64.h" +#include "uuencode.h" +#include "unmime.h" +#include "codeconv.h" +#include "utils.h" +#include "prefs_common.h" +#include "passphrase.h" +#include "select-keys.h" +#include "sigstatus.h" +#include "rfc2015.h" + +#define DIM(v) (sizeof(v)/sizeof((v)[0])) + +static char *content_names[] = { + "Content-Type", + "Content-Disposition", + "Content-Transfer-Encoding", + NULL +}; + +static char *mime_version_name[] = { + "Mime-Version", + NULL +}; + +#if 0 +static void dump_mimeinfo (const char *text, MimeInfo *x) +{ + debug_print ("MimeInfo[%s] %p level=%d\n", + text, x, x? x->level:0 ); + if (!x) + return; + + debug_print (" enc=`%s' enc_type=%d mime_type=%d\n", + x->encoding, x->encoding_type, x->mime_type ); + debug_print (" cont_type=`%s' cs=`%s' name=`%s' bnd=`%s'\n", + x->content_type, x->charset, x->name, x->boundary ); + debug_print (" cont_disp=`%s' fname=`%s' fpos=%ld size=%u, lvl=%d\n", + x->content_disposition, x->filename, x->fpos, x->size, + x->level ); + dump_mimeinfo (".main", x->main ); + dump_mimeinfo (".sub", x->sub ); + dump_mimeinfo (".next", x->next ); + debug_print ("MimeInfo[.parent] %p\n", x ); + dump_mimeinfo (".children", x->children ); + dump_mimeinfo (".plaintext", x->plaintext ); +} + +static void dump_part ( MimeInfo *mimeinfo, FILE *fp ) +{ + unsigned int size = mimeinfo->size; + int c; + + if (fseek (fp, mimeinfo->fpos, SEEK_SET)) { + debug_print ("dump_part: fseek error\n"); + return; + } + + debug_print ("--- begin dump_part ----\n"); + while (size-- && (c = getc (fp)) != EOF) + putc (c, stderr); + if (ferror (fp)) + debug_print ("dump_part: read error\n"); + debug_print ("--- end dump_part ----\n"); +} +#endif + +void +rfc2015_disable_all (void) +{ + /* FIXME: set a flag, so that we don't bother the user with failed + * gpgme messages */ +} + + +void +rfc2015_secure_remove (const char *fname) +{ + if (!fname) + return; + /* fixme: overwrite the file first */ + remove (fname); +} + + +static const gchar * +sig_status_to_string (GpgmeSigStat status) +{ + const gchar *result; + + switch (status) { + case GPGME_SIG_STAT_NONE: + result = _("Oops: Signature not verified"); + break; + case GPGME_SIG_STAT_NOSIG: + result = _("No signature found"); + break; + case GPGME_SIG_STAT_GOOD: + result = _("Good signature"); + break; + case GPGME_SIG_STAT_BAD: + result = _("BAD signature"); + break; + case GPGME_SIG_STAT_NOKEY: + result = _("No public key to verify the signature"); + break; + case GPGME_SIG_STAT_ERROR: + result = _("Error verifying the signature"); + break; + case GPGME_SIG_STAT_DIFF: + result = _("Different results for signatures"); + break; + default: + result = _("Error: Unknown status"); + break; + } + + return result; +} + +static const gchar * +sig_status_with_name (GpgmeSigStat status) +{ + const gchar *result; + + switch (status) { + case GPGME_SIG_STAT_NONE: + result = _("Oops: Signature not verified"); + break; + case GPGME_SIG_STAT_NOSIG: + result = _("No signature found"); + break; + case GPGME_SIG_STAT_GOOD: + result = _("Good signature from \"%s\""); + break; + case GPGME_SIG_STAT_BAD: + result = _("BAD signature from \"%s\""); + break; + case GPGME_SIG_STAT_NOKEY: + result = _("No public key to verify the signature"); + break; + case GPGME_SIG_STAT_ERROR: + result = _("Error verifying the signature"); + break; + case GPGME_SIG_STAT_DIFF: + result = _("Different results for signatures"); + break; + default: + result = _("Error: Unknown status"); + break; + } + + return result; +} + +static void +sig_status_for_key(GString *str, GpgmeCtx ctx, GpgmeSigStat status, + GpgmeKey key, const gchar *fpr) +{ + gint idx = 0; + const char *uid; + + uid = gpgme_key_get_string_attr (key, GPGME_ATTR_USERID, NULL, idx); + if (uid == NULL) { + g_string_sprintfa (str, "%s\n", + sig_status_to_string (status)); + if ((fpr != NULL) && (*fpr != '\0')) + g_string_sprintfa (str, "Key fingerprint: %s\n", fpr); + g_string_append (str, _("Cannot find user ID for this key.")); + return; + } + g_string_sprintfa (str, sig_status_with_name (status), uid); + g_string_append (str, "\n"); + + while (1) { + uid = gpgme_key_get_string_attr (key, GPGME_ATTR_USERID, + NULL, ++idx); + if (uid == NULL) + break; + g_string_sprintfa (str, _(" aka \"%s\"\n"), + uid); + } +} + +static gchar * +sig_status_full (GpgmeCtx ctx) +{ + GString *str; + gint sig_idx = 0; + GpgmeError err; + GpgmeSigStat status; + GpgmeKey key; + const char *fpr; + time_t created; + struct tm *ctime_val; + char ctime_str[80]; + gchar *retval; + + str = g_string_new (""); + + fpr = gpgme_get_sig_status (ctx, sig_idx, &status, &created); + while (fpr != NULL) { + if (created != 0) { + ctime_val = localtime (&created); + strftime (ctime_str, sizeof (ctime_str), "%c", + ctime_val); + g_string_sprintfa (str, + _("Signature made at %s\n"), + ctime_str); + } + err = gpgme_get_sig_key (ctx, sig_idx, &key); + if (err != 0) { + g_string_sprintfa (str, "%s\n", + sig_status_to_string (status)); + if ((fpr != NULL) && (*fpr != '\0')) + g_string_sprintfa (str, + _("Key fingerprint: %s\n"), + fpr); + } else { + sig_status_for_key (str, ctx, status, key, fpr); + gpgme_key_unref (key); + } + g_string_append (str, "\n\n"); + + fpr = gpgme_get_sig_status (ctx, ++sig_idx, &status, &created); + } + + retval = str->str; + g_string_free (str, FALSE); + return retval; +} + +static void check_signature (MimeInfo *mimeinfo, MimeInfo *partinfo, FILE *fp) +{ + GpgmeCtx ctx = NULL; + GpgmeError err; + GpgmeData sig = NULL, text = NULL; + GpgmeSigStat status = GPGME_SIG_STAT_NONE; + GpgmegtkSigStatus statuswindow = NULL; + const char *result = NULL; + gchar *tmp_file; + gint n_exclude_chars = 0; + + if (prefs_common.gpg_signature_popup) + statuswindow = gpgmegtk_sig_status_create (); + + err = gpgme_new (&ctx); + if (err) { + debug_print ("gpgme_new failed: %s\n", gpgme_strerror (err)); + goto leave; + } + + /* don't include the last empty line. + It does not belong to the signed text */ + if (mimeinfo->children->size > 0) { + if (fseek(fp, mimeinfo->children->fpos + mimeinfo->children->size - 1, + SEEK_SET) < 0) { + perror("fseek"); + goto leave; + } + if (fgetc(fp) == '\n') { + n_exclude_chars++; + if (mimeinfo->children->size > 1) { + if (fseek(fp, mimeinfo->children->fpos + mimeinfo->children->size - 2, + SEEK_SET) < 0) { + perror("fseek"); + goto leave; + } + if (fgetc(fp) == '\r') + n_exclude_chars++; + } + } + } + + /* canonicalize the file part. */ + tmp_file = get_tmp_file(); + if (copy_file_part(fp, mimeinfo->children->fpos, + mimeinfo->children->size - n_exclude_chars, + tmp_file) < 0) { + g_free(tmp_file); + goto leave; + } + if (canonicalize_file_replace(tmp_file) < 0) { + unlink(tmp_file); + g_free(tmp_file); + goto leave; + } + + err = gpgme_data_new_from_file(&text, tmp_file, 1); + + unlink(tmp_file); + g_free(tmp_file); + + if (!err) + err = gpgme_data_new_from_filepart (&sig, NULL, fp, + partinfo->fpos, partinfo->size); + if (err) { + debug_print ("gpgme_data_new_from_filepart failed: %s\n", + gpgme_strerror (err)); + goto leave; + } + + err = gpgme_op_verify (ctx, sig, text, &status); + if (err) { + debug_print ("gpgme_op_verify failed: %s\n", gpgme_strerror (err)); + goto leave; + } + + /* FIXME: check what the heck this sig_status_full stuff is. + * it should better go into sigstatus.c */ + g_free (partinfo->sigstatus_full); + partinfo->sigstatus_full = sig_status_full (ctx); + +leave: + result = gpgmegtk_sig_status_to_string(status); + debug_print("verification status: %s\n", result); + if (prefs_common.gpg_signature_popup) + gpgmegtk_sig_status_update (statuswindow, ctx); + + g_free (partinfo->sigstatus); + partinfo->sigstatus = g_strdup (result); + + gpgme_data_release (sig); + gpgme_data_release (text); + gpgme_release (ctx); + if (prefs_common.gpg_signature_popup) + gpgmegtk_sig_status_destroy (statuswindow); +} + +/* + * Copy a gpgme data object to a temporary file and + * return this filename + */ +#if 0 +static char * +copy_gpgmedata_to_temp (GpgmeData data, guint *length) +{ + static int id; + char *tmp; + FILE *fp; + char buf[100]; + size_t nread; + GpgmeError err; + + tmp = g_strdup_printf("%s%cgpgtmp.%08x", + get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id ); + + if ((fp = fopen(tmp, "wb")) == NULL) { + FILE_OP_ERROR(tmp, "fopen"); + g_free(tmp); + return NULL; + } + + err = gpgme_data_rewind ( data ); + if (err) + debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err)); + + while (!(err = gpgme_data_read (data, buf, 100, &nread))) { + fwrite ( buf, nread, 1, fp ); + } + + if (err != GPGME_EOF) + debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err)); + + fclose (fp); + *length = nread; + + return tmp; +} +#endif + +static GpgmeData +pgp_decrypt (MimeInfo *partinfo, FILE *fp) +{ + GpgmeCtx ctx = NULL; + GpgmeError err; + GpgmeData cipher = NULL, plain = NULL; + struct passphrase_cb_info_s info; + + memset (&info, 0, sizeof info); + + err = gpgme_new (&ctx); + if (err) { + debug_print ("gpgme_new failed: %s\n", gpgme_strerror (err)); + goto leave; + } + + err = gpgme_data_new_from_filepart (&cipher, NULL, fp, + partinfo->fpos, partinfo->size); + if (err) { + debug_print ("gpgme_data_new_from_filepart failed: %s\n", + gpgme_strerror (err)); + goto leave; + } + + err = gpgme_data_new (&plain); + if (err) { + debug_print ("gpgme_new failed: %s\n", gpgme_strerror (err)); + goto leave; + } + + if (!getenv("GPG_AGENT_INFO")) { + info.c = ctx; + gpgme_set_passphrase_cb (ctx, gpgmegtk_passphrase_cb, &info); + } + + err = gpgme_op_decrypt (ctx, cipher, plain); + +leave: + gpgme_data_release (cipher); + if (err) { + gpgmegtk_free_passphrase(); + debug_print ("decryption failed: %s\n", gpgme_strerror (err)); + gpgme_data_release (plain); + plain = NULL; + } + else + debug_print ("** decryption succeeded\n"); + + gpgme_release (ctx); + return plain; +} + +MimeInfo * rfc2015_find_signature (MimeInfo *mimeinfo) +{ + MimeInfo *partinfo; + int n = 0; + + if (!mimeinfo) + return NULL; + if (g_strcasecmp (mimeinfo->content_type, "multipart/signed")) + return NULL; + + debug_print ("** multipart/signed encountered\n"); + + /* check that we have at least 2 parts of the correct type */ + for (partinfo = mimeinfo->children; + partinfo != NULL; partinfo = partinfo->next) { + if (++n > 1 && !g_strcasecmp (partinfo->content_type, + "application/pgp-signature")) + break; + } + + return partinfo; +} + +gboolean rfc2015_has_signature (MimeInfo *mimeinfo) +{ + return rfc2015_find_signature (mimeinfo) != NULL; +} + +void rfc2015_check_signature (MimeInfo *mimeinfo, FILE *fp) +{ + MimeInfo *partinfo; + + partinfo = rfc2015_find_signature (mimeinfo); + if (!partinfo) + return; + +#if 0 + g_message ("** yep, it is a pgp signature"); + dump_mimeinfo ("gpg-signature", partinfo ); + dump_part (partinfo, fp ); + dump_mimeinfo ("signed text", mimeinfo->children ); + dump_part (mimeinfo->children, fp); +#endif + + check_signature (mimeinfo, partinfo, fp); +} + +int rfc2015_is_encrypted (MimeInfo *mimeinfo) +{ + if (!mimeinfo || mimeinfo->mime_type != MIME_MULTIPART) + return 0; + if (g_strcasecmp (mimeinfo->content_type, "multipart/encrypted")) + return 0; + /* fixme: we should check the protocol parameter */ + return 1; +} + +gboolean rfc2015_msg_is_encrypted (const gchar *file) +{ + FILE *fp; + MimeInfo *mimeinfo; + int ret; + + if ((fp = fopen(file, "rb")) == NULL) + return FALSE; + + mimeinfo = procmime_scan_mime_header(fp); + if(!mimeinfo) { + fclose(fp); + return FALSE; + } + + ret = rfc2015_is_encrypted(mimeinfo); + procmime_mimeinfo_free_all(mimeinfo); + return ret != 0 ? TRUE : FALSE; +} + +static int +name_cmp(const char *a, const char *b) +{ + for( ; *a && *b; a++, b++) { + if(*a != *b + && toupper(*(unsigned char *)a) != toupper(*(unsigned char *)b)) + return 1; + } + + return *a != *b; +} + +static int +headerp(char *p, char **names) +{ + int i, c; + char *p2; + + p2 = strchr(p, ':'); + if(!p2 || p == p2) { + return 0; + } + if(p2[-1] == ' ' || p2[-1] == '\t') { + return 0; + } + + if(!names[0]) + return 1; + + c = *p2; + *p2 = 0; + for(i = 0 ; names[i] != NULL; i++) { + if(!name_cmp (names[i], p)) + break; + } + *p2 = c; + + return names[i] != NULL; +} + + +#define DECRYPTION_ABORT() \ +{ \ + procmime_mimeinfo_free_all(tmpinfo); \ + msginfo->decryption_failed = 1; \ + return; \ +} + +void rfc2015_decrypt_message (MsgInfo *msginfo, MimeInfo *mimeinfo, FILE *fp) +{ + static int id; + MimeInfo *tmpinfo, *partinfo; + int ver_ok = 0; + char *fname; + GpgmeData plain; + FILE *dstfp; + size_t nread; + char buf[BUFFSIZE]; + int in_cline; + GpgmeError err; + + g_return_if_fail (msginfo != NULL); + g_return_if_fail (mimeinfo != NULL); + g_return_if_fail (fp != NULL); + g_return_if_fail (mimeinfo->mime_type == MIME_MULTIPART); + + debug_print ("** decrypting multipart/encrypted message\n"); + + /* skip headers */ + if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) + perror("fseek"); + tmpinfo = procmime_scan_mime_header(fp); + if (!tmpinfo || tmpinfo->mime_type != MIME_MULTIPART) { + DECRYPTION_ABORT(); + } + + procmime_scan_multipart_message(tmpinfo, fp); + + /* check that we have the 2 parts */ + partinfo = tmpinfo->children; + if (!partinfo || !partinfo->next) { + DECRYPTION_ABORT(); + } + if (!g_strcasecmp (partinfo->content_type, "application/pgp-encrypted")) { + /* Fixme: check that the version is 1 */ + ver_ok = 1; + } + partinfo = partinfo->next; + if (ver_ok && + !g_strcasecmp (partinfo->content_type, "application/octet-stream")) { + if (partinfo->next) + g_warning ("oops: pgp_encrypted with more than 2 parts"); + } + else { + DECRYPTION_ABORT(); + } + + debug_print ("** yep, it is pgp encrypted\n"); + + plain = pgp_decrypt (partinfo, fp); + if (!plain) { + DECRYPTION_ABORT(); + } + + fname = g_strdup_printf("%s%cplaintext.%08x", + get_mime_tmp_dir(), G_DIR_SEPARATOR, ++id); + + if ((dstfp = fopen(fname, "wb")) == NULL) { + FILE_OP_ERROR(fname, "fopen"); + g_free(fname); + DECRYPTION_ABORT(); + } + + /* write the orginal header to the new file */ + if (fseek(fp, tmpinfo->fpos, SEEK_SET) < 0) + perror("fseek"); + + in_cline = 0; + while (fgets(buf, sizeof(buf), fp)) { + if (headerp (buf, content_names)) { + in_cline = 1; + continue; + } + if (in_cline) { + if (buf[0] == ' ' || buf[0] == '\t') + continue; + in_cline = 0; + } + if (buf[0] == '\r' || buf[0] == '\n') + break; + fputs (buf, dstfp); + } + + err = gpgme_data_rewind (plain); + if (err) + debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err)); + + while (!(err = gpgme_data_read (plain, buf, sizeof(buf), &nread))) { + fwrite (buf, nread, 1, dstfp); + } + + if (err != GPGME_EOF) { + debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err)); + } + + fclose (dstfp); + procmime_mimeinfo_free_all(tmpinfo); + + msginfo->plaintext_file = fname; + msginfo->decryption_failed = 0; +} + +#undef DECRYPTION_ABORT + + +/* + * plain contains an entire mime object. + * Encrypt it and return an GpgmeData object with the encrypted version of + * the file or NULL in case of error. + */ +static GpgmeData +pgp_encrypt ( GpgmeData plain, GpgmeRecipients rset ) +{ + GpgmeCtx ctx = NULL; + GpgmeError err; + GpgmeData cipher = NULL; + + err = gpgme_new (&ctx); + if (!err) + err = gpgme_data_new (&cipher); + if (!err) { + gpgme_set_armor (ctx, 1); + err = gpgme_op_encrypt (ctx, rset, plain, cipher); + } + + if (err) { + debug_print ("encryption failed: %s\n", gpgme_strerror (err)); + gpgme_data_release (cipher); + cipher = NULL; + } + else { + debug_print ("** encryption succeeded\n"); + } + + gpgme_release (ctx); + return cipher; +} + +/* + * Create and return a list of keys matching a key id + */ + +GSList *rfc2015_create_signers_list (const char *keyid) +{ + GSList *key_list = NULL; + GpgmeCtx list_ctx = NULL; + GSList *p; + GpgmeError err; + GpgmeKey key; + + err = gpgme_new (&list_ctx); + if (err) + goto leave; + err = gpgme_op_keylist_start (list_ctx, keyid, 1); + if (err) + goto leave; + while ( !(err = gpgme_op_keylist_next (list_ctx, &key)) ) { + key_list = g_slist_append (key_list, key); + } + if (err != GPGME_EOF) + goto leave; + err = 0; + if (key_list == NULL) { + debug_print ("no keys found for keyid \"%s\"\n", keyid); + } + +leave: + if (err) { + debug_print ("rfc2015_create_signers_list failed: %s\n", gpgme_strerror (err)); + for (p = key_list; p != NULL; p = p->next) + gpgme_key_unref ((GpgmeKey) p->data); + g_slist_free (key_list); + } + if (list_ctx) + gpgme_release (list_ctx); + return err ? NULL : key_list; +} + +/* + * Encrypt the file by extracting all recipients and finding the + * encryption keys for all of them. The file content is then replaced + * by the encrypted one. */ +int +rfc2015_encrypt (const char *file, GSList *recp_list, gboolean ascii_armored) +{ + FILE *fp = NULL; + char buf[BUFFSIZE]; + int i, clineidx, saved_last; + char *clines[3] = {NULL}; + GpgmeError err; + GpgmeData header = NULL; + GpgmeData plain = NULL; + GpgmeData cipher = NULL; + GpgmeRecipients rset = NULL; + size_t nread; + int mime_version_seen = 0; + char *boundary; + + boundary = generate_mime_boundary ("Encrypt"); + + /* Create the list of recipients */ + rset = gpgmegtk_recipient_selection (recp_list); + if (!rset) { + debug_print ("error creating recipient list\n" ); + goto failure; + } + + /* Open the source file */ + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + goto failure; + } + + err = gpgme_data_new (&header); + if (!err) + err = gpgme_data_new (&plain); + if (err) { + debug_print ("gpgme_data_new failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + /* get the content header lines from the source */ + clineidx = 0; + saved_last = 0; + while (!err && fgets(buf, sizeof(buf), fp)) { + /* fixme: check for overlong lines */ + if (headerp (buf, content_names)) { + if (clineidx >= DIM (clines)) { + debug_print ("rfc2015_encrypt: too many content lines\n"); + goto failure; + } + clines[clineidx++] = g_strdup (buf); + saved_last = 1; + continue; + } + if (saved_last) { + if (*buf == ' ' || *buf == '\t') { + char *last = clines[clineidx - 1]; + clines[clineidx - 1] = g_strconcat (last, buf, NULL); + g_free (last); + continue; + } + saved_last = 0; + } + + if (headerp (buf, mime_version_name)) + mime_version_seen = 1; + + if (buf[0] == '\r' || buf[0] == '\n') + break; + err = gpgme_data_write (header, buf, strlen (buf)); + } + if (ferror (fp)) { + FILE_OP_ERROR (file, "fgets"); + goto failure; + } + + /* write them to the temp data and add the rest of the message */ + for (i = 0; !err && i < clineidx; i++) { + debug_print ("%% %s:%d: cline=`%s'", __FILE__ ,__LINE__, clines[i]); + err = gpgme_data_write (plain, clines[i], strlen (clines[i])); + } + if (!err) + err = gpgme_data_write (plain, "\r\n", 2); + while (!err && fgets(buf, sizeof(buf), fp)) { + err = gpgme_data_write (plain, buf, strlen (buf)); + } + if (ferror (fp)) { + FILE_OP_ERROR (file, "fgets"); + goto failure; + } + if (err) { + debug_print ("gpgme_data_write failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + cipher = pgp_encrypt (plain, rset); + gpgme_data_release (plain); plain = NULL; + gpgme_recipients_release (rset); rset = NULL; + if (!cipher) + goto failure; + + /* we have the encrypted message available in cipher and now we + * are going to rewrite the source file. To be sure that file has + * been truncated we use an approach which should work everywhere: + * close the file and then reopen it for writing. It is important + * that this works, otherwise it may happen that parts of the + * plaintext are still in the file (The encrypted stuff is, due to + * compression, usually shorter than the plaintext). + * + * Yes, there is a race condition here, but everyone, who is so + * stupid to store the temp file with the plaintext in a public + * directory has to live with this anyway. */ + if (fclose (fp)) { + FILE_OP_ERROR(file, "fclose"); + goto failure; + } + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + goto failure; + } + + /* Write the header, append new content lines, part 1 and part 2 header */ + err = gpgme_data_rewind (header); + if (err) { + debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err)); + goto failure; + } + while (!(err = gpgme_data_read (header, buf, BUFFSIZE, &nread))) { + fwrite (buf, nread, 1, fp); + } + if (err != GPGME_EOF) { + debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err)); + goto failure; + } + if (ferror (fp)) { + FILE_OP_ERROR (file, "fwrite"); + goto failure; + } + gpgme_data_release (header); header = NULL; + + if (!mime_version_seen) + fputs ("MIME-Version: 1\r\n", fp); + + if (ascii_armored) { + fprintf(fp, + "Content-Type: text/plain; charset=US-ASCII\r\n" + "Content-Transfer-Encoding: 7bit\r\n" + "\r\n"); + } else { + fprintf (fp, + "Content-Type: multipart/encrypted;" + " protocol=\"application/pgp-encrypted\";\r\n" + " boundary=\"%s\"\r\n" + "\r\n" + "--%s\r\n" + "Content-Type: application/pgp-encrypted\r\n" + "\r\n" + "Version: 1\r\n" + "\r\n" + "--%s\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n", + boundary, boundary, boundary); + } + + /* append the encrypted stuff */ + err = gpgme_data_rewind (cipher); + if (err) { + debug_print ("** gpgme_data_rewind on cipher failed: %s\n", + gpgme_strerror (err)); + goto failure; + } + + while (!(err = gpgme_data_read (cipher, buf, BUFFSIZE, &nread))) { + fwrite (buf, nread, 1, fp); + } + if (err != GPGME_EOF) { + debug_print ("** gpgme_data_read failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + /* and the final boundary */ + if (!ascii_armored) { + fprintf (fp, + "\r\n" + "--%s--\r\n", + boundary); + } + fflush (fp); + if (ferror (fp)) { + FILE_OP_ERROR (file, "fwrite"); + goto failure; + } + fclose (fp); + gpgme_data_release (cipher); + return 0; + +failure: + if (fp) + fclose (fp); + gpgme_data_release (header); + gpgme_data_release (plain); + gpgme_data_release (cipher); + gpgme_recipients_release (rset); + g_free (boundary); + return -1; /* error */ +} + +/* + * plain contains an entire mime object. Sign it and return an + * GpgmeData object with the signature of it or NULL in case of error. + * r_siginfo returns an XML object with information about the signature. + */ +static GpgmeData +pgp_sign (GpgmeData plain, GSList *key_list, gboolean clearsign, + char **r_siginfo) +{ + GSList *p; + GpgmeCtx ctx = NULL; + GpgmeError err; + GpgmeData sig = NULL; + struct passphrase_cb_info_s info; + + *r_siginfo = NULL; + memset (&info, 0, sizeof info); + + err = gpgme_new (&ctx); + if (err) + goto leave; + err = gpgme_data_new (&sig); + if (err) + goto leave; + + if (!getenv("GPG_AGENT_INFO")) { + info.c = ctx; + gpgme_set_passphrase_cb (ctx, gpgmegtk_passphrase_cb, &info); + } + gpgme_set_textmode (ctx, 1); + gpgme_set_armor (ctx, 1); + gpgme_signers_clear (ctx); + for (p = key_list; p != NULL; p = p->next) { + err = gpgme_signers_add (ctx, (GpgmeKey) p->data); + if (err) + goto leave; + } + for (p = key_list; p != NULL; p = p->next) + gpgme_key_unref ((GpgmeKey) p->data); + g_slist_free (key_list); + + if (err) + goto leave; + err = gpgme_op_sign + (ctx, plain, sig, + clearsign ? GPGME_SIG_MODE_CLEAR : GPGME_SIG_MODE_DETACH); + if (!err) + *r_siginfo = gpgme_get_op_info (ctx, 0); + +leave: + if (err) { + gpgmegtk_free_passphrase(); + debug_print ("signing failed: %s\n", gpgme_strerror (err)); + gpgme_data_release (sig); + sig = NULL; + } + else { + debug_print ("signing succeeded\n"); + } + + gpgme_release (ctx); + return sig; +} + +/* + * Find TAG in XML and return a pointer into xml set just behind the + * closing angle. Return NULL if not found. + */ +static const char * +find_xml_tag (const char *xml, const char *tag) +{ + int taglen = strlen (tag); + const char *s = xml; + + while ( (s = strchr (s, '<')) ) { + s++; + if (!strncmp (s, tag, taglen)) { + const char *s2 = s + taglen; + if (*s2 == '>' || isspace (*(const unsigned char*)s2) ) { + /* found */ + while (*s2 && *s2 != '>') /* skip attributes */ + s2++; + /* fixme: do need to handle angles inside attribute vallues? */ + return *s2? (s2+1):NULL; + } + } + while (*s && *s != '>') /* skip to end of tag */ + s++; + } + return NULL; +} + + +/* + * Extract the micalg from an GnupgOperationInfo XML container. + */ +static char * +extract_micalg (char *xml) +{ + const char *s; + + s = find_xml_tag (xml, "GnupgOperationInfo"); + if (s) { + const char *s_end = find_xml_tag (s, "/GnupgOperationInfo"); + s = find_xml_tag (s, "signature"); + if (s && s_end && s < s_end) { + const char *s_end2 = find_xml_tag (s, "/signature"); + if (s_end2 && s_end2 < s_end) { + s = find_xml_tag (s, "micalg"); + if (s && s < s_end2) { + s_end = strchr (s, '<'); + if (s_end) { + char *p = g_malloc (s_end - s + 1); + memcpy (p, s, s_end - s); + p[s_end-s] = 0; + return p; + } + } + } + } + } + return NULL; +} + + +/* + * Sign the file and replace its content with the signed one. + */ +int +rfc2015_sign (const char *file, GSList *key_list) +{ + FILE *fp = NULL; + char buf[BUFFSIZE]; + int i, clineidx, saved_last; + char *clines[3] = {NULL}; + GpgmeError err; + GpgmeData header = NULL; + GpgmeData plain = NULL; + GpgmeData sigdata = NULL; + size_t nread; + int mime_version_seen = 0; + char *boundary; + char *micalg = NULL; + char *siginfo; + + boundary = generate_mime_boundary ("Signature"); + + /* Open the source file */ + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + goto failure; + } + + err = gpgme_data_new (&header); + if (!err) + err = gpgme_data_new (&plain); + if (err) { + debug_print ("gpgme_data_new failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + /* get the content header lines from the source */ + clineidx = 0; + saved_last = 0; + while (!err && fgets(buf, sizeof(buf), fp)) { + /* fixme: check for overlong lines */ + if (headerp (buf, content_names)) { + if (clineidx >= DIM (clines)) { + debug_print ("rfc2015_sign: too many content lines\n"); + goto failure; + } + clines[clineidx++] = g_strdup (buf); + saved_last = 1; + continue; + } + if (saved_last) { + if (*buf == ' ' || *buf == '\t') { + char *last = clines[clineidx - 1]; + clines[clineidx - 1] = g_strconcat (last, buf, NULL); + g_free (last); + continue; + } + saved_last = 0; + } + + if (headerp (buf, mime_version_name)) + mime_version_seen = 1; + + if (buf[0] == '\r' || buf[0] == '\n') + break; + err = gpgme_data_write (header, buf, strlen (buf)); + } + if (ferror (fp)) { + FILE_OP_ERROR (file, "fgets"); + goto failure; + } + + /* write them to the temp data and add the rest of the message */ + for (i = 0; !err && i < clineidx; i++) { + err = gpgme_data_write (plain, clines[i], strlen (clines[i])); + } + if (!err) + err = gpgme_data_write (plain, "\r\n", 2 ); + while (!err && fgets(buf, sizeof(buf), fp)) { + err = gpgme_data_write (plain, buf, strlen (buf)); + } + if (ferror (fp)) { + FILE_OP_ERROR (file, "fgets"); + goto failure; + } + if (err) { + debug_print ("gpgme_data_write failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + sigdata = pgp_sign (plain, key_list, FALSE, &siginfo); + if (siginfo) { + micalg = extract_micalg (siginfo); + free (siginfo); + } + if (!sigdata) + goto failure; + + /* we have the signed message available in sigdata and now we are + * going to rewrite the original file. To be sure that file has + * been truncated we use an approach which should work everywhere: + * close the file and then reopen it for writing. */ + if (fclose (fp)) { + FILE_OP_ERROR(file, "fclose"); + goto failure; + } + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + goto failure; + } + + /* Write the rfc822 header and add new content lines */ + err = gpgme_data_rewind (header); + if (err) + debug_print ("gpgme_data_rewind failed: %s\n", gpgme_strerror (err)); + while (!(err = gpgme_data_read (header, buf, BUFFSIZE, &nread))) { + fwrite (buf, nread, 1, fp); + } + if (err != GPGME_EOF) { + debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err)); + goto failure; + } + if (ferror (fp)) { + FILE_OP_ERROR (file, "fwrite"); + goto failure; + } + gpgme_data_release (header); + header = NULL; + + if (!mime_version_seen) + fputs ("MIME-Version: 1.0\r\n", fp); + fprintf (fp, "Content-Type: multipart/signed; " + "protocol=\"application/pgp-signature\";\r\n"); + if (micalg) + fprintf (fp, " micalg=\"%s\";\r\n", micalg); + fprintf (fp, " boundary=\"%s\"\r\n", boundary); + + /* Part 1: signed material */ + fprintf (fp, "\r\n" + "--%s\r\n", + boundary); + err = gpgme_data_rewind (plain); + if (err) { + debug_print ("gpgme_data_rewind on plain failed: %s\n", + gpgme_strerror (err)); + goto failure; + } + while (!(err = gpgme_data_read (plain, buf, BUFFSIZE, &nread))) { + fwrite (buf, nread, 1, fp); + } + if (err != GPGME_EOF) { + debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + /* Part 2: signature */ + fprintf (fp, "\r\n" + "--%s\r\n", + boundary); + fputs ("Content-Type: application/pgp-signature\r\n" + "\r\n", fp); + + err = gpgme_data_rewind (sigdata); + if (err) { + debug_print ("gpgme_data_rewind on sigdata failed: %s\n", + gpgme_strerror (err)); + goto failure; + } + + while (!(err = gpgme_data_read (sigdata, buf, BUFFSIZE, &nread))) { + fwrite (buf, nread, 1, fp); + } + if (err != GPGME_EOF) { + debug_print ("gpgme_data_read failed: %s\n", gpgme_strerror (err)); + goto failure; + } + + /* Final boundary */ + fprintf (fp, "\r\n" + "--%s--\r\n", + boundary); + fflush (fp); + if (ferror (fp)) { + FILE_OP_ERROR (file, "fwrite"); + goto failure; + } + fclose (fp); + gpgme_data_release (header); + gpgme_data_release (plain); + gpgme_data_release (sigdata); + g_free (boundary); + g_free (micalg); + return 0; + +failure: + if (fp) + fclose (fp); + gpgme_data_release (header); + gpgme_data_release (plain); + gpgme_data_release (sigdata); + g_free (boundary); + g_free (micalg); + return -1; /* error */ +} + + +/* + * Sign the file with clear text and replace its content with the signed one. + */ +gint +rfc2015_clearsign (const gchar *file, GSList *key_list) +{ + FILE *fp; + gchar buf[BUFFSIZE]; + GpgmeError err; + GpgmeData text = NULL; + GpgmeData sigdata = NULL; + size_t nread; + gchar *siginfo; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + goto failure; + } + + err = gpgme_data_new(&text); + if (err) { + debug_print("gpgme_data_new failed: %s\n", gpgme_strerror(err)); + goto failure; + } + + while (!err && fgets(buf, sizeof(buf), fp)) { + err = gpgme_data_write(text, buf, strlen(buf)); + } + if (ferror(fp)) { + FILE_OP_ERROR(file, "fgets"); + goto failure; + } + if (err) { + debug_print("gpgme_data_write failed: %s\n", gpgme_strerror(err)); + goto failure; + } + + sigdata = pgp_sign(text, key_list, TRUE, &siginfo); + if (siginfo) { + g_free(siginfo); + } + if (!sigdata) + goto failure; + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + fp = NULL; + goto failure; + } + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + goto failure; + } + + err = gpgme_data_rewind(sigdata); + if (err) { + debug_print("gpgme_data_rewind on sigdata failed: %s\n", + gpgme_strerror(err)); + goto failure; + } + + while (!(err = gpgme_data_read(sigdata, buf, sizeof(buf), &nread))) { + fwrite(buf, nread, 1, fp); + } + if (err != GPGME_EOF) { + debug_print("gpgme_data_read failed: %s\n", gpgme_strerror(err)); + goto failure; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + fp = NULL; + goto failure; + } + gpgme_data_release(text); + gpgme_data_release(sigdata); + return 0; + +failure: + if (fp) + fclose(fp); + gpgme_data_release(text); + gpgme_data_release(sigdata); + return -1; +} + +#endif /* USE_GPGME */ diff --git a/src/rfc2015.h b/src/rfc2015.h new file mode 100644 index 00000000..d9def6fb --- /dev/null +++ b/src/rfc2015.h @@ -0,0 +1,48 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __RFC2015_H__ +#define __RFC2015_H__ + +#include +#include + +#include "procmime.h" + +void rfc2015_disable_all (void); +void rfc2015_secure_remove (const gchar *fname); +MimeInfo *rfc2015_find_signature (MimeInfo *mimeinfo); +gboolean rfc2015_has_signature (MimeInfo *mimeinfo); +void rfc2015_check_signature (MimeInfo *mimeinfo, + FILE *fp); +gint rfc2015_is_encrypted (MimeInfo *mimeinfo); +gboolean rfc2015_msg_is_encrypted (const gchar *file); +void rfc2015_decrypt_message (MsgInfo *msginfo, + MimeInfo *mimeinfo, + FILE *fp); +GSList *rfc2015_create_signers_list (const gchar *keyid); +gint rfc2015_encrypt (const gchar *file, + GSList *recp_list, + gboolean ascii_armored); +gint rfc2015_sign (const gchar *file, + GSList *key_list); +gint rfc2015_clearsign (const gchar *file, + GSList *key_list); + +#endif /* __RFC2015_H__ */ diff --git a/src/select-keys.c b/src/select-keys.c new file mode 100644 index 00000000..24e82a39 --- /dev/null +++ b/src/select-keys.c @@ -0,0 +1,525 @@ +/* select-keys.c - GTK+ based key selection + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef USE_GPGME +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "select-keys.h" +#include "utils.h" +#include "gtkutils.h" +#include "inputdialog.h" +#include "manage_window.h" + +#define DIM(v) (sizeof(v)/sizeof((v)[0])) +#define DIMof(type,member) DIM(((type *)0)->member) + + +enum col_titles { + COL_ALGO, + COL_KEYID, + COL_NAME, + COL_EMAIL, + COL_VALIDITY, + + N_COL_TITLES +}; + +struct select_keys_s { + int okay; + GtkWidget *window; + GtkLabel *toplabel; + GtkCList *clist; + const char *pattern; + GpgmeRecipients rset; + GpgmeCtx select_ctx; + + GtkSortType sort_type; + enum col_titles sort_column; + +}; + + +static void set_row (GtkCList *clist, GpgmeKey key); +static void fill_clist (struct select_keys_s *sk, const char *pattern); +static void create_dialog (struct select_keys_s *sk); +static void open_dialog (struct select_keys_s *sk); +static void close_dialog (struct select_keys_s *sk); +static gint delete_event_cb (GtkWidget *widget, + GdkEventAny *event, gpointer data); +static gboolean key_pressed_cb (GtkWidget *widget, + GdkEventKey *event, gpointer data); +static void select_btn_cb (GtkWidget *widget, gpointer data); +static void cancel_btn_cb (GtkWidget *widget, gpointer data); +static void other_btn_cb (GtkWidget *widget, gpointer data); +static void sort_keys (struct select_keys_s *sk, enum col_titles column); +static void sort_keys_name (GtkWidget *widget, gpointer data); +static void sort_keys_email (GtkWidget *widget, gpointer data); + + +static void +update_progress (struct select_keys_s *sk, int running, const char *pattern) +{ + static int windmill[] = { '-', '\\', '|', '/' }; + char *buf; + + if (!running) + buf = g_strdup_printf (_("Please select key for `%s'"), + pattern); + else + buf = g_strdup_printf (_("Collecting info for `%s' ... %c"), + pattern, + windmill[running%DIM(windmill)]); + gtk_label_set_text (sk->toplabel, buf); + g_free (buf); +} + + +/** + * select_keys_get_recipients: + * @recp_names: A list of email addresses + * + * Select a list of recipients from a given list of email addresses. + * This may pop up a window to present the user a choice, it will also + * check that the recipients key are all valid. + * + * Return value: NULL on error or a list of list of recipients. + **/ +GpgmeRecipients +gpgmegtk_recipient_selection (GSList *recp_names) +{ + struct select_keys_s sk; + GpgmeError err; + + memset (&sk, 0, sizeof sk); + + err = gpgme_recipients_new (&sk.rset); + if (err) { + g_warning ("failed to allocate recipients set: %s", + gpgme_strerror (err)); + return NULL; + } + + open_dialog (&sk); + + do { + sk.pattern = recp_names? recp_names->data:NULL; + gtk_clist_clear (sk.clist); + fill_clist (&sk, sk.pattern); + update_progress (&sk, 0, sk.pattern); + gtk_main (); + if (recp_names) + recp_names = recp_names->next; + } while (sk.okay && recp_names); + + close_dialog (&sk); + + if (!sk.okay) { + gpgme_recipients_release (sk.rset); + sk.rset = NULL; + } + return sk.rset; +} + +static void +destroy_key (gpointer data) +{ + GpgmeKey key = data; + gpgme_key_release (key); +} + +static void +set_row (GtkCList *clist, GpgmeKey key) +{ + const char *s; + const char *text[N_COL_TITLES]; + char *algo_buf; + int row; + + /* first check whether the key is capable of encryption which is not + * the case for revoked, expired or sign-only keys */ + if ( !gpgme_key_get_ulong_attr (key, GPGME_ATTR_CAN_ENCRYPT, NULL, 0 ) ) + return; + + algo_buf = g_strdup_printf ("%lu/%s", + gpgme_key_get_ulong_attr (key, GPGME_ATTR_LEN, NULL, 0 ), + gpgme_key_get_string_attr (key, GPGME_ATTR_ALGO, NULL, 0 ) ); + text[COL_ALGO] = algo_buf; + + s = gpgme_key_get_string_attr (key, GPGME_ATTR_KEYID, NULL, 0); + if (strlen (s) == 16) + s += 8; /* show only the short keyID */ + text[COL_KEYID] = s; + + s = gpgme_key_get_string_attr (key, GPGME_ATTR_NAME, NULL, 0); + text[COL_NAME] = s; + + s = gpgme_key_get_string_attr (key, GPGME_ATTR_EMAIL, NULL, 0); + text[COL_EMAIL] = s; + + s = gpgme_key_get_string_attr (key, GPGME_ATTR_VALIDITY, NULL, 0); + text[COL_VALIDITY] = s; + + row = gtk_clist_append (clist, (gchar**)text); + g_free (algo_buf); + + gtk_clist_set_row_data_full (clist, row, key, destroy_key); +} + + +static void +fill_clist (struct select_keys_s *sk, const char *pattern) +{ + GtkCList *clist; + GpgmeCtx ctx; + GpgmeError err; + GpgmeKey key; + int running=0; + + g_return_if_fail (sk); + clist = sk->clist; + g_return_if_fail (clist); + + debug_print ("select_keys:fill_clist: pattern `%s'\n", pattern); + + /*gtk_clist_freeze (select_keys.clist);*/ + err = gpgme_new (&ctx); + g_assert (!err); + + sk->select_ctx = ctx; + + update_progress (sk, ++running, pattern); + while (gtk_events_pending ()) + gtk_main_iteration (); + + err = gpgme_op_keylist_start (ctx, pattern, 0); + if (err) { + debug_print ("** gpgme_op_keylist_start(%s) failed: %s", + pattern, gpgme_strerror (err)); + sk->select_ctx = NULL; + return; + } + update_progress (sk, ++running, pattern); + while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) { + debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ ); + set_row (clist, key ); key = NULL; + update_progress (sk, ++running, pattern); + while (gtk_events_pending ()) + gtk_main_iteration (); + } + debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ ); + if (err != GPGME_EOF) + debug_print ("** gpgme_op_keylist_next failed: %s", + gpgme_strerror (err)); + sk->select_ctx = NULL; + gpgme_release (ctx); + /*gtk_clist_thaw (select_keys.clist);*/ +} + + +static void +create_dialog (struct select_keys_s *sk) +{ + GtkWidget *window; + GtkWidget *vbox, *vbox2, *hbox; + GtkWidget *bbox; + GtkWidget *scrolledwin; + GtkWidget *clist; + GtkWidget *label; + GtkWidget *select_btn, *cancel_btn, *other_btn; + const char *titles[N_COL_TITLES]; + + g_assert (!sk->window); + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 520, 280); + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + gtk_window_set_title (GTK_WINDOW (window), _("Select Keys")); + gtk_window_set_modal (GTK_WINDOW (window), TRUE); + g_signal_connect (G_OBJECT (window), "delete_event", + G_CALLBACK (delete_event_cb), sk); + g_signal_connect (G_OBJECT (window), "key_press_event", + G_CALLBACK (key_pressed_cb), sk); + MANAGE_WINDOW_SIGNALS_CONNECT (window); + + vbox = gtk_vbox_new (FALSE, 8); + gtk_container_add (GTK_CONTAINER (window), vbox); + + hbox = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new ( "" ); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); + + scrolledwin = gtk_scrolled_window_new (NULL, NULL); + gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + titles[COL_ALGO] = _("Size"); + titles[COL_KEYID] = _("Key ID"); + titles[COL_NAME] = _("Name"); + titles[COL_EMAIL] = _("Address"); + titles[COL_VALIDITY] = _("Val"); + + clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles); + gtk_container_add (GTK_CONTAINER (scrolledwin), clist); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO, 72); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID, 76); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME, 130); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL, 130); + gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY, 20); + gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE); + g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button), + "clicked", + G_CALLBACK(sort_keys_name), sk); + g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button), + "clicked", + G_CALLBACK(sort_keys_email), sk); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + gtkut_button_set_create (&bbox, + &select_btn, _("Select"), + &cancel_btn, _("Cancel"), + &other_btn, _("Other")); + gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0); + gtk_widget_grab_default (select_btn); + + g_signal_connect (G_OBJECT (select_btn), "clicked", + G_CALLBACK (select_btn_cb), sk); + g_signal_connect (G_OBJECT(cancel_btn), "clicked", + G_CALLBACK (cancel_btn_cb), sk); + g_signal_connect (G_OBJECT (other_btn), "clicked", + G_CALLBACK (other_btn_cb), sk); + + vbox2 = gtk_vbox_new (FALSE, 4); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0); + + gtk_widget_show_all (window); + + sk->window = window; + sk->toplabel = GTK_LABEL (label); + sk->clist = GTK_CLIST (clist); +} + + +static void +open_dialog (struct select_keys_s *sk) +{ + if (!sk->window) + create_dialog (sk); + manage_window_set_transient (GTK_WINDOW (sk->window)); + sk->okay = 0; + sk->sort_column = N_COL_TITLES; /* use an invalid value */ + sk->sort_type = GTK_SORT_ASCENDING; + gtk_widget_show (sk->window); +} + + +static void +close_dialog (struct select_keys_s *sk) +{ + g_return_if_fail (sk); + gtk_widget_destroy (sk->window); + sk->window = NULL; +} + + +static gint +delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + struct select_keys_s *sk = data; + + sk->okay = 0; + gtk_main_quit (); + + return TRUE; +} + + +static gboolean +key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct select_keys_s *sk = data; + + g_return_val_if_fail (sk, FALSE); + if (event && event->keyval == GDK_Escape) { + sk->okay = 0; + gtk_main_quit (); + } + return FALSE; +} + + +static void +select_btn_cb (GtkWidget *widget, gpointer data) +{ + struct select_keys_s *sk = data; + int row; + GpgmeKey key; + + g_return_if_fail (sk); + if (!sk->clist->selection) { + debug_print ("** nothing selected"); + return; + } + row = GPOINTER_TO_INT(sk->clist->selection->data); + key = gtk_clist_get_row_data(sk->clist, row); + if (key) { + const char *s = gpgme_key_get_string_attr (key, + GPGME_ATTR_FPR, + NULL, 0 ); + if ( gpgme_key_get_ulong_attr (key, GPGME_ATTR_VALIDITY, NULL, 0 ) + < GPGME_VALIDITY_FULL ) { + debug_print ("** FIXME: we are faking the trust calculation"); + } + if (!gpgme_recipients_add_name_with_validity (sk->rset, s, + GPGME_VALIDITY_FULL) ) { + sk->okay = 1; + gtk_main_quit (); + } + } +} + + +static void +cancel_btn_cb (GtkWidget *widget, gpointer data) +{ + struct select_keys_s *sk = data; + + g_return_if_fail (sk); + sk->okay = 0; + if (sk->select_ctx) + gpgme_cancel (sk->select_ctx); + gtk_main_quit (); +} + + +static void +other_btn_cb (GtkWidget *widget, gpointer data) +{ + struct select_keys_s *sk = data; + char *uid; + + g_return_if_fail (sk); + uid = input_dialog ( _("Add key"), + _("Enter another user or key ID:"), + NULL ); + if (!uid) + return; + fill_clist (sk, uid); + update_progress (sk, 0, sk->pattern); + g_free (uid); +} + + +static gint +cmp_attr (gconstpointer pa, gconstpointer pb, GpgmeAttr attr) +{ + GpgmeKey a = ((GtkCListRow *)pa)->data; + GpgmeKey b = ((GtkCListRow *)pb)->data; + const char *sa, *sb; + + sa = a? gpgme_key_get_string_attr (a, attr, NULL, 0 ) : NULL; + sb = b? gpgme_key_get_string_attr (b, attr, NULL, 0 ) : NULL; + if (!sa) + return !!sb; + if (!sb) + return -1; + return strcasecmp(sa, sb); +} + +static gint +cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb) +{ + return cmp_attr (pa, pb, GPGME_ATTR_NAME); +} + +static gint +cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb) +{ + return cmp_attr (pa, pb, GPGME_ATTR_EMAIL); +} + +static void +sort_keys ( struct select_keys_s *sk, enum col_titles column) +{ + GtkCList *clist = sk->clist; + + switch (column) { + case COL_NAME: + gtk_clist_set_compare_func (clist, cmp_name); + break; + case COL_EMAIL: + gtk_clist_set_compare_func (clist, cmp_email); + break; + default: + return; + } + + /* column clicked again: toggle as-/decending */ + if ( sk->sort_column == column) { + sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ? + GTK_SORT_DESCENDING : GTK_SORT_ASCENDING; + } + else + sk->sort_type = GTK_SORT_ASCENDING; + + sk->sort_column = column; + gtk_clist_set_sort_type (clist, sk->sort_type); + gtk_clist_sort (clist); +} + +static void +sort_keys_name (GtkWidget *widget, gpointer data) +{ + sort_keys ((struct select_keys_s*)data, COL_NAME); +} + +static void +sort_keys_email (GtkWidget *widget, gpointer data) +{ + sort_keys ((struct select_keys_s*)data, COL_EMAIL); +} + +#endif /*USE_GPGME*/ diff --git a/src/select-keys.h b/src/select-keys.h new file mode 100644 index 00000000..009afc10 --- /dev/null +++ b/src/select-keys.h @@ -0,0 +1,29 @@ +/* select-keys.h - GTK+ based key selection + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef GPGMEGTK_SELECT_KEYS_H +#define GPGMEGTK_SELECT_KEYS_H + +#include +#include + + +GpgmeRecipients gpgmegtk_recipient_selection (GSList *recp_names); + + +#endif /* GPGMEGTK_SELECT_KEYS_H */ diff --git a/src/send_message.c b/src/send_message.c new file mode 100644 index 00000000..75d36c61 --- /dev/null +++ b/src/send_message.c @@ -0,0 +1,620 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "send_message.h" +#include "session.h" +#include "ssl.h" +#include "smtp.h" +#include "news.h" +#include "prefs_common.h" +#include "prefs_account.h" +#include "procheader.h" +#include "account.h" +#include "progressdialog.h" +#include "statusbar.h" +#include "inputdialog.h" +#include "alertpanel.h" +#include "manage_window.h" +#include "utils.h" + +#define SMTP_PORT 25 +#if USE_SSL +#define SSMTP_PORT 465 +#endif + +typedef struct _SendProgressDialog SendProgressDialog; + +struct _SendProgressDialog +{ + ProgressDialog *dialog; + Session *session; + gboolean cancelled; +}; + +static gint send_message_local (const gchar *command, + FILE *fp); +static gint send_message_smtp (PrefsAccount *ac_prefs, + GSList *to_list, + FILE *fp); + +static gint send_recv_message (Session *session, + const gchar *msg, + gpointer data); +static gint send_send_data_progressive (Session *session, + guint cur_len, + guint total_len, + gpointer data); +static gint send_send_data_finished (Session *session, + guint len, + gpointer data); + +static SendProgressDialog *send_progress_dialog_create(void); +static void send_progress_dialog_destroy(SendProgressDialog *dialog); + +static void send_cancel_button_cb (GtkWidget *widget, + gpointer data); + +static void send_put_error (Session *session); + + +gint send_message(const gchar *file, PrefsAccount *ac_prefs, GSList *to_list) +{ + FILE *fp; + gint val; + + g_return_val_if_fail(file != NULL, -1); + g_return_val_if_fail(ac_prefs != NULL, -1); + g_return_val_if_fail(to_list != NULL, -1); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + if (prefs_common.use_extsend && prefs_common.extsend_cmd) { + val = send_message_local(prefs_common.extsend_cmd, fp); + fclose(fp); + return val; + } + + val = send_message_smtp(ac_prefs, to_list, fp); + + fclose(fp); + return val; +} + +enum +{ + Q_SENDER = 0, + Q_SMTPSERVER = 1, + Q_RECIPIENTS = 2, + Q_ACCOUNT_ID = 3 +}; + +QueueInfo *send_get_queue_info(const gchar *file) +{ + static HeaderEntry qentry[] = {{"S:", NULL, FALSE}, + {"SSV:", NULL, FALSE}, + {"R:", NULL, FALSE}, + {"AID:", NULL, FALSE}, + {NULL, NULL, FALSE}}; + FILE *fp; + gchar buf[BUFFSIZE]; + gint hnum; + QueueInfo *qinfo; + + g_return_val_if_fail(file != NULL, NULL); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + qinfo = g_new0(QueueInfo, 1); + + while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry)) + != -1) { + gchar *p; + + p = buf + strlen(qentry[hnum].name); + + switch (hnum) { + case Q_SENDER: + if (!qinfo->from) + qinfo->from = g_strdup(p); + break; + case Q_SMTPSERVER: + if (!qinfo->server) + qinfo->server = g_strdup(p); + break; + case Q_RECIPIENTS: + qinfo->to_list = + address_list_append(qinfo->to_list, p); + break; + case Q_ACCOUNT_ID: + qinfo->ac = account_find_from_id(atoi(p)); + break; + default: + break; + } + } + + qinfo->fp = fp; + + if (((!qinfo->ac || (qinfo->ac && qinfo->ac->protocol != A_NNTP)) && + !qinfo->to_list) || !qinfo->from) { + g_warning(_("Queued message header is broken.\n")); + send_queue_info_free(qinfo); + return NULL; + } + + if (!qinfo->ac) { + qinfo->ac = account_find_from_smtp_server(qinfo->from, + qinfo->server); + if (!qinfo->ac) { + g_warning("Account not found. " + "Using current account...\n"); + qinfo->ac = cur_account; + } + } + + return qinfo; +} + +void send_queue_info_free(QueueInfo *qinfo) +{ + if (qinfo == NULL) return; + + slist_free_strings(qinfo->to_list); + g_slist_free(qinfo->to_list); + g_free(qinfo->from); + g_free(qinfo->server); + if (qinfo->fp) + fclose(qinfo->fp); + g_free(qinfo); +} + +gint send_message_queue(QueueInfo *qinfo) +{ + gint val = 0; + glong fpos; + PrefsAccount *mailac = NULL, *newsac = NULL; + + g_return_val_if_fail(qinfo != NULL, -1); + + if (prefs_common.use_extsend && prefs_common.extsend_cmd) { + val = send_message_local(prefs_common.extsend_cmd, qinfo->fp); + } else { + if (qinfo->ac && qinfo->ac->protocol == A_NNTP) { + newsac = qinfo->ac; + + /* search mail account */ + mailac = account_find_from_address(qinfo->from); + if (!mailac) { + if (cur_account && + cur_account->protocol != A_NNTP) + mailac = cur_account; + else { + mailac = account_get_default(); + if (mailac->protocol == A_NNTP) + mailac = NULL; + } + } + } else + mailac = qinfo->ac; + + fpos = ftell(qinfo->fp); + if (qinfo->to_list) { + if (mailac) + val = send_message_smtp(mailac, qinfo->to_list, + qinfo->fp); + else { + PrefsAccount tmp_ac; + + g_warning("Account not found.\n"); + + memset(&tmp_ac, 0, sizeof(PrefsAccount)); + tmp_ac.address = qinfo->from; + tmp_ac.smtp_server = qinfo->server; + tmp_ac.smtpport = SMTP_PORT; + val = send_message_smtp(&tmp_ac, qinfo->to_list, + qinfo->fp); + } + } + + if (val == 0 && newsac) { + fseek(qinfo->fp, fpos, SEEK_SET); + val = news_post_stream(FOLDER(newsac->folder), + qinfo->fp); + if (val < 0) + alertpanel_error(_("Error occurred while posting the message to %s ."), + newsac->nntp_server); + } + } + + return val; +} + +static gint send_message_local(const gchar *command, FILE *fp) +{ + FILE *pipefp; + gchar buf[BUFFSIZE]; + + g_return_val_if_fail(command != NULL, -1); + g_return_val_if_fail(fp != NULL, -1); + + pipefp = popen(command, "w"); + if (!pipefp) { + g_warning("Can't execute external command: %s\n", command); + return -1; + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (buf[0] == '.' && buf[1] == '\0') + fputc('.', pipefp); + fputs(buf, pipefp); + fputc('\n', pipefp); + } + + pclose(pipefp); + + return 0; +} + +static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp) +{ + Session *session; + SMTPSession *smtp_session; + gushort port; + SendProgressDialog *dialog; + GtkCList *clist; + const gchar *text[3]; + gchar buf[BUFFSIZE]; + gint ret = 0; + + g_return_val_if_fail(ac_prefs != NULL, -1); + g_return_val_if_fail(ac_prefs->address != NULL, -1); + g_return_val_if_fail(ac_prefs->smtp_server != NULL, -1); + g_return_val_if_fail(to_list != NULL, -1); + g_return_val_if_fail(fp != NULL, -1); + + session = smtp_session_new(); + smtp_session = SMTP_SESSION(session); + + smtp_session->hostname = + ac_prefs->set_domain ? g_strdup(ac_prefs->domain) : NULL; + + if (ac_prefs->use_smtp_auth) { + smtp_session->forced_auth_type = ac_prefs->smtp_auth_type; + + if (ac_prefs->smtp_userid) { + smtp_session->user = g_strdup(ac_prefs->smtp_userid); + if (ac_prefs->smtp_passwd) + smtp_session->pass = + g_strdup(ac_prefs->smtp_passwd); + else if (ac_prefs->tmp_smtp_pass) + smtp_session->pass = + g_strdup(ac_prefs->tmp_smtp_pass); + else { + smtp_session->pass = + input_dialog_query_password + (ac_prefs->smtp_server, + smtp_session->user); + if (!smtp_session->pass) + smtp_session->pass = g_strdup(""); + ac_prefs->tmp_smtp_pass = + g_strdup(smtp_session->pass); + } + } else { + smtp_session->user = g_strdup(ac_prefs->userid); + if (ac_prefs->passwd) + smtp_session->pass = g_strdup(ac_prefs->passwd); + else if (ac_prefs->tmp_pass) + smtp_session->pass = + g_strdup(ac_prefs->tmp_pass); + else { + smtp_session->pass = + input_dialog_query_password + (ac_prefs->smtp_server, + smtp_session->user); + if (!smtp_session->pass) + smtp_session->pass = g_strdup(""); + ac_prefs->tmp_pass = + g_strdup(smtp_session->pass); + } + } + } else { + smtp_session->user = NULL; + smtp_session->pass = NULL; + } + + smtp_session->from = g_strdup(ac_prefs->address); + smtp_session->to_list = to_list; + smtp_session->cur_to = to_list; + smtp_session->send_data = get_outgoing_rfc2822_str(fp); + smtp_session->send_data_len = strlen(smtp_session->send_data); + +#if USE_SSL + port = ac_prefs->set_smtpport ? ac_prefs->smtpport : + ac_prefs->ssl_smtp == SSL_TUNNEL ? SSMTP_PORT : SMTP_PORT; + session->ssl_type = ac_prefs->ssl_smtp; + if (ac_prefs->ssl_smtp != SSL_NONE) + session->nonblocking = ac_prefs->use_nonblocking_ssl; +#else + port = ac_prefs->set_smtpport ? ac_prefs->smtpport : SMTP_PORT; +#endif + + dialog = send_progress_dialog_create(); + dialog->session = session; + + text[0] = NULL; + text[1] = ac_prefs->smtp_server; + text[2] = _("Connecting"); + clist = GTK_CLIST(dialog->dialog->clist); + gtk_clist_append(clist, (gchar **)text); + + g_snprintf(buf, sizeof(buf), _("Connecting to SMTP server: %s ..."), + ac_prefs->smtp_server); + progress_dialog_set_label(dialog->dialog, buf); + log_message("%s\n", buf); + + session_set_recv_message_notify(session, send_recv_message, dialog); + session_set_send_data_progressive_notify + (session, send_send_data_progressive, dialog); + session_set_send_data_notify(session, send_send_data_finished, dialog); + + if (session_connect(session, ac_prefs->smtp_server, port) < 0) { + session_destroy(session); + send_progress_dialog_destroy(dialog); + return -1; + } + + debug_print("send_message_smtp(): begin event loop\n"); + + while (session_is_connected(session) && dialog->cancelled == FALSE) + gtk_main_iteration(); + + if (SMTP_SESSION(session)->error_val == SM_AUTHFAIL) { + if (ac_prefs->smtp_userid && ac_prefs->tmp_smtp_pass) { + g_free(ac_prefs->tmp_smtp_pass); + ac_prefs->tmp_smtp_pass = NULL; + } + ret = -1; + } else if (session->state == SESSION_ERROR || + session->state == SESSION_EOF || + session->state == SESSION_TIMEOUT || + SMTP_SESSION(session)->state == SMTP_ERROR || + SMTP_SESSION(session)->error_val != SM_OK) + ret = -1; + else if (dialog->cancelled == TRUE) + ret = -1; + + if (ret == -1) { + manage_window_focus_in(dialog->dialog->window, NULL, NULL); + send_put_error(session); + manage_window_focus_out(dialog->dialog->window, NULL, NULL); + } + + session_destroy(session); + send_progress_dialog_destroy(dialog); + + return ret; +} + +static gint send_recv_message(Session *session, const gchar *msg, gpointer data) +{ + gchar buf[BUFFSIZE]; + SMTPSession *smtp_session = SMTP_SESSION(session); + SendProgressDialog *dialog = (SendProgressDialog *)data; + gchar *state_str = NULL; + + g_return_val_if_fail(dialog != NULL, -1); + + switch (smtp_session->state) { + case SMTP_READY: + case SMTP_CONNECTED: + return 0; + case SMTP_HELO: + g_snprintf(buf, sizeof(buf), _("Sending HELO...")); + state_str = _("Authenticating"); + statusbar_print_all(_("Sending message...")); + break; + case SMTP_EHLO: + g_snprintf(buf, sizeof(buf), _("Sending EHLO...")); + state_str = _("Authenticating"); + statusbar_print_all(_("Sending message...")); + break; + case SMTP_AUTH: + g_snprintf(buf, sizeof(buf), _("Authenticating...")); + state_str = _("Authenticating"); + break; + case SMTP_FROM: + g_snprintf(buf, sizeof(buf), _("Sending MAIL FROM...")); + state_str = _("Sending"); + break; + case SMTP_RCPT: + g_snprintf(buf, sizeof(buf), _("Sending RCPT TO...")); + state_str = _("Sending"); + break; + case SMTP_DATA: + case SMTP_EOM: + g_snprintf(buf, sizeof(buf), _("Sending DATA...")); + state_str = _("Sending"); + break; + case SMTP_QUIT: + g_snprintf(buf, sizeof(buf), _("Quitting...")); + state_str = _("Quitting"); + break; + case SMTP_ERROR: + g_warning("send: error: %s\n", msg); + return 0; + default: + return 0; + } + + progress_dialog_set_label(dialog->dialog, buf); + gtk_clist_set_text(GTK_CLIST(dialog->dialog->clist), 0, 2, state_str); + + return 0; +} + +static gint send_send_data_progressive(Session *session, guint cur_len, + guint total_len, gpointer data) +{ + gchar buf[BUFFSIZE]; + SendProgressDialog *dialog = (SendProgressDialog *)data; + + g_return_val_if_fail(dialog != NULL, -1); + + if (SMTP_SESSION(session)->state != SMTP_SEND_DATA && + SMTP_SESSION(session)->state != SMTP_EOM) + return 0; + + g_snprintf(buf, sizeof(buf), _("Sending message (%d / %d bytes)"), + cur_len, total_len); + progress_dialog_set_label(dialog->dialog, buf); + progress_dialog_set_percentage + (dialog->dialog, (gfloat)cur_len / (gfloat)total_len); + + return 0; +} + +static gint send_send_data_finished(Session *session, guint len, gpointer data) +{ + SendProgressDialog *dialog = (SendProgressDialog *)data; + + g_return_val_if_fail(dialog != NULL, -1); + + send_send_data_progressive(session, len, len, dialog); + return 0; +} + +static SendProgressDialog *send_progress_dialog_create(void) +{ + SendProgressDialog *dialog; + ProgressDialog *progress; + + dialog = g_new0(SendProgressDialog, 1); + + progress = progress_dialog_create(); + gtk_window_set_title(GTK_WINDOW(progress->window), + _("Sending message")); + g_signal_connect(G_OBJECT(progress->cancel_btn), "clicked", + G_CALLBACK(send_cancel_button_cb), dialog); + g_signal_connect(G_OBJECT(progress->window), "delete_event", + G_CALLBACK(gtk_true), NULL); + gtk_window_set_modal(GTK_WINDOW(progress->window), TRUE); + manage_window_set_transient(GTK_WINDOW(progress->window)); + + progress_dialog_set_value(progress, 0.0); + + gtk_widget_show_now(progress->window); + + dialog->dialog = progress; + + return dialog; +} + +static void send_progress_dialog_destroy(SendProgressDialog *dialog) +{ + g_return_if_fail(dialog != NULL); + + progress_dialog_destroy(dialog->dialog); + g_free(dialog); +} + +static void send_cancel_button_cb(GtkWidget *widget, gpointer data) +{ + SendProgressDialog *dialog = (SendProgressDialog *)data; + + dialog->cancelled = TRUE; +} + +static void send_put_error(Session *session) +{ + gchar *msg; + gchar *log_msg = NULL; + gchar *err_msg = NULL; + + msg = SMTP_SESSION(session)->error_msg; + + switch (SMTP_SESSION(session)->error_val) { + case SM_ERROR: + case SM_UNRECOVERABLE: + log_msg = _("Error occurred while sending the message."); + if (msg) + err_msg = g_strdup_printf + (_("Error occurred while sending the message:\n%s"), + msg); + else + err_msg = g_strdup(log_msg); + break; + case SM_AUTHFAIL: + log_msg = _("Authentication failed."); + if (msg) + err_msg = g_strdup_printf + (_("Authentication failed:\n%s"), msg); + else + err_msg = g_strdup(log_msg); + break; + default: + switch (session->state) { + case SESSION_ERROR: + log_msg = + _("Error occurred while sending the message."); + err_msg = g_strdup(log_msg); + break; + case SESSION_EOF: + log_msg = _("Connection closed by the remote host."); + err_msg = g_strdup(log_msg); + break; + case SESSION_TIMEOUT: + log_msg = _("Session timed out."); + err_msg = g_strdup(log_msg); + break; + default: + break; + } + break; + } + + if (log_msg) + log_warning("%s\n", log_msg); + if (err_msg) { + alertpanel_error("%s", err_msg); + g_free(err_msg); + } +} + diff --git a/src/send_message.h b/src/send_message.h new file mode 100644 index 00000000..e2149712 --- /dev/null +++ b/src/send_message.h @@ -0,0 +1,46 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SEND_MESSAGE_H__ +#define __SEND_MESSAGE_H__ + +#include +#include + +typedef struct _QueueInfo QueueInfo; + +#include "prefs_account.h" + +struct _QueueInfo +{ + gchar *from; + gchar *server; + GSList *to_list; + PrefsAccount *ac; + FILE *fp; +}; + +gint send_message (const gchar *file, + PrefsAccount *ac_prefs, + GSList *to_list); +QueueInfo *send_get_queue_info (const gchar *file); +void send_queue_info_free (QueueInfo *qinfo); +gint send_message_queue (QueueInfo *qinfo); + +#endif /* __SEND_H__ */ diff --git a/src/session.c b/src/session.c new file mode 100644 index 00000000..e0527752 --- /dev/null +++ b/src/session.c @@ -0,0 +1,734 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "session.h" +#include "utils.h" + +static gint session_connect_cb (SockInfo *sock, + gpointer data); +static gint session_close (Session *session); + +static gboolean session_timeout_cb (gpointer data); + +static gboolean session_recv_msg_idle_cb (gpointer data); +static gboolean session_recv_data_idle_cb (gpointer data); + +static gboolean session_read_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_read_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_msg_cb (SockInfo *source, + GIOCondition condition, + gpointer data); +static gboolean session_write_data_cb (SockInfo *source, + GIOCondition condition, + gpointer data); + + +void session_init(Session *session) +{ + session->type = SESSION_UNKNOWN; + session->sock = NULL; + session->server = NULL; + session->port = 0; +#if USE_SSL + session->ssl_type = SSL_NONE; +#endif + session->nonblocking = TRUE; + session->state = SESSION_READY; + session->last_access_time = time(NULL); + + gettimeofday(&session->tv_prev, NULL); + + session->conn_id = 0; + + session->io_tag = 0; + + session->read_buf_p = session->read_buf; + session->read_buf_len = 0; + + session->read_msg_buf = g_string_sized_new(1024); + session->read_data_buf = g_byte_array_new(); + + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + session->timeout_tag = 0; + session->timeout_interval = 0; + + session->data = NULL; +} + +gint session_connect(Session *session, const gchar *server, gushort port) +{ + session->server = g_strdup(server); + session->port = port; + + session->conn_id = sock_connect_async(server, port, session_connect_cb, + session); + if (session->conn_id < 0) { + g_warning("can't connect to server."); + session_close(session); + return -1; + } + + return 0; +} + +static gint session_connect_cb(SockInfo *sock, gpointer data) +{ + Session *session = SESSION(data); + + session->conn_id = 0; + + if (!sock) { + g_warning("can't connect to server."); + session->state = SESSION_ERROR; + return -1; + } + + session->sock = sock; + +#if USE_SSL + if (session->ssl_type == SSL_TUNNEL) { + sock_set_nonblocking_mode(sock, FALSE); + if (!ssl_init_socket(sock)) { + g_warning("can't initialize SSL."); + session->state = SESSION_ERROR; + return -1; + } + } +#endif + + sock_set_nonblocking_mode(sock, session->nonblocking); + + debug_print("session (%p): connected\n", session); + + session->state = SESSION_RECV; + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, + session); + + return 0; +} + +gint session_disconnect(Session *session) +{ + session_close(session); + return 0; +} + +void session_destroy(Session *session) +{ + g_return_if_fail(session != NULL); + g_return_if_fail(session->destroy != NULL); + + session_close(session); + session->destroy(session); + g_free(session->server); + g_string_free(session->read_msg_buf, TRUE); + g_byte_array_free(session->read_data_buf, TRUE); + g_free(session->read_data_terminator); + g_free(session->write_buf); + + debug_print("session (%p): destroyed\n", session); + + g_free(session); +} + +gboolean session_is_connected(Session *session) +{ + return (session->state == SESSION_READY || + session->state == SESSION_SEND || + session->state == SESSION_RECV); +} + +void session_set_access_time(Session *session) +{ + session->last_access_time = time(NULL); +} + +void session_set_timeout(Session *session, guint interval) +{ + if (session->timeout_tag > 0) + g_source_remove(session->timeout_tag); + + session->timeout_interval = interval; + if (interval > 0) + session->timeout_tag = + g_timeout_add(interval, session_timeout_cb, session); + else + session->timeout_tag = 0; +} + +static gboolean session_timeout_cb(gpointer data) +{ + Session *session = SESSION(data); + + g_warning("session timeout.\n"); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session->timeout_tag = 0; + session->state = SESSION_TIMEOUT; + + return FALSE; +} + +void session_set_recv_message_notify(Session *session, + RecvMsgNotify notify_func, gpointer data) +{ + session->recv_msg_notify = notify_func; + session->recv_msg_notify_data = data; +} + +void session_set_recv_data_progressive_notify + (Session *session, + RecvDataProgressiveNotify notify_func, + gpointer data) +{ + session->recv_data_progressive_notify = notify_func, + session->recv_data_progressive_notify_data = data; +} + +void session_set_recv_data_notify(Session *session, RecvDataNotify notify_func, + gpointer data) +{ + session->recv_data_notify = notify_func; + session->recv_data_notify_data = data; +} + +void session_set_send_data_progressive_notify + (Session *session, + SendDataProgressiveNotify notify_func, + gpointer data) +{ + session->send_data_progressive_notify = notify_func; + session->send_data_progressive_notify_data = data; +} + +void session_set_send_data_notify(Session *session, SendDataNotify notify_func, + gpointer data) +{ + session->send_data_notify = notify_func; + session->send_data_notify_data = data; +} + +static gint session_close(Session *session) +{ + g_return_val_if_fail(session != NULL, -1); + + if (session->conn_id > 0) { + sock_connect_async_cancel(session->conn_id); + session->conn_id = 0; + debug_print("session (%p): connection cancelled\n", session); + } + + session_set_timeout(session, 0); + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + if (session->sock) { + sock_close(session->sock); + session->sock = NULL; + session->state = SESSION_DISCONNECTED; + debug_print("session (%p): closed\n", session); + } + + return 0; +} + +#if USE_SSL +gint session_start_tls(Session *session) +{ + gboolean nb_mode; + + nb_mode = sock_is_nonblocking_mode(session->sock); + + if (nb_mode) + sock_set_nonblocking_mode(session->sock, FALSE); + + if (!ssl_init_socket_with_method(session->sock, SSL_METHOD_TLSv1)) { + g_warning("can't start TLS session.\n"); + if (nb_mode) + sock_set_nonblocking_mode(session->sock, TRUE); + return -1; + } + + if (nb_mode) + sock_set_nonblocking_mode(session->sock, session->nonblocking); + + return 0; +} +#endif + +gint session_send_msg(Session *session, SessionMsgType type, const gchar *msg) +{ + gboolean ret; + + g_return_val_if_fail(session->write_buf == NULL, -1); + g_return_val_if_fail(msg != NULL, -1); + g_return_val_if_fail(msg[0] != '\0', -1); + + session->state = SESSION_SEND; + session->write_buf = g_strconcat(msg, "\r\n", NULL); + session->write_buf_p = session->write_buf; + session->write_buf_len = strlen(msg) + 2; + + ret = session_write_msg_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_msg_cb, session); + else if (session->state == SESSION_ERROR) + return -1; + + return 0; +} + +gint session_recv_msg(Session *session) +{ + g_return_val_if_fail(session->read_msg_buf->len == 0, -1); + + session->state = SESSION_RECV; + + if (session->read_buf_len > 0) + g_idle_add(session_recv_msg_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return 0; +} + +static gboolean session_recv_msg_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + + ret = session_read_msg_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_msg_cb, session); + + return FALSE; +} + +gint session_send_data(Session *session, const guchar *data, guint size) +{ + gboolean ret; + + g_return_val_if_fail(session->write_buf == NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(size != 0, -1); + + session->state = SESSION_SEND; + + session->write_buf = g_malloc(size); + session->write_buf_p = session->write_buf; + memcpy(session->write_buf, data, size); + session->write_buf_len = size; + gettimeofday(&session->tv_prev, NULL); + + ret = session_write_data_cb(session->sock, G_IO_OUT, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_OUT, + session_write_data_cb, + session); + else if (session->state == SESSION_ERROR) + return -1; + + return 0; +} + +gint session_recv_data(Session *session, guint size, const gchar *terminator) +{ + g_return_val_if_fail(session->read_data_buf->len == 0, -1); + + session->state = SESSION_RECV; + + g_free(session->read_data_terminator); + session->read_data_terminator = g_strdup(terminator); + gettimeofday(&session->tv_prev, NULL); + + if (session->read_buf_len > 0) + g_idle_add(session_recv_data_idle_cb, session); + else + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); + + return 0; +} + +static gboolean session_recv_data_idle_cb(gpointer data) +{ + Session *session = SESSION(data); + gboolean ret; + + ret = session_read_data_cb(session->sock, G_IO_IN, session); + + if (ret == TRUE) + session->io_tag = sock_add_watch(session->sock, G_IO_IN, + session_read_data_cb, session); + + return FALSE; +} + +static gboolean session_read_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + gchar buf[SESSION_BUFFSIZE]; + gint line_len; + gchar *newline; + gchar *msg; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + session_set_timeout(session, session->timeout_interval); + + if (session->read_buf_len == 0) { + gint read_len; + + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE - 1); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("sock_read: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + if ((newline = memchr(session->read_buf_p, '\n', session->read_buf_len)) + != NULL) + line_len = newline - session->read_buf_p + 1; + else + line_len = session->read_buf_len; + + if (line_len == 0) + return TRUE; + + memcpy(buf, session->read_buf_p, line_len); + buf[line_len] = '\0'; + + g_string_append(session->read_msg_buf, buf); + + session->read_buf_len -= line_len; + if (session->read_buf_len == 0) + session->read_buf_p = session->read_buf; + else + session->read_buf_p += line_len; + + /* incomplete read */ + if (buf[line_len - 1] != '\n') + return TRUE; + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + msg = g_strdup(session->read_msg_buf->str); + strretchomp(msg); + g_string_truncate(session->read_msg_buf, 0); + + ret = session->recv_msg(session, msg); + session->recv_msg_notify(session, msg, session->recv_msg_notify_data); + + g_free(msg); + + if (ret < 0) + session->state = SESSION_ERROR; + + return FALSE; +} + +static gboolean session_read_data_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + GByteArray *data_buf; + gint terminator_len; + gboolean complete = FALSE; + guint data_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_IN, FALSE); + + session_set_timeout(session, session->timeout_interval); + + if (session->read_buf_len == 0) { + gint read_len; + + read_len = sock_read(session->sock, session->read_buf, + SESSION_BUFFSIZE); + + if (read_len == 0) { + g_warning("sock_read: received EOF\n"); + session->state = SESSION_EOF; + return FALSE; + } + + if (read_len < 0) { + switch (errno) { + case EAGAIN: + return TRUE; + default: + g_warning("sock_read: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return FALSE; + } + } + + session->read_buf_len = read_len; + } + + data_buf = session->read_data_buf; + terminator_len = strlen(session->read_data_terminator); + + if (session->read_buf_len == 0) + return TRUE; + + g_byte_array_append(data_buf, session->read_buf_p, + session->read_buf_len); + + session->read_buf_len = 0; + session->read_buf_p = session->read_buf; + + /* check if data is terminated */ + if (data_buf->len >= terminator_len) { + if (memcmp(data_buf->data, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + else if (data_buf->len >= terminator_len + 2 && + memcmp(data_buf->data + data_buf->len - + (terminator_len + 2), "\r\n", 2) == 0 && + memcmp(data_buf->data + data_buf->len - + terminator_len, session->read_data_terminator, + terminator_len) == 0) + complete = TRUE; + } + + /* incomplete read */ + if (!complete) { + struct timeval tv_cur; + + gettimeofday(&tv_cur, NULL); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session->recv_data_progressive_notify + (session, data_buf->len, 0, + session->recv_data_progressive_notify_data); + gettimeofday(&session->tv_prev, NULL); + } + return TRUE; + } + + /* complete */ + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + data_len = data_buf->len - terminator_len; + + /* callback */ + ret = session->recv_data_finished(session, (gchar *)data_buf->data, + data_len); + + g_byte_array_set_size(data_buf, 0); + + session->recv_data_notify(session, data_len, + session->recv_data_notify_data); + + if (ret < 0) + session->state = SESSION_ERROR; + + return FALSE; +} + +static gint session_write_buf(Session *session) +{ + gint write_len; + gint to_write_len; + + g_return_val_if_fail(session->write_buf != NULL, -1); + g_return_val_if_fail(session->write_buf_p != NULL, -1); + g_return_val_if_fail(session->write_buf_len > 0, -1); + + to_write_len = session->write_buf_len - + (session->write_buf_p - session->write_buf); + to_write_len = MIN(to_write_len, SESSION_BUFFSIZE); + + write_len = sock_write(session->sock, session->write_buf_p, + to_write_len); + + if (write_len < 0) { + switch (errno) { + case EAGAIN: + write_len = 0; + break; + default: + g_warning("sock_write: %s\n", g_strerror(errno)); + session->state = SESSION_ERROR; + return -1; + } + } + + /* incomplete write */ + if (session->write_buf_p - session->write_buf + write_len < + session->write_buf_len) { + session->write_buf_p += write_len; + return 1; + } + + g_free(session->write_buf); + session->write_buf = NULL; + session->write_buf_p = NULL; + session->write_buf_len = 0; + + return 0; +} + +static gboolean session_write_msg_cb(SockInfo *source, GIOCondition condition, + gpointer data) +{ + Session *session = SESSION(data); + gint ret; + + g_return_val_if_fail(condition == G_IO_OUT, FALSE); + g_return_val_if_fail(session->write_buf != NULL, FALSE); + g_return_val_if_fail(session->write_buf_p != NULL, FALSE); + g_return_val_if_fail(session->write_buf_len > 0, FALSE); + + ret = session_write_buf(session); + + if (ret < 0) { + session->state = SESSION_ERROR; + return FALSE; + } else if (ret > 0) + return TRUE; + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + session_recv_msg(session); + + return FALSE; +} + +static gboolean session_write_data_cb(SockInfo *source, + GIOCondition condition, gpointer data) +{ + Session *session = SESSION(data); + guint write_buf_len; + gint ret; + + g_return_val_if_fail(condition == G_IO_OUT, FALSE); + g_return_val_if_fail(session->write_buf != NULL, FALSE); + g_return_val_if_fail(session->write_buf_p != NULL, FALSE); + g_return_val_if_fail(session->write_buf_len > 0, FALSE); + + write_buf_len = session->write_buf_len; + + ret = session_write_buf(session); + + if (ret < 0) { + session->state = SESSION_ERROR; + return FALSE; + } else if (ret > 0) { + struct timeval tv_cur; + + gettimeofday(&tv_cur, NULL); + if (tv_cur.tv_sec - session->tv_prev.tv_sec > 0 || + tv_cur.tv_usec - session->tv_prev.tv_usec > + UI_REFRESH_INTERVAL) { + session_set_timeout(session, session->timeout_interval); + session->send_data_progressive_notify + (session, + session->write_buf_p - session->write_buf, + write_buf_len, + session->send_data_progressive_notify_data); + gettimeofday(&session->tv_prev, NULL); + } + return TRUE; + } + + if (session->io_tag > 0) { + g_source_remove(session->io_tag); + session->io_tag = 0; + } + + /* callback */ + ret = session->send_data_finished(session, write_buf_len); + session->send_data_notify(session, write_buf_len, + session->send_data_notify_data); + + return FALSE; +} diff --git a/src/session.h b/src/session.h new file mode 100644 index 00000000..093f12f4 --- /dev/null +++ b/src/session.h @@ -0,0 +1,200 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SESSION_H__ +#define __SESSION_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include +#include + +#include "socket.h" + +#define SESSION_BUFFSIZE 4096 + +typedef struct _Session Session; + +#define SESSION(obj) ((Session *)obj) + +typedef enum { + SESSION_UNKNOWN, + SESSION_IMAP, + SESSION_NEWS, + SESSION_SMTP, + SESSION_POP3 +} SessionType; + +typedef enum { + SESSION_READY, + SESSION_SEND, + SESSION_RECV, + SESSION_EOF, + SESSION_TIMEOUT, + SESSION_ERROR, + SESSION_DISCONNECTED +} SessionState; + +typedef enum +{ + SESSION_MSG_NORMAL, + SESSION_MSG_SEND_DATA, + SESSION_MSG_RECV_DATA, + SESSION_MSG_CONTROL, + SESSION_MSG_ERROR, + SESSION_MSG_UNKNOWN +} SessionMsgType; + +typedef gint (*RecvMsgNotify) (Session *session, + const gchar *msg, + gpointer user_data); +typedef gint (*RecvDataProgressiveNotify) (Session *session, + guint cur_len, + guint total_len, + gpointer user_data); +typedef gint (*RecvDataNotify) (Session *session, + guint len, + gpointer user_data); +typedef gint (*SendDataProgressiveNotify) (Session *session, + guint cur_len, + guint total_len, + gpointer user_data); +typedef gint (*SendDataNotify) (Session *session, + guint len, + gpointer user_data); + +struct _Session +{ + SessionType type; + + SockInfo *sock; + + gchar *server; + gushort port; + +#if USE_SSL + SSLType ssl_type; +#endif + + gboolean nonblocking; + + SessionState state; + + time_t last_access_time; + struct timeval tv_prev; + + gint conn_id; + + gint io_tag; + + gchar read_buf[SESSION_BUFFSIZE]; + gchar *read_buf_p; + gint read_buf_len; + + GString *read_msg_buf; + GByteArray *read_data_buf; + gchar *read_data_terminator; + + gchar *write_buf; + gchar *write_buf_p; + gint write_buf_len; + + guint timeout_tag; + guint timeout_interval; + + gpointer data; + + /* virtual methods to parse server responses */ + gint (*recv_msg) (Session *session, + const gchar *msg); + + gint (*send_data_finished) (Session *session, + guint len); + gint (*recv_data_finished) (Session *session, + guchar *data, + guint len); + + void (*destroy) (Session *session); + + /* notification functions */ + RecvMsgNotify recv_msg_notify; + RecvDataProgressiveNotify recv_data_progressive_notify; + RecvDataNotify recv_data_notify; + SendDataProgressiveNotify send_data_progressive_notify; + SendDataNotify send_data_notify; + + gpointer recv_msg_notify_data; + gpointer recv_data_progressive_notify_data; + gpointer recv_data_notify_data; + gpointer send_data_progressive_notify_data; + gpointer send_data_notify_data; +}; + +void session_init (Session *session); +gint session_connect (Session *session, + const gchar *server, + gushort port); +gint session_disconnect (Session *session); +void session_destroy (Session *session); +gboolean session_is_connected (Session *session); + +void session_set_access_time (Session *session); + +void session_set_timeout (Session *session, + guint interval); + +void session_set_recv_message_notify (Session *session, + RecvMsgNotify notify_func, + gpointer data); +void session_set_recv_data_progressive_notify + (Session *session, + RecvDataProgressiveNotify notify_func, + gpointer data); +void session_set_recv_data_notify (Session *session, + RecvDataNotify notify_func, + gpointer data); +void session_set_send_data_progressive_notify + (Session *session, + SendDataProgressiveNotify notify_func, + gpointer data); +void session_set_send_data_notify (Session *session, + SendDataNotify notify_func, + gpointer data); + +#if USE_SSL +gint session_start_tls (Session *session); +#endif + +gint session_send_msg (Session *session, + SessionMsgType type, + const gchar *msg); +gint session_recv_msg (Session *session); +gint session_send_data (Session *session, + const guchar *data, + guint size); +gint session_recv_data (Session *session, + guint size, + const gchar *terminator); + +#endif /* __SESSION_H__ */ diff --git a/src/setup.c b/src/setup.c new file mode 100644 index 00000000..ef4ba94e --- /dev/null +++ b/src/setup.c @@ -0,0 +1,95 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "intl.h" +#include "inputdialog.h" +#include "alertpanel.h" +#include "mainwindow.h" +#include "gtkutils.h" + +#define SETUP_DIALOG_WIDTH 540 + +static void scan_tree_func(Folder *folder, FolderItem *item, gpointer data); + +void setup(MainWindow *mainwin) +{ + gchar *path; + Folder *folder; + + path = input_dialog + (_("Mailbox setting"), + _("First, you have to set the location of mailbox.\n" + "You can use existing mailbox in MH format\n" + "if you have the one.\n" + "If you're not sure, just select OK."), + "Mail"); + if (!path) return; + if (folder_find_from_path(path)) { + g_warning("The mailbox already exists.\n"); + g_free(path); + return; + } + + if (!strcmp(path, "Mail")) + folder = folder_new(F_MH, _("Mailbox"), path); + else + folder = folder_new(F_MH, g_basename(path), path); + g_free(path); + + if (folder->klass->create_tree(folder) < 0) { + alertpanel_error(_("Creation of the mailbox failed.\n" + "Maybe some files already exist, or you don't have the permission to write there.")); + folder_destroy(folder); + return; + } + + folder_add(folder); + folder_set_ui_func(folder, scan_tree_func, mainwin); + folder->klass->scan_tree(folder); + folder_set_ui_func(folder, NULL, NULL); +} + +static void scan_tree_func(Folder *folder, FolderItem *item, gpointer data) +{ + MainWindow *mainwin = (MainWindow *)data; + gchar *str; + + if (item->path) + str = g_strdup_printf(_("Scanning folder %s%c%s ..."), + LOCAL_FOLDER(folder)->rootpath, + G_DIR_SEPARATOR, + item->path); + else + str = g_strdup_printf(_("Scanning folder %s ..."), + LOCAL_FOLDER(folder)->rootpath); + + gtk_statusbar_push(GTK_STATUSBAR(mainwin->statusbar), + mainwin->mainwin_cid, str); + gtkut_widget_wait_for_draw(mainwin->hbox_stat); + gtk_statusbar_pop(GTK_STATUSBAR(mainwin->statusbar), + mainwin->mainwin_cid); + g_free(str); +} diff --git a/src/setup.h b/src/setup.h new file mode 100644 index 00000000..5b889d33 --- /dev/null +++ b/src/setup.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SETUP_H__ +#define __SETUP_H__ + +#include + +#include "mainwindow.h" + +void setup(MainWindow *mainwin); + +#endif /* __SETUP_H__ */ diff --git a/src/sigstatus.c b/src/sigstatus.c new file mode 100644 index 00000000..a9905f0a --- /dev/null +++ b/src/sigstatus.c @@ -0,0 +1,244 @@ +/* sigstatus.h - GTK+ based signature status display + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_GPGME + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "gtkutils.h" +#include "utils.h" +#include "sigstatus.h" + +/* remove the window after 30 seconds to avoid cluttering the deskop + * with too many of them */ +#define MY_TIMEOUT (30*1000) + +struct gpgmegtk_sig_status_s { + GtkWidget *mainwindow; + GtkWidget *label; + gint running; + gint destroy_pending; + guint timeout_id; + gint timeout_id_valid; +}; + + +static void do_destroy(GpgmegtkSigStatus hd) +{ + if (!hd->running) { + if (hd->mainwindow) { + gtk_widget_destroy ( hd->mainwindow ); + hd->mainwindow = NULL; + } + if (hd->timeout_id_valid) { + gtk_timeout_remove(hd->timeout_id); + hd->timeout_id_valid = 0; + } + if (hd->destroy_pending) + g_free(hd); + } +} + +static void okay_cb(GtkWidget *widget, gpointer data) +{ + GpgmegtkSigStatus hd = data; + + hd->running = 0; + do_destroy(hd); +} + +static gint delete_event(GtkWidget *widget, GdkEventAny *event, gpointer data) +{ + GpgmegtkSigStatus hd = data; + + hd->running = 0; + do_destroy(hd); + + return TRUE; +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + GpgmegtkSigStatus hd = data; + + if (event && event->keyval == GDK_Escape) { + hd->running = 0; + do_destroy(hd); + } + return FALSE; +} + +GpgmegtkSigStatus gpgmegtk_sig_status_create(void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *okay_btn; + GtkWidget *okay_area; + GpgmegtkSigStatus hd; + + hd = g_malloc0(sizeof *hd); + hd->running = 1; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + hd->mainwindow = window; + gtk_widget_set_size_request(window, 400, -1); + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(delete_event), hd); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), hd); + + vbox = gtk_vbox_new(FALSE, 8); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 8); + gtk_widget_show(hbox); + + label = gtk_label_new(_("Checking signature")); + hd->label = label; + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 8); + gtk_widget_show(label); + + gtkut_button_set_create(&okay_area, &okay_btn, _("OK"), + NULL, NULL, NULL, NULL); + gtk_box_pack_end(GTK_BOX(vbox), okay_area, FALSE, FALSE, 0); + gtk_widget_grab_default(okay_btn); + g_signal_connect(G_OBJECT(okay_btn), "clicked", + G_CALLBACK(okay_cb), hd); + + gtk_widget_show_all(window); + + while (gtk_events_pending()) + gtk_main_iteration(); + + return hd; +} + +static gint timeout_cb(gpointer data) +{ + GpgmegtkSigStatus hd = data; + + hd->running = 0; + hd->timeout_id_valid = 0; + do_destroy(hd); + + return FALSE; +} + +void gpgmegtk_sig_status_destroy(GpgmegtkSigStatus hd) +{ + if (hd) { + hd->destroy_pending = 1; + if (hd->running && !hd->timeout_id_valid) { + hd->timeout_id = gtk_timeout_add(MY_TIMEOUT, + timeout_cb, hd); + hd->timeout_id_valid = 1; + } + do_destroy(hd); + } +} + +/* Fixme: remove status and get it from the context */ +void gpgmegtk_sig_status_update(GpgmegtkSigStatus hd, GpgmeCtx ctx) +{ + gint idx; + time_t created; + GpgmeSigStat status; + gchar *text = NULL; + + if (!hd || !hd->running || !ctx) + return; + + for (idx = 0; gpgme_get_sig_status(ctx, idx, &status, &created); + idx++) { + gchar *tmp; + const gchar *userid; + GpgmeKey key = NULL; + + if (!gpgme_get_sig_key (ctx, idx, &key)) { + userid = gpgme_key_get_string_attr + (key, GPGME_ATTR_USERID, NULL, 0); + } else + userid = "[?]"; + + tmp = g_strdup_printf(_("%s%s%s from \"%s\""), + text ? text : "", + text ? "\n" : "", + gpgmegtk_sig_status_to_string(status), + userid); + g_free (text); + text = tmp; + gpgme_key_unref (key); + } + + gtk_label_set_text(GTK_LABEL(hd->label), text); + g_free(text); + + while (gtk_events_pending()) + gtk_main_iteration(); +} + +const char *gpgmegtk_sig_status_to_string(GpgmeSigStat status) +{ + const char *result = "?"; + + switch (status) { + case GPGME_SIG_STAT_NONE: + result = _("Oops: Signature not verified"); + break; + case GPGME_SIG_STAT_NOSIG: + result = _("No signature found"); + break; + case GPGME_SIG_STAT_GOOD: + result = _("Good signature"); + break; + case GPGME_SIG_STAT_BAD: + result = _("BAD signature"); + break; + case GPGME_SIG_STAT_NOKEY: + result = _("No public key to verify the signature"); + break; + case GPGME_SIG_STAT_ERROR: + result = _("Error verifying the signature"); + break; + default: + break; + } + + return result; +} + +#endif /* USE_GPGME */ diff --git a/src/sigstatus.h b/src/sigstatus.h new file mode 100644 index 00000000..f4834b07 --- /dev/null +++ b/src/sigstatus.h @@ -0,0 +1,33 @@ +/* sigstatus.h - GTK+ based signature status display + * Copyright (C) 2001 Werner Koch (dd9jn) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef GPGMEGTK_SIGSTATUS_H +#define GPGMEGTK_SIGSTATUS_H + +#include + +struct gpgmegtk_sig_status_s; +typedef struct gpgmegtk_sig_status_s *GpgmegtkSigStatus; + +GpgmegtkSigStatus gpgmegtk_sig_status_create(void); +void gpgmegtk_sig_status_destroy(GpgmegtkSigStatus hd); +void gpgmegtk_sig_status_update(GpgmegtkSigStatus hd, GpgmeCtx ctx); + +const char *gpgmegtk_sig_status_to_string(GpgmeSigStat status); + +#endif /* GPGMEGTK_SIGSTATUS_H */ diff --git a/src/simple-gettext.c b/src/simple-gettext.c new file mode 100644 index 00000000..be6292bb --- /dev/null +++ b/src/simple-gettext.c @@ -0,0 +1,386 @@ +/* simple-gettext.c - a simplified version of gettext. + * Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 + */ + +/* This is a simplified version of gettext written by Ulrich Drepper. + * It is used for the Win32 version of GnuPG becuase all the overhead + * of gettext is not needed and we have to do some special Win32 stuff. + * I decided that this is far easier than to tweak gettext for the special + * cases (I tried it but it is a lot of code). wk 15.09.99 + */ + +#include +#ifdef USE_SIMPLE_GETTEXT +#ifndef __MINGW32__ + #error This file can only be used with MinGW32 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include "w32reg.h" + +typedef unsigned int u32; /* That is fine with MingW32 */ + +typedef unsigned long ulong; + +/* The magic number of the GNU message catalog format. */ +#define MAGIC 0x950412de +#define MAGIC_SWAPPED 0xde120495 + +/* Revision number of the currently used .mo (binary) file format. */ +#define MO_REVISION_NUMBER 0 + + +/* Header for binary .mo file format. */ +struct mo_file_header +{ + /* The magic number. */ + u32 magic; + /* The revision number of the file format. */ + u32 revision; + /* The number of strings pairs. */ + u32 nstrings; + /* Offset of table with start offsets of original strings. */ + u32 orig_tab_offset; + /* Offset of table with start offsets of translation strings. */ + u32 trans_tab_offset; + /* Size of hashing table. */ + u32 hash_tab_size; + /* Offset of first hashing entry. */ + u32 hash_tab_offset; +}; + +struct string_desc +{ + /* Length of addressed string. */ + u32 length; + /* Offset of string in file. */ + u32 offset; +}; + + + +struct loaded_domain +{ + char *data; + int must_swap; + u32 nstrings; + char *mapped; + struct string_desc *orig_tab; + struct string_desc *trans_tab; + u32 hash_size; + u32 *hash_tab; +}; + + +static struct loaded_domain *the_domain; + +static __inline__ u32 +do_swap_u32( u32 i ) +{ + return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24); +} + +#define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) ) + + +/* We assume to have `unsigned long int' value with at least 32 bits. */ +#define HASHWORDBITS 32 + +/* The so called `hashpjw' function by P.J. Weinberger + [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools, + 1986, 1987 Bell Telephone Laboratories, Inc.] */ + +static __inline__ ulong +hash_string( const char *str_param ) +{ + unsigned long int hval, g; + const char *str = str_param; + + hval = 0; + while (*str != '\0') + { + hval <<= 4; + hval += (unsigned long int) *str++; + g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4)); + if (g != 0) + { + hval ^= g >> (HASHWORDBITS - 8); + hval ^= g; + } + } + return hval; +} + + +static struct loaded_domain * +load_domain( const char *filename ) +{ + FILE *fp; + size_t size; + struct stat st; + struct mo_file_header *data = NULL; + struct loaded_domain *domain = NULL; + size_t to_read; + char *read_ptr; + + fp = fopen( filename, "rb" ); + if( !fp ) + return NULL; /* can't open the file */ + /* we must know about the size of the file */ + if( fstat( fileno(fp ), &st ) + || (size = (size_t)st.st_size) != st.st_size + || size < sizeof (struct mo_file_header) ) { + fclose( fp ); + return NULL; + } + + data = malloc( size ); + if( !data ) { + fclose( fp ); + return NULL; /* out of memory */ + } + + to_read = size; + read_ptr = (char *) data; + do { + long int nb = fread( read_ptr, 1, to_read, fp ); + if( nb < to_read ) { + fclose (fp); + free(data); + return NULL; /* read error */ + } + read_ptr += nb; + to_read -= nb; + } while( to_read > 0 ); + fclose (fp); + + /* Using the magic number we can test whether it really is a message + * catalog file. */ + if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) { + /* The magic number is wrong: not a message catalog file. */ + free( data ); + return NULL; + } + + domain = calloc( 1, sizeof *domain ); + if( !domain ) { + free( data ); + return NULL; + } + domain->data = (char *) data; + domain->must_swap = data->magic != MAGIC; + + /* Fill in the information about the available tables. */ + switch( SWAPIT(domain->must_swap, data->revision) ) { + case 0: + domain->nstrings = SWAPIT(domain->must_swap, data->nstrings); + domain->orig_tab = (struct string_desc *) + ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset)); + domain->trans_tab = (struct string_desc *) + ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset)); + domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size); + domain->hash_tab = (u32 *) + ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset)); + break; + + default: /* This is an invalid revision. */ + free( data ); + free( domain ); + return NULL; + } + + /* allocate an array to keep track of code page mappings */ + domain->mapped = calloc( 1, domain->nstrings ); + if( !domain->mapped ) { + free( data ); + free( domain ); + return NULL; + } + + return domain; +} + + +/**************** + * Set the file used for translations. Pass a NULL to disable + * translation. A new filename may be set at anytime. + * WARNING: After changing the filename you shoudl not access any data + * retrieved by gettext(). + */ +int +set_gettext_file( const char *filename ) +{ + struct loaded_domain *domain = NULL; + + if( filename && *filename ) { + if( filename[0] == '/' + #ifdef HAVE_DRIVE_LETTERS + || ( isalpha(filename[0]) + && filename[1] == ':' + && (filename[2] == '/' || filename[2] == '\\') ) + #endif + ) { + /* absolute path - use it as is */ + domain = load_domain( filename ); + } + else { /* relative path - append ".mo" and get dir from the environment */ + char *buf = NULL; + char *dir; + + dir = read_w32_registry_string( NULL, + "Control Panel\\Mingw32\\NLS", + "MODir" ); + if( dir && (buf=malloc(strlen(dir)+strlen(filename)+1+3+1)) ) { + strcpy(stpcpy(stpcpy(stpcpy( buf, dir),"\\"), filename),".mo"); + domain = load_domain( buf ); + free(buf); + } + free(dir); + } + if( !domain ) { + return -1; } + } + if( the_domain ) { + free( the_domain->data ); + free( the_domain->mapped ); + free( the_domain ); + the_domain = NULL; + } + the_domain = domain; + return NULL; +} + + +static const char* +get_string( struct loaded_domain *domain, u32 idx ) +{ + char *p = domain->data + SWAPIT(domain->must_swap, + domain->trans_tab[idx].offset); + + /* status of domain->mapped[idx] is ignored. + * not sure about the consequences. + * perhaps mapped can entirely be removed? + */ + + /* we assume, strings are already correctly + * encoded. + */ + + return (const char*)p; +} + + + +const char * +gettext( const char *msgid ) +{ + struct loaded_domain *domain; + size_t act = 0; + size_t top, bottom; + + if( !(domain = the_domain) ) { + goto not_found; + } + + /* Locate the MSGID and its translation. */ + if( domain->hash_size > 2 && domain->hash_tab ) { + /* Use the hashing table. */ + u32 len = strlen (msgid); + u32 hash_val = hash_string (msgid); + u32 idx = hash_val % domain->hash_size; + u32 incr = 1 + (hash_val % (domain->hash_size - 2)); + u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]); + + if ( !nstr ) /* Hash table entry is empty. */ + goto not_found; + + if( SWAPIT(domain->must_swap, + domain->orig_tab[nstr - 1].length) == len + && !strcmp( msgid, + domain->data + SWAPIT(domain->must_swap, + domain->orig_tab[nstr - 1].offset)) ) + return get_string( domain, nstr - 1 ); + + for(;;) { + if (idx >= domain->hash_size - incr) + idx -= domain->hash_size - incr; + else + idx += incr; + + nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]); + if( !nstr ) + goto not_found; /* Hash table entry is empty. */ + + if ( SWAPIT(domain->must_swap, + domain->orig_tab[nstr - 1].length) == len + && !strcmp (msgid, + domain->data + SWAPIT(domain->must_swap, + domain->orig_tab[nstr - 1].offset))) + return get_string( domain, nstr-1 ); + } + /* NOTREACHED */ + } + + /* Now we try the default method: binary search in the sorted + array of messages. */ + bottom = 0; + top = domain->nstrings; + while( bottom < top ) { + int cmp_val; + + act = (bottom + top) / 2; + cmp_val = strcmp(msgid, domain->data + + SWAPIT(domain->must_swap, + domain->orig_tab[act].offset)); + if (cmp_val < 0) + top = act; + else if (cmp_val > 0) + bottom = act + 1; + else + return get_string( domain, act ); + } + + not_found: + return msgid; +} + +#if 0 + unsigned int cp1, cp2; + + cp1 = GetConsoleCP(); + cp2 = GetConsoleOutputCP(); + + log_info("InputCP=%u OutputCP=%u\n", cp1, cp2 ); + + if( !SetConsoleOutputCP( 1252 ) ) + log_info("SetConsoleOutputCP failed: %d\n", (int)GetLastError() ); + + cp1 = GetConsoleCP(); + cp2 = GetConsoleOutputCP(); + log_info("InputCP=%u OutputCP=%u after switch1\n", cp1, cp2 ); +#endif + +#endif /* USE_SIMPLE_GETTEXT */ diff --git a/src/smtp.c b/src/smtp.c new file mode 100644 index 00000000..4b8ae355 --- /dev/null +++ b/src/smtp.c @@ -0,0 +1,565 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "intl.h" +#include "smtp.h" +#include "md5.h" +#include "base64.h" +#include "utils.h" + +static void smtp_session_destroy(Session *session); + +static gint smtp_from(SMTPSession *session); + +static gint smtp_auth(SMTPSession *session); +static gint smtp_starttls(SMTPSession *session); +static gint smtp_auth_cram_md5(SMTPSession *session); +static gint smtp_auth_login(SMTPSession *session); + +static gint smtp_ehlo(SMTPSession *session); +static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg); + +static gint smtp_helo(SMTPSession *session); +static gint smtp_rcpt(SMTPSession *session); +static gint smtp_data(SMTPSession *session); +static gint smtp_send_data(SMTPSession *session); +/* static gint smtp_rset(SMTPSession *session); */ +static gint smtp_quit(SMTPSession *session); +static gint smtp_eom(SMTPSession *session); + +static gint smtp_session_recv_msg(Session *session, const gchar *msg); +static gint smtp_session_send_data_finished(Session *session, guint len); + + +Session *smtp_session_new(void) +{ + SMTPSession *session; + + session = g_new0(SMTPSession, 1); + + session_init(SESSION(session)); + + SESSION(session)->type = SESSION_SMTP; + + SESSION(session)->recv_msg = smtp_session_recv_msg; + + SESSION(session)->recv_data_finished = NULL; + SESSION(session)->send_data_finished = smtp_session_send_data_finished; + + SESSION(session)->destroy = smtp_session_destroy; + + session->state = SMTP_READY; + +#if USE_SSL + session->tls_init_done = FALSE; +#endif + + session->hostname = NULL; + session->user = NULL; + session->pass = NULL; + + session->from = NULL; + session->to_list = NULL; + session->cur_to = NULL; + + session->send_data = NULL; + session->send_data_len = 0; + + session->avail_auth_type = 0; + session->forced_auth_type = 0; + session->auth_type = 0; + + session->error_val = SM_OK; + session->error_msg = NULL; + + return SESSION(session); +} + +static void smtp_session_destroy(Session *session) +{ + SMTPSession *smtp_session = SMTP_SESSION(session); + + g_free(smtp_session->hostname); + g_free(smtp_session->user); + g_free(smtp_session->pass); + g_free(smtp_session->from); + + g_free(smtp_session->send_data); + + g_free(smtp_session->error_msg); +} + +static gint smtp_from(SMTPSession *session) +{ + gchar buf[MSGBUFSIZE]; + + g_return_val_if_fail(session->from != NULL, SM_ERROR); + + session->state = SMTP_FROM; + + if (strchr(session->from, '<')) + g_snprintf(buf, sizeof(buf), "MAIL FROM:%s", session->from); + else + g_snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", session->from); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("SMTP> %s\n", buf); + + return SM_OK; +} + +static gint smtp_auth(SMTPSession *session) +{ + + g_return_val_if_fail(session->user != NULL, SM_ERROR); + + session->state = SMTP_AUTH; + + if (session->forced_auth_type == SMTPAUTH_CRAM_MD5 || + (session->forced_auth_type == 0 && + (session->avail_auth_type & SMTPAUTH_CRAM_MD5) != 0)) + smtp_auth_cram_md5(session); + else if (session->forced_auth_type == SMTPAUTH_LOGIN || + (session->forced_auth_type == 0 && + (session->avail_auth_type & SMTPAUTH_LOGIN) != 0)) + smtp_auth_login(session); + else { + log_warning(_("SMTP AUTH not available\n")); + return SM_AUTHFAIL; + } + + return SM_OK; +} + +static gint smtp_auth_recv(SMTPSession *session, const gchar *msg) +{ + gchar buf[MSGBUFSIZE]; + + switch (session->auth_type) { + case SMTPAUTH_LOGIN: + session->state = SMTP_AUTH_LOGIN_USER; + + if (!strncmp(msg, "334 ", 4)) { + base64_encode(buf, session->user, strlen(session->user)); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + buf); + log_print("ESMTP> [USERID]\n"); + } else { + /* Server rejects AUTH */ + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + "*"); + log_print("ESMTP> *\n"); + } + break; + case SMTPAUTH_CRAM_MD5: + session->state = SMTP_AUTH_CRAM_MD5; + + if (!strncmp(msg, "334 ", 4)) { + gchar *response; + gchar *response64; + gchar *challenge; + gint challengelen; + guchar hexdigest[33]; + + challenge = g_malloc(strlen(msg + 4) + 1); + challengelen = base64_decode(challenge, msg + 4, -1); + challenge[challengelen] = '\0'; + log_print("ESMTP< [Decoded: %s]\n", challenge); + + g_snprintf(buf, sizeof(buf), "%s", session->pass); + md5_hex_hmac(hexdigest, challenge, challengelen, + buf, strlen(session->pass)); + g_free(challenge); + + response = g_strdup_printf + ("%s %s", session->user, hexdigest); + log_print("ESMTP> [Encoded: %s]\n", response); + + response64 = g_malloc((strlen(response) + 3) * 2 + 1); + base64_encode(response64, response, strlen(response)); + g_free(response); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + response64); + log_print("ESMTP> %s\n", response64); + g_free(response64); + } else { + /* Server rejects AUTH */ + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, + "*"); + log_print("ESMTP> *\n"); + } + break; + case SMTPAUTH_DIGEST_MD5: + default: + /* stop smtp_auth when no correct authtype */ + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*"); + log_print("ESMTP> *\n"); + break; + } + + return SM_OK; +} + +static gint smtp_auth_login_user_recv(SMTPSession *session, const gchar *msg) +{ + gchar buf[MSGBUFSIZE]; + + session->state = SMTP_AUTH_LOGIN_PASS; + + if (!strncmp(msg, "334 ", 4)) + base64_encode(buf, session->pass, strlen(session->pass)); + else + /* Server rejects AUTH */ + g_snprintf(buf, sizeof(buf), "*"); + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("ESMTP> [PASSWORD]\n"); + + return SM_OK; +} + +static gint smtp_ehlo(SMTPSession *session) +{ + gchar buf[MSGBUFSIZE]; + + session->state = SMTP_EHLO; + + session->avail_auth_type = 0; + + g_snprintf(buf, sizeof(buf), "EHLO %s", + session->hostname ? session->hostname : get_domain_name()); + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("ESMTP> %s\n", buf); + + return SM_OK; +} + +static gint smtp_ehlo_recv(SMTPSession *session, const gchar *msg) +{ + if (strncmp(msg, "250", 3) == 0) { + const gchar *p = msg; + p += 3; + if (*p == '-' || *p == ' ') p++; + if (g_strncasecmp(p, "AUTH", 4) == 0) { + p += 5; + if (strcasestr(p, "LOGIN")) + session->avail_auth_type |= SMTPAUTH_LOGIN; + if (strcasestr(p, "CRAM-MD5")) + session->avail_auth_type |= SMTPAUTH_CRAM_MD5; + if (strcasestr(p, "DIGEST-MD5")) + session->avail_auth_type |= SMTPAUTH_DIGEST_MD5; + } + return SM_OK; + } else if ((msg[0] == '1' || msg[0] == '2' || msg[0] == '3') && + (msg[3] == ' ' || msg[3] == '\0')) + return SM_OK; + else if (msg[0] == '5' && msg[1] == '0' && + (msg[2] == '4' || msg[2] == '3' || msg[2] == '1')) + return SM_ERROR; + + return SM_ERROR; +} + +static gint smtp_starttls(SMTPSession *session) +{ + session->state = SMTP_STARTTLS; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "STARTTLS"); + log_print("ESMTP> STARTTLS\n"); + + return SM_OK; +} + +static gint smtp_auth_cram_md5(SMTPSession *session) +{ + session->state = SMTP_AUTH; + session->auth_type = SMTPAUTH_CRAM_MD5; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH CRAM-MD5"); + log_print("ESMTP> AUTH CRAM-MD5\n"); + + return SM_OK; +} + +static gint smtp_auth_login(SMTPSession *session) +{ + session->state = SMTP_AUTH; + session->auth_type = SMTPAUTH_LOGIN; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "AUTH LOGIN"); + log_print("ESMTP> AUTH LOGIN\n"); + + return SM_OK; +} + +static gint smtp_helo(SMTPSession *session) +{ + gchar buf[MSGBUFSIZE]; + + session->state = SMTP_HELO; + + g_snprintf(buf, sizeof(buf), "HELO %s", + session->hostname ? session->hostname : get_domain_name()); + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("SMTP> %s\n", buf); + + return SM_OK; +} + +static gint smtp_rcpt(SMTPSession *session) +{ + gchar buf[MSGBUFSIZE]; + gchar *to; + + g_return_val_if_fail(session->cur_to != NULL, SM_ERROR); + + session->state = SMTP_RCPT; + + to = (gchar *)session->cur_to->data; + + if (strchr(to, '<')) + g_snprintf(buf, sizeof(buf), "RCPT TO:%s", to); + else + g_snprintf(buf, sizeof(buf), "RCPT TO:<%s>", to); + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf); + log_print("SMTP> %s\n", buf); + + session->cur_to = session->cur_to->next; + + return SM_OK; +} + +static gint smtp_data(SMTPSession *session) +{ + session->state = SMTP_DATA; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "DATA"); + log_print("SMTP> DATA\n"); + + return SM_OK; +} + +static gint smtp_send_data(SMTPSession *session) +{ + session->state = SMTP_SEND_DATA; + + session_send_data(SESSION(session), session->send_data, + session->send_data_len); + + return SM_OK; +} + +#if 0 +static gint smtp_rset(SMTPSession *session) +{ + session->state = SMTP_RSET; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "RSET"); + log_print("SMTP> RSET\n"); + + return SM_OK; +} +#endif + +static gint smtp_quit(SMTPSession *session) +{ + session->state = SMTP_QUIT; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "QUIT"); + log_print("SMTP> QUIT\n"); + + return SM_OK; +} + +static gint smtp_eom(SMTPSession *session) +{ + session->state = SMTP_EOM; + + session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "."); + log_print("SMTP> . (EOM)\n"); + + return SM_OK; +} + +static gint smtp_session_recv_msg(Session *session, const gchar *msg) +{ + SMTPSession *smtp_session = SMTP_SESSION(session); + gboolean cont = FALSE; + + if (strlen(msg) < 4) { + log_warning(_("bad SMTP response\n")); + return -1; + } + + switch (smtp_session->state) { + case SMTP_EHLO: + case SMTP_STARTTLS: + case SMTP_AUTH: + case SMTP_AUTH_LOGIN_USER: + case SMTP_AUTH_LOGIN_PASS: + case SMTP_AUTH_CRAM_MD5: + log_print("ESMTP< %s\n", msg); + break; + default: + log_print("SMTP< %s\n", msg); + break; + } + + if (msg[0] == '5' && msg[1] == '0' && + (msg[2] == '4' || msg[2] == '3' || msg[2] == '1')) { + log_warning(_("error occurred on SMTP session\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_ERROR; + g_free(smtp_session->error_msg); + smtp_session->error_msg = g_strdup(msg); + return -1; + } + + if (!strncmp(msg, "535", 3)) { + log_warning(_("error occurred on authentication\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_AUTHFAIL; + g_free(smtp_session->error_msg); + smtp_session->error_msg = g_strdup(msg); + return -1; + } + + if (msg[0] != '1' && msg[0] != '2' && msg[0] != '3') { + log_warning(_("error occurred on SMTP session\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_ERROR; + g_free(smtp_session->error_msg); + smtp_session->error_msg = g_strdup(msg); + return -1; + } + + if (msg[3] == '-') + cont = TRUE; + else if (msg[3] != ' ' && msg[3] != '\0') { + log_warning(_("bad SMTP response\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_UNRECOVERABLE; + return -1; + } + + /* ignore all multiline responses except for EHLO */ + if (cont && smtp_session->state != SMTP_EHLO) + return session_recv_msg(session); + + switch (smtp_session->state) { + case SMTP_READY: + case SMTP_CONNECTED: +#if USE_SSL + if (smtp_session->user || session->ssl_type != SSL_NONE) +#else + if (smtp_session->user) +#endif + smtp_ehlo(smtp_session); + else + smtp_helo(smtp_session); + break; + case SMTP_HELO: + smtp_from(smtp_session); + break; + case SMTP_EHLO: + smtp_ehlo_recv(smtp_session, msg); + if (cont == TRUE) + break; +#if USE_SSL + if (session->ssl_type == SSL_STARTTLS && + smtp_session->tls_init_done == FALSE) { + smtp_starttls(smtp_session); + break; + } +#endif + if (smtp_session->user) { + if (smtp_auth(smtp_session) != SM_OK) + smtp_from(smtp_session); + } else + smtp_from(smtp_session); + break; + case SMTP_STARTTLS: +#if USE_SSL + if (session_start_tls(session) < 0) { + log_warning(_("can't start TLS session\n")); + smtp_session->state = SMTP_ERROR; + smtp_session->error_val = SM_ERROR; + return -1; + } + smtp_session->tls_init_done = TRUE; + smtp_ehlo(smtp_session); +#endif + break; + case SMTP_AUTH: + smtp_auth_recv(smtp_session, msg); + break; + case SMTP_AUTH_LOGIN_USER: + smtp_auth_login_user_recv(smtp_session, msg); + break; + case SMTP_AUTH_LOGIN_PASS: + case SMTP_AUTH_CRAM_MD5: + smtp_from(smtp_session); + break; + case SMTP_FROM: + if (smtp_session->cur_to) + smtp_rcpt(smtp_session); + break; + case SMTP_RCPT: + if (smtp_session->cur_to) + smtp_rcpt(smtp_session); + else + smtp_data(smtp_session); + break; + case SMTP_DATA: + smtp_send_data(smtp_session); + break; + case SMTP_EOM: + smtp_quit(smtp_session); + break; + case SMTP_QUIT: + session_disconnect(session); + break; + case SMTP_ERROR: + default: + log_warning(_("error occurred on SMTP session\n")); + smtp_session->error_val = SM_ERROR; + return -1; + } + + if (cont) + return session_recv_msg(session); + + return 0; +} + +static gint smtp_session_send_data_finished(Session *session, guint len) +{ + smtp_eom(SMTP_SESSION(session)); + return 0; +} diff --git a/src/smtp.h b/src/smtp.h new file mode 100644 index 00000000..19629333 --- /dev/null +++ b/src/smtp.h @@ -0,0 +1,115 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SMTP_H__ +#define __SMTP_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "session.h" + +typedef struct _SMTPSession SMTPSession; + +#define SMTP_SESSION(obj) ((SMTPSession *)obj) + +#define MSGBUFSIZE 8192 + +typedef enum +{ + SM_OK = 0, + SM_ERROR = 128, + SM_UNRECOVERABLE = 129, + SM_AUTHFAIL = 130 +} SMTPErrorValue; + +typedef enum +{ + ESMTP_8BITMIME = 1 << 0, + ESMTP_SIZE = 1 << 1, + ESMTP_ETRN = 1 << 2 +} ESMTPFlag; + +typedef enum +{ + SMTPAUTH_LOGIN = 1 << 0, + SMTPAUTH_CRAM_MD5 = 1 << 1, + SMTPAUTH_DIGEST_MD5 = 1 << 2 +} SMTPAuthType; + +typedef enum +{ + SMTP_READY, + SMTP_CONNECTED, + SMTP_HELO, + SMTP_EHLO, + SMTP_STARTTLS, + SMTP_FROM, + SMTP_AUTH, + SMTP_AUTH_LOGIN_USER, + SMTP_AUTH_LOGIN_PASS, + SMTP_AUTH_CRAM_MD5, + SMTP_RCPT, + SMTP_DATA, + SMTP_SEND_DATA, + SMTP_EOM, + SMTP_RSET, + SMTP_QUIT, + SMTP_ERROR, + SMTP_DISCONNECTED, + + N_SMTP_PHASE +} SMTPState; + +struct _SMTPSession +{ + Session session; + + SMTPState state; + +#if USE_SSL + gboolean tls_init_done; +#endif + + gchar *hostname; + + gchar *user; + gchar *pass; + + gchar *from; + GSList *to_list; + GSList *cur_to; + + guchar *send_data; + guint send_data_len; + + SMTPAuthType avail_auth_type; + SMTPAuthType forced_auth_type; + SMTPAuthType auth_type; + + SMTPErrorValue error_val; + gchar *error_msg; +}; + +Session *smtp_session_new (void); + +#endif /* __SMTP_H__ */ diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 00000000..71810d20 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,1293 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_SYS_SELECT_H +# include +#endif + +#include "socket.h" +#if USE_SSL +# include "ssl.h" +#endif + +#define BUFFSIZE 8192 + +typedef gint (*SockAddrFunc) (GList *addr_list, + gpointer data); + +typedef struct _SockConnectData SockConnectData; +typedef struct _SockLookupData SockLookupData; +typedef struct _SockAddrData SockAddrData; +typedef struct _SockSource SockSource; + +struct _SockConnectData { + gint id; + gchar *hostname; + gushort port; + GList *addr_list; + GList *cur_addr; + SockLookupData *lookup_data; + GIOChannel *channel; + guint io_tag; + SockConnectFunc func; + gpointer data; +}; + +struct _SockLookupData { + gchar *hostname; + pid_t child_pid; + GIOChannel *channel; + guint io_tag; + SockAddrFunc func; + gpointer data; +}; + +struct _SockAddrData { + gint family; + gint socktype; + gint protocol; + gint addr_len; + struct sockaddr *addr; +}; + +struct _SockSource { + GSource parent; + SockInfo *sock; +}; + +static guint io_timeout = 60; + +static GList *sock_connect_data_list = NULL; + +static gboolean sock_prepare (GSource *source, + gint *timeout); +static gboolean sock_check (GSource *source); +static gboolean sock_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); + +GSourceFuncs sock_watch_funcs = { + sock_prepare, + sock_check, + sock_dispatch, + NULL +}; + +static gint sock_connect_with_timeout (gint sock, + const struct sockaddr *serv_addr, + gint addrlen, + guint timeout_secs); + +#ifndef INET6 +static gint sock_connect_by_hostname (gint sock, + const gchar *hostname, + gushort port); +#else +static gint sock_connect_by_getaddrinfo (const gchar *hostname, + gushort port); +#endif + +static void sock_address_list_free (GList *addr_list); + +static gboolean sock_connect_async_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); +static gint sock_connect_async_get_address_info_cb + (GList *addr_list, + gpointer data); + +static gint sock_connect_address_list_async (SockConnectData *conn_data); + +static gboolean sock_get_address_info_async_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); +static SockLookupData *sock_get_address_info_async + (const gchar *hostname, + gushort port, + SockAddrFunc func, + gpointer data); +static gint sock_get_address_info_async_cancel (SockLookupData *lookup_data); + + +gint sock_set_io_timeout(guint sec) +{ + io_timeout = sec; + return 0; +} + +gint fd_connect_unix(const gchar *path) +{ + gint sock; + struct sockaddr_un addr; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("sock_connect_unix(): socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(sock); + return -1; + } + + return sock; +} + +gint fd_open_unix(const gchar *path) +{ + gint sock; + struct sockaddr_un addr; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + + if (sock < 0) { + perror("sock_open_unix(): socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + close(sock); + return -1; + } + + if (listen(sock, 1) < 0) { + perror("listen"); + close(sock); + return -1; + } + + return sock; +} + +gint fd_accept(gint sock) +{ + struct sockaddr_in caddr; + gint caddr_len; + + caddr_len = sizeof(caddr); + return accept(sock, (struct sockaddr *)&caddr, &caddr_len); +} + + +static gint set_nonblocking_mode(gint fd, gboolean nonblock) +{ + gint flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return -1; + } + + if (nonblock) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + return fcntl(fd, F_SETFL, flags); +} + +gint sock_set_nonblocking_mode(SockInfo *sock, gboolean nonblock) +{ + g_return_val_if_fail(sock != NULL, -1); + + return set_nonblocking_mode(sock->sock, nonblock); +} + +static gboolean is_nonblocking_mode(gint fd) +{ + gint flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return FALSE; + } + + return ((flags & O_NONBLOCK) != 0); +} + +gboolean sock_is_nonblocking_mode(SockInfo *sock) +{ + g_return_val_if_fail(sock != NULL, FALSE); + + return is_nonblocking_mode(sock->sock); +} + + +static gboolean sock_prepare(GSource *source, gint *timeout) +{ + *timeout = 1; + return FALSE; +} + +static gboolean sock_check(GSource *source) +{ + SockInfo *sock = ((SockSource *)source)->sock; + struct timeval timeout = {0, 0}; + fd_set fds; + GIOCondition condition = sock->condition; + +#if USE_SSL + if (sock->ssl) { + if (condition & G_IO_IN) { + if (SSL_pending(sock->ssl) > 0) + return TRUE; + if (SSL_want_write(sock->ssl)) + condition |= G_IO_OUT; + } + + if (condition & G_IO_OUT) { + if (SSL_want_read(sock->ssl)) + condition |= G_IO_IN; + } + } +#endif + + FD_ZERO(&fds); + FD_SET(sock->sock, &fds); + + select(sock->sock + 1, + (condition & G_IO_IN) ? &fds : NULL, + (condition & G_IO_OUT) ? &fds : NULL, + NULL, &timeout); + + return FD_ISSET(sock->sock, &fds) != 0; +} + +static gboolean sock_dispatch(GSource *source, GSourceFunc callback, + gpointer user_data) +{ + SockInfo *sock = ((SockSource *)source)->sock; + + return sock->callback(sock, sock->condition, sock->data); +} + +static gboolean sock_watch_cb(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + SockInfo *sock = (SockInfo *)data; + + return sock->callback(sock, condition, sock->data); +} + +guint sock_add_watch(SockInfo *sock, GIOCondition condition, SockFunc func, + gpointer data) +{ + sock->callback = func; + sock->condition = condition; + sock->data = data; + +#if USE_SSL + if (sock->ssl) { + GSource *source; + + source = g_source_new(&sock_watch_funcs, sizeof(SockSource)); + ((SockSource *)source)->sock = sock; + g_source_set_priority(source, G_PRIORITY_DEFAULT); + g_source_set_can_recurse(source, FALSE); + g_source_attach(source, NULL); + } +#endif + + return g_io_add_watch(sock->sock_ch, condition, sock_watch_cb, sock); +} + +static gint fd_check_io(gint fd, GIOCondition cond) +{ + struct timeval timeout; + fd_set fds; + + if (is_nonblocking_mode(fd)) + return 0; + + timeout.tv_sec = io_timeout; + timeout.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (cond == G_IO_IN) { + select(fd + 1, &fds, NULL, NULL, + io_timeout > 0 ? &timeout : NULL); + } else { + select(fd + 1, NULL, &fds, NULL, + io_timeout > 0 ? &timeout : NULL); + } + + if (FD_ISSET(fd, &fds)) { + return 0; + } else { + g_warning("Socket IO timeout\n"); + return -1; + } +} + +static sigjmp_buf jmpenv; + +static void timeout_handler(gint sig) +{ + siglongjmp(jmpenv, 1); +} + +static gint sock_connect_with_timeout(gint sock, + const struct sockaddr *serv_addr, + gint addrlen, + guint timeout_secs) +{ + gint ret; + void (*prev_handler)(gint); + + alarm(0); + prev_handler = signal(SIGALRM, timeout_handler); + if (sigsetjmp(jmpenv, 1)) { + alarm(0); + signal(SIGALRM, prev_handler); + errno = ETIMEDOUT; + return -1; + } + alarm(timeout_secs); + + ret = connect(sock, serv_addr, addrlen); + + alarm(0); + signal(SIGALRM, prev_handler); + + return ret; +} + +struct hostent *my_gethostbyname(const gchar *hostname) +{ + struct hostent *hp; + void (*prev_handler)(gint); + + alarm(0); + prev_handler = signal(SIGALRM, timeout_handler); + if (sigsetjmp(jmpenv, 1)) { + alarm(0); + signal(SIGALRM, prev_handler); + fprintf(stderr, "%s: host lookup timed out.\n", hostname); + errno = 0; + return NULL; + } + alarm(io_timeout); + + if ((hp = gethostbyname(hostname)) == NULL) { + alarm(0); + signal(SIGALRM, prev_handler); + fprintf(stderr, "%s: unknown host.\n", hostname); + errno = 0; + return NULL; + } + + alarm(0); + signal(SIGALRM, prev_handler); + + return hp; +} + +#ifndef INET6 +static gint my_inet_aton(const gchar *hostname, struct in_addr *inp) +{ +#if HAVE_INET_ATON + return inet_aton(hostname, inp); +#else +#if HAVE_INET_ADDR + guint32 inaddr; + + inaddr = inet_addr(hostname); + if (inaddr != -1) { + memcpy(inp, &inaddr, sizeof(inaddr)); + return 1; + } else + return 0; +#else + return 0; +#endif +#endif /* HAVE_INET_ATON */ +} + +static gint sock_connect_by_hostname(gint sock, const gchar *hostname, + gushort port) +{ + struct hostent *hp; + struct sockaddr_in ad; + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + ad.sin_port = htons(port); + + if (!my_inet_aton(hostname, &ad.sin_addr)) { + if ((hp = my_gethostbyname(hostname)) == NULL) { + fprintf(stderr, "%s: unknown host.\n", hostname); + errno = 0; + return -1; + } + + if (hp->h_length != 4 && hp->h_length != 8) { + fprintf(stderr, "illegal address length received for host %s\n", hostname); + errno = 0; + return -1; + } + + memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); + } + + return sock_connect_with_timeout(sock, (struct sockaddr *)&ad, + sizeof(ad), io_timeout); +} + +#else /* INET6 */ +static gint sock_connect_by_getaddrinfo(const gchar *hostname, gushort port) +{ + gint sock = -1, gai_error; + struct addrinfo hints, *res, *ai; + gchar port_str[6]; + + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_CANONNAME; */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* convert port from integer to string. */ + g_snprintf(port_str, sizeof(port_str), "%d", port); + + if ((gai_error = getaddrinfo(hostname, port_str, &hints, &res)) != 0) { + fprintf(stderr, "getaddrinfo for %s:%s failed: %s\n", + hostname, port_str, gai_strerror(gai_error)); + return -1; + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) + continue; + + if (sock_connect_with_timeout + (sock, ai->ai_addr, ai->ai_addrlen, io_timeout) == 0) + break; + + close(sock); + } + + if (res != NULL) + freeaddrinfo(res); + + if (ai == NULL) + return -1; + + return sock; +} +#endif /* !INET6 */ + +SockInfo *sock_connect(const gchar *hostname, gushort port) +{ + gint sock; + SockInfo *sockinfo; + +#ifdef INET6 + if ((sock = sock_connect_by_getaddrinfo(hostname, port)) < 0) + return NULL; +#else + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("socket"); + return NULL; + } + + if (sock_connect_by_hostname(sock, hostname, port) < 0) { + if (errno != 0) perror("connect"); + close(sock); + return NULL; + } +#endif /* INET6 */ + + sockinfo = g_new0(SockInfo, 1); + sockinfo->sock = sock; + sockinfo->sock_ch = g_io_channel_unix_new(sock); + sockinfo->hostname = g_strdup(hostname); + sockinfo->port = port; + sockinfo->state = CONN_ESTABLISHED; + + usleep(100000); + + return sockinfo; +} + +static void sock_address_list_free(GList *addr_list) +{ + GList *cur; + + for (cur = addr_list; cur != NULL; cur = cur->next) { + SockAddrData *addr_data = (SockAddrData *)cur->data; + g_free(addr_data->addr); + g_free(addr_data); + } + + g_list_free(addr_list); +} + +/* asynchronous TCP connection */ + +static gboolean sock_connect_async_cb(GIOChannel *source, + GIOCondition condition, gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + gint fd; + gint val; + gint len; + SockInfo *sockinfo; + + fd = g_io_channel_unix_get_fd(source); + + conn_data->io_tag = 0; + conn_data->channel = NULL; + g_io_channel_unref(source); + + len = sizeof(val); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len) < 0) { + perror("getsockopt"); + close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + if (val != 0) { + close(fd); + sock_connect_address_list_async(conn_data); + return FALSE; + } + + sockinfo = g_new0(SockInfo, 1); + sockinfo->sock = fd; + sockinfo->sock_ch = g_io_channel_unix_new(fd); + sockinfo->hostname = g_strdup(conn_data->hostname); + sockinfo->port = conn_data->port; + sockinfo->state = CONN_ESTABLISHED; + + conn_data->func(sockinfo, conn_data->data); + + sock_connect_async_cancel(conn_data->id); + + return FALSE; +} + +static gint sock_connect_async_get_address_info_cb(GList *addr_list, + gpointer data) +{ + SockConnectData *conn_data = (SockConnectData *)data; + + conn_data->addr_list = addr_list; + conn_data->cur_addr = addr_list; + conn_data->lookup_data = NULL; + + return sock_connect_address_list_async(conn_data); +} + +gint sock_connect_async(const gchar *hostname, gushort port, + SockConnectFunc func, gpointer data) +{ + static gint id = 1; + SockConnectData *conn_data; + + conn_data = g_new0(SockConnectData, 1); + conn_data->id = id++; + conn_data->hostname = g_strdup(hostname); + conn_data->port = port; + conn_data->addr_list = NULL; + conn_data->cur_addr = NULL; + conn_data->io_tag = 0; + conn_data->func = func; + conn_data->data = data; + + conn_data->lookup_data = sock_get_address_info_async + (hostname, port, sock_connect_async_get_address_info_cb, + conn_data); + + if (conn_data->lookup_data == NULL) { + g_free(conn_data->hostname); + g_free(conn_data); + return -1; + } + + sock_connect_data_list = g_list_append(sock_connect_data_list, + conn_data); + + return conn_data->id; +} + +gint sock_connect_async_cancel(gint id) +{ + SockConnectData *conn_data = NULL; + GList *cur; + + for (cur = sock_connect_data_list; cur != NULL; cur = cur->next) { + if (((SockConnectData *)cur->data)->id == id) { + conn_data = (SockConnectData *)cur->data; + break; + } + } + + if (conn_data) { + sock_connect_data_list = g_list_remove(sock_connect_data_list, + conn_data); + + if (conn_data->lookup_data) + sock_get_address_info_async_cancel + (conn_data->lookup_data); + + if (conn_data->io_tag > 0) + g_source_remove(conn_data->io_tag); + if (conn_data->channel) { + g_io_channel_close(conn_data->channel); + g_io_channel_unref(conn_data->channel); + } + + sock_address_list_free(conn_data->addr_list); + g_free(conn_data->hostname); + g_free(conn_data); + } else { + g_warning("sock_connect_async_cancel: id %d not found.\n", id); + return -1; + } + + return 0; +} + +static gint sock_connect_address_list_async(SockConnectData *conn_data) +{ + SockAddrData *addr_data; + gint sock = -1; + + for (; conn_data->cur_addr != NULL; + conn_data->cur_addr = conn_data->cur_addr->next) { + addr_data = (SockAddrData *)conn_data->cur_addr->data; + + if ((sock = socket(addr_data->family, addr_data->socktype, + addr_data->protocol)) < 0) { + perror("socket"); + continue; + } + + set_nonblocking_mode(sock, TRUE); + + if (connect(sock, addr_data->addr, addr_data->addr_len) < 0) { + if (EINPROGRESS == errno) { + break; + } else { + perror("connect"); + close(sock); + } + } else + break; + } + + if (conn_data->cur_addr == NULL) { + g_warning("sock_connect_address_list_async: " + "connection to %s:%d failed\n", + conn_data->hostname, conn_data->port); + conn_data->func(NULL, conn_data->data); + sock_connect_async_cancel(conn_data->id); + return -1; + } + + conn_data->cur_addr = conn_data->cur_addr->next; + + conn_data->channel = g_io_channel_unix_new(sock); + conn_data->io_tag = g_io_add_watch(conn_data->channel, G_IO_IN|G_IO_OUT, + sock_connect_async_cb, conn_data); + + return 0; +} + +/* asynchronous DNS lookup */ + +static gboolean sock_get_address_info_async_cb(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + SockLookupData *lookup_data = (SockLookupData *)data; + GList *addr_list = NULL; + SockAddrData *addr_data; + gsize bytes_read; + gint ai_member[4]; + struct sockaddr *addr; + + for (;;) { + if (g_io_channel_read(source, (gchar *)ai_member, + sizeof(ai_member), &bytes_read) + != G_IO_ERROR_NONE) { + g_warning("sock_get_address_info_async_cb: " + "address length read error\n"); + break; + } + + if (bytes_read == 0 || bytes_read != sizeof(ai_member)) + break; + + if (ai_member[0] == AF_UNSPEC) { + g_warning("DNS lookup failed\n"); + break; + } + + addr = g_malloc(ai_member[3]); + if (g_io_channel_read(source, (gchar *)addr, ai_member[3], + &bytes_read) + != G_IO_ERROR_NONE) { + g_warning("sock_get_address_info_async_cb: " + "address data read error\n"); + g_free(addr); + break; + } + + if (bytes_read != ai_member[3]) { + g_warning("sock_get_address_info_async_cb: " + "incomplete address data\n"); + g_free(addr); + break; + } + + addr_data = g_new0(SockAddrData, 1); + addr_data->family = ai_member[0]; + addr_data->socktype = ai_member[1]; + addr_data->protocol = ai_member[2]; + addr_data->addr_len = ai_member[3]; + addr_data->addr = addr; + + addr_list = g_list_append(addr_list, addr_data); + } + + g_io_channel_close(source); + g_io_channel_unref(source); + + kill(lookup_data->child_pid, SIGKILL); + waitpid(lookup_data->child_pid, NULL, 0); + + lookup_data->func(addr_list, lookup_data->data); + + g_free(lookup_data->hostname); + g_free(lookup_data); + + return FALSE; +} + +static SockLookupData *sock_get_address_info_async(const gchar *hostname, + gushort port, + SockAddrFunc func, + gpointer data) +{ + SockLookupData *lookup_data = NULL; + gint pipe_fds[2]; + pid_t pid; + + if (pipe(pipe_fds) < 0) { + perror("pipe"); + func(NULL, data); + return NULL; + } + + if ((pid = fork()) < 0) { + perror("fork"); + func(NULL, data); + return NULL; + } + + /* child process */ + if (pid == 0) { +#ifdef INET6 + gint gai_err; + struct addrinfo hints, *res, *ai; + gchar port_str[6]; +#else /* !INET6 */ + struct hostent *hp; + gchar **addr_list_p; + struct sockaddr_in ad; +#endif /* INET6 */ + gint ai_member[4] = {AF_UNSPEC, 0, 0, 0}; + + close(pipe_fds[0]); + +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_CANONNAME; */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + g_snprintf(port_str, sizeof(port_str), "%d", port); + + gai_err = getaddrinfo(hostname, port_str, &hints, &res); + if (gai_err != 0) { + g_warning("getaddrinfo for %s:%s failed: %s\n", + hostname, port_str, gai_strerror(gai_err)); + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + close(pipe_fds[1]); + _exit(1); + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + ai_member[0] = ai->ai_family; + ai_member[1] = ai->ai_socktype; + ai_member[2] = ai->ai_protocol; + ai_member[3] = ai->ai_addrlen; + + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + fd_write_all(pipe_fds[1], (gchar *)ai->ai_addr, + ai->ai_addrlen); + } + + if (res != NULL) + freeaddrinfo(res); +#else /* !INET6 */ + hp = my_gethostbyname(hostname); + if (hp == NULL || hp->h_addrtype != AF_INET) { + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + close(pipe_fds[1]); + _exit(1); + } + + ai_member[0] = AF_INET; + ai_member[1] = SOCK_STREAM; + ai_member[2] = IPPROTO_TCP; + ai_member[3] = sizeof(ad); + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + ad.sin_port = htons(port); + + for (addr_list_p = hp->h_addr_list; *addr_list_p != NULL; + addr_list_p++) { + memcpy(&ad.sin_addr, *addr_list_p, hp->h_length); + fd_write_all(pipe_fds[1], (gchar *)ai_member, + sizeof(ai_member)); + fd_write_all(pipe_fds[1], (gchar *)&ad, sizeof(ad)); + } +#endif /* INET6 */ + + close(pipe_fds[1]); + + _exit(0); + } else { + close(pipe_fds[1]); + + lookup_data = g_new0(SockLookupData, 1); + lookup_data->hostname = g_strdup(hostname); + lookup_data->child_pid = pid; + lookup_data->func = func; + lookup_data->data = data; + + lookup_data->channel = g_io_channel_unix_new(pipe_fds[0]); + lookup_data->io_tag = g_io_add_watch + (lookup_data->channel, G_IO_IN, + sock_get_address_info_async_cb, lookup_data); + } + + return lookup_data; +} + +static gint sock_get_address_info_async_cancel(SockLookupData *lookup_data) +{ + if (lookup_data->io_tag > 0) + g_source_remove(lookup_data->io_tag); + if (lookup_data->channel) { + g_io_channel_close(lookup_data->channel); + g_io_channel_unref(lookup_data->channel); + } + + if (lookup_data->child_pid > 0) { + kill(lookup_data->child_pid, SIGKILL); + waitpid(lookup_data->child_pid, NULL, 0); + } + + g_free(lookup_data->hostname); + g_free(lookup_data); + + return 0; +} + + +gint sock_printf(SockInfo *sock, const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + return sock_write_all(sock, buf, strlen(buf)); +} + +gint sock_read(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_read(sock->ssl, buf, len); +#endif + return fd_read(sock->sock, buf, len); +} + +gint fd_read(gint fd, gchar *buf, gint len) +{ + if (fd_check_io(fd, G_IO_IN) < 0) + return -1; + + return read(fd, buf, len); +} + +#if USE_SSL +gint ssl_read(SSL *ssl, gchar *buf, gint len) +{ + gint ret; + + if (SSL_pending(ssl) == 0) { + if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0) + return -1; + } + + ret = SSL_read(ssl, buf, len); + + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + default: + return -1; + } +} +#endif + +gint sock_write(SockInfo *sock, const gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_write(sock->ssl, buf, len); +#endif + return fd_write(sock->sock, buf, len); +} + +gint fd_write(gint fd, const gchar *buf, gint len) +{ + if (fd_check_io(fd, G_IO_OUT) < 0) + return -1; + + return write(fd, buf, len); +} + +#if USE_SSL +gint ssl_write(SSL *ssl, const gchar *buf, gint len) +{ + gint ret; + + ret = SSL_write(ssl, buf, len); + + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + default: + return -1; + } +} +#endif + +gint sock_write_all(SockInfo *sock, const gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_write_all(sock->ssl, buf, len); +#endif + return fd_write_all(sock->sock, buf, len); +} + +gint fd_write_all(gint fd, const gchar *buf, gint len) +{ + gint n, wrlen = 0; + + while (len) { + if (fd_check_io(fd, G_IO_OUT) < 0) + return -1; + n = write(fd, buf, len); + if (n <= 0) + return -1; + len -= n; + wrlen += n; + buf += n; + } + + return wrlen; +} + +#if USE_SSL +gint ssl_write_all(SSL *ssl, const gchar *buf, gint len) +{ + gint n, wrlen = 0; + + while (len) { + n = ssl_write(ssl, buf, len); + if (n <= 0) + return -1; + len -= n; + wrlen += n; + buf += n; + } + + return wrlen; +} +#endif + +gint fd_recv(gint fd, gchar *buf, gint len, gint flags) +{ + if (fd_check_io(fd, G_IO_IN) < 0) + return -1; + + return recv(fd, buf, len, flags); +} + +gint fd_gets(gint fd, gchar *buf, gint len) +{ + gchar *newline, *bp = buf; + gint n; + + if (--len < 1) + return -1; + do { + if ((n = fd_recv(fd, bp, len, MSG_PEEK)) <= 0) + return -1; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = fd_read(fd, bp, n)) < 0) + return -1; + bp += n; + len -= n; + } while (!newline && len); + + *bp = '\0'; + return bp - buf; +} + +#if USE_SSL +gint ssl_gets(SSL *ssl, gchar *buf, gint len) +{ + gchar *newline, *bp = buf; + gint n; + + if (--len < 1) + return -1; + do { + if ((n = ssl_peek(ssl, bp, len)) <= 0) + return -1; + if ((newline = memchr(bp, '\n', n)) != NULL) + n = newline - bp + 1; + if ((n = ssl_read(ssl, bp, n)) < 0) + return -1; + bp += n; + len -= n; + } while (!newline && len); + + *bp = '\0'; + return bp - buf; +} +#endif + +gint sock_gets(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_gets(sock->ssl, buf, len); +#endif + return fd_gets(sock->sock, buf, len); +} + +gchar *fd_getline(gint fd) +{ + gchar buf[BUFFSIZE]; + gchar *str = NULL; + gint len; + gulong size = 1; + + while ((len = fd_gets(fd, buf, sizeof(buf))) > 0) { + size += len; + if (!str) + str = g_strdup(buf); + else { + str = g_realloc(str, size); + strcat(str, buf); + } + if (buf[len - 1] == '\n') + break; + } + + return str; +} + +#if USE_SSL +gchar *ssl_getline(SSL *ssl) +{ + gchar buf[BUFFSIZE]; + gchar *str = NULL; + gint len; + gulong size = 1; + + while ((len = ssl_gets(ssl, buf, sizeof(buf))) > 0) { + size += len; + if (!str) + str = g_strdup(buf); + else { + str = g_realloc(str, size); + strcat(str, buf); + } + if (buf[len - 1] == '\n') + break; + } + + return str; +} +#endif + +gchar *sock_getline(SockInfo *sock) +{ + g_return_val_if_fail(sock != NULL, NULL); + +#if USE_SSL + if (sock->ssl) + return ssl_getline(sock->ssl); +#endif + return fd_getline(sock->sock); +} + +gint sock_puts(SockInfo *sock, const gchar *buf) +{ + gint ret; + + if ((ret = sock_write_all(sock, buf, strlen(buf))) < 0) + return ret; + return sock_write_all(sock, "\r\n", 2); +} + +/* peek at the socket data without actually reading it */ +#if USE_SSL +gint ssl_peek(SSL *ssl, gchar *buf, gint len) +{ + gint ret; + + if (SSL_pending(ssl) == 0) { + if (fd_check_io(SSL_get_rfd(ssl), G_IO_IN) < 0) + return -1; + } + + ret = SSL_peek(ssl, buf, len); + + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_NONE: + return ret; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + default: + return -1; + } +} +#endif + +gint sock_peek(SockInfo *sock, gchar *buf, gint len) +{ + g_return_val_if_fail(sock != NULL, -1); + +#if USE_SSL + if (sock->ssl) + return ssl_peek(sock->ssl, buf, len); +#endif + return fd_recv(sock->sock, buf, len, MSG_PEEK); +} + +gint sock_close(SockInfo *sock) +{ + gint ret; + + if (!sock) + return 0; + + if (sock->sock_ch) + g_io_channel_unref(sock->sock_ch); + +#if USE_SSL + if (sock->ssl) + ssl_done_socket(sock); +#endif + ret = fd_close(sock->sock); + g_free(sock->hostname); + g_free(sock); + + return ret; +} + +gint fd_close(gint fd) +{ + return close(fd); +} diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 00000000..05c43b89 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,117 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct _SockInfo SockInfo; + +#if USE_SSL +# include "ssl.h" +#endif + +typedef enum +{ + CONN_READY, + CONN_LOOKUPSUCCESS, + CONN_ESTABLISHED, + CONN_LOOKUPFAILED, + CONN_FAILED +} ConnectionState; + +typedef gint (*SockConnectFunc) (SockInfo *sock, + gpointer data); +typedef gboolean (*SockFunc) (SockInfo *sock, + GIOCondition condition, + gpointer data); + +struct _SockInfo +{ + gint sock; +#if USE_SSL + SSL *ssl; +#endif + GIOChannel *sock_ch; + + gchar *hostname; + gushort port; + ConnectionState state; + gpointer data; + + SockFunc callback; + GIOCondition condition; +}; + +gint sock_set_io_timeout (guint sec); + +gint sock_set_nonblocking_mode (SockInfo *sock, gboolean nonblock); +gboolean sock_is_nonblocking_mode (SockInfo *sock); + +guint sock_add_watch (SockInfo *sock, GIOCondition condition, + SockFunc func, gpointer data); + +struct hostent *my_gethostbyname (const gchar *hostname); + +SockInfo *sock_connect (const gchar *hostname, gushort port); +gint sock_connect_async (const gchar *hostname, gushort port, + SockConnectFunc func, gpointer data); +gint sock_connect_async_cancel (gint id); + +/* Basic I/O functions */ +gint sock_printf (SockInfo *sock, const gchar *format, ...) + G_GNUC_PRINTF(2, 3); +gint sock_read (SockInfo *sock, gchar *buf, gint len); +gint sock_write (SockInfo *sock, const gchar *buf, gint len); +gint sock_write_all (SockInfo *sock, const gchar *buf, gint len); +gint sock_gets (SockInfo *sock, gchar *buf, gint len); +gchar *sock_getline (SockInfo *sock); +gint sock_puts (SockInfo *sock, const gchar *buf); +gint sock_peek (SockInfo *sock, gchar *buf, gint len); +gint sock_close (SockInfo *sock); + +/* Functions to directly work on FD. They are needed for pipes */ +gint fd_connect_unix (const gchar *path); +gint fd_open_unix (const gchar *path); +gint fd_accept (gint sock); + +gint fd_read (gint sock, gchar *buf, gint len); +gint fd_write (gint sock, const gchar *buf, gint len); +gint fd_write_all (gint sock, const gchar *buf, gint len); +gint fd_gets (gint sock, gchar *buf, gint len); +gchar *fd_getline (gint sock); +gint fd_close (gint sock); + +/* Functions for SSL */ +#if USE_SSL +gint ssl_read (SSL *ssl, gchar *buf, gint len); +gint ssl_write (SSL *ssl, const gchar *buf, gint len); +gint ssl_write_all (SSL *ssl, const gchar *buf, gint len); +gint ssl_gets (SSL *ssl, gchar *buf, gint len); +gchar *ssl_getline (SSL *ssl); +gint ssl_peek (SSL *ssl, gchar *buf, gint len); +#endif + +#endif /* __SOCKET_H__ */ diff --git a/src/sourcewindow.c b/src/sourcewindow.c new file mode 100644 index 00000000..069ce7fb --- /dev/null +++ b/src/sourcewindow.c @@ -0,0 +1,183 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "sourcewindow.h" +#include "procmsg.h" +#include "utils.h" +#include "gtkutils.h" +#include "prefs_common.h" + +static void source_window_size_alloc_cb (GtkWidget *widget, + GtkAllocation *allocation); +static void source_window_destroy_cb (GtkWidget *widget, + SourceWindow *sourcewin); +static gboolean key_pressed (GtkWidget *widget, + GdkEventKey *event, + SourceWindow *sourcewin); + +static void source_window_init() +{ +} + +SourceWindow *source_window_create(void) +{ + SourceWindow *sourcewin; + GtkWidget *window; + GtkWidget *scrolledwin; + GtkWidget *text; + static PangoFontDescription *font_desc = NULL; + + debug_print(_("Creating source window...\n")); + sourcewin = g_new0(SourceWindow, 1); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), _("Source of the message")); + gtk_window_set_wmclass(GTK_WINDOW(window), "source_window", "Sylpheed"); + gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE); + gtk_widget_set_size_request(window, prefs_common.sourcewin_width, + prefs_common.sourcewin_height); + g_signal_connect(G_OBJECT(window), "size_allocate", + G_CALLBACK(source_window_size_alloc_cb), sourcewin); + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(source_window_destroy_cb), sourcewin); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), sourcewin); + gtk_widget_realize(window); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_container_add(GTK_CONTAINER(window), scrolledwin); + gtk_widget_show(scrolledwin); + + text = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + if (!font_desc && prefs_common.textfont) + font_desc = pango_font_description_from_string + (prefs_common.textfont); + if (font_desc) + gtk_widget_modify_font(text, font_desc); + gtk_container_add(GTK_CONTAINER(scrolledwin), text); + gtk_widget_show(text); + + sourcewin->window = window; + sourcewin->scrolledwin = scrolledwin; + sourcewin->text = text; + + source_window_init(); + + return sourcewin; +} + +void source_window_show(SourceWindow *sourcewin) +{ + gtk_widget_show_all(sourcewin->window); +} + +void source_window_destroy(SourceWindow *sourcewin) +{ + g_free(sourcewin); +} + +void source_window_show_msg(SourceWindow *sourcewin, MsgInfo *msginfo) +{ + gchar *file; + gchar *title; + FILE *fp; + gchar buf[BUFFSIZE]; + + g_return_if_fail(msginfo != NULL); + + file = procmsg_get_message_file(msginfo); + g_return_if_fail(file != NULL); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + g_free(file); + return; + } + + debug_print(_("Displaying the source of %s ...\n"), file); + + title = g_strdup_printf(_("%s - Source"), file); + gtk_window_set_title(GTK_WINDOW(sourcewin->window), title); + g_free(title); + g_free(file); + + while (fgets(buf, sizeof(buf), fp) != NULL) + source_window_append(sourcewin, buf); + + fclose(fp); +} + +void source_window_append(SourceWindow *sourcewin, const gchar *str) +{ + GtkTextView *text = GTK_TEXT_VIEW(sourcewin->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + gchar *out; + gint len; + + buffer = gtk_text_view_get_buffer(text); + + len = strlen(str) + 1; + Xalloca(out, len, return); +#warning FIXME_GTK2 + conv_localetodisp(out, len, str); + + gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); + gtk_text_buffer_insert(buffer, &iter, out, -1); +} + +static void source_window_size_alloc_cb(GtkWidget *widget, + GtkAllocation *allocation) +{ + g_return_if_fail(allocation != NULL); + + prefs_common.sourcewin_width = allocation->width; + prefs_common.sourcewin_height = allocation->height; +} + +static void source_window_destroy_cb(GtkWidget *widget, + SourceWindow *sourcewin) +{ + source_window_destroy(sourcewin); +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + SourceWindow *sourcewin) +{ + if (event && event->keyval == GDK_Escape) + gtk_widget_destroy(sourcewin->window); + return FALSE; +} diff --git a/src/sourcewindow.h b/src/sourcewindow.h new file mode 100644 index 00000000..a50a1329 --- /dev/null +++ b/src/sourcewindow.h @@ -0,0 +1,45 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SOURCEWINDOW_H__ +#define __SOURCEWINDOW_H__ + +#include +#include + +#include "procmsg.h" + +typedef struct _SourceWindow SourceWindow; + +struct _SourceWindow +{ + GtkWidget *window; + GtkWidget *scrolledwin; + GtkWidget *text; +}; + +SourceWindow *source_window_create (void); +void source_window_show (SourceWindow *sourcewin); +void source_window_destroy (SourceWindow *sourcewin); +void source_window_show_msg (SourceWindow *sourcewin, + MsgInfo *msginfo); +void source_window_append (SourceWindow *sourcewin, + const gchar *str); + +#endif /* __SOURCEWINDOW_H__ */ diff --git a/src/ssl.c b/src/ssl.c new file mode 100644 index 00000000..17ce8934 --- /dev/null +++ b/src/ssl.c @@ -0,0 +1,146 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include "defs.h" + +#include + +#include "intl.h" +#include "utils.h" +#include "ssl.h" + +static SSL_CTX *ssl_ctx_SSLv23; +static SSL_CTX *ssl_ctx_TLSv1; + +void ssl_init(void) +{ + SSL_library_init(); + SSL_load_error_strings(); + + ssl_ctx_SSLv23 = SSL_CTX_new(SSLv23_client_method()); + if (ssl_ctx_SSLv23 == NULL) { + debug_print(_("SSLv23 not available\n")); + } else { + debug_print(_("SSLv23 available\n")); + } + + ssl_ctx_TLSv1 = SSL_CTX_new(TLSv1_client_method()); + if (ssl_ctx_TLSv1 == NULL) { + debug_print(_("TLSv1 not available\n")); + } else { + debug_print(_("TLSv1 available\n")); + } +} + +void ssl_done(void) +{ + if (ssl_ctx_SSLv23) { + SSL_CTX_free(ssl_ctx_SSLv23); + } + + if (ssl_ctx_TLSv1) { + SSL_CTX_free(ssl_ctx_TLSv1); + } +} + +gboolean ssl_init_socket(SockInfo *sockinfo) +{ + return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23); +} + +gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) +{ + X509 *server_cert; + gint ret; + + switch (method) { + case SSL_METHOD_SSLv23: + if (!ssl_ctx_SSLv23) { + g_warning(_("SSL method not available\n")); + return FALSE; + } + sockinfo->ssl = SSL_new(ssl_ctx_SSLv23); + break; + case SSL_METHOD_TLSv1: + if (!ssl_ctx_TLSv1) { + g_warning(_("SSL method not available\n")); + return FALSE; + } + sockinfo->ssl = SSL_new(ssl_ctx_TLSv1); + break; + default: + g_warning(_("Unknown SSL method *PROGRAM BUG*\n")); + return FALSE; + break; + } + + if (sockinfo->ssl == NULL) { + g_warning(_("Error creating ssl context\n")); + return FALSE; + } + + SSL_set_fd(sockinfo->ssl, sockinfo->sock); + if ((ret = SSL_connect(sockinfo->ssl)) == -1) { + g_warning(_("SSL connect failed (%s)\n"), + ERR_error_string(ERR_get_error(), NULL)); + return FALSE; + } + + /* Get the cipher */ + + debug_print(_("SSL connection using %s\n"), + SSL_get_cipher(sockinfo->ssl)); + + /* Get server's certificate (note: beware of dynamic allocation) */ + + if ((server_cert = SSL_get_peer_certificate(sockinfo->ssl)) != NULL) { + gchar *str; + + debug_print(_("Server certificate:\n")); + + if ((str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0)) != NULL) { + debug_print(_(" Subject: %s\n"), str); + free(str); + } + + if ((str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0)) != NULL) { + debug_print(_(" Issuer: %s\n"), str); + free(str); + } + + X509_free(server_cert); + } + + return TRUE; +} + +void ssl_done_socket(SockInfo *sockinfo) +{ + if (sockinfo->ssl) { + SSL_free(sockinfo->ssl); + } +} + +#endif /* USE_SSL */ diff --git a/src/ssl.h b/src/ssl.h new file mode 100644 index 00000000..6c13ddac --- /dev/null +++ b/src/ssl.h @@ -0,0 +1,58 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SSL_H__ +#define __SSL_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if USE_SSL + +#include +#include +#include +#include +#include +#include + +#include "socket.h" + +typedef enum { + SSL_METHOD_SSLv23, + SSL_METHOD_TLSv1 +} SSLMethod; + +typedef enum { + SSL_NONE, + SSL_TUNNEL, + SSL_STARTTLS +} SSLType; + +void ssl_init (void); +void ssl_done (void); +gboolean ssl_init_socket (SockInfo *sockinfo); +gboolean ssl_init_socket_with_method (SockInfo *sockinfo, + SSLMethod method); +void ssl_done_socket (SockInfo *sockinfo); + +#endif /* USE_SSL */ + +#endif /* __SSL_H__ */ diff --git a/src/statusbar.c b/src/statusbar.c new file mode 100644 index 00000000..38d1377f --- /dev/null +++ b/src/statusbar.c @@ -0,0 +1,125 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "intl.h" +#include "statusbar.h" +#include "gtkutils.h" +#include "utils.h" + +#define BUFFSIZE 1024 + +static GList *statusbar_list = NULL; + +GtkWidget *statusbar_create(void) +{ + GtkWidget *statusbar; + + statusbar = gtk_statusbar_new(); + gtk_widget_set_usize(statusbar, 1, -1); + statusbar_list = g_list_append(statusbar_list, statusbar); + + return statusbar; +} + +void statusbar_puts(GtkStatusbar *statusbar, const gchar *str) +{ + gint cid; + gchar *buf; + + buf = g_strdup(str); + strretchomp(buf); + if (strlen(buf) > 76) { + wchar_t *wbuf; + + wbuf = strdup_mbstowcs(buf); + + if (wcslen(wbuf) > 60) { + gchar *tmp; + + g_free(buf); + wbuf[60] = (wchar_t)0; + tmp = strdup_wcstombs(wbuf); + buf = g_strconcat(tmp, "...", NULL); + g_free(tmp); + } + + g_free(wbuf); + } + + cid = gtk_statusbar_get_context_id(statusbar, "Standard Output"); + gtk_statusbar_pop(statusbar, cid); + gtk_statusbar_push(statusbar, cid, buf); + gtkut_widget_wait_for_draw(GTK_WIDGET(statusbar)->parent); + + g_free(buf); +} + +void statusbar_puts_all(const gchar *str) +{ + GList *cur; + + for (cur = statusbar_list; cur != NULL; cur = cur->next) + statusbar_puts(GTK_STATUSBAR(cur->data), str); +} + +void statusbar_print(GtkStatusbar *statusbar, const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + statusbar_puts(statusbar, buf); +} + +void statusbar_print_all(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + GList *cur; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + for (cur = statusbar_list; cur != NULL; cur = cur->next) + statusbar_puts(GTK_STATUSBAR(cur->data), buf); +} + +void statusbar_pop_all(void) +{ + GList *cur; + gint cid; + + for (cur = statusbar_list; cur != NULL; cur = cur->next) { + cid = gtk_statusbar_get_context_id(GTK_STATUSBAR(cur->data), + "Standard Output"); + gtk_statusbar_pop(GTK_STATUSBAR(cur->data), cid); + } +} diff --git a/src/statusbar.h b/src/statusbar.h new file mode 100644 index 00000000..991b672b --- /dev/null +++ b/src/statusbar.h @@ -0,0 +1,38 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __STATUSBAR_H__ +#define __STATUSBAR_H__ + +#include +#include +#include + +GtkWidget *statusbar_create (void); +void statusbar_puts (GtkStatusbar *statusbar, + const gchar *str); +void statusbar_puts_all (const gchar *str); +void statusbar_print (GtkStatusbar *statusbar, + const gchar *format, ...) + G_GNUC_PRINTF(2, 3); +void statusbar_print_all (const gchar *format, ...) + G_GNUC_PRINTF(1, 2); +void statusbar_pop_all (void); + +#endif /* __STATUSBAR_H__ */ diff --git a/src/stock_pixmap.c b/src/stock_pixmap.c new file mode 100644 index 00000000..ca5c98b6 --- /dev/null +++ b/src/stock_pixmap.c @@ -0,0 +1,185 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include +#include + +#include "stock_pixmap.h" +#include "gtkutils.h" + +#include "pixmaps/address.xpm" +#include "pixmaps/book.xpm" +#include "pixmaps/category.xpm" +#include "pixmaps/checkbox_off.xpm" +#include "pixmaps/checkbox_on.xpm" +#include "pixmaps/clip.xpm" +#include "pixmaps/complete.xpm" +#include "pixmaps/continue.xpm" +#include "pixmaps/deleted.xpm" +#include "pixmaps/dir-close.xpm" +#include "pixmaps/dir-open.xpm" +#include "pixmaps/dir-noselect.xpm" +#include "pixmaps/error.xpm" +#include "pixmaps/forwarded.xpm" +#include "pixmaps/group.xpm" +#include "pixmaps/inbox.xpm" +#include "pixmaps/interface.xpm" +#include "pixmaps/jpilot.xpm" +#include "pixmaps/ldap.xpm" +#include "pixmaps/linewrap.xpm" +#include "pixmaps/mark.xpm" +#include "pixmaps/new.xpm" +#include "pixmaps/outbox.xpm" +#include "pixmaps/replied.xpm" +#include "pixmaps/stock_close.xpm" +#include "pixmaps/stock_down_arrow.xpm" +#include "pixmaps/stock_exec.xpm" +#include "pixmaps/stock_mail.xpm" +#include "pixmaps/stock_mail_attach.xpm" +#include "pixmaps/stock_mail_compose.xpm" +#include "pixmaps/stock_mail_forward.xpm" +#include "pixmaps/stock_mail_receive.xpm" +#include "pixmaps/stock_mail_receive_all.xpm" +#include "pixmaps/stock_mail_reply.xpm" +#include "pixmaps/stock_mail_reply_to_all.xpm" +#include "pixmaps/stock_mail_send.xpm" +#include "pixmaps/stock_mail_send_queue.xpm" +#include "pixmaps/stock_paste.xpm" +#include "pixmaps/stock_preferences.xpm" +#include "pixmaps/stock_properties.xpm" +#include "pixmaps/sylpheed-logo.xpm" +#include "pixmaps/tb_address_book.xpm" +#include "pixmaps/trash.xpm" +#include "pixmaps/unread.xpm" +#include "pixmaps/vcard.xpm" +#include "pixmaps/online.xpm" +#include "pixmaps/offline.xpm" +#include "pixmaps/stock_dialog_info_48.xpm" +#include "pixmaps/stock_dialog_question_48.xpm" +#include "pixmaps/stock_dialog_warning_48.xpm" +#include "pixmaps/stock_dialog_error_48.xpm" +#include "pixmaps/stock_add_16.xpm" +#include "pixmaps/stock_remove_16.xpm" +#include "pixmaps/mail.xpm" + +typedef struct _StockPixmapData StockPixmapData; + +struct _StockPixmapData +{ + gchar **data; + GdkPixmap *pixmap; + GdkBitmap *mask; +}; + +static StockPixmapData pixmaps[] = +{ + {address_xpm , NULL, NULL}, + {book_xpm , NULL, NULL}, + {category_xpm , NULL, NULL}, + {checkbox_off_xpm , NULL, NULL}, + {checkbox_on_xpm , NULL, NULL}, + {clip_xpm , NULL, NULL}, + {complete_xpm , NULL, NULL}, + {continue_xpm , NULL, NULL}, + {deleted_xpm , NULL, NULL}, + {dir_close_xpm , NULL, NULL}, + {dir_open_xpm , NULL, NULL}, + {dir_noselect_xpm , NULL, NULL}, + {error_xpm , NULL, NULL}, + {forwarded_xpm , NULL, NULL}, + {group_xpm , NULL, NULL}, + {inbox_xpm , NULL, NULL}, + {interface_xpm , NULL, NULL}, + {jpilot_xpm , NULL, NULL}, + {ldap_xpm , NULL, NULL}, + {linewrap_xpm , NULL, NULL}, + {mark_xpm , NULL, NULL}, + {new_xpm , NULL, NULL}, + {outbox_xpm , NULL, NULL}, + {replied_xpm , NULL, NULL}, + {stock_close_xpm , NULL, NULL}, + {stock_down_arrow_xpm , NULL, NULL}, + {stock_exec_xpm , NULL, NULL}, + {stock_mail_xpm , NULL, NULL}, + {stock_mail_attach_xpm , NULL, NULL}, + {stock_mail_compose_xpm , NULL, NULL}, + {stock_mail_forward_xpm , NULL, NULL}, + {stock_mail_receive_xpm , NULL, NULL}, + {stock_mail_receive_all_xpm , NULL, NULL}, + {stock_mail_reply_xpm , NULL, NULL}, + {stock_mail_reply_to_all_xpm , NULL, NULL}, + {stock_mail_send_xpm , NULL, NULL}, + {stock_mail_send_queue_xpm , NULL, NULL}, + {stock_paste_xpm , NULL, NULL}, + {stock_preferences_xpm , NULL, NULL}, + {stock_properties_xpm , NULL, NULL}, + {sylpheed_logo_xpm , NULL, NULL}, + {tb_address_book_xpm , NULL, NULL}, + {trash_xpm , NULL, NULL}, + {unread_xpm , NULL, NULL}, + {vcard_xpm , NULL, NULL}, + {online_xpm , NULL, NULL}, + {offline_xpm , NULL, NULL}, + {stock_dialog_info_48_xpm , NULL, NULL}, + {stock_dialog_question_48_xpm , NULL, NULL}, + {stock_dialog_warning_48_xpm , NULL, NULL}, + {stock_dialog_error_48_xpm , NULL, NULL}, + {stock_add_16_xpm , NULL, NULL}, + {stock_remove_16_xpm , NULL, NULL}, + {mail_xpm , NULL, NULL}, +}; + +/* return newly constructed GtkPixmap from GdkPixmap */ +GtkWidget *stock_pixmap_widget(GtkWidget *window, StockPixmap icon) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + + g_return_val_if_fail(window != NULL, NULL); + g_return_val_if_fail(icon >= 0 && icon < N_STOCK_PIXMAPS, NULL); + + stock_pixmap_gdk(window, icon, &pixmap, &mask); + return gtk_pixmap_new(pixmap, mask); +} + +/* create GdkPixmap if it has not created yet */ +gint stock_pixmap_gdk(GtkWidget *window, StockPixmap icon, + GdkPixmap **pixmap, GdkBitmap **mask) +{ + StockPixmapData *pix_d; + + if (pixmap) *pixmap = NULL; + if (mask) *mask = NULL; + + g_return_val_if_fail(window != NULL, -1); + g_return_val_if_fail(icon >= 0 && icon < N_STOCK_PIXMAPS, -1); + + pix_d = &pixmaps[icon]; + + if (!pix_d->pixmap) { + PIXMAP_CREATE(window, pix_d->pixmap, pix_d->mask, + pix_d->data); + } + + if (pixmap) *pixmap = pix_d->pixmap; + if (mask) *mask = pix_d->mask; + + return 0; +} diff --git a/src/stock_pixmap.h b/src/stock_pixmap.h new file mode 100644 index 00000000..a296906d --- /dev/null +++ b/src/stock_pixmap.h @@ -0,0 +1,93 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __STOCK_PIXMAP_H__ +#define __STOCK_PIXMAP_H__ + +#include +#include + +typedef enum +{ + STOCK_PIXMAP_ADDRESS, + STOCK_PIXMAP_BOOK, + STOCK_PIXMAP_CATEGORY, + STOCK_PIXMAP_CHECKBOX_OFF, + STOCK_PIXMAP_CHECKBOX_ON, + STOCK_PIXMAP_CLIP, + STOCK_PIXMAP_COMPLETE, + STOCK_PIXMAP_CONTINUE, + STOCK_PIXMAP_DELETED, + STOCK_PIXMAP_DIR_CLOSE, + STOCK_PIXMAP_DIR_OPEN, + STOCK_PIXMAP_DIR_NOSELECT, + STOCK_PIXMAP_ERROR, + STOCK_PIXMAP_FORWARDED, + STOCK_PIXMAP_GROUP, + STOCK_PIXMAP_INBOX, + STOCK_PIXMAP_INTERFACE, + STOCK_PIXMAP_JPILOT, + STOCK_PIXMAP_LDAP, + STOCK_PIXMAP_LINEWRAP, + STOCK_PIXMAP_MARK, + STOCK_PIXMAP_NEW, + STOCK_PIXMAP_OUTBOX, + STOCK_PIXMAP_REPLIED, + STOCK_PIXMAP_CLOSE, + STOCK_PIXMAP_DOWN_ARROW, + STOCK_PIXMAP_EXEC, + STOCK_PIXMAP_MAIL, + STOCK_PIXMAP_MAIL_ATTACH, + STOCK_PIXMAP_MAIL_COMPOSE, + STOCK_PIXMAP_MAIL_FORWARD, + STOCK_PIXMAP_MAIL_RECEIVE, + STOCK_PIXMAP_MAIL_RECEIVE_ALL, + STOCK_PIXMAP_MAIL_REPLY, + STOCK_PIXMAP_MAIL_REPLY_TO_ALL, + STOCK_PIXMAP_MAIL_SEND, + STOCK_PIXMAP_MAIL_SEND_QUEUE, + STOCK_PIXMAP_PASTE, + STOCK_PIXMAP_PREFERENCES, + STOCK_PIXMAP_PROPERTIES, + STOCK_PIXMAP_SYLPHEED_LOGO, + STOCK_PIXMAP_ADDRESS_BOOK, + STOCK_PIXMAP_TRASH, + STOCK_PIXMAP_UNREAD, + STOCK_PIXMAP_VCARD, + STOCK_PIXMAP_ONLINE, + STOCK_PIXMAP_OFFLINE, + STOCK_PIXMAP_DIALOG_INFO, + STOCK_PIXMAP_DIALOG_QUESTION, + STOCK_PIXMAP_DIALOG_WARNING, + STOCK_PIXMAP_DIALOG_ERROR, + STOCK_PIXMAP_ADD, + STOCK_PIXMAP_REMOVE, + STOCK_PIXMAP_MAIL_SMALL, + + N_STOCK_PIXMAPS +} StockPixmap; + +GtkWidget *stock_pixmap_widget (GtkWidget *window, + StockPixmap icon); +gint stock_pixmap_gdk (GtkWidget *window, + StockPixmap icon, + GdkPixmap **pixmap, + GdkBitmap **mask); + +#endif /* __STOCK_PIXMAP_H__ */ diff --git a/src/stringtable.c b/src/stringtable.c new file mode 100644 index 00000000..da5e0ea4 --- /dev/null +++ b/src/stringtable.c @@ -0,0 +1,163 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include + +#include "stringtable.h" +#include "utils.h" + +/* alfons - hashed string table (I wasn't content with GStringChunk; + * can't recall why :-) */ + +#if 0 +#define XXX_DEBUG \ + debug_print +#else +#define XXX_DEBUG \ + if (0) debug_print +#endif + +typedef struct StringEntry_ { + gint ref_count; + gchar *string; +} StringEntry; + +static StringEntry *string_entry_new(const gchar *str) +{ + StringEntry *entry; + + entry = g_new0(StringEntry, 1); + entry->ref_count = 1; + entry->string = g_strdup(str); + return entry; +} + +static void string_entry_free(StringEntry *entry) +{ + g_return_if_fail(entry != NULL); + + g_free(entry->string); + g_free(entry); +} + +StringTable *string_table_new(void) +{ + StringTable *strtable; + + strtable = g_new0(StringTable, 1); + g_return_val_if_fail(strtable != NULL, NULL); + strtable->hash_table = g_hash_table_new(g_str_hash, g_str_equal); + g_return_val_if_fail(strtable->hash_table, NULL); + return strtable; +} + +gchar *string_table_lookup_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + return entry->string; + } else { + return NULL; + } +} + +gchar *string_table_insert_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + entry->ref_count++; + XXX_DEBUG ("ref++ for %s (%d)\n", entry->string, + entry->ref_count); + } else { + entry = string_entry_new(str); + XXX_DEBUG ("inserting %s\n", str); + /* insert entry->string instead of str, since it can be + * invalid pointer after this. */ + g_hash_table_insert(table->hash_table, entry->string, entry); + } + + return entry->string; +} + +void string_table_free_string(StringTable *table, const gchar *str) +{ + StringEntry *entry; + + entry = g_hash_table_lookup(table->hash_table, str); + + if (entry) { + entry->ref_count--; + if (entry->ref_count <= 0) { + XXX_DEBUG ("refcount of string %s dropped to zero\n", + entry->string); + g_hash_table_remove(table->hash_table, str); + string_entry_free(entry); + } else { + XXX_DEBUG ("ref-- for %s (%d)\n", entry->string, + entry->ref_count); + } + } +} + +static gboolean string_table_remove_for_each_fn(gchar *key, StringEntry *entry, + gpointer user_data) +{ + g_return_val_if_fail(key != NULL, TRUE); + g_return_val_if_fail(entry != NULL, TRUE); + + string_entry_free(entry); + + return TRUE; +} + +void string_table_free(StringTable *table) +{ + g_return_if_fail(table != NULL); + g_return_if_fail(table->hash_table != NULL); + + g_hash_table_foreach_remove(table->hash_table, + (GHRFunc)string_table_remove_for_each_fn, + NULL); + g_hash_table_destroy(table->hash_table); + g_free(table); +} + +static void string_table_stats_for_each_fn(gchar *key, StringEntry *entry, + guint *totals) +{ + if (entry->ref_count > 1) { + *totals += strlen(key) * (entry->ref_count - 1); + } +} + +void string_table_get_stats(StringTable *table) +{ + guint totals = 0; + + g_hash_table_foreach(table->hash_table, + (GHFunc)string_table_stats_for_each_fn, &totals); + XXX_DEBUG ("TOTAL UNSPILLED %d (%dK)\n", totals, totals / 1024); +} diff --git a/src/stringtable.h b/src/stringtable.h new file mode 100644 index 00000000..337ef2c7 --- /dev/null +++ b/src/stringtable.h @@ -0,0 +1,38 @@ +/* + * sylpheed -- a gtk+ based, lightweight, and fast e-mail client + * copyright (c) 1999-2001 hiroyuki yamamoto + * + * this program is free software; you can redistribute it and/or modify + * it under the terms of the gnu general public license as published by + * the free software foundation; either version 2 of the license, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef STRINGTABLE_H__ +#define STRINGTABLE_H__ + +#include + +typedef struct { + GHashTable *hash_table; +} StringTable; + +StringTable *string_table_new (void); +void string_table_free (StringTable *table); + +gchar *string_table_lookup_string (StringTable *table, const gchar *str); +gchar *string_table_insert_string (StringTable *table, const gchar *str); +void string_table_free_string (StringTable *table, const gchar *str); + +void string_table_get_stats (StringTable *table); + +#endif /* STRINGTABLE_H__ */ diff --git a/src/summary_search.c b/src/summary_search.c new file mode 100644 index 00000000..9ee39dde --- /dev/null +++ b/src/summary_search.c @@ -0,0 +1,474 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "summary_search.h" +#include "summaryview.h" +#include "messageview.h" +#include "mainwindow.h" +#include "menu.h" +#include "utils.h" +#include "gtkutils.h" +#include "manage_window.h" +#include "alertpanel.h" + +static GtkWidget *window; +static GtkWidget *bool_optmenu; +static GtkWidget *from_entry; +static GtkWidget *to_entry; +static GtkWidget *subject_entry; +static GtkWidget *body_entry; +static GtkWidget *case_checkbtn; +static GtkWidget *backward_checkbtn; +static GtkWidget *all_checkbtn; +static GtkWidget *search_btn; +static GtkWidget *clear_btn; +static GtkWidget *close_btn; + +static void summary_search_create(SummaryView *summaryview); +static void summary_search_execute(GtkButton *button, gpointer data); +static void summary_search_clear(GtkButton *button, gpointer data); +static void from_activated(void); +static void to_activated(void); +static void subject_activated(void); +static void body_activated(void); +static void all_clicked(GtkButton *button); +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data); + +void summary_search(SummaryView *summaryview) +{ + if (!window) + summary_search_create(summaryview); + else + gtk_widget_hide(window); + + gtk_widget_grab_focus(search_btn); + gtk_widget_grab_focus(subject_entry); + gtk_widget_show(window); +} + +static void summary_search_create(SummaryView *summaryview) +{ + GtkWidget *vbox1; + GtkWidget *bool_hbox; + GtkWidget *bool_menu; + GtkWidget *menuitem; + GtkWidget *table1; + GtkWidget *from_label; + GtkWidget *to_label; + GtkWidget *subject_label; + GtkWidget *body_label; + GtkWidget *checkbtn_hbox; + GtkWidget *confirm_area; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW (window), _("Search messages")); + gtk_widget_set_size_request(window, 450, -1); + gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE); + gtk_container_set_border_width(GTK_CONTAINER (window), 8); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(key_pressed), NULL); + MANAGE_WINDOW_SIGNALS_CONNECT(window); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (window), vbox1); + + bool_hbox = gtk_hbox_new(FALSE, 4); + gtk_widget_show(bool_hbox); + gtk_box_pack_start(GTK_BOX(vbox1), bool_hbox, FALSE, FALSE, 0); + + bool_optmenu = gtk_option_menu_new(); + gtk_widget_show(bool_optmenu); + gtk_box_pack_start(GTK_BOX(bool_hbox), bool_optmenu, FALSE, FALSE, 0); + + bool_menu = gtk_menu_new(); + MENUITEM_ADD(bool_menu, menuitem, _("Match any of the following"), 0); + MENUITEM_ADD(bool_menu, menuitem, _("Match all of the following"), 1); + gtk_option_menu_set_menu(GTK_OPTION_MENU(bool_optmenu), bool_menu); + + table1 = gtk_table_new (4, 3, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vbox1), table1, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 4); + gtk_table_set_row_spacings (GTK_TABLE (table1), 8); + gtk_table_set_col_spacings (GTK_TABLE (table1), 8); + + from_entry = gtk_entry_new (); + gtk_widget_show (from_entry); + gtk_table_attach (GTK_TABLE (table1), from_entry, 1, 3, 0, 1, + GTK_EXPAND|GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(from_entry), "activate", + G_CALLBACK(from_activated), summaryview); + + to_entry = gtk_entry_new (); + gtk_widget_show (to_entry); + gtk_table_attach (GTK_TABLE (table1), to_entry, 1, 3, 1, 2, + GTK_EXPAND|GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(to_entry), "activate", + G_CALLBACK(to_activated), summaryview); + + subject_entry = gtk_entry_new (); + gtk_widget_show (subject_entry); + gtk_table_attach (GTK_TABLE (table1), subject_entry, 1, 3, 2, 3, + GTK_EXPAND|GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(subject_entry), "activate", + G_CALLBACK(subject_activated), summaryview); + + body_entry = gtk_entry_new (); + gtk_widget_show (body_entry); + gtk_table_attach (GTK_TABLE (table1), body_entry, 1, 3, 3, 4, + GTK_EXPAND|GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(body_entry), "activate", + G_CALLBACK(body_activated), summaryview); + + from_label = gtk_label_new (_("From:")); + gtk_widget_show (from_label); + gtk_table_attach (GTK_TABLE (table1), from_label, 0, 1, 0, 1, + GTK_FILL, 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (from_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (from_label), 1, 0.5); + + to_label = gtk_label_new (_("To:")); + gtk_widget_show (to_label); + gtk_table_attach (GTK_TABLE (table1), to_label, 0, 1, 1, 2, + GTK_FILL, 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (to_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (to_label), 1, 0.5); + + subject_label = gtk_label_new (_("Subject:")); + gtk_widget_show (subject_label); + gtk_table_attach (GTK_TABLE (table1), subject_label, 0, 1, 2, 3, + GTK_FILL, 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (subject_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (subject_label), 1, 0.5); + + body_label = gtk_label_new (_("Body:")); + gtk_widget_show (body_label); + gtk_table_attach (GTK_TABLE (table1), body_label, 0, 1, 3, 4, + GTK_FILL, 0, 0, 0); + gtk_label_set_justify (GTK_LABEL (body_label), GTK_JUSTIFY_RIGHT); + gtk_misc_set_alignment (GTK_MISC (body_label), 1, 0.5); + + checkbtn_hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (checkbtn_hbox); + gtk_box_pack_start (GTK_BOX (vbox1), checkbtn_hbox, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (checkbtn_hbox), 8); + + case_checkbtn = gtk_check_button_new_with_label (_("Case sensitive")); + gtk_widget_show (case_checkbtn); + gtk_box_pack_start (GTK_BOX (checkbtn_hbox), case_checkbtn, + FALSE, FALSE, 0); + + backward_checkbtn = + gtk_check_button_new_with_label (_("Backward search")); + gtk_widget_show (backward_checkbtn); + gtk_box_pack_start (GTK_BOX (checkbtn_hbox), backward_checkbtn, + FALSE, FALSE, 0); + + all_checkbtn = + gtk_check_button_new_with_label (_("Select all matched")); + gtk_widget_show (all_checkbtn); + gtk_box_pack_start (GTK_BOX (checkbtn_hbox), all_checkbtn, + FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(all_checkbtn), "clicked", + G_CALLBACK(all_clicked), summaryview); + + gtkut_button_set_create(&confirm_area, + &search_btn, _("Search"), + &clear_btn, _("Clear"), + &close_btn, _("Close")); + gtk_widget_show (confirm_area); + gtk_box_pack_start (GTK_BOX (vbox1), confirm_area, FALSE, FALSE, 0); + gtk_widget_grab_default(search_btn); + + g_signal_connect(G_OBJECT(search_btn), "clicked", + G_CALLBACK(summary_search_execute), summaryview); + g_signal_connect(G_OBJECT(clear_btn), "clicked", + G_CALLBACK(summary_search_clear), summaryview); + g_signal_connect_closure + (G_OBJECT(close_btn), "clicked", + g_cclosure_new_swap(G_CALLBACK(gtk_widget_hide), + window, NULL), + FALSE); +} + +static void summary_search_execute(GtkButton *button, gpointer data) +{ + SummaryView *summaryview = data; + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + gboolean bool_and; + gboolean case_sens; + gboolean backward; + gboolean search_all; + gboolean all_searched = FALSE; + gboolean matched; + gboolean body_matched; + const gchar *from_str, *to_str, *subject_str, *body_str; + StrFindFunc str_find_func; + + if (summary_is_locked(summaryview)) return; + summary_lock(summaryview); + + bool_and = menu_get_option_menu_active_index + (GTK_OPTION_MENU(bool_optmenu)); + case_sens = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(case_checkbtn)); + backward = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(backward_checkbtn)); + search_all = gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(all_checkbtn)); + + if (case_sens) + str_find_func = str_find; + else + str_find_func = str_case_find; + + from_str = gtk_entry_get_text(GTK_ENTRY(from_entry)); + to_str = gtk_entry_get_text(GTK_ENTRY(to_entry)); + subject_str = gtk_entry_get_text(GTK_ENTRY(subject_entry)); + body_str = gtk_entry_get_text(GTK_ENTRY(body_entry)); + + if (search_all) { + gtk_clist_freeze(GTK_CLIST(ctree)); + gtk_clist_unselect_all(GTK_CLIST(ctree)); + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + backward = FALSE; + } else if (!summaryview->selected) { + if (backward) + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list_end); + else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + if (!node) { + summary_unlock(summaryview); + return; + } + } else { + if (backward) + node = gtkut_ctree_node_prev + (ctree, summaryview->selected); + else + node = gtkut_ctree_node_next + (ctree, summaryview->selected); + } + + if (*body_str) + main_window_cursor_wait(summaryview->mainwin); + + for (;;) { + if (!node) { + gchar *str; + AlertValue val; + + if (search_all) { + gtk_clist_thaw(GTK_CLIST(ctree)); + break; + } + + if (all_searched) { + alertpanel_message + (_("Search failed"), + _("Search string not found."), + ALERT_WARNING); + break; + } + + if (backward) + str = _("Beginning of list reached; continue from end?"); + else + str = _("End of list reached; continue from beginning?"); + + val = alertpanel(_("Search finished"), str, + _("Yes"), _("No"), NULL); + if (G_ALERTDEFAULT == val) { + if (backward) + node = GTK_CTREE_NODE + (GTK_CLIST(ctree)->row_list_end); + else + node = GTK_CTREE_NODE + (GTK_CLIST(ctree)->row_list); + + all_searched = TRUE; + + manage_window_focus_in(window, NULL, NULL); + } else + break; + } + + + msginfo = gtk_ctree_node_get_row_data(ctree, node); + body_matched = FALSE; + + if (bool_and) { + matched = TRUE; + if (*from_str) { + if (!msginfo->from || + !str_find_func(msginfo->from, from_str)) + matched = FALSE; + } + if (matched && *to_str) { + if (!msginfo->to || + !str_find_func(msginfo->to, to_str)) + matched = FALSE; + } + if (matched && *subject_str) { + if (!msginfo->subject || + !str_find_func(msginfo->subject, subject_str)) + matched = FALSE; + } + if (matched && *body_str) { + if (procmime_find_string(msginfo, body_str, + str_find_func)) + body_matched = TRUE; + else + matched = FALSE; + } + if (matched && !*from_str && !*to_str && + !*subject_str && !*body_str) + matched = FALSE; + } else { + matched = FALSE; + if (*from_str && msginfo->from) { + if (str_find_func(msginfo->from, from_str)) + matched = TRUE; + } + if (!matched && *to_str && msginfo->to) { + if (str_find_func(msginfo->to, to_str)) + matched = TRUE; + } + if (!matched && *subject_str && msginfo->subject) { + if (str_find_func(msginfo->subject, subject_str)) + matched = TRUE; + } + if (!matched && *body_str) { + if (procmime_find_string(msginfo, body_str, + str_find_func)) { + matched = TRUE; + body_matched = TRUE; + } + } + } + + if (matched) { + if (search_all) + gtk_ctree_select(ctree, node); + else { + if (messageview_is_visible + (summaryview->messageview)) { + summary_unlock(summaryview); + summary_select_node + (summaryview, node, TRUE, TRUE); + summary_lock(summaryview); + if (body_matched) { + messageview_search_string + (summaryview->messageview, + body_str, case_sens); + } + } else { + summary_select_node + (summaryview, node, FALSE, TRUE); + } + break; + } + } + + node = backward ? gtkut_ctree_node_prev(ctree, node) + : gtkut_ctree_node_next(ctree, node); + } + + if (*body_str) + main_window_cursor_normal(summaryview->mainwin); + + summary_unlock(summaryview); +} + +static void summary_search_clear(GtkButton *button, gpointer data) +{ + gtk_editable_delete_text(GTK_EDITABLE(from_entry), 0, -1); + gtk_editable_delete_text(GTK_EDITABLE(to_entry), 0, -1); + gtk_editable_delete_text(GTK_EDITABLE(subject_entry), 0, -1); + gtk_editable_delete_text(GTK_EDITABLE(body_entry), 0, -1); +} + +static void from_activated(void) +{ + gtk_widget_grab_focus(to_entry); +} + +static void to_activated(void) +{ + gtk_widget_grab_focus(subject_entry); +} + +static void subject_activated(void) +{ + gtk_button_clicked(GTK_BUTTON(search_btn)); +} + +static void body_activated(void) +{ + gtk_button_clicked(GTK_BUTTON(search_btn)); +} + +static void all_clicked(GtkButton *button) +{ + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) + gtk_widget_set_sensitive(backward_checkbtn, FALSE); + else + gtk_widget_set_sensitive(backward_checkbtn, TRUE); +} + +static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event && event->keyval == GDK_Escape) + gtk_widget_hide(window); + return FALSE; +} diff --git a/src/summary_search.h b/src/summary_search.h new file mode 100644 index 00000000..8e3fcc0c --- /dev/null +++ b/src/summary_search.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SUMMARY_SEARCH_H__ +#define __SUMMARY_SEARCH_H__ + +#include + +#include "summaryview.h" + +void summary_search (SummaryView *summaryview); + +#endif /* __SUMMARY_SEARCH_H__ */ diff --git a/src/summaryview.c b/src/summaryview.c new file mode 100644 index 00000000..53d9b349 --- /dev/null +++ b/src/summaryview.c @@ -0,0 +1,4195 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "menu.h" +#include "mainwindow.h" +#include "folderview.h" +#include "summaryview.h" +#include "messageview.h" +#include "foldersel.h" +#include "procmsg.h" +#include "procheader.h" +#include "sourcewindow.h" +#include "prefs_common.h" +#include "prefs_summary_column.h" +#include "prefs_filter.h" +#include "account.h" +#include "compose.h" +#include "utils.h" +#include "gtkutils.h" +#include "stock_pixmap.h" +#include "filesel.h" +#include "alertpanel.h" +#include "inputdialog.h" +#include "statusbar.h" +#include "filter.h" +#include "folder.h" +#include "colorlabel.h" +#include "inc.h" +#include "imap.h" + +#define STATUSBAR_PUSH(mainwin, str) \ +{ \ + gtk_statusbar_push(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->summaryview_cid, str); \ + gtkut_widget_wait_for_draw(mainwin->hbox_stat); \ +} + +#define STATUSBAR_POP(mainwin) \ +{ \ + gtk_statusbar_pop(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->summaryview_cid); \ +} + +#define SUMMARY_COL_MARK_WIDTH 10 +#define SUMMARY_COL_UNREAD_WIDTH 13 +#define SUMMARY_COL_MIME_WIDTH 10 + +static GtkStyle *bold_style; +static GtkStyle *bold_marked_style; +static GtkStyle *bold_deleted_style; + +static GdkPixmap *markxpm; +static GdkBitmap *markxpmmask; +static GdkPixmap *deletedxpm; +static GdkBitmap *deletedxpmmask; + +static GdkPixmap *mailxpm; +static GdkBitmap *mailxpmmask; +static GdkPixmap *newxpm; +static GdkBitmap *newxpmmask; +static GdkPixmap *unreadxpm; +static GdkBitmap *unreadxpmmask; +static GdkPixmap *repliedxpm; +static GdkBitmap *repliedxpmmask; +static GdkPixmap *forwardedxpm; +static GdkBitmap *forwardedxpmmask; + +static GdkPixmap *clipxpm; +static GdkBitmap *clipxpmmask; + +static void summary_free_msginfo_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); +static void summary_set_marks_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); +static void summary_write_cache_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); + +static void summary_set_menu_sensitive (SummaryView *summaryview); + +static guint summary_get_msgnum (SummaryView *summaryview, + GtkCTreeNode *node); + +static GtkCTreeNode *summary_find_prev_msg + (SummaryView *summaryview, + GtkCTreeNode *current_node); +static GtkCTreeNode *summary_find_next_msg + (SummaryView *summaryview, + GtkCTreeNode *current_node); + +static GtkCTreeNode *summary_find_prev_flagged_msg + (SummaryView *summaryview, + GtkCTreeNode *current_node, + MsgPermFlags flags, + gboolean start_from_prev); +static GtkCTreeNode *summary_find_next_flagged_msg + (SummaryView *summaryview, + GtkCTreeNode *current_node, + MsgPermFlags flags, + gboolean start_from_next); + +static GtkCTreeNode *summary_find_msg_by_msgnum + (SummaryView *summaryview, + guint msgnum); + +static void summary_update_status (SummaryView *summaryview); + +/* display functions */ +static void summary_status_show (SummaryView *summaryview); +static void summary_set_column_titles (SummaryView *summaryview); +static void summary_set_ctree_from_list (SummaryView *summaryview, + GSList *mlist); +static void summary_set_header (SummaryView *summaryview, + gchar *text[], + MsgInfo *msginfo); +static void summary_display_msg (SummaryView *summaryview, + GtkCTreeNode *row); +static void summary_display_msg_full (SummaryView *summaryview, + GtkCTreeNode *row, + gboolean new_window, + gboolean all_headers); +static void summary_set_row_marks (SummaryView *summaryview, + GtkCTreeNode *row); + +/* message handling */ +static void summary_mark_row (SummaryView *summaryview, + GtkCTreeNode *row); +static void summary_mark_row_as_read (SummaryView *summaryview, + GtkCTreeNode *row); +static void summary_mark_row_as_unread (SummaryView *summaryview, + GtkCTreeNode *row); +static void summary_delete_row (SummaryView *summaryview, + GtkCTreeNode *row); +static void summary_unmark_row (SummaryView *summaryview, + GtkCTreeNode *row); +static void summary_move_row_to (SummaryView *summaryview, + GtkCTreeNode *row, + FolderItem *to_folder); +static void summary_copy_row_to (SummaryView *summaryview, + GtkCTreeNode *row, + FolderItem *to_folder); + +static void summary_delete_duplicated_func + (GtkCTree *ctree, + GtkCTreeNode *node, + SummaryView *summaryview); + +static void summary_remove_invalid_messages + (SummaryView *summaryview); + +static gint summary_execute_move (SummaryView *summaryview); +static void summary_execute_move_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); +static gint summary_execute_copy (SummaryView *summaryview); +static void summary_execute_copy_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); +static gint summary_execute_delete (SummaryView *summaryview); +static void summary_execute_delete_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); + +static void summary_thread_init (SummaryView *summaryview); + +static void summary_unthread_for_exec (SummaryView *summaryview); +static void summary_unthread_for_exec_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); + +static void summary_filter_func (GtkCTree *ctree, + GtkCTreeNode *node, + gpointer data); + +static void summary_colorlabel_menu_item_activate_cb + (GtkWidget *widget, + gpointer data); +static void summary_colorlabel_menu_item_activate_item_cb + (GtkMenuItem *label_menu_item, + gpointer data); +static void summary_colorlabel_menu_create(SummaryView *summaryview); + +static GtkWidget *summary_ctree_create (SummaryView *summaryview); + +/* callback functions */ +static gboolean summary_toggle_pressed (GtkWidget *eventbox, + GdkEventButton *event, + SummaryView *summaryview); +static gboolean summary_button_pressed (GtkWidget *ctree, + GdkEventButton *event, + SummaryView *summaryview); +static gboolean summary_button_released (GtkWidget *ctree, + GdkEventButton *event, + SummaryView *summaryview); +static gboolean summary_key_pressed (GtkWidget *ctree, + GdkEventKey *event, + SummaryView *summaryview); +static void summary_open_row (GtkSCTree *sctree, + SummaryView *summaryview); +static void summary_tree_expanded (GtkCTree *ctree, + GtkCTreeNode *node, + SummaryView *summaryview); +static void summary_tree_collapsed (GtkCTree *ctree, + GtkCTreeNode *node, + SummaryView *summaryview); +static void summary_selected (GtkCTree *ctree, + GtkCTreeNode *row, + gint column, + SummaryView *summaryview); +static void summary_col_resized (GtkCList *clist, + gint column, + gint width, + SummaryView *summaryview); +static void summary_reply_cb (SummaryView *summaryview, + guint action, + GtkWidget *widget); +static void summary_show_all_header_cb (SummaryView *summaryview, + guint action, + GtkWidget *widget); + +static void summary_add_address_cb (SummaryView *summaryview, + guint action, + GtkWidget *widget); + +static void summary_mark_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_unread_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_mime_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_num_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_size_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_date_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_from_clicked (GtkWidget *button, + SummaryView *summaryview); +static void summary_subject_clicked (GtkWidget *button, + SummaryView *summaryview); + +static void summary_start_drag (GtkWidget *widget, + int button, + GdkEvent *event, + SummaryView *summaryview); +static void summary_drag_data_get (GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *selection_data, + guint info, + guint time, + SummaryView *summaryview); + +/* custom compare functions for sorting */ + +static gint summary_cmp_by_mark (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_unread (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_mime (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_num (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_size (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_date (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_from (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_label (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_to (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); +static gint summary_cmp_by_subject (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); + +GtkTargetEntry summary_drag_types[1] = +{ + {"text/plain", GTK_TARGET_SAME_APP, TARGET_DUMMY} +}; + +static GtkItemFactoryEntry summary_popup_entries[] = +{ + {N_("/_Reply"), NULL, summary_reply_cb, COMPOSE_REPLY, NULL}, + {N_("/Repl_y to"), NULL, NULL, 0, ""}, + {N_("/Repl_y to/_all"), NULL, summary_reply_cb, COMPOSE_REPLY_TO_ALL, NULL}, + {N_("/Repl_y to/_sender"), NULL, summary_reply_cb, COMPOSE_REPLY_TO_SENDER, NULL}, + {N_("/Repl_y to/mailing _list"), + NULL, summary_reply_cb, COMPOSE_REPLY_TO_LIST, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Forward"), NULL, summary_reply_cb, COMPOSE_FORWARD, NULL}, + {N_("/For_ward as attachment"), NULL, summary_reply_cb, COMPOSE_FORWARD_AS_ATTACH, NULL}, + {N_("/Redirec_t"), NULL, summary_reply_cb, COMPOSE_REDIRECT, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/M_ove..."), NULL, summary_move_to, 0, NULL}, + {N_("/_Copy..."), NULL, summary_copy_to, 0, NULL}, + {N_("/_Delete"), NULL, summary_delete, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Mark"), NULL, NULL, 0, ""}, + {N_("/_Mark/_Mark"), NULL, summary_mark, 0, NULL}, + {N_("/_Mark/_Unmark"), NULL, summary_unmark, 0, NULL}, + {N_("/_Mark/---"), NULL, NULL, 0, ""}, + {N_("/_Mark/Mark as unr_ead"), NULL, summary_mark_as_unread, 0, NULL}, + {N_("/_Mark/Mark as rea_d"), + NULL, summary_mark_as_read, 0, NULL}, + {N_("/_Mark/Mark all _read"), NULL, summary_mark_all_read, 0, NULL}, + {N_("/Color la_bel"), NULL, NULL, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/Re-_edit"), NULL, summary_reedit, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/Add sender to address boo_k"), + NULL, summary_add_address_cb, 0, NULL}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_View"), NULL, NULL, 0, ""}, + {N_("/_View/Open in new _window"), + NULL, summary_open_msg, 0, NULL}, + {N_("/_View/_Source"), NULL, summary_view_source, 0, NULL}, + {N_("/_View/All _header"), NULL, summary_show_all_header_cb, 0, ""}, + {N_("/---"), NULL, NULL, 0, ""}, + {N_("/_Print..."), NULL, summary_print, 0, NULL} +}; + +static const gchar *const col_label[N_SUMMARY_COLS] = { + N_("M"), /* S_COL_MARK */ + N_("U"), /* S_COL_UNREAD */ + "", /* S_COL_MIME */ + N_("Subject"), /* S_COL_SUBJECT */ + N_("From"), /* S_COL_FROM */ + N_("Date"), /* S_COL_DATE */ + N_("Size"), /* S_COL_SIZE */ + N_("No."), /* S_COL_NUMBER */ +}; + +SummaryView *summary_create(void) +{ + SummaryView *summaryview; + GtkWidget *vbox; + GtkWidget *scrolledwin; + GtkWidget *ctree; + GtkWidget *hbox; + GtkWidget *hbox_l; + GtkWidget *statlabel_folder; + GtkWidget *statlabel_select; + GtkWidget *statlabel_msgs; + GtkWidget *hbox_spc; + GtkWidget *toggle_eventbox; + GtkWidget *toggle_arrow; + GtkWidget *popupmenu; + GtkItemFactory *popupfactory; + gint n_entries; + GList *child; + + debug_print(_("Creating summary view...\n")); + summaryview = g_new0(SummaryView, 1); + + vbox = gtk_vbox_new(FALSE, 2); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); + gtk_widget_set_size_request(vbox, + prefs_common.summaryview_width, + prefs_common.summaryview_height); + + ctree = summary_ctree_create(summaryview); + + gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_CLIST(ctree)->hadjustment); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_CLIST(ctree)->vadjustment); + gtk_container_add(GTK_CONTAINER(scrolledwin), ctree); + + /* create status label */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + hbox_l = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), hbox_l, TRUE, TRUE, 0); + + statlabel_folder = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox_l), statlabel_folder, FALSE, FALSE, 2); + statlabel_select = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox_l), statlabel_select, FALSE, FALSE, 12); + + /* toggle view button */ + toggle_eventbox = gtk_event_box_new(); + gtk_box_pack_end(GTK_BOX(hbox), toggle_eventbox, FALSE, FALSE, 4); + toggle_arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(toggle_eventbox), toggle_arrow); + g_signal_connect(G_OBJECT(toggle_eventbox), "button_press_event", + G_CALLBACK(summary_toggle_pressed), summaryview); + + statlabel_msgs = gtk_label_new(""); + gtk_box_pack_end(GTK_BOX(hbox), statlabel_msgs, FALSE, FALSE, 4); + + hbox_spc = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), hbox_spc, FALSE, FALSE, 6); + + /* create popup menu */ + n_entries = sizeof(summary_popup_entries) / + sizeof(summary_popup_entries[0]); + popupmenu = menu_create_items(summary_popup_entries, n_entries, + "", &popupfactory, + summaryview); + + summaryview->vbox = vbox; + summaryview->scrolledwin = scrolledwin; + summaryview->ctree = ctree; + summaryview->hbox = hbox; + summaryview->hbox_l = hbox_l; + summaryview->statlabel_folder = statlabel_folder; + summaryview->statlabel_select = statlabel_select; + summaryview->statlabel_msgs = statlabel_msgs; + summaryview->toggle_eventbox = toggle_eventbox; + summaryview->toggle_arrow = toggle_arrow; + summaryview->popupmenu = popupmenu; + summaryview->popupfactory = popupfactory; + summaryview->lock_count = 0; + + summaryview->reedit_menuitem = + gtk_item_factory_get_widget(popupfactory, "/Re-edit"); + child = g_list_find(GTK_MENU_SHELL(popupmenu)->children, + summaryview->reedit_menuitem); + summaryview->reedit_separator = GTK_WIDGET(child->next->data); + + gtk_widget_show_all(vbox); + + return summaryview; +} + +void summary_init(SummaryView *summaryview) +{ + GtkStyle *style; + GtkWidget *pixmap; + + gtk_widget_realize(summaryview->ctree); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_MARK, + &markxpm, &markxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_DELETED, + &deletedxpm, &deletedxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_MAIL_SMALL, + &mailxpm, &mailxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_NEW, + &newxpm, &newxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_UNREAD, + &unreadxpm, &unreadxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_REPLIED, + &repliedxpm, &repliedxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_FORWARDED, + &forwardedxpm, &forwardedxpmmask); + stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_CLIP, + &clipxpm, &clipxpmmask); + + if (!bold_style) { + PangoFontDescription *font_desc = NULL; + + bold_style = gtk_style_copy + (gtk_widget_get_style(summaryview->ctree)); + if (prefs_common.boldfont) + font_desc = pango_font_description_from_string + (prefs_common.boldfont); + if (font_desc) { + if (bold_style->font_desc) + pango_font_description_free + (bold_style->font_desc); + bold_style->font_desc = font_desc; + } + bold_marked_style = gtk_style_copy(bold_style); + bold_marked_style->fg[GTK_STATE_NORMAL] = + summaryview->color_marked; + bold_deleted_style = gtk_style_copy(bold_style); + bold_deleted_style->fg[GTK_STATE_NORMAL] = + summaryview->color_dim; + } + + style = gtk_style_copy(gtk_widget_get_style + (summaryview->statlabel_folder)); + if (prefs_common.smallfont) { + PangoFontDescription *font_desc = NULL; + + if (prefs_common.smallfont) + font_desc = pango_font_description_from_string + (prefs_common.smallfont); + if (font_desc) { + if (style->font_desc) + pango_font_description_free(style->font_desc); + style->font_desc = font_desc; + } + } + gtk_widget_set_style(summaryview->statlabel_folder, style); + gtk_widget_set_style(summaryview->statlabel_select, style); + gtk_widget_set_style(summaryview->statlabel_msgs, style); + + pixmap = stock_pixmap_widget(summaryview->hbox_l, STOCK_PIXMAP_DIR_OPEN); + gtk_box_pack_start(GTK_BOX(summaryview->hbox_l), pixmap, FALSE, FALSE, 4); + gtk_box_reorder_child(GTK_BOX(summaryview->hbox_l), pixmap, 0); + gtk_widget_show(pixmap); + + summary_clear_list(summaryview); + summary_set_column_titles(summaryview); + summary_colorlabel_menu_create(summaryview); + summary_set_menu_sensitive(summaryview); +} + +gboolean summary_show(SummaryView *summaryview, FolderItem *item, + gboolean update_cache) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + GSList *mlist = NULL; + gchar *buf; + gboolean is_refresh; + guint selected_msgnum = 0; + guint displayed_msgnum = 0; + GtkCTreeNode *selected_node = summaryview->folderview->selected; + + if (summary_is_locked(summaryview)) return FALSE; + + inc_lock(); + summary_lock(summaryview); + + STATUSBAR_POP(summaryview->mainwin); + + is_refresh = (item == summaryview->folder_item && + update_cache == FALSE) ? TRUE : FALSE; + if (is_refresh) { + selected_msgnum = summary_get_msgnum(summaryview, + summaryview->selected); + displayed_msgnum = summary_get_msgnum(summaryview, + summaryview->displayed); + } + + /* process the marks if any */ + if (summaryview->mainwin->lock_count == 0 && + (summaryview->moved > 0 || summaryview->copied > 0)) { + AlertValue val; + + val = alertpanel(_("Process mark"), + _("Some marks are left. Process it?"), + _("Yes"), _("No"), _("Cancel")); + if (G_ALERTDEFAULT == val) { + summary_unlock(summaryview); + summary_execute(summaryview); + summary_lock(summaryview); + } else if (G_ALERTALTERNATE == val) + summary_write_cache(summaryview); + else { + summary_unlock(summaryview); + inc_unlock(); + return FALSE; + } + } else + summary_write_cache(summaryview); + + summaryview->folderview->opened = selected_node; + + gtk_clist_freeze(GTK_CLIST(ctree)); + + summary_clear_list(summaryview); + summary_set_column_titles(summaryview); + + buf = NULL; + if (!item || !item->path || !item->parent || item->no_select || + (FOLDER_TYPE(item->folder) == F_MH && + ((buf = folder_item_get_path(item)) == NULL || + change_dir(buf) < 0))) { + g_free(buf); + debug_print("empty folder\n\n"); + summary_clear_all(summaryview); + summaryview->folder_item = item; + gtk_clist_thaw(GTK_CLIST(ctree)); + summary_unlock(summaryview); + inc_unlock(); + return TRUE; + } + g_free(buf); + + if (!is_refresh) + messageview_clear(summaryview->messageview); + + summaryview->folder_item = item; + + g_signal_handlers_block_matched(G_OBJECT(ctree), + (GSignalMatchType)G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, summaryview); + + buf = g_strdup_printf(_("Scanning folder (%s)..."), item->path); + debug_print("%s\n", buf); + STATUSBAR_PUSH(summaryview->mainwin, buf); + g_free(buf); + + main_window_cursor_wait(summaryview->mainwin); + + mlist = folder_item_get_msg_list(item, !update_cache); + + statusbar_pop_all(); + STATUSBAR_POP(summaryview->mainwin); + + /* set ctree and hash table from the msginfo list, and + create the thread */ + summary_set_ctree_from_list(summaryview, mlist); + + g_slist_free(mlist); + + summary_write_cache(summaryview); + + item->opened = TRUE; + + g_signal_handlers_unblock_matched(G_OBJECT(ctree), + (GSignalMatchType)G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, summaryview); + + gtk_clist_thaw(GTK_CLIST(ctree)); + + if (is_refresh) { + summaryview->displayed = + summary_find_msg_by_msgnum(summaryview, + displayed_msgnum); + if (!summaryview->displayed) + messageview_clear(summaryview->messageview); + summary_select_by_msgnum(summaryview, selected_msgnum); + if (!summaryview->selected) { + /* no selected message - select first unread + message, but do not display it */ + node = summary_find_next_flagged_msg(summaryview, NULL, + MSG_UNREAD, FALSE); + if (node == NULL && GTK_CLIST(ctree)->row_list != NULL) + node = gtk_ctree_node_nth + (ctree, + item->sort_type == SORT_DESCENDING + ? 0 : GTK_CLIST(ctree)->rows - 1); + summary_select_node(summaryview, node, FALSE, TRUE); + } + } else { + /* select first unread message */ + node = summary_find_next_flagged_msg(summaryview, NULL, + MSG_UNREAD, FALSE); + if (node == NULL && GTK_CLIST(ctree)->row_list != NULL) { + node = gtk_ctree_node_nth + (ctree, + item->sort_type == SORT_DESCENDING + ? 0 : GTK_CLIST(ctree)->rows - 1); + } + if (prefs_common.open_unread_on_enter || + prefs_common.always_show_msg) { + summary_unlock(summaryview); + summary_select_node(summaryview, node, TRUE, TRUE); + summary_lock(summaryview); + } else + summary_select_node(summaryview, node, FALSE, TRUE); + } + + summary_set_column_titles(summaryview); + summary_status_show(summaryview); + summary_set_menu_sensitive(summaryview); + main_window_set_toolbar_sensitive(summaryview->mainwin); + + debug_print("\n"); + STATUSBAR_PUSH(summaryview->mainwin, _("Done.")); + + main_window_cursor_normal(summaryview->mainwin); + summary_unlock(summaryview); + inc_unlock(); + + return TRUE; +} + +void summary_clear_list(SummaryView *summaryview) +{ + GtkCList *clist = GTK_CLIST(summaryview->ctree); + gint optimal_width; + + gtk_clist_freeze(clist); + + gtk_ctree_pre_recursive(GTK_CTREE(summaryview->ctree), + NULL, summary_free_msginfo_func, NULL); + + if (summaryview->folder_item) { + folder_item_close(summaryview->folder_item); + summaryview->folder_item = NULL; + } + + summaryview->display_msg = FALSE; + + summaryview->selected = NULL; + summaryview->displayed = NULL; + summaryview->total_size = 0; + summaryview->deleted = summaryview->moved = 0; + summaryview->copied = 0; + if (summaryview->msgid_table) { + g_hash_table_destroy(summaryview->msgid_table); + summaryview->msgid_table = NULL; + } + summaryview->mlist = NULL; + if (summaryview->folder_table) { + g_hash_table_destroy(summaryview->folder_table); + summaryview->folder_table = NULL; + } + + gtk_clist_clear(clist); + if (summaryview->col_pos[S_COL_SUBJECT] == N_SUMMARY_COLS - 1) { + optimal_width = gtk_clist_optimal_column_width + (clist, summaryview->col_pos[S_COL_SUBJECT]); + gtk_clist_set_column_width + (clist, summaryview->col_pos[S_COL_SUBJECT], + optimal_width); + } + + gtk_clist_thaw(clist); +} + +void summary_clear_all(SummaryView *summaryview) +{ + messageview_clear(summaryview->messageview); + summary_clear_list(summaryview); + summary_set_menu_sensitive(summaryview); + main_window_set_toolbar_sensitive(summaryview->mainwin); + summary_status_show(summaryview); +} + +void summary_lock(SummaryView *summaryview) +{ + summaryview->lock_count++; +} + +void summary_unlock(SummaryView *summaryview) +{ + if (summaryview->lock_count) + summaryview->lock_count--; +} + +gboolean summary_is_locked(SummaryView *summaryview) +{ + return summaryview->lock_count > 0; +} + +SummarySelection summary_get_selection_type(SummaryView *summaryview) +{ + GtkCList *clist = GTK_CLIST(summaryview->ctree); + SummarySelection selection; + + if (!clist->row_list) + selection = SUMMARY_NONE; + else if (!clist->selection) + selection = SUMMARY_SELECTED_NONE; + else if (!clist->selection->next) + selection = SUMMARY_SELECTED_SINGLE; + else + selection = SUMMARY_SELECTED_MULTIPLE; + + return selection; +} + +GSList *summary_get_selected_msg_list(SummaryView *summaryview) +{ + GSList *mlist = NULL; + GList *cur; + MsgInfo *msginfo; + + for (cur = GTK_CLIST(summaryview->ctree)->selection; cur != NULL; + cur = cur->next) { + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(cur->data); + mlist = g_slist_prepend(mlist, msginfo); + } + + mlist = g_slist_reverse(mlist); + + return mlist; +} + +GSList *summary_get_msg_list(SummaryView *summaryview) +{ + GSList *mlist = NULL; + GtkCTree *ctree; + GtkCTreeNode *node; + MsgInfo *msginfo; + + ctree = GTK_CTREE(summaryview->ctree); + + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + mlist = g_slist_prepend(mlist, msginfo); + } + + mlist = g_slist_reverse(mlist); + + return mlist; +} + +static void summary_set_menu_sensitive(SummaryView *summaryview) +{ + GtkItemFactory *ifactory = summaryview->popupfactory; + SummarySelection selection; + GtkWidget *menuitem; + gboolean sens; + + selection = summary_get_selection_type(summaryview); + sens = (selection == SUMMARY_SELECTED_MULTIPLE) ? FALSE : TRUE; + + main_window_set_menu_sensitive(summaryview->mainwin); + + if (summaryview->folder_item && + (summaryview->folder_item->stype == F_OUTBOX || + summaryview->folder_item->stype == F_DRAFT || + summaryview->folder_item->stype == F_QUEUE)) { + gtk_widget_show(summaryview->reedit_menuitem); + gtk_widget_show(summaryview->reedit_separator); + menu_set_sensitive(ifactory, "/Re-edit", sens); + } else { + gtk_widget_hide(summaryview->reedit_menuitem); + gtk_widget_hide(summaryview->reedit_separator); + menu_set_sensitive(ifactory, "/Re-edit", FALSE); + } + + if (selection == SUMMARY_NONE) { + menu_set_insensitive_all + (GTK_MENU_SHELL(summaryview->popupmenu)); + return; + } + + if (FOLDER_TYPE(summaryview->folder_item->folder) != F_NEWS) { + menu_set_sensitive(ifactory, "/Move...", TRUE); + menu_set_sensitive(ifactory, "/Delete", TRUE); + } else { + menu_set_sensitive(ifactory, "/Move...", FALSE); + menu_set_sensitive(ifactory, "/Delete", FALSE); + } + + menu_set_sensitive(ifactory, "/Copy...", TRUE); + + menu_set_sensitive(ifactory, "/Mark", TRUE); + menu_set_sensitive(ifactory, "/Mark/Mark", TRUE); + menu_set_sensitive(ifactory, "/Mark/Unmark", TRUE); + + menu_set_sensitive(ifactory, "/Mark/Mark as unread", TRUE); + menu_set_sensitive(ifactory, "/Mark/Mark as read", TRUE); + menu_set_sensitive(ifactory, "/Mark/Mark all read", TRUE); + + menu_set_sensitive(ifactory, "/Color label", TRUE); + + menu_set_sensitive(ifactory, "/Reply", sens); + menu_set_sensitive(ifactory, "/Reply to", sens); + menu_set_sensitive(ifactory, "/Reply to/all", sens); + menu_set_sensitive(ifactory, "/Reply to/sender", sens); + menu_set_sensitive(ifactory, "/Reply to/mailing list", sens); + menu_set_sensitive(ifactory, "/Forward", TRUE); + menu_set_sensitive(ifactory, "/Forward as attachment", TRUE); + menu_set_sensitive(ifactory, "/Redirect", sens); + + menu_set_sensitive(ifactory, "/Add sender to address book", sens); + + menu_set_sensitive(ifactory, "/View", sens); + menu_set_sensitive(ifactory, "/View/Open in new window", sens); + menu_set_sensitive(ifactory, "/View/Source", sens); + menu_set_sensitive(ifactory, "/View/All header", sens); + + menu_set_sensitive(ifactory, "/Print...", TRUE); + + summary_lock(summaryview); + menuitem = gtk_item_factory_get_widget(ifactory, "/View/All header"); + gtk_check_menu_item_set_active + (GTK_CHECK_MENU_ITEM(menuitem), + summaryview->messageview->textview->show_all_headers); + summary_unlock(summaryview); +} + +void summary_select_prev_unread(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + node = summary_find_prev_flagged_msg + (summaryview, summaryview->selected, MSG_UNREAD, FALSE); + + if (!node) { + AlertValue val; + + val = alertpanel(_("No more unread messages"), + _("No unread message found. " + "Search from the end?"), + _("Yes"), _("No"), NULL); + if (val != G_ALERTDEFAULT) return; + node = summary_find_prev_flagged_msg(summaryview, NULL, + MSG_UNREAD, FALSE); + } + + if (!node) + alertpanel_notice(_("No unread messages.")); + else + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_next_unread(SummaryView *summaryview) +{ + GtkCTreeNode *node = summaryview->selected; + //GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + + while ((node = summary_find_next_flagged_msg + (summaryview, node, MSG_UNREAD, FALSE)) == NULL) { + AlertValue val; + + val = alertpanel(_("No more unread messages"), + _("No unread message found. " + "Go to next folder?"), + _("Yes"), _("Search again"), _("No")); + if (val == G_ALERTDEFAULT) { +#if 0 + if (gtk_signal_n_emissions_by_name + (G_OBJECT(ctree), "key_press_event") > 0) + gtk_signal_emit_stop_by_name + (G_OBJECT(ctree), + "key_press_event"); +#endif + folderview_select_next_unread(summaryview->folderview); + return; + } else if (val == G_ALERTALTERNATE) + node = NULL; + else + return; + } + + if (node) + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_prev_new(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + node = summary_find_prev_flagged_msg + (summaryview, summaryview->selected, MSG_NEW, FALSE); + + if (!node) { + AlertValue val; + + val = alertpanel(_("No more new messages"), + _("No new message found. " + "Search from the end?"), + _("Yes"), _("No"), NULL); + if (val != G_ALERTDEFAULT) return; + node = summary_find_prev_flagged_msg(summaryview, NULL, + MSG_NEW, FALSE); + } + + if (!node) + alertpanel_notice(_("No new messages.")); + else + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_next_new(SummaryView *summaryview) +{ + GtkCTreeNode *node = summaryview->selected; + //GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + + while ((node = summary_find_next_flagged_msg + (summaryview, node, MSG_NEW, FALSE)) == NULL) { + AlertValue val; + + val = alertpanel(_("No more new messages"), + _("No new message found. " + "Go to next folder?"), + _("Yes"), _("Search again"), _("No")); + if (val == G_ALERTDEFAULT) { +#if 0 + if (gtk_signal_n_emissions_by_name + (G_OBJECT(ctree), "key_press_event") > 0) + gtk_signal_emit_stop_by_name + (G_OBJECT(ctree), + "key_press_event"); +#endif + folderview_select_next_unread(summaryview->folderview); + return; + } else if (val == G_ALERTALTERNATE) + node = NULL; + else + return; + } + + if (node) + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_prev_marked(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + node = summary_find_prev_flagged_msg + (summaryview, summaryview->selected, MSG_MARKED, TRUE); + + if (!node) { + AlertValue val; + + val = alertpanel(_("No more marked messages"), + _("No marked message found. " + "Search from the end?"), + _("Yes"), _("No"), NULL); + if (val != G_ALERTDEFAULT) return; + node = summary_find_prev_flagged_msg(summaryview, NULL, + MSG_MARKED, TRUE); + } + + if (!node) + alertpanel_notice(_("No marked messages.")); + else + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_next_marked(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + node = summary_find_next_flagged_msg + (summaryview, summaryview->selected, MSG_MARKED, TRUE); + + if (!node) { + AlertValue val; + + val = alertpanel(_("No more marked messages"), + _("No marked message found. " + "Search from the beginning?"), + _("Yes"), _("No"), NULL); + if (val != G_ALERTDEFAULT) return; + node = summary_find_next_flagged_msg(summaryview, NULL, + MSG_MARKED, TRUE); + } + + if (!node) + alertpanel_notice(_("No marked messages.")); + else + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_prev_labeled(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + node = summary_find_prev_flagged_msg + (summaryview, summaryview->selected, MSG_CLABEL_FLAG_MASK, TRUE); + + if (!node) { + AlertValue val; + + val = alertpanel(_("No more labeled messages"), + _("No labeled message found. " + "Search from the end?"), + _("Yes"), _("No"), NULL); + if (val != G_ALERTDEFAULT) return; + node = summary_find_prev_flagged_msg(summaryview, NULL, + MSG_CLABEL_FLAG_MASK, TRUE); + } + + if (!node) + alertpanel_notice(_("No labeled messages.")); + else + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_next_labeled(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + node = summary_find_next_flagged_msg + (summaryview, summaryview->selected, MSG_CLABEL_FLAG_MASK, TRUE); + + if (!node) { + AlertValue val; + + val = alertpanel(_("No more labeled messages"), + _("No labeled message found. " + "Search from the beginning?"), + _("Yes"), _("No"), NULL); + if (val != G_ALERTDEFAULT) return; + node = summary_find_next_flagged_msg(summaryview, NULL, + MSG_CLABEL_FLAG_MASK, TRUE); + } + + if (!node) + alertpanel_notice(_("No labeled messages.")); + else + summary_select_node(summaryview, node, TRUE, FALSE); +} + +void summary_select_by_msgnum(SummaryView *summaryview, guint msgnum) +{ + GtkCTreeNode *node; + + node = summary_find_msg_by_msgnum(summaryview, msgnum); + summary_select_node(summaryview, node, FALSE, TRUE); +} + +/** + * summary_select_node: + * @summaryview: Summary view. + * @node: Summary tree node. + * @display_msg: TRUE to display the selected message. + * @do_refresh: TRUE to refresh the widget. + * + * Select @node (bringing it into view by scrolling and expanding its + * thread, if necessary) and unselect all others. If @display_msg is + * TRUE, display the corresponding message in the message view. + * If @do_refresh is TRUE, the widget is refreshed. + **/ +void summary_select_node(SummaryView *summaryview, GtkCTreeNode *node, + gboolean display_msg, gboolean do_refresh) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + + if (node) { + gtkut_ctree_expand_parent_all(ctree, node); + if (do_refresh) { + GTK_EVENTS_FLUSH(); + gtk_widget_grab_focus(GTK_WIDGET(ctree)); + gtk_ctree_node_moveto(ctree, node, -1, 0.5, 0); + } + gtk_sctree_unselect_all(GTK_SCTREE(ctree)); + if (display_msg && summaryview->displayed == node) + summaryview->displayed = NULL; + summaryview->display_msg = display_msg; + gtk_sctree_select(GTK_SCTREE(ctree), node); + } +} + +static guint summary_get_msgnum(SummaryView *summaryview, GtkCTreeNode *node) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + if (!node) + return 0; + msginfo = gtk_ctree_node_get_row_data(ctree, node); + return msginfo->msgnum; +} + +static GtkCTreeNode *summary_find_prev_msg(SummaryView *summaryview, + GtkCTreeNode *current_node) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + + if (current_node) + node = current_node; + else + node = gtk_ctree_node_nth(ctree, GTK_CLIST(ctree)->rows - 1); + + for (; node != NULL; node = GTK_CTREE_NODE_PREV(node)) { + msginfo = gtk_ctree_node_get_row_data(ctree, node); + if (msginfo && !MSG_IS_INVALID(msginfo->flags) && + !MSG_IS_DELETED(msginfo->flags)) break; + } + + return node; +} + +static GtkCTreeNode *summary_find_next_msg(SummaryView *summaryview, + GtkCTreeNode *current_node) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + + if (current_node) + node = current_node; + else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + for (; node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + msginfo = gtk_ctree_node_get_row_data(ctree, node); + if (msginfo && !MSG_IS_INVALID(msginfo->flags) && + !MSG_IS_DELETED(msginfo->flags)) break; + } + + return node; +} + +static GtkCTreeNode *summary_find_prev_flagged_msg(SummaryView *summaryview, + GtkCTreeNode *current_node, + MsgPermFlags flags, + gboolean start_from_prev) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + + if (current_node) { + if (start_from_prev) + node = GTK_CTREE_NODE_PREV(current_node); + else + node = current_node; + } else + node = gtk_ctree_node_nth(ctree, GTK_CLIST(ctree)->rows - 1); + + for (; node != NULL; node = GTK_CTREE_NODE_PREV(node)) { + msginfo = gtk_ctree_node_get_row_data(ctree, node); + if (msginfo && (msginfo->flags.perm_flags & flags) != 0) break; + } + + return node; +} + +static GtkCTreeNode *summary_find_next_flagged_msg(SummaryView *summaryview, + GtkCTreeNode *current_node, + MsgPermFlags flags, + gboolean start_from_next) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + + if (current_node) { + if (start_from_next) + node = gtkut_ctree_node_next(ctree, current_node); + else + node = current_node; + } else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + for (; node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + msginfo = gtk_ctree_node_get_row_data(ctree, node); + if (msginfo && (msginfo->flags.perm_flags & flags) != 0) break; + } + + return node; +} + +static GtkCTreeNode *summary_find_msg_by_msgnum(SummaryView *summaryview, + guint msgnum) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + for (; node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + msginfo = gtk_ctree_node_get_row_data(ctree, node); + if (msginfo && msginfo->msgnum == msgnum) break; + } + + return node; +} + +static guint attract_hash_func(gconstpointer key) +{ + gchar *str; + gchar *p; + guint h; + + Xstrdup_a(str, (const gchar *)key, return 0); + trim_subject_for_compare(str); + + p = str; + h = *p; + + if (h) { + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + } + + return h; +} + +static gint attract_compare_func(gconstpointer a, gconstpointer b) +{ + return subject_compare((const gchar *)a, (const gchar *)b) == 0; +} + +void summary_attract_by_subject(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCList *clist = GTK_CLIST(ctree); + GtkCTreeNode *src_node; + GtkCTreeNode *dst_node, *sibling; + GtkCTreeNode *tmp; + MsgInfo *src_msginfo, *dst_msginfo; + GHashTable *subject_table; + + debug_print(_("Attracting messages by subject...")); + STATUSBAR_PUSH(summaryview->mainwin, + _("Attracting messages by subject...")); + + main_window_cursor_wait(summaryview->mainwin); + gtk_clist_freeze(clist); + + subject_table = g_hash_table_new(attract_hash_func, + attract_compare_func); + + for (src_node = GTK_CTREE_NODE(clist->row_list); + src_node != NULL; + src_node = tmp) { + tmp = GTK_CTREE_ROW(src_node)->sibling; + src_msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(src_node); + if (!src_msginfo) continue; + if (!src_msginfo->subject) continue; + + /* find attracting node */ + dst_node = g_hash_table_lookup(subject_table, + src_msginfo->subject); + + if (dst_node) { + dst_msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(dst_node); + + /* if the time difference is more than 20 days, + don't attract */ + if (ABS(src_msginfo->date_t - dst_msginfo->date_t) + > 60 * 60 * 24 * 20) + continue; + + sibling = GTK_CTREE_ROW(dst_node)->sibling; + if (src_node != sibling) + gtk_ctree_move(ctree, src_node, NULL, sibling); + } + + g_hash_table_insert(subject_table, + src_msginfo->subject, src_node); + } + + g_hash_table_destroy(subject_table); + + gtk_ctree_node_moveto(ctree, summaryview->selected, -1, 0.5, 0); + + gtk_clist_thaw(clist); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + + main_window_cursor_normal(summaryview->mainwin); +} + +static void summary_free_msginfo_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + MsgInfo *msginfo = gtk_ctree_node_get_row_data(ctree, node); + + if (msginfo) + procmsg_msginfo_free(msginfo); +} + +static void summary_set_marks_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + SummaryView *summaryview = data; + MsgInfo *msginfo; + + msginfo = gtk_ctree_node_get_row_data(ctree, node); + + if (MSG_IS_DELETED(msginfo->flags)) + summaryview->deleted++; + + summaryview->total_size += msginfo->size; + + summary_set_row_marks(summaryview, node); +} + +static void summary_update_status(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + MsgInfo *msginfo; + + summaryview->total_size = + summaryview->deleted = summaryview->moved = summaryview->copied = 0; + + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + + if (MSG_IS_DELETED(msginfo->flags)) + summaryview->deleted++; + if (MSG_IS_MOVE(msginfo->flags)) + summaryview->moved++; + if (MSG_IS_COPY(msginfo->flags)) + summaryview->copied++; + summaryview->total_size += msginfo->size; + } +} + +static void summary_status_show(SummaryView *summaryview) +{ + gchar *str; + gchar *del, *mv, *cp; + gchar *sel; + gchar *spc; + GList *rowlist, *cur; + guint n_selected = 0; + off_t sel_size = 0; + MsgInfo *msginfo; + + if (!summaryview->folder_item) { + gtk_label_set(GTK_LABEL(summaryview->statlabel_folder), ""); + gtk_label_set(GTK_LABEL(summaryview->statlabel_select), ""); + gtk_label_set(GTK_LABEL(summaryview->statlabel_msgs), ""); + return; + } + + rowlist = GTK_CLIST(summaryview->ctree)->selection; + for (cur = rowlist; cur != NULL; cur = cur->next) { + msginfo = gtk_ctree_node_get_row_data + (GTK_CTREE(summaryview->ctree), + GTK_CTREE_NODE(cur->data)); + if (!msginfo) + g_warning("summary_status_show(): msginfo == NULL\n"); + else { + sel_size += msginfo->size; + n_selected++; + } + } + + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_NEWS) { + gchar *group; + group = get_abbrev_newsgroup_name + (g_basename(summaryview->folder_item->path), + prefs_common.ng_abbrev_len); + str = trim_string_before(group, 32); + g_free(group); + } else + str = trim_string_before(summaryview->folder_item->path, 32); + gtk_label_set(GTK_LABEL(summaryview->statlabel_folder), str); + g_free(str); + + if (summaryview->deleted) + del = g_strdup_printf(_("%d deleted"), summaryview->deleted); + else + del = g_strdup(""); + if (summaryview->moved) + mv = g_strdup_printf(_("%s%d moved"), + summaryview->deleted ? _(", ") : "", + summaryview->moved); + else + mv = g_strdup(""); + if (summaryview->copied) + cp = g_strdup_printf(_("%s%d copied"), + summaryview->deleted || + summaryview->moved ? _(", ") : "", + summaryview->copied); + else + cp = g_strdup(""); + + if (summaryview->deleted || summaryview->moved || summaryview->copied) + spc = " "; + else + spc = ""; + + if (n_selected) + sel = g_strdup_printf(" (%s)", to_human_readable(sel_size)); + else + sel = g_strdup(""); + str = g_strconcat(n_selected ? itos(n_selected) : "", + n_selected ? _(" item(s) selected") : "", + sel, spc, del, mv, cp, NULL); + gtk_label_set(GTK_LABEL(summaryview->statlabel_select), str); + g_free(str); + g_free(sel); + g_free(del); + g_free(mv); + g_free(cp); + + if (FOLDER_IS_LOCAL(summaryview->folder_item->folder)) { + str = g_strdup_printf(_("%d new, %d unread, %d total (%s)"), + summaryview->folder_item->new, + summaryview->folder_item->unread, + summaryview->folder_item->total, + to_human_readable(summaryview->total_size)); + } else { + str = g_strdup_printf(_("%d new, %d unread, %d total"), + summaryview->folder_item->new, + summaryview->folder_item->unread, + summaryview->folder_item->total); + } + gtk_label_set(GTK_LABEL(summaryview->statlabel_msgs), str); + g_free(str); + + folderview_update_msg_num(summaryview->folderview, + summaryview->folderview->opened); +} + +static void summary_set_column_titles(SummaryView *summaryview) +{ + GtkCList *clist = GTK_CLIST(summaryview->ctree); + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *arrow; + gint pos; + const gchar *title; + SummaryColumnType type; + GtkJustification justify; + FolderItem *item = summaryview->folder_item; + + static FolderSortKey sort_by[N_SUMMARY_COLS] = { + SORT_BY_MARK, + SORT_BY_UNREAD, + SORT_BY_MIME, + SORT_BY_SUBJECT, + SORT_BY_FROM, + SORT_BY_DATE, + SORT_BY_SIZE, + SORT_BY_NUMBER + }; + + for (pos = 0; pos < N_SUMMARY_COLS; pos++) { + type = summaryview->col_state[pos].type; + + justify = (type == S_COL_NUMBER || type == S_COL_SIZE) + ? GTK_JUSTIFY_RIGHT : GTK_JUSTIFY_LEFT; + + switch (type) { + case S_COL_SUBJECT: + case S_COL_FROM: + case S_COL_DATE: + case S_COL_NUMBER: + if (prefs_common.trans_hdr) + title = gettext(col_label[type]); + else + title = col_label[type]; + break; + default: + title = gettext(col_label[type]); + } + + if (type == S_COL_MARK) { + label = gtk_pixmap_new(markxpm, markxpmmask); + gtk_widget_show(label); + gtk_clist_set_column_widget(clist, pos, label); + continue; + } else if (type == S_COL_UNREAD) { + label = gtk_pixmap_new(mailxpm, mailxpmmask); + gtk_widget_show(label); + gtk_clist_set_column_widget(clist, pos, label); + continue; + } else if (type == S_COL_MIME) { + label = gtk_pixmap_new(clipxpm, clipxpmmask); + gtk_widget_show(label); + gtk_clist_set_column_widget(clist, pos, label); + continue; + } + + hbox = gtk_hbox_new(FALSE, 4); + label = gtk_label_new(title); + if (justify == GTK_JUSTIFY_RIGHT) + gtk_box_pack_end(GTK_BOX(hbox), label, + FALSE, FALSE, 0); + else + gtk_box_pack_start(GTK_BOX(hbox), label, + FALSE, FALSE, 0); + + if (item && item->sort_key == sort_by[type]) { + arrow = gtk_arrow_new + (item->sort_type == SORT_ASCENDING + ? GTK_ARROW_UP : GTK_ARROW_DOWN, + GTK_SHADOW_IN); + if (justify == GTK_JUSTIFY_RIGHT) + gtk_box_pack_start(GTK_BOX(hbox), arrow, + FALSE, FALSE, 0); + else + gtk_box_pack_end(GTK_BOX(hbox), arrow, + FALSE, FALSE, 0); + } + + gtk_widget_show_all(hbox); + gtk_clist_set_column_widget(clist, pos, hbox); + } +} + +void summary_sort(SummaryView *summaryview, + FolderSortKey sort_key, FolderSortType sort_type) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCList *clist = GTK_CLIST(summaryview->ctree); + GtkCListCompareFunc cmp_func; + FolderItem *item = summaryview->folder_item; + + if (!item || !item->path || !item->parent || item->no_select) return; + + switch (sort_key) { + case SORT_BY_MARK: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_mark; + break; + case SORT_BY_UNREAD: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_unread; + break; + case SORT_BY_MIME: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_mime; + break; + case SORT_BY_NUMBER: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_num; + break; + case SORT_BY_SIZE: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_size; + break; + case SORT_BY_DATE: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_date; + break; + case SORT_BY_FROM: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_from; + break; + case SORT_BY_SUBJECT: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_subject; + break; + case SORT_BY_LABEL: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_label; + break; + case SORT_BY_TO: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_to; + break; + case SORT_BY_NONE: + item->sort_key = sort_key; + item->sort_type = SORT_ASCENDING; + summary_set_column_titles(summaryview); + summary_set_menu_sensitive(summaryview); + return; + default: + return; + } + + debug_print(_("Sorting summary...")); + STATUSBAR_PUSH(summaryview->mainwin, _("Sorting summary...")); + + main_window_cursor_wait(summaryview->mainwin); + + gtk_clist_set_compare_func(clist, cmp_func); + + gtk_clist_set_sort_type(clist, (GtkSortType)sort_type); + item->sort_key = sort_key; + item->sort_type = sort_type; + + summary_set_column_titles(summaryview); + summary_set_menu_sensitive(summaryview); + + gtk_ctree_sort_recursive(ctree, NULL); + + gtk_ctree_node_moveto(ctree, summaryview->selected, -1, 0.5, 0); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + + main_window_cursor_normal(summaryview->mainwin); +} + +gboolean summary_insert_gnode_func(GtkCTree *ctree, guint depth, GNode *gnode, + GtkCTreeNode *cnode, gpointer data) +{ + SummaryView *summaryview = (SummaryView *)data; + MsgInfo *msginfo = (MsgInfo *)gnode->data; + gchar *text[N_SUMMARY_COLS]; + gint *col_pos = summaryview->col_pos; + const gchar *msgid = msginfo->msgid; + GHashTable *msgid_table = summaryview->msgid_table; + + summary_set_header(summaryview, text, msginfo); + + gtk_ctree_set_node_info(ctree, cnode, text[col_pos[S_COL_SUBJECT]], 2, + NULL, NULL, NULL, NULL, FALSE, + gnode->parent->parent ? TRUE : FALSE); +#define SET_TEXT(col) \ + gtk_ctree_node_set_text(ctree, cnode, col_pos[col], \ + text[col_pos[col]]) + + SET_TEXT(S_COL_NUMBER); + SET_TEXT(S_COL_SIZE); + SET_TEXT(S_COL_DATE); + SET_TEXT(S_COL_FROM); + SET_TEXT(S_COL_SUBJECT); + +#undef SET_TEXT + + GTKUT_CTREE_NODE_SET_ROW_DATA(cnode, msginfo); + summary_set_marks_func(ctree, cnode, summaryview); + + if (msgid && msgid[0] != '\0') + g_hash_table_insert(msgid_table, (gchar *)msgid, cnode); + + return TRUE; +} + +static void summary_set_ctree_from_list(SummaryView *summaryview, + GSList *mlist) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + GtkCTreeNode *node = NULL; + GHashTable *msgid_table; + + if (!mlist) return; + + debug_print(_("\tSetting summary from message data...")); + STATUSBAR_PUSH(summaryview->mainwin, + _("Setting summary from message data...")); + gdk_flush(); + + msgid_table = g_hash_table_new(g_str_hash, g_str_equal); + summaryview->msgid_table = msgid_table; + + if (summaryview->folder_item->threaded) { + GNode *root, *gnode; + + root = procmsg_get_thread_tree(mlist); + + for (gnode = root->children; gnode != NULL; + gnode = gnode->next) { + node = gtk_ctree_insert_gnode + (ctree, NULL, node, gnode, + summary_insert_gnode_func, summaryview); + } + + g_node_destroy(root); + + summary_thread_init(summaryview); + } else { + gchar *text[N_SUMMARY_COLS]; + + mlist = g_slist_reverse(mlist); + for (; mlist != NULL; mlist = mlist->next) { + msginfo = (MsgInfo *)mlist->data; + + summary_set_header(summaryview, text, msginfo); + + node = gtk_ctree_insert_node + (ctree, NULL, node, text, 2, + NULL, NULL, NULL, NULL, FALSE, FALSE); + GTKUT_CTREE_NODE_SET_ROW_DATA(node, msginfo); + summary_set_marks_func(ctree, node, summaryview); + + if (msginfo->msgid && msginfo->msgid[0] != '\0') + g_hash_table_insert(msgid_table, + msginfo->msgid, node); + } + mlist = g_slist_reverse(mlist); + } + + if (prefs_common.enable_hscrollbar && + summaryview->col_pos[S_COL_SUBJECT] == N_SUMMARY_COLS - 1) { + gint optimal_width; + + optimal_width = gtk_clist_optimal_column_width + (GTK_CLIST(ctree), summaryview->col_pos[S_COL_SUBJECT]); + gtk_clist_set_column_width(GTK_CLIST(ctree), + summaryview->col_pos[S_COL_SUBJECT], + optimal_width); + } + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + if (debug_mode) + debug_print("\tmsgid hash table size = %d\n", + g_hash_table_size(msgid_table)); +} + +struct wcachefp +{ + FILE *cache_fp; + FILE *mark_fp; +}; + +gint summary_write_cache(SummaryView *summaryview) +{ + struct wcachefp fps; + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + FolderItem *item; + gchar *buf; + + item = summaryview->folder_item; + if (!item || !item->path) + return -1; + + fps.cache_fp = procmsg_open_cache_file(item, DATA_WRITE); + if (fps.cache_fp == NULL) + return -1; + fps.mark_fp = procmsg_open_mark_file(item, DATA_WRITE); + if (fps.mark_fp == NULL) { + fclose(fps.cache_fp); + return -1; + } + + buf = g_strdup_printf(_("Writing summary cache (%s)..."), item->path); + debug_print(buf); + STATUSBAR_PUSH(summaryview->mainwin, buf); + g_free(buf); + + gtk_ctree_pre_recursive(ctree, NULL, summary_write_cache_func, &fps); + + procmsg_flush_mark_queue(item, fps.mark_fp); + item->unmarked_num = 0; + + fclose(fps.cache_fp); + fclose(fps.mark_fp); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + + return 0; +} + +static void summary_write_cache_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + struct wcachefp *fps = data; + MsgInfo *msginfo = gtk_ctree_node_get_row_data(ctree, node); + + if (msginfo == NULL) return; + + if (msginfo->folder->mark_queue != NULL) { + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW); + } + + procmsg_write_cache(msginfo, fps->cache_fp); + procmsg_write_flags(msginfo, fps->mark_fp); +} + +static void summary_set_header(SummaryView *summaryview, gchar *text[], + MsgInfo *msginfo) +{ + static gchar date_modified[80]; + static gchar *to = NULL; + static gchar *subject = NULL; + gint *col_pos = summaryview->col_pos; + + text[col_pos[S_COL_MARK]] = NULL; + text[col_pos[S_COL_UNREAD]] = NULL; + text[col_pos[S_COL_MIME]] = NULL; + text[col_pos[S_COL_NUMBER]] = itos(msginfo->msgnum); + text[col_pos[S_COL_SIZE]] = to_human_readable(msginfo->size); + + if (msginfo->date_t) { + procheader_date_get_localtime(date_modified, + sizeof(date_modified), + msginfo->date_t); + text[col_pos[S_COL_DATE]] = date_modified; + } else if (msginfo->date) + text[col_pos[S_COL_DATE]] = msginfo->date; + else + text[col_pos[S_COL_DATE]] = _("(No Date)"); + + text[col_pos[S_COL_FROM]] = msginfo->fromname ? msginfo->fromname : + _("(No From)"); + if (prefs_common.swap_from && msginfo->from && msginfo->to && + cur_account && cur_account->address) { + gchar *from; + + Xstrdup_a(from, msginfo->from, return); + extract_address(from); + if (!strcmp(from, cur_account->address)) { + g_free(to); + to = g_strconcat("-->", msginfo->to, NULL); + text[col_pos[S_COL_FROM]] = to; + } + } + + if (msginfo->subject) { + if (msginfo->folder && msginfo->folder->trim_summary_subject) { + g_free(subject); + subject = g_strdup(msginfo->subject); + trim_subject(subject); + text[col_pos[S_COL_SUBJECT]] = subject; + } else + text[col_pos[S_COL_SUBJECT]] = msginfo->subject; + } else + text[col_pos[S_COL_SUBJECT]] = _("(No Subject)"); +} + +static void summary_display_msg(SummaryView *summaryview, GtkCTreeNode *row) +{ + summary_display_msg_full(summaryview, row, FALSE, FALSE); +} + +static void summary_display_msg_full(SummaryView *summaryview, + GtkCTreeNode *row, + gboolean new_window, gboolean all_headers) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + gint val; + + if (!new_window && summaryview->displayed == row) return; + g_return_if_fail(row != NULL); + + if (summary_is_locked(summaryview)) return; + summary_lock(summaryview); + + STATUSBAR_POP(summaryview->mainwin); + GTK_EVENTS_FLUSH(); + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + + if (new_window) { + MessageView *msgview; + + msgview = messageview_create_with_new_window(); + val = messageview_show(msgview, msginfo, all_headers); + } else { + MessageView *msgview; + + msgview = summaryview->messageview; + + summaryview->displayed = row; + if (!messageview_is_visible(msgview)) + main_window_toggle_message_view(summaryview->mainwin); + val = messageview_show(msgview, msginfo, all_headers); + if (msgview->type == MVIEW_TEXT || + (msgview->type == MVIEW_MIME && + (GTK_CLIST(msgview->mimeview->ctree)->row_list == NULL || + gtk_notebook_get_current_page + (GTK_NOTEBOOK(msgview->mimeview->notebook)) == 0))) + gtk_widget_grab_focus(summaryview->ctree); + GTK_EVENTS_FLUSH(); + gtkut_ctree_node_move_if_on_the_edge(ctree, row); + } + + if (val == 0 && + (new_window || !prefs_common.mark_as_read_on_new_window)) { + if (MSG_IS_NEW(msginfo->flags)) + summaryview->folder_item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + summaryview->folder_item->unread--; + if (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)) { + MSG_UNSET_PERM_FLAGS + (msginfo->flags, MSG_NEW | MSG_UNREAD); + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_unset_perm_flags + (msginfo, MSG_NEW | MSG_UNREAD); + summary_set_row_marks(summaryview, row); + gtk_clist_thaw(GTK_CLIST(ctree)); + summary_status_show(summaryview); + } + } + + summary_set_menu_sensitive(summaryview); + main_window_set_toolbar_sensitive(summaryview->mainwin); + + statusbar_pop_all(); + + summary_unlock(summaryview); +} + +void summary_display_msg_selected(SummaryView *summaryview, + gboolean all_headers) +{ + if (summary_is_locked(summaryview)) return; + summaryview->displayed = NULL; + summary_display_msg_full(summaryview, summaryview->selected, FALSE, + all_headers); +} + +void summary_redisplay_msg(SummaryView *summaryview) +{ + GtkCTreeNode *node; + + if (summaryview->displayed) { + node = summaryview->displayed; + summaryview->displayed = NULL; + summary_display_msg(summaryview, node); + } +} + +void summary_open_msg(SummaryView *summaryview) +{ + if (!summaryview->selected) return; + + summary_display_msg_full(summaryview, summaryview->selected, + TRUE, FALSE); +} + +void summary_view_source(SummaryView * summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + SourceWindow *srcwin; + + if (!summaryview->selected) return; + + srcwin = source_window_create(); + msginfo = gtk_ctree_node_get_row_data(ctree, summaryview->selected); + source_window_show_msg(srcwin, msginfo); + source_window_show(srcwin); +} + +void summary_reedit(SummaryView *summaryview) +{ + MsgInfo *msginfo; + + if (!summaryview->selected) return; + if (!summaryview->folder_item) return; + if (summaryview->folder_item->stype != F_OUTBOX && + summaryview->folder_item->stype != F_DRAFT && + summaryview->folder_item->stype != F_QUEUE) return; + + msginfo = gtk_ctree_node_get_row_data(GTK_CTREE(summaryview->ctree), + summaryview->selected); + if (!msginfo) return; + + compose_reedit(msginfo); +} + +void summary_step(SummaryView *summaryview, GtkScrollType type) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + + if (summary_is_locked(summaryview)) return; + + if (type == GTK_SCROLL_STEP_FORWARD) { + node = gtkut_ctree_node_next(ctree, summaryview->selected); + if (node) + gtkut_ctree_expand_parent_all(ctree, node); + else + return; + } else { + if (summaryview->selected) { + node = GTK_CTREE_NODE_PREV(summaryview->selected); + if (!node) return; + } + } + + if (messageview_is_visible(summaryview->messageview)) + summaryview->display_msg = TRUE; + + g_signal_emit_by_name(G_OBJECT(ctree), "scroll_vertical", type, 0.0); + + if (GTK_CLIST(ctree)->selection) + gtk_sctree_set_anchor_row + (GTK_SCTREE(ctree), + GTK_CTREE_NODE(GTK_CLIST(ctree)->selection->data)); +} + +void summary_toggle_view(SummaryView *summaryview) +{ + if (!messageview_is_visible(summaryview->messageview) && + summaryview->selected) + summary_display_msg(summaryview, + summaryview->selected); + else + main_window_toggle_message_view(summaryview->mainwin); +} + +static gboolean summary_search_unread_recursive(GtkCTree *ctree, + GtkCTreeNode *node) +{ + MsgInfo *msginfo; + + if (node) { + msginfo = gtk_ctree_node_get_row_data(ctree, node); + if (msginfo && MSG_IS_UNREAD(msginfo->flags)) + return TRUE; + node = GTK_CTREE_ROW(node)->children; + } else + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + while (node) { + if (summary_search_unread_recursive(ctree, node) == TRUE) + return TRUE; + node = GTK_CTREE_ROW(node)->sibling; + } + + return FALSE; +} + +static gboolean summary_have_unread_children(SummaryView *summaryview, + GtkCTreeNode *node) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + + if (!node) return FALSE; + + node = GTK_CTREE_ROW(node)->children; + + while (node) { + if (summary_search_unread_recursive(ctree, node) == TRUE) + return TRUE; + node = GTK_CTREE_ROW(node)->sibling; + } + + return FALSE; +} + +static void summary_set_row_marks(SummaryView *summaryview, GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkStyle *style = NULL; + MsgInfo *msginfo; + MsgFlags flags; + gint *col_pos = summaryview->col_pos; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + if (!msginfo) return; + + flags = msginfo->flags; + + gtk_ctree_node_set_foreground(ctree, row, NULL); + + /* set new/unread column */ + if (MSG_IS_NEW(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_UNREAD], + newxpm, newxpmmask); + } else if (MSG_IS_UNREAD(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_UNREAD], + unreadxpm, unreadxpmmask); + } else if (MSG_IS_REPLIED(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_UNREAD], + repliedxpm, repliedxpmmask); + } else if (MSG_IS_FORWARDED(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_UNREAD], + forwardedxpm, forwardedxpmmask); + } else { + gtk_ctree_node_set_text(ctree, row, col_pos[S_COL_UNREAD], + NULL); + } + + if (prefs_common.bold_unread && + (MSG_IS_UNREAD(flags) || + (!GTK_CTREE_ROW(row)->expanded && + GTK_CTREE_ROW(row)->children && + summary_have_unread_children(summaryview, row)))) + style = bold_style; + + /* set mark column */ + if (MSG_IS_DELETED(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_MARK], + deletedxpm, deletedxpmmask); + if (style) + style = bold_deleted_style; + else + gtk_ctree_node_set_foreground + (ctree, row, &summaryview->color_dim); + } else if (MSG_IS_MOVE(flags)) { + gtk_ctree_node_set_text(ctree, row, col_pos[S_COL_MARK], "o"); + if (style) + style = bold_marked_style; + else + gtk_ctree_node_set_foreground + (ctree, row, &summaryview->color_marked); + } else if (MSG_IS_COPY(flags)) { + gtk_ctree_node_set_text(ctree, row, col_pos[S_COL_MARK], "O"); + if (style) + style = bold_marked_style; + else + gtk_ctree_node_set_foreground + (ctree, row, &summaryview->color_marked); + } else if (MSG_IS_MARKED(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_MARK], + markxpm, markxpmmask); + } else { + gtk_ctree_node_set_text(ctree, row, col_pos[S_COL_MARK], NULL); + } + + if (MSG_IS_MIME(flags)) { + gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_MIME], + clipxpm, clipxpmmask); + } else { + gtk_ctree_node_set_text(ctree, row, col_pos[S_COL_MIME], NULL); + } + + gtk_ctree_node_set_row_style(ctree, row, style); + + if (MSG_GET_COLORLABEL(flags)) + summary_set_colorlabel_color(ctree, row, + MSG_GET_COLORLABEL_VALUE(flags)); +} + +void summary_set_marks_selected(SummaryView *summaryview) +{ + GList *cur; + + for (cur = GTK_CLIST(summaryview->ctree)->selection; cur != NULL; + cur = cur->next) + summary_set_row_marks(summaryview, GTK_CTREE_NODE(cur->data)); +} + +static void summary_mark_row(SummaryView *summaryview, GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + msginfo->to_folder = NULL; + if (MSG_IS_DELETED(msginfo->flags)) + summaryview->deleted--; + if (MSG_IS_MOVE(msginfo->flags)) + summaryview->moved--; + if (MSG_IS_COPY(msginfo->flags)) + summaryview->copied--; + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_DELETED); + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_MOVE | MSG_COPY); + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_MARKED); + summary_set_row_marks(summaryview, row); + debug_print(_("Message %d is marked\n"), msginfo->msgnum); +} + +void summary_mark(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GList *cur; + + for (cur = GTK_CLIST(ctree)->selection; cur != NULL; cur = cur->next) + summary_mark_row(summaryview, GTK_CTREE_NODE(cur->data)); + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_IMAP) { + GSList *msglist; + msglist = summary_get_selected_msg_list(summaryview); + imap_msg_list_set_perm_flags(msglist, MSG_MARKED); + g_slist_free(msglist); + } + + /* summary_step(summaryview, GTK_SCROLL_STEP_FORWARD); */ +} + +static void summary_mark_row_as_read(SummaryView *summaryview, + GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + if (MSG_IS_NEW(msginfo->flags)) + summaryview->folder_item->new--; + if (MSG_IS_UNREAD(msginfo->flags)) + summaryview->folder_item->unread--; + if (MSG_IS_NEW(msginfo->flags) || + MSG_IS_UNREAD(msginfo->flags)) { + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_NEW | MSG_UNREAD); + summary_set_row_marks(summaryview, row); + debug_print(_("Message %d is marked as being read\n"), + msginfo->msgnum); + } +} + +void summary_mark_as_read(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GList *cur; + + for (cur = GTK_CLIST(ctree)->selection; cur != NULL; cur = cur->next) + summary_mark_row_as_read(summaryview, + GTK_CTREE_NODE(cur->data)); + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_IMAP) { + GSList *msglist; + msglist = summary_get_selected_msg_list(summaryview); + imap_msg_list_unset_perm_flags(msglist, MSG_NEW | MSG_UNREAD); + g_slist_free(msglist); + } + + summary_status_show(summaryview); +} + +void summary_mark_all_read(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCList *clist = GTK_CLIST(summaryview->ctree); + GtkCTreeNode *node; + + gtk_clist_freeze(clist); + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); node != NULL; + node = gtkut_ctree_node_next(ctree, node)) + summary_mark_row_as_read(summaryview, node); + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); node != NULL; + node = gtkut_ctree_node_next(ctree, node)) { + if (!GTK_CTREE_ROW(node)->expanded) + summary_set_row_marks(summaryview, node); + } + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_IMAP) { + GSList *msglist; + msglist = summary_get_msg_list(summaryview); + imap_msg_list_unset_perm_flags(msglist, MSG_NEW | MSG_UNREAD); + g_slist_free(msglist); + } + gtk_clist_thaw(clist); + + summary_status_show(summaryview); +} + +static void summary_mark_row_as_unread(SummaryView *summaryview, + GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + if (MSG_IS_DELETED(msginfo->flags)) { + msginfo->to_folder = NULL; + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_DELETED); + summaryview->deleted--; + } + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_REPLIED | MSG_FORWARDED); + if (!MSG_IS_UNREAD(msginfo->flags)) { + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_UNREAD); + summaryview->folder_item->unread++; + debug_print(_("Message %d is marked as unread\n"), + msginfo->msgnum); + } + summary_set_row_marks(summaryview, row); +} + +void summary_mark_as_unread(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GList *cur; + + for (cur = GTK_CLIST(ctree)->selection; cur != NULL; cur = cur->next) + summary_mark_row_as_unread(summaryview, + GTK_CTREE_NODE(cur->data)); + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_IMAP) { + GSList *msglist; + msglist = summary_get_selected_msg_list(summaryview); + imap_msg_list_unset_perm_flags(msglist, MSG_REPLIED); + imap_msg_list_set_perm_flags(msglist, MSG_UNREAD); + g_slist_free(msglist); + } + + summary_status_show(summaryview); +} + +static void summary_delete_row(SummaryView *summaryview, GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + + if (MSG_IS_DELETED(msginfo->flags)) return; + + msginfo->to_folder = NULL; + if (MSG_IS_MOVE(msginfo->flags)) + summaryview->moved--; + if (MSG_IS_COPY(msginfo->flags)) + summaryview->copied--; + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_MOVE | MSG_COPY); + MSG_SET_PERM_FLAGS(msginfo->flags, MSG_DELETED); + summaryview->deleted++; + + if (!prefs_common.immediate_exec && + summaryview->folder_item->stype != F_TRASH) + summary_set_row_marks(summaryview, row); + + debug_print(_("Message %s/%d is set to delete\n"), + msginfo->folder->path, msginfo->msgnum); +} + +void summary_delete(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + FolderItem *item = summaryview->folder_item; + GList *cur; + GtkCTreeNode *sel_last = NULL; + GtkCTreeNode *node; + + if (!item || FOLDER_TYPE(item->folder) == F_NEWS) return; + + if (summary_is_locked(summaryview)) return; + + /* if current folder is trash, ask for confirmation */ + if (item->stype == F_TRASH) { + AlertValue aval; + + aval = alertpanel(_("Delete message(s)"), + _("Do you really want to delete message(s) from the trash?"), + _("Yes"), _("No"), NULL); + if (aval != G_ALERTDEFAULT) return; + } + + /* next code sets current row focus right. We need to find a row + * that is not deleted. */ + for (cur = GTK_CLIST(ctree)->selection; cur != NULL; cur = cur->next) { + sel_last = GTK_CTREE_NODE(cur->data); + summary_delete_row(summaryview, sel_last); + } + + node = summary_find_next_msg(summaryview, sel_last); + if (!node) + node = summary_find_prev_msg(summaryview, sel_last); + + if (node) { + if (sel_last && node == gtkut_ctree_node_next(ctree, sel_last)) + summary_step(summaryview, GTK_SCROLL_STEP_FORWARD); + else if (sel_last && node == GTK_CTREE_NODE_PREV(sel_last)) + summary_step(summaryview, GTK_SCROLL_STEP_BACKWARD); + else + summary_select_node + (summaryview, node, + messageview_is_visible(summaryview->messageview), + FALSE); + } + + if (prefs_common.immediate_exec || item->stype == F_TRASH) + summary_execute(summaryview); + else + summary_status_show(summaryview); +} + +void summary_delete_duplicated(SummaryView *summaryview) +{ + if (!summaryview->folder_item || + FOLDER_TYPE(summaryview->folder_item->folder) == F_NEWS) return; + if (summaryview->folder_item->stype == F_TRASH) return; + + main_window_cursor_wait(summaryview->mainwin); + debug_print(_("Deleting duplicated messages...")); + STATUSBAR_PUSH(summaryview->mainwin, + _("Deleting duplicated messages...")); + + gtk_ctree_pre_recursive(GTK_CTREE(summaryview->ctree), NULL, + GTK_CTREE_FUNC(summary_delete_duplicated_func), + summaryview); + + if (prefs_common.immediate_exec) + summary_execute(summaryview); + else + summary_status_show(summaryview); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + main_window_cursor_normal(summaryview->mainwin); +} + +static void summary_delete_duplicated_func(GtkCTree *ctree, GtkCTreeNode *node, + SummaryView *summaryview) +{ + GtkCTreeNode *found; + MsgInfo *msginfo; + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + + if (!msginfo || !msginfo->msgid || !*msginfo->msgid) return; + + found = g_hash_table_lookup(summaryview->msgid_table, msginfo->msgid); + + if (found && found != node) + summary_delete_row(summaryview, node); +} + +static void summary_unmark_row(SummaryView *summaryview, GtkCTreeNode *row) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + msginfo->to_folder = NULL; + if (MSG_IS_DELETED(msginfo->flags)) + summaryview->deleted--; + if (MSG_IS_MOVE(msginfo->flags)) + summaryview->moved--; + if (MSG_IS_COPY(msginfo->flags)) + summaryview->copied--; + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_MARKED | MSG_DELETED); + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_MOVE | MSG_COPY); + summary_set_row_marks(summaryview, row); + + debug_print(_("Message %s/%d is unmarked\n"), + msginfo->folder->path, msginfo->msgnum); +} + +void summary_unmark(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GList *cur; + + for (cur = GTK_CLIST(ctree)->selection; cur != NULL; cur = cur->next) + summary_unmark_row(summaryview, GTK_CTREE_NODE(cur->data)); + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_IMAP) { + GSList *msglist; + msglist = summary_get_selected_msg_list(summaryview); + imap_msg_list_unset_perm_flags(msglist, MSG_MARKED); + g_slist_free(msglist); + } + + summary_status_show(summaryview); +} + +static void summary_move_row_to(SummaryView *summaryview, GtkCTreeNode *row, + FolderItem *to_folder) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + g_return_if_fail(to_folder != NULL); + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + msginfo->to_folder = to_folder; + if (MSG_IS_DELETED(msginfo->flags)) + summaryview->deleted--; + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_DELETED); + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_COPY); + if (!MSG_IS_MOVE(msginfo->flags)) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_MOVE); + summaryview->moved++; + } + if (!prefs_common.immediate_exec) + summary_set_row_marks(summaryview, row); + + debug_print(_("Message %d is set to move to %s\n"), + msginfo->msgnum, to_folder->path); +} + +void summary_move_selected_to(SummaryView *summaryview, FolderItem *to_folder) +{ + GList *cur; + + if (!to_folder) return; + if (!summaryview->folder_item || + FOLDER_TYPE(summaryview->folder_item->folder) == F_NEWS) return; + + if (summary_is_locked(summaryview)) return; + + if (summaryview->folder_item == to_folder) { + alertpanel_warning(_("Destination is same as current folder.")); + return; + } + + for (cur = GTK_CLIST(summaryview->ctree)->selection; + cur != NULL; cur = cur->next) + summary_move_row_to + (summaryview, GTK_CTREE_NODE(cur->data), to_folder); + + summary_step(summaryview, GTK_SCROLL_STEP_FORWARD); + + if (prefs_common.immediate_exec) + summary_execute(summaryview); + else + summary_status_show(summaryview); +} + +void summary_move_to(SummaryView *summaryview) +{ + FolderItem *to_folder; + + if (!summaryview->folder_item || + FOLDER_TYPE(summaryview->folder_item->folder) == F_NEWS) return; + + to_folder = foldersel_folder_sel(summaryview->folder_item->folder, + FOLDER_SEL_MOVE, NULL); + summary_move_selected_to(summaryview, to_folder); +} + +static void summary_copy_row_to(SummaryView *summaryview, GtkCTreeNode *row, + FolderItem *to_folder) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + + g_return_if_fail(to_folder != NULL); + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + msginfo->to_folder = to_folder; + if (MSG_IS_DELETED(msginfo->flags)) + summaryview->deleted--; + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_DELETED); + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_MOVE); + if (!MSG_IS_COPY(msginfo->flags)) { + MSG_SET_TMP_FLAGS(msginfo->flags, MSG_COPY); + summaryview->copied++; + } + if (!prefs_common.immediate_exec) + summary_set_row_marks(summaryview, row); + + debug_print(_("Message %d is set to copy to %s\n"), + msginfo->msgnum, to_folder->path); +} + +void summary_copy_selected_to(SummaryView *summaryview, FolderItem *to_folder) +{ + GList *cur; + + if (!to_folder) return; + if (!summaryview->folder_item) return; + + if (summary_is_locked(summaryview)) return; + + if (summaryview->folder_item == to_folder) { + alertpanel_warning + (_("Destination for copy is same as current folder.")); + return; + } + + for (cur = GTK_CLIST(summaryview->ctree)->selection; + cur != NULL; cur = cur->next) + summary_copy_row_to + (summaryview, GTK_CTREE_NODE(cur->data), to_folder); + + summary_step(summaryview, GTK_SCROLL_STEP_FORWARD); + + if (prefs_common.immediate_exec) + summary_execute(summaryview); + else + summary_status_show(summaryview); +} + +void summary_copy_to(SummaryView *summaryview) +{ + FolderItem *to_folder; + + if (!summaryview->folder_item) return; + + to_folder = foldersel_folder_sel(summaryview->folder_item->folder, + FOLDER_SEL_COPY, NULL); + summary_copy_selected_to(summaryview, to_folder); +} + +void summary_add_address(SummaryView *summaryview) +{ + MsgInfo *msginfo; + gchar *from; + + msginfo = gtk_ctree_node_get_row_data(GTK_CTREE(summaryview->ctree), + summaryview->selected); + if (!msginfo) return; + + Xstrdup_a(from, msginfo->from, return); + eliminate_address_comment(from); + extract_address(from); + addressbook_add_contact(msginfo->fromname, from, NULL); +} + +void summary_select_all(SummaryView *summaryview) +{ + if (!summaryview->folder_item) return; + + if (summaryview->folder_item->total >= 500) { + STATUSBAR_PUSH(summaryview->mainwin, + _("Selecting all messages...")); + main_window_cursor_wait(summaryview->mainwin); + } + + gtk_clist_select_all(GTK_CLIST(summaryview->ctree)); + + if (summaryview->folder_item->total >= 500) { + STATUSBAR_POP(summaryview->mainwin); + main_window_cursor_normal(summaryview->mainwin); + } +} + +void summary_unselect_all(SummaryView *summaryview) +{ + gtk_sctree_unselect_all(GTK_SCTREE(summaryview->ctree)); +} + +void summary_select_thread(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node = summaryview->selected; + + if (!node) return; + + while (GTK_CTREE_ROW(node)->parent != NULL) + node = GTK_CTREE_ROW(node)->parent; + + if (node != summaryview->selected) + summary_select_node + (summaryview, node, + messageview_is_visible(summaryview->messageview), + FALSE); + + gtk_ctree_select_recursive(ctree, node); + + summary_status_show(summaryview); +} + +void summary_save_as(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + MsgInfo *msginfo; + gchar *filename = NULL; + gchar *src, *dest; + + if (!summaryview->selected) return; + msginfo = gtk_ctree_node_get_row_data(ctree, summaryview->selected); + if (!msginfo) return; + + if (msginfo->subject) { + Xstrdup_a(filename, msginfo->subject, return); + subst_for_filename(filename); + } + dest = filesel_select_file(_("Save as"), filename); + if (!dest) return; + if (is_file_exist(dest)) { + AlertValue aval; + + aval = alertpanel(_("Overwrite"), + _("Overwrite existing file?"), + _("OK"), _("Cancel"), NULL); + if (G_ALERTDEFAULT != aval) return; + } + + src = procmsg_get_message_file(msginfo); + if (copy_file(src, dest, TRUE) < 0) { + alertpanel_error(_("Can't save the file `%s'."), + g_basename(dest)); + } + g_free(src); +} + +void summary_print(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCList *clist = GTK_CLIST(summaryview->ctree); + MsgInfo *msginfo; + GList *cur; + gchar *cmdline; + gchar *p; + + if (clist->selection == NULL) return; + + cmdline = input_dialog(_("Print"), + _("Enter the print command line:\n" + "(`%s' will be replaced with file name)"), + prefs_common.print_cmd); + if (!cmdline) return; + if (!(p = strchr(cmdline, '%')) || *(p + 1) != 's' || + strchr(p + 2, '%')) { + alertpanel_error(_("Print command line is invalid:\n`%s'"), + cmdline); + g_free(cmdline); + return; + } + + for (cur = clist->selection; cur != NULL; cur = cur->next) { + msginfo = gtk_ctree_node_get_row_data + (ctree, GTK_CTREE_NODE(cur->data)); + if (msginfo) procmsg_print_message(msginfo, cmdline); + } + + g_free(cmdline); +} + +gboolean summary_execute(SummaryView *summaryview) +{ + GtkCList *clist = GTK_CLIST(summaryview->ctree); + gint val = 0; + + if (!summaryview->folder_item) return FALSE; + + if (summary_is_locked(summaryview)) return FALSE; + summary_lock(summaryview); + + gtk_clist_freeze(clist); + + val |= summary_execute_move(summaryview); + val |= summary_execute_copy(summaryview); + val |= summary_execute_delete(summaryview); + + statusbar_pop_all(); + STATUSBAR_POP(summaryview->mainwin); + + summary_remove_invalid_messages(summaryview); + + gtk_clist_thaw(clist); + + summary_unlock(summaryview); + + if (val != 0) { + alertpanel_error(_("Error occurred while processing messages.")); + } + + return TRUE; +} + +static void summary_remove_invalid_messages(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCList *clist = GTK_CLIST(summaryview->ctree); + GtkCTreeNode *node, *next; + GtkCTreeNode *new_selected = NULL; + + gtk_clist_freeze(clist); + + if (summaryview->folder_item->threaded) + summary_unthread_for_exec(summaryview); + + node = GTK_CTREE_NODE(clist->row_list); + for (; node != NULL; node = next) { + MsgInfo *msginfo; + + next = gtkut_ctree_node_next(ctree, node); + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + if (!msginfo || !MSG_IS_INVALID(msginfo->flags)) + continue; + + if (node == summaryview->displayed) { + messageview_clear(summaryview->messageview); + summaryview->displayed = NULL; + } + if (GTK_CTREE_ROW(node)->children != NULL) { + g_warning("summary_execute(): children != NULL\n"); + continue; + } + + if (!new_selected && + gtkut_ctree_node_is_selected(ctree, node)) { + gtk_sctree_unselect_all(GTK_SCTREE(ctree)); + new_selected = summary_find_next_msg(summaryview, node); + if (!new_selected) + new_selected = summary_find_prev_msg + (summaryview, node); + } + + if (msginfo->msgid && *msginfo->msgid && + node == g_hash_table_lookup(summaryview->msgid_table, + msginfo->msgid)) + g_hash_table_remove(summaryview->msgid_table, + msginfo->msgid); + + gtk_ctree_remove_node(ctree, node); + procmsg_msginfo_free(msginfo); + } + + if (new_selected) { + gtk_sctree_select + (GTK_SCTREE(ctree), + summaryview->displayed ? summaryview->displayed + : new_selected); + } + + if (summaryview->folder_item->threaded) + summary_thread_build(summaryview); + + summaryview->selected = clist->selection ? + GTK_CTREE_NODE(clist->selection->data) : NULL; + + if (!GTK_CLIST(summaryview->ctree)->row_list) { + menu_set_insensitive_all + (GTK_MENU_SHELL(summaryview->popupmenu)); + gtk_widget_grab_focus(summaryview->folderview->ctree); + } else + gtk_widget_grab_focus(summaryview->ctree); + + summary_write_cache(summaryview); + + summary_update_status(summaryview); + summary_status_show(summaryview); + + gtk_ctree_node_moveto(ctree, summaryview->selected, -1, 0.5, 0); + + gtk_clist_thaw(clist); +} + +static gint summary_execute_move(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + gint val = 0; + + summaryview->folder_table = g_hash_table_new(NULL, NULL); + + /* search moving messages and execute */ + gtk_ctree_pre_recursive(ctree, NULL, summary_execute_move_func, + summaryview); + + if (summaryview->mlist) { + summaryview->mlist = g_slist_reverse(summaryview->mlist); + val = procmsg_move_messages(summaryview->mlist); + + folder_item_scan_foreach(summaryview->folder_table); + folderview_update_item_foreach(summaryview->folder_table, + FALSE); + + g_slist_free(summaryview->mlist); + summaryview->mlist = NULL; + } + + g_hash_table_destroy(summaryview->folder_table); + summaryview->folder_table = NULL; + + return val; +} + +static void summary_execute_move_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + SummaryView *summaryview = data; + MsgInfo *msginfo; + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + + if (msginfo && MSG_IS_MOVE(msginfo->flags) && msginfo->to_folder) { + g_hash_table_insert(summaryview->folder_table, + msginfo->to_folder, GINT_TO_POINTER(1)); + + summaryview->mlist = + g_slist_prepend(summaryview->mlist, msginfo); + + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_MOVE); + summary_set_row_marks(summaryview, node); + } +} + +static gint summary_execute_copy(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + gint val = 0; + + summaryview->folder_table = g_hash_table_new(NULL, NULL); + + /* search copying messages and execute */ + gtk_ctree_pre_recursive(ctree, NULL, summary_execute_copy_func, + summaryview); + + if (summaryview->mlist) { + summaryview->mlist = g_slist_reverse(summaryview->mlist); + val = procmsg_copy_messages(summaryview->mlist); + + folder_item_scan_foreach(summaryview->folder_table); + folderview_update_item_foreach(summaryview->folder_table, + FALSE); + + g_slist_free(summaryview->mlist); + summaryview->mlist = NULL; + } + + g_hash_table_destroy(summaryview->folder_table); + summaryview->folder_table = NULL; + + return val; +} + +static void summary_execute_copy_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + SummaryView *summaryview = data; + MsgInfo *msginfo; + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + + if (msginfo && MSG_IS_COPY(msginfo->flags) && msginfo->to_folder) { + g_hash_table_insert(summaryview->folder_table, + msginfo->to_folder, GINT_TO_POINTER(1)); + + summaryview->mlist = + g_slist_prepend(summaryview->mlist, msginfo); + + MSG_UNSET_TMP_FLAGS(msginfo->flags, MSG_COPY); + summary_set_row_marks(summaryview, node); + } +} + +static gint summary_execute_delete(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + FolderItem *trash; + gint val; + + trash = summaryview->folder_item->folder->trash; + if (FOLDER_TYPE(summaryview->folder_item->folder) == F_MH) { + g_return_val_if_fail(trash != NULL, 0); + } + + /* search deleting messages and execute */ + gtk_ctree_pre_recursive + (ctree, NULL, summary_execute_delete_func, summaryview); + + if (!summaryview->mlist) return 0; + + summaryview->mlist = g_slist_reverse(summaryview->mlist); + + if (summaryview->folder_item != trash) + val = folder_item_move_msgs(trash, summaryview->mlist); + else + val = folder_item_remove_msgs(trash, summaryview->mlist); + + g_slist_free(summaryview->mlist); + summaryview->mlist = NULL; + + if (summaryview->folder_item != trash) { + folder_item_scan(trash); + folderview_update_item(trash, FALSE); + } + + return val == -1 ? -1 : 0; +} + +static void summary_execute_delete_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + SummaryView *summaryview = data; + MsgInfo *msginfo; + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + + if (msginfo && MSG_IS_DELETED(msginfo->flags)) { + summaryview->mlist = + g_slist_prepend(summaryview->mlist, msginfo); + } +} + +/* thread functions */ + +void summary_thread_build(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + GtkCTreeNode *next; + GtkCTreeNode *parent; + MsgInfo *msginfo; + + summary_lock(summaryview); + + debug_print(_("Building threads...")); + STATUSBAR_PUSH(summaryview->mainwin, _("Building threads...")); + main_window_cursor_wait(summaryview->mainwin); + + g_signal_handlers_block_by_func(G_OBJECT(ctree), + G_CALLBACK(summary_tree_expanded), + summaryview); + gtk_clist_freeze(GTK_CLIST(ctree)); + + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + while (node) { + next = GTK_CTREE_ROW(node)->sibling; + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + if (msginfo && msginfo->inreplyto) { + parent = g_hash_table_lookup(summaryview->msgid_table, + msginfo->inreplyto); + if (parent && parent != node) { + gtk_ctree_move(ctree, node, parent, NULL); + gtk_ctree_expand(ctree, node); + } + } + + node = next; + } + + node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + while (node) { + next = GTK_CTREE_NODE_NEXT(node); + if (prefs_common.expand_thread) + gtk_ctree_expand(ctree, node); + if (prefs_common.bold_unread && + GTK_CTREE_ROW(node)->children) + summary_set_row_marks(summaryview, node); + node = next; + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + g_signal_handlers_unblock_by_func(G_OBJECT(ctree), + G_CALLBACK(summary_tree_expanded), + summaryview); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + main_window_cursor_normal(summaryview->mainwin); + + summary_unlock(summaryview); +} + +static void summary_thread_init(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + GtkCTreeNode *next; + + if (prefs_common.expand_thread) { + while (node) { + next = GTK_CTREE_ROW(node)->sibling; + if (GTK_CTREE_ROW(node)->children) + gtk_ctree_expand(ctree, node); + node = next; + } + } else if (prefs_common.bold_unread) { + while (node) { + next = GTK_CTREE_ROW(node)->sibling; + if (GTK_CTREE_ROW(node)->children) + summary_set_row_marks(summaryview, node); + node = next; + } + } +} + +void summary_unthread(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node; + GtkCTreeNode *child; + GtkCTreeNode *sibling; + GtkCTreeNode *next_child; + + summary_lock(summaryview); + + debug_print(_("Unthreading...")); + STATUSBAR_PUSH(summaryview->mainwin, _("Unthreading...")); + main_window_cursor_wait(summaryview->mainwin); + + g_signal_handlers_block_by_func(G_OBJECT(ctree), + G_CALLBACK(summary_tree_collapsed), + summaryview); + gtk_clist_freeze(GTK_CLIST(ctree)); + + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node != NULL; node = GTK_CTREE_NODE_NEXT(node)) { + child = GTK_CTREE_ROW(node)->children; + sibling = GTK_CTREE_ROW(node)->sibling; + + while (child != NULL) { + next_child = GTK_CTREE_ROW(child)->sibling; + gtk_ctree_move(ctree, child, NULL, sibling); + child = next_child; + } + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + g_signal_handlers_unblock_by_func(G_OBJECT(ctree), + G_CALLBACK(summary_tree_collapsed), + summaryview); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + main_window_cursor_normal(summaryview->mainwin); + + summary_unlock(summaryview); +} + +static void summary_unthread_for_exec(SummaryView *summaryview) +{ + GtkCTreeNode *node; + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + + summary_lock(summaryview); + + debug_print(_("Unthreading for execution...")); + + gtk_clist_freeze(GTK_CLIST(ctree)); + + for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + node != NULL; node = gtkut_ctree_node_next(ctree, node)) { + summary_unthread_for_exec_func(ctree, node, NULL); + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + + debug_print(_("done.\n")); + + summary_unlock(summaryview); +} + +static void summary_unthread_for_exec_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + MsgInfo *msginfo; + GtkCTreeNode *top_parent; + GtkCTreeNode *child; + GtkCTreeNode *sibling; + + msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + + if (!msginfo || !MSG_IS_INVALID(msginfo->flags)) + return; + child = GTK_CTREE_ROW(node)->children; + if (!child) return; + + for (top_parent = node; + GTK_CTREE_ROW(top_parent)->parent != NULL; + top_parent = GTK_CTREE_ROW(top_parent)->parent) + ; + sibling = GTK_CTREE_ROW(top_parent)->sibling; + + while (child != NULL) { + GtkCTreeNode *next_child; + + next_child = GTK_CTREE_ROW(child)->sibling; + gtk_ctree_move(ctree, child, NULL, sibling); + child = next_child; + } +} + +void summary_expand_threads(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + gtk_clist_freeze(GTK_CLIST(ctree)); + + while (node) { + if (GTK_CTREE_ROW(node)->children) + gtk_ctree_expand(ctree, node); + node = GTK_CTREE_NODE_NEXT(node); + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + + gtk_ctree_node_moveto(ctree, summaryview->selected, -1, 0.5, 0); +} + +void summary_collapse_threads(SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCTreeNode *node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); + + gtk_clist_freeze(GTK_CLIST(ctree)); + + while (node) { + if (GTK_CTREE_ROW(node)->children) + gtk_ctree_collapse(ctree, node); + node = GTK_CTREE_ROW(node)->sibling; + } + + gtk_clist_thaw(GTK_CLIST(ctree)); + + gtk_ctree_node_moveto(ctree, summaryview->selected, -1, 0.5, 0); +} + +void summary_filter(SummaryView *summaryview, gboolean selected_only) +{ + if (!prefs_common.fltlist) return; + + summary_lock(summaryview); + + STATUSBAR_POP(summaryview->mainwin); + + debug_print(_("filtering...")); + STATUSBAR_PUSH(summaryview->mainwin, _("Filtering...")); + main_window_cursor_wait(summaryview->mainwin); + + gtk_clist_freeze(GTK_CLIST(summaryview->ctree)); + + summaryview->filtered = 0; + + if (selected_only) { + GList *cur; + + for (cur = GTK_CLIST(summaryview->ctree)->selection; + cur != NULL; cur = cur->next) { + summary_filter_func(GTK_CTREE(summaryview->ctree), + GTK_CTREE_NODE(cur->data), + summaryview); + } + } else { + gtk_ctree_pre_recursive(GTK_CTREE(summaryview->ctree), NULL, + GTK_CTREE_FUNC(summary_filter_func), + summaryview); + } + + summary_unlock(summaryview); + + if (prefs_common.immediate_exec) + summary_execute(summaryview); + else + summary_status_show(summaryview); + + folderview_update_all_updated(FALSE); + + gtk_clist_thaw(GTK_CLIST(summaryview->ctree)); + + debug_print(_("done.\n")); + STATUSBAR_POP(summaryview->mainwin); + main_window_cursor_normal(summaryview->mainwin); + + if (summaryview->filtered > 0) { + gchar result_msg[BUFFSIZE]; + g_snprintf(result_msg, sizeof(result_msg), + _("%d message(s) have been filtered."), + summaryview->filtered); + STATUSBAR_PUSH(summaryview->mainwin, result_msg); + } + summaryview->filtered = 0; +} + +static void summary_filter_func(GtkCTree *ctree, GtkCTreeNode *node, + gpointer data) +{ + MsgInfo *msginfo = GTKUT_CTREE_NODE_GET_ROW_DATA(node); + SummaryView *summaryview = (SummaryView *)data; + FilterInfo *fltinfo; + + fltinfo = filter_info_new(); + fltinfo->flags = msginfo->flags; + filter_apply_msginfo(prefs_common.fltlist, msginfo, fltinfo); + if (fltinfo->actions[FLT_ACTION_MOVE] || + fltinfo->actions[FLT_ACTION_COPY] || + fltinfo->actions[FLT_ACTION_DELETE] || + fltinfo->actions[FLT_ACTION_EXEC] || + fltinfo->actions[FLT_ACTION_EXEC_ASYNC] || + fltinfo->actions[FLT_ACTION_MARK] || + fltinfo->actions[FLT_ACTION_COLOR_LABEL] || + fltinfo->actions[FLT_ACTION_MARK_READ] || + fltinfo->actions[FLT_ACTION_FORWARD] || + fltinfo->actions[FLT_ACTION_FORWARD_AS_ATTACHMENT] || + fltinfo->actions[FLT_ACTION_REDIRECT]) + summaryview->filtered++; + + if ((fltinfo->actions[FLT_ACTION_MARK] || + fltinfo->actions[FLT_ACTION_COLOR_LABEL] || + fltinfo->actions[FLT_ACTION_MARK_READ])) { + msginfo->flags = fltinfo->flags; + summary_set_row_marks(summaryview, node); + } + + if (fltinfo->actions[FLT_ACTION_MOVE] && fltinfo->move_dest) + summary_move_row_to(summaryview, node, fltinfo->move_dest); + else if (fltinfo->actions[FLT_ACTION_DELETE]) + summary_delete_row(summaryview, node); + + filter_info_free(fltinfo); +} + +void summary_filter_open(SummaryView *summaryview, PrefsFilterType type) +{ + MsgInfo *msginfo; + gchar *header = NULL; + gchar *key = NULL; + + if (!summaryview->selected) return; + + msginfo = gtk_ctree_node_get_row_data(GTK_CTREE(summaryview->ctree), + summaryview->selected); + if (!msginfo) return; + + procmsg_get_filter_keyword(msginfo, &header, &key, type); + prefs_filter_open(msginfo, header); + + g_free(header); + g_free(key); +} + +void summary_reply(SummaryView *summaryview, ComposeMode mode) +{ + GList *sel = GTK_CLIST(summaryview->ctree)->selection; + GSList *mlist = NULL; + MsgInfo *msginfo; + MsgInfo *displayed_msginfo = NULL; + gchar *text = NULL; + + for (; sel != NULL; sel = sel->next) { + mlist = g_slist_append(mlist, + gtk_ctree_node_get_row_data + (GTK_CTREE(summaryview->ctree), + GTK_CTREE_NODE(sel->data))); + } + if (!mlist) return; + msginfo = (MsgInfo *)mlist->data; + + if (summaryview->displayed) { + displayed_msginfo = gtk_ctree_node_get_row_data + (GTK_CTREE(summaryview->ctree), summaryview->displayed); + } + + /* use selection only if the displayed message is selected */ + if (!mlist->next && msginfo == displayed_msginfo) { + text = gtkut_editable_get_selection + (GTK_EDITABLE(summaryview->messageview->textview->text)); + if (text && *text == '\0') { + g_free(text); + text = NULL; + } + } + + if (!COMPOSE_QUOTE_MODE(mode)) + mode |= prefs_common.reply_with_quote + ? COMPOSE_WITH_QUOTE : COMPOSE_WITHOUT_QUOTE; + + switch (COMPOSE_MODE(mode)) { + case COMPOSE_REPLY: + case COMPOSE_REPLY_TO_SENDER: + case COMPOSE_REPLY_TO_ALL: + case COMPOSE_REPLY_TO_LIST: + compose_reply(msginfo, summaryview->folder_item, mode, text); + break; + case COMPOSE_FORWARD: + compose_forward(mlist, summaryview->folder_item, FALSE, text); + break; + case COMPOSE_FORWARD_AS_ATTACH: + compose_forward(mlist, summaryview->folder_item, TRUE, NULL); + break; + case COMPOSE_REDIRECT: + compose_redirect(msginfo, summaryview->folder_item); + break; + default: + g_warning("summary_reply(): invalid mode: %d\n", mode); + } + + summary_set_marks_selected(summaryview); + g_free(text); + g_slist_free(mlist); +} + +/* color label */ + +#define N_COLOR_LABELS colorlabel_get_color_count() + +static void summary_colorlabel_menu_item_activate_cb(GtkWidget *widget, + gpointer data) +{ + guint color = GPOINTER_TO_UINT(data); + SummaryView *summaryview; + + summaryview = g_object_get_data(G_OBJECT(widget), "summaryview"); + g_return_if_fail(summaryview != NULL); + + /* "dont_toggle" state set? */ + if (g_object_get_data(G_OBJECT(summaryview->colorlabel_menu), + "dont_toggle")) + return; + + summary_set_colorlabel(summaryview, color, NULL); +} + +/* summary_set_colorlabel_color() - labelcolor parameter is the color *flag* + * for the messsage; not the color index */ +void summary_set_colorlabel_color(GtkCTree *ctree, GtkCTreeNode *node, + guint labelcolor) +{ + GdkColor color; + GtkStyle *style, *prev_style, *ctree_style; + MsgInfo *msginfo; + gint color_index; + + msginfo = gtk_ctree_node_get_row_data(ctree, node); + MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_CLABEL_FLAG_MASK); + MSG_SET_COLORLABEL_VALUE(msginfo->flags, labelcolor); + + color_index = labelcolor == 0 ? -1 : (gint)labelcolor - 1; + ctree_style = gtk_widget_get_style(GTK_WIDGET(ctree)); + prev_style = gtk_ctree_node_get_row_style(ctree, node); + + if (color_index < 0 || color_index >= N_COLOR_LABELS) { + if (!prev_style) return; + style = gtk_style_copy(prev_style); + color = ctree_style->fg[GTK_STATE_NORMAL]; + style->fg[GTK_STATE_NORMAL] = color; + color = ctree_style->fg[GTK_STATE_SELECTED]; + style->fg[GTK_STATE_SELECTED] = color; + } else { + if (prev_style) + style = gtk_style_copy(prev_style); + else + style = gtk_style_copy(ctree_style); + color = colorlabel_get_color(color_index); + style->fg[GTK_STATE_NORMAL] = color; + /* get the average of label color and selected fg color + for visibility */ + style->fg[GTK_STATE_SELECTED].red = (color.red + ctree_style->fg[GTK_STATE_SELECTED].red ) / 2; + style->fg[GTK_STATE_SELECTED].green = (color.green + ctree_style->fg[GTK_STATE_SELECTED].green) / 2; + style->fg[GTK_STATE_SELECTED].blue = (color.blue + ctree_style->fg[GTK_STATE_SELECTED].blue ) / 2; + } + + gtk_ctree_node_set_row_style(ctree, node, style); +} + +void summary_set_colorlabel(SummaryView *summaryview, guint labelcolor, + GtkWidget *widget) +{ + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GtkCList *clist = GTK_CLIST(summaryview->ctree); + GList *cur; + + for (cur = clist->selection; cur != NULL; cur = cur->next) + summary_set_colorlabel_color(ctree, GTK_CTREE_NODE(cur->data), + labelcolor); +} + +static void summary_colorlabel_menu_item_activate_item_cb(GtkMenuItem *menuitem, + gpointer data) +{ + SummaryView *summaryview; + GtkMenuShell *menu; + GtkCheckMenuItem **items; + gint n; + GList *cur, *sel; + + summaryview = (SummaryView *)data; + g_return_if_fail(summaryview != NULL); + + sel = GTK_CLIST(summaryview->ctree)->selection; + if (!sel) return; + + menu = GTK_MENU_SHELL(summaryview->colorlabel_menu); + g_return_if_fail(menu != NULL); + + Xalloca(items, (N_COLOR_LABELS + 1) * sizeof(GtkWidget *), return); + + /* NOTE: don't return prematurely because we set the "dont_toggle" + * state for check menu items */ + g_object_set_data(G_OBJECT(menu), "dont_toggle", GINT_TO_POINTER(1)); + + /* clear items. get item pointers. */ + for (n = 0, cur = menu->children; cur != NULL; cur = cur->next) { + if (GTK_IS_CHECK_MENU_ITEM(cur->data)) { + gtk_check_menu_item_set_state + (GTK_CHECK_MENU_ITEM(cur->data), FALSE); + items[n] = GTK_CHECK_MENU_ITEM(cur->data); + n++; + } + } + + if (n == (N_COLOR_LABELS + 1)) { + /* iterate all messages and set the state of the appropriate + * items */ + for (; sel != NULL; sel = sel->next) { + MsgInfo *msginfo; + gint clabel; + + msginfo = gtk_ctree_node_get_row_data + (GTK_CTREE(summaryview->ctree), + GTK_CTREE_NODE(sel->data)); + if (msginfo) { + clabel = MSG_GET_COLORLABEL_VALUE(msginfo->flags); + if (!items[clabel]->active) + gtk_check_menu_item_set_state + (items[clabel], TRUE); + } + } + } else + g_warning("invalid number of color elements (%d)\n", n); + + /* reset "dont_toggle" state */ + g_object_set_data(G_OBJECT(menu), "dont_toggle", GINT_TO_POINTER(0)); +} + +static void summary_colorlabel_menu_create(SummaryView *summaryview) +{ + GtkWidget *label_menuitem; + GtkWidget *menu; + GtkWidget *item; + gint i; + + label_menuitem = gtk_item_factory_get_item(summaryview->popupfactory, + "/Color label"); + g_signal_connect(G_OBJECT(label_menuitem), "activate", + G_CALLBACK(summary_colorlabel_menu_item_activate_item_cb), + summaryview); + gtk_widget_show(label_menuitem); + + menu = gtk_menu_new(); + + /* create sub items. for the menu item activation callback we pass the + * index of label_colors[] as data parameter. for the None color we + * pass an invalid (high) value. also we attach a data pointer so we + * can always get back the SummaryView pointer. */ + + item = gtk_check_menu_item_new_with_label(_("None")); + gtk_menu_append(GTK_MENU(menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(summary_colorlabel_menu_item_activate_cb), + GUINT_TO_POINTER(0)); + g_object_set_data(G_OBJECT(item), "summaryview", summaryview); + gtk_widget_show(item); + + item = gtk_menu_item_new(); + gtk_menu_append(GTK_MENU(menu), item); + gtk_widget_show(item); + + /* create pixmap/label menu items */ + for (i = 0; i < N_COLOR_LABELS; i++) { + item = colorlabel_create_check_color_menu_item(i); + gtk_menu_append(GTK_MENU(menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(summary_colorlabel_menu_item_activate_cb), + GUINT_TO_POINTER(i + 1)); + g_object_set_data(G_OBJECT(item), "summaryview", summaryview); + gtk_widget_show(item); + } + + gtk_widget_show(menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(label_menuitem), menu); + summaryview->colorlabel_menu = menu; +} + +static GtkWidget *summary_ctree_create(SummaryView *summaryview) +{ + GtkWidget *ctree; + gint *col_pos = summaryview->col_pos; + SummaryColumnState *col_state; + gchar *titles[N_SUMMARY_COLS]; + SummaryColumnType type; + gint pos; + + memset(titles, 0, sizeof(titles)); + + col_state = prefs_summary_column_get_config(); + for (pos = 0; pos < N_SUMMARY_COLS; pos++) { + summaryview->col_state[pos] = col_state[pos]; + type = col_state[pos].type; + col_pos[type] = pos; + } + col_state = summaryview->col_state; + + ctree = gtk_sctree_new_with_titles + (N_SUMMARY_COLS, col_pos[S_COL_SUBJECT], titles); + + gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_EXTENDED); + gtk_clist_set_column_justification(GTK_CLIST(ctree), col_pos[S_COL_MARK], + GTK_JUSTIFY_CENTER); + gtk_clist_set_column_justification(GTK_CLIST(ctree), col_pos[S_COL_UNREAD], + GTK_JUSTIFY_CENTER); + gtk_clist_set_column_justification(GTK_CLIST(ctree), col_pos[S_COL_MIME], + GTK_JUSTIFY_CENTER); + gtk_clist_set_column_justification(GTK_CLIST(ctree), col_pos[S_COL_SIZE], + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_justification(GTK_CLIST(ctree), col_pos[S_COL_NUMBER], + GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_MARK], + SUMMARY_COL_MARK_WIDTH); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_UNREAD], + SUMMARY_COL_UNREAD_WIDTH); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_MIME], + SUMMARY_COL_MIME_WIDTH); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_SUBJECT], + prefs_common.summary_col_size[S_COL_SUBJECT]); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_FROM], + prefs_common.summary_col_size[S_COL_FROM]); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_DATE], + prefs_common.summary_col_size[S_COL_DATE]); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_SIZE], + prefs_common.summary_col_size[S_COL_SIZE]); + gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_NUMBER], + prefs_common.summary_col_size[S_COL_NUMBER]); + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED); + gtk_ctree_set_expander_style(GTK_CTREE(ctree), + GTK_CTREE_EXPANDER_SQUARE); +#if 0 + gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_NONE); + gtk_ctree_set_expander_style(GTK_CTREE(ctree), + GTK_CTREE_EXPANDER_TRIANGLE); +#endif + gtk_ctree_set_indent(GTK_CTREE(ctree), 16); + g_object_set_data(G_OBJECT(ctree), "user_data", summaryview); + + for (pos = 0; pos < N_SUMMARY_COLS; pos++) { + GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[pos].button, + GTK_CAN_FOCUS); + gtk_clist_set_column_visibility + (GTK_CLIST(ctree), pos, col_state[pos].visible); + } + + /* connect signal to the buttons for sorting */ +#define CLIST_BUTTON_SIGNAL_CONNECT(col, func) \ + g_signal_connect \ + (G_OBJECT(GTK_CLIST(ctree)->column[col_pos[col]].button), \ + "clicked", \ + G_CALLBACK(func), \ + summaryview) + + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_MARK , summary_mark_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_UNREAD , summary_unread_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_MIME , summary_mime_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_NUMBER , summary_num_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_SIZE , summary_size_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_DATE , summary_date_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_FROM , summary_from_clicked); + CLIST_BUTTON_SIGNAL_CONNECT(S_COL_SUBJECT, summary_subject_clicked); + +#undef CLIST_BUTTON_SIGNAL_CONNECT + + g_signal_connect(G_OBJECT(ctree), "tree_select_row", + G_CALLBACK(summary_selected), summaryview); + g_signal_connect(G_OBJECT(ctree), "button_press_event", + G_CALLBACK(summary_button_pressed), summaryview); + g_signal_connect(G_OBJECT(ctree), "button_release_event", + G_CALLBACK(summary_button_released), summaryview); + g_signal_connect(G_OBJECT(ctree), "key_press_event", + G_CALLBACK(summary_key_pressed), summaryview); + g_signal_connect(G_OBJECT(ctree), "resize_column", + G_CALLBACK(summary_col_resized), summaryview); + g_signal_connect(G_OBJECT(ctree), "open_row", + G_CALLBACK(summary_open_row), summaryview); + + g_signal_connect_after(G_OBJECT(ctree), "tree_expand", + G_CALLBACK(summary_tree_expanded), + summaryview); + g_signal_connect_after(G_OBJECT(ctree), "tree_collapse", + G_CALLBACK(summary_tree_collapsed), + summaryview); + + g_signal_connect(G_OBJECT(ctree), "start_drag", + G_CALLBACK(summary_start_drag), summaryview); + g_signal_connect(G_OBJECT(ctree), "drag_data_get", + G_CALLBACK(summary_drag_data_get), summaryview); + + return ctree; +} + +void summary_set_column_order(SummaryView *summaryview) +{ + GtkWidget *ctree; + GtkWidget *scrolledwin = summaryview->scrolledwin; + GtkWidget *pixmap; + FolderItem *item; + + item = summaryview->folder_item; + summary_clear_all(summaryview); + gtk_widget_destroy(summaryview->ctree); + + summaryview->ctree = ctree = summary_ctree_create(summaryview); + pixmap = gtk_pixmap_new(clipxpm, clipxpmmask); + gtk_clist_set_column_widget(GTK_CLIST(ctree), + summaryview->col_pos[S_COL_MIME], pixmap); + gtk_widget_show(pixmap); + gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_CLIST(ctree)->hadjustment); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_CLIST(ctree)->vadjustment); + gtk_container_add(GTK_CONTAINER(scrolledwin), ctree); + gtk_widget_show(ctree); + + summary_show(summaryview, item, FALSE); +} + + +/* callback functions */ + +static gboolean summary_toggle_pressed(GtkWidget *eventbox, + GdkEventButton *event, + SummaryView *summaryview) +{ + if (!event) return FALSE; + + summary_toggle_view(summaryview); + return FALSE; +} + +static gboolean summary_button_pressed(GtkWidget *ctree, GdkEventButton *event, + SummaryView *summaryview) +{ + if (!event) return FALSE; + + if (event->button == 3) { + /* right clicked */ + gtk_menu_popup(GTK_MENU(summaryview->popupmenu), NULL, NULL, + NULL, NULL, event->button, event->time); + } else if (event->button == 2) { + summaryview->display_msg = TRUE; + } else if (event->button == 1) { + if (!prefs_common.emulate_emacs && + messageview_is_visible(summaryview->messageview)) + summaryview->display_msg = TRUE; + } + + return FALSE; +} + +static gboolean summary_button_released(GtkWidget *ctree, GdkEventButton *event, + SummaryView *summaryview) +{ + return FALSE; +} + +void summary_pass_key_press_event(SummaryView *summaryview, GdkEventKey *event) +{ + summary_key_pressed(summaryview->ctree, event, summaryview); +} + +#define BREAK_ON_MODIFIER_KEY() \ + if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break + +static gboolean summary_key_pressed(GtkWidget *widget, GdkEventKey *event, + SummaryView *summaryview) +{ + GtkCTree *ctree = GTK_CTREE(widget); + GtkCTreeNode *node; + MessageView *messageview; + TextView *textview; + GtkAdjustment *adj; + gboolean mod_pressed; + + if (summary_is_locked(summaryview)) return FALSE; + if (!event) return FALSE; + + switch (event->keyval) { + case GDK_Left: /* Move focus */ + adj = gtk_scrolled_window_get_hadjustment + (GTK_SCROLLED_WINDOW(summaryview->scrolledwin)); + if (adj->lower != adj->value) + break; + /* FALLTHROUGH */ + case GDK_Escape: + gtk_widget_grab_focus(summaryview->folderview->ctree); + return FALSE; + default: + break; + } + + if (!summaryview->selected) { + node = gtk_ctree_node_nth(ctree, 0); + if (node) + gtk_sctree_select(GTK_SCTREE(ctree), node); + else + return FALSE; + } + + messageview = summaryview->messageview; + if (messageview->type == MVIEW_MIME && + gtk_notebook_get_current_page + (GTK_NOTEBOOK(messageview->mimeview->notebook)) == 1) + textview = messageview->mimeview->textview; + else + textview = messageview->textview; + + switch (event->keyval) { + case GDK_space: /* Page down or go to the next */ + if (summaryview->displayed != summaryview->selected) { + summary_display_msg(summaryview, + summaryview->selected); + break; + } + mod_pressed = + ((event->state & (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0); + if (mod_pressed) { + if (!textview_scroll_page(textview, TRUE)) + summary_select_prev_unread(summaryview); + } else { + if (!textview_scroll_page(textview, FALSE)) + summary_select_next_unread(summaryview); + } + break; + case GDK_BackSpace: /* Page up */ + textview_scroll_page(textview, TRUE); + break; + case GDK_Return: /* Scroll up/down one line */ + if (summaryview->displayed != summaryview->selected) { + summary_display_msg(summaryview, + summaryview->selected); + break; + } + textview_scroll_one_line + (textview, (event->state & + (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0); + break; + case GDK_Delete: + BREAK_ON_MODIFIER_KEY(); + summary_delete(summaryview); + break; + default: + break; + } + + return FALSE; +} + +static void summary_open_row(GtkSCTree *sctree, SummaryView *summaryview) +{ + if (summaryview->folder_item->stype == F_OUTBOX || + summaryview->folder_item->stype == F_DRAFT || + summaryview->folder_item->stype == F_QUEUE) + summary_reedit(summaryview); + else + summary_open_msg(summaryview); + + summaryview->display_msg = FALSE; +} + +static void summary_tree_expanded(GtkCTree *ctree, GtkCTreeNode *node, + SummaryView *summaryview) +{ + summary_set_row_marks(summaryview, node); +} + +static void summary_tree_collapsed(GtkCTree *ctree, GtkCTreeNode *node, + SummaryView *summaryview) +{ + summary_set_row_marks(summaryview, node); +} + +static void summary_selected(GtkCTree *ctree, GtkCTreeNode *row, + gint column, SummaryView *summaryview) +{ + MsgInfo *msginfo; + + summary_status_show(summaryview); + + if (GTK_CLIST(ctree)->selection && + GTK_CLIST(ctree)->selection->next) { + summaryview->display_msg = FALSE; + summary_set_menu_sensitive(summaryview); + main_window_set_toolbar_sensitive(summaryview->mainwin); + return; + } + + summaryview->selected = row; + + msginfo = gtk_ctree_node_get_row_data(ctree, row); + g_return_if_fail(msginfo != NULL); + + switch (column < 0 ? column : summaryview->col_state[column].type) { + case S_COL_MARK: + if (!MSG_IS_DELETED(msginfo->flags) && + !MSG_IS_MOVE(msginfo->flags) && + !MSG_IS_COPY(msginfo->flags)) { + if (MSG_IS_MARKED(msginfo->flags)) { + summary_unmark_row(summaryview, row); + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_unset_perm_flags(msginfo, + MSG_MARKED); + } else { + summary_mark_row(summaryview, row); + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_set_perm_flags(msginfo, + MSG_MARKED); + } + } + break; + case S_COL_UNREAD: + if (MSG_IS_UNREAD(msginfo->flags)) { + summary_mark_row_as_read(summaryview, row); + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_unset_perm_flags + (msginfo, MSG_NEW | MSG_UNREAD); + summary_status_show(summaryview); + } else if (!MSG_IS_REPLIED(msginfo->flags) && + !MSG_IS_FORWARDED(msginfo->flags)) { + summary_mark_row_as_unread(summaryview, row); + if (MSG_IS_IMAP(msginfo->flags)) + imap_msg_set_perm_flags(msginfo, MSG_UNREAD); + summary_status_show(summaryview); + } + break; + default: + break; + } + + if (summaryview->display_msg || + (prefs_common.always_show_msg && + messageview_is_visible(summaryview->messageview))) { + summaryview->display_msg = FALSE; + if (summaryview->displayed != row) { + summary_display_msg(summaryview, row); + return; + } + } + + summary_set_menu_sensitive(summaryview); + main_window_set_toolbar_sensitive(summaryview->mainwin); +} + +static void summary_col_resized(GtkCList *clist, gint column, gint width, + SummaryView *summaryview) +{ + SummaryColumnType type = summaryview->col_state[column].type; + + prefs_common.summary_col_size[type] = width; +} + +static void summary_reply_cb(SummaryView *summaryview, guint action, + GtkWidget *widget) +{ + summary_reply(summaryview, (ComposeMode)action); +} + +static void summary_show_all_header_cb(SummaryView *summaryview, + guint action, GtkWidget *widget) +{ + summary_display_msg_selected(summaryview, + GTK_CHECK_MENU_ITEM(widget)->active); +} + +static void summary_add_address_cb(SummaryView *summaryview, + guint action, GtkWidget *widget) +{ + summary_add_address(summaryview); +} + +static void summary_sort_by_column_click(SummaryView *summaryview, + FolderSortKey sort_key) +{ + FolderItem *item = summaryview->folder_item; + + if (!item) return; + + if (item->sort_key == sort_key) + summary_sort(summaryview, sort_key, + item->sort_type == SORT_ASCENDING + ? SORT_DESCENDING : SORT_ASCENDING); + else + summary_sort(summaryview, sort_key, SORT_ASCENDING); +} + +static void summary_mark_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_MARK); +} + +static void summary_unread_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_UNREAD); +} + +static void summary_mime_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_MIME); +} + +static void summary_num_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_NUMBER); +} + +static void summary_size_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_SIZE); +} + +static void summary_date_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_DATE); +} + +static void summary_from_clicked(GtkWidget *button, SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_FROM); +} + +static void summary_subject_clicked(GtkWidget *button, + SummaryView *summaryview) +{ + summary_sort_by_column_click(summaryview, SORT_BY_SUBJECT); +} + +static void summary_start_drag(GtkWidget *widget, gint button, GdkEvent *event, + SummaryView *summaryview) +{ + GtkTargetList *list; + GdkDragContext *context; + + g_return_if_fail(summaryview != NULL); + g_return_if_fail(summaryview->folder_item != NULL); + g_return_if_fail(summaryview->folder_item->folder != NULL); + if (summaryview->selected == NULL) return; + + list = gtk_target_list_new(summary_drag_types, 1); + + if (FOLDER_ITEM_CAN_ADD(summaryview->folder_item)) { + context = gtk_drag_begin + (widget, list, + GDK_ACTION_MOVE | GDK_ACTION_COPY, button, event); + } else { + context = gtk_drag_begin(widget, list, GDK_ACTION_COPY, + button, event); + } + gtk_drag_set_icon_default(context); +} + +static void summary_drag_data_get(GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *selection_data, + guint info, + guint time, + SummaryView *summaryview) +{ + if (info == TARGET_MAIL_URI_LIST) { + GtkCTree *ctree = GTK_CTREE(summaryview->ctree); + GList *cur; + MsgInfo *msginfo; + gchar *mail_list = NULL, *tmp1, *tmp2; + + for (cur = GTK_CLIST(ctree)->selection; + cur != NULL; cur = cur->next) { + msginfo = gtk_ctree_node_get_row_data + (ctree, GTK_CTREE_NODE(cur->data)); + tmp2 = procmsg_get_message_file(msginfo); + if (!tmp2) continue; + tmp1 = g_strconcat("file://", tmp2, NULL); + g_free(tmp2); + + if (!mail_list) { + mail_list = tmp1; + } else { + tmp2 = g_strconcat(mail_list, tmp1, NULL); + g_free(mail_list); + g_free(tmp1); + mail_list = tmp2; + } + } + + if (mail_list != NULL) { + gtk_selection_data_set(selection_data, + selection_data->target, 8, + mail_list, strlen(mail_list)); + g_free(mail_list); + } + } else if (info == TARGET_DUMMY) { + if (GTK_CLIST(summaryview->ctree)->selection) + gtk_selection_data_set(selection_data, + selection_data->target, 8, + "Dummy", 6); + } +} + + +/* custom compare functions for sorting */ + +#define CMP_FUNC_DEF(func_name, val) \ +static gint func_name(GtkCList *clist, \ + gconstpointer ptr1, gconstpointer ptr2) \ +{ \ + MsgInfo *msginfo1 = ((GtkCListRow *)ptr1)->data; \ + MsgInfo *msginfo2 = ((GtkCListRow *)ptr2)->data; \ + \ + if (!msginfo1 || !msginfo2) \ + return -1; \ + \ + return (val); \ +} + +CMP_FUNC_DEF(summary_cmp_by_mark, + MSG_IS_MARKED(msginfo1->flags) - MSG_IS_MARKED(msginfo2->flags)) +CMP_FUNC_DEF(summary_cmp_by_unread, + MSG_IS_UNREAD(msginfo1->flags) - MSG_IS_UNREAD(msginfo2->flags)) +CMP_FUNC_DEF(summary_cmp_by_mime, + MSG_IS_MIME(msginfo1->flags) - MSG_IS_MIME(msginfo2->flags)) +CMP_FUNC_DEF(summary_cmp_by_label, + MSG_GET_COLORLABEL(msginfo1->flags) - + MSG_GET_COLORLABEL(msginfo2->flags)) + +CMP_FUNC_DEF(summary_cmp_by_num, msginfo1->msgnum - msginfo2->msgnum) +CMP_FUNC_DEF(summary_cmp_by_size, msginfo1->size - msginfo2->size) +CMP_FUNC_DEF(summary_cmp_by_date, msginfo1->date_t - msginfo2->date_t) + +#undef CMP_FUNC_DEF +#define CMP_FUNC_DEF(func_name, var_name) \ +static gint func_name(GtkCList *clist, \ + gconstpointer ptr1, gconstpointer ptr2) \ +{ \ + MsgInfo *msginfo1 = ((GtkCListRow *)ptr1)->data; \ + MsgInfo *msginfo2 = ((GtkCListRow *)ptr2)->data; \ + \ + if (!msginfo1->var_name) \ + return (msginfo2->var_name != NULL); \ + if (!msginfo2->var_name) \ + return -1; \ + \ + return strcasecmp(msginfo1->var_name, msginfo2->var_name); \ +} + +CMP_FUNC_DEF(summary_cmp_by_from, fromname) +CMP_FUNC_DEF(summary_cmp_by_to, to); + +#undef CMP_FUNC_DEF + +static gint summary_cmp_by_subject(GtkCList *clist, \ + gconstpointer ptr1, \ + gconstpointer ptr2) \ +{ \ + MsgInfo *msginfo1 = ((GtkCListRow *)ptr1)->data; \ + MsgInfo *msginfo2 = ((GtkCListRow *)ptr2)->data; \ + \ + if (!msginfo1->subject) \ + return (msginfo2->subject != NULL); \ + if (!msginfo2->subject) \ + return -1; \ + \ + return subject_compare_for_sort \ + (msginfo1->subject, msginfo2->subject); \ +} diff --git a/src/summaryview.h b/src/summaryview.h new file mode 100644 index 00000000..b577c0f1 --- /dev/null +++ b/src/summaryview.h @@ -0,0 +1,239 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __SUMMARY_H__ +#define __SUMMARY_H__ + +#include +#include +#include +#include +#include +#include + +typedef struct _SummaryView SummaryView; +typedef struct _SummaryColumnState SummaryColumnState; + +#include "mainwindow.h" +#include "folderview.h" +#include "headerview.h" +#include "messageview.h" +#include "compose.h" +#include "prefs_filter.h" +#include "folder.h" +#include "gtksctree.h" + +typedef enum +{ + S_COL_MARK, + S_COL_UNREAD, + S_COL_MIME, + S_COL_SUBJECT, + S_COL_FROM, + S_COL_DATE, + S_COL_SIZE, + S_COL_NUMBER, +} SummaryColumnType; + +#define N_SUMMARY_COLS 8 + +typedef enum +{ + SUMMARY_NONE, + SUMMARY_SELECTED_NONE, + SUMMARY_SELECTED_SINGLE, + SUMMARY_SELECTED_MULTIPLE +} SummarySelection; + +typedef enum +{ + TARGET_MAIL_URI_LIST, + TARGET_DUMMY +} TargetInfo; + +extern GtkTargetEntry summary_drag_types[1]; + +struct _SummaryColumnState +{ + SummaryColumnType type; + gboolean visible; +}; + +struct _SummaryView +{ + GtkWidget *vbox; + GtkWidget *scrolledwin; + GtkWidget *ctree; + GtkWidget *hbox; + GtkWidget *hbox_l; + GtkWidget *statlabel_folder; + GtkWidget *statlabel_select; + GtkWidget *statlabel_msgs; + GtkWidget *toggle_eventbox; + GtkWidget *toggle_arrow; + GtkWidget *popupmenu; + GtkWidget *colorlabel_menu; + + GtkItemFactory *popupfactory; + + GtkWidget *reedit_menuitem; + GtkWidget *reedit_separator; + + GtkWidget *window; + + GtkCTreeNode *selected; + GtkCTreeNode *displayed; + + gboolean display_msg; + + SummaryColumnState col_state[N_SUMMARY_COLS]; + gint col_pos[N_SUMMARY_COLS]; + + GdkColor color_marked; + GdkColor color_dim; + + guint lock_count; + + MainWindow *mainwin; + FolderView *folderview; + HeaderView *headerview; + MessageView *messageview; + + FolderItem *folder_item; + + /* current message status */ + off_t total_size; + gint deleted; + gint moved; + gint copied; + +/* +private: +*/ + /* table for looking up message-id */ + GHashTable *msgid_table; + + /* list for moving/deleting messages */ + GSList *mlist; + /* table for updating folder tree */ + GHashTable *folder_table; + /* counter for filtering */ + gint filtered; +}; + +SummaryView *summary_create(void); + +void summary_init (SummaryView *summaryview); +gboolean summary_show (SummaryView *summaryview, + FolderItem *fitem, + gboolean update_cache); +void summary_clear_list (SummaryView *summaryview); +void summary_clear_all (SummaryView *summaryview); + +void summary_lock (SummaryView *summaryview); +void summary_unlock (SummaryView *summaryview); +gboolean summary_is_locked (SummaryView *summaryview); + +SummarySelection summary_get_selection_type (SummaryView *summaryview); +GSList *summary_get_selected_msg_list (SummaryView *summaryview); + +void summary_select_prev_unread (SummaryView *summaryview); +void summary_select_next_unread (SummaryView *summaryview); +void summary_select_prev_new (SummaryView *summaryview); +void summary_select_next_new (SummaryView *summaryview); +void summary_select_prev_marked (SummaryView *summaryview); +void summary_select_next_marked (SummaryView *summaryview); +void summary_select_prev_labeled (SummaryView *summaryview); +void summary_select_next_labeled (SummaryView *summaryview); +void summary_select_by_msgnum (SummaryView *summaryview, + guint msgnum); +void summary_select_node (SummaryView *summaryview, + GtkCTreeNode *node, + gboolean display_msg, + gboolean do_refresh); + +void summary_thread_build (SummaryView *summaryview); +void summary_unthread (SummaryView *summaryview); + +void summary_expand_threads (SummaryView *summaryview); +void summary_collapse_threads (SummaryView *summaryview); + +void summary_filter (SummaryView *summaryview, + gboolean selected_only); +void summary_filter_open (SummaryView *summaryview, + PrefsFilterType type); + +void summary_sort (SummaryView *summaryview, + FolderSortKey sort_key, + FolderSortType sort_type); + +void summary_delete (SummaryView *summaryview); +void summary_delete_duplicated (SummaryView *summaryview); + +gboolean summary_execute (SummaryView *summaryview); + +void summary_attract_by_subject (SummaryView *summaryview); + +gint summary_write_cache (SummaryView *summaryview); + +void summary_pass_key_press_event (SummaryView *summaryview, + GdkEventKey *event); + +void summary_display_msg_selected (SummaryView *summaryview, + gboolean all_headers); +void summary_redisplay_msg (SummaryView *summaryview); +void summary_open_msg (SummaryView *summaryview); +void summary_view_source (SummaryView *summaryview); +void summary_reedit (SummaryView *summaryview); +void summary_step (SummaryView *summaryview, + GtkScrollType type); +void summary_toggle_view (SummaryView *summaryview); +void summary_set_marks_selected (SummaryView *summaryview); + +void summary_move_selected_to (SummaryView *summaryview, + FolderItem *to_folder); +void summary_move_to (SummaryView *summaryview); +void summary_copy_selected_to (SummaryView *summaryview, + FolderItem *to_folder); +void summary_copy_to (SummaryView *summaryview); +void summary_save_as (SummaryView *summaryview); +void summary_print (SummaryView *summaryview); +void summary_mark (SummaryView *summaryview); +void summary_unmark (SummaryView *summaryview); +void summary_mark_as_unread (SummaryView *summaryview); +void summary_mark_as_read (SummaryView *summaryview); +void summary_mark_all_read (SummaryView *summaryview); +void summary_add_address (SummaryView *summaryview); +void summary_select_all (SummaryView *summaryview); +void summary_unselect_all (SummaryView *summaryview); +void summary_select_thread (SummaryView *summaryview); + +void summary_reply (SummaryView *summaryview, + ComposeMode mode); + +void summary_set_colorlabel (SummaryView *summaryview, + guint labelcolor, + GtkWidget *widget); +void summary_set_colorlabel_color (GtkCTree *ctree, + GtkCTreeNode *node, + guint labelcolor); + +void summary_set_column_order (SummaryView *summaryview); + +#endif /* __SUMMARY_H__ */ diff --git a/src/syldap.c b/src/syldap.c new file mode 100644 index 00000000..2646dbf3 --- /dev/null +++ b/src/syldap.c @@ -0,0 +1,1129 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Functions necessary to access LDAP servers. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef USE_LDAP + +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ + +#include "mgutils.h" +#include "addritem.h" +#include "addrcache.h" +#include "syldap.h" +#include "utils.h" + +/* +* Create new LDAP server interface object. +*/ +SyldapServer *syldap_create() { + SyldapServer *ldapServer; + + debug_print("Creating LDAP server interface object\n"); + + ldapServer = g_new0( SyldapServer, 1 ); + ldapServer->name = NULL; + ldapServer->hostName = NULL; + ldapServer->port = SYLDAP_DFL_PORT; + ldapServer->baseDN = NULL; + ldapServer->bindDN = NULL; + ldapServer->bindPass = NULL; + ldapServer->searchCriteria = NULL; + ldapServer->searchValue = NULL; + ldapServer->entriesRead = 0; + ldapServer->maxEntries = SYLDAP_MAX_ENTRIES; + ldapServer->timeOut = SYLDAP_DFL_TIMEOUT; + ldapServer->newSearch = TRUE; + ldapServer->addressCache = addrcache_create(); + ldapServer->thread = NULL; + ldapServer->busyFlag = FALSE; + ldapServer->retVal = MGU_SUCCESS; + ldapServer->callBack = NULL; + ldapServer->accessFlag = FALSE; + ldapServer->idleId = 0; + return ldapServer; +} + +/* +* Specify name to be used. +*/ +void syldap_set_name( SyldapServer* ldapServer, const gchar *value ) { + ldapServer->name = mgu_replace_string( ldapServer->name, value ); + g_strstrip( ldapServer->name ); +} + +/* +* Specify hostname to be used. +*/ +void syldap_set_host( SyldapServer* ldapServer, const gchar *value ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->hostName = mgu_replace_string( ldapServer->hostName, value ); + g_strstrip( ldapServer->hostName ); +} + +/* +* Specify port to be used. +*/ +void syldap_set_port( SyldapServer* ldapServer, const gint value ) { + addrcache_refresh( ldapServer->addressCache ); + if( value > 0 ) { + ldapServer->port = value; + } + else { + ldapServer->port = SYLDAP_DFL_PORT; + } +} + +/* +* Specify base DN to be used. +*/ +void syldap_set_base_dn( SyldapServer* ldapServer, const gchar *value ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->baseDN = mgu_replace_string( ldapServer->baseDN, value ); + g_strstrip( ldapServer->baseDN ); +} + +/* +* Specify bind DN to be used. +*/ +void syldap_set_bind_dn( SyldapServer* ldapServer, const gchar *value ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->bindDN = mgu_replace_string( ldapServer->bindDN, value ); + g_strstrip( ldapServer->bindDN ); +} + +/* +* Specify bind password to be used. +*/ +void syldap_set_bind_password( SyldapServer* ldapServer, const gchar *value ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->bindPass = mgu_replace_string( ldapServer->bindPass, value ); + g_strstrip( ldapServer->bindPass ); +} + +/* +* Specify search criteria to be used. +*/ +void syldap_set_search_criteria( SyldapServer* ldapServer, const gchar *value ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->searchCriteria = mgu_replace_string( ldapServer->searchCriteria, value ); + g_strstrip( ldapServer->searchCriteria ); + ldapServer->newSearch = TRUE; +} + +/* +* Specify search value to be searched for. +*/ +void syldap_set_search_value( SyldapServer* ldapServer, const gchar *value ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->searchValue = mgu_replace_string( ldapServer->searchValue, value ); + g_strstrip( ldapServer->searchValue ); + ldapServer->newSearch = TRUE; +} + +/* +* Specify maximum number of entries to retrieve. +*/ +void syldap_set_max_entries( SyldapServer* ldapServer, const gint value ) { + addrcache_refresh( ldapServer->addressCache ); + if( value > 0 ) { + ldapServer->maxEntries = value; + } + else { + ldapServer->maxEntries = SYLDAP_MAX_ENTRIES; + } +} + +/* +* Specify timeout value for LDAP operation (in seconds). +*/ +void syldap_set_timeout( SyldapServer* ldapServer, const gint value ) { + addrcache_refresh( ldapServer->addressCache ); + if( value > 0 ) { + ldapServer->timeOut = value; + } + else { + ldapServer->timeOut = SYLDAP_DFL_TIMEOUT; + } +} + +/* +* Register a callback function. When called, the function will be passed +* this object as an argument. +*/ +void syldap_set_callback( SyldapServer *ldapServer, void *func ) { + ldapServer->callBack = func; +} + +void syldap_set_accessed( SyldapServer *ldapServer, const gboolean value ) { + g_return_if_fail( ldapServer != NULL ); + ldapServer->accessFlag = value; +} + +/* +* Refresh internal variables to force a file read. +*/ +void syldap_force_refresh( SyldapServer *ldapServer ) { + addrcache_refresh( ldapServer->addressCache ); + ldapServer->newSearch = TRUE; +} + +gint syldap_get_status( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, -1 ); + return ldapServer->retVal; +} + +ItemFolder *syldap_get_root_folder( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, NULL ); + return addrcache_get_root_folder( ldapServer->addressCache ); +} + +gchar *syldap_get_name( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, NULL ); + return ldapServer->name; +} + +gboolean syldap_get_accessed( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, FALSE ); + return ldapServer->accessFlag; +} + +/* +* Free up LDAP server interface object by releasing internal memory. +*/ +void syldap_free( SyldapServer *ldapServer ) { + g_return_if_fail( ldapServer != NULL ); + + debug_print("Freeing LDAP server interface object\n"); + + ldapServer->callBack = NULL; + + /* Free internal stuff */ + g_free( ldapServer->name ); + g_free( ldapServer->hostName ); + g_free( ldapServer->baseDN ); + g_free( ldapServer->bindDN ); + g_free( ldapServer->bindPass ); + g_free( ldapServer->searchCriteria ); + g_free( ldapServer->searchValue ); + g_free( ldapServer->thread ); + + ldapServer->port = 0; + ldapServer->entriesRead = 0; + ldapServer->maxEntries = 0; + ldapServer->newSearch = FALSE; + + /* Clear cache */ + addrcache_clear( ldapServer->addressCache ); + addrcache_free( ldapServer->addressCache ); + + /* Clear pointers */ + ldapServer->name = NULL; + ldapServer->hostName = NULL; + ldapServer->baseDN = NULL; + ldapServer->bindDN = NULL; + ldapServer->bindPass = NULL; + ldapServer->searchCriteria = NULL; + ldapServer->searchValue = NULL; + ldapServer->addressCache = NULL; + ldapServer->thread = NULL; + ldapServer->busyFlag = FALSE; + ldapServer->retVal = MGU_SUCCESS; + ldapServer->accessFlag = FALSE; + + /* Now release LDAP object */ + g_free( ldapServer ); + +} + +/* +* Display object to specified stream. +*/ +void syldap_print_data( SyldapServer *ldapServer, FILE *stream ) { + g_return_if_fail( ldapServer != NULL ); + + fprintf( stream, "SyldapServer:\n" ); + fprintf( stream, " name: '%s'\n", ldapServer->name ); + fprintf( stream, "host name: '%s'\n", ldapServer->hostName ); + fprintf( stream, " port: %d\n", ldapServer->port ); + fprintf( stream, " base dn: '%s'\n", ldapServer->baseDN ); + fprintf( stream, " bind dn: '%s'\n", ldapServer->bindDN ); + fprintf( stream, "bind pass: '%s'\n", ldapServer->bindPass ); + fprintf( stream, " criteria: '%s'\n", ldapServer->searchCriteria ); + fprintf( stream, "searchval: '%s'\n", ldapServer->searchValue ); + fprintf( stream, "max entry: %d\n", ldapServer->maxEntries ); + fprintf( stream, " num read: %d\n", ldapServer->entriesRead ); + fprintf( stream, " ret val: %d\n", ldapServer->retVal ); + addrcache_print( ldapServer->addressCache, stream ); + addritem_print_item_folder( ldapServer->addressCache->rootFolder, stream ); +} + +/* +* Display object to specified stream. +*/ +void syldap_print_short( SyldapServer *ldapServer, FILE *stream ) { + g_return_if_fail( ldapServer != NULL ); + + fprintf( stream, "SyldapServer:\n" ); + fprintf( stream, " name: '%s'\n", ldapServer->name ); + fprintf( stream, "host name: '%s'\n", ldapServer->hostName ); + fprintf( stream, " port: %d\n", ldapServer->port ); + fprintf( stream, " base dn: '%s'\n", ldapServer->baseDN ); + fprintf( stream, " bind dn: '%s'\n", ldapServer->bindDN ); + fprintf( stream, "bind pass: '%s'\n", ldapServer->bindPass ); + fprintf( stream, " criteria: '%s'\n", ldapServer->searchCriteria ); + fprintf( stream, "searchval: '%s'\n", ldapServer->searchValue ); + fprintf( stream, "max entry: %d\n", ldapServer->maxEntries ); + fprintf( stream, " num read: %d\n", ldapServer->entriesRead ); + fprintf( stream, " ret val: %d\n", ldapServer->retVal ); +} + +#if 0 +/* +* Build an address list entry and append to list of address items. Name is formatted +* as it appears in the common name (cn) attribute. +*/ +static void syldap_build_items_cn( SyldapServer *ldapServer, GSList *listName, GSList *listAddr ) { + ItemPerson *person; + ItemEMail *email; + GSList *nodeName = listName; + + while( nodeName ) { + GSList *nodeAddress = listAddr; + person = addritem_create_item_person(); + addritem_person_set_common_name( person, nodeName->data ); + addrcache_id_person( ldapServer->addressCache, person ); + addrcache_add_person( ldapServer->addressCache, person ); + + while( nodeAddress ) { + email = addritem_create_item_email(); + addritem_email_set_address( email, nodeAddress->data ); + addrcache_id_email( ldapServer->addressCache, email ); + addrcache_person_add_email( ldapServer->addressCache, person, email ); + nodeAddress = g_slist_next( nodeAddress ); + ldapServer->entriesRead++; + } + nodeName = g_slist_next( nodeName ); + } +} +#endif + +/* +* Build an address list entry and append to list of address items. Name is formatted +* as " ". +*/ +static void syldap_build_items_fl( SyldapServer *ldapServer, GSList *listAddr, GSList *listFirst, GSList *listLast ) { + GSList *nodeFirst = listFirst; + GSList *nodeAddress = listAddr; + gchar *firstName = NULL, *lastName = NULL, *fullName = NULL; + gint iLen = 0, iLenT = 0; + ItemPerson *person; + ItemEMail *email; + + /* Find longest first name in list */ + while( nodeFirst ) { + if( firstName == NULL ) { + firstName = nodeFirst->data; + iLen = strlen( firstName ); + } + else { + if( ( iLenT = strlen( nodeFirst->data ) ) > iLen ) { + firstName = nodeFirst->data; + iLen = iLenT; + } + } + nodeFirst = g_slist_next( nodeFirst ); + } + + /* Format name */ + if( listLast ) { + lastName = listLast->data; + } + + if( firstName ) { + if( lastName ) { + fullName = g_strdup_printf( "%s %s", firstName, lastName ); + } + else { + fullName = g_strdup_printf( "%s", firstName ); + } + } + else { + if( lastName ) { + fullName = g_strdup_printf( "%s", lastName ); + } + } + if( fullName ) { + g_strchug( fullName ); g_strchomp( fullName ); + } + + if( nodeAddress ) { + person = addritem_create_item_person(); + addritem_person_set_common_name( person, fullName ); + addritem_person_set_first_name( person, firstName ); + addritem_person_set_last_name( person, lastName ); + addrcache_id_person( ldapServer->addressCache, person ); + addrcache_add_person( ldapServer->addressCache, person ); + } + + /* Add address item */ + while( nodeAddress ) { + email = addritem_create_item_email(); + addritem_email_set_address( email, nodeAddress->data ); + addrcache_id_email( ldapServer->addressCache, email ); + addrcache_person_add_email( ldapServer->addressCache, person, email ); + nodeAddress = g_slist_next( nodeAddress ); + ldapServer->entriesRead++; + } + g_free( fullName ); + fullName = firstName = lastName = NULL; + +} + +/* +* Add all attribute values to a list. +*/ +static GSList *syldap_add_list_values( LDAP *ld, LDAPMessage *entry, char *attr ) { + GSList *list = NULL; + gint i; + gchar **vals; + + if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) { + for( i = 0; vals[i] != NULL; i++ ) { + /* printf( "lv\t%s: %s\n", attr, vals[i] ); */ + list = g_slist_append( list, g_strdup( vals[i] ) ); + } + } + ldap_value_free( vals ); + return list; +} + +/* +* Add a single attribute value to a list. +*/ +static GSList *syldap_add_single_value( LDAP *ld, LDAPMessage *entry, char *attr ) { + GSList *list = NULL; + gchar **vals; + + if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) { + if( vals[0] != NULL ) { + /* printf( "sv\t%s: %s\n", attr, vals[0] ); */ + list = g_slist_append( list, g_strdup( vals[0] ) ); + } + } + ldap_value_free( vals ); + return list; +} + +/* +* Free linked lists of character strings. +*/ +static void syldap_free_lists( GSList *listName, GSList *listAddr, GSList *listID, GSList *listDN, GSList *listFirst, GSList *listLast ) { + mgu_free_list( listName ); + mgu_free_list( listAddr ); + mgu_free_list( listID ); + mgu_free_list( listDN ); + mgu_free_list( listFirst ); + mgu_free_list( listLast ); +} + +/* +* Check parameters that are required for a search. This should +* be called before performing a search. +* Return: TRUE if search criteria appear OK. +*/ +gboolean syldap_check_search( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, FALSE ); + + ldapServer->retVal = MGU_LDAP_CRITERIA; + + /* Test search criteria */ + if( ldapServer->searchCriteria == NULL ) { + return FALSE; + } + if( strlen( ldapServer->searchCriteria ) < 1 ) { + return FALSE; + } + + if( ldapServer->searchValue == NULL ) { + return FALSE; + } + if( strlen( ldapServer->searchValue ) < 1 ) { + return FALSE; + } + + ldapServer->retVal = MGU_SUCCESS; + return TRUE; +} + +/* +* Perform the LDAP search, reading LDAP entries into cache. +* Note that one LDAP entry can have multiple values for many of its +* attributes. If these attributes are E-Mail addresses; these are +* broken out into separate address items. For any other attribute, +* only the first occurrence is read. +*/ +gint syldap_search( SyldapServer *ldapServer ) { + LDAP *ld; + LDAPMessage *result, *e; + char *attribs[10]; + char *attribute; + gchar *criteria; + BerElement *ber; + gint rc; + GSList *listName = NULL, *listAddress = NULL, *listID = NULL; + GSList *listFirst = NULL, *listLast = NULL, *listDN = NULL; + struct timeval timeout; + gboolean entriesFound = FALSE; + + g_return_val_if_fail( ldapServer != NULL, -1 ); + + ldapServer->retVal = MGU_SUCCESS; + if( ! syldap_check_search( ldapServer ) ) { + return ldapServer->retVal; + } + + /* Set timeout */ + timeout.tv_sec = ldapServer->timeOut; + timeout.tv_usec = 0L; + + ldapServer->entriesRead = 0; + if( ( ld = ldap_init( ldapServer->hostName, ldapServer->port ) ) == NULL ) { + ldapServer->retVal = MGU_LDAP_INIT; + return ldapServer->retVal; + } + + /* printf( "connected to LDAP host %s on port %d\n", ldapServer->hostName, ldapServer->port ); */ + + /* Bind to the server, if required */ + if( ldapServer->bindDN ) { + if( * ldapServer->bindDN != '\0' ) { + /* printf( "binding...\n" ); */ + rc = ldap_simple_bind_s( ld, ldapServer->bindDN, ldapServer->bindPass ); + /* printf( "rc=%d\n", rc ); */ + if( rc != LDAP_SUCCESS ) { + /* printf( "LDAP Error: ldap_simple_bind_s: %s\n", ldap_err2string( rc ) ); */ + ldap_unbind( ld ); + ldapServer->retVal = MGU_LDAP_BIND; + return ldapServer->retVal; + } + } + } + + /* Define all attributes we are interested in. */ + attribs[0] = SYLDAP_ATTR_DN; + attribs[1] = SYLDAP_ATTR_COMMONNAME; + attribs[2] = SYLDAP_ATTR_GIVENNAME; + attribs[3] = SYLDAP_ATTR_SURNAME; + attribs[4] = SYLDAP_ATTR_EMAIL; + attribs[5] = SYLDAP_ATTR_UID; + attribs[6] = NULL; + + /* Create LDAP search string and apply search criteria */ + criteria = g_strdup_printf( ldapServer->searchCriteria, ldapServer->searchValue ); + rc = ldap_search_ext_s( ld, ldapServer->baseDN, LDAP_SCOPE_SUBTREE, criteria, attribs, 0, NULL, NULL, + &timeout, 0, &result ); + g_free( criteria ); + criteria = NULL; + if( rc == LDAP_TIMEOUT ) { + ldap_unbind( ld ); + ldapServer->retVal = MGU_LDAP_TIMEOUT; + return ldapServer->retVal; + } + if( rc != LDAP_SUCCESS ) { + /* printf( "LDAP Error: ldap_search_st: %s\n", ldap_err2string( rc ) ); */ + ldap_unbind( ld ); + ldapServer->retVal = MGU_LDAP_SEARCH; + return ldapServer->retVal; + } + + /* printf( "Total results are: %d\n", ldap_count_entries( ld, result ) ); */ + + /* Clear the cache if we have new entries, otherwise leave untouched. */ + if( ldap_count_entries( ld, result ) > 0 ) { + addrcache_clear( ldapServer->addressCache ); + } + + /* Process results */ + ldapServer->entriesRead = 0; + for( e = ldap_first_entry( ld, result ); e != NULL; e = ldap_next_entry( ld, e ) ) { + entriesFound = TRUE; + if( ldapServer->entriesRead >= ldapServer->maxEntries ) break; + /* printf( "DN: %s\n", ldap_get_dn( ld, e ) ); */ + + /* Process all attributes */ + for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL; + attribute = ldap_next_attribute( ld, e, ber ) ) { + if( strcasecmp( attribute, SYLDAP_ATTR_COMMONNAME ) == 0 ) { + listName = syldap_add_list_values( ld, e, attribute ); + } + if( strcasecmp( attribute, SYLDAP_ATTR_EMAIL ) == 0 ) { + listAddress = syldap_add_list_values( ld, e, attribute ); + } + if( strcasecmp( attribute, SYLDAP_ATTR_UID ) == 0 ) { + listID = syldap_add_single_value( ld, e, attribute ); + } + if( strcasecmp( attribute, SYLDAP_ATTR_GIVENNAME ) == 0 ) { + listFirst = syldap_add_list_values( ld, e, attribute ); + } + if( strcasecmp( attribute, SYLDAP_ATTR_SURNAME ) == 0 ) { + listLast = syldap_add_single_value( ld, e, attribute ); + } + if( strcasecmp( attribute, SYLDAP_ATTR_DN ) == 0 ) { + listDN = syldap_add_single_value( ld, e, attribute ); + } + } + + /* Free memory used to store attribute */ + ldap_memfree( attribute ); + + /* Format and add items to cache */ + syldap_build_items_fl( ldapServer, listAddress, listFirst, listLast ); + + /* Free up */ + syldap_free_lists( listName, listAddress, listID, listDN, listFirst, listLast ); + listName = listAddress = listID = listFirst = listLast = listDN = NULL; + + if( ber != NULL ) { + ber_free( ber, 0 ); + } + } + + syldap_free_lists( listName, listAddress, listID, listDN, listFirst, listLast ); + listName = listAddress = listID = listFirst = listLast = listDN = NULL; + + /* Free up and disconnect */ + ldap_msgfree( result ); + ldap_unbind( ld ); + ldapServer->newSearch = FALSE; + if( entriesFound ) { + ldapServer->retVal = MGU_SUCCESS; + } + else { + ldapServer->retVal = MGU_LDAP_NOENTRIES; + } + return ldapServer->retVal; +} + +/* syldap_display_search_results() - updates the ui. this function is called from the + * main thread (the thread running the GTK event loop). */ +static gint syldap_display_search_results(SyldapServer *ldapServer) +{ + /* NOTE: when this function is called the accompanying thread should + * already be terminated. */ + gtk_idle_remove(ldapServer->idleId); + ldapServer->callBack(ldapServer); + /* FIXME: match should know whether to free this SyldapServer stuff. */ + g_free(ldapServer->thread); + ldapServer->thread = NULL; + return TRUE; +} + +/* ============================================================================================ */ +/* +* Read data into list. Main entry point +* Return: TRUE if file read successfully. +*/ +/* ============================================================================================ */ +gint syldap_read_data( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, -1 ); + + ldapServer->accessFlag = FALSE; + pthread_detach( pthread_self() ); + if( ldapServer->newSearch ) { + /* Read data into the list */ + syldap_search( ldapServer ); + + /* Mark cache */ + ldapServer->addressCache->modified = FALSE; + ldapServer->addressCache->dataRead = TRUE; + ldapServer->accessFlag = FALSE; + } + + /* Callback */ + ldapServer->busyFlag = FALSE; + if( ldapServer->callBack ) { + /* make the ui thread update the search results */ + /* TODO: really necessary to call gdk_threads_XXX()??? gtk_idle_add() + * should do this - could someone check the GTK sources please? */ + gdk_threads_enter(); + ldapServer->idleId = gtk_idle_add((GtkFunction)syldap_display_search_results, ldapServer); + gdk_threads_leave(); + } + + return ldapServer->retVal; +} + +/* ============================================================================================ */ +/* +* Cancel read with thread. +*/ +/* ============================================================================================ */ +void syldap_cancel_read( SyldapServer *ldapServer ) { + g_return_if_fail( ldapServer != NULL ); + + /* DELETEME: this is called from inside UI thread so it's OK, Christoph! */ + if( ldapServer->thread ) { + /* printf( "thread cancelled\n" ); */ + pthread_cancel( *ldapServer->thread ); + } + g_free(ldapServer->thread); + ldapServer->thread = NULL; + ldapServer->busyFlag = FALSE; +} + +/* ============================================================================================ */ +/* +* Read data into list using a background thread. +* Return: TRUE if file read successfully. Callback function will be +* notified when search is complete. +*/ +/* ============================================================================================ */ +gint syldap_read_data_th( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, -1 ); + + ldapServer->busyFlag = FALSE; + syldap_check_search( ldapServer ); + if( ldapServer->retVal == MGU_SUCCESS ) { + /* debug_print("Staring LDAP read thread\n"); */ + + ldapServer->busyFlag = TRUE; + ldapServer->thread = g_new0(pthread_t, 1); + pthread_create( ldapServer->thread, NULL, (void *) syldap_read_data, (void *) ldapServer ); + } + return ldapServer->retVal; +} + +/* +* Return link list of persons. +*/ +GList *syldap_get_list_person( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, NULL ); + return addrcache_get_list_person( ldapServer->addressCache ); +} + +/* +* Return link list of folders. This is always NULL since there are +* no folders in GnomeCard. +* Return: NULL. +*/ +GList *syldap_get_list_folder( SyldapServer *ldapServer ) { + g_return_val_if_fail( ldapServer != NULL, NULL ); + return NULL; +} + +#define SYLDAP_TEST_FILTER "(objectclass=*)" +#define SYLDAP_SEARCHBASE_V2 "cn=config" +#define SYLDAP_SEARCHBASE_V3 "" +#define SYLDAP_V2_TEST_ATTR "database" +#define SYLDAP_V3_TEST_ATTR "namingcontexts" + +/* +* Attempt to discover the base DN for the server. +* Enter: +* host Host name +* port Port number +* bindDN Bind DN (optional). +* bindPW Bind PW (optional). +* tov Timeout value (seconds), or 0 for none, default 30 secs. +* Return: List of Base DN's, or NULL if could not read. Base DN should +* be g_free() when done. +*/ +GList *syldap_read_basedn_s( const gchar *host, const gint port, const gchar *bindDN, const gchar *bindPW, const gint tov ) { + GList *baseDN = NULL; + LDAP *ld; + gint rc, i; + LDAPMessage *result, *e; + gchar *attribs[10]; + BerElement *ber; + gchar *attribute; + gchar **vals; + struct timeval timeout; + + if( host == NULL ) return baseDN; + if( port < 1 ) return baseDN; + + /* Set timeout */ + timeout.tv_usec = 0L; + if( tov > 0 ) { + timeout.tv_sec = tov; + } + else { + timeout.tv_sec = 30L; + } + + /* Connect to server. */ + if( ( ld = ldap_init( host, port ) ) == NULL ) { + return baseDN; + } + + /* Bind to the server, if required */ + if( bindDN ) { + if( *bindDN != '\0' ) { + rc = ldap_simple_bind_s( ld, bindDN, bindPW ); + if( rc != LDAP_SUCCESS ) { + /* printf( "LDAP Error: ldap_simple_bind_s: %s\n", ldap_err2string( rc ) ); */ + ldap_unbind( ld ); + return baseDN; + } + } + } + + /* Test for LDAP version 3 */ + attribs[0] = SYLDAP_V3_TEST_ATTR; + attribs[1] = NULL; + rc = ldap_search_ext_s( ld, SYLDAP_SEARCHBASE_V3, LDAP_SCOPE_BASE, SYLDAP_TEST_FILTER, attribs, + 0, NULL, NULL, &timeout, 0, &result ); + if( rc == LDAP_SUCCESS ) { + /* Process entries */ + for( e = ldap_first_entry( ld, result ); e != NULL; e = ldap_next_entry( ld, e ) ) { + /* printf( "DN: %s\n", ldap_get_dn( ld, e ) ); */ + + /* Process attributes */ + for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL; + attribute = ldap_next_attribute( ld, e, ber ) ) { + if( strcasecmp( attribute, SYLDAP_V3_TEST_ATTR ) == 0 ) { + if( ( vals = ldap_get_values( ld, e, attribute ) ) != NULL ) { + for( i = 0; vals[i] != NULL; i++ ) { + /* printf( "\t%s: %s\n", attribute, vals[i] ); */ + baseDN = g_list_append( baseDN, g_strdup( vals[i] ) ); + } + } + ldap_value_free( vals ); + } + } + ldap_memfree( attribute ); + if( ber != NULL ) { + ber_free( ber, 0 ); + } + } + ldap_msgfree( result ); + } + else { + } + + if( baseDN == NULL ) { + /* Test for LDAP version 2 */ + attribs[0] = NULL; + rc = ldap_search_ext_s( ld, SYLDAP_SEARCHBASE_V2, LDAP_SCOPE_BASE, SYLDAP_TEST_FILTER, attribs, + 0, NULL, NULL, &timeout, 0, &result ); + if( rc == LDAP_SUCCESS ) { + /* Process entries */ + for( e = ldap_first_entry( ld, result ); e != NULL; e = ldap_next_entry( ld, e ) ) { + /* if( baseDN ) break; */ + /* printf( "DN: %s\n", ldap_get_dn( ld, e ) ); */ + + /* Process attributes */ + for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL; + attribute = ldap_next_attribute( ld, e, ber ) ) { + /* if( baseDN ) break; */ + if( strcasecmp( attribute, SYLDAP_V2_TEST_ATTR ) == 0 ) { + if( ( vals = ldap_get_values( ld, e, attribute ) ) != NULL ) { + for( i = 0; vals[i] != NULL; i++ ) { + char *ch; + /* Strip the 'ldb:' from the front of the value */ + ch = ( char * ) strchr( vals[i], ':' ); + if( ch ) { + gchar *bn = g_strdup( ++ch ); + g_strchomp( bn ); + g_strchug( bn ); + baseDN = g_list_append( baseDN, g_strdup( bn ) ); + } + } + } + ldap_value_free( vals ); + } + } + ldap_memfree( attribute ); + if( ber != NULL ) { + ber_free( ber, 0 ); + } + } + ldap_msgfree( result ); + } + } + ldap_unbind( ld ); + return baseDN; +} + +/* +* Attempt to discover the base DN for the server. +* Enter: ldapServer Server to test. +* Return: List of Base DN's, or NULL if could not read. Base DN should +* be g_free() when done. Return code set in ldapServer. +*/ +GList *syldap_read_basedn( SyldapServer *ldapServer ) { + GList *baseDN = NULL; + LDAP *ld; + gint rc, i; + LDAPMessage *result, *e; + gchar *attribs[10]; + BerElement *ber; + gchar *attribute; + gchar **vals; + struct timeval timeout; + + ldapServer->retVal = MGU_BAD_ARGS; + if( ldapServer == NULL ) return baseDN; + if( ldapServer->hostName == NULL ) return baseDN; + if( ldapServer->port < 1 ) return baseDN; + + /* Set timeout */ + timeout.tv_usec = 0L; + if( ldapServer->timeOut > 0 ) { + timeout.tv_sec = ldapServer->timeOut; + } + else { + timeout.tv_sec = 30L; + } + + /* Connect to server. */ + if( ( ld = ldap_init( ldapServer->hostName, ldapServer->port ) ) == NULL ) { + ldapServer->retVal = MGU_LDAP_INIT; + return baseDN; + } + + /* Bind to the server, if required */ + if( ldapServer->bindDN ) { + if( *ldapServer->bindDN != '\0' ) { + rc = ldap_simple_bind_s( ld, ldapServer->bindDN, ldapServer->bindPass ); + if( rc != LDAP_SUCCESS ) { + /* printf( "LDAP Error: ldap_simple_bind_s: %s\n", ldap_err2string( rc ) ); */ + ldap_unbind( ld ); + ldapServer->retVal = MGU_LDAP_BIND; + return baseDN; + } + } + } + + ldapServer->retVal = MGU_LDAP_SEARCH; + + /* Test for LDAP version 3 */ + attribs[0] = SYLDAP_V3_TEST_ATTR; + attribs[1] = NULL; + rc = ldap_search_ext_s( ld, SYLDAP_SEARCHBASE_V3, LDAP_SCOPE_BASE, SYLDAP_TEST_FILTER, attribs, + 0, NULL, NULL, &timeout, 0, &result ); + if( rc == LDAP_SUCCESS ) { + /* Process entries */ + for( e = ldap_first_entry( ld, result ); e != NULL; e = ldap_next_entry( ld, e ) ) { + /* printf( "DN: %s\n", ldap_get_dn( ld, e ) ); */ + + /* Process attributes */ + for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL; + attribute = ldap_next_attribute( ld, e, ber ) ) { + if( strcasecmp( attribute, SYLDAP_V3_TEST_ATTR ) == 0 ) { + if( ( vals = ldap_get_values( ld, e, attribute ) ) != NULL ) { + for( i = 0; vals[i] != NULL; i++ ) { + /* printf( "\t%s: %s\n", attribute, vals[i] ); */ + baseDN = g_list_append( baseDN, g_strdup( vals[i] ) ); + } + } + ldap_value_free( vals ); + } + } + ldap_memfree( attribute ); + if( ber != NULL ) { + ber_free( ber, 0 ); + } + } + ldap_msgfree( result ); + ldapServer->retVal = MGU_SUCCESS; + } + else if( rc == LDAP_TIMEOUT ) { + ldapServer->retVal = MGU_LDAP_TIMEOUT; + } + + if( baseDN == NULL ) { + /* Test for LDAP version 2 */ + attribs[0] = NULL; + rc = ldap_search_ext_s( ld, SYLDAP_SEARCHBASE_V2, LDAP_SCOPE_BASE, SYLDAP_TEST_FILTER, attribs, + 0, NULL, NULL, &timeout, 0, &result ); + if( rc == LDAP_SUCCESS ) { + /* Process entries */ + for( e = ldap_first_entry( ld, result ); e != NULL; e = ldap_next_entry( ld, e ) ) { + /* if( baseDN ) break; */ + /* printf( "DN: %s\n", ldap_get_dn( ld, e ) ); */ + + /* Process attributes */ + for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL; + attribute = ldap_next_attribute( ld, e, ber ) ) { + /* if( baseDN ) break; */ + if( strcasecmp( attribute, SYLDAP_V2_TEST_ATTR ) == 0 ) { + if( ( vals = ldap_get_values( ld, e, attribute ) ) != NULL ) { + for( i = 0; vals[i] != NULL; i++ ) { + char *ch; + /* Strip the 'ldb:' from the front of the value */ + ch = ( char * ) strchr( vals[i], ':' ); + if( ch ) { + gchar *bn = g_strdup( ++ch ); + g_strchomp( bn ); + g_strchug( bn ); + baseDN = g_list_append( baseDN, g_strdup( bn ) ); + } + } + } + ldap_value_free( vals ); + } + } + ldap_memfree( attribute ); + if( ber != NULL ) { + ber_free( ber, 0 ); + } + } + ldap_msgfree( result ); + ldapServer->retVal = MGU_SUCCESS; + } + else if( rc == LDAP_TIMEOUT ) { + ldapServer->retVal = MGU_LDAP_TIMEOUT; + } + } + ldap_unbind( ld ); + + return baseDN; +} + +/* +* Attempt to connect to the server. +* Enter: +* host Host name +* port Port number +* Return: TRUE if connected successfully. +*/ +gboolean syldap_test_connect_s( const gchar *host, const gint port ) { + gboolean retVal = FALSE; + LDAP *ld; + + if( host == NULL ) return retVal; + if( port < 1 ) return retVal; + if( ( ld = ldap_open( host, port ) ) != NULL ) { + retVal = TRUE; + } + if( ld != NULL ) { + ldap_unbind( ld ); + } + return retVal; +} + +/* +* Attempt to connect to the server. +* Enter: ldapServer Server to test. +* Return: TRUE if connected successfully. Return code set in ldapServer. +*/ +gboolean syldap_test_connect( SyldapServer *ldapServer ) { + gboolean retVal = FALSE; + LDAP *ld; + + ldapServer->retVal = MGU_BAD_ARGS; + if( ldapServer == NULL ) return retVal; + if( ldapServer->hostName == NULL ) return retVal; + if( ldapServer->port < 1 ) return retVal; + ldapServer->retVal = MGU_LDAP_INIT; + if( ( ld = ldap_open( ldapServer->hostName, ldapServer->port ) ) != NULL ) { + ldapServer->retVal = MGU_SUCCESS; + retVal = TRUE; + } + if( ld != NULL ) { + ldap_unbind( ld ); + } + return retVal; +} + +#define LDAP_LINK_LIB_NAME_1 "libldap.so" +#define LDAP_LINK_LIB_NAME_2 "liblber.so" +#define LDAP_LINK_LIB_NAME_3 "libresolv.so" +#define LDAP_LINK_LIB_NAME_4 "libpthread.so" + +/* +* Test whether LDAP libraries installed. +* Return: TRUE if library available. +*/ +#if 0 +gboolean syldap_test_ldap_lib() { + void *handle, *fun; + + /* Get library */ + handle = dlopen( LDAP_LINK_LIB_NAME_1, RTLD_LAZY ); + if( ! handle ) { + return FALSE; + } + + /* Test for symbols we need */ + fun = dlsym( handle, "ldap_init" ); + if( ! fun ) { + dlclose( handle ); + return FALSE; + } + dlclose( handle ); handle = NULL; fun = NULL; + + handle = dlopen( LDAP_LINK_LIB_NAME_2, RTLD_LAZY ); + if( ! handle ) { + return FALSE; + } + fun = dlsym( handle, "ber_init" ); + if( ! fun ) { + dlclose( handle ); + return FALSE; + } + dlclose( handle ); handle = NULL; fun = NULL; + + handle = dlopen( LDAP_LINK_LIB_NAME_3, RTLD_LAZY ); + if( ! handle ) { + return FALSE; + } + fun = dlsym( handle, "res_query" ); + if( ! fun ) { + dlclose( handle ); + return FALSE; + } + dlclose( handle ); handle = NULL; fun = NULL; + + handle = dlopen( LDAP_LINK_LIB_NAME_4, RTLD_LAZY ); + if( ! handle ) { + return FALSE; + } + fun = dlsym( handle, "pthread_create" ); + if( ! fun ) { + dlclose( handle ); + return FALSE; + } + dlclose( handle ); handle = NULL; fun = NULL; + + return TRUE; +} +#endif /* 0 */ + +#endif /* USE_LDAP */ + +/* +* End of Source. +*/ diff --git a/src/syldap.h b/src/syldap.h new file mode 100644 index 00000000..f760dfad --- /dev/null +++ b/src/syldap.h @@ -0,0 +1,111 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Definitions necessary to access LDAP servers. + */ + +#ifndef __SYLDAP_H__ +#define __SYLDAP_H__ + +#ifdef USE_LDAP + +#include +#include + +#include "addritem.h" +#include "addrcache.h" + +#define SYLDAP_DFL_PORT 389 +#define SYLDAP_MAX_ENTRIES 20 +#define SYLDAP_DFL_TIMEOUT 30 +#define SYLDAP_DFL_CRITERIA "(&(mail=*)(cn=%s*))" + +#define SYLDAP_ATTR_DN "dn" +#define SYLDAP_ATTR_COMMONNAME "cn" +#define SYLDAP_ATTR_GIVENNAME "givenName" +#define SYLDAP_ATTR_SURNAME "sn" +#define SYLDAP_ATTR_EMAIL "mail" +#define SYLDAP_ATTR_UID "uid" + +typedef struct _SyldapServer SyldapServer; +struct _SyldapServer { + gchar *name; + gchar *hostName; + gint port; + gchar *baseDN; + gchar *bindDN; + gchar *bindPass; + gchar *searchCriteria; + gchar *searchValue; + gint entriesRead; + gint maxEntries; + gint timeOut; + gboolean newSearch; + AddressCache *addressCache; + /* ItemFolder *rootFolder; */ + gboolean accessFlag; + gint retVal; + pthread_t *thread; + gboolean busyFlag; + void (*callBack)( void * ); + guint idleId; +}; + +/* Function prototypes */ +SyldapServer *syldap_create ( void ); +void syldap_set_name ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_host ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_port ( SyldapServer* ldapServer, const gint value ); +void syldap_set_base_dn ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_bind_dn ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_bind_password ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_search_criteria ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_search_value ( SyldapServer* ldapServer, const gchar *value ); +void syldap_set_max_entries ( SyldapServer* ldapServer, const gint value ); +void syldap_set_timeout ( SyldapServer* ldapServer, const gint value ); +void syldap_set_callback ( SyldapServer *ldapServer, void *func ); +void syldap_set_accessed ( SyldapServer *ldapServer, const gboolean value ); +void syldap_force_refresh ( SyldapServer *ldapServer ); +void syldap_free ( SyldapServer *ldapServer ); +gint syldap_get_status ( SyldapServer *ldapServer ); +gboolean syldap_get_accessed ( SyldapServer *ldapServer ); +gchar *syldap_get_name ( SyldapServer *ldapServer ); + +void syldap_print_data ( SyldapServer *ldapServer, FILE *stream ); +gboolean syldap_check_search ( SyldapServer *ldapServer ); +gint syldap_read_data ( SyldapServer *ldapServer ); +gint syldap_read_data_th ( SyldapServer *ldapServer ); +void syldap_cancel_read ( SyldapServer *ldapServer ); + +/* GList *syldap_get_address_list ( const SyldapServer *ldapServer ); */ +ItemFolder *syldap_get_root_folder ( SyldapServer *ldapServer ); +GList *syldap_get_list_person ( SyldapServer *ldapServer ); +GList *syldap_get_list_folder ( SyldapServer *ldapServer ); + +GList *syldap_read_basedn_s ( const gchar *host, const gint port, const gchar *bindDN, + const gchar *bindPW, const gint tov ); +GList *syldap_read_basedn ( SyldapServer *ldapServer ); +gboolean syldap_test_connect_s ( const gchar *host, const gint port ); +gboolean syldap_test_connect ( SyldapServer *ldapServer ); +/* gboolean syldap_test_ldap_lib ( void ); */ + +#endif /* USE_LDAP */ + +#endif /* __SYLDAP_H__ */ diff --git a/src/sylpheed-marshal.list b/src/sylpheed-marshal.list new file mode 100644 index 00000000..7e722df0 --- /dev/null +++ b/src/sylpheed-marshal.list @@ -0,0 +1,2 @@ +NONE:POINTER +NONE:INT,POINTER diff --git a/src/template.c b/src/template.c new file mode 100644 index 00000000..ddfb2b5f --- /dev/null +++ b/src/template.c @@ -0,0 +1,219 @@ +/* + * Sylpheed templates subsystem + * Copyright (C) 2001 Alexander Barinov + * Copyright (C) 2001-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "defs.h" + +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "template.h" +#include "utils.h" + +static GSList *template_list; + +static Template *template_load(gchar *filename) +{ + Template *tmpl; + FILE *fp; + gchar buf[BUFFSIZE]; + gint bytes_read; + + if ((fp = fopen(filename, "rb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + return NULL; + } + + tmpl = g_new(Template, 1); + tmpl->name = NULL; + tmpl->to = NULL; + tmpl->cc = NULL; + tmpl->subject = NULL; + tmpl->value = NULL; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (buf[0] == '\n') + break; + else if (!g_strncasecmp(buf, "Name:", 5)) + tmpl->name = g_strdup(g_strstrip(buf + 5)); + else if (!g_strncasecmp(buf, "To:", 3)) + tmpl->to = g_strdup(g_strstrip(buf + 3)); + else if (!g_strncasecmp(buf, "Cc:", 3)) + tmpl->cc = g_strdup(g_strstrip(buf + 3)); + else if (!g_strncasecmp(buf, "Subject:", 8)) + tmpl->subject = g_strdup(g_strstrip(buf + 8)); + } + + if (!tmpl->name) { + g_warning("wrong template format\n"); + template_free(tmpl); + return NULL; + } + + if ((bytes_read = fread(buf, 1, sizeof(buf), fp)) == 0) { + if (ferror(fp)) { + FILE_OP_ERROR(filename, "fread"); + template_free(tmpl); + return NULL; + } + } + fclose(fp); + tmpl->value = g_strndup(buf, bytes_read); + + return tmpl; +} + +void template_free(Template *tmpl) +{ + g_free(tmpl->name); + g_free(tmpl->to); + g_free(tmpl->cc); + g_free(tmpl->subject); + g_free(tmpl->value); + g_free(tmpl); +} + +void template_clear_config(GSList *tmpl_list) +{ + GSList *cur; + Template *tmpl; + + for (cur = tmpl_list; cur != NULL; cur = cur->next) { + tmpl = (Template *)cur->data; + template_free(tmpl); + } + g_slist_free(tmpl_list); +} + +GSList *template_read_config(void) +{ + const gchar *path; + gchar *filename; + DIR *dp; + struct dirent *de; + struct stat s; + Template *tmpl; + GSList *tmpl_list = NULL; + + path = get_template_dir(); + debug_print("%s:%d reading templates dir %s\n", + __FILE__, __LINE__, path); + + if (!is_dir_exist(path)) { + if (make_dir(path) < 0) + return NULL; + } + + if ((dp = opendir(path)) == NULL) { + FILE_OP_ERROR(path, "opendir"); + return NULL; + } + + while ((de = readdir(dp)) != NULL) { + if (*de->d_name == '.') continue; + + filename = g_strconcat(path, G_DIR_SEPARATOR_S, de->d_name, NULL); + + if (stat(filename, &s) != 0 || !S_ISREG(s.st_mode) ) { + debug_print("%s:%d %s is not an ordinary file\n", + __FILE__, __LINE__, filename); + continue; + } + + tmpl = template_load(filename); + if (tmpl) + tmpl_list = g_slist_append(tmpl_list, tmpl); + g_free(filename); + } + + closedir(dp); + + return tmpl_list; +} + +void template_write_config(GSList *tmpl_list) +{ + const gchar *path; + GSList *cur; + Template *tmpl; + FILE *fp; + gint tmpl_num; + + debug_print("%s:%d writing templates\n", __FILE__, __LINE__); + + path = get_template_dir(); + + if (!is_dir_exist(path)) { + if (is_file_exist(path)) { + g_warning(_("file %s already exists\n"), path); + return; + } + if (make_dir(path) < 0) + return; + } + + remove_all_files(path); + + for (cur = tmpl_list, tmpl_num = 1; cur != NULL; + cur = cur->next, tmpl_num++) { + gchar *filename; + + tmpl = cur->data; + + filename = g_strconcat(path, G_DIR_SEPARATOR_S, + itos(tmpl_num), NULL); + + if ((fp = fopen(filename, "wb")) == NULL) { + FILE_OP_ERROR(filename, "fopen"); + g_free(filename); + return; + } + + fprintf(fp, "Name: %s\n", tmpl->name); + if (tmpl->to && *tmpl->to != '\0') + fprintf(fp, "To: %s\n", tmpl->to); + if (tmpl->cc && *tmpl->cc != '\0') + fprintf(fp, "Cc: %s\n", tmpl->cc); + if (tmpl->subject && *tmpl->subject != '\0') + fprintf(fp, "Subject: %s\n", tmpl->subject); + fputs("\n", fp); + fwrite(tmpl->value, sizeof(gchar) * strlen(tmpl->value), 1, fp); + fclose(fp); + } +} + +GSList *template_get_config(void) +{ + if (!template_list) + template_list = template_read_config(); + + return template_list; +} + +void template_set_config(GSList *tmpl_list) +{ + template_clear_config(template_list); + template_write_config(tmpl_list); + template_list = tmpl_list; +} diff --git a/src/template.h b/src/template.h new file mode 100644 index 00000000..a03b226a --- /dev/null +++ b/src/template.h @@ -0,0 +1,45 @@ +/* + * Sylpheed templates subsystem + * Copyright (C) 2001 Alexander Barinov + * Copyright (C) 2001-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __TEMPLATE_H__ +#define __TEMPLATE_H__ + +#include + +typedef struct _Template Template; + +struct _Template { + gchar *name; + gchar *to; + gchar *cc; + gchar *subject; + gchar *value; +}; + +void template_free (Template *tmpl); +void template_clear_config (GSList *tmpl_list); + +GSList *template_read_config (void); +void template_write_config (GSList *tmpl_list); + +GSList *template_get_config (void); +void template_set_config (GSList *tmpl_list); + +#endif /* __TEMPLATE_H__ */ diff --git a/src/textview.c b/src/textview.c new file mode 100644 index 00000000..69fe7ca8 --- /dev/null +++ b/src/textview.c @@ -0,0 +1,1664 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "main.h" +#include "summaryview.h" +#include "procheader.h" +#include "prefs_common.h" +#include "codeconv.h" +#include "statusbar.h" +#include "utils.h" +#include "gtkutils.h" +#include "procmime.h" +#include "account.h" +#include "html.h" +#include "compose.h" +#include "displayheader.h" +#include "alertpanel.h" + +typedef struct _RemoteURI RemoteURI; + +struct _RemoteURI +{ + gchar *uri; + + guint start; + guint end; +}; + +static GdkColor quote_colors[3] = { + {(gulong)0, (gushort)0, (gushort)0, (gushort)0}, + {(gulong)0, (gushort)0, (gushort)0, (gushort)0}, + {(gulong)0, (gushort)0, (gushort)0, (gushort)0} +}; + +static GdkColor uri_color = { + (gulong)0, + (gushort)0, + (gushort)0, + (gushort)0 +}; + +static GdkColor emphasis_color = { + (gulong)0, + (gushort)0, + (gushort)0, + (gushort)0xcfff +}; + +#if 0 +static GdkColor error_color = { + (gulong)0, + (gushort)0xefff, + (gushort)0, + (gushort)0 +}; +#endif + +#if USE_GPGME +static GdkColor good_sig_color = { + (gulong)0, + (gushort)0, + (gushort)0xbfff, + (gushort)0 +}; + +static GdkColor nocheck_sig_color = { + (gulong)0, + (gushort)0, + (gushort)0, + (gushort)0xcfff +}; + +static GdkColor bad_sig_color = { + (gulong)0, + (gushort)0xefff, + (gushort)0, + (gushort)0 +}; +#endif + +#define STATUSBAR_PUSH(textview, str) \ +{ \ + gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \ + textview->messageview->statusbar_cid, str); \ +} + +#define STATUSBAR_POP(textview) \ +{ \ + gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \ + textview->messageview->statusbar_cid); \ +} + +static void textview_add_part (TextView *textview, + MimeInfo *mimeinfo, + FILE *fp); +static void textview_add_parts (TextView *textview, + MimeInfo *mimeinfo, + FILE *fp); +static void textview_write_body (TextView *textview, + MimeInfo *mimeinfo, + FILE *fp, + const gchar *charset); +static void textview_show_html (TextView *textview, + FILE *fp, + CodeConverter *conv); + +static void textview_write_line (TextView *textview, + const gchar *str, + CodeConverter *conv); +static void textview_write_link (TextView *textview, + const gchar *str, + const gchar *uri, + CodeConverter *conv); + +static GPtrArray *textview_scan_header (TextView *textview, + FILE *fp); +static void textview_show_header (TextView *textview, + GPtrArray *headers); + +static gboolean textview_key_pressed (GtkWidget *widget, + GdkEventKey *event, + TextView *textview); +static gboolean textview_uri_button_pressed (GtkTextTag *tag, + GObject *obj, + GdkEvent *event, + GtkTextIter *iter, + TextView *textview); + +static void textview_smooth_scroll_do (TextView *textview, + gfloat old_value, + gfloat last_value, + gint step); +static void textview_smooth_scroll_one_line (TextView *textview, + gboolean up); +static gboolean textview_smooth_scroll_page (TextView *textview, + gboolean up); + +static gboolean textview_uri_security_check (TextView *textview, + RemoteURI *uri); +static void textview_uri_list_remove_all (GSList *uri_list); + + +TextView *textview_create(void) +{ + TextView *textview; + GtkWidget *vbox; + GtkWidget *scrolledwin; + GtkWidget *text; + GtkTextBuffer *buffer; + GtkClipboard *clipboard; + //PangoFontDescription *font_desc = NULL; + + debug_print(_("Creating text view...\n")); + textview = g_new0(TextView, 1); + + scrolledwin = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_widget_set_size_request + (scrolledwin, prefs_common.mainview_width, -1); + + text = gtk_text_view_new(); + gtk_widget_show(text); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 4); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 4); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); + clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_text_buffer_add_selection_clipboard(buffer, clipboard); + +#if 0 + if (prefs_common.normalfont) + font_desc = pango_font_description_from_string + (prefs_common.normalfont); + if (font_desc) + gtk_widget_modify_font(text, font_desc); + pango_font_description_free(font_desc); +#endif + + gtk_widget_ref(scrolledwin); + + gtk_container_add(GTK_CONTAINER(scrolledwin), text); + + g_signal_connect(G_OBJECT(text), "key_press_event", + G_CALLBACK(textview_key_pressed), textview); + + gtk_widget_show(scrolledwin); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0); + + gtk_widget_show(vbox); + + textview->vbox = vbox; + textview->scrolledwin = scrolledwin; + textview->text = text; + textview->uri_list = NULL; + textview->body_pos = 0; + //textview->cur_pos = 0; + textview->show_all_headers = FALSE; + + return textview; +} + +static void textview_create_tags(GtkTextView *text, TextView *textview) +{ + GtkTextBuffer *buffer; + GtkTextTag *tag; + + buffer = gtk_text_view_get_buffer(text); + + gtk_text_buffer_create_tag(buffer, "header", + "pixels-above-lines", 0, + "pixels-above-lines-set", TRUE, + "pixels-below-lines", 0, + "pixels-below-lines-set", TRUE, + //"left-margin", 0, + //"left-margin-set", TRUE, + NULL); + gtk_text_buffer_create_tag(buffer, "header_title", + "font", prefs_common.boldfont, + NULL); + gtk_text_buffer_create_tag(buffer, "quote0", + "foreground-gdk", "e_colors[0], + NULL); + gtk_text_buffer_create_tag(buffer, "quote1", + "foreground-gdk", "e_colors[1], + NULL); + gtk_text_buffer_create_tag(buffer, "quote2", + "foreground-gdk", "e_colors[2], + NULL); + gtk_text_buffer_create_tag(buffer, "emphasis", + "foreground-gdk", &emphasis_color, + NULL); + tag = gtk_text_buffer_create_tag(buffer, "link", + "foreground-gdk", &uri_color, + NULL); +#if USE_GPGME + gtk_text_buffer_create_tag(buffer, "good-signature", + "foreground-gdk", &good_sig_color, + NULL); + gtk_text_buffer_create_tag(buffer, "bad-signature", + "foreground-gdk", &bad_sig_color, + NULL); + gtk_text_buffer_create_tag(buffer, "nocheck-signature", + "foreground-gdk", &nocheck_sig_color, + NULL); +#endif /* USE_GPGME */ + + g_signal_connect(G_OBJECT(tag), "event", + G_CALLBACK(textview_uri_button_pressed), textview); +} + +void textview_init(TextView *textview) +{ + //gtkut_widget_disable_theme_engine(textview->text); + textview_update_message_colors(); + textview_set_all_headers(textview, FALSE); + textview_set_font(textview, NULL); + textview_create_tags(GTK_TEXT_VIEW(textview->text), textview); +} + +void textview_update_message_colors(void) +{ + GdkColor black = {0, 0, 0, 0}; + + if (prefs_common.enable_color) { + /* grab the quote colors, converting from an int to a GdkColor */ + gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col, + "e_colors[0]); + gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col, + "e_colors[1]); + gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col, + "e_colors[2]); + gtkut_convert_int_to_gdk_color(prefs_common.uri_col, + &uri_color); + } else { + quote_colors[0] = quote_colors[1] = quote_colors[2] = + uri_color = emphasis_color = black; + } +} + +void textview_show_message(TextView *textview, MimeInfo *mimeinfo, + const gchar *file) +{ + FILE *fp; + const gchar *charset = NULL; + GPtrArray *headers = NULL; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return; + } + + if (textview->messageview->forced_charset) + charset = textview->messageview->forced_charset; + else if (prefs_common.force_charset) + charset = prefs_common.force_charset; + else if (mimeinfo->charset) + charset = mimeinfo->charset; + + textview_set_font(textview, charset); + textview_clear(textview); + + if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek"); + headers = textview_scan_header(textview, fp); + if (headers) { + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + + textview_show_header(textview, headers); + procheader_header_array_destroy(headers); + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_end_iter(buffer, &iter); + textview->body_pos = gtk_text_iter_get_offset(&iter); + } + + textview_add_parts(textview, mimeinfo, fp); + + fclose(fp); + + textview_set_position(textview, 0); +} + +void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp) +{ + gchar buf[BUFFSIZE]; + const gchar *boundary = NULL; + gint boundary_len = 0; + const gchar *charset = NULL; + GPtrArray *headers = NULL; + gboolean is_rfc822_part = FALSE; + + g_return_if_fail(mimeinfo != NULL); + g_return_if_fail(fp != NULL); + + if (mimeinfo->mime_type == MIME_MULTIPART) { + textview_clear(textview); + textview_add_parts(textview, mimeinfo, fp); + return; + } + + if (mimeinfo->parent && mimeinfo->parent->boundary) { + boundary = mimeinfo->parent->boundary; + boundary_len = strlen(boundary); + } + + if (!boundary && mimeinfo->mime_type == MIME_TEXT) { + if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) + perror("fseek"); + headers = textview_scan_header(textview, fp); + } else { + if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) { + glong fpos; + MimeInfo *parent = mimeinfo->parent; + + while (parent->parent) { + if (parent->main && + parent->main->mime_type == + MIME_MESSAGE_RFC822) + break; + parent = parent->parent; + } + + if ((fpos = ftell(fp)) < 0) + perror("ftell"); + else if (fseek(fp, parent->fpos, SEEK_SET) < 0) + perror("fseek"); + else { + headers = textview_scan_header(textview, fp); + if (fseek(fp, fpos, SEEK_SET) < 0) + perror("fseek"); + } + } + /* skip MIME part headers */ + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + } + + /* display attached RFC822 single text message */ + if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + if (headers) procheader_header_array_destroy(headers); + if (!mimeinfo->sub) { + textview_clear(textview); + return; + } + headers = textview_scan_header(textview, fp); + mimeinfo = mimeinfo->sub; + is_rfc822_part = TRUE; + } + + if (textview->messageview->forced_charset) + charset = textview->messageview->forced_charset; + else if (prefs_common.force_charset) + charset = prefs_common.force_charset; + else if (mimeinfo->charset) + charset = mimeinfo->charset; + + textview_set_font(textview, charset); + + textview_clear(textview); + + if (headers) { + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + + textview_show_header(textview, headers); + procheader_header_array_destroy(headers); + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_end_iter(buffer, &iter); + textview->body_pos = gtk_text_iter_get_offset(&iter); + if (!mimeinfo->main) + gtk_text_buffer_insert(buffer, &iter, "\n", 1); + } + + if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part) + textview_add_parts(textview, mimeinfo, fp); + else + textview_write_body(textview, mimeinfo, fp, charset); +} + +static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + gchar buf[BUFFSIZE]; + const gchar *boundary = NULL; + gint boundary_len = 0; + const gchar *charset = NULL; + GPtrArray *headers = NULL; + + g_return_if_fail(mimeinfo != NULL); + g_return_if_fail(fp != NULL); + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_end_iter(buffer, &iter); + + if (mimeinfo->mime_type == MIME_MULTIPART) return; + + if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) { + perror("fseek"); + return; + } + + if (mimeinfo->parent && mimeinfo->parent->boundary) { + boundary = mimeinfo->parent->boundary; + boundary_len = strlen(boundary); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + + if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) { + headers = textview_scan_header(textview, fp); + if (headers) { + gtk_text_buffer_insert(buffer, &iter, "\n", 1); + textview_show_header(textview, headers); + procheader_header_array_destroy(headers); + } + return; + } + +#if USE_GPGME + if (mimeinfo->sigstatus) + g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n", + mimeinfo->content_type, mimeinfo->sigstatus); + else +#endif + if (mimeinfo->filename || mimeinfo->name) + g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n", + mimeinfo->filename ? mimeinfo->filename : + mimeinfo->name, + mimeinfo->content_type, mimeinfo->size); + else + g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n", + mimeinfo->content_type, mimeinfo->size); + +#if USE_GPGME + if (mimeinfo->sigstatus) { + const gchar *color; + if (!strcmp(mimeinfo->sigstatus, _("Good signature"))) + color = "good-signature"; + else if (!strcmp(mimeinfo->sigstatus, _("BAD signature"))) + color = "bad-signature"; + else + color = "nocheck-signature"; + gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, buf, -1, + color, NULL); + } else +#endif + if (mimeinfo->mime_type != MIME_TEXT && + mimeinfo->mime_type != MIME_TEXT_HTML) { + gtk_text_buffer_insert(buffer, &iter, buf, -1); + } else { + if (!mimeinfo->main && + mimeinfo->parent && + mimeinfo->parent->children != mimeinfo) + gtk_text_buffer_insert(buffer, &iter, buf, -1); + else + gtk_text_buffer_insert(buffer, &iter, "\n", 1); + if (textview->messageview->forced_charset) + charset = textview->messageview->forced_charset; + else if (prefs_common.force_charset) + charset = prefs_common.force_charset; + else if (mimeinfo->charset) + charset = mimeinfo->charset; + textview_write_body(textview, mimeinfo, fp, charset); + } +} + +static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp) +{ + gint level; + + g_return_if_fail(mimeinfo != NULL); + g_return_if_fail(fp != NULL); + + level = mimeinfo->level; + + for (;;) { + textview_add_part(textview, mimeinfo, fp); + if (mimeinfo->parent && mimeinfo->parent->content_type && + !strcasecmp(mimeinfo->parent->content_type, + "multipart/alternative")) + mimeinfo = mimeinfo->parent->next; + else + mimeinfo = procmime_mimeinfo_next(mimeinfo); + if (!mimeinfo || mimeinfo->level <= level) + break; + } +} + +#define TEXT_INSERT(str) \ + gtk_text_buffer_insert(buffer, &iter, str, -1) + +void textview_show_error(TextView *textview) +{ + GtkTextBuffer *buffer; + GtkTextIter iter; + + textview_set_font(textview, NULL); + textview_clear(textview); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text)); + gtk_text_buffer_get_start_iter(buffer, &iter); + TEXT_INSERT(_("This message can't be displayed.\n")); +} + +void textview_show_mime_part(TextView *textview, MimeInfo *partinfo) +{ + GtkTextBuffer *buffer; + GtkTextIter iter; + + if (!partinfo) return; + + textview_set_font(textview, NULL); + textview_clear(textview); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text)); + gtk_text_buffer_get_start_iter(buffer, &iter); + + TEXT_INSERT(_("To save this part, pop up the context menu with ")); + TEXT_INSERT(_("right click and select `Save as...', ")); + TEXT_INSERT(_("or press `y' key.\n\n")); + + TEXT_INSERT(_("To display this part as a text message, select ")); + TEXT_INSERT(_("`Display as text', or press `t' key.\n\n")); + + TEXT_INSERT(_("To open this part with external program, select ")); + TEXT_INSERT(_("`Open' or `Open with...', ")); + TEXT_INSERT(_("or double-click, or click the center button, ")); + TEXT_INSERT(_("or press `l' key.")); +} + +#if USE_GPGME +void textview_show_signature_part(TextView *textview, MimeInfo *partinfo) +{ + GtkTextBuffer *buffer; + GtkTextIter iter; + + if (!partinfo) return; + + textview_set_font(textview, NULL); + textview_clear(textview); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text)); + gtk_text_buffer_get_start_iter(buffer, &iter); + + if (partinfo->sigstatus_full == NULL) { + TEXT_INSERT(_("This signature has not been checked yet.\n")); + TEXT_INSERT(_("To check it, pop up the context menu with\n")); + TEXT_INSERT(_("right click and select `Check signature'.\n")); + } else { + TEXT_INSERT(partinfo->sigstatus_full); + } +} +#endif /* USE_GPGME */ + +#undef TEXT_INSERT + +static void textview_write_body(TextView *textview, MimeInfo *mimeinfo, + FILE *fp, const gchar *charset) +{ + FILE *tmpfp; + gchar buf[BUFFSIZE]; + CodeConverter *conv; + + conv = conv_code_converter_new(charset); + + tmpfp = procmime_decode_content(NULL, fp, mimeinfo); + if (tmpfp) { + if (mimeinfo->mime_type == MIME_TEXT_HTML) + textview_show_html(textview, tmpfp, conv); + else + while (fgets(buf, sizeof(buf), tmpfp) != NULL) + textview_write_line(textview, buf, conv); + fclose(tmpfp); + } + + conv_code_converter_destroy(conv); +} + +static void textview_show_html(TextView *textview, FILE *fp, + CodeConverter *conv) +{ + HTMLParser *parser; + gchar *str; + + parser = html_parser_new(fp, conv); + g_return_if_fail(parser != NULL); + + while ((str = html_parse(parser)) != NULL) { + if (parser->href != NULL) + textview_write_link(textview, str, parser->href, NULL); + else + textview_write_line(textview, str, NULL); + } + html_parser_destroy(parser); +} + +/* get_uri_part() - retrieves a URI starting from scanpos. + Returns TRUE if succesful */ +static gboolean get_uri_part(const gchar *start, const gchar *scanpos, + const gchar **bp, const gchar **ep) +{ + const gchar *ep_; + + g_return_val_if_fail(start != NULL, FALSE); + g_return_val_if_fail(scanpos != NULL, FALSE); + g_return_val_if_fail(bp != NULL, FALSE); + g_return_val_if_fail(ep != NULL, FALSE); + + *bp = scanpos; + + /* find end point of URI */ + for (ep_ = scanpos; *ep_ != '\0'; ep_++) { + if (!isgraph(*(const guchar *)ep_) || + !isascii(*(const guchar *)ep_) || + strchr("()<>\"", *ep_)) + break; + } + + /* no punctuation at end of string */ + + /* FIXME: this stripping of trailing punctuations may bite with other URIs. + * should pass some URI type to this function and decide on that whether + * to perform punctuation stripping */ + +#define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/')) + + for (; ep_ - 1 > scanpos + 1 && + IS_REAL_PUNCT(*(const guchar *)(ep_ - 1)); + ep_--) + ; + +#undef IS_REAL_PUNCT + + *ep = ep_; + + return TRUE; +} + +static gchar *make_uri_string(const gchar *bp, const gchar *ep) +{ + return g_strndup(bp, ep - bp); +} + +/* valid mail address characters */ +#define IS_RFC822_CHAR(ch) \ + (isascii(ch) && \ + (ch) > 32 && \ + (ch) != 127 && \ + !isspace(ch) && \ + !strchr("(),;<>\"", (ch))) + +/* alphabet and number within 7bit ASCII */ +#define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch)) + +/* get_email_part() - retrieves an email address. Returns TRUE if succesful */ +static gboolean get_email_part(const gchar *start, const gchar *scanpos, + const gchar **bp, const gchar **ep) +{ + /* more complex than the uri part because we need to scan back and forward starting from + * the scan position. */ + gboolean result = FALSE; + const gchar *bp_; + const gchar *ep_; + + g_return_val_if_fail(start != NULL, FALSE); + g_return_val_if_fail(scanpos != NULL, FALSE); + g_return_val_if_fail(bp != NULL, FALSE); + g_return_val_if_fail(ep != NULL, FALSE); + + /* scan start of address */ + for (bp_ = scanpos - 1; + bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--) + ; + + /* TODO: should start with an alnum? */ + bp_++; + for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++) + ; + + if (bp_ != scanpos) { + /* scan end of address */ + for (ep_ = scanpos + 1; + *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++) + ; + + /* TODO: really should terminate with an alnum? */ + for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_); + --ep_) + ; + ep_++; + + if (ep_ > scanpos + 1) { + *ep = ep_; + *bp = bp_; + result = TRUE; + } + } + + return result; +} + +#undef IS_ASCII_ALNUM +#undef IS_RFC822_CHAR + +static gchar *make_email_string(const gchar *bp, const gchar *ep) +{ + /* returns a mailto: URI; mailto: is also used to detect the + * uri type later on in the button_pressed signal handler */ + gchar *tmp; + gchar *result; + + tmp = g_strndup(bp, ep - bp); + result = g_strconcat("mailto:", tmp, NULL); + g_free(tmp); + + return result; +} + +#define ADD_TXT_POS(bp_, ep_, pti_) \ + if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \ + last = last->next; \ + last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \ + last->next = NULL; \ + } else { \ + g_warning("alloc error scanning URIs\n"); \ + gtk_text_buffer_insert_with_tags_by_name \ + (buffer, &iter, linebuf, -1, fg_tag, NULL); \ + return; \ + } + +/* textview_make_clickable_parts() - colorizes clickable parts */ +static void textview_make_clickable_parts(TextView *textview, + const gchar *fg_tag, + const gchar *uri_tag, + const gchar *linebuf) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + + /* parse table - in order of priority */ + struct table { + const gchar *needle; /* token */ + + /* token search function */ + gchar *(*search) (const gchar *haystack, + const gchar *needle); + /* part parsing function */ + gboolean (*parse) (const gchar *start, + const gchar *scanpos, + const gchar **bp_, + const gchar **ep_); + /* part to URI function */ + gchar *(*build_uri) (const gchar *bp, + const gchar *ep); + }; + + static struct table parser[] = { + {"http://", strcasestr, get_uri_part, make_uri_string}, + {"https://", strcasestr, get_uri_part, make_uri_string}, + {"ftp://", strcasestr, get_uri_part, make_uri_string}, + {"www.", strcasestr, get_uri_part, make_uri_string}, + {"mailto:", strcasestr, get_uri_part, make_uri_string}, + {"@", strcasestr, get_email_part, make_email_string} + }; + const gint PARSE_ELEMS = sizeof parser / sizeof parser[0]; + + gint n; + const gchar *walk, *bp, *ep; + + struct txtpos { + const gchar *bp, *ep; /* text position */ + gint pti; /* index in parse table */ + struct txtpos *next; /* next */ + } head = {NULL, NULL, 0, NULL}, *last = &head; + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_end_iter(buffer, &iter); + + /* parse for clickable parts, and build a list of begin and + end positions */ + for (walk = linebuf, n = 0;;) { + gint last_index = PARSE_ELEMS; + gchar *scanpos = NULL; + + /* FIXME: this looks phony. scanning for anything in the + parse table */ + for (n = 0; n < PARSE_ELEMS; n++) { + gchar *tmp; + + tmp = parser[n].search(walk, parser[n].needle); + if (tmp) { + if (scanpos == NULL || tmp < scanpos) { + scanpos = tmp; + last_index = n; + } + } + } + + if (scanpos) { + /* check if URI can be parsed */ + if (parser[last_index].parse(walk, scanpos, &bp, &ep) + && (ep - bp - 1) > strlen(parser[last_index].needle)) { + ADD_TXT_POS(bp, ep, last_index); + walk = ep; + } else + walk = scanpos + + strlen(parser[last_index].needle); + } else + break; + } + + /* colorize this line */ + if (head.next) { + const gchar *normal_text = linebuf; + + /* insert URIs */ + for (last = head.next; last != NULL; + normal_text = last->ep, last = last->next) { + RemoteURI *uri; + + uri = g_new(RemoteURI, 1); + if (last->bp - normal_text > 0) + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, + normal_text, + last->bp - normal_text, + fg_tag, NULL); + uri->uri = parser[last->pti].build_uri(last->bp, + last->ep); + uri->start = gtk_text_iter_get_offset(&iter); + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, last->bp, last->ep - last->bp, + uri_tag, NULL); + uri->end = gtk_text_iter_get_offset(&iter); + textview->uri_list = + g_slist_append(textview->uri_list, uri); + } + + if (*normal_text) + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, normal_text, -1, fg_tag, NULL); + } else { + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, linebuf, -1, fg_tag, NULL); + } +} + +#undef ADD_TXT_POS + +static void textview_write_line(TextView *textview, const gchar *str, + CodeConverter *conv) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + gchar buf[BUFFSIZE]; + gchar *fg_color; + gint quotelevel = -1; + gchar quote_tag_str[10]; + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_end_iter(buffer, &iter); + + if (!conv) + strncpy2(buf, str, sizeof(buf)); + else if (conv_convert(conv, buf, sizeof(buf), str) < 0) + conv_localetodisp(buf, sizeof(buf), str); + + strcrchomp(buf); + //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf); + fg_color = NULL; + + /* change color of quotation + >, foo>, _> ... ok, , foo bar>, foo-> ... ng + Up to 3 levels of quotations are detected, and each + level is colored using a different color. */ + if (prefs_common.enable_color && strchr(buf, '>')) { + quotelevel = get_quote_level(buf); + + /* set up the correct foreground color */ + if (quotelevel > 2) { + /* recycle colors */ + if (prefs_common.recycle_quote_colors) + quotelevel %= 3; + else + quotelevel = 2; + } + } + + if (quotelevel == -1) + fg_color = NULL; + else { + g_snprintf(quote_tag_str, sizeof(quote_tag_str), + "quote%d", quotelevel); + fg_color = quote_tag_str; + } + + if (prefs_common.enable_color) + textview_make_clickable_parts(textview, fg_color, "link", buf); + else + textview_make_clickable_parts(textview, fg_color, NULL, buf); +} + +void textview_write_link(TextView *textview, const gchar *str, + const gchar *uri, CodeConverter *conv) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + gchar buf[BUFFSIZE]; + gchar *bufp; + RemoteURI *r_uri; + + if (*str == '\0') + return; + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_get_end_iter(buffer, &iter); + + if (!conv) + strncpy2(buf, str, sizeof(buf)); + else if (conv_convert(conv, buf, sizeof(buf), str) < 0) + conv_localetodisp(buf, sizeof(buf), str); + + strcrchomp(buf); + + for (bufp = buf; isspace(*(guchar *)bufp); bufp++) + gtk_text_buffer_insert(buffer, &iter, bufp, 1); + + r_uri = g_new(RemoteURI, 1); + r_uri->uri = g_strdup(uri); + r_uri->start = gtk_text_iter_get_offset(&iter); + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, bufp, -1, "link", NULL); + r_uri->end = gtk_text_iter_get_offset(&iter); + textview->uri_list = g_slist_append(textview->uri_list, r_uri); +} + +void textview_clear(TextView *textview) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer(text); + gtk_text_buffer_set_text(buffer, "", -1); + + STATUSBAR_POP(textview); + textview_uri_list_remove_all(textview->uri_list); + textview->uri_list = NULL; + + textview->body_pos = 0; + //textview->cur_pos = 0; +} + +void textview_destroy(TextView *textview) +{ + textview_uri_list_remove_all(textview->uri_list); + textview->uri_list = NULL; + g_free(textview); +} + +void textview_set_all_headers(TextView *textview, gboolean all_headers) +{ + textview->show_all_headers = all_headers; +} + +void textview_set_font(TextView *textview, const gchar *codeset) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + + if (prefs_common.textfont) { + PangoFontDescription *font_desc; + font_desc = pango_font_description_from_string + (prefs_common.textfont); + if (font_desc) { + gtk_widget_modify_font(textview->text, font_desc); + pango_font_description_free(font_desc); + } + } + + gtk_text_view_set_pixels_above_lines(text, prefs_common.line_space / 2); + gtk_text_view_set_pixels_below_lines(text, prefs_common.line_space / 2); +#if 0 + if (prefs_common.head_space) + gtk_text_view_set_left_margin(text, 6); + else + gtk_text_view_set_left_margin(text, 0); +#endif +} + +void textview_set_position(TextView *textview, gint pos) +{ + GtkTextBuffer *buffer; + GtkTextIter iter; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text)); + gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos); + gtk_text_buffer_place_cursor(buffer, &iter); +} + +static GPtrArray *textview_scan_header(TextView *textview, FILE *fp) +{ + gchar buf[BUFFSIZE]; + GPtrArray *headers, *sorted_headers; + GSList *disphdr_list; + Header *header; + gint i; + + g_return_val_if_fail(fp != NULL, NULL); + + if (textview->show_all_headers) + return procheader_get_header_array_asis(fp); + + if (!prefs_common.display_header) { + while (fgets(buf, sizeof(buf), fp) != NULL) + if (buf[0] == '\r' || buf[0] == '\n') break; + return NULL; + } + + headers = procheader_get_header_array_asis(fp); + + sorted_headers = g_ptr_array_new(); + + for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL; + disphdr_list = disphdr_list->next) { + DisplayHeaderProp *dp = + (DisplayHeaderProp *)disphdr_list->data; + + for (i = 0; i < headers->len; i++) { + header = g_ptr_array_index(headers, i); + + if (!g_strcasecmp(header->name, dp->name)) { + if (dp->hidden) + procheader_header_free(header); + else + g_ptr_array_add(sorted_headers, header); + + g_ptr_array_remove_index(headers, i); + i--; + } + } + } + + if (prefs_common.show_other_header) { + for (i = 0; i < headers->len; i++) { + header = g_ptr_array_index(headers, i); + g_ptr_array_add(sorted_headers, header); + } + g_ptr_array_free(headers, TRUE); + } else + procheader_header_array_destroy(headers); + + + return sorted_headers; +} + +static void textview_show_header(TextView *textview, GPtrArray *headers) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkTextBuffer *buffer; + GtkTextIter iter; + Header *header; + gint i; + + g_return_if_fail(headers != NULL); + + buffer = gtk_text_view_get_buffer(text); + + for (i = 0; i < headers->len; i++) { + header = g_ptr_array_index(headers, i); + g_return_if_fail(header->name != NULL); + + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, header->name, -1, + "header_title", "header", NULL); + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, ":", 1, + "header_title", "header", NULL); + + if (!g_strcasecmp(header->name, "Subject") || + !g_strcasecmp(header->name, "From") || + !g_strcasecmp(header->name, "To") || + !g_strcasecmp(header->name, "Cc")) + unfold_line(header->body); + +#if 0 + if (textview->text_is_mb == TRUE) + conv_unreadable_locale(header->body); +#endif + + if (prefs_common.enable_color && + (!strncmp(header->name, "X-Mailer", 8) || + !strncmp(header->name, "X-Newsreader", 12)) && + strstr(header->body, "Sylpheed") != NULL) { + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, header->body, -1, + "header", "emphasis", NULL); + } else if (prefs_common.enable_color) { + textview_make_clickable_parts + (textview, "header", "link", header->body); + } else { + textview_make_clickable_parts + (textview, "header", NULL, header->body); + } + gtk_text_buffer_get_end_iter(buffer, &iter); // + gtk_text_buffer_insert_with_tags_by_name + (buffer, &iter, "\n", 1, "header", NULL); + } +} + +gboolean textview_search_string(TextView *textview, const gchar *str, + gboolean case_sens) +{ +#warning FIXME_GTK2 +#if 0 + GtkSText *text = GTK_STEXT(textview->text); + gint pos; + gint len; + + g_return_val_if_fail(str != NULL, FALSE); + + len = get_mbs_len(str); + g_return_val_if_fail(len >= 0, FALSE); + + pos = textview->cur_pos; + if (pos < textview->body_pos) + pos = textview->body_pos; + + if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) { + gtk_editable_set_position(GTK_EDITABLE(text), pos + len); + gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len); + textview_set_position(textview, pos + len); + return TRUE; + } + +#endif + return FALSE; +} + +gboolean textview_search_string_backward(TextView *textview, const gchar *str, + gboolean case_sens) +{ +#warning FIXME_GTK2 +#if 0 + GtkSText *text = GTK_STEXT(textview->text); + gint pos; + wchar_t *wcs; + gint len; + gint text_len; + gboolean found = FALSE; + + g_return_val_if_fail(str != NULL, FALSE); + + wcs = strdup_mbstowcs(str); + g_return_val_if_fail(wcs != NULL, FALSE); + len = wcslen(wcs); + pos = textview->cur_pos; + text_len = gtk_stext_get_length(text); + if (text_len - textview->body_pos < len) { + g_free(wcs); + return FALSE; + } + if (pos <= textview->body_pos || text_len - pos < len) + pos = text_len - len; + + for (; pos >= textview->body_pos; pos--) { + if (gtkut_stext_match_string(text, pos, wcs, len, case_sens) + == TRUE) { + gtk_editable_set_position(GTK_EDITABLE(text), pos); + gtk_editable_select_region(GTK_EDITABLE(text), + pos, pos + len); + textview_set_position(textview, pos - 1); + found = TRUE; + break; + } + if (pos == textview->body_pos) break; + } + + g_free(wcs); + return found; +#endif + return FALSE; +} + +void textview_scroll_one_line(TextView *textview, gboolean up) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkAdjustment *vadj = text->vadjustment; + gfloat upper; + + if (prefs_common.enable_smooth_scroll) { + textview_smooth_scroll_one_line(textview, up); + return; + } + + if (!up) { + upper = vadj->upper - vadj->page_size; + if (vadj->value < upper) { + vadj->value += vadj->step_increment * 4; + vadj->value = MIN(vadj->value, upper); + g_signal_emit_by_name(G_OBJECT(vadj), + "value_changed", 0); + } + } else { + if (vadj->value > 0.0) { + vadj->value -= vadj->step_increment * 4; + vadj->value = MAX(vadj->value, 0.0); + g_signal_emit_by_name(G_OBJECT(vadj), + "value_changed", 0); + } + } +} + +gboolean textview_scroll_page(TextView *textview, gboolean up) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkAdjustment *vadj = text->vadjustment; + gfloat upper; + gfloat page_incr; + + if (prefs_common.enable_smooth_scroll) + return textview_smooth_scroll_page(textview, up); + + if (prefs_common.scroll_halfpage) + page_incr = vadj->page_increment / 2; + else + page_incr = vadj->page_increment; + + if (!up) { + upper = vadj->upper - vadj->page_size; + if (vadj->value < upper) { + vadj->value += page_incr; + vadj->value = MIN(vadj->value, upper); + g_signal_emit_by_name(G_OBJECT(vadj), + "value_changed", 0); + } else + return FALSE; + } else { + if (vadj->value > 0.0) { + vadj->value -= page_incr; + vadj->value = MAX(vadj->value, 0.0); + g_signal_emit_by_name(G_OBJECT(vadj), + "value_changed", 0); + } else + return FALSE; + } + + return TRUE; +} + +static void textview_smooth_scroll_do(TextView *textview, + gfloat old_value, gfloat last_value, + gint step) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkAdjustment *vadj = text->vadjustment; + gint change_value; + gboolean up; + gint i; + + if (old_value < last_value) { + change_value = last_value - old_value; + up = FALSE; + } else { + change_value = old_value - last_value; + up = TRUE; + } + +#warning FIXME_GTK2 + /* gdk_key_repeat_disable(); */ + + for (i = step; i <= change_value; i += step) { + vadj->value = old_value + (up ? -i : i); + g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0); + } + + vadj->value = last_value; + g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0); + +#warning FIXME_GTK2 + /* gdk_key_repeat_restore(); */ +} + +static void textview_smooth_scroll_one_line(TextView *textview, gboolean up) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkAdjustment *vadj = text->vadjustment; + gfloat upper; + gfloat old_value; + gfloat last_value; + + if (!up) { + upper = vadj->upper - vadj->page_size; + if (vadj->value < upper) { + old_value = vadj->value; + last_value = vadj->value + vadj->step_increment * 4; + last_value = MIN(last_value, upper); + + textview_smooth_scroll_do(textview, old_value, + last_value, + prefs_common.scroll_step); + } + } else { + if (vadj->value > 0.0) { + old_value = vadj->value; + last_value = vadj->value - vadj->step_increment * 4; + last_value = MAX(last_value, 0.0); + + textview_smooth_scroll_do(textview, old_value, + last_value, + prefs_common.scroll_step); + } + } +} + +static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up) +{ + GtkTextView *text = GTK_TEXT_VIEW(textview->text); + GtkAdjustment *vadj = text->vadjustment; + gfloat upper; + gfloat page_incr; + gfloat old_value; + gfloat last_value; + + if (prefs_common.scroll_halfpage) + page_incr = vadj->page_increment / 2; + else + page_incr = vadj->page_increment; + + if (!up) { + upper = vadj->upper - vadj->page_size; + if (vadj->value < upper) { + old_value = vadj->value; + last_value = vadj->value + page_incr; + last_value = MIN(last_value, upper); + + textview_smooth_scroll_do(textview, old_value, + last_value, + prefs_common.scroll_step); + } else + return FALSE; + } else { + if (vadj->value > 0.0) { + old_value = vadj->value; + last_value = vadj->value - page_incr; + last_value = MAX(last_value, 0.0); + + textview_smooth_scroll_do(textview, old_value, + last_value, + prefs_common.scroll_step); + } else + return FALSE; + } + + return TRUE; +} + +#warning FIXME_GTK2 +#if 0 +#define KEY_PRESS_EVENT_STOP() \ + if (gtk_signal_n_emissions_by_name \ + (GTK_OBJECT(widget), "key_press_event") > 0) { \ + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \ + "key_press_event"); \ + } +#else +#define KEY_PRESS_EVENT_STOP() \ + g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); +#endif + +static gboolean textview_key_pressed(GtkWidget *widget, GdkEventKey *event, + TextView *textview) +{ + SummaryView *summaryview = NULL; + MessageView *messageview = textview->messageview; + + if (!event) return FALSE; + if (messageview->mainwin) + summaryview = messageview->mainwin->summaryview; + + switch (event->keyval) { + case GDK_Tab: + case GDK_Home: + case GDK_Left: + case GDK_Up: + case GDK_Right: + case GDK_Down: + case GDK_Page_Up: + case GDK_Page_Down: + case GDK_End: + case GDK_Control_L: + case GDK_Control_R: + break; + case GDK_space: + if (summaryview) + summary_pass_key_press_event(summaryview, event); + else + textview_scroll_page + (textview, + (event->state & + (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0); + break; + case GDK_BackSpace: + textview_scroll_page(textview, TRUE); + break; + case GDK_Return: + textview_scroll_one_line + (textview, (event->state & + (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0); + break; + case GDK_Delete: + if (summaryview) + summary_pass_key_press_event(summaryview, event); + break; + case GDK_n: + case GDK_N: + case GDK_p: + case GDK_P: + case GDK_y: + case GDK_t: + case GDK_l: + if (messageview->type == MVIEW_MIME && + textview == messageview->mimeview->textview) { + KEY_PRESS_EVENT_STOP(); + mimeview_pass_key_press_event(messageview->mimeview, + event); + break; + } + /* fall through */ + default: + if (summaryview && + event->window != messageview->mainwin->window->window) { + GdkEventKey tmpev = *event; + + tmpev.window = messageview->mainwin->window->window; + KEY_PRESS_EVENT_STOP(); + gtk_widget_event(messageview->mainwin->window, + (GdkEvent *)&tmpev); + } + break; + } + + return FALSE; +} + +static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj, + GdkEvent *event, GtkTextIter *iter, + TextView *textview) +{ + GtkTextIter start_iter, end_iter; + gint start_pos, end_pos; + GdkEventButton *bevent; + RemoteURI *uri = NULL; + GSList *cur; + gchar *trimmed_uri; + + if (!event) + return FALSE; + + if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS) + return FALSE; + + start_iter = *iter; + + if (!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) { + debug_print("Can't find start point."); + return FALSE; + } + start_pos = gtk_text_iter_get_offset(&start_iter); + + end_iter = *iter; + if (!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) { + debug_print("Can't find end"); + return FALSE; + } + end_pos = gtk_text_iter_get_offset(&end_iter); + + for (cur = textview->uri_list; cur != NULL; cur = cur->next) { + RemoteURI *uri_ = (RemoteURI *)cur->data; + + if (start_pos == uri_->start && end_pos == uri_->end) { + uri = uri_; + break; + } + } + + STATUSBAR_POP(textview); + + if (!uri) + return FALSE; + + trimmed_uri = trim_string(uri->uri, 60); + STATUSBAR_PUSH(textview, trimmed_uri); + g_free(trimmed_uri); + + bevent = (GdkEventButton *)event; + if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) || + bevent->button == 2) { + if (!g_strncasecmp(uri->uri, "mailto:", 7)) { + PrefsAccount *ac = NULL; + MsgInfo *msginfo = textview->messageview->msginfo; + + if (msginfo && msginfo->folder) + ac = account_find_from_item(msginfo->folder); + if (ac && ac->protocol == A_NNTP) + ac = NULL; + compose_new(ac, msginfo->folder, uri->uri + 7, NULL); + } else if (textview_uri_security_check(textview, uri) == TRUE) { + open_uri(uri->uri, prefs_common.uri_cmd); + return TRUE; // + } + } + + return FALSE; +} + +static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri) +{ + GtkTextBuffer *buffer; + GtkTextIter start_iter, end_iter; + gchar *visible_str; + gboolean retval = TRUE; + + if (is_uri_string(uri->uri) == FALSE) + return TRUE; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text)); + gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start); + gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, uri->end); + visible_str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, + FALSE); + if (visible_str == NULL) + return TRUE; + + if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) { + gchar *uri_path; + gchar *visible_uri_path; + + uri_path = get_uri_path(uri->uri); + visible_uri_path = get_uri_path(visible_str); + if (strcmp(uri_path, visible_uri_path) != 0) + retval = FALSE; + } + + if (retval == FALSE) { + gchar *msg; + AlertValue aval; + + msg = g_strdup_printf(_("The real URL (%s) is different from\n" + "the apparent URL (%s).\n" + "Open it anyway?"), + uri->uri, visible_str); + aval = alertpanel(_("Warning"), msg, _("Yes"), _("No"), NULL); + g_free(msg); + if (aval == G_ALERTDEFAULT) + retval = TRUE; + } + + g_free(visible_str); + + return retval; +} + +static void textview_uri_list_remove_all(GSList *uri_list) +{ + GSList *cur; + + for (cur = uri_list; cur != NULL; cur = cur->next) { + if (cur->data) { + g_free(((RemoteURI *)cur->data)->uri); + g_free(cur->data); + } + } + + g_slist_free(uri_list); +} diff --git a/src/textview.h b/src/textview.h new file mode 100644 index 00000000..1fcc3ce4 --- /dev/null +++ b/src/textview.h @@ -0,0 +1,90 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __TEXTVIEW_H__ +#define __TEXTVIEW_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct _TextView TextView; + +#include "messageview.h" +#include "procmime.h" + +struct _TextView +{ + GtkWidget *vbox; + GtkWidget *scrolledwin; + GtkWidget *text; + + GtkWidget *popup_menu; + GtkItemFactory *popup_factory; + + GSList *uri_list; + gint body_pos; + //gint cur_pos; + + gboolean show_all_headers; + + MessageView *messageview; +}; + +TextView *textview_create (void); +void textview_init (TextView *textview); +void textview_show_message (TextView *textview, + MimeInfo *mimeinfo, + const gchar *file); +void textview_show_part (TextView *textview, + MimeInfo *mimeinfo, + FILE *fp); +void textview_show_error (TextView *textview); +void textview_show_mime_part (TextView *textview, + MimeInfo *partinfo); +#if USE_GPGME +void textview_show_signature_part(TextView *textview, + MimeInfo *partinfo); +#endif +void textview_clear (TextView *textview); +void textview_destroy (TextView *textview); +void textview_set_all_headers (TextView *textview, + gboolean all_headers); +void textview_set_font (TextView *textview, + const gchar *codeset); +void textview_set_position (TextView *textview, + gint pos); +void textview_scroll_one_line (TextView *textview, + gboolean up); +gboolean textview_scroll_page (TextView *textview, + gboolean up); + +void textview_update_message_colors (void); + +gboolean textview_search_string (TextView *textview, + const gchar *str, + gboolean case_sens); +gboolean textview_search_string_backward (TextView *textview, + const gchar *str, + gboolean case_sens); + +#endif /* __TEXTVIEW_H__ */ diff --git a/src/undo.c b/src/undo.c new file mode 100644 index 00000000..3828aebb --- /dev/null +++ b/src/undo.c @@ -0,0 +1,646 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* code ported from gedit */ +/* This is for my patient girlfirend Regina */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include /* for strlen */ +#include /* for mbstowcs */ + +#include "undo.h" +#include "utils.h" +#include "prefs_common.h" + +typedef struct _UndoInfo UndoInfo; + +struct _UndoInfo +{ + UndoAction action; + gchar *text; + gint start_pos; + gint end_pos; + gfloat window_position; + gint mergeable; +}; + +static void undo_free_list (GList **list_pointer); +static void undo_check_size (UndoMain *undostruct); +static gint undo_merge (GList *list, + guint start_pos, + guint end_pos, + gint action, + const guchar *text); +static void undo_add (const gchar *text, + gint start_pos, + gint end_pos, + UndoAction action, + UndoMain *undostruct); +static gint undo_get_selection (GtkTextView *textview, + guint *start, + guint *end); +static void undo_insert_text_cb (GtkTextBuffer *textbuf, + GtkTextIter *iter, + gchar *new_text, + gint new_text_length, + UndoMain *undostruct); +static void undo_delete_text_cb (GtkTextBuffer *textbuf, + GtkTextIter *start, + GtkTextIter *end, + UndoMain *undostruct); + +static void undo_paste_clipboard_cb (GtkTextView *textview, + UndoMain *undostruct); + +void undo_undo (UndoMain *undostruct); +void undo_redo (UndoMain *undostruct); + + +UndoMain *undo_init(GtkWidget *text) +{ + UndoMain *undostruct; + GtkTextView *textview = GTK_TEXT_VIEW(text); + GtkTextBuffer *textbuf; + + g_return_val_if_fail(text != NULL, NULL); + + textbuf = gtk_text_view_get_buffer(textview); + + undostruct = g_new(UndoMain, 1); + undostruct->textview = textview; + undostruct->undo = NULL; + undostruct->redo = NULL; + undostruct->paste = 0; + undostruct->undo_state = FALSE; + undostruct->redo_state = FALSE; + + g_signal_connect(G_OBJECT(text), "insert-text", + G_CALLBACK(undo_insert_text_cb), undostruct); + g_signal_connect(G_OBJECT(text), "delete-text", + G_CALLBACK(undo_delete_text_cb), undostruct); + g_signal_connect(G_OBJECT(text), "paste-clipboard", + G_CALLBACK(undo_paste_clipboard_cb), undostruct); + + return undostruct; +} + +void undo_destroy (UndoMain *undostruct) +{ + undo_free_list(&undostruct->undo); + undo_free_list(&undostruct->redo); + g_free(undostruct); +} + +static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos, + UndoAction action, gfloat window_position) +{ + UndoInfo *undoinfo; + undoinfo = g_new (UndoInfo, 1); + undoinfo->text = text; + undoinfo->start_pos = start_pos; + undoinfo->end_pos = end_pos; + undoinfo->action = action; + undoinfo->window_position = window_position; + return undoinfo; +} + +static void undo_object_free(UndoInfo *undo) +{ + g_free (undo->text); + g_free (undo); +} + +/** + * undo_free_list: + * @list_pointer: list to be freed + * + * frees and undo structure list + **/ +static void undo_free_list(GList **list_pointer) +{ + UndoInfo *undo; + GList *cur, *list = *list_pointer; + + if (list == NULL) return; + + for (cur = list; cur != NULL; cur = cur->next) { + undo = (UndoInfo *)cur->data; + undo_object_free(undo); + } + + g_list_free(list); + *list_pointer = NULL; +} + +void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func, + gpointer data) +{ + g_return_if_fail(undostruct != NULL); + + undostruct->change_state_func = func; + undostruct->change_state_data = data; +} + +/** + * undo_check_size: + * @compose: document to check + * + * Checks that the size of compose->undo does not excede settings->undo_levels and + * frees any undo level above sett->undo_level. + * + **/ +static void undo_check_size(UndoMain *undostruct) +{ + UndoInfo *last_undo; + guint length; + + if (prefs_common.undolevels < 1) return; + + /* No need to check for the redo list size since the undo + list gets freed on any call to compose_undo_add */ + length = g_list_length(undostruct->undo); + if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) { + last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data; + undostruct->undo = g_list_remove(undostruct->undo, last_undo); + undo_object_free(last_undo); + } +} + +/** + * undo_merge: + * @last_undo: + * @start_pos: + * @end_pos: + * @action: + * + * This function tries to merge the undo object at the top of + * the stack with a new set of data. So when we undo for example + * typing, we can undo the whole word and not each letter by itself + * + * Return Value: TRUE is merge was sucessful, FALSE otherwise + **/ +static gint undo_merge(GList *list, guint start_pos, guint end_pos, + gint action, const guchar *text) +{ + guchar *temp_string; + UndoInfo *last_undo; + + /* This are the cases in which we will NOT merge : + 1. if (last_undo->mergeable == FALSE) + [mergeable = FALSE when the size of the undo data was not 1. + or if the data was size = 1 but = '\n' or if the undo object + has been "undone" already ] + 2. The size of text is not 1 + 3. If the new merging data is a '\n' + 4. If the last char of the undo_last data is a space/tab + and the new char is not a space/tab ( so that we undo + words and not chars ) + 5. If the type (action) of undo is different from the last one + Chema */ + + if (list == NULL) return FALSE; + + last_undo = list->data; + + if (!last_undo->mergeable) return FALSE; + + if (end_pos - start_pos != 1 || + text[0] == '\n' || + action != last_undo->action || + action == UNDO_ACTION_REPLACE_INSERT || + action == UNDO_ACTION_REPLACE_DELETE) { + last_undo->mergeable = FALSE; + return FALSE; + } + + if (action == UNDO_ACTION_DELETE) { + gboolean checkit = TRUE; + + if (last_undo->start_pos != end_pos && + last_undo->start_pos != start_pos) { + last_undo->mergeable = FALSE; + return FALSE; + } else if (last_undo->start_pos == start_pos) { + /* Deleted with the delete key */ + if (text[0] != ' ' && text[0] != '\t' && + (last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == ' ' || + last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == '\t')) + checkit = FALSE; + + temp_string = g_strdup_printf("%s%s", last_undo->text, text); + last_undo->end_pos++; + g_free(last_undo->text); + last_undo->text = temp_string; + } else { + /* Deleted with the backspace key */ + if (text[0] != ' ' && text[0] != '\t' && + (last_undo->text[0] == ' ' || + last_undo->text[0] == '\t')) + checkit = FALSE; + + temp_string = g_strdup_printf("%s%s", text, last_undo->text); + last_undo->start_pos = start_pos; + g_free(last_undo->text); + last_undo->text = temp_string; + } + + if (!checkit) { + last_undo->mergeable = FALSE; + return FALSE; + } + } else if (action == UNDO_ACTION_INSERT) { + if (last_undo->end_pos != start_pos) { + last_undo->mergeable = FALSE; + return FALSE; + } else { + temp_string = g_strdup_printf("%s%s", last_undo->text, text); + g_free(last_undo->text); + last_undo->end_pos = end_pos; + last_undo->text = temp_string; + } + } else + debug_print("Unknown action [%i] inside undo merge encountered", action); + + return TRUE; +} + +/** + * compose_undo_add: + * @text: + * @start_pos: + * @end_pos: + * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE + * @compose: + * @view: The view so that we save the scroll bar position. + * + * Adds text to the undo stack. It also performs test to limit the number + * of undo levels and deltes the redo list + **/ + +static void undo_add(const gchar *text, + gint start_pos, gint end_pos, + UndoAction action, UndoMain *undostruct) +{ + UndoInfo *undoinfo; + GtkAdjustment *vadj; + + g_return_if_fail(text != NULL); + g_return_if_fail(end_pos >= start_pos); + + undo_free_list(&undostruct->redo); + + /* Set the redo sensitivity */ + undostruct->change_state_func(undostruct, + UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE, + undostruct->change_state_data); + + if (undostruct->paste != 0) { + if (action == UNDO_ACTION_INSERT) + action = UNDO_ACTION_REPLACE_INSERT; + else + action = UNDO_ACTION_REPLACE_DELETE; + undostruct->paste = undostruct->paste + 1; + if (undostruct->paste == 3) + undostruct->paste = 0; + } + + if (undo_merge(undostruct->undo, start_pos, end_pos, action, text)) + return; + + undo_check_size(undostruct); + + vadj = GTK_ADJUSTMENT(GTK_TEXT_VIEW(undostruct->textview)->vadjustment); + undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action, + vadj->value); + + if (end_pos - start_pos != 1 || text[0] == '\n') + undoinfo->mergeable = FALSE; + else + undoinfo->mergeable = TRUE; + + undostruct->undo = g_list_prepend(undostruct->undo, undoinfo); + + undostruct->change_state_func(undostruct, + UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, + undostruct->change_state_data); +} + +/** + * undo_undo: + * @w: not used + * @data: not used + * + * Executes an undo request on the current document + **/ +void undo_undo(UndoMain *undostruct) +{ + UndoInfo *undoinfo; + GtkTextView *textview; + GtkTextBuffer *buffer; + GtkTextIter iter, start_iter, end_iter; + GtkTextMark *mark; + + g_return_if_fail(undostruct != NULL); + + if (undostruct->undo == NULL) return; + + /* The undo data we need is always at the top op the + stack. So, therefore, the first one */ + undoinfo = (UndoInfo *)undostruct->undo->data; + g_return_if_fail(undoinfo != NULL); + undoinfo->mergeable = FALSE; + undostruct->redo = g_list_prepend(undostruct->redo, undoinfo); + undostruct->undo = g_list_remove(undostruct->undo, undoinfo); + + textview = undostruct->textview; + buffer = gtk_text_view_get_buffer(textview); + + undo_block(undostruct); + + /* Check if there is a selection active */ + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + gtk_text_buffer_place_cursor(buffer, &iter); + + /* Move the view (scrollbars) to the correct position */ + gtk_adjustment_set_value(GTK_ADJUSTMENT(textview->vadjustment), + undoinfo->window_position); + + switch (undoinfo->action) { + case UNDO_ACTION_DELETE: + gtk_text_buffer_get_iter_at_offset + (buffer, &iter, undoinfo->start_pos); + gtk_text_buffer_insert(buffer, &iter, undoinfo->text, -1); + debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text); + break; + case UNDO_ACTION_INSERT: + gtk_text_buffer_get_iter_at_offset + (buffer, &start_iter, undoinfo->start_pos); + gtk_text_buffer_get_iter_at_offset + (buffer, &end_iter, undoinfo->end_pos); + gtk_text_buffer_delete(buffer, &start_iter, &end_iter); + debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos); + break; + case UNDO_ACTION_REPLACE_INSERT: + gtk_text_buffer_get_iter_at_offset + (buffer, &start_iter, undoinfo->start_pos); + gtk_text_buffer_get_iter_at_offset + (buffer, &end_iter, undoinfo->end_pos); + debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text); + /* "pull" another data structure from the list */ + undoinfo = (UndoInfo *)undostruct->undo->data; + g_return_if_fail(undoinfo != NULL); + undostruct->redo = g_list_prepend(undostruct->redo, undoinfo); + undostruct->undo = g_list_remove(undostruct->undo, undoinfo); + g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE); + gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1); + debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text); + break; + case UNDO_ACTION_REPLACE_DELETE: + g_warning("This should not happen. UNDO_REPLACE_DELETE"); + break; + default: + g_assert_not_reached(); + break; + } + + undostruct->change_state_func(undostruct, + UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE, + undostruct->change_state_data); + + if (undostruct->undo == NULL) + undostruct->change_state_func(undostruct, + UNDO_STATE_FALSE, + UNDO_STATE_UNCHANGED, + undostruct->change_state_data); + + undo_unblock(undostruct); +} + +/** + * undo_redo: + * @w: not used + * @data: not used + * + * executes a redo request on the current document + **/ +void undo_redo(UndoMain *undostruct) +{ + UndoInfo *redoinfo; + GtkTextView *textview; + GtkTextBuffer *buffer; + GtkTextIter iter, start_iter, end_iter; + GtkTextMark *mark; + + g_return_if_fail(undostruct != NULL); + + if (undostruct->redo == NULL) return; + + redoinfo = (UndoInfo *)undostruct->redo->data; + g_return_if_fail (redoinfo != NULL); + undostruct->undo = g_list_prepend(undostruct->undo, redoinfo); + undostruct->redo = g_list_remove(undostruct->redo, redoinfo); + + textview = undostruct->textview; + buffer = gtk_text_view_get_buffer(textview); + + undo_block(undostruct); + + /* Check if there is a selection active */ + mark = gtk_text_buffer_get_insert(buffer); + gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); + gtk_text_buffer_place_cursor(buffer, &iter); + + /* Move the view to the right position. */ + gtk_adjustment_set_value(textview->vadjustment, + redoinfo->window_position); + + switch (redoinfo->action) { + case UNDO_ACTION_INSERT: + gtk_text_buffer_get_iter_at_offset + (buffer, &iter, redoinfo->start_pos); + gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1); + debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text); + break; + case UNDO_ACTION_DELETE: + gtk_text_buffer_get_iter_at_offset + (buffer, &start_iter, redoinfo->start_pos); + gtk_text_buffer_get_iter_at_offset + (buffer, &end_iter, redoinfo->end_pos); + gtk_text_buffer_delete(buffer, &start_iter, &end_iter); + debug_print("UNDO_ACTION_INSERT %d\n", + redoinfo->end_pos-redoinfo->start_pos); + break; + case UNDO_ACTION_REPLACE_DELETE: + gtk_text_buffer_get_iter_at_offset + (buffer, &start_iter, redoinfo->start_pos); + gtk_text_buffer_get_iter_at_offset + (buffer, &end_iter, redoinfo->end_pos); + gtk_text_buffer_delete(buffer, &start_iter, &end_iter); + debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text); + /* "pull" another data structure from the list */ + redoinfo = (UndoInfo *)undostruct->redo->data; + g_return_if_fail(redoinfo != NULL); + undostruct->undo = g_list_prepend(undostruct->undo, redoinfo); + undostruct->redo = g_list_remove(undostruct->redo, redoinfo); + g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT); + gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1); + break; + case UNDO_ACTION_REPLACE_INSERT: + g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT"); + break; + default: + g_assert_not_reached(); + break; + } + + undostruct->change_state_func(undostruct, + UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, + undostruct->change_state_data); + + if (undostruct->redo == NULL) + undostruct->change_state_func(undostruct, + UNDO_STATE_UNCHANGED, + UNDO_STATE_FALSE, + undostruct->change_state_data); + + undo_unblock(undostruct); +} + +void undo_block(UndoMain *undostruct) +{ + GtkTextBuffer *buffer; + + g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview)); + + buffer = gtk_text_view_get_buffer(undostruct->textview); + g_signal_handlers_block_by_func + (buffer, undo_insert_text_cb, undostruct); + g_signal_handlers_block_by_func + (buffer, undo_delete_text_cb, undostruct); + g_signal_handlers_block_by_func + (buffer, undo_paste_clipboard_cb, undostruct); +} + +void undo_unblock(UndoMain *undostruct) +{ + GtkTextBuffer *buffer; + + g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview)); + + buffer = gtk_text_view_get_buffer(undostruct->textview); + g_signal_handlers_unblock_by_func + (buffer, undo_insert_text_cb, undostruct); + g_signal_handlers_unblock_by_func + (buffer, undo_delete_text_cb, undostruct); + g_signal_handlers_unblock_by_func + (buffer, undo_paste_clipboard_cb, undostruct); +} + +void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter, + gchar *new_text, gint new_text_length, + UndoMain *undostruct) +{ + gchar *text_to_insert; + gint pos; + + if (prefs_common.undolevels <= 0) return; + + pos = gtk_text_iter_get_offset(iter); + + Xstrndup_a(text_to_insert, new_text, new_text_length, return); + undo_add(text_to_insert, pos, pos + g_utf8_strlen(text_to_insert, -1), + UNDO_ACTION_INSERT, undostruct); +} + +void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start, + GtkTextIter *end, UndoMain *undostruct) +{ + gchar *text_to_delete; + gint start_pos, end_pos; + + if (prefs_common.undolevels <= 0) return; + + text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE); + if (!text_to_delete || !*text_to_delete) return; + + start_pos = gtk_text_iter_get_offset(start); + end_pos = gtk_text_iter_get_offset(end); + + undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE, + undostruct); + g_free(text_to_delete); +} + +void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct) +{ + debug_print("before Paste: %d\n", undostruct->paste); + if (prefs_common.undolevels > 0) + if (undo_get_selection(textview, NULL, NULL)) + undostruct->paste = TRUE; + debug_print("after Paste: %d\n", undostruct->paste); +} + +/** + * undo_get_selection: + * @text: Text to get the selection from + * @start: return here the start position of the selection + * @end: return here the end position of the selection + * + * Gets the current selection for View + * + * Return Value: TRUE if there is a selection active, FALSE if not + **/ +static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end) +{ + GtkTextBuffer *buffer; + GtkTextIter start_iter, end_iter; + guint start_pos, end_pos; + + buffer = gtk_text_view_get_buffer(textview); + gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter); + + start_pos = gtk_text_iter_get_offset(&start_iter); + end_pos = gtk_text_iter_get_offset(&end_iter); + + /* The user can select from end to start too. If so, swap it*/ + if (end_pos < start_pos) { + guint swap_pos; + swap_pos = end_pos; + end_pos = start_pos; + start_pos = swap_pos; + } + + if (start != NULL) + *start = start_pos; + + if (end != NULL) + *end = end_pos; + + if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos)) + return TRUE; + else + return FALSE; +} diff --git a/src/undo.h b/src/undo.h new file mode 100644 index 00000000..1376d1e3 --- /dev/null +++ b/src/undo.h @@ -0,0 +1,80 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* code ported from gedit */ + +#ifndef __UNDO_H__ +#define __UNDO_H__ + +#include +#include +#include + +typedef enum +{ + UNDO_ACTION_INSERT, + UNDO_ACTION_DELETE, + UNDO_ACTION_REPLACE_INSERT, + UNDO_ACTION_REPLACE_DELETE, +} UndoAction; + +typedef enum +{ + UNDO_STATE_TRUE, + UNDO_STATE_FALSE, + UNDO_STATE_UNCHANGED, + UNDO_STATE_REFRESH, +} UndoState; + +typedef struct _UndoMain UndoMain; + +typedef void (*UndoChangeStateFunc) (UndoMain *undostruct, + gint undo_state, + gint redo_state, + gpointer data); + +struct _UndoMain +{ + GtkTextView *textview; + + GList *undo; + GList *redo; + + UndoChangeStateFunc change_state_func; + gpointer change_state_data; + + gboolean undo_state : 1; + gboolean redo_state : 1; + + gint paste; +}; + +UndoMain *undo_init (GtkWidget *text); +void undo_destroy (UndoMain *undostruct); + +void undo_set_change_state_func (UndoMain *undostruct, + UndoChangeStateFunc func, + gpointer data); + +void undo_undo (UndoMain *undostruct); +void undo_redo (UndoMain *undostruct); +void undo_block (UndoMain *undostruct); +void undo_unblock (UndoMain *undostruct); + +#endif /* __UNDO_H__ */ diff --git a/src/unmime.c b/src/unmime.c new file mode 100644 index 00000000..e7dce098 --- /dev/null +++ b/src/unmime.c @@ -0,0 +1,133 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2003 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "codeconv.h" +#include "base64.h" +#include "quoted-printable.h" + +#define ENCODED_WORD_BEGIN "=?" +#define ENCODED_WORD_END "?=" + +/* Decodes headers based on RFC2045 and RFC2047. */ + +void unmime_header(gchar *out, const gchar *str) +{ + const gchar *p = str; + gchar *outp = out; + const gchar *eword_begin_p, *encoding_begin_p, *text_begin_p, + *eword_end_p; + gchar charset[32]; + gchar encoding; + gchar *conv_str; + gint len; + + while (*p != '\0') { + gchar *decoded_text = NULL; + + eword_begin_p = strstr(p, ENCODED_WORD_BEGIN); + if (!eword_begin_p) { + strcpy(outp, p); + return; + } + encoding_begin_p = strchr(eword_begin_p + 2, '?'); + if (!encoding_begin_p) { + strcpy(outp, p); + return; + } + text_begin_p = strchr(encoding_begin_p + 1, '?'); + if (!text_begin_p) { + strcpy(outp, p); + return; + } + eword_end_p = strstr(text_begin_p + 1, ENCODED_WORD_END); + if (!eword_end_p) { + strcpy(outp, p); + return; + } + + if (p == str) { + memcpy(outp, p, eword_begin_p - p); + outp += eword_begin_p - p; + p = eword_begin_p; + } else { + /* ignore spaces between encoded words */ + const gchar *sp; + + for (sp = p; sp < eword_begin_p; sp++) { + if (!isspace(*(const guchar *)sp)) { + memcpy(outp, p, eword_begin_p - p); + outp += eword_begin_p - p; + p = eword_begin_p; + break; + } + } + } + + len = MIN(sizeof(charset) - 1, + encoding_begin_p - (eword_begin_p + 2)); + memcpy(charset, eword_begin_p + 2, len); + charset[len] = '\0'; + encoding = toupper(*(encoding_begin_p + 1)); + + if (encoding == 'B') { + decoded_text = g_malloc + (eword_end_p - (text_begin_p + 1) + 1); + len = base64_decode(decoded_text, text_begin_p + 1, + eword_end_p - (text_begin_p + 1)); + decoded_text[len] = '\0'; + } else if (encoding == 'Q') { + decoded_text = g_malloc + (eword_end_p - (text_begin_p + 1) + 1); + len = qp_decode_q_encoding + (decoded_text, text_begin_p + 1, + eword_end_p - (text_begin_p + 1)); + } else { + memcpy(outp, p, eword_end_p + 2 - p); + outp += eword_end_p + 2 - p; + p = eword_end_p + 2; + continue; + } + + /* convert to locale encoding */ + conv_str = conv_codeset_strdup(decoded_text, charset, NULL); + if (conv_str) { + len = strlen(conv_str); + memcpy(outp, conv_str, len); + g_free(conv_str); + } else { + len = strlen(decoded_text); + conv_localetodisp(outp, len + 1, decoded_text); + } + outp += len; + + g_free(decoded_text); + + p = eword_end_p + 2; + } + + *outp = '\0'; +} diff --git a/src/unmime.h b/src/unmime.h new file mode 100644 index 00000000..8e6d8397 --- /dev/null +++ b/src/unmime.h @@ -0,0 +1,29 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2002 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __UNMIME_H__ +#define __UNMIME_H__ + +#include + +void unmime_header (gchar *out, + const gchar *str); +gint unmime_quoted_printable_line (gchar *str); + +#endif /* __UNMIME_H__ */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 00000000..493c9597 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,3189 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "defs.h" + +#include +#include +#include +#include +#include + +#if (HAVE_WCTYPE_H && HAVE_WCHAR_H) +# include +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intl.h" +#include "utils.h" +#include "socket.h" +#include "statusbar.h" +#include "logwindow.h" + +#define BUFFSIZE 8192 + +extern gboolean debug_mode; + +static void hash_free_strings_func(gpointer key, gpointer value, gpointer data); + +void list_free_strings(GList *list) +{ + list = g_list_first(list); + + while (list != NULL) { + g_free(list->data); + list = list->next; + } +} + +void slist_free_strings(GSList *list) +{ + while (list != NULL) { + g_free(list->data); + list = list->next; + } +} + +static void hash_free_strings_func(gpointer key, gpointer value, gpointer data) +{ + g_free(key); +} + +void hash_free_strings(GHashTable *table) +{ + g_hash_table_foreach(table, hash_free_strings_func, NULL); +} + +static void hash_free_value_mem_func(gpointer key, gpointer value, + gpointer data) +{ + g_free(value); +} + +void hash_free_value_mem(GHashTable *table) +{ + g_hash_table_foreach(table, hash_free_value_mem_func, NULL); +} + +gint str_case_equal(gconstpointer v, gconstpointer v2) +{ + return strcasecmp((const gchar *)v, (const gchar *)v2) == 0; +} + +guint str_case_hash(gconstpointer key) +{ + const gchar *p = key; + guint h = *p; + + if (h) { + h = tolower(h); + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + tolower(*p); + } + + return h; +} + +void ptr_array_free_strings(GPtrArray *array) +{ + gint i; + gchar *str; + + g_return_if_fail(array != NULL); + + for (i = 0; i < array->len; i++) { + str = g_ptr_array_index(array, i); + g_free(str); + } +} + +gboolean str_find(const gchar *haystack, const gchar *needle) +{ + return strstr(haystack, needle) != NULL ? TRUE : FALSE; +} + +gboolean str_case_find(const gchar *haystack, const gchar *needle) +{ + return strcasestr(haystack, needle) != NULL ? TRUE : FALSE; +} + +gboolean str_find_equal(const gchar *haystack, const gchar *needle) +{ + return strcmp(haystack, needle) == 0; +} + +gboolean str_case_find_equal(const gchar *haystack, const gchar *needle) +{ + return strcasecmp(haystack, needle) == 0; +} + +gint to_number(const gchar *nstr) +{ + register const guchar *p; + + if (*nstr == '\0') return -1; + + for (p = nstr; *p != '\0'; p++) + if (!isdigit(*p)) return -1; + + return atoi(nstr); +} + +/* convert integer into string, + nstr must be not lower than 11 characters length */ +gchar *itos_buf(gchar *nstr, gint n) +{ + g_snprintf(nstr, 11, "%d", n); + return nstr; +} + +/* convert integer into string */ +gchar *itos(gint n) +{ + static gchar nstr[11]; + + return itos_buf(nstr, n); +} + +gchar *to_human_readable(off_t size) +{ + static gchar str[10]; + + if (size < 1024) + g_snprintf(str, sizeof(str), _("%dB"), (gint)size); + else if (size >> 10 < 1024) + g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10)); + else if (size >> 20 < 1024) + g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20)); + else + g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30)); + + return str; +} + +/* strcmp with NULL-checking */ +gint strcmp2(const gchar *s1, const gchar *s2) +{ + if (s1 == NULL || s2 == NULL) + return -1; + else + return strcmp(s1, s2); +} + +/* compare paths */ +gint path_cmp(const gchar *s1, const gchar *s2) +{ + gint len1, len2; + + if (s1 == NULL || s2 == NULL) return -1; + if (*s1 == '\0' || *s2 == '\0') return -1; + + len1 = strlen(s1); + len2 = strlen(s2); + + if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--; + if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--; + + return strncmp(s1, s2, MAX(len1, len2)); +} + +/* remove trailing return code */ +gchar *strretchomp(gchar *str) +{ + register gchar *s; + + if (!*str) return str; + + for (s = str + strlen(str) - 1; + s >= str && (*s == '\n' || *s == '\r'); + s--) + *s = '\0'; + + return str; +} + +/* remove trailing character */ +gchar *strtailchomp(gchar *str, gchar tail_char) +{ + register gchar *s; + + if (!*str) return str; + if (tail_char == '\0') return str; + + for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--) + *s = '\0'; + + return str; +} + +/* remove CR (carriage return) */ +gchar *strcrchomp(gchar *str) +{ + register gchar *s; + + if (!*str) return str; + + s = str + strlen(str) - 1; + if (*s == '\n' && s > str && *(s - 1) == '\r') { + *(s - 1) = '\n'; + *s = '\0'; + } + + return str; +} + +/* Similar to `strstr' but this function ignores the case of both strings. */ +gchar *strcasestr(const gchar *haystack, const gchar *needle) +{ + register size_t haystack_len, needle_len; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!strncasecmp(haystack, needle, needle_len)) + return (gchar *)haystack; + else { + haystack++; + haystack_len--; + } + } + + return NULL; +} + +gpointer my_memmem(gconstpointer haystack, size_t haystacklen, + gconstpointer needle, size_t needlelen) +{ + const gchar *haystack_ = (const gchar *)haystack; + const gchar *needle_ = (const gchar *)needle; + const gchar *haystack_cur = (const gchar *)haystack; + + if (needlelen == 1) + return memchr(haystack_, *needle_, haystacklen); + + while ((haystack_cur = memchr(haystack_cur, *needle_, haystacklen)) + != NULL) { + if (haystacklen - (haystack_cur - haystack_) < needlelen) + break; + if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0) + return (gpointer)haystack_cur; + else + haystack_cur++; + } + + return NULL; +} + +/* Copy no more than N characters of SRC to DEST, with NULL terminating. */ +gchar *strncpy2(gchar *dest, const gchar *src, size_t n) +{ + register gchar c; + gchar *s = dest; + + do { + if (--n == 0) { + *dest = '\0'; + return s; + } + c = *src++; + *dest++ = c; + } while (c != '\0'); + + /* don't do zero fill */ + return s; +} + +#if !HAVE_ISWALNUM +int iswalnum(wint_t wc) +{ + return isalnum((int)wc); +} +#endif + +#if !HAVE_ISWSPACE +int iswspace(wint_t wc) +{ + return isspace((int)wc); +} +#endif + +#if !HAVE_TOWLOWER +wint_t towlower(wint_t wc) +{ + if (wc >= L'A' && wc <= L'Z') + return wc + L'a' - L'A'; + + return wc; +} +#endif + +#if !HAVE_WCSLEN +size_t wcslen(const wchar_t *s) +{ + size_t len = 0; + + while (*s != L'\0') + ++len, ++s; + + return len; +} +#endif + +#if !HAVE_WCSCPY +/* Copy SRC to DEST. */ +wchar_t *wcscpy(wchar_t *dest, const wchar_t *src) +{ + wint_t c; + wchar_t *s = dest; + + do { + c = *src++; + *dest++ = c; + } while (c != L'\0'); + + return s; +} +#endif + +#if !HAVE_WCSNCPY +/* Copy no more than N wide-characters of SRC to DEST. */ +wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n) +{ + wint_t c; + wchar_t *s = dest; + + do { + c = *src++; + *dest++ = c; + if (--n == 0) + return s; + } while (c != L'\0'); + + /* zero fill */ + do + *dest++ = L'\0'; + while (--n > 0); + + return s; +} +#endif + +/* Duplicate S, returning an identical malloc'd string. */ +wchar_t *wcsdup(const wchar_t *s) +{ + wchar_t *new_str; + + if (s) { + new_str = g_new(wchar_t, wcslen(s) + 1); + wcscpy(new_str, s); + } else + new_str = NULL; + + return new_str; +} + +/* Duplicate no more than N wide-characters of S, + returning an identical malloc'd string. */ +wchar_t *wcsndup(const wchar_t *s, size_t n) +{ + wchar_t *new_str; + + if (s) { + new_str = g_new(wchar_t, n + 1); + wcsncpy(new_str, s, n); + new_str[n] = (wchar_t)0; + } else + new_str = NULL; + + return new_str; +} + +wchar_t *strdup_mbstowcs(const gchar *s) +{ + wchar_t *new_str; + + if (s) { + new_str = g_new(wchar_t, strlen(s) + 1); + if (mbstowcs(new_str, s, strlen(s) + 1) < 0) { + g_free(new_str); + new_str = NULL; + } else + new_str = g_realloc(new_str, + sizeof(wchar_t) * (wcslen(new_str) + 1)); + } else + new_str = NULL; + + return new_str; +} + +gchar *strdup_wcstombs(const wchar_t *s) +{ + gchar *new_str; + size_t len; + + if (s) { + len = wcslen(s) * MB_CUR_MAX + 1; + new_str = g_new(gchar, len); + if (wcstombs(new_str, s, len) < 0) { + g_free(new_str); + new_str = NULL; + } else + new_str = g_realloc(new_str, strlen(new_str) + 1); + } else + new_str = NULL; + + return new_str; +} + +/* Compare S1 and S2, ignoring case. */ +gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n) +{ + wint_t c1; + wint_t c2; + + while (n--) { + c1 = towlower(*s1++); + c2 = towlower(*s2++); + if (c1 != c2) + return c1 - c2; + else if (c1 == 0 && c2 == 0) + break; + } + + return 0; +} + +/* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case. */ +wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle) +{ + register size_t haystack_len, needle_len; + + haystack_len = wcslen(haystack); + needle_len = wcslen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!wcsncasecmp(haystack, needle, needle_len)) + return (wchar_t *)haystack; + else { + haystack++; + haystack_len--; + } + } + + return NULL; +} + +gint get_mbs_len(const gchar *s) +{ + const gchar *p = s; + gint mb_len; + gint len = 0; + + if (!p) + return -1; + + while (*p != '\0') { + mb_len = mblen(p, MB_LEN_MAX); + if (mb_len == 0) + break; + else if (mb_len < 0) + return -1; + else + len++; + + p += mb_len; + } + + return len; +} + +/* Examine if next block is non-ASCII string */ +gboolean is_next_nonascii(const guchar *s) +{ + const guchar *p; + + /* skip head space */ + for (p = s; *p != '\0' && isspace(*p); p++) + ; + for (; *p != '\0' && !isspace(*p); p++) { + if (*p > 127 || *p < 32) + return TRUE; + } + + return FALSE; +} + +gint get_next_word_len(const guchar *s) +{ + gint len = 0; + + for (; *s != '\0' && !isspace(*s); s++, len++) + ; + + return len; +} + +/* compare subjects */ +gint subject_compare(const gchar *s1, const gchar *s2) +{ + gchar *str1, *str2; + + if (!s1 || !s2) return -1; + if (!*s1 || !*s2) return -1; + + Xstrdup_a(str1, s1, return -1); + Xstrdup_a(str2, s2, return -1); + + trim_subject_for_compare(str1); + trim_subject_for_compare(str2); + + if (!*str1 || !*str2) return -1; + + return strcmp(str1, str2); +} + +gint subject_compare_for_sort(const gchar *s1, const gchar *s2) +{ + gchar *str1, *str2; + + if (!s1 || !s2) return -1; + + Xstrdup_a(str1, s1, return -1); + Xstrdup_a(str2, s2, return -1); + + trim_subject_for_sort(str1); + trim_subject_for_sort(str2); + + return strcasecmp(str1, str2); +} + +void trim_subject_for_compare(gchar *str) +{ + guchar *srcp; + + eliminate_parenthesis(str, '[', ']'); + eliminate_parenthesis(str, '(', ')'); + g_strstrip(str); + + while (!strncasecmp(str, "Re:", 3)) { + srcp = str + 3; + while (isspace(*srcp)) srcp++; + memmove(str, srcp, strlen(srcp) + 1); + } +} + +void trim_subject_for_sort(gchar *str) +{ + guchar *srcp; + + g_strstrip(str); + + while (!strncasecmp(str, "Re:", 3)) { + srcp = str + 3; + while (isspace(*srcp)) srcp++; + memmove(str, srcp, strlen(srcp) + 1); + } +} + +void trim_subject(gchar *str) +{ + register guchar *srcp, *destp; + gchar op, cl; + gint in_brace; + + destp = str; + while (!strncasecmp(destp, "Re:", 3)) { + destp += 3; + while (isspace(*destp)) destp++; + } + + if (*destp == '[') { + op = '['; + cl = ']'; + } else if (*destp == '(') { + op = '('; + cl = ')'; + } else + return; + + srcp = destp + 1; + in_brace = 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); +} + +void eliminate_parenthesis(gchar *str, gchar op, gchar cl) +{ + register guchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((destp = strchr(destp, op))) { + in_brace = 1; + srcp = destp + 1; + while (*srcp) { + if (*srcp == op) + in_brace++; + else if (*srcp == cl) + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } +} + +void extract_parenthesis(gchar *str, gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((srcp = strchr(destp, op))) { + if (destp > str) + *destp++ = ' '; + memmove(destp, srcp + 1, strlen(srcp)); + in_brace = 1; + while(*destp) { + if (*destp == op) + in_brace++; + else if (*destp == cl) + in_brace--; + + if (in_brace == 0) + break; + + destp++; + } + } + *destp = '\0'; +} + +void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr, + gchar op, gchar cl) +{ + register gchar *srcp, *destp; + gint in_brace; + gboolean in_quote = FALSE; + + srcp = destp = str; + + while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) { + if (destp > str) + *destp++ = ' '; + memmove(destp, srcp + 1, strlen(srcp)); + in_brace = 1; + while(*destp) { + if (*destp == op && !in_quote) + in_brace++; + else if (*destp == cl && !in_quote) + in_brace--; + else if (*destp == quote_chr) + in_quote ^= TRUE; + + if (in_brace == 0) + break; + + destp++; + } + } + *destp = '\0'; +} + +void eliminate_quote(gchar *str, gchar quote_chr) +{ + register guchar *srcp, *destp; + + srcp = destp = str; + + while ((destp = strchr(destp, quote_chr))) { + if ((srcp = strchr(destp + 1, quote_chr))) { + srcp++; + while (isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } else { + *destp = '\0'; + break; + } + } +} + +void extract_quote(gchar *str, gchar quote_chr) +{ + register gchar *p; + + if ((str = strchr(str, quote_chr))) { + if ((p = strchr(str + 1, quote_chr))) { + *p = '\0'; + memmove(str, str + 1, p - str); + } + } +} + +void eliminate_address_comment(gchar *str) +{ + register guchar *srcp, *destp; + gint in_brace; + + srcp = destp = str; + + while ((destp = strchr(destp, '"'))) { + if ((srcp = strchr(destp + 1, '"'))) { + srcp++; + if (*srcp == '@') { + destp = srcp + 1; + } else { + while (isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } + } else { + *destp = '\0'; + break; + } + } + + srcp = destp = str; + + while ((destp = strchr_with_skip_quote(destp, '"', '('))) { + in_brace = 1; + srcp = destp + 1; + while (*srcp) { + if (*srcp == '(') + in_brace++; + else if (*srcp == ')') + in_brace--; + srcp++; + if (in_brace == 0) + break; + } + while (isspace(*srcp)) srcp++; + memmove(destp, srcp, strlen(srcp) + 1); + } +} + +gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) +{ + gboolean in_quote = FALSE; + + while (*str) { + if (*str == c && !in_quote) + return (gchar *)str; + if (*str == quote_chr) + in_quote ^= TRUE; + str++; + } + + return NULL; +} + +gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) +{ + gboolean in_quote = FALSE; + const gchar *p; + + p = str + strlen(str) - 1; + while (p >= str) { + if (*p == c && !in_quote) + return (gchar *)p; + if (*p == quote_chr) + in_quote ^= TRUE; + p--; + } + + return NULL; +} + +void extract_address(gchar *str) +{ + eliminate_address_comment(str); + if (strchr_with_skip_quote(str, '"', '<')) + extract_parenthesis_with_skip_quote(str, '"', '<', '>'); + g_strstrip(str); +} + +void extract_list_id_str(gchar *str) +{ + if (strchr_with_skip_quote(str, '"', '<')) + extract_parenthesis_with_skip_quote(str, '"', '<', '>'); + g_strstrip(str); +} + +GSList *address_list_append(GSList *addr_list, const gchar *str) +{ + gchar *work; + gchar *workp; + + if (!str) return addr_list; + + Xstrdup_a(work, str, return addr_list); + + eliminate_address_comment(work); + workp = work; + + while (workp && *workp) { + gchar *p, *next; + + if ((p = strchr_with_skip_quote(workp, '"', ','))) { + *p = '\0'; + next = p + 1; + } else + next = NULL; + + if (strchr_with_skip_quote(workp, '"', '<')) + extract_parenthesis_with_skip_quote + (workp, '"', '<', '>'); + + g_strstrip(workp); + if (*workp) + addr_list = g_slist_append(addr_list, g_strdup(workp)); + + workp = next; + } + + return addr_list; +} + +GSList *references_list_append(GSList *msgid_list, const gchar *str) +{ + const gchar *strp; + + if (!str) return msgid_list; + strp = str; + + while (strp && *strp) { + const gchar *start, *end; + gchar *msgid; + + if ((start = strchr(strp, '<')) != NULL) { + end = strchr(start + 1, '>'); + if (!end) break; + } else + break; + + msgid = g_strndup(start + 1, end - start - 1); + g_strstrip(msgid); + if (*msgid) + msgid_list = g_slist_append(msgid_list, msgid); + else + g_free(msgid); + + strp = end + 1; + } + + return msgid_list; +} + +GSList *newsgroup_list_append(GSList *group_list, const gchar *str) +{ + gchar *work; + gchar *workp; + + if (!str) return group_list; + + Xstrdup_a(work, str, return group_list); + + workp = work; + + while (workp && *workp) { + gchar *p, *next; + + if ((p = strchr_with_skip_quote(workp, '"', ','))) { + *p = '\0'; + next = p + 1; + } else + next = NULL; + + g_strstrip(workp); + if (*workp) + group_list = g_slist_append(group_list, + g_strdup(workp)); + + workp = next; + } + + return group_list; +} + +GList *add_history(GList *list, const gchar *str) +{ + GList *old; + + g_return_val_if_fail(str != NULL, list); + + old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2); + if (old) { + g_free(old->data); + list = g_list_remove(list, old->data); + } else if (g_list_length(list) >= MAX_HISTORY_SIZE) { + GList *last; + + last = g_list_last(list); + if (last) { + g_free(last->data); + g_list_remove(list, last->data); + } + } + + list = g_list_prepend(list, g_strdup(str)); + + return list; +} + +void remove_return(gchar *str) +{ + register gchar *p = str; + + while (*p) { + if (*p == '\n' || *p == '\r') + memmove(p, p + 1, strlen(p)); + else + p++; + } +} + +void remove_space(gchar *str) +{ + register guchar *p = str; + register gint spc; + + while (*p) { + spc = 0; + while (isspace(*(p + spc))) + spc++; + if (spc) + memmove(p, p + spc, strlen(p + spc) + 1); + else + p++; + } +} + +void unfold_line(gchar *str) +{ + register guchar *p = str; + register gint spc; + + while (*p) { + if (*p == '\n' || *p == '\r') { + *p++ = ' '; + spc = 0; + while (isspace(*(p + spc))) + spc++; + if (spc) + memmove(p, p + spc, strlen(p + spc) + 1); + } else + p++; + } +} + +void subst_char(gchar *str, gchar orig, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (*p == orig) + *p = subst; + p++; + } +} + +void subst_chars(gchar *str, gchar *orig, gchar subst) +{ + register gchar *p = str; + + while (*p) { + if (strchr(orig, *p) != NULL) + *p = subst; + p++; + } +} + +void subst_for_filename(gchar *str) +{ + subst_chars(str, " \t\r\n\"'/\\", '_'); +} + +gboolean is_header_line(const gchar *str) +{ + if (str[0] == ':') return FALSE; + + while (*str != '\0' && *str != ' ') { + if (*str == ':') + return TRUE; + str++; + } + + return FALSE; +} + +gboolean is_ascii_str(const guchar *str) +{ + while (*str != '\0') { + if (*str != '\t' && *str != ' ' && + *str != '\r' && *str != '\n' && + (*str < 32 || *str >= 127)) + return FALSE; + str++; + } + + return TRUE; +} + +gint get_quote_level(const gchar *str) +{ + const guchar *first_pos; + const guchar *last_pos; + const guchar *p = str; + gint quote_level = -1; + + /* speed up line processing by only searching to the last '>' */ + if ((first_pos = strchr(str, '>')) != NULL) { + /* skip a line if it contains a '<' before the initial '>' */ + if (memchr(str, '<', first_pos - (const guchar *)str) != NULL) + return -1; + last_pos = strrchr(first_pos, '>'); + } else + return -1; + + while (p <= last_pos) { + while (p < last_pos) { + if (isspace(*p)) + p++; + else + break; + } + + if (*p == '>') + quote_level++; + else if (*p != '-' && !isspace(*p) && p <= last_pos) { + /* any characters are allowed except '-' and space */ + while (*p != '-' && *p != '>' && !isspace(*p) && + p < last_pos) + p++; + if (*p == '>') + quote_level++; + else + break; + } + + p++; + } + + return quote_level; +} + +gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle) +{ + register guint haystack_len, needle_len; + gboolean in_squote = FALSE, in_dquote = FALSE; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (haystack_len < needle_len || needle_len == 0) + return NULL; + + while (haystack_len >= needle_len) { + if (!in_squote && !in_dquote && + !strncmp(haystack, needle, needle_len)) + return (gchar *)haystack; + + /* 'foo"bar"' -> foo"bar" + "foo'bar'" -> foo'bar' */ + if (*haystack == '\'') { + if (in_squote) + in_squote = FALSE; + else if (!in_dquote) + in_squote = TRUE; + } else if (*haystack == '\"') { + if (in_dquote) + in_dquote = FALSE; + else if (!in_squote) + in_dquote = TRUE; + } + + haystack++; + haystack_len--; + } + + return NULL; +} + +gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl) +{ + const gchar *p; + gchar quote_chr = '"'; + gint in_brace; + gboolean in_quote = FALSE; + + p = str; + + if ((p = strchr_with_skip_quote(p, quote_chr, op))) { + p++; + in_brace = 1; + while (*p) { + if (*p == op && !in_quote) + in_brace++; + else if (*p == cl && !in_quote) + in_brace--; + else if (*p == quote_chr) + in_quote ^= TRUE; + + if (in_brace == 0) + return (gchar *)p; + + p++; + } + } + + return NULL; +} + +gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array; + const gchar *s_op, *s_cl; + guint i, n = 1; + + g_return_val_if_fail(str != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s_op = strchr_with_skip_quote(str, '"', op); + if (!s_op) return NULL; + str = s_op; + s_cl = strchr_parenthesis_close(str, op, cl); + if (s_cl) { + do { + guint len; + gchar *new_string; + + str++; + len = s_cl - str; + new_string = g_new(gchar, len + 1); + strncpy(new_string, str, len); + new_string[len] = 0; + string_list = g_slist_prepend(string_list, new_string); + n++; + str = s_cl + 1; + + while (*str && isspace(*(guchar *)str)) str++; + if (*str != op) { + string_list = g_slist_prepend(string_list, + g_strdup("")); + n++; + s_op = strchr_with_skip_quote(str, '"', op); + if (!--max_tokens || !s_op) break; + str = s_op; + } else + s_op = str; + s_cl = strchr_parenthesis_close(str, op, cl); + } while (--max_tokens && s_cl); + } + + str_array = g_new(gchar*, n); + + i = n - 1; + + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +gchar **strsplit_with_quote(const gchar *str, const gchar *delim, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array, *s, *new_str; + guint i, n = 1, len; + + g_return_val_if_fail(str != NULL, NULL); + g_return_val_if_fail(delim != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s = strstr_with_skip_quote(str, delim); + if (s) { + guint delimiter_len = strlen(delim); + + do { + len = s - str; + new_str = g_strndup(str, len); + + if (new_str[0] == '\'' || new_str[0] == '\"') { + if (new_str[len - 1] == new_str[0]) { + new_str[len - 1] = '\0'; + memmove(new_str, new_str + 1, len - 1); + } + } + string_list = g_slist_prepend(string_list, new_str); + n++; + str = s + delimiter_len; + s = strstr_with_skip_quote(str, delim); + } while (--max_tokens && s); + } + + if (*str) { + new_str = g_strdup(str); + if (new_str[0] == '\'' || new_str[0] == '\"') { + len = strlen(str); + if (new_str[len - 1] == new_str[0]) { + new_str[len - 1] = '\0'; + memmove(new_str, new_str + 1, len - 1); + } + } + string_list = g_slist_prepend(string_list, new_str); + n++; + } + + str_array = g_new(gchar*, n); + + i = n - 1; + + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free(string_list); + + return str_array; +} + +gchar *get_abbrev_newsgroup_name(const gchar *group, gint len) +{ + gchar *abbrev_group; + gchar *ap; + const gchar *p = group; + const gchar *last; + + last = group + strlen(group); + abbrev_group = ap = g_malloc(strlen(group) + 1); + + while (*p) { + while (*p == '.') + *ap++ = *p++; + if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) { + *ap++ = *p++; + while (*p != '.') p++; + } else { + strcpy(ap, p); + return abbrev_group; + } + } + + *ap = '\0'; + return abbrev_group; +} + +gchar *trim_string(const gchar *str, gint len) +{ + const gchar *p = str; + gint mb_len; + gchar *new_str; + gint new_len = 0; + + if (!str) return NULL; + if (strlen(str) <= len) + return g_strdup(str); + + while (*p != '\0') { + mb_len = mblen(p, MB_LEN_MAX); + if (mb_len == 0) + break; + else if (mb_len < 0) + return g_strdup(str); + else if (new_len + mb_len > len) + break; + + new_len += mb_len; + p += mb_len; + } + + Xstrndup_a(new_str, str, new_len, return g_strdup(str)); + return g_strconcat(new_str, "...", NULL); +} + +gchar *trim_string_before(const gchar *str, gint len) +{ + const gchar *p = str; + gint mb_len; + gint new_len; + + if (!str) return NULL; + if ((new_len = strlen(str)) <= len) + return g_strdup(str); + + while (*p != '\0') { + mb_len = mblen(p, MB_LEN_MAX); + if (mb_len == 0) + break; + else if (mb_len < 0) + return g_strdup(str); + + new_len -= mb_len; + p += mb_len; + + if (new_len <= len) + break; + } + + return g_strconcat("...", p, NULL); +} + +GList *uri_list_extract_filenames(const gchar *uri_list) +{ + GList *result = NULL; + const guchar *p, *q; + gchar *file; + + p = uri_list; + + while (p) { + if (*p != '#') { + while (isspace(*p)) p++; + if (!strncmp(p, "file:", 5)) { + p += 5; + q = p; + while (*q && *q != '\n' && *q != '\r') q++; + + if (q > p) { + q--; + while (q > p && isspace(*q)) q--; + file = g_malloc(q - p + 2); + strncpy(file, p, q - p + 1); + file[q - p + 1] = '\0'; + result = g_list_append(result,file); + } + } + } + p = strchr(p, '\n'); + if (p) p++; + } + + return result; +} + +#define HEX_TO_INT(val, hex) \ +{ \ + gchar c = hex; \ + \ + if ('0' <= c && c <= '9') { \ + val = c - '0'; \ + } else if ('a' <= c && c <= 'f') { \ + val = c - 'a' + 10; \ + } else if ('A' <= c && c <= 'F') { \ + val = c - 'A' + 10; \ + } else { \ + val = 0; \ + } \ +} + +/* Converts two-digit hexadecimal to decimal. Used for unescaping escaped + * characters. + */ +static gint axtoi(const gchar *hex_str) +{ + gint hi, lo; + + HEX_TO_INT(hi, hex_str[0]); + HEX_TO_INT(lo, hex_str[1]); + + return (hi << 4) + lo; +} + +gboolean is_uri_string(const gchar *str) +{ + return (g_strncasecmp(str, "http://", 7) == 0 || + g_strncasecmp(str, "https://", 8) == 0 || + g_strncasecmp(str, "ftp://", 6) == 0 || + g_strncasecmp(str, "www.", 4) == 0); +} + +gchar *get_uri_path(const gchar *uri) +{ + if (g_strncasecmp(uri, "http://", 7) == 0) + return (gchar *)(uri + 7); + else if (g_strncasecmp(uri, "https://", 8) == 0) + return (gchar *)(uri + 8); + else if (g_strncasecmp(uri, "ftp://", 6) == 0) + return (gchar *)(uri + 6); + else + return (gchar *)uri; +} + +/* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by + * plusses, and escape characters are used) + */ +void decode_uri(gchar *decoded_uri, const gchar *encoded_uri) +{ + gchar *dec = decoded_uri; + const gchar *enc = encoded_uri; + + while (*enc) { + if (*enc == '%') { + enc++; + if (isxdigit((guchar)enc[0]) && + isxdigit((guchar)enc[1])) { + *dec = axtoi(enc); + dec++; + enc += 2; + } + } else { + if (*enc == '+') + *dec = ' '; + else + *dec = *enc; + dec++; + enc++; + } + } + + *dec = '\0'; +} + +gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc, + gchar **subject, gchar **body) +{ + gchar *tmp_mailto; + gchar *p; + + Xstrdup_a(tmp_mailto, mailto, return -1); + + if (!strncmp(tmp_mailto, "mailto:", 7)) + tmp_mailto += 7; + + p = strchr(tmp_mailto, '?'); + if (p) { + *p = '\0'; + p++; + } + + if (to && !*to) + *to = g_strdup(tmp_mailto); + + while (p) { + gchar *field, *value; + + field = p; + + p = strchr(p, '='); + if (!p) break; + *p = '\0'; + p++; + + value = p; + + p = strchr(p, '&'); + if (p) { + *p = '\0'; + p++; + } + + if (*value == '\0') continue; + + if (cc && !*cc && !g_strcasecmp(field, "cc")) { + *cc = g_strdup(value); + } else if (bcc && !*bcc && !g_strcasecmp(field, "bcc")) { + *bcc = g_strdup(value); + } else if (subject && !*subject && + !g_strcasecmp(field, "subject")) { + *subject = g_malloc(strlen(value) + 1); + decode_uri(*subject, value); + } else if (body && !*body && !g_strcasecmp(field, "body")) { + *body = g_malloc(strlen(value) + 1); + decode_uri(*body, value); + } + } + + return 0; +} + +/* + * We need this wrapper around g_get_home_dir(), so that + * we can fix some Windoze things here. Should be done in glibc of course + * but as long as we are not able to do our own extensions to glibc, we do + * it here. + */ +const gchar *get_home_dir(void) +{ +#if HAVE_DOSISH_SYSTEM + static gchar *home_dir; + + if (!home_dir) { + home_dir = read_w32_registry_string(NULL, + "Software\\Sylpheed", "HomeDir" ); + if (!home_dir || !*home_dir) { + if (getenv ("HOMEDRIVE") && getenv("HOMEPATH")) { + const char *s = g_get_home_dir(); + if (s && *s) + home_dir = g_strdup (s); + } + if (!home_dir || !*home_dir) + home_dir = g_strdup ("c:\\sylpheed"); + } + debug_print("initialized home_dir to `%s'\n", home_dir); + } + return home_dir; +#else /* standard glib */ + return g_get_home_dir(); +#endif +} + +const gchar *get_rc_dir(void) +{ + static gchar *rc_dir = NULL; + + if (!rc_dir) + rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, + RC_DIR, NULL); + + return rc_dir; +} + +const gchar *get_news_cache_dir(void) +{ + static gchar *news_cache_dir = NULL; + + if (!news_cache_dir) + news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + NEWS_CACHE_DIR, NULL); + + return news_cache_dir; +} + +const gchar *get_imap_cache_dir(void) +{ + static gchar *imap_cache_dir = NULL; + + if (!imap_cache_dir) + imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + IMAP_CACHE_DIR, NULL); + + return imap_cache_dir; +} + +const gchar *get_mime_tmp_dir(void) +{ + static gchar *mime_tmp_dir = NULL; + + if (!mime_tmp_dir) + mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + MIME_TMP_DIR, NULL); + + return mime_tmp_dir; +} + +const gchar *get_template_dir(void) +{ + static gchar *template_dir = NULL; + + if (!template_dir) + template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + TEMPLATE_DIR, NULL); + + return template_dir; +} + +const gchar *get_tmp_dir(void) +{ + static gchar *tmp_dir = NULL; + + if (!tmp_dir) + tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + TMP_DIR, NULL); + + return tmp_dir; +} + +gchar *get_tmp_file(void) +{ + gchar *tmp_file; + static guint32 id = 0; + + tmp_file = g_strdup_printf("%s%ctmpfile.%08x", + get_tmp_dir(), G_DIR_SEPARATOR, id++); + + return tmp_file; +} + +const gchar *get_domain_name(void) +{ + static gchar *domain_name = NULL; + + if (!domain_name) { + gchar buf[128] = ""; + struct hostent *hp; + + if (gethostname(buf, sizeof(buf)) < 0) { + perror("gethostname"); + domain_name = "unknown"; + } else { + buf[sizeof(buf) - 1] = '\0'; + if ((hp = my_gethostbyname(buf)) == NULL) { + perror("gethostbyname"); + domain_name = g_strdup(buf); + } else { + domain_name = g_strdup(hp->h_name); + } + } + + debug_print("domain name = %s\n", domain_name); + } + + return domain_name; +} + +off_t get_file_size(const gchar *file) +{ + struct stat s; + + if (stat(file, &s) < 0) { + FILE_OP_ERROR(file, "stat"); + return -1; + } + + return s.st_size; +} + +off_t get_file_size_as_crlf(const gchar *file) +{ + FILE *fp; + off_t size = 0; + gchar buf[BUFFSIZE]; + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + size += strlen(buf) + 2; + } + + if (ferror(fp)) { + FILE_OP_ERROR(file, "fgets"); + size = -1; + } + + fclose(fp); + + return size; +} + +off_t get_left_file_size(FILE *fp) +{ + glong pos; + glong end; + off_t size; + + if ((pos = ftell(fp)) < 0) { + perror("ftell"); + return -1; + } + if (fseek(fp, 0L, SEEK_END) < 0) { + perror("fseek"); + return -1; + } + if ((end = ftell(fp)) < 0) { + perror("fseek"); + return -1; + } + size = end - pos; + if (fseek(fp, pos, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + return size; +} + +gboolean file_exist(const gchar *file, gboolean allow_fifo) +{ + struct stat s; + + if (file == NULL) + return FALSE; + + if (stat(file, &s) < 0) { + if (ENOENT != errno) FILE_OP_ERROR(file, "stat"); + return FALSE; + } + + if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode))) + return TRUE; + + return FALSE; +} + +gboolean is_dir_exist(const gchar *dir) +{ + struct stat s; + + if (dir == NULL) + return FALSE; + + if (stat(dir, &s) < 0) { + if (ENOENT != errno) FILE_OP_ERROR(dir, "stat"); + return FALSE; + } + + if (S_ISDIR(s.st_mode)) + return TRUE; + + return FALSE; +} + +gboolean is_file_entry_exist(const gchar *file) +{ + struct stat s; + + if (file == NULL) + return FALSE; + + if (stat(file, &s) < 0) { + if (ENOENT != errno) FILE_OP_ERROR(file, "stat"); + return FALSE; + } + + return TRUE; +} + +gboolean dirent_is_regular_file(struct dirent *d) +{ + struct stat s; + +#ifdef HAVE_DIRENT_D_TYPE + if (d->d_type == DT_REG) + return TRUE; + else if (d->d_type != DT_UNKNOWN) + return FALSE; +#endif + + return (stat(d->d_name, &s) == 0 && S_ISREG(s.st_mode)); +} + +gboolean dirent_is_directory(struct dirent *d) +{ + struct stat s; + +#ifdef HAVE_DIRENT_D_TYPE + if (d->d_type == DT_DIR) + return TRUE; + else if (d->d_type != DT_UNKNOWN) + return FALSE; +#endif + + return (stat(d->d_name, &s) == 0 && S_ISDIR(s.st_mode)); +} + +gint change_dir(const gchar *dir) +{ + gchar *prevdir = NULL; + + if (debug_mode) + prevdir = g_get_current_dir(); + + if (chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + if (debug_mode) g_free(prevdir); + return -1; + } else if (debug_mode) { + gchar *cwd; + + cwd = g_get_current_dir(); + if (strcmp(prevdir, cwd) != 0) + g_print("current dir: %s\n", cwd); + g_free(cwd); + g_free(prevdir); + } + + return 0; +} + +gint make_dir(const gchar *dir) +{ + if (mkdir(dir, S_IRWXU) < 0) { + FILE_OP_ERROR(dir, "mkdir"); + return -1; + } + if (chmod(dir, S_IRWXU) < 0) + FILE_OP_ERROR(dir, "chmod"); + + return 0; +} + +gint make_dir_hier(const gchar *dir) +{ + gchar *parent_dir; + const gchar *p; + + for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) { + parent_dir = g_strndup(dir, p - dir); + if (*parent_dir != '\0') { + if (!is_dir_exist(parent_dir)) { + if (make_dir(parent_dir) < 0) { + g_free(parent_dir); + return -1; + } + } + } + g_free(parent_dir); + } + + if (!is_dir_exist(dir)) { + if (make_dir(dir) < 0) + return -1; + } + + return 0; +} + +gint remove_all_files(const gchar *dir) +{ + DIR *dp; + struct dirent *d; + gchar *prev_dir; + + prev_dir = g_get_current_dir(); + + if (chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(dir, "opendir"); + g_free(prev_dir); + return -1; + } + + while ((d = readdir(dp)) != NULL) { + if (!strcmp(d->d_name, ".") || + !strcmp(d->d_name, "..")) + continue; + + if (unlink(d->d_name) < 0) + FILE_OP_ERROR(d->d_name, "unlink"); + } + + closedir(dp); + + if (chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_numbered_files(const gchar *dir, guint first, guint last) +{ + DIR *dp; + struct dirent *d; + gchar *prev_dir; + gint fileno; + + prev_dir = g_get_current_dir(); + + if (chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(dir, "opendir"); + g_free(prev_dir); + return -1; + } + + while ((d = readdir(dp)) != NULL) { + fileno = to_number(d->d_name); + if (fileno >= 0 && first <= fileno && fileno <= last) { + if (is_dir_exist(d->d_name)) + continue; + if (unlink(d->d_name) < 0) + FILE_OP_ERROR(d->d_name, "unlink"); + } + } + + closedir(dp); + + if (chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_all_numbered_files(const gchar *dir) +{ + return remove_numbered_files(dir, 0, UINT_MAX); +} + +gint remove_expired_files(const gchar *dir, guint hours) +{ + DIR *dp; + struct dirent *d; + struct stat s; + gchar *prev_dir; + gint fileno; + time_t mtime, now, expire_time; + + prev_dir = g_get_current_dir(); + + if (chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(dir, "opendir"); + g_free(prev_dir); + return -1; + } + + now = time(NULL); + expire_time = hours * 60 * 60; + + while ((d = readdir(dp)) != NULL) { + fileno = to_number(d->d_name); + if (fileno >= 0) { + if (stat(d->d_name, &s) < 0) { + FILE_OP_ERROR(d->d_name, "stat"); + continue; + } + if (S_ISDIR(s.st_mode)) + continue; + mtime = MAX(s.st_mtime, s.st_atime); + if (now - mtime > expire_time) { + if (unlink(d->d_name) < 0) + FILE_OP_ERROR(d->d_name, "unlink"); + } + } + } + + closedir(dp); + + if (chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + return 0; +} + +gint remove_dir_recursive(const gchar *dir) +{ + struct stat s; + DIR *dp; + struct dirent *d; + gchar *prev_dir; + + /* g_print("dir = %s\n", dir); */ + + if (stat(dir, &s) < 0) { + FILE_OP_ERROR(dir, "stat"); + if (ENOENT == errno) return 0; + return -1; + } + + if (!S_ISDIR(s.st_mode)) { + if (unlink(dir) < 0) { + FILE_OP_ERROR(dir, "unlink"); + return -1; + } + + return 0; + } + + prev_dir = g_get_current_dir(); + /* g_print("prev_dir = %s\n", prev_dir); */ + + if (!path_cmp(prev_dir, dir)) { + g_free(prev_dir); + if (chdir("..") < 0) { + FILE_OP_ERROR(dir, "chdir"); + return -1; + } + prev_dir = g_get_current_dir(); + } + + if (chdir(dir) < 0) { + FILE_OP_ERROR(dir, "chdir"); + g_free(prev_dir); + return -1; + } + + if ((dp = opendir(".")) == NULL) { + FILE_OP_ERROR(dir, "opendir"); + chdir(prev_dir); + g_free(prev_dir); + return -1; + } + + /* remove all files in the directory */ + while ((d = readdir(dp)) != NULL) { + if (!strcmp(d->d_name, ".") || + !strcmp(d->d_name, "..")) + continue; + + /* g_print("removing %s\n", d->d_name); */ + + if (dirent_is_directory(d)) { + if (remove_dir_recursive(d->d_name) < 0) { + g_warning("can't remove directory\n"); + return -1; + } + } else { + if (unlink(d->d_name) < 0) + FILE_OP_ERROR(d->d_name, "unlink"); + } + } + + closedir(dp); + + if (chdir(prev_dir) < 0) { + FILE_OP_ERROR(prev_dir, "chdir"); + g_free(prev_dir); + return -1; + } + + g_free(prev_dir); + + if (rmdir(dir) < 0) { + FILE_OP_ERROR(dir, "rmdir"); + return -1; + } + + return 0; +} + +gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup) +{ + FILE *src_fp, *dest_fp; + gint n_read; + gchar buf[BUFSIZ]; + gchar *dest_bak = NULL; + gboolean err = FALSE; + + if ((src_fp = fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + if (is_file_exist(dest)) { + dest_bak = g_strconcat(dest, ".bak", NULL); + if (rename(dest, dest_bak) < 0) { + FILE_OP_ERROR(dest, "rename"); + fclose(src_fp); + g_free(dest_bak); + return -1; + } + } + + if ((dest_fp = fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + if (dest_bak) { + if (rename(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning(_("can't change file mode\n")); + } + + while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) { + if (n_read < sizeof(buf) && ferror(src_fp)) + break; + if (fwrite(buf, n_read, 1, dest_fp) < 1) { + g_warning(_("writing to %s failed.\n"), dest); + fclose(dest_fp); + fclose(src_fp); + unlink(dest); + if (dest_bak) { + if (rename(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fread"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + unlink(dest); + if (dest_bak) { + if (rename(dest_bak, dest) < 0) + FILE_OP_ERROR(dest_bak, "rename"); + g_free(dest_bak); + } + return -1; + } + + if (keep_backup == FALSE && dest_bak) + unlink(dest_bak); + + g_free(dest_bak); + + return 0; +} + +gint move_file(const gchar *src, const gchar *dest, gboolean overwrite) +{ + if (overwrite == FALSE && is_file_exist(dest)) { + g_warning("move_file(): file %s already exists.", dest); + return -1; + } + + if (rename(src, dest) == 0) return 0; + + if (EXDEV != errno) { + FILE_OP_ERROR(src, "rename"); + return -1; + } + + if (copy_file(src, dest, FALSE) < 0) return -1; + + unlink(src); + + return 0; +} + +gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest) +{ + FILE *dest_fp; + gint n_read; + gint bytes_left, to_read; + gchar buf[BUFSIZ]; + gboolean err = FALSE; + + if (fseek(fp, offset, SEEK_SET) < 0) { + perror("fseek"); + return -1; + } + + if ((dest_fp = fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + bytes_left = length; + to_read = MIN(bytes_left, sizeof(buf)); + + while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) { + if (n_read < to_read && ferror(fp)) + break; + if (fwrite(buf, n_read, 1, dest_fp) < 1) { + g_warning(_("writing to %s failed.\n"), dest); + fclose(dest_fp); + unlink(dest); + return -1; + } + bytes_left -= n_read; + if (bytes_left == 0) + break; + to_read = MIN(bytes_left, sizeof(buf)); + } + + if (ferror(fp)) { + perror("fread"); + err = TRUE; + } + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + unlink(dest); + return -1; + } + + return 0; +} + +/* convert line endings into CRLF. If the last line doesn't end with + * linebreak, add it. + */ +gchar *canonicalize_str(const gchar *str) +{ + const gchar *p; + guint new_len = 0; + gchar *out, *outp; + + for (p = str; *p != '\0'; ++p) { + if (*p != '\r') { + ++new_len; + if (*p == '\n') + ++new_len; + } + } + if (p == str || *(p - 1) != '\n') + new_len += 2; + + out = outp = g_malloc(new_len + 1); + for (p = str; *p != '\0'; ++p) { + if (*p != '\r') { + if (*p == '\n') + *outp++ = '\r'; + *outp++ = *p; + } + } + if (p == str || *(p - 1) != '\n') { + *outp++ = '\r'; + *outp++ = '\n'; + } + *outp = '\0'; + + return out; +} + +gint canonicalize_file(const gchar *src, const gchar *dest) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + gint len; + gboolean err = FALSE; + gboolean last_linebreak = FALSE; + + if ((src_fp = fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + + if ((dest_fp = fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + gint r = 0; + + len = strlen(buf); + if (len == 0) break; + last_linebreak = FALSE; + + if (buf[len - 1] != '\n') { + last_linebreak = TRUE; + r = fputs(buf, dest_fp); + } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { + r = fputs(buf, dest_fp); + } else { + if (len > 1) { + r = fwrite(buf, len - 1, 1, dest_fp); + if (r != 1) + r = EOF; + } + if (r != EOF) + r = fputs("\r\n", dest_fp); + } + + if (r == EOF) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + fclose(src_fp); + unlink(dest); + return -1; + } + } + + if (last_linebreak == TRUE) { + if (fputs("\r\n", dest_fp) == EOF) + err = TRUE; + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + unlink(dest); + return -1; + } + + return 0; +} + +gint canonicalize_file_replace(const gchar *file) +{ + gchar *tmp_file; + + tmp_file = get_tmp_file(); + + if (canonicalize_file(file, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (move_file(tmp_file, file, TRUE) < 0) { + g_warning("can't replace %s .\n", file); + unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(tmp_file); + return 0; +} + +gint uncanonicalize_file(const gchar *src, const gchar *dest) +{ + FILE *src_fp, *dest_fp; + gchar buf[BUFFSIZE]; + gboolean err = FALSE; + + if ((src_fp = fopen(src, "rb")) == NULL) { + FILE_OP_ERROR(src, "fopen"); + return -1; + } + + if ((dest_fp = fopen(dest, "wb")) == NULL) { + FILE_OP_ERROR(dest, "fopen"); + fclose(src_fp); + return -1; + } + + if (change_file_mode_rw(dest_fp, dest) < 0) { + FILE_OP_ERROR(dest, "chmod"); + g_warning("can't change file mode\n"); + } + + while (fgets(buf, sizeof(buf), src_fp) != NULL) { + strcrchomp(buf); + if (fputs(buf, dest_fp) == EOF) { + g_warning("writing to %s failed.\n", dest); + fclose(dest_fp); + fclose(src_fp); + unlink(dest); + return -1; + } + } + + if (ferror(src_fp)) { + FILE_OP_ERROR(src, "fgets"); + err = TRUE; + } + fclose(src_fp); + if (fclose(dest_fp) == EOF) { + FILE_OP_ERROR(dest, "fclose"); + err = TRUE; + } + + if (err) { + unlink(dest); + return -1; + } + + return 0; +} + +gint uncanonicalize_file_replace(const gchar *file) +{ + gchar *tmp_file; + + tmp_file = get_tmp_file(); + + if (uncanonicalize_file(file, tmp_file) < 0) { + g_free(tmp_file); + return -1; + } + + if (move_file(tmp_file, file, TRUE) < 0) { + g_warning("can't replace %s .\n", file); + unlink(tmp_file); + g_free(tmp_file); + return -1; + } + + g_free(tmp_file); + return 0; +} + +gchar *normalize_newlines(const gchar *str) +{ + const gchar *p = str; + gchar *out, *outp; + + out = outp = g_malloc(strlen(str) + 1); + for (p = str; *p != '\0'; ++p) { + if (*p == '\r') { + if (*(p + 1) != '\n') + *outp++ = '\n'; + } else + *outp++ = *p; + } + + *outp = '\0'; + + return out; +} + +gchar *get_outgoing_rfc2822_str(FILE *fp) +{ + gchar buf[BUFFSIZE]; + GString *str; + gchar *ret; + + str = g_string_new(NULL); + + /* output header part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (!g_strncasecmp(buf, "Bcc:", 4)) { + gint next; + + for (;;) { + next = fgetc(fp); + if (next == EOF) + break; + else if (next != ' ' && next != '\t') { + ungetc(next, fp); + break; + } + if (fgets(buf, sizeof(buf), fp) == NULL) + break; + } + } else { + g_string_append(str, buf); + g_string_append(str, "\r\n"); + if (buf[0] == '\0') + break; + } + } + + /* output body part */ + while (fgets(buf, sizeof(buf), fp) != NULL) { + strretchomp(buf); + if (buf[0] == '.') + g_string_append_c(str, '.'); + g_string_append(str, buf); + g_string_append(str, "\r\n"); + } + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +/* + * Create a new boundary in a way that it is very unlikely that this + * will occur in the following text. It would be easy to ensure + * uniqueness if everything is either quoted-printable or base64 + * encoded (note that conversion is allowed), but because MIME bodies + * may be nested, it may happen that the same boundary has already + * been used. We avoid scanning the message for conflicts and hope the + * best. + * + * boundary := 0*69 bcharsnospace + * bchars := bcharsnospace / " " + * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + * "+" / "_" / "," / "-" / "." / + * "/" / ":" / "=" / "?" + * + * some special characters removed because of buggy MTAs + */ + +gchar *generate_mime_boundary(const gchar *prefix) +{ + static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "1234567890+_./="; + gchar buf_uniq[17]; + gchar buf_date[64]; + gint i; + gint pid; + + pid = getpid(); + + /* We make the boundary depend on the pid, so that all running + * processes generate different values even when they have been + * started within the same second and srandom(time(NULL)) has been + * used. I can't see whether this is really an advantage but it + * doesn't do any harm. + */ + for (i = 0; i < sizeof(buf_uniq) - 1; i++) + buf_uniq[i] = tbl[(random() ^ pid) % (sizeof(tbl) - 1)]; + buf_uniq[i] = '\0'; + + get_rfc822_date(buf_date, sizeof(buf_date)); + subst_char(buf_date, ' ', '_'); + subst_char(buf_date, ',', '_'); + subst_char(buf_date, ':', '_'); + + return g_strdup_printf("%s=_%s_%s", prefix ? prefix : "Multipart", + buf_date, buf_uniq); +} + +gint change_file_mode_rw(FILE *fp, const gchar *file) +{ +#if HAVE_FCHMOD + return fchmod(fileno(fp), S_IRUSR|S_IWUSR); +#else + return chmod(file, S_IRUSR|S_IWUSR); +#endif +} + +FILE *my_tmpfile(void) +{ +#if HAVE_MKSTEMP + const gchar suffix[] = ".XXXXXX"; + const gchar *tmpdir; + guint tmplen; + const gchar *progname; + guint proglen; + gchar *fname; + gint fd; + FILE *fp; + + tmpdir = get_tmp_dir(); + tmplen = strlen(tmpdir); + progname = g_get_prgname(); + proglen = strlen(progname); + Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix), + return tmpfile()); + + memcpy(fname, tmpdir, tmplen); + fname[tmplen] = G_DIR_SEPARATOR; + memcpy(fname + tmplen + 1, progname, proglen); + memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix)); + + fd = mkstemp(fname); + if (fd < 0) + return tmpfile(); + + unlink(fname); + + fp = fdopen(fd, "w+b"); + if (!fp) + close(fd); + else + return fp; +#endif /* HAVE_MKSTEMP */ + + return tmpfile(); +} + +FILE *str_open_as_stream(const gchar *str) +{ + FILE *fp; + size_t len; + + g_return_val_if_fail(str != NULL, NULL); + + fp = my_tmpfile(); + if (!fp) { + FILE_OP_ERROR("str_open_as_stream", "my_tmpfile"); + return NULL; + } + + len = strlen(str); + if (len == 0) return fp; + + if (fwrite(str, len, 1, fp) != 1) { + FILE_OP_ERROR("str_open_as_stream", "fwrite"); + fclose(fp); + return NULL; + } + + rewind(fp); + return fp; +} + +gint str_write_to_file(const gchar *str, const gchar *file) +{ + FILE *fp; + size_t len; + + g_return_val_if_fail(str != NULL, -1); + g_return_val_if_fail(file != NULL, -1); + + if ((fp = fopen(file, "wb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return -1; + } + + len = strlen(str); + if (len == 0) { + fclose(fp); + return 0; + } + + if (fwrite(str, len, 1, fp) != 1) { + FILE_OP_ERROR(file, "fwrite"); + fclose(fp); + unlink(file); + return -1; + } + + if (fclose(fp) == EOF) { + FILE_OP_ERROR(file, "fclose"); + unlink(file); + return -1; + } + + return 0; +} + +gchar *file_read_to_str(const gchar *file) +{ + FILE *fp; + gchar *str; + + g_return_val_if_fail(file != NULL, NULL); + + if ((fp = fopen(file, "rb")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + return NULL; + } + + str = file_read_stream_to_str(fp); + + fclose(fp); + + return str; +} + +gchar *file_read_stream_to_str(FILE *fp) +{ + GByteArray *array; + gchar buf[BUFSIZ]; + gint n_read; + gchar *str; + + g_return_val_if_fail(fp != NULL, NULL); + + array = g_byte_array_new(); + + while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) { + if (n_read < sizeof(buf) && ferror(fp)) + break; + g_byte_array_append(array, buf, n_read); + } + + if (ferror(fp)) { + FILE_OP_ERROR("file stream", "fread"); + g_byte_array_free(array, TRUE); + return NULL; + } + + buf[0] = '\0'; + g_byte_array_append(array, buf, 1); + str = (gchar *)array->data; + g_byte_array_free(array, FALSE); + + return str; +} + +gint execute_async(gchar *const argv[]) +{ + pid_t pid; + gint status; + + if ((pid = fork()) < 0) { + perror("fork"); + return -1; + } + + if (pid == 0) { /* child process */ + pid_t gch_pid; + + if ((gch_pid = fork()) < 0) { + perror("fork"); + _exit(1); + } + + if (gch_pid == 0) { /* grandchild process */ + execvp(argv[0], argv); + + perror("execvp"); + _exit(1); + } + + _exit(0); + } + + waitpid(pid, &status, 0); + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + else + return -1; +} + +gint execute_sync(gchar *const argv[]) +{ + pid_t pid; + gint status; + + if ((pid = fork()) < 0) { + perror("fork"); + return -1; + } + + if (pid == 0) { /* child process */ + execvp(argv[0], argv); + + perror("execvp"); + _exit(1); + } + + waitpid(pid, &status, 0); + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + else + return -1; +} + +gint execute_command_line(const gchar *cmdline, gboolean async) +{ + gchar **argv; + gint ret; + + debug_print("executing: %s\n", cmdline); + + argv = strsplit_with_quote(cmdline, " ", 0); + + if (async) + ret = execute_async(argv); + else + ret = execute_sync(argv); + + g_strfreev(argv); + + return ret; +} + +gchar *get_command_output(const gchar *cmdline) +{ + gchar buf[BUFFSIZE]; + FILE *fp; + GString *str; + gchar *ret; + + g_return_val_if_fail(cmdline != NULL, NULL); + + if ((fp = popen(cmdline, "r")) == NULL) { + FILE_OP_ERROR(cmdline, "popen"); + return NULL; + } + + str = g_string_new(""); + + while (fgets(buf, sizeof(buf), fp) != NULL) + g_string_append(str, buf); + + pclose(fp); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +gint open_uri(const gchar *uri, const gchar *cmdline) +{ + gchar buf[BUFFSIZE]; + gchar *p; + + g_return_val_if_fail(uri != NULL, -1); + + if (cmdline && + (p = strchr(cmdline, '%')) && *(p + 1) == 's' && + !strchr(p + 2, '%')) + g_snprintf(buf, sizeof(buf), cmdline, uri); + else { + if (cmdline) + g_warning("Open URI command line is invalid " + "(there must be only one '%%s'): %s", + cmdline); + g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, uri); + } + + execute_command_line(buf, TRUE); + + return 0; +} + +time_t remote_tzoffset_sec(const gchar *zone) +{ + static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT"; + gchar zone3[4]; + gchar *p; + gchar c; + gint iustz; + gint offset; + time_t remoteoffset; + + strncpy(zone3, zone, 3); + zone3[3] = '\0'; + remoteoffset = 0; + + if (sscanf(zone, "%c%d", &c, &offset) == 2 && + (c == '+' || c == '-')) { + remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60; + if (c == '-') + remoteoffset = -remoteoffset; + } else if (!strncmp(zone, "UT" , 2) || + !strncmp(zone, "GMT", 2)) { + remoteoffset = 0; + } else if (strlen(zone3) == 3) { + for (p = ustzstr; *p != '\0'; p += 3) { + if (!strncasecmp(p, zone3, 3)) { + iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8; + remoteoffset = iustz * 3600; + break; + } + } + if (*p == '\0') + return -1; + } else if (strlen(zone3) == 1) { + switch (zone[0]) { + case 'Z': remoteoffset = 0; break; + case 'A': remoteoffset = -1; break; + case 'B': remoteoffset = -2; break; + case 'C': remoteoffset = -3; break; + case 'D': remoteoffset = -4; break; + case 'E': remoteoffset = -5; break; + case 'F': remoteoffset = -6; break; + case 'G': remoteoffset = -7; break; + case 'H': remoteoffset = -8; break; + case 'I': remoteoffset = -9; break; + case 'K': remoteoffset = -10; break; /* J is not used */ + case 'L': remoteoffset = -11; break; + case 'M': remoteoffset = -12; break; + case 'N': remoteoffset = 1; break; + case 'O': remoteoffset = 2; break; + case 'P': remoteoffset = 3; break; + case 'Q': remoteoffset = 4; break; + case 'R': remoteoffset = 5; break; + case 'S': remoteoffset = 6; break; + case 'T': remoteoffset = 7; break; + case 'U': remoteoffset = 8; break; + case 'V': remoteoffset = 9; break; + case 'W': remoteoffset = 10; break; + case 'X': remoteoffset = 11; break; + case 'Y': remoteoffset = 12; break; + default: remoteoffset = 0; break; + } + remoteoffset = remoteoffset * 3600; + } else + return -1; + + return remoteoffset; +} + +time_t tzoffset_sec(time_t *now) +{ + struct tm gmt, *lt; + gint off; + + gmt = *gmtime(now); + lt = localtime(now); + + off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; + + if (lt->tm_year < gmt.tm_year) + off -= 24 * 60; + else if (lt->tm_year > gmt.tm_year) + off += 24 * 60; + else if (lt->tm_yday < gmt.tm_yday) + off -= 24 * 60; + else if (lt->tm_yday > gmt.tm_yday) + off += 24 * 60; + + if (off >= 24 * 60) /* should be impossible */ + off = 23 * 60 + 59; /* if not, insert silly value */ + if (off <= -24 * 60) + off = -(23 * 60 + 59); + + return off * 60; +} + +/* calculate timezone offset */ +gchar *tzoffset(time_t *now) +{ + static gchar offset_string[6]; + struct tm gmt, *lt; + gint off; + gchar sign = '+'; + + gmt = *gmtime(now); + lt = localtime(now); + + off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; + + if (lt->tm_year < gmt.tm_year) + off -= 24 * 60; + else if (lt->tm_year > gmt.tm_year) + off += 24 * 60; + else if (lt->tm_yday < gmt.tm_yday) + off -= 24 * 60; + else if (lt->tm_yday > gmt.tm_yday) + off += 24 * 60; + + if (off < 0) { + sign = '-'; + off = -off; + } + + if (off >= 24 * 60) /* should be impossible */ + off = 23 * 60 + 59; /* if not, insert silly value */ + + sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60); + + return offset_string; +} + +void get_rfc822_date(gchar *buf, gint len) +{ + struct tm *lt; + time_t t; + gchar day[4], mon[4]; + gint dd, hh, mm, ss, yyyy; + + t = time(NULL); + lt = localtime(&t); + + sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n", + day, mon, &dd, &hh, &mm, &ss, &yyyy); + g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s", + day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t)); +} + +static FILE *log_fp = NULL; + +void set_log_file(const gchar *filename) +{ + if (log_fp) return; + log_fp = fopen(filename, "wb"); + if (!log_fp) + FILE_OP_ERROR(filename, "fopen"); +} + +void close_log_file(void) +{ + if (log_fp) { + fclose(log_fp); + log_fp = NULL; + } +} + +static guint log_verbosity_count = 0; + +void log_verbosity_set(gboolean verbose) +{ + if (verbose) + log_verbosity_count++; + else if (log_verbosity_count > 0) + log_verbosity_count--; +} + +void debug_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE]; + + if (!debug_mode) return; + + va_start(args, format); + g_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + fputs(buf, stdout); +} + +#define TIME_LEN 11 + +void log_print(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + if (debug_mode) fputs(buf, stdout); + log_window_append(buf, LOG_NORMAL); + if (log_fp) { + fputs(buf, log_fp); + fflush(log_fp); + } + if (log_verbosity_count) + statusbar_puts_all(buf + TIME_LEN); +} + +void log_message(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + if (debug_mode) g_message("%s", buf + TIME_LEN); + log_window_append(buf + TIME_LEN, LOG_MSG); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("* message: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } + statusbar_puts_all(buf + TIME_LEN); +} + +void log_warning(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + g_warning("%s", buf); + log_window_append(buf + TIME_LEN, LOG_WARN); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("** warning: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } +} + +void log_error(const gchar *format, ...) +{ + va_list args; + gchar buf[BUFFSIZE + TIME_LEN]; + time_t t; + + time(&t); + strftime(buf, TIME_LEN + 1, "[%H:%M:%S] ", localtime(&t)); + + va_start(args, format); + g_vsnprintf(buf + TIME_LEN, BUFFSIZE, format, args); + va_end(args); + + g_warning("%s", buf); + log_window_append(buf + TIME_LEN, LOG_ERROR); + if (log_fp) { + fwrite(buf, TIME_LEN, 1, log_fp); + fputs("*** error: ", log_fp); + fputs(buf + TIME_LEN, log_fp); + fflush(log_fp); + } +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..b9788181 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,428 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_ALLOCA_H +# include +#endif +#if HAVE_WCHAR_H +# include +#endif + +/* The AC_CHECK_SIZEOF() in configure fails for some machines. + * we provide some fallback values here */ +#if !SIZEOF_UNSIGNED_SHORT + #undef SIZEOF_UNSIGNED_SHORT + #define SIZEOF_UNSIGNED_SHORT 2 +#endif +#if !SIZEOF_UNSIGNED_INT + #undef SIZEOF_UNSIGNED_INT + #define SIZEOF_UNSIGNED_INT 4 +#endif +#if !SIZEOF_UNSIGNED_LONG + #undef SIZEOF_UNSIGNED_LONG + #define SIZEOF_UNSIGNED_LONG 4 +#endif + +#ifndef HAVE_U32_TYPEDEF + #undef u32 /* maybe there is a macro with this name */ + typedef guint32 u32; + #define HAVE_U32_TYPEDEF +#endif + +#ifndef BIG_ENDIAN_HOST + #if (G_BYTE_ORDER == G_BIG_ENDIAN) + #define BIG_ENDIAN_HOST 1 + #endif +#endif + +#define CHDIR_RETURN_IF_FAIL(dir) \ +{ \ + if (change_dir(dir) < 0) return; \ +} + +#define CHDIR_RETURN_VAL_IF_FAIL(dir, val) \ +{ \ + if (change_dir(dir) < 0) return val; \ +} + +#define Xalloca(ptr, size, iffail) \ +{ \ + if ((ptr = alloca(size)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } \ +} + +#define Xstrdup_a(ptr, str, iffail) \ +{ \ + gchar *__tmp; \ + \ + if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else \ + strcpy(__tmp, str); \ + \ + ptr = __tmp; \ +} + +#define Xstrndup_a(ptr, str, len, iffail) \ +{ \ + gchar *__tmp; \ + \ + if ((__tmp = alloca(len + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else { \ + strncpy(__tmp, str, len); \ + __tmp[len] = '\0'; \ + } \ + \ + ptr = __tmp; \ +} + +#define Xstrcat_a(ptr, str1, str2, iffail) \ +{ \ + gchar *__tmp; \ + gint len1, len2; \ + \ + len1 = strlen(str1); \ + len2 = strlen(str2); \ + if ((__tmp = alloca(len1 + len2 + 1)) == NULL) { \ + g_warning("can't allocate memory\n"); \ + iffail; \ + } else { \ + memcpy(__tmp, str1, len1); \ + memcpy(__tmp + len1, str2, len2 + 1); \ + } \ + \ + ptr = __tmp; \ +} + +#define AUTORELEASE_STR(str, iffail) \ +{ \ + gchar *__str; \ + Xstrdup_a(__str, str, iffail); \ + g_free(str); \ + str = __str; \ +} + +#define FILE_OP_ERROR(file, func) \ +{ \ + fprintf(stderr, "%s: ", file); \ + perror(func); \ +} + +/* for macro expansion */ +#define Str(x) #x +#define Xstr(x) Str(x) + +void list_free_strings (GList *list); +void slist_free_strings (GSList *list); + +void hash_free_strings (GHashTable *table); +void hash_free_value_mem (GHashTable *table); + +gint str_case_equal (gconstpointer v, + gconstpointer v2); +guint str_case_hash (gconstpointer key); + +void ptr_array_free_strings (GPtrArray *array); + +typedef gboolean (*StrFindFunc) (const gchar *haystack, + const gchar *needle); + +gboolean str_find (const gchar *haystack, + const gchar *needle); +gboolean str_case_find (const gchar *haystack, + const gchar *needle); +gboolean str_find_equal (const gchar *haystack, + const gchar *needle); +gboolean str_case_find_equal (const gchar *haystack, + const gchar *needle); + +/* number-string conversion */ +gint to_number (const gchar *nstr); +gchar *itos_buf (gchar *nstr, + gint n); +gchar *itos (gint n); +gchar *to_human_readable (off_t size); + +/* alternative string functions */ +gint strcmp2 (const gchar *s1, + const gchar *s2); +gint path_cmp (const gchar *s1, + const gchar *s2); +gchar *strretchomp (gchar *str); +gchar *strtailchomp (gchar *str, + gchar tail_char); +gchar *strcrchomp (gchar *str); +gchar *strcasestr (const gchar *haystack, + const gchar *needle); +gpointer my_memmem (gconstpointer haystack, + size_t haystacklen, + gconstpointer needle, + size_t needlelen); +gchar *strncpy2 (gchar *dest, + const gchar *src, + size_t n); + +/* wide-character functions */ +#if !HAVE_ISWALNUM +int iswalnum (wint_t wc); +#endif +#if !HAVE_ISWSPACE +int iswspace (wint_t wc); +#endif +#if !HAVE_TOWLOWER +wint_t towlower (wint_t wc); +#endif + +#if !HAVE_WCSLEN +size_t wcslen (const wchar_t *s); +#endif +#if !HAVE_WCSCPY +wchar_t *wcscpy (wchar_t *dest, + const wchar_t *src); +#endif +#if !HAVE_WCSNCPY +wchar_t *wcsncpy (wchar_t *dest, + const wchar_t *src, + size_t n); +#endif + +wchar_t *wcsdup (const wchar_t *s); +wchar_t *wcsndup (const wchar_t *s, + size_t n); +wchar_t *strdup_mbstowcs (const gchar *s); +gchar *strdup_wcstombs (const wchar_t *s); +gint wcsncasecmp (const wchar_t *s1, + const wchar_t *s2, + size_t n); +wchar_t *wcscasestr (const wchar_t *haystack, + const wchar_t *needle); +gint get_mbs_len (const gchar *s); + +gboolean is_next_nonascii (const guchar *s); +gint get_next_word_len (const guchar *s); + +/* functions for string parsing */ +gint subject_compare (const gchar *s1, + const gchar *s2); +gint subject_compare_for_sort (const gchar *s1, + const gchar *s2); +void trim_subject_for_compare (gchar *str); +void trim_subject_for_sort (gchar *str); +void trim_subject (gchar *str); +void eliminate_parenthesis (gchar *str, + gchar op, + gchar cl); +void extract_parenthesis (gchar *str, + gchar op, + gchar cl); + +void extract_parenthesis_with_skip_quote (gchar *str, + gchar quote_chr, + gchar op, + gchar cl); + +void eliminate_quote (gchar *str, + gchar quote_chr); +void extract_quote (gchar *str, + gchar quote_chr); +void eliminate_address_comment (gchar *str); +gchar *strchr_with_skip_quote (const gchar *str, + gint quote_chr, + gint c); +gchar *strrchr_with_skip_quote (const gchar *str, + gint quote_chr, + gint c); +void extract_address (gchar *str); +void extract_list_id_str (gchar *str); + +GSList *address_list_append (GSList *addr_list, + const gchar *str); +GSList *references_list_append (GSList *msgid_list, + const gchar *str); +GSList *newsgroup_list_append (GSList *group_list, + const gchar *str); + +GList *add_history (GList *list, + const gchar *str); + +void remove_return (gchar *str); +void remove_space (gchar *str); +void unfold_line (gchar *str); +void subst_char (gchar *str, + gchar orig, + gchar subst); +void subst_chars (gchar *str, + gchar *orig, + gchar subst); +void subst_for_filename (gchar *str); +gboolean is_header_line (const gchar *str); +gboolean is_ascii_str (const guchar *str); +gint get_quote_level (const gchar *str); + +gchar *strstr_with_skip_quote (const gchar *haystack, + const gchar *needle); +gchar *strchr_parenthesis_close (const gchar *str, + gchar op, + gchar cl); + +gchar **strsplit_parenthesis (const gchar *str, + gchar op, + gchar cl, + gint max_tokens); +gchar **strsplit_with_quote (const gchar *str, + const gchar *delim, + gint max_tokens); + +gchar *get_abbrev_newsgroup_name (const gchar *group, + gint len); +gchar *trim_string (const gchar *str, + gint len); +gchar *trim_string_before (const gchar *str, + gint len); + +GList *uri_list_extract_filenames (const gchar *uri_list); +gboolean is_uri_string (const gchar *str); +gchar *get_uri_path (const gchar *uri); +void decode_uri (gchar *decoded_uri, + const gchar *encoded_uri); +gint scan_mailto_url (const gchar *mailto, + gchar **to, + gchar **cc, + gchar **bcc, + gchar **subject, + gchar **body); + +/* return static strings */ +const gchar *get_home_dir (void); +const gchar *get_rc_dir (void); +const gchar *get_news_cache_dir (void); +const gchar *get_imap_cache_dir (void); +const gchar *get_mime_tmp_dir (void); +const gchar *get_template_dir (void); +const gchar *get_tmp_dir (void); +gchar *get_tmp_file (void); +const gchar *get_domain_name (void); + +/* file / directory handling */ +off_t get_file_size (const gchar *file); +off_t get_file_size_as_crlf (const gchar *file); +off_t get_left_file_size (FILE *fp); + +gboolean file_exist (const gchar *file, + gboolean allow_fifo); +gboolean is_dir_exist (const gchar *dir); +gboolean is_file_entry_exist (const gchar *file); +gboolean dirent_is_regular_file (struct dirent *d); +gboolean dirent_is_directory (struct dirent *d); + +#define is_file_exist(file) file_exist(file, FALSE) +#define is_file_or_fifo_exist(file) file_exist(file, TRUE) + +gint change_dir (const gchar *dir); +gint make_dir (const gchar *dir); +gint make_dir_hier (const gchar *dir); +gint remove_all_files (const gchar *dir); +gint remove_numbered_files (const gchar *dir, + guint first, + guint last); +gint remove_all_numbered_files (const gchar *dir); +gint remove_expired_files (const gchar *dir, + guint hours); +gint remove_dir_recursive (const gchar *dir); +gint copy_file (const gchar *src, + const gchar *dest, + gboolean keep_backup); +gint move_file (const gchar *src, + const gchar *dest, + gboolean overwrite); +gint copy_file_part (FILE *fp, + off_t offset, + size_t length, + const gchar *dest); + +gchar *canonicalize_str (const gchar *str); +gint canonicalize_file (const gchar *src, + const gchar *dest); +gint canonicalize_file_replace (const gchar *file); +gint uncanonicalize_file (const gchar *src, + const gchar *dest); +gint uncanonicalize_file_replace(const gchar *file); + +gchar *normalize_newlines (const gchar *str); + +gchar *get_outgoing_rfc2822_str (FILE *fp); +gchar *generate_mime_boundary (const gchar *prefix); + +gint change_file_mode_rw (FILE *fp, + const gchar *file); +FILE *my_tmpfile (void); +FILE *str_open_as_stream (const gchar *str); +gint str_write_to_file (const gchar *str, + const gchar *file); +gchar *file_read_to_str (const gchar *file); +gchar *file_read_stream_to_str (FILE *fp); + +/* process execution */ +gint execute_async (gchar *const argv[]); +gint execute_sync (gchar *const argv[]); +gint execute_command_line (const gchar *cmdline, + gboolean async); +gchar *get_command_output (const gchar *cmdline); + +/* open URI with external browser */ +gint open_uri(const gchar *uri, const gchar *cmdline); + +/* time functions */ +time_t remote_tzoffset_sec (const gchar *zone); +time_t tzoffset_sec (time_t *now); +gchar *tzoffset (time_t *now); +void get_rfc822_date (gchar *buf, + gint len); + +/* logging */ +void set_log_file (const gchar *filename); +void close_log_file (void); +void log_verbosity_set (gboolean verbose); +void debug_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_print (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_message (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_warning (const gchar *format, ...) G_GNUC_PRINTF(1, 2); +void log_error (const gchar *format, ...) G_GNUC_PRINTF(1, 2); + +#endif /* __UTILS_H__ */ diff --git a/src/uuencode.c b/src/uuencode.c new file mode 100644 index 00000000..e0b2e79a --- /dev/null +++ b/src/uuencode.c @@ -0,0 +1,101 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include + +#define UUDECODE(c) (c=='`' ? 0 : c - ' ') +#define N64(i) (i & ~63) + +const char uudigit[64] = +{ + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_' +}; + +int touufrombits(unsigned char *out, const unsigned char *in, int inlen) +{ + int len; + + if (inlen > 45) return -1; + len = (inlen * 4 + 2) / 3 + 1; + *out++ = uudigit[inlen]; + + for (; inlen >= 3; inlen -= 3) { + *out++ = uudigit[in[0] >> 2]; + *out++ = uudigit[((in[0] << 4) & 0x30) | (in[1] >> 4)]; + *out++ = uudigit[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; + *out++ = uudigit[in[2] & 0x3f]; + in += 3; + } + + if (inlen > 0) { + *out++ = uudigit[(in[0] >> 2)]; + if (inlen == 1) { + *out++ = uudigit[((in[0] << 4) & 0x30)]; + } else { + *out++ = uudigit[(((in[0] << 4) & 0x30) | (in[1] >> 4))] ; + *out++ = uudigit[((in[1] << 2) & 0x3c)]; + } + } + *out = '\0'; + + return len; +} + +int fromuutobits(char *out, const char *in) +{ + int len, outlen, inlen; + register unsigned char digit1, digit2; + + outlen = UUDECODE(in[0]); + in += 1; + if(outlen < 0 || outlen > 45) + return -2; + if(outlen == 0) + return 0; + inlen = (outlen * 4 + 2) / 3; + len = 0; + + for( ; inlen>0; inlen-=4) { + digit1 = UUDECODE(in[0]); + if (N64(digit1)) return -1; + digit2 = UUDECODE(in[1]); + if (N64(digit2)) return -1; + out[len++] = (digit1 << 2) | (digit2 >> 4); + if (inlen > 2) { + digit1 = UUDECODE(in[2]); + if (N64(digit1)) return -1; + out[len++] = (digit2 << 4) | (digit1 >> 2); + if (inlen > 3) { + digit2 = UUDECODE(in[3]); + if (N64(digit2)) return -1; + out[len++] = (digit1 << 6) | digit2; + } + } + in += 4; + } + + return len == outlen ? len : -3; +} diff --git a/src/uuencode.h b/src/uuencode.h new file mode 100644 index 00000000..3658ebc6 --- /dev/null +++ b/src/uuencode.h @@ -0,0 +1,24 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999,2000 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +void touufrombits(unsigned char *, const unsigned char *, int); +int fromuutobits(char *, const char *); + +#define X_UUENCODE_END_LINE '`' +#define UUENCODE_END_LINE ' ' diff --git a/src/vcard.c b/src/vcard.c new file mode 100644 index 00000000..55fe6fd4 --- /dev/null +++ b/src/vcard.c @@ -0,0 +1,777 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Functions necessary to access vCard files. vCard files are used + * by GnomeCard for addressbook, and Netscape for sending business + * card information. Refer to RFC2426 for more information. + */ + +#include +#include +#include + +#include "mgutils.h" +#include "vcard.h" +#include "addritem.h" +#include "addrcache.h" + +#define GNOMECARD_DIR ".gnome" +#define GNOMECARD_FILE "GnomeCard" +#define GNOMECARD_SECTION "[file]" +#define GNOMECARD_PARAM "open" + +#define VCARD_TEST_LINES 200 + +/* +* Create new cardfile object. +*/ +VCardFile *vcard_create() { + VCardFile *cardFile; + cardFile = g_new0( VCardFile, 1 ); + cardFile->name = NULL; + cardFile->path = NULL; + cardFile->file = NULL; + cardFile->bufptr = cardFile->buffer; + cardFile->addressCache = addrcache_create(); + cardFile->retVal = MGU_SUCCESS; + cardFile->accessFlag = FALSE; + return cardFile; +} + +/* +* Properties... +*/ +void vcard_set_name( VCardFile* cardFile, const gchar *value ) { + g_return_if_fail( cardFile != NULL ); + cardFile->name = mgu_replace_string( cardFile->name, value ); + g_strstrip( cardFile->name ); +} +void vcard_set_file( VCardFile* cardFile, const gchar *value ) { + g_return_if_fail( cardFile != NULL ); + addrcache_refresh( cardFile->addressCache ); + cardFile->path = mgu_replace_string( cardFile->path, value ); + g_strstrip( cardFile->path ); +} +void vcard_set_accessed( VCardFile *cardFile, const gboolean value ) { + g_return_if_fail( cardFile != NULL ); + cardFile->accessFlag = value; +} + +/* +* Test whether file was modified since last access. +* Return: TRUE if file was modified. +*/ +gboolean vcard_get_modified( VCardFile *vcardFile ) { + g_return_val_if_fail( vcardFile != NULL, FALSE ); + return addrcache_check_file( vcardFile->addressCache, vcardFile->path ); +} +gboolean vcard_get_accessed( VCardFile *vcardFile ) { + g_return_val_if_fail( vcardFile != NULL, FALSE ); + return addrcache_check_file( vcardFile->addressCache, vcardFile->path ); +} + +/* +* Test whether file was read. +* Return: TRUE if file was read. +*/ +gboolean vcard_get_read_flag( VCardFile *vcardFile ) { + g_return_val_if_fail( vcardFile != NULL, FALSE ); + return vcardFile->addressCache->dataRead; +} + +/* +* Return status code from last file operation. +* Return: Status code. +*/ +gint vcard_get_status( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, -1 ); + return cardFile->retVal; +} + +ItemFolder *vcard_get_root_folder( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, NULL ); + return addrcache_get_root_folder( cardFile->addressCache ); +} +gchar *vcard_get_name( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, NULL ); + return cardFile->name; +} + +/* +* Refresh internal variables to force a file read. +*/ +void vcard_force_refresh( VCardFile *cardFile ) { + addrcache_refresh( cardFile->addressCache ); +} + +/* +* Create new cardfile object for specified file. +*/ +VCardFile *vcard_create_path( const gchar *path ) { + VCardFile *cardFile; + cardFile = vcard_create(); + vcard_set_file(cardFile, path); + return cardFile; +} + +/* +* Free up cardfile object by releasing internal memory. +*/ +void vcard_free( VCardFile *cardFile ) { + g_return_if_fail( cardFile != NULL ); + + /* Close file */ + if( cardFile->file ) fclose( cardFile->file ); + + /* Free internal stuff */ + g_free( cardFile->name ); + g_free( cardFile->path ); + + /* Clear cache */ + addrcache_clear( cardFile->addressCache ); + addrcache_free( cardFile->addressCache ); + + /* Clear pointers */ + cardFile->file = NULL; + cardFile->name = NULL; + cardFile->path = NULL; + cardFile->addressCache = NULL; + cardFile->retVal = MGU_SUCCESS; + cardFile->accessFlag = FALSE; + + /* Now release file object */ + g_free( cardFile ); + +} + +/* +* Display object to specified stream. +*/ +void vcard_print_file( VCardFile *cardFile, FILE *stream ) { + g_return_if_fail( cardFile != NULL ); + + fprintf( stream, "VCardFile:\n" ); + fprintf( stream, " name: '%s'\n", cardFile->name ); + fprintf( stream, "file spec: '%s'\n", cardFile->path ); + fprintf( stream, " ret val: %d\n", cardFile->retVal ); + addrcache_print( cardFile->addressCache, stream ); + addritem_print_item_folder( cardFile->addressCache->rootFolder, stream ); +} + +/* +* Open file for read. +* return: TRUE if file opened successfully. +*/ +static gint vcard_open_file( VCardFile* cardFile ) { + g_return_val_if_fail( cardFile != NULL, -1 ); + + /* fprintf( stdout, "Opening file\n" ); */ + cardFile->addressCache->dataRead = FALSE; + if( cardFile->path ) { + cardFile->file = fopen( cardFile->path, "rb" ); + if( ! cardFile->file ) { + /* fprintf( stderr, "can't open %s\n", cardFile->path ); */ + cardFile->retVal = MGU_OPEN_FILE; + return cardFile->retVal; + } + } + else { + /* fprintf( stderr, "file not specified\n" ); */ + cardFile->retVal = MGU_NO_FILE; + return cardFile->retVal; + } + + /* Setup a buffer area */ + cardFile->buffer[0] = '\0'; + cardFile->bufptr = cardFile->buffer; + cardFile->retVal = MGU_SUCCESS; + return cardFile->retVal; +} + +/* +* Close file. +*/ +static void vcard_close_file( VCardFile *cardFile ) { + g_return_if_fail( cardFile != NULL ); + if( cardFile->file ) fclose( cardFile->file ); + cardFile->file = NULL; +} + +/* +* Read line of text from file. +* Return: ptr to buffer where line starts. +*/ +static gchar *vcard_read_line( VCardFile *cardFile ) { + while( *cardFile->bufptr == '\n' || *cardFile->bufptr == '\0' ) { + if( fgets( cardFile->buffer, VCARDBUFSIZE, cardFile->file ) == NULL ) + return NULL; + g_strstrip( cardFile->buffer ); + cardFile->bufptr = cardFile->buffer; + } + return cardFile->bufptr; +} + +/* +* Read line of text from file. +* Return: ptr to buffer where line starts. +*/ +static gchar *vcard_get_line( VCardFile *cardFile ) { + gchar buf[ VCARDBUFSIZE ]; + gchar *start, *end; + gint len; + + if (vcard_read_line( cardFile ) == NULL ) { + buf[0] = '\0'; + return NULL; + } + + /* Copy into private buffer */ + start = cardFile->bufptr; + len = strlen( start ); + end = start + len; + strncpy( buf, start, len ); + buf[ len ] = '\0'; + g_strstrip(buf); + cardFile->bufptr = end + 1; + + /* Return a copy of buffer */ + return g_strdup( buf ); +} + +/* +* Free linked lists of character strings. +*/ +static void vcard_free_lists( GSList *listName, GSList *listAddr, GSList *listRem, GSList* listID ) { + mgu_free_list( listName ); + mgu_free_list( listAddr ); + mgu_free_list( listRem ); + mgu_free_list( listID ); +} + +/* +* Read quoted-printable text, which may span several lines into one long string. +* Param: cardFile - object. +* Param: tagvalue - will be placed into the linked list. +*/ +static gchar *vcard_read_qp( VCardFile *cardFile, gchar *tagvalue ) { + GSList *listQP = NULL; + gint len = 0; + gchar *line = tagvalue; + + while( line ) { + listQP = g_slist_append( listQP, line ); + len = strlen( line ) - 1; + if( len > 0 ) { + if( line[ len ] != '=' ) break; + line[ len ] = '\0'; + } + line = vcard_get_line( cardFile ); + } + + /* Coalesce linked list into one long buffer. */ + line = mgu_list_coalesce( listQP ); + + /* Clean up */ + mgu_free_list( listQP ); + listQP = NULL; + return line; +} + +/* +* Parse tag name from line buffer. +* Return: Buffer containing the tag name, or NULL if no delimiter char found. +*/ +static gchar *vcard_get_tagname( gchar* line, gchar dlm ) { + gint len = 0; + gchar *tag = NULL; + gchar *lptr = line; + + while( *lptr++ ) { + if( *lptr == dlm ) { + len = lptr - line; + tag = g_strndup( line, len+1 ); + tag[ len ] = '\0'; + g_strdown( tag ); + return tag; + } + } + return tag; +} + +/* +* Parse tag value from line buffer. +* Return: Buffer containing the tag value. Empty string is returned if +* no delimiter char found. +*/ +static gchar *vcard_get_tagvalue( gchar* line, gchar dlm ) { + gchar *value = NULL; + gchar *start = NULL; + gchar *lptr; + gint len = 0; + + for( lptr = line; *lptr; lptr++ ) { + if( *lptr == dlm ) { + if( ! start ) + start = lptr + 1; + } + } + if( start ) { + len = lptr - start; + value = g_strndup( start, len+1 ); + } + else { + /* Ensure that we get an empty string */ + value = g_strndup( "", 1 ); + } + value[ len ] = '\0'; + return value; +} + +#if 0 +/* +* Dump linked lists of character strings (for debug). +*/ +static void vcard_dump_lists( GSList *listName, GSList *listAddr, GSList *listRem, GSList *listID, FILE *stream ) { + fprintf( stream, "dump name\n" ); + fprintf( stream, "------------\n" ); + mgu_print_list( listName, stdout ); + fprintf( stream, "dump address\n" ); + fprintf( stream, "------------\n" ); + mgu_print_list( listAddr, stdout ); + fprintf( stream, "dump remarks\n" ); + fprintf( stdout, "------------\n" ); + mgu_print_list( listRem, stdout ); + fprintf( stream, "dump id\n" ); + fprintf( stdout, "------------\n" ); + mgu_print_list( listID, stdout ); +} +#endif + +/* +* Build an address list entry and append to list of address items. +*/ +static void vcard_build_items( VCardFile *cardFile, GSList *listName, GSList *listAddr, GSList *listRem, + GSList *listID ) +{ + GSList *nodeName = listName; + GSList *nodeID = listID; + gchar *str; + while( nodeName ) { + GSList *nodeAddress = listAddr; + GSList *nodeRemarks = listRem; + ItemPerson *person = addritem_create_item_person(); + addritem_person_set_common_name( person, nodeName->data ); + while( nodeAddress ) { + str = nodeAddress->data; + if( *str != '\0' ) { + ItemEMail *email = addritem_create_item_email(); + addritem_email_set_address( email, str ); + str = nodeRemarks->data; + if( nodeRemarks ) { + if( str ) { + if( g_strcasecmp( str, "internet" ) != 0 ) { + if( *str != '\0' ) addritem_email_set_remarks( email, str ); + } + } + } + addrcache_id_email( cardFile->addressCache, email ); + addrcache_person_add_email( cardFile->addressCache, person, email ); + } + nodeAddress = g_slist_next( nodeAddress ); + nodeRemarks = g_slist_next( nodeRemarks ); + } + if( person->listEMail ) { + addrcache_id_person( cardFile->addressCache, person ); + addrcache_add_person( cardFile->addressCache, person ); + } + else { + addritem_free_item_person( person ); + } + if( nodeID ) { + str = nodeID->data; + addritem_person_set_external_id( person, str ); + } + nodeName = g_slist_next( nodeName ); + nodeID = g_slist_next( nodeID ); + } +} + +/* Unescape characters in quoted-printable string. */ +static void vcard_unescape_qp( gchar *value ) { + gchar *ptr, *src, *dest; + gint d, v = 0; + gchar ch; + gboolean gotch; + ptr = value; + while( *ptr ) { + gotch = FALSE; + if( *ptr == '=' ) { + v = 0; + ch = *(ptr + 1); + if( ch ) { + if( ch > '0' && ch < '8' ) v = ch - '0'; + } + d = -1; + ch = *(ptr + 2); + if( ch ) { + if( ch > '\x60' ) ch -= '\x20'; + if( ch > '0' && ch < ' ' ) d = ch - '0'; + d = ch - '0'; + if( d > 9 ) d -= 7; + if( d > -1 && d < 16 ) { + v = ( 16 * v ) + d; + gotch = TRUE; + } + } + } + if( gotch ) { + /* Replace = with char and move down in buffer */ + *ptr = v; + src = ptr + 3; + dest = ptr + 1; + while( *src ) { + *dest++ = *src++; + } + *dest = '\0'; + } + ptr++; + } +} + +/* +* Read file data into root folder. +* Note that one vCard can have multiple E-Mail addresses (MAIL tags); +* these are broken out into separate address items. An address item +* is generated for the person identified by FN tag and each EMAIL tag. +* If a sub-type is included in the EMAIL entry, this will be used as +* the Remarks member. Also note that it is possible for one vCard +* entry to have multiple FN tags; this might not make sense. However, +* it will generate duplicate address entries for each person listed. +*/ +static void vcard_read_file( VCardFile *cardFile ) { + gchar *tagtemp = NULL, *tagname = NULL, *tagvalue = NULL, *tagtype = NULL; + GSList *listName = NULL, *listAddress = NULL, *listRemarks = NULL, *listID = NULL; + /* GSList *listQP = NULL; */ + + for( ;; ) { + gchar *line; + + line = vcard_get_line( cardFile ); + if( line == NULL ) break; + + /* fprintf( stdout, "%s\n", line ); */ + + /* Parse line */ + tagtemp = vcard_get_tagname( line, VCARD_SEP_TAG ); + if( tagtemp == NULL ) { + g_free( line ); + continue; + } + + /* fprintf( stdout, "\ttemp: %s\n", tagtemp ); */ + + tagvalue = vcard_get_tagvalue( line, VCARD_SEP_TAG ); + if( tagvalue == NULL ) { + g_free( tagtemp ); + g_free( line ); + continue; + } + + tagname = vcard_get_tagname( tagtemp, VCARD_SEP_TYPE ); + tagtype = vcard_get_tagvalue( tagtemp, VCARD_SEP_TYPE ); + if( tagname == NULL ) { + tagname = tagtemp; + tagtemp = NULL; + } + + /* fprintf( stdout, "\tname: %s\n", tagname ); */ + /* fprintf( stdout, "\ttype: %s\n", tagtype ); */ + /* fprintf( stdout, "\tvalue: %s\n", tagvalue ); */ + + if( g_strcasecmp( tagtype, VCARD_TYPE_QP ) == 0 ) { + /* Quoted-Printable: could span multiple lines */ + tagvalue = vcard_read_qp( cardFile, tagvalue ); + vcard_unescape_qp( tagvalue ); + /* fprintf( stdout, "QUOTED-PRINTABLE !!! final\n>%s<\n", tagvalue ); */ + } + + if( g_strcasecmp( tagname, VCARD_TAG_START ) == 0 && + g_strcasecmp( tagvalue, VCARD_NAME ) == 0 ) { + /* fprintf( stdout, "start card\n" ); */ + vcard_free_lists( listName, listAddress, listRemarks, listID ); + listName = listAddress = listRemarks = listID = NULL; + } + if( g_strcasecmp( tagname, VCARD_TAG_FULLNAME ) == 0 ) { + /* fprintf( stdout, "- full name: %s\n", tagvalue ); */ + listName = g_slist_append( listName, g_strdup( tagvalue ) ); + } + if( g_strcasecmp( tagname, VCARD_TAG_EMAIL ) == 0 ) { + /* fprintf( stdout, "- address: %s\n", tagvalue ); */ + listAddress = g_slist_append( listAddress, g_strdup( tagvalue ) ); + listRemarks = g_slist_append( listRemarks, g_strdup( tagtype ) ); + } + if( g_strcasecmp( tagname, VCARD_TAG_UID ) == 0 ) { + /* fprintf( stdout, "- id: %s\n", tagvalue ); */ + listID = g_slist_append( listID, g_strdup( tagvalue ) ); + } + if( g_strcasecmp( tagname, VCARD_TAG_END ) == 0 && + g_strcasecmp( tagvalue, VCARD_NAME ) == 0 ) { + /* vCard is complete */ + /* fprintf( stdout, "end card\n--\n" ); */ + /* vcard_dump_lists( listName, listAddress, listRemarks, listID, stdout ); */ + vcard_build_items( cardFile, listName, listAddress, listRemarks, listID ); + vcard_free_lists( listName, listAddress, listRemarks, listID ); + listName = listAddress = listRemarks = listID = NULL; + } + + g_free( tagname ); + g_free( tagtype ); + g_free( tagvalue ); + g_free( tagtemp ); + g_free( line ); + } + + /* Free lists */ + vcard_free_lists( listName, listAddress, listRemarks, listID ); + listName = listAddress = listRemarks = listID = NULL; +} + +/* ============================================================================================ */ +/* +* Read file into list. Main entry point +* Return: TRUE if file read successfully. +*/ +/* ============================================================================================ */ +gint vcard_read_data( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, -1 ); + + cardFile->retVal = MGU_SUCCESS; + cardFile->accessFlag = FALSE; + if( addrcache_check_file( cardFile->addressCache, cardFile->path ) ) { + addrcache_clear( cardFile->addressCache ); + vcard_open_file( cardFile ); + if( cardFile->retVal == MGU_SUCCESS ) { + /* Read data into the list */ + vcard_read_file( cardFile ); + vcard_close_file( cardFile ); + + /* Mark cache */ + addrcache_mark_file( cardFile->addressCache, cardFile->path ); + cardFile->addressCache->modified = FALSE; + cardFile->addressCache->dataRead = TRUE; + } + } + return cardFile->retVal; +} + +/* +* Return link list of persons. +*/ +GList *vcard_get_list_person( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, NULL ); + return addrcache_get_list_person( cardFile->addressCache ); +} + +/* +* Return link list of folders. This is always NULL since there are +* no folders in GnomeCard. +* Return: NULL. +*/ +GList *vcard_get_list_folder( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, NULL ); + return NULL; +} + +/* +* Return link list of all persons. Note that the list contains references +* to items. Do *NOT* attempt to use the addrbook_free_xxx() functions... +* this will destroy the addressbook data! +* Return: List of items, or NULL if none. +*/ +GList *vcard_get_all_persons( VCardFile *cardFile ) { + g_return_val_if_fail( cardFile != NULL, NULL ); + return addrcache_get_all_persons( cardFile->addressCache ); +} + +/* +* Validate that all parameters specified. +* Return: TRUE if data is good. +*/ +gboolean vcard_validate( const VCardFile *cardFile ) { + gboolean retVal; + + g_return_val_if_fail( cardFile != NULL, FALSE ); + + retVal = TRUE; + if( cardFile->path ) { + if( strlen( cardFile->path ) < 1 ) retVal = FALSE; + } + else { + retVal = FALSE; + } + if( cardFile->name ) { + if( strlen( cardFile->name ) < 1 ) retVal = FALSE; + } + else { + retVal = FALSE; + } + return retVal; +} + +#define WORK_BUFLEN 1024 + +/* +* Attempt to find a valid GnomeCard file. +* Return: Filename, or home directory if not found. Filename should +* be g_free() when done. +*/ +gchar *vcard_find_gnomecard( void ) { + const gchar *homedir; + gchar buf[ WORK_BUFLEN ]; + gchar str[ WORK_BUFLEN ]; + gchar *fileSpec; + gint len, lenlbl, i; + FILE *fp; + + homedir = g_get_home_dir(); + if( ! homedir ) return NULL; + + strcpy( str, homedir ); + len = strlen( str ); + if( len > 0 ) { + if( str[ len-1 ] != G_DIR_SEPARATOR ) { + str[ len ] = G_DIR_SEPARATOR; + str[ ++len ] = '\0'; + } + } + strcat( str, GNOMECARD_DIR ); + strcat( str, G_DIR_SEPARATOR_S ); + strcat( str, GNOMECARD_FILE ); + + fileSpec = NULL; + if( ( fp = fopen( str, "rb" ) ) != NULL ) { + /* Read configuration file */ + lenlbl = strlen( GNOMECARD_SECTION ); + while( fgets( buf, sizeof( buf ), fp ) != NULL ) { + if( 0 == g_strncasecmp( buf, GNOMECARD_SECTION, lenlbl ) ) { + break; + } + } + + while( fgets( buf, sizeof( buf ), fp ) != NULL ) { + g_strchomp( buf ); + if( buf[0] == '[' ) break; + for( i = 0; i < lenlbl; i++ ) { + if( buf[i] == '=' ) { + if( 0 == g_strncasecmp( buf, GNOMECARD_PARAM, i ) ) { + fileSpec = g_strdup( buf + i + 1 ); + g_strstrip( fileSpec ); + } + } + } + } + fclose( fp ); + } + + if( fileSpec == NULL ) { + /* Use the home directory */ + str[ len ] = '\0'; + fileSpec = g_strdup( str ); + } + + return fileSpec; +} + +/* +* Attempt to read file, testing for valid vCard format. +* Return: TRUE if file appears to be valid format. +*/ +gint vcard_test_read_file( const gchar *fileSpec ) { + gboolean haveStart; + gchar *tagtemp = NULL, *tagname = NULL, *tagvalue = NULL, *tagtype = NULL, *line; + VCardFile *cardFile; + gint retVal, lines; + + if( ! fileSpec ) return MGU_NO_FILE; + + cardFile = vcard_create_path( fileSpec ); + cardFile->retVal = MGU_SUCCESS; + vcard_open_file( cardFile ); + if( cardFile->retVal == MGU_SUCCESS ) { + cardFile->retVal = MGU_BAD_FORMAT; + haveStart = FALSE; + lines = VCARD_TEST_LINES; + while( lines > 0 ) { + lines--; + if( ( line = vcard_get_line( cardFile ) ) == NULL ) break; + + /* Parse line */ + tagtemp = vcard_get_tagname( line, VCARD_SEP_TAG ); + if( tagtemp == NULL ) { + g_free( line ); + continue; + } + + tagvalue = vcard_get_tagvalue( line, VCARD_SEP_TAG ); + if( tagvalue == NULL ) { + g_free( tagtemp ); + g_free( line ); + continue; + } + + tagname = vcard_get_tagname( tagtemp, VCARD_SEP_TYPE ); + tagtype = vcard_get_tagvalue( tagtemp, VCARD_SEP_TYPE ); + if( tagname == NULL ) { + tagname = tagtemp; + tagtemp = NULL; + } + + if( g_strcasecmp( tagtype, VCARD_TYPE_QP ) == 0 ) { + /* Quoted-Printable: could span multiple lines */ + tagvalue = vcard_read_qp( cardFile, tagvalue ); + vcard_unescape_qp( tagvalue ); + } + if( g_strcasecmp( tagname, VCARD_TAG_START ) == 0 && + g_strcasecmp( tagvalue, VCARD_NAME ) == 0 ) { + haveStart = TRUE; + } + if( g_strcasecmp( tagname, VCARD_TAG_END ) == 0 && + g_strcasecmp( tagvalue, VCARD_NAME ) == 0 ) { + /* vCard is complete */ + if( haveStart ) cardFile->retVal = MGU_SUCCESS; + } + + g_free( tagname ); + g_free( tagtype ); + g_free( tagvalue ); + g_free( tagtemp ); + g_free( line ); + } + vcard_close_file( cardFile ); + } + retVal = cardFile->retVal; + vcard_free( cardFile ); + cardFile = NULL; + return retVal; +} + +/* +* End of Source. +*/ diff --git a/src/vcard.h b/src/vcard.h new file mode 100644 index 00000000..889949fd --- /dev/null +++ b/src/vcard.h @@ -0,0 +1,105 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2001 Match Grun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * Definitions necessary to access vCard files. vCard files are used + * by GnomeCard for addressbook, and Netscape for sending business + * card information. Refer to RFC2426 for more information. + */ + +#ifndef __VCARD_H__ +#define __VCARD_H__ + +#include +#include + +#include "addritem.h" +#include "addrcache.h" + +#define VCARDBUFSIZE 1024 + +#define VCARD_TAG_START "begin" +#define VCARD_TAG_END "end" +#define VCARD_NAME "vcard" + +#define VCARD_TAG_FULLNAME "fn" +#define VCARD_TAG_NAME "n" +#define VCARD_TAG_EMAIL "email" +#define VCARD_TAG_UID "uid" + +#define VCARD_TYPE_QP "quoted-printable" + +#define VCARD_SEP_TAG ':' +#define VCARD_SEP_TYPE ';' + +/* +// Typical vCard entry: +// +// BEGIN:VCARD +// FN:Axle Rose +// N:Rose;Axle;D;Ms;Jnr +// REV:2001-04-22T03:52:05 +// ADR;HOME:;;777 Lexington Avenue;Denver;CO;80299;USA +// ADR;POSTAL:P O Box 777;;;Denver;CO;80298;Usa +// TEL;HOME:303-555-1234 +// EMAIL;AOL:axlerose@aol.com +// EMAIL;INTERNET:axlerose@netscape.net +// TITLE:Janitor +// ORG:The Company +// URL:http://www.axlerose.com +// END:VCARD +*/ + +/* vCard object */ +typedef struct _VCardFile VCardFile; +struct _VCardFile { + gchar *name; + FILE *file; + gchar *path; + gchar buffer[ VCARDBUFSIZE ]; + gchar *bufptr; + AddressCache *addressCache; + gint retVal; + gboolean accessFlag; +}; + +/* Function prototypes */ +VCardFile *vcard_create ( void ); +VCardFile *vcard_create_path ( const gchar *path ); +void vcard_set_name ( VCardFile* cardFile, const gchar *value ); +void vcard_set_file ( VCardFile* cardFile, const gchar *value ); +void vcard_set_modified ( VCardFile *vcardFile, const gboolean value ); +void vcard_set_accessed ( VCardFile *vcardFile, const gboolean value ); +gboolean vcard_get_modified ( VCardFile *vcardFile ); +gboolean vcard_get_accessed ( VCardFile *vcardFile ); +gboolean vcard_get_read_flag ( VCardFile *vcardFile ); +gint vcard_get_status ( VCardFile *cardFile ); +ItemFolder *vcard_get_root_folder ( VCardFile *cardFile ); +gchar *vcard_get_name ( VCardFile *cardFile ); +void vcard_free ( VCardFile *cardFile ); +void vcard_force_refresh ( VCardFile *cardFile ); +gint vcard_read_data ( VCardFile *cardFile ); +GList *vcard_get_list_person ( VCardFile *cardFile ); +GList *vcard_get_list_folder ( VCardFile *cardFile ); +GList *vcard_get_all_persons ( VCardFile *cardFile ); +gboolean vcard_validate ( const VCardFile *cardFile ); +gchar *vcard_find_gnomecard ( void ); +gint vcard_test_read_file ( const gchar *fileSpec ); + +#endif /* __VCARD_H__ */ diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 00000000..3e92ad2d --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,27 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2001 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __VERSION_H__ +#define __VERSION_H__ + +#define PACKAGE "@PACKAGE@" +#define VERSION "@VERSION@" +#define PROG_VERSION "Sylpheed version "VERSION + +#endif /* __VERSION_H__ */ diff --git a/src/xml.c b/src/xml.c new file mode 100644 index 00000000..fe90327a --- /dev/null +++ b/src/xml.c @@ -0,0 +1,619 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include +#include +#include +#include + +#include "xml.h" +#include "main.h" +#include "utils.h" +#include "codeconv.h" + +#define SPARSE_MEMORY +/* if this is defined all attr.names and tag.names are stored + * in a hash table */ +#if defined(SPARSE_MEMORY) +#include "stringtable.h" + +static StringTable *xml_string_table; + +static void xml_string_table_create(void) +{ + if (xml_string_table == NULL) + xml_string_table = string_table_new(); +} +#define XML_STRING_ADD(str) \ + string_table_insert_string(xml_string_table, (str)) +#define XML_STRING_FREE(str) \ + string_table_free_string(xml_string_table, (str)) + +#define XML_STRING_TABLE_CREATE() \ + xml_string_table_create() + +#else /* !SPARSE_MEMORY */ + +#define XML_STRING_ADD(str) \ + g_strdup(str) +#define XML_STRING_FREE(str) \ + g_free(str) + +#define XML_STRING_TABLE_CREATE() + +#endif /* SPARSE_MEMORY */ + +static void xml_free_tag (XMLTag *tag); +static gint xml_get_parenthesis (XMLFile *file, + gchar *buf, + gint len); + +XMLFile *xml_open_file(const gchar *path) +{ + XMLFile *newfile; + + g_return_val_if_fail(path != NULL, NULL); + + XML_STRING_TABLE_CREATE(); + + newfile = g_new(XMLFile, 1); + + newfile->fp = fopen(path, "rb"); + if (!newfile->fp) { + g_free(newfile); + return NULL; + } + + newfile->buf = g_string_new(NULL); + newfile->bufp = newfile->buf->str; + + newfile->dtd = NULL; + newfile->tag_stack = NULL; + newfile->level = 0; + newfile->is_empty_element = FALSE; + + return newfile; +} + +void xml_close_file(XMLFile *file) +{ + g_return_if_fail(file != NULL); + + if (file->fp) fclose(file->fp); + + g_string_free(file->buf, TRUE); + + g_free(file->dtd); + + while (file->tag_stack != NULL) + xml_pop_tag(file); + + g_free(file); +} + +static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level) +{ + GNode *node = NULL; + XMLNode *xmlnode; + XMLTag *tag; + + while (xml_parse_next_tag(file) == 0) { + if (file->level < level) break; + if (file->level == level) { + g_warning("xml_build_tree(): Parse error\n"); + break; + } + + tag = xml_get_current_tag(file); + if (!tag) break; + xmlnode = xml_node_new(xml_copy_tag(tag), NULL); + xmlnode->element = xml_get_element(file); + if (!parent) + node = g_node_new(xmlnode); + else + node = g_node_append_data(parent, xmlnode); + + xml_build_tree(file, node, file->level); + if (file->level == 0) break; + } + + return node; +} + +GNode *xml_parse_file(const gchar *path) +{ + XMLFile *file; + GNode *node; + + file = xml_open_file(path); + g_return_val_if_fail(file != NULL, NULL); + + xml_get_dtd(file); + + node = xml_build_tree(file, NULL, file->level); + + xml_close_file(file); + +#if defined(SPARSE_MEMORY) + if (debug_mode) + string_table_get_stats(xml_string_table); +#endif + + return node; +} + +gint xml_get_dtd(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gchar *bufp = buf; + + if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1; + + if ((*bufp++ == '?') && + (bufp = strcasestr(bufp, "xml")) && + (bufp = strcasestr(bufp + 3, "version")) && + (bufp = strchr(bufp + 7, '?'))) + file->dtd = g_strdup(buf); + else { + g_warning("Can't get xml dtd\n"); + return -1; + } + + return 0; +} + +gint xml_parse_next_tag(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + guchar *bufp = buf; + XMLTag *tag; + gint len; + + if (file->is_empty_element == TRUE) { + file->is_empty_element = FALSE; + xml_pop_tag(file); + return 0; + } + + if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) { + g_warning("xml_parse_next_tag(): Can't parse next tag\n"); + return -1; + } + + /* end-tag */ + if (buf[0] == '/') { + if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) { + g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf); + return -1; + } + xml_pop_tag(file); + return 0; + } + + tag = xml_tag_new(NULL); + xml_push_tag(file, tag); + + len = strlen(buf); + if (len > 0 && buf[len - 1] == '/') { + file->is_empty_element = TRUE; + buf[len - 1] = '\0'; + g_strchomp(buf); + } + if (strlen(buf) == 0) { + g_warning("xml_parse_next_tag(): Tag name is empty\n"); + return -1; + } + + while (*bufp != '\0' && !isspace(*bufp)) bufp++; + if (*bufp == '\0') { + tag->tag = XML_STRING_ADD(buf); + return 0; + } else { + *bufp++ = '\0'; + tag->tag = XML_STRING_ADD(buf); + } + + /* parse attributes ( name=value ) */ + while (*bufp) { + XMLAttr *attr; + gchar *attr_name; + gchar *attr_value; + gchar *p; + gchar quote; + + while (isspace(*bufp)) bufp++; + attr_name = bufp; + if ((p = strchr(attr_name, '=')) == NULL) { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + bufp = p; + *bufp++ = '\0'; + while (isspace(*bufp)) bufp++; + + if (*bufp != '"' && *bufp != '\'') { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + quote = *bufp; + bufp++; + attr_value = bufp; + if ((p = strchr(attr_value, quote)) == NULL) { + g_warning("xml_parse_next_tag(): Syntax error in tag\n"); + return -1; + } + bufp = p; + *bufp++ = '\0'; + + g_strchomp(attr_name); + xml_unescape_str(attr_value); + + attr = xml_attr_new(attr_name, attr_value); + xml_tag_add_attr(tag, attr); + } + + return 0; +} + +void xml_push_tag(XMLFile *file, XMLTag *tag) +{ + g_return_if_fail(tag != NULL); + + file->tag_stack = g_list_prepend(file->tag_stack, tag); + file->level++; +} + +void xml_pop_tag(XMLFile *file) +{ + XMLTag *tag; + + if (!file->tag_stack) return; + + tag = (XMLTag *)file->tag_stack->data; + + xml_free_tag(tag); + file->tag_stack = g_list_remove(file->tag_stack, tag); + file->level--; +} + +XMLTag *xml_get_current_tag(XMLFile *file) +{ + if (file->tag_stack) + return (XMLTag *)file->tag_stack->data; + else + return NULL; +} + +GList *xml_get_current_tag_attr(XMLFile *file) +{ + XMLTag *tag; + + tag = xml_get_current_tag(file); + if (!tag) return NULL; + + return tag->attr; +} + +gchar *xml_get_element(XMLFile *file) +{ + gchar *str; + gchar *end; + + while ((end = strchr(file->bufp, '<')) == NULL) + if (xml_read_line(file) < 0) return NULL; + + if (end == file->bufp) + return NULL; + + str = g_strndup(file->bufp, end - file->bufp); + /* this is not XML1.0 strict */ + g_strstrip(str); + xml_unescape_str(str); + + file->bufp = end; + xml_truncate_buf(file); + + if (str[0] == '\0') { + g_free(str); + return NULL; + } + + return str; +} + +gint xml_read_line(XMLFile *file) +{ + gchar buf[XMLBUFSIZE]; + gint index; + + if (fgets(buf, sizeof(buf), file->fp) == NULL) + return -1; + + index = file->bufp - file->buf->str; + + g_string_append(file->buf, buf); + + file->bufp = file->buf->str + index; + + return 0; +} + +void xml_truncate_buf(XMLFile *file) +{ + gint len; + + len = file->bufp - file->buf->str; + if (len > 0) { + g_string_erase(file->buf, 0, len); + file->bufp = file->buf->str; + } +} + +gboolean xml_compare_tag(XMLFile *file, const gchar *name) +{ + XMLTag *tag; + + tag = xml_get_current_tag(file); + + if (tag && strcmp(tag->tag, name) == 0) + return TRUE; + else + return FALSE; +} + +XMLNode *xml_node_new(XMLTag *tag, const gchar *text) +{ + XMLNode *node; + + node = g_new(XMLNode, 1); + node->tag = tag; + node->element = g_strdup(text); + + return node; +} + +XMLTag *xml_tag_new(const gchar *tag) +{ + XMLTag *new_tag; + + new_tag = g_new(XMLTag, 1); + if (tag) + new_tag->tag = XML_STRING_ADD(tag); + else + new_tag->tag = NULL; + new_tag->attr = NULL; + + return new_tag; +} + +XMLAttr *xml_attr_new(const gchar *name, const gchar *value) +{ + XMLAttr *new_attr; + + new_attr = g_new(XMLAttr, 1); + new_attr->name = XML_STRING_ADD(name); + new_attr->value = g_strdup(value); + + return new_attr; +} + +void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr) +{ + tag->attr = g_list_append(tag->attr, attr); +} + +XMLTag *xml_copy_tag(XMLTag *tag) +{ + XMLTag *new_tag; + XMLAttr *attr; + GList *list; + + new_tag = xml_tag_new(tag->tag); + for (list = tag->attr; list != NULL; list = list->next) { + attr = xml_copy_attr((XMLAttr *)list->data); + xml_tag_add_attr(new_tag, attr); + } + + return new_tag; +} + +XMLAttr *xml_copy_attr(XMLAttr *attr) +{ + return xml_attr_new(attr->name, attr->value); +} + +gint xml_unescape_str(gchar *str) +{ + gchar *start; + gchar *end; + gchar *p = str; + gchar *esc_str; + gchar ch; + gint len; + + while ((start = strchr(p, '&')) != NULL) { + if ((end = strchr(start + 1, ';')) == NULL) { + g_warning("Unescaped `&' appeared\n"); + p = start + 1; + continue; + } + len = end - start + 1; + if (len < 3) { + p = end + 1; + continue; + } + + Xstrndup_a(esc_str, start, len, return -1); + if (!strcmp(esc_str, "<")) + ch = '<'; + else if (!strcmp(esc_str, ">")) + ch = '>'; + else if (!strcmp(esc_str, "&")) + ch = '&'; + else if (!strcmp(esc_str, "'")) + ch = '\''; + else if (!strcmp(esc_str, """)) + ch = '\"'; + else { + p = end + 1; + continue; + } + + *start = ch; + memmove(start + 1, end + 1, strlen(end + 1) + 1); + p = start + 1; + } + + return 0; +} + +gint xml_file_put_escape_str(FILE *fp, const gchar *str) +{ + const gchar *p; + + g_return_val_if_fail(fp != NULL, -1); + + if (!str) return 0; + + for (p = str; *p != '\0'; p++) { + switch (*p) { + case '<': + fputs("<", fp); + break; + case '>': + fputs(">", fp); + break; + case '&': + fputs("&", fp); + break; + case '\'': + fputs("'", fp); + break; + case '\"': + fputs(""", fp); + break; + default: + fputc(*p, fp); + } + } + + return 0; +} + +gint xml_file_put_xml_decl(FILE *fp) +{ + g_return_val_if_fail(fp != NULL, -1); + + fprintf(fp, "\n", + conv_get_internal_charset_str()); + return 0; +} + +gint xml_file_put_node(FILE *fp, XMLNode *node) +{ + GList *cur; + + g_return_val_if_fail(fp != NULL, -1); + g_return_val_if_fail(node != NULL, -1); + + fprintf(fp, "<%s", node->tag->tag); + + for (cur = node->tag->attr; cur != NULL; cur = cur->next) { + XMLAttr *attr = (XMLAttr *)cur->data; + fprintf(fp, " %s=\"", attr->name); + xml_file_put_escape_str(fp, attr->value); + fputs("\"", fp); + } + + if (node->element) { + fputs(">", fp); + xml_file_put_escape_str(fp, node->element); + fprintf(fp, "\n", node->tag->tag); + } else { + fputs(" />\n", fp); + } + + return 0; +} + +void xml_free_node(XMLNode *node) +{ + if (!node) return; + + xml_free_tag(node->tag); + g_free(node->element); + g_free(node); +} + +static gboolean xml_free_func(GNode *node, gpointer data) +{ + XMLNode *xmlnode = node->data; + + xml_free_node(xmlnode); + return FALSE; +} + +void xml_free_tree(GNode *node) +{ + g_return_if_fail(node != NULL); + + g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func, + NULL); + + g_node_destroy(node); +} + +static void xml_free_tag(XMLTag *tag) +{ + if (!tag) return; + + XML_STRING_FREE(tag->tag); + while (tag->attr != NULL) { + XMLAttr *attr = (XMLAttr *)tag->attr->data; + XML_STRING_FREE(attr->name); + g_free(attr->value); + g_free(attr); + tag->attr = g_list_remove(tag->attr, tag->attr->data); + } + g_free(tag); +} + +static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len) +{ + gchar *start; + gchar *end; + + buf[0] = '\0'; + + while ((start = strchr(file->bufp, '<')) == NULL) + if (xml_read_line(file) < 0) return -1; + + start++; + file->bufp = start; + + while ((end = strchr(file->bufp, '>')) == NULL) + if (xml_read_line(file) < 0) return -1; + + strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len)); + g_strstrip(buf); + file->bufp = end + 1; + xml_truncate_buf(file); + + return 0; +} diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 00000000..ddb8b2da --- /dev/null +++ b/src/xml.h @@ -0,0 +1,106 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __XML_H__ +#define __XML_H__ + +#include +#include + +#define XMLBUFSIZE 8192 + +typedef struct _XMLAttr XMLAttr; +typedef struct _XMLTag XMLTag; +typedef struct _XMLNode XMLNode; +typedef struct _XMLFile XMLFile; + +struct _XMLAttr +{ + gchar *name; + gchar *value; +}; + +struct _XMLTag +{ + gchar *tag; + GList *attr; +}; + +struct _XMLNode +{ + XMLTag *tag; + gchar *element; +}; + +struct _XMLFile +{ + FILE *fp; + + GString *buf; + gchar *bufp; + + gchar *dtd; + GList *tag_stack; + guint level; + + gboolean is_empty_element; +}; + +XMLFile *xml_open_file (const gchar *path); +void xml_close_file (XMLFile *file); +GNode *xml_parse_file (const gchar *path); + +gint xml_get_dtd (XMLFile *file); +gint xml_parse_next_tag (XMLFile *file); +void xml_push_tag (XMLFile *file, + XMLTag *tag); +void xml_pop_tag (XMLFile *file); + +XMLTag *xml_get_current_tag (XMLFile *file); +GList *xml_get_current_tag_attr(XMLFile *file); +gchar *xml_get_element (XMLFile *file); + +gint xml_read_line (XMLFile *file); +void xml_truncate_buf (XMLFile *file); +gboolean xml_compare_tag (XMLFile *file, + const gchar *name); + +XMLNode *xml_node_new (XMLTag *tag, + const gchar *text); +XMLTag *xml_tag_new (const gchar *tag); +XMLAttr *xml_attr_new (const gchar *name, + const gchar *value); +void xml_tag_add_attr (XMLTag *tag, + XMLAttr *attr); + +XMLTag *xml_copy_tag (XMLTag *tag); +XMLAttr *xml_copy_attr (XMLAttr *attr); + +gint xml_unescape_str (gchar *str); +gint xml_file_put_escape_str (FILE *fp, + const gchar *str); + +gint xml_file_put_xml_decl (FILE *fp); +gint xml_file_put_node (FILE *fp, + XMLNode *node); + +void xml_free_node (XMLNode *node); +void xml_free_tree (GNode *node); + +#endif /* __XML_H__ */ -- cgit v1.2.3