aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhiro <hiro@ee746299-78ed-0310-b773-934348b2243d>2005-01-12 11:22:08 +0000
committerhiro <hiro@ee746299-78ed-0310-b773-934348b2243d>2005-01-12 11:22:08 +0000
commitb9ca7b1ef5cd1f96ae6e28ae78d12c1e3258c23f (patch)
tree1203adec5f70af1ddd49868528d8d3a5b9004329 /src
Initial import of Sylpheed (GTK2 version).
git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@1 ee746299-78ed-0310-b773-934348b2243d
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am212
-rw-r--r--src/about.c251
-rw-r--r--src/about.h25
-rw-r--r--src/account.c990
-rw-r--r--src/account.h64
-rw-r--r--src/action.c1269
-rw-r--r--src/action.h56
-rw-r--r--src/addr_compl.c954
-rw-r--r--src/addr_compl.h54
-rw-r--r--src/addrbook.c2010
-rw-r--r--src/addrbook.h114
-rw-r--r--src/addrcache.c1232
-rw-r--r--src/addrcache.h123
-rw-r--r--src/addressadd.c401
-rw-r--r--src/addressadd.h31
-rw-r--r--src/addressbook.c3503
-rw-r--r--src/addressbook.h49
-rw-r--r--src/addressitem.h158
-rw-r--r--src/addrindex.c1892
-rw-r--r--src/addrindex.h130
-rw-r--r--src/addritem.c989
-rw-r--r--src/addritem.h174
-rw-r--r--src/alertpanel.c353
-rw-r--r--src/alertpanel.h67
-rw-r--r--src/base64.c168
-rw-r--r--src/base64.h46
-rw-r--r--src/codeconv.c1769
-rw-r--r--src/codeconv.h238
-rw-r--r--src/colorlabel.c338
-rw-r--r--src/colorlabel.h34
-rw-r--r--src/compose.c6204
-rw-r--r--src/compose.h214
-rw-r--r--src/customheader.c100
-rw-r--r--src/customheader.h40
-rw-r--r--src/defs.h110
-rw-r--r--src/displayheader.c59
-rw-r--r--src/displayheader.h37
-rw-r--r--src/editaddress.c1198
-rw-r--r--src/editaddress.h29
-rw-r--r--src/editbook.c348
-rw-r--r--src/editbook.h29
-rw-r--r--src/editgroup.c540
-rw-r--r--src/editgroup.h26
-rw-r--r--src/editjpilot.c447
-rw-r--r--src/editjpilot.h33
-rw-r--r--src/editldap.c600
-rw-r--r--src/editldap.h29
-rw-r--r--src/editldap_basedn.c332
-rw-r--r--src/editldap_basedn.h30
-rw-r--r--src/editvcard.c329
-rw-r--r--src/editvcard.h29
-rw-r--r--src/export.c263
-rw-r--r--src/export.h29
-rw-r--r--src/filesel.c145
-rw-r--r--src/filesel.h27
-rw-r--r--src/filter.c1218
-rw-r--r--src/filter.h194
-rw-r--r--src/folder.c1529
-rw-r--r--src/folder.h386
-rw-r--r--src/foldersel.c493
-rw-r--r--src/foldersel.h39
-rw-r--r--src/folderview.c2378
-rw-r--r--src/folderview.h96
-rw-r--r--src/grouplistdialog.c567
-rw-r--r--src/grouplistdialog.h29
-rw-r--r--src/gtksctree.c573
-rw-r--r--src/gtksctree.h66
-rw-r--r--src/gtkshruler.c218
-rw-r--r--src/gtkshruler.h63
-rw-r--r--src/gtkutils.c686
-rw-r--r--src/gtkutils.h166
-rw-r--r--src/headerview.c348
-rw-r--r--src/headerview.h55
-rw-r--r--src/html.c777
-rw-r--r--src/html.h87
-rw-r--r--src/imageview.c251
-rw-r--r--src/imageview.h52
-rw-r--r--src/imap.c3976
-rw-r--r--src/imap.h114
-rw-r--r--src/import.c268
-rw-r--r--src/import.h29
-rw-r--r--src/importldif.c846
-rw-r--r--src/importldif.h34
-rw-r--r--src/inc.c1337
-rw-r--r--src/inc.h103
-rw-r--r--src/inputdialog.c332
-rw-r--r--src/inputdialog.h39
-rw-r--r--src/intl.h22
-rw-r--r--src/jpilot.c1732
-rw-r--r--src/jpilot.h116
-rw-r--r--src/ldif.c933
-rw-r--r--src/ldif.h119
-rw-r--r--src/logwindow.c221
-rw-r--r--src/logwindow.h55
-rw-r--r--src/main.c721
-rw-r--r--src/main.h32
-rw-r--r--src/mainwindow.c3174
-rw-r--r--src/mainwindow.h177
-rw-r--r--src/manage_window.c92
-rw-r--r--src/manage_window.h58
-rw-r--r--src/manual.c85
-rw-r--r--src/manual.h36
-rw-r--r--src/mbox.c455
-rw-r--r--src/mbox.h47
-rw-r--r--src/md5.c433
-rw-r--r--src/md5.h49
-rw-r--r--src/menu.c262
-rw-r--r--src/menu.h90
-rw-r--r--src/message_search.c226
-rw-r--r--src/message_search.h29
-rw-r--r--src/messageview.c877
-rw-r--r--src/messageview.h96
-rw-r--r--src/mgutils.c220
-rw-r--r--src/mgutils.h62
-rw-r--r--src/mh.c1282
-rw-r--r--src/mh.h38
-rw-r--r--src/mimeview.c998
-rw-r--r--src/mimeview.h81
-rw-r--r--src/news.c1056
-rw-r--r--src/news.h59
-rw-r--r--src/nntp.c431
-rw-r--r--src/nntp.h109
-rw-r--r--src/passphrase.c342
-rw-r--r--src/passphrase.h34
-rw-r--r--src/pixmaps/address.xpm23
-rw-r--r--src/pixmaps/book.xpm23
-rw-r--r--src/pixmaps/category.xpm35
-rw-r--r--src/pixmaps/checkbox_off.xpm20
-rw-r--r--src/pixmaps/checkbox_on.xpm20
-rw-r--r--src/pixmaps/clip.xpm17
-rw-r--r--src/pixmaps/complete.xpm17
-rw-r--r--src/pixmaps/continue.xpm42
-rw-r--r--src/pixmaps/deleted.xpm15
-rw-r--r--src/pixmaps/dir-close.xpm100
-rw-r--r--src/pixmaps/dir-noselect.xpm94
-rw-r--r--src/pixmaps/dir-open.xpm83
-rw-r--r--src/pixmaps/error.xpm45
-rw-r--r--src/pixmaps/forwarded.xpm23
-rw-r--r--src/pixmaps/group.xpm97
-rw-r--r--src/pixmaps/inbox.xpm25
-rw-r--r--src/pixmaps/interface.xpm24
-rw-r--r--src/pixmaps/jpilot.xpm25
-rw-r--r--src/pixmaps/ldap.xpm25
-rw-r--r--src/pixmaps/linewrap.xpm29
-rw-r--r--src/pixmaps/mail.xpm41
-rw-r--r--src/pixmaps/mark.xpm16
-rw-r--r--src/pixmaps/new.xpm62
-rw-r--r--src/pixmaps/offline.xpm228
-rw-r--r--src/pixmaps/online.xpm232
-rw-r--r--src/pixmaps/outbox.xpm27
-rw-r--r--src/pixmaps/replied.xpm24
-rw-r--r--src/pixmaps/stock_add_16.xpm35
-rw-r--r--src/pixmaps/stock_close.xpm29
-rw-r--r--src/pixmaps/stock_dialog_error_48.xpm115
-rw-r--r--src/pixmaps/stock_dialog_info_48.xpm115
-rw-r--r--src/pixmaps/stock_dialog_question_48.xpm115
-rw-r--r--src/pixmaps/stock_dialog_warning_48.xpm83
-rw-r--r--src/pixmaps/stock_down_arrow.xpm100
-rw-r--r--src/pixmaps/stock_exec.xpm107
-rw-r--r--src/pixmaps/stock_mail.xpm143
-rw-r--r--src/pixmaps/stock_mail_attach.xpm134
-rw-r--r--src/pixmaps/stock_mail_compose.xpm144
-rw-r--r--src/pixmaps/stock_mail_forward.xpm153
-rw-r--r--src/pixmaps/stock_mail_receive.xpm175
-rw-r--r--src/pixmaps/stock_mail_receive_all.xpm181
-rw-r--r--src/pixmaps/stock_mail_reply.xpm154
-rw-r--r--src/pixmaps/stock_mail_reply_to_all.xpm126
-rw-r--r--src/pixmaps/stock_mail_send.xpm162
-rw-r--r--src/pixmaps/stock_mail_send_queue.xpm244
-rw-r--r--src/pixmaps/stock_paste.xpm132
-rw-r--r--src/pixmaps/stock_preferences.xpm80
-rw-r--r--src/pixmaps/stock_properties.xpm140
-rw-r--r--src/pixmaps/stock_remove_16.xpm28
-rw-r--r--src/pixmaps/stock_search.xpm155
-rw-r--r--src/pixmaps/stock_trash.xpm112
-rw-r--r--src/pixmaps/stock_up_arrow.xpm100
-rw-r--r--src/pixmaps/sylpheed-logo.xpm53
-rw-r--r--src/pixmaps/tb_address_book.xpm56
-rw-r--r--src/pixmaps/trash.xpm29
-rw-r--r--src/pixmaps/unread.xpm50
-rw-r--r--src/pixmaps/vcard.xpm25
-rw-r--r--src/pop.c862
-rw-r--r--src/pop.h153
-rw-r--r--src/prefs.c817
-rw-r--r--src/prefs.h169
-rw-r--r--src/prefs_account.c2295
-rw-r--r--src/prefs_account.h175
-rw-r--r--src/prefs_actions.c666
-rw-r--r--src/prefs_actions.h29
-rw-r--r--src/prefs_common.c3545
-rw-r--r--src/prefs_common.h246
-rw-r--r--src/prefs_customheader.c623
-rw-r--r--src/prefs_customheader.h29
-rw-r--r--src/prefs_display_header.c631
-rw-r--r--src/prefs_display_header.h27
-rw-r--r--src/prefs_filter.c841
-rw-r--r--src/prefs_filter.h58
-rw-r--r--src/prefs_filter_edit.c2035
-rw-r--r--src/prefs_filter_edit.h28
-rw-r--r--src/prefs_folder_item.c579
-rw-r--r--src/prefs_folder_item.h29
-rw-r--r--src/prefs_summary_column.c537
-rw-r--r--src/prefs_summary_column.h30
-rw-r--r--src/prefs_template.c533
-rw-r--r--src/prefs_template.h25
-rw-r--r--src/procheader.c764
-rw-r--r--src/procheader.h91
-rw-r--r--src/procmime.c1128
-rw-r--r--src/procmime.h176
-rw-r--r--src/procmsg.c1519
-rw-r--r--src/procmsg.h280
-rw-r--r--src/progressdialog.c135
-rw-r--r--src/progressdialog.h46
-rw-r--r--src/quote_fmt.h13
-rw-r--r--src/quote_fmt_lex.h47
-rw-r--r--src/quote_fmt_lex.l46
-rw-r--r--src/quote_fmt_parse.y459
-rw-r--r--src/quoted-printable.c231
-rw-r--r--src/quoted-printable.h36
-rw-r--r--src/recv.c227
-rw-r--r--src/recv.h46
-rw-r--r--src/rfc2015.c1395
-rw-r--r--src/rfc2015.h48
-rw-r--r--src/select-keys.c525
-rw-r--r--src/select-keys.h29
-rw-r--r--src/send_message.c620
-rw-r--r--src/send_message.h46
-rw-r--r--src/session.c734
-rw-r--r--src/session.h200
-rw-r--r--src/setup.c95
-rw-r--r--src/setup.h29
-rw-r--r--src/sigstatus.c244
-rw-r--r--src/sigstatus.h33
-rw-r--r--src/simple-gettext.c386
-rw-r--r--src/smtp.c565
-rw-r--r--src/smtp.h115
-rw-r--r--src/socket.c1293
-rw-r--r--src/socket.h117
-rw-r--r--src/sourcewindow.c183
-rw-r--r--src/sourcewindow.h45
-rw-r--r--src/ssl.c146
-rw-r--r--src/ssl.h58
-rw-r--r--src/statusbar.c125
-rw-r--r--src/statusbar.h38
-rw-r--r--src/stock_pixmap.c185
-rw-r--r--src/stock_pixmap.h93
-rw-r--r--src/stringtable.c163
-rw-r--r--src/stringtable.h38
-rw-r--r--src/summary_search.c474
-rw-r--r--src/summary_search.h29
-rw-r--r--src/summaryview.c4195
-rw-r--r--src/summaryview.h239
-rw-r--r--src/syldap.c1129
-rw-r--r--src/syldap.h111
-rw-r--r--src/sylpheed-marshal.list2
-rw-r--r--src/template.c219
-rw-r--r--src/template.h45
-rw-r--r--src/textview.c1664
-rw-r--r--src/textview.h90
-rw-r--r--src/undo.c646
-rw-r--r--src/undo.h80
-rw-r--r--src/unmime.c133
-rw-r--r--src/unmime.h29
-rw-r--r--src/utils.c3189
-rw-r--r--src/utils.h428
-rw-r--r--src/uuencode.c101
-rw-r--r--src/uuencode.h24
-rw-r--r--src/vcard.c777
-rw-r--r--src/vcard.h105
-rw-r--r--src/version.h.in27
-rw-r--r--src/xml.c619
-rw-r--r--src/xml.h106
272 files changed, 108021 insertions, 0 deletions
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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkhseparator.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtktextview.h>
+#include <gtk/gtkbutton.h>
+#if HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#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 <hiro-y@kcn.ne.jp>");
+ 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 <dd9jn@gnu.org>\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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <errno.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#ifdef GDK_WINDOWING_X11
+# include <gdk/gdkx.h>
+#endif /* GDK_WINDOWING_X11 */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+
+#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, "<Branch>"};
+
+ 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 <glib.h>
+#include <gtk/gtkitemfactory.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkscrolledwindow.h>
+
+#include <string.h>
+#include <ctype.h>
+#if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
+# include <wchar.h>
+# include <wctype.h>
+#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 <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <setjmp.h>
+
+#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( "</", fp );
+ fputs( name, 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( "<?xml version=\"1.0\" ?>\n", fp );
+#else
+ pfile = prefs_file_open( fileSpec );
+ g_free( fileSpec );
+ if( pfile ) {
+ fp = pfile->fp;
+ fprintf( fp, "<?xml version=\"1.0\" encoding=\"%s\" ?>\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 <stdio.h>
+#include <glib.h>
+#include <setjmp.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/* #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 <time.h>
+#include <stdio.h>
+#include <glib.h>
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkhpaned.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkitemfactory.h>
+#include <string.h>
+#include <setjmp.h>
+
+#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 <pthread.h>
+#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, "<Branch>"},
+ {N_("/_File/New _Book"), "<alt>B", addressbook_new_book_cb, 0, NULL},
+ {N_("/_File/New _vCard"), "<alt>D", addressbook_new_vcard_cb, 0, NULL},
+#ifdef USE_JPILOT
+ {N_("/_File/New _JPilot"), "<alt>J", addressbook_new_jpilot_cb, 0, NULL},
+#endif
+#ifdef USE_LDAP
+ {N_("/_File/New _Server"), "<alt>S", addressbook_new_ldap_cb, 0, NULL},
+#endif
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/_File/_Save"), "<alt>S", addressbook_file_save_cb, 0, NULL},
+ {N_("/_File/_Close"), "<alt>W", close_cb, 0, NULL},
+ {N_("/_Address"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Address/New _Address"), "<alt>N", addressbook_new_address_cb, 0, NULL},
+ {N_("/_Address/New _Group"), "<alt>G", addressbook_new_group_cb, 0, NULL},
+ {N_("/_Address/New _Folder"), "<alt>R", addressbook_new_folder_cb, 0, NULL},
+ {N_("/_Address/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Address/_Edit"), "<alt>Return", addressbook_edit_address_cb, 0, NULL},
+ {N_("/_Address/_Delete"), NULL, addressbook_delete_address_cb, 0, NULL},
+ {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/Import _LDIF file"), NULL, addressbook_import_ldif_cb, 0, NULL},
+ {N_("/_Help"), NULL, NULL, 0, "<LastBranch>"},
+ {N_("/_Help/_About"), NULL, about_show, 0, NULL}
+};
+
+/* New options to be added. */
+/*
+ {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Edit/C_ut"), "<ctl>X", NULL, 0, NULL},
+ {N_("/_Edit/_Copy"), "<ctl>C", NULL, 0, NULL},
+ {N_("/_Edit/_Paste"), "<ctl>V", NULL, 0, NULL},
+ {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/Import _Mozilla"), NULL, NULL, 0, NULL},
+ {N_("/_Tools/Import _vCard"), NULL, NULL, 0, NULL},
+ {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {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,
+ "<AddressBook>", 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,
+ "<AddressBookTree>", &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,
+ "<AddressBookList>", &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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkctree.h>
+
+#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 <glib.h>
+#include <stdlib.h>
+
+#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( "</", fp );
+ fputs( name, 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( "<?xml version=\"1.0\" ?>\n", fp );
+#else
+ pfile = prefs_file_open( fileSpec );
+ g_free( fileSpec );
+ if( pfile ) {
+ fp = pfile->fp;
+ fprintf( fp, "<?xml version=\"1.0\" encoding=\"%s\" ?>\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 <stdio.h>
+#include <glib.h>
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <stdio.h>
+#include <glib.h>
+
+#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 <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <glib.h>
+#include <gtk/gtkwindow.h>
+
+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 <glib.h>
+#include <ctype.h>
+#include <string.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#if HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+#if HAVE_ICONV
+# include <iconv.h>
+#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 <glib.h>
+
+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 <glib.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkpixmap.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkalignment.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkdrawingarea.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkoptionmenu.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkhandlebox.h>
+#include <gtk/gtktoolbar.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtktreeview.h>
+#include <gtk/gtkdnd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+/* #include <sys/utsname.h> */
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+
+#if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
+# include <wchar.h>
+# include <wctype.h>
+#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, "<Separator>"},
+ {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
+};
+
+static GtkItemFactoryEntry compose_entries[] =
+{
+ {N_("/_File"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_File/_Send"), "<control>Return",
+ compose_send_cb, 0, NULL},
+ {N_("/_File/Send _later"), "<shift><control>S",
+ compose_send_later_cb, 0, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_File/Save to _draft folder"),
+ "<shift><control>D", compose_draft_cb, 0, NULL},
+ {N_("/_File/Save and _keep editing"),
+ "<control>S", compose_draft_cb, 1, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_File/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
+ {N_("/_File/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
+ {N_("/_File/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_File/_Close"), "<control>W", compose_close_cb, 0, NULL},
+
+ {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
+ {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
+ {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
+ {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
+ {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
+ {N_("/_Edit/Paste as _quotation"),
+ NULL, compose_paste_as_quote_cb, 0, NULL},
+ {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
+ {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Edit/A_dvanced/Move a character backward"),
+ "<control>B",
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_BACKWARD_CHARACTER,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move a character forward"),
+ "<control>F",
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_FORWARD_CHARACTER,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move a word backward"),
+ NULL, /* "<alt>B" */
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_BACKWARD_WORD,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move a word forward"),
+ NULL, /* "<alt>F" */
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_FORWARD_WORD,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move to beginning of line"),
+ NULL, /* "<control>A" */
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_BEGINNING_OF_LINE,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move to end of line"),
+ "<control>E",
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_END_OF_LINE,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move to previous line"),
+ "<control>P",
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_PREVIOUS_LINE,
+ NULL},
+ {N_("/_Edit/A_dvanced/Move to next line"),
+ "<control>N",
+ compose_action_cb,
+ COMPOSE_ACTION_MOVE_NEXT_LINE,
+ NULL},
+ {N_("/_Edit/A_dvanced/Delete a character backward"),
+ "<control>H",
+ compose_action_cb,
+ COMPOSE_ACTION_DELETE_BACKWARD_CHARACTER,
+ NULL},
+ {N_("/_Edit/A_dvanced/Delete a character forward"),
+ "<control>D",
+ compose_action_cb,
+ COMPOSE_ACTION_DELETE_FORWARD_CHARACTER,
+ NULL},
+ {N_("/_Edit/A_dvanced/Delete a word backward"),
+ NULL, /* "<control>W" */
+ compose_action_cb,
+ COMPOSE_ACTION_DELETE_BACKWARD_WORD,
+ NULL},
+ {N_("/_Edit/A_dvanced/Delete a word forward"),
+ NULL, /* "<alt>D", */
+ compose_action_cb,
+ COMPOSE_ACTION_DELETE_FORWARD_WORD,
+ NULL},
+ {N_("/_Edit/A_dvanced/Delete line"),
+ "<control>U",
+ compose_action_cb,
+ COMPOSE_ACTION_DELETE_LINE,
+ NULL},
+ {N_("/_Edit/A_dvanced/Delete to end of line"),
+ "<control>K",
+ compose_action_cb,
+ COMPOSE_ACTION_DELETE_TO_LINE_END,
+ NULL},
+ {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Edit/_Wrap current paragraph"),
+ "<control>L", compose_wrap_line, 0, NULL},
+ {N_("/_Edit/Wrap all long _lines"),
+ "<control><alt>L", compose_wrap_line_all, 0, NULL},
+ {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
+ {N_("/_View"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/_To"), NULL, compose_toggle_to_cb , 0, "<ToggleItem>"},
+ {N_("/_View/_Cc"), NULL, compose_toggle_cc_cb , 0, "<ToggleItem>"},
+ {N_("/_View/_Bcc"), NULL, compose_toggle_bcc_cb , 0, "<ToggleItem>"},
+ {N_("/_View/_Reply to"), NULL, compose_toggle_replyto_cb, 0, "<ToggleItem>"},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/_Followup to"), NULL, compose_toggle_followupto_cb, 0, "<ToggleItem>"},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/R_uler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/_Attachment"), NULL, compose_toggle_attach_cb, 0, "<ToggleItem>"},
+
+ {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
+ {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Tools/Edit with e_xternal editor"),
+ "<shift><control>X", compose_ext_editor_cb, 0, NULL},
+#if USE_GPGME
+ {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Tools/PGP Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
+ {N_("/_Tools/PGP _Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
+#endif /* USE_GPGME */
+
+ {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
+ {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 <CR> if DOS/Windows file,
+ replace <CR> with <LF> 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>", 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,
+ "<Compose>", &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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkitemfactory.h>
+
+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 <glib.h>
+#include <string.h>
+#include <stdlib.h>
+
+#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 <glib.h>
+
+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 <paths.h>
+#endif
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#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 <glib.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktable.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkfilesel.h>
+#include <gtk/gtksignal.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkfilesel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <time.h>
+
+#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("<rule> 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("<rule> 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<filter>\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(" <rule name=\"", pfile->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, " <condition-list bool=\"%s\">\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(" </condition-list>\n", pfile->fp);
+
+ fputs(" <action-list>\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(" </action-list>\n", pfile->fp);
+
+ fputs(" </rule>\n", pfile->fp);
+ }
+
+ fputs("</filter>\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 <glib.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#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, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
+ conv_get_internal_charset_str());
+ fputs("\n<folderlist>\n", pfile->fp);
+
+ for (list = folder_list; list != NULL; list = list->next) {
+ folder = list->data;
+ folder_write_list_recursive(folder->node, pfile->fp);
+ }
+
+ fputs("</folderlist>\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, "<folder type=\"%s\"",
+ folder_type_str[FOLDER_TYPE(folder)]);
+ if (folder->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, "<folderitem type=\"%s\"",
+ folder_item_stype_str[item->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, "</%s>\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 <glib.h>
+#include <time.h>
+
+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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtksignal.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#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 <glib.h>
+#include <gtk/gtkwindow.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkstyle.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkstatusbar.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkitemfactory.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#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, "<Separator>"},
+ {N_("/Empty _trash"), NULL, folderview_empty_trash_cb, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/Empty _trash"), NULL, folderview_empty_trash_cb, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/Down_load"), NULL, folderview_download_cb, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/Down_load"), NULL, folderview_download_cb, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Check for new messages"),
+ NULL, folderview_update_tree_cb, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {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,
+ "<MailFolder>", &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,
+ "<IMAPFolder>", &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,
+ "<NewsFolder>", &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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkctree.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkhbbox.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#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 <glib.h>
+
+#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 <gtk/gtk.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkctree.h>
+
+/* 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 <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtkhruler.h>
+#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 <gdk/gdk.h>
+#include <gtk/gtkhruler.h>
+
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkcombo.h>
+#include <gtk/gtkbindings.h>
+#include <gtk/gtkitemfactory.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
+# include <wchar.h>
+# include <wctype.h>
+#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 <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkcombo.h>
+#include <gtk/gtktextview.h>
+#include <gtk/gtkitemfactory.h>
+#include <stdlib.h>
+#if HAVE_WCHAR_H
+# include <wchar.h>
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkstyle.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkpixmap.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#if HAVE_LIBCOMPFACE
+# include <compface.h>
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#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[] = {
+ {"&lt;" , "<"},
+ {"&gt;" , ">"},
+ {"&amp;" , "&"},
+ {"&quot;" , "\""},
+ {"&nbsp;" , " "},
+ {"&trade;" , "(TM)"},
+
+ {"&#153;", "(TM)"},
+};
+
+static HTMLSymbol ascii_symbol_list[] = {
+ {"&iexcl;" , "^!"},
+ {"&brvbar;", "|"},
+ {"&copy;" , "(C)"},
+ {"&laquo;" , "<<"},
+ {"&reg;" , "(R)"},
+
+ {"&sup2;" , "^2"},
+ {"&sup3;" , "^3"},
+ {"&acute;" , "'"},
+ {"&cedil;" , ","},
+ {"&sup1;" , "^1"},
+ {"&raquo;" , ">>"},
+ {"&frac14;", "1/4"},
+ {"&frac12;", "1/2"},
+ {"&frac34;", "3/4"},
+ {"&iquest;", "^?"},
+
+ {"&Agrave;", "A`"},
+ {"&Aacute;", "A'"},
+ {"&Acirc;" , "A^"},
+ {"&Atilde;", "A~"},
+ {"&AElig;" , "AE"},
+ {"&Egrave;", "E`"},
+ {"&Eacute;", "E'"},
+ {"&Ecirc;" , "E^"},
+ {"&Igrave;", "I`"},
+ {"&Iacute;", "I'"},
+ {"&Icirc;" , "I^"},
+
+ {"&Ntilde;", "N~"},
+ {"&Ograve;", "O`"},
+ {"&Oacute;", "O'"},
+ {"&Ocirc;" , "O^"},
+ {"&Otilde;", "O~"},
+ {"&Ugrave;", "U`"},
+ {"&Uacute;", "U'"},
+ {"&Ucirc;" , "U^"},
+ {"&Yacute;", "Y'"},
+
+ {"&agrave;", "a`"},
+ {"&aacute;", "a'"},
+ {"&acirc;" , "a^"},
+ {"&atilde;", "a~"},
+ {"&aelig;" , "ae"},
+ {"&egrave;", "e`"},
+ {"&eacute;", "e'"},
+ {"&ecirc;" , "e^"},
+ {"&igrave;", "i`"},
+ {"&iacute;", "i'"},
+ {"&icirc;" , "i^"},
+
+ {"&ntilde;", "n~"},
+ {"&ograve;", "o`"},
+ {"&oacute;", "o'"},
+ {"&ocirc;" , "o^"},
+ {"&otilde;", "o~"},
+ {"&ugrave;", "u`"},
+ {"&uacute;", "u'"},
+ {"&ucirc;" , "u^"},
+ {"&yacute;", "y'"},
+};
+
+static HTMLSymbol eucjp_symbol_list[] = {
+ {"&iexcl;" , "^!"},
+ {"&cent;" , "\xa1\xf1"},
+ {"&pound;" , "\xa1\xf2"},
+ {"&yen;" , "\xa1\xef"},
+ {"&brvbar;", "|"},
+ {"&sect;" , "\xa1\xf8"},
+ {"&uml;" , "\xa1\xaf"},
+ {"&copy;" , "(C)"},
+ {"&laquo;" , "<<"},
+ {"&reg;" , "(R)"},
+
+ {"&deg;" , "\xa1\xeb"},
+ {"&plusmn;", "\xa1\xde"},
+ {"&sup2;" , "^2"},
+ {"&sup3;" , "^3"},
+ {"&acute;" , "'"},
+ {"&micro;" , "\xa6\xcc"},
+ {"&para;" , "\xa2\xf9"},
+ {"&middot;", "\xa1\xa6"},
+ {"&cedil;" , ","},
+ {"&sup1;" , "^1"},
+ {"&raquo;" , ">>"},
+ {"&frac14;", "1/4"},
+ {"&frac12;", "1/2"},
+ {"&frac34;", "3/4"},
+ {"&iquest;", "^?"},
+
+ {"&Agrave;", "A`"},
+ {"&Aacute;", "A'"},
+ {"&Acirc;" , "A^"},
+ {"&Atilde;", "A~"},
+ {"&Auml;" , "A\xa1\xaf"},
+ {"&Aring;" , "A\xa1\xeb"},
+ {"&AElig;" , "AE"},
+ {"&Egrave;", "E`"},
+ {"&Eacute;", "E'"},
+ {"&Ecirc;" , "E^"},
+ {"&Euml;" , "E\xa1\xaf"},
+ {"&Igrave;", "I`"},
+ {"&Iacute;", "I'"},
+ {"&Icirc;" , "I^"},
+ {"&Iuml;" , "I\xa1\xaf"},
+
+ {"&Ntilde;", "N~"},
+ {"&Ograve;", "O`"},
+ {"&Oacute;", "O'"},
+ {"&Ocirc;" , "O^"},
+ {"&Otilde;", "O~"},
+ {"&Ouml;" , "O\xa1\xaf"},
+ {"&times;" , "\xa1\xdf"},
+ {"&Ugrave;", "U`"},
+ {"&Uacute;", "U'"},
+ {"&Ucirc;" , "U^"},
+ {"&Uuml;" , "U\xa1\xaf"},
+ {"&Yacute;", "Y'"},
+
+ {"&agrave;", "a`"},
+ {"&aacute;", "a'"},
+ {"&acirc;" , "a^"},
+ {"&atilde;", "a~"},
+ {"&auml;" , "a\xa1\xaf"},
+ {"&aring;" , "a\xa1\xeb"},
+ {"&aelig;" , "ae"},
+ {"&egrave;", "e`"},
+ {"&eacute;", "e'"},
+ {"&ecirc;" , "e^"},
+ {"&euml;" , "e\xa1\xaf"},
+ {"&igrave;", "i`"},
+ {"&iacute;", "i'"},
+ {"&icirc;" , "i^"},
+ {"&iuml;" , "i\xa1\xaf"},
+
+ {"&eth;" , "\xa2\xdf"},
+ {"&ntilde;", "n~"},
+ {"&ograve;", "o`"},
+ {"&oacute;", "o'"},
+ {"&ocirc;" , "o^"},
+ {"&otilde;", "o~"},
+ {"&ouml;" , "o\xa1\xaf"},
+ {"&divide;", "\xa1\xe0"},
+ {"&ugrave;", "u`"},
+ {"&uacute;", "u'"},
+ {"&ucirc;" , "u^"},
+ {"&uuml;" , "u\xa1\xaf"},
+ {"&yacute;", "y'"},
+ {"&yuml;" , "y\xa1\xaf"},
+};
+
+static HTMLSymbol latin_symbol_list[] = {
+ {"&iexcl;" , "\xa1"},
+ {"&cent;" , "\xa2"},
+ {"&pound;" , "\xa3"},
+ {"&curren;", "\xa4"},
+ {"&yen;" , "\xa5"},
+ {"&brvbar;", "\xa6"},
+ {"&sect;" , "\xa7"},
+ {"&uml;" , "\xa8"},
+ {"&copy;" , "\xa9"},
+ {"&ordf;" , "\xaa"},
+ {"&laquo;" , "\xab"},
+ {"&not;" , "\xac"},
+ {"&shy;" , "\xad"},
+ {"&reg;" , "\xae"},
+ {"&macr;" , "\xaf"},
+
+ {"&deg;" , "\xb0"},
+ {"&plusmn;", "\xb1"},
+ {"&sup2;" , "\xb2"},
+ {"&sup3;" , "\xb3"},
+ {"&acute;" , "\xb4"},
+ {"&micro;" , "\xb5"},
+ {"&para;" , "\xb6"},
+ {"&middot;", "\xb7"},
+ {"&cedil;" , "\xb8"},
+ {"&sup1;" , "\xb9"},
+ {"&ordm;" , "\xba"},
+ {"&raquo;" , "\xbb"},
+ {"&frac14;", "\xbc"},
+ {"&frac12;", "\xbd"},
+ {"&frac34;", "\xbe"},
+ {"&iquest;", "\xbf"},
+
+ {"&Agrave;", "\xc0"},
+ {"&Aacute;", "\xc1"},
+ {"&Acirc;" , "\xc2"},
+ {"&Atilde;", "\xc3"},
+ {"&Auml;" , "\xc4"},
+ {"&Aring;" , "\xc5"},
+ {"&AElig;" , "\xc6"},
+ {"&Ccedil;", "\xc7"},
+ {"&Egrave;", "\xc8"},
+ {"&Eacute;", "\xc9"},
+ {"&Ecirc;" , "\xca"},
+ {"&Euml;" , "\xcb"},
+ {"&Igrave;", "\xcc"},
+ {"&Iacute;", "\xcd"},
+ {"&Icirc;" , "\xce"},
+ {"&Iuml;" , "\xcf"},
+
+ {"&ETH;" , "\xd0"},
+ {"&Ntilde;", "\xd1"},
+ {"&Ograve;", "\xd2"},
+ {"&Oacute;", "\xd3"},
+ {"&Ocirc;" , "\xd4"},
+ {"&Otilde;", "\xd5"},
+ {"&Ouml;" , "\xd6"},
+ {"&times;" , "\xd7"},
+ {"&Oslash;", "\xd8"},
+ {"&Ugrave;", "\xd9"},
+ {"&Uacute;", "\xda"},
+ {"&Ucirc;" , "\xdb"},
+ {"&Uuml;" , "\xdc"},
+ {"&Yacute;", "\xdd"},
+ {"&THORN;" , "\xde"},
+ {"&szlig;" , "\xdf"},
+
+ {"&agrave;", "\xe0"},
+ {"&aacute;", "\xe1"},
+ {"&acirc;" , "\xe2"},
+ {"&atilde;", "\xe3"},
+ {"&auml;" , "\xe4"},
+ {"&aring;" , "\xe5"},
+ {"&aelig;" , "\xe6"},
+ {"&ccedil;", "\xe7"},
+ {"&egrave;", "\xe8"},
+ {"&eacute;", "\xe9"},
+ {"&ecirc;" , "\xea"},
+ {"&euml;" , "\xeb"},
+ {"&igrave;", "\xec"},
+ {"&iacute;", "\xed"},
+ {"&icirc;" , "\xee"},
+ {"&iuml;" , "\xef"},
+
+ {"&eth;" , "\xf0"},
+ {"&ntilde;", "\xf1"},
+ {"&ograve;", "\xf2"},
+ {"&oacute;", "\xf3"},
+ {"&ocirc;" , "\xf4"},
+ {"&otilde;", "\xf5"},
+ {"&ouml;" , "\xf6"},
+ {"&divide;", "\xf7"},
+ {"&oslash;", "\xf8"},
+ {"&ugrave;", "\xf9"},
+ {"&uacute;", "\xfa"},
+ {"&ucirc;" , "\xfb"},
+ {"&uuml;" , "\xfc"},
+ {"&yacute;", "\xfd"},
+ {"&thorn;" , "\xfe"},
+ {"&yuml;" , "\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, "<!--", 4)) {
+ parser->bufp += 4;
+ while ((p = strstr(parser->bufp, "-->")) == NULL)
+ if (html_read_line(parser) == HTML_EOF) return;
+ parser->bufp = p + 3;
+ return;
+ }
+ if (!g_strncasecmp(parser->bufp, "<style", 6)) {
+ parser->bufp += 6;
+ while ((p = strcasestr(parser->bufp, "</style>")) == NULL)
+ if (html_read_line(parser) == HTML_EOF) return;
+ parser->bufp = p + 8;
+ return;
+ }
+ if (!g_strncasecmp(parser->bufp, "<script", 7)) {
+ parser->bufp += 7;
+ while ((p = strcasestr(parser->bufp, "</script>")) == 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 <glib.h>
+#include <stdio.h>
+
+#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 <glib.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkimage.h>
+
+#if HAVE_GDK_PIXBUF
+# include <gdk-pixbuf/gdk-pixbuf.h>
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#if HAVE_ICONV
+# include <iconv.h>
+#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 <glib.h>
+#include <time.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkfilesel.h>
+#include <gtk/gtksignal.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkprogressbar.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+
+#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,
+ &currentxpm, &currentxpmmask);
+ 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 <glib.h>
+#include <time.h>
+#include <sys/time.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkcombo.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkhbbox.h>
+
+#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 <glib.h>
+
+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 <libintl.h>
+# 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 <glib.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+/* #include <dlfcn.h> */
+#include <netinet/in.h>
+
+#ifdef HAVE_LIBPISOCK_PI_ARGS_H
+# include <libpisock/pi-args.h>
+# include <libpisock/pi-appinfo.h>
+# include <libpisock/pi-address.h>
+#else
+# include <pi-args.h>
+# include <pi-appinfo.h>
+# include <pi-address.h>
+#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;i<num_bytes;i++) {
+ n = n*256+bytes[i];
+ }
+ return n;
+}
+
+/* Shamelessly copied from JPilot (utils.c) */
+/* These next 2 functions were copied from pi-file.c in the pilot-link app */
+/* Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT" */
+#define PILOT_TIME_DELTA (unsigned)(2082844800)
+
+time_t pilot_time_to_unix_time ( unsigned long raw_time ) {
+ return (time_t)(raw_time - PILOT_TIME_DELTA);
+}
+
+/* Shamelessly copied from JPilot (libplugin.c) */
+static int raw_header_to_header(RawDBHeader *rdbh, DBHeader *dbh) {
+ unsigned long temp;
+
+ strncpy(dbh->db_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 <glib.h>
+#include <stdio.h>
+
+#ifdef HAVE_LIBPISOCK_PI_ADDRESS_H
+# include <libpisock/pi-address.h>
+#else
+# include <pi-address.h>
+#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 <glib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#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 "<first-name> <last-name>".
+*/
+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 <stdio.h>
+#include <glib.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtktextview.h>
+#include <gtk/gtkstyle.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkrc.h>
+
+#if HAVE_GDK_IMLIB
+# include <gdk_imlib.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#if HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+#if USE_GPGME
+# include <gpgme.h>
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkstatusbar.h>
+#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkhpaned.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkhandlebox.h>
+#include <gtk/gtktoolbar.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtktooltips.h>
+#include <string.h>
+
+#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, "<Branch>"},
+ {N_("/_File/_Folder"), NULL, NULL, 0, "<Branch>"},
+ {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, "<Branch>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/_File/_Mailbox/R_ebuild folder tree"),
+ NULL, rebuild_tree_cb, 0, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/_File/Empty all _trash"), NULL, empty_trash_cb, 0, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_File/_Save as..."), "<control>S", save_as_cb, 0, NULL},
+ {N_("/_File/_Print..."), NULL, print_cb, 0, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_File/_Work offline"), NULL, toggle_offline_cb, 0, "<ToggleItem>"},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ /* {N_("/_File/_Close"), "<alt>W", app_exit_cb, 0, NULL}, */
+ {N_("/_File/E_xit"), "<control>Q", app_exit_cb, 0, NULL},
+
+ {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Edit/_Copy"), "<control>C", copy_cb, 0, NULL},
+ {N_("/_Edit/Select _all"), "<control>A", allsel_cb, 0, NULL},
+ {N_("/_Edit/Select _thread"), NULL, select_thread_cb, 0, NULL},
+ {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Edit/_Find in current message..."),
+ "<control>F", search_cb, 0, NULL},
+ {N_("/_Edit/_Search messages..."), "<shift><control>F", search_cb, 1, NULL},
+
+ {N_("/_View"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/Show or hi_de"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/Show or hi_de/_Folder tree"),
+ NULL, toggle_folder_cb, 0, "<ToggleItem>"},
+ {N_("/_View/Show or hi_de/_Message view"),
+ "V", toggle_message_cb, 0, "<ToggleItem>"},
+ {N_("/_View/Show or hi_de/_Toolbar"),
+ NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/Show or hi_de/_Toolbar/Icon _and text"),
+ NULL, toggle_toolbar_cb, TOOLBAR_BOTH, "<RadioItem>"},
+ {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, "<ToggleItem>"},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/Separate f_older tree"), NULL, separate_widget_cb, SEPARATE_FOLDER, "<ToggleItem>"},
+ {N_("/_View/Separate m_essage view"), NULL, separate_widget_cb, SEPARATE_MESSAGE, "<ToggleItem>"},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/_Sort"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/_Sort/by _number"), NULL, sort_summary_cb, SORT_BY_NUMBER, "<RadioItem>"},
+ {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, "<Separator>"},
+ {N_("/_View/_Sort/Ascending"), NULL, sort_summary_type_cb, SORT_ASCENDING, "<RadioItem>"},
+ {N_("/_View/_Sort/Descending"), NULL, sort_summary_type_cb, SORT_DESCENDING, "/View/Sort/Ascending"},
+ {N_("/_View/_Sort/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/_Sort/_Attract by subject"),
+ NULL, attract_by_subject_cb, 0, NULL},
+ {N_("/_View/Th_read view"), "<control>T", thread_cb, 0, "<ToggleItem>"},
+ {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, "<Separator>"},
+ {N_("/_View/_Go to"), NULL, NULL, 0, "<Branch>"},
+ {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, "<Separator>"},
+ {N_("/_View/_Go to/P_rev unread message"),
+ "<shift>P", prev_unread_cb, 0, NULL},
+ {N_("/_View/_Go to/N_ext unread message"),
+ "<shift>N", next_unread_cb, 0, NULL},
+ {N_("/_View/_Go to/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/_View/_Go to/Other _folder..."), "G", goto_folder_cb, 0, NULL},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+
+#define CODESET_SEPARATOR \
+ {N_("/_View/_Code set/---"), NULL, NULL, 0, "<Separator>"}
+#define CODESET_ACTION(action) \
+ NULL, set_charset_cb, action, "/View/Code set/Auto detect"
+
+ {N_("/_View/_Code set"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/_Code set/_Auto detect"),
+ NULL, set_charset_cb, C_AUTO, "<RadioItem>"},
+ {N_("/_View/_Code set/---"), NULL, NULL, 0, "<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, "<Separator>"},
+ {N_("/_View/Open in new _window"), "<control><alt>N", open_msg_cb, 0, NULL},
+ {N_("/_View/Mess_age source"), "<control>U", view_source_cb, 0, NULL},
+ {N_("/_View/Show all _header"), "<control>H", show_all_header_cb, 0, "<ToggleItem>"},
+ {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View/_Update summary"), "<control><alt>U", update_summary_cb, 0, NULL},
+
+ {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Message/Recei_ve"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Message/Recei_ve/Get from _current account"),
+ "<control>I", inc_mail_cb, 0, NULL},
+ {N_("/_Message/Recei_ve/Get from _all accounts"),
+ "<shift><control>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, "<Separator>"},
+ {N_("/_Message/_Send queued messages"), NULL, send_queue_cb, 0, NULL},
+ {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/Compose _new message"), "<control>M", compose_cb, 0, NULL},
+ {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/_Reply"), "<control>R", reply_cb, COMPOSE_REPLY, NULL},
+ {N_("/_Message/Repl_y to"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Message/Repl_y to/_all"), "<shift><control>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"),
+ "<control>L", reply_cb, COMPOSE_REPLY_TO_LIST, NULL},
+ {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/_Forward"), "<control><alt>F", reply_cb, COMPOSE_FORWARD, NULL},
+ {N_("/_Message/For_ward as attachment"),
+ "<shift><control><alt>F", reply_cb, COMPOSE_FORWARD_AS_ATTACH, NULL},
+ {N_("/_Message/Redirec_t"), NULL, reply_cb, COMPOSE_REDIRECT, NULL},
+ {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/M_ove..."), "<control>O", move_to_cb, 0, NULL},
+ {N_("/_Message/_Copy..."), "<shift><control>O", copy_to_cb, 0, NULL},
+ {N_("/_Message/_Delete"), "<control>D", delete_cb, 0, NULL},
+ {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/_Mark"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Message/_Mark/_Mark"), "<shift>asterisk", mark_cb, 0, NULL},
+ {N_("/_Message/_Mark/_Unmark"), "U", unmark_cb, 0, NULL},
+ {N_("/_Message/_Mark/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/_Mark/Mark as unr_ead"), "<shift>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, "<Separator>"},
+ {N_("/_Message/Re-_edit"), NULL, reedit_cb, 0, NULL},
+
+ {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/_Address book"), "<shift><control>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, "<Separator>"},
+ {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, "<Branch>"},
+ {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, "<Separator>"},
+ {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Tools/Delete du_plicated messages"),
+ NULL, delete_duplicated_cb, 0, NULL},
+ {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Tools/E_xecute"), "X", execute_summary_cb, 0, NULL},
+ {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Tools/_Log window"), "<shift><control>L", log_window_show_cb, 0, NULL},
+
+ {N_("/_Configuration"), NULL, NULL, 0, "<Branch>"},
+ {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, "<Separator>"},
+ {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, "<Branch>"},
+
+ {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Help/_Manual"), NULL, NULL, 0, "<Branch>"},
+ {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, "<Branch>"},
+ {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, "<Separator>"},
+ {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, "<Main>", 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,
+ "<Reply>", 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,
+ "<Forward>", 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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtktooltips.h>
+
+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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+
+#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 <glib.h>
+#include <string.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <ctype.h>
+#include <time.h>
+
+#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 <glib.h>
+
+#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 <drepper@gnu.ai.mit.edu>, 1995. */
+/* heavily modified for GnuPG by <werner.koch@guug.de> */
+/* modified again for Sylpheed by <wk@gnupg.org> 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 <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#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 <mascha@ma-scha.de>
+*/
+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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenubar.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkwindow.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenushell.h>
+#include <gtk/gtkoptionmenu.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkctree.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkeditable.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtktextview.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#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, "<Branch>"},
+ {N_("/_File/_Save as..."), NULL, save_as_cb, 0, NULL},
+ {N_("/_File/_Print..."), NULL, print_cb, 0, NULL},
+ {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_File/_Close"), NULL, close_cb, 0, NULL},
+
+ {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Edit/_Copy"), NULL, copy_cb, 0, NULL},
+ {N_("/_Edit/Select _all"), NULL, allsel_cb, 0, NULL},
+ {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Edit/_Find in current message..."),
+ NULL, search_cb, 0, NULL},
+
+ {N_("/_View"), NULL, NULL, 0, "<Branch>"},
+
+#define CODESET_SEPARATOR \
+ {N_("/_View/_Code set/---"), NULL, NULL, 0, "<Separator>"}
+#define CODESET_ACTION(action) \
+ NULL, set_charset_cb, action, "/View/Code set/Auto detect"
+
+ {N_("/_View/_Code set"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_View/_Code set/_Auto detect"),
+ NULL, set_charset_cb, C_AUTO, "<RadioItem>"},
+ 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, "<Separator>"},
+ {N_("/_View/Mess_age source"), NULL, view_source_cb, 0, NULL},
+ {N_("/_View/Show all _header"), NULL, show_all_header_cb, 0, "<ToggleItem>"},
+
+ {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Message/Compose _new message"),
+ NULL, compose_cb, 0, NULL},
+ {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/_Message/Re-_edit"), NULL, reedit_cb, 0, NULL},
+
+ {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
+ {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, "<Separator>"},
+ {N_("/_Tools/_Create filter rule"),
+ NULL, NULL, 0, "<Branch>"},
+ {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, "<Separator>"},
+ {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
+
+ {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
+ {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,
+ "<MessageView>", msgview);
+#warning FIXME_GTK2
+#if 0
+ menu_factory_copy_rc("<Main>", "<MessageView>");
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <stdio.h>
+#include <glib.h>
+
+/* 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 <glib.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#undef MEASURE_TIME
+
+#ifdef MEASURE_TIME
+# include <sys/time.h>
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkvpaned.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkdnd.h>
+#include <gtk/gtkselection.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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,
+ "<MimeView>", &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 <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkctree.h>
+
+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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <time.h>
+
+#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 <glib.h>
+#include <stdio.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <config.h>
+#endif
+
+#if USE_GPGME
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <glib.h>
+#include <gdk/gdkkeysyms.h>
+#ifdef GDK_WINDOWING_X11
+# include <gdk/gdkx.h> /* GDK_DISPLAY() */
+#endif /* GDK_WINDOWING_X11 */
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkfilesel.h>
+#include <gtk/gtksignal.h>
+
+#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 <glib.h>
+#include <gpgme.h>
+
+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<def8glmno.",
+" .bpqrs|thmuvw.",
+" .x6yz*ABCnDEF.",
+" .GHIJKLMNOwFP.",
+" ............ ",
+" ",
+" "};
diff --git a/src/pixmaps/dir-noselect.xpm b/src/pixmaps/dir-noselect.xpm
new file mode 100644
index 00000000..177488d9
--- /dev/null
+++ b/src/pixmaps/dir-noselect.xpm
@@ -0,0 +1,94 @@
+/* XPM */
+static char * dir_noselect_xpm[] = {
+"16 16 75 1",
+" c None",
+". c #7F7F7F",
+"+ c #BCCFE9",
+"@ c #C8DBF1",
+"# c #BACCE8",
+"$ c #C5D9EF",
+"% c #C4D7EF",
+"& c #CDDEF2",
+"* c #C9DBF1",
+"= c #C5D8EF",
+"- c #BBCEEB",
+"; c #F2F8FC",
+"> 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*<[[[//(////~'~~''''------=%$+ ",
+" |$((//(///(~''~''''{-----=%$+ ",
+" _$</(/<(/'~~'''''------=@$+ ",
+" _${/(//'~''~'''--{--=%@.+ ",
+" e.='/'~~''''------=%$.+ ",
+" _$='''''''----=%$.#, ",
+" n_*%%=---==%$$.+, ",
+" 6_;&...&&#^9 ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" "};
diff --git a/src/pixmaps/stock_dialog_info_48.xpm b/src/pixmaps/stock_dialog_info_48.xpm
new file mode 100644
index 00000000..5440906d
--- /dev/null
+++ b/src/pixmaps/stock_dialog_info_48.xpm
@@ -0,0 +1,115 @@
+/* XPM */
+static char * stock_dialog_info_48_xpm[] = {
+"48 48 64 1",
+" c None",
+". c #171815",
+"+ c #262724",
+"@ c #323332",
+"# c #3A341C",
+"$ c #3E403F",
+"% c #4C4C4A",
+"& c #5A595A",
+"* c #70643B",
+"= c #686968",
+"- c #85794B",
+"; c #787978",
+"> 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<!;, ",
+" [!5tttqqnnnnni<=; ",
+" ~<ttututtqqqnniie!% ",
+" !0uvvxxvttqqnnneiii|% ",
+" !cuvxxxxvttttqnnieeii|% ",
+" ~cvvxxyxxvuttqqqnnneeee|@ ",
+" [[vvxxxxxxtuuttqqineeeeie]% ",
+" !tvxxxxyxvvtttttqnieieebeb& ",
+" [5vvxxxxxyxvvttttnqnieeeeeb]% ",
+" ^uvvxxxxxxvvvvvttqnnnieeeebb$ ",
+" p^uvvxxxyyxyxvvvutttnneeeeeee,; ",
+" 55tvvxxxxxxxxvvvvuutqnqeeeeee]& ",
+" [quvvvxxxxxxxxvvvvuttqneeeffb|@ ",
+" <tuvvvxxyxxxyxxxvvvtttqnnffebi+ ",
+" <ttutvxxxxxxyxxyxyvvttnnfgffeb+ ",
+" ^qttuvxvxxyyxyvyvxyrptqqmmgfbb+ ",
+" <0ttttvvvrxxyvyvyvvvutqqlllfe|@ ",
+" 5<tttturvvrvvvvvvtuptqqmlllgf<$ ",
+" 9!ttttuuvv5vvvtutttpqqmllklff,= ",
+" ,qqmttrtt5ttuttttu9qmmlkklff@ ",
+" !|qmrrrutrttqqtqqm^qllkkklf]+ ",
+" ;qqmrrrmr9mmqmmmm^mlkkkkff% ",
+" ~]qqrlllr^mmmggg26glkkkgf!+ ",
+" &0nmllll^mggggm6glkkkgf|. ",
+" &0nnmmm69mlmgl~gkkkgfb@ ",
+" %|eeegg^9999l~lgggf|@ ",
+" %]e0<[6[2677672cf!. ",
+" =&7[<[[5266666/%+ ",
+" ,o11622267113'+ ",
+" ^dowwwssssj4{*+ ",
+" ^dddddaa}}}:'>+ ",
+" <wsd44((:{(_4'+ ",
+" ^ajwyysojjh3{-+ ",
+" ]j8djdda}}::)>+ ",
+" ^soj43_({{'14>+ ",
+" ]ajwywwsohh4{-+ ",
+" ^j8jjd8}:::{'*+ ",
+" <ssh33({{''__>+ ",
+" [-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]<nwwwwwwwwwy(&.(vvwx|++(nvvuuuuuuvv711,: ",
+" g{7/,iwwwwwwvvvxl-.)vvxp).+5lvuuuuuutvo711,2 ",
+" =b1]fswvwvvvvvvwi5qvwx5@.)iquuttvtttvd91}; ",
+" >64]6nvvvvvvvvvuuuuwxe).+|itttvttttuv911]> ",
+" 3'7/!guvvvvvuuuuvuwxq)+.)etttttttttvo911,: ",
+" =b1,cnvuuuuuuuuutxt(&.&eptttttttstvd71/* ",
+" =64^[isuuuuttttttxi-.+eittttsttsstr911]> ",
+" 2{91,hivtttvtttttx5@.)astssssssssuhb1/;: ",
+" %61^[iptttttttttwa+..assssssssstrb71,$ ",
+" _{41,fitttttsttsst|@@isssssssssvhb1};) ",
+" %61/<inststsssssvneinsssssssstrd71]$ ",
+" 3{71]cisssssssssvxppqsqssqsssud91/,- ",
+" $61},epsssssssuw)&)lpqqqqsstm941]$ ",
+" 2{91^[iqsssssswe..@(lqqqqstr941/;- ",
+" $[11,cissqsqsve...(lqqqqsrd41/,# ",
+" %b11,0nqsqsqss)+-alqqqsth711,% ",
+" :;911[anqqqqqqnlelqqqstk911];& ",
+" ~'71}[anqqqqqnlnqpqttmb11/;# ",
+" $<711<0lqpnppqpqstsdb41/,# ",
+" $<711^8iqqqqqqstrbb41/,$ ",
+" $<411}<clqsssrh9b11},$ ",
+" $<111}/7cmmd97411},$ ",
+" >,}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~. ",
+" .<tu8mvwuxyzA*. ",
+" ..3ax0rBxC3.. ",
+" .DuhE8rz3F. ",
+" .Gt}HIJ1K^*L. ",
+" .~..MbN..O. ",
+" . .*_;. . ",
+" ... ",
+" ",
+" "};
diff --git a/src/pixmaps/stock_mail.xpm b/src/pixmaps/stock_mail.xpm
new file mode 100644
index 00000000..0e27ce4b
--- /dev/null
+++ b/src/pixmaps/stock_mail.xpm
@@ -0,0 +1,143 @@
+/* XPM */
+static char * stock_mail_xpm[] = {
+"24 24 116 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",
+" ",
+" ",
+" ",
+" ",
+" ",
+" . . . . ",
+" . . . . . + @ # $ . ",
+" . . . . . % $ & * = - ; > , . ",
+" . ' ) ! ~ { ] ^ * / ( _ : < [ } . ",
+" . | 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",
+" ........... ",
+".+@##@@##@@$.",
+".@+@@#%@&*+=.",
+".@-+#@*;*+>,.",
+".#@@+#@#')>>.",
+".!#~+{@]+^!/.",
+".#%+@;+^!+(_.",
+".@+&@;:<![}|.",
+".+1123[45678.",
+" ........... "};
diff --git a/src/pixmaps/vcard.xpm b/src/pixmaps/vcard.xpm
new file mode 100644
index 00000000..7b1aeec4
--- /dev/null
+++ b/src/pixmaps/vcard.xpm
@@ -0,0 +1,25 @@
+/* XPM */
+static char * vcard_xpm[] = {
+"16 16 6 1",
+" c None",
+". c #000000",
+"+ c #FFFFFF",
+"@ c #4D4D4D",
+"# c #CFBF7A",
+"$ c #858585",
+" ",
+" ",
+" ...............",
+" .+++++++++++++.",
+" .+++++++++++++.",
+" .+@@@+++++++++.",
+" .+@#@+.+.+.+.+.",
+" .+@@@+++++++++.",
+" .+#@#+.+..+..+.",
+" .+@#@+++++++++.",
+" .+@$@+..+..+.+.",
+" .+@$@+++++++++.",
+" .+++++++++++++.",
+" ...............",
+" ",
+" "};
diff --git a/src/pop.c b/src/pop.c
new file mode 100644
index 00000000..483a076f
--- /dev/null
+++ b/src/pop.c
@@ -0,0 +1,862 @@
+/*
+ * 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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+#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 <glib.h>
+#include <time.h>
+
+#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 <gtk/gtk.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#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(&param[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(&param[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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkbox.h>
+#include <stdio.h>
+
+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 <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#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,
+ &quote.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, &quote.text_quotefmt,
+ prefs_set_data_from_text, prefs_set_text},
+
+ {"forward_quote_mark", "> ", &prefs_common.fw_quotemark, P_STRING,
+ &quote.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, &quote.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[] = {
+ {"<Main>/File/Empty all trash", ""},
+ {"<Main>/File/Save as...", "<control>S"},
+ {"<Main>/File/Print...", ""},
+ {"<Main>/File/Exit", "<control>Q"},
+
+ {"<Main>/Edit/Copy", "<control>C"},
+ {"<Main>/Edit/Select all", "<control>A"},
+ {"<Main>/Edit/Find in current message...", "<control>F"},
+ {"<Main>/Edit/Search messages...", "<shift><control>F"},
+
+ {"<Main>/View/Show or hide/Message view", "V"},
+ {"<Main>/View/Thread view", "<control>T"},
+ {"<Main>/View/Go to/Prev message", "P"},
+ {"<Main>/View/Go to/Next message", "N"},
+ {"<Main>/View/Go to/Prev unread message", "<shift>P"},
+ {"<Main>/View/Go to/Next unread message", "<shift>N"},
+ {"<Main>/View/Go to/Other folder...", "G"},
+ {"<Main>/View/Open in new window", "<control><alt>N"},
+ {"<Main>/View/View source", "<control>U"},
+ {"<Main>/View/Show all header", "<control>H"},
+ {"<Main>/View/Update", "<control><alt>U"},
+
+ {"<Main>/Message/Receive/Get new mail", "<control>I"},
+ {"<Main>/Message/Receive/Get from all accounts", "<shift><control>I"},
+ {"<Main>/Message/Compose new message", "<control>M"},
+ {"<Main>/Message/Reply", "<control>R"},
+ {"<Main>/Message/Reply to/all", "<shift><control>R"},
+ {"<Main>/Message/Reply to/sender", ""},
+ {"<Main>/Message/Reply to/mailing list", "<control>L"},
+ {"<Main>/Message/Forward", "<control><alt>F"},
+ {"<Main>/Message/Forward as attachment", ""},
+ {"<Main>/Message/Move...", "<control>O"},
+ {"<Main>/Message/Copy...", "<shift><control>O"},
+ {"<Main>/Message/Delete", "<control>D"},
+ {"<Main>/Message/Mark/Mark", "<shift>asterisk"},
+ {"<Main>/Message/Mark/Unmark", "U"},
+ {"<Main>/Message/Mark/Mark as unread", "<shift>exclam"},
+ {"<Main>/Message/Mark/Mark as read", ""},
+
+ {"<Main>/Tools/Address book", "<shift><control>A"},
+ {"<Main>/Tools/Execute", "X"},
+ {"<Main>/Tools/Log window", "<shift><control>L"},
+
+ {"<Compose>/File/Close", "<control>W"},
+ {"<Compose>/Edit/Select all", "<control>A"},
+ {"<Compose>/Edit/Advanced/Move a word backward", ""},
+ {"<Compose>/Edit/Advanced/Move a word forward", ""},
+ {"<Compose>/Edit/Advanced/Move to beginning of line", ""},
+ {"<Compose>/Edit/Advanced/Delete a word backward", ""},
+ {"<Compose>/Edit/Advanced/Delete a word forward", ""},
+ };
+
+ static struct KeyBind mew_wl_menurc[] = {
+ {"<Main>/File/Empty all trash", "<shift>D"},
+ {"<Main>/File/Save as...", "Y"},
+ {"<Main>/File/Print...", "<shift>numbersign"},
+ {"<Main>/File/Exit", "<shift>Q"},
+
+ {"<Main>/Edit/Copy", "<control>C"},
+ {"<Main>/Edit/Select all", "<control>A"},
+ {"<Main>/Edit/Find in current message...", "<control>F"},
+ {"<Main>/Edit/Search messages...", "<control>S"},
+
+ {"<Main>/View/Show or hide/Message view", "V"},
+ {"<Main>/View/Thread view", "<shift>T"},
+ {"<Main>/View/Go to/Prev message", "P"},
+ {"<Main>/View/Go to/Next message", "N"},
+ {"<Main>/View/Go to/Prev unread message", "<shift>P"},
+ {"<Main>/View/Go to/Next unread message", "<shift>N"},
+ {"<Main>/View/Go to/Other folder...", "G"},
+ {"<Main>/View/Open in new window", "<control><alt>N"},
+ {"<Main>/View/View source", "<control>U"},
+ {"<Main>/View/Show all header", "<shift>H"},
+ {"<Main>/View/Update", "<shift>S"},
+
+ {"<Main>/Message/Receive/Get new mail", "<control>I"},
+ {"<Main>/Message/Receive/Get from all accounts", "<shift><control>I"},
+ {"<Main>/Message/Compose new message", "W"},
+ {"<Main>/Message/Reply", "<control>R"},
+ {"<Main>/Message/Reply to/all", "<shift>A"},
+ {"<Main>/Message/Reply to/sender", ""},
+ {"<Main>/Message/Reply to/mailing list", "<control>L"},
+ {"<Main>/Message/Forward", "F"},
+ {"<Main>/Message/Forward as attachment", "<shift>F"},
+ {"<Main>/Message/Move...", "O"},
+ {"<Main>/Message/Copy...", "<shift>O"},
+ {"<Main>/Message/Delete", "D"},
+ {"<Main>/Message/Mark/Mark", "<shift>asterisk"},
+ {"<Main>/Message/Mark/Unmark", "U"},
+ {"<Main>/Message/Mark/Mark as unread", "<shift>exclam"},
+ {"<Main>/Message/Mark/Mark as read", "<shift>R"},
+
+ {"<Main>/Tools/Address book", "<shift><control>A"},
+ {"<Main>/Tools/Execute", "X"},
+ {"<Main>/Tools/Log window", "<shift><control>L"},
+
+ {"<Compose>/File/Close", "<alt>W"},
+ {"<Compose>/Edit/Select all", ""},
+ {"<Compose>/Edit/Advanced/Move a word backward", "<alt>B"},
+ {"<Compose>/Edit/Advanced/Move a word forward", "<alt>F"},
+ {"<Compose>/Edit/Advanced/Move to beginning of line", "<control>A"},
+ {"<Compose>/Edit/Advanced/Delete a word backward", "<control>W"},
+ {"<Compose>/Edit/Advanced/Delete a word forward", "<alt>D"},
+ };
+
+ static struct KeyBind mutt_menurc[] = {
+ {"<Main>/File/Empty all trash", ""},
+ {"<Main>/File/Save as...", "S"},
+ {"<Main>/File/Print...", "P"},
+ {"<Main>/File/Exit", "Q"},
+
+ {"<Main>/Edit/Copy", "<control>C"},
+ {"<Main>/Edit/Select all", "<control>A"},
+ {"<Main>/Edit/Find in current message...", "<control>F"},
+ {"<Main>/Edit/Search messages...", "slash"},
+
+ {"<Main>/View/Show or hide/Message view", "V"},
+ {"<Main>/View/Thread view", "<control>T"},
+ {"<Main>/View/Go to/Prev message", ""},
+ {"<Main>/View/Go to/Next message", ""},
+ {"<Main>/View/Go to/Prev unread message", ""},
+ {"<Main>/View/Go to/Next unread message", ""},
+ {"<Main>/View/Go to/Other folder...", "C"},
+ {"<Main>/View/Open in new window", "<control><alt>N"},
+ {"<Main>/View/View source", "<control>U"},
+ {"<Main>/View/Show all header", "<control>H"},
+ {"<Main>/View/Update", "<control><alt>U"},
+
+ {"<Main>/Message/Receive/Get new mail", "<control>I"},
+ {"<Main>/Message/Receive/Get from all accounts", "<shift><control>I"},
+ {"<Main>/Message/Compose new message", "M"},
+ {"<Main>/Message/Reply", "R"},
+ {"<Main>/Message/Reply to/all", "G"},
+ {"<Main>/Message/Reply to/sender", ""},
+ {"<Main>/Message/Reply to/mailing list", "<control>L"},
+ {"<Main>/Message/Forward", "F"},
+ {"<Main>/Message/Forward as attachment", ""},
+ {"<Main>/Message/Move...", "<control>O"},
+ {"<Main>/Message/Copy...", "<shift>C"},
+ {"<Main>/Message/Delete", "D"},
+ {"<Main>/Message/Mark/Mark", "<shift>F"},
+ {"<Main>/Message/Mark/Unmark", "U"},
+ {"<Main>/Message/Mark/Mark as unread", "<shift>N"},
+ {"<Main>/Message/Mark/Mark as read", ""},
+
+ {"<Main>/Tools/Address book", "<shift><control>A"},
+ {"<Main>/Tools/Execute", "X"},
+ {"<Main>/Tools/Log window", "<shift><control>L"},
+
+ {"<Compose>/File/Close", "<alt>W"},
+ {"<Compose>/Edit/Select all", ""},
+ {"<Compose>/Edit/Advanced/Move a word backward", "<alt>B"},
+ {"<Compose>/Edit/Advanced/Move a word forward", "<alt>F"},
+ {"<Compose>/Edit/Advanced/Move to beginning of line", "<control>A"},
+ {"<Compose>/Edit/Advanced/Delete a word backward", "<control>W"},
+ {"<Compose>/Edit/Advanced/Delete a word forward", "<alt>D"},
+ };
+
+ static struct KeyBind old_sylpheed_menurc[] = {
+ {"<Main>/File/Empty all trash", ""},
+ {"<Main>/File/Save as...", ""},
+ {"<Main>/File/Print...", "<alt>P"},
+ {"<Main>/File/Exit", "<alt>Q"},
+
+ {"<Main>/Edit/Copy", "<control>C"},
+ {"<Main>/Edit/Select all", "<control>A"},
+ {"<Main>/Edit/Find in current message...", "<control>F"},
+ {"<Main>/Edit/Search messages...", "<control>S"},
+
+ {"<Main>/View/Show or hide/Message view", "V"},
+ {"<Main>/View/Thread view", "<control>T"},
+ {"<Main>/View/Go to/Prev message", "P"},
+ {"<Main>/View/Go to/Next message", "N"},
+ {"<Main>/View/Go to/Prev unread message", "<shift>P"},
+ {"<Main>/View/Go to/Next unread message", "<shift>N"},
+ {"<Main>/View/Go to/Other folder...", "<alt>G"},
+ {"<Main>/View/Open in new window", "<shift><control>N"},
+ {"<Main>/View/View source", "<control>U"},
+ {"<Main>/View/Show all header", "<control>H"},
+ {"<Main>/View/Update", "<alt>U"},
+
+ {"<Main>/Message/Receive/Get new mail", "<alt>I"},
+ {"<Main>/Message/Receive/Get from all accounts", "<shift><alt>I"},
+ {"<Main>/Message/Compose new message", "<alt>N"},
+ {"<Main>/Message/Reply", "<alt>R"},
+ {"<Main>/Message/Reply to/all", "<shift><alt>R"},
+ {"<Main>/Message/Reply to/sender", "<control><alt>R"},
+ {"<Main>/Message/Reply to/mailing list", "<control>L"},
+ {"<Main>/Message/Forward", "<shift><alt>F"},
+ {"<Main>/Message/Forward as attachment", "<shift><control>F"},
+ {"<Main>/Message/Move...", "<alt>O"},
+ {"<Main>/Message/Copy...", ""},
+ {"<Main>/Message/Delete", "<alt>D"},
+ {"<Main>/Message/Mark/Mark", "<shift>asterisk"},
+ {"<Main>/Message/Mark/Unmark", "U"},
+ {"<Main>/Message/Mark/Mark as unread", "<shift>exclam"},
+ {"<Main>/Message/Mark/Mark as read", ""},
+
+ {"<Main>/Tools/Address book", "<alt>A"},
+ {"<Main>/Tools/Execute", "<alt>X"},
+ {"<Main>/Tools/Log window", "<alt>L"},
+
+ {"<Compose>/File/Close", "<alt>W"},
+ {"<Compose>/Edit/Select all", ""},
+ {"<Compose>/Edit/Advanced/Move a word backward", "<alt>B"},
+ {"<Compose>/Edit/Advanced/Move a word forward", "<alt>F"},
+ {"<Compose>/Edit/Advanced/Move to beginning of line", "<control>A"},
+ {"<Compose>/Edit/Advanced/Delete a word backward", "<control>W"},
+ {"<Compose>/Edit/Advanced/Delete a word forward", "<alt>D"},
+ };
+
+ static struct KeyBind empty_menurc[] = {
+ {"<Main>/File/Empty all trash", ""},
+ {"<Main>/File/Save as...", ""},
+ {"<Main>/File/Print...", ""},
+ {"<Main>/File/Exit", ""},
+
+ {"<Main>/Edit/Copy", ""},
+ {"<Main>/Edit/Select all", ""},
+ {"<Main>/Edit/Find in current message...", ""},
+ {"<Main>/Edit/Search messages...", ""},
+
+ {"<Main>/View/Show or hide/Message view", ""},
+ {"<Main>/View/Thread view", ""},
+ {"<Main>/View/Go to/Prev message", ""},
+ {"<Main>/View/Go to/Next message", ""},
+ {"<Main>/View/Go to/Prev unread message", ""},
+ {"<Main>/View/Go to/Next unread message", ""},
+ {"<Main>/View/Go to/Other folder...", ""},
+ {"<Main>/View/Open in new window", ""},
+ {"<Main>/View/View source", ""},
+ {"<Main>/View/Show all header", ""},
+ {"<Main>/View/Update", ""},
+
+ {"<Main>/Message/Receive/Get new mail", ""},
+ {"<Main>/Message/Receive/Get from all accounts", ""},
+ {"<Main>/Message/Compose new message", ""},
+ {"<Main>/Message/Reply", ""},
+ {"<Main>/Message/Reply to/all", ""},
+ {"<Main>/Message/Reply to/sender", ""},
+ {"<Main>/Message/Reply to/mailing list", ""},
+ {"<Main>/Message/Forward", ""},
+ {"<Main>/Message/Forward as attachment", ""},
+ {"<Main>/Message/Move...", ""},
+ {"<Main>/Message/Copy...", ""},
+ {"<Main>/Message/Delete", ""},
+ {"<Main>/Message/Mark/Mark", ""},
+ {"<Main>/Message/Mark/Unmark", ""},
+ {"<Main>/Message/Mark/Mark as unread", ""},
+ {"<Main>/Message/Mark/Mark as read", ""},
+
+ {"<Main>/Tools/Address book", ""},
+ {"<Main>/Tools/Execute", ""},
+ {"<Main>/Tools/Log window", ""},
+
+ {"<Compose>/File/Close", ""},
+ {"<Compose>/Edit/Select all", ""},
+ {"<Compose>/Edit/Advanced/Move a word backward", ""},
+ {"<Compose>/Edit/Advanced/Move a word forward", ""},
+ {"<Compose>/Edit/Advanced/Move to beginning of line", ""},
+ {"<Compose>/Edit/Advanced/Delete a word backward", ""},
+ {"<Compose>/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 <glib.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtkbutton.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <time.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+
+#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 <glib.h>
+#include <stdio.h>
+
+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 <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/types.h>
+#include <string.h>
+
+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 <glib.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkbutton.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <ctype.h>
+
+#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 <chr> CHARACTER
+%type <chr> 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 <glib.h>
+#include <ctype.h>
+
+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 <glib.h>
+
+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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+
+#include <gpgme.h>
+
+#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 <glib.h>
+#include <stdio.h>
+
+#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 <config.h>
+#endif
+
+#ifdef USE_GPGME
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkclist.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtksignal.h>
+
+#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 <glib.h>
+#include <gpgme.h>
+
+
+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 <glib.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkclist.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <glib.h>
+#include <stdio.h>
+
+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 <glib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <time.h>
+#include <errno.h>
+
+#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 <glib.h>
+
+#include <time.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#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 <glib.h>
+#include <gtk/gtkstatusbar.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkbutton.h>
+#include <gdk/gdkkeysyms.h>
+#include <gpgme.h>
+
+#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 <gpgme.h>
+
+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 <config.h>
+#ifdef USE_SIMPLE_GETTEXT
+#ifndef __MINGW32__
+ #error This file can only be used with MinGW32
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <windows.h>
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <setjmp.h>
+#if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#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 <glib.h>
+#include <netdb.h>
+
+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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtktextview.h>
+#include <gtk/gtkstyle.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#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 <glib.h>
+#include <gtk/gtkstatusbar.h>
+#include <stdarg.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkstatusbar.h>
+
+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 <glib.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkpixmap.h>
+
+#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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <string.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkoptionmenu.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkcheckbutton.h>
+#include <gtk/gtkhbbox.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkctree.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <glib.h>
+
+#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 <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtktext.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkstyle.h>
+#include <gtk/gtkarrow.h>
+#include <gtk/gtkeventbox.h>
+#include <gtk/gtkstatusbar.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#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, "<Branch>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/_Mark"), NULL, NULL, 0, "<Branch>"},
+ {N_("/_Mark/_Mark"), NULL, summary_mark, 0, NULL},
+ {N_("/_Mark/_Unmark"), NULL, summary_unmark, 0, NULL},
+ {N_("/_Mark/---"), NULL, NULL, 0, "<Separator>"},
+ {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, "<Separator>"},
+ {N_("/Re-_edit"), NULL, summary_reedit, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/Add sender to address boo_k"),
+ NULL, summary_add_address_cb, 0, NULL},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_View"), NULL, NULL, 0, "<Branch>"},
+ {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, "<ToggleItem>"},
+ {N_("/---"), NULL, NULL, 0, "<Separator>"},
+ {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,
+ "<SummaryView>", &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 <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkitemfactory.h>
+#include <gtk/gtkctree.h>
+#include <gtk/gtkdnd.h>
+
+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 <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkmain.h>
+#include <sys/time.h>
+#include <string.h>
+#include <ldap.h>
+#include <lber.h>
+#include <pthread.h>
+/* #include <dlfcn.h> */
+
+#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 "<first-name> <last-name>".
+*/
+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 <glib.h>
+#include <pthread.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtksignal.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#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", &quote_colors[0],
+ NULL);
+ gtk_text_buffer_create_tag(buffer, "quote1",
+ "foreground-gdk", &quote_colors[1],
+ NULL);
+ gtk_text_buffer_create_tag(buffer, "quote2",
+ "foreground-gdk", &quote_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,
+ &quote_colors[0]);
+ gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
+ &quote_colors[1]);
+ gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
+ &quote_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>, 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 <glib.h>
+#include <gtk/gtkwidget.h>
+
+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 <glib.h>
+#include <gtk/gtktextview.h>
+
+#include <string.h> /* for strlen */
+#include <stdlib.h> /* 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 <glib.h>
+#include <gtk/gtktextview.h>
+#include <gtk/gtkitemfactory.h>
+
+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 <glib.h>
+#include <string.h>
+#include <ctype.h>
+
+#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 <glib.h>
+
+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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
+# include <wchar.h>
+# include <wctype.h>
+#endif
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <time.h>
+
+#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<bchars> 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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+#if HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if HAVE_WCHAR_H
+# include <wchar.h>
+#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 <ctype.h>
+
+#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 <glib.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#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 <stdio.h>
+#include <glib.h>
+
+#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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#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, "&lt;"))
+ ch = '<';
+ else if (!strcmp(esc_str, "&gt;"))
+ ch = '>';
+ else if (!strcmp(esc_str, "&amp;"))
+ ch = '&';
+ else if (!strcmp(esc_str, "&apos;"))
+ ch = '\'';
+ else if (!strcmp(esc_str, "&quot;"))
+ 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("&lt;", fp);
+ break;
+ case '>':
+ fputs("&gt;", fp);
+ break;
+ case '&':
+ fputs("&amp;", fp);
+ break;
+ case '\'':
+ fputs("&apos;", fp);
+ break;
+ case '\"':
+ fputs("&quot;", 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, "<?xml version=\"1.0\" encoding=\"%s\"?>\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, "</%s>\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 <glib.h>
+#include <stdio.h>
+
+#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__ */