## ------------------------------------------------------------------------
## iSiLK - A python-based graphical user interface for SiLK
## ------------------------------------------------------------------------
## Copyright (c) 2007-2008 Carnegie Mellon University. All Rights Reserved.
## ------------------------------------------------------------------------
## Authors: Andrew Kompanek (ajk@cert.org)
## ------------------------------------------------------------------------
## GNU General Public License (GPL) Rights pursuant to Version 2, June 1991
## Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013
## ------------------------------------------------------------------------
## See the LICENSE file included in this distribution
## ------------------------------------------------------------------------

'''A re-usable panel for selecting common filter options'''

import wx, ws
import pdb
import string, sys, re, textwrap
import constants
import config.filteroptions as config
import silkutils
from filemanager import FilePicker
#from library import FilePicker

CONTROL_LABEL_WIDTH = 55
CONTROL_SM_LABEL_WIDTH = 39

_ip_doc_ = '''Specify an IP address or a set of IP addresses to filter \
using the form supported by the \
SiLK tools.  Each octet can be expressed as a specific number between 0 \
and 255 (use "x" as a shortcut for 0-255) or as a range of values.

For example: 10.10,16-31.x.x

CIDR notation is also accepted.'''

_port_doc_ = '''Specify a port or a list of ports using the form \
supported by the SiLK tools.  For example all three of the following \
are valid ways to specify a port filter:

80
80,8080,443
0-1023,8080'''

_proto_doc_ = '''Specify a protocols or a list of protocols using the form \
supported by the SiLK tools.  For example all three of the following \
are valid ways to specify a protocol filter:

6
0-16
1,6'''

_icmp_type_doc_ = '''Specify an ICMP type or a list of types using the form \
supported by the SiLK tools.  For example all three of the following \
are valid ways to specify a filter:

6
0-16
1,6'''

_icmp_code_doc_ = '''Specify a ICMP code or a list of codes using the form \
supported by the SiLK tools.  For example all three of the following \
are valid ways to specify a filter:

6
0-16
1,6'''

_bytes_doc_ = '''Specify a range of traffic volumes using the form \
supported by the SiLK tools: a comma separated list of values and ranges
of values.  For example all of the following \
are valid ways to specify a byte count:

0-80 (not much data)
80-  (some data)
1063 (particular value)

'''

_packets_doc_ = '''Specify a range of traffic volumes using the form \
supported by the SiLK tools: a comma separated list of values and ranges
of values.  For example all three of the following \
are valid ways to specify a packet count:

1-3 (small number of packets)
3-  (not a small number)'''

_flags_doc_='''Specify TCP flag filtering with two strings separated \
by a "/".  Each of which contains a series of characters from the list \
F,S,R,P,A,U,E,C.

Flags to the right of the slash are the mask; any flag not listed in the mask may \
have any value in the flow. Flags to the left of the slash are the expected \
high flags; they must be set in the flow. Thus, flags listed in mask but not \
in high must be off for all packets in the flow. It is an error if a flag is \
listed in high but not in mask.  Some examples:

AS/ASFR
   means ACK,SYN must be high, FIN,RST must be low, and the other flags \
(PSH, URG, ECE, CWR) may have any value.

F/S
   is an error; use F/FS instead, which means FIN must be high, SYN \
must be low, and other flags can have any value'''

_bin_size_doc_ = '''Specify an integer value for the bin size.  
[FIXME: advice here on picking a bin size maybe?]'''

def format_help_doc(doc_str):
    paras = doc_str.split("\n\n")
    paras = [textwrap.fill(x) for x in paras]
    return string.join(paras, "\n\n")

class ChooseMoreDialog2(wx.Dialog):
    def __init__(self, cfgOptions, *args, **kargs):
        wx.Dialog.__init__(self, *args, **kargs)
        

class ChooseMoreDialog(wx.Dialog):
    def __init__(self, cfgOptions, *args, **kargs):
        wx.Dialog.__init__(self, *args, **kargs)
        # Create the controls (search control and list box)
        szV = wx.BoxSizer(wx.VERTICAL)
        self.cfgOptions = cfgOptions
        self.allChoices = self.cfgOptions.keys()
        self.lb = wx.ListBox(self, -1, (0, 0), (60,300), choices=self.allChoices)
        self.searchCtrl = wx.SearchCtrl(self, size=(200,-1), style=wx.TE_PROCESS_ENTER)
        self.searchCtrl.ShowCancelButton(True)
        szH = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.NO_DEFAULT)
        szV.Add(self.searchCtrl, flag=wx.ALL|wx.EXPAND, border=5)
        szV.Add(self.lb, flag=wx.EXPAND|wx.ALL, border=5)
        szV.Add(szH, flag=wx.ALL, border=5)
        self.SetSizerAndFit(szV)
        # Handle search events
        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnCancel, self.searchCtrl)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnDoSearch, self.searchCtrl)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnDblClick, self.lb)
    
    def OnDblClick(self, evt):
        self.EndModal(wx.ID_OK)
    
    def UpdateChoices(self):
        s = self.searchCtrl.GetValue()
        # Repopulate the list box based on the search
        index = 0
        self.lb.Clear()
        s.upper()
        for item in self.allChoices:
            if (item.find(s) > -1) or (item.find(s.lower()) > -1):
                self.lb.Insert(item, index)
    
    def OnDoSearch(self, evt):
        self.UpdateChoices()
    
    def OnCancel(self, evt):
        # Repopulate with everything
        index = 0
        for item in self.allChoices:
            self.lb.Insert(item, index)
    
    def GetSelection(self):
        val = self.lb.GetStringSelection()
        return self.cfgOptions[val]


