#! /usr/bin/env python
###############################################################################
#                                                                             #
#   Copyright 2005 University of Cambridge Computer Laboratory.               #
#                                                                             #
#   This file is part of Nprobe.                                              #
#                                                                             #
#   Nprobe is free software; you can redistribute it and/or modify            #
#   it under the terms of the GNU General Public License as published by      #
#   the Free Software Foundation; either version 2 of the License, or         #
#   (at your option) any later version.                                       #
#                                                                             #
#   Nprobe is distributed in the hope that it will be useful,                 #
#   but WITHOUT ANY WARRANTY; without even the implied warranty of            #
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             #
#   GNU General Public License for more details.                              #
#                                                                             #
#   You should have received a copy of the GNU General Public License         #
#   along with Nprobe; if not, write to the Free Software                     #
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
#                                                                             #
###############################################################################


##############################################################################
## 
## 
## Data set class and data loader for np_plot
## 
##
##
############################################################################ 
############################################################################


import os
import tkSimpleDialog, tkFileDialog
from Tkinter import *
from signal import *
from np_widgets import Dialogue, BetterEntry
from np_drawutil import field_l2str, field_str2l, fields_eq
import math
from math import *
import re

############################################################################ 
############################################################################

#
# Basic data set types
#

DATA_TS = 0x1
DATA_PDF = 0x2
DATA_CDF = 0x4
DATA_HIST = 0x8
DATA_SM = 0x10

#
# Histogram data only - denotes bucket ranges rather than discrete values
#
DATA_RANGES = 0x40

############################################################################ 
############################################################################

#
# Edit types for data edit
#
DELETE = 0
EDIT = 1

############################################################################ 
############################################################################
#
# Data format for np_Plot:
#
# DATA := [DataSet 0, DataSet 1, ...]
#
# DataSet := (data, data type, path, tag, callback)
#
# data := [data point 0, data point 1, ...]
#
# statsinfo = (low_outliers, high outliers, bktsize)
#
# outliers := [data]
#
# data point := [xcoord, ycoord, [s.d., s.e.], (int)tag, [tag_list]]
#   optional s.d. and.s.e. if specified by fields attribute
#
# Setting dataset tag to None will colour each plotted point determined by
#   point tag value, setting dataset tag to a value will determine uniform
#   colour for all points in the set
#

class DataSet:

    def __init__(self, data, type, path, tag, style=None,
                 callback=None, fields=[], is_filter=0):

        dlen = len(data)
	if dlen == 0:
	    raise EmptyDataSetError, path
	try:
	    if type == DATA_TS:
		self.tsdata = data
		self.pdfdata = None
		self.cdfdata = None
                self.histdata = None
		self.tslen = dlen
		self.pdflen = None
		self.cdflen = None
                self.histlen = None
	    elif type == DATA_PDF:
		self.pdfdata = data
		self.tsdata = None
		self.tsdata_sm = None
		self.cdfdata = None
                self.histdata = None
		self.pdflen = dlen
		self.tslen = None
		self.cdflen = None
                self.histlen = None
	    elif type == DATA_CDF:
		self.cdfdata = data
		self.tsdata = None
		self.tsdata_sm = None
		self.pdfdata = None
                self.histdata = None
		self.cdflen = dlen
		self.tslen = None
		self.pdflen = None
                self.histlen = None
	    elif type == DATA_HIST:
		self.histdata = data
		self.tsdata = None
		self.tsdata_sm = None
		self.pdfdata = None
		self.cdfdata = None
		self.histlen = dlen
		self.tslen = None
		self.pdflen = None
		self.cdflen = None
	except AttributeError:
	    raise WrongDataTypeError
        
        self.tsdata_sm = {}
        self.pdfdata_sm = {}
        self.cdfdata_sm = {}
        self.histdata_sm = {}

        self.bktsz = None
	
	self.data = data
	self.len = dlen
	self.type = type
	self.path = path
        self.opath = '' # addition to label if smoothed
	self.tag = tag
        self.style = style
	self.callback = callback
        self.is_filter = is_filter

        if fields == None:
            self.fields = ['X', 'Y', 'T']
        else:
            self.fields = fields

        self.edits = []

    def printself(self):

	print 'len=%d type=%d path=%s' % (self.len, self.type, self.path)

    def initialise(self):
        #print 'initialising %s' % (self.path)
	self.data.sort()
	self.hide = IntVar()
	self.hidden = [0]
        self.docol = IntVar()
	self.docol.set(0)

	self.reset()

    def reset(self):

	#self.printself()
	self.hide.set(self.hidden[-1])
	self.hidden = [self.hide.get()]
	self.curr_indices = [(0, self.len - 1)]
	try:
	    #self.ops.configure(fg='black')
	    self.ops.top.configure(state=NORMAL)
	    self.ops.hide.configure(state=NORMAL)
	    self.ops.col.configure(state=NORMAL)
	except AttributeError:
	    # only one set
		pass

