Index: invariantview/perspectives/invariant/invariant.py
===================================================================
--- invariantview/perspectives/invariant/invariant.py	(revision d7a39e5e4d2fdf0181a63b2cb239064dd754a674)
+++ invariantview/perspectives/invariant/invariant.py	(revision 4e1c362faa5def12383a4fd3550db72d838ee262)
@@ -11,7 +11,9 @@
 #copyright 2009, University of Tennessee
 ################################################################################
-
+import os
+import sys
 import wx
-
+import copy
+import logging, time
 from sans.invariant import invariant
 
@@ -21,10 +23,12 @@
 from sans.guicomm.events import NewPlotEvent, StatusEvent
 from sans.guicomm.events import ERR_DATA
+from invariant_state import Reader as reader
+from DataLoader.loader import Loader
+from invariant_panel import InvariantPanel
 
 class Plugin:
     """
     This class defines the interface for invariant Plugin class
-    that can be used by the gui_manager.
-         
+    that can be used by the gui_manager.       
     """
     
@@ -43,4 +47,14 @@
         #  for your plug-in. This defines your plug-in "perspective"
         self.perspective = []
+        
+        self.state_reader = None   
+        """
+        # Create a CanSAS/Pr reader
+        self.state_reader = Reader(self.set_state)
+        l = Loader()
+        l.associate_file_reader('.inv', self.state_reader)  
+        """   
+        # Log startup
+        logging.info("Invariant plug-in started")
        
     def populate_menu(self, id, parent):
@@ -53,5 +67,4 @@
         
         :return: plug-in menu
-        
         """
         return []
@@ -60,5 +73,4 @@
         """
         Show a general help dialog. 
-    
         """
         from help_panel import  HelpWindow
@@ -78,5 +90,4 @@
         
         :return: list of panels
-        
         """
         ## Save a reference to the parent
@@ -84,8 +95,14 @@
         #add error back to the data
         self.parent.Bind(ERR_DATA, self._on_data_error)
-        from invariant_panel import InvariantPanel
+        
+        
         self.invariant_panel = InvariantPanel(parent=self.parent)
         self.invariant_panel.set_manager(manager=self)
-        self.perspective.append(self.invariant_panel.window_name)   
+        self.perspective.append(self.invariant_panel.window_name)  
+        #Create reader when fitting panel are created
+        self.state_reader = reader(self.set_state)   
+        #append that reader to list of available reader 
+        loader = Loader()
+        loader.associate_file_reader(".inv", self.state_reader)
         # Return the list of panels
         return [self.invariant_panel]
@@ -118,5 +135,4 @@
         
         :return: a list of menu items with call-back function
-        
         """
         self.graph = graph
@@ -148,5 +164,4 @@
         
         :param event: menu event
-        
         """
         self.parent.set_perspective(self.perspective)
@@ -171,5 +186,4 @@
         receive a data 1D and the list of errors on dy
         and create a new data1D data
-        
         """
         id = None
@@ -182,4 +196,5 @@
         data.dy = dy
         data.name = item.name
+        
         ## allow to highlight data when plotted
         data.interactive = item.interactive
@@ -217,4 +232,54 @@
                 # Set the data set to be user for invariant calculation
                 self.invariant_panel.set_data(data=data)
+                
+    def save_file(self, filepath, state=None):
+        """
+        Save data in provided state object.
+                
+        :param filepath: path of file to write to
+        :param state: invariant state 
+        """     
+        
+        # Write the state to file
+        # First, check that the data is of the right type
+
+        current_plottable = self.__data
+
+        if issubclass(current_plottable.__class__, LoaderData1D):
+            self.state_reader.write(filepath, current_plottable, state)
+        else:
+            raise RuntimeError, "invariant.save_file: the data being saved is not a DataLoader.data_info.Data1D object" 
+
+    def set_state(self, state, datainfo=None):    
+        """
+        Call-back method for the state reader.
+        This method is called when a .inv file is loaded.
+        
+        :param state: State object
+        """
+        
+        try:
+            if datainfo is None:
+                raise RuntimeError, "invariant.set_state: datainfo parameter cannot be None in standalone mode"
+            datainfo.meta_data['invstate'].file = datainfo.meta_data['invstate'].file+"_copy"
+            datainfo.name = datainfo.meta_data['invstate'].file
+            datainfo.filename = datainfo.meta_data['invstate'].file
+            self.__data = datainfo
+            self.__data.group_id = datainfo.filename
+            self.__data.id = datainfo.filename
+
+            temp_state = copy.deepcopy(state)
+            
+            # Load the invariant states
+            # Make sure the user sees the invariant panel after loading
+            self.parent.set_perspective(self.perspective)
+
+            # set state
+            self.invariant_panel.set_state(state=temp_state,data=self.__data)                
+
+        except:
+            logging.error("invariant.set_state: %s" % sys.exc_value)
+        
+    
         
     def plot_theory(self, data=None, name=None):
@@ -224,6 +289,6 @@
         :param data: extrapolated data to be plotted
         :param name: Data's name to use for the legend
-        
-        """
+        """
+        import copy
         if data is None:
             new_plot = Theory1D(x=[], y=[], dy=None)
@@ -245,4 +310,12 @@
         new_plot.group_id = self.__data.group_id
         new_plot.id = self.__data.id + name
+        # Save theory_data in a state
+        if data != None:
+            name_head = name.split('-')
+            if name_head[0]=='Low':
+                self.invariant_panel.state.theory_lowQ = copy.deepcopy(new_plot)
+            elif name_head[0]=='High':
+                self.invariant_panel.state.theory_highQ = copy.deepcopy(new_plot)
+
         wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=self.__data.name))
         
@@ -255,4 +328,8 @@
         new_plot.group_id = self.__data.group_id
         new_plot.id = self.__data.id 
+       
+        # Save data in a state: but seems to never happen 
+        if new_plot != None:
+            self.invariant_panel.state.data = copy.deepcopy(new_plot)
         wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
         
Index: invariantview/perspectives/invariant/invariant_panel.py
===================================================================
--- invariantview/perspectives/invariant/invariant_panel.py	(revision 90b9a1751aae8d088b9493b8954a1628ab998332)
+++ invariantview/perspectives/invariant/invariant_panel.py	(revision 4e1c362faa5def12383a4fd3550db72d838ee262)
@@ -6,5 +6,5 @@
 import wx
 
-import sys
+import sys,os
 from wx.lib.scrolledpanel import ScrolledPanel
 from sans.invariant import invariant
@@ -13,5 +13,6 @@
 from invariant_details import InvariantDetailsPanel, InvariantContainer
 from invariant_widgets import OutputTextCtrl, InvTextCtrl
-
+from invariant_state import InvariantState as IState
+import copy,time
 # The minimum q-value to be used when extrapolating
 Q_MINIMUM  = 1e-5
@@ -32,4 +33,5 @@
 #Invariant panel size 
 _BOX_WIDTH = 76
+
 
 if sys.platform.count("win32")>0:
@@ -70,5 +72,14 @@
         self._scale = SCALE 
         self._background = BACKGROUND
-
+        self._bmark = None
+        self.bookmark_num = 0
+        
+        self._set_bookmark_menu()
+        #Init state
+        self.set_state()
+        # default flags for state
+        self.new_state = False
+        self.is_power_out = False
+        
         #container of invariant value
         self.inv_container = None
@@ -76,8 +87,13 @@
         self._do_layout()
         self.reset_panel()
+        self._reset_state_list()
+        
         if self.parent is not None:
             msg = ""
             wx.PostEvent(self.parent,StatusEvent(status=msg, info="info"))
-
+            
+        ## Default file location for save
+        self._default_save_location = os.getcwd()
+ 
     def err_check_on_data(self):
         """
@@ -100,18 +116,35 @@
         """
         Set the data
-        """
+        
+        : return: True/False; if False, it will not set_data
+        """
+        # warn the users
+        if self._data != None and data != None:
+            if not self._show_message():
+                return False
+            
         self._data = data
+        # reset popUpMenu
+        self._set_bookmark_menu()
         #edit the panel
         if self._data is not None:
+            
             self.err_check_on_data()
+            self.get_state_by_num(0)
             data_name = self._data.name
             data_qmin = min (self._data.x)
             data_qmax = max (self._data.x)
-            self.data_name_tcl.SetValue(str(data_name))
+            self.data_name_tcl.SetValue(str(data_name))      
             self.data_min_tcl.SetLabel(str(data_qmin))
             self.data_max_tcl.SetLabel(str(data_qmax))
+            self.button_save.Enable(True)  
             self.reset_panel()
             self.compute_invariant(event=None)
-             
+            #Reset the list of states
+            self.state.data = copy.deepcopy(data)
+            self._reset_state_list()
+            
+        return True     
+
     def set_message(self):
         """