class ValidationError(Exception):
    def __init__(self, summary, details, panel=None):
        self.summary = summary
        self.details = details
        self.panel = panel
    def __str__(self):
        return "ValidationError: %s - %s" % (self.summary, self.details)
    def ShowDialog(self):
        d = wx.MessageDialog(wx.GetApp().GetTopWindow(), self.details,
                "%s" % self.summary, wx.ICON_EXCLAMATION)
        return d.ShowModal()

from    wx.lib import masked

import wx.combo

class PopupListCtrl(wx.ListCtrl, wx.combo.ComboPopup):
    def __init__(self):
        self.PostCreate(wx.PreListCtrl())
        wx.combo.ComboPopup.__init__(self)
        self.vals = {}
        
    def AddItem(self, txt, val):
        self.InsertStringItem(self.GetItemCount(), txt)
        self.vals[txt] = val
    
    def OnMotion(self, evt):
        item, flags = self.HitTest(evt.GetPosition())
        if item >= 0:
            self.Select(item)
            self.curitem = item

    def OnLeftDown(self, evt):
        self.value = self.curitem
        self.Dismiss()    
    
    def Init(self):
        self.value = -1
        self.curitem = -1
        
    def Create(self, parent):
        wx.ListCtrl.Create(self, parent,
            style=wx.LC_REPORT|wx.LC_NO_HEADER|wx.LC_SINGLE_SEL|wx.SIMPLE_BORDER)
        self.InsertColumn(0, "")
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        return True

    def GetControl(self):
        return self
        
    def SetStringValue(self, val):
        idx = self.FindItem(-1, val)
        if idx != wx.NOT_FOUND:
            self.Select(idx)         

    def GetStringValue(self):
        if self.value >= 0:
            return self.vals[self.GetItemText(self.value)]
        else:
            return self.vals[self.GetItemText(0)]

    def OnPopup(self):
        print self.GetStringValue()
        wx.combo.ComboPopup.OnPopup(self)
        print "OnPopup"
        
    def OnDismiss(self):
        print "OnDismiss"
        self.GetCombo().GetParent().OnOptionCombo(None) 
        wx.combo.ComboPopup.OnDismiss(self)
        

class FieldComboCtrl(wx.combo.ComboCtrl):
    def __init__(self, *args, **kargs):
        wx.combo.ComboCtrl.__init__(self, *args, **kargs)
        self.SetPopupMaxHeight(160)
        self.popup = PopupListCtrl()
        self.SetPopupControl(self.popup)
        
    def OnPopupDismiss(self):
        # FIXME: not a nice way to handle this event
        print "OnPopupDismiss"
        self.GetParent().OnOptionCombo(None) 
        wx.combo.ComboCtrl.OnPopupDismiss()       
        
    def SetChoices(self, options):
        if isinstance(options, list):
            for c in options:
                self.popup.AddItem(c, c)
        else:
            for c in options.keys():
                self.popup.AddItem(c, options[c])
            self.popup.SetColumnWidth(0, self.GetSize()[0] - 25)


class FieldPanel(wx.Panel):
    def __init__(self, parent, label_text, field_name, initial_val, choices,
                    config_options, validator=None, small_field=True, no_combo=False,
                    label_width=None,
                    *args, **kargs):
        wx.Panel.__init__(self, parent, *args, **kargs)
        # Create controls: panel with label and combo
        self.field_name = field_name
        if small_field:
            if not label_width:
                self.label = ws.SmStaticLabel(self, -1, label_text,
                    size=(CONTROL_SM_LABEL_WIDTH,-1), style=wx.ALIGN_RIGHT)
            else:
                self.label = ws.SmStaticLabel(self, -1, label_text,
                    size=(label_width,-1), style=wx.ALIGN_RIGHT)            
        else:
            if not label_width:
                self.label = wx.StaticText(self, -1, label_text,
                    size=(CONTROL_LABEL_WIDTH,-1), style=wx.ALIGN_LEFT)
            else:
                self.label = wx.StaticText(self, -1, label_text,
                    size=(label_width,-1), style=wx.ALIGN_LEFT) 
        if no_combo:
            self.combo = wx.TextCtrl(self, -1, size=(200,-1))
            if validator: self.combo.SetValidator(validator)
        else:
            self.combo = FieldComboCtrl(self, -1, size=(200,-1))
            if validator: self.combo.SetValidator(validator)            
        if not no_combo:
            self.combo.SetChoices(choices)
        self.combo.config_options = config_options
        self.combo.last_val = initial_val
        self.default_value  = initial_val
        # Layout the controls
        sz = wx.GridBagSizer()
        if not small_field:
            flag = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT
            indent = 0
        else:
            flag = wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT
            indent = 16
        sz.Add(self.label, (0,0), flag=flag, border=indent)
        sz.Add(self.combo, (0,1), flag=wx.LEFT|wx.EXPAND, border=16)
        sz.AddGrowableCol(1)
        self.SetSizerAndFit(sz)
        self.combo.SetValue(initial_val)
        self.Bind(wx.EVT_TEXT, self.OnOptionCombo, self.combo)
    
    def OnOptionCombo(self, evt):
        #print "FieldPanel.OnOptionCombo"
        if self.combo.GetValue() == "Other":
            return
        wx.PostEvent(self.GetParent(),
            ws.OptionChangeEvent(-1, self.field_name, self.combo.GetValue()))        
        return
    
    def GetArg(self, args):
        if not self.IsEnabled(): 
            return        
        val = self.GetValue()
        val = val.replace(" ", "")
        val = val.replace('""', "")
        if not val in (self.default_value, ""):
            args[self.field_name] = val
    
    def SetFieldName(self, field_name):
        self.field_name = field_name
    
    def SetValue(self, val):
        self.combo.SetValue(val)
    
    def GetValue(self):
        return self.combo.GetValue()
        
    def ResetValue(self):
        self.combo.SetValue(self.default_value)
    
    def _checkOption(self, long_name, validate_func, doc_str):
        try:
            validate_func(self.GetValue())
        except Exception, e:
            summary = "Invalid %s" % (long_name)
            msg = "%s\n\n%s" % (str(e), format_help_doc(doc_str))
            raise ValidationError(summary, msg, self)


