"""

This module makes it possible to view streamlines, streamtubes, and
stream ribbons for any type of vector data.  Any number of point
sources can be added and deleted.  A fairly powerful UI is provided.
This module should work with any dataset.

This code is distributed under the conditions of the GPL (GNU General
Public License).  Look at the file LICENSE included with the
distribution for details.

Copyright (C) 2001, Prabhu Ramachandran.
"""

__author__ = "Prabhu Ramachandran <prabhu_r@users.sf.net>"
__version__ = "$Revision: 1.4 $"
__date__ = "$Date: 2001/10/01 03:09:23 $"

import Base.Objects, Common
import Tkinter, tkColorChooser, tkMessageBox, tkFileDialog, math
import vtkpython
import vtkPipeline.vtkMethodParser
import vtkPipeline.ConfigVtkObj

tk_fopen = tkFileDialog.askopenfilename
tk_fsave = tkFileDialog.asksaveasfilename
debug = Common.debug


class SeedManager:

    """ Abstracts out a seed manager.  This class is capable of
    handling a vtkPointSource as well as a vtkDiskSource.  The
    DiskSource is used for 2D data.  The DiskSource is appropriately
    oriented so that it is on the right plane."""
    
    def __init__(self):        
        """ Create the SeedManager. Call initialize to setup the seed."""
        debug ("In SeedManager::__init__ ()")
        self.dim = 0
        self.last_cen = [0.0]*3
        self.seed = None
        self.transform = None
        
    def initialize (self, valid_coord):        
        """ Initializes the seed given an array of valid co-ordinate
        directions. [x-axis, y-axis, z_axis] is the format.  For
        instance if x-axis == 0 then the data is along the YZ plane.
        This method is responsible for actually creating the seed. """

        debug ("In SeedManager::initialize ()")
        assert len (valid_coord) == 3
        self.dim = reduce (lambda x, y: x+y, valid_coord)
        if self.dim == 3:
            self.seed = vtkpython.vtkPointSource ()
        else:
            self.seed = vtkpython.vtkDiskSource ()
            self.seed.SetRadialResolution (1)
            self.seed.SetInnerRadius (0.0)
            self.transform = vtkpython.vtkTransformFilter ()
            self.transform.SetTransform (vtkpython.vtkTransform ())
            self.transform.SetInput (self.seed.GetOutput ())
            self.orient_2d (valid_coord)
                
    def orient_2d (self, valid_coord):
        """ Orients the vtkDiskSource for the given co-ordinates."""
        debug ("In SeedManager::orient_2d ()")
        assert len (valid_coord) == 3
        transform = self.transform.GetTransform ()
        if valid_coord[0] == 0: # Data plane is along YZ plane
            transform.RotateY(90)
        elif valid_coord[1] == 0: # Data plane is along XZ plane
            transform.RotateX(90)
        elif valid_coord[2] == 0: # Data plane is along XY plane
            pass # default orientation!

    def set_n_points (self, n):
        """ Set the number of points created by the seed."""
        debug ("In SeedManager::set_n_points ()")
        if self.dim == 3:
            self.seed.SetNumberOfPoints (n)
        else:
            self.seed.SetCircumferentialResolution (n)
        
    def get_n_points (self):
        """ Get the number of seed points."""
        debug ("In SeedManager::get_n_points ()")
        if self.dim == 3:
            return self.seed.GetNumberOfPoints ()
        else:
            return self.seed.GetCircumferentialResolution ()

    def set_radius (self, rad):
        """ Set the seed radius. """
        debug ("In SeedManager::set_radius ()")
        if self.dim == 3:
            self.seed.SetRadius (rad)
        else:
            self.seed.SetOuterRadius (rad)

    def set_center (self, cen):
        """ Set the seed's center given an array of co-ordinates."""
        debug ("In SeedManager::set_center ()")
        if self.dim == 3:
            apply (self.seed.SetCenter, cen)
        else:
            diff = map (lambda x, y: x-y, cen, self.last_cen)
            tr = self.transform.GetTransform ()
            apply (tr.Translate, diff)
            self.last_cen = cen[:]

    def GetOutput (self):
        """ Get the VTK Output of the appropriate seed."""
        debug ("In SeedManager::GetOutput ()")
        if self.dim == 3:
            return self.seed.GetOutput ()
        else:
            return self.transform.GetOutput ()