############################################################################## 
#############################################################################

#
# Empty data set exception
#

class EmptyDataSetError:

    def __init__(self, val):
        self.val = val

    def __str__(self):
	return self.val

############################################################################## 
#############################################################################

#
# Wrong data type exception
#

class WrongDataTypeError:

    def __init__(self, val):
	self.value = val

    def __str__(self):
	return self.value
	    
############################################################################## 
#############################################################################

#
# General data exception
# 
class DataError(Exception):
    
    def __init__(self, value):
        self.value = value
        
    def __str__(self):
        return `self.value`
	    
############################################################################## 
#############################################################################

#
# Decorations for plots
#

class Plot_Decoration:

    def __init__(self, type, coords, colour='red', label=''):

        self.type = type
        self.coords = coords
        self.col = colour
        self.label = label
	    
############################################################################## 
#############################################################################
#
# Class to read data from file(s)
#
#############################################################################

class DataReader:

    def __init__(self, args, fields=[]):

        self.args = args
        self.fields = fields

        self.gets = [
            None,
            self.get1,
            self.get2,
            self.get3,
            self.get4
            ]
            

    
#############################################################################

    def get_float(self, s):

        try:
            f = float(s)
        except ValueError:
            toks = s.split('E')
            try:
                exp = int(toks[1])
            except ValueError:
                # may be comma separated
                exp = int(toks[1][:-1])

            f = float(toks[0])*pow(10, exp)

        return f
        

##############################################################################

    def get_file(self, dir):

        root = Tk()
        root.winfo_toplevel().title('Plot:')
        fnm =  tkFileDialog.askopenfilename( \
            title='Plot', initialdir=dir,     
            filetypes=[('All files', '.*')])

        root.destroy()
        del root
        return fnm
	    
	     
##############################################################################

    def get1(self, ss):

        return [float(ss[0])]
	    
	     
##############################################################################

    def get2(self, ss):

        return [float(ss[0]), float(ss[1])]
	    
	     
##############################################################################

    def get3(self, ss):

        return [float(ss[0]), float(ss[1]), float(ss[2])]
	    
	     
##############################################################################

    def get4(self, ss):

        return [float(ss[0]), float(ss[1]), float(ss[2]), float(ss[3])]
	    
	     
##############################################################################

    def get5(self, ss):

        return [float(ss[0]), float(ss[1]), float(ss[2]), float(ss[3]), float(ss[4])]
	    
	     
##############################################################################

    def get_values(self, f, getf):

        f.seek(0)
        
        lno = 0
        data = []

        try:
            for s in f.readlines():
                lno += 1
                if not len(s):
                    break
                s = s.strip()
                if (not s) or s[0] == ' ' or s[0] =='' or s[0] =='\n' or s[0] == '#':
                    continue
                ss = s.replace(',', ' ').split()
                data.append(getf(ss))
        except IndexError:
                    raise DataError, 'Malformed data at line %d in %s\'%s\'' % (lno, f.name, s.rstrip())
            

        return data
	    
	     
##############################################################################

    def get_values_xing(self, f, getf):

        f.seek(0)
        
        lno = 0
        data = []
        x = 0

        try:
            for s in f:
                lno += 1
                if not len(s):
                    break
                if (not s) or s[0] == '' or s[0] == ' ' or s[0] =='\n' or s[0] == '#':
                    continue
                ss = s.split()
                d = getf(ss)
                d.insert(0, x)
                x += 1
                data.append(d)
        except IndexError:
                    raise DataError, 'Malformed data at line %d in %s\'%s\'' % (lno, f.name, s)
            

        return data
	    
	     
##############################################################################

    def get_stuff(self, files):

        def reslabel(s, key, line):
            s = s.replace(key, '')[:-1]
          ##   if indata:
##                 raise DataError, 'Plot directive follows data line %s' % (line)
            return s.replace('\\n', '\n')

        #print files
        #path = os.path.split(files[0])[0]
        title = ''
        xlab = ''
        ylab = ''
        decs = []
        path = files[0]
        tagno = 0
        tag = None
        sets = []
        types = []
        for fl in files:
            if fl[-4:] == '.cdf':
                type = DATA_CDF
            if fl[-5:] == '.hist':
                type = DATA_HIST
            elif fl[-4:] == '.pdf':
                type = DATA_PDF
            else:
                type = DATA_TS
            types.append(type)
            data = []
            fields = thesefields = self.fields
            notags = 1
            indata = 0
            line = 0
            xing = 1

            f = open(fl, 'r')
            #for s in f:
            for s in f.readlines():
                #print s
                line = line + 1
                if not len(s):
                    break
                if s[0] == ' ' or s[0] =='\n':
                    continue
                if s[0] == '#':
                    try:
                        if s.find('#plot-title=') == 0:
                            title = reslabel(s, '#plot-title=', line)
                        if s.find('#plot-xlab=') == 0:
                            xlab = reslabel(s, '#plot-xlab=', line)
                        if s.find('#plot-ylab=') == 0:
                            ylab = reslabel(s,'#plot-ylab=' , line)
                        if s.find('#fields=') == 0:
                            thesefields = field_str2l(reslabel(s,'#fields=' , line))
                        if s.find('#plot-dec=') == 0:
                            dec = []
                            toks = string.split(string.replace(s, '#plot-dec=', '')[:-1], ':')
                            dec.append(string.atoi(toks[0]))
                            coords = []
                            cs = string.split(toks[1][1:-1], ',')
                            for coord in cs:
                                coords.append(string.atof(coord))
                            dec.append(coords)
                            dec.append(toks[2])
                            dec.append(toks[3])
                            decs.append(dec)
                    except DataError, s:
                        s = 'Data Error %s in file %s' % (s, fl)
                        raise DataError, s

                    continue
                else:
                    # first data line
                    break

            if (fields and thesefields) and not fields_eq(fields, thesefields):
                print 'Fields contradiction', field_l2str(fields), field_l2str(thesefields)
                fields = thesefields
            
            vs = s.split()
            if not fields or thesefields:
                # get an idea of data layout
                dl = len(vs)
                if dl == 1:
                    fields = ['Y']
                elif dl == 2:
                    fields = ['X', 'Y']
                elif dl == 3:
                    fields = ['X', 'Y', 'T']
                elif dl == 4:
                    fields = ['X', 'Y', 'SD', 'SE']
            dl = len(fields)
            if 'X' in fields:
                xing = 0
            if 'T' in fields:
                tag = None
            else:
                tag = tagno


            getf = self.gets[dl]
            
            if xing:
                data = self.get_values_xing(f, getf)
            else:
                data = self.get_values(f, getf)
            tagno += 1
                
            try:
                ds = DataSet(data, type, fl, tag, fields=fields)
                sets.append(ds)
            except EmptyDataSetError:
                raise DataError, 'File %s contains no data' % (fl)

            #print title, xlab, ylab

        alltype = type = types[0]
        all_same = 1
        for t in types:
            if t != type:
                print 'Warning - mixed data types'
                alltype = DATA_TS

        return (sets, path, title, xlab, ylab, decs, alltype)
	    
	     
##############################################################################

    def get_data(self):


        files = []
        dirs = []

        if self.args:
            for arg in self.args:
                if os.path.isfile(arg):
                    files.append(arg)
                elif os.path.isdir(arg):
                    dirs.append(arg)

            for dir in dirs:
                fnm =  self.get_file(dir)
                if fnm:
                    files.append(fnm)
        else:
            fnm =  self.get_file(os.getcwd())
            if fnm:
                files.append(fnm)


        if not files:
            raise DataError, 'No data files'
        else:
            decorations = [] 
            sets, path, title, xlab, ylab, decs, mode = self.get_stuff(files)

            for dec in decs:
                decorations.append(Plot_Decoration(dec[0], dec[1],
                                      colour=dec[2], label=dec[3]))


        return (sets, path, title, xlab, ylab, decs, mode, decorations)

    
##############################################################################
##############################################################################
#
# Class to read in function for plotter to draw (may be filter for existing
#  data set
#
##############################################################################
##############################################################################