class FieldValidator(ws.BasicValidator):
    def __init__(self, panel, long_name, valid_chars, validate_func, doc_str):
        ws.BasicValidator.__init__(self, valid_chars)
        self.panel = panel
        self.long_name = long_name
        self.validate_func = validate_func
        self.doc_str = format_help_doc(doc_str)
        
    def Validate(self, win):
        if not ws.BasicValidator.Validate(win):
            return False
        try:
            self.validate_func(self.GetValue())
            return True
        except Exception, e:
            summary = "Invalid %s" % (self.long_name)
            msg = "%s\n\n%s" % (str(e), self.doc_str)
            print "msg='%s'" % msg
            raise ValidationError(summary, msg, self.panel)
    def Clone(self):
        return FieldValidator(self.panel, self.long_name, self.valid_chars,
            self.validate_func, self.doc_str)


class AddressCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "x.x.x.x"
        choices = config.opts["COMMON_IP_OPTIONS"].keys()
        config_options = config.opts["COMMON_IP_OPTIONS"]
        validator = FieldValidator(self, "IP Address", string.digits + ".,-/x",
            silkutils.validate_ip_arg, _ip_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #    choices, config_options, validator=validator, *args, **kargs)
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
             config.opts["COMMON_IP_OPTIONS"], config_options, validator=validator, 
             no_combo=True, *args, **kargs)
    
    def Validate(self):
        self._checkOption("IP Address", silkutils.validate_ip_arg, _ip_doc_)


class ValCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = ""
        validator = ws.LabelValidator()
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
             None, None, validator=validator, no_combo=True, *args, **kargs)

    def Validate(self):
        pass

class PortCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "0-65535"
        choices = config.opts["COMMON_PORTS_OPTIONS"].keys()
        config_options = config.opts["COMMON_PORTS_OPTIONS"]
        validator = FieldValidator(self, "Port", string.digits + ",-",
            silkutils.validate_port_arg, _port_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #    choices, config_options, validator=validator, *args, **kargs)
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
            config.opts["COMMON_PORTS_OPTIONS"], config_options, validator=validator, *args, **kargs)
    
    def Validate(self):
        self._checkOption("Port", silkutils.validate_port_arg, _port_doc_)
    
    def OnOptionCombo(self, evt):
        FieldPanel.OnOptionCombo(self, evt)
        if self.combo.GetValue() == "Other":
            self.combo.SetValue(self.combo.last_val)
            dlg = ChooseMoreDialog(config.opts["MORE_PORTS_OPTIONS"], self,
                            pos = self.combo.GetScreenPosition(),
                            title="More Ports")
            if dlg.ShowModal() == wx.ID_OK:
                val = dlg.GetSelection()
                self.SetValue(val)
                #self.combo.SelectAll()
                self.combo.last_val = val
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, self.field_name, self.combo.GetValue()))


class ProtoCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "0-255"
        choices = config.opts["COMMON_PROTOCOLS_OPTIONS"].keys()
        config_options = config.opts["COMMON_PROTOCOLS_OPTIONS"]
        validator = FieldValidator(self, "Protocol", string.digits + ",-",
            silkutils.validate_proto_arg, _proto_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #        choices, config_options, small_field=False, 
        #        validator=validator, *args, **kargs)
                
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
                config.opts["COMMON_PROTOCOLS_OPTIONS"], config_options, small_field=False, 
                validator=validator, *args, **kargs)
        self.Bind(wx.EVT_COMBOBOX, self.OnOptionCombo, self.combo)
    
    def Validate(self):
        self._checkOption("Protocol", silkutils.validate_proto_arg, _proto_doc_)
    
    def OnOptionCombo(self, evt):
        FieldPanel.OnOptionCombo(self, evt)
        if self.combo.GetValue() == "Other":
            self.combo.SetValue(self.combo.last_val)
            dlg = ChooseMoreDialog(config.opts["MORE_PROTOCOLS_OPTIONS"], self,
                            pos = self.combo.GetScreenPosition(),
                            title="More Protocols")
            if dlg.ShowModal() == wx.ID_OK:
                val = dlg.GetSelection()
                self.SetValue(val)
                #self.combo.SelectAll()
                self.combo.last_val = val
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, self.field_name, self.combo.GetValue()))


class BytesCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "1-"
        choices = config.opts["COMMON_BYTES_OPTIONS"].keys()
        config_options = config.opts["COMMON_BYTES_OPTIONS"]
        validator = FieldValidator(self, "Bytes", string.digits + ",-",
            silkutils.validate_volume_arg, _bytes_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #    choices, config_options, validator=validator, *args, **kargs)
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
            config.opts["COMMON_BYTES_OPTIONS"], config_options, validator=validator, *args, **kargs)            
        self.combo.SetSize((-1, 50))
        self.Bind(wx.EVT_COMBOBOX, self.OnOptionCombo, self.combo)
    def Validate(self):
        self._checkOption("Byte Count", silkutils.validate_volume_arg,
            _bytes_doc_)


class PacketsCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "1-"
        choices = config.opts["COMMON_PACKETS_OPTIONS"].keys()
        config_options = config.opts["COMMON_PACKETS_OPTIONS"]
        validator = FieldValidator(self, "Packets", string.digits + ",-",
            silkutils.validate_volume_arg, _packets_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #    choices, config_options, validator=validator, *args, **kargs)
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
            config.opts["COMMON_PACKETS_OPTIONS"], config_options, validator=validator, *args, **kargs)            
        self.combo.SetSize((-1, 50))
        self.Bind(wx.EVT_COMBOBOX, self.OnOptionCombo, self.combo)
    
    def Validate(self):
        self._checkOption("Packet Count", silkutils.validate_volume_arg,
            _packets_doc_)
            
            
class ICMPTypeCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "0-255"
        choices = config.opts["COMMON_ICMP_TYPE_OPTIONS"].keys()
        config_options = config.opts["COMMON_ICMP_TYPE_OPTIONS"]
        validator = FieldValidator(self, "ICMP Type", string.digits + ",-",
            silkutils.validate_icmp_type_arg, _icmp_type_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #        choices, config_options, validator=validator, *args, **kargs)
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
                config.opts["COMMON_ICMP_TYPE_OPTIONS"], config_options,
                validator=validator, label_width=75, *args, **kargs)                
       # self.Bind(wx.EVT_COMBOBOX, self.OnOptionCombo, self.combo)
    
    def Validate(self):
        self._checkOption("ICMP Type", silkutils.validate_icmp_type_arg, _icmp_type_doc_)
    
    def OnOptionCombo(self, evt):
        FieldPanel.OnOptionCombo(self, evt)
        if self.combo.GetValue() == "Other":
            self.combo.SetValue(self.combo.last_val)
            dlg = ChooseMoreDialog(config.opts["MORE_ICMP_TYPE_OPTIONS"], self,
                            pos = self.combo.GetScreenPosition(),
                            title="More Protocols")
            if dlg.ShowModal() == wx.ID_OK:
                val = dlg.GetSelection()
                #if self.GetValue() != "":
                #    val = self.GetValue() + "," + val
                self.SetValue(val)
                #self.combo.SelectAll()
                self.combo.last_val = val
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, self.field_name, self.combo.GetValue()))


class ICMPCodeCtrl(FieldPanel):
    def __init__(self, parent, label_text, field_name, *args, **kargs):
        initial_val = "0-255"
        choices = config.opts["COMMON_ICMP_CODE_OPTIONS"].keys()
        config_options = config.opts["COMMON_ICMP_CODE_OPTIONS"]
        validator = FieldValidator(self, "ICMP Code", string.digits + ",-",
            silkutils.validate_icmp_type_arg, _icmp_code_doc_)
        #FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
        #        choices, config_options, validator=validator, *args, **kargs)
        FieldPanel.__init__(self, parent, label_text, field_name, initial_val,
                config.opts["COMMON_ICMP_CODE_OPTIONS"], config_options,
                validator=validator, label_width=75, *args, **kargs)                
        self.Bind(wx.EVT_COMBOBOX, self.OnOptionCombo, self.combo)
    
    def Validate(self):
        self._checkOption("ICMP Code", silkutils.validate_icmp_code_arg, _icmp_code_doc_)
    
    def OnOptionCombo(self, evt):
        FieldPanel.OnOptionCombo(self, evt)
        if self.combo.GetValue() == "Other...":
            self.combo.SetValue(self.combo.last_val)
            dlg = ChooseMoreDialog(config.opts["MORE_ICMP_CODE_OPTIONS"], self,
                            pos = self.combo.GetScreenPosition(),
                            title="More Protocols")
            if dlg.ShowModal() == wx.ID_OK:
                val = dlg.GetSelection()
                self.SetValue(val)
                self.combo.SelectAll()
                self.combo.last_val = val
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, self.field_name, self.combo.GetValue()))