@@ -137,5 +170,40 @@
         """
         self._manager = manager 
-    
+        
+    def set_state(self,state=None,data=None):
+        """
+        set state when loading it from a .inv file
+        """
+        if state == None or data == None:
+            self.state = IState()
+        else:
+            if not self.set_data(data):
+                return
+            self.new_state = True
+            self.state = state   
+
+            num = self.state.saved_state['state_num']
+            self.get_state_by_num(state_num=num)
+            
+            if num >0 :
+                self._undo_enable()
+            if num < len(state.state_list)-1:
+                self._redo_enable()
+                
+            
+            # get bookmarks
+            self.bookmark_num = len(self.state.bookmark_list)
+            total_bookmark_num = self.bookmark_num+1
+            for ind in range(1,total_bookmark_num):
+                #bookmark_num = ind
+                value = self.state.bookmark_list[ind]
+                name = "%d] bookmarked at %s on %s"% (ind,value[0], value[1])
+                # append it to menu
+                id = wx.NewId()
+                self.popUpMenu.Append(id,name,str(''))
+                wx.EVT_MENU(self, id, self._back_to_bookmark)   
+            self.new_state = False 
+
+
     def get_background(self):
         """
@@ -272,5 +340,7 @@
                 wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
         else:
-            self._manager.plot_theory(name="Low-Q extrapolation")
+            try:
+                self._manager.plot_theory(name="Low-Q extrapolation")
+            except: pass
             
     def get_high_qstar(self, inv, high_q=False):
@@ -296,6 +366,8 @@
                 wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
         else:
-            self._manager.plot_theory(name="High-Q extrapolation")
-            
+            try:
+                self._manager.plot_theory(name="High-Q extrapolation")
+            except: pass
+
     def get_qstar(self, inv):
         """
@@ -341,5 +413,6 @@
                                    function=function_low, power=power_low)    
         return inv, npts_low  
-        
+    
+
     def set_extrapolation_high(self, inv, high_q=False):
         """
@@ -387,9 +460,16 @@
         compute invariant 
         """
-        msg= ""
-        wx.PostEvent(self.parent, StatusEvent(status=msg))
+        if self._data == None:
+            msg = "\n\nData must be loaded first in order to perform a compution..."
+            wx.PostEvent(self.parent, StatusEvent(status=msg))
+        # set a state for this computation for saving
+        elif event != None: 
+            self._set_compute_state(state='compute')
+            msg= "\n\nStarting a new invariant computation..."            
+            wx.PostEvent(self.parent, StatusEvent(status=msg))
+
         if self._data is None or self.err_check_on_data():
             return
-    
+        
         #clear outputs textctrl 
         self._reset_output()
@@ -442,4 +522,5 @@
         porod_const = self.get_porod_const()        
         contrast = self.get_contrast()
+        
         try:
             #Compute volume and set value to txtcrtl
@@ -453,4 +534,5 @@
             self.get_surface(inv=inv, contrast=contrast, porod_const=porod_const, 
                                     extrapolation=extrapolation)
+            
         except:
             msg = "Error occurred computing invariant: %s"%sys.exc_value
@@ -460,10 +542,174 @@
         #compute percentage of each invariant
         self.inv_container.compute_percentage()
+        
         #display a message
         self.set_message()
+        
+        # reset power_out to default to get ready for another '_on_text'
+        if self.is_power_out == True:
+            self.state.container = copy.deepcopy(self.inv_container)
+            self.state.timestamp= self._get_time_stamp()
+            msg = self.state.__str__()
+            self.is_power_out = False
+            wx.PostEvent(self.parent, StatusEvent(status = msg ))
+        
         #enable the button_ok for more details
         self.button_details.Enable()
+        
         self.button_details.SetFocus()
-        
+        if event != None: 
+            wx.PostEvent(self.parent, StatusEvent(status = '\nFinished invariant computation...'))
+    
+    def undo(self,event=None):
+        """
+        Go back to the previous state
+        
+        : param event: undo button event
+        
+        """
+        if event != None: event.Skip()
+        if self.state.state_num <0: return
+        self.is_power_out = True
+        # get the previous state_num
+        pre_state_num = int(self.state.saved_state['state_num']) - 1
+        self.get_state_by_num(state_num=str(pre_state_num))
+        
+        if float(pre_state_num) <=0:
+            self.button_undo.Disable()
+        elif not self.button_undo.Enabled:
+            self.button_undo.Enable(True)
+
+        self._redo_enable()
+        self.is_power_out = False  
+        self._info_state_num()
+        
+
+        
+    def redo(self,event=None):
+        """
+        Go forward to the previous state
+        
+        : param event: redo button event
+        
+        """
+        if event != None: event.Skip()
+        self.is_power_out = True
+        next_state_num = int(self.state.saved_state['state_num']) + 1
+
+        self.get_state_by_num(state_num=str(next_state_num))
+        
+        if float(next_state_num)+2 > len(self.state.state_list):
+            self.button_redo.Disable()
+        elif not self.button_redo.Enabled:
+            self.button_redo.Enable(True)
+        
+        self._undo_enable()
+        self.is_power_out = False
+        self._info_state_num()
+        
+    def get_state_by_num(self,state_num=None):
+        """
+        Get the state given by number
+        
+        : param state_num: the given state number
+        
+        """
+       
+        if state_num == None:
+            return
+
+        backup_state_list = copy.deepcopy(self.state.state_list)
+
+        # get the previous state
+        try:
+            current_state = copy.deepcopy(self.state.state_list[str(state_num)])
+            # get the previously computed state number (computation before the state changes happened)
+            current_compute_num = str(current_state['compute_num'])
+        except :
+            raise ValueError,  "No such state exists in history"
+
+        # get the state at pre_compute_num
+        comp_state = copy.deepcopy(self.state.state_list[current_compute_num])
+
+        # set the parameters
+        for key in comp_state:
+            value = comp_state[key]
+            try:
+                exec "self.%s.SetValue(str(%s))" % (key, value)
+            except TypeError:
+                exec "self.%s.SetValue(%s)" % (key, value)
+            except:
+                pass
+            
+        self.compute_invariant(event=None)
+        # set the input params at the state at pre_state_num
+        for key in current_state:
+            # Do not reset set some outputs
+            #key_split = key.split('_') 
+            #if key_split[0] == 'surface' or key_split[0] == 'volume':
+            #    continue
+            # set the inputs and boxes
+            value = current_state[key]
+
+            try:
+                exec 'self.%s.SetValue(str(%s))' % (key, value)
+            except TypeError:
+                exec 'self.%s.SetValue(%s)' % (key, value)
+            except:
+                pass
+
+        
+        self._enable_high_q_section(event=None)
+        self._enable_low_q_section(event=None)
+        self.state.state_list = backup_state_list
+        self.state.saved_state = current_state
+        self.state.state_num = state_num
+
+        
+    def get_bookmark_by_num(self, num=None):
+        """
+        Get the bookmark state given by number
+        
+        : param num: the given bookmark number
+        
+        """
+        current_state = {}
+        comp_state = {}
+        backup_state_list = copy.deepcopy(self.state.state_list)
+
+        # get the previous state
+        try:
+            time,date,current_state,comp_state = self.state.bookmark_list[int(num)] 
+        except :
+            raise ValueError,  "No such bookmark exists"
+
+        # set the parameters
+        for key in comp_state:
+            value = comp_state[key]
+            try:
+                exec "self.%s.SetValue(str(%s))" % (key, value)
+            except TypeError:
+                exec "self.%s.SetValue(%s)" % (key, value)
+            except:
+                pass
+
+        self.compute_invariant(event=None)
+        # set the input params at the state of pre_state_num
+        for key in current_state:
+            value = current_state[key]
+            try:
+                exec 'self.%s.SetValue(str(%s))' % (key, value)
+            except TypeError:
+                exec 'self.%s.SetValue(%s)' % (key, value)
+            except:
+                pass
+        self.state.saved_state = copy.deepcopy(current_state)
+
+        self._enable_high_q_section(event=None)
+        self._enable_low_q_section(event=None)
+        self.state.state_list = backup_state_list
+        #self.state.saved_state = current_state
+        #self.state.state_num = state_num
+
     def reset_panel(self):
         """
@@ -490,5 +736,341 @@
         self._enable_high_q_section()
         self._reset_output()
+        self.button_undo.Disable()
+        self.button_redo.Disable()
         self.button_calculate.SetFocus()
