"""

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 BSD license.  See
LICENSE.txt for details.

Copyright (c) 2001-2002, Prabhu Ramachandran.
"""

__author__ = "Prabhu Ramachandran <prabhu_r@users.sf.net>"
__version__ = "$Revision: 1.7 $"
__date__ = "$Date: 2003/10/11 18:59:55 $"

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:
            self.seed.SetCenter (*cen)
        else:
            diff = map (lambda x, y: x-y, cen, self.last_cen)
            tr = self.transform.GetTransform ()
            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
        self.sphere_src.SetCenter (*self.cen)
        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 _lift (self):
        """Lifts an already created configuration window to the
        top."""
        debug ("In PointStreamer::_lift ()")
        self.root.deiconify ()
        self.root.lift ()
        self.root.focus_set ()

    def configure (self, master=None):
        debug ("In PointStreamer::configure ()")
        if (self.root and self.root.winfo_exists ()):
            return self._lift ()
        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)
        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])
        
        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])
            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 ()
        self.root = None

    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 ()