class FlagsCtrl(wx.Panel):
    def __init__(self, parent, field_name, *args, **kargs):
        wx.Panel.__init__(self, parent, *args, **kargs)
        self.field_name = field_name
        # Create TCP flags editor
        sz = wx.FlexGridSizer(2,9)
        flag = "FSRPAUEC"
        sz.Add((0,0))
        for i in range(0,8):
            sz.Add(ws.SmStaticLabel(self,-1,flag[i]), flag=wx.ALIGN_CENTER)
        self.flagCtrls = []
        sz.Add(ws.SmStaticLabel(self, -1, "TCP Flags", size=(60,-1), style=wx.ALIGN_RIGHT),
                            flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=16)
        for i in range(0,8):
            cb = wx.CheckBox(self,-1,
                    style=wx.CHK_ALLOW_3RD_STATE_FOR_USER|wx.CHK_3STATE)
            self.Bind(wx.EVT_CHECKBOX, self.OnCheck)
            self.flagCtrls.append(cb)
            self.flagCtrls[i].Set3StateValue(wx.CHK_UNDETERMINED)
            sz.Add(self.flagCtrls[i], flag=wx.CENTER|wx.BOTTOM, border=4)
        self.SetSizerAndFit(sz)
    
    def _checkOption(self, long_name, validate_func, doc_str):
        try:
            validate_func(self.GetValue())
        except Exception, e:
            summary = "Invalid %s" % (long_name)
            msg = "%s\n\n%s" % (str(e), doc_str)
            raise ValidationError(summary, msg, self)
    
    def Validate(self):
        self._checkOption("TCP Flags", silkutils.validate_flags_arg, _flags_doc_)
    
    def GetArg(self, args):
        if not self.IsEnabled(): 
            return
        val = self.GetValue()
        val = val.replace(" ", "")
        val = val.replace('""', "")
        if val != "":
            args[self.field_name] = val
    
    def SetFieldName(self, field_name):
        self.field_name = field_name
    
    def OnCheck(self, evt):
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))
    
    def SetValue(self, val):
        ''' Set the value using a string.  Will raise an exception if a
            a bad value is given. '''
        flags = silkutils_parse_flags_arg(val)
        flag = "FSRPAUEC"
        for i in range(0,8):
            if flag[i] in flags.keys():
                if flags[flag[i]]:
                    self.flagCtrls[i].Set3StateValue(wx.CK_CHECKED)
                else:
                    self.flagCtrls[i].Set3StateValue(wx.CHK_UNCHECKED)
            else:
                self.flagCtrls[i].Set3StateValue(wx.CHK_UNDETERMINED)
    
    def GetValue(self):
        flag = "FSRPAUEC"
        (left, right) = ("","")
        for i in range(0,8):
            val = self.flagCtrls[i].Get3StateValue()
            if val == wx.CHK_CHECKED:
                left += flag[i]
                right += flag[i]
            elif val == wx.CHK_UNCHECKED:
                right += flag[i]
        if right == "":
            return ""
        else:
            return "%s/%s" % (left, right)


class FilterOptionsPanel(wx.Panel):
    '''Panel for choosing common filter options.
       
       Usage:
           p = FilterOptionsPanel(...)
           ...
           self.Bind(FilterOptionsPanel.EVT_PANEL_CHANGED, self.OnPanelChanged(), p)
    '''
    def __init__(self, parent, *args, **kargs):
        wx.Panel.__init__(self, parent, *args, **kargs)
        self._lastfocus = None
        self.fieldControls = {}
        self.fields = []
         

    def OnChildFocus(self, evt):
        print "Child focus on %s (%s)" % (evt.GetWindow().GetParent().field_name, evt.GetWindow())
       
        
        if False and self._lastfocus and self._lastfocus == evt.GetWindow().FindFocus():
            self._lastfocus = evt.GetWindow().FindFocus()
            evt.Skip()
            return
        #self._lastfocus = evt.GetWindow().FindFocus()            
        #try:
        self.ValidateOptions()
        #except ValidationError, e:
            #e.panel.combo.SetFocus()
         #   print "Displaying validaton box for error in %s" % e.panel.combo
          #  d = wx.MessageDialog(self, e.details, "%s" % e.summary, 
          #      wx.ICON_EXCLAMATION)
          #  d.ShowModal()
            #return
        evt.Skip()

    def _addCtrl(self, ctrl):
        self.fields.append(ctrl.field_name)
        self.fieldControls[ctrl.field_name] = ctrl
        #ctrl.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocus)
        return ctrl
    
    def CopyState(self, other):
        self.SetOptions(other.GetOptions())
    
    def ResetOptions(self):
        self.txtsipset.SetValue("(Choose a set)")
        self.txtdipset.SetValue("(Choose a set)")
        for opt in self.fieldControls:
            self.fieldControls[opt].ResetValue()
            
    def GetOptionNames(self):
        return self.opt_names
    
    def SetOptions(self, args):
        for arg in args.keys():
            if arg in self.fieldControls:
                self.fieldControls[arg].SetValue(args[arg])
    
    def GetOptions(self):
        args = {}
        for opt in self.fieldControls:
            ctrl = self.fieldControls[opt]
            ctrl.GetArg(args)
        #print "Got options for %s" % str(self.__class__)
        #print args
        return args
    
    def ValidateOptions(self):
        for opt in self.fields:
            ctrl = self.fieldControls[opt]
            ctrl.Validate()
        #print "Returning true"
        return True
            #try:            
            #    print "Validating control"
            #    ctrl.Validate()
             #   print "Control validated"
            #except ValidationError, e:
                #   print "Displaying validaton box for error in %s" % e.panel.combo
            #    d = wx.MessageDialog(self, e.details, "%s" % e.summary, 
             #           wx.ICON_EXCLAMATION)
              #  d.ShowModal()
               # return False
       # print "Returning true"
        #return True
        
        
