## $Id: filter.py,v 1.8 2001/05/02 12:35:36 kjetilja Exp $

## System modules
from gtk import *
from gnome.ui import *
import string, marshal, re

## Local modules
import folderops

# Actions to take if filter matches
ACTION_FORWARD = 1
ACTION_DELETE  = 2
ACTION_MOVE    = 3
ACTION_SCORE   = 4
ACTION_NONE    = 5

ENTRY_TX = 0
ENTRY_IN = 1
ENTRY_AC = 2
ENTRY_TO = 3
ENTRY_RE = 4

actions = {
    ACTION_FORWARD : 'Forward to: ',
    ACTION_DELETE  : 'Delete',
    ACTION_MOVE    : 'Move to folder: ',
    ACTION_SCORE   : 'Score: ',
    ACTION_NONE    : 'No action',
    }


class FilterEditWindow:


    def __init__(self, parent, fld, clicked_callback):
        self.fld = fld
        self.prefs = self.fld.prefs
        
        # Wigdgets
        self.text_entry = None
        self.in_combo = None
        
        w = GnomeDialog('Edit', 'Ok', 'Cancel')
        self.win = w
        w.set_parent(parent)
        
        w.connect('clicked', clicked_callback)

        self.init_match()
        self.init_action()
        
        w.vbox.pack_start(self.matchframe)
        w.vbox.pack_start(self.actionframe)
        w.show()


    ##
    ## Method init_match (self)
    ##
    ##    Widgets in the match frame.
    ##
    ##
    def init_match(self):
        frame = GtkFrame('Match')
        frame.set_border_width(5)
        frame.show()
        t = GtkTable(2,2,0)
        t.show()
	ln = GtkLabel("Text:")
	ln.show()
	text = GtkEntry()
        self.text_entry = text
        text.show()
        
	le = GtkLabel("In:")
	le.show()
	ic = GtkCombo()
        self.in_combo = ic
        ic.entry.set_editable(FALSE)
        ic.show()
        ic.set_popdown_strings(['To:',
                                'From:',
                                'Sender:',
                                'Subject:',
                                'Cc:',
                                'Any recipient'])
        
        t.attach(ln, 0, 1, 0, 1, yoptions=0, ypadding=3, xpadding=5)
        t.attach(text, 1, 2, 0, 1, xpadding=10, yoptions=0, ypadding=5)
        t.attach(le, 0, 1, 1, 2, yoptions=0, ypadding=3, xpadding=5)
        t.attach(ic, 1, 2, 1, 2, xpadding=10, yoptions=0, ypadding=5)
        frame.add(t)
        self.matchframe = frame


    ##
    ## Method init_action (self)
    ##
    ##    Widgets in the action frame.
    ##
    ##
    def init_action(self):
        frame = GtkFrame('Action')
        frame.set_border_width(5)
        frame.show()
        t = GtkTable(2,3,0)
        t.show()

        button1 = GtkRadioButton(None, "Move to folder:")
        self.move_button = button1
        button1.show()
        
        button2 = GtkRadioButton(button1, "Forward to:")
        self.forward_button = button2
        button2.show()
        
        button3 = GtkRadioButton(button1, "Delete message")
        self.delete_button = button3
        button3.show()

        button4 = GtkRadioButton(button1, "No action")
        self.none_button = button4
        button4.show()

        forw = GtkEntry()
        self.forward_entry = forw
        forw.show()
        
        from posixpath import split
        flds = self.fld.total.keys()
        for fname in self.prefs.default_folders:
            flds.remove(fname)
        flds.sort()

	move = GtkCombo()
        self.move_combo = move
        move.entry.set_editable(FALSE)
        if len(flds) > 0:
            move.set_popdown_strings(flds)
        move.show()

        t.attach(button1, 0, 1, 0, 1, yoptions=0, ypadding=3, xpadding=5)
        t.attach(button2, 0, 1, 1, 2, yoptions=0, ypadding=3, xpadding=5)
        t.attach(button3, 0, 1, 2, 3, yoptions=0, ypadding=3, xpadding=5)
        t.attach(button4, 0, 1, 3, 4, yoptions=0, ypadding=3, xpadding=5)
        t.attach(forw,    1, 2, 1, 2, yoptions=0, ypadding=3, xpadding=5)
        t.attach(move,    1, 2, 0, 1, yoptions=0, ypadding=3, xpadding=5)
 
        frame.add(t)
        self.actionframe = frame


    ##
    ## Method set_values ()
    ##
    def set_filter(self, fi):
        text, _in, action, to, = fi
        if text:
            self.text_entry.set_text(text)
        if _in:
            self.in_combo.entry.set_text(_in)

        ## Actions
        if action == ACTION_FORWARD and to:
            self.forward_entry.set_text(to)

        if action == ACTION_MOVE:
            self.move_button.set_active(1)

        if action == ACTION_DELETE:
            self.delete_button.set_active(1)

        if action == ACTION_NONE:
            self.none_button.set_active(1)

        if action == ACTION_FORWARD:
            self.forward_button.set_active(1)
        
        if action == ACTION_MOVE and to:
            self.move_combo.entry.set_text(to)
            
    def get_filter(self):
        text = self.text_entry.get_text()
        _in = self.in_combo.entry.get_text()
        
        # Read the setting of the radiobuttons
        if self.move_button.get_active():
            action = ACTION_MOVE
            parm = self.move_combo.entry.get_text()
        elif self.forward_button.get_active():
            action = ACTION_FORWARD
            parm = self.forward_entry.get_text()
        elif self.delete_button.get_active():
            action = ACTION_DELETE
            parm = ''
        elif self.none_button.get_active():
            action = ACTION_NONE
            parm = ''
                
        return (text, _in, action, parm)