class AddFun(Toplevel):

    def __init__(self, plot, newdata):

        self.plot = plot
        self.master = plot.root
        self.newdata = newdata
        fnspec = plot.fnspec
        if fnspec:
            self.filter = fnspec[0]
            self.xmin =  fnspec[1]
            self.xmax = fnspec[2] 
            self.inc = fnspec[3] 
            self.consta = fnspec[4] 
            self.constb = fnspec[5] 
            self.constc = fnspec[6] 
            self.constm = fnspec[7] 
            self.constn = fnspec[8] 
            self.fn = fnspec[9]
        elif plot.sets:
            self.filter = None
            self.xmin = plot.canvasses[-1].xmin
            self.xmax = plot.canvasses[-1].xmax 
            self.inc = (self.xmax - self.xmin)/100 
            self.consta = None 
            self.constb = None 
            self.constc = None
            self.constm = None
            self.constn = None  
            self.fn = None
        else:
            self.filter = None
            self.xmin = None
            self.xmax = None
            self.inc = 1.0 
            self.consta = None 
            self.constb = None 
            self.constc = None 
            self.constm = None
            self.constn = None  
            self.fn = None

        self.fn_checked = 0

        self.get_maths()
        
	Toplevel.__init__(self, self.master)
	self.transient(self.master)

        self.title('Add function')
	
	self.configure(bg='grey')
	
	body = Frame(self, bg='grey')
	self.initial_focus = self.body(body)
	body.pack(padx=5, pady=5)
	body.configure(bg='grey')
	
	self.grab_set()
	
	if not self.initial_focus:
	    self.initial_focus = self
	    
	self.protocol("WM_DELETE_WINDOW", self.cancel)

	ypos = self.master.winfo_rooty()/2
	
	#self.geometry("+%d+%d" % (master.winfo_rootx(),ypos))
	self.initial_focus.focus_set()
	self.wait_window(self)
	     
##############################################################################

    def get_maths(self):

        # allowable math functions
        melist = dir(math)
        melist.remove('__doc__')
        melist.remove('__name__')
        self.melist = melist
        # operators
        self.elist = ['+', '-', '*', '%', '/', '(', ')', ' ', '.']
        # math fns with multiple args
        self.marg_elist = ['atan', 'fmod', 'hypot', 'ldexp', 'pow']
        # filter operators and fns
        self.complist = ['if', '>', '<', '==', '!=', 'or', 'and', ',']
	     