class BasicFilterOptionsPanel(FilterOptionsPanel):
    def __init__(self, parent, *args, **kargs):
        self.opt_names = ["saddress", "sport", "daddress", "dport", "proto"]
        FilterOptionsPanel.__init__(self, parent, *args, **kargs)
        # Grab list of choices for configurable controls
        box = wx.StaticBox(self, -1, "IP Addresses and Ports")
        self.gbs = gbs = wx.StaticBoxSizer(box, wx.VERTICAL)
        # Create controls
        #szpmap = wx.FlexGridSizer(1,2)
        #self.chkApplyPrefixMap = wx.CheckBox(self,-1,
        #    " Apply a pmap")
        #self.chkApplyPrefixMap.Enable(False)
        #self.txtPrefixMap = ws.SmTextCtrl(self, -1, "(Choose a pmap)", size=(225,-1))
        #self.txtPrefixMap.Enable(False)
        #szpmap.Add(self.chkApplyPrefixMap, flag=wx.RIGHT, border=6)
        #szpmap.Add(self.txtPrefixMap, flag=wx.EXPAND)
        
        self.chkDirection = wx.CheckBox(self, -1,
                " Filter based on source and destination")
        self.chkDirection.SetValue(wx.CHK_CHECKED)

        self.lblSource = wx.StaticText(self, -1, "Source")#, style=wx.ALIGN_RIGHT)
        self.lblDestination = wx.StaticText(self, -1, "Destination")#, style=wx.ALIGN_RIGHT)
        # Create all the field editors
        self.saddress = self._addCtrl(AddressCtrl(self, "IP", "saddress"))
        self.sport    = self._addCtrl(PortCtrl(self, "Port", "sport"))
        self.daddress = self._addCtrl(AddressCtrl(self, "IP", "daddress"))
        self.dport    = self._addCtrl(PortCtrl(self, "Port", "dport"))
        # Prefix map
        #self.svaladdress = self._addCtrl(ValCtrl(self, "sval Label", "sval"))
        #self.dvaladdress = self._addCtrl(ValCtrl(self, "dval Label", "dval"))       
        #self.svaladdress.Enable(False) 
        #self.dvaladdress.Enable(False)
        # Create the other controls
        self.lblsipset = ws.SmStaticText(self, -1, "IP Set", size=(CONTROL_LABEL_WIDTH,-1), style=wx.ALIGN_RIGHT)
        self.txtsipset = ws.SmTextCtrl(self, -1, "(Choose a set)", size=(230,-1), style=wx.TE_READONLY)
        self.btnclrsip = ws.SmButton(self, -1, "Clear")
        self.btnsipset = ws.SmButton(self, -1, "Choose...")
        self.lbldipset = ws.SmStaticText(self, -1, "IP Set", size=(CONTROL_LABEL_WIDTH,-1), style=wx.ALIGN_RIGHT)        
        self.txtdipset = ws.SmTextCtrl(self, -1, "(Choose a set)", size=(230,-1), style=wx.TE_READONLY)
        self.btnclrdip = ws.SmButton(self, -1, "Clear")
        self.btndipset = ws.SmButton(self, -1, "Choose...")
        # Package up the buttons
        szsip = wx.FlexGridSizer(1, 2)
        szsip.Add(self.btnclrsip, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=6)
        szsip.Add(self.btnsipset, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=6)
        szdip = wx.FlexGridSizer(1,2)
        szdip.Add(self.btnclrdip, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=6)
        szdip.Add(self.btndipset, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=6)                
        szSipSet = wx.FlexGridSizer(2,2)
        szSipSet.Add(self.lblsipset, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=0)
        szSipSet.Add(self.txtsipset, flag=wx.LEFT|wx.EXPAND|wx.ALIGN_LEFT, border=18)
        szSipSet.Add((0,0))
        szSipSet.Add(szsip, flag=wx.ALL|wx.ALIGN_RIGHT, border=6)
        szDipSet = wx.FlexGridSizer(2,2)
        szDipSet.Add(self.lbldipset, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=0)
        szDipSet.Add(self.txtdipset, flag=wx.LEFT|wx.EXPAND|wx.ALIGN_LEFT, border=18)
        szDipSet.Add((0,0))
        szDipSet.Add(szdip, flag=wx.ALL|wx.ALIGN_RIGHT, border=6)        
        # Do top-level layout all the controls
        #self.gbs.Add(szpmap, flag=wx.TOP|wx.BOTTOM|wx.EXPAND, border=6)
        self.gbs.Add(self.chkDirection, flag=wx.BOTTOM, border=4)
        self.gbs.Add(self.lblSource, flag=wx.TOP|wx.BOTTOM, border=4)
        self.gbs.Add(self.saddress, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND,border=4)        
        self.gbs.Add(szSipSet, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=4)                 
        #self.gbs.Add(self.svaladdress, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP,border=6)               
        self.gbs.Add(self.sport, flag=wx.LEFT|wx.RIGHT,border=4)
        self.gbs.Add((0,0),flag=wx.TOP, border=2)
        self.gbs.Add(self.lblDestination, flag=wx.TOP|wx.BOTTOM, border=4)
        self.gbs.Add(self.daddress, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND, border=4)
        self.gbs.Add(szDipSet, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=4)                        
        #self.gbs.Add(self.dvaladdress, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP,border=6)                       
        self.gbs.Add(self.dport, flag=wx.ALL,border=4)
        self.SetSizerAndFit(self.gbs)
        self.Bind(wx.EVT_CHECKBOX, self.OnCheckUseDirection, self.chkDirection)
        self.Bind(wx.EVT_BUTTON, self.OnPickSipSet, self.btnsipset)
        self.Bind(wx.EVT_BUTTON, self.OnClrSipSet, self.btnclrsip)
        self.Bind(wx.EVT_BUTTON, self.OnPickDipSet, self.btndipset)        
        self.Bind(wx.EVT_BUTTON, self.OnClrDipSet, self.btnclrdip)        
    
    def GetOptions(self):
        opts = FilterOptionsPanel.GetOptions(self)
        if self.chkDirection.GetValue() == wx.CHK_CHECKED:
            if self.txtsipset.GetValue()[0] != "(":
                opts["sipset"] = self.txtsipset.GetValue()
            if self.txtdipset.GetValue()[0] != "(":            
                opts["dipset"] = self.txtdipset.GetValue()
        else:
            if self.txtsipset.GetValue()[0] != "(":
                opts["anyset"] = self.txtsipset.GetValue()
        return opts
        
    def OnClrSipSet(self, evt):
        print "OnClrSipSet"
        val = "(Choose a set)"   
        self.txtsipset.SetValue(val)     
        wx.PostEvent(self, ws.OptionChangeEvent(-1, "sip", val))  
        
    def OnClrDipSet(self, evt):
        print "OnClrDipSet"   
        val = "(Choose a set)"     
        self.txtdipset.SetValue(val)             
        wx.PostEvent(self, ws.OptionChangeEvent(-1, "dip", val))  
        
    def OnPickSipSet(self, evt):
        # Create a set picker
        dlg = FilePicker(self, title="Choose source address IP set")
        dlg.CenterOnParent()
        if dlg.ShowModal() == wx.ID_OK:
            val = dlg.GetResult()
        else:
            val = "(Choose a set)"
        self.txtsipset.SetValue(val)
        wx.PostEvent(self, ws.OptionChangeEvent(-1, "sip", val))
   
    def OnPickDipSet(self, evt):
        # Create a set picker
        dlg = FilePicker(self, title="Choose destination address IP set")
        dlg.CenterOnParent()
        if dlg.ShowModal() == wx.ID_OK:
            val = dlg.GetResult()
        else:
            val = "(Choose a set)"
        self.txtdipset.SetValue(val)
        wx.PostEvent(self, ws.OptionChangeEvent(-1, "dip", val))        
        
    def OnCheckUseDirection(self, evt):
        enable = evt.GetEventObject().GetValue() == wx.CHK_CHECKED
        self.lblDestination.Enable(enable)
        self.daddress.Enable(enable)
        self.dport.Enable(enable)
        self.btnclrdip.Enable(enable)
        self.btndipset.Enable(enable)
        self.lbldipset.Enable(enable)
        self.txtdipset.Enable(enable)
        if enable:
            self.lblSource.SetLabel("Source")
            self.saddress.SetFieldName("saddress")
            self.sport.SetFieldName("sport")
        else:
            self.lblSource.SetLabel("Source or Destination")
            self.saddress.SetFieldName("any-address")
            self.sport.SetFieldName("aport")
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))


