## $Id: edit.py,v 1.133 2001/10/16 17:12:23 kjetilja Exp $

## System modules
from gtk import *
from gnome.ui import *
import mimify, string, rfc822, cStringIO, sys, MimeWriter, mimetools, os.path

## Local modules
import cite, folderops, fileops, smileys, sendmail, mime, editor, builtineditor
import externaleditor

## Mail composition modes
NEW = 1
REPLY = 2
REPLYALL = 3
FORW = 4
EDIT = 5
MAILTO = 6

## File reference for mail parts without a name
NONAME = "<Unnamed>"

## Escaped dot
DOT = string.upper(hex(ord('.'))[2:])

## Error messages from the GUI
err1 = "You must specify a recipient for your message!"
err2 = "This mail has malformed content which cannot be\nsuccessfully viewed."
err3 = "The file cannot be inserted because it contains null bytes."

## Other messages from the GUI
pop1 = "The message has been sent."
pop2 = "The message has been stored in the 'drafts' folder.\n\nDouble-click "\
       "on the message in the 'drafts' folder to edit it."


## Filter callback to determine whether to quote text or not
def needsquoting(c):
    if c == '\n' or c == '\t':
        return 0
    return c == '=' or not(' ' <= c <= '~')

## Function that ensures split email addresses are parsed correctly
def verify_split(addr):
    for name in addr:
        if name[0] == '"' or name[0] == "'":
            if string.rfind(name, '"') > 0 or\
               string.rfind(name, "'") > 0:
                continue
            idx = addr.index(name)
            if len(addr) > idx+1:
                addr[idx] = addr[idx] +", "+ addr[idx+1]
                del addr[idx+1]