class PointStreamer (Base.Objects.Module):
    """Provides the basic class for streamline support. """
    def __init__ (self, mod_m):
        debug ("In PointStreamer::__init__ ()")
        Common.state.busy ()
        Base.Objects.Module.__init__ (self, mod_m)
        self.sphere_src = vtkpython.vtkSphereSource ()
        self.sphere_map = vtkpython.vtkPolyDataMapper ()
        self.sphere_act = vtkpython.vtkActor ()        
        self.seed = SeedManager ()
        self.radius = 1.0
        self.cen = [0.0, 0.0, 0.0]
        self.n_pnt = 50
        self.strmln_mode = 0
        self.color_mode = 1 # 1 is vector, 0 is scalar, -1 is no color
        self.integration_mode = 2 # 2 is Runge-Kutta 2 and 4 is RK 4.

        self.strmln = vtkpython.vtkStreamLine ()
        self.ribbonf = vtkpython.vtkRibbonFilter ()
        self.tubef = vtkpython.vtkTubeFilter ()
        self.mapper = self.stream_map = vtkpython.vtkPolyDataMapper ()
        self.actor = self.stream_act = vtkpython.vtkActor ()
        self.done_init = 0
        Common.state.idle ()

    def __del__ (self):
        debug ("In PointStreamer::__del__ ()")
        Common.state.busy ()
        self.renwin.remove_actors ((self.sphere_act, self.stream_act))
        self.renwin.Render ()
        Common.state.idle ()

    def get_defaults (self):
        debug ("In PointStreamer::get_defaults ()")
        self.resoln_var = []
        self.def_dim = []
        self.dim = []
        self.data_dimn = 0
        self.coord = [0,0,0]
        dim1 = self.mod_m.get_data_source ().GetOutput ().GetBounds ()
        max_len = -1.0
        for i in range (0,3):
            self.dim.append ((dim1[2*i], dim1[2*i+1]))
            val = dim1[2*i] + dim1[2*i+1]
            self.def_dim.append (val*0.5)
            mag = abs (dim1[2*i+1] - dim1[2*i])
            if mag > max_len:
                max_len = mag
            var = Tkinter.DoubleVar ()
            var.set (mag*0.02)
            self.resoln_var.append (var)
            if dim1[2*i] != dim1[2*i+1]:
                self.coord[i] = 1
                self.data_dimn = self.data_dimn + 1

        # now that we know enough about the data - initialize the seed.
        self.seed.initialize (self.coord)

        self.cen = self.def_dim
        self.radius = max_len*0.1
        apply (self.sphere_src.SetCenter, self.cen)
        apply (self.sphere_src.SetRadius, (self.radius, ))
        # setting some sane defaults
        self.ribbonf.SetWidth (self.radius*0.075)
        self.tubef.SetRadius (self.radius*0.075)

    def initialize (self, master=None):
        debug ("In PointStreamer::initialize ()")
        self.get_defaults ()
        self.sphere_src.SetThetaResolution (10)
        self.sphere_src.SetPhiResolution (10)
        self.sphere_map.SetInput (self.sphere_src.GetOutput ())
        self.sphere_act.SetMapper (self.sphere_map)
        self.sphere_act.GetProperty ().SetColor (0.0, 0.0, 0.5)
        self.sphere_act.GetProperty ().SetOpacity (0.5)
        self.max_v = self.mod_m.get_vector_data_range ()[1]

        self.seed.set_n_points (self.n_pnt)
        self.strmln.SetSource (self.seed.GetOutput ())
        self.strmln.SpeedScalarsOn ()
        self.stream_map.SetInput (self.strmln.GetOutput ())
        self.stream_map.SetScalarRange (0, self.max_v)
        self.stream_map.SetLookupTable (self.mod_m.get_vector_lut ())
        self.stream_act.SetMapper (self.stream_map)

        self.stream_act.VisibilityOff ()
        self.renwin.add_actors ((self.sphere_act, self.stream_act))
        self.configure (master)

    def SetInput (self, source):
        debug ("In PointStreamer::SetInput ()")
        self.strmln.SetInput (source)
        self.do_color_mode ()

    def make_ball_gui (self, master):
        debug ("In PointStreamer::make_ball_gui ()")
        frame = Tkinter.Frame (master, relief="ridge", bd=2)
        frame.pack (side='top')
        self.coord_var = Tkinter.StringVar ()
        self.coord_var.set (self.cen)
        lab = Tkinter.Label (frame, text="Center of Source:")
        lab.grid (row=0, column=0, columnspan=1, sticky='w')
        entr = Tkinter.Entry (frame, width=20, relief='sunken', 
                      textvariable=self.coord_var)
        entr.grid (row=0, column=1, columnspan=2, sticky='w')

        def_name = ["Move along X-axis", "Move along Y-axis ", 
                    "Move along Z-axis"]
        res_name = ["X resolution:", "Y resolution:", "Z resolution:"]
        self.slider = []
        
        rw = 1
        for i in range (0,3):
            if self.coord[i] == 1:
                sl = Tkinter.Scale (frame, label=def_name[i], 
                                    from_=self.dim[i][0],
                                    to=self.dim[i][1], 
                                    length="8c", orient='horizontal', 
                                    resolution=self.resoln_var[i].get ())
                sl.set (self.cen[i])
                sl.grid (row=rw, column=0, columnspan=3, sticky='ew')
                rw = rw + 1
                sl.bind ("<ButtonRelease>", self.change_locator)
                lab = Tkinter.Label (frame, text=res_name[i])
                lab.grid (row=rw, column=0, sticky='w')
                entr = Tkinter.Entry (frame, width=5,
                                      relief='sunken',
                                      textvariable=self.resoln_var[i])
                entr.grid (row=rw, column=1, sticky='w')
                entr.bind ("<Return>", self.set_resolution)
                rw = rw+1
                self.slider.append (sl)        

        self.radius_var = Tkinter.DoubleVar ()
        self.radius_var.set (self.radius)
        self.n_pnt_var = Tkinter.DoubleVar ()
        self.n_pnt_var.set (self.n_pnt)
        lab = Tkinter.Label (frame, text="Radius of source:")
        lab.grid (row=rw, column=0, sticky='w')
        entr = Tkinter.Entry (frame, width=5, relief='sunken',
                              textvariable=self.radius_var)
        entr.grid (row=rw, column=1, sticky='w')
        entr.bind ("<Return>", self.setup_ball)
        rw = rw + 1
        lab = Tkinter.Label (frame, text="Number of seed points:")
        lab.grid (row=rw, column=0, sticky='w')
        entr = Tkinter.Entry (frame, width=5, relief='sunken',
                      textvariable=self.n_pnt_var)
        entr.grid (row=rw, column=1, sticky='w')
        rw = rw + 1
        entr.bind ("<Return>", self.setup_ball)
        self.ball_var = Tkinter.IntVar ()
        self.ball_var.set (1)
        cb = Tkinter.Checkbutton (frame, text="Show PointSource",
                                  variable=self.ball_var, onvalue=1,
                                  offvalue=0, command=self.show_ball)
        cb.grid (row=rw, column=0, columnspan=2, sticky='w')

    def make_color_mode_gui (self, master):
        debug ("In PointStreamer::make_color_mode_gui ()")
        f = Tkinter.Frame (master, bd=2)
        f.pack (side='top')
        frame = Tkinter.Frame (f, relief="ridge")
        frame.grid (row=0, column=0, sticky='w')
        self.color_var = Tkinter.IntVar ()
        self.color_var.set (self.color_mode)
        rb = Tkinter.Radiobutton (frame, text="No Coloring",
                                  variable=self.color_var, value=-1,
                                  command=self.set_color_mode_gui)
        rb.grid (row=0, column=0, sticky='w')
        rb = Tkinter.Radiobutton (frame, text="Scalar Coloring",
                                  variable=self.color_var, value=0,
                                  command=self.set_color_mode_gui)
        rb.grid (row=1, column=0, sticky='w')
        rb = Tkinter.Radiobutton (frame, text="Vector Coloring",
                                  variable=self.color_var, value=1,
                                  command=self.set_color_mode_gui)
        rb.grid (row=2, column=0, sticky='w')
        but = Tkinter.Button (frame, text="Streamline Color",
                              command=self.set_stream_color)
        but.grid (row=3, column=0, sticky='ew')

        frame = Tkinter.Frame (f, relief="ridge")
        frame.grid (row=0, column=1, sticky='w')
        self.integration_var = Tkinter.IntVar ()
        self.integration_var.set (self.integration_mode)
        lab = Tkinter.Label (frame, text="Integration Type")
        lab.grid (row=0, column=0, columnspan=1, sticky='ew')
        rb = Tkinter.Radiobutton (frame, text="Runge-Kutta 2nd Order",
                                  variable=self.integration_var, value=2,
                                  command=self.set_integration_gui)
        rb.grid (row=1, column=0, sticky='w')
        rb = Tkinter.Radiobutton (frame, text="Runge-Kutta 4th Order",
                                  variable=self.integration_var, value=4,
                                  command=self.set_integration_gui)
        rb.grid (row=2, column=0, sticky='w')

    def make_other_gui (self, master):
        debug ("In PointStreamer::make_other_gui ()")
        frame = Tkinter.Frame (master, relief="ridge", bd=2)
        frame.pack (side='top')
        lab = Tkinter.Label (frame, text="Integration parameters:")
        lab.grid (row=0, column=0, sticky='w', pady=5)
        but = Tkinter.Button (frame, text="Configure",
                              command=self.setup_strmln)
        but.grid (row=0, column=1, sticky='w')
        self.strmln_var = Tkinter.IntVar ()
        self.strmln_var.set (self.strmln_mode)
        rb = Tkinter.Radiobutton (frame, text="Show Streamlines",
                                  variable=self.strmln_var, value=0,
                                  command=self.set_streamline_mode)
        rb.grid (row=1, column=0, sticky='w')
        rb = Tkinter.Radiobutton (frame, text="Show Streamribbons",
                                  variable=self.strmln_var, value=1,
                                  command=self.set_streamline_mode)
        rb.grid (row=2, column=0, sticky='w')
        but = Tkinter.Button (frame, text="Configure",
                              command=self.setup_ribbonf)
        but.grid (row=2, column=1, sticky='w')
        rb = Tkinter.Radiobutton (frame, text="Show Streamtubes",
                                  variable=self.strmln_var, value=2,
                                  command=self.set_streamline_mode)
        rb.grid (row=3, column=0, sticky='w')
        but = Tkinter.Button (frame, text="Configure",
                              command=self.setup_tubef)
        but.grid (row=3, column=1, sticky='w')        
        
        but = Tkinter.Button (frame, text="Update changes", 
                      command=self.update, underline=0)
        but.grid (row=4, column=0, sticky='w')        
        self.root.bind ("<Alt-u>", self.update)
        but = Tkinter.Button (frame, text="Close", 
                      command=self.quit, underline=0)
        but.grid (row=4, column=1, sticky='w')
        self.root.bind ("<Alt-c>", self.quit)        

    def configure (self, master=None):
        debug ("In PointStreamer::configure ()")
        self.n_pnt = self.seed.get_n_points ()
        self.radius = self.sphere_src.GetRadius ()
        self.cen = list (self.sphere_src.GetCenter ())
        self.root = Tkinter.Toplevel (master)
        self.root.transient (master)
        self.root.title ("Configure the streamlines.")
        self.make_ball_gui (self.root)
        self.make_color_mode_gui (self.root)
        self.make_actor_gui (scalar=0, color=0, compact=1)
        self.make_other_gui (self.root)
        self.sphere_act.VisibilityOn ()
        self.renwin.Render ()

    def setup_ball (self, event=None):
        debug ("In PointStreamer::setup_ball ()")
        Common.state.busy ()
        val = self.n_pnt_var.get ()        
        self.n_pnt = val
        self.seed.set_n_points (self.n_pnt)
        val = self.radius_var.get ()
        self.radius = val
        #self.seed.set_radius (self.radius)
        apply (self.sphere_src.SetRadius, (self.radius, ))
        self.renwin.Render ()
        Common.state.idle ()

    def set_streamline_mode (self, event=None):
        debug ("In PointStreamer::set_streamline_mode ()")
        Common.state.busy ()
        self.strmln_mode = self.strmln_var.get ()
        self.setup_stream_pipeline ()
        self.renwin.Render ()
        Common.state.idle ()

    def setup_stream_pipeline (self):
        debug ("In PointStreamer::setup_stream_pipeline ()")
        val = self.strmln_mode
        if val == 0:
            self.stream_map.SetInput (self.strmln.GetOutput ())
        elif val == 1:
            self.ribbonf.SetInput (self.strmln.GetOutput ())
            self.stream_map.SetInput (self.ribbonf.GetOutput ())
        elif val == 2:
            self.tubef.SetInput (self.strmln.GetOutput ())
            self.stream_map.SetInput (self.tubef.GetOutput ())

    def setup_ribbonf (self, event=None):
        debug ("In PointStreamer::setup_ribbonf ()")
        conf = vtkPipeline.ConfigVtkObj.ConfigVtkObj (self.renwin)
        conf.configure (None, self.ribbonf)

    def setup_tubef (self, event=None):
        debug ("In PointStreamer::setup_tubef ()")
        conf = vtkPipeline.ConfigVtkObj.ConfigVtkObj (self.renwin)
        conf.configure (None, self.tubef)

    def setup_strmln (self, event=None):
        debug ("In PointStreamer::setup_strmln ()")
        conf = vtkPipeline.ConfigVtkObj.ConfigVtkObj (self.renwin)
        conf.configure (None, self.strmln)
    
    def change_locator (self, event=None):
        debug ("In PointStreamer::change_locator ()")
        val = []
        for i in range (0, 3):
            if self.coord[i] == 1:
                val.append (self.slider[i].get ())
            else:
                val.append (self.cen[i])
        
        apply (self.sphere_src.SetCenter, val)
        self.cen = val
        self.coord_var.set (val)
        self.renwin.Render ()

    def show_ball (self, event=None):
        debug ("In PointStreamer::show_ball ()")
        Common.state.busy ()
        val = self.ball_var.get ()
        if val == 1:
            self.sphere_act.VisibilityOn ()
        elif val == 0:
            self.sphere_act.VisibilityOff ()
        self.renwin.Render ()
        Common.state.idle ()

    def set_resolution (self, event=None):
        debug ("In PointStreamer::set_resolution ()")
        for i in range (0, 3):
            if self.coord[i] == 1:
                val = self.resoln_var[i].get ()
                self.slider[i].config (resolution=val)

    def set_color_mode_gui (self, event=None):
        "This sets up the data to setup the actual coloring mode."
        debug ("In PointStreamer::set_color_mode_gui ()")
        Common.state.busy ()
        self.color_mode = self.color_var.get ()
        self.do_color_mode ()
        self.renwin.Render ()
        Common.state.idle ()

    def do_color_mode (self):
        debug ("In PointStreamer::do_color_mode ()")
        if self.color_mode == 1: # Vector Coloring
            dr = self.mod_m.get_vector_data_range ()
            self.max_v = dr[1]
            self.stream_map.ScalarVisibilityOn ()
            self.strmln.SpeedScalarsOn ()
            self.stream_map.SetScalarRange (dr)
            self.stream_map.SetLookupTable (self.mod_m.get_vector_lut ())
        elif self.color_mode == 0: # Scalar Coloring
            self.stream_map.ScalarVisibilityOn ()
            self.strmln.SpeedScalarsOff ()
            dr = self.mod_m.get_scalar_data_range ()
            self.stream_map.SetScalarRange (dr)
            self.stream_map.SetLookupTable (self.mod_m.get_scalar_lut ())
        elif self.color_mode == -1: # No colouring
            self.stream_map.ScalarVisibilityOff ()
            self.strmln.SpeedScalarsOff ()

    def set_stream_color (self, event=None):
        debug ("In PointStreamer::set_stream_color ()")
        clr = self.stream_act.GetProperty ().GetColor ()
        init_clr = "#%02x%02x%02x"%(clr[0]*255, clr[1]*255, clr[2]*255)
        color = tkColorChooser.askcolor (title="Change axes color", 
                                         initialcolor=init_clr)
        if color[1] is not None:
            Common.state.busy ()
            clr = Common.tk_2_vtk_color (color[0])
            apply (self.stream_act.GetProperty ().SetColor, clr)
            self.renwin.Render ()        
            Common.state.idle ()
            if self.color_mode != -1:
                msg = "Warning: This setting will have effect only if "\
                      "the coloring mode is set to 'No Coloring'."
                Common.print_err (msg)

    def set_integration_gui (self, event=None):
        debug ("In PointStreamer::set_integration_gui ()")
        val = self.integration_var.get()
        self.integration_mode = val
        self.update_integration_mode ()

    def update_integration_mode (self):
        debug ("In PointStreamer::update_integration_mode ()")
        Common.state.busy ()
        val = self.integration_mode
        try:
            if val == 2:
                self.strmln.SetIntegrator (vtkpython.vtkRungeKutta2 ())
            elif val == 4:
                self.strmln.SetIntegrator (vtkpython.vtkRungeKutta4 ())
        except AttributeError, err:            
            msg = "Sorry unable to change the integration type. "\
                  "Try upgrading your VTK installation to a more "\
                  "recent version."
            Common.print_err (msg)            
        self.renwin.Render ()
        Common.state.idle ()
            
    def update (self, event=None):
        debug ("In PointStreamer::update ()")
        Common.state.busy ()
        self.seed.set_center (self.cen)
        self.seed.set_radius (self.radius)
        self.seed.set_n_points (self.n_pnt)
        if not self.done_init:
            self.strmln.SetInput (self.mod_m.GetOutput ())
            self.done_init = 1
            self.stream_act.VisibilityOn ()
        self.strmln.Update ()
        self.renwin.Render ()
        Common.state.idle ()
        
    def save_config (self, file):
        debug ("In PointStreamer::save_config ()")
        file.write ("%d, %d, %d\n"%(self.n_pnt, self.strmln_mode,
                                    self.integration_mode))
        # For backward compatibility - the dummy is actually unused.
        dummy_seed = vtkpython.vtkPointSource ()
        p = vtkPipeline.vtkMethodParser.VtkPickler ()
        for i in (self.sphere_src, self.sphere_map, self.sphere_act, 
                  self.sphere_act.GetProperty (),
                  dummy_seed, self.strmln, self.ribbonf, self.tubef, 
                  self.stream_map, self.stream_act,
                  self.stream_act.GetProperty ()):
            p.dump (i, file)

    def load_config (self, file):
        debug ("In PointStreamer::load_config ()")
        self.setup_pipeline ()
        val = file.readline ()
        try:
            self.n_pnt, self.strmln_mode, self.integration_mode = eval (val)
        except ValueError: # old format
            self.n_pnt, self.strmln_mode = eval (val)
        # For backward compatibility - the dummy is actually unused.
        dummy_seed = vtkpython.vtkPointSource ()        
        p = vtkPipeline.vtkMethodParser.VtkPickler ()
        for i in (self.sphere_src, self.sphere_map, self.sphere_act,
                  self.sphere_act.GetProperty (),
                  dummy_seed, self.strmln, self.ribbonf, self.tubef, 
                  self.stream_map, self.stream_act,
                  self.stream_act.GetProperty ()):
            p.load (i, file)
        self.setup_stream_pipeline ()
        self.radius = self.sphere_src.GetRadius ()
        self.cen = list (self.sphere_src.GetCenter ())
        self.color_mode = self.strmln.GetSpeedScalars ()
        if self.stream_map.GetScalarVisibility () == 0:
            self.color_mode = -1
        self.do_color_mode ()
        self.update ()
        self.update_integration_mode ()

    def setup_pipeline (self):
        debug ("In PointStreamer::setup_pipeline ()")
        self.get_defaults ()
        self.sphere_map.SetInput (self.sphere_src.GetOutput ())
        self.sphere_act.SetMapper (self.sphere_map)
        self.max_v = self.mod_m.get_vector_data_range ()[1]

        self.strmln.SetSource (self.seed.GetOutput ())
        self.stream_map.SetInput (self.strmln.GetOutput ())
        self.stream_map.SetScalarRange (0, self.max_v)
        self.stream_map.SetLookupTable (self.mod_m.get_vector_lut ())
        self.stream_act.SetMapper (self.stream_map)

        self.stream_act.VisibilityOff ()
        self.renwin.add_actors ((self.sphere_act, self.stream_act))        

    def quit (self, event=None):
        debug ("In PointStreamer::quit ()")
        self.sphere_act.VisibilityOff ()
        self.renwin.Render ()
        self.root.destroy ()
        del self.root

    def get_actors (self):
        debug ("In PointStreamer::get_actors ()")
        return [self.sphere_act, self.stream_act]