class SizeFilterOptionsPanel(FilterOptionsPanel):
    def __init__(self, parent, *args, **kargs):
        self.opt_names = ["bytes", "packets", "flags-all"]
        FilterOptionsPanel.__init__(self, parent, *args, **kargs)
        # Grab list of choices for configurable controls
        box = wx.StaticBox(self, -1, "Flow size fields")
        self.gbs = gbs = wx.StaticBoxSizer(box, wx.VERTICAL)
        self.fieldControls = {}
        # Create controls
        self.bytes = self._addCtrl(BytesCtrl(self, "Bytes", "bytes"))
        self.packets = self._addCtrl(PacketsCtrl(self, "Pkts", "packets"))
        # Layout all the controls
        self.gbs.Add(self.bytes, flag=wx.EXPAND|wx.ALL, border=6)
        self.gbs.Add(self.packets, flag=wx.EXPAND|wx.ALL, border=6)
        self.SetSizerAndFit(self.gbs)


class PrefixMapFilterPanel(FilterOptionsPanel):
    def __init__(self, parent, *args, **kargs):
        self.opt_names = ["pmap-file", "pmap-saddress", "pmap-daddress"]
        FilterOptionsPanel.__init__(self, parent, *args, **kargs)
        box = wx.StaticBox(self, -1, "Apply a Prefix Map")
        self.gbs = gbs = wx.StaticBoxSizer(box, wx.VERTICAL)
        sz = self._init_pmap_controls()
        # sval, dval
        szsval = wx.FlexGridSizer(2,2)
        szsval.Add(self.lblsval, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=0)
        szsval.Add(self.txtsval, flag=wx.LEFT|wx.EXPAND|wx.ALIGN_LEFT, border=18)
        szdval = wx.FlexGridSizer(2,2)
        szdval.Add(self.lbldval, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=0)
        szdval.Add(self.txtdval, flag=wx.LEFT|wx.EXPAND|wx.ALIGN_LEFT, border=18)

        self.gbs.Add(sz, flag=wx.TOP, border=12)
        self.gbs.Add(szsval, flag=wx.TOP, border=12)
        self.gbs.Add(szdval, flag=wx.TOP, border=12)        
        self.SetSizerAndFit(self.gbs)
        self.txtsval.Enable(False)
        self.txtdval.Enable(False)
        self.Bind(wx.EVT_BUTTON, self.OnPick, self.btnpmap)
        self.Bind(wx.EVT_BUTTON, self.OnClr, self.btnclrpmap)
        self.Bind(wx.EVT_TEXT, self.OnChangeFile, self.txtpmap)
        self.Bind(wx.EVT_TEXT, self.OnChangeSVal, self.txtsval)
        self.Bind(wx.EVT_TEXT, self.OnChangeDVal, self.txtdval) 
        
    def _init_pmap_controls(self):
        # Create controls
        self.lblpmap = ws.SmStaticText(self, -1, "File", size=(CONTROL_LABEL_WIDTH,-1), style=wx.ALIGN_RIGHT)
        self.txtpmap = ws.SmTextCtrl(self, -1, "(Choose a prefix map)", size=(230,-1), style=wx.TE_READONLY)
        self.lblsval = ws.SmStaticText(self, -1, "saddress", size=(CONTROL_LABEL_WIDTH,-1), style=wx.ALIGN_RIGHT)
        self.txtsval = ws.SmTextCtrl(self, -1, "", size=(230,-1),
                validator=ws.PmapKeyValidator())
        self.lbldval = ws.SmStaticText(self, -1, "daddress", size=(CONTROL_LABEL_WIDTH,-1), style=wx.ALIGN_RIGHT)
        self.txtdval = ws.SmTextCtrl(self, -1, "", size=(230,-1),
                validator=ws.PmapKeyValidator())        
        self.btnclrpmap = ws.SmButton(self, -1, "Clear")
        self.btnpmap = ws.SmButton(self, -1, "Choose...")
        # Pack buttons
        szpick = wx.FlexGridSizer(1, 2)
        szpick.Add(self.btnclrpmap, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=6)
        szpick.Add(self.btnpmap, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_RIGHT, border=6)
        # and file name
        sz = wx.FlexGridSizer(2,2)
        sz.Add(self.lblpmap, flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=0)
        sz.Add(self.txtpmap, flag=wx.LEFT|wx.EXPAND|wx.ALIGN_LEFT, border=18)
        sz.Add((0,0))
        sz.Add(szpick, flag=wx.ALL|wx.ALIGN_RIGHT, border=6)
        return sz
            
    def OnChangeFile(self, evt):
        field = "pmap-file"
        if field in self.GetOptions():
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, field, self.GetOptions()[field]))
            
    def OnChangeSVal(self, evt):
        field = "pmap-saddress"
        if field in self.GetOptions():        
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, field, self.GetOptions()[field]))
                    
    def OnChangeDVal(self, evt):
        field = "pmap-daddress"
        if field in self.GetOptions():        
            wx.PostEvent(self.GetParent(),
                ws.OptionChangeEvent(-1, field, self.GetOptions()[field]))

    def ResetOptions(self):
        self.txtpmap.SetValue("(Choose a prefix map)")
        self.txtsval.SetValue("")
        self.txtdval.SetValue("")
        self.txtsval.Enable(False)
        self.txtdval.Enable(False)

    def GetOptions(self):
        opts = {}
        if self.txtpmap.GetValue()[0] != "(":
            opts["pmap-file"] = self.txtpmap.GetValue()
        saddress = self.txtsval.GetValue().replace(" ", "")
        daddress = self.txtdval.GetValue().replace(" ", "")
        if saddress != "":
            opts["pmap-saddress"] = self.txtsval.GetValue()
        if daddress != "":
            opts["pmap-daddress"] = self.txtdval.GetValue()        
        return opts
        
    def SetOptions(self, opts):
        if "pmap-file" in opts:
            self.txtpmap.SetValue(opts.get("pmap-file"))
            self.txtsval.SetValue(opts.get("pmap-saddress", ""))
            self.txtdval.SetValue(opts.get("pmap-daddress", ""))
        else:
            self.txtsval.Enable(False)
            self.txtdval.Enable(False)
                    
        
    def OnClr(self, evt):
       val = "(Choose a file)"
       self.txtpmap.SetValue(val)
       self.txtsval.Enable(False)
       self.txtdval.Enable(False)
       wx.PostEvent(self, ws.OptionChangeEvent(-1, "pmap-file", val)) 
           
    def OnPick(self, evt):
       # Create a set picker
       dlg = FilePicker(self, ext=[".pmap"], cancel_label="No Prefix Map",
        title="Choose prefix map file")
       dlg.CenterOnParent()
       if dlg.ShowModal() == wx.ID_OK:
           val = dlg.GetResult()
           self.txtsval.Enable(True)
           self.txtdval.Enable(True)
       else:
           val = "(Choose a prefix map)"
           self.txtsval.Enable(False)
           self.txtdval.Enable(False)           
       self.txtpmap.SetValue(val)
       wx.PostEvent(self, ws.OptionChangeEvent(-1, "sip", val))