##############################################################################

    def body(self, master):

        def sz(s):

            try:
                while s[-1] in [' ', '0']:
                    s = s[:-1]
            except IndexError:
                s = '0'
            if s[-1] == '.':
                s = s[:-1]
            return s

        
	Label(master, text='', bg='grey').grid(row=0, column=0)
	# Title entry
	Label(master, text='Enter function', bg='grey').grid(row=1, column=0, 
						    sticky=W, columnspan=2)

        self.fn_entry = BetterEntry(master)
	self.fn_entry.configure(bg='white', width=100)
	self.fn_entry.grid(row=1, column=2, columnspan=10)

        self.fn_entry.bind('<Key>', self.activate_ok)
        self.fn_entry.count = 0
        #self.fn = 'sin(x)'
        if self.fn:
            self.fn_entry.insert(END, self.fn)
            self.fn_entry.count = len(self.fn)
        
	Label(master, text='', bg='grey').grid(row=2, column=0)

        
	Label(master, text='x range', bg='grey').grid(row=3, column=0, 
						    sticky=W)

        f = Frame(master, bg='red', height=5)
        f.grid(row=3, column=1,sticky=EW, columnspan=2)

        self.xrange1 = BetterEntry(f)
        #self.xrange1.configure(bg='white', width=5)
        self.xrange1.configure(bg='white')
        self.xrange1.pack(side=LEFT,fill=Y)

        self.xrange1.bind('<Key>', self.activate_ok)
        if self.xmin != None:
            es = sz('%.10f' % (self.xmin))
        else:
            es = ''
        self.xrange1.count = len(es)
        self.xrange1.insert(END, es)

        lab = Label(f, text=':', bg='grey').pack(side=LEFT,fill=Y)

        self.xrange2 = BetterEntry(f)
        #self.xrange2.configure(bg='white', width=5)
        self.xrange2.configure(bg='white')
        self.xrange2.pack(side=LEFT,fill=Y)

        self.xrange2.bind('<Key>', self.activate_ok)
        if self.xmax != None:
            es = sz('%.10f' % (self.xmax))
        else:
            es = ''
        self.xrange2.insert(END, es)
        self.xrange2.count = len(es)

        Label(master, text='xinc', bg='grey').grid(row=3, column=3, sticky=E)
        self.xinc = BetterEntry(master)
        #self.xinc.configure(bg='white', width=5)
        self.xinc.configure(bg='white')
        self.xinc.grid(row=3, column=4, sticky=EW)
        self.xinc.insert(END, sz('%.10f' % (self.inc)))
        self.xinc.bind('<Key>', self.activate_ok)
        self.xinc.count = len(self.xinc.get())

        self.constcol = 4

        self.constalab = Label(master, text='a', bg='grey')#.grid(row=3, column=3, sticky=E)
        self.constaentry = BetterEntry(master)
        self.constaentry.configure(bg='white', width=5)
        if self.consta != None:
            s = sz('%.10f' % (self.consta))
        else:
            s = ''
        self.constaentry.insert(END, s)
        self.constaentry.bind('<Key>', self.activate_ok)
        self.constaentry.count = len(self.constaentry.get())

        self.constblab = Label(master, text='b', bg='grey')#.grid(row=3, column=5, sticky=E)
        self.constbentry = BetterEntry(master)
        self.constbentry.configure(bg='white', width=5)
        if self.constb != None:
            s = sz('%.10f' % (self.constb))
        else:
            s = ''
        self.constbentry.insert(END, s)
        self.constbentry.bind('<Key>', self.activate_ok)
        self.constbentry.count = len(self.constbentry.get())

        self.constclab = Label(master, text='c', bg='grey')#.grid(row=3, column=7, sticky=E)
        self.constcentry = BetterEntry(master)
        self.constcentry.configure(bg='white', width=5)
        if self.constc != None:
            s = sz('%.10f' % (self.constc))
        else:
            s = ''
        self.constcentry.insert(END, s)
        self.constcentry.bind('<Key>', self.activate_ok)
        self.constcentry.count = len(self.constcentry.get())

        self.constmlab = Label(master, text='m', bg='grey')#.grid(row=3, column=7, sticky=E)
        self.constmentry = BetterEntry(master)
        self.constmentry.configure(bg='white', width=5)
        if self.constm != None:
            s = sz('%.10f' % (self.constm))
        else:
            s = ''
        self.constmentry.insert(END, s)
        self.constmentry.bind('<Key>', self.activate_ok)
        self.constmentry.count = len(self.constmentry.get())

        self.constnlab = Label(master, text='n', bg='grey')#.grid(row=3, column=7, sticky=E)
        self.constnentry = BetterEntry(master)
        self.constnentry.configure(bg='white', width=5)
        if self.constn != None:
            s = sz('%.10f' % (self.constn))
        else:
            s = ''
        self.constnentry.insert(END, s)
        self.constnentry.bind('<Key>', self.activate_ok)
        self.constnentry.count = len(self.constnentry.get())

	# OK/Cancel buttons
	self.okb = Button(master, text='  OK  ', bg='grey', fg='grey56', 
	       command = self.ok, state=DISABLED)
	self.okb.grid(row=4, column=9, columnspan=1)

	self.cancb = Button(master, text='Cancel', bg='grey', 
	       command=self.cancel).grid(row=4, column=10, columnspan=1)
        
	self.errlabel = Label(master, text='', bg='grey',
                              font=("Times", 12)

)
        self.errlabel.grid(row=4, column=0, columnspan = 8, sticky=W)

        
        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)



        self.entries = [(self.xrange1, 'x range0 '),
                     (self.xrange2, 'x range1 '),
                     (self.xinc, 'x increment '),
                     (self.fn_entry, 'Can\'t evaluate function ')]
                     

        self.consts = {
        'a': [self.constaentry, self.constalab, 'const a', 0, self.consta],
        'b': [self.constbentry, self.constblab, 'const b', 0, self.constb],
        'c': [self.constcentry, self.constclab, 'const c', 0, self.constc],
        'm': [self.constmentry, self.constmlab, 'const m', 0, self.constm],
        'n': [self.constnentry, self.constnlab, 'const n', 0, self.constn]
                             }

        t = self.consts.items()
        t.sort()
        for c, e in t:
            if e[4] != None:
                self.show_const(c)


        return self.fn_entry
	     
##############################################################################

    def show_const(self, c):
        
        e = self.consts[c]
        if not e[3]:
            e[1].grid(row=3, column=self.constcol, sticky=E)
            e[0].grid(row=3, column=self.constcol+1 , sticky=W)
            self.constcol += 2
            e[3] = 1
            e[0].focus_set()
            
        return e[0]
        
	     