##
##
## Edit/Compose window class
##
##
class EditWindow:		

    ##
    ## Method __init__ (self, folder window class instance,
    ##                  delivery mode, message class instance)
    ##
    ##    Main widget constructor.
    ##
    ##
    def __init__(self, fld, mode, msg=None, mailto=None):
	# Add class instance references 
        self.fld = fld
	self.prefs = fld.prefs
	self.mode = mode
	self.msg = msg
        self.next = 0
        self.headers = {}
        self.smileys_active = 0
        self.more = 0
        self.has_cc = 0
        self.attachments = []
        self.attachment_table = None
        self.current_attachment = None
	self.privacy = self.fld.privacy
	self.mbox = fld.mbox
        self.mail_is_sent = 0
        self.mailto = mailto

        # Make a separate window to play in
    	self.vbox = GtkVBox()
	self.vbox.set_spacing(5)
	self.vbox.show()
	self.win = GnomeApp('Pygmy Viewer', ':Pygmy - Compose Message')
        self.win.set_wmclass('pygmy viewer', ':Pygmy - Compose Message')
        self.win.set_border_width(5)
        self.win.set_default_size(512, 480)
        self.win.set_contents(self.vbox)
	self.win.connect('delete_event', self.destroy)
	self.win.show()


    ##
    ## Method setup_widgets (self)
    ##
    ##    Setup the window
    ##
    ##
    def setup_widgets(self):
        # Set up widgets for this page
	self.init_header()
	self.init_editor()

        # Create menus and toolbar
	self.win.create_menus(self.create_menu())
	self.win.create_toolbar(self.create_toolbar())

        # Fill widgets with content
	self.init_mode()

        # Update the status indicator for the message if not new
        self.update_status_field()

        # Update the window counter in the folder instance
        self.fld.num_edits = self.fld.num_edits + 1


    ##
    ## Method edit_mode (self)
    ##
    ##    Handle edit
    ##
    ##
    def edit_mode(self):
	msg = self.msg
        # Fill in the subject, to, date and body parts
        sub = mimify.mime_decode_header(msg.getheader('subject'))
        self.e3.set_text(sub)

        # Get all the target addresses
        to = rfc822.AddressList(msg.getheader('to'))
        cc = rfc822.AddressList(msg.getheader('cc'))
        bcc = rfc822.AddressList(msg.getheader('bcc'))
        tostr = []
        for e in to.addresslist:
            tostr.append(e[1])
        ccstr = []
        for e in cc.addresslist:
            ccstr.append(e[1])
        bccstr = []
        for e in bcc.addresslist:
            bccstr.append(e[1])
        self.e1.set_text(mimify.mime_decode_header(string.join(tostr, ', ')))
        self.e2.set_text(mimify.mime_decode_header(string.join(ccstr, ', ')))
        self.e4.set_text(mimify.mime_decode_header(string.join(bccstr, ', ')))

        # If (B)CC is set, show all of the header display
        if ccstr != [] or bccstr != []:
            self.more_toggle(None)

        # Draw text and set the scroll position to the top of the text
        self.ed.set_scroll_position( 'top' )
        self.ed.redraw()

        # Update the status indicator for the message
        self.status_update = folderops.STATUS_READ

        irto = msg.getheader('message-id')
        if irto != None:
            self.headers['In-Reply-To'] = irto

        # Check for MIME messages
        self.ed.freeze()
        if msg.getmaintype() == 'multipart':
            text = ''
            not_viewed = []
            try:
                p = mime.find_all_parts(msg.getbodyparts(), new_parts=[])
            except SystemError:
                text = text + err2
            else:
                for sm in p:
                    type = sm.gettype()
                    # Display some types directly
                    if type == 'text/plain':
                        text = text + sm.getbodytext()
                    else:
                        not_viewed.append(sm)
                del p
            self.ed.insert_text( text, pos='bottom' )
            if len(not_viewed) > 0:
                for sm in not_viewed:
                    type = sm.gettype()
                    name = mimify.mime_decode_header((sm.getparam('name') or\
                                              NONAME))[:40]
                    # Append attachment to list
                    self.attachments.insert(0, (name, type, sm))
                # Update view
                self.view_attachment_table()
                del not_viewed
        else:
            msg.fp.seek(msg.startofbody)
            text = msg.fp.read(msg.stop - msg.startofbody)
            
	    # Decode quoted-printable mails 
            if msg.getencoding() == 'quoted-printable':
                text = mime.decode_quoted_printable(text)
            elif msg.getencoding() == 'base64':
                text = mime.decode_base64(text) 
            self.ed.insert_text(text, pos='bottom')
        self.ed.thaw()
        self.ed.set_scroll_position( 'top' )
        self.ed.redraw()
        self.ed.grab_focus()


    ##
    ## Method forward_mode (self)
    ##
    ##    Handle forward
    ##
    ##
    def forward_mode(self):
        body = ''
	msg = self.msg
        # Extract some header information we would like to embed in the message
        sub = msg.getheader('subject')
        if sub:
            sub = mimify.mime_decode_header(sub)
        frm = msg.getheader('from')
        if frm:
            frm = mimify.mime_decode_header(frm)
        date = msg.getheader('date')
        if date:
            date = mimify.mime_decode_header(date)
        cc = msg.getheader('cc')
        if cc:
            cc = mimify.mime_decode_header(cc)
        to = msg.getheader('to')
        if to:
            to = mimify.mime_decode_header(to)

        # Fill in the subject and body parts (forward)
        self.e3.set_text(sub)
        self.ed.set_scroll_position( 'top' )
        self.ed.redraw()
        self.e1.grab_focus()

        # Update the status indicator for the message
        self.status_update = folderops.STATUS_FORW

        # Check for MIME messages
        if msg.getmaintype() == 'multipart':
            text = ''
            not_viewed = []
            try:
                p = mime.find_all_parts(msg.getbodyparts(), new_parts=[])
            except SystemError:
                text = text + err2
            else:
                for sm in p:
                    type = sm.gettype()
                    # Display some types directly
                    if type == 'text/plain':
                        text = text + sm.getbodytext()
                    else:
                        not_viewed.append(sm)
                del p
                text = self.privacy.handle_read(text, self.init_mode)
            body = body + cite.body_forward(text, sub, frm, date, cc, to)
            if len(not_viewed) > 0:
                for sm in not_viewed:
                    type = sm.gettype()
                    name = mimify.mime_decode_header((sm.getparam('name') or\
                                                      NONAME))[:40]
                    # Append attachment to list
                    self.attachments.insert(0, (name, type, sm))
                # Update view
                self.view_attachment_table()
                del not_viewed
        else:
            if msg.gettype() == 'text/plain':
                msg.fp.seek(msg.startofbody)
                text = msg.fp.read(msg.stop - msg.startofbody)
                text = self.privacy.handle_read(text, self.init_mode)
            else:
                text = ''
                self.attachments.insert(0, (NONAME, msg.gettype(), msg))
                self.view_attachment_table()

            # Decode quoted-printable mails 
            if msg.getencoding() == 'quoted-printable':
                text = mime.decode_quoted_printable(text)
            elif msg.getencoding() == 'base64':
                text = mime.decode_base64(text) 
            body = body + cite.body_forward(text, sub, frm, date, cc, to)

        # add signature if necessary
	body = self.check_add_sig( FORW, body )
        self.ed.freeze()
        self.ed.insert_text( body, pos='cursor')
        self.ed.thaw()


    ##
    ## Method reply_mode (self, all)
    ##
    ##    Handle (group) reply
    ##
    ##
    def reply_mode(self, all=0):
        body = ''
        msg = self.msg

        date = msg.getheader('date')
        frm = mimify.mime_decode_header(msg.getaddr('from')[0] or 'you')

        # Fill in the subject, to, date and body parts (reply to all)
        sub = cite.citesubject(mimify.mime_decode_header(msg.getheader('subject') or ''))
        self.e3.set_text(sub)
        # Use the reply-to header as reply address if specified
        rt = msg.getheader('reply-to')
        if rt == None or rt == '':
            rt = msg.getheader('from')
            if rt == None or rt == '':
                rt = 'No valid email address found'

        # Get all the relevant addresses from the accounts
        ownaddrs = []
        for a in self.prefs.accounts.keys():
            username, emailaddr, sigfile, replyaddr = self.prefs.accounts[a]
            if emailaddr != '':
                ownaddrs.append(string.lower(emailaddr))
            if replyaddr != '':
                ownaddrs.append(string.lower(replyaddr))

        # Do group reply or just a regular reply
        if all == 1:
            # Get all the target addresses
            to = rfc822.AddressList(rt) + \
                 rfc822.AddressList(msg.getheader('to'))
            cc = rfc822.AddressList(msg.getheader('cc'))

            tostr = []
            for i in to.addresslist:
                if i[1] != None and string.lower(i[1]) not in ownaddrs:
                    tostr.append(i[1])

            ccstr = []
            for i in cc.addresslist:
                if i[1] != None and string.lower(i[1]) not in ownaddrs:
                    ccstr.append(i[1])

            self.e1.set_text(mimify.mime_decode_header(string.join(tostr, ', ')))
            self.e2.set_text(mimify.mime_decode_header(string.join(ccstr, ', ')))
            # If CC is set, show all of the header display
            if ccstr != []:
                self.more_toggle(None)
        else:
            # Just regular reply here, not group reply
            self.e1.set_text(mimify.mime_decode_header(rt))

        # Set message status
        self.status_update = folderops.STATUS_REPL

        # Create In-Reply-To header entry
        irto = msg.getheader('message-id')
        if irto != None:
            self.headers['In-Reply-To'] = irto

        # Check for MIME messages
        if msg.getmaintype() == 'multipart':
            text = ''
            try:
                p = mime.find_all_parts(msg.getbodyparts(), new_parts=[])
            except SystemError:
                text = text + err2
            else:
                for sm in p:
                    type = sm.gettype()
                    # Display some types directly
                    if type == 'text/plain':
                        text = text + sm.getbodytext()
                del p
                text = self.privacy.handle_read(text, self.init_mode)
            body = body + cite.citebody(text, date, frm, self.prefs.citechar)
        else:
            if msg.gettype() == 'text/plain':
                msg.fp.seek(msg.startofbody)
                text = msg.fp.read(msg.stop - msg.startofbody)
                text = self.privacy.handle_read(text, self.init_mode)
            else:
                text = ''
            # Decode quoted-printable mails 
            if msg.getencoding() == 'quoted-printable':
                text = mime.decode_quoted_printable(text)
            elif msg.getencoding() == 'base64':
                text = mime.decode_base64(text)
            body = body + cite.citebody(text, date, frm, self.prefs.citechar)

        # add signature if necessary
	body = self.check_add_sig( REPLY, body )
        self.ed.freeze()
        self.ed.insert_text( body, pos='cursor' )
        self.ed.thaw()
        self.ed.set_scroll_position( 'top' )
        self.ed.redraw()
        self.ed.grab_focus()

    

    ##
    ## Method init_mode (self)
    ##
    ##    Fill the widgets according forward, edit, reply or new message
    ##
    ##
    def init_mode(self):
	mode = self.mode
        if mode == NEW:
            self.e1.grab_focus()
            self.status_update = None
            body = ''
	    body = self.check_add_sig( NEW, body )
            self.ed.insert_text( body )
        elif mode == EDIT:
            self.edit_mode()
        elif mode == FORW:  
            self.forward_mode()
        elif mode == REPLY:
            self.reply_mode(all=0)
        elif mode == REPLYALL:
            self.reply_mode(all=1)
        elif mode == MAILTO:
            if len(self.mailto) > len('mailto:'):
                if self.mailto[:len('mailto:')] == 'mailto:':
                    self.mailto = self.mailto[len('mailto:'):]
            self.e1.set_text(mimify.mime_decode_header(self.mailto))
            self.e3.grab_focus()
            self.status_update = None
	    self.ed.insert_text( '' )

            
    ##
    ## Method create_menu (self)
    ##
    ##    Update the status indicator for the message both on screen and in index
    ##
    ##
    def update_status_field(self):
        if self.status_update != None:
            fld = self.fld
            pathname = folderops.get_folder_pathname(fld.prefs.folders,
                                                     self.mbox)
            try:
                folderops.update_folder_index_status(pathname, self.msg.start,
                                                     self.status_update)
            except KeyError:
                print '** Warning: unable to update status field, to be fixed later'
                return
            pos, rest =  fld.fdisp.get_row_data(fld.row)
            rest[0] = self.status_update
            fld.fdisp.set_row_data(fld.row, (pos, rest))
            fld.fdisp.set_text(fld.row, 3, self.status_update)


    ##
    ## Method create_menu (self)
    ##
    ##    Create the menu elements.
    ##
    ##
    def create_menu(self):
        from prefix import PYGMY_ICONDIR
        import os.path

        file_menu = [
            UIINFO_ITEM_STOCK('Insert File', None, self.insert_file, STOCK_MENU_OPEN),
            UIINFO_ITEM_STOCK('Attach File', None, self.add_attachment, STOCK_MENU_ATTACH),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Close', None, self.destroy, STOCK_MENU_CLOSE)
            ]
        edit_menu = [
            UIINFO_ITEM_STOCK('Cut', None, self.edit_cut, STOCK_MENU_CUT),
            UIINFO_ITEM_STOCK('Copy', None, self.edit_copy, STOCK_MENU_COPY),
            UIINFO_ITEM_STOCK('Paste', None, self.edit_paste, STOCK_MENU_PASTE),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Select All', None, self.sel_all, STOCK_MENU_BLANK),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Fill Paragraph', None, self.fill_paragraph, STOCK_MENU_BLANK),
            ]
        msg_menu = [
            UIINFO_ITEM_STOCK('Send', None, self.send_mail,
                              STOCK_MENU_MAIL_SND), 
            UIINFO_ITEM_STOCK('Draft', None, self.send_mail_later,
                              STOCK_MENU_TIMER),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Security', None, self.privacy_dialog,
                              PYGMY_ICONDIR+"/encrypt.xpm"), 
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Add Signature', None, self.add_sig,
                              STOCK_MENU_BLANK), 
            UIINFO_ITEM_STOCK('Add Smileys', None, self.smileys,
                              STOCK_MENU_BLANK), 
           ]
        menu_info = [
            UIINFO_SUBTREE('File', file_menu),
            UIINFO_SUBTREE('Edit', edit_menu),
            UIINFO_SUBTREE('Message', msg_menu),
            ]

        return menu_info


    ##
    ## Method create_toolbar (self)
    ##
    ##    Create the toolbar elements.
    ##
    ##
    def create_toolbar(self):
        toolbar_info = [
            UIINFO_ITEM_STOCK('Send', None, self.send_mail,
                              STOCK_PIXMAP_MAIL_SND),
            UIINFO_ITEM_STOCK('Draft', None, self.send_mail_later,
                              STOCK_PIXMAP_TIMER),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Attach', None, self.add_attachment,
                              STOCK_PIXMAP_ATTACH),
            UIINFO_ITEM_STOCK('Insert', None, self.insert_file,
                              STOCK_PIXMAP_OPEN),
            ]
        return toolbar_info



    ##
    ## Method {sel|usel}_all (self, b)
    ##
    ##    Select/Unselect all functions.
    ##
    ##
    def sel_all(self, b):
        self.ed.sel_all()


    ##
    ## Method edit_{cut|copy|paste} (self, b)
    ##
    ##    Clipboard functions.
    ##
    ##
    def edit_cut(self, b):
        self.ed.cut_clipboard()
    def edit_copy(self, b):
        self.ed.copy_clipboard()
    def edit_paste(self, b):
        self.ed.paste_clipboard()


    ##
    ## Method insert_file (self)
    ##
    ##    Insert a file in the current editor window.
    ##
    ##
    def insert_file(self, button=None):
        import fileops
        fname = fileops.getfilename( self.prefs.filepaths,'', 'Select file to insert...', 'insert-file' )
	if fname and os.path.isfile(fname):
	    self.ed.insert_file( fname, pos='cursor' )

    def smileys(self, buttion=None):
        if self.smileys_active:
            return
        self.smileys_active = 1
        self.smileyswnd = smileys.SmileysWindow(self)


    def insert_smiley(self, smiley):
        self.ed.insert_text(smiley, pos='cursor')


    ##
    ## Method add_sig (self)
    ##
    ##    Add signature to message (this action is selected manually by the user).
    ##
    ##
    def add_sig(self, button=None):
        import fileops
        sigfile = self.prefs.accounts[self.account_name][2]
        buf = fileops.getsignature(sigfile)
        if buf != None and buf != '':
	    self.ed.insert_text( "\n" + buf, pos='bottom' )
            del buf


    ##
    ## Method check_add_sig (self)
    ##
    ##  Check if a signature is to be added automatically, and 
    ##  whether it should be placed before or after the main text 
    ##  (depending on the mode) and do it.
    ##
    ##
    def check_add_sig( self, mode, body='' ):
        if not self.prefs.addsig:
	    return body

	sigfile = self.prefs.accounts[self.account_name][2]
	sig = fileops.getsignature(sigfile)
	if mode == REPLY:
	    if self.prefs.sigreply == 'before':
		body = "\n" + sig + body
	    elif self.prefs.sigreply == 'after':
		body = body + "\n" + sig
	elif mode == FORW:
	    if self.prefs.addsig:
		if self.prefs.sigforward == 'before':
		    body = "\n" + sig + body
		elif self.prefs.sigforward == 'after':
		    body = body + "\n" + sig
	elif mode == NEW:
	    body = body + "\n" + sig
	return body
        


    def set_current_attachment(self, button, name):
        self.current_attachment = name


    def view_attachment_table(self):
        # If already set, remove the old version
        if self.attachment_table != None:
            self.attachment_table.destroy()

        # If no attachments, return
        if len(self.attachments) == 0:
            return

        # Create a table to hold the attachment entries
        t = GtkTable(1,2,0)
        t.show()
        c = GtkOptionMenu()
        c.show()
        m = GtkMenu()
        m.show()
        current_num = 1
        total_num = len(self.attachments)
        for name, type, sm in self.attachments:
            if len(name) > 36:
                namestr = ".." + name[-36:]
            else:
                namestr = name
            menustr = "%s (%s, %d of %d)" % (namestr, type,
                                             current_num, total_num)
            i = GtkMenuItem(menustr)
            i.show()
            i.connect('activate', self.set_current_attachment, (name, type, sm))
            m.append(i)
            current_num = current_num + 1
        c.set_menu(m)
	t.attach(c, 0,1,0,1, xpadding=4, ypadding=1)

        # Remove attachment button
        w = GnomeStock(STOCK_MENU_TRASH)
        w.show()
        h = GtkVBox()
        h.show()
        h.add(w)
        b = GtkButton()
        b.show()
        b.add(h)
        b.set_border_width(1)
        b.set_relief(RELIEF_NONE)
        b.connect('clicked', self.remove_attachment)
        t.attach(b, 1, 2, 0, 1, xoptions=0, yoptions=0, xpadding=3, ypadding=0)

        # Set the current attachment if not set
        if self.current_attachment not in self.attachments:
            self.current_attachment = self.attachments[0]

        # Store a reference to the attachment
        self.vbox.pack_start(t, expand=0)
        self.attachment_table = t


    ##
    ## Method add_attachment (self)
    ##
    ##    Add an attachment to the document.
    ##
    ##
    def add_attachment(self, button=None, fname=None):
        import mimetypes, gnome.mime, os.path

        # Get attachment file name
        if fname == None:
            # Request dialog
            name = fileops.getfilename( self.prefs.filepaths, '',
                                        "Select file to attach...", 'load-attachment' )
            if name == None:
                return
        else:
            # gmc drag and drop
            name = fname

        # Check that attachment is actually a file and not a dir or something
        if not os.path.isfile(name):
            return
            
        # Try using gnome.mime to determine the mime-type
        type = gnome.mime.type_of_file(os.path.basename(name))
        if type == 'text/plain':
            # Try using python libs to determine mime-type
            type, encoding = mimetypes.guess_type(name)

        if type == None:
            # Got no type, set to application/octet-stream -- hmmm....
            type = 'application/octet-stream'

        # If we alreay have the attachment, ignore it
        if (name, type, None) in self.attachments:
            return

        # Append attachment to list
        self.attachments.insert(0, (name, type, None))
        # Update view
        self.view_attachment_table()        


    ## Callback for the remove attachment button
    def remove_attachment(self, button):
        # Remove attachment from list
        self.attachments.remove(self.current_attachment)
        # Update view
        self.view_attachment_table()
	    

    ##
    ## Method init_editor (self)
    ##
    ##    Editor widget setup.
    ##
    ##
    def init_editor(self):		
        from gnome.zvt import ZvtTerm
        targets = [
            ('text/plain', 0, -1)
            ]

	# create editor widget
	if not self.prefs.usebuiltined and "writechild" in dir(ZvtTerm):
            try:
                self.ed = externaleditor.ExternalEditor( self.prefs, self.fld.comp_nfont )
            except "BadEditorPath", path:
                # fallback to builtin editor
                self.ed = builtineditor.BuiltinEditor( self.prefs, self.fld.comp_nfont )
	else:
	    self.ed = builtineditor.BuiltinEditor( self.prefs, self.fld.comp_nfont )

        edwidget = self.ed.widget()
        edwidget.connect('drag_data_received', self.drag_drop_attachment)
        edwidget.drag_dest_set(DEST_DEFAULT_ALL, targets, GDK.ACTION_COPY)
	self.vbox.pack_start(edwidget, expand=TRUE)

    ## Callback for dnd to: from GnomeCard
    def drag_drop_to(self, w, context, x, y, data, info, time):
        self.insert_dragdrop(data, self.e1)

    ## Callback for dnd cc: from GnomeCard
    def drag_drop_cc(self, w, context, x, y, data, info, time):
        self.insert_dragdrop(data, self.e2)

    ## Callback for dnd bcc: from GnomeCard
    def drag_drop_bcc(self, w, context, x, y, data, info, time):
        self.insert_dragdrop(data, self.e4)

    ## Callback for attachment dnd from gmc and mozilla -- jeez what a hack dnd is
    def drag_drop_attachment(self, w, context, x, y, data, info, time):
        filename = data.data
        if filename[:len('file:')] == 'file:':
            # Strip away the file: part
            filename = filename[len('file:'):]
        elif filename[0] != '/':
            # We only handle file: protocol for now
            return
        tmp = string.split(filename, ' ')
        if len(tmp) > 1:
            # We get two refs from mozilla, only interested in the first
            filename = tmp[0]
        if filename[:len('///')] == '///':
            # Hack away the prefix in mozilla
            filename = filename[len('//'):]
        if filename[-1] == '\000':
            # Hack away the string termination in gmc and nautilus
            filename = filename[:-1]
        if filename[-2:] == '\015\012':
            filename = filename[:-2]
        self.add_attachment(None, filename)
        
    ## Do the actual insert in the widget
    def insert_dragdrop(self, data, e):
        # This is for parsing GnomeCard stuff
        import string
        lines = string.split(data.data, '\012')
        for i in range(len(lines)):
            if lines[i][:len('E-mail:')] == 'E-mail:':
                if e.get_text() == '':  pre = ''
                else:  pre = ', '
                e.append_text(pre+string.lstrip(lines[i][len('E-mail:'):]))


    ## Set the account name to be used here
    def select_from(self, button, name):
        self.account_name = name


    ##
    ## Method init_header (self)
    ##
    ##    Header widget setup.
    ##
    ##
    def init_header(self):
        targets = [
            ('text/plain', 0, -1)
            ]
	t = GtkTable(4,2,0)
        t.set_border_width(3)
	t.show()
        self.ht = t

        # Determine whether to display the From: field
        if len(self.prefs.accounts) <= 1:
            showfrom = 0
        else:
            showfrom = 1

        # Set the default account to use -- should be adaptive to
        # the recipient address if replying to a mail -- later
        self.account_name = self.prefs.accounts.keys()[0]
        
        # Determine which account is the default one
        e = self.prefs.accounts.keys()
        e.sort()
        if self.prefs.defacc and len(e) > 1:
            item = e.index(self.prefs.defacc)
            self.account_name = self.prefs.defacc
        else:
            item = 0

        # From: entry
 	l = GtkLabel("From")
	t.attach(l, 0,1,0,1, xoptions=FILL, xpadding=3)
        c = GtkOptionMenu()
        m = GtkMenu()

        # Generate menu of accounts
        for menu in e:
            menustr = "%s: %s <%s>" % \
                      (menu, self.prefs.accounts[menu][0],
                       self.prefs.accounts[menu][1])
            i = GtkMenuItem(menustr)
            i.show()
            i.connect('activate', self.select_from, menu)
            m.append(i)
        m.set_active(item)
        c.set_menu(m)
	t.attach(c, 1,2,0,1, xpadding=4, ypadding=1)

        # Only display the From: field if there is more than one account
        if showfrom:
            l.show()
            c.show()
	
	# To: entry
        l = GtkButton('To')
	l.show()
        l.unset_flags(CAN_FOCUS) 
        l.connect('clicked', self.add_to)
	t.attach(l, 0,1,1,2, xoptions=FILL, xpadding=3, ypadding=3)

        # DnD from GnomeCard for To:
	self.e1 = GtkEntry()
        self.e1.connect('drag_data_received', self.drag_drop_to)
        self.e1.drag_dest_set(DEST_DEFAULT_ALL, targets, GDK.ACTION_COPY)
	self.e1.show()
	t.attach(self.e1, 1,2,1,2, xpadding=5, ypadding=3)

	# Subject: entry
	l = GtkLabel("Subject")
	l.show()
        l.set_alignment(0.0, 0.5)
	t.attach(l, 0,1,2,3, xoptions=FILL, xpadding=3)
	
	self.e3 = GtkEntry()
	t.attach(self.e3, 1,2,2,3, xpadding=5, ypadding=3)
	self.e3.show()

        # The 'More' arrow button
        w = GnomeStock(STOCK_MENU_DOWN)
        w.show()
        h = GtkVBox()
        h.show()
        h.add(w)
        b = GtkButton()
        b.show()
        b.add(h)
        b.set_relief(RELIEF_NONE)
        b.unset_flags(CAN_FOCUS) 
        b.connect('clicked', self.more_toggle)
        t.attach(b, 2,3,2,3, xoptions=0)

	# Cc: entry
	self.l1 = GtkButton("Cc")
        self.l1.unset_flags(CAN_FOCUS) 
        self.l1.connect('clicked', self.add_cc)
	t.attach(self.l1, 0,1,3,4, xoptions=FILL, xpadding=3, ypadding=3)
	
        # DnD from GnomeCard for Cc:
	self.e2 = GtkEntry()
        self.e2.connect('drag_data_received', self.drag_drop_cc)
        self.e2.drag_dest_set(DEST_DEFAULT_ALL, targets, GDK.ACTION_COPY)
	t.attach(self.e2, 1,2,3,4, xpadding=5, ypadding=3)

        # BCC and from
 	self.l2 = GtkButton("Bcc")
        self.l2.unset_flags(CAN_FOCUS) 
        self.l2.connect('clicked', self.add_bcc)
	t.attach(self.l2, 0,1,4,5, xoptions=FILL, xpadding=3, ypadding=3)
	
	self.e4 = GtkEntry()
        self.e4.connect('drag_data_received', self.drag_drop_bcc)
        self.e4.drag_dest_set(DEST_DEFAULT_ALL, targets, GDK.ACTION_COPY)
	t.attach(self.e4, 1,2,4,5, xpadding=5, ypadding=3)

	# Pack the widgets together
        self.vbox.pack_start(t, expand=FALSE)

        # Only show if we have a Cc entry
        if self.has_cc:
            self.l1.show()
            self.e2.show()
            self.l2.show()
            self.e4.show()


    ## Toggle more or less information for headers
    def more_toggle(self, button=None):
        if not self.more:
            self.l1.show()
            self.e2.show()
            self.l2.show()
            self.e4.show()
            self.more = 1
            w = GnomeStock(STOCK_MENU_UP)
            w.show()
            h = GtkVBox()
            h.show()
            h.add(w)
            b = GtkButton()
            b.show()
            b.add(h)
            b.set_relief(RELIEF_NONE)
            b.connect('clicked', self.more_toggle)
            b.unset_flags(CAN_FOCUS) 
            self.ht.attach(b, 2,3,2,3, xoptions=0)
        else:
            self.l1.hide()
            self.e2.hide()
            self.l2.hide()
            self.e4.hide()
            self.more = 0
            w = GnomeStock(STOCK_MENU_DOWN)
            w.show()
            h = GtkVBox()
            h.show()
            h.add(w)
            b = GtkButton()
            b.show()
            b.add(h)
            b.set_relief(RELIEF_NONE)
            b.connect('clicked', self.more_toggle)
            b.unset_flags(CAN_FOCUS) 
            self.ht.attach(b, 2,3,2,3, xoptions=0)
        self.ht.queue_resize()


    ## Launch the Address List with callback to To:
    def add_to(self, button):
        self.field = 'to'
        self.fld.addresslist(None, self, self.win)


    ## Launch the Address List with callback to CC:
    def add_cc(self, button):
        self.field = 'cc'
        self.fld.addresslist(None, self, self.win)

    ## Launch the Address List with callback to Bcc:
    def add_bcc(self, button):
        self.field = 'bcc'
        self.fld.addresslist(None, self, self.win)


    ##
    ## Method destroy (self)
    ##
    ##    Destructor, clean up window stuff.
    ##
    ##
    def destroy(self, button=None, eventx=None):
        # If this is in edit mode, save the document again
        if self.mode == EDIT and self.mail_is_sent == 0:
            self.mail_is_sent = 1
            self.send_mail_later()
            
        self.ht.destroy()
        self.ed.destroy()
        self.win.destroy()

        # Decrease window reference counter
        self.fld.num_edits = self.fld.num_edits - 1

        # Destroy the main window if mailto is set
        if self.mailto != None:
            self.fld.prefs.mailnotify = self.fld.oldmailnotify
            self.fld.quit()


    def fill_paragraph(self, button=None):
	self.ed.format_paragraph( int(self.prefs.fillwidth ) )
        
	
    ##
    ## Method expand (self, to, cc, bcc)
    ##
    ##    Expand nicknames from the the address list.
    ##
    ##
    def expand(self, to, cc, bcc):
        import marshal
        import expand

        # Load address list
        try:
            alist = marshal.load(open(self.prefs.alistfile))
        except:
            # No defined address list, just return
            return 0

        # Expand names to cc: and to:
        for name in to:
            if alist.has_key(name):
                if self.prefs.expand:
                    e = expand.ExpandWindow(self.fld, name, alist[name], 'To:')
                    e.mainloop()
                    if self.fld.status == 0:
                        continue
                to[to.index(name)] = alist[name]

        for name in cc:
            if alist.has_key(name):
                if self.prefs.expand:
                    e = expand.ExpandWindow(self.fld, name, alist[name], 'Cc:')
                    e.mainloop()
                    if self.fld.status == 0:
                        continue
                cc[cc.index(name)] = alist[name]

        # Expand names for blind copy recipients as well
        for name in bcc:
            if alist.has_key(name):
                if self.prefs.expand:
                    e = expand.ExpandWindow(self.fld, name, alist[name], 'Bcc:')
                    e.mainloop()
                    if self.fld.status == 0:
                        continue
                bcc[bcc.index(name)] = alist[name]

        del alist


    ##
    ## Method send_mail_later (self, None)
    ##
    ##    Send mail later by appending to drafts folder (callback).
    ##
    ##
    def send_mail_later(self, button=None):
        self.send_mail(immediate=0)

    def privacy_dialog(self, button=None):
        self.privacy.parent = self.win
	self.privacy.dialog(self.fld, self.e1.get_text())


    ##
    ## Method send_mail (self, button, immediate)
    ##
    ##    Send mail message (callback).
    ##
    ##
    def send_mail(self, button=None, immediate=1):
        import string, time

        # Get the account we should use for sending the mail
        account = self.prefs.accounts[self.account_name]
        username, emailaddr, sigfile, replyaddr = account

        # Extract To and CC widget contents
        to = self.e1.get_text()
        cc = self.e2.get_text()
        bcc = self.e4.get_text()
        # Make the from address proper
        frm = mime_encode(username) +" <"+ emailaddr +">"
        if to == '':
            w = GnomeErrorDialog(err1)
            w.set_parent(self.win)
            w.show()
	    return 0

        # Add (B)CC to list of recipients
        to = string.split(string.replace(to, ', ', ','), ',')
        if cc != '':
            cc = string.split(string.replace(cc, ', ', ','), ',')
        else:
            cc = []
        if bcc != '':
            bcc = string.split(string.replace(bcc, ', ', ','), ',')
        else:
            bcc = []

        # Handle the case of "Bar, Foo" foo@bar address entries
        verify_split(to)
        verify_split(cc)
        verify_split(bcc)

        # Expand nicknames
        self.expand(to, cc, bcc)

        # Mimify the to and cc stuff
        newto = map(mime_encode, to)
        newcc = map(mime_encode, cc)
        newbcc = map(mime_encode, bcc)

        # Need to have separate recipients to handle to/cc/bcc correctly
        recp_tocc = 'To: %s\n' % string.join(to, ',\n    ')
        if cc != []:
            recp_tocc = recp_tocc + 'Cc: %s\n' % string.join(cc, ',\n    ')
        if bcc != []:
            recp_bcc = 'To: %s\n' % string.join(bcc, ',\n    ')
        headers = 'From: %s\n' % frm
        if replyaddr != '':
            headers = headers + 'Reply-To: %s\n' % mime_encode(replyaddr)
        headers = headers + 'Subject: %s\n' % mime_encode(self.e3.get_text())
        # No need to mime-encode this one since content is static
        headers = headers + 'X-Mailer: Pygmy (v%s)\n' % self.prefs.version

        # Set date header entry
        headers = headers + 'Date: %s\n' % \
                  mime_encode(time.strftime('%a, %d %b %Y %H:%M:%S %Z',
                                            time.localtime(time.time())))

        # Add the rest of the headers
        for h in self.headers.keys():
            headers = headers + '%s: %s\n' % (h, mime_encode(self.headers[h]))

        # Message body
        msg = ''
        if len(self.attachments) > 0:
            # Compose multipart message with inlined attachments
            s = cStringIO.StringIO()
            t = MimeWriter.MimeWriter(s)
            f = t.startmultipartbody('mixed')
            f.write("This is a multi-part message in MIME format.\n")
            # Write text body if there is any contents
            textlength = self.ed.get_length()
            if textlength > 0:
                p = t.nextpart()
                txt = self.ed.get_text()
                # Check if text needs quoting
                if filter(needsquoting, txt) != '' or string.find(txt, '\n.\n') != -1:
                    # Must quote the text
                    enc = 'quoted-printable'
                    p.addheader('Content-Transfer-Encoding', enc)
                    inbody = cStringIO.StringIO(txt)
                    outbody = cStringIO.StringIO()
                    mimetools.encode(inbody, outbody, enc)
                    txt = '\n' + outbody.getvalue()
                    del inbody, outbody
                    while string.find(txt, '\n.\n') != -1:
                        txt = string.replace(txt, '\n.\n', '\n=%s\n' % DOT)
                    txt = txt[1:] # Strip first newline character

                txt = self.privacy.handle_write(txt, self.send_mail)
                if not txt: # Will be None if e.g. passphrase was wrong
                    return
                b = p.startbody('text/plain')
                b.write(txt) 

            # Write all the attachments
            enc = 'base64'
            for name, type, sm in self.attachments:
                p = t.nextpart()
                p.addheader('Content-Transfer-Encoding', enc)
                filename = os.path.basename(name)
                b = p.startbody(type, [('name', filename)])
                if sm != None:
                    # A submessage may be included (e.g. for forward)
                    mimetools.encode(cStringIO.StringIO(sm.getbodytext()), b, enc)
                    del sm
                else:
                    # ... or just a file name reference (for new messages)
                    file = open(name)
                    mimetools.encode(file, b, enc)
                    file.close()
            t.lastpart()

            # Some clients need this to catch multipart msgs, e.g. pine
            msg = msg + 'Mime-Version: 1.0\n' + s.getvalue()
            s.close()
            del s, t, f
        else:
            # Compose singlepart message
            msg = '\n' + self.ed.get_text() 

            # Check if text needs quoting
            if filter(needsquoting, msg) != '' or string.find(msg, '\n.\n') != -1:
                # Must quote the text
                enc = 'quoted-printable'
                inbody = cStringIO.StringIO(msg)
                outbody = cStringIO.StringIO()
                mimetools.encode(inbody, outbody, enc)
                msg = outbody.getvalue()
                del inbody, outbody
                headers = headers + "Content-type: text/plain\n"
                headers = headers + "Content-Transfer-Encoding: %s\n" % enc
                # Must encode single dots on lines (quoted-printable)
                while string.find(msg, '\n.\n') != -1:
                    msg = string.replace(msg, '\n.\n', '\n=%s\n' % DOT)

	    # Need encryption or signature
	    msg = self.privacy.handle_write(msg, self.send_mail)
	    if msg == None: # Will be None if e.g. passphrase was wrong
		return

        # Send mail to target host if immediate send was specified
        if immediate:
            target = 'sent-mail'
            if self.prefs.usemailserver:
                # Use direct connection to mail server
                message = recp_tocc + headers + msg
                if not sendmail.sendmail_server(self.prefs.serverentry, frm,
                                                to+cc, message):
                    return 0
                if bcc != []:
                    message = recp_bcc + headers + msg
                    if not sendmail.sendmail_server(self.prefs.serverentry, frm,
                                                    bcc, message):
                        return 0
            elif self.prefs.usesendmail:
                # Use sendmail command at localhost
                message = recp_tocc + headers + msg
                if not sendmail.sendmail_cmd(self.prefs.sendmailentry, message):
                    return 0                
                if bcc != []:
                    message = recp_bcc + headers + msg
                    if not sendmail.sendmail_cmd(self.prefs.sendmailentry, message):
                        return 0
            # Popup that the message was successfully sent/queued
            if self.prefs.confirmsend:
                w = GnomeOkDialog(pop1)
                w.set_parent(self.win)
                w.show()
        else:
            target = 'drafts'
            # Notify the user that the message is drafted
            if self.prefs.confirmsend:
                w = GnomeOkDialog(pop2)
                w.set_parent(self.win)
                w.show()
            
        # Add a date header and the From line at the top of the message
        prefix = 'From %s  %s\n' % (emailaddr, time.ctime(time.time()))

        # Open the target folder and store the message there
        fo = open(self.prefs.folders+'/'+target, 'a')
        fo.write(prefix)
        fo.write(recp_tocc)
        if not immediate:
            # Write the bcc entry if the mail is to be sent later
            # so we can reconstruct the bcc field if the mail is edited
            fo.write('Bcc: %s\n' % string.join(bcc, ',\n    '))
        fo.write(headers)
        fo.write(msg)
        fo.write('\n\n')
        fo.close()

        # Update the folder index for the target folder
        pathname = folderops.get_folder_pathname(self.prefs.folders,
                                                 target)
        folderops.update_folder_index(pathname)
        self.fld.unread[target] = self.fld.unread[target] + 1
        self.fld.total[target] = self.fld.total[target] + 1

        # Update folder if currently viewed in list
        if self.fld.mbox == target:
            self.fld.update_folderview(self.fld.mbox)

        # Update folder tree view
        self.fld.update_foldertree_nodes([target])

        # Check if we should call destroy
        if self.mail_is_sent == 0:
            self.mail_is_sent = 1
            self.destroy()
	return 1



##
## Function mime_encode (line)
##
##    Encode mime headers
##
##
def mime_encode(line):
    import re, string
    reg = re.compile('[\t\n=?\177-\377]') # quote these in header
    newline = ''
    pos = 0
    while 1:
        res = reg.search(line, pos)
        if res is None:
            break
        newline = newline + line[pos:res.start(0)] + \
                  string.upper('=%02x' % ord(res.group(0)))
        pos = res.end(0)

    # This means we had no mime-chars, just return the original
    if pos == 0:
        return line

    line = newline + line[pos:]
    # Quote spaces as well (we don't want these to be encoded otherwise)
    line = string.replace(line, ' ', '_')
    # Return an quoted-printable armoured version of the line
    return '=?%s?Q?%s?=' % (mimify.CHARSET, line)