##
##
## Main filter window class
##
##
class FilterWindow:


    ##
    ## Method __init__ (self, folder window class instance)
    ##
    ##    Filter window constructor.
    ##
    ##
    def __init__(self, fld):
        self.fld = fld
        self.prefs = self.fld.prefs
        self.win = GnomeDialog(':Pygmy - Edit Filters',
                               'Add', 'Edit', 'Remove',
                               STOCK_BUTTON_OK, STOCK_BUTTON_CANCEL)
        self.win.set_parent(self.fld.win)
        self.vbox = self.win.vbox
        self.win.connect('clicked', self.handle_callbacks)
	self.win.connect('delete_event', self.destroy)
	self.win.connect('destroy', self.destroy)

        self.init_filterlist()
        self.win.show()


    def init_filterlist(self):
        hbox = GtkHBox()
        hbox.show()
        self.vbox.pack_start(hbox)
        
        self.fdisp = GtkCList(3, ['Text', 'In', 'Action'])
        self.fdisp.connect("click_column", self.clickcolumn)
        self.fdisp.connect("select_row", self.selectrow)
        
        # Widget attributes
        self.fdisp.set_selection_mode(SELECTION_SINGLE)
        self.fdisp.set_column_width(0, 150)
        self.fdisp.set_column_width(1, 90)
        self.fdisp.set_column_width(2, 150)
        self.fdisp.set_border_width(2)
	self.fdisp.set_usize(0, 256)

        # Add scrollbars
        swin = GtkScrolledWindow()
        swin.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
        hbox.pack_start(swin)
        swin.add(self.fdisp)
        swin.show()

        # Add navigation buttons
        vbox = GtkVBox()
        vbox.show()
        w = GnomeStock(STOCK_PIXMAP_UP)
        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.up_callback)

        tt = GtkTooltips()
        tt.set_tip(b, "Increase priority", "inc")
        
        vbox.pack_start(b, expand=TRUE, fill=FALSE)
        w = GnomeStock(STOCK_PIXMAP_DOWN)
        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.down_callback)

        tt = GtkTooltips()
        tt.set_tip(b, "Decrease priority", "dec")
        
        vbox.pack_start(b, expand=TRUE, fill=FALSE)
        hbox.pack_start(vbox, FALSE, FALSE)
        
        self.fdisp.show()
        # Store reference to filter list
        try:
            self.filters = marshal.load(open(self.prefs.flistfile))
        except:
            self.filters = []

        # Inject entries into the listview
        for fi in self.filters:
            self.fdisp.append((fi[ENTRY_TX], fi[ENTRY_IN],
                               actions[fi[ENTRY_AC]] + fi[ENTRY_TO]))



    ##
    ## Method validate_filter (self, text)
    ##
    ##    Check that the filter text can be compiled to a regex
    ##
    ##
    def validate_filter(self, text):
        try:
            re.compile(text)
        except:
            return 0
        else:
            return 1
        

    ##
    ## Method handle_callbacks (self, button, no)
    ##
    ##    Handle callbacks for the action buttons.
    ##
    ##
    def handle_callbacks(self, button, no):
        if no == 0:
            self.add_callback(button)
        elif no == 1:
            self.edit_callback(button)
        elif no == 2:
            self.delete_callback(button)
        elif no == 3:
            self.ok_callback(button)
        elif no == 4:
            self.destroy()
        else:
            print "filter -- got unknown button callback event"

    def delete_callback(self, button=None):
        if self.fdisp.selection == []:
            return
        sel = self.fdisp.selection
        self.delname = {}
        for index in sel:
            self.delname[index] = self.fdisp.get_text(index,0)
	l = GtkLabel("Are you sure you want to remove '"+ \
                     string.join(self.delname.values(), "\n") + "' ?")
	l.show()
        v = GnomeDialog('Remove filter', 'Ok', 'Cancel')
        v.set_parent(self.win)
        v.vbox.pack_start(l)
        v.connect('clicked', self.do_delete)
        v.show()

    def do_delete(self, button, no):
        if no == 0:
            # Ok button
            for index in self.delname.keys():
                self.fdisp.remove(index)
                del self.filters[index]
            button.destroy()
        elif no == 1:
            # Cancel button
            button.destroy()

    def edit_callback(self, button=None):
        if self.fdisp.selection == []:
            return
        self.index = self.fdisp.selection[0]
        text = self.fdisp.get_text(self.index, 0)
        self.oldname = text

        fi = self.filters[self.index]
        _in = fi[ENTRY_IN]
        self.edit_window = FilterEditWindow(self.win, self.fld, self.do_edit)
        self.edit_window.set_filter(fi)
        
    def add_callback(self, button=None):
        self.edit_window = FilterEditWindow(self.win, self.fld, self.do_add)

    def do_add(self, button, no):
        if no == 0:
            # Ok button
            fi = self.edit_window.get_filter()
            if not self.validate_filter(fi[ENTRY_TX]):
                w = GnomeErrorDialog("Invalid regular expression in 'Text'")
                w.set_parent(self.win)
                w.show()
                return FALSE
            self.fdisp.append((fi[ENTRY_TX], fi[ENTRY_IN],
                               actions[fi[ENTRY_AC]] + fi[ENTRY_TO]))
            self.filters.append(fi)
        elif no == 1:
            # Cancel button
            pass
        button.destroy()

    def do_edit(self, button, no):
        if no == 0:
            # Ok button
            fi = self.edit_window.get_filter()
            if not self.validate_filter(fi[ENTRY_TX]):
                w = GnomeErrorDialog("Invalid regular expression in 'Text'")
                w.set_parent(self.win)
                w.show()
                return FALSE
            # Update display
            self.fdisp.set_text(self.fdisp.selection[0], 0, fi[ENTRY_TX])
            self.fdisp.set_text(self.fdisp.selection[0], 1, fi[ENTRY_IN])
            self.fdisp.set_text(self.fdisp.selection[0], 2,
                                actions[fi[ENTRY_AC]] + fi[ENTRY_TO])

            # Update internal list
            self.filters[self.index] = fi
        elif no == 1:
            # Cancel button
            pass
        button.destroy()

    ## Shortcut function
    def sort(self, c=0):
        self.fdisp.set_sort_column(c)
        self.fdisp.set_sort_type(SORT_ASCENDING)
        self.fdisp.sort()

    ## Sort the filter list alphabetically when clicked
    def clickcolumn(self, clist, c):
        self.sort(c)
        
    ## Need this one to catch double clicks on a list item.
    def selectrow(self, clist, r, c, event):
        if hasattr(event, 'type'):
            # Check for double clicks on the list item
            if event.type == 5: 
                # Launch a edit box (should something else happen?)
                self.edit_callback()


    def set_filterline(self, index, fi):
        self.fdisp.set_text(self.fdisp.selection[0], 0, fi[ENTRY_TX])
        self.fdisp.set_text(self.fdisp.selection[0], 1, fi[ENTRY_IN])
        self.fdisp.set_text(self.fdisp.selection[0], 2,
                            actions[fi[ENTRY_AC]] + fi[ENTRY_TO])
        
    def up_callback(self, button=None):
        if self.fdisp.selection == []:
            return
        index = self.fdisp.selection[0]

        # Check if already at start
        if index == 0:
            return

        # Update internal filter list
        tmp = self.filters[index]
        self.filters[index] = self.filters[index-1]
        self.filters[index-1] = tmp

        # Update filterview
        self.fdisp.row_move(index, index-1)
        
    def down_callback(self, button=None):
        if self.fdisp.selection == []:
            return
        index = self.fdisp.selection[0]

        # Check if already at end
        if index == len(self.filters)-1:
            return

        # Update internal filter list
        tmp = self.filters[index]
        self.filters[index] = self.filters[index+1]
        self.filters[index+1] = tmp

        # Update folderview
        self.fdisp.row_move(index, index+1)


    ##
    ## Method apply_callback (self, button)
    ##
    ##    Callback for the Apply button.
    ##
    ##
    def apply_callback(self, button):
        self.prefs.filters = self.filters
        self.fld.set_filters(self.filters)


    ##
    ## Method ok_callback (self, button)
    ##
    ##    Callback for the Ok button.
    ##
    ##
    def ok_callback(self, button):
        # Quit
        self.prefs.filters = self.filters
        self.fld.set_filters(self.filters)
        marshal.dump(self.filters, open(self.prefs.flistfile, 'w'))
        self.destroy()


    ##
    ## Method destroy (self, None, None)
    ##
    ##    Filter edit widget destructor.
    ##
    ##
    def destroy(self, foo=None, bar=None):
        self.fld.filters_active = 0
        self.win.destroy()