##############################################################################

    def hide_const(self, c):

       e = self.consts[c]
       if e[3]:
           e[1].grid_forget()
           e[0].grid_forget()
           self.constcol -= 2
           e[3] = 0
        
	     
##############################################################################

    def activate_ok(self, event=None):

        # keep count of the chars in each entry field
        if event.char and ord(event.char) == 8:
            event.widget.count = max(0, event.widget.count-1)
        elif event.char:
            event.widget.count += 1

        if event.widget == self.fn_entry:
            self.fn_checked = 0

        # only activate OK button if entry in each field
        # (ok fn checks entry values)
        ok = 1
        for field in [self.fn_entry, self.xrange1, self.xrange2, self.xinc]:
            if field.count <= 0:
                ok = 0
        
        if ok:
            self.okb.configure(state=NORMAL, fg='black')
            self.errlabel.configure(text='', fg='grey')
        else:
            self.okb.configure(state=DISABLED, fg='grey56')
        
	     
##############################################################################

    def error(self, s):
        
        self.errlabel.configure(text=s, fg='red')
        self.okb.configure(state=DISABLED, fg='grey56')

        return None
        
	     
##############################################################################

    def inform(self, s):
        
        self.errlabel.configure(text=s, fg='blue')
        #self.okb.configure(state=DISABLED, fg='grey56')

        return None
	     
##############################################################################

    #
    # Examine entered fn to check vars/consts in allowed range, and if it's
    # a filter - basic method is simple, take everything away which is
    # understood and see what's left

    def check_eval(self, fn):
        
        vars = []
        digits = []
        self.filter = 0

        for e in self.melist:
            #fn = fn.replace(e, '')
            exp = '%s.*?\(.*?\)' % (e)
            fn = re.sub(exp, '', fn)
        for e in self.elist:
            fn = fn.replace(e, '')
        for e in self.complist:
            if fn.count(e):
                self.filter = 1
                fn = fn.replace(e, '')
        for c in fn:
            if c == 'y':
                self.filter = 1
            if not c.isdigit():
                vars.append(c)
            else:
                digits.append(c)

        possvars = ['a', 'b', 'c', 'm', 'n', 'x', 'y']
        usevars = vars[:]
                              
        for c in possvars:
            while c in vars:
                vars.remove(c)
            fn = fn.replace(c, '')

        for d in digits:
            fn = fn.replace(d, '')

        # if there's anything left it's an error
        if vars:
            return self.error('Unknown variable or constant \'%s\'' % (vars[0]))
        if fn:
            return self.error('Don\'t understand \'%s\' in function definition' % (fn))

        # light up any constant entry fields and report
        for const in ['a', 'b', 'c', 'm', 'n']:
            if const in usevars:
                ce = self.show_const(const)
                if ce.get() == '':
                    return self.error('Need value for const %s' % (const))
                self.consts[const][4] = self.EVAL(ce.get(), 'Can\'t evaluate const %s' % (const), None, None, None, None, None, None, None, None)
                if self.consts[const][4] == None:
                    return self.consts[const][4]
            else:
                self.hide_const(const)

        return 1 # success return
    
        
	     
##############################################################################

    #
    # Build a Python list comprehension from the filter specified
    #

    def build_fn(self, fn):

        if not self.filter:
            return fn

        def repc(mo):
            #print mo.groups()
            return mo.group(0).replace(',', '$')

        #
        # Need to split filter specifiers on commas, but some math funs with
        #  multiple args contain commas in arglist - find these and substitute
        #  '$' for the moment
        #
        for f in self.marg_elist:
            exp = '%s.*?\(.*?(,).*?\)' % (f)
            fn = re.sub(exp, repc, fn)

        #print 'mod fn', fn

        # separate spec. from conditional clause
        i = fn.find('if')
        if i > 0:
            # both spec and conditional
            f1 = fn[:i]
            f2 = fn[i:]
        elif i == 0:
            # conditional only - add in spec
            f1 = 'x,y'
            f2 = fn
        else:
            # no conditional
            f1 = fn
            f2 = ''
        #print 'if split', f1, ',', f2

        # replace 'x'/'y' references with data point x/y indices
        f1 = f1.replace('x', 'd[0]')
        f1 = f1.replace('y', 'd[1]')
        f2 = f2.replace('x', 'd[0]')
        f2 = f2.replace('y', 'd[1]')

        # what's the form of the spec
        ff = f1.split(',')
        #print 'f1 split', ff, len(ff)
        if len(ff) == 1:
            # single spec - assume applies to yvals
            f1 = '[d[0], %s]' % (f1)
        elif len(ff[0]) == 0:
            # no x spec
            f1 = '[d[0], %s]' % (ff[1])
        elif len(ff[1]) == 0:
            # no y spec
            f1 = '[%s, d[1]]' % ff[0]
        else:
            # both x, y specs
            f1 = '[%s, %s]' % (ff[0], ff[1])
        
        fn = '[%s for d in data %s]' % (f1, f2)
        # restore commas
        fn = fn.replace('$', ',')
        #print fn

        return fn
        
	     