+        #self.SetupScrolling()
+        
+    def _set_state(self, event):
+        """
+        Set the state list
+        
+        :param event: rb/cb event
+        
+        """
+        if event == None:
+            return
+        obj = event.GetEventObject()
+        name = str(obj.GetName())
+        value = str(obj.GetValue())
+        rb_list = [['power_law_low','guinier'],['fit_enable_low','fix_enable_low'],['fit_enable_high','fix_enable_high']]
+
+        try:
+            if value == None or value.lstrip().rstrip() =='':
+                value = 'None'
+            exec 'self.state.%s = %s' % (name, value)
+            exec "self.state.saved_state['%s'] = %s" %  (name, value)
+            
+            # set the count part of radio button clicked False for the saved_state
+            for title,content in rb_list:
+                if name ==  title:
+                    name = content 
+                    value = False     
+                elif name == content:
+                    name = title
+                    value = False 
+            exec "self.state.saved_state['%s'] = %s" %  (name, value)     
+            
+            # Instead of changing the future, create a new future.
+            max_state_num = len(self.state.state_list)-1   
+            self.state.saved_state['state_num'] = max_state_num   
+            
+            self.state.saved_state['state_num'] +=1
+            self.state.state_num = self.state.saved_state['state_num']
+            self.state.state_list[str(self.state.state_num)] = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+
+        except:           
+            pass
+
+        event.Skip()
+        self._undo_enable()
+        self._redo_disable()
+            
+    def _set_compute_state(self,state=None):
+        """
+        Notify the compute_invariant state to self.state
+        
+        : param state: set 'compute' when the computation is activated by the 'compute' button, else None
+        
+        """
+        # reset the default
+        if state != 'compute':
+            self.new_state = False
+            self.is_power_out = False
+        else:
+            self.is_power_out = True
+        # Instead of changing the future, create a new future.
+        max_state_num = len(self.state.state_list)-1   
+        self.state.saved_state['state_num'] = max_state_num        
+        # A new computation is also A state
+        temp_saved_states = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+        temp_saved_states['state_num'] +=1
+        self.state.state_num = temp_saved_states['state_num']
+
+                
+        # set the state number of the computation 
+        if state == 'compute':
+            temp_saved_states['compute_num'] = self.state.state_num
+        self.state.saved_state= copy.deepcopy(temp_saved_states)
+        self.state.state_list[str(self.state.state_num)] = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+        
+        # A computation is a new state, so delete the states with any higher state numbers
+        for i in range(self.state.state_num+1,len(self.state.state_list)):
+            try:
+                del (self.state.state_list[str(i)])
+            except: 
+                pass
+        # Enable the undo button if it was not
+        self._undo_enable()
+        self._redo_disable()
+        
+    def _reset_state_list(self,data=None):
+        """
+        Reset the state_list just before data was loading: Used in 'set_data()'
+        """
+        #if data == None: return
+        #temp_state = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+        # Clear the list 
+        self.state.state_list.clear()
+        self.state.bookmark_list.clear()
+        # Set defaults
+        self.state.saved_state['state_num'] = 0
+        self.state.saved_state['compute_num'] = 0
+        if self._data != None:
+            self.state.saved_state['file'] = str(self._data.name)
+        else:
+            self.state.saved_state['file'] = 'None'
+        self.state.file = self.state.saved_state['file']
+
+        self.state.state_num = self.state.saved_state['state_num']
+        # Put only the current state in the list
+        self.state.state_list[str(self.state.state_num)] = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+        self._undo_disable()
+        
+    def _on_text(self, event):
+        """
+        Catch text change event to add the state to the state_list
+        
+        :param event: txtctr event ; assumes not None
+        
+        """
+        if self._data == None: 
+            return
+        # check if this event is from do/undo button
+        if self.state.saved_state['is_time_machine'] or self.new_state:
+            event.Skip()
+            return
+        
+        # get the object
+        obj = event.GetEventObject()
+        name = str(obj.GetName())
+        value = str(obj.GetValue())
+        state_num = self.state.saved_state['state_num']
+
+        # text event is a new state, so delete the states with higher state_num
+        # i.e., change the future
+        for i in range(int(state_num)+1,len(self.state.state_list)):
+            try:
+                del (self.state.state_list[str(i)])
+            except: 
+                pass
+        
+        # Instead of changing the future, create a new future.
+        #max_state_num = len(self.state.state_list)-1   
+        #self.state.saved_state['state_num'] = max_state_num
+
+        # try to add new state of the text changes in the state_list
+        try:
+            if value.strip() == None: value = ''
+            exec "self.state.%s = '%s'" % (name, value)
+            exec "self.state.saved_state['%s'] = '%s'" %  (name, value)
+            exec "self.state.input_list['%s'] = '%s'" % (name, value)
+            if not self.is_power_out:
+                if name != 'power_low_tcl' and name !='power_high_tcl':
+                    self.state.saved_state['state_num'] += 1
+            self.state.state_num = self.state.saved_state['state_num']
+            self.state.state_list[str(self.state.state_num)] = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+        except:
+            pass
+
+        event.Skip()
+        self._undo_enable()
+        self._redo_disable()
+        
+    def _on_out_text(self, event):     
+        """
+        Catch ouput text change to add the state 
+        
+        :param event: txtctr event ; assumes not None
+        
+        """    
+        # get the object
+        obj = event.GetEventObject()
+        name = str(obj.GetName())
+        value = str(obj.GetValue())
+        try:
+            exec "self.state.saved_state['%s'] = '%s'" %  (name, value)
+            self.state.state_list[str(self.state.state_num)] = self.state.clone_state()
+        except:
+            pass
+        if event != None: event.Skip()\
+        
+    def _set_bookmark_menu(self):
+        """
+        Setup 'bookmark' context menu
+        """
+        ## Create context menu for page
+        self.popUpMenu = wx.Menu()
+        id = wx.NewId()
+        self._bmark = wx.MenuItem(self.popUpMenu,id,"BookMark"," Bookmark the panel to recall it later")
+        self.popUpMenu.AppendItem(self._bmark)
+        self._bmark.Enable(True)
+        wx.EVT_MENU(self, id, self._on_bookmark)
+        self.popUpMenu.AppendSeparator()
+        self.Bind(wx.EVT_CONTEXT_MENU, self._on_context_menu)
+        
+    def _on_bookmark(self,event):
+        """
+        Save the panel state in memory and add the list on the popup menu on bookmark context menu event
+        """ 
+        if self._data == None: return
+        if event == None: return
+        self.bookmark_num += 1
+        # date and time of the event
+        #year, month, day,hour,minute,second,tda,ty,tm_isdst= time.localtime()
+        #my_time= str(hour)+" : "+str(minute)+" : "+str(second)+" "
+        #date= str( month)+"/"+str(day)+"/"+str(year)
+        my_time, date = self._get_time_stamp()
+        state_num = self.state.state_num
+        compute_num = self.state.saved_state['compute_num']
+        # name and message of the bookmark list
+        msg=  "State saved at %s on %s"%(my_time, date)
+         ## post help message for the selected model 
+        msg +=" Saved! right click on this page to retrieve this model"
+        #wx.PostEvent(self.parent.parent, StatusEvent(status = msg ))
+        name = "%d] bookmarked at %s on %s"%(self.bookmark_num,my_time, date)
+        
+        # append it to menu
+        id = wx.NewId()
+        self.popUpMenu.Append(id,name,str(msg))
+        wx.EVT_MENU(self, id, self._back_to_bookmark )
+        state = self.state.clone_state()
+        comp_state = copy.deepcopy(self.state.state_list[str(compute_num)])
+        self.state.bookmark_list[self.bookmark_num] = [my_time,date,state,comp_state]
+        self.state.toXML(self, doc=None, entry_node=None)
+
+    def _back_to_bookmark(self,event):
+        """
+        Bring the panel back to the state of bookmarked requested by context menu event
+        and set it as a new state
+        """
+        ## post help message for the selected model 
+        msg = self.popUpMenu.GetHelpString(event.GetId())
+        msg +=" reloaded"
+        wx.PostEvent(self.parent, StatusEvent(status = msg ))
+        
+        name= self.popUpMenu.GetLabel(event.GetId())
+        num,time = name.split(']')
+        current_state_num = self.state.state_num  
+        self.get_bookmark_by_num(num)
+        state_num = int(current_state_num)+1
+        
+        self.state.saved_state['state_num'] = state_num
+        self.state.state_list[str(state_num)] = self.state.clone_state()#copy.deepcopy(self.state.saved_state)
+        self.state.state_num = state_num
+        self._undo_enable()
+        self._info_bookmark_num(event)
+        
+    def _info_bookmark_num(self,event=None):
+        """
+        print the bookmark number in info
+        
+        : event: popUpMenu event
+        """
+        if event == None: return
+        # get the object
+        item = self.popUpMenu.FindItemById(event.GetId())
+        text = item.GetText()
+        num = text.split(']')[0]
+        msg = "bookmark num = %s "% num
+        
+        wx.PostEvent(self.parent, StatusEvent(status = msg ))
+        
+    def _info_state_num(self):
+        """
+        print the current state number in info
+        """
+        msg = "state num = "
+        msg += self.state.state_num
+        
+        wx.PostEvent(self.parent, StatusEvent(status = msg))
+                         
+    def _get_time_stamp(self):
+        """
+        return time and date stings
+        """
+        # date and time 
+        year, month, day,hour,minute,second,tda,ty,tm_isdst= time.localtime()
+        my_time= str(hour)+":"+str(minute)+":"+str(second)
+        date= str( month)+"/"+str(day)+"/"+str(year)
+        return my_time, date
+    
+    def _undo_enable(self):
+        """
+        Enable undo button
+        """
+        if not self.button_undo.IsEnabled():
+            self.button_undo.Enable(True)
+
+    def _undo_disable(self):
+        """
+        Disable undo button
+        """
+        if self.button_undo.IsEnabled():
+            self.button_undo.Disable()
+
+    def _redo_enable(self):
+        """
+        Enable redo button
+        """
+        if not self.button_redo.IsEnabled():
+            self.button_redo.Enable(True)
+
+    def _redo_disable(self):
+        """
+        Disable redo button
+        """
+        if self.button_redo.IsEnabled():
+            self.button_redo.Disable()
+            
+    def _on_save_button(self, evt=None): 
+        """
+        Save invariant state into a file
+        """
+        # Ask the user the location of the file to write to.
+        path = None
+        dlg = wx.FileDialog(self, "Choose a file", self._default_save_location, "", "*.inv", wx.SAVE)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            self._default_save_location = os.path.dirname(path)
+        else:
+            return None
+        
+        dlg.Destroy()
+        
+        self._manager.save_file(filepath=path, state=self.state)
+        
+    def _show_message(self, mssg='',msg='Warning'):
+        """
+        Show warning message when resetting data
+        """
+        
+        mssg += 'Loading a new data set will reset all the work done in this panel. \n\r'
+        mssg += 'Please make sure to save it first... \n\r'
+        answer = wx.MessageBox(mssg, msg, wx.CANCEL|wx.OK|wx.ICON_EXCLAMATION)
+
+        if answer == wx.OK:
+            return True
+        else:
+            return False
         
     def _reset_output(self):