class MoreFilterOptionsPanel(FilterOptionsPanel):
    def __init__(self, parent, *args, **kargs):
        self.opt_names = ["icmp_type", "icmp_code", "bytes", "packets", "flags-all"]
        FilterOptionsPanel.__init__(self, parent, *args, **kargs)
        # Grab list of choices for configurable controls
        box = wx.StaticBox(self, -1, "Protocol and protocol-specific fields")
        self.gbs = gbs = wx.StaticBoxSizer(box, wx.VERTICAL)
        self.fieldControls = {}
        # Create controls
        self.proto = self._addCtrl(ProtoCtrl(self, "Protocol", "proto"))
        self.icmp_type = self._addCtrl(ICMPTypeCtrl(self, "ICMP Type", "icmp_type"))
        self.icmp_code = self._addCtrl(ICMPCodeCtrl(self, "ICMP Code", "icmp_code"))
        self.flags = self._addCtrl(FlagsCtrl(self, "flags-all"))
        # Layout all the controls
        self.gbs.Add(self.proto, flag=wx.EXPAND|wx.ALL,border=6)
        self.gbs.Add(self.flags, flag=wx.EXPAND|wx.ALL, border=6)
        self.gbs.Add(self.icmp_type, flag=wx.EXPAND|wx.ALL, border=6)
        self.gbs.Add(self.icmp_code, flag=wx.EXPAND|wx.ALL, border=6)
        self.SetSizerAndFit(self.gbs)
        self.Bind(ws.EVT_OPTION_CHANGE, self.OnOptionChange) 
        
    def OnOptionChange(self, evt):
        if evt.optName == "proto": 
            # We want to enable the flags only if proto 6 falls in the range
            # specified by the user.  But since this is called with every key
            # stroke, the value may be temporarily invalid (i.e. if the user
            # types a comma, the value is invalid until he types
            # another integer.  So, simply ignore errors.
            protos = self.proto.combo.GetValue()

            if protos != '':
                try:
                    self.flags.Enable(silkutils.check_list_membership(protos, "6", 0, 255))
                except:
                    pass
        evt.Skip()   # Make sure parent gets the event, too