##############################################################################

    #
    # Trial evaluation of fn to see if parser can handle it
    #

    def EVAL(self, e, ss, x, y, a, b, c, m, n, data):
        
        try:
            v = eval(e)
        except ValueError, s:
            return self.error(ss + s.args[0])
        except NameError, s:
            return self.error(s = ss + s[0])
        except SyntaxError, s:
            return self.error(ss + '- syntax error:' + '\''+ s[1][-1] + '\'')
        return v
        
	     
##############################################################################

    def fn_ok(self, fn):

        fn = fn[1:-1]
        fn = fn.replace('for d in data', '')
        fn = fn.replace('d[0]', 'x')
        fn = fn.replace('d[1]', 'y')
        fn = fn.replace('[', '(')
        fn = fn.replace(']', ')')

        self.inform('Apply %s ?' % (fn))
        self.fn_checked = 1
	     
##############################################################################

    def ok(self, event=None):

        if not self.check_eval(self.fn_entry.get()):
            return
        
        x = 3.4785724
        y = 0.394726
        a = self.consts['a'][4]
        b = self.consts['b'][4]
        c = self.consts['c'][4]
        m = self.consts['m'][4]
        n = self.consts['n'][4]

        data = [[1,1], [2,2]]

        vals = []
        for e, s in self.entries:
            #print e,s
            if e != self.fn_entry:
                val = self.EVAL(e.get(), s, x, y, a, b, c, m, n, data)
            else:
                val = self.EVAL(self.build_fn(e.get()), s, x, y, a, b, c, m, n, data)
            if val == None:
                return
            vals.append(val)

        x0, x1, inc = vals[:3]
        fn = self.build_fn(self.fn_entry.get())

        if inc == 0:
            self.error('zero xinc')
            return

        if self.filter and not self.fn_checked:
            self.fn_ok(fn)
            return
            
        try:
            self.make_data(fn, x0, x1, inc, a, b, c, m, n)
        except DataError, s:
            self.error(s)
            return

        self.cancel()
	     
##############################################################################

    def cancel(self, event=None):
	self.plot.canvasses[-1].canv.focus_set()
	self.destroy()
	     
##############################################################################

    def get_path_suff(self): 

        def sbc(sp):
            thing = sp.group(2)
            newthing = thing
            for c in ['a', 'b', 'c', 'm', 'n']:
                newthing = newthing.replace(c, c.upper())
            #news = sp.group(1).replace(thing, newthing)
            return sp.group(1).replace(thing, newthing)
                

        s = self.fn_entry.get()
        #avoid PATH confusion
        s = s.replace('/', 'DIV')
        # subst const vals
        contslist = self.melist + self.complist
        clist = [e for e in contslist if 'a' in e or 'b' in e or 'c' in e or 'm' in e or 'n' in e]
        #print clist
        for e in clist:
            exp = '(.*?(%s).*?)' % (e)
            s = re.sub(exp, sbc, s)
        s = s.replace('a', self.constaentry.get())
        s = s.replace('b', self.constbentry.get())
        s = s.replace('c', self.constcentry.get())
        s = s.replace('m', self.constmentry.get())
        s = s.replace('n', self.constnentry.get())
        s = s.replace('A', 'a')
        s = s.replace('B', 'b')
        s = s.replace('C', 'c')
        s = s.replace('M', 'm')
        s = s.replace('N', 'n')
        
        return s
	     