@@ -504,5 +1086,13 @@
         #prepare a new container to put result of invariant
         self.inv_container = InvariantContainer()
+
+    
+    def _on_context_menu(self,event):
+        
+        pos = event.GetPosition()
+        pos = self.ScreenToClient(pos)
        
+        self.PopupMenu(self.popUpMenu, pos) 
+      
     def _define_structure(self):
         """
@@ -550,5 +1140,7 @@
         #Sizer related to button
         self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
-       
+        #Sizer related to save button
+        self.save_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        
     def _layout_data_name(self):
         """
@@ -557,5 +1149,5 @@
         #Sizer hint 
         hint_msg = "First open data file from 'File' menu.  Then Highlight and right click on the data plot. \n"
-        hint_msg += "Finally, select 'Compute Invariant'. \n"
+        hint_msg += "Finally, select 'Compute Invariant'."
         self.hint_msg_txt = wx.StaticText(self, -1, hint_msg)  
         self.hint_msg_txt.SetForegroundColour("red")
@@ -566,5 +1158,5 @@
         data_name_txt = wx.StaticText(self, -1, 'Data : ')  
        
-        self.data_name_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH*5, 20), style=0) 
+        self.data_name_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH*5, 20), style=0, name='data_name_tcl') 
         self.data_name_tcl.SetToolTipString("Data's name.")
         self.data_name_sizer.AddMany([(data_name_txt, 0, wx.LEFT|wx.RIGHT, 10),
@@ -573,8 +1165,8 @@
         data_range_txt = wx.StaticText(self, -1, 'Total Q Range (1/A): ') 
         data_min_txt = wx.StaticText(self, -1, 'Min : ')  
-        self.data_min_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.data_min_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0, name='data_min_tcl')
         self.data_min_tcl.SetToolTipString("The minimum value of q range.")
         data_max_txt = wx.StaticText(self, -1, 'Max : ') 
-        self.data_max_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0) 
+        self.data_max_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0, name='data_max_tcl') 
         self.data_max_tcl.SetToolTipString("The maximum value of q range.")
         self.data_range_sizer.AddMany([(data_range_txt, 0, wx.RIGHT, 10),
@@ -583,5 +1175,5 @@
                                        (data_max_txt, 0, wx.RIGHT, 10),
                                        (self.data_max_tcl, 0, wx.RIGHT, 10)])
-        self.data_name_boxsizer.AddMany([(self.hint_msg_sizer, 0 , wx.ALL, 10),
+        self.data_name_boxsizer.AddMany([(self.hint_msg_sizer, 0 , wx.ALL, 5),
                                          (self.data_name_sizer, 0 , wx.RIGHT, 10),
                                          (self.data_range_sizer, 0 , wx.ALL, 10)])
@@ -592,10 +1184,12 @@
         """
         background_txt = wx.StaticText(self, -1, 'Background : ')  
-        self.background_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0) 
-        background_hint_txt = "background"
+        self.background_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0, name='background_tcl') 
+        wx.EVT_TEXT(self, self.background_tcl.GetId(), self._on_text)
+        background_hint_txt = "Background"
         self.background_tcl.SetToolTipString(background_hint_txt)
         background_unit_txt = wx.StaticText(self, -1, '[1/cm]')  
         scale_txt = wx.StaticText(self, -1, 'Scale : ')  
-        self.scale_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.scale_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0, name='scale_tcl')
+        wx.EVT_TEXT(self, self.scale_tcl.GetId(), self._on_text)
         scale_hint_txt = "Scale"
         self.scale_tcl.SetToolTipString(scale_hint_txt)
@@ -611,5 +1205,6 @@
         """
         contrast_txt = wx.StaticText(self, -1, 'Contrast : ')  
-        self.contrast_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.contrast_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0,name='contrast_tcl')
+        wx.EVT_TEXT(self, self.contrast_tcl.GetId(), self._on_text)
         contrast_hint_txt = "Contrast"
         self.contrast_tcl.SetToolTipString(contrast_hint_txt)
@@ -617,5 +1212,6 @@
         porod_const_txt = wx.StaticText(self, -1, 'Porod Constant:')  
         self.porod_constant_tcl = InvTextCtrl(self, -1, 
-                                              size=(_BOX_WIDTH, 20), style=0) 
+                                              size=(_BOX_WIDTH, 20), style=0,name='porod_constant_tcl') 
+        wx.EVT_TEXT(self, self.porod_constant_tcl.GetId(), self._on_text)
         porod_const_hint_txt = "Porod Constant"
         self.porod_constant_tcl.SetToolTipString(porod_const_hint_txt)
@@ -632,14 +1228,22 @@
         Enable and disable the power value editing
         """
+        if event != None: 
+            print "enable fit==>event!=None"
+
         if self.fix_enable_low.IsEnabled():
+            
             if self.fix_enable_low.GetValue():
+                self.fit_enable_low.SetValue(False)
                 self.power_low_tcl.Enable()
             else:
+                self.fit_enable_low.SetValue(True)
                 self.power_low_tcl.Disable()
-            
+        self._set_state(event=event)
+           
     def _enable_low_q_section(self, event=None):
         """
         Disable or enable some button if the user enable low q extrapolation
         """
+        #if event != None: self._set_compute_state()
         if self.enable_low_cbox.GetValue():
             self.npts_low_tcl.Enable()
@@ -655,21 +1259,27 @@
             self.guinier.Disable()
             self.power_law_low.Disable()
+        
         self._enable_power_law_low()
         self._enable_fit_power_law_low()
+        self._set_state(event=event)
         self.button_calculate.SetFocus()
-    
+        
     def _enable_power_law_low(self, event=None):
         """
         Enable editing power law section at low q range
         """
+        #if event != None: self._set_compute_state()
         if self.guinier.GetValue():
+            self.power_law_low.SetValue(False)
             self.fix_enable_low.Disable()
             self.fit_enable_low.Disable()
             self.power_low_tcl.Disable()
         else:
+            self.power_law_low.SetValue(True)
             self.fix_enable_low.Enable()
             self.fit_enable_low.Enable()
             self.power_low_tcl.Enable()
         self._enable_fit_power_law_low()
+        self._set_state(event=event)
             
     def _layout_extrapolation_low(self):
@@ -677,29 +1287,31 @@
         Draw widgets related to extrapolation at low q range
         """
-        self.enable_low_cbox = wx.CheckBox(self, -1, "Enable Extrapolate Low Q")
+        self.enable_low_cbox = wx.CheckBox(self, -1, "Enable Extrapolate Low Q",name='enable_low_cbox')
         wx.EVT_CHECKBOX(self, self.enable_low_cbox.GetId(),
                                          self._enable_low_q_section)
         self.fix_enable_low = wx.RadioButton(self, -1, 'Fix',
-                                         (10, 10),style=wx.RB_GROUP)
-        self.fit_enable_low = wx.RadioButton(self, -1, 'Fit', (10, 10))
+                                         (10, 10),style=wx.RB_GROUP,name='fix_enable_low')
         self.Bind(wx.EVT_RADIOBUTTON, self._enable_fit_power_law_low,
                                      id=self.fix_enable_low.GetId())
+        self.fit_enable_low = wx.RadioButton(self, -1, 'Fit', (10, 10),name='fit_enable_low')
         self.Bind(wx.EVT_RADIOBUTTON, self._enable_fit_power_law_low, 
                                         id=self.fit_enable_low.GetId())
         self.guinier = wx.RadioButton(self, -1, 'Guinier',
-                                         (10, 10),style=wx.RB_GROUP)
-        self.power_law_low = wx.RadioButton(self, -1, 'Power Law', (10, 10))
+                                         (10, 10),style=wx.RB_GROUP, name='guinier')
         self.Bind(wx.EVT_RADIOBUTTON, self._enable_power_law_low,
-                                     id=self.guinier.GetId())
+                                     id=self.guinier.GetId())        
+        self.power_law_low = wx.RadioButton(self, -1, 'Power Law', (10, 10),name='power_law_low')
         self.Bind(wx.EVT_RADIOBUTTON, self._enable_power_law_low, 
                                         id=self.power_law_low.GetId())
         
         npts_low_txt = wx.StaticText(self, -1, 'Npts')
