## $Id: msg.py,v 1.89 2001/10/10 15:11:40 kjetilja Exp $

## System modules
from gtk import *
from gnome.ui import *
import gnome.mime
import rfc822, string, mimify
import os, cStringIO

## Local modules
import folderops, fileops, edit, headers, mime, addresslist, privacy, fonts
from prefs import MSG_SIZE

## Try to load the GtkHTML rendering widget
try:
    from html import HtmlWindow
    gtkhtml_present = 1
except:
    gtkhtml_present = 0

## Error messages from the GUI
err1 = """Error opening destination file"""
err2 = """No viewer specified for type '%s'
Define a viewer, or save the attachment and view manually"""
err3 = """Unable to store attachment file"""
err4 = """Unable to start viewer: %s"""
err5 = """Unable to save attachment as %s"""


##
##
## Message (view) window class
##
##
class MsgWindow:
    
    ##
    ## Method __init__ (self, folder window instance, msg instance)
    ##
    ##    Message widget constructor.
    ##
    ##
    def __init__(self, fld, msg, inline):
        self.fld = fld
        self.prefs = fld.prefs
        self.msg = msg
        self.hdr = msg.headers
        self.multipart = 0
        self.mime = 0
        self.inline = inline
        self.tmpfiles = []
	self.privacy = self.fld.privacy
        self.gtkhtml = gtkhtml_present and self.prefs.gtkhtml

        # Font styles
        self.bold_font = load_font(fonts.BOLD_FONT)
        self.normal_font = load_font(fonts.NORMAL_FONT)
       
        # Make a separate window to play in
        self.vbox = GtkVBox()
        self.vbox.show()

        # View inline
        if not inline:
            self.win = GnomeApp('Pygmy Viewer', ':Pygmy - View Mail Message')
            self.win.set_wmclass('pygmy viewer', ':Pygmy - View Mail Message')
            self.win.set_border_width(5)
            self.win.connect_after('size_allocate', self.resize)
            apply(self.win.set_default_size, self.prefs.size[MSG_SIZE])
            self.win.set_contents(self.vbox)
            self.win.show()
            self.win.create_menus(self.create_menu())
            self.win.create_toolbar(self.create_toolbar())
        else:
            self.win = None
            self.fld.vpaned.pack2(self.vbox)
            
        # Initialize window
        self.init_msg_window()

        # Setup the headers
        self.init_contents()

        # Set viewpoint to start of text widget
        if not self.gtkhtml:
            self.msg_text.get_vadjustment().set_value(0)

        # Make the text widget grab focus if not inline viewer
        if not inline:
            self.msg_text.grab_focus()

        # View more headers if configured
        self.prefs.more = not self.prefs.more
        self.more_toggle()
            

    ## Catch resize events which are saved later
    def resize(self, w=None, a=None, c=None):
        size = w.get_allocation()
        if w == self.win:
            self.prefs.size[MSG_SIZE] = size[2:4]
        

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

        file_menu = [
            UIINFO_ITEM_STOCK('Save As...', None, self.saveas, STOCK_MENU_SAVE_AS),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Close', None, self.destroy, STOCK_MENU_QUIT)
            ]
        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),
            ]
        msg_menu = [
            UIINFO_ITEM_STOCK('Reply', None, self.reply_callback,
                              STOCK_MENU_MAIL_RPL),
            UIINFO_ITEM_STOCK('Reply All', None, self.replyall_callback,
                              PYGMY_ICONDIR+"/reply_to_all_menu.xpm"),
            UIINFO_ITEM_STOCK('Forward', None, self.fwd_callback,
                              STOCK_MENU_MAIL_FWD),
            UIINFO_ITEM_STOCK('View All Headers', None, self.all_headers,
                              STOCK_MENU_BLANK), 
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Previous', None, self.prev_msg_callback,
                              STOCK_MENU_BACK),
            UIINFO_ITEM_STOCK('Next', None, self.next_msg_callback,
                              STOCK_MENU_FORWARD),
           ]
        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):
        from prefix import PYGMY_ICONDIR

        toolbar_info = [
            UIINFO_ITEM_STOCK('Reply', None, self.reply_callback,
                              STOCK_PIXMAP_MAIL_RPL),
            UIINFO_ITEM_STOCK('Reply All', None, self.replyall_callback,
                              PYGMY_ICONDIR+"/reply_to_all.xpm"),
            UIINFO_ITEM_STOCK('Forward', None, self.fwd_callback,
                              STOCK_PIXMAP_MAIL_FWD),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Previous', None, self.prev_msg_callback,
                              STOCK_PIXMAP_BACK),
            UIINFO_ITEM_STOCK('Next', None, self.next_msg_callback,
                              STOCK_PIXMAP_FORWARD),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Headers', None, self.all_headers,
                              STOCK_PIXMAP_MAIL),
            UIINFO_SEPARATOR,
            UIINFO_ITEM_STOCK('Close', None, self.destroy,
                              STOCK_PIXMAP_CLOSE),
            ]
        return toolbar_info


    ##
    ## Method next_msg_callback ()
    ##
    def next_msg_callback(self, button):
        # This is to be able to browse from folder view window
        if self.fld.fdisp.selection == []:
            if self.fld.numrows >= 1:  self.fld.row = -1
            else:  return
        else:
            self.fld.row = self.fld.fdisp.selection[0]
        if self.fld.row+1 < self.fld.numrows:
            self.fld.row = self.fld.row + 1
            self.fld.fdisp.unselect_row(self.fld.row-1, 0)
            self.fld.fdisp.select_row(self.fld.row, 0)
            self.fld.fdisp.moveto(row=self.fld.row)
            self.fld.view_mail(self.fld.row, inline=0, msgwin=self)
            self.msg_text.grab_focus()


    ##
    ## Method prev_msg_callback ()
    ##
    def prev_msg_callback(self, button):
        # This is to be able to browse from folder view window
        if self.fld.fdisp.selection == []:
            return
        self.fld.row = self.fld.fdisp.selection[0]
        if self.fld.row-1 >= 0:
            self.fld.row = self.fld.row - 1
            self.fld.fdisp.unselect_row(self.fld.row+1, 0)
            self.fld.fdisp.select_row(self.fld.row, 0)
            self.fld.fdisp.moveto(row=self.fld.row)
            self.fld.view_mail(self.fld.row, inline=0, msgwin=self)
            self.msg_text.grab_focus()


    ##
    ## Method saveas (self, b)
    ##
    ##    Save the currently viewed message to disk.
    ##
    ##
    def saveas(self, button=None):
        import fileops
        fname = fileops.getfilename( self.prefs.filepaths, '', 'Save message to file...', 'save-message')
        if fname == None:  return
        try:
            f = open(fname, 'w')
        except:
            # Unable to open file, show error
            w = GnomeErrorDialog(err1)
            w.set_parent(self.win)
            w.show()
        else:
            # Blast the message into the file
            import types
            if type(self.msg.fp) != types.FileType:
                f.write(self.msg.fp.getvalue())
            else:
                pathname = folderops.get_folder_pathname(self.prefs.folders, self.msg.mbox)
                m = open(pathname)
                m.seek(self.msg.start)
                f.write(m.read(self.msg.stop-self.msg.start))
                m.close()
            f.close()


    ##
    ## Method {sel|usel}_all (self, b)
    ##
    ##    Select/Unselect all functions.
    ##
    ##
    def sel_all(self, b):
        if not self.gtkhtml:
            self.msg_text.select_region(0, self.msg_text.get_length())
        else:
            print 'not implemented for gtkhtml yet'
            

    ##
    ## Method edit_{cut|copy|paste} (self, b)
    ##
    ##    Clipboard functions.
    ##
    ##
    def edit_cut(self, b):
        if not self.gtkhtml:
            self.msg_text.cut_clipboard()
        else:
            print 'not implemented for gtkhtml yet'
        
    def edit_copy(self, b):
        if not self.gtkhtml:
            self.msg_text.copy_clipboard()
        else:
            print 'not implemented for gtkhtml yet'

    def edit_paste(self, b):
        if not self.gtkhtml:
            self.msg_text.paste_clipboard()
        else:
            print 'not implemented for gtkhtml yet'


    ## Reply callback
    def reply_callback(self, button=None):
        edtwin = edit.EditWindow(self.fld, edit.REPLY, self.msg)
        edtwin.setup_widgets()
        self.destroy()


    ## Forward callback
    def fwd_callback(self, button=None):
        edtwin = edit.EditWindow(self.fld, edit.FORW, self.msg)
        edtwin.setup_widgets()
        self.destroy()


    ## Reply all callback
    def replyall_callback(self, button=None):
        edtwin = edit.EditWindow(self.fld, edit.REPLYALL, self.msg)
        edtwin.setup_widgets()
        self.destroy()


    ## View all headers callback
    def all_headers(self, button=None):
        headers.HeaderWindow(self.hdr, self.win)


    ## Construct the buttons used for attachments
    def open_att_button(self, next, t):
        # Open attachment button
        w = GnomeStock(STOCK_MENU_OPEN)
        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.view_gnome)
        t.attach(b, 1, 2, next, next+1, xoptions=0, yoptions=0,
                 xpadding=3)


    def save_att_button(self, next, t):
        w = GnomeStock(STOCK_MENU_SAVE)
        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.save)
        t.attach(b, 2, 3, next, next+1, xoptions=0, yoptions=0,
                 xpadding=3)


    
    ##
    ## Method init_contents ()
    ##
    def init_contents(self):
        msg = self.msg
        fld = self.fld
        self.current_attachment = None
        self.attachments = []
        
        # Update the folder index status field
        status = folderops.STATUS_READ
        # Set message status to 'read' if this message is unread
        if msg.msg_unread:
            pathname = folderops.get_folder_pathname(fld.prefs.folders,
                                                     msg.mbox)
            folderops.update_folder_index_status(pathname, str(msg.start),
                                                 status)
            pos, rest =  fld.fdisp.get_row_data(fld.row)
            rest[0] = status
            fld.fdisp.set_row_data(fld.row, (pos,rest))
            fld.fdisp.set_text(fld.row, 3, status)
            style = fld.fdisp.get_style().copy()
            style.font = fld.sub_nfont
            fld.fdisp.set_row_style(fld.row, style)
        
        # Shortcut some references
        m = self.msg
        f = self.fld

        # Construct To: string
        to = rfc822.AddressList(m.getheader('to'))
        tostr = []
        for i in to.addresslist: tostr.append(i[1])
        if len(tostr) == 0 or (len(tostr) != 0 and tostr[0] == None):
            # To: is missing, insert the email address in the default account instead
            if self.prefs.defacc:
                tostr = [self.prefs.accounts[self.prefs.defacc][1]]
            else:
                if len(self.prefs.accounts) > 0:
                    tostr = [self.prefs.accounts.keys()[0][1]]
                else:
                    tostr = "<Unknown>"

        self.e4.set_text(mimify.mime_decode_header(string.join(tostr, ', '))[:70])

        # Construct Cc: string
        cc = rfc822.AddressList(m.getheader('cc'))
        ccstr = []
        for i in cc.addresslist: ccstr.append(i[1])
        if len(ccstr) != 0 and ccstr[0] != None:
            ccstr = string.join(ccstr, ', ')
            self.e5.set_text(mimify.mime_decode_header(ccstr)[:70])
            if self.prefs.more:
                self.l2.show()
                self.e5.show()
            self.has_cc = 1
        else:
            self.e5.set_text('')
            self.e5.hide()
            self.l2.hide()
            self.has_cc = 0
                
        # Get from, subject and date
        self.frm = folderops.get_from(m)
        self.mail_field = self.frm
        sub = folderops.get_subject(m)
        date = m.getheader('date') or ''

        # Insert default header entries
        self.e1.set_text(mimify.mime_decode_header(sub)[:60])
        self.e2.set_text(mimify.mime_decode_header('%s <%s>' %\
                                                   (self.frm[0], self.frm[1])))
        self.e3.set_text(date)

        # Clear the text area
        if not self.gtkhtml:
            self.msg_text.freeze()
            self.msg_text.delete_text(0, -1) # Clear
        else:
            self.msg_text.load_empty()

        # Container for attachments that are not viewed inline
        if not hasattr(self, 't'):
            t = GtkTable(1,3,0)
            c = GtkOptionMenu()
            t.attach(c, 0,1,0,1, xpadding=4, ypadding=3)
            self.t = t
            self.c = c
            self.vbox.pack_start(t, expand=0)
            # Open attachment button
            self.open_att_button(0, t)
            # Save attachment button
            self.save_att_button(0, t)
        else:
            t = self.t
            c = self.c

        t.show()
        c.show()

        menu = GtkMenu()
        menu.show()

        # Check for messages types, single or multipart
        if m.getmaintype() == 'multipart':
            self.multipart = 1
            self.mime = 0

            # Multipart message
            try:
                p = mime.find_all_parts(m.getbodyparts(), new_parts=[])
            except SystemError:
                # Unable to parse the mail
                self.insert_body_text("This mail has malformed content", 'text/plain')
                self.msg_text.thaw()
                t.hide()
                return
            
            total_num = len(p)
            current_num = 1
            first_not_inline = None
            for sm in p:
                type = sm.gettype()
                shown = 0
                # Display some types inline
                if type == 'text/plain' or type == 'text/html':
                    # Plain text, print in the message window
                    shown = 1
                    try:
                        txt = sm.getbodytext()
                    except:
                        txt = 'Message contains null-bytes and will not be displayed'
		    # Check if message has been encrypted or signed
		    txt = self.privacy.handle_read(txt, self.init_contents)
                    self.insert_body_text(txt, type)
                    del txt
                elif type == 'application/pgp-signature':
                    # Do not search for boundaries here since they may be invalid
                    # when the mime-type is specified
                    try:
                        txt = sm.getbodytext()
                    except:
                        txt = 'Message contains null-bytes and will not be displayed'
                    self.privacy.decrypt(txt)
                    del txt

                # Register the first attachment which is not inline
                if first_not_inline == None and shown == 0:
                    first_not_inline = current_num - 1

                name = mimify.mime_decode_header((sm.getparam('name') or\
                                                  edit.NONAME))[:40]
                if len(name) > 36:
                    namestr = ".." + name[-36:]
                else:
                    namestr = name
                menustr = "%s (%s, part %d of %d)" % (namestr, type,
                                                      current_num, total_num)
                if shown:
                    menustr = menustr + ", shown"
                i = GtkMenuItem(menustr)
                i.show()
                i.connect('activate', self.set_current_attachment, (name, type, sm))
                menu.append(i)
                self.attachments.append((name, type, sm))
                current_num = current_num + 1

            # Ensure the attachment menu index is actually set
            if first_not_inline == None:
                first_not_inline = 0

            # Set the default entry in the attachment menu
            menu.set_active(first_not_inline)
            # Set current attachment
            self.current_attachment = self.attachments[first_not_inline]

            c.set_menu(menu)
            t.queue_draw()
            self.vbox.queue_resize()
            self.vbox.queue_draw()
        else:
            # We have a single part, possibly mime-encoded message
            valid_types = ['text/plain', 'text/html']
            type = m.gettype() or 'text/plain'
            shown = type in valid_types
            self.mime = 1
            self.multipart = 0

            # Need to add a backref here
            m.fp.seek(m.startofbody)
  	    self.body = m.fp.read(m.stop-m.startofbody)
            type = m.gettype()
            name = mimify.mime_decode_header((m.getparam('name') or\
                                              edit.NONAME))[:40]
            if len(name) > 36:
                namestr = ".." + name[-36:]
            else:
                namestr = name
            menustr = "%s (%s, part 1 of 1)" % (namestr, type)
            if shown:
                menustr = menustr + ", shown"
            i = GtkMenuItem(menustr)
            i.show()
            i.connect('activate', self.set_current_attachment, (name, type, m))
            menu.append(i)
            self.attachments.append((name, type, m))
            # Set current attachment
            self.current_attachment = self.attachments[0]
            c.set_menu(menu)
            t.queue_draw()
            self.vbox.queue_resize()
            self.vbox.queue_draw()

	    # Decode encoded messages
	    encoding = m.getencoding()
	    if encoding == 'quoted-printable':
		self.body = mime.decode_quoted_printable(self.body)
	    elif encoding == 'base64':
		self.body = mime.decode_base64(self.body) 

            # Insert message if it can be viewed inside
            if shown:
                self.body = self.privacy.handle_read(self.body, self.init_contents)
                self.insert_body_text(self.body, type)

        if self.gtkhtml:
            self.msg_text.end_insert()
        self.msg_text.thaw()


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


    def insert_body_text(self, text, type):
        if not self.gtkhtml:
            self.msg_text.insert(None, None, None, text)
        else:
            self.msg_text.insert(text, type)



    ##
    ## Method save (self, button)
    ##
    ##    Save an attachment to disk.
    ##
    ##
    def save(self, button):
        # Get a filename to save as
        name, type, message = self.current_attachment
	attname = mimify.mime_decode_header(message.getparam('name') or '')
        savename = fileops.getfilename( self.prefs.filepaths, attname or 'unnamed', 'Save attachment as...', 'save-attachment' )
        if savename == None:
            # No filename given
            return
        try:
            f = open(savename, 'w')
        except:
            w = GnomeErrorDialog(err5 % savename)
            w.set_parent(self.fld.win)
            w.show()
            return

        # Save the thing to disk
        if self.mime:
            body = self.body
        else:
            body = message.getbodytext()
        f.write(body)
        f.close()


    ##
    ## Method view_gnome (self, button)
    ##
    ##    View an attachment, creates temp file and starts viewer app.
    ##
    ##
    def view_gnome(self, button):
        name, type, message = self.current_attachment

        # Insert name if no name is specified
        if name == edit.NONAME:
            name = 'unnamed'

        # Make a temporary file with the attachment contents
        import tempfile
        fn = tempfile.mktemp()+os.path.basename(name)
        try:
            f = open(fn, 'w')
        except:
            w = GnomeErrorDialog(err3)
            w.set_parent(self.fld.win)
            w.show()
            return
        if self.mime:
            body = self.body
        else:
            body = message.getbodytext()
        f.write(body)
        f.close()

        # Log the file to be deleted when the widget exits
        self.tmpfiles.append(fn)

        # Determine mime-type and which application to invoke
        if type == 'message/rfc822':
            # Instantiate another message viewer window and throw in
            # the message for display
            m = mime.Message(cStringIO.StringIO(body), 0, len(body))
            m.msg_unread = 0
            MsgWindow(self.fld, m, 0)
            return
        else:
            # Use the regular method of finding the name/application
            gtype = gnome.mime.type_or_default_of_file(os.path.basename(name), type)
            gtypekeys = gnome.mime.get_keys(gtype)
            if 'open' in gtypekeys:
                prog = gnome.mime.program(gtype)
                if not prog:
                    w = GnomeErrorDialog(err2 % type)
                    w.set_parent(self.fld.win)
                    w.show()
                    return
            else:
                w = GnomeErrorDialog(err2 % type)
                w.set_parent(self.fld.win)
                w.show()
                return

	# Strip off %f at the end of the programs name
	prog = prog[:-2]
	cmd = prog + "'" + fn + "' 2>/dev/null &"

	# Lauch program
	os.system(cmd)



    ##
    ## Method destroy (self)
    ##
    ##    Destructor, clean up window stuff.
    ##
    ##
    def destroy(self, button=None):            
        # Take out the main stuff and mime
        self.msg_text.destroy()
        self.ht.destroy()
        self.swin.destroy()
        if self.multipart or self.mime:
            try: self.t.destroy()
            except: pass
            self.t = None
        self.vbox.destroy()
        del self.msg
        
        # Remove the body text cache
        try: del self.body
        except: pass

        # Remove attachments
        try: self.attachments = []
        except: pass
        self.current_attachment = None

        # Wipe out temporary files
        for file in self.tmpfiles:
            try:  os.unlink(file)
            except:  pass
        self.tmpfiles = []
            
        # Crunch the message window and update the pane of inline
        if self.inline:
            self.fld.msgwin = None
            self.fld.vpaned.set_position(-1)
        else:
            self.win.destroy()
        

    ##
    ## Method display_headers (self)
    ##
    ##    Create widgets for viewing header contents.
    ##
    ##
    def display_headers(self):
	t = GtkTable(4,3,0)
        t.set_border_width(3)
	t.show()
        self.ht = t
        self.vbox.pack_start(self.ht, expand=FALSE)
        
	l = GtkLabel("Subject")
        l.set_alignment(0.0, 0.5)
        style = l.get_style().copy()
        style.font = self.bold_font
        l.set_style(style)
	l.show()
	t.attach(l, 0,1,0,1, xoptions=FILL, xpadding=3)
	self.e1 = GtkLabel()
        self.e1.set_alignment(0.0, 0.5)
	self.e1.show()
	t.attach(self.e1, 1,2,0,1, xoptions=EXPAND|FILL, xpadding=10)

        if self.inline:
            w = GnomeStock(STOCK_MENU_CLOSE)
            w.show()
            h = GtkVBox()
            h.show()
            h.add(w)
            b = GtkButton()
            b.show()
            b.add(h)
            b.set_relief(RELIEF_NONE)
            b.set_border_width(0)
            b.connect('clicked', self.destroy)
            t.attach(b, 2,3,0,1, xoptions=0)

	l = GtkLabel("From")
        l.set_alignment(0.0, 0.5)
        style = l.get_style().copy()
        style.font = self.bold_font
        l.set_style(style)
	l.show()
	t.attach(l, 0,1,1,2, xoptions=FILL, xpadding=3)
	self.e2 = GtkLabel()
        self.e2.set_alignment(0.0, 0.5)
	self.e2.show()
        
        b = GtkButton()
        b.show()
        b.add(self.e2)
        b.set_relief(RELIEF_NONE)
        b.set_border_width(0)
        b.connect('clicked', self.add_to_address)
	t.attach(b, 1,2,1,2, xoptions=EXPAND|FILL, xpadding=7)

        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)

        t.attach(b, 2,3,1,2, xoptions=0)

        # Optional headers
        self.l0 = GtkLabel("Date")
        self.l0.set_alignment(0.0, 0.5)
        style = self.l0.get_style().copy()
        style.font = self.bold_font
        self.l0.set_style(style)
        self.e3 = GtkLabel()
        self.e3.set_alignment(0.0, 0.5)
        self.ht.attach(self.l0, 0,1,2,3, xoptions=FILL, xpadding=3)
        self.ht.attach(self.e3, 1,2,2,3, xoptions=EXPAND|FILL, xpadding=10)

        self.l1 = GtkLabel("To")
        self.l1.set_alignment(0.0, 0.5)
        style = self.l1.get_style().copy()
        style.font = self.bold_font
        self.l1.set_style(style)
        self.e4 = GtkLabel()
        self.e4.set_alignment(0.0, 0.5)
        self.ht.attach(self.l1, 0,1,3,4, xoptions=FILL, xpadding=3)
        self.ht.attach(self.e4, 1,2,3,4, xoptions=EXPAND|FILL, xpadding=10)

        self.l2 = GtkLabel("Cc")
        self.l2.set_alignment(0.0, 0.5)
        style = self.l2.get_style().copy()
        style.font = self.bold_font
        self.l2.set_style(style)
        self.e5 = GtkLabel()
        self.e5.set_alignment(0.0, 0.5)
        self.ht.attach(self.l2, 0,1,4,5, xoptions=FILL, xpadding=3)
        self.ht.attach(self.e5, 1,2,4,5, xoptions=EXPAND|FILL, xpadding=10)


    ##
    ## Method more_toggle (self, button=None)
    ##
    ##    Toggle whether to give extended header list.
    ##
    ##
    def more_toggle(self, button=None):
        if not self.prefs.more:
            if self.has_cc:
                self.e5.show()
                self.l2.show()
            self.e4.show()
            self.l1.show()
            self.e3.show()
            self.l0.show()
            self.prefs.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)
            self.ht.attach(b, 2,3,1,2, xoptions=0)
        else:
            if self.has_cc:
                self.e5.hide()
                self.l2.hide()
            self.e4.hide()
            self.l1.hide()
            self.e3.hide()
            self.l0.hide()
            self.prefs.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)
            self.ht.attach(b, 2,3,1,2, xoptions=0)
        self.ht.queue_resize()
        

    ##
    ## Method init_msg_window (self)
    ##
    ##    Initialize message display window.
    ##
    ##
    def init_msg_window(self):
        # Build the header widget
        self.display_headers()

	# Message body widget
        if self.gtkhtml:
            self.msg_text = HtmlWindow(self)
        else:
            self.msg_text = GtkText()
            self.msg_text.set_editable(FALSE)
            self.msg_text.set_word_wrap(TRUE)
            self.msg_text.show()

            style = self.msg_text.get_style().copy()
            style.font = load_font(self.prefs.msg_font)
            self.msg_text.set_style(style)

        # Crank text widget into a scrolledwindow
        self.swin = GtkScrolledWindow()
        self.swin.set_policy(POLICY_NEVER, POLICY_AUTOMATIC)
        self.vbox.pack_start(self.swin)
        self.swin.add(self.msg_text)
        self.swin.show()


    ##
    ##
    ## Method add_to_address ()
    ##
    def add_to_address(self, button=None):
        # Launch address list window
        adr = addresslist.AddressListWindow(self.fld, None, self.win)

        # Update GUI to ensure we get the list window up before the popup
        while events_pending():
            mainiteration(FALSE)

        # Force a popup with the name and address filled in
        adr.add_name_and_edit(self.frm[0], self.frm[1])