##############################################################################

    def make_data(self, fn, x0, x1, inc, a, b, c, m, n):

       # print fn, x0, x1, inc

       sets = []
       if not self.filter:
           newd = []
           inc = fabs(inc)
           if x0 > x1:
               x0, x1 = x1, x0
               
           x = x0
           try:
               while x <= x1:
                   newd.append([x, eval(fn)])
                   x += inc
           except OverflowError:
               self.errlabel.configure(text='Overflow during evaluation', fg='red')
               self.okb.configure(state=DISABLED, fg='grey56')
               return
           path_suff = self.get_path_suff()

           try:
               sets.append(DataSet(newd, DATA_TS, path_suff, None, fields=['X', 'Y'], is_filter=0))
           except EmptyDataSetError:
               raise DataError, 'Function plots no data'

       else:

           topseti = self.plot.topset.get()
           for s in self.plot.sets:
               #if s.is_filter or s.hidden[-1]:
               if s.indx != topseti:
                   continue
               data = s.data
               #print data
               try:
                   newd = eval(fn)
               except OverflowError:
                   self.error('Overflow during evaluation')
                   return
               path_suff = self.get_path_suff()
               try:
                   sets.append(DataSet(newd, DATA_TS, s.path+path_suff,
                                       None, fields=['X', 'Y'], is_filter=1))
               except EmptyDataSetError:
                   #raise DataError, 'Function plots no data'
                   self.error('Function plots no data')
                   return

       self.newdata.append((sets, (self.filter, x0, x1, inc, a, b, c, m, n,
                            self.fn_entry.get())))

       self.master.focus_set()

    
##############################################################################
##############################################################################
#
# Class to edit data point values
#
##############################################################################
##############################################################################

class Dedit(Toplevel):

    def __init__(self, master, pts, newpts):
        
        self.master = master
        self.pts = pts
        self.newpts = newpts
        self.entries = []
        
	Toplevel.__init__(self, self.master)
	self.transient(self.master)

        self.title('Edit data')
	
	self.configure(bg='grey')
	
	body = Frame(self, bg='grey')
	self.initial_focus = self.body(body)
	body.pack(padx=5, pady=5)
	body.configure(bg='grey')
	
	self.grab_set()
	
	if not self.initial_focus:
	    self.initial_focus = self
	    
	self.protocol("WM_DELETE_WINDOW", self.cancel)

	ypos = self.master.winfo_rooty()/2
	
	#self.geometry("+%d+%d" % (master.winfo_rootx(),ypos))
	self.initial_focus.focus_set()
	self.wait_window(self)
	     
##############################################################################

    def body(self, master):
        
	Label(master, text='', bg='grey').grid(row=0, column=0)

        row = 1
        for indx, ent in self.pts:
            Label(master, text='Index %d' % indx, bg='grey').grid(row=row,
                                         column=0, sticky=W)
            f = BetterEntry(master)
            f.configure(bg='white', width=100)
            f.grid(row=row, column=2)
            f.insert(0, ent)
            self.entries.append((indx, f))
            row += 1

        self.errlabel = Label(master, text='', bg='grey', fg='red')
        self.errlabel.grid(row=row, column=0, sticky=W, columnspan=2)
        row += 1

	# OK/Cancel buttons
	self.okb = Button(master, text='  OK  ', bg='grey', 
	       command = self.ok)
	self.okb.grid(row=row, column=0)

	self.cancb = Button(master, text='Cancel', bg='grey', 
	       command=self.cancel).grid(row=row, column=1)
	     
##############################################################################

    def ok(self, event=None):

        for indx in range(len(self.entries)):
            i, f = self.entries[indx]
            newent = f.get()
            i1, ent = self.pts[indx]
            if i != i1 or len(newent.split()) != len(ent.split()):
                self.errlabel.configure(text='Data fields wrong - index %d' % (i1))
                return
            for fld in newent.split():
                try:
                    val = float(fld)
                except:
                    self.errlabel.configure(text='Can\'t evaluate value - index %d' % (i1))
                    return
                     
            self.newpts.append((i, newent))

        self.cancel()
	     
##############################################################################

    def cancel(self, event=None):
        
	self.destroy()

        

        

       
##############################################################################
##############################################################################

class Nowt:

    pass


def quit():
    sys.exit(0)

def handle_sigint(n, f):
    print 'SIGINT'
    #handle_sigint_def(n, f)
    sys.exit(0)

def main():

   # print 'main'

    testroot = Tk()
    r = Nowt()
    r.root = testroot
    r.sets = [Nowt()]
    r.sets[0].data = [[1,1], [1, 2]]
    r.sets[0].path = 'Data'
    r.fnspec = (0, 0.0, 10.0, 1.0, None, None, None, None, None, None)



    handle_sigint_def = signal(SIGINT, handle_sigint)

    d = []
    AddFun(r, d)
    
    testroot.mainloop()

    for v in d[0][0]:
        print v.data

	     
##############################################################################


# Call main when run as script
if __name__ == '__main__':
        main()