-        self.npts_low_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1))
+        self.npts_low_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1),name='npts_low_tcl')
+        wx.EVT_TEXT(self, self.npts_low_tcl.GetId(), self._on_text)
         msg_hint = "Number of Q points to consider"
         msg_hint +="while extrapolating the low-Q region"
         self.npts_low_tcl.SetToolTipString(msg_hint)
         power_txt = wx.StaticText(self, -1, 'Power')
-        self.power_low_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1))
+        self.power_low_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1),name='power_low_tcl')
+        wx.EVT_TEXT(self, self.power_low_tcl.GetId(), self._on_text)
        
         power_hint_txt = "Exponent to apply to the Power_law function."
@@ -747,9 +1359,13 @@
         Enable and disable the power value editing
         """
+        #if event != None: self._set_compute_state()
         if self.fix_enable_high.IsEnabled():
             if self.fix_enable_high.GetValue():
+                self.fit_enable_high.SetValue(False)
                 self.power_high_tcl.Enable()
             else:
+                self.fit_enable_high.SetValue(True)
                 self.power_high_tcl.Disable()
+        self._set_state(event=event)
         
     def _enable_high_q_section(self, event=None):
@@ -757,4 +1373,5 @@
         Disable or enable some button if the user enable high q extrapolation
         """
+        #if event != None: self._set_compute_state()
         if self.enable_high_cbox.GetValue():
             self.npts_high_tcl.Enable()
@@ -770,4 +1387,5 @@
             self.fit_enable_high.Disable()
         self._enable_fit_power_law_high()
+        self._set_state(event=event)
         self.button_calculate.SetFocus()
  
@@ -776,13 +1394,12 @@
         Draw widgets related to extrapolation at high q range
         """
-        self.enable_high_cbox = wx.CheckBox(self, -1, "Enable Extrapolate high-Q")
+        self.enable_high_cbox = wx.CheckBox(self, -1, "Enable Extrapolate high-Q", name='enable_high_cbox')
         wx.EVT_CHECKBOX(self, self.enable_high_cbox.GetId(),
                                          self._enable_high_q_section)
-      
         self.fix_enable_high = wx.RadioButton(self, -1, 'Fix',
-                                         (10, 10),style=wx.RB_GROUP)
-        self.fit_enable_high = wx.RadioButton(self, -1, 'Fit', (10, 10))
+                                         (10, 10),style=wx.RB_GROUP,name='fix_enable_high')
         self.Bind(wx.EVT_RADIOBUTTON, self._enable_fit_power_law_high,
                                      id=self.fix_enable_high.GetId())
+        self.fit_enable_high = wx.RadioButton(self, -1, 'Fit', (10, 10),name='fit_enable_high')     
         self.Bind(wx.EVT_RADIOBUTTON, self._enable_fit_power_law_high, 
                                         id=self.fit_enable_high.GetId())
@@ -792,10 +1409,12 @@
         self.power_law_high.SetToolTipString(msg_hint)
         npts_high_txt = wx.StaticText(self, -1, 'Npts')
-        self.npts_high_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1))
+        self.npts_high_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1),name='npts_high_tcl')
+        wx.EVT_TEXT(self, self.npts_high_tcl.GetId(), self._on_text)
         msg_hint = "Number of Q points to consider"
         msg_hint += "while extrapolating the high-Q region"
         self.npts_high_tcl.SetToolTipString(msg_hint)
         power_txt = wx.StaticText(self, -1, 'Power')
-        self.power_high_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1))
+        self.power_high_tcl = InvTextCtrl(self, -1, size=(_BOX_WIDTH*2/3, -1),name='power_high_tcl')
+        wx.EVT_TEXT(self, self.power_high_tcl.GetId(), self._on_text)
         power_hint_txt = "Exponent to apply to the Power_law function."
         self.power_high_tcl.SetToolTipString(power_hint_txt)
@@ -841,12 +1460,12 @@
         extra_hint_txt = wx.StaticText(self, -1, extra_hint)
         #Extrapolation range [string]
-        extrapolation_min_txt = wx.StaticText(self, -1, 'Min : ')  
+        extrapolation_min_txt = wx.StaticText(self, -1, 'Min :')  
         self.extrapolation_min_tcl = OutputTextCtrl(self, -1, 
-                                                size=(_BOX_WIDTH, 20), style=0)
+                                                size=(_BOX_WIDTH, 20), style=0,name='extrapolation_min_tcl')
         self.extrapolation_min_tcl.SetValue(str(Q_MINIMUM))
         self.extrapolation_min_tcl.SetToolTipString("The minimum extrapolated q value.")
-        extrapolation_max_txt = wx.StaticText(self, -1, 'Max : ') 
+        extrapolation_max_txt = wx.StaticText(self, -1, 'Max :') 
         self.extrapolation_max_tcl = OutputTextCtrl(self, -1,
-                                                  size=(_BOX_WIDTH, 20), style=0) 
+                                                  size=(_BOX_WIDTH, 20), style=0,name='extrapolation_max_tcl') 
         self.extrapolation_max_tcl.SetValue(str(Q_MAXIMUM))
         self.extrapolation_max_tcl.SetToolTipString("The maximum extrapolated q value.")
@@ -862,11 +1481,11 @@
         self._layout_extrapolation_high()
         self.extrapolation_low_high_sizer.AddMany([(self.low_extrapolation_sizer,
-                                                     0, wx.ALL, 10),
+                                                     0, wx.ALL, 5),
                                                    (self.high_extrapolation_sizer,
-                                                    0, wx.ALL, 10)])
+                                                    0, wx.ALL, 5)])
         self.extrapolation_sizer.AddMany([(self.extrapolation_range_sizer, 0,
-                                            wx.RIGHT, 10),
+                                            wx.RIGHT, 5),
                                         (self.extrapolation_low_high_sizer, 0,
-                                           wx.ALL, 10)])
+                                           wx.ALL, 5)])
         
     def _layout_volume_surface_sizer(self):
@@ -878,14 +1497,18 @@
         uncertainty = "+/-" 
         volume_txt = wx.StaticText(self, -1, 'Volume Fraction      ')
-        self.volume_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1))
+        self.volume_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1),name='volume_tcl')
+        wx.EVT_TEXT(self, self.volume_tcl.GetId(), self._on_out_text)
         self.volume_tcl.SetToolTipString("Volume fraction.")
-        self.volume_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1))
+        self.volume_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1),name='volume_err_tcl')
+        wx.EVT_TEXT(self, self.volume_err_tcl.GetId(), self._on_out_text)
         self.volume_err_tcl.SetToolTipString("Uncertainty on the volume fraction.")
         volume_units_txt = wx.StaticText(self, -1, unit_volume)
         
         surface_txt = wx.StaticText(self, -1, 'Specific Surface')
-        self.surface_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1))
+        self.surface_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1),name='surface_tcl')
+        wx.EVT_TEXT(self, self.surface_tcl.GetId(), self._on_out_text)
         self.surface_tcl.SetToolTipString("Specific surface value.")
-        self.surface_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1))
+        self.surface_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1),name='surface_err_tcl')
+        wx.EVT_TEXT(self, self.surface_err_tcl.GetId(), self._on_out_text)
         self.surface_err_tcl.SetToolTipString("Uncertainty on the specific surface.")
         surface_units_txt = wx.StaticText(self, -1, unit_surface)
@@ -930,8 +1553,8 @@
         unit_invariant = '[1/(cm * A)]'
         invariant_total_txt = wx.StaticText(self, -1, 'Invariant Total [Q*]')
-        self.invariant_total_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1))
+        self.invariant_total_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1),name='invariant_total_tcl')
         msg_hint = "Total invariant [Q*], including extrapolated regions."
         self.invariant_total_tcl.SetToolTipString(msg_hint)
-        self.invariant_total_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1))
+        self.invariant_total_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH,-1),name='invariant_total_err_tcl')
         self.invariant_total_err_tcl.SetToolTipString("Uncertainty on invariant.")
         invariant_total_units_txt = wx.StaticText(self, -1, unit_invariant)
@@ -980,5 +1603,5 @@
         #compute button
         id = wx.NewId()
-        self.button_calculate = wx.Button(self, id, "Compute")
+        self.button_calculate = wx.Button(self, id, "Compute", name ='compute_invariant' )
         self.button_calculate.SetToolTipString("Compute invariant")
         self.Bind(wx.EVT_BUTTON, self.compute_invariant, id=id)   
@@ -986,14 +1609,46 @@
         id = wx.NewId()
         self.button_details = wx.Button(self, id, "Details?")
-        self.button_details.SetToolTipString("Give Details on Computation")
+        self.button_details.SetToolTipString("Details about the results of the computation")
         self.Bind(wx.EVT_BUTTON, self.display_details, id=id)
         details = "Details on Invariant Total Calculations"
         details_txt = wx.StaticText(self, -1, details)
-        self.button_sizer.AddMany([((10,10), 0 , wx.LEFT,0),
+        self.button_sizer.AddMany([((50,10), 0 , wx.LEFT,0),
                                    (details_txt, 0 , 
                                     wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
                                    (self.button_details, 0 , wx.ALL, 10),
-                        (self.button_calculate, 0 , wx.RIGHT|wx.TOP|wx.BOTTOM, 10)])
-        
+                        (self.button_calculate, 0 , wx.RIGHT|wx.TOP|wx.BOTTOM, 10)])#,
+                                   #(self.button_undo, 0 , wx.ALL, 10),
+                                   #(self.button_redo, 0 , wx.ALL, 10)])
+    def _layout_save_button(self):  
+        """
+        Do the layout for the save button widgets
+        """ 
+        import sans.perspectives.invariant as invariant
+        path = invariant.get_data_path(media='media')
+        self.undo_png = os.path.join(path,"undo.png")
+        self.redo_png = os.path.join(path,"redo.png")
+        self.save_png = os.path.join(path,"save.png")
+        #undo button
+        id = wx.NewId()
+        self.button_undo = wx.BitmapButton(self, id,wx.Bitmap(self.undo_png))#wx.Button(self, id, "Undo",size=(50,20))
+        self.button_undo.SetToolTipString("Undo")
+        self.Bind(wx.EVT_BUTTON, self.undo, id=id)
+        self.button_undo.Disable()
+        #redo button
+        id = wx.NewId()
+        self.button_redo = wx.BitmapButton(self, id,wx.Bitmap(self.redo_png))#wx.Button(self, id, "Redo",size=(50,20))
+        self.button_redo.SetToolTipString("Redo")
+        self.Bind(wx.EVT_BUTTON, self.redo, id=id)
+        self.button_redo.Disable()   
+        #save button
+        id = wx.NewId()
+        self.button_save = wx.BitmapButton(self, id,wx.Bitmap(self.save_png), name ='Save_invariant')#wx.Button(self, id, "Save", name ='Save_invariant' )
+        self.button_save.SetToolTipString("Save as a file")
+        self.Bind(wx.EVT_BUTTON, self._on_save_button, id=id)   
+        self.button_save.Disable()  
+        self.save_button_sizer.AddMany([((PANEL_WIDTH/1.5,20), 1 , wx.EXPAND|wx.ADJUST_MINSIZE,0),
+                                   (self.button_undo, 0 ,wx.LEFT|wx.ADJUST_MINSIZE, 10),
+                                   (self.button_redo, 0 ,wx.LEFT|wx.ADJUST_MINSIZE, 10),
+                                   (self.button_save, 0 ,wx.LEFT|wx.ADJUST_MINSIZE, 10)])        
     def _do_layout(self):
         """