class Streamlines (Base.Objects.Module):

    """ This module makes it possible to view streamlines,
    streamtubes, and stream ribbons for any type of vector data.  Any
    number of point sources can be added and deleted.  A fairly
    powerful UI is provided.  This module should work with any
    dataset.  """

    def __init__ (self, mod_m):
        debug ("In Streamlines::__init__ ()")
        Common.state.busy ()
        Base.Objects.Module.__init__ (self, mod_m)
        self.stream_ctl = {}
        self.pipe_objs = []
        self.n_streams = 0
        Common.state.idle ()

    def __del__ (self):
        debug ("In Streamlines::__del__ ()")
        Common.state.busy ()
        for key in self.stream_ctl.keys ():
            del self.stream_ctl[key]
        del self.pipe_objs
        self.renwin.Render ()
        Common.state.idle ()

    def SetInput (self, source):
        debug ("In Streamlines::SetInput ()")
        Common.state.busy ()
        for obj in self.stream_ctl.values ():
            obj.SetInput (source)
        Common.state.idle ()

    def add_streamline (self, key):
        debug ("In Streamlines::add_streamline ()")
        ctl = PointStreamer (self.mod_m)
        ctl.initialize (self.root.master)
        self.stream_ctl[key] = ctl
        self.pipe_objs.extend (ctl.get_actors ())

    def config_streamline (self, key):
        debug ("In Streamlines::config_streamline ()")
        self.stream_ctl[key].configure (self.root.master)

    def delete_streamline (self, key):
        debug ("In Streamlines::delete_streamline ()")
        del self.stream_ctl[key]
        
    def save_config (self, file):
        debug ("In Streamlines::save_config ()")
        n = len (self.stream_ctl)
        file.write ("%d\n"%n)
        for key in self.stream_ctl.keys ():
            self.stream_ctl[key].save_config (file)
        file.flush ()

    def load_config (self, file):
        debug ("In Streamlines::load_config ()")
        start_val = self.n_streams
        n = eval (file.readline ())
        for i in range (0, n):
            ctl = PointStreamer (self.mod_m)
            ctl.load_config (file)
            key = "PointSource%d"%(start_val+i)
            self.stream_ctl[key] = ctl
            self.pipe_objs.extend (ctl.get_actors ())
            self.n_streams = self.n_streams + 1

    def make_custom_gui (self):
        debug ("In Streamlines::make_custom_gui ()")
        self.make_pipeline_gui ()
        self.make_main_gui ()
        self.make_close_button ()

    def make_main_gui (self):
        debug ("In Streamlines::make_main_gui ()")
        frame = Tkinter.Frame (self.root, relief='ridge', bd=2)
        frame.pack (side='top')
        scr = Tkinter.Scrollbar (frame, orient='vertical')
        self.str_lst = Tkinter.Listbox (frame, yscrollcommand=scr.set, 
                                        selectmode='single')
        scr.config (command=self.str_lst.yview)        
        self.str_lst.grid (row=0, column=0, sticky='ewns')
        scr.grid (row=0, column=1, sticky='ns')
        self.str_lst.bind ("<Double-Button-1>", self.config_streamline_gui)

        for key in self.stream_ctl.keys ():
            self.str_lst.insert ('end', key)

        but1 = Tkinter.Button (frame, text="Add streamline source", 
                               command=self.add_streamline_gui)
        but2 = Tkinter.Button (frame, text="Delete streamline source", 
                               command=self.delete_streamline_gui)
        but1.grid (row=1, column=0, columnspan=2, sticky='ew')
        but2.grid (row=2, column=0, columnspan=2, sticky='ew')
        but2 = Tkinter.Button (frame, text="Save streamlines", 
                               command=self.save)
        but2.grid (row=3, column=0, columnspan=2, sticky='ew')
        but2 = Tkinter.Button (frame, text="Load streamlines",
                               command=self.load)
        but2.grid (row=4, column=0, columnspan=2, sticky='ew')
        
    def add_streamline_gui (self):
        debug ("In Streamlines::add_streamline_gui ()")
        Common.state.busy ()
        str = "PointSource%d"%self.n_streams
        self.str_lst.insert ('end', str)
        self.n_streams = self.n_streams + 1
        self.add_streamline (str)
        Common.state.idle ()

    def delete_streamline_gui (self):
        debug ("In Streamlines::delete_streamline_gui ()")
        Common.state.busy ()
        str = self.str_lst.get ('active')
        msg = "You are about to delete the streamlines due to the "\
              "%s. Are you sure you want to do this?" %(str)
        ans = tkMessageBox.askyesno ("Delete Streamline?", msg)
        if ans == 1:
            self.str_lst.delete ('active')            
            self.delete_streamline (str)
        Common.state.idle ()

    def config_streamline_gui (self, event=None):
        debug ("In Streamlines::config_streamline_gui ()")
        str = self.str_lst.get ('active')
        self.config_streamline (str)

    def save (self):
        debug ("In Streamlines::save ()")
        file_name = tk_fsave (title="Save streamlines", 
                              initialdir=Common.config.initial_dir,
                              defaultextension=".str",
                              filetypes=[("Streamline files", "*.str"), 
                                         ("All files", "*")])
        if file_name:
            Common.state.busy ()
            file = open (file_name, "w")
            self.save_config (file)
            file.close ()
            Common.state.idle ()

    def load (self):
        debug ("In Streamlines::load ()")
        file_name = tk_fopen (title="Load streamlines", 
                              initialdir=Common.config.initial_dir,
                              filetypes=[("Streamline files", "*.str"), 
                                         ("All files", "*")])
        if file_name:
            Common.state.busy ()
            file = open (file_name, "r")
            n_orig = len (self.stream_ctl)
            self.load_config (file)
            n = len (self.stream_ctl) - n_orig
            for i in range (0, n):
                str = "PointSource%d"%self.n_streams
                self.str_lst.insert ('end', str)
                self.n_streams = self.n_streams + 1
            file.close ()
            Common.state.idle ()
