Index: guiframe/calcthread.py
===================================================================
--- guiframe/calcthread.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
+++ guiframe/calcthread.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -0,0 +1,297 @@
+# This program is public domain
+
+## \file
+#  \brief Abstract class for defining calculation threads.
+#
+
+import thread, traceback
+
+import sys
+if sys.platform.count("darwin")>0:
+    import mactime as time
+else:
+    import time
+
+class CalcThread:
+    """Threaded calculation class.  Inherit from here and specialize
+    the compute() method to perform the appropriate operations for the
+    class.
+
+    If you specialize the __init__ method be sure to call
+    CalcThread.__init__, passing it the keyword arguments for
+    yieldtime, worktime, update and complete.
+    
+    When defining the compute() method you need to include code which
+    allows the GUI to run.  They are as follows:
+        self.isquit()          call frequently to check for interrupts
+        self.update(kw=...)    call when the GUI could be updated
+        self.complete(kw=...)  call before exiting compute()
+    The update() and complete() calls accept field=value keyword
+    arguments which are passed to the called function.  complete()
+    should be called before exiting the GUI function.  A KeyboardInterrupt
+    event is triggered if the GUI signals that the computation should
+    be halted.
+
+    The following documentation should be included in the description
+    of the derived class.
+
+    The user of this class will call the following:
+
+        thread = Work(...,kw=...)  prepare the work thread.
+        thread.queue(...,kw=...)   queue a work unit
+        thread.requeue(...,kw=...) replace work unit on the end of queue
+        thread.reset(...,kw=...)   reset the queue to the given work unit
+        thread.stop()              clear the queue and halt
+        thread.interrupt()         halt the current work unit but continue
+        thread.ready(delay=0.)     request an update signal after delay
+        thread.isrunning()         returns true if compute() is running
+
+    Use queue() when all work must be done.  Use requeue() when intermediate
+    work items don't need to be done (e.g., in response to a mouse move
+    event).  Use reset() when the current item doesn't need to be completed
+    before the new event (e.g., in response to a mouse release event).  Use
+    stop() to halt the current and pending computations (e.g., in response to
+    a stop button).
+    
+    The methods queue(), requeue() and reset() are proxies for the compute()
+    method in the subclass.  Look there for a description of the arguments.
+    The compute() method can be called directly to run the computation in
+    the main thread, but it should not be called if isrunning() returns true.
+
+    The constructor accepts additional keywords yieldtime=0.01 and
+    worktime=0.01 which determine the cooperative multitasking
+    behaviour.  Yield time is the duration of the sleep period
+    required to give other processes a chance to run.  Work time
+    is the duration between sleep periods.
+
+    Notifying the GUI thread of work in progress and work complete
+    is done with updatefn=updatefn and completefn=completefn arguments
+    to the constructor.  Details of the parameters to the functions
+    depend on the particular calculation class, but they will all
+    be passed as keyword arguments.  Details of how the functions
+    should be implemented vary from framework to framework.
+
+    For wx, something like the following is needed:
+
+        import wx, wx.lib.newevent
+        (CalcCompleteEvent, EVT_CALC_COMPLETE) = wx.lib.newevent.NewEvent()
+
+        # methods in the main window class of your application
+        def __init__():
+            ...
+            # Prepare the calculation in the GUI thread.
+            self.work = Work(completefn=self.CalcComplete)
+            self.Bind(EVT_CALC_COMPLETE, self.OnCalcComplete)
+            ...
+            # Bind work queue to a menu event.
+            self.Bind(wx.EVT_MENU, self.OnCalcStart, id=idCALCSTART)
+            ...
+
+        def OnCalcStart(self,event):
+            # Start the work thread from the GUI thread.
+            self.work.queue(...work unit parameters...)
+
+        def CalcComplete(self,**kwargs):
+            # Generate CalcComplete event in the calculation thread.
+            # kwargs contains field1, field2, etc. as defined by
+            # the Work thread class.
+            event = CalcCompleteEvent(**kwargs)
+            wx.PostEvent(self, event)
+
+        def OnCalcComplete(self,event):
+            # Process CalcComplete event in GUI thread.
+            # Use values from event.field1, event.field2 etc. as
+            # defined by the Work thread class to show the results.
+            ...
+    """
+
+    def __init__(self,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        """Prepare the calculator"""
+        self.yieldtime     = yieldtime
+        self.worktime      = worktime
+        self.completefn    = completefn
+        self.updatefn      = updatefn
+        self._interrupting = False
+        self._running      = False
+        self._queue        = []
+        self._lock         = thread.allocate_lock()
+        self._delay        = 1e6
+
+    def queue(self,*args,**kwargs):
+        """Add a work unit to the end of the queue.  See the compute()
+        method for details of the arguments to the work unit."""
+        self._lock.acquire()
+        self._queue.append((args,kwargs))
+        # Cannot do start_new_thread call within the lock
+        self._lock.release()
+        if not self._running:
+            # print "Starting thread"
+            self._time_for_update = time.clock()+1e6
+            thread.start_new_thread(self._run,())
+
+    def requeue(self,*args,**kwargs):
+        """Replace the work unit on the end of the queue.  See the compute()
+        method for details of the arguments to the work unit."""
+        self._lock.acquire()
+        self._queue = self._queue[:-1]
+        self._lock.release()
+        self.queue(*args,**kwargs)
+
+    def reset(self,*args,**kwargs):
+        """Clear the queue and start a new work unit.  See the compute()
+        method for details of the arguments to the work unit."""
+        self.stop()
+        self.queue(*args,**kwargs)
+
+    def stop(self):
+        """Clear the queue and stop the thread.  New items may be
+        queued after stop.  To stop just the current work item, and
+        continue the rest of the queue call the interrupt method"""
+        self._lock.acquire()
+        self._interrupting = True
+        self._queue = []
+        self._lock.release()
+
+    def interrupt(self):
+        """Stop the current work item.  To clear the work queue as
+        well call the stop() method."""
+        self._lock.acquire()
+        self._interrupting = True
+        self._lock.release()
+
+    def isrunning(self): return self._running
+
+    def ready(self, delay=0.):
+        """Ready for another update after delay=t seconds.  Call
+        this for threads which can show intermediate results from
+        long calculations."""
+        self._delay = delay
+        self._lock.acquire()
+        self._time_for_update = time.clock() + delay
+        # print "setting _time_for_update to ",self._time_for_update
+        self._lock.release()
+
+    def isquit(self):
+        """Check for interrupts.  Should be called frequently to
+        provide user responsiveness.  Also yields to other running
+        threads, which is required for good performance on OS X."""
+
+        # Only called from within the running thread so no need to lock
+        if self._running and self.yieldtime>0 and time.clock()>self._time_for_nap:
+            # print "sharing"
+            time.sleep(self.yieldtime)
+            self._time_for_nap = time.clock() + self.worktime
+        if self._interrupting: raise KeyboardInterrupt
+
+    def update(self,**kwargs):
+       
+        """Update GUI with the lastest results from the current work unit."""
+      
+        if self.updatefn != None and time.clock() > self._time_for_update:
+            self._lock.acquire()
+            self._time_for_update = time.clock() + self._delay
+            self._lock.release()
+            
+            #self._time_for_update += 1e6  # No more updates
+            
+            self.updatefn(**kwargs)
+            time.sleep(self.yieldtime)
+            if self._interrupting: raise KeyboardInterrupt
+        else:
+            self.isquit()
+        return
+
+    def complete(self,**kwargs):
+        """Update the GUI with the completed results from a work unit."""
+        if self.completefn != None:
+            self.completefn(**kwargs)
+            time.sleep(self.yieldtime)
+        return
+
+    def compute(self,*args,**kwargs):
+        """Perform a work unit.  The subclass will provide details of
+        the arguments."""
+        raise NotImplemented, "Calculation thread needs compute method"
+
+    def _run(self):
+        """Internal function to manage the thread."""
+        # The code for condition wait in the threading package is
+        # implemented using polling.  I'll accept for now that the
+        # authors of this code are clever enough that polling is
+        # difficult to avoid.  Rather than polling, I will exit the
+        # thread when the queue is empty and start a new thread when
+        # there is more work to be done.
+        while 1:
+            self._lock.acquire()
+            # print "lock aquired"
+            self._time_for_nap = time.clock()+self.worktime
+            self._running = True
+            if self._queue == []: break
+            self._interrupting = False
+            args,kwargs = self._queue[0]
+            self._queue = self._queue[1:]
+            self._lock.release()
+            # print "lock released"
+            try:
+                self.compute(*args,**kwargs)
+            except KeyboardInterrupt:
+                pass
+            except:
+                traceback.print_exc()
+                #print 'CalcThread exception',
+        self._running = False
+
+# ======================================================================
+# Demonstration of calcthread in action
+class CalcDemo(CalcThread):
+    """Example of a calculation thread."""
+    def compute(self,n):
+        total = 0.
+        for i in range(n):
+            #self.update(i=i)
+            for j in range(n):
+                self.isquit()
+                total += j
+        self.complete(total=total)
+
+class CalcCommandline:
+    def __init__(self, n=20000):
+        print thread.get_ident()
+        self.starttime = time.clock()
+        self.done = False
+        self.work = CalcDemo(completefn=self.complete,
+                             updatefn=self.update, yieldtime=0.001)
+        #self.work2 = CalcDemo(completefn=self.complete,
+        #                     updatefn=self.update)
+        #self.work3 = CalcDemo(completefn=self.complete,
+        #                     updatefn=self.update)
+        self.work.queue(n)
+        #self.work2.queue(n)
+        #self.work3.queue(n)
+        print "Expect updates from Main every second and from thread every 2.5 seconds"
+        print ""
+        self.work.ready(.5)
+        while not self.done:
+            time.sleep(1)
+            print "Main thread %d at %.2f"%(thread.get_ident(),time.clock()-self.starttime)
+
+    def update(self,i=0):
+        print "Update i=%d from thread %d at %.2f"%(i,thread.get_ident(),time.clock()-self.starttime)
+        self.work.ready(2.5)
+
+    def complete(self,total=0.0):
+        print "Complete total=%g from thread %d at %.2f"%(total,thread.get_ident(),time.clock()-self.starttime)
+        self.done = True
+
+if __name__ == "__main__":
+    CalcCommandline()
+
+# version
+__id__ = "$Id: calcthread.py 249 2007-06-15 17:03:01Z ziwen $"
+
+# End of file
Index: guiframe/gui_manager.py
===================================================================
--- guiframe/gui_manager.py	(revision 0d9dae840601f23ae4dd0b9e0317056593727f90)
+++ guiframe/gui_manager.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -36,5 +36,5 @@
     
 from sans.guicomm.events import EVT_STATUS,Model2DPanelEvent
-
+import history
 import warnings
 warnings.simplefilter("ignore")
@@ -220,5 +220,7 @@
         ## Default welcome panel
         self.defaultPanel    = DefaultPanel(self, -1, style=wx.RAISED_BORDER)
-
+        
+        # History panel
+        self.histPanel     = history.HistoryPanel(self, style=wx.RAISED_BORDER)
         # self.build_gui()
        
@@ -347,4 +349,7 @@
         # It also sets the size of the application windows
         self.panels["default"] = self.defaultPanel
+         # History panel
+        self.panels["historyPanel"] = self.histPanel  
+        
         self._mgr.AddPane(self.defaultPanel, wx.aui.AuiPaneInfo().
                               Name("default").
@@ -354,4 +359,18 @@
                               MinSize(wx.Size(self._window_width, self._window_height)).
                               Show())
+        self._mgr.AddPane(self.histPanel, wx.aui.AuiPaneInfo().
+                          Name("historyPanel").Caption("History").
+                          #Float().
+                          Bottom().
+                          Dock().
+                          TopDockable().
+                          BottomDockable().
+                          LeftDockable().
+                          RightDockable().
+                          MinimizeButton().
+                          Hide().
+                          #Show().
+                          BestSize(wx.Size(500,600)).
+                          MinSize(wx.Size(200,150)))
 
         # Add the panels to the AUI manager
@@ -478,4 +497,8 @@
         
         id = wx.NewId()
+        filemenu.Append(id, '&History', 'Register previous States')
+        wx.EVT_MENU(self, id, self._onHistoryPanel)
+        
+        id = wx.NewId()
         filemenu.Append(id,'&Quit', 'Exit') 
         wx.EVT_MENU(self, id, self.Close)
@@ -600,4 +623,10 @@
                 
             self._mgr.Update()
+    def _onHistoryPanel(self, event):
+        print "on history"
+        if not self._mgr.GetPane("historyPanel").IsShown():
+            self._mgr.GetPane("historyPanel").Show()
+        self._mgr.Update()
+        return
         
     def _on_open(self, event):
Index: guiframe/history.py
===================================================================
--- guiframe/history.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
+++ guiframe/history.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -0,0 +1,201 @@
+"""
+    Panel class to show the model history
+"""
+#TODO: when a model change is followed by a parameter change,
+#      we end up with the same model twice in a row in the history
+#TODO: implement aging of history items
+
+import wx
+import wx.lib.newevent
+import config
+
+# History events
+(HistoryEvent, EVT_HISTORY) = wx.lib.newevent.NewEvent()
+
+# Maximum number of full history events in the buffer
+MAX_BUFF_LEN = 30
+
+isWindows = False
+import sys
+if sys.platform.count("win32")>0:
+    isWindows = True
+    
+if isWindows:
+    from ctypes import *
+    from ctypes.wintypes import DWORD
+
+    SIZE_T = c_ulong
+
+    class _MEMORYSTATUS(Structure):
+        _fields_ = [("dwLength", DWORD),
+                    ("dwMemoryLength", DWORD),
+                    ("dwTotalPhys", SIZE_T),
+                    ("dwAvailPhys", SIZE_T),
+                    ("dwTotalPageFile", SIZE_T),
+                    ("dwAvailPageFile", SIZE_T),
+                    ("dwTotalVirtual", SIZE_T),
+                    ("dwAvailVirtualPhys", SIZE_T)]
+        def show(self):
+            for field_name, field_type in self._fields_:
+                print field_name, getattr(self, field_name)
+    
+
+
+
+class HistoryPanel(wx.Panel):
+    """
+        Panel to show history items
+    """
+    #TODO: show units
+    #TODO: order parameters properly
+    ## Internal name for the AUI manager
+    window_name = "historyPanel"
+    ## Title to appear on top of the window
+    window_caption = "historyPanel"
+    
+    CENTER_PANE = False
+    def __init__(self, parent, *args, **kwargs):
+        wx.Panel.__init__(self, parent, *args, **kwargs)
+        self.parent = parent
+
+        # Listen to history events
+        self.parent.Bind(EVT_HISTORY, self.onEVT_HISTORY)
+
+        # Internal counter of history items
+        self.counter = 0
+        
+        # Buffer filled flag
+        self.buffer_filled = False
+        
+        # Set up the layout
+        self._set_layout()
+        
+        # Save current flag
+        self.current_saved = False
+        
+    def _set_layout(self):
+        """
+            Set up the layout of the panel
+        """
+        self.sizer = wx.GridBagSizer(0,5)
+        
+        title = wx.StaticText(self, -1, "History", style=wx.ALIGN_LEFT)         
+        self.sizer.Add(title, (0,0), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
+
+        self.view_box = wx.ListBox(self, 101, wx.DefaultPosition, (295, 200), 
+                                   [], wx.LB_SINGLE | wx.LB_HSCROLL)
+        
+        self.sizer.Add(self.view_box, (1,0), flag=wx.TOP, border=0)
+        
+        self.SetSizer(self.sizer)
+        
+        self.Bind(wx.EVT_CONTEXT_MENU, self.onShowPopup, id=101)
+        self.Bind(wx.EVT_LISTBOX, self.onSelect, id=101)
+        
+    def onRemove(self, ev):
+        """
+            Remove an item
+        """
+        indices = self.view_box.GetSelections()
+        if len(indices)>0:
+            self.view_box.Delete(indices[0])
+        
+    def onRename(self, ev):
+        """
+            Rename an item
+        """
+        indices = self.view_box.GetSelections()
+        if len(indices)>0:
+            print "NOT YET IMPLMENTED"
+            print "renaming", self.view_box.GetString(indices[0])
+        
+    def onShowPopup(self, event):
+        """
+            Popup context menu event
+        """
+        # menu
+        popupmenu = wx.Menu()
+        id = wx.NewId()
+        popupmenu.Append(id, "&Remove Selected")
+        #popupmenu.Append(102, "&Rename Selected")
+        wx.EVT_MENU(self, id, self.onRemove)
+        #wx.EVT_MENU(self, 102, self.onRename)
+
+        pos = event.GetPosition()
+        pos = self.ScreenToClient(pos)
+        self.PopupMenu(popupmenu, pos)
+
+    def onSelect(self, event):
+        """
+            Process item selection events
+        """
+        index = event.GetSelection()
+        view_name = self.view_box.GetString(index)
+        view = event.GetClientData()
+
+        wx.PostEvent(self.parent, view)
+                
+    def onEVT_HISTORY(self, event):
+        """
+            Process EVT_HISTORY events
+            When a history event arrives, put it in the list
+            of history items
+            
+            @param event: EVT_HISTORY event
+        """
+        print "onEVT_HISTORY",event.name
+        #TODO: this will only work on Windows
+        import distutils.util
+        isWindows = False
+        if sys.platform.count("win32")>0:
+            isWindows = True
+            
+        if isWindows:
+            memstatus = _MEMORYSTATUS()
+            windll.kernel32.GlobalMemoryStatus(byref(memstatus))
+            size_0   = getattr(memstatus, 'dwAvailPhys')
+            size_obj = 1000.0
+            if not event.item.state._data_2D == None:
+                size_obj = len(event.item.state._data_2D)*len(event.item.state._data_2D)*32.0
+        
+        #print "HistoryPanel.EVT_HISTORY"
+        self.counter += 1
+        self.view_box.Insert("%d: %s" % (self.counter, event.name), 
+                             self.view_box.GetCount(), event.item)
+                
+        deleteOldItem = False
+        if isWindows:
+            # This break our scheme of the history panel not knowing
+            # about the events, but we need to protect against 
+            # filling up the memory. 
+            # TODO: fix this
+        
+            if size_0/size_obj < 10:
+                deleteOldItem = True
+                
+        else:
+        
+            # If the buffer is already full, write the earlier ones
+            # in a file.
+            if self.view_box.GetCount() > MAX_BUFF_LEN:
+                deleteOldItem = True
+            
+        if deleteOldItem:
+            #item_0 = self.view_box.GetString(0)
+            #import os
+            # Write it
+            #filename = "history_%i.dat" % os.getpid()
+            #fd = open(filename,'a')
+            #fd.write("test\n")    
+        
+            # For now, delete item from list 
+            if not self.buffer_filled:
+                evt = config.StatusBarEvent( \
+                    message = "Memory running low, oldest history events will be deleted")
+                    #message = "Maximum number of history events reached (%g)" % MAX_BUFF_LEN)
+                wx.PostEvent(self.parent, evt)
+                self.buffer_filled = True
+            
+            self.view_box.Delete(0)
+
+
Index: guiframe/local_perspectives/plotting/AnnulusSlicer.py
===================================================================
--- guiframe/local_perspectives/plotting/AnnulusSlicer.py	(revision 0d9dae840601f23ae4dd0b9e0317056593727f90)
+++ guiframe/local_perspectives/plotting/AnnulusSlicer.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -47,7 +47,5 @@
         self.outer_circle = RingInteractor(self, self.base.subplot, zorder=zorder+1, r=self.qmax/1.8,sign=self.sign)
         self.outer_circle.qmax = self.qmax*1.2
-        #self.outer_circle.set_cursor(self.base.qmax/1.8, 0)
-        
-                      
+       
         self.update()
         self._post_data()
Index: guiframe/model_thread.py
===================================================================
--- guiframe/model_thread.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
+++ guiframe/model_thread.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -0,0 +1,345 @@
+import time
+from calcthread import CalcThread
+from sans.guicomm.events import NewPlotEvent, StatusEvent  
+import sys
+import wx
+class Calc2D_all(CalcThread):
+    """
+        Compute 2D model
+        This calculation assumes a 2-fold symmetry of the model
+        where points are computed for one half of the detector
+        and I(qx, qy) = I(-qx, -qy) is assumed.
+    """
+    
+    def __init__(self, x, y, model,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        
+        self.x = x
+        self.y = y
+        self.model = model
+        self.starttime = 0
+        
+    def isquit(self):
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            #printEVT("Calc %s interrupted" % self.model.name)
+            wx.PostEvent(self.parent, StatusEvent(status=\
+                       "Calc %s interrupted" % self.model.name))
+            raise KeyboardInterrupt
+        
+    def compute(self):
+        import numpy
+        x = self.x
+        y = self.y
+        output = numpy.zeros((len(x),len(y)))
+            
+        self.starttime = time.time()
+        lx = len(self.x)
+        
+        #for i_x in range(int(len(self.x)/2)):
+        for i_x in range(len(self.x)):
+            if i_x%2==1:
+                continue
+            
+            # Check whether we need to bail out
+            self.update(output=output)
+            self.isquit()
+                
+            for i_y in range(len(self.y)):
+                value = self.model.runXY([self.x[i_x], self.y[i_y]])
+                output[i_y][i_x] = value
+                #output[lx-i_y-1][lx-i_x-1] = value
+                
+        if lx%2==1:
+            i_x = int(len(self.x)/2)
+            for i_y in range(len(self.y)):
+                value = self.model.runXY([self.x[i_x], self.y[i_y]])
+                output[i_y][i_x] = value
+                
+        #for i_x in range(int(len(self.x)/2)):
+        for i_x in range(len(self.x)):
+            if not i_x%2==1:
+                continue
+
+            # Check whether we need to bail out
+            self.update(output=output)
+            self.isquit()
+            
+            for i_y in range(len(self.y)):
+                value = self.model.runXY([self.x[i_x], self.y[i_y]])
+                output[i_y][i_x] = value
+                #output[lx-i_y-1][lx-i_x-1] = value
+            
+        elapsed = time.time()-self.starttime
+        self.complete(output=output, elapsed=elapsed)
+
+
+class Calc2D(CalcThread):
+    """
+        Compute 2D model
+        This calculation assumes a 2-fold symmetry of the model
+        where points are computed for one half of the detector
+        and I(qx, qy) = I(-qx, -qy) is assumed.
+    """
+    
+    def __init__(self,parent, x, y, model,qmin, qmax,qstep,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.parent =parent
+        self.qmin= qmin
+        self.qmax=qmax
+        self.qstep= qstep
+        self.x = x
+        self.y = y
+        self.model = model
+        self.starttime = 0
+       
+        
+    def isquit(self):
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            #printEVT("Calc %s interrupted" % self.model.name)
+            wx.PostEvent(self.parent, StatusEvent(status=\
+                       "Calc %s interrupted" % self.model.name))
+           
+            raise KeyboardInterrupt
+        
+    def update(self, output=None, time=None):
+        
+        wx.PostEvent(self.parent, StatusEvent(status="Plot \
+        updating ... %g sec" % time))
+        
+    def compute(self):
+        import numpy
+        x = self.x
+        y = self.y
+        output = numpy.zeros((len(x),len(y)))
+      
+        center_x=0
+        center_y=0
+       
+        self.starttime = time.time()
+        wx.PostEvent(self.parent, StatusEvent(status=\
+                       "Start Drawing model %g " % self.starttime))
+        lx = len(self.x)
+       
+        for i_x in range(len(self.x)):
+            # Check whether we need to bail out
+            self.update(output=output, time=time.time() )
+            self.isquit()
+            
+            for i_y in range(int(len(self.y))):
+                try:
+                    if (self.x[i_x]*self.x[i_x]+self.y[i_y]*self.y[i_y]) \
+                        < self.qmin * self.qmin:
+                        
+                        output[i_x] [i_y]=0   
+                    else:
+                        value = self.model.runXY([self.x[i_x]-center_x, self.y[i_y]-center_y])
+                        output[i_x] [i_y]=value   
+                except:
+                     wx.PostEvent(self.parent, StatusEvent(status=\
+                       "Error computing %s at [%g,%g]" %(self.model.name, self.x[i_x],self.y[i_y])))
+           
+        elapsed = time.time()-self.starttime
+        self.complete(
+                      output=output, elapsed=elapsed,model= self.model,
+                      qmin= self.qmin,
+                      qmax=self.qmax,
+                      qstep=self.qstep)
+
+class Calc2D_4fold(CalcThread):
+    """
+        Compute 2D model
+        This calculation assumes a 4-fold symmetry of the model.
+        Really is the same calculation time since we have to 
+        calculate points for 0<phi<pi anyway.
+    """
+    
+    def __init__(self, x, y, model,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.x = x
+        self.y = y
+        self.model = model
+        self.starttime = 0
+        
+    def isquit(self):
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            #printEVT("Calc %s interrupted" % self.model.name)
+            wx.PostEvent(self.parent, StatusEvent(status=\
+                       "Calc %s interrupted" % self.model.name))
+           
+            raise KeyboardInterrupt
+        
+    def compute(self):
+        import numpy
+        x = self.x
+        y = self.y
+        output = numpy.zeros((len(x),len(y)))
+            
+        self.starttime = time.time()
+        lx = len(self.x)
+        
+        for i_x in range(int(len(self.x)/2)):
+            if i_x%2==1:
+                continue
+            
+            # Check whether we need to bail out
+            self.update(output=output)
+            self.isquit()
+                
+            for i_y in range(int(len(self.y)/2)):
+                value1 = self.model.runXY([self.x[i_x], self.y[i_y]])
+                value2 = self.model.runXY([self.x[i_x], self.y[lx-i_y-1]])
+                output[i_y][i_x] = value1 + value2
+                output[lx-i_y-1][lx-i_x-1] = value1 + value2
+                output[lx-i_y-1][i_x] = value1 + value2
+                output[i_y][lx-i_x-1] = value1 + value2
+                
+        if lx%2==1:
+            i_x = int(len(self.x)/2)
+            for i_y in range(int(len(self.y)/2)):
+                value1 = self.model.runXY([self.x[i_x], self.y[i_y]])
+                value2 = self.model.runXY([self.x[i_x], self.y[lx-i_y-1]])
+                output[i_y][i_x] = value1 + value2
+                output[lx-i_y-1][lx-i_x-1] = value1 + value2
+                output[lx-i_y-1][i_x] = value1 + value2
+                output[i_y][lx-i_x-1] = value1 + value2
+                
+        for i_x in range(int(len(self.x)/2)):
+            if not i_x%2==1:
+                continue
+
+            # Check whether we need to bail out
+            self.update(output=output)
+            self.isquit()
+            
+            for i_y in range(int(len(self.y)/2)):
+                value1 = self.model.runXY([self.x[i_x], self.y[i_y]])
+                value2 = self.model.runXY([self.x[i_x], self.y[lx-i_y-1]])
+                output[i_y][i_x] = value1 + value2
+                output[lx-i_y-1][lx-i_x-1] = value1 + value2
+                output[lx-i_y-1][i_x] = value1 + value2
+                output[i_y][lx-i_x-1] = value1 + value2
+            
+        elapsed = time.time()-self.starttime
+        self.complete(output=output, elapsed=elapsed)
+
+
+
+class Calc1D(CalcThread):
+    """Compute 1D data"""
+    
+    def __init__(self, x, model,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.x = x
+        self.model = model
+        self.starttime = 0
+        
+    def compute(self):
+        import numpy
+        x = self.x
+        output = numpy.zeros(len(x))
+            
+        self.starttime = time.time()
+        
+        for i_x in range(len(self.x)):
+             
+            # Check whether we need to bail out
+            self.isquit()
+                
+            try:
+                value = self.model.run(self.x[i_x])
+                output[i_x] = value
+            except:
+            
+                wx.PostEvent(self.parent, StatusEvent(status=\
+                       "Error computing %s at %g" %(self.model.name, self.x[i_x])))
+           
+        elapsed = time.time()-self.starttime
+        self.complete(output=output, elapsed=elapsed)
+
+class CalcCommandline:
+    def __init__(self, n=20000):
+        #print thread.get_ident()
+        from sans.models.CylinderModel import CylinderModel
+        from sans.models.DisperseModel import DisperseModel
+        import Averager2D
+        import pylab
+        
+        submodel = CylinderModel()
+        #model = Averager2D.Averager2D()
+        #model.set_model(submodel)
+        #model.set_dispersity([['cyl_phi',0.2,10],
+        #                      ['cyl_theta',0.2,10],
+        #                      ['length',10,10],])
+        
+        model = DisperseModel(submodel, ['cyl_phi', 'cyl_theta', 'length'],
+                                        [0.2, 0.2, 10.0])
+        model.setParam('n_pts', 10)
+         
+        print model.runXY([0.01, 0.02])
+        
+        qmax = 0.01
+        qstep = 0.0001
+        self.done = False
+        
+        x = pylab.arange(-qmax, qmax+qstep*0.01, qstep)
+        y = pylab.arange(-qmax, qmax+qstep*0.01, qstep)
+    
+        calc_thread_2D = Calc2D(x, y, model.clone(), 
+                                        completefn=self.complete,
+                                        updatefn=self.update,
+                                        yieldtime=0.0)
+     
+        calc_thread_2D.queue()
+        calc_thread_2D.ready(2.5)
+        
+        while not self.done:
+            time.sleep(1)
+
+    def update(self,output):
+        print "update"
+
+    def complete(self,output, elapsed=0.0):
+        print "complete"
+        self.done = True
+
+if __name__ == "__main__":
+    CalcCommandline()
+   
Index: guiframe/state.py
===================================================================
--- guiframe/state.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
+++ guiframe/state.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -0,0 +1,217 @@
+"""
+    Object to manage the state of the application
+"""
+
+#TODO: use both CPUs and break the calculations in two threads
+#TODO: stop threads before starting a new one
+
+import wx
+import wx.lib.newevent
+
+import ModelParameters
+import history
+import modelData
+from model_thread import Calc2D, Calc1D, Calc2D_4fold, Calc2D_all
+from copy import deepcopy
+
+#TODO: State should report back to StateManager instead
+#    of firing these events itself
+(Refresh2DEvent, EVT_2DREFRESH) = wx.lib.newevent.NewEvent()
+(Refresh1DEvent, EVT_1DREFRESH) = wx.lib.newevent.NewEvent()
+
+# Debug printout
+from config import printEVT
+
+
+
+class State:
+    
+    def __init__(self, target=None, model=None, slicer=None):
+        self.target = target
+        self.model  = model
+        self.slicer = slicer
+        self.model_par_memento = None
+        self.detector_memento = None
+        self.averager_memento = None
+        self.pars1D_memento   = None
+        
+        # Keep the latest data 
+        self._data_2D = None
+        self._data_1D = None
+        # The following should disappear
+        self.qmax = None
+        self.x_1D = None
+        
+        # Threads
+        self.active = True
+        self.calc_thread_2D = None
+        self.calc_thread_1D = None
+        
+        # Info for history
+        self.description = ''
+        
+    def clone(self):
+        obj          = State(self.target, self.model.clone(), self.slicer)
+        obj._data_1D = deepcopy(self._data_1D)
+        obj._data_2D = deepcopy(self._data_2D)
+        obj.qmax     = deepcopy(self.qmax)
+        obj.x_1D     = deepcopy(self.x_1D)
+        obj.model_par_memento = deepcopy(self.model_par_memento)
+        obj.detector_memento = deepcopy(self.detector_memento)
+        obj.averager_memento = deepcopy(self.averager_memento)
+        obj.description = self.description
+        return obj
+        
+    def clear_data(self):
+        self._data_1D = None
+        self._data_2D = None
+        
+    def stop(self):
+        self.active = False
+        if self.calc_thread_1D != None and self.calc_thread_1D.isrunning():
+            self.calc_thread_1D.stop()
+        if self.calc_thread_2D != None and self.calc_thread_2D.isrunning():
+            self.calc_thread_2D.stop()
+        
+    def setModelParMemento(self, memento):
+        self.model_par_memento = memento
+        
+    def setDetectorMemento(self, memento):
+        self.clear_data()
+        # Change description
+        self.description = '%s' % memento.toString(self.detector_memento)
+        self.detector_memento = memento
+        
+    def setPars1DMemento(self, memento):
+        self.clear_data()
+        # Change description
+        self.description = '%s' % memento.toString(self.pars1D_memento)
+        self.pars1D_memento = memento
+        
+    def setAveragerMemento(self, memento):
+        self.clear_data()
+        self.averager_memento = memento
+        
+    def setModel(self, model):
+        """
+            Set the model and send a history event
+        """
+        # Clean the data to invoke recalc
+        self._data_1D = None
+        self._data_2D = None
+        
+        # Set the model 
+        self.model = model
+        
+        self.description = ''
+        
+        return str(self.model.name)
+        
+    def setParams(self, params):
+        """
+            Update parameters and return a name
+            that represents the change (key)
+        """
+        # Clean the data to invoke recalc
+        self._data_1D = None
+        self._data_2D = None
+
+        # Compose string to summarize change        
+        name = self.model.name
+        
+        # Identify and perform changes
+        for item in params:
+            name += "/%s=%-5.5g" % (item, self.model.getParam(item))
+            self.model.setParam(item, params[item])
+        
+        return name
+
+    def setSlicer(self, slicer):
+        self.slicer = slicer
+        
+    def get_data_1d(self, x):
+        """
+            Calculate the data 
+            @param min: minimum q-value
+            @param max: maximum q-value
+        """
+        
+        printEVT("State.get_data_1d")
+        if not self.model:
+            return
+        
+        if self._data_1D == None:
+            self.x_1D = deepcopy(x)
+            if self.calc_thread_1D != None and self.calc_thread_1D.isrunning():
+                self.calc_thread_1D.stop()
+                
+            self.calc_thread_1D = Calc1D(x, self.model.clone(), 
+                                completefn=self.complete1D,
+                                updatefn=self.update1D)
+            self.calc_thread_1D.queue()
+            self.calc_thread_1D.ready(2.5)
+        else:
+            printEVT("Dispatch 1D non-recalc data") 
+            wx.PostEvent(self.target, Refresh1DEvent(name=self.model.name, x = self.x_1D,
+                                                     output=deepcopy(self._data_1D)))
+         
+    
+    def get_data_2d(self, max, x, y):
+        """ 
+            Calculate 2D data
+            @param max: maximum q-value
+        """
+        if not self.model:
+            return
+        
+        if self._data_2D == None:
+            self.qmax = max
+            if self.calc_thread_2D != None and self.calc_thread_2D.isrunning():
+                self.calc_thread_2D.stop()
+                
+            if self.detector_memento == None or self.detector_memento.sym4 == False:    
+                self.calc_thread_2D = Calc2D(x, y, self.model.clone(), 
+                                    completefn=self.complete2D,
+                                    updatefn=self.update2D,
+                                    yieldtime=0.0)
+            else:
+                self.calc_thread_2D = Calc2D_4fold(x, y, self.model.clone(), 
+                                    completefn=self.complete2D,
+                                    updatefn=self.update2D,
+                                    yieldtime=0.0)
+                
+            self.calc_thread_2D.queue()
+            self.calc_thread_2D.ready(2.5)
+        else:
+            printEVT("Dispatch 2D non-recalc data") 
+            wx.PostEvent(self.target, Refresh2DEvent(output=deepcopy(self._data_2D),
+                                                    qmax=self.qmax))
+            
+             
+    def update2D(self, output):
+        printEVT("State.updated2D")
+        if self.active:
+            #self._data_2D = output
+            self._data_2D = deepcopy(output)
+            wx.PostEvent(self.target, Refresh2DEvent(output=deepcopy(self._data_2D),
+                                                qmax=self.qmax))
+    
+    def complete2D(self, output, elapsed):
+        printEVT("%s: Calc 2D complete in %g sec" % (self.model.name, elapsed)) 
+        if self.active:
+            self._data_2D = deepcopy(output)
+            wx.PostEvent(self.target, Refresh2DEvent(output=deepcopy(self._data_2D),
+                                                qmax=self.qmax))
+        else:
+            printEVT("Active = False!")
+
+    def update1D(self, output):
+        pass
+    
+    def complete1D(self, output, elapsed):
+        printEVT("Calc 1D complete in %g sec" % elapsed) 
+        self._data_1D = deepcopy(output)
+        wx.PostEvent(self.target, Refresh1DEvent(name=self.model.name, x = self.x_1D,
+                                                 output=deepcopy(self._data_1D)))
+
+    
Index: guiframe/stateManager.py
===================================================================
--- guiframe/stateManager.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
+++ guiframe/stateManager.py	(revision ca88b2eded93a483ace5848e87ba70d47c410949)
@@ -0,0 +1,323 @@
+"""
+    Object to manage the state of the application
+"""
+
+import wx
+import wx.lib.newevent
+
+import ModelParameters
+import AveragerParameters
+import Averager2D
+import history
+import modelData
+from model_thread import Calc2D
+from copy import deepcopy
+from fitting import ChiUpdateEvent 
+
+(Refresh2DEvent, EVT_2DREFRESH) = wx.lib.newevent.NewEvent()
+
+# Debug printout
+from config import printEVT
+import config
+from state import State
+import Plotter1D
+import Plotter2D
+
+(StateEvent, EVT_STATE)   = wx.lib.newevent.NewEvent()
+
+class DetectorMemento:
+    def __init__(self, qmax=0.1, npts=10, beam=None, zmin=None, zmax=None, sym4=False):
+        self.qmax = qmax
+        self.npts = npts
+        self.zmin = zmin
+        self.zmax = zmax
+        self.beam = beam
+        self.sym4 = sym4
+        
+    def toString(self, memento):
+        """
+            Return a string that describes the difference
+            between the given memento and the current one
+            @param memento: memento to compare to
+            @return: string
+        """
+        if memento == None:
+            return "/Qmax = %g/Det npts = %g" % (self.qmax, self.npts)
+        else:
+            descr = ''
+            if not memento.qmax == self.qmax:
+                descr += "/Qmax = %g" % self.qmax
+            if not memento.npts == self.npts:
+                descr += "/Det npts = %g" % self.npts
+            if not self.zmin==None and not memento.zmin == self.zmin:
+                descr += "/Zmin = %g" % self.zmin
+            if self.zmax and not memento.zmax == self.zmax:
+                descr += "/Zmax = %g" % self.zmax
+            if not memento.beam == self.beam:
+                descr += "/beamstop = %g" % self.beam
+            return descr
+
+class StateManager:
+    
+    def __init__(self, target=None, model=None, slicer=None):
+        self.target = target
+        self.state  = State(target, model, slicer)
+        
+        # Listen to parameter changes to fire history events
+
+        self.target.Bind(ModelParameters.EVT_MODEL_PARS, self._onEVT_MODEL_PARS)
+        self.target.Bind(AveragerParameters.EVT_AVG_PARS, self._onEVT_AVG_PARS)
+        self.target.Bind(EVT_STATE, self._onEVT_STATE)
+        self.target.Bind(Plotter2D.EVT_DETECTOR, self._onEVT_DETECTOR)
+        self.target.Bind(Plotter1D.EVT_PLOT1D_PARS, self._onEVT_PLOT1D_PARS)
+                
+    def setModel(self, target, model, tag=''):
+        """
+            Set the model and send a history event
+        """
+        # Prepare the restore event
+        self._saveCurrent(tag)
+        
+        # Now change the model
+        self.state.setModel(model)
+        evt = ModelParameters.ModelEvent(model=model, data=self.state, new=True)
+        wx.PostEvent(target, evt)
+        # Posting averager state
+        wx.PostEvent(target, AveragerParameters.AveragerStateEvent(memento=AveragerParameters.AveragerParameterMemento()))
+        
+    def _saveCurrent(self, tag=''):
+        """
+            Send a history event to capture the current state
+        """
+        #TODO: need a way to find the difference between old and new model
+        if not self.state.model == None:
+            stored_event = StateEvent(state = self.state.clone())
+            
+            # Send the history event
+            evt = history.HistoryEvent(name = self.state.model.name+'%s/%s' % (self.state.description, tag),
+                                       item = stored_event)
+            wx.PostEvent(self.target, evt)
+        
+        
+    def setSlicer(self, slicer):
+        """
+            Set the slicer
+        """
+        printEVT("StateManager.setSlicer: Slicer not implmeted")
+        self.state.slicer = slicer
+        
+    def _onEVT_STATE(self, event):
+        """
+            Restor a stored state
+            @param event: state event
+        """
+        self.state.stop()
+        self.state = event.state.clone()
+        
+        # Posting detector parameter panel state event
+        if not self.state.detector_memento == None:
+            det_event = Plotter2D.createDetectorParsEvent(qmax=self.state.detector_memento.qmax,
+                                                          npts=self.state.detector_memento.npts,
+                                                          beam=self.state.detector_memento.beam,
+                                                          zmin=self.state.detector_memento.zmin,
+                                                          zmax=self.state.detector_memento.zmax,
+                                                          sym4=self.state.detector_memento.sym4,
+                                                          info_only=True)
+        else:
+            det_event = Plotter2D.createDetectorParsEvent(qmax=None,
+                                                          npts=None,
+                                                          info_only=True)
+        printEVT("StateManager: posting detector memento")
+        wx.PostEvent(self.target, det_event)
+        
+        # Posting 1D plot parameters
+        if not self.state.pars1D_memento == None:
+            pars1D_event = Plotter1D.createPlot1DParsEvent(qmax=self.state.pars1D_memento.qmax,
+                                                           npts=self.state.pars1D_memento.npts,
+                                                           qmin=self.state.pars1D_memento.qmin,
+                                                           info_only=True)
+        else:
+            pars1D_event = Plotter1D.createPlot1DParsEvent(qmin=None,
+                                                           qmax=None,
+                                                           npts=None,
+                                                           info_only=True)
+        printEVT("StateManager: posting 1D plot pars memento")
+        wx.PostEvent(self.target, pars1D_event)
+        
+        
+        # Posting averager state
+        wx.PostEvent(self.target, AveragerParameters.AveragerStateEvent(memento=event.state.averager_memento))
+        
+        # Posting model event
+        evt = ModelParameters.ModelEvent(model=self.state.model, data=self.state, new=True)
+        wx.PostEvent(self.target, evt)
+        
+        # Posting parameter panel state event
+        evt2 = ModelParameters.ModelParameterStateEvent(memento=event.state.model_par_memento)
+        wx.PostEvent(self.target, evt2)
+        
+        printEVT("State.onEVT_STATE: history item restored: %s" % self.state.model.name)
+        
+    def _onEVT_DETECTOR(self, event):
+        config.printEVT("StateManager._onEVT_DETECTOR")
+        if event.info_only:
+            config.printEVT("   skip EVT_DETECTOR")
+            return
+        
+        event.Skip()
+        self._saveCurrent()
+        
+        # Clear current data to invoke recalculation
+        #self.state.clear_data()
+        self.state.setDetectorMemento(DetectorMemento(event.qmax, event.npts,
+                                                      zmin = event.zmin, zmax = event.zmax,
+                                                      beam = event.beam, sym4 = event.sym4))
+        
+        # Send an update event to the plotters
+        evt = ModelParameters.ModelEvent(model=self.state.model, data=self.state, new=False)
+        wx.PostEvent(self.target, evt)
+
+    def _onEVT_PLOT1D_PARS(self, event):
+        config.printEVT("StateManager._onEVT_PLOT1D_PARS")
+        if event.info_only:
+            config.printEVT("   skip EVT_PLOT1D_PARS")
+            return
+        
+        event.Skip()
+        self._saveCurrent()
+        
+        # Update the state
+        self.state.setPars1DMemento(Plotter1D.Plot1DMemento(event.qmin, 
+                                                              event.qmax, 
+                                                              event.npts))
+        
+        # Send an update event to the plotters
+        evt = ModelParameters.ModelEvent(model=self.state.model, data=self.state, new=False)
+        wx.PostEvent(self.target, evt)
+
+        
+    def _onEVT_MODEL_PARS(self, event):
+        """
+            Modify the parameters of the model and
+            fire a history event
+            
+            @param event: parameter change event
+        """
+        printEVT("State.onEVT_MODEL_PARS")
+
+        # Save the state before changing it
+        stored_event = StateEvent(state = self.state.clone())
+
+        # Post message to status bar
+        wx.PostEvent(self.target, config.StatusBarEvent(message=event.status))
+        
+        # Dispersion?
+        disp_tag = ''
+        if event.disp_changed and len(event.disp)>0:
+            if not self.state.model.__class__.__name__ == "Averager2D":
+                config.printEVT("StateMng: setting up averager")
+                new_model = Averager2D.Averager2D()
+                flag = new_model.set_model(self.state.model.clone())
+                self.state.setModel(new_model)
+            else:
+                disp_list = deepcopy(self.state.model.get_dispersity())
+                for item in disp_list:
+                    disp_tag += '/Disp[%s]=%g [%g pts]' % (item[0], item[1], item[2])
+                
+            self.state.model.set_dispersity(event.disp)
+            
+        if not event.average and \
+            self.state.model.__class__.__name__ == "Averager2D":
+            disp_list = deepcopy(self.state.model.get_dispersity())
+            if self.state.model.set_dispersity([]):
+                for item in disp_list:
+                    disp_tag += '/Disp[%s]=%g [%g pts]' % (item[0], item[1], item[2])
+        
+        # Push parameters to model
+        name = self.state.setParams(event.params)
+        self.state.setModelParMemento(event.memento)
+
+        #print str(self.state.model)
+
+        # Send an update event to the plotters
+        evt = ModelParameters.ModelEvent(model=self.state.model, data=self.state, new=False)
+        wx.PostEvent(self.target, evt)
+
+        if len(event.params)>0 or len(event.disp)>0 or event.disp_changed:      
+            # Send the history event
+            evt = history.HistoryEvent(name = name+disp_tag,
+                                       item = stored_event)
+            wx.PostEvent(self.target, evt)
+            
+            # Post chi^2 recalculation event for fit panel
+            wx.PostEvent(self.target, ChiUpdateEvent())
+            
+    def _onEVT_AVG_PARS(self, event):
+        printEVT("State._onEVT_AVG_PARS")
+
+        # Save the state before changing it
+        stored_event = StateEvent(state = self.state.clone())
+        
+        # Inspect the change to give a proper title to the 
+        # history item
+        info = "Phi = OFF/THETA=ON (filename)"
+        
+        # Set averager memento to retreive the same state from history panel
+        self.state.setAveragerMemento(event.memento)
+        
+        # Flags for history event
+        phi_flag   = "OFF"
+        theta_flag = "OFF"
+                    
+        #theta_file = os.path.basename(self.theta_file_path)
+        
+        # Post message to status bar if needed
+        if not event.status == None:
+             wx.PostEvent(self.target, config.StatusBarEvent(message=event.status))
+            
+        # Check if we already have an Averager2D model
+        # When we reach this points, either we need an Averager2D 
+        # or we already have one.
+        if not self.state.model.__class__.__name__ == "Averager2D":
+            model = Averager2D.Averager2D()
+            flag = model.set_model(self.state.model.clone())
+            if flag:
+                self.state.setModel(model)                        
+            else:
+                message = "Current model doesn't have theta or phi: skipping"
+                wx.PostEvent(self.target, config.StatusBarEvent(message=message))
+                return
+        else:
+            if self.state.model.phi_on:
+                phi_flag = "ON"
+            if self.state.model.theta_on:
+                theta_flag = "ON"
+            
+        # Set phi info
+        if event.phi_on:
+            if not self.state.model.setPhiFile(event.phi_file):
+                message = "Current model doesn't have phi: skipping"
+                wx.PostEvent(self.target, config.StatusBarEvent(message=message))
+        else:
+            self.state.model.setPhiFile(None)
+        
+        # Set theta info
+        if event.theta_on:
+            if not self.state.model.setThetaFile(event.theta_file):
+                message = "Current model doesn't have phi: skipping"
+                wx.PostEvent(self.target, config.StatusBarEvent(message=message))                
+        else:
+            self.state.model.setThetaFile(None)
+        
+        # Send the history event
+        hist_evt = history.HistoryEvent(name = "%s/Phi %s/Theta %s" % \
+                                        (self.state.model.name, phi_flag, theta_flag),
+                                   item = stored_event)
+        wx.PostEvent(self.target, hist_evt)
+        
+        # Send an update event to the plotters
+        evt = ModelParameters.ModelEvent(model=self.state.model, data=self.state, new=False)
+        wx.PostEvent(self.target, evt)
+
+            
+            