@@ -1006,4 +1661,5 @@
         self._layout_outputs_sizer()
         self._layout_button()
+        self._layout_save_button()
         self.main_sizer.AddMany([(self.data_name_boxsizer,0, wx.ALL, 10),
                                   (self.outputs_sizer, 0,
@@ -1014,4 +1670,6 @@
                                   wx.LEFT|wx.RIGHT|wx.BOTTOM, 10),
                                   (self.extrapolation_sizer, 0,
+                                  wx.LEFT|wx.RIGHT|wx.BOTTOM, 10),\
+                                  (self.save_button_sizer,0,
                                   wx.LEFT|wx.RIGHT|wx.BOTTOM, 10)])
         self.SetSizer(self.main_sizer)
@@ -1037,6 +1695,13 @@
         wx.Frame.__init__(self, parent, id, title, size=(PANEL_WIDTH +100,
                                                              PANEL_HEIGHT+100))
-        
+        from DataLoader.loader import  Loader
+        self.loader = Loader()
+        import invariant
+
+        data= self.loader.load("C:/ECLPS/workspace/trunk/DataLoader/test/ascii_test_3.txt")
         self.panel = InvariantPanel(self)
+
+        data.name = data.filename
+        self.panel.set_data(data)
         self.Centre()
         self.Show(True)
Index: invariantview/perspectives/invariant/invariant_state.py
===================================================================
--- invariantview/perspectives/invariant/invariant_state.py	(revision 4e1c362faa5def12383a4fd3550db72d838ee262)
+++ invariantview/perspectives/invariant/invariant_state.py	(revision 4e1c362faa5def12383a4fd3550db72d838ee262)
@@ -0,0 +1,608 @@
+import time, os, sys
+import logging
+import copy
+import DataLoader
+from xml.dom.minidom import parse
+from lxml import etree
+
+from DataLoader.readers.cansas_reader import Reader as CansasReader
+from DataLoader.readers.cansas_reader import get_content
+from sans.guiframe.utils import format_number
+from sans.guiframe.dataFitting import Theory1D, Data1D
+INVNODE_NAME = 'invariant'
+CANSAS_NS = "cansas1d/1.0"
+# default state
+list = {'file': 'None',
+        'compute_num':0,
+        'state_num':0,
+        'is_time_machine':False,
+        'background_tcl':0.0,
+        'scale_tcl':1.0,
+        'contrast_tcl':1.0,
+        'porod_constant_tcl':'',
+        'npts_low_tcl':10,
+        'npts_high_tcl':10,
+        'power_high_tcl':4.0,
+        'power_low_tcl': 4.0,
+        'enable_high_cbox':False,
+        'enable_low_cbox':False,
+        'guinier': True,
+        'power_law_high': False,
+        'power_law_low': False,
+        'fit_enable_high': False,
+        'fit_enable_low': False,
+        'fix_enable_high':True,
+        'fix_enable_low':True,
+        'volume_tcl':'',
+        'volume_err_tcl':'',
+        'surface_tcl':'',
+        'surface_err_tcl':''}
+# list of states: This list will be filled as panel init and the number of states increases
+state_list = {}
+bookmark_list = {}
+# list of input parameters (will be filled up on panel init) used by __str__ 
+input_list = {}   
+# list of output parameters (order sensitive) used by __str__    
+output_list = [["qstar_low",                  "Q* from low Q extrapolation [1/(cm*A)]"],
+               ["qstar_low_err",             "dQ* from low Q extrapolation"],
+               ["qstar_low_percent",  "Q* percent from low Q extrapolation"],
+               ["qstar",                                     "Q* from data [1/(cm*A)]"],
+               ["qstar_err",                                "dQ* from data"],
+               ["qstar_percent",                     "Q* percent from data"],
+               ["qstar_high",                "Q* from high Q extrapolation [1/(cm*A)]"],
+               ["qstar_high_err",           "dQ* from high Q extrapolation"],
+               ["qstar_high_percent", "Q* percent from low Q extrapolation"],
+               ["qstar_total",                                   "total Q* [1/(cm*A)]"],
+               ["qstar_total_err",                              "total dQ*"],
+               ["volume",                                 "volume fraction"],
+               ["volume_err",                       "volume fraction error"],
+               ["surface",                               "specific surface"],
+               ["surface_err",                     "specific surface error"]]
+
+   
+
+class InvariantState(object):
+    """
+    Class to hold the state information of the InversionControl panel.
+    """
+    def __init__(self):
+        """
+        Default values
+        """
+        # Input 
+        self.file  = None
+        self.data = Data1D(x=[], y=[], dx=None, dy=None)
+        self.theory_lowQ =  Theory1D(x=[], y=[], dy=None)
+        self.theory_highQ = Theory1D(x=[], y=[], dy=None)
+        #self.is_time_machine = False
+        self.saved_state = list
+        self.state_list = state_list
+        self.bookmark_list = bookmark_list
+        self.input_list = input_list
+        self.output_list = output_list
+        
+        self.compute_num = 0
+        self.state_num = 0
+        self.timestamp = None
+        self.container = None
+        
+        
+    def __str__(self):
+        """
+        Pretty print
+            
+        : return: string representing the state
+        """
+        # Input string
+        compute_num = self.saved_state['compute_num']
+        compute_state = self.state_list[str(compute_num)]
+        my_time, date = self.timestamp
+        file_name = self.file
+
+        state_num = int(self.saved_state['state_num'])
+        state = "\n[Invariant computation for %s: "% file_name
+        state += "performed at %s on %s] \n"%(my_time, date)
+        state += "State No.: %d \n"% state_num
+        state += "\n=== Inputs ===\n"
+        
+        # text ctl general inputs ( excluding extrapolation text ctl)
+        for key,value in self.input_list.iteritems(): 
+            if value == '':
+                continue
+            key_split = key.split('_') 
+            max_ind = len(key_split)-1
+            if key_split[max_ind] == 'tcl': 
+                name =""
+                if key_split[1]== 'low' or key_split[1]== 'high':
+                    continue
+                for ind in range(0,max_ind):
+                    name +=" %s"%key_split[ind]
+                state += "%s:   %s\n"% (name.lstrip(" "),value)
+                
+        # other input parameters       
+        extra_lo = compute_state['enable_low_cbox']
+        if compute_state['enable_low_cbox']:
+            if compute_state['guinier']:
+                extra_lo = 'Guinier'
+            else:
+                extra_lo = 'Power law'
+        extra_hi = compute_state['enable_high_cbox']
+        if compute_state['enable_high_cbox']:
+            extra_hi = 'Power law'
+        state += "\nExtrapolation:  High=%s; Low=%s\n" %(extra_hi,extra_lo)   
+        low_off = False
+        high_off = False
+        for key,value in self.input_list.iteritems(): 
+            key_split = key.split('_') 
+            max_ind = len(key_split)-1   
+            if key_split[max_ind] == 'tcl': 
+                name ="" 
+                # check each buttons whether or not ON or OFF
+                if key_split[1]== 'low' or key_split[1]== 'high':
+                    if not compute_state['enable_low_cbox'] and key_split[max_ind-1] == 'low':
+                        low_off = True
+                        continue              
+                    elif not compute_state['enable_high_cbox'] and key_split[max_ind-1]== 'high':
+                        high_off = True
+                        continue
+                    elif extra_lo == 'Guinier' and key_split[0]== 'power' and key_split[max_ind-1]== 'low':
+                        continue
+                    for ind in range(0,max_ind):
+                        name +=" %s"%key_split[ind]
+                    name = name.lstrip(" ")
+                    if name == "power low" :
+                        if compute_state['fix_enable_low']:
+                            name += ' (Fixed)'
+                        else:
+                            name += ' (Fitted)'
+                    if name == "power high" :
+                        if compute_state['fix_enable_high']:
+                            name += ' (Fixed)'
+                        else:
+                            name += ' (Fitted)'
+                    state += "%s:   %s\n"% (name,value)
+        # Outputs
+        state += "\n=== Outputs ==="
+        for item in output_list:
+            item_split = item[0].split('_') 
+            # Exclude the extrapolation that turned off
+            if len(item_split)>1:
+                if low_off and item_split[1] == 'low': continue
+                if high_off and item_split[1] == 'high': continue
+            max_ind = len(item_split)-1
+            value = None
+            try:
+                # Q* outputs
+                exec "value = self.container.%s\n"% item[0]
+            except:
+                # other outputs than Q*
+                name = item[0]+"_tcl"
+                exec "value = self.saved_state['%s']"% name
+                
+            # Exclude the outputs w/''    
+            if value == '': continue    
+            
+            # Error outputs
+            if item_split[max_ind] == 'err':
+                state += "+- %s "%format_number(value)
+                
+            # Percentage outputs
+            elif item_split[max_ind] == 'percent':
+                try:
+                    value = float(value)*100
+                except:
+                    pass
+                state += "(%s %s)"%(format_number(value),'%')
+            # Outputs
+            else:
+                state += "\n%s:   %s "%(item[1],format_number(value,high=True))
+        # Include warning msg
+        state += "\n\nNote:\n" + self.container.warning_msg
+
+        return state
+
+    
+    def clone_state(self):
+        """
+        deepcopy the state
+        """
+        return copy.deepcopy(self.saved_state)
+    
+
+    def toXML(self, file="inv_state.inv", doc=None, entry_node=None):
+        """
+        Writes the state of the InversionControl panel to file, as XML.
+        
+        Compatible with standalone writing, or appending to an
+        already existing XML document. In that case, the XML document
+        is required. An optional entry node in the XML document may also be given.
+        
+        : param file: file to write to
+        : param doc: XML document object [optional]
+        : param entry_node: XML node within the XML document at which we will append the data [optional]   
+        """
+        from xml.dom.minidom import getDOMImplementation
+        import time
+        timestamp = time.time()
+        # Check whether we have to write a standalone XML file
+        if doc is None:
+            impl = getDOMImplementation()
+        
+            doc_type = impl.createDocumentType(INVNODE_NAME, "1.0", "1.0")     
+        
+            newdoc = impl.createDocument(None, INVNODE_NAME, doc_type)
+            top_element = newdoc.documentElement
+        else:
+            # We are appending to an existing document
+            newdoc = doc
+            top_element = newdoc.createElement(INVNODE_NAME)
+            if entry_node is None:
+                newdoc.documentElement.appendChild(top_element)
+            else:
+                entry_node.appendChild(top_element)
+            
+        attr = newdoc.createAttribute("version")
+        attr.nodeValue = '1.0'
+        top_element.setAttributeNode(attr)
+        
+        # File name
+        element = newdoc.createElement("filename")
+        if self.file is not None:
+            element.appendChild(newdoc.createTextNode(str(self.file)))
+        else:
+            element.appendChild(newdoc.createTextNode(str(file)))
+        top_element.appendChild(element)
+        
+        element = newdoc.createElement("timestamp")
+        element.appendChild(newdoc.createTextNode(time.ctime(timestamp)))
+        attr = newdoc.createAttribute("epoch")
+        attr.nodeValue = str(timestamp)
+        element.setAttributeNode(attr)
+        top_element.appendChild(element)
+        
+        # Current state
+        state = newdoc.createElement("state")
+        top_element.appendChild(state)
+        
+        for name,value in self.saved_state.iteritems():
+            element = newdoc.createElement(str(name))
+            element.appendChild(newdoc.createTextNode(str(value)))
+            state.appendChild(element)
+              
+        # State history list
+        history = newdoc.createElement("history")
+        top_element.appendChild(history)
+        
+        for name, value in self.state_list.iteritems():
+            history_element = newdoc.createElement('state_'+str(name))
+            for state_name,state_value in value.iteritems():
+                state_element = newdoc.createElement(str(state_name))
+                state_element.appendChild(newdoc.createTextNode(str(state_value)))
+                history_element.appendChild(state_element)
+            #history_element.appendChild(state_list_element)
+            history.appendChild(history_element)
+
+        # Bookmarks  bookmark_list[self.bookmark_num] = [my_time,date,state,comp_state]
+        bookmark = newdoc.createElement("bookmark")
+        top_element.appendChild(bookmark)
+        item_list = ['time','date','state','comp_state']
+        for name, value_list in self.bookmark_list.iteritems():
+            element = newdoc.createElement('mark_'+ str(name))
+            time,date,state,comp_state = value_list
+            time_element = newdoc.createElement('time')
+            time_element.appendChild(newdoc.createTextNode(str(value_list[0])))
+            date_element = newdoc.createElement('date')
+            date_element.appendChild(newdoc.createTextNode(str(value_list[1])))
+            state_list_element = newdoc.createElement('state')
+            comp_state_list_element = newdoc.createElement('comp_state')
+            for state_name,state_value in value_list[2].iteritems():
+                state_element = newdoc.createElement(str(state_name))
+                state_element.appendChild(newdoc.createTextNode(str(state_value)))
+                state_list_element.appendChild(state_element)
+            for comp_name,comp_value in value_list[3].iteritems():
+                comp_element = newdoc.createElement(str(comp_name))
+                comp_element.appendChild(newdoc.createTextNode(str(comp_value)))
+                comp_state_list_element.appendChild(comp_element)
+            element.appendChild(time_element)
+            element.appendChild(date_element)
+            element.appendChild(state_list_element)
+            element.appendChild(comp_state_list_element)
+            bookmark.appendChild(element)
+
+        # Save the file
+        if doc is None:
+            fd = open('test000', 'w')
+            fd.write(newdoc.toprettyxml())
+            fd.close()
+            return None
+        else:
+            return newdoc.toprettyxml()
+        
+    def fromXML(self, file=None, node=None):
+        """
+        Load invariant states from a file
+            
+        : param file: .inv file
+        : param node: node of a XML document to read from       
+        """
+        if file is not None:
+            raise RuntimeError, "InvariantSate no longer supports non-CanSAS format for invariant files"
+        
+        if node.get('version')\
+            and node.get('version') == '1.0':
+
+            # Get file name
+            entry = get_content('ns:filename', node)
+            if entry is not None:
+                self.file = entry.text.strip()
+            
+            # Get time stamp
+            entry = get_content('ns:timestamp', node)
+            if entry is not None and entry.get('epoch'):
+                try:
+                    timestamp = (entry.get('epoch'))
+                except:
+                    logging.error("InvariantSate.fromXML: Could not read timestamp\n %s" % sys.exc_value)
+            
+            # Parse bookmarks
+            entry_bookmark = get_content('ns:bookmark', node)
+
+            for ind in range(1,len(entry_bookmark)+1):
+                temp_state = {}
+                temp_bookmark = {}
+                entry = get_content('ns:mark_%s' % ind, entry_bookmark) 
+                                
+                if entry is not None:
+                    time = get_content('ns:time', entry )
+                    val_time = str(time.text.strip())
+                    date = get_content('ns:date', entry )
+                    val_date = str(date.text.strip())
+                    state_entry = get_content('ns:state', entry )
+                    for item in list:
+
+                        input_field = get_content('ns:%s' % item, state_entry )
+                        val = str(input_field.text.strip())
+
+                        if input_field is not None:
+                            try:
+                                exec "temp_state['%s'] = %s"% (item,val)      
+                            except:
+                                exec "temp_state['%s'] = '%s'"% (item,val)
+
+                            
+                    comp_entry = get_content('ns:comp_state', entry )
+                    
+                    for item in list:
+                        input_field = get_content('ns:%s' % item, comp_entry )
+                        val = str(input_field.text.strip())
+                        if input_field is not None:
+                            try:
+                                exec "temp_bookmark['%s'] = %s"% (item,val)      
+                            except:
+                                exec "temp_bookmark['%s'] = '%s'"% (item,val)
+                    try:
+                        exec "self.bookmark_list[%s] = [val_time,val_date,temp_state,temp_bookmark]"% ind
+
+                    except:
+                        raise "missing components of bookmarks..."
+    
+            # Parse histories
+            entry_history = get_content('ns:history', node)
+
+            for ind in range(0,len(entry_history)):
+                temp_state = {}
+                entry = get_content('ns:state_%s' % ind, entry_history) 
+
+                if entry is not None:
+                    for item in list:
+                        input_field = get_content('ns:%s' % item, entry )
+                        val = str(input_field.text.strip())
+
+                        if input_field is not None:
+                            try:
+                                exec "temp_state['%s'] = %s"% (item,val)         
+                            except:
+                                exec "temp_state['%s'] = '%s'"% (item,val)
+                            finally:
+                                exec "self.state_list['%s'] = temp_state"% ind 
+            
+            # Parse current state (ie, saved_state)
+            entry = get_content('ns:state', node)           
+            if entry is not None:
+                for item in list:
+                    input_field = get_content('ns:%s' % item, entry)
+                    val = str(input_field.text.strip())
+                    if input_field is not None:
+                        self.set_saved_state(name=item, value=val)
+
+
+            
+    def set_saved_state(self, name, value):
+        """
+        Set the state list 
+                    
+        : param name: name of the state component
+        : param value: value of the state component
+        """
+        rb_list = [['power_law_low','guinier'],['fit_enable_low','fix_enable_low'],['fit_enable_high','fix_enable_high']]
+
+        try:
+            if value == None or value.lstrip().rstrip() =='':
+                exec "self.%s = '%s'" % (name, value)
+                exec "self.saved_state['%s'] = '%s'" %  (name, value)
+            else:
+                exec 'self.%s = %s' % (name, value)
+                exec "self.saved_state['%s'] = %s" %  (name, value)
+
+            
+            # set the count part of radio button clicked False for the saved_state
+            for title,content in rb_list:
+                if name ==  title:
+                    name = content 
+                    value = False     
+                elif name == content:
+                    name = title
+                    value = False 
+            exec "self.saved_state['%s'] = '%s'" %  (name, value)     
+            self.state_num = self.saved_state['state_num']
+        except:           
+            pass
+
+
+
+class Reader(CansasReader):
+    """
+    Class to load a .inv invariant file
+    """
+    ## File type
+    type_name = "Invariant"
+    
+    ## Wildcards
+    type = ["Invariant file (*.inv)|*.inv"]
+    ## List of allowed extensions
+    ext=['.inv', '.INV']  
+    
+    def __init__(self, call_back, cansas=True):
+        """
+        Initialize the call-back method to be called
+        after we load a file
+        
+        : param call_back: call-back method
+        : param cansas:  True = files will be written/read in CanSAS format
+                        False = write CanSAS format  
+        """
+        ## Call back method to be executed after a file is read
+        self.call_back = call_back
+        ## CanSAS format flag
+        self.cansas = cansas
+
+    def read(self, path):
+        """ 
+        Load a new invariant state from file
+        
+        : param path: file path
+        : return: None
+        """
+        if self.cansas==True:
+            return self._read_cansas(path)
+        else:
+            return self._read_standalone(path)
+        
+    def _read_standalone(self, path):
+        """ 
+        Load a new invariant state from file.
+        The invariant node is assumed to be the top element.
+        
+        : param path: file path
+        : return: None
+        """
+        # Read the new state from file
+        state = InvariantState()
+
+        state.fromXML(file=path)
+        
+        # Call back to post the new state
+        self.call_back(state)
+        return None
+    
+    def _parse_state(self, entry):
+        """
+        Read an invariant result from an XML node
+        
+        : param entry: XML node to read from 
+        : return: InvariantState object
+        """
+        # Create an empty state
+        state = InvariantState()
+
+        # Locate the invariant node
+        try:
+            nodes = entry.xpath('ns:%s' % INVNODE_NAME, namespaces={'ns': CANSAS_NS})
+            state.fromXML(node=nodes[0])
+        except:
+            logging.info("XML document does not contain invariant information.\n %s" % sys.exc_value)  
+        return state
+    
+    def _read_cansas(self, path):
+        """ 
+        Load data and invariant information from a CanSAS XML file.
+        
+        : param path: file path
+        : return: Data1D object if a single SASentry was found, 
+                    or a list of Data1D objects if multiple entries were found,
+                    or None of nothing was found
+        : raise RuntimeError: when the file can't be opened
+        : raise ValueError: when the length of the data vectors are inconsistent
+        """
+        output = []
+        
+        if os.path.isfile(path):
+            basename  = os.path.basename(path)
+            root, extension = os.path.splitext(basename)
+            
+            if  extension.lower() in self.ext or \
+                extension.lower() == '.xml':
+                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
+       
+                # Check the format version number
+                # Specifying the namespace will take care of the file format version 
+                root = tree.getroot()
+                
+                entry_list = root.xpath('/ns:SASroot/ns:SASentry', namespaces={'ns': CANSAS_NS})
+                
+                for entry in entry_list:
+                    
+                    sas_entry = self._parse_entry(entry)
+                    invstate = self._parse_state(entry)
+                    sas_entry.meta_data['invstate'] = invstate
+
+                    sas_entry.filename = invstate.file
+                    output.append(sas_entry)
+                
+        else:
+            raise RuntimeError, "%s is not a file" % path
+
+        # Return output consistent with the loader's api
+        if len(output)==0:
+            return None
+        elif len(output)==1:
+            # Call back to post the new state
+
+            self.call_back(state=output[0].meta_data['invstate'], datainfo = output[0])
+            return output[0]
+        else:
+            return output                
+    
+    
+    def write(self, filename, datainfo=None, invstate=None):
+        """
+        Write the content of a Data1D as a CanSAS XML file
+        
+        : param filename: name of the file to write
+        : param datainfo: Data1D object
+        : param invstate: InvariantState object
+        """
+
+        # Sanity check
+        if self.cansas == True:
+            if datainfo is None:
+                datainfo = DataLoader.data_info.Data1D(x=[], y=[])    
+            elif not issubclass(datainfo.__class__, DataLoader.data_info.Data1D):
+                raise RuntimeError, "The cansas writer expects a Data1D instance: %s" % str(datainfo.__class__.__name__)
+        
+            # Create basic XML document
+            doc, sasentry = self._to_xml_doc(datainfo)
+        
+            # Add the invariant information to the XML document
+            if invstate is not None:
+                invstate.toXML(doc=doc, entry_node=sasentry)
+        
+            # Write the XML document
+            fd = open(filename, 'w')
+            fd.write(doc.toprettyxml())
+            fd.close()
+        else:
+            invstate.toXML(file=filename)
+        
+    
+    
Index: invariantview/perspectives/invariant/invariant_widgets.py
===================================================================
--- invariantview/perspectives/invariant/invariant_widgets.py	(revision d7a39e5e4d2fdf0181a63b2cb239064dd754a674)
+++ invariantview/perspectives/invariant/invariant_widgets.py	(revision 4e1c362faa5def12383a4fd3550db72d838ee262)
@@ -15,4 +15,6 @@
 import wx
 import os
+from invariant_state import InvariantState as IState
+import copy
 
 class InvTextCtrl(wx.TextCtrl):
@@ -22,15 +24,14 @@
     """
     def __init__(self, *args, **kwds):
-        
         wx.TextCtrl.__init__(self, *args, **kwds)
-        
         ## Set to True when the mouse is clicked while the whole string is selected
-        full_selection = False
+        self.full_selection = False
         ## Call back for EVT_SET_FOCUS events
         _on_set_focus_callback = None
+
         # Bind appropriate events
         self.Bind(wx.EVT_LEFT_UP, self._highlight_text)
         self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
-
+        
     def _on_set_focus(self, event):
         """
@@ -39,5 +40,4 @@
         
         :param event: mouse event
-        
         """
         event.Skip()
@@ -49,5 +49,4 @@
         
         :param event: mouse event
-        
         """
         # Make sure the mouse event is available to other listeners
@@ -63,5 +62,7 @@
                 if start==end:
                     control.SetSelection(-1,-1)
+           
 
+    
 class OutputTextCtrl(wx.TextCtrl):
     """
@@ -78,5 +79,7 @@
         # The event will be skipped once the call-back
         # is called.
+        
         self.Bind(wx.EVT_MOUSE_EVENTS, self._click)
+
         
     def _click(self, event):
@@ -86,3 +89,6 @@
         """ 
         pass
+    
+    
+
  
