Index: src/sas/sasgui/perspectives/calculator/__init__.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/__init__.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/__init__.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,47 @@
+PLUGIN_ID = "Calculator plug-in 1.0"
+import os
+from distutils.filelist import findall
+from calculator import *
+N_DIR = 12
+def get_data_path(media):
+    """
+    """
+    # Check for data path in the package
+    path = os.path.join(os.path.dirname(__file__), media)
+    if os.path.isdir(path):
+        return path
+    # Check for data path next to exe/zip file.
+    # If we are inside a py2exe zip file, we need to go up
+    # two levels to get to the directory containing the exe
+    # We will check if the exe and the xsf are in the same
+    # directory.
+    path = os.path.dirname(__file__)
+    #Look for maximum n_dir up of the current dir to find media
+   
+    #for i in range(n_dir):
+    i = 0
+    while(i < N_DIR):
+        path, _ = os.path.split(path)
+        media_path = os.path.join(path, media)
+        if os.path.isdir(media_path):
+             module_media_path = os.path.join(media_path,'calculator_%s'% media)
+             if os.path.isdir(module_media_path):
+                 return module_media_path
+             return media_path
+        i += 1
+   
+    raise RuntimeError('Could not find calculator media files')
+
+def data_files():
+    """
+    Return the data files associated with media calculator.
+    
+    The format is a list of (directory, [files...]) pairs which can be
+    used directly in setup(...,data_files=...) for setup.py.
+
+    """
+    data_files = []
+    path = get_data_path(media="media")
+    for f in findall(path):
+        data_files.append(('media/calculator_media', [f]))
+    return data_files
Index: src/sas/sasgui/perspectives/calculator/aperture_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/aperture_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/aperture_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,378 @@
+
+import wx
+import sys
+from copy import deepcopy
+from sas.sasgui.guiframe.utils import check_float
+
+_BOX_WIDTH = 60
+if sys.platform.count("win32") > 0:
+    _STATICBOX_WIDTH = 450
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 290
+    FONT_VARIANT = 0
+else:
+    _STATICBOX_WIDTH = 480
+    PANEL_WIDTH = 530
+    PANEL_HEIGHT = 320
+    FONT_VARIANT = 1
+
+class ApertureDialog(wx.Dialog):
+    def __init__(self, parent=None, manager=None, aperture=None, *args, **kwds):
+        """
+        Dialog allows to enter values for aperture
+        """
+        kwds['size'] = (PANEL_WIDTH, PANEL_HEIGHT)
+        kwds['title'] = "Aperture Editor"
+        wx.Dialog.__init__(self, parent=parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self._aperture = aperture
+        self._reset_aperture = deepcopy(aperture)
+        self._notes = ""
+
+        #Attributes for panel
+        self.aperture_name_tcl = None
+        self.main_sizer = None
+        self.box_aperture = None
+        self.boxsizer_aperture = None
+        self.name_sizer = None
+        self.name_sizer = None
+        self.size_name_tcl = None
+        self.type_sizer = None
+        self.distance_sizer = None
+        self.size_name_sizer = None
+        self.aperture_size_unit_tcl = None
+        self.aperture_size_sizer = None
+        self.button_sizer = None
+        self.aperture_name_tcl = None
+        self.type_tcl = None
+        self.distance_tcl = None
+        self.distance_unit_tcl = None
+        self.x_aperture_size_tcl = None
+        self.y_aperture_size_tcl = None
+        self.z_aperture_size_tcl = None
+        self.bt_apply = None
+        self.bt_cancel = None
+        self.bt_close = None
+
+        self._do_layout()
+        self.set_values()
+
+    def _define_structure(self):
+        """
+        define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_aperture = wx.StaticBox(self, -1, str("Aperture"))
+        self.boxsizer_aperture = wx.StaticBoxSizer(self.box_aperture, wx.VERTICAL)
+        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.type_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.distance_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.size_name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.aperture_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_name(self):
+        """
+        Do the layout for aperture name related widgets
+        """
+        #Aperture name [string]
+        aperture_name_txt = wx.StaticText(self, -1, 'Aperture Name : ')
+        self.aperture_name_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH * 5, 20), style=0)
+        self.name_sizer.AddMany([(aperture_name_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                 (self.aperture_name_tcl, 0, wx.EXPAND)])
+    def _layout_type(self):
+        """
+        Do the  layout for aperture type  related widgets
+        """
+        #Aperture type [string]
+        type_txt = wx.StaticText(self, -1, 'Type: ')
+        self.type_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.type_sizer.AddMany([(type_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                 (self.type_tcl, 0, wx.LEFT, 20)])
+
+    def _layout_distance(self):
+        """
+        Do the  layout for aperture distance related widgets
+        """
+        #Aperture distance [float]
+        distance_txt = wx.StaticText(self, -1, 'Distance:')
+        self.distance_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                        style=0)
+        distance_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.distance_unit_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.distance_sizer.AddMany([(distance_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.distance_tcl, 0, wx.LEFT, 10),
+                                     (distance_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.distance_unit_tcl, 0, wx.EXPAND)])
+    def _layout_size_name(self):
+        """
+        Do the  layout for size name related widgets
+        """
+        # Size name [string]
+        size_name_txt = wx.StaticText(self, -1, 'Size Name : ')
+        self.size_name_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH * 5, 20), style=0)
+        self.size_name_sizer.AddMany([(size_name_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                      (self.size_name_tcl, 0, wx.EXPAND)])
+
+    def _layout_size(self):
+        """
+        Do the  layout for aperture size related widgets
+        """
+        #Aperture size [Vector]
+        aperture_size_txt = wx.StaticText(self, -1, 'Size:')
+        x_aperture_size_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_aperture_size_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        y_aperture_size_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_aperture_size_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        z_aperture_size_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_aperture_size_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        aperture_size_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.aperture_size_unit_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.aperture_size_sizer.AddMany([(aperture_size_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                          (x_aperture_size_txt, 0, wx.LEFT, 17),
+                                          (self.x_aperture_size_tcl, 0, wx.RIGHT, 10),
+                                          (y_aperture_size_txt, 0, wx.EXPAND),
+                                          (self.y_aperture_size_tcl, 0, wx.RIGHT, 10),
+                                          (z_aperture_size_txt, 0, wx.EXPAND),
+                                          (self.z_aperture_size_tcl, 0, wx.RIGHT, 10),
+                                          (aperture_size_unit_txt, 0, wx.EXPAND),
+                                          (self.aperture_size_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_button(self):
+        """
+        Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, 'Apply')
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+        self.bt_apply.SetToolTipString("Apply current changes to aperture.")
+        self.bt_cancel = wx.Button(self, -1, 'Cancel')
+        self.bt_cancel.SetToolTipString("Cancel current changes.")
+        self.bt_cancel.Bind(wx.EVT_BUTTON, self.on_click_cancel)
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.SetToolTipString("Close window.")
+        self.button_sizer.AddMany([(self.bt_apply, 0, wx.LEFT, 200),
+                                   (self.bt_cancel, 0, wx.LEFT, 10),
+                                   (self.bt_close, 0, wx.LEFT, 10)])
+
+    def _do_layout(self):#, data=None):
+        """
+        Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_type()
+        self._layout_distance()
+        self._layout_size_name()
+        self._layout_size()
+        self._layout_button()
+        self.boxsizer_aperture.AddMany([(self.name_sizer, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                        (self.type_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                        (self.distance_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                        (self.size_name_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                        (self.aperture_size_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.main_sizer.AddMany([(self.boxsizer_aperture, 0, wx.ALL, 10),
+                                 (self.button_sizer, 0,
+                                  wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def set_manager(self, manager):
+        """
+        Set manager of this window
+        """
+        self.manager = manager
+
+    def reset_aperture(self):
+        """
+        put the default value of the detector back to the current aperture
+        """
+        self._aperture.name = self._reset_aperture.name
+        self._aperture.type = self._reset_aperture.type
+        self._aperture.size_name = self._reset_aperture.size_name
+        self._aperture.size.x = self._reset_aperture.size.x
+        self._aperture.size.y = self._reset_aperture.size.y
+        self._aperture.size.z = self._reset_aperture.size.z
+        self._aperture.size_unit = self._reset_aperture.size_unit
+        self._aperture.distance = self._reset_aperture.distance
+        self._aperture.distance_unit = self._reset_aperture.distance_unit
+
+    def set_values(self):
+        """
+        take the aperture values of the current data and display them
+        through the panel
+        """
+        aperture = self._aperture
+        #Name
+        self.aperture_name_tcl.SetValue(str(aperture.name))
+        #Type
+        self.type_tcl.SetValue(str(aperture.type))
+        #distance
+        self.distance_tcl.SetValue(str(aperture.distance))
+        #distance unit
+        self.distance_unit_tcl.SetValue(str(aperture.distance_unit))
+        #Size name
+        self.size_name_tcl.SetValue(str(aperture.size_name))
+        #Aperture size as a vector
+        x, y, z = aperture.size.x, aperture.size.y, aperture.size.z
+        self.x_aperture_size_tcl.SetValue(str(x))
+        self.y_aperture_size_tcl.SetValue(str(y))
+        self.z_aperture_size_tcl.SetValue(str(z))
+        self.aperture_size_unit_tcl.SetValue(str(aperture.size_unit))
+
+    def get_aperture(self):
+        """
+        return the current aperture
+        """
+        return self._aperture
+
+    def get_notes(self):
+        """
+        return notes
+        """
+        return self._notes
+
+    def on_change_name(self):
+        """
+        Change name
+        """
+        #Change the name of the aperture
+        name = self.aperture_name_tcl.GetValue().lstrip().rstrip()
+        if name == "":
+            name = str(None)
+        if self._aperture.name != name:
+            self._notes += "Change sample 's "
+            self._notes += "name from %s to %s \n" % (self._aperture.name, name)
+            self._aperture.name = name
+
+    def on_change_type(self):
+        """
+        Change aperture type
+        """
+        #Change type
+        type_value = self.type_tcl.GetValue().lstrip().rstrip()
+        self._aperture.type = type_value
+        self._notes += " Change type from"
+        self._notes += " %s to %s \n" % (self._aperture.type, type_value)
+
+    def on_change_distance(self):
+        """
+        Change distance of the aperture
+        """
+        #Change distance
+        distance = self.distance_tcl.GetValue().lstrip().rstrip()
+        if distance == "" or distance == str(None):
+            distance = None
+            self._aperture.distance = distance
+        else:
+            if check_float(self.distance_tcl):
+                if self._aperture.distance != float(distance):
+                    self._notes += "Change distance from "
+                    self._notes += "%s to %s \n" % (self._aperture.distance, distance)
+                    self._aperture.distance = float(distance)
+            else:
+                self._notes += "Error: Expected a float for distance  "
+                self._notes += "won't changes distance from "
+                self._notes += "%s to %s" % (self._aperture.distance, distance)
+        #change the distance unit
+        unit = self.distance_unit_tcl.GetValue().lstrip().rstrip()
+        if self._aperture.distance_unit != unit:
+            self._notes += " Change distance 's unit from "
+            self._notes += "%s to %s" % (self._aperture.distance_unit, unit)
+
+    def on_change_size_name(self):
+        """
+        Change the size's name
+        """
+        #Change size name
+        size_name = self.size_name_tcl.GetValue().lstrip().rstrip()
+        self._aperture.size_name = size_name
+        self._notes += " Change size name from"
+        self._notes += " %s to %s \n" % (self._aperture.size_name, size_name)
+
+    def on_change_size(self):
+        """
+        Change aperture size
+        """
+        #Change x coordinate
+        x_aperture_size = self.x_aperture_size_tcl.GetValue().lstrip().rstrip()
+        if x_aperture_size == "" or x_aperture_size == str(None):
+            x_aperture_size = None
+        else:
+            if check_float(self.x_aperture_size_tcl):
+                if self._aperture.size.x != float(x_aperture_size):
+                    self._notes += "Change x of aperture size from "
+                    self._notes += "%s to %s \n" % (self._aperture.size.x, x_aperture_size)
+                    self._aperture.aperture_size.x = float(x_aperture_size)
+            else:
+                self._notes += "Error: Expected a"
+                self._notes += " float for the aperture size 's x "
+                self._notes += "won't changes x aperture size from "
+                self._notes += "%s to %s" % (self._aperture.size.x, x_aperture_size)
+        #Change y coordinate
+        y_aperture_size = self.y_aperture_size_tcl.GetValue().lstrip().rstrip()
+        if y_aperture_size == "" or y_aperture_size == str(None):
+            y_aperture_size = None
+            self._aperture.size.y = y_aperture_size
+        else:
+            if check_float(self.y_aperture_size_tcl):
+                if self._aperture.size.y != float(y_aperture_size):
+                    self._notes += "Change y of aperture size from "
+                    self._notes += "%s to %s \n" % (self._aperture.size.y, y_aperture_size)
+                    self._aperture.size.y = float(y_aperture_size)
+            else:
+                self._notes += "Error: Expected a float for the"
+                self._notes += " aperture size's y "
+                self._notes += "won't changes y aperture size from "
+                self._notes += "%s to %s" % (self._aperture.size.y, y_aperture_size)
+        #Change z coordinate
+        z_aperture_size = self.z_aperture_size_tcl.GetValue().lstrip().rstrip()
+        if z_aperture_size == "" or z_aperture_size == str(None):
+            z_aperture_size = None
+            self._aperture.size.z = z_aperture_size
+        else:
+            if check_float(self.z_aperture_size_tcl):
+                if self._aperture.size.z != float(z_aperture_size):
+                    self._notes += "Change z of aperture size from "
+                    self._notes += "%s to %s \n" % (self._aperture.size.z, z_aperture_size)
+                    self._aperture.size.z = float(z_aperture_size)
+            else:
+                self._notes += "Error: Expected a float for the offset 's x "
+                self._notes += "won't changes z aperture size from "
+                self._notes += "%s to %s" % (self._aperture.size.z, z_aperture_size)
+        #change the aperture center unit
+        unit = self.aperture_size_unit_tcl.GetValue().lstrip().rstrip()
+        if self._aperture.size_unit != unit:
+            self._notes += " Change aperture size's unit from "
+            self._notes += "%s to %s" % (self._aperture.size_unit, unit)
+            self._aperture.size_unit = unit
+
+    def on_click_apply(self, event):
+        """
+        Apply user values to the aperture
+        """
+        self.on_change_name()
+        self.on_change_type()
+        self.on_change_distance()
+        self.on_change_size_name()
+        self.on_change_size()
+        self.set_values()
+        if self.manager is not None:
+            self.manager.set_aperture(self._aperture)
+        if event is not None:
+            event.Skip()
+
+    def on_click_cancel(self, event):
+        """
+        reset the current aperture to its initial values
+        """
+        self.reset_aperture()
+        self.set_values()
+        if self.manager is not None:
+            self.manager.set_aperture(self._aperture)
+        if event is not None:
+            event.Skip()
Index: src/sas/sasgui/perspectives/calculator/calculator.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/calculator.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/calculator.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,233 @@
+"""
+Calculator Module
+"""
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2010, University of Tennessee
+################################################################################
+
+import wx
+from sas.sasgui.guiframe.plugin_base import PluginBase
+from sas.sasgui.perspectives.calculator.data_operator import DataOperatorWindow
+from sas.sasgui.perspectives.calculator.data_editor import DataEditorWindow
+from sas.sasgui.perspectives.calculator.kiessig_calculator_panel import KiessigWindow
+from sas.sasgui.perspectives.calculator.sld_panel import SldWindow
+from sas.sasgui.perspectives.calculator.density_panel import DensityWindow
+from sas.sasgui.perspectives.calculator.slit_length_calculator_panel \
+            import SlitLengthCalculatorWindow
+from sas.sasgui.perspectives.calculator.resolution_calculator_panel \
+            import ResolutionWindow
+from sas.sasgui.perspectives.calculator.gen_scatter_panel import SasGenWindow
+from sas.sasgui.perspectives.calculator.image_viewer import ImageView
+from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
+import logging
+
+class Plugin(PluginBase):
+    """
+    This class defines the interface for a Plugin class
+    for calculator perspective
+    """
+    def __init__(self):
+        PluginBase.__init__(self, name="Calculator")
+        # Log startup
+        logging.info("Calculator plug-in started")
+        self.sub_menu = "Tool"
+        self.data_edit_frame = None
+        # data operator use one frame all the time
+        self.data_operator_frame = None
+        self.kiessig_frame = None
+        self.sld_frame = None
+        self.cal_md_frame = None
+        self.cal_slit_frame = None
+        self.cal_res_frame = None
+        self.gen_frame = None
+        self.image_view = None
+        self.py_frame = None
+
+
+    def get_tools(self):
+        """
+        Returns a set of menu entries for tools
+        """
+        data_oper_help = "Perform arithmetic data operation (+...) "
+        data_oper_help += "and combination (|)"
+        kiessig_help = "Approximately computes the "
+        kiessig_help += "thickness of a shell or the size of "
+        kiessig_help += "particles \n from the width of a Kiessig fringe."
+        sld_help = "Computes the Scattering Length Density."
+        slit_length_help = "Computes the slit length from the beam profile."
+        resolution_help = "Approximately estimates the "
+        resolution_help += "resolution of Q in 2D based on the SAS "
+        resolution_help += "instrumental parameter values."
+        mass_volume_help = "Based on the chemical formula, "
+        mass_volume_help += "compute the mass density or the molar volume."
+        gensas_help = "Generic SAS"
+        pyconsole_help = "Python Console."
+        imageviewer_help = "Load an image file and display the image."
+        #data_editor_help = "Meta Data Editor"
+        return [("Data Operation",
+                        data_oper_help, self.on_data_operation),
+                ("SLD Calculator", sld_help, self.on_calculate_sld),
+                ("Density/Volume Calculator", mass_volume_help,
+                                            self.on_calculate_dv),
+                ("Slit Size Calculator", slit_length_help,
+                        self.on_calculate_slit_size),
+                ("Kiessig Thickness Calculator",
+                        kiessig_help, self.on_calculate_kiessig),
+                          ("SAS Resolution Estimator",
+                        resolution_help, self.on_calculate_resoltuion),
+                ("Generic Scattering Calculator",
+                        gensas_help, self.on_gen_model),
+                ("Python Shell/Editor", pyconsole_help, self.on_python_console),
+                ("Image Viewer", imageviewer_help, self.on_image_viewer), ]
+
+    def on_edit_data(self, event):
+        """
+        Edit meta data
+        """
+        if self.data_edit_frame == None:
+            self.data_edit_frame = DataEditorWindow(parent=self.parent,
+                                                    manager=self, data=[],
+                                                    title="Data Editor")
+            self.put_icon(self.data_edit_frame)
+        else:
+            self.data_edit_frame.Show(False)
+        self.data_edit_frame.Show(True)
+
+    def on_data_operation(self, event):
+        """
+        Data operation
+        """
+        if self.data_operator_frame == None:
+            # Use one frame all the time
+            self.data_operator_frame = DataOperatorWindow(parent=self.parent,
+                                                manager=self,
+                                                title="Data Operation")
+            self.put_icon(self.data_operator_frame)
+        else:
+            self.data_operator_frame.Show(False)
+        self.data_operator_frame.panel.set_panel_on_focus(None)
+        self.data_operator_frame.Show(True)
+
+    def on_calculate_kiessig(self, event):
+        """
+        Compute the Kiessig thickness
+        """
+        if self.kiessig_frame == None:
+            frame = KiessigWindow(parent=self.parent, manager=self)
+            self.put_icon(frame)
+            self.kiessig_frame = frame
+        else:
+            self.kiessig_frame.Show(False)
+        self.kiessig_frame.Show(True)
+
+    def on_calculate_sld(self, event):
+        """
+        Compute the scattering length density of molecula
+        """
+        if self.sld_frame == None:
+            frame = SldWindow(parent=self.parent,
+                                  base=self.parent, manager=self)
+            self.put_icon(frame)
+            self.sld_frame = frame
+        else:
+            self.sld_frame.Show(False)
+        self.sld_frame.Show(True)
+
+    def on_calculate_dv(self, event):
+        """
+        Compute the mass density or molar voulme
+        """
+        if self.cal_md_frame == None:
+            frame = DensityWindow(parent=self.parent,
+                                  base=self.parent, manager=self)
+            self.put_icon(frame)
+            self.cal_md_frame = frame
+        else:
+            self.cal_md_frame.Show(False)
+        self.cal_md_frame.Show(True)
+
+    def on_calculate_slit_size(self, event):
+        """
+        Compute the slit size a given data
+        """
+        if self.cal_slit_frame == None:
+            frame = SlitLengthCalculatorWindow(parent=self.parent, manager=self)
+            self.put_icon(frame)
+            self.cal_slit_frame = frame
+        else:
+            self.cal_slit_frame.Show(False)
+        self.cal_slit_frame.Show(True)
+
+    def on_calculate_resoltuion(self, event):
+        """
+        Estimate the instrumental resolution
+        """
+        if self.cal_res_frame == None:
+            frame = ResolutionWindow(parent=self.parent, manager=self)
+            self.put_icon(frame)
+            self.cal_res_frame = frame
+        else:
+            self.cal_res_frame.Show(False)
+        self.cal_res_frame.Show(True)
+
+    def on_gen_model(self, event):
+        """
+        On Generic model menu event
+        """
+        if self.gen_frame == None:
+            frame = SasGenWindow(parent=self.parent, manager=self)
+            self.put_icon(frame)
+            self.gen_frame = frame
+        else:
+            self.gen_frame.Show(False)
+        self.gen_frame.Show(True)
+
+    def on_image_viewer(self, event):
+        """
+        Get choose an image file dialog
+
+        :param event: menu event
+        """
+        self.image_view = ImageView(parent=self.parent)
+        self.image_view.load()
+
+    def on_python_console(self, event):
+        """
+        Open Python Console
+
+        :param event: menu event
+        """
+        self.get_python_panel(filename=None)
+
+    def get_python_panel(self, filename=None):
+        """
+        Get the python shell panel
+
+        :param filename: file name to open in editor
+        """
+        if self.py_frame == None:
+            frame = PyConsole(parent=self.parent, base=self,
+                              filename=filename)
+            self.put_icon(frame)
+            self.py_frame = frame
+        else:
+            self.py_frame.Show(False)
+        self.py_frame.Show(True)
+
+    def put_icon(self, frame):
+        """
+        Put icon in the frame title bar
+        """
+        if hasattr(frame, "IsIconized"):
+            if not frame.IsIconized():
+                try:
+                    icon = self.parent.GetIcon()
+                    frame.SetIcon(icon)
+                except:
+                    pass
Index: src/sas/sasgui/perspectives/calculator/calculator_widgets.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/calculator_widgets.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/calculator_widgets.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,97 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation.
+
+See the license text in license.txt
+
+copyright 2009, University of Tennessee
+"""
+import wx
+import sys
+CHILD_FRAME = wx.MDIChildFrame
+if sys.platform.count("win32") < 1:
+    if sys.platform.count("darwin") < 1:
+        CHILD_FRAME = wx.Frame
+
+class InputTextCtrl(wx.TextCtrl):
+    """
+        Text control for model and fit parameters.
+        Binds the appropriate events for user interactions.
+    """
+    def __init__(self, parent=None, *args, **kwds):
+
+        wx.TextCtrl.__init__(self, parent, *args, **kwds)
+
+        ## Set to True when the mouse is clicked while the whole 
+        #string is selected
+        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)
+        self.Bind(wx.EVT_TEXT_ENTER, parent._onparamEnter)
+
+    def _on_set_focus(self, event):
+        """
+            Catch when the text control is set in focus to highlight the whole
+            text if necessary
+            @param event: mouse event
+        """
+        event.Skip()
+        self.full_selection = True
+
+    def _highlight_text(self, event):
+        """
+            Highlight text of a TextCtrl only of no text has be selected
+            @param event: mouse event
+        """
+        # Make sure the mouse event is available to other listeners
+        event.Skip()
+        control = event.GetEventObject()
+        if self.full_selection:
+            self.full_selection = False
+            # Check that we have a TextCtrl
+            if issubclass(control.__class__, wx.TextCtrl):
+                # Check whether text has been selected, 
+                # if not, select the whole string
+                (start, end) = control.GetSelection()
+                if start == end:
+                    control.SetSelection(-1, -1)
+
+
+class InterActiveOutputTextCtrl(wx.TextCtrl):
+    """
+        Text control used to display outputs.
+        No editing allowed. The background is
+        grayed out. User can't select text.
+    """
+    def __init__(self, *args, **kwds):
+        wx.TextCtrl.__init__(self, *args, **kwds)
+        self.SetEditable(False)
+        self.SetBackgroundColour(self.GetParent().GetBackgroundColour())
+
+class OutputTextCtrl(InterActiveOutputTextCtrl):
+    """
+        Text control used to display outputs.
+        No editing allowed. The background is
+        grayed out. User can't select text.
+    """
+    def __init__(self, *args, **kwds):
+        InterActiveOutputTextCtrl.__init__(self, *args, **kwds)
+        self.SetEditable(False)
+        self.SetBackgroundColour(self.GetParent().GetBackgroundColour())
+
+        # Bind to mouse event to avoid text highlighting
+        # The event will be skipped once the call-back
+        # is called.
+        self.Bind(wx.EVT_MOUSE_EVENTS, self._click)
+
+    def _click(self, event):
+        """
+            Prevent further handling of the mouse event
+            by not calling Skip().
+        """
+        pass
+
Index: src/sas/sasgui/perspectives/calculator/collimation_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/collimation_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/collimation_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,547 @@
+"""
+"""
+import wx
+import sys
+from copy import deepcopy
+from sas.sascalc.dataloader.loader import Loader
+from sas.sascalc.dataloader.data_info import Aperture, Collimation
+from aperture_editor import ApertureDialog
+
+from sas.sasgui.guiframe.utils import check_float
+_BOX_WIDTH = 60
+
+if sys.platform.count("win32") > 0:
+    _STATICBOX_WIDTH = 500
+    PANEL_WIDTH = 530
+    PANEL_HEIGHT = 430
+    FONT_VARIANT = 0
+else:
+    _STATICBOX_WIDTH = 550
+    PANEL_WIDTH = 600
+    PANEL_HEIGHT = 480
+    FONT_VARIANT = 1
+
+class CollimationDialog(wx.Dialog):
+    """
+    """
+    def __init__(self, parent=None, manager=None,
+                 collimation=[], *args, **kwds):
+        """
+        """
+        kwds['size'] = (PANEL_WIDTH, PANEL_HEIGHT)
+        kwds['title'] = "Collimation Editor"
+        wx.Dialog.__init__(self, parent=parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self._collimation = collimation
+        self._reset_collimation = deepcopy(collimation)
+        self._notes = ""
+        self._description = "Edit collimation"
+        #layout attributes
+        self.main_sizer = None
+        self.box_collimation = None
+        self.boxsizer_collimation = None
+
+
+        self._do_layout()
+        self.set_values()
+
+    def _define_structure(self):
+        """
+        define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_collimation = wx.StaticBox(self, -1,
+                                            str("Edit Selected Collimation"))
+        self.boxsizer_collimation = wx.StaticBoxSizer(self.box_collimation,
+                                                       wx.VERTICAL)
+
+        collimation_box = wx.StaticBox(self, -1, "Edit Number of Collimations")
+        self.collimation_sizer = wx.StaticBoxSizer(collimation_box, wx.VERTICAL)
+        self.collimation_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
+        self.collimation_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.collimation_hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.length_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        aperture_box = wx.StaticBox(self, -1, "Edit Aperture")
+        self.aperture_sizer = wx.StaticBoxSizer(aperture_box, wx.VERTICAL)
+        self.aperture_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.aperture_hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_collimation(self):
+        """
+        Do the layout for collimation related widgets
+        """
+        collimation_name_txt = wx.StaticText(self, -1, "Collimation:")
+        hint_collimation_txt = 'Current available collimation.'
+        collimation_name_txt.SetToolTipString(hint_collimation_txt)
+        self.collimation_cbox = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.collimation_cbox, -1, self.on_select_collimation)
+        hint_collimation_name_txt = 'Name of collimations.'
+        self.collimation_cbox.SetToolTipString(hint_collimation_name_txt)
+
+        self.bt_add_collimation = wx.Button(self, -1, "Add")
+        self.bt_add_collimation.SetToolTipString("Edit data's collimation.")
+        self.bt_add_collimation.Bind(wx.EVT_BUTTON, self.add_collimation)
+
+        self.bt_remove_collimation = wx.Button(self, -1, "Remove")
+        hint = "Remove data's collimation."
+        self.bt_remove_collimation.SetToolTipString(hint)
+        self.bt_remove_collimation.Bind(wx.EVT_BUTTON, self.remove_collimation)
+
+        self.collimation_button_sizer.AddMany([(collimation_name_txt, 0,
+                                                 wx.LEFT, 15),
+                                     (self.collimation_cbox, 0, wx.LEFT, 5),
+                                     (self.bt_add_collimation, 0, wx.LEFT, 10),
+                                     (self.bt_remove_collimation,
+                                       0, wx.LEFT, 5)])
+        collimation_hint_txt = 'No collimation is available for this data.'
+        self.collimation_txt = wx.StaticText(self, -1, collimation_hint_txt)
+        self.collimation_hint_sizer.Add(self.collimation_txt, 0, wx.LEFT, 10)
+        self.collimation_sizer.AddMany([(self.collimation_button_sizer,
+                                          0, wx.ALL, 10),
+                                     (self.collimation_hint_sizer,
+                                       0, wx.ALL, 10)])
+
+        self.fill_collimation_combox()
+        self.enable_collimation()
+
+
+    def _layout_name(self):
+        """
+        Do the layout for collimation name related widgets
+        """
+        #Collimation name [string]
+        name_txt = wx.StaticText(self, -1, 'Name : ')
+        self.name_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH * 5, 20), style=0)
+        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                       (self.name_tcl, 0, wx.EXPAND)])
+
+    def _layout_length(self):
+        """
+        Do the  layout for length related widgets
+        """
+        #Collimation length
+        length_txt = wx.StaticText(self, -1, 'Length:')
+        self.length_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        length_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.length_unit_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                           style=0)
+        self.length_sizer.AddMany([(length_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.length_tcl, 0, wx.LEFT, 12),
+                                     (length_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.length_unit_tcl, 0, wx.EXPAND)])
+
+    def _layout_button(self):
+        """
+        Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, 'Apply')
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+        self.bt_apply.SetToolTipString("Apply current changes to collimation.")
+        self.bt_cancel = wx.Button(self, -1, 'Cancel')
+        self.bt_cancel.SetToolTipString("Cancel current changes.")
+        self.bt_cancel.Bind(wx.EVT_BUTTON, self.on_click_cancel)
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.SetToolTipString("Close window.")
+        self.button_sizer.AddMany([(self.bt_apply, 0, wx.LEFT, 200),
+                                   (self.bt_cancel, 0, wx.LEFT, 10),
+                                   (self.bt_close, 0, wx.LEFT, 10)])
+    def _layout_aperture(self):
+        """
+        Do the layout for aperture related widgets
+        """
+        aperture_name_txt = wx.StaticText(self, -1, "Aperture:")
+        hint_aperture_txt = 'Current available aperture.'
+        aperture_name_txt.SetToolTipString(hint_aperture_txt)
+        self.aperture_cbox = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        hint_aperture_name_txt = 'Name of apertures.'
+        self.aperture_cbox.SetToolTipString(hint_aperture_name_txt)
+
+        self.bt_add_aperture = wx.Button(self, -1, "Add")
+        self.bt_add_aperture.SetToolTipString("Edit data's aperture.")
+        self.bt_add_aperture.Bind(wx.EVT_BUTTON, self.add_aperture)
+        self.bt_edit_aperture = wx.Button(self, -1, "Edit")
+        self.bt_edit_aperture.SetToolTipString("Edit data's aperture.")
+        self.bt_edit_aperture.Bind(wx.EVT_BUTTON, self.edit_aperture)
+        self.bt_remove_aperture = wx.Button(self, -1, "Remove")
+        self.bt_remove_aperture.SetToolTipString("Remove data's aperture.")
+        self.bt_remove_aperture.Bind(wx.EVT_BUTTON, self.remove_aperture)
+
+        self.aperture_button_sizer.AddMany([(aperture_name_txt, 0, wx.LEFT, 15),
+                                     (self.aperture_cbox, 0, wx.LEFT, 5),
+                                     (self.bt_add_aperture, 0, wx.LEFT, 10),
+                                     (self.bt_edit_aperture, 0, wx.LEFT, 5),
+                                     (self.bt_remove_aperture, 0, wx.LEFT, 5)])
+        aperture_hint_txt = 'No aperture is available for this data.'
+        self.aperture_txt = wx.StaticText(self, -1, aperture_hint_txt)
+        self.aperture_hint_sizer.Add(self.aperture_txt, 0, wx.LEFT, 10)
+        self.aperture_sizer.AddMany([(self.aperture_button_sizer,
+                                       0, wx.ALL, 10),
+                                     (self.aperture_hint_sizer, 0, wx.ALL, 10)])
+        self.fill_aperture_combox()
+        self.enable_aperture()
+
+    def _do_layout(self):
+        """
+        Draw the current panel
+        """
+        self._define_structure()
+        self._layout_collimation()
+        self._layout_name()
+        self._layout_length()
+        self._layout_aperture()
+        self._layout_button()
+
+        self.boxsizer_collimation.AddMany([(self.name_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                          (self.length_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                     (self.aperture_sizer, 0,
+                                     wx.EXPAND | wx.ALL, 10)])
+        self.main_sizer.AddMany([(self.collimation_sizer, 0, wx.ALL, 10),
+                                  (self.boxsizer_collimation, 0, wx.ALL, 10),
+                                  (self.button_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def get_current_collimation(self):
+        """
+        """
+        if not self.collimation_cbox.IsEnabled():
+            return None, None, None
+        position = self.collimation_cbox.GetSelection()
+        if position == wx.NOT_FOUND:
+            return None, None, None
+        collimation_name = self.collimation_cbox.GetStringSelection()
+        collimation = self.collimation_cbox.GetClientData(position)
+        return collimation, collimation_name, position
+
+    def fill_collimation_combox(self):
+        """
+        fill the current combobox with the available collimation
+        """
+        if self._collimation is None or self._collimation == []:
+            return
+        for collimation in self._collimation:
+            pos = self.collimation_cbox.Append(str(collimation.name))
+            self.collimation_cbox.SetClientData(pos, collimation)
+            self.collimation_cbox.SetSelection(pos)
+            self.collimation_cbox.SetStringSelection(str(collimation.name))
+
+    def add_collimation(self, event):
+        """
+        Append empty collimation to data's list of collimation
+        """
+
+        if not self.collimation_cbox.IsEnabled():
+            self.collimation_cbox.Enable()
+        collimation = Collimation()
+        self._collimation.append(collimation)
+        position = self.collimation_cbox.Append(str(collimation.name))
+        self.collimation_cbox.SetClientData(position, collimation)
+        self.collimation_cbox.SetSelection(position)
+        self.enable_collimation()
+        self.bt_add_aperture.Enable()
+        self.fill_aperture_combox()
+        self.enable_aperture()
+        self.set_values()
+
+    def remove_collimation(self, event):
+        """
+        Remove collimation to data's list of collimation
+        """
+        if self.collimation_cbox.IsEnabled():
+            if self.collimation_cbox.GetCount() > 0:
+                position = self.collimation_cbox.GetCurrentSelection()
+                collimation = self.collimation_cbox.GetClientData(position)
+                if collimation in self._collimation:
+                    self._collimation.remove(collimation)
+                    self.collimation_cbox.Delete(position)
+                    #set the combo box box the next available item
+                    position = self.collimation_cbox.GetCount()
+                    if position > 0:
+                        position -= 1
+                    self.collimation_cbox.SetSelection(position)
+        if not self._collimation and self.collimation_cbox.GetCount() == 0:
+            self.bt_add_aperture.Disable()
+            self.name_tcl.Clear()
+            self.length_tcl.Clear()
+            self.length_unit_tcl.Clear()
+            self.aperture_cbox.Clear()
+            self.aperture_cbox.Disable()
+        #disable or enable the combo box when necessary
+        self.enable_collimation()
+        self.enable_aperture()
+
+    def on_select_collimation(self, event):
+        """
+        fill the control on the panel according to
+        the current  selected collimation
+        """
+        self.set_values()
+        self.fill_aperture_combox()
+        self.enable_aperture()
+
+    def enable_collimation(self):
+        """
+        Enable /disable widgets related to collimation
+        """
+        if self._collimation is not None and \
+            self.collimation_cbox.GetCount() > 0:
+            self.collimation_cbox.Enable()
+            self.bt_remove_collimation.Enable()
+            n_collimation = self.collimation_cbox.GetCount()
+            collimation_hint_txt = "collimations"
+            collimation_hint_txt += " available: %s " % str(n_collimation)
+            if len(self._collimation) > 0:
+                self.bt_remove_collimation.Enable()
+            else:
+                self.bt_remove_collimation.Disable()
+        else:
+            self.collimation_cbox.Disable()
+            self.bt_remove_collimation.Disable()
+            collimation_hint_txt = 'No collimation is available for this data.'
+        self.collimation_txt.SetLabel(collimation_hint_txt)
+
+
+    def reset_collimation_combobox(self, edited_collimation):
+        """
+        take all edited editor and reset clientdata of collimation combo box
+        """
+        for position in range(self.collimation_cbox.GetCount()):
+            collimation = self.collimation_cbox.GetClientData(position)
+            if collimation == edited_collimation:
+                collimation = edited_collimation
+                self.collimation_cbox.SetString(position, str(collimation.name))
+                self.collimation_cbox.SetClientData(position, collimation)
+                self.collimation_cbox.SetStringSelection(str(collimation.name))
+
+    def add_aperture(self, event):
+        """
+        Append empty aperture to data's list of aperture
+        """
+        collimation, _, _ = self.get_current_collimation()
+        if not self.aperture_cbox.IsEnabled():
+            self.aperture_cbox.Enable()
+        aperture = Aperture()
+        collimation.aperture.append(aperture)
+        position = self.aperture_cbox.Append(str(aperture.name))
+        self.aperture_cbox.SetClientData(position, aperture)
+        self.aperture_cbox.SetSelection(position)
+        self.enable_aperture()
+
+    def edit_aperture(self, event):
+        """
+        Edit the selected aperture
+        """
+        if self._collimation is None or not self.aperture_cbox.IsEnabled():
+            return
+        position = self.aperture_cbox.GetSelection()
+        if position != wx.NOT_FOUND:
+            name = self.aperture_cbox.GetStringSelection()
+            aperture = self.aperture_cbox.GetClientData(position)
+            dlg = ApertureDialog(parent=self, aperture=aperture)
+            dlg.set_manager(self)
+            dlg.ShowModal()
+
+    def remove_aperture(self, event):
+        """
+        Remove aperture to data's list of aperture
+        """
+        if self._collimation is None or not self._collimation:
+            return
+        collimation, _, _ = self.get_current_collimation()
+        if self.aperture_cbox.IsEnabled():
+            if self.aperture_cbox.GetCount() > 1:
+                position = self.aperture_cbox.GetCurrentSelection()
+                aperture = self.aperture_cbox.GetClientData(position)
+                if aperture in collimation.aperture:
+                    collimation.aperture.remove(aperture)
+                    self.aperture_cbox.Delete(position)
+                    #set the combo box box the next available item
+                    position = self.aperture_cbox.GetCount()
+                    if position > 0:
+                        position -= 1
+                    self.aperture_cbox.SetSelection(position)
+
+        #disable or enable the combo box when necessary
+        self.enable_aperture()
+
+    def set_aperture(self, aperture):
+        """
+        set aperture for data
+        """
+        if self._collimation is None or not self._collimation:
+            return
+        collimation, _, _ = self.get_current_collimation()
+        if collimation.aperture:
+            for item in collimation.aperture:
+                if item == aperture:
+                    item = aperture
+                    self.reset_aperture_combobox(edited_aperture=aperture)
+                    return
+
+    def enable_aperture(self):
+        """
+        Enable /disable widgets crelated to aperture
+        """
+        collimation, _, _ = self.get_current_collimation()
+        if  self.aperture_cbox.GetCount() > 0:
+            self.aperture_cbox.Enable()
+            self.bt_edit_aperture.Enable()
+            self.bt_remove_aperture.Enable()
+            n_aperture = self.aperture_cbox.GetCount()
+            aperture_hint_txt = 'apertures available: %s ' % str(n_aperture)
+            if len(collimation.aperture) > 0:
+                self.bt_remove_aperture.Enable()
+            else:
+                self.bt_remove_aperture.Disable()
+        else:
+            self.aperture_cbox.Disable()
+            self.bt_edit_aperture.Disable()
+            self.bt_remove_aperture.Disable()
+            aperture_hint_txt = 'No aperture is available for this data.'
+        self.aperture_txt.SetLabel(aperture_hint_txt)
+
+    def reset_aperture_combobox(self, edited_aperture):
+        """
+        take all edited editor and reset clientdata of aperture combo box
+        """
+        for position in range(self.aperture_cbox.GetCount()):
+            aperture = self.aperture_cbox.GetClientData(position)
+            if aperture == edited_aperture:
+                aperture = edited_aperture
+                self.aperture_cbox.SetString(position, str(aperture.name))
+                self.aperture_cbox.SetClientData(position, aperture)
+                self.aperture_cbox.SetStringSelection(str(aperture.name))
+
+    def fill_aperture_combox(self):
+        """
+        fill the current combobox with the available aperture
+        """
+        self.aperture_cbox.Clear()
+        collimation, _, _ = self.get_current_collimation()
+        if collimation is None or not collimation.aperture:
+            return
+        for aperture in collimation.aperture:
+            pos = self.aperture_cbox.Append(str(aperture.name))
+            self.aperture_cbox.SetClientData(pos, aperture)
+            self.aperture_cbox.SetSelection(pos)
+            self.aperture_cbox.SetStringSelection(str(aperture.name))
+
+    def set_manager(self, manager):
+        """
+        Set manager of this window
+        """
+        self.manager = manager
+
+    def set_values(self):
+        """
+        take the collimation values of the current data and display them
+        through the panel
+        """
+        collimation, _, _ = self.get_current_collimation()
+        if collimation is None:
+            self.bt_add_aperture.Disable()
+            self.length_tcl.SetValue("")
+            self.name_tcl.SetValue("")
+            self.length_unit_tcl.SetValue("")
+            return
+        #Name
+        self.name_tcl.SetValue(str(collimation.name))
+        #length
+        self.length_tcl.SetValue(str(collimation.length))
+        #Length unit
+        self.length_unit_tcl.SetValue(str(collimation.length_unit))
+
+    def get_collimation(self):
+        """
+        return the current collimation
+        """
+        return self._collimation
+
+    def get_notes(self):
+        """
+        return notes
+        """
+        return self._notes
+
+    def on_change_name(self):
+        """
+        Change name
+        """
+        collimation, collimation_name, position = self.get_current_collimation()
+        if collimation is None:
+            return
+        #Change the name of the collimation
+        name = self.name_tcl.GetValue().lstrip().rstrip()
+        if name == "" or name == str(None):
+            name = None
+        if collimation.name != name:
+            self._notes += "Change collimation 's "
+            self._notes += "name from %s to %s \n" % (collimation.name, name)
+            collimation.name = name
+            self.collimation_cbox.SetString(position, str(collimation.name))
+            self.collimation_cbox.SetClientData(position, collimation)
+            self.collimation_cbox.SetStringSelection(str(collimation.name))
+
+    def on_change_length(self):
+        """
+        Change the length
+        """
+        collimation, collimation_name, position = self.get_current_collimation()
+        if collimation is None:
+            return
+        #Change length  
+        length = self.length_tcl.GetValue().lstrip().rstrip()
+        if length == "" or length == str(None):
+            length = None
+            collimation.length = length
+        else:
+            if check_float(self.length_tcl):
+                if collimation.length != float(length):
+                    self._notes += "Change Collimation length from "
+                    self._notes += "%s to %s \n" % (collimation.length, length)
+                    collimation.length = float(length)
+            else:
+                self._notes += "Error: Expected a float for collimation length"
+                self._notes += " won't changes length from "
+                self._notes += "%s to %s" % (collimation.length, length)
+        #change length  unit
+        unit = self.length_unit_tcl.GetValue().lstrip().rstrip()
+        if collimation.length_unit != unit:
+            self._notes += " Change length's unit from "
+            self._notes += "%s to %s" % (collimation.length_unit, unit)
+            collimation.length_unit = unit
+
+    def on_click_apply(self, event):
+        """
+        Apply user values to the collimation
+        """
+        self.on_change_name()
+        self.on_change_length()
+        self.set_values()
+        if self.manager is not None:
+            self.manager.set_collimation(self._collimation, self._notes)
+
+    def on_click_cancel(self, event):
+        """
+        leave the collimation as it is and close
+        """
+        self._collimation = deepcopy(self._reset_collimation)
+        self.set_values()
+        if self.manager is not None:
+            self.manager.set_collimation(self._collimation)
+
+
+if __name__ == "__main__":
+
+    app = wx.App()
+    dlg = CollimationDialog(collimation=[Collimation()])
+    dlg.ShowModal()
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/console.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/console.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/console.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,66 @@
+"""
+Console Module display message of a dialog
+"""
+import wx
+import sys
+from sas.sascalc.dataloader.loader import Loader
+
+_BOX_WIDTH = 60
+CONSOLE_WIDTH = 340
+CONSOLE_HEIGHT = 240
+if sys.platform.count("win32") > 0:
+    _STATICBOX_WIDTH = 450
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 550
+    FONT_VARIANT = 0
+else:
+    _STATICBOX_WIDTH = 480
+    PANEL_WIDTH = 530
+    PANEL_HEIGHT = 560
+    FONT_VARIANT = 1
+
+class ConsoleDialog(wx.Dialog):
+    """
+        Data summary dialog
+    """
+    def __init__(self, parent=None, manager=None, data=None,
+                    title="Data Summary", size=(PANEL_WIDTH, PANEL_HEIGHT)):
+        wx.Dialog.__init__(self, parent=parent, title=title, size=size)
+
+        self.parent = parent
+        self._manager = manager
+        self._data = data
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        self.msg_txt = wx.TextCtrl(self, size=(PANEL_WIDTH - 40,
+                                                PANEL_HEIGHT - 60),
+                                        style=wx.TE_MULTILINE)
+        self.msg_txt.SetEditable(False)
+        self.msg_txt.SetValue('No message available')
+        self.sizer.Add(self.msg_txt, 1, wx.EXPAND | wx.ALL, 10)
+        if self._data is not None:
+            self.set_message(msg=self._data.__str__())
+
+        self.SetSizer(self.sizer)
+
+    def set_manager(self, manager):
+        """
+        Set the manager of this window
+        """
+        self._manager = manager
+
+    def set_message(self, msg=""):
+        """
+        Display the message received
+        """
+        self.msg_txt.SetValue(str(msg))
+
+if __name__ == "__main__":
+
+    app = wx.App()
+    # Instantiate a loader 
+    loader = Loader()
+    # Load data 
+    test_data = loader.load("MAR07232_rest.ASC")
+    dlg = ConsoleDialog(data=test_data)
+    dlg.ShowModal()
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/data_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/data_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/data_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,652 @@
+
+import wx
+import sys
+import os
+from copy import deepcopy
+
+from sas.sascalc.dataloader.loader import Loader
+from sas.sascalc.dataloader.data_info import Data2D
+from detector_editor import DetectorDialog
+from collimation_editor import CollimationDialog
+from console import ConsoleDialog
+
+from sas.sasgui.guiframe.events import StatusEvent
+
+
+_QMIN_DEFAULT = 0.001
+_QMAX_DEFAULT = 0.13
+_NPTS_DEFAULT = 50
+#Control panel width 
+if sys.platform.count("darwin") == 0:
+    PANEL_WIDTH = 500
+    PANEL_HEIGTH = 350
+    FONT_VARIANT = 0
+    _BOX_WIDTH = 51
+    ON_MAC = False
+else:
+    _BOX_WIDTH = 76
+    PANEL_WIDTH = 550
+    PANEL_HEIGTH = 400
+    FONT_VARIANT = 1
+    ON_MAC = True
+
+def load_error(error=None):
+    """
+        Pop up an error message.
+
+        @param error: details error message to be displayed
+    """
+    message = "You had to try this, didn't you?\n\n"
+    message += "The data file you selected could not be loaded.\n"
+    message += "Make sure the content of your file is properly formatted.\n\n"
+
+    if error is not None:
+        message += "When contacting the SasView team,"
+        message += " mention the following:\n%s" % str(error)
+
+    dial = wx.MessageDialog(None, message,
+                            'Error Loading File', wx.OK | wx.ICON_EXCLAMATION)
+    dial.ShowModal()
+
+
+class DataEditorPanel(wx.ScrolledWindow):
+    """
+    :param data: when not empty the class can
+        same information into a dat object
+        and post event containing the changed data object to some other frame
+    """
+    def __init__(self, parent, data=[], *args, **kwds):
+        kwds['name'] = "Data Editor"
+        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGTH)
+        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        self._data = data
+        self._reset_data = deepcopy(data)
+        self.reader = None
+        self._notes = ""
+        self._description = "Edit Data"
+        self._default_save_location = os.getcwd()
+        self._do_layout()
+        self.reset_panel()
+        self.bt_apply.Disable()
+        if data:
+            self.complete_loading(data=data)
+            self.bt_apply.Enable()
+
+    def _define_structure(self):
+        """
+        define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        name_box = wx.StaticBox(self, -1, "Load Data")
+        self.name_sizer = wx.StaticBoxSizer(name_box, wx.HORIZONTAL)
+
+        self.title_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.run_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.instrument_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        edit_box = wx.StaticBox(self, -1, "Edit ")
+        self.edit_sizer = wx.StaticBoxSizer(edit_box, wx.HORIZONTAL)
+
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_name(self):
+        """
+        Do the layout for data name related widgets
+        """
+        #data name [string]
+        data_name_txt = wx.StaticText(self, -1, 'Data : ')
+        self.data_cbox = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.data_cbox, -1, self.on_select_data)
+        hint_data = "Loaded data."
+        self.data_cbox.SetToolTipString(hint_data)
+        id = wx.NewId()
+        self.browse_button = wx.Button(self, id, "Browse")
+        hint_on_browse = "Click on this button to import data in this panel."
+        self.browse_button.SetToolTipString(hint_on_browse)
+        self.Bind(wx.EVT_BUTTON, self.on_click_browse, id=id)
+        self.name_sizer.AddMany([(data_name_txt, 0, wx.LEFT, 15),
+                                       (self.data_cbox, 0, wx.LEFT, 10),
+                                       (self.browse_button, 0, wx.LEFT, 10)])
+
+    def _layout_title(self):
+        """
+        Do the layout for data title related widgets
+        """
+        #title name [string]
+        data_title_txt = wx.StaticText(self, -1, 'Title : ')
+        self.data_title_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
+        self.data_title_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_title)
+        hint_title = "Data's title."
+        self.data_title_tcl.SetToolTipString(hint_title)
+        self.title_sizer.AddMany([(data_title_txt, 0, wx.LEFT, 15),
+                                       (self.data_title_tcl, 0, wx.LEFT, 10)])
+
+    def _layout_run(self):
+        """
+        Do the layout for data run related widgets
+        """
+        data_run_txt = wx.StaticText(self, -1, 'Run : ')
+        data_run_txt.SetToolTipString('')
+        self.data_run_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1),
+                                         style=wx.TE_MULTILINE)
+        hint_run = "Data's run."
+        self.data_run_tcl.SetToolTipString(hint_run)
+        self.run_sizer.AddMany([(data_run_txt, 0, wx.LEFT, 15),
+                                       (self.data_run_tcl, 0, wx.LEFT, 10)])
+
+    def _layout_instrument(self):
+        """
+        Do the layout for instrument related widgets
+        """
+        instrument_txt = wx.StaticText(self, -1, 'Instrument : ')
+        hint_instrument_txt = ''
+        instrument_txt.SetToolTipString(hint_instrument_txt)
+        self.instrument_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH * 5, 20))
+        hint_instrument = "Instrument."
+        self.instrument_tcl.SetToolTipString(hint_instrument)
+        self.instrument_sizer.AddMany([(instrument_txt, 0, wx.LEFT, 15),
+                                (self.instrument_tcl, 0, wx.LEFT, 10)])
+
+    def _layout_editor(self):
+        """
+        Do the layout for sample related widgets
+        """
+        self.detector_rb = wx.RadioButton(self, -1, "Detector",
+                                           style=wx.RB_GROUP)
+        self.sample_rb = wx.RadioButton(self, -1, "Sample")
+        self.source_rb = wx.RadioButton(self, -1, "Source")
+        self.collimation_rb = wx.RadioButton(self, -1, "Collimation")
+
+        self.bt_edit = wx.Button(self, -1, "Edit")
+        self.bt_edit.SetToolTipString("Edit data.")
+        self.bt_edit.Bind(wx.EVT_BUTTON, self.on_edit)
+        self.edit_sizer.AddMany([(self.detector_rb, 0, wx.ALL, 10),
+                        (self.sample_rb, 0, wx.RIGHT | wx.BOTTOM | wx.TOP, 10),
+                        (self.source_rb, 0, wx.RIGHT | wx.BOTTOM | wx.TOP, 10),
+                    (self.collimation_rb, 0, wx.RIGHT | wx.BOTTOM | wx.TOP, 10),
+                    (self.bt_edit, 0,
+                                  wx.RIGHT | wx.BOTTOM | wx.TOP, 10)])
+        self.reset_radiobox()
+
+
+    def _layout_source(self):
+        """
+            Do the layout for source related widgets
+        """
+        source_txt = wx.StaticText(self, -1, 'Source ')
+        hint_source_txt = ''
+        source_txt.SetToolTipString(hint_source_txt)
+        self.bt_edit_source = wx.Button(self, -1, "Edit")
+        self.bt_edit_source.SetToolTipString("Edit data's sample.")
+        self.bt_edit_source.Bind(wx.EVT_BUTTON, self.edit_source)
+        #self.source_sizer.AddMany([(source_txt, 0, wx.ALL, 10),
+        #                        (self.bt_edit_source, 0,
+        #                          wx.RIGHT|wx.BOTTOM|wx.TOP, 10)])
+
+    def _layout_summary(self):
+        """
+            Layout widgets related to data's summary
+        """
+        self.data_summary = wx.TextCtrl(self, -1,
+                                         style=wx.TE_MULTILINE | wx.HSCROLL,
+                                        size=(-1, 200))
+        summary = 'No data info available...'
+        self.data_summary.SetValue(summary)
+        #self.summary_sizer.Add(self.data_summary, 1, wx.EXPAND|wx.ALL, 10)  
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.bt_summary = wx.Button(self, -1, "View", size=(_BOX_WIDTH, -1))
+        self.bt_summary.SetToolTipString("View final changes on data.")
+        self.bt_summary.Bind(wx.EVT_BUTTON, self.on_click_view)
+
+        self.bt_save = wx.Button(self, -1, "Save As", size=(_BOX_WIDTH, -1))
+        self.bt_save.SetToolTipString("Save changes in a file.")
+        self.bt_save.Bind(wx.EVT_BUTTON, self.on_click_save)
+
+        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH, -1))
+        self.bt_apply.SetToolTipString("Save changes into the imported data.")
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+
+        self.bt_reset = wx.Button(self, -1, 'Reset', size=(_BOX_WIDTH, -1))
+        self.bt_reset.Bind(wx.EVT_BUTTON, self.on_click_reset)
+        self.bt_reset.SetToolTipString("Reset data to its initial state.")
+
+        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH, -1))
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this panel.")
+
+        self.button_sizer.AddMany([(self.bt_save, 0, wx.LEFT, 120),
+                                   (self.bt_apply, 0, wx.LEFT, 10),
+                                   (self.bt_reset, 0, wx.LEFT | wx.RIGHT, 10),
+                                   (self.bt_summary, 0, wx.RIGHT, 10),
+                                   (self.bt_close, 0, wx.RIGHT, 10)])
+
+    def _do_layout(self):
+        """
+        Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_title()
+        self._layout_run()
+        self._layout_editor()
+        self._layout_button()
+        self.main_sizer.AddMany([(self.name_sizer, 0, wx.EXPAND | wx.ALL, 10),
+                                (self.title_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                (self.run_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                (self.instrument_sizer, 0,
+                                         wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                (self.edit_sizer, 0,
+                                        wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10),
+                                (self.button_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetScrollbars(20, 20, 25, 65)
+        self.SetAutoLayout(True)
+
+    def fill_data_combox(self):
+        """
+        fill the current combobox with the available data
+        """
+        if not self._data:
+            return
+        self.data_cbox.Clear()
+        for data in self._data:
+            name = data.title
+            if data.run:
+                name = data.run[0]
+            if name.lstrip().rstrip() == "":
+                name = data.filename
+            pos = self.data_cbox.Append(str(name))
+            self.data_cbox.SetClientData(pos, data)
+            self.data_cbox.SetSelection(pos)
+            self.data_cbox.SetStringSelection(str(name))
+
+    def reset_panel(self):
+        """
+        """
+        self.enable_data_cbox()
+        self.data_title_tcl.SetValue("")
+        self.data_run_tcl.SetValue("")
+
+    def on_select_data(self, event=None):
+        """
+        """
+        data, _, _ = self.get_current_data()
+        self.reset_panel()
+        if data is None:
+            return
+        self.data_title_tcl.SetValue(str(data.title))
+        text = ""
+        if data.run:
+            for item in data.run:
+                text += item + "\n"
+        self.data_run_tcl.SetValue(str(text))
+
+    def get_current_data(self):
+        """
+        """
+        position = self.data_cbox.GetSelection()
+        if position == wx.NOT_FOUND:
+            return None, None, None
+        data_name = self.data_cbox.GetStringSelection()
+        data = self.data_cbox.GetClientData(position)
+        return data, data_name, position
+
+    def enable_data_cbox(self):
+        """
+        """
+        if self._data:
+            self.data_cbox.Enable()
+            self.bt_summary.Enable()
+            self.bt_reset.Enable()
+            self.bt_save.Enable()
+            self.bt_edit.Enable()
+        else:
+            self.data_cbox.Disable()
+            self.bt_summary.Disable()
+            self.bt_reset.Disable()
+            self.bt_save.Disable()
+            self.bt_edit.Disable()
+
+    def reset_radiobox(self):
+        """
+        """
+        self.detector_rb.SetValue(True)
+        self.source_rb.SetValue(False)
+        self.sample_rb.SetValue(False)
+        self.collimation_rb.SetValue(False)
+
+    def set_sample(self, sample, notes=None):
+        """
+        set sample for data
+        """
+        data, _, _ = self.get_current_data()
+        if data is None:
+            return
+        data.sample = sample
+        if notes is not None:
+            data.process.append(notes)
+
+    def set_source(self, source, notes=None):
+        """
+        set source for data
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        data.source = source
+        if notes is not None:
+            data.process.append(notes)
+
+    def set_detector(self, detector, notes=None):
+        """
+        set detector for data
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        data.detector = detector
+        if notes is not None:
+            data.process.append(notes)
+
+    def set_collimation(self, collimation, notes=None):
+        """
+        set collimation for data
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        data.collimation = collimation
+        if notes is not None:
+            data.process.append(notes)
+
+    def edit_collimation(self):
+        """
+        Edit the selected collimation
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        dlg = CollimationDialog(collimation=data.collimation)
+        dlg.set_manager(self)
+        dlg.ShowModal()
+
+    def edit_detector(self):
+        """
+        Edit the selected detector
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        dlg = DetectorDialog(detector=data.detector)
+        dlg.set_manager(self)
+        dlg.ShowModal()
+
+    def edit_sample(self):
+        """
+        Open the dialog to edit the sample of the current data
+        """
+        data, _, _ = self.get_current_data()
+        if data is None:
+            return
+        from sample_editor import SampleDialog
+        dlg = SampleDialog(parent=self, sample=data.sample)
+        dlg.set_manager(self)
+        dlg.ShowModal()
+
+    def edit_source(self):
+        """
+        Open the dialog to edit the saource of the current data
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        from source_editor import SourceDialog
+        dlg = SourceDialog(parent=self, source=data.source)
+        dlg.set_manager(self)
+        dlg.ShowModal()
+
+    def choose_data_file(self, location=None):
+        """
+        Open a file dialog to allow loading a file
+        """
+        path = None
+        if location == None:
+            location = os.getcwd()
+
+        l = Loader()
+        cards = l.get_wildcards()
+        wlist = '|'.join(cards)
+
+        dlg = wx.FileDialog(self, "Choose a file", location, "", wlist, wx.OPEN)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            mypath = os.path.basename(path)
+        dlg.Destroy()
+        return path
+
+
+    def complete_loading(self, data=None, filename=''):
+        """
+        Complete the loading and compute the slit size
+        """
+        self.done = True
+        self._data = []
+        if data is None:
+            msg = "Couldn't load data"
+            wx.PostEvent(self.parent.parent, StatusEvent(status=msg,
+                                             info="warning", type='stop'))
+            return
+        if not  data.__class__.__name__ == "list":
+            self._data.append(data)
+            self._reset_data.append(deepcopy(data))
+        else:
+            self._data = deepcopy(data)
+            self._reset_data = deepcopy(data)
+        self.set_values()
+        if self.parent.parent is None:
+            return
+        msg = "Load Complete"
+        wx.PostEvent(self.parent.parent, StatusEvent(status=msg,
+                                                info="info", type='stop'))
+
+    def set_values(self):
+        """
+        take the aperture values of the current data and display them
+        through the panel
+        """
+        if self._data:
+            self.fill_data_combox()
+            self.on_select_data(event=None)
+
+    def get_data(self):
+        """
+        return the current data
+        """
+        return self._data
+
+    def get_notes(self):
+        """
+        return notes
+        """
+        return self._notes
+
+    def on_change_run(self, event=None):
+        """
+        Change run
+        """
+        run = []
+        data, _, _ = self.get_current_data()
+        for i in range(self.data_run_tcl.GetNumberOfLines()):
+            text = self.data_run_tcl.GetLineText(i).lstrip().rstrip()
+            if text != "":
+                run.append(text)
+        if data.run != run:
+            self._notes += "Change data 's "
+            self._notes += "run from %s to %s \n" % (data.run, str(run))
+            data.run = run
+        if event is not None:
+            event.Skip()
+
+    def on_change_title(self, event=None):
+        """
+        Change title
+        """
+        data, _, _ = self.get_current_data()
+        #Change data's name
+        title = self.data_title_tcl.GetValue().lstrip().rstrip()
+
+        if data.title != title:
+            self._notes += "Change data 's "
+            self._notes += "title from %s to %s \n" % (data.title, str(title))
+            data.title = title
+        if event is not None:
+            event.Skip()
+
+    def on_click_browse(self, event):
+        """
+        Open a file dialog to allow the user to select a given file.
+        Display the loaded data if available.
+        """
+        path = self.choose_data_file(location=self._default_save_location)
+        if path is None:
+            return
+        if self.parent.parent is not None:
+            wx.PostEvent(self.parent.parent, StatusEvent(status="Loading...",
+                                        info="info", type="progress"))
+
+        self.done = False
+        self._default_save_location = path
+        try:
+            #Load data
+            from load_thread import DataReader
+            ## If a thread is already started, stop it
+            if self.reader is not None and self.reader.isrunning():
+                self.reader.stop()
+            self.reader = DataReader(path=path,
+                                    completefn=self.complete_loading,
+                                    updatefn=None)
+            self.reader.queue()
+        except:
+            msg = "Data Editor: %s" % (sys.exc_value)
+            load_error(msg)
+            return
+        event.Skip()
+
+    def on_edit(self, event):
+        """
+        """
+        if self.detector_rb.GetValue():
+            self.edit_detector()
+        if self.sample_rb.GetValue():
+            self.edit_sample()
+        if self.source_rb.GetValue():
+            self.edit_source()
+        if self.collimation_rb.GetValue():
+            self.edit_collimation()
+        event.Skip()
+
+    def on_click_apply(self, event):
+        """
+        changes are saved in data object imported to edit
+        """
+        data, _, _ = self.get_current_data()
+        if data is None:
+            return
+        self.on_change_run(event=None)
+        self.on_change_title(event=None)
+        #must post event here
+        event.Skip()
+
+    def on_click_save(self, event):
+        """
+        Save change into a file
+        """
+        if not self._data:
+            return
+        self.on_change_run(event=None)
+        self.on_change_title(event=None)
+        path = None
+        wildcard = "CanSAS 1D files(*.xml)|*.xml"
+        dlg = wx.FileDialog(self, "Choose a file",
+                            self._default_save_location, "", wildcard , wx.SAVE)
+
+        for data in self._data:
+            if issubclass(data.__class__, Data2D):
+                msg = "No conventional writing format for \n\n"
+                msg += "Data2D at this time.\n"
+                dlg = wx.MessageDialog(None, msg, 'Error Loading File',
+                                             wx.OK | wx.ICON_EXCLAMATION)
+                dlg.ShowModal()
+            else:
+                if dlg.ShowModal() == wx.ID_OK:
+                    path = dlg.GetPath()
+                    mypath = os.path.basename(path)
+                    loader = Loader()
+                    format = ".xml"
+                    if os.path.splitext(mypath)[1].lower() == format:
+                        loader.save(path, data, format)
+                    try:
+                        self._default_save_location = os.path.dirname(path)
+                    except:
+                        pass
+                    dlg.Destroy()
+        event.Skip()
+
+    def on_click_view(self, event):
+        """
+        Display data info
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        self.on_change_run(event=None)
+        self.on_change_title(event=None)
+        dlg = ConsoleDialog(data=data)
+        dlg.ShowModal()
+        event.Skip()
+
+    def on_click_reset(self, event):
+        """
+        """
+        data, data_name, position = self.get_current_data()
+        if data is None:
+            return
+        self._data[position] = deepcopy(self._reset_data[position])
+        self.set_values()
+        event.Skip()
+
+    def on_close(self, event):
+        """
+        leave data as it is and close
+        """
+        self.parent.Close()
+        event.Skip()
+
+class DataEditorWindow(wx.Frame):
+    def __init__(self, parent, manager, data=None, *args, **kwds):
+        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGTH)
+        wx.Frame.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self.panel = DataEditorPanel(parent=self, data=data)
+        self.Show()
+
+    def get_data(self):
+        """
+            return the current data
+        """
+        return self.panel.get_data()
+
+if __name__ == "__main__":
+
+    app = wx.App()
+    window = DataEditorWindow(parent=None, data=[], title="Data Editor")
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/data_operator.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/data_operator.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/data_operator.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,986 @@
+"""
+GUI for the data operations panel (sum and multiply)
+"""
+import wx
+import sys
+import time
+import numpy
+from sas.sascalc.dataloader.data_info import Data1D
+from sas.sasgui.plottools.PlotPanel import PlotPanel
+from sas.sasgui.plottools.plottables import Graph
+from sas.sasgui.plottools import transform
+from matplotlib.font_manager import FontProperties
+from sas.sasgui.guiframe.events import StatusEvent
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+#Control panel width 
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    PANEL_WIDTH = 790
+    PANEL_HEIGTH = 370
+    FONT_VARIANT = 0
+    _BOX_WIDTH = 200
+    ON_MAC = False
+else:
+    PANEL_TOP = 60
+    _BOX_WIDTH = 230
+    PANEL_WIDTH = 900
+    PANEL_HEIGTH = 430
+    FONT_VARIANT = 1
+    ON_MAC = True
+
+class DataOperPanel(wx.ScrolledWindow):
+    """
+    """
+    def __init__(self, parent, *args, **kwds):
+        kwds['name'] = "Data Operation"
+        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGTH)
+        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        #sizers etc.
+        self.main_sizer = None
+        self.name_sizer = None
+        self.button_sizer = None
+        self.data_namectr = None
+        self.numberctr = None
+        self.data1_cbox = None
+        self.operator_cbox = None
+        self.data2_cbox = None
+        self.data_title_tcl = None
+        self.out_pic = None
+        self.equal_pic = None
+        self.data1_pic = None
+        self.operator_pic = None
+        self.data2_pic = None
+        self.output = None
+        self._notes = None
+        #text grayed color
+        self.color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
+        #data
+        self._data = self.get_datalist()
+        self._do_layout()
+        self.fill_data_combox()
+        self.fill_oprator_combox()
+        self.Bind(wx.EVT_SET_FOCUS, self.set_panel_on_focus)
+
+    def _define_structure(self):
+        """
+        define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        title = "Data Operation "
+        title += "[ + (add); - (subtract); "
+        title += "* (multiply); / (divide); "
+        title += "| (append) ]"
+        name_box = wx.StaticBox(self, -1, title)
+        self.name_sizer = wx.StaticBoxSizer(name_box, wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_name(self):
+        """
+        Do the layout for data name related widgets
+        """
+        new_data_sizer = wx.BoxSizer(wx.VERTICAL)
+        equal_sizer = wx.BoxSizer(wx.VERTICAL)
+        old_data1_sizer = wx.BoxSizer(wx.VERTICAL)
+        operator_sizer = wx.BoxSizer(wx.VERTICAL)
+        old_data2_sizer = wx.BoxSizer(wx.VERTICAL)
+        data2_hori_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        data_name = wx.StaticText(self, -1, 'Output Data Name')
+        equal_name = wx.StaticText(self, -1, ' =', size=(50, 25))
+        data1_name = wx.StaticText(self, -1, 'Data1')
+        operator_name = wx.StaticText(self, -1, 'Operator')
+        data2_name = wx.StaticText(self, -1, 'Data2 (or Number)')
+        self.data_namectr = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 25), style=wx.TE_PROCESS_ENTER)
+        self.data_namectr.SetToolTipString("Hit 'Enter' key after typing.")
+        self.data_namectr.SetValue(str('MyNewDataName'))
+        self.numberctr = wx.TextCtrl(self, -1, size=(_BOX_WIDTH / 3, 25), style=wx.TE_PROCESS_ENTER)
+        self.numberctr.SetToolTipString("Hit 'Enter' key after typing.")
+        self.numberctr.SetValue(str(1.0))
+        self.data1_cbox = wx.ComboBox(self, -1, size=(_BOX_WIDTH, 25),
+                                      style=wx.CB_READONLY)
+        self.operator_cbox = wx.ComboBox(self, -1, size=(70, 25),
+                                         style=wx.CB_READONLY)
+        operation_tip = "Add: +, Subtract: -, "
+        operation_tip += "Multiply: *, Divide: /, "
+        operation_tip += "Append(Combine): | "
+        self.operator_cbox.SetToolTipString(operation_tip)
+        self.data2_cbox = wx.ComboBox(self, -1, size=(_BOX_WIDTH * 2 / 3, 25),
+                                       style=wx.CB_READONLY)
+
+        self.out_pic = SmallPanel(self, -1, True,
+                                    size=(_BOX_WIDTH, _BOX_WIDTH),
+                                    style=wx.NO_BORDER)
+        self.equal_pic = SmallPanel(self, -1, True, '=',
+                                    size=(50, _BOX_WIDTH),
+                                    style=wx.NO_BORDER)
+        self.data1_pic = SmallPanel(self, -1, True,
+                                    size=(_BOX_WIDTH, _BOX_WIDTH),
+                                    style=wx.NO_BORDER)
+        self.operator_pic = SmallPanel(self, -1, True, '+',
+                                    size=(70, _BOX_WIDTH),
+                                    style=wx.NO_BORDER)
+        self.data2_pic = SmallPanel(self, -1, True,
+                                    size=(_BOX_WIDTH, _BOX_WIDTH),
+                                    style=wx.NO_BORDER)
+        for ax in self.equal_pic.axes:
+            ax.set_frame_on(False)
+        for ax in self.operator_pic.axes:
+            ax.set_frame_on(False)
+
+        new_data_sizer.AddMany([(data_name, 0, wx.LEFT, 3),
+                                       (self.data_namectr, 0, wx.LEFT, 3),
+                                       (self.out_pic, 0, wx.LEFT, 3)])
+        equal_sizer.AddMany([(13, 13), (equal_name, 0, wx.LEFT, 3),
+                                       (self.equal_pic, 0, wx.LEFT, 3)])
+        old_data1_sizer.AddMany([(data1_name, 0, wx.LEFT, 3),
+                                       (self.data1_cbox, 0, wx.LEFT, 3),
+                                       (self.data1_pic, 0, wx.LEFT, 3)])
+        operator_sizer.AddMany([(operator_name, 0, wx.LEFT, 3),
+                                 (self.operator_cbox, 0, wx.LEFT, 3),
+                                 (self.operator_pic, 0, wx.LEFT, 3)])
+        data2_hori_sizer.AddMany([(self.data2_cbox, 0, wx.LEFT, 0),
+                                       (self.numberctr, 0, wx.RIGHT, 0)])
+        old_data2_sizer.AddMany([(data2_name, 0, wx.LEFT, 3),
+                                       (data2_hori_sizer, 0, wx.LEFT, 3),
+                                       (self.data2_pic, 0, wx.LEFT, 3)])
+        self.name_sizer.AddMany([(new_data_sizer, 0, wx.LEFT | wx.TOP, 5),
+                                       (equal_sizer, 0, wx.TOP, 5),
+                                       (old_data1_sizer, 0, wx.TOP, 5),
+                                       (operator_sizer, 0, wx.TOP, 5),
+                                       (old_data2_sizer, 0, wx.TOP, 5)])
+        self.data2_cbox.Show(True)
+
+        self._show_numctrl(self.numberctr, False)
+
+        wx.EVT_TEXT_ENTER(self.data_namectr, -1, self.on_name)
+        wx.EVT_TEXT_ENTER(self.numberctr, -1, self.on_number)
+        wx.EVT_COMBOBOX(self.data1_cbox, -1, self.on_select_data1)
+        wx.EVT_COMBOBOX(self.operator_cbox, -1, self.on_select_operator)
+        wx.EVT_COMBOBOX(self.data2_cbox, -1, self.on_select_data2)
+
+    def _show_numctrl(self, ctrl, enable=True):
+        """
+        Show/Hide on Win
+        Enable/Disable on MAC
+        """
+        if ON_MAC:
+            ctrl.Enable(enable)
+            children = ctrl.GetChildren()
+            if len(children) > 0:
+                ctrl.GetChildren()[0].SetBackGroundColour(self.color)
+            if enable:
+                wx.EVT_TEXT_ENTER(self.numberctr, -1, self.on_number)
+        else:
+            if not ctrl.IsEnabled():
+                ctrl.Enable(True)
+            ctrl.Show(enable)
+
+    def on_name(self, event=None):
+        """
+        On data name typing
+        """
+        if event != None:
+            event.Skip()
+        item = event.GetEventObject()
+        if item.IsEnabled():
+            self._set_textctrl_color(item, 'white')
+        else:
+            self._set_textctrl_color(item, self.color)
+        text = item.GetValue().strip()
+        self._check_newname(text)
+
+    def _check_newname(self, name=None):
+        """
+        Check name ctr strings
+        """
+        self.send_warnings('')
+        msg = ''
+        if name == None:
+            text = self.data_namectr.GetValue().strip()
+        else:
+            text = name
+        state_list = self.get_datalist().values()
+        name_list = []
+        for state in state_list:
+            if state.data == None:
+                theory_list = state.get_theory()
+                theory, _ = theory_list.values()[0]
+                d_name = str(theory.name)
+            else:
+                d_name = str(state.data.name)
+            name_list.append(d_name)
+        if text in name_list:
+            self._set_textctrl_color(self.data_namectr, 'pink')
+            msg = "DataOperation: The name already exists."
+        if len(text) == 0:
+            self._set_textctrl_color(self.data_namectr, 'pink')
+            msg = "DataOperation: Type the data name first."
+        if self._notes:
+            self.send_warnings(msg, 'error')
+        self.name_sizer.Layout()
+        self.Refresh()
+
+    def _set_textctrl_color(self, ctrl, color):
+        """
+        Set TextCtrl color
+        """
+        if ON_MAC:
+            children = ctrl.GetChildren()
+            if len(children) > 0:
+                children[0].SetBackgroundColour(color)
+        else:
+            ctrl.SetBackgroundColour(color)
+        self.name_sizer.Layout()
+
+    def on_number(self, event=None):
+        """
+        On selecting Number for Data2
+        """
+        self.send_warnings('')
+        item = event.GetEventObject()
+        text = item.GetValue().strip()
+        if self.numberctr.IsShown():
+            if self.numberctr.IsEnabled():
+                self._set_textctrl_color(self.numberctr, 'white')
+                try:
+                    val = float(text)
+                    pos = self.data2_cbox.GetCurrentSelection()
+                    self.data2_cbox.SetClientData(pos, val)
+                except:
+                    self._set_textctrl_color(self.numberctr, 'pink')
+                    msg = "DataOperation: Number requires a float number."
+                    self.send_warnings(msg, 'error')
+                    return
+            else:
+                self._set_textctrl_color(self.numberctr, self.color)
+
+        self.put_text_pic(self.data2_pic, content=str(val))
+        self.check_data_inputs()
+        if self.output != None:
+            self.output.name = str(self.data_namectr.GetValue())
+        self.draw_output(self.output)
+        self.Refresh()
+
+    def on_select_data1(self, event=None):
+        """
+        On select data1
+        """
+        self.send_warnings('')
+        item = event.GetEventObject()
+        pos = item.GetCurrentSelection()
+        data = item.GetClientData(pos)
+        if data == None:
+            content = "?"
+            self.put_text_pic(self.data1_pic, content)
+        else:
+            self.data1_pic.add_image(data)
+        self.check_data_inputs()
+        if self.output != None:
+            self.output.name = str(self.data_namectr.GetValue())
+        self.draw_output(self.output)
+
+    def on_select_operator(self, event=None):
+        """
+        On Select an Operator
+        """
+        self.send_warnings('')
+        item = event.GetEventObject()
+        text = item.GetValue().strip()
+        self.put_text_pic(self.operator_pic, content=text)
+        self.check_data_inputs()
+        if self.output != None:
+            self.output.name = str(self.data_namectr.GetValue())
+        self.draw_output(self.output)
+
+    def on_select_data2(self, event=None):
+        """
+        On Selecting Data2
+        """
+        self.send_warnings('')
+        item = event.GetEventObject()
+        text = item.GetValue().strip().lower()
+        self._show_numctrl(self.numberctr, text == 'number')
+        pos = item.GetCurrentSelection()
+        data = item.GetClientData(pos)
+        content = "?"
+        if not (self.numberctr.IsShown() and self.numberctr.IsEnabled()):
+            if data == None:
+                content = "?"
+                self.put_text_pic(self.data2_pic, content)
+            else:
+                self.data2_pic.add_image(data)
+            self.check_data_inputs()
+        else:
+            content = str(self.numberctr.GetValue().strip())
+            try:
+                content = float(content)
+                data = content
+            except:
+                self._set_textctrl_color(self.numberctr, 'pink')
+                content = "?"
+                data = None
+            item.SetClientData(pos, data)
+            if data != None:
+                self.check_data_inputs()
+
+            self.put_text_pic(self.data2_pic, content)
+
+        if self.output != None:
+            self.output.name = str(self.data_namectr.GetValue())
+        self.draw_output(self.output)
+
+    def put_text_pic(self, pic=None, content=''):
+        """
+        Put text to the pic
+        """
+        pic.set_content(content)
+        pic.add_text()
+        pic.draw()
+
+    def check_data_inputs(self):
+        """
+        Check data1 and data2 whether or not they are ready for operation
+        """
+        self._set_textctrl_color(self.data1_cbox, 'white')
+        self._set_textctrl_color(self.data2_cbox, 'white')
+        flag = False
+        pos1 = self.data1_cbox.GetCurrentSelection()
+        data1 = self.data1_cbox.GetClientData(pos1)
+        if data1 == None:
+            self.output = None
+            return flag
+        pos2 = self.data2_cbox.GetCurrentSelection()
+        data2 = self.data2_cbox.GetClientData(pos2)
+
+        if data2 == None:
+            self.output = None
+            return flag
+        if self.numberctr.IsShown():
+            if self.numberctr.IsEnabled():
+                self._set_textctrl_color(self.numberctr, 'white')
+                try:
+                    float(data2)
+                    if self.operator_cbox.GetValue().strip() == '|':
+                        msg = "DataOperation: This operation can not accept "
+                        msg += "a float number."
+                        self.send_warnings(msg, 'error')
+                        self._set_textctrl_color(self.numberctr, 'pink')
+                        self.output = None
+                        return flag
+                except:
+                    msg = "DataOperation: Number requires a float number."
+                    self.send_warnings(msg, 'error')
+                    self._set_textctrl_color(self.numberctr, 'pink')
+                    self.output = None
+                    return flag
+            else:
+                self._set_textctrl_color(self.numberctr, self.color)
+        elif data1.__class__.__name__ != data2.__class__.__name__:
+            self._set_textctrl_color(self.data1_cbox, 'pink')
+            self._set_textctrl_color(self.data2_cbox, 'pink')
+            msg = "DataOperation: Data types must be same."
+            self.send_warnings(msg, 'error')
+            self.output = None
+            return flag
+        try:
+            self.output = self.make_data_out(data1, data2)
+        except:
+            self._check_newname()
+            self._set_textctrl_color(self.data1_cbox, 'pink')
+            self._set_textctrl_color(self.data2_cbox, 'pink')
+            msg = "DataOperation: Data types must be same."
+            self.send_warnings(msg, 'error')
+            self.output = None
+            return flag
+        return True
+
+    def make_data_out(self, data1, data2):
+        """
+        Make a temp. data output set
+        """
+        output = None
+        pos = self.operator_cbox.GetCurrentSelection()
+        operator = self.operator_cbox.GetClientData(pos)
+        try:
+            exec "output = data1 %s data2" % operator
+        except:
+            raise
+        return output
+
+
+    def draw_output(self, output):
+        """
+        Draw output data(temp)
+        """
+        out = self.out_pic
+        if output == None:
+            content = "?"
+            self.put_text_pic(out, content)
+        else:
+            out.add_image(output)
+        wx.CallAfter(self.name_sizer.Layout)
+        self.Layout()
+        self.Refresh()
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH / 2, -1))
+        app_tip = "Generate the Data and send to Data Explorer."
+        self.bt_apply.SetToolTipString(app_tip)
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+
+        self.bt_help = wx.Button(self, -1, "HELP")
+        app_tip = "Get help on Data Operations."
+        self.bt_help.SetToolTipString(app_tip)
+        self.bt_help.Bind(wx.EVT_BUTTON, self.on_help)
+
+        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH / 2, -1))
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this panel.")
+
+        self.button_sizer.AddMany([(PANEL_WIDTH / 2, 25),
+                                   (self.bt_apply, 0, wx.RIGHT, 10),
+                                   (self.bt_help, 0, wx.RIGHT, 10),
+                                   (self.bt_close, 0, wx.RIGHT, 10)])
+
+    def _do_layout(self):
+        """
+        Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_button()
+        self.main_sizer.AddMany([(self.name_sizer, 0, wx.EXPAND | wx.ALL, 10),
+                                (self.button_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetScrollbars(20, 20, 25, 65)
+        self.SetAutoLayout(True)
+
+    def set_panel_on_focus(self, event):
+        """
+        On Focus at this window
+        """
+        if event != None:
+            event.Skip()
+        self._data = self.get_datalist()
+        if ON_MAC:
+            self.fill_data_combox()
+        else:
+            children = self.GetChildren()
+            # update the list only when it is on the top
+            if self.FindFocus() in children:
+                self.fill_data_combox()
+
+    def fill_oprator_combox(self):
+        """
+        fill the current combobox with the operator
+        """
+        operator_list = [' +', ' -', ' *', " /", " |"]
+        for oper in operator_list:
+            pos = self.operator_cbox.Append(str(oper))
+            self.operator_cbox.SetClientData(pos, str(oper.strip()))
+        self.operator_cbox.SetSelection(0)
+
+
+    def fill_data_combox(self):
+        """
+        fill the current combobox with the available data
+        """
+        pos_pre1 = self.data1_cbox.GetCurrentSelection()
+        pos_pre2 = self.data2_cbox.GetCurrentSelection()
+        current1 = self.data1_cbox.GetLabel()
+        current2 = self.data2_cbox.GetLabel()
+        if pos_pre1 < 0:
+            pos_pre1 = 0
+        if pos_pre2 < 0:
+            pos_pre2 = 0
+        self.data1_cbox.Clear()
+        self.data2_cbox.Clear()
+
+        if not self._data:
+            pos = self.data1_cbox.Append('No Data Available')
+            self.data1_cbox.SetSelection(pos)
+            self.data1_cbox.SetClientData(pos, None)
+            pos2 = self.data2_cbox.Append('No Data Available')
+            self.data2_cbox.SetSelection(pos2)
+            self.data2_cbox.SetClientData(pos2, None)
+            return
+        pos1 = self.data1_cbox.Append('Select Data')
+        self.data1_cbox.SetSelection(pos1)
+        self.data1_cbox.SetClientData(pos1, None)
+        pos2 = self.data2_cbox.Append('Select Data')
+        self.data2_cbox.SetSelection(pos2)
+        self.data2_cbox.SetClientData(pos2, None)
+        pos3 = self.data2_cbox.Append('Number')
+        val = None
+        if (self.numberctr.IsShown() and self.numberctr.IsEnabled()):
+            try:
+                val = float(self.numberctr.GetValue())
+            except:
+                val = None
+        self.data2_cbox.SetClientData(pos3, val)
+        dnames = []
+        ids = self._data.keys()
+        for id in ids:
+            if id != None:
+                if self._data[id].data != None:
+                    dnames.append(self._data[id].data.name)
+                else:
+                    theory_list = self._data[id].get_theory()
+                    theory, _ = theory_list.values()[0]
+                    dnames.append(theory.name)
+        ind = numpy.argsort(dnames)
+        if len(ind) > 0:
+            val_list = numpy.array(self._data.values())[ind]
+            for datastate in val_list:
+                data = datastate.data
+                if data != None:
+                    name = data.name
+                    pos1 = self.data1_cbox.Append(str(name))
+                    self.data1_cbox.SetClientData(pos1, data)
+                    pos2 = self.data2_cbox.Append(str(name))
+                    self.data2_cbox.SetClientData(pos2, data)
+                    if str(current1) == str(name):
+                      pos_pre1 = pos1
+                    if str(current2) == str(name):
+                      pos_pre2 = pos2
+                try:
+                    theory_list = datastate.get_theory()
+                    for theory, _ in theory_list.values():
+                        th_name = theory.name
+                        posth1 = self.data1_cbox.Append(str(th_name))
+                        self.data1_cbox.SetClientData(posth1, theory)
+                        posth2 = self.data2_cbox.Append(str(th_name))
+                        self.data2_cbox.SetClientData(posth2, theory)
+                        if str(current1) == str(th_name):
+                            pos_pre1 = posth1
+                        if str(current2) == str(th_name):
+                            pos_pre2 = posth2
+                except:
+                    continue
+        self.data1_cbox.SetSelection(pos_pre1)
+        self.data2_cbox.SetSelection(pos_pre2)
+
+    def get_datalist(self):
+        """
+        """
+        data_manager = self.parent.parent.get_data_manager()
+        if data_manager != None:
+            return  data_manager.get_all_data()
+        else:
+            return {}
+
+    def on_click_apply(self, event):
+        """
+        changes are saved in data object imported to edit
+        """
+        self.send_warnings('')
+        self.data_namectr.SetBackgroundColour('white')
+        state_list = self.get_datalist().values()
+        name = self.data_namectr.GetValue().strip()
+        name_list = []
+        for state in state_list:
+            if state.data == None:
+                theory_list = state.get_theory()
+                theory, _ = theory_list.values()[0]
+                d_name = str(theory.name)
+            else:
+                d_name = str(state.data.name)
+            name_list.append(d_name)
+        if name in name_list:
+            self._set_textctrl_color(self.data_namectr, 'pink')
+            msg = "The Output Data Name already exists...   "
+            wx.MessageBox(msg, 'Error')
+            return
+        if name == '':
+            self._set_textctrl_color(self.data_namectr, 'pink')
+            msg = "Please type the output data name first...   "
+            wx.MessageBox(msg, 'Error')
+            return
+        if self.output == None:
+            msg = "No Output Data has been generated...   "
+            wx.MessageBox(msg, 'Error')
+            return
+        # send data to data manager
+        self.output.name = name
+        self.output.run = "Data Operation"
+        self.output.instrument = "SasView"
+        self.output.id = str(name) + str(time.time())
+        data = {self.output.id :self.output}
+        self.parent.parent.add_data(data)
+        self.name_sizer.Layout()
+        self.Refresh()
+        #must post event here
+        event.Skip()
+
+    def on_help(self, event):
+        """
+        Bring up the Data Operations Panel Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/calculator/data_operator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "Data Operation Help")
+
+    def disconnect_panels(self):
+        """
+        """
+        self.out_pic.connect.disconnect()
+        self.equal_pic.connect.disconnect()
+        self.data1_pic.connect.disconnect()
+        self.operator_pic.connect.disconnect()
+        self.data2_pic.connect.disconnect()
+
+    def on_close(self, event):
+        """
+        leave data as it is and close
+        """
+        self.parent.OnClose()
+
+    def set_plot_unfocus(self):
+        """
+        Unfocus on right click
+        """
+
+    def send_warnings(self, msg='', info='info'):
+        """
+        Send warning to status bar
+        """
+        wx.PostEvent(self.parent.parent, StatusEvent(status=msg, info=info))
+
+class SmallPanel(PlotPanel):
+    """
+    PlotPanel for Quick plot and masking plot
+    """
+    def __init__(self, parent, id= -1, is_number=False, content='?', **kwargs):
+        """
+        """
+        PlotPanel.__init__(self, parent, id=id, **kwargs)
+        self.is_number = is_number
+        self.content = content
+        self.point = None
+        self.position = (0.4, 0.5)
+        self.scale = 'linear'
+        self.prevXtrans = "x"
+        self.prevYtrans = "y"
+        self.viewModel = "--"
+        self.subplot.set_xticks([])
+        self.subplot.set_yticks([])
+        self.add_text()
+        self.figure.subplots_adjust(left=0.1, bottom=0.1)
+
+    def set_content(self, content=''):
+        """
+        Set text content
+        """
+        self.content = str(content)
+
+    def add_toolbar(self):
+        """
+        Add toolbar
+        """
+        # Not implemented
+        pass
+
+    def on_set_focus(self, event):
+        """
+        send to the parenet the current panel on focus
+        """
+        pass
+
+    def add_image(self, plot):
+        """
+        Add Image
+        """
+        self.content = ''
+        self.textList = []
+        self.plots = {}
+        self.clear()
+        self.point = plot
+        try:
+            self.figure.delaxes(self.figure.axes[0])
+            self.subplot = self.figure.add_subplot(111)
+            #self.figure.delaxes(self.figure.axes[1])
+        except:
+            pass
+        try:
+            name = plot.name
+        except:
+            name = plot.filename
+        self.plots[name] = plot
+
+        #init graph
+        self.graph = Graph()
+
+        #add plot
+        self.graph.add(plot)
+        #draw        
+        self.graph.render(self)
+
+        try:
+            self.figure.delaxes(self.figure.axes[1])
+        except:
+            pass
+        self.subplot.figure.canvas.resizing = False
+        self.subplot.tick_params(axis='both', labelsize=9)
+        # Draw zero axis lines
+        self.subplot.axhline(linewidth=1, color='r')
+        self.subplot.axvline(linewidth=1, color='r')
+
+        self.erase_legend()
+        try:
+            # mpl >= 1.1.0
+            self.figure.tight_layout()
+        except:
+            self.figure.subplots_adjust(left=0.1, bottom=0.1)
+        self.subplot.figure.canvas.draw()
+
+    def add_text(self):
+        """
+        Text in the plot
+        """
+        if not self.is_number:
+            return
+
+        self.clear()
+        try:
+            self.figure.delaxes(self.figure.axes[0])
+            self.subplot = self.figure.add_subplot(111)
+            self.figure.delaxes(self.figure.axes[1])
+        except:
+            pass
+        self.subplot.set_xticks([])
+        self.subplot.set_yticks([])
+        label = self.content
+        FONT = FontProperties()
+        xpos, ypos = (0.4, 0.5)
+        font = FONT.copy()
+        font.set_size(14)
+
+        self.textList = []
+        self.subplot.set_xlim((0, 1))
+        self.subplot.set_ylim((0, 1))
+
+        try:
+            if self.content != '?':
+                float(label)
+        except:
+            self.subplot.set_frame_on(False)
+        try:
+            # mpl >= 1.1.0
+            self.figure.tight_layout()
+        except:
+            self.figure.subplots_adjust(left=0.1, bottom=0.1)
+        if len(label) > 0 and xpos > 0 and ypos > 0:
+            new_text = self.subplot.text(str(xpos), str(ypos), str(label),
+                                           fontproperties=font)
+            self.textList.append(new_text)
+
+    def erase_legend(self):
+        """
+        Remove Legend
+        """
+        #for ax in self.axes:
+        self.remove_legend(self.subplot)
+
+    def onMouseMotion(self, event):
+        """
+        Disable dragging 2D image
+        """
+
+    def onWheel(self, event):
+        """
+        """
+
+    def onLeftDown(self, event):
+        """
+        Disables LeftDown
+        """
+
+    def onPick(self, event):
+        """
+        Remove Legend
+        """
+        for ax in self.axes:
+            self.remove_legend(ax)
+
+
+    def draw(self):
+        """
+        Draw
+        """
+        if self.dimension == 3:
+            pass
+        else:
+            self.subplot.figure.canvas.resizing = False
+            self.subplot.tick_params(axis='both', labelsize=9)
+            self.erase_legend()
+            self.subplot.figure.canvas.draw_idle()
+            try:
+                self.figure.delaxes(self.figure.axes[1])
+            except:
+                pass
+
+
+    def onContextMenu(self, event):
+        """
+        Default context menu for a plot panel
+        """
+        id = wx.NewId()
+        slicerpop = wx.Menu()
+        data = self.point
+        if issubclass(data.__class__, Data1D):
+            slicerpop.Append(id, '&Change Scale')
+            wx.EVT_MENU(self, id, self._onProperties)
+        else:
+            slicerpop.Append(id, '&Toggle Linear/Log Scale')
+            wx.EVT_MENU(self, id, self.ontogglescale)
+        try:
+            # mouse event
+            pos_evt = event.GetPosition()
+            pos = self.ScreenToClient(pos_evt)
+        except:
+            # toolbar event
+            pos_x, pos_y = self.toolbar.GetPositionTuple()
+            pos = (pos_x, pos_y + 5)
+        self.PopupMenu(slicerpop, pos)
+
+    def ontogglescale(self, event):
+        """
+        On toggle 2d scale
+        """
+        self._onToggleScale(event)
+        try:
+            # mpl >= 1.1.0
+            self.figure.tight_layout()
+        except:
+            self.figure.subplots_adjust(left=0.1, bottom=0.1)
+        try:
+            self.figure.delaxes(self.figure.axes[1])
+        except:
+            pass
+
+    def _onProperties(self, event):
+        """
+        when clicking on Properties on context menu ,
+        The Property dialog is displayed
+        The user selects a transformation for x or y value and
+        a new plot is displayed
+        """
+        list = []
+        list = self.graph.returnPlottable()
+        if len(list.keys()) > 0:
+            first_item = list.keys()[0]
+            if first_item.x != []:
+                from sas.sasgui.plottools.PropertyDialog import Properties
+                dial = Properties(self, -1, 'Change Scale')
+                # type of view or model used
+                dial.xvalue.Clear()
+                dial.yvalue.Clear()
+                dial.view.Clear()
+                dial.xvalue.Insert("x", 0)
+                dial.xvalue.Insert("log10(x)", 1)
+                dial.yvalue.Insert("y", 0)
+                dial.yvalue.Insert("log10(y)", 1)
+                dial.view.Insert("--", 0)
+                dial.view.Insert("Linear y vs x", 1)
+                dial.setValues(self.prevXtrans, self.prevYtrans, self.viewModel)
+                dial.Update()
+                if dial.ShowModal() == wx.ID_OK:
+                    self.xLabel, self.yLabel, self.viewModel = dial.getValues()
+                    if self.viewModel == "Linear y vs x":
+                        self.xLabel = "x"
+                        self.yLabel = "y"
+                        self.viewModel = "--"
+                        dial.setValues(self.xLabel, self.yLabel, self.viewModel)
+                    self._onEVT_FUNC_PROPERTY()
+                dial.Destroy()
+
+    def _onEVT_FUNC_PROPERTY(self, remove_fit=True):
+        """
+        Receive the x and y transformation from myDialog,
+        Transforms x and y in View
+        and set the scale
+        """
+        list = []
+        list = self.graph.returnPlottable()
+        # Changing the scale might be incompatible with
+        # currently displayed data (for instance, going
+        # from ln to log when all plotted values have
+        # negative natural logs).
+        # Go linear and only change the scale at the end.
+        self.set_xscale("linear")
+        self.set_yscale("linear")
+        _xscale = 'linear'
+        _yscale = 'linear'
+        for item in list:
+            item.setLabel(self.xLabel, self.yLabel)
+            # control axis labels from the panel itself
+            yname, yunits = item.get_yaxis()
+            xname, xunits = item.get_xaxis()
+            # Goes through all possible scales
+            # Goes through all possible scales
+            if(self.xLabel == "x"):
+                item.transformX(transform.toX, transform.errToX)
+                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
+            if(self.xLabel == "log10(x)"):
+                item.transformX(transform.toX_pos, transform.errToX_pos)
+                _xscale = 'log'
+                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
+            if(self.yLabel == "y"):
+                item.transformY(transform.toX, transform.errToX)
+                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
+            if(self.yLabel == "log10(y)"):
+                item.transformY(transform.toX_pos, transform.errToX_pos)
+                _yscale = 'log'
+                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
+            item.transformView()
+        self.prevXtrans = self.xLabel
+        self.prevYtrans = self.yLabel
+        self.set_xscale(_xscale)
+        self.set_yscale(_yscale)
+        self.draw()
+
+class DataOperatorWindow(widget.CHILD_FRAME):
+    def __init__(self, parent, manager, *args, **kwds):
+        kwds["size"] = (PANEL_WIDTH, PANEL_HEIGTH)
+        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self.panel = DataOperPanel(parent=self)
+        wx.EVT_CLOSE(self, self.OnClose)
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show()
+
+    def OnClose(self, event=None):
+        """
+        On close event
+        """
+        if self.manager != None:
+            self.manager.data_operator_frame = None
+        self.panel.disconnect_panels()
+        self.Destroy()
+
+
+if __name__ == "__main__":
+
+    app = wx.App()
+    widget.CHILD_FRAME = wx.Frame
+    window = DataOperatorWindow(parent=None, data=[], title="Data Editor")
+    app.MainLoop()
+
Index: src/sas/sasgui/perspectives/calculator/density_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/density_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/density_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,459 @@
+"""
+This module provide GUI for the mass density calculator
+
+"""
+import wx
+import sys
+from sas.sasgui.guiframe.panel_base import PanelBase
+from wx.lib.scrolledpanel import ScrolledPanel
+from sas.sasgui.guiframe.utils import check_float
+from sas.sasgui.guiframe.events import StatusEvent
+from periodictable import formula as Formula
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+AVOGADRO = 6.02214129e23
+_INPUTS = ['Mass Density', 'Molar Volume']
+_UNITS = ['g/cm^(3)     ', 'cm^(3)/mol ']
+#Density panel size 
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    _STATICBOX_WIDTH = 410
+    _BOX_WIDTH = 200
+    PANEL_SIZE = 440
+    FONT_VARIANT = 0
+else:
+    PANEL_TOP = 60
+    _STATICBOX_WIDTH = 430
+    _BOX_WIDTH = 200
+    PANEL_SIZE = 460
+    FONT_VARIANT = 1
+
+class DensityPanel(ScrolledPanel, PanelBase):
+    """
+    Provides the mass density calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "Mass Density Calculator"
+    ## Name to appear on the window title bar
+    window_caption = "Mass Density Calculator"
+    ## Flag to tell the AUI manager to put this panel in the center pane
+    CENTER_PANE = True
+
+    def __init__(self, parent, base=None, *args, **kwds):
+        """
+        """
+        ScrolledPanel.__init__(self, parent, *args, **kwds)
+        PanelBase.__init__(self)
+        self.SetupScrolling()
+        #Font size 
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        # Object that receive status event
+        self.base = base
+        self.parent = parent
+        # chemeical formula, string
+        self.compound = ''
+        # value of the density/volume, float
+        self.input = None
+        # text controls
+        self.compound_ctl = None
+        self.input_ctl = None
+        self.molar_mass_ctl = None
+        self.output_ctl = None
+        self.ctr_color = self.GetBackgroundColour()
+        # button
+        self.button_calculate = None
+        # list
+        self._input_list = _INPUTS
+        self._input = self._input_list[1]
+        self._output = self._input_list[0]
+        self._unit_list = _UNITS
+        #Draw the panel
+        self._do_layout()
+        self.SetAutoLayout(True)
+        self.Layout()
+
+    def _do_layout(self):
+        """
+        Draw window content
+        """
+        # units
+        unit_density = self._unit_list[0]
+        unit_volume = self._unit_list[1]
+
+        # sizers
+        sizer_input = wx.GridBagSizer(5, 5)
+        sizer_output = wx.GridBagSizer(5, 5)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        self.sizer1 = wx.BoxSizer(wx.HORIZONTAL)
+        self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
+        sizer3 = wx.BoxSizer(wx.HORIZONTAL)
+        vbox = wx.BoxSizer(wx.VERTICAL)
+
+        # inputs
+        inputbox = wx.StaticBox(self, -1, "Inputs")
+        boxsizer1 = wx.StaticBoxSizer(inputbox, wx.VERTICAL)
+        boxsizer1.SetMinSize((_STATICBOX_WIDTH, -1))
+        compound_txt = wx.StaticText(self, -1, 'Molecular Formula ')
+        self.compound_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        self.compound_eg1 = wx.StaticText(self, -1, '     e.g., H2O')
+        self.compound_eg2 = wx.StaticText(self, -1, 'e.g., D2O')
+        self.input_cb = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.input_cb, -1, self.on_select_input)
+        hint_input_name_txt = 'Mass or volume.'
+        self.input_cb.SetToolTipString(hint_input_name_txt)
+        unit_density1 = "     " + unit_density
+        self.input_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        self.unit_input_density = wx.StaticText(self, -1, unit_density1)
+        self.unit_input_volume = wx.StaticText(self, -1, unit_volume)
+        iy = 0
+        ix = 0
+        sizer_input.Add(compound_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_input.Add(self.compound_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_input.Add(self.compound_eg1, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+
+        ix += 1
+        sizer_input.Add(self.compound_eg2, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        self.compound_eg1.Show(False)
+        iy += 1
+        ix = 0
+        sizer_input.Add(self.input_cb, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_input.Add(self.input_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_input.Add(self.unit_input_density, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        self.unit_input_density.Show(False)
+        sizer_input.Add(self.unit_input_volume, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        boxsizer1.Add(sizer_input)
+        self.sizer1.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
+
+        # outputs
+        outputbox = wx.StaticBox(self, -1, "Outputs")
+        boxsizer2 = wx.StaticBoxSizer(outputbox, wx.VERTICAL)
+        boxsizer2.SetMinSize((_STATICBOX_WIDTH, -1))
+
+        molar_mass_txt = wx.StaticText(self, -1, 'Molar Mass ')
+        self.molar_mass_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        self.molar_mass_ctl.SetEditable(False)
+        self.molar_mass_ctl.SetBackgroundColour(self.ctr_color)
+        self.molar_mass_unit1 = wx.StaticText(self, -1, '     g/mol')
+        self.molar_mass_unit2 = wx.StaticText(self, -1, 'g/mol')
+
+        self.output_cb = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.output_cb, -1, self.on_select_output)
+        hint_output_name_txt = 'Mass or volume.'
+        self.output_cb.SetToolTipString(hint_output_name_txt)
+        list = []
+        for item in self._input_list:
+            name = str(item)
+            list.append(name)
+        list.sort()
+        for idx in range(len(list)):
+            self.input_cb.Append(list[idx], idx)
+            self.output_cb.Append(list[idx], idx)
+        self.input_cb.SetStringSelection("Molar Volume")
+        self.output_cb.SetStringSelection("Mass Density")
+        unit_volume = "     " + unit_volume
+        self.output_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        self.output_ctl.SetEditable(False)
+        self.output_ctl.SetBackgroundColour(self.ctr_color)
+        self.unit_output_density = wx.StaticText(self, -1, unit_density)
+        self.unit_output_volume = wx.StaticText(self, -1, unit_volume)
+        iy = 0
+        ix = 0
+        sizer_output.Add(molar_mass_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.molar_mass_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.molar_mass_unit1, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.molar_mass_unit2, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        self.molar_mass_unit1.Show(False)
+        iy += 1
+        ix = 0
+        sizer_output.Add(self.output_cb, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.output_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.unit_output_volume,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.unit_output_density,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+
+        self.unit_output_volume.Show(False)
+        boxsizer2.Add(sizer_output)
+        self.sizer2.Add(boxsizer2, 0, wx.EXPAND | wx.ALL, 10)
+
+        # buttons
+        id = wx.NewId()
+        self.button_calculate = wx.Button(self, id, "Calculate")
+        self.button_calculate.SetToolTipString("Calculate.")
+        self.Bind(wx.EVT_BUTTON, self.calculate, id=id)
+
+        id = wx.NewId()
+        self.button_help = wx.Button(self, id, "HELP")
+        self.button_help.SetToolTipString("Help for density calculator.")
+        self.Bind(wx.EVT_BUTTON, self.on_help, id=id)
+
+        self.button_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.button_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.button_close.SetToolTipString("Close this window.")
+
+        sizer_button.Add((100, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(self.button_calculate, 0,
+                                        wx.RIGHT | wx.ADJUST_MINSIZE, 20)
+        sizer_button.Add(self.button_help, 0,
+                                        wx.RIGHT | wx.ADJUST_MINSIZE, 20)
+        sizer_button.Add(self.button_close, 0,
+                                        wx.RIGHT | wx.ADJUST_MINSIZE, 20)
+        sizer3.Add(sizer_button)
+
+        # layout
+        vbox.Add(self.sizer1)
+        vbox.Add(self.sizer2)
+        vbox.Add(sizer3)
+        vbox.Fit(self)
+        self.SetSizer(vbox)
+
+    def on_select_input(self, event):
+        """
+        On selection of input combobox,
+        update units and output combobox
+        """
+        if event == None:
+            return
+        event.Skip()
+
+        combo = event.GetEventObject()
+        self._input = combo.GetValue()
+        for name in self._input_list:
+            if self._input != name:
+                self._output = name
+                break
+
+        self.set_values()
+
+    def on_select_output(self, event):
+        """
+        On selection of output combobox,
+        update units and input combobox
+        """
+        if event == None:
+            return
+        event.Skip()
+
+        combo = event.GetEventObject()
+        self._output = combo.GetValue()
+        for name in self._input_list:
+            if self._output != name:
+                self._input = name
+                break
+
+        self.set_values()
+
+    def set_values(self):
+        """
+        Sets units and combobox values
+        """
+        input, output = self.get_input()
+        if input is None:
+            return
+        # input
+        self.input_cb.SetValue(str(input))
+        # output
+        self.output_cb.SetValue(str(output))
+        # unit
+        if self._input_list.index(input) == 0:
+            self.molar_mass_unit1.Show(True)
+            self.molar_mass_unit2.Show(False)
+            self.compound_eg1.Show(True)
+            self.compound_eg2.Show(False)
+            self.unit_input_density.Show(True)
+            self.unit_output_volume.Show(True)
+            self.unit_input_volume.Show(False)
+            self.unit_output_density.Show(False)
+        else:
+            self.molar_mass_unit1.Show(False)
+            self.molar_mass_unit2.Show(True)
+            self.compound_eg1.Show(False)
+            self.compound_eg2.Show(True)
+            self.unit_input_volume.Show(True)
+            self.unit_output_density.Show(True)
+            self.unit_input_density.Show(False)
+            self.unit_output_volume.Show(False)
+        # layout    
+        self.clear_outputs()
+        self.sizer1.Layout()
+        self.sizer2.Layout()
+
+    def get_input(self):
+        """
+        Return the current input and output combobox values
+        """
+        return self._input, self._output
+
+    def check_inputs(self):
+        """
+        Check validity user inputs
+        """
+        flag = True
+        msg = ""
+        if check_float(self.input_ctl):
+            self.input = float(self.input_ctl.GetValue())
+        else:
+            flag = False
+            input_type = str(self.input_cb.GetValue())
+            msg += "Error for %s value :expect float" % input_type
+
+        self.compound = self.compound_ctl.GetValue().lstrip().rstrip()
+        if self.compound != "":
+            try :
+                Formula(self.compound)
+                self.compound_ctl.SetBackgroundColour(wx.WHITE)
+                self.compound_ctl.Refresh()
+            except:
+                self.compound_ctl.SetBackgroundColour("pink")
+                self.compound_ctl.Refresh()
+                flag = False
+                msg += "Enter correct formula"
+        else:
+            self.compound_ctl.SetBackgroundColour("pink")
+            self.compound_ctl.Refresh()
+            flag = False
+            msg += "Enter Formula"
+        return flag, msg
+
+
+    def calculate(self, event):
+        """
+        Calculate the mass Density/molar Volume of the molecules
+        """
+        self.clear_outputs()
+        try:
+            #Check validity user inputs
+            flag, msg = self.check_inputs()
+            if self.base is not None and msg.lstrip().rstrip() != "":
+                msg = "Density/Volume Calculator: %s" % str(msg)
+                wx.PostEvent(self.base, StatusEvent(status=msg))
+            if not flag:
+               return
+            #get ready to compute
+            mol_formula = Formula(self.compound)
+            molar_mass = float(mol_formula.molecular_mass) * AVOGADRO
+            output = self._format_number(molar_mass / self.input)
+            self.molar_mass_ctl.SetValue(str(self._format_number(molar_mass)))
+            self.output_ctl.SetValue(str(output))
+        except:
+            if self.base is not None:
+                msg = "Density/Volume Calculator: %s" % (sys.exc_value)
+                wx.PostEvent(self.base, StatusEvent(status=msg))
+        if event is not None:
+            event.Skip()
+
+    def on_help(self, event):
+        """
+        Bring up the density/volume calculator Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/calculator/density_calculator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "Density/Volume Calculator Help")
+
+    def on_close(self, event):
+        """
+        close the window containing this panel
+        """
+        self.parent.Close()
+
+    def clear_outputs(self):
+        """
+        Clear the outputs textctrl
+        """
+        self.molar_mass_ctl.SetValue("")
+        self.output_ctl.SetValue("")
+
+    def _format_number(self, value=None):
+        """
+        Return a float in a standardized, human-readable formatted string
+        """
+        try:
+            value = float(value)
+        except:
+            output = ''
+            return output
+
+        output = "%-12.5f" % value
+        return output.lstrip().rstrip()
+
+class DensityWindow(widget.CHILD_FRAME):
+    """
+    """
+    def __init__(self, parent=None, title="Density/Volume Calculator",
+                  base=None, manager=None,
+                  size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.55), *args, **kwds):
+        """
+        """
+        kwds['title'] = title
+        kwds['size'] = size
+        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
+        """
+        """
+        self.manager = manager
+        self.panel = DensityPanel(self, base=base)
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show(True)
+
+    def on_close(self, event):
+        """
+        On close event
+        """
+        if self.manager != None:
+            self.manager.cal_md_frame = None
+        self.Destroy()
+
+
+class ViewApp(wx.App):
+    """
+    """
+    def OnInit(self):
+        """
+        """
+        widget.CHILD_FRAME = wx.Frame
+        frame = DensityWindow(None, title="Density/Volume Calculator")
+        frame.Show(True)
+        self.SetTopWindow(frame)
+        return True
+
+
+if __name__ == "__main__":
+    app = ViewApp(0)
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/detector_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/detector_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/detector_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,859 @@
+
+import wx
+import sys
+from copy import deepcopy
+from sas.sascalc.dataloader.data_info import Detector
+from sas.sasgui.guiframe.utils import check_float
+
+_BOX_WIDTH = 60
+if sys.platform.count("win32") > 0:
+    _STATICBOX_WIDTH = 465
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 450
+    FONT_VARIANT = 0
+else:
+    _STATICBOX_WIDTH = 480
+    PANEL_WIDTH = 550
+    PANEL_HEIGHT = 480
+    FONT_VARIANT = 1
+
+class DetectorDialog(wx.Dialog):
+    def __init__(self, parent=None, manager=None, detector=None,
+                        title="Detector Editor",
+                        size=(PANEL_WIDTH, PANEL_HEIGHT)):
+        wx.Dialog.__init__(self, parent=parent, id=id, title=title, size=size)
+        try:
+            self.parent = parent
+            self.manager = manager
+            self._detector = detector
+
+            self._reset_detector = deepcopy(detector)
+            self._notes = ""
+            self._description = "Edit Detector"
+            self._do_layout()
+            self.set_values()
+        except:
+            print "error", sys.exc_value
+
+    def _define_structure(self):
+        """
+            define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_detector = wx.StaticBox(self, -1, str("Edit Selected Detector"))
+        self.boxsizer_detector = wx.StaticBoxSizer(self.box_detector,
+                                                    wx.VERTICAL)
+        detector_box = wx.StaticBox(self, -1, "Edit Number of Detectors")
+        self.detector_sizer = wx.StaticBoxSizer(detector_box, wx.VERTICAL)
+        self.detector_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
+        self.detector_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.detector_hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        self.detector_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.detector_hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.instrument_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.distance_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.offset_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.orientation_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.beam_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.pixel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.slit_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_detector(self):
+        """
+            Do the layout for detector related widgets
+        """
+        detector_name_txt = wx.StaticText(self, -1, "Detector:")
+        hint_detector_txt = 'Current available detector.'
+        detector_name_txt.SetToolTipString(hint_detector_txt)
+        self.detector_cbox = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        hint_detector_name_txt = 'Name of detectors.'
+        self.detector_cbox.SetToolTipString(hint_detector_name_txt)
+
+        self.bt_add_detector = wx.Button(self, -1, "Add")
+        self.bt_add_detector.SetToolTipString("Add data's detector.")
+        self.bt_add_detector.Bind(wx.EVT_BUTTON, self.add_detector)
+
+        self.bt_remove_detector = wx.Button(self, -1, "Remove")
+        self.bt_remove_detector.SetToolTipString("Remove data's detector.")
+        self.bt_remove_detector.Bind(wx.EVT_BUTTON, self.remove_detector)
+
+        self.detector_button_sizer.AddMany([(detector_name_txt, 0, wx.LEFT, 15),
+                                     (self.detector_cbox, 0, wx.LEFT, 5),
+                                     (self.bt_add_detector, 0, wx.LEFT, 10),
+                                     (self.bt_remove_detector, 0, wx.LEFT, 5)])
+        detector_hint_txt = 'No detector is available for this data.'
+        self.detector_txt = wx.StaticText(self, -1, detector_hint_txt)
+        self.detector_hint_sizer.Add(self.detector_txt, 0, wx.LEFT, 10)
+        self.detector_sizer.AddMany([(self.detector_button_sizer, 0,
+                                       wx.ALL, 10),
+                                     (self.detector_hint_sizer, 0, wx.ALL, 10)])
+
+        self.fill_detector_combox()
+        self.enable_detector()
+
+    def _layout_instrument_sizer(self):
+        """
+            Do the layout for instrument related widgets
+        """
+        #Instrument
+        instrument_name_txt = wx.StaticText(self, -1, 'Instrument Name : ')
+        self.instrument_name_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH * 5, 20), style=0)
+        self.instrument_sizer.AddMany([(instrument_name_txt, 0,
+                                        wx.LEFT | wx.RIGHT, 10),
+                                    (self.instrument_name_tcl, 0, wx.EXPAND)])
+    def _layout_distance(self):
+        """
+            Do the  layout for distance related widgets
+        """
+        distance_txt = wx.StaticText(self, -1, 'Sample to Detector Distance : ')
+        self.distance_tcl = wx.TextCtrl(self, -1,
+                                         size=(_BOX_WIDTH, 20), style=0)
+        distance_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.distance_unit_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        self.distance_sizer.AddMany([(distance_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.distance_tcl, 0, wx.RIGHT, 10),
+                                     (distance_unit_txt, 0, wx.EXPAND),
+                                     (self.distance_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_offset(self):
+        """
+            Do the  layout for offset related widgets
+        """
+        #Offset
+        offset_txt = wx.StaticText(self, -1, 'Offset:')
+        x_offset_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_offset_tcl = wx.TextCtrl(self, -1,
+                                        size=(_BOX_WIDTH, 20), style=0)
+        y_offset_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_offset_tcl = wx.TextCtrl(self, -1,
+                                         size=(_BOX_WIDTH, 20), style=0)
+        z_offset_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_offset_tcl = wx.TextCtrl(self, -1,
+                                         size=(_BOX_WIDTH, 20), style=0)
+        offset_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.offset_unit_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        self.offset_sizer.AddMany([(offset_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (x_offset_txt, 0, wx.LEFT, 30),
+                                     (self.x_offset_tcl, 0, wx.RIGHT, 10),
+                                     (y_offset_txt, 0, wx.EXPAND),
+                                     (self.y_offset_tcl, 0, wx.RIGHT, 10),
+                                     (z_offset_txt, 0, wx.EXPAND),
+                                     (self.z_offset_tcl, 0, wx.RIGHT, 10),
+                                     (offset_unit_txt, 0, wx.EXPAND),
+                                     (self.offset_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_orientation(self):
+        """
+            Do the  layout for orientation related widgets
+        """
+        #Orientation
+        orientation_txt = wx.StaticText(self, -1, 'Orientation:')
+        x_orientation_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_orientation_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        y_orientation_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_orientation_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        z_orientation_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_orientation_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        orientation_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.orientation_unit_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.orientation_sizer.AddMany([(orientation_txt, 0,
+                                          wx.LEFT | wx.RIGHT, 10),
+                                     (x_orientation_txt, 0, wx.LEFT, 7),
+                                     (self.x_orientation_tcl, 0, wx.RIGHT, 10),
+                                     (y_orientation_txt, 0, wx.EXPAND),
+                                     (self.y_orientation_tcl, 0, wx.RIGHT, 10),
+                                     (z_orientation_txt, 0, wx.EXPAND),
+                                     (self.z_orientation_tcl, 0, wx.RIGHT, 10),
+                                     (orientation_unit_txt, 0, wx.EXPAND),
+                            (self.orientation_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_beam_center(self):
+        """
+            Do the  layout for beam center related widgets
+        """
+        #Beam center
+        beam_center_txt = wx.StaticText(self, -1, 'Beam Center:')
+        x_beam_center_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_beam_center_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        y_beam_center_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_beam_center_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        z_beam_center_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_beam_center_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        beam_center_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.beam_center_unit_tcl = wx.TextCtrl(self, -1,
+                                                 size=(_BOX_WIDTH, 20), style=0)
+        self.beam_sizer.AddMany([(beam_center_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (x_beam_center_txt, 0, wx.EXPAND),
+                                     (self.x_beam_center_tcl, 0, wx.RIGHT, 10),
+                                     (y_beam_center_txt, 0, wx.EXPAND),
+                                     (self.y_beam_center_tcl, 0, wx.RIGHT, 10),
+                                     (z_beam_center_txt, 0, wx.EXPAND),
+                                     (self.z_beam_center_tcl, 0, wx.RIGHT, 10),
+                                     (beam_center_unit_txt, 0, wx.EXPAND),
+                                (self.beam_center_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_pixel_size(self):
+        """
+            Do the  layout for pixel size related widgets
+        """
+        #Pixel Size
+        pixel_size_txt = wx.StaticText(self, -1, 'Pixel Size:')
+        x_pixel_size_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_pixel_size_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        y_pixel_size_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_pixel_size_tcl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, 20), style=0)
+        z_pixel_size_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_pixel_size_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        pixel_size_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.pixel_size_unit_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.pixel_sizer.AddMany([(pixel_size_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (x_pixel_size_txt, 0, wx.LEFT, 17),
+                                     (self.x_pixel_size_tcl, 0, wx.RIGHT, 10),
+                                     (y_pixel_size_txt, 0, wx.EXPAND),
+                                     (self.y_pixel_size_tcl, 0, wx.RIGHT, 10),
+                                     (z_pixel_size_txt, 0, wx.EXPAND),
+                                     (self.z_pixel_size_tcl, 0, wx.RIGHT, 10),
+                                     (pixel_size_unit_txt, 0, wx.EXPAND),
+                                (self.pixel_size_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_slit_length(self):
+        """
+            Do the  layout for slit length related widgets
+        """
+        #slit length
+        slit_length_txt = wx.StaticText(self, -1, 'Slit Length: ')
+        self.slit_length_tcl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, 20), style=0)
+        slit_length_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.slit_length_unit_tcl = wx.TextCtrl(self, -1,
+                                                 size=(_BOX_WIDTH, 20), style=0)
+        self.slit_sizer.AddMany([(slit_length_txt, 0, wx.LEFT, 10),
+                                     (self.slit_length_tcl, 0, wx.RIGHT, 10),
+                                     (slit_length_unit_txt, 0, wx.EXPAND),
+                            (self.slit_length_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, 'Apply')
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+        self.bt_apply.SetToolTipString("Apply current changes to the detector.")
+        self.bt_cancel = wx.Button(self, -1, 'Cancel')
+        self.bt_cancel.SetToolTipString("Cancel current changes.")
+        self.bt_cancel.Bind(wx.EVT_BUTTON, self.on_click_cancel)
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.SetToolTipString("Close window.")
+        self.button_sizer.AddMany([(self.bt_apply, 0, wx.LEFT, 200),
+                                   (self.bt_cancel, 0, wx.LEFT, 10),
+                                   (self.bt_close, 0, wx.LEFT, 10)])
+
+    def _do_layout(self, data=None):
+        """
+             Draw the current panel
+        """
+        self._define_structure()
+        self._layout_detector()
+        self._layout_instrument_sizer()
+        self._layout_distance()
+        self._layout_offset()
+        self._layout_orientation()
+        self._layout_beam_center()
+        self._layout_pixel_size()
+        self._layout_slit_length()
+        self._layout_button()
+
+        self.boxsizer_detector.AddMany([(self.instrument_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.distance_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.offset_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.orientation_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.beam_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.pixel_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.slit_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.main_sizer.AddMany([(self.detector_sizer, 0, wx.ALL, 10),
+                                 (self.boxsizer_detector, 0, wx.ALL, 10),
+                                  (self.button_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def reset_detector(self):
+        """
+            put the default value of the detector back to the current detector
+        """
+        self._detector = deepcopy(self._reset_detector)
+        self.detector_cbox.Clear()
+        self.fill_detector_combox()
+        self.set_values()
+
+    def set_manager(self, manager):
+        """
+            Set manager of this window
+        """
+        self.manager = manager
+
+    def fill_detector_combox(self):
+        """
+            fill the current combobox with the available detector
+        """
+        for detector in self._detector:
+            pos = self.detector_cbox.Append(str(detector.name))
+            self.detector_cbox.SetClientData(pos, detector)
+            self.detector_cbox.SetSelection(pos)
+            self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def reset_detector_combobox(self, edited_detector):
+        """
+            take all edited editor and reset clientdata of detector combo box
+        """
+        for position in range(self.detector_cbox.GetCount()):
+            detector = self.detector_cbox.GetClientData(position)
+            if detector == edited_detector:
+                detector = edited_detector
+                self.detector_cbox.SetString(position, str(detector.name))
+                self.detector_cbox.SetClientData(position, detector)
+                self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def add_detector(self, event):
+        """
+            Append empty detector to data's list of detector
+        """
+
+        if not self.detector_cbox.IsEnabled():
+            self.detector_cbox.Enable()
+        detector = Detector()
+        self._detector.append(detector)
+        position = self.detector_cbox.Append(str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetSelection(position)
+        self.enable_detector()
+        self.set_values()
+
+    def remove_detector(self, event):
+        """
+            Remove detector to data's list of detector
+        """
+        if self.detector_cbox.IsEnabled():
+            if self.detector_cbox.GetCount() > 1:
+                position = self.detector_cbox.GetCurrentSelection()
+                detector = self.detector_cbox.GetClientData(position)
+                if detector in self._detector:
+                    self._detector.remove(detector)
+                    self.detector_cbox.Delete(position)
+                    #set the combo box box the next available item
+                    position = self.detector_cbox.GetCount()
+                    if position > 0:
+                        position -= 1
+                    self.detector_cbox.SetSelection(position)
+                    self.set_values()
+        #disable or enable the combo box when necessary
+        self.enable_detector()
+
+    def enable_detector(self):
+        """
+            Enable /disable widgets crelated to detector
+        """
+        if self._detector is not None and self.detector_cbox.GetCount() > 0:
+            self.detector_cbox.Enable()
+            self.bt_remove_detector.Enable()
+            n_detector = self.detector_cbox.GetCount()
+            detector_hint_txt = 'Detectors available: %s ' % str(n_detector)
+            if len(self._detector) > 1:
+                self.bt_remove_detector.Enable()
+            else:
+                self.bt_remove_detector.Disable()
+        else:
+            self.detector_cbox.Disable()
+            self.bt_remove_detector.Disable()
+            detector_hint_txt = 'No detector is available for this data.'
+        self.detector_txt.SetLabel(detector_hint_txt)
+
+    def set_detector(self, detector):
+        """
+            set detector for data
+        """
+        if self._detector is None:
+            return
+        if self._detector:
+            for item in self._detector:
+                if item == detector:
+                    item = detector
+                    self.reset_detector_combobox(edited_detector=detector)
+                    return
+
+    def get_current_detector(self):
+        """
+        """
+        if not self.detector_cbox.IsEnabled():
+            return None, None, None
+        position = self.detector_cbox.GetSelection()
+        if position == wx.NOT_FOUND:
+            return None, None, None
+        detector_name = self.detector_cbox.GetStringSelection()
+        detector = self.detector_cbox.GetClientData(position)
+        return detector, detector_name, position
+
+    def set_values(self):
+        """
+            take the detector values of the current data and display them
+            through the panel
+        """
+        detector, _, _ = self.get_current_detector()
+        if detector is None:
+            return
+        self.instrument_name_tcl.SetValue(str(detector.name))
+        #Distance
+        distance = detector.distance
+        self.distance_tcl.SetValue(str(distance))
+        self.distance_unit_tcl.SetValue(str(detector.distance_unit))
+        #Offset
+        x, y, z = detector.offset.x, detector.offset.y , detector.offset.z
+        self.x_offset_tcl.SetValue(str(x))
+        self.y_offset_tcl.SetValue(str(y))
+        self.z_offset_tcl.SetValue(str(z))
+        self.offset_unit_tcl.SetValue(str(detector.offset_unit))
+        #Orientation
+        x, y = detector.orientation.x, detector.orientation.y
+        z = detector.orientation.z
+        self.x_orientation_tcl.SetValue(str(x))
+        self.y_orientation_tcl.SetValue(str(y))
+        self.z_orientation_tcl.SetValue(str(z))
+        self.orientation_unit_tcl.SetValue(str(detector.orientation_unit))
+        #Beam center
+        x, y = detector.beam_center.x, detector.beam_center.y
+        z = detector.beam_center.z
+        self.x_beam_center_tcl.SetValue(str(x))
+        self.y_beam_center_tcl.SetValue(str(y))
+        self.z_beam_center_tcl.SetValue(str(z))
+        self.beam_center_unit_tcl.SetValue(str(detector.beam_center_unit))
+        #Pixel size 
+        x, y = detector.pixel_size.x, detector.pixel_size.y
+        z = detector.pixel_size.z
+        self.x_pixel_size_tcl.SetValue(str(x))
+        self.y_pixel_size_tcl.SetValue(str(y))
+        self.z_pixel_size_tcl.SetValue(str(z))
+        self.pixel_size_unit_tcl.SetValue(str(detector.pixel_size_unit))
+        #Slit length
+        slit_length = detector.slit_length
+        self.slit_length_tcl.SetValue(str(detector.slit_length))
+        self.slit_length_unit_tcl.SetValue(str(detector.slit_length_unit))
+
+    def get_detector(self):
+        """
+            return the current detector
+        """
+        return self._detector
+
+    def get_notes(self):
+        """
+            return notes
+        """
+        return self._notes
+
+    def on_change_instrument(self):
+        """
+            Change instrument
+        """
+        detector, detector_name, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change the name of the detector
+        name = self.instrument_name_tcl.GetValue().lstrip().rstrip()
+        if name == "" or name == str(None):
+            name = None
+        if detector_name != name:
+            self._notes += " Instrument's "
+            self._notes += "name from %s to %s \n" % (detector_name, name)
+            detector.name = name
+            self.detector_cbox.SetString(position, str(detector.name))
+            self.detector_cbox.SetClientData(position, detector)
+            self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_change_distance(self):
+        """
+            Change distance of the sample to the detector
+        """
+        detector, _, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change the distance
+        distance = self.distance_tcl.GetValue().lstrip().rstrip()
+        if distance == "" or distance == str(None):
+            distance = None
+            detector.distance = distance
+        else:
+            if check_float(self.distance_tcl):
+                if detector.distance != float(distance):
+                    self._notes += " Change Distance from"
+                    self._notes += " %s to %s \n" % (detector.distance, distance)
+                    detector.distance = float(distance)
+            else:
+                self._notes += "Error: Expected a float for "
+                self._notes += " the distance won't changes "
+                self._notes += "%s to %s" % (detector.distance, distance)
+        #change the distance unit
+        unit = self.distance_unit_tcl.GetValue().lstrip().rstrip()
+        if detector.distance_unit != unit:
+            self._notes += " Change distance's unit from "
+            self._notes += "%s to %s" % (detector.distance_unit, unit)
+
+        self.detector_cbox.SetString(position, str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_change_offset(self):
+        """
+            Change the detector offset
+        """
+        detector, _, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change x coordinate
+        x_offset = self.x_offset_tcl.GetValue().lstrip().rstrip()
+        if x_offset == "" or x_offset == str(None):
+            x_offset = None
+            detector.offset.x = x_offset
+        else:
+            if check_float(self.x_offset_tcl):
+                if detector.offset.x != float(x_offset):
+                    self._notes += "Change x of offset from"
+                    self._notes += " %s to %s \n" % (detector.offset.x,
+                                                      x_offset)
+                    detector.offset.x = float(x_offset)
+            else:
+                self._notes += "Error: Expected a float for the offset 's x "
+                self._notes += "won't changes x offset"
+                self._notes += " from %s to %s" % (detector.offset.x, x_offset)
+        #Change y coordinate
+        y_offset = self.y_offset_tcl.GetValue().lstrip().rstrip()
+        if y_offset == "" or y_offset == str(None):
+            y_offset = None
+            detector.offset.y = y_offset
+        else:
+            if check_float(self.y_offset_tcl):
+                if detector.offset.y != float(y_offset):
+                    self._notes += "Change y of offset from "
+                    self._notes += "%s to %s \n" % (detector.offset.y, y_offset)
+                    detector.offset.y = float(y_offset)
+            else:
+                self._notes += "Error: Expected a float for the offset 's y "
+                self._notes += "won't changes y "
+                self._notes += "offset from %s to %s" % (detector.offset.y,
+                                                         y_offset)
+        #Change z coordinate
+        z_offset = self.z_offset_tcl.GetValue().lstrip().rstrip()
+        if z_offset == "" or z_offset == str(None):
+            z_offset = None
+            detector.offset.z = z_offset
+        else:
+            if check_float(self.z_offset_tcl):
+                if detector.offset.z != float(z_offset):
+                    self._notes += "Change z of offset from"
+                    self._notes += " %s to %s \n" % (detector.offset.z,
+                                                              z_offset)
+                    detector.offset.z = float(z_offset)
+            else:
+                self._notes += "Error: Expected a float for the offset 's x "
+                self._notes += "won't changes z"
+                self._notes += "offset from %s to %s" % (detector.offset.z,
+                                                       z_offset)
+        #change the offset unit
+        unit = self.offset_unit_tcl.GetValue().lstrip().rstrip()
+        if detector.offset_unit != unit:
+            self._notes += " Change Offset's"
+            self._notes += "unit from %s to %s" % (detector.offset_unit, unit)
+
+        self.detector_cbox.SetString(position, str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_change_orientation(self):
+        """
+            Change the detector orientation
+        """
+        detector, _, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change x coordinate
+        x_orientation = self.x_orientation_tcl.GetValue().lstrip().rstrip()
+        if x_orientation == "" or x_orientation == str(None):
+            x_orientation = None
+            detector.orientation.x = x_orientation
+        else:
+            if check_float(self.x_orientation_tcl):
+                if detector.orientation.x != float(x_orientation):
+                    self._notes += "Change x of orientation from "
+                    self._notes += "%s to %s \n" % (detector.orientation.x,
+                                                   x_orientation)
+                    detector.orientation.x = float(x_orientation)
+            else:
+                self._notes += "Error: Expected a float for the orientation "
+                self._notes += "'s x  won't changes x orientation from "
+                self._notes += "%s to %s" % (detector.orientation.x,
+                                              x_orientation)
+        #Change y coordinate
+        y_orientation = self.y_orientation_tcl.GetValue().lstrip().rstrip()
+        if y_orientation == "" or y_orientation == str(None):
+            y_orientation = None
+            detector.orientation.y = y_orientation
+        else:
+            if check_float(self.y_orientation_tcl):
+                if detector.orientation.y != float(y_orientation):
+                    self._notes += "Change y of orientation from "
+                    self._notes += "%s to %s \n" % (detector.orientation.y,
+                                                     y_orientation)
+                    detector.orientation.y = float(y_orientation)
+            else:
+                self._notes += "Error: Expected a float for the orientation's "
+                self._notes += " y won't changes y orientation from "
+                self._notes += "%s to %s" % (detector.orientation.y,
+                                            y_orientation)
+        #Change z coordinate
+        z_orientation = self.z_orientation_tcl.GetValue().lstrip().rstrip()
+        if z_orientation == "" or z_orientation == str(None):
+            z_orientation = None
+            detector.orientation.z = z_orientation
+        else:
+            if check_float(self.z_orientation_tcl):
+                if detector.orientation.z != float(z_orientation):
+                    self._notes += "Change z of offset from "
+                    self._notes += "%s to %s \n" % (detector.orientation.z,
+                                                   z_orientation)
+                    detector.orientation.z = float(z_orientation)
+            else:
+                self._notes += "Error: Expected a float for the orientation 's"
+                self._notes += " x won't changes z orientation from "
+                self._notes += "%s to %s" % (detector.orientation.z,
+                                              z_orientation)
+        #change the orientation unit
+        unit = self.orientation_unit_tcl.GetValue().lstrip().rstrip()
+        if detector.orientation_unit != unit:
+            self._notes += " Change orientation's unit from "
+            self._notes += "%s to %s" % (detector.orientation_unit, unit)
+
+        self.detector_cbox.SetString(position, str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_change_beam_center(self):
+        """
+            Change the detector beam center
+        """
+
+        detector, _, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change x coordinate
+        x_beam_center = self.x_beam_center_tcl.GetValue().lstrip().rstrip()
+        if x_beam_center == "" or x_beam_center == str(None):
+            x_beam_center = None
+            detector.beam_center.x = x_beam_center
+        else:
+            if check_float(self.x_beam_center_tcl):
+                if detector.beam_center.x != float(x_beam_center):
+                    self._notes += "Change x of offset from "
+                    self._notes += "%s to %s \n" % (detector.beam_center.x,
+                                                     x_beam_center)
+                    detector.beam_center.x = float(x_beam_center)
+            else:
+                self._notes += "Error: Expected a float for the beam "
+                self._notes += "center 's x won't changes x beam center from "
+                self._notes += "%s to %s" % (detector.beam_center.x,
+                                            x_beam_center)
+        #Change y coordinate
+        y_beam_center = self.y_beam_center_tcl.GetValue().lstrip().rstrip()
+        if y_beam_center == "" or y_beam_center == str(None):
+            y_beam_center = None
+            detector.beam_center.y = y_beam_center
+        else:
+            if check_float(self.y_beam_center_tcl):
+                if detector.beam_center.y != float(y_beam_center):
+                    self._notes += "Change y of beam center from "
+                    self._notes += "%s to %s \n" % (detector.beam_center.y,
+                                                     y_beam_center)
+                    detector.beam_center.y = float(y_beam_center)
+            else:
+                self._notes += "Error: Expected a float for the beam "
+                self._notes += "center 's y won't changes y beam center from "
+                self._notes += "%s to %s" % (detector.beam_center.y,
+                                              y_beam_center)
+        #Change z coordinate
+        z_beam_center = self.z_beam_center_tcl.GetValue().lstrip().rstrip()
+        if z_beam_center == "" or z_beam_center == str(None):
+            z_beam_center = None
+            detector.beam_center.z = z_beam_center
+        else:
+            if check_float(self.z_beam_center_tcl):
+                if detector.beam_center.z != float(z_beam_center):
+                    self._notes += "Change z of beam center from "
+                    self._notes += "%s to %s \n" % (detector.beam_center.z,
+                                                     z_beam_center)
+                    detector.beam_center.z = float(z_beam_center)
+            else:
+                self._notes += "Error: Expected a float for the offset 's x "
+                self._notes += "won't changes z beam center from "
+                self._notes += "%s to %s" % (detector.beam_center.z,
+                                            z_beam_center)
+        #change the beam center unit
+        unit = self.beam_center_unit_tcl.GetValue().lstrip().rstrip()
+        if detector.beam_center_unit != unit:
+            self._notes += " Change beam center's unit from "
+            self._notes += "%s to %s" % (detector.beam_center_unit, unit)
+
+        self.detector_cbox.SetString(position, str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_change_pixel_size(self):
+        """
+            Change the detector pixel size
+        """
+        detector, _, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change x coordinate
+        x_pixel_size = self.x_pixel_size_tcl.GetValue().lstrip().rstrip()
+        if x_pixel_size == "" or x_pixel_size == str(None):
+            x_pixel_size = None
+        else:
+            if check_float(self.x_pixel_size_tcl):
+                if detector.pixel_size.x != float(x_pixel_size) :
+                    self._notes += "Change x of pixel size from "
+                    self._notes += "%s to %s \n" % (detector.pixel_size.x,
+                                                   x_pixel_size)
+                    detector.pixel_size.x = float(x_pixel_size)
+            else:
+                self._notes += "Error: Expected a float for the pixel"
+                self._notes += " size 's x  won't changes x pixel size from "
+                self._notes += "%s to %s" % (detector.pixel_size.x,
+                                             x_pixel_size)
+        #Change y coordinate
+        y_pixel_size = self.y_pixel_size_tcl.GetValue().lstrip().rstrip()
+        if y_pixel_size == "" or y_pixel_size == str(None):
+            y_pixel_size = None
+            detector.pixel_size.y = y_pixel_size
+        else:
+            if check_float(self.y_pixel_size_tcl):
+                if detector.pixel_size.y != float(y_pixel_size):
+                    self._notes += "Change y of pixel size from "
+                    self._notes += "%s to %s \n" % (detector.pixel_size.y,
+                                                   y_pixel_size)
+                    detector.pixel_size.y = float(y_pixel_size)
+            else:
+                self._notes += "Error: Expected a float for the pixel "
+                self._notes += "size's y  won't changes y pixel size from "
+                self._notes += "%s to %s" % (detector.pixel_size.y,
+                                              y_pixel_size)
+        #Change z coordinate
+        z_pixel_size = self.z_pixel_size_tcl.GetValue().lstrip().rstrip()
+        if z_pixel_size == "" or z_pixel_size == str(None):
+            z_pixel_size = None
+            detector.pixel_size.z = z_pixel_size
+        else:
+            if check_float(self.z_pixel_size_tcl):
+                if detector.pixel_size.z != float(z_pixel_size):
+                    self._notes += "Change z of pixel size from "
+                    self._notes += "%s to %s \n" % (detector.pixel_size.z,
+                                                   z_pixel_size)
+                    detector.pixel_size.z = float(z_pixel_size)
+            else:
+                self._notes += "Error: Expected a float for the offset 's x "
+                self._notes += "won't changes z pixel size from "
+                self._notes += "%s to %s" % (detector.pixel_size.z, z_pixel_size)
+        #change the beam center unit
+        unit = self.pixel_size_unit_tcl.GetValue().lstrip().rstrip()
+        if detector.pixel_size_unit != unit:
+            self._notes += " Change pixel size's unit from "
+            self._notes += "%s to %s" % (detector.pixel_size_unit, unit)
+
+        self.detector_cbox.SetString(position, str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_change_slit_length(self):
+        """
+            Change slit length of the detector
+        """
+        detector, _, position = self.get_current_detector()
+        if detector is None:
+            return
+        #Change the distance
+        slit_length = self.slit_length_tcl.GetValue().lstrip().rstrip()
+        if slit_length == "" or slit_length == str(None):
+            slit_length = None
+            detector.slit_length = slit_length
+        else:
+            if check_float(self.slit_length_tcl):
+                if detector.slit_length != float(slit_length):
+                    self._notes += " Change slit length from"
+                    self._notes += " %s to %s \n" % (detector.slit_length,
+                                                    slit_length)
+                    detector.slit_length = float(slit_length)
+            else:
+                self._notes += "Error: Expected a float"
+                self._notes += " for the slit length won't changes "
+                self._notes += "%s to %s" % (detector.slit_length, slit_length)
+        #change the distance unit
+        unit = self.slit_length_unit_tcl.GetValue().lstrip().rstrip()
+        if detector.slit_length_unit != unit:
+            self._notes += " Change slit length's unit from "
+            self._notes += "%s to %s" % (detector.slit_length_unit_tcl, unit)
+
+        self.detector_cbox.SetString(position, str(detector.name))
+        self.detector_cbox.SetClientData(position, detector)
+        self.detector_cbox.SetStringSelection(str(detector.name))
+
+    def on_click_cancel(self, event):
+        """
+            reset the current detector to its initial values
+        """
+        self.reset_detector()
+        self.set_values()
+        if self.manager is not None:
+             self.manager.set_detector(self._detector)
+
+    def on_click_apply(self, event):
+        """
+            Apply user values to the detector
+        """
+        self.on_change_instrument()
+        self.on_change_distance()
+        self.on_change_instrument()
+        self.on_change_beam_center()
+        self.on_change_offset()
+        self.on_change_orientation()
+        self.on_change_pixel_size()
+        self.on_change_slit_length()
+        for detector in self._detector:
+            self.manager.set_detector(self._detector, self._notes)
+
+if __name__ == "__main__":
+    app = wx.App()
+    test_detector = Detector()
+    dlg = DetectorDialog(detector=[test_detector])
+    dlg.ShowModal()
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/gen_scatter_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/gen_scatter_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/gen_scatter_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,2053 @@
+"""
+Generic Scattering panel.
+This module relies on guiframe manager.
+"""
+
+import wx
+import sys
+import os
+import numpy
+#import math
+import wx.aui as aui
+#import wx.lib.agw.aui as aui
+import logging
+import time
+
+import matplotlib
+matplotlib.interactive(False)
+#Use the WxAgg back end. The Wx one takes too long to render
+matplotlib.use('WXAgg')
+
+#from sas.sasgui.guiframe.gui_manager import MDIFrame
+from sas.sascalc.data_util.calcthread import CalcThread
+from sas.sasgui.guiframe.local_perspectives.plotting.SimplePlot import PlotFrame
+from sas.sasgui.guiframe.dataFitting import Data2D
+from sas.sasgui.guiframe.dataFitting import Data1D
+from sas.sascalc.dataloader.data_info import Detector
+from sas.sascalc.dataloader.data_info import Source
+from sas.sasgui.guiframe.panel_base import PanelBase
+from sas.sasgui.guiframe.utils import format_number
+from sas.sasgui.guiframe.events import StatusEvent
+from sas.sascalc.calculator import sas_gen
+from sas.sasgui.perspectives.calculator.calculator_widgets import OutputTextCtrl
+from sas.sasgui.perspectives.calculator.calculator_widgets import InputTextCtrl
+from wx.lib.scrolledpanel import ScrolledPanel
+from sas.sasgui.perspectives.calculator.load_thread import GenReader
+from sas.sasgui.plottools.arrow3d import Arrow3D
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.events import NewPlotEvent
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+_BOX_WIDTH = 76
+#Slit length panel size 
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    PANEL_WIDTH = 570
+    PANEL_HEIGHT = 370
+    FONT_VARIANT = 0
+else:
+    PANEL_TOP = 60
+    PANEL_WIDTH = 620
+    PANEL_HEIGHT = 370
+    FONT_VARIANT = 1
+_QMAX_DEFAULT = 0.3
+_NPTS_DEFAULT = 50
+_Q1D_MIN = 0.001
+
+def add_icon(parent, frame):
+    """
+    Add icon in the frame
+    """
+    if parent != None:
+        if hasattr(frame, "IsIconized"):
+            if not frame.IsIconized():
+                try:
+                    icon = parent.GetIcon()
+                    frame.SetIcon(icon)
+                except:
+                    pass
+
+def _set_error(panel, item, show_msg=False):
+    """
+    Set_error dialog
+    """
+    if item != None:
+        item.SetBackgroundColour("pink")
+        item.Refresh()
+    if show_msg:
+        msg = "Error: wrong (or out of range) value entered."
+        if panel.parent.parent != None:
+            wx.PostEvent(panel.parent.parent,
+                     StatusEvent(status=msg, info='Error'))
+            panel.SetFocus()
+    return False
+
+
+
+class CalcGen(CalcThread):
+    """
+    Computation
+    """
+    def __init__(self,
+                 id= -1,
+                 input=None,
+                 completefn=None,
+                 updatefn=None,
+                 #elapsed = 0,
+                 yieldtime=0.01,
+                 worktime=0.01):
+        """
+        """
+        CalcThread.__init__(self, completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.starttime = 0
+        self.id = id
+        self.input = input
+        self.update_fn = updatefn
+
+    def compute(self):
+        """
+        excuting computation
+        """
+        #elapsed = time.time() - self.starttime
+        self.starttime = time.time()
+        self.complete(input=self.input, update=self.update_fn)
+
+class SasGenPanel(ScrolledPanel, PanelBase):
+    """
+        Provides the sas gen calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "Generic SAS Calculator"
+    ## Name to appear on the window title bar
+    window_caption = "Generic SAS "
+
+    def __init__(self, parent, *args, **kwds):
+        ScrolledPanel.__init__(self, parent, style=wx.RAISED_BORDER,
+                               *args, **kwds)
+        #kwds['style'] = wx.SUNKEN_BORDER
+        PanelBase.__init__(self)
+        #Font size 
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        self.SetupScrolling()
+        #thread to read data 
+        self.reader = None
+        self.ext = None
+        self.id = 'GenSAS'
+        self.file_name = ''
+        self.time_text = None
+        self.orient_combo = None
+        self.omfreader = sas_gen.OMFReader()
+        self.sldreader = sas_gen.SLDReader()
+        self.pdbreader = sas_gen.PDBReader()
+        self.model = sas_gen.GenSAS()
+        self.param_dic = self.model.params
+        self.parameters = []
+        self.data = None
+        self.scale2d = None
+        self.is_avg = False
+        self.plot_frame = None
+        self.qmax_x = _QMAX_DEFAULT
+        self.npts_x = _NPTS_DEFAULT
+        self.sld_data = None
+        self.graph_num = 1
+        self.default_shape = 'rectangular'
+        # Object that receive status event
+        self.parent = parent
+        self._do_layout()
+        self._create_default_sld_data()
+        self._create_default_2d_data()
+        wx.CallAfter(self._set_sld_data_helper)
+
+    def _define_structure(self):
+        """
+            Define the main sizers building to build this application.
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_source = wx.StaticBox(self, -1, str("SLD Data File"))
+        self.box_parameters = wx.StaticBox(self, -1, str("Input Parameters"))
+        self.box_qrange = wx.StaticBox(self, -1, str("Q Range"))
+        self.boxsizer_source = wx.StaticBoxSizer(self.box_source,
+                                                    wx.VERTICAL)
+        self.boxsizer_parameters = wx.StaticBoxSizer(self.box_parameters,
+                                                    wx.VERTICAL)
+        self.boxsizer_qrange = wx.StaticBoxSizer(self.box_qrange,
+                                                    wx.VERTICAL)
+        self.data_name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.param_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.shape_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.qrange_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_data_name(self):
+        """
+            Fill the sizer containing data's name
+        """
+        data_name_txt = wx.StaticText(self, -1, 'Data: ')
+        self.data_name_tcl = OutputTextCtrl(self, -1,
+                                            size=(_BOX_WIDTH * 4, -1))
+        data_hint = "Loaded data"
+        self.data_name_tcl.SetToolTipString(data_hint)
+        #control that triggers importing data
+        id = wx.NewId()
+        self.browse_button = wx.Button(self, id, "Load")
+        hint_on_browse = "Click to load data into this panel."
+        self.browse_button.SetToolTipString(hint_on_browse)
+        self.Bind(wx.EVT_BUTTON, self.on_load_data, id=id)
+        self.data_name_sizer.AddMany([(data_name_txt, 0, wx.LEFT, 15),
+                                      (self.data_name_tcl, 0, wx.LEFT, 10),
+                                      (self.browse_button, 0, wx.LEFT, 10)])
+    def _layout_param_size(self):
+        """
+            Fill the sizer containing slit size information
+        """
+        self.parameters = []
+        sizer = wx.GridBagSizer(3, 6)
+        model = self.model
+        details = self.model.details
+        params = self.model.params
+        ix = 0
+        iy = 0
+        param_title = wx.StaticText(self, -1, 'Parameter')
+        sizer.Add(param_title, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        value_title = wx.StaticText(self, -1, 'Value')
+        sizer.Add(value_title, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        unit_title = wx.StaticText(self, -1, 'Unit')
+        sizer.Add(unit_title, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        key_list = params.keys()
+        key_list.sort()
+        for param in key_list:
+            iy += 1
+            ix = 0
+            p_name = wx.StaticText(self, -1, param)
+            sizer.Add(p_name, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            ## add parameter value
+            ix += 1
+            value = model.getParam(param)
+            ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH * 2, 20),
+                                style=wx.TE_PROCESS_ENTER)
+            #ctl.SetToolTipString(\
+            #            "Hit 'Enter' after typing to update the plot.")
+            ctl.SetValue(format_number(value, True))
+            sizer.Add(ctl, (iy, ix), (1, 1), wx.EXPAND)
+            ## add unit
+            ix += 1
+            unit = wx.StaticText(self, -1, details[param][0])
+            sizer.Add(unit, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            self.parameters.append([p_name, ctl, unit])
+
+        self.param_sizer.Add(sizer, 0, wx.LEFT, 10)
+
+    def _layout_hint(self):
+        """
+            Fill the sizer containing hint
+        """
+        hint_msg = "We support omf, sld or pdb data files only."
+        hint_msg += "         "
+        if FONT_VARIANT < 1:
+            hint_msg += "Very "
+        hint_msg += "SLOW drawing -->"
+        hint_txt = wx.StaticText(self, -1, hint_msg)
+
+        id = wx.NewId()
+        self.draw_button = wx.Button(self, id, "Arrow Draw")
+        hint_on_draw = "Draw with arrows. Caution: it is a very slow drawing."
+        self.draw_button.SetToolTipString(hint_on_draw)
+        self.draw_button.Bind(wx.EVT_BUTTON, self.sld_draw, id=id)
+
+        self.draw_button.Enable(False)
+        self.hint_sizer.AddMany([(hint_txt, 0, wx.LEFT, 15),
+                                 (self.draw_button, 0, wx.LEFT, 7)])
+
+    def _layout_shape(self):
+        """
+        Fill the shape sizer
+        """
+        label_txt = wx.StaticText(self, -1, "Shape:")
+        self.shape_combo = self._fill_shape_combo()
+        self.shape_sizer.AddMany([(label_txt, 0, wx.LEFT, 15),
+                                (self.shape_combo, 0, wx.LEFT, 5)])
+
+    def _fill_shape_combo(self):
+        """
+        Fill up the shape combo box
+        """
+        shape_combo = wx.ComboBox(self, -1, size=(150, -1),
+                                      style=wx.CB_READONLY)
+        shape_combo.Append('Rectangular')
+        shape_combo.Append('Ellipsoid')
+        shape_combo.Bind(wx.EVT_COMBOBOX, self._on_shape_select)
+        shape_combo.SetSelection(0)
+        return shape_combo
+
+    def _on_shape_select(self, event):
+        """
+        On selecting a shape
+        """
+        event.Skip()
+        label = event.GetEventObject().GetValue().lower()
+        self.default_shape = label
+        self.parent.set_omfpanel_default_shap(self.default_shape)
+        self.parent.set_omfpanel_npts()
+
+    def _fill_orient_combo(self):
+        """
+        Fill up the orientation combo box: used only for atomic structure
+        """
+        orient_combo = wx.ComboBox(self, -1, size=(150, -1),
+                                      style=wx.CB_READONLY)
+        orient_combo.Append('Fixed orientation')
+        orient_combo.Append('Debye full avg.')
+        #orient_combo.Append('Debye sph. sym.')
+
+        orient_combo.Bind(wx.EVT_COMBOBOX, self._on_orient_select)
+        orient_combo.SetSelection(0)
+        return orient_combo
+
+    def _on_orient_select(self, event):
+        """
+        On selecting a orientation
+        """
+        event.Skip()
+        cb = event.GetEventObject()
+        if cb.GetCurrentSelection() == 2:
+            self.is_avg = None
+        else:
+            is_avg = cb.GetCurrentSelection() == 1
+            self.is_avg = is_avg
+        self.model.set_is_avg(self.is_avg)
+        self.set_est_time()
+
+    def _layout_qrange(self):
+        """
+        Fill the sizer containing qrange
+        """
+        sizer = wx.GridBagSizer(2, 3)
+        ix = 0
+        iy = 0
+        #key_list.sort()
+        name = wx.StaticText(self, -1, 'No. of Qx (Qy) bins: ')
+        sizer.Add(name, (iy, ix), (1, 1), \
+                        wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ## add parameter value
+        ix += 1
+        self.npt_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH * 1.5, 20),
+                            style=wx.TE_PROCESS_ENTER)
+        self.npt_ctl.Bind(wx.EVT_TEXT, self._onparamEnter)
+        self.npt_ctl.SetValue(format_number(self.npts_x, True))
+        sizer.Add(self.npt_ctl, (iy, ix), (1, 1), wx.EXPAND)
+        ## add unit
+        ix += 1
+        unit = wx.StaticText(self, -1, '')
+        sizer.Add(unit, (iy, ix), (1, 1), \
+                        wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        name = wx.StaticText(self, -1, 'Qx (Qy) Max: ')
+        sizer.Add(name, (iy, ix), (1, 1), \
+                        wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ## add parameter value
+        ix += 1
+        self.qmax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH * 1.5, 20),
+                            style=wx.TE_PROCESS_ENTER)
+        self.qmax_ctl.Bind(wx.EVT_TEXT, self._onparamEnter)
+        self.qmax_ctl.SetValue(format_number(self.qmax_x, True))
+        sizer.Add(self.qmax_ctl, (iy, ix), (1, 1), wx.EXPAND)
+        ## add unit
+        ix += 1
+        unit = wx.StaticText(self, -1, '[1/A]')
+        sizer.Add(unit, (iy, ix), (1, 1), \
+                        wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        self.qrange_sizer.Add(sizer, 0, wx.LEFT, 10)
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.est_time = '*Estimated Computation time :\n  %s'
+        self.time_text = wx.StaticText(self, -1, self.est_time % str('2 sec'))
+        self.orient_combo = self._fill_orient_combo()
+        self.orient_combo.Show(False)
+
+        self.bt_compute = wx.Button(self, wx.NewId(), 'Compute')
+        self.bt_compute.Bind(wx.EVT_BUTTON, self.on_compute)
+        self.bt_compute.SetToolTipString("Compute 2D Scattering Pattern.")
+
+        self.bt_help = wx.Button(self, wx.NewId(), 'HELP')
+        self.bt_help.Bind(wx.EVT_BUTTON, self.on_help)
+        self.bt_help.SetToolTipString("Help on Scatter Calculator")
+
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_panel_close)
+        self.bt_close.SetToolTipString("Close this window")
+
+        self.button_sizer.AddMany([(self.time_text , 0, wx.LEFT, 20),
+                                   (self.orient_combo , 0, wx.LEFT, 20),
+                                   (self.bt_compute, 0, wx.LEFT, 20),
+                                   (self.bt_help, 0, wx.LEFT, 20),
+                                   (self.bt_close, 0, wx.LEFT, 5)])
+
+    def estimate_ctime(self):
+        """
+        Calculation time estimation
+        """
+        # magic equation: not very accurate
+        factor = 1
+        n_qbins = float(self.npt_ctl.GetValue())
+        n_qbins *= n_qbins
+        n_pixs = float(self.parent.get_npix())
+        if self.is_avg:
+            factor = 6
+            n_pixs *= (n_pixs / 200)
+        x_in = n_qbins * n_pixs / 100000
+        etime = factor + 0.085973 * x_in
+        return int(etime)
+
+    def set_est_time(self):
+        """
+        Set text for est. computation time
+        """
+        unit = 'sec'
+        if self.time_text != None:
+            self.time_text.SetForegroundColour('black')
+            etime = self.estimate_ctime()
+            if etime > 60:
+                etime /= 60
+                unit = 'min'
+                self.time_text.SetForegroundColour('red')
+            time_str = str(etime) + ' ' + unit
+            self.time_text.SetLabel(self.est_time % time_str)
+
+    def _do_layout(self):
+        """
+        Draw window content
+        """
+        self._define_structure()
+        self._layout_data_name()
+        self._layout_param_size()
+        self._layout_qrange()
+        self._layout_hint()
+        self._layout_shape()
+        self._layout_button()
+        self.boxsizer_source.AddMany([(self.data_name_sizer, 0,
+                                        wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.hint_sizer, 0,
+                                        wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.shape_sizer, 0,
+                                        wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.boxsizer_parameters.AddMany([(self.param_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5), ])
+        self.boxsizer_qrange.AddMany([(self.qrange_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5), ])
+        self.main_sizer.AddMany([(self.boxsizer_source, 0,
+                                  wx.EXPAND | wx.ALL, 10),
+                                 (self.boxsizer_parameters, 0,
+                                  wx.EXPAND | wx.ALL, 10),
+                                 (self.boxsizer_qrange, 0,
+                                  wx.EXPAND | wx.ALL, 10),
+                                 (self.button_sizer, 0,
+                                  wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def _create_default_sld_data(self):
+        """
+        Making default sld-data
+        """
+        sld_n_default = 6.97e-06
+        omfdata = sas_gen.OMFData()
+        omf2sld = sas_gen.OMF2SLD()
+        omf2sld.set_data(omfdata, self.default_shape)
+        self.sld_data = omf2sld.output
+        self.sld_data.is_data = False
+        self.sld_data.filename = "Default SLD Profile"
+        self.sld_data.set_sldn(sld_n_default)
+        self.data_name_tcl.SetValue(self.sld_data.filename)
+
+    def choose_data_file(self, location=None):
+        """
+        Choosing a dtata file
+        """
+        path = None
+        filename = ''
+        if location == None:
+            location = os.getcwd()
+
+        exts = "*" + self.omfreader.ext[0]
+        exts += ", *" + self.sldreader.ext[0]
+        exts += ", *" + self.pdbreader.ext[0]
+        all_type = "All GEN files (%s, %s) | %s" % (exts.upper(), exts.lower(),
+                                               exts.lower().replace(',', ';'))
+        wildcard = [all_type]
+        omf_type = self.omfreader.type
+        sld_type = self.sldreader.type
+        pdb_type = self.pdbreader.type
+
+        for type in sld_type:
+            wildcard.append(type)
+        for type in omf_type:
+            wildcard.append(type)
+        for type in pdb_type:
+            wildcard.append(type)
+        wildcard = '|'.join(wildcard)
+        dlg = wx.FileDialog(self, "Choose a file", location,
+                            "", wildcard, wx.OPEN)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            filename = os.path.basename(path)
+        dlg.Destroy()
+        return path
+
+    def on_load_data(self, event):
+        """
+        Open a file dialog to allow the user to select a given file.
+        The user is only allow to load file with extension .omf, .txt, .sld.
+        Display the slit size corresponding to the loaded data.
+        """
+        location = self.parent.get_path()
+        path = self.choose_data_file(location=location)
+        if path is None:
+            return
+
+        self.shape_sizer.ShowItems(False)
+        self.default_shape = 'rectangular'
+        self.parent.set_omfpanel_default_shap(self.default_shape)
+
+        self.parent.set_file_location(os.path.dirname(path))
+        try:
+            #Load data
+            self.ext = os.path.splitext(path)[-1]
+            if self.ext in self.omfreader.ext:
+                loader = self.omfreader
+            elif self.ext in self.sldreader.ext:
+                loader = self.sldreader
+            elif self.ext in self.pdbreader.ext:
+                loader = self.pdbreader
+            else:
+                loader = None
+            if self.reader is not None and self.reader.isrunning():
+                self.reader.stop()
+            self.browse_button.Enable(False)
+            self.browse_button.SetLabel("Loading...")
+            if self.parent.parent is not None:
+                wx.PostEvent(self.parent.parent,
+                                StatusEvent(status="Loading...",
+                                type="progress"))
+            self.reader = GenReader(path=path, loader=loader,
+                                    completefn=self.complete_loading,
+                                    updatefn=self.load_update)
+            self.reader.queue()
+            #self.load_update()
+        except:
+            self.ext = None
+            if self.parent.parent is None:
+                return
+            msg = "Generic SAS Calculator: %s" % (sys.exc_value)
+            wx.PostEvent(self.parent.parent,
+                          StatusEvent(status=msg, type='stop'))
+            self.SetFocus()
+            return
+
+    def load_update(self):
+        """
+        print update on the status bar
+        """
+        if self.parent.parent is None:
+                return
+        if self.reader.isrunning():
+            type = "progress"
+        else:
+            type = "stop"
+        wx.PostEvent(self.parent.parent, StatusEvent(status="",
+                                                  type=type))
+
+    def complete_loading(self, data=None, filename=''):
+        """
+        Complete the loading
+        """
+        #compute the slit size
+        self.browse_button.Enable(True)
+        self.browse_button.SetLabel('Load')
+        try:
+            is_pdbdata = False
+            filename = data.filename
+            self.data_name_tcl.SetValue(str(filename))
+            self.file_name = filename.split('.')[0]
+            self.orient_combo.SetSelection(0)
+            self.is_avg = False
+            if self.ext in self.omfreader.ext:
+                gen = sas_gen.OMF2SLD()
+                gen.set_data(data)
+                #omf_data = data
+                self.sld_data = gen.get_magsld()
+            elif self.ext in self.sldreader.ext:
+                self.sld_data = data
+            elif self.ext in self.pdbreader.ext:
+                self.sld_data = data
+                is_pdbdata = True
+                #omf_data = None
+            else:
+                raise
+            self.orient_combo.Show(is_pdbdata)
+            self.button_sizer.Layout()
+            self._set_sld_data_helper(True)
+        except:
+            if self.parent.parent is None:
+                raise
+            msg = "Loading Error: This file format is not supported "
+            msg += "for GenSAS."
+            wx.PostEvent(self.parent.parent,
+                          StatusEvent(status=msg, type='stop', info='Error'))
+            self.SetFocus()
+            return
+        if self.parent.parent is None:
+            return
+
+        msg = "Load Complete"
+        wx.PostEvent(self.parent.parent, StatusEvent(status=msg, type='stop'))
+        self.SetFocus()
+
+    def _set_sld_data_helper(self, is_draw=False):
+        """
+        Set sld data helper
+        """
+        #is_avg = self.orient_combo.GetCurrentSelection() == 1
+        self.model.set_is_avg(self.is_avg)
+        self.model.set_sld_data(self.sld_data)
+
+        self.draw_button.Enable(self.sld_data != None)
+        wx.CallAfter(self.parent.set_sld_data, self.sld_data)
+        self._update_model_params()
+        if is_draw:
+            wx.CallAfter(self.sld_draw, None, False)
+
+    def _update_model_params(self):
+        """
+        Update the model parameter values
+        """
+        for list in self.parameters:
+            param_name = list[0].GetLabelText()
+            val = str(self.model.params[param_name])
+            list[1].SetValue(val)
+
+    def set_volume_ctl_val(self, val):
+        """
+        Set volume txtctrl value
+        """
+        for list in self.parameters:
+            param_name = list[0].GetLabelText()
+            if param_name.lower() == 'total_volume':
+                list[1].SetValue(val)
+                list[1].Refresh()
+                break
+
+    def _onparamEnter(self, event):
+        """
+        On param enter
+        """
+        try:
+            item = event.GetEventObject()
+            self._check_value()
+            item.Refresh()
+        except:
+            pass
+
+    def sld_draw(self, event=None, has_arrow=True):
+        """
+        Draw 3D sld profile
+        """
+        flag = self.parent.check_omfpanel_inputs()
+        if not flag:
+            infor = 'Error'
+            msg = 'Error: Wrong inputs in the SLD info panel.'
+            # inform msg to wx
+            wx.PostEvent(self.parent.parent,
+                    StatusEvent(status=msg, info=infor))
+            self.SetFocus()
+            return
+
+        self.sld_data = self.parent.get_sld_from_omf()
+        output = self.sld_data
+        #frame_size = wx.Size(470, 470)    
+        self.plot_frame = PlotFrame(self, -1, 'testView')
+        frame = self.plot_frame
+        frame.Show(False)
+        add_icon(self.parent, frame)
+        panel = frame.plotpanel
+        try:
+            # mpl >= 1.0.0
+            ax = panel.figure.gca(projection='3d')
+        except:
+            # mpl < 1.0.0
+            try:
+                from mpl_toolkits.mplot3d import Axes3D
+                ax = Axes3D(panel.figure)
+            except:
+                logging.error("PlotPanel could not import Axes3D")
+                raise
+        panel.dimension = 3
+        graph_title = self._sld_plot_helper(ax, output, has_arrow)
+        # Use y, z axes (in mpl 3d) as z, y axes 
+        # that consistent with our SAS detector coords.
+        ax.set_xlabel('x ($\A%s$)' % output.pos_unit)
+        ax.set_ylabel('z ($\A%s$)' % output.pos_unit)
+        ax.set_zlabel('y ($\A%s$)' % output.pos_unit)
+        panel.subplot.figure.subplots_adjust(left=0.05, right=0.95,
+                                             bottom=0.05, top=0.96)
+        if output.pix_type == 'atom':
+            ax.legend(loc='upper left', prop={'size':10})
+        num_graph = str(self.graph_num)
+        frame.SetTitle('Graph %s: %s' % (num_graph, graph_title))
+        wx.CallAfter(frame.Show, True)
+        self.graph_num += 1
+
+    def _sld_plot_helper(self, ax, output, has_arrow=False):
+        """
+        Actual plot definition happens here
+        :Param ax: axis3d
+        :Param output: sld_data [MagSLD]
+        :Param has_arrow: whether or not draws M vector [bool]
+        """
+        # Set the locals
+        color_dic = {'H':'blue', 'D':'purple', 'N': 'orange',
+                     'O':'red', 'C':'green', 'P':'cyan', 'Other':'k'}
+        marker = ','
+        m_size = 2
+        graph_title = self.file_name
+        graph_title += "   3D SLD Profile "
+        pos_x = output.pos_x
+        pos_y = output.pos_y
+        pos_z = output.pos_z
+        sld_mx = output.sld_mx
+        sld_my = output.sld_my
+        sld_mz = output.sld_mz
+        pix_symbol = output.pix_symbol
+        if output.pix_type == 'atom':
+            marker = 'o'
+            m_size = 3.5
+        sld_tot = (numpy.fabs(sld_mx) + numpy.fabs(sld_my) + \
+                   numpy.fabs(sld_mz) + numpy.fabs(output.sld_n))
+        is_nonzero = sld_tot > 0.0
+        is_zero = sld_tot == 0.0
+        # I. Plot null points
+        if is_zero.any():
+            ax.plot(pos_x[is_zero], pos_z[is_zero], pos_y[is_zero], marker,
+                    c="y", alpha=0.5, markeredgecolor='y', markersize=m_size)
+            pos_x = pos_x[is_nonzero]
+            pos_y = pos_y[is_nonzero]
+            pos_z = pos_z[is_nonzero]
+            sld_mx = sld_mx[is_nonzero]
+            sld_my = sld_my[is_nonzero]
+            sld_mz = sld_mz[is_nonzero]
+            pix_symbol = output.pix_symbol[is_nonzero]
+        # II. Plot selective points in color
+        other_color = numpy.ones(len(pix_symbol), dtype='bool')
+        for key in color_dic.keys():
+            chosen_color = pix_symbol == key
+            if numpy.any(chosen_color):
+                other_color = other_color & (chosen_color != True)
+                color = color_dic[key]
+                ax.plot(pos_x[chosen_color], pos_z[chosen_color],
+                        pos_y[chosen_color], marker, c=color, alpha=0.5,
+                        markeredgecolor=color, markersize=m_size, label=key)
+        # III. Plot All others        
+        if numpy.any(other_color):
+            a_name = ''
+            if output.pix_type == 'atom':
+                # Get atom names not in the list
+                a_names = [symb  for symb in pix_symbol \
+                           if symb not in color_dic.keys()]
+                a_name = a_names[0]
+                for name in a_names:
+                    new_name = ", " + name
+                    if a_name.count(name) == 0:
+                        a_name += new_name
+            # plot in black
+            ax.plot(pos_x[other_color], pos_z[other_color], pos_y[other_color],
+                    marker, c="k", alpha=0.5, markeredgecolor="k",
+                    markersize=m_size, label=a_name)
+        # IV. Draws atomic bond with grey lines if any
+        if output.has_conect:
+            for ind in range(len(output.line_x)):
+                ax.plot(output.line_x[ind], output.line_z[ind],
+                        output.line_y[ind], '-', lw=0.6, c="grey", alpha=0.3)
+        # V. Draws magnetic vectors
+        if has_arrow and len(pos_x) > 0:
+            graph_title += " - Magnetic Vector as Arrow -"
+            panel = self.plot_frame.plotpanel
+            def _draw_arrow(input=None, update=None):
+                """
+                draw magnetic vectors w/arrow
+                """
+                max_mx = max(numpy.fabs(sld_mx))
+                max_my = max(numpy.fabs(sld_my))
+                max_mz = max(numpy.fabs(sld_mz))
+                max_m = max(max_mx, max_my, max_mz)
+                try:
+                    max_step = max(output.xstepsize, output.ystepsize,
+                                   output.zstepsize)
+                except:
+                    max_step = 0
+                if max_step <= 0:
+                    max_step = 5
+                try:
+                    if max_m != 0:
+                        unit_x2 = sld_mx / max_m
+                        unit_y2 = sld_my / max_m
+                        unit_z2 = sld_mz / max_m
+                        # 0.8 is for avoiding the color becomes white=(1,1,1))
+                        color_x = numpy.fabs(unit_x2 * 0.8)
+                        color_y = numpy.fabs(unit_y2 * 0.8)
+                        color_z = numpy.fabs(unit_z2 * 0.8)
+                        x2 = pos_x + unit_x2 * max_step
+                        y2 = pos_y + unit_y2 * max_step
+                        z2 = pos_z + unit_z2 * max_step
+                        x_arrow = numpy.column_stack((pos_x, x2))
+                        y_arrow = numpy.column_stack((pos_y, y2))
+                        z_arrow = numpy.column_stack((pos_z, z2))
+                        colors = numpy.column_stack((color_x, color_y, color_z))
+                        arrows = Arrow3D(panel, x_arrow, z_arrow, y_arrow,
+                                        colors, mutation_scale=10, lw=1,
+                                        arrowstyle="->", alpha=0.5)
+                        ax.add_artist(arrows)
+                except:
+                    pass
+                msg = "Arrow Drawing completed.\n"
+                status_type = 'stop'
+                self._status_info(msg, status_type)
+            msg = "Arrow Drawing is in progress..."
+            status_type = 'progress'
+            self._status_info(msg, status_type)
+            draw_out = CalcGen(input=ax,
+                             completefn=_draw_arrow, updatefn=self._update)
+            draw_out.queue()
+        return graph_title
+
+    def set_input_params(self):
+        """
+        Set model parameters
+        """
+        for list in self.parameters:
+            param_name = list[0].GetLabelText()
+            param_value = float(list[1].GetValue())
+            self.model.setParam(param_name, param_value)
+
+    def on_compute(self, event):
+        """
+        Compute I(qx, qy)
+        """
+        flag = self.parent.check_omfpanel_inputs()
+        if not flag and self.parent.parent != None:
+            infor = 'Error'
+            msg = 'Error: Wrong inputs in the SLD info panel.'
+            # inform msg to wx
+            wx.PostEvent(self.parent.parent,
+                    StatusEvent(status=msg, info=infor))
+            self.SetFocus()
+            return
+        self.sld_data = self.parent.get_sld_from_omf()
+        if self.sld_data == None:
+            if self.parent.parent != None:
+                infor = 'Error'
+                msg = 'Error: No data has been selected.'
+                # inform msg to wx
+                wx.PostEvent(self.parent.parent,
+                        StatusEvent(status=msg, info=infor))
+                self.SetFocus()
+            return
+        flag = self._check_value()
+        if not flag:
+            _set_error(self, None, True)
+            return
+        try:
+            self.model.set_sld_data(self.sld_data)
+            self.set_input_params()
+            if self.is_avg or self.is_avg == None:
+                self._create_default_1d_data()
+                i_out = numpy.zeros(len(self.data.y))
+                inputs = [self.data.x, [], i_out]
+            else:
+                self._create_default_2d_data()
+                i_out = numpy.zeros(len(self.data.data))
+                inputs = [self.data.qx_data, self.data.qy_data, i_out]
+
+            msg = "Computation is in progress..."
+            status_type = 'progress'
+            self._status_info(msg, status_type)
+            cal_out = CalcGen(input=inputs,
+                              completefn=self.complete,
+                              updatefn=self._update)
+            cal_out.queue()
+
+        except:
+            msg = "%s." % sys.exc_value
+            status_type = 'stop'
+            self._status_info(msg, status_type)
+            wx.PostEvent(self.parent.parent,
+                        StatusEvent(status=msg, info='Error'))
+            self.SetFocus()
+
+    def on_help(self, event):
+        """
+        Bring up the General scattering Calculator Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/calculator/sas_calculator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "General Scattering Calculator Help")
+
+    def _check_value(self):
+        """
+        Check input values if float
+        """
+        flag = True
+        self.npt_ctl.SetBackgroundColour("white")
+        self.qmax_ctl.SetBackgroundColour("white")
+        try:
+            npt_val = float(self.npt_ctl.GetValue())
+            if npt_val < 2 or npt_val > 1000:
+                raise
+            self.npt_ctl.SetValue(str(int(npt_val)))
+            self.set_est_time()
+        except:
+            flag = _set_error(self, self.npt_ctl)
+        try:
+            qmax_val = float(self.qmax_ctl.GetValue())
+            if qmax_val <= 0 or qmax_val > 1000:
+                raise
+        except:
+            flag = _set_error(self, self.qmax_ctl)
+        for list in self.parameters:
+            list[1].SetBackgroundColour("white")
+            param_name = list[0].GetLabelText()
+            try:
+                param_val = float(list[1].GetValue())
+                if param_name.count('frac') > 0:
+                    if param_val < 0 or param_val > 1:
+                       raise
+            except:
+                flag = _set_error(self, list[1])
+        return flag
+
+    def _status_info(self, msg='', type="update"):
+        """
+        Status msg
+        """
+        if type == "stop":
+            label = "Compute"
+            able = True
+        else:
+            label = "Wait..."
+            able = False
+        self.bt_compute.Enable(able)
+        self.bt_compute.SetLabel(label)
+        self.bt_compute.SetToolTipString(label)
+        if self.parent.parent != None:
+            wx.PostEvent(self.parent.parent,
+                             StatusEvent(status=msg, type=type))
+
+    def _update(self, time=None):
+        """
+        Update the progress bar
+        """
+        if self.parent.parent == None:
+            return
+        type = "progress"
+        msg = "Please wait. Computing... (Note: Window may look frozen.)"
+        wx.PostEvent(self.parent.parent, StatusEvent(status=msg,
+                                                  type=type))
+
+    def complete(self, input, update=None):
+        """
+        Gen compute complete function
+        :Param input: input list [qx_data, qy_data, i_out]
+        """
+        out = numpy.empty(0)
+        #s = time.time()
+        for ind in range(len(input[0])):
+            if self.is_avg:
+                if ind % 1 == 0 and update != None:
+                    update()
+                    time.sleep(0.1)
+                inputi = [input[0][ind:ind + 1], [], input[2][ind:ind + 1]]
+                outi = self.model.run(inputi)
+                out = numpy.append(out, outi)
+            else:
+                if ind % 50 == 0  and update != None:
+                    update()
+                    time.sleep(0.001)
+                inputi = [input[0][ind:ind + 1], input[1][ind:ind + 1],
+                          input[2][ind:ind + 1]]
+                outi = self.model.runXY(inputi)
+                out = numpy.append(out, outi)
+        #print time.time() - s
+        if self.is_avg or self.is_avg == None:
+            self._draw1D(out)
+        else:
+            #out = self.model.runXY(input)
+            self._draw2D(out)
+
+        msg = "Gen computation completed.\n"
+        status_type = 'stop'
+        self._status_info(msg, status_type)
+
+    def _create_default_2d_data(self):
+        """
+        Create 2D data by default
+        Only when the page is on theory mode.
+        :warning: This data is never plotted.
+        """
+        self.qmax_x = float(self.qmax_ctl.GetValue())
+        self.npts_x = int(float(self.npt_ctl.GetValue()))
+        self.data = Data2D()
+        qmax = self.qmax_x #/ numpy.sqrt(2)
+        self.data.xaxis('\\rm{Q_{x}}', '\AA^{-1}')
+        self.data.yaxis('\\rm{Q_{y}}', '\AA^{-1}')
+        self.data.is_data = False
+        self.data.id = str(self.uid) + " GenData"
+        self.data.group_id = str(self.uid) + " Model2D"
+        ## Default values
+        self.data.detector.append(Detector())
+        index = len(self.data.detector) - 1
+        self.data.detector[index].distance = 8000   # mm
+        self.data.source.wavelength = 6             # A
+        self.data.detector[index].pixel_size.x = 5  # mm
+        self.data.detector[index].pixel_size.y = 5  # mm
+        self.data.detector[index].beam_center.x = qmax
+        self.data.detector[index].beam_center.y = qmax
+        xmax = qmax
+        xmin = -qmax
+        ymax = qmax
+        ymin = -qmax
+        qstep = self.npts_x
+
+        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
+        y = numpy.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
+        ## use data info instead
+        new_x = numpy.tile(x, (len(y), 1))
+        new_y = numpy.tile(y, (len(x), 1))
+        new_y = new_y.swapaxes(0, 1)
+        # all data reuire now in 1d array
+        qx_data = new_x.flatten()
+        qy_data = new_y.flatten()
+        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
+        # set all True (standing for unmasked) as default
+        mask = numpy.ones(len(qx_data), dtype=bool)
+        # store x and y bin centers in q space
+        x_bins = x
+        y_bins = y
+        self.data.source = Source()
+        self.data.data = numpy.ones(len(mask))
+        self.data.err_data = numpy.ones(len(mask))
+        self.data.qx_data = qx_data
+        self.data.qy_data = qy_data
+        self.data.q_data = q_data
+        self.data.mask = mask
+        self.data.x_bins = x_bins
+        self.data.y_bins = y_bins
+        # max and min taking account of the bin sizes
+        self.data.xmin = xmin
+        self.data.xmax = xmax
+        self.data.ymin = ymin
+        self.data.ymax = ymax
+
+    def _create_default_1d_data(self):
+        """
+        Create 2D data by default
+        Only when the page is on theory mode.
+        :warning: This data is never plotted.
+                    residuals.x = data_copy.x[index]
+            residuals.dy = numpy.ones(len(residuals.y))
+            residuals.dx = None
+            residuals.dxl = None
+            residuals.dxw = None
+        """
+        self.qmax_x = float(self.qmax_ctl.GetValue())
+        self.npts_x = int(float(self.npt_ctl.GetValue()))
+        qmax = self.qmax_x #/ numpy.sqrt(2)
+        ## Default values
+        xmax = qmax
+        xmin = qmax * _Q1D_MIN
+        qstep = self.npts_x
+        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
+        # store x and y bin centers in q space
+        #self.data.source = Source()
+        y = numpy.ones(len(x))
+        dy = numpy.zeros(len(x))
+        dx = numpy.zeros(len(x))
+        self.data = Data1D(x=x, y=y)
+        self.data.dx = dx
+        self.data.dy = dy
+
+    def _draw1D(self, y_out):
+        """
+        Complete get the result of modelthread and create model 2D
+        that can be plot.
+        """
+        page_id = self.id
+        data = self.data
+
+        model = self.model
+        state = None
+
+        new_plot = Data1D(x=data.x, y=y_out)
+        new_plot.dx = data.dx
+        new_plot.dy = data.dy
+        new_plot.xaxis('\\rm{Q_{x}}', '\AA^{-1}')
+        new_plot.yaxis('\\rm{Intensity}', 'cm^{-1}')
+        new_plot.is_data = False
+        new_plot.id = str(self.uid) + " GenData1D"
+        new_plot.group_id = str(self.uid) + " Model1D"
+        new_plot.name = model.name + '1d'
+        new_plot.title = "Generic model1D "
+        new_plot.id = str(page_id) + ': ' + self.file_name \
+                        + ' #%s' % str(self.graph_num) + "_1D"
+        new_plot.group_id = str(page_id) + " Model1D" + \
+                             ' #%s' % str(self.graph_num) + "_1D"
+        new_plot.is_data = False
+
+        title = new_plot.title
+        _yaxis, _yunit = new_plot.get_yaxis()
+        _xaxis, _xunit = new_plot.get_xaxis()
+        new_plot.xaxis(str(_xaxis), str(_xunit))
+        new_plot.yaxis(str(_yaxis), str(_yunit))
+
+        if new_plot.is_data:
+            data_name = str(new_plot.name)
+        else:
+            data_name = str(model.__class__.__name__) + '1d'
+
+        if len(title) > 1:
+            new_plot.title = "Gen Theory for %s " % model.name + data_name
+        new_plot.name = new_plot.id
+        new_plot.label = new_plot.id
+        #theory_data = deepcopy(new_plot)
+        if self.parent.parent != None:
+            self.parent.parent.update_theory(data_id=new_plot.id,
+                                           theory=new_plot,
+                                           state=state)
+        title = new_plot.title
+        num_graph = str(self.graph_num)
+        wx.CallAfter(self.parent.draw_graph, new_plot,
+                     title="GEN Graph %s: " % num_graph + new_plot.id)
+        self.graph_num += 1
+
+    def _draw2D(self, image):
+        """
+        Complete get the result of modelthread and create model 2D
+        that can be plot.
+        """
+        page_id = self.id
+        data = self.data
+
+        model = self.model
+        qmin = 0.0
+        state = None
+
+        numpy.nan_to_num(image)
+        new_plot = Data2D(image=image, err_image=data.err_data)
+        new_plot.name = model.name + '2d'
+        new_plot.title = "Generic model 2D "
+        new_plot.id = str(page_id) + ': ' + self.file_name \
+                        + ' #%s' % str(self.graph_num) + "_2D"
+        new_plot.group_id = str(page_id) + " Model2D" \
+                        + ' #%s' % str(self.graph_num) + "_2D"
+        new_plot.detector = data.detector
+        new_plot.source = data.source
+        new_plot.is_data = False
+        new_plot.qx_data = data.qx_data
+        new_plot.qy_data = data.qy_data
+        new_plot.q_data = data.q_data
+        new_plot.mask = data.mask
+        ## plot boundaries
+        new_plot.ymin = data.ymin
+        new_plot.ymax = data.ymax
+        new_plot.xmin = data.xmin
+        new_plot.xmax = data.xmax
+        title = data.title
+        _yaxis, _yunit = data.get_yaxis()
+        _xaxis, _xunit = data.get_xaxis()
+        new_plot.xaxis(str(_xaxis), str(_xunit))
+        new_plot.yaxis(str(_yaxis), str(_yunit))
+
+        new_plot.is_data = False
+        if data.is_data:
+            data_name = str(data.name)
+        else:
+            data_name = str(model.__class__.__name__) + '2d'
+
+        if len(title) > 1:
+            new_plot.title = "Gen Theory for %s " % model.name + data_name
+        new_plot.name = new_plot.id
+        new_plot.label = new_plot.id
+        #theory_data = deepcopy(new_plot)
+        if self.parent.parent != None:
+            self.parent.parent.update_theory(data_id=data.id,
+                                           theory=new_plot,
+                                           state=state)
+        title = new_plot.title
+        num_graph = str(self.graph_num)
+        wx.CallAfter(self.parent.draw_graph, new_plot,
+                     title="GEN Graph %s: " % num_graph + new_plot.id)
+        self.graph_num += 1
+
+    def set_scale2d(self, scale):
+        """
+        Set SLD plot scale
+        """
+        self.scale2d = None
+
+    def on_panel_close(self, event):
+        """
+        close the window containing this panel
+        """
+        self.parent.Close()
+
+class OmfPanel(ScrolledPanel, PanelBase):
+    """
+        Provides the sas gen calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "SLD Pixel Info"
+    ## Name to appear on the window title bar
+    window_caption = "SLD Pixel Info "
+
+    def __init__(self, parent, *args, **kwds):
+        ScrolledPanel.__init__(self, parent, style=wx.RAISED_BORDER,
+                               *args, **kwds)
+        PanelBase.__init__(self)
+        #Font size 
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        self.SetupScrolling()
+        # Object that receive status event
+        self.parent = parent
+        self.sld_data = sas_gen.MagSLD([0], [0], [0])
+        self.sld_ctl = None
+        self.default_shape = 'rectangular'
+        self._do_layout()
+
+    def set_slddata(self, slddata):
+        """
+        Set sld data related items
+        """
+        self.sld_data = slddata
+        self._set_slddata_ctr_val(slddata)
+        # Make sure that self._set_slddata_ctr_val() is finished
+        wx.CallAfter(self._set_omfdata_ctr, slddata)
+
+    def get_sld_val(self):
+        """
+        Set sld_n of slddata on sld input
+        """
+        sld_sets = {}
+        if not self.sld_data.is_data:
+            self._get_other_val()
+        for list in self.slds:
+            if list[1].IsEnabled():
+                list[1].SetBackgroundColour("white")
+                list[1].Refresh()
+                try:
+                    val = float(list[1].GetValue())
+                    sld_sets[list[0]] = val
+                except:
+                    flag = _set_error(self, list[1])
+                    if not flag:
+                        return self.sld_data
+            else:
+               sld_sets[list[0]] = None
+        for key in sld_sets.keys():
+            key_low = key.lower()
+            if key_low.count('mx') > 0:
+                if sld_sets[key] == None:
+                    sld_sets[key] = self.sld_data.sld_mx
+                mx = sld_sets[key]
+            elif key_low.count('my') > 0:
+                if sld_sets[key] == None:
+                    sld_sets[key] = self.sld_data.sld_my
+                my = sld_sets[key]
+            elif key_low.count('mz') > 0:
+                if sld_sets[key] == None:
+                    sld_sets[key] = self.sld_data.sld_mz
+                mz = sld_sets[key]
+            else:
+                if sld_sets[key] != None:
+                    self.sld_data.set_sldn(sld_sets[key])
+        self.sld_data.set_sldms(mx, my, mz)
+        self._set_slddata_ctr_val(self.sld_data)
+
+        return self.sld_data
+
+    def get_pix_volumes(self):
+        """
+        Get the pixel volume
+        """
+        vol = self.sld_data.vol_pix
+
+        return vol
+
+    def _get_other_val(self):
+        """
+        """
+        omfdata = sas_gen.OMFData()
+        sets = {}
+        try:
+            for lst in self.stepsize:
+                if lst[1].IsEnabled():
+                    val = float(lst[1].GetValue())
+                    sets[lst[0]] = val
+                else:
+                    sets[lst[0]] = None
+                    return
+            for lst in self.nodes:
+                if lst[1].IsEnabled():
+                    val = float(lst[1].GetValue())
+                    sets[lst[0]] = val
+                else:
+                    sets[lst[0]] = None
+                    return
+
+            for key in sets.keys():
+                setattr(omfdata, key, sets[key])
+
+            omf2sld = sas_gen.OMF2SLD()
+            omf2sld.set_data(omfdata, self.default_shape)
+            self.sld_data = omf2sld.output
+            self.sld_data.is_data = False
+            self.sld_data.filename = "Default SLD Profile"
+        except:
+            msg = "OMF Panel: %s" % sys.exc_value
+            infor = 'Error'
+            #logging.error(msg)
+            if self.parent.parent != None:
+                # inform msg to wx
+                wx.PostEvent(self.parent.parent,
+                        StatusEvent(status=msg, info=infor))
+                self.SetFocus()
+
+    def _set_slddata_ctr_val(self, slddata):
+        """
+        Set slddata crl
+        """
+        try:
+            val = str(len(slddata.sld_n))
+        except:
+            val = 'Unknown'
+        self.npix_ctl.SetValue(val)
+
+    def _set_omfdata_ctr(self, omfdata):
+        """
+        Set the textctr box values
+        """
+
+        if omfdata == None:
+            self._set_none_text()
+            return
+        nodes_list = self._get_nodes_key_list(omfdata)
+        step_list = self._get_step_key_list(omfdata)
+        for ctr_list in self.nodes:
+            for key in nodes_list.keys():
+                if ctr_list[0] == key:
+                    ctr_list[1].SetValue(format_number(nodes_list[key], True))
+                    ctr_list[1].Enable(not omfdata.is_data)
+                    break
+        for ctr_list in self.stepsize:
+            for key in step_list.keys():
+                if ctr_list[0] == key:
+                    ctr_list[1].SetValue(format_number(step_list[key], True))
+                    ctr_list[1].Enable(not omfdata.is_data)
+                    break
+
+    def _set_none_text(self):
+        """
+        Set Unknown in textctrls
+        """
+        val = 'Unknown'
+        for ctr_list in self.nodes:
+            ctr_list[1].SetValue(val)
+        for ctr_list in self.stepsize:
+            ctr_list[1].SetValue(val)
+
+    def _define_structure(self):
+        """
+        Define the main sizers building to build this application.
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+
+        self.npixels_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.box_sld = wx.StaticBox(self, -1,
+                                    str("Mean SLD"))
+        self.box_node = wx.StaticBox(self, -1, str("Nodes"))
+        self.boxsizer_sld = wx.StaticBoxSizer(self.box_sld, wx.VERTICAL)
+        self.box_stepsize = wx.StaticBox(self, -1, str("Step Size"))
+        self.boxsizer_node = wx.StaticBoxSizer(self.box_node, wx.VERTICAL)
+        self.boxsizer_stepsize = wx.StaticBoxSizer(self.box_stepsize,
+                                                    wx.VERTICAL)
+        self.sld_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.node_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.step_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_npix(self):
+        """
+        Build No of pixels sizer
+        """
+        num_pix_text = wx.StaticText(self, -1, "No. of Pixels: ")
+        self.npix_ctl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+        self._set_slddata_ctr_val(self.sld_data)
+        self._set_omfdata_ctr(self.sld_data)
+        self.npixels_sizer.AddMany([(num_pix_text, 0,
+                                          wx.EXPAND | wx.LEFT | wx.TOP, 5),
+                                     (self.npix_ctl, 0,
+                                     wx.EXPAND | wx.TOP, 5)])
+
+    def _layout_slds(self):
+        """
+        Build nuclear sld sizer
+        """
+        self.slds = []
+        omfdata = self.sld_data
+        if omfdata == None:
+            raise
+        sld_key_list = self._get_slds_key_list(omfdata)
+        # Dic is not sorted
+        key_list = [key for key in sld_key_list.keys()]
+        # Sort here
+        key_list.sort()
+        is_data = self.sld_data.is_data
+        sizer = wx.GridBagSizer(2, 3)
+        ix = 0
+        iy = -1
+        for key in key_list:
+            value = sld_key_list[key]
+            iy += 1
+            ix = 0
+            name = wx.StaticText(self, -1, key)
+            sizer.Add(name, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            ## add parameter value
+            ix += 1
+            ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+            ctl.SetValue(format_number(value, True))
+            ctl.Enable(not is_data)
+            sizer.Add(ctl, (iy, ix), (1, 1), wx.EXPAND)
+            ## add unit
+            ix += 1
+            s_unit = '[' + omfdata.sld_unit + ']'
+            unit = wx.StaticText(self, -1, s_unit)
+            sizer.Add(unit, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            self.slds.append([key, ctl, unit])
+        self.sld_sizer.Add(sizer, 0, wx.LEFT, 10)
+
+    def _layout_nodes(self):
+        """
+        Fill the sizer containing data's name
+        """
+        self.nodes = []
+        omfdata = self.sld_data
+        if omfdata == None:
+            raise
+        key_list = self._get_nodes_key_list(omfdata)
+        is_data = self.sld_data.is_data
+        sizer = wx.GridBagSizer(2, 3)
+        ix = 0
+        iy = -1
+        for key, value in key_list.iteritems():
+            iy += 1
+            ix = 0
+            name = wx.StaticText(self, -1, key)
+            sizer.Add(name, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            ## add parameter value
+            ix += 1
+            ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+            ctl.Bind(wx.EVT_TEXT, self._onparamEnter)
+            ctl.SetValue(format_number(value, True))
+            ctl.Enable(not is_data)
+            sizer.Add(ctl, (iy, ix), (1, 1), wx.EXPAND)
+            ## add unit
+            ix += 1
+            unit = wx.StaticText(self, -1, '')
+            sizer.Add(unit, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            self.nodes.append([key, ctl, unit])
+        self.node_sizer.Add(sizer, 0, wx.LEFT, 10)
+
+    def _layout_stepsize(self):
+        """
+        Fill the sizer containing slit size information
+        """
+        self.stepsize = []
+        omfdata = self.sld_data
+        if omfdata == None:
+            raise
+        key_list = self._get_step_key_list(omfdata)
+        is_data = self.sld_data.is_data
+        sizer = wx.GridBagSizer(2, 3)
+        ix = 0
+        iy = -1
+        #key_list.sort()
+        for key, value in key_list.iteritems():
+            iy += 1
+            ix = 0
+            name = wx.StaticText(self, -1, key)
+            sizer.Add(name, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            ## add parameter value
+            ix += 1
+            ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+            ctl.Bind(wx.EVT_TEXT, self._onstepsize)
+            ctl.SetValue(format_number(value, True))
+            ctl.Enable(not is_data)
+            sizer.Add(ctl, (iy, ix), (1, 1), wx.EXPAND)
+            ## add unit
+            ix += 1
+            p_unit = '[' + omfdata.pos_unit + ']'
+            unit = wx.StaticText(self, -1, p_unit)
+            sizer.Add(unit, (iy, ix), (1, 1), \
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+            self.stepsize.append([key, ctl, unit])
+        self.step_sizer.Add(sizer, 0, wx.LEFT, 10)
+
+    def _layout_hint(self):
+        """
+        Fill the sizer containing hint
+        """
+        hint_msg = "Load an omf or 3d sld profile data file."
+        self.hint_txt = wx.StaticText(self, -1, hint_msg)
+        self.hint_sizer.AddMany([(self.hint_txt, 0, wx.LEFT, 15)])
+
+    def _layout_button(self):
+        """
+        Do the layout for the button widgets
+        """
+        self.bt_draw = wx.Button(self, wx.NewId(), 'Draw Points')
+        self.bt_draw.Bind(wx.EVT_BUTTON, self.on_sld_draw)
+        self.bt_draw.SetToolTipString("Draw a scatter plot for sld profile.")
+        self.bt_save = wx.Button(self, wx.NewId(), 'Save SLD Data')
+        self.bt_save.Bind(wx.EVT_BUTTON, self.on_save)
+        self.bt_save.Enable(False)
+        self.bt_save.SetToolTipString("Save SLD data.")
+        self.button_sizer.AddMany([(self.bt_draw, 0, wx.LEFT, 10),
+                                   (self.bt_save, 0, wx.LEFT, 10)])
+
+    def _do_layout(self):
+        """
+        Draw omf panel content, used to define sld s.
+
+        """
+        self._define_structure()
+        self._layout_nodes()
+        self._layout_stepsize()
+        self._layout_npix()
+        self._layout_slds()
+        #self._layout_hint()
+        self._layout_button()
+        self.boxsizer_node.AddMany([(self.node_sizer, 0,
+                                    wx.EXPAND | wx.TOP, 5),
+                                     (self.hint_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.boxsizer_stepsize.AddMany([(self.step_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5), ])
+        self.boxsizer_sld.AddMany([(self.sld_sizer, 0,
+                                     wx.EXPAND | wx.BOTTOM, 5), ])
+        self.main_sizer.AddMany([(self.npixels_sizer, 0, wx.EXPAND | wx.ALL, 10),
+                        (self.boxsizer_sld, 0, wx.EXPAND | wx.ALL, 10),
+                        (self.boxsizer_node, 0, wx.EXPAND | wx.ALL, 10),
+                        (self.boxsizer_stepsize, 0, wx.EXPAND | wx.ALL, 10),
+                        (self.button_sizer, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def _get_nodes_key_list(self, data):
+        """
+        Return nodes key list
+
+        :Param data: OMFData
+        """
+        key_list = {'xnodes' : data.xnodes,
+                    'ynodes' : data.ynodes,
+                    'znodes' : data.znodes}
+        return key_list
+
+    def _get_slds_key_list(self, data):
+        """
+        Return nodes key list
+
+        :Param data: OMFData
+        """
+        key_list = {'Nucl.' : data.sld_n,
+                    'Mx' : data.sld_mx,
+                    'My' : data.sld_my,
+                    'Mz' : data.sld_mz}
+        return key_list
+
+    def _get_step_key_list(self, data):
+        """
+        Return step key list
+
+        :Param data: OMFData
+        """
+        key_list = {'xstepsize' : data.xstepsize,
+                    'ystepsize' : data.ystepsize,
+                    'zstepsize' : data.zstepsize}
+        return key_list
+
+    def set_sld_ctr(self, sld_data):
+        """
+        Set sld textctrls
+        """
+        if sld_data == None:
+            for ctr_list in self.slds:
+                ctr_list[1].Enable(False)
+                #break   
+            return
+
+        self.sld_data = sld_data
+        sld_list = self._get_slds_key_list(sld_data)
+        for ctr_list in self.slds:
+            for key in sld_list.keys():
+                if ctr_list[0] == key:
+                    min_val = numpy.min(sld_list[key])
+                    max_val = numpy.max(sld_list[key])
+                    mean_val = numpy.mean(sld_list[key])
+                    enable = (min_val == max_val) and \
+                             sld_data.pix_type == 'pixel'
+                    ctr_list[1].SetValue(format_number(mean_val, True))
+                    ctr_list[1].Enable(enable)
+                    #ctr_list[2].SetLabel("[" + sld_data.sld_unit + "]")
+                    break
+
+    def on_sld_draw(self, event):
+        """
+        Draw sld profile as scattered plot
+        """
+        self.parent.sld_draw()
+
+    def on_save(self, event):
+        """
+        Close the window containing this panel
+        """
+        flag = True
+        flag = self.check_inputs()
+        if not flag:
+            return
+        self.sld_data = self.get_sld_val()
+        self.parent.set_main_panel_sld_data(self.sld_data)
+
+        reader = sas_gen.SLDReader()
+        extension = '*.sld'
+        path = None
+        data = None
+        location = self.parent.get_path()
+        dlg = wx.FileDialog(self, "Save sld file",
+                            location, "sld_file",
+                             extension,
+                             wx.SAVE)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            self.parent.set_file_location(os.path.dirname(path))
+        else:
+            return None
+        dlg.Destroy()
+        try:
+            if path is None:
+                return
+
+            data = self.parent.get_sld_data()
+            fName = os.path.splitext(path)[0] + '.' + extension.split('.')[-1]
+            if data != None:
+                try:
+                    reader.write(fName, data)
+                except:
+                    raise
+            else:
+                msg = "%s cannot write %s\n" % ('Generic Scattering', str(path))
+                infor = 'Error'
+                #logging.error(msg)
+                if self.parent.parent != None:
+                    # inform msg to wx
+                    wx.PostEvent(self.parent.parent,
+                            StatusEvent(status=msg, info=infor))
+                    self.SetFocus()
+            return
+        except:
+            msg = "Error occurred while saving. "
+            infor = 'Error'
+            if self.parent.parent != None:
+                # inform msg to wx
+                wx.PostEvent(self.parent.parent,
+                        StatusEvent(status=msg, info=infor))
+                self.SetFocus()
+
+    def _onparamEnter(self, event):
+        """
+        """
+        flag = True
+        if event != None:
+            event.Skip()
+            ctl = event.GetEventObject()
+            ctl.SetBackgroundColour("white")
+            #_set_error(self, ctl)
+        try:
+            float(ctl.GetValue())
+        except:
+            flag = _set_error(self, ctl)
+        if flag:
+            npts = 1
+            for item in self.nodes:
+                n_val = float(item[1].GetValue())
+                if n_val <= 0:
+                    item[1].SetBackgroundColour("pink")
+                    npts = -1
+                    break
+                if numpy.isfinite(n_val):
+                    npts *= int(n_val)
+            if npts > 0:
+                nop = self.set_npts_from_slddata()
+                if nop == None:
+                    nop = npts
+                self.display_npts(nop)
+
+        ctl.Refresh()
+        return flag
+
+    def _set_volume_ctr_val(self, npts):
+        """
+        Set total volume
+        """
+        total_volume = npts * self.sld_data.vol_pix[0]
+        self.parent.set_volume_ctr_val(total_volume)
+
+    def _onstepsize(self, event):
+        """
+        On stepsize event
+        """
+        flag = True
+        if event != None:
+            event.Skip()
+            ctl = event.GetEventObject()
+            ctl.SetBackgroundColour("white")
+
+        if flag and not self.sld_data.is_data:#ctl.IsEnabled():
+            s_size = 1.0
+            try:
+                for item in self.stepsize:
+                    s_val = float(item[1].GetValue())
+                    if s_val <= 0:
+                        item[1].SetBackgroundColour("pink")
+                        ctl.Refresh()
+                        return
+                    if numpy.isfinite(s_val):
+                        s_size *= s_val
+                self.sld_data.set_pixel_volumes(s_size)
+                if ctl.IsEnabled():
+                    total_volume = sum(self.sld_data.vol_pix)
+                    self.parent.set_volume_ctr_val(total_volume)
+            except:
+                pass
+        ctl.Refresh()
+
+
+    def set_npts_from_slddata(self):
+        """
+        Set total n. of points form the sld data
+        """
+        try:
+            sld_data = self.parent.get_sld_from_omf()
+            #nop = (nop * numpy.pi) / 6
+            nop = len(sld_data.sld_n)
+        except:
+            nop = None
+        return nop
+
+    def display_npts(self, nop):
+        """
+        Displays Npts ctrl
+        """
+        try:
+            self.npix_ctl.SetValue(str(nop))
+            self.npix_ctl.Refresh()
+            self.parent.set_etime()
+            wx.CallAfter(self._set_volume_ctr_val, nop)
+        except:
+            # On Init
+            pass
+
+    def check_inputs(self):
+        """
+        check if the inputs are valid
+        """
+        flag = self._check_input_helper(self.slds)
+        if flag:
+            flag = self._check_input_helper(self.nodes)
+        if flag:
+            flag = self._check_input_helper(self.stepsize)
+        return flag
+
+    def _check_input_helper(self, list):
+        """
+        Check list values
+        """
+        flag = True
+        for item in list:
+            item[1].SetBackgroundColour("white")
+            item[1].Refresh()
+            try:
+                float(item[1].GetValue())
+            except:
+                flag = _set_error(self, item[1])
+                break
+        return flag
+
+class SasGenWindow(widget.CHILD_FRAME):
+    """
+    GEN SAS main window
+    """
+    def __init__(self, parent=None, manager=None, title="Generic Scattering Calculator",
+                size=(PANEL_WIDTH * 1.4, PANEL_HEIGHT * 1.65), *args, **kwds):
+        """
+        Init
+        """
+        kwds['size'] = size
+        kwds['title'] = title
+        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        self.base = manager
+        self.omfpanel = OmfPanel(parent=self)
+        self.panel = SasGenPanel(parent=self)
+        self.data = None
+        self.omfdata = sas_gen.OMFData()
+        self.sld_data = None
+        self._default_save_location = os.getcwd()
+
+        self._mgr = aui.AuiManager(self)
+        self._mgr.SetDockSizeConstraint(0.5, 0.5)
+        self._plot_title = ''
+        self.scale2d = 'log_{10}'
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+
+
+        self.build_panels()
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show(True)
+
+    def build_panels(self):
+        """
+        """
+
+        self.set_sld_data(self.sld_data)
+        self._mgr.AddPane(self.panel, aui.AuiPaneInfo().
+                              Name(self.panel.window_name).
+                              CenterPane().
+                              # This is where we set the size of
+                              # the application window
+                              BestSize(wx.Size(PANEL_WIDTH,
+                                               PANEL_HEIGHT)).
+                              Show())
+        self._mgr.AddPane(self.omfpanel, aui.AuiPaneInfo().
+                              Name(self.omfpanel.window_name).
+                              Caption(self.omfpanel.window_caption).
+                              CloseButton(False).
+                              Right().
+                              Floatable(False).
+                              BestSize(wx.Size(PANEL_WIDTH / 2.5, PANEL_HEIGHT)).
+                              Show())
+        self._mgr.Update()
+
+    def get_sld_data(self):
+        """
+        Return slddata
+        """
+        return self.sld_data
+
+    def get_sld_from_omf(self):
+        """
+        """
+        self.sld_data = self.omfpanel.get_sld_val()
+        return self.sld_data
+
+    def set_sld_n(self, sld):
+        """
+        """
+        self.panel.sld_data = sld
+        self.panel.model.set_sld_data(sld)
+
+    def set_sld_data(self, data):
+        """
+        Set omfdata
+        """
+        if data == None:
+            return
+        self.sld_data = data
+        enable = (not data == None)
+        self._set_omfpanel_sld_data(self.sld_data)
+        self.omfpanel.bt_save.Enable(enable)
+        self.set_etime()
+
+    def set_omfpanel_npts(self):
+        """
+        Set Npts in omf panel
+        """
+        nop = self.omfpanel.set_npts_from_slddata()
+        self.omfpanel.display_npts(nop)
+
+    def _set_omfpanel_sld_data(self, data):
+        """
+        Set sld_data in omf panel
+        """
+        self.omfpanel.set_slddata(data)
+        self.omfpanel.set_sld_ctr(data)
+
+    def check_omfpanel_inputs(self):
+        """
+        Check OMF panel inputs
+        """
+        return self.omfpanel.check_inputs()
+
+    def set_main_panel_sld_data(self, sld_data):
+        """
+        """
+        self.sld_data = sld_data
+
+    def set_file_location(self, path):
+        """
+        File location
+        """
+        self._default_save_location = path
+
+    def get_path(self):
+        """
+        File location
+        """
+        return self._default_save_location
+
+    def draw_graph(self, plot, title=''):
+        """
+        """
+        try:
+            wx.PostEvent(self.parent, NewPlotEvent(plot=plot, title=title))
+        except:
+            # standalone
+            frame = PlotFrame(self, -1, 'testView', self.scale2d)
+            #add_icon(self.parent, frame)
+            frame.add_plot(plot)
+            frame.SetTitle(title)
+            frame.Show(True)
+            frame.SetFocus()
+
+    def set_schedule_full_draw(self, panel=None, func='del'):
+        """
+        Send full draw to gui frame
+        """
+        if self.parent != None:
+            self.parent.set_schedule_full_draw(panel, func)
+
+    def get_npix(self):
+        """
+        Get no. of pixels from omf panel
+        """
+        n_pix = self.omfpanel.npix_ctl.GetValue()
+        return n_pix
+
+    def get_pix_volumes(self):
+        """
+        Get a pixel volume
+        """
+        vol = self.omfpanel.get_pix_volumes()
+        return vol
+
+    def set_volume_ctr_val(self, val):
+        """
+        Set volume txtctl value
+        """
+        try:
+            self.panel.set_volume_ctl_val(str(val))
+        except:
+            print "self.panel is not initialized yet"
+
+    def set_omfpanel_default_shap(self, shape):
+        """
+        Set default_shape in omfpanel
+        """
+        self.omfpanel.default_shape = shape
+
+    def set_etime(self):
+        """
+        Sets est. computation time on panel
+        """
+        self.panel.set_est_time()
+
+    def get_sld_data_from_omf(self):
+        """
+        """
+        data = self.omfpanel.get_sld_val()
+        return data
+
+    def set_scale2d(self, scale):
+        """
+        """
+        self.scale2d = scale
+
+    def on_panel_close(self, event):
+        """
+        """
+        #Not implemented
+
+    def on_open_file(self, event):
+        """
+        On Open
+        """
+        self.panel.on_load_data(event)
+
+    def sld_draw(self):
+        """
+        sld draw
+        """
+        self.panel.sld_draw(event=None, has_arrow=False)
+
+    def on_save_file(self, event):
+        """
+        On Close
+        """
+        self.omfpanel.on_save(event)
+
+    def on_close(self, event):
+        """
+        Close
+        """
+        if self.base != None:
+            self.base.gen_frame = None
+        self.Destroy()
+
+if __name__ == "__main__":
+    app = wx.PySimpleApp()
+    widget.CHILD_FRAME = wx.Frame
+    SGframe = SasGenWindow()
+    SGframe.Show(True)
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/image_viewer.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/image_viewer.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/image_viewer.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,438 @@
+import os
+import sys
+import wx
+import numpy as np
+import matplotlib
+matplotlib.interactive(False)
+#Use the WxAgg back end. The Wx one takes too long to render
+matplotlib.use('WXAgg')
+from sas.sasgui.guiframe.local_perspectives.plotting.SimplePlot import PlotFrame
+#import matplotlib.pyplot as plt
+import matplotlib.image as mpimg
+import matplotlib.colors as colors
+from sas.sasgui.guiframe.events import StatusEvent
+from sas.sasgui.perspectives.calculator.calculator_widgets import InputTextCtrl
+from sas.sascalc.dataloader.data_info import Data2D
+from sas.sascalc.dataloader.data_info import Detector
+from sas.sascalc.dataloader.manipulations import reader2D_converter
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+_BOX_WIDTH = 60
+IS_WIN = True
+if sys.platform.count("win32") > 0:
+    _DIALOG_WIDTH = 400
+else:
+    _DIALOG_WIDTH = 480
+    IS_WIN = False
+
+class ImageView:
+    """
+    Open a file dialog to allow the user to select a given file.
+    Display the loaded data if available.
+    """
+    def __init__(self, parent=None):
+        """
+        Init
+        """
+        self.parent = parent
+
+    def load(self):
+        """
+        load image files
+        """
+        parent = self.parent
+        if parent == None:
+            location = os.getcwd()
+        else:
+            location = parent._default_save_location
+        path_list = self.choose_data_file(location=location)
+        if path_list == None:
+            return
+        if len(path_list) >= 0 and not(path_list[0]is None):
+            if parent != None:
+                parent._default_save_location = os.path.dirname(path_list[0])
+        err_msg = ''
+        for file_path in path_list:
+            basename = os.path.basename(file_path)
+            _, extension = os.path.splitext(basename)
+            try:
+                img = mpimg.imread(file_path)
+                is_png = extension.lower() == '.png'
+                plot_frame = ImageFrame(parent, -1, basename, img)
+                plot_frame.Show(False)
+                ax = plot_frame.plotpanel
+                if not is_png:
+                    ax.subplot.set_ylim(ax.subplot.get_ylim()[::-1])
+                ax.subplot.set_xlabel('x [pixel]')
+                ax.subplot.set_ylabel('y [pixel]')
+                ax.figure.subplots_adjust(left=0.15, bottom=0.1,
+                                          right=0.95, top=0.95)
+                plot_frame.SetTitle('Picture -- %s --' % basename)
+                plot_frame.Show(True)
+                if parent != None:
+                    parent.put_icon(plot_frame)
+            except:
+                print "parent", parent
+                err_msg += "Failed to load '%s'.\n" % basename
+        if err_msg:
+            if parent is not None:
+                wx.PostEvent(parent, StatusEvent(status=err_msg, info="error"))
+            else:
+                print err_msg
+
+    def choose_data_file(self, location=None):
+        """
+        Open a file dialog to allow loading a file
+        """
+        path = None
+        if location == None:
+            location = os.getcwd()
+        dlg = wx.FileDialog(self.parent, "Image Viewer: Choose a image file",
+                            location, "", "", style=wx.FD_OPEN | wx.FD_MULTIPLE)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPaths()
+        else:
+            return None
+        dlg.Destroy()
+        return path
+
+class ImageFrame(PlotFrame):
+    """
+    Frame for simple plot
+    """
+    def __init__(self, parent, id, title, image=None, scale='log_{10}',
+                 size=wx.Size(550, 470)):
+        """
+        comment
+        :Param data: image array got from imread() of matplotlib [narray]
+        :param parent: parent panel/container
+        """
+        # Initialize the Frame object
+        PlotFrame.__init__(self, parent, id, title, scale, size)
+        self.parent = parent
+        self.data = image
+        self.file_name = title
+
+        menu = wx.Menu()
+        id = wx.NewId()
+        item = wx.MenuItem(menu, id, "&Convert to Data")
+        menu.AppendItem(item)
+        wx.EVT_MENU(self, id, self.on_set_data)
+        self.menu_bar.Append(menu, "&Image")
+
+        menu_help = wx.Menu()
+        id = wx.NewId()
+        item = wx.MenuItem(menu_help, id, "&HowTo")
+        menu_help.AppendItem(item)
+        wx.EVT_MENU(self, id, self.on_help)
+        self.menu_bar.Append(menu_help, "&Help")
+
+        self.SetMenuBar(self.menu_bar)
+        self.im_show(image)
+
+    def on_set_data(self, event):
+        """
+        Rescale the x y range, make 2D data and send it to data explore
+        """
+        title = self.file_name
+        self.panel = SetDialog(parent=self, title=title, image=self.data)
+        self.panel.ShowModal()
+
+    def on_help(self, event):
+        """
+        Bring up Image Viewer Documentation from the image viewer window
+        whenever the help menu item "how to" is clicked. Calls
+        DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".
+
+        :param evt: Triggers on clicking "how to" in help menu
+        """
+
+        _TreeLocation = "user/perspectives/calculator/image_viewer_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "Image Viewer Help")
+
+
+class SetDialog(wx.Dialog):
+    """
+    Dialog for Data Set
+    """
+    def __init__(self, parent, id= -1, title="Convert to Data", image=None,
+                 size=(_DIALOG_WIDTH, 270)):
+        wx.Dialog.__init__(self, parent, id, title, size)
+        # parent
+        self.parent = parent
+        self.base = parent.parent
+        self.title = title
+        self.image = np.array(image)
+        self.z_ctrl = None
+        self.xy_ctrls = []
+        self.is_png = self._get_is_png()
+        self._build_layout()
+        my_title = "Convert Image to Data - %s -" % self.title
+        self.SetTitle(my_title)
+        self.SetSize(size)
+
+    def _get_is_png(self):
+        """
+        Get if the image file is png
+        """
+        _, extension = os.path.splitext(self.title)
+        return extension.lower() == '.png'
+
+    def _build_layout(self):
+        """
+        Layout
+        """
+        vbox = wx.BoxSizer(wx.VERTICAL)
+        zbox = wx.BoxSizer(wx.HORIZONTAL)
+        xbox = wx.BoxSizer(wx.HORIZONTAL)
+        ybox = wx.BoxSizer(wx.HORIZONTAL)
+        btnbox = wx.BoxSizer(wx.VERTICAL)
+
+        sb_title = wx.StaticBox(self, -1, 'Transform Axes')
+        boxsizer = wx.StaticBoxSizer(sb_title, wx.VERTICAL)
+        z_title = wx.StaticText(self, -1, 'z values (range: 0 - 255) to:')
+        ztime_title = wx.StaticText(self, -1, 'z *')
+        x_title = wx.StaticText(self, -1, 'x values from pixel # to:')
+        xmin_title = wx.StaticText(self, -1, 'xmin:')
+        xmax_title = wx.StaticText(self, -1, 'xmax:')
+        y_title = wx.StaticText(self, -1, 'y values from pixel # to:')
+        ymin_title = wx.StaticText(self, -1, 'ymin: ')
+        ymax_title = wx.StaticText(self, -1, 'ymax:')
+        z_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH , 20),
+                                style=wx.TE_PROCESS_ENTER)
+
+        xmin_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+        xmax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+        ymin_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+        ymax_ctl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                style=wx.TE_PROCESS_ENTER)
+        z_ctl.SetValue('1.0')
+        xmin_ctl.SetValue('-0.3')
+        xmax_ctl.SetValue('0.3')
+        ymin_ctl.SetValue('-0.3')
+        ymax_ctl.SetValue('0.3')
+        z_ctl.Bind(wx.EVT_TEXT, self._on_z_enter)
+        xmin_ctl.Bind(wx.EVT_TEXT, self._onparam)
+        xmax_ctl.Bind(wx.EVT_TEXT, self._onparam)
+        ymin_ctl.Bind(wx.EVT_TEXT, self._onparam)
+        ymax_ctl.Bind(wx.EVT_TEXT, self._onparam)
+        xbox.AddMany([(x_title , 0, wx.LEFT, 0),
+                      (xmin_title , 0, wx.LEFT, 10),
+                      (xmin_ctl , 0, wx.LEFT, 10),
+                      (xmax_title , 0, wx.LEFT, 10),
+                      (xmax_ctl , 0, wx.LEFT, 10)])
+        ybox.AddMany([(y_title , 0, wx.LEFT, 0),
+                      (ymin_title , 0, wx.LEFT, 10),
+                      (ymin_ctl , 0, wx.LEFT, 10),
+                      (ymax_title , 0, wx.LEFT, 10),
+                      (ymax_ctl , 0, wx.LEFT, 10)])
+        zbox.AddMany([(z_title , 0, wx.LEFT, 0),
+                      (ztime_title, 0, wx.LEFT, 10),
+                      (z_ctl , 0, wx.LEFT, 7),
+                      ])
+        msg = "The data rescaled will show up in the Data Explorer. \n"
+        msg += "*Note: Recommend to use an image with 8 bit Grey \n"
+        msg += "  scale (and with No. of pixels < 300 x 300).\n"
+        msg += "  Otherwise, z = 0.299R + 0.587G + 0.114B."
+        note_txt = wx.StaticText(self, -1, msg)
+        note_txt.SetForegroundColour("black")
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        okButton = wx.Button(self, -1, 'OK')
+        okButton.Bind(wx.EVT_BUTTON, self.on_set)
+        cancelButton = wx.Button(self, -1, 'Cancel')
+        cancelButton.Bind(wx.EVT_BUTTON, self.OnClose)
+        btnbox.Add(okButton, 0, wx.LEFT | wx.BOTTOM, 5)
+        btnbox.Add(cancelButton, 0, wx.LEFT | wx.TOP, 5)
+        hbox.Add(note_txt, 0, wx.LEFT, 5)
+        hbox.Add(btnbox, 0, wx.LEFT, 15)
+        vbox.Add((10, 15))
+        boxsizer.Add(xbox, 1, wx.LEFT | wx.BOTTOM, 5)
+        boxsizer.Add(ybox, 1, wx.LEFT | wx.BOTTOM, 5)
+        boxsizer.Add(zbox, 1, wx.LEFT | wx.BOTTOM, 5)
+        vbox.Add(boxsizer, 0, wx.LEFT, 20)
+        vbox.Add(hbox, 0, wx.LEFT | wx.TOP, 15)
+        okButton.SetFocus()
+        # set sizer
+        self.SetSizer(vbox)
+        #pos = self.parent.GetPosition()
+        #self.SetPosition(pos)
+        self.z_ctrl = z_ctl
+        self.xy_ctrls = [[xmin_ctl, xmax_ctl], [ymin_ctl, ymax_ctl]]
+
+    def _onparamEnter(self, event=None):
+        """
+        By pass original txtcrl binding
+        """
+        pass
+
+    def _onparam(self, event=None):
+        """
+        Set to default
+        """
+        item = event.GetEventObject()
+        self._check_ctrls(item)
+
+    def _check_ctrls(self, item, is_button=False):
+        """
+        """
+        flag = True
+        item.SetBackgroundColour("white")
+        try:
+            val = float(item.GetValue())
+            if val < -10.0 or val > 10.0:
+                item.SetBackgroundColour("pink")
+                item.Refresh()
+                flag = False
+        except:
+            item.SetBackgroundColour("pink")
+            item.Refresh()
+            flag = False
+        if not flag and is_button:
+            err_msg = "The allowed range of the min and max values are \n"
+            err_msg += "between -10 and 10."
+            if self.base is not None:
+                wx.PostEvent(self.base, StatusEvent(status=err_msg,
+                                                    info="error"))
+            else:
+                print err_msg
+        return flag
+
+    def _on_z_enter(self, event=None):
+        """
+        On z factor enter
+        """
+        item = event.GetEventObject()
+        self._check_z_ctrl(item)
+
+    def _check_z_ctrl(self, item, is_button=False):
+        """
+        """
+        flag = True
+        item.SetBackgroundColour("white")
+        try:
+            val = float(item.GetValue())
+            if val <= 0:
+                item.SetBackgroundColour("pink")
+                item.Refresh()
+                flag = False
+        except:
+            item.SetBackgroundColour("pink")
+            item.Refresh()
+            flag = False
+        if not flag and is_button:
+            err_msg = "The z scale value should be larger than 0."
+            if self.base is not None:
+                wx.PostEvent(self.base, StatusEvent(status=err_msg,
+                                                    info="error"))
+            else:
+                print err_msg
+        return flag
+
+    def on_set(self, event):
+        """
+        Set image as data
+        """
+        event.Skip()
+        # Check the textctrl values
+        for item_list in self.xy_ctrls:
+            for item in item_list:
+                 if not self._check_ctrls(item, True):
+                     return
+        if not self._check_z_ctrl(self.z_ctrl, True):
+            return
+        try:
+            image = self.image
+            xmin = float(self.xy_ctrls[0][0].GetValue())
+            xmax = float(self.xy_ctrls[0][1].GetValue())
+            ymin = float(self.xy_ctrls[1][0].GetValue())
+            ymax = float(self.xy_ctrls[1][1].GetValue())
+            zscale = float(self.z_ctrl.GetValue())
+            self.convert_image(image, xmin, xmax, ymin, ymax, zscale)
+        except:
+            err_msg = "Error occurred while converting Image to Data."
+            if self.base is not None:
+                wx.PostEvent(self.base, StatusEvent(status=err_msg,
+                                                    info="error"))
+            else:
+                print err_msg
+
+        self.OnClose(event)
+
+    def convert_image(self, rgb, xmin, xmax, ymin, ymax, zscale):
+        """
+        Convert image to data2D
+        """
+        x_len = len(rgb[0])
+        y_len = len(rgb)
+        x_vals = np.linspace(xmin, xmax, num=x_len)
+        y_vals = np.linspace(ymin, ymax, num=y_len)
+        # Instantiate data object
+        output = Data2D()
+        output.filename = os.path.basename(self.title)
+        output.id = output.filename
+        detector = Detector()
+        detector.pixel_size.x = None
+        detector.pixel_size.y = None
+        # Store the sample to detector distance
+        detector.distance = None
+        output.detector.append(detector)
+        # Initiazed the output data object
+        output.data = zscale * self.rgb2gray(rgb)
+        output.err_data = np.zeros([x_len, y_len])
+        output.mask = np.ones([x_len, y_len], dtype=bool)
+        output.xbins = x_len
+        output.ybins = y_len
+        output.x_bins = x_vals
+        output.y_bins = y_vals
+        output.qx_data = np.array(x_vals)
+        output.qy_data = np.array(y_vals)
+        output.xmin = xmin
+        output.xmax = xmax
+        output.ymin = ymin
+        output.ymax = ymax
+        output.xaxis('\\rm{Q_{x}}', '\AA^{-1}')
+        output.yaxis('\\rm{Q_{y}}', '\AA^{-1}')
+        # Store loading process information
+        output.meta_data['loader'] = self.title.split('.')[-1] + "Reader"
+        output.is_data = True
+        output = reader2D_converter(output)
+        if self.base != None:
+            data = self.base.create_gui_data(output, self.title)
+            self.base.add_data({data.id:data})
+
+    def rgb2gray(self, rgb):
+        """
+        RGB to Grey
+        """
+        if self.is_png:
+            # png image limits: 0 to 1, others 0 to 255
+            #factor = 255.0
+            rgb = rgb[::-1]
+        if rgb.ndim == 2:
+            grey = np.rollaxis(rgb, axis=0)
+        else:
+            red, green, blue = np.rollaxis(rgb[..., :3], axis= -1)
+            grey = 0.299 * red + 0.587 * green + 0.114 * blue
+        max_i = rgb.max()
+        factor = 255.0 / max_i
+        grey *= factor
+        return np.array(grey)
+
+    def OnClose(self, event):
+        """
+        Close event
+        """
+        # clear event
+        event.Skip()
+        self.Destroy()
+
+if __name__ == "__main__":
+    app = wx.App()
+    ImageView(None).load()
+    app.MainLoop()
+
Index: src/sas/sasgui/perspectives/calculator/kiessig_calculator_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/kiessig_calculator_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/kiessig_calculator_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,247 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation.
+
+See the license text in license.txt
+
+copyright 2008, 2009, University of Tennessee
+"""
+
+import wx
+import sys
+
+from sas.sasgui.guiframe.panel_base import PanelBase
+from sas.sascalc.calculator.kiessig_calculator import KiessigThicknessCalculator
+from calculator_widgets import OutputTextCtrl
+from calculator_widgets import InputTextCtrl
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+_BOX_WIDTH = 77
+#Slit length panel size
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 230
+    FONT_VARIANT = 0
+else:
+    PANEL_TOP = 60
+    PANEL_WIDTH = 560
+    PANEL_HEIGHT = 230
+    FONT_VARIANT = 1
+ 
+class KiessigThicknessCalculatorPanel(wx.Panel, PanelBase):
+    """
+    Provides the Kiessig thickness calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "Kiessig Thickness Calculator"
+    ## Name to appear on the window title bar
+    window_caption = "Kiessig Thickness Calculator"
+    ## Flag to tell the AUI manager to put this panel in the center pane
+    CENTER_PANE = True
+
+    def __init__(self, parent, *args, **kwds):
+        wx.Panel.__init__(self, parent, *args, **kwds)
+        PanelBase.__init__(self)
+        #Font size
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        # Object that receive status event
+        self.parent = parent
+        self.kiessig = KiessigThicknessCalculator()
+        #layout attribute
+        self.hint_sizer = None
+        self._do_layout()
+
+    def _define_structure(self):
+        """
+        Define the main sizers building to build this application.
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_source = wx.StaticBox(self, -1,
+                                str("Kiessig Thickness Calculator"))
+        self.boxsizer_source = wx.StaticBoxSizer(self.box_source,
+                                                 wx.VERTICAL)
+        self.dq_name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.thickness_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_dq_name(self):
+        """
+        Fill the sizer containing dq name
+        """
+        # get the default dq
+        dq_value = str(self.kiessig.get_deltaq())
+        dq_unit_txt = wx.StaticText(self, -1, '[1/A]')
+        dq_name_txt = wx.StaticText(self, -1,
+                                    'Kiessig Fringe Width (Delta Q): ')
+        self.dq_name_tcl = InputTextCtrl(self, -1, 
+                                         size=(_BOX_WIDTH,-1))
+        dq_hint = "Type the Kiessig Fringe Width (Delta Q)"
+        self.dq_name_tcl.SetValue(dq_value)
+        self.dq_name_tcl.SetToolTipString(dq_hint)
+        #control that triggers importing data
+        id = wx.NewId()
+        self.compute_button = wx.Button(self, id, "Compute")
+        hint_on_compute = "Compute the diameter/thickness in the real space."
+        self.compute_button.SetToolTipString(hint_on_compute)
+        self.Bind(wx.EVT_BUTTON, self.on_compute, id=id)
+        self.dq_name_sizer.AddMany([(dq_name_txt, 0, wx.LEFT, 15),
+                                    (self.dq_name_tcl, 0, wx.LEFT, 15),
+                                    (dq_unit_txt, 0, wx.LEFT, 10),
+                                    (self.compute_button, 0, wx.LEFT, 30)])
+
+    def _layout_thickness_size(self):
+        """
+        Fill the sizer containing thickness information
+        """
+        thick_unit = '['+self.kiessig.get_thickness_unit() +']'
+        thickness_size_txt = wx.StaticText(self, -1,
+                                           'Thickness (or Diameter): ')
+        self.thickness_size_tcl = OutputTextCtrl(self, -1,
+                                                 size=(_BOX_WIDTH,-1))
+        thickness_size_hint = " Estimated Size in Real Space"
+        self.thickness_size_tcl.SetToolTipString(thickness_size_hint)
+        thickness_size_unit_txt = wx.StaticText(self, -1, thick_unit)
+
+        self.thickness_size_sizer.AddMany([(thickness_size_txt, 0, wx.LEFT, 15),
+                                           (self.thickness_size_tcl, 0, wx.LEFT, 15),
+                                           (thickness_size_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_hint(self):
+        """
+        Fill the sizer containing hint 
+        """
+        hint_msg = "This tool is to approximately estimate "
+        hint_msg += "the thickness of a layer"
+        hint_msg += " or the diameter of particles\n "
+        hint_msg += "from the Kiessig fringe width in SAS/NR data."
+        hint_msg += ""
+        self.hint_txt = wx.StaticText(self, -1, hint_msg)
+        self.hint_sizer.AddMany([(self.hint_txt, 0, wx.LEFT, 15)])
+
+    def _layout_button(self):  
+        """
+        Do the layout for the button widgets
+        """ 
+        id = wx.NewId()
+        self.bt_help = wx.Button(self, id, 'HELP')
+        self.bt_help.Bind(wx.EVT_BUTTON, self.on_help)
+        self.bt_help.SetToolTipString("Help using the Kiessig fringe calculator.")
+
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this window.")
+        self.button_sizer.AddMany([(self.bt_help, 0, wx.LEFT, 260),
+                                   (self.bt_close, 0, wx.LEFT, 20)])
+
+    def _do_layout(self):
+        """
+            Draw window content
+        """
+        self._define_structure()
+        self._layout_dq_name()
+        self._layout_thickness_size()
+        self._layout_hint()
+        self._layout_button()
+        self.boxsizer_source.AddMany([(self.dq_name_sizer, 0,
+                                       wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
+                                      (self.thickness_size_sizer, 0,
+                                       wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
+                                      (self.hint_sizer, 0,
+                                       wx.EXPAND|wx.TOP|wx.BOTTOM, 5)])
+        self.main_sizer.AddMany([(self.boxsizer_source, 0, wx.ALL, 10),
+                                 (self.button_sizer, 0,
+                                  wx.EXPAND|wx.TOP|wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def on_help(self, event):
+        """
+        Bring up the Kiessig fringe calculator Documentation whenever
+        the HELP button is clicked.
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+        _TreeLocation = "user/perspectives/calculator/kiessig_calculator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "Density/Volume Calculator Help")
+
+    def on_close(self, event):
+        """
+        close the window containing this panel
+        """
+        self.parent.Close()
+        if event is not None:
+            event.Skip()
+
+    def on_compute(self, event):
+        """
+        Execute the computation of thickness
+        """
+        # skip for another event
+        if event != None:
+            event.Skip()
+        dq = self.dq_name_tcl.GetValue()
+        self.kiessig.set_deltaq(dq)
+        # calculate the thickness 
+        output = self.kiessig.compute_thickness()
+        thickness = self.format_number(output)
+        # set tcl
+        self.thickness_size_tcl.SetValue(str(thickness))
+
+    def format_number(self, value=None):
+        """
+        Return a float in a standardized, human-readable formatted string
+        """
+        try:
+            value = float(value)
+        except:
+            output = None
+            return output
+
+        output = "%-7.4g" % value
+        return output.lstrip().rstrip()
+    
+    def _onparamEnter(self, event = None):
+        """
+        On Text_enter_callback, perform compute
+        """
+        self.on_compute(event)
+
+class KiessigWindow(widget.CHILD_FRAME):
+    def __init__(self, parent=None, manager=None, 
+                 title="Kiessig Thickness Calculator",
+                 size=(PANEL_WIDTH,PANEL_HEIGHT), *args, **kwds):
+        kwds['title'] = title
+        kwds['size'] = size
+        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self.panel = KiessigThicknessCalculatorPanel(parent=self)
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show(True)
+        
+    def on_close(self, event):
+        """
+        Close event
+        """
+        if self.manager != None:
+            self.manager.kiessig_frame = None
+        self.Destroy()
+
+if __name__ == "__main__":
+    app = wx.PySimpleApp()
+    widget.CHILD_FRAME = wx.Frame
+    frame = KiessigWindow()
+    frame.Show(True)
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/load_thread.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/load_thread.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/load_thread.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,89 @@
+"""
+    Thread handler used to load data
+"""
+import time
+from sas.sascalc.data_util.calcthread import CalcThread
+from sas.sascalc.dataloader.loader import Loader
+
+class DataReader(CalcThread):
+    """
+        Load a data given a filename
+    """
+    def __init__(self, path, completefn=None,
+                 updatefn=None,
+                 yieldtime=0.01,
+                 worktime=0.01
+                 ):
+        CalcThread.__init__(self, completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.path = path
+        #Instantiate a loader 
+        self.loader = Loader()
+        self.starttime = 0
+
+    def isquit(self):
+        """
+             @raise KeyboardInterrupt: when the thread is interrupted
+        """
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            raise KeyboardInterrupt
+
+
+    def compute(self):
+        """
+            read some data
+        """
+        self.starttime = time.time()
+        try:
+            data = self.loader.load(self.path)
+            self.complete(data=data)
+        except KeyboardInterrupt:
+            # Thread was interrupted, just proceed and re-raise.
+            # Real code should not print, but this is an example...
+            raise
+
+class GenReader(CalcThread):
+    """
+        Load a sld data given a filename
+    """
+    def __init__(self, path, loader,
+                 completefn=None,
+                 updatefn=None,
+                 yieldtime=0.01,
+                 worktime=0.01
+                 ):
+        CalcThread.__init__(self, completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.path = path
+        #Instantiate a loader 
+        self.loader = loader
+        self.starttime = 0
+
+    def isquit(self):
+        """
+             @raise KeyboardInterrupt: when the thread is interrupted
+        """
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            raise KeyboardInterrupt
+
+
+    def compute(self):
+        """
+            read some data
+        """
+        self.starttime = time.time()
+        try:
+            data = self.loader.read(self.path)
+            self.complete(data=data)
+        except:
+            # Thread was interrupted, just proceed and re-raise.
+            # Real code should not print, but this is an example...
+            raise
Index: src/sas/sasgui/perspectives/calculator/media/data_operator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/data_operator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/data_operator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,55 @@
+.. data_operator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+Data Operations Tool
+====================
+
+Description
+-----------
+
+This tool permits arithmetic operations between two data sets. Alternatively, 
+the last data set can be a number.
+
+*NOTE! When* Data1 *and* Data2 *are both data, their Q (or Qx and Qy for 2D) 
+value(s) must match with each other UNLESS using the 'append' operator.*
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+1) Ensure you have loaded data into the *Data Explorer* (see :ref:`Loading_data`).
+
+2) Select *Data Operation* from the *Tool* menu on the SasView toolbar.
+
+3) Select a dataset/theory in the drop-down menu *Data1*. A mini-plot of the
+   data will appear underneath.
+
+4) Select a dataset/theory in the drop-down menu *Data2* or select *Number* 
+   and enter a number in the box that appears alongside.
+
+5) Select an arithmetic operator symbol from the *Operator* drop-down. The 
+   available operators are:
+   
+*   \+ (for addition)
+*   \- (for subtraction) 
+*   \* (for multiplication)
+*   \/ (for division)
+*   \| (for combination of two data sets)
+
+   If two data sets do not match, the operation will fail and the background 
+   color of the combo box items will turn to red (WIN only).
+
+6) If the operation is successful, hit the Apply button to make the new dataset.
+   The new dataset will appear in the *Data Explorer*.
+
+*NOTE! Any errors and warnings will be displayed at the bottom of the SasView
+window.*
+
+.. image:: data_oper_pic.png
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
Index: src/sas/sasgui/perspectives/calculator/media/density_calculator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/density_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/density_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,35 @@
+.. density_calculator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+Density/Volume Calculator Tool
+==============================
+
+Description
+-----------
+
+This tool calculates the mass density from the molar volume or vice 
+versa. To calculate the mass density, the chemical formula and molar volume 
+should be provided.
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+1) Select *Density/Volume Calculator* from the *Tool* menu on the SasView toolbar.
+
+2) Enter the empirical formula of a molecule. For mixtures, the ratio of each 
+   of the molecules should be used, for example, (H2O)0.5(D2O)0.5.
+
+3) Use the input combo box to choose between molar volume or mass density and 
+   then type in an input value.
+
+4) Click the 'Calculate' button to perform the calculation.
+
+.. image:: density_tutor.gif
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
Index: src/sas/sasgui/perspectives/calculator/media/gen_sas_help.html
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/gen_sas_help.html	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/gen_sas_help.html	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,147 @@
+<body>
+<h4>Generic Scattering Calculator:</h4>
+Polarization and Magnetic Scattering
+<br>
+<br>
+<ul>
+<li><a href="#theory">Theory:</a></li>
+<li><a href="#gui">GUI</a></li>
+<li><a href="#pdb">PDB Data</a></li>
+</ul>
+<br>
+<br>
+<b>1. <a name="theory">Theory</a> </b>
+<br>
+In general, a particle with a volume V can be described by an ensemble containing N 
+3-dimensional rectangular pixels where each pixels are much smaller than V. 
+Assuming that
+all the pixel sizes are same, the elastic scattering intensity 
+by the particle 
+<p>
+<img src="gen_i.gif"/>
+</p>
+<br>
+where &#946;<sub>j</sub> and r<sub>j</sub> are the scattering 
+length density and the position 
+of the j'th pixel respectively. And the total volume
+<p>
+<img src="v_j.gif"/>
+</p>
+<br>
+for &#946;<sub>j</sub> &#8800; 0 where v<sub>j</sub> is the volume of the j'th pixel 
+(or the j'th natural atomic volume (= atomic mass/natural molar density/Avogadro number) 
+ for the atomic structures). The total volume V can be corrected by users. 
+This correction is useful especially for an atomic structure (taken from a pdb file) to get the right 
+normalization. Note that the  &#946;<sub>j</sub> displayed in GUI may be incorrect but will not 
+affect the scattering computation if the correction of the total volume is made.
+<br>
+The scattering length density (SLD) of each pixel where the SLD is uniform, is a combination of the nuclear and magnetic SLDs 
+and depends on the spin states of the neutrons as follows:
+<br>
+<br>
+For magnetic scattering, only the magnetization component, <b>M</b><sub>perp</sub>, 
+perpendicular to the scattering vector <b>Q</b> contributes to the the magnetic 
+scattering length. (Figure below).
+<p>
+<img src="mag_vector.bmp"/>
+</p>
+<br>
+The magnetic scattering length density is then
+<p>
+<img src="dm_eq.gif"/>
+</p>
+<br>
+where &#947; = -1.913 the gyromagnetic ratio,  	&#956;<sub>B</sub> is the Bohr magneton, 
+r<sub>0</sub> is the classical radius of electron, 
+and <b>&#963;</b> is the Pauli spin.
+<br>
+For polarized neutron, the magnetic scattering is depending on the spin states.
+Let's consider that the incident neutrons are polarized parallel (+)/anti-parallel 
+(&#8211;) to the x' axis (See both Figures above).
+The possible out-coming states then are + and - states for both incident states.
+<br>
+ - Non-spin-flips:  	(+ +) and  	(- -)
+<br>
+ - Spin-flips:  	(+ -) and  	(- +)
+<br>
+<p>
+<img src="gen_mag_pic.bmp"/>
+</p>
+<br>
+<br>
+Now, let's assume that the angles of the <b>Q</b> vector and the spin-axis (x') from x-axis 
+are &#966; and  &#952;<sub>up</sub>, respectively (See Figure above).
+Then, depending upon the polarization (spin) state of neutrons, the scattering length
+densities , including the nuclear scattering length density (&#946; <sub>N</sub>) are given as, for non-spin-flips,
+<p>
+<img src="sld1.gif"/>
+</p>
+<br>
+<br>
+for spin-flips,
+<p>
+<img src="sld2.gif"/>
+</p>
+<br>
+<br>
+where
+<p>
+<img src="mxp.gif"/>
+</p>
+<p>
+<img src="myp.gif"/>
+</p>
+<p>
+<img src="mzp.gif"/>
+</p>
+<p>
+<img src="mqx.gif"/>
+</p>
+<p>
+<img src="mqy.gif"/>
+</p>
+<br>
+<br>
+Here, the M<sub>0x</sub>, M<sub>0y</sub> and M<sub>0z</sub> are the x, y and z 
+components of the magnetization vector given in the xyz lab frame.
+<br>
+<br>
+<b>2. <a name="gui">GUI</a> </b>
+<br>
+<p>
+<img src="gen_gui_help.bmp"/>
+</p>
+<br>
+<p>
+After the computation, the result will be listed in the 'Theory' box 
+in the data explorer panel on the main window.
+<br>
+The 'Up_frac_in' and 'Up_frac_out' are the ratio, (spin up) /(spin up + spin down) neutrons 
+before the sample and at the analyzer, respectively.
+</p>
+<br>
+*Note I: The values of 'Up_frac_in' and 'Up_frac_out' must be in the range between 0 and 1.
+For example, both values are 0.5 for unpolarized neutrons.
+<br>
+*Note II: This computation is totally based on the pixel (or atomic) data fixed
+in the xyz coordinates. Thus no angular orientational averaging is considered.
+<br>
+*Note III: For the nuclear scattering length density, only the real component is taken account.
+<br>
+<br>
+<b>3. <a name="pdb">PDB Data</a> </b>
+<br>
+This Generic scattering calculator also supports some pdb files without considering polarized/magnetic scattering
+ so that the related parameters such as Up_*** will be ignored (see the Picture below). The calculation for fixed orientation uses (the first) Equation above resulting 
+in a 2D output, whileas the scattering calculation averaged over all the orientations uses the Debye equation providing a 1D output:
+ <p>
+<img src="gen_debye_eq.gif"/>
+</p>
+<br>
+where v<sub>j</sub>&#946;<sub>j</sub> &#8801; b<sub>j</sub> is the scattering length of the j'th atom.
+The resultant outputs will be displayed in the DataExporer for further uses. 
+<br>
+ <p>
+<img src="pdb_combo.jpg"/>
+</p>
+</body>
Index: src/sas/sasgui/perspectives/calculator/media/image_viewer_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/image_viewer_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/image_viewer_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,55 @@
+.. image_viewer_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+Image Viewer Tool
+=================
+
+Description
+-----------
+
+This tool loads image files and displays them as 2D (x-y coordinate against 
+counts per pixel). The plot can then can be saved, printed, and copied. The 
+plot can also be resized by dragging the corner of the panel.
+
+The supported input image formats are:
+
+*  BMP (bitmap format)
+*  GIF (graphical interchange format)
+*  JPG (joint photographic experts group format)
+*  PNG (portable network graphics format)
+*  TIF (tagged image format)
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+1) Select *Image Viewer* from the *Tool* menu on the SasView toolbar.
+
+2) Select a file and then click *Open*. If the loading is successful the image 
+   will be displayed.
+
+.. image:: load_image.bmp
+
+3) To save, print, or copy the image, or to apply a grid overlay, right-click 
+   anywhere in the plot.
+
+.. image:: pic_plot.bmp
+
+4. If the image is taken from a 2D detector, SasView can attempt to convert 
+   the colour/grey scale into pseudo-intensity 2D data using 
+
+   z = (0.299 x R) + (0.587 x G) + (0.114 x B)
+
+   unless the image is formatted as 8-bit grey-scale TIF.
+
+5. In the *Convert to Data* dialog, set the parameters relevant to the data and 
+   then click the OK.
+
+.. image:: pic_convert.bmp
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
Index: src/sas/sasgui/perspectives/calculator/media/kiessig_calculator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/kiessig_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/kiessig_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,32 @@
+.. kiessig_calculator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+.. |pi| unicode:: U+03C0
+.. |Ang| unicode:: U+212B
+
+Kiessig Thickness Calculator Tool
+=================================
+
+Description
+-----------
+
+This tool is approximately estimates the thickness of a layer or the diameter 
+of particles from the position of the Kiessig fringe/Bragg peak in NR/SAS data 
+using the relation
+
+(thickness *or* size) = 2 * |pi| / (fringe_width *or* peak position)
+  
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+To get a rough thickness or particle size, simply type the fringe or peak 
+position (in units of 1/|Ang|\) and click on the *Compute* button.
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
+
Index: src/sas/sasgui/perspectives/calculator/media/python_shell_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/python_shell_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/python_shell_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,38 @@
+.. python_shell_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+Python Shell Tool
+=================
+
+Description 
+-----------
+
+This is a Python shell/editor (PyCrust) provided with WxPython. An editing 
+notebook will show up when a Python file is created/loaded with the *New* or 
+*Open* options on the menu. 
+
+*Run* enables the editor to compile and run the Python code.
+
+For the details about the Python, visit the website http://docs.python.org/tutorial/
+
+The NumPy, SciPy, and Matplotlib, etc, libraries are shipped with SasView. 
+However, some functionality may not work.
+
+PyCrust has its own Help.
+
+*NOTE! The Help() and Credits() calls do not work on Macs.*
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Example
+-------
+
+An example calling the Matplotlib plotting gallery:
+
+.. image:: pycrust_example.png
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 19Feb2015
Index: src/sas/sasgui/perspectives/calculator/media/resolution_calculator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/resolution_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/resolution_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,107 @@
+.. resolution_calculator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+.. |pi| unicode:: U+03C0
+.. |lambda| unicode:: U+03BB
+.. |Ang| unicode:: U+212B
+
+Q Resolution Estimator Tool
+===========================
+
+Description
+-----------
+
+This tool is approximately estimates the resolution of Q from SAS instrumental 
+parameter values assuming that the detector is flat and normal to the 
+incident beam.
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+1) Select *SAS Resolution Estimator* from the *Tool* menu on the SasView toolbar.
+
+2) Select the source (Neutron or Photon) and source type (Monochromatic or TOF).
+
+   *NOTE! The computational difference between the sources is only the 
+   gravitational contribution due to the mass of the particles.*
+
+3) Change the default values of the instrumental parameters as required. Be 
+   careful to note that distances are specified in cm!
+
+4) Enter values for the source wavelength(s), |lambda|\ , and its spread (= FWHM/|lambda|\ ).
+   
+   For monochromatic sources, the inputs are just one value. For TOF sources, 
+   the minimum and maximum values should be separated by a '-' to specify a 
+   range.
+   
+   Optionally, the wavelength (BUT NOT of the wavelength spread) can be extended 
+   by adding '; nn' where the 'nn' specifies the number of the bins for the 
+   numerical integration. The default value is nn = 10. The same number of bins 
+   will be used for the corresponding wavelength spread.
+
+5) For TOF, the default wavelength spectrum is flat. A custom spectral 
+   distribution file (2-column text: wavelength (|Ang|\) vs Intensity) can also 
+   be loaded by selecting *Add new* in the combo box.
+
+6) When ready, click the *Compute* button. Depending on the computation the 
+   calculation time will vary.
+
+7) 1D and 2D dQ values will be displayed at the bottom of the panel, and a 2D 
+   resolution weight distribution (a 2D elliptical Gaussian function) will also 
+   be displayed in the plot panel even if the Q inputs are outside of the 
+   detector limit (the red lines indicate the limits of the detector).
+   
+   TOF only: green lines indicate the limits of the maximum Q range accessible 
+   for the longest wavelength due to the size of the detector.
+    
+   Note that the effect from the beam block/stop is ignored, so in the small Q 
+   region near the beam block/stop 
+
+   [ie., Q < 2. |pi|\ .(beam block diameter) / (sample-to-detector distance) / |lambda|\_min] 
+
+   the variance is slightly under estimated.
+
+8) A summary of the calculation is written to the SasView *Console* at the 
+   bottom of the main SasView window.
+
+.. image:: resolution_tutor.gif
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Theory
+------
+
+The scattering wave transfer vector is by definition
+
+.. image:: q.gif
+
+In the small-angle limit, the variance of Q is to a first-order 
+approximation
+
+.. image:: sigma_q.gif
+
+The geometric and gravitational contributions can then be summarised as
+
+.. image:: sigma_table.gif
+
+Finally, a Gaussian function is used to describe the 2D weighting distribution 
+of the uncertainty in Q.
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+References
+----------
+
+D.F.R. Mildner and J.M. Carpenter 
+*J. Appl. Cryst.* 17 (1984) 249-256
+
+D.F.R. Mildner, J.M. Carpenter and D.L. Worcester 
+*J. Appl. Cryst.* 19 (1986) 311-319
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
Index: src/sas/sasgui/perspectives/calculator/media/sas_calculator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/sas_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/sas_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,166 @@
+.. sas_calculator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+.. |beta| unicode:: U+03B2
+.. |gamma| unicode:: U+03B3
+.. |theta| unicode:: U+03B8
+.. |mu| unicode:: U+03BC
+.. |sigma| unicode:: U+03C3
+.. |phi| unicode:: U+03C6
+
+.. |equiv| unicode:: U+2261
+.. |noteql| unicode:: U+2260
+
+Generic SANS Calculator Tool
+============================
+
+Description
+-----------
+
+This tool attempts to simulate the SANS expected from a specified
+shape/structure or scattering length density profile. The tool can
+handle both nuclear and magnetic contributions to the scattering.
+
+Theory
+------
+
+In general, a particle with a volume *V* can be described by an ensemble 
+containing *N* 3-dimensional rectangular pixels where each pixel is much 
+smaller than *V*.
+
+Assuming that all the pixel sizes are the same, the elastic scattering 
+intensity from the particle is
+
+.. image:: gen_i.gif
+
+Equation 1.
+
+where |beta|\ :sub:`j` and *r*\ :sub:`j` are the scattering length density and 
+the position of the j'th pixel respectively.
+
+The total volume *V*
+
+.. image:: v_j.gif
+
+for |beta|\ :sub:`j` |noteql|\0 where *v*\ :sub:`j` is the volume of the j'th 
+pixel (or the j'th natural atomic volume (= atomic mass / (natural molar 
+density * Avogadro number) for the atomic structures).
+
+*V* can be corrected by users. This correction is useful especially for an 
+atomic structure (such as taken from a PDB file) to get the right normalization. 
+
+*NOTE!* |beta|\ :sub:`j` *displayed in the GUI may be incorrect but this will not 
+affect the scattering computation if the correction of the total volume V is made.*
+
+The scattering length density (SLD) of each pixel, where the SLD is uniform, is 
+a combination of the nuclear and magnetic SLDs and depends on the spin states 
+of the neutrons as follows.
+
+Magnetic Scattering
+^^^^^^^^^^^^^^^^^^^
+
+For magnetic scattering, only the magnetization component, *M*\ :sub:`perp`\ , 
+perpendicular to the scattering vector *Q* contributes to the magnetic 
+scattering length.
+
+.. image:: mag_vector.bmp
+
+The magnetic scattering length density is then
+
+.. image:: dm_eq.gif
+
+where the gyromagnetic ratio |gamma| = -1.913, |mu|\ :sub:`B` is the Bohr 
+magneton, *r*\ :sub:`0` is the classical radius of electron, and |sigma| is the 
+Pauli spin.
+
+For a polarized neutron, the magnetic scattering is depending on the spin states.
+
+Let us consider that the incident neutrons are polarised both parallel (+) and  
+anti-parallel (-) to the x' axis (see below). The possible states after 
+scattering from the sample are then 
+
+*  Non-spin flips: (+ +) and (- -)
+*  Spin flips:     (+ -) and (- +)
+
+.. image:: gen_mag_pic.bmp
+
+Now let us assume that the angles of the *Q* vector and the spin-axis (x') 
+to the x-axis are |phi| and |theta|\ :sub:`up` respectively (see above). Then, 
+depending upon the polarization (spin) state of neutrons, the scattering 
+length densities, including the nuclear scattering length density (|beta|\ :sub:`N`\ ) 
+are given as
+
+*  for non-spin-flips
+
+   .. image:: sld1.gif
+
+*  for spin-flips
+
+   .. image:: sld2.gif
+
+where
+
+.. image:: mxp.gif
+
+.. image:: myp.gif
+
+.. image:: mzp.gif
+
+.. image:: mqx.gif
+
+.. image:: mqy.gif
+
+Here the *M0*\ :sub:`x`\ , *M0*\ :sub:`y` and *M0*\ :sub:`z` are the x, y and z 
+components of the magnetisation vector in the laboratory xyz frame. 
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+.. image:: gen_gui_help.bmp
+
+After computation the result will appear in the *Theory* box in the SasView  
+*Data Explorer* panel.
+
+*Up_frac_in* and *Up_frac_out* are the ratio 
+
+   (spin up) / (spin up + spin down)
+   
+of neutrons before the sample and at the analyzer, respectively.
+
+*NOTE 1. The values of* Up_frac_in *and* Up_frac_out *must be in the range 
+0.0 to 1.0. Both values are 0.5 for unpolarized neutrons.*
+
+*NOTE 2. This computation is totally based on the pixel (or atomic) data fixed 
+in xyz coordinates. No angular orientational averaging is considered.*
+
+*NOTE 3. For the nuclear scattering length density, only the real component 
+is taken account.*
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using PDB/OMF or SLD files
+--------------------------
+
+The SANS Calculator tool can read some PDB, OMF or SLD files but ignores
+polarized/magnetic scattering when doing so, thus related parameters such as 
+*Up_frac_in*, etc, will be ignored.
+
+The calculation for fixed orientation uses Equation 1 above resulting in a 2D 
+output, whereas the scattering calculation averaged over all the orientations 
+uses the Debye equation below providing a 1D output
+
+.. image:: gen_debye_eq.gif
+
+where *v*\ :sub:`j` |beta|\ :sub:`j` |equiv| *b*\ :sub:`j` is the scattering 
+length of the j'th atom. The calculation output is passed to the *Data Explorer* 
+for further use.
+
+.. image:: pdb_combo.jpg
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
Index: src/sas/sasgui/perspectives/calculator/media/sld_calculator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/sld_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/sld_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,58 @@
+.. sld_calculator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+.. |Ang| unicode:: U+212B
+
+SLD Calculator Tool
+===================
+
+Description
+-----------
+
+The neutron scattering length density (SLD) is defined as
+
+  SLD = (b_c1 + b_c2 + ... + b_cn) / Vm
+
+where b_ci is the bound coherent scattering length of ith of n atoms in a molecule
+with the molecular volume Vm
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Specifying the Compound Name
+----------------------------
+
+To calculate scattering length densities enter the empirical formula of a 
+compound and its mass density and click "Calculate".
+
+Entering a wavelength value is optional (a default value of 6.0 |Ang| will 
+be used).
+
+TIPS!
+
+*  Formula strings consist of atoms and the number of them, such as "CaCO3+6H2O".
+
+*  Groups can be separated by *'+'* or *space*, so "CaCO3 6H2O" works as well.
+
+*  Groups can be defined using parentheses, such as "CaCO3(H2O)6".
+
+*  Parentheses can be nested, such as "(CaCO3(H2O)6)1".
+
+*  Isotopes are represented by their atomic number in *square brackets*, such 
+   as "CaCO[18]3+6H2O", H[1], or H[2].
+
+*  Numbers of atoms can be integer or decimal, such as "CaCO3+(3HO0.5)2".
+
+*  The SLD of mixtures can be calculated as well. For example, for a 70-30 
+   mixture of H2O/D2O write "H14O7+D6O3" or more simply "H7D3O5" (i.e. this says
+   7 hydrogens, 3 deuteriums, and 5 oxygens) and enter a mass density calculated
+   on the percentages of H2O and D2O.
+
+*  Type "C[13]6 H[2]12 O[18]6" for C(13)6H(2)12O(18)6 (6 Carbon-13 atoms, 12 
+   deuterium atoms, and 6 Oxygen-18 atoms).
+   
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
+
Index: src/sas/sasgui/perspectives/calculator/media/slit_calculator_help.rst
===================================================================
--- src/sas/sasgui/perspectives/calculator/media/slit_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/media/slit_calculator_help.rst	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,43 @@
+.. slit_calculator_help.rst
+
+.. This is a port of the original SasView html help file to ReSTructured text
+.. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.
+
+.. |Ang| unicode:: U+212B
+.. |TM| unicode:: U+2122
+
+Slit Size Calculator Tool
+=========================
+
+Description
+-----------
+
+This tool enables X-ray users to calculate the slit size (FWHM/2) for smearing 
+based on their half beam profile data.
+
+*NOTE! Whilst it may have some more generic applicability, the calculator has
+only been tested with beam profile data from Anton-Paar SAXSess*\ |TM|\  
+*software.*
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+Using the tool
+--------------
+
+1) Select *Slit Size Calculator* from the *Tool* menu on the SasView toolbar.
+
+2) Load a beam profile file in the *Data* field using the *Browse* button.
+
+   *NOTE! To see an example of the beam profile file format, visit the file 
+   beam profile.DAT in your {installation_directory}/SasView/test folder.*
+
+3) Once a data is loaded, the slit size is automatically computed and displayed 
+   in the tool window.
+
+*NOTE! The beam profile file does not carry any information about the units of 
+the Q data. This calculator assumes the data has units of 1/\ |Ang|\ . If the
+data is not in these units it must be manually converted beforehand.*
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+.. note::  This help document was last changed by Steve King, 01May2015
Index: src/sas/sasgui/perspectives/calculator/model_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/model_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/model_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,1596 @@
+'''
+This module provides three model editor classes: the composite model editor,
+the easy editor which provides a simple interface with tooltip help to enter
+the parameters of the model and their default value and a panel to input a
+function of y (usually the intensity).  It also provides a drop down of
+standard available math functions.  Finally a full python editor panel for
+complete customizatin is provided.
+
+:TODO the writiong of the file and name checking (and maybe some other
+funtions?) should be moved to a computational module which could be called
+fropm a python script.  Basically one just needs to pass the name,
+description text and function text (or in the case of the composite editor
+the names of the first and second model and the operator to be used).
+'''
+
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+import wx
+import sys
+import os
+import math
+import re
+from wx.py.editwindow import EditWindow
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+
+if sys.platform.count("win32") > 0:
+    FONT_VARIANT = 0
+    PNL_WIDTH = 450
+    PNL_HEIGHT = 320
+else:
+    FONT_VARIANT = 1
+    PNL_WIDTH = 590
+    PNL_HEIGHT = 350
+M_NAME = 'Model'
+EDITOR_WIDTH = 800
+EDITOR_HEIGTH = 735
+PANEL_WIDTH = 500
+_BOX_WIDTH = 55
+
+
+def _compile_file(path):
+    """
+    Compile the file in the path
+    """
+    try:
+        import py_compile
+        py_compile.compile(file=path, doraise=True)
+        return ''
+    except:
+        _, value, _ = sys.exc_info()
+        return value
+
+def _delete_file(path):
+    """
+    Delete file in the path
+    """
+    try:
+        os.remove(path)
+    except:
+        raise
+
+
+class TextDialog(wx.Dialog):
+    """
+    Dialog for easy custom composite models.  Provides a wx.Dialog panel
+    to choose two existing models (including pre-existing custom models which
+    may themselves be composite models) as well as an operation on those models
+    (add or multiply) the resulting model will add a scale parameter for summed
+    models and a background parameter for a multiplied model.
+
+    The user also gives a brief help for the model in a description box and
+    must provide a unique name which is verified as unique before the new
+    model is saved.
+
+    This Dialog pops up for the user when they press 'Sum|Multi(p1,p2)' under
+    'Edit Custom Model' under 'Fitting' menu.  This is currently called as
+    a Modal Dialog.
+
+    :TODO the build in compiler currently balks at when it tries to import
+    a model whose name contains spaces or symbols (such as + ... underscore
+    should be fine).  Have fixed so the editor cannot save such a file name
+    but if a file is dropped in the plugin directory from outside this class
+    will create a file that cannot be compiled.  Should add the check to
+    the write method or to the on_modelx method.
+
+    - PDB:April 5, 2015
+    """
+    def __init__(self, parent=None, base=None, id=None, title='',
+                 model_list=[], plugin_dir=None):
+        """
+        This class is run when instatiated.  The __init__ initializes and
+        calls the internal methods necessary.  On exiting the wx.Dialog
+        window should be destroyed.
+        """
+        wx.Dialog.__init__(self, parent=parent, id=id,
+                           title=title, size=(PNL_WIDTH, PNL_HEIGHT))
+        self.parent = base
+        #Font
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        # default
+        self.overwrite_name = False
+        self.plugin_dir = plugin_dir
+        self.model_list = model_list
+        self.model1_string = "SphereModel"
+        self.model2_string = "CylinderModel"
+        self.name = 'Sum' + M_NAME
+        self.factor = 'scale_factor'
+        self._notes = ''
+        self._operator = '+'
+        self._operator_choice = None
+        self.explanation = ''
+        self.explanationctr = None
+        self.type = None
+        self.name_sizer = None
+        self.name_tcl = None
+        self.desc_sizer = None
+        self.desc_tcl = None
+        self._selection_box = None
+        self.model1 = None
+        self.model2 = None
+        self.static_line_1 = None
+        self.ok_button = None
+        self.close_button = None
+        self._msg_box = None
+        self.msg_sizer = None
+        self.fname = None
+        self.cm_list = None
+        self.is_p1_custom = False
+        self.is_p2_custom = False
+        self._build_sizer()
+        self.model1_name = str(self.model1.GetValue())
+        self.model2_name = str(self.model2.GetValue())
+        self.good_name = True
+        self.fill_oprator_combox()
+
+    def _layout_name(self):
+        """
+        Do the layout for file/function name related widgets
+        """
+        #container for new model name input
+        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        #set up label and input box with tool tip and event handling
+        name_txt = wx.StaticText(self, -1, 'Function Name : ')
+        self.name_tcl = wx.TextCtrl(self, -1, value='MySumFunction')
+        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
+        hint_name = "Unique Sum/Multiply Model Function Name."
+        self.name_tcl.SetToolTipString(hint_name)
+
+        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT | wx.TOP, 10),
+                                 (self.name_tcl, -1,
+                                  wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM,
+                                  10)])
+
+
+    def _layout_description(self):
+        """
+        Do the layout for description related widgets
+        """
+        #container for new model description input
+        self.desc_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        #set up description label and input box with tool tip and event handling
+        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')
+        self.desc_tcl = wx.TextCtrl(self, -1)
+        hint_desc = "Write a short description of this model function."
+        self.desc_tcl.SetToolTipString(hint_desc)
+
+        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT | wx.TOP, 10),
+                                 (self.desc_tcl, -1,
+                                  wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM,
+                                  10)])
+
+
+    def _layout_model_selection(self):
+        """
+        Do the layout for model selection related widgets
+        """
+        box_width = 195 # combobox width
+
+        #First set up main sizer for the selection
+        selection_box_title = wx.StaticBox(self, -1, 'Select',
+                                           size=(PNL_WIDTH - 30, 70))
+        self._selection_box = wx.StaticBoxSizer(selection_box_title,
+                                                wx.VERTICAL)
+
+        #Next create the help labels for the model selection
+        select_help_box = wx.BoxSizer(wx.HORIZONTAL)
+        model_string = " Model%s (p%s):"
+        select_help_box.Add(wx.StaticText(self, -1, model_string % (1, 1)),
+                            0, 0)
+        select_help_box.Add((box_width - 25, 10), 0, 0)
+        select_help_box.Add(wx.StaticText(self, -1, model_string % (2, 2)),
+                            0, 0)
+        self._selection_box.Add(select_help_box, 0, 0)
+
+        #Next create the actual selection box with 3 combo boxes
+        selection_box_choose = wx.BoxSizer(wx.HORIZONTAL)
+
+        self.model1 = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.model1, -1, self.on_model1)
+        self.model1.SetMinSize((box_width * 5 / 6, -1))
+        self.model1.SetToolTipString("model1")
+
+        self._operator_choice = wx.ComboBox(self, -1, size=(50, -1),
+                                            style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self._operator_choice, -1, self.on_select_operator)
+        operation_tip = "Add: +, Multiply: * "
+        self._operator_choice.SetToolTipString(operation_tip)
+
+        self.model2 = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.model2, -1, self.on_model2)
+        self.model2.SetMinSize((box_width * 5 / 6, -1))
+        self.model2.SetToolTipString("model2")
+        self._set_model_list()
+
+        selection_box_choose.Add(self.model1, 0, 0)
+        selection_box_choose.Add((15, 10))
+        selection_box_choose.Add(self._operator_choice, 0, 0)
+        selection_box_choose.Add((15, 10))
+        selection_box_choose.Add(self.model2, 0, 0)
+        # add some space between labels and selection
+        self._selection_box.Add((20, 5), 0, 0)
+        self._selection_box.Add(selection_box_choose, 0, 0)
+
+    def _build_sizer(self):
+        """
+        Build GUI with calls to _layout_name, _layout Description
+        and _layout_model_selection which each build a their portion of the
+        GUI.
+        """
+        mainsizer = wx.BoxSizer(wx.VERTICAL) # create main sizer for dialog
+
+        # build fromm top by calling _layout_name and _layout_description
+        # and adding to main sizer
+        self._layout_name()
+        mainsizer.Add(self.name_sizer, 0, wx.EXPAND)
+        self._layout_description()
+        mainsizer.Add(self.desc_sizer, 0, wx.EXPAND)
+
+        # Add an explanation of dialog (short help)
+        self.explanationctr = wx.StaticText(self, -1, self.explanation)
+        self.fill_explanation_helpstring(self._operator)
+        mainsizer.Add(self.explanationctr, 0, wx.LEFT | wx.EXPAND, 15)
+
+        # Add the selection box stuff with border and labels built
+        # by _layout_model_selection
+        self._layout_model_selection()
+        mainsizer.Add(self._selection_box, 0, wx.LEFT, 15)
+
+        # Add a space and horizontal line before the notification
+        #messages and the buttons at the bottom
+        mainsizer.Add((10, 10))
+        self.static_line_1 = wx.StaticLine(self, -1)
+        mainsizer.Add(self.static_line_1, 0, wx.EXPAND, 10)
+
+        # Add action status notification line (null at startup)
+        self._msg_box = wx.StaticText(self, -1, self._notes)
+        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 0)
+        mainsizer.Add(self.msg_sizer, 0,
+                      wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE | wx.BOTTOM, 10)
+
+        # Finally add the buttons (apply and close) on the bottom
+        # Eventually need to add help here
+        self.ok_button = wx.Button(self, wx.ID_OK, 'Apply')
+        _app_tip = "Save the new Model."
+        self.ok_button.SetToolTipString(_app_tip)
+        self.ok_button.Bind(wx.EVT_BUTTON, self.check_name)
+        self.help_button = wx.Button(self, -1, 'HELP')
+        _app_tip = "Help on composite model creation."
+        self.help_button.SetToolTipString(_app_tip)
+        self.help_button.Bind(wx.EVT_BUTTON, self.on_help)
+        self.close_button = wx.Button(self, wx.ID_CANCEL, 'Close')
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_button.AddMany([((20, 20), 1, 0),
+                              (self.ok_button, 0, 0),
+                              (self.help_button, 0, 0),
+                              (self.close_button, 0, wx.LEFT | wx.RIGHT, 10)])
+        mainsizer.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
+
+        self.SetSizer(mainsizer)
+        self.Centre()
+
+    def on_change_name(self, event=None):
+        """
+        Change name
+        """
+        if event is not None:
+            event.Skip()
+        self.name_tcl.SetBackgroundColour('white')
+        self.Refresh()
+
+    def check_name(self, event=None):
+        """
+        Check that proposed new model name is a valid Python module name
+        and that it does not already exist. If not show error message and
+        pink background in text box else call on_apply
+
+        :TODO this should be separated out from the GUI code.  For that we
+        need to pass it the name (or if we want to keep the default name
+        option also need to pass the self._operator attribute) We just need
+        the function to return an error code that the name is good or if
+        not why (not a valid name, name exists already).  The rest of the
+        error handling should be done in this module. so on_apply would then
+        start by checking the name and then either raise errors or do the
+        deed.
+        """
+        #Get the function/file name
+        mname = M_NAME
+        self.on_change_name(None)
+        title = self.name_tcl.GetValue().lstrip().rstrip()
+        if title == '':
+            text = self._operator
+            if text.count('+') > 0:
+                mname = 'Sum'
+            else:
+                mname = 'Multi'
+            mname += M_NAME
+            title = mname
+        self.name = title
+        t_fname = title + '.py'
+
+        #First check if the name is a valid Python name
+        if re.match('^[A-Za-z0-9_]*$', title):
+            self.good_name = True
+        else:
+            self.good_name = False
+            msg = ("%s is not a valid Python name. Only alphanumeric \n" \
+                   "and underscore allowed" % self.name)
+
+        #Now check if the name already exists
+        if not self.overwrite_name and self.good_name:
+            #Create list of existing model names for comparison
+            list_fnames = os.listdir(self.plugin_dir)
+            # fake existing regular model name list
+            m_list = [model + ".py" for model in self.model_list]
+            list_fnames.append(m_list)
+            if t_fname in list_fnames and title != mname:
+                self.good_name = False
+                msg = "Name exists already."
+
+        if self.good_name == False:
+            self.name_tcl.SetBackgroundColour('pink')
+            info = 'Error'
+            wx.MessageBox(msg, info)
+            self._notes = msg
+            color = 'red'
+            self._msg_box.SetLabel(msg)
+            self._msg_box.SetForegroundColour(color)
+            return self.good_name
+        self.fname = os.path.join(self.plugin_dir, t_fname)
+        s_title = title
+        if len(title) > 20:
+            s_title = title[0:19] + '...'
+        self._notes = "Model function (%s) has been set! \n" % str(s_title)
+        self.good_name = True
+        self.on_apply(self.fname)
+        return self.good_name
+
+    def on_apply(self, path):
+        """
+        This method is a misnomer - it is not bound to the apply button
+        event.  Instead the apply button event goes to check_name which
+        then calls this method if the name of the new file is acceptable.
+
+        :TODO this should be bound to the apply button.  The first line
+        should call the check_name method which itself should be in another
+        module separated from the the GUI modules.
+        """
+        self.name_tcl.SetBackgroundColour('white')
+        try:
+            label = self.get_textnames()
+            fname = path
+            name1 = label[0]
+            name2 = label[1]
+            self.write_string(fname, name1, name2)
+            self.compile_file(fname)
+            self.parent.update_custom_combo()
+            msg = self._notes
+            info = 'Info'
+            color = 'blue'
+        except:
+            msg = "Easy Custom Sum/Multipy: Error occurred..."
+            info = 'Error'
+            color = 'red'
+        self._msg_box.SetLabel(msg)
+        self._msg_box.SetForegroundColour(color)
+        if self.parent.parent != None:
+            from sas.sasgui.guiframe.events import StatusEvent
+            wx.PostEvent(self.parent.parent, StatusEvent(status=msg,
+                                                         info=info))
+        else:
+            raise
+
+    def on_help(self, event):
+        """
+        Bring up the Composite Model Editor Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/fitting/fitting_help.html"
+        _PageAnchor = "#sum-multi-p1-p2"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, _PageAnchor,
+                                          "Composite Model Editor Help")
+
+    def _set_model_list(self):
+        """
+        Set the list of models
+        """
+        # list of model names
+        # get regular models
+        main_list = self.model_list
+        # get custom models
+        self.update_cm_list()
+        # add custom models to model list
+        for name in self.cm_list:
+            if name not in main_list:
+                main_list.append(name)
+
+        if len(main_list) > 1:
+            main_list.sort()
+        for idx in range(len(main_list)):
+            self.model1.Append(str(main_list[idx]), idx)
+            self.model2.Append(str(main_list[idx]), idx)
+        self.model1.SetStringSelection(self.model1_string)
+        self.model2.SetStringSelection(self.model2_string)
+
+    def update_cm_list(self):
+        """
+        Update custom model list
+        """
+        cm_list = []
+        al_list = os.listdir(self.plugin_dir)
+        for c_name in al_list:
+            if c_name.split('.')[-1] == 'py' and \
+                    c_name.split('.')[0] != '__init__':
+                name = str(c_name.split('.')[0])
+                cm_list.append(name)
+        self.cm_list = cm_list
+
+    def on_model1(self, event):
+        """
+        Set model1
+        """
+        event.Skip()
+        self.update_cm_list()
+        self.model1_name = str(self.model1.GetValue())
+        self.model1_string = self.model1_name
+        if self.model1_name in self.cm_list:
+            self.is_p1_custom = True
+        else:
+            self.is_p1_custom = False
+
+    def on_model2(self, event):
+        """
+        Set model2
+        """
+        event.Skip()
+        self.update_cm_list()
+        self.model2_name = str(self.model2.GetValue())
+        self.model2_string = self.model2_name
+        if self.model2_name in self.cm_list:
+            self.is_p2_custom = True
+        else:
+            self.is_p2_custom = False
+
+    def on_select_operator(self, event=None):
+        """
+        On Select an Operator
+        """
+        # For Mac
+        if event != None:
+            event.Skip()
+        item = event.GetEventObject()
+        text = item.GetValue()
+        self.fill_explanation_helpstring(text)
+
+    def fill_explanation_helpstring(self, operator):
+        """
+        Choose the equation to use depending on whether we now have
+        a sum or multiply model then create the appropriate string
+        """
+
+        name = ''
+
+        if operator == '*':
+            name = 'Multi'
+            factor = 'BackGround'
+            f_oper = '+'
+        else:
+            name = 'Sum'
+            factor = 'scale_factor'
+            f_oper = '*'
+
+        self.factor = factor
+        self._operator = operator
+        self.explanation = "  Custom Model = %s %s (model1 %s model2)\n" % \
+                           (self.factor, f_oper, self._operator)
+        self.explanationctr.SetLabel(self.explanation)
+        self.name = name + M_NAME
+
+
+    def fill_oprator_combox(self):
+        """
+        fill the current combobox with the operator
+        """
+        operator_list = [' +', ' *']
+        for oper in operator_list:
+            pos = self._operator_choice.Append(str(oper))
+            self._operator_choice.SetClientData(pos, str(oper.strip()))
+        self._operator_choice.SetSelection(0)
+
+    def get_textnames(self):
+        """
+        Returns model name string as list
+        """
+        return [self.model1_name, self.model2_name]
+
+    def write_string(self, fname, name1, name2):
+        """
+        Write and Save file
+        """
+        self.fname = fname
+        description = self.desc_tcl.GetValue().lstrip().rstrip()
+        if description == '':
+            description = name1 + self._operator + name2
+        text = self._operator_choice.GetValue()
+        if text.count('+') > 0:
+            factor = 'scale_factor'
+            f_oper = '*'
+            default_val = '1.0'
+        else:
+            factor = 'BackGround'
+            f_oper = '+'
+            default_val = '0.0'
+        path = self.fname
+        try:
+            out_f = open(path, 'w')
+        except:
+            raise
+        lines = SUM_TEMPLATE.split('\n')
+        for line in lines:
+            try:
+                if line.count("scale_factor"):
+                    line = line.replace('scale_factor', factor)
+                    #print "scale_factor", line
+                if line.count("= %s"):
+                    out_f.write(line % (default_val) + "\n")
+                elif line.count("import Model as P1"):
+                    if self.is_p1_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % name1 + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("import %s as P1"):
+                    if not self.is_p1_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % (name1, name1) + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("import Model as P2"):
+                    if self.is_p2_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % name2 + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("import %s as P2"):
+                    if not self.is_p2_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % (name2, name2) + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("self.description = '%s'"):
+                    out_f.write(line % description + "\n")
+                #elif line.count("run") and line.count("%s"):
+                #    out_f.write(line % self._operator + "\n")
+                #elif line.count("evalDistribution") and line.count("%s"):
+                #    out_f.write(line % self._operator + "\n")
+                elif line.count("return") and line.count("%s") == 2:
+                    #print "line return", line
+                    out_f.write(line % (f_oper, self._operator) + "\n")
+                elif line.count("out2")and line.count("%s"):
+                    out_f.write(line % self._operator + "\n")
+                else:
+                    out_f.write(line + "\n")
+            except:
+                raise
+        out_f.close()
+        #else:
+        #    msg = "Name exists already."
+
+    def compile_file(self, path):
+        """
+        Compile the file in the path
+        """
+        path = self.fname
+        _compile_file(path)
+
+    def delete_file(self, path):
+        """
+        Delete file in the path
+        """
+        _delete_file(path)
+
+
+class EditorPanel(wx.ScrolledWindow):
+    """
+    Custom model function editor
+    """
+    def __init__(self, parent, base, path, title, *args, **kwds):
+        kwds['name'] = title
+#        kwds["size"] = (EDITOR_WIDTH, EDITOR_HEIGTH)
+        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
+        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
+        self.SetScrollbars(1,1,1,1)
+        self.parent = parent
+        self.base = base
+        self.path = path
+        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
+        self.font.SetPointSize(10)
+        self.reader = None
+        self.name = 'untitled'
+        self.overwrite_name = False
+        self.is_2d = False
+        self.fname = None
+        self.main_sizer = None
+        self.name_sizer = None
+        self.name_hsizer = None
+        self.name_tcl = None
+        self.desc_sizer = None
+        self.desc_tcl = None
+        self.param_sizer = None
+        self.param_tcl = None
+        self.function_sizer = None
+        self.func_horizon_sizer = None
+        self.button_sizer = None
+        self.param_strings = ''
+        self.function_strings = ''
+        self._notes = ""
+        self._msg_box = None
+        self.msg_sizer = None
+        self.warning = ""
+        self._description = "New Custom Model"
+        self.function_tcl = None
+        self.math_combo = None
+        self.bt_apply = None
+        self.bt_close = None
+        #self._default_save_location = os.getcwd()
+        self._do_layout()
+
+
+
+    def _define_structure(self):
+        """
+        define initial sizer
+        """
+        #w, h = self.parent.GetSize()
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.desc_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.param_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.function_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.func_horizon_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_name(self):
+        """
+        Do the layout for file/function name related widgets
+        """
+        #title name [string]
+        name_txt = wx.StaticText(self, -1, 'Function Name : ')
+        overwrite_cb = wx.CheckBox(self, -1, "Overwrite?", (10, 10))
+        overwrite_cb.SetValue(False)
+        overwrite_cb.SetToolTipString("Overwrite it if already exists?")
+        wx.EVT_CHECKBOX(self, overwrite_cb.GetId(), self.on_over_cb)
+        #overwrite_cb.Show(False)
+        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
+        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
+        self.name_tcl.SetValue('MyFunction')
+        self.name_tcl.SetFont(self.font)
+        hint_name = "Unique Model Function Name."
+        self.name_tcl.SetToolTipString(hint_name)
+        self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT | wx.TOP, 0),
+                                  (overwrite_cb, 0, wx.LEFT, 20)])
+        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT | wx.TOP, 10),
+                                 (self.name_hsizer, 0,
+                                  wx.LEFT | wx.TOP | wx.BOTTOM, 10)])
+
+
+    def _layout_description(self):
+        """
+        Do the layout for description related widgets
+        """
+        #title name [string]
+        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')
+        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
+        self.desc_tcl.SetValue('')
+        hint_desc = "Write a short description of the model function."
+        self.desc_tcl.SetToolTipString(hint_desc)
+        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT | wx.TOP, 10),
+                                 (self.desc_tcl, 0,
+                                  wx.LEFT | wx.TOP | wx.BOTTOM, 10)])
+    def _layout_param(self):
+        """
+        Do the layout for parameter related widgets
+        """
+        param_txt = wx.StaticText(self, -1, 'Fit Parameters (if any): ')
+
+        param_tip = "#Set the parameters and initial values.\n"
+        param_tip += "#Example:\n"
+        param_tip += "A = 1\nB = 1"
+        #param_txt.SetToolTipString(param_tip)
+        newid = wx.NewId()
+        self.param_tcl = EditWindow(self, newid, wx.DefaultPosition,
+                                    wx.DefaultSize,
+                                    wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
+        self.param_tcl.setDisplayLineNumbers(True)
+        self.param_tcl.SetToolTipString(param_tip)
+
+        self.param_sizer.AddMany([(param_txt, 0, wx.LEFT, 10),
+                                  (self.param_tcl, 1, wx.EXPAND | wx.ALL, 10)])
+
+    def _layout_function(self):
+        """
+        Do the layout for function related widgets
+        """
+        function_txt = wx.StaticText(self, -1, 'Function(x) : ')
+        hint_function = "#Example:\n"
+        hint_function += "if x <= 0:\n"
+        hint_function += "    y = A + B\n"
+        hint_function += "else:\n"
+        hint_function += "    y = A + B * cos(2 * pi * x)\n"
+        hint_function += "return y\n"
+        math_txt = wx.StaticText(self, -1, '*Useful math functions: ')
+        math_combo = self._fill_math_combo()
+
+        newid = wx.NewId()
+        self.function_tcl = EditWindow(self, newid, wx.DefaultPosition,
+                                       wx.DefaultSize,
+                                       wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
+        self.function_tcl.setDisplayLineNumbers(True)
+        self.function_tcl.SetToolTipString(hint_function)
+
+        self.func_horizon_sizer.Add(function_txt)
+        self.func_horizon_sizer.Add(math_txt, 0, wx.LEFT, 250)
+        self.func_horizon_sizer.Add(math_combo, 0, wx.LEFT, 10)
+
+        self.function_sizer.Add(self.func_horizon_sizer, 0, wx.LEFT, 10)
+        self.function_sizer.Add(self.function_tcl, 1, wx.EXPAND | wx.ALL, 10)
+
+    def _layout_msg(self):
+        """
+        Layout msg
+        """
+        self._msg_box = wx.StaticText(self, -1, self._notes,
+                                      size=(PANEL_WIDTH, -1))
+        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 10)
+
+    def _layout_button(self):
+        """
+        Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH, -1))
+        self.bt_apply.SetToolTipString("Save changes into the imported data.")
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+
+        self.bt_help = wx.Button(self, -1, "HELP", size=(_BOX_WIDTH, -1))
+        self.bt_help.SetToolTipString("Get Help For Model Editor")
+        self.bt_help.Bind(wx.EVT_BUTTON, self.on_help)
+
+        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH, -1))
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this panel.")
+
+        self.button_sizer.AddMany([(self.bt_apply, 0,
+                                    wx.LEFT, EDITOR_WIDTH * 0.8),
+                                   (self.bt_help, 0,
+                                    wx.LEFT,15),
+                                   (self.bt_close, 0,
+                                    wx.LEFT | wx.BOTTOM, 15)])
+
+    def _do_layout(self):
+        """
+        Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_description()
+        self._layout_param()
+        self._layout_function()
+        self._layout_msg()
+        self._layout_button()
+        self.main_sizer.AddMany([(self.name_sizer, 0, wx.EXPAND | wx.ALL, 5),
+                                 (wx.StaticLine(self), 0,
+                                  wx.ALL | wx.EXPAND, 5),
+                                 (self.desc_sizer, 0, wx.EXPAND | wx.ALL, 5),
+                                 (wx.StaticLine(self), 0,
+                                  wx.ALL | wx.EXPAND, 5),
+                                 (self.param_sizer, 1, wx.EXPAND | wx.ALL, 5),
+                                 (wx.StaticLine(self), 0,
+                                  wx.ALL | wx.EXPAND, 5),
+                                 (self.function_sizer, 2,
+                                  wx.EXPAND | wx.ALL, 5),
+                                 (wx.StaticLine(self), 0,
+                                  wx.ALL | wx.EXPAND, 5),
+                                 (self.msg_sizer, 0, wx.EXPAND | wx.ALL, 5),
+                                 (self.button_sizer, 0, wx.EXPAND | wx.ALL, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def _fill_math_combo(self):
+        """
+        Fill up the math combo box
+        """
+        self.math_combo = wx.ComboBox(self, -1, size=(100, -1),
+                                      style=wx.CB_READONLY)
+        for item in dir(math):
+            if item.count("_") < 1:
+                try:
+                    exec "float(math.%s)" % item
+                    self.math_combo.Append(str(item))
+                except:
+                    self.math_combo.Append(str(item) + "()")
+        self.math_combo.Bind(wx.EVT_COMBOBOX, self._on_math_select)
+        self.math_combo.SetSelection(0)
+        return self.math_combo
+
+    def _on_math_select(self, event):
+        """
+        On math selection on ComboBox
+        """
+        event.Skip()
+        label = self.math_combo.GetValue()
+        self.function_tcl.SetFocus()
+        # Put the text at the cursor position
+        pos = self.function_tcl.GetCurrentPos()
+        self.function_tcl.InsertText(pos, label)
+        # Put the cursor at appropriate position
+        length = len(label)
+        print length
+        if label[length-1] == ')':
+            length -= 1
+        f_pos = pos + length
+        self.function_tcl.GotoPos(f_pos)
+
+    def get_notes(self):
+        """
+        return notes
+        """
+        return self._notes
+
+    def on_change_name(self, event=None):
+        """
+        Change name
+        """
+        if event is not None:
+            event.Skip()
+        self.name_tcl.SetBackgroundColour('white')
+        self.Refresh()
+
+    def check_name(self):
+        """
+        Check name if exist already
+        """
+        self._notes = ''
+        self.on_change_name(None)
+        plugin_dir = self.path
+        list_fnames = os.listdir(plugin_dir)
+        # function/file name
+        title = self.name_tcl.GetValue().lstrip().rstrip()
+        self.name = title
+        t_fname = title + '.py'
+        if not self.overwrite_name:
+            if t_fname in list_fnames:
+                self.name_tcl.SetBackgroundColour('pink')
+                return False
+        self.fname = os.path.join(plugin_dir, t_fname)
+        s_title = title
+        if len(title) > 20:
+            s_title = title[0:19] + '...'
+        self._notes += "Model function name is set "
+        self._notes += "to %s. \n" % str(s_title)
+        return True
+
+    def on_over_cb(self, event):
+        """
+        Set overwrite name flag on cb event
+        """
+        if event is not None:
+            event.Skip()
+        cb_value = event.GetEventObject()
+        self.overwrite_name = cb_value.GetValue()
+
+    def on_click_apply(self, event):
+        """
+        Changes are saved in data object imported to edit.
+
+        checks firs for valid name, then if it already exists then checks
+        that a function was entered and finally that if entered it contains at
+        least a return statement.  If all passes writes file then tries to
+        compile.  If compile fails or import module fails or run method fails
+        tries to remove any .py and pyc files that may have been created and
+        sets error message.
+
+        :todo this code still could do with a careful going over to clean
+        up and simplify. the non GUI methods such as this one should be removed
+        to computational code of SasView. Most of those computational methods
+        would be the same for both the simple editors.
+        """
+        #must post event here
+        event.Skip()
+        name = self.name_tcl.GetValue().lstrip().rstrip()
+        info = 'Info'
+        msg = ''
+        # Sort out the errors if occur
+        # First check for valid python name then if the name already exists
+        if not re.match('^[A-Za-z0-9_]*$', name):
+            msg = "not a valid python name. Name must include only alpha \n"
+            msg += "numeric or underline characters and no spaces"
+        elif self.check_name():
+            description = self.desc_tcl.GetValue()
+            param_str = self.param_tcl.GetText()
+            func_str = self.function_tcl.GetText()
+            # No input for the model function
+            if func_str.lstrip().rstrip():
+                if func_str.count('return') > 0:
+                    self.write_file(self.fname, description, param_str,
+                                    func_str)
+                    tr_msg = _compile_file(self.fname)
+                    msg = str(tr_msg.__str__())
+                    # Compile error
+                    if msg:
+                        msg.replace("  ", "\n")
+                        msg += "\nCompiling Failed"
+                        try:
+                            # try to remove pyc file if exists
+                            _delete_file(self.fname)
+                            _delete_file(self.fname + "c")
+                        except:
+                            pass
+                else:
+                    msg = "Error: The func(x) must 'return' a value at least.\n"
+                    msg += "For example: \n\nreturn 2*x"
+            else:
+                msg = 'Error: Function is not defined.'
+        else:
+            msg = "Name exists already."
+        # Prepare the messagebox
+        if self.base != None and not msg:
+            self.base.update_custom_combo()
+            Model = None
+            try:
+                exec "from %s import Model" % name
+            except:
+                msg = 'new model fails to import in python'
+                try:
+                    # try to remove pyc file if exists
+                    _delete_file(self.fname + "c")
+                except:
+                    pass
+        if self.base != None and not msg:
+            try:
+                Model().run(0.01)
+            except:
+                msg = "new model fails on run method:"
+                _, value, _ = sys.exc_info()
+                msg += "in %s:\n%s\n" % (name, value)
+                try:
+                    # try to remove pyc file if exists
+                    _delete_file(self.fname + "c")
+                except:
+                    pass
+        # Prepare the messagebox
+        if msg:
+            info = 'Error'
+            color = 'red'
+        else:
+            msg = "Successful! "
+            msg += "  " + self._notes
+            msg += " Please look for it in the Customized Models."
+            info = 'Info'
+            color = 'blue'
+        self._msg_box.SetLabel(msg)
+        self._msg_box.SetForegroundColour(color)
+        # Send msg to the top window
+        if self.base != None:
+            from sas.sasgui.guiframe.events import StatusEvent
+            wx.PostEvent(self.base.parent, StatusEvent(status=msg, info=info))
+        self.warning = msg
+
+
+    def write_file(self, fname, desc_str, param_str, func_str):
+        """
+        Write content in file
+
+        :param fname: full file path
+        :param desc_str: content of the description strings
+        :param param_str: content of params; Strings
+        :param func_str: content of func; Strings
+        """
+        try:
+            out_f = open(fname, 'w')
+        except:
+            raise
+        # Prepare the content of the function
+        lines = CUSTOM_TEMPLATE.split('\n')
+
+        has_scipy = func_str.count("scipy.")
+        self.is_2d = func_str.count("#self.ndim = 2")
+        line_2d = ''
+        if self.is_2d:
+            line_2d = CUSTOM_2D_TEMP.split('\n')
+        line_test = TEST_TEMPLATE.split('\n')
+        local_params = ''
+        spaces = '        '#8spaces
+        # write function here
+        for line in lines:
+            # The location where to put the strings is
+            # hard-coded in the template as shown below.
+            if line.count("#self.params here"):
+                for param_line in param_str.split('\n'):
+                    p_line = param_line.lstrip().rstrip()
+                    if p_line:
+                        p0_line = self.set_param_helper(p_line)
+                        local_params += self.set_function_helper(p_line)
+                        out_f.write(p0_line)
+            elif line.count("#local params here"):
+                if local_params:
+                    out_f.write(local_params)
+            elif line.count("self.description = "):
+                des0 = self.name + "\\n"
+                desc = str(desc_str.lstrip().rstrip().replace('\"', ''))
+                out_f.write(line % (des0 + desc) + "\n")
+            elif line.count("def function(self, x=0.0%s):"):
+                if self.is_2d:
+                    y_str = ', y=0.0'
+                    out_f.write(line % y_str + "\n")
+                else:
+                    out_f.write(line % '' + "\n")
+            elif line.count("#function here"):
+                for func_line in func_str.split('\n'):
+                    f_line = func_line.rstrip()
+                    if f_line:
+                        out_f.write(spaces + f_line + "\n")
+                if not func_str:
+                    dep_var = 'y'
+                    if self.is_2d:
+                        dep_var = 'z'
+                    out_f.write(spaces + 'return %s' % dep_var + "\n")
+            elif line.count("#import scipy?"):
+                if has_scipy:
+                    out_f.write("import scipy" + "\n")
+            #elif line.count("name = "):
+            #    out_f.write(line % self.name + "\n")
+            elif line:
+                out_f.write(line + "\n")
+        # run string for 2d
+        if line_2d:
+            for line in line_2d:
+                out_f.write(line + "\n")
+        # Test strins
+        for line in line_test:
+            out_f.write(line + "\n")
+
+        out_f.close()
+
+    def set_param_helper(self, line):
+        """
+        Get string in line to define the params dictionary
+
+        :param line: one line of string got from the param_str
+        """
+        params_str = ''
+        spaces = '        '#8spaces
+        items = line.split(";")
+        for item in items:
+            name = item.split("=")[0].lstrip().rstrip()
+            try:
+                value = item.split("=")[1].lstrip().rstrip()
+                float(value)
+            except:
+                value = 1.0 # default
+            params_str += spaces + "self.params['%s'] = %s\n" % (name, value)
+
+        return params_str
+
+    def set_function_helper(self, line):
+        """
+        Get string in line to define the local params
+
+        :param line: one line of string got from the param_str
+        """
+        params_str = ''
+        spaces = '        '#8spaces
+        items = line.split(";")
+        for item in items:
+            name = item.split("=")[0].lstrip().rstrip()
+            params_str += spaces + "%s = self.params['%s']\n" % (name, name)
+        return params_str
+
+    def get_warning(self):
+        """
+        Get the warning msg
+        """
+        return self.warning
+
+    def on_help(self, event):
+        """
+        Bring up the Custom Model Editor Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/fitting/fitting_help.html"
+        _PageAnchor = "#custom-model-editor"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, _PageAnchor,
+                                          "Custom Model Editor Help")
+
+    def on_close(self, event):
+        """
+        leave data as it is and close
+        """
+        self.parent.Show(False)#Close()
+        event.Skip()
+
+class EditorWindow(wx.Frame):
+    """
+    Editor Window
+    """
+    def __init__(self, parent, base, path, title,
+                 size=(EDITOR_WIDTH, EDITOR_HEIGTH), *args, **kwds):
+        """
+        Init
+        """
+        kwds["title"] = title
+        kwds["size"] = size
+        wx.Frame.__init__(self, parent=None, *args, **kwds)
+        self.parent = parent
+        self.panel = EditorPanel(parent=self, base=parent,
+                                 path=path, title=title)
+        self.Show(True)
+        wx.EVT_CLOSE(self, self.on_close)
+
+    def on_close(self, event):
+        """
+        On close event
+        """
+        self.Show(False)
+        #if self.parent != None:
+        #    self.parent.new_model_frame = None
+        #self.Destroy()
+
+## Templates for custom models
+CUSTOM_TEMPLATE = """
+from sas.models.pluginmodel import Model1DPlugin
+from math import *
+import os
+import sys
+import numpy
+#import scipy?
+class Model(Model1DPlugin):
+    name = ""
+    def __init__(self):
+        Model1DPlugin.__init__(self, name=self.name)
+        #set name same as file name
+        self.name = self.get_fname()
+        #self.params here
+        self.description = "%s"
+        self.set_details()
+    def function(self, x=0.0%s):
+        #local params here
+        #function here
+"""
+CUSTOM_2D_TEMP = """
+    def run(self, x=0.0, y=0.0):
+        if x.__class__.__name__ == 'list':
+            x_val = x[0]
+            y_val = y[0]*0.0
+            return self.function(x_val, y_val)
+        elif x.__class__.__name__ == 'tuple':
+            msg = "Tuples are not allowed as input to BaseComponent models"
+            raise ValueError, msg
+        else:
+            return self.function(x, 0.0)
+    def runXY(self, x=0.0, y=0.0):
+        if x.__class__.__name__ == 'list':
+            return self.function(x, y)
+        elif x.__class__.__name__ == 'tuple':
+            msg = "Tuples are not allowed as input to BaseComponent models"
+            raise ValueError, msg
+        else:
+            return self.function(x, y)
+    def evalDistribution(self, qdist):
+        if qdist.__class__.__name__ == 'list':
+            msg = "evalDistribution expects a list of 2 ndarrays"
+            if len(qdist)!=2:
+                raise RuntimeError, msg
+            if qdist[0].__class__.__name__ != 'ndarray':
+                raise RuntimeError, msg
+            if qdist[1].__class__.__name__ != 'ndarray':
+                raise RuntimeError, msg
+            v_model = numpy.vectorize(self.runXY, otypes=[float])
+            iq_array = v_model(qdist[0], qdist[1])
+            return iq_array
+        elif qdist.__class__.__name__ == 'ndarray':
+            v_model = numpy.vectorize(self.runXY, otypes=[float])
+            iq_array = v_model(qdist)
+            return iq_array
+"""
+TEST_TEMPLATE = """
+    def get_fname(self):
+        path = sys._getframe().f_code.co_filename
+        basename  = os.path.basename(path)
+        name, _ = os.path.splitext(basename)
+        return name
+######################################################################
+## THIS IS FOR TEST. DO NOT MODIFY THE FOLLOWING LINES!!!!!!!!!!!!!!!!
+if __name__ == "__main__":
+    m= Model()
+    out1 = m.runXY(0.0)
+    out2 = m.runXY(0.01)
+    isfine1 = numpy.isfinite(out1)
+    isfine2 = numpy.isfinite(out2)
+    print "Testing the value at Q = 0.0:"
+    print out1, " : finite? ", isfine1
+    print "Testing the value at Q = 0.01:"
+    print out2, " : finite? ", isfine2
+    if isfine1 and isfine2:
+        print "===> Simple Test: Passed!"
+    else:
+        print "===> Simple Test: Failed!"
+"""
+SUM_TEMPLATE = """
+# A sample of an experimental model function for Sum/Multiply(Pmodel1,Pmodel2)
+import copy
+from sas.models.pluginmodel import Model1DPlugin
+# User can change the name of the model (only with single functional model)
+#P1_model:
+#from sas.models.%s import %s as P1
+#from %s import Model as P1
+
+#P2_model:
+#from sas.models.%s import %s as P2
+#from %s import Model as P2
+import os
+import sys
+
+class Model(Model1DPlugin):
+    name = ""
+    def __init__(self):
+        Model1DPlugin.__init__(self, name='')
+        p_model1 = P1()
+        p_model2 = P2()
+        ## Setting  model name model description
+        self.description = '%s'
+        self.name = self.get_fname()
+        if self.name.rstrip().lstrip() == '':
+            self.name = self._get_name(p_model1.name, p_model2.name)
+        if self.description.rstrip().lstrip() == '':
+            self.description = p_model1.name
+            self.description += p_model2.name
+            self.fill_description(p_model1, p_model2)
+
+        ## Define parameters
+        self.params = {}
+
+        ## Parameter details [units, min, max]
+        self.details = {}
+        ## Magnetic Panrameters
+        self.magnetic_params = []
+        # non-fittable parameters
+        self.non_fittable = p_model1.non_fittable
+        self.non_fittable += p_model2.non_fittable
+
+        ##models
+        self.p_model1= p_model1
+        self.p_model2= p_model2
+
+
+        ## dispersion
+        self._set_dispersion()
+        ## Define parameters
+        self._set_params()
+        ## New parameter:scaling_factor
+        self.params['scale_factor'] = %s
+
+        ## Parameter details [units, min, max]
+        self._set_details()
+        self.details['scale_factor'] = ['', None, None]
+
+
+        #list of parameter that can be fitted
+        self._set_fixed_params()
+        ## parameters with orientation
+        for item in self.p_model1.orientation_params:
+            new_item = "p1_" + item
+            if not new_item in self.orientation_params:
+                self.orientation_params.append(new_item)
+
+        for item in self.p_model2.orientation_params:
+            new_item = "p2_" + item
+            if not new_item in self.orientation_params:
+                self.orientation_params.append(new_item)
+        ## magnetic params
+        for item in self.p_model1.magnetic_params:
+            new_item = "p1_" + item
+            if not new_item in self.magnetic_params:
+                self.magnetic_params.append(new_item)
+
+        for item in self.p_model2.magnetic_params:
+            new_item = "p2_" + item
+            if not new_item in self.magnetic_params:
+                self.magnetic_params.append(new_item)
+        # get multiplicity if model provide it, else 1.
+        try:
+            multiplicity1 = p_model1.multiplicity
+            try:
+                multiplicity2 = p_model2.multiplicity
+            except:
+                multiplicity2 = 1
+        except:
+            multiplicity1 = 1
+            multiplicity2 = 1
+        ## functional multiplicity of the model
+        self.multiplicity1 = multiplicity1
+        self.multiplicity2 = multiplicity2
+        self.multiplicity_info = []
+
+    def _clone(self, obj):
+        obj.params     = copy.deepcopy(self.params)
+        obj.description     = copy.deepcopy(self.description)
+        obj.details    = copy.deepcopy(self.details)
+        obj.dispersion = copy.deepcopy(self.dispersion)
+        obj.p_model1  = self.p_model1.clone()
+        obj.p_model2  = self.p_model2.clone()
+        #obj = copy.deepcopy(self)
+        return obj
+
+    def _get_name(self, name1, name2):
+        p1_name = self._get_upper_name(name1)
+        if not p1_name:
+            p1_name = name1
+        name = p1_name
+        name += "_and_"
+        p2_name = self._get_upper_name(name2)
+        if not p2_name:
+            p2_name = name2
+        name += p2_name
+        return name
+
+    def _get_upper_name(self, name=None):
+        if name == None:
+            return ""
+        upper_name = ""
+        str_name = str(name)
+        for index in range(len(str_name)):
+            if str_name[index].isupper():
+                upper_name += str_name[index]
+        return upper_name
+
+    def _set_dispersion(self):
+        ##set dispersion only from p_model
+        for name , value in self.p_model1.dispersion.iteritems():
+            #if name.lower() not in self.p_model1.orientation_params:
+            new_name = "p1_" + name
+            self.dispersion[new_name]= value
+        for name , value in self.p_model2.dispersion.iteritems():
+            #if name.lower() not in self.p_model2.orientation_params:
+            new_name = "p2_" + name
+            self.dispersion[new_name]= value
+
+    def function(self, x=0.0):
+        return 0
+
+    def getProfile(self):
+        try:
+            x,y = self.p_model1.getProfile()
+        except:
+            x = None
+            y = None
+
+        return x, y
+
+    def _set_params(self):
+        for name , value in self.p_model1.params.iteritems():
+            # No 2D-supported
+            #if name not in self.p_model1.orientation_params:
+            new_name = "p1_" + name
+            self.params[new_name]= value
+
+        for name , value in self.p_model2.params.iteritems():
+            # No 2D-supported
+            #if name not in self.p_model2.orientation_params:
+            new_name = "p2_" + name
+            self.params[new_name]= value
+
+        # Set "scale" as initializing
+        self._set_scale_factor()
+
+
+    def _set_details(self):
+        for name ,detail in self.p_model1.details.iteritems():
+            new_name = "p1_" + name
+            #if new_name not in self.orientation_params:
+            self.details[new_name]= detail
+
+        for name ,detail in self.p_model2.details.iteritems():
+            new_name = "p2_" + name
+            #if new_name not in self.orientation_params:
+            self.details[new_name]= detail
+
+    def _set_scale_factor(self):
+        pass
+
+
+    def setParam(self, name, value):
+        # set param to this (p1, p2) model
+        self._setParamHelper(name, value)
+
+        ## setParam to p model
+        model_pre = ''
+        new_name = ''
+        name_split = name.split('_', 1)
+        if len(name_split) == 2:
+            model_pre = name.split('_', 1)[0]
+            new_name = name.split('_', 1)[1]
+        if model_pre == "p1":
+            if new_name in self.p_model1.getParamList():
+                self.p_model1.setParam(new_name, value)
+        elif model_pre == "p2":
+             if new_name in self.p_model2.getParamList():
+                self.p_model2.setParam(new_name, value)
+        elif name == 'scale_factor':
+            self.params['scale_factor'] = value
+        else:
+            raise ValueError, "Model does not contain parameter %s" % name
+
+    def getParam(self, name):
+        # Look for dispersion parameters
+        toks = name.split('.')
+        if len(toks)==2:
+            for item in self.dispersion.keys():
+                # 2D not supported
+                if item.lower()==toks[0].lower():
+                    for par in self.dispersion[item]:
+                        if par.lower() == toks[1].lower():
+                            return self.dispersion[item][par]
+        else:
+            # Look for standard parameter
+            for item in self.params.keys():
+                if item.lower()==name.lower():
+                    return self.params[item]
+        return
+        #raise ValueError, "Model does not contain parameter %s" % name
+
+    def _setParamHelper(self, name, value):
+        # Look for dispersion parameters
+        toks = name.split('.')
+        if len(toks)== 2:
+            for item in self.dispersion.keys():
+                if item.lower()== toks[0].lower():
+                    for par in self.dispersion[item]:
+                        if par.lower() == toks[1].lower():
+                            self.dispersion[item][par] = value
+                            return
+        else:
+            # Look for standard parameter
+            for item in self.params.keys():
+                if item.lower()== name.lower():
+                    self.params[item] = value
+                    return
+
+        raise ValueError, "Model does not contain parameter %s" % name
+
+
+    def _set_fixed_params(self):
+        for item in self.p_model1.fixed:
+            new_item = "p1" + item
+            self.fixed.append(new_item)
+        for item in self.p_model2.fixed:
+            new_item = "p2" + item
+            self.fixed.append(new_item)
+
+        self.fixed.sort()
+
+
+    def run(self, x = 0.0):
+        self._set_scale_factor()
+        return self.params['scale_factor'] %s \
+(self.p_model1.run(x) %s self.p_model2.run(x))
+
+    def runXY(self, x = 0.0):
+        self._set_scale_factor()
+        return self.params['scale_factor'] %s \
+(self.p_model1.runXY(x) %s self.p_model2.runXY(x))
+
+    ## Now (May27,10) directly uses the model eval function
+    ## instead of the for-loop in Base Component.
+    def evalDistribution(self, x = []):
+        self._set_scale_factor()
+        return self.params['scale_factor'] %s \
+(self.p_model1.evalDistribution(x) %s \
+self.p_model2.evalDistribution(x))
+
+    def set_dispersion(self, parameter, dispersion):
+        value= None
+        new_pre = parameter.split("_", 1)[0]
+        new_parameter = parameter.split("_", 1)[1]
+        try:
+            if new_pre == 'p1' and \
+new_parameter in self.p_model1.dispersion.keys():
+                value= self.p_model1.set_dispersion(new_parameter, dispersion)
+            if new_pre == 'p2' and \
+new_parameter in self.p_model2.dispersion.keys():
+                value= self.p_model2.set_dispersion(new_parameter, dispersion)
+            self._set_dispersion()
+            return value
+        except:
+            raise
+
+    def fill_description(self, p_model1, p_model2):
+        description = ""
+        description += "This model gives the summation or multiplication of"
+        description += "%s and %s. "% ( p_model1.name, p_model2.name )
+        self.description += description
+
+    def get_fname(self):
+        path = sys._getframe().f_code.co_filename
+        basename  = os.path.basename(path)
+        name, _ = os.path.splitext(basename)
+        return name
+
+if __name__ == "__main__":
+    m1= Model()
+    #m1.setParam("p1_scale", 25)
+    #m1.setParam("p1_length", 1000)
+    #m1.setParam("p2_scale", 100)
+    #m1.setParam("p2_rg", 100)
+    out1 = m1.runXY(0.01)
+
+    m2= Model()
+    #m2.p_model1.setParam("scale", 25)
+    #m2.p_model1.setParam("length", 1000)
+    #m2.p_model2.setParam("scale", 100)
+    #m2.p_model2.setParam("rg", 100)
+    out2 = m2.p_model1.runXY(0.01) %s m2.p_model2.runXY(0.01)\n
+    print "My name is %s."% m1.name
+    print out1, " = ", out2
+    if out1 == out2:
+        print "===> Simple Test: Passed!"
+    else:
+        print "===> Simple Test: Failed!"
+"""
+
+if __name__ == "__main__":
+#    app = wx.PySimpleApp()
+    main_app = wx.App()
+    main_frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"],
+                       plugin_dir='../fitting/plugin_models')
+    main_frame.ShowModal()
+    main_app.MainLoop()
+
+#if __name__ == "__main__":
+#    from sas.sasgui.perspectives.fitting import models
+#    dir_path = models.find_plugins_dir()
+#    app = wx.App()
+#    window = EditorWindow(parent=None, base=None, path=dir_path, title="Editor")
+#    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/pyconsole.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/pyconsole.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/pyconsole.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,317 @@
+"""
+Console Module display Python console
+"""
+import sys
+import os
+import wx
+import wx.lib.dialogs
+import wx.py.editor as editor
+import wx.py.frame as frame
+import py_compile
+
+if sys.platform.count("win32") > 0:
+    PANEL_WIDTH = 800
+    PANEL_HEIGHT = 700
+    FONT_VARIANT = 0
+else:
+    PANEL_WIDTH = 830
+    PANEL_HEIGHT = 730
+    FONT_VARIANT = 1
+ID_COMPILE = wx.NewId()
+ID_RUN = wx.NewId()
+
+def compile_file(path):
+    """
+    Compile a python file
+    """
+    try:
+        import py_compile
+        py_compile.compile(file=path, doraise=True)
+    except:
+        type, value, traceback = sys.exc_info()
+        return value
+    return None
+
+class PyConsole(editor.EditorNotebookFrame):
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "Custom Model Editor"
+    ## Name to appear on the window title bar
+    window_caption = "Custom Model Editor"
+    ## Flag to tell the AUI manager to put this panel in the center pane
+    CENTER_PANE = False
+    def __init__(self, parent=None, base=None, manager=None, panel=None,
+                    title='Python Shell/Editor', filename=None,
+                    size=(PANEL_WIDTH, PANEL_HEIGHT)):
+        self.config = None
+        editor.EditorNotebookFrame.__init__(self, parent=parent,
+                                        title=title, size=size,
+                                        filename=filename)
+        self.parent = parent
+        self._manager = manager
+        self.base = base
+        self.panel = panel
+        self._add_menu()
+        if filename != None:
+            dataDir = os.path.dirname(filename)
+        elif self.parent != None:
+            dataDir = self.parent._default_save_location
+        else:
+             dataDir = None
+        self.dataDir = dataDir
+        self.Centre()
+
+        self.Bind(wx.EVT_MENU, self.OnNewFile, id=wx.ID_NEW)
+        self.Bind(wx.EVT_MENU, self.OnOpenFile, id=wx.ID_OPEN)
+        self.Bind(wx.EVT_MENU, self.OnSaveFile, id=wx.ID_SAVE)
+        self.Bind(wx.EVT_MENU, self.OnSaveAsFile, id=wx.ID_SAVEAS)
+        self.Bind(wx.EVT_MENU, self.OnCompile, id=ID_COMPILE)
+        self.Bind(wx.EVT_MENU, self.OnRun, id=ID_RUN)
+        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateCompileMenu, id=ID_COMPILE)
+        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateCompileMenu, id=ID_RUN)
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+        if not title.count('Python Shell'):
+            # Delete menu item (open and new) if not python shell
+            #self.fileMenu.Delete(wx.ID_NEW)
+            self.fileMenu.Delete(wx.ID_OPEN)
+
+
+    def _add_menu(self):
+        """
+        Add menu
+        """
+        self.compileMenu = wx.Menu()
+        self.compileMenu.Append(ID_COMPILE, 'Compile',
+                 'Compile the file')
+        self.compileMenu.AppendSeparator()
+        self.compileMenu.Append(ID_RUN, 'Run in Shell',
+                 'Run the file in the Python Shell')
+        self.MenuBar.Insert(3, self.compileMenu, '&Run')
+
+    def OnHelp(self, event):
+        """
+        Show a help dialog.
+        """
+        import  wx.lib.dialogs
+        title = 'Help on key bindings'
+        text = wx.py.shell.HELP_TEXT
+        dlg = wx.lib.dialogs.ScrolledMessageDialog(self, text, title,
+                                                   size=((700, 540)))
+        fnt = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL)
+        dlg.GetChildren()[0].SetFont(fnt)
+        dlg.GetChildren()[0].SetInsertionPoint(0)
+        dlg.ShowModal()
+        dlg.Destroy()
+
+    def set_manager(self, manager):
+        """
+        Set the manager of this window
+        """
+        self._manager = manager
+
+    def OnAbout(self, event):
+        """
+        On About
+        """
+        message = ABOUT
+        dial = wx.MessageDialog(self, message, 'About',
+                           wx.OK | wx.ICON_INFORMATION)
+        dial.ShowModal()
+
+    def OnNewFile(self, event):
+        """
+        OnFileOpen
+        """
+        self.OnFileNew(event)
+
+    def OnOpenFile(self, event):
+        """
+        OnFileOpen
+        """
+        self.OnFileOpen(event)
+        self.Show(False)
+        self.Show(True)
+
+    def OnSaveFile(self, event):
+        """
+        OnFileSave overwrite
+        """
+        self.OnFileSave(event)
+        self.Show(False)
+        self.Show(True)
+
+    def OnSaveAsFile(self, event):
+        """
+        OnFileSaveAs overwrite
+        """
+        self.OnFileSaveAs(event)
+        self.Show(False)
+        self.Show(True)
+
+    def bufferOpen(self):
+        """
+        Open file in buffer, bypassing editor bufferOpen
+        """
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        filedir = ''
+        if self.buffer and self.buffer.doc.filedir:
+            filedir = self.buffer.doc.filedir
+        if not filedir:
+            filedir = self.dataDir
+        result = editor.openSingle(directory=filedir,
+                            wildcard='Python Files (*.py)|*.py')
+        if result.path:
+            self.bufferCreate(result.path)
+        cancel = False
+        return cancel
+
+    def bufferSaveAs(self):
+        """
+        Save buffer to a new filename: Bypassing editor bufferSaveAs
+        """
+        filedir = ''
+        if self.buffer and self.buffer.doc.filedir:
+            filedir = self.buffer.doc.filedir
+        if not filedir:
+            filedir = self.dataDir
+        result = editor.saveSingle(directory=filedir,
+                                   filename='untitled.py',
+                                   wildcard='Python Files (*.py)|*.py')
+        if result.path:
+            self.buffer.confirmed = True
+            self.buffer.saveAs(result.path)
+            cancel = False
+        else:
+            cancel = True
+        return cancel
+
+    def OnRun(self, event):
+        """
+        Run
+        """
+        if self._check_changed():
+            return True
+        if self.buffer and self.buffer.doc.filepath:
+            self.editor.setFocus()
+            # Why we have to do this (Otherwise problems on Windows)?
+            forward_path = self.buffer.doc.filepath.replace('\\', '/')
+            self.shell.Execute("execfile('%s')" % forward_path)
+            self.shell.Hide()
+            self.shell.Show(True)
+            return self.shell.GetText().split(">>>")[-2]
+        else:
+            mssg = "\n This is not a python file."
+            title = 'Error'
+            icon = wx.ICON_ERROR
+            wx.MessageBox(str(mssg), title, style=icon)
+            return 0
+
+    def OnCompile(self, event):
+        """
+        Compile
+        """
+        if self._check_changed():
+            return True
+        run_out = self.OnRun(None)
+        if self._get_err_msg(run_out):
+            if self._manager != None and self.panel != None:
+                self._manager.set_edit_menu_helper(self.parent)
+                # Update custom model list in fitpage combobox
+                wx.CallAfter(self._manager.update_custom_combo)
+
+    def _check_changed(self):
+        """
+        If content was changed, suggest to save it first
+        """
+        if self.bufferHasChanged() and self.buffer.doc.filepath:
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+
+    def _get_err_msg(self, text=''):
+        """
+        Get err_msg
+        """
+        name = None
+        mssg = "\n This is not a python file."
+        title = 'Error'
+        icon = wx.ICON_ERROR
+        try:
+            fname = self.editor.getStatus()[0]
+            name = os.path.basename(fname)
+            if name.split('.')[-1] != 'py':
+                wx.MessageBox(str(mssg), title, style=icon)
+                return False
+            msg = compile_file(fname)
+        except:
+            msg = None
+        if name == None:
+            wx.MessageBox(str(mssg), title, style=icon)
+            return False
+        mssg = "Compiling '%s'...\n" % name
+        if msg != None:
+            mssg += "Error occurred:\n"
+            mssg += str(msg) + "\n\n"
+            if text:
+                mssg += "Run-Test results:\n"
+                mssg += str(text)
+                title = 'Warning'
+                icon = wx.ICON_WARNING
+        else:
+            mssg += "Successful.\n\n"
+            if text:
+                if text.count('Failed') or text.count('Error:') > 0:
+                    mssg += "But Simple Test FAILED: Please check your code.\n"
+                mssg += "Run-Test results:\n"
+                mssg += str(text)
+            title = 'Info'
+            icon = wx.ICON_INFORMATION
+        dlg = wx.lib.dialogs.ScrolledMessageDialog(self, mssg, title,
+                                                   size=((550, 250)))
+        fnt = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL)
+        dlg.GetChildren()[0].SetFont(fnt)
+        dlg.GetChildren()[0].SetInsertionPoint(0)
+        dlg.ShowModal()
+        dlg.Destroy()
+        return True
+
+    def OnUpdateCompileMenu(self, event):
+        """
+        Update Compile menu items based on current tap.
+        """
+        win = wx.Window.FindFocus()
+        id = event.GetId()
+        event.Enable(True)
+        try:
+            if id == ID_COMPILE or id == ID_RUN:
+                menu_on = False
+                if self.buffer and self.buffer.doc.filepath:
+                    menu_on = True
+                event.Enable(menu_on)
+        except AttributeError:
+            # This menu option is not supported in the current context.
+            event.Enable(False)
+
+    def on_close(self, event):
+        """
+        Close event
+        """
+        if self.base != None:
+            self.base.py_frame = None
+        self.Destroy()
+
+ABOUT = "Welcome to Python %s! \n\n" % sys.version.split()[0]
+ABOUT += "This uses Py Shell/Editor in wx (developed by Patrick K. O'Brien).\n"
+ABOUT += "If this is your first time using Python, \n"
+ABOUT += "you should definitely check out the tutorial "
+ABOUT += "on the Internet at http://www.python.org/doc/tut/."
+
+
+if __name__ == "__main__":
+
+    app = wx.App()
+    dlg = PyConsole()
+    dlg.Show()
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/resolcal_thread.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/resolcal_thread.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/resolcal_thread.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,54 @@
+"""
+Thread for Resolution computation
+"""
+import time
+from sas.sascalc.data_util.calcthread import CalcThread
+
+class CalcRes(CalcThread):
+    """
+    Compute Resolution
+    """
+    def __init__(self,
+                 id= -1,
+                 func=None,
+                 qx=None,
+                 qy=None,
+                 qx_min=None,
+                 qx_max=None,
+                 qy_min=None,
+                 qy_max=None,
+                 image=None,
+                 completefn=None,
+                 updatefn=None,
+                 elapsed=0,
+                 yieldtime=0.01,
+                 worktime=0.01
+                 ):
+        """
+        """
+        CalcThread.__init__(self, completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.starttime = 0
+        self.id = id
+        self.func = func
+        self.qx = qx
+        self.qy = qy
+        self.qx_min = qx_min
+        self.qx_max = qx_max
+        self.qy_min = qy_min
+        self.qy_max = qy_max
+        self.image = image
+
+    def compute(self):
+        """
+        excuting computation
+        """
+        self.image = map(self.func, self.qx, self.qy,
+                        self.qx_min, self.qx_max,
+                        self.qy_min, self.qy_max)[0]
+        elapsed = time.time() - self.starttime
+
+        self.complete(image=self.image,
+                      elapsed=elapsed)
Index: src/sas/sasgui/perspectives/calculator/resolution_calculator_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/resolution_calculator_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/resolution_calculator_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,1357 @@
+# pylint: disable=attribute-defined-outside-init
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation.
+
+See the license text in license.txt
+
+copyright 2008, 2009, 2010 University of Tennessee
+"""
+import wx
+import sys
+import os
+import matplotlib
+import math
+import logging
+#Use the WxAgg back end. The Wx one takes too long to render
+matplotlib.use('WXAgg')
+from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
+from matplotlib.backends.backend_wxagg import NavigationToolbar2Wx as Toolbar
+from matplotlib.backend_bases import FigureManagerBase
+# Wx-Pylab magic for displaying plots within an application's window.
+from matplotlib import _pylab_helpers
+# The Figure object is used to create backend-independent plot representations.
+from matplotlib.figure import Figure
+
+#from sas.guicomm.events import StatusEvent
+from sas.sascalc.calculator.resolution_calculator import ResolutionCalculator
+from sas.sasgui.guiframe.events import StatusEvent
+from sas.sasgui.perspectives.calculator.calculator_widgets import OutputTextCtrl
+from sas.sasgui.perspectives.calculator.calculator_widgets import InputTextCtrl
+from wx.lib.scrolledpanel import ScrolledPanel
+from math import fabs
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+_BOX_WIDTH = 100
+_Q_DEFAULT = 0.0
+#Slit length panel size
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    PANEL_WIDTH = 525
+    PANEL_HEIGHT = 653
+    FONT_VARIANT = 0
+    IS_WIN = True
+else:
+    PANEL_TOP = 60
+    PANEL_WIDTH = 540
+    PANEL_HEIGHT = 662
+    FONT_VARIANT = 1
+    IS_WIN = False
+
+_SOURCE_MASS = {'Alpha':6.64465620E-24,
+                'Deuteron':3.34358320E-24,
+                'Neutron':1.67492729E-24,
+                'Photon': 0.0,
+                'Proton':1.67262137E-24,
+                'Triton':5.00826667E-24}
+
+class ResolutionCalculatorPanel(ScrolledPanel):
+    """
+    Provides the Resolution calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "SAS Resolution Estimator"
+    ## Name to appear on the window title bar
+    window_caption = ""
+    ## Flag to tell the AUI manager to put this panel in the center pane
+    CENTER_PANE = True
+
+    def __init__(self, parent, *args, **kwds):
+        kwds["size"] = (PANEL_WIDTH * 2, PANEL_HEIGHT)
+        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
+        ScrolledPanel.__init__(self, parent, *args, **kwds)
+        self.SetupScrolling()
+        self.parent = parent
+
+        # input defaults
+        self.qx = []
+        self.qy = []
+        # dQ defaults
+        self.sigma_r = None
+        self.sigma_phi = None
+        self.sigma_1d = None
+        # monchromatic or polychromatic
+        self.wave_color = 'mono'
+        self.num_wave = 10
+        self.spectrum_dic = {}
+        # dQ 2d image
+        self.image = None
+        # results of sigmas
+        self.sigma_strings = ' '
+        #Font size
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        # Object that receive status event
+        self.resolution = ResolutionCalculator()
+        # Source selection dic
+        self.source_mass = _SOURCE_MASS
+        #layout attribute
+        self.hint_sizer = None
+        # detector coordinate of estimation of sigmas
+        self.det_coordinate = 'cartesian'
+        self.source_cb = None
+        self._do_layout()
+
+    def _define_structure(self):
+        """
+        Define the main sizers building to build this application.
+        """
+        self.main_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.vertical_l_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.vertical_r_spacer = wx.BoxSizer(wx.VERTICAL)
+        self.vertical_r_frame = wx.StaticBox(self, -1, '')
+        self.vertical_r_sizer = wx.StaticBoxSizer(self.vertical_r_frame,
+                                                  wx.VERTICAL)
+        self.box_source = wx.StaticBox(self, -1, str(self.window_caption))
+        self.boxsizer_source = wx.StaticBoxSizer(self.box_source, wx.VERTICAL)
+        self.mass_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.intensity_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.wavelength_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.wavelength_spread_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.source_aperture_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.sample_aperture_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.source2sample_distance_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.sample2sample_distance_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.sample2detector_distance_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.detector_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.detector_pix_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.input_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.output_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_mass(self):
+        """
+        Fill the sizer containing mass
+        """
+        # get the mass
+        mass_value = str(self.resolution.mass)
+        self.mass_txt = wx.StaticText(self, -1, 'Source: ')
+        self.mass_hint = "Mass of Neutrons m = %s [g]" % str(self.resolution.mass)
+        self.source_cb = wx.ComboBox(self, -1,
+                                     style=wx.CB_READONLY,
+                                     name='%s' % mass_value)
+        # Sort source name because wx2.9 on Mac does not support CB_SORT
+        # Custom sorting
+        source_list = []
+        for key, _ in self.source_mass.iteritems():
+            name_source = str(key)
+            source_list.append(name_source)
+        source_list.sort()
+        for idx in range(len(source_list)):
+            self.source_cb.Append(source_list[idx], idx)
+        self.source_cb.SetStringSelection("Neutron")
+        wx.EVT_COMBOBOX(self.source_cb, -1, self._on_source_selection)
+
+        # combo box for color
+        self.wave_color_cb = wx.ComboBox(self, -1,
+                                         style=wx.CB_READONLY,
+                                         name='color')
+        # two choices
+        self.wave_color_cb.Append('Monochromatic')
+        self.wave_color_cb.Append('TOF')
+        self.wave_color_cb.SetStringSelection("Monochromatic")
+        wx.EVT_COMBOBOX(self.wave_color_cb, -1, self._on_source_color)
+
+        source_hint = "Source Selection: Affect on"
+        source_hint += " the gravitational contribution.\n"
+        source_hint += "Mass of %s: m = %s [g]" % \
+                            ('Neutron', str(self.resolution.mass))
+        self.mass_txt.SetToolTipString(source_hint)
+        self.mass_sizer.AddMany([(self.mass_txt, 0, wx.LEFT, 15),
+                                 (self.source_cb, 0, wx.LEFT, 15),
+                                 (self.wave_color_cb, 0, wx.LEFT, 15)])
+
+    def _layout_intensity(self):
+        """
+        Fill the sizer containing intensity
+        """
+        # get the intensity
+        intensity_value = str(self.resolution.intensity)
+        intensity_unit_txt = wx.StaticText(self, -1, '[counts/s]')
+        intensity_txt = wx.StaticText(self, -1, 'Intensity: ')
+        self.intensity_tcl = InputTextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, -1))
+        intensity_hint = "Intensity of Neutrons"
+        self.intensity_tcl.SetValue(intensity_value)
+        self.intensity_tcl.SetToolTipString(intensity_hint)
+        self.intensity_sizer.AddMany([(intensity_txt, 0, wx.LEFT, 15),
+                                      (self.intensity_tcl, 0, wx.LEFT, 15),
+                                      (intensity_unit_txt, 0, wx.LEFT, 10)])
+
+
+    def _layout_wavelength(self):
+        """
+        Fill the sizer containing wavelength
+        """
+        # get the wavelength
+        wavelength_value = str(self.resolution.get_wavelength())
+        wavelength_unit_txt = wx.StaticText(self, -1, '[A]')
+        wavelength_txt = wx.StaticText(self, -1, 'Wavelength: ')
+        self.wavelength_tcl = InputTextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, -1))
+        wavelength_hint = "Wavelength of Neutrons"
+        self.wavelength_tcl.SetValue(wavelength_value)
+        self.wavelength_tcl.SetToolTipString(wavelength_hint)
+
+        # get the spectrum
+        spectrum_value = self.resolution.get_default_spectrum()
+        self.spectrum_dic['Add new'] = ''
+        self.spectrum_dic['Flat'] = spectrum_value
+
+        self.spectrum_txt = wx.StaticText(self, -1, 'Spectrum: ')
+        self.spectrum_cb = wx.ComboBox(self, -1,
+                                       style=wx.CB_READONLY,
+                                       size=(_BOX_WIDTH, -1),
+                                       name='spectrum')
+        self.spectrum_cb.Append('Add new')
+        self.spectrum_cb.Append('Flat')
+        wx.EVT_COMBOBOX(self.spectrum_cb, -1, self._on_spectrum_cb)
+        spectrum_hint = "Wavelength Spectrum: Intensity vs. wavelength"
+        self.spectrum_cb.SetStringSelection('Flat')
+        self.spectrum_cb.SetToolTipString(spectrum_hint)
+        self.wavelength_sizer.AddMany([(wavelength_txt, 0, wx.LEFT, 15),
+                                       (self.wavelength_tcl, 0, wx.LEFT, 5),
+                                       (wavelength_unit_txt, 0, wx.LEFT, 5),
+                                       (self.spectrum_txt, 0, wx.LEFT, 20),
+                                       (self.spectrum_cb, 0, wx.LEFT, 5)])
+        self.spectrum_txt.Show(False)
+        self.spectrum_cb.Show(False)
+
+    def _layout_wavelength_spread(self):
+        """
+        Fill the sizer containing wavelength
+        """
+        # get the wavelength
+        wavelength_spread_value = str(self.resolution.get_wavelength_spread())
+        wavelength_spread_unit_txt = wx.StaticText(self, -1, '')
+        wavelength_spread_txt = wx.StaticText(self, -1, 'Wavelength Spread: ')
+        self.wavelength_spread_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        wavelength_spread_hint = "Wavelength  Spread of Neutrons"
+        self.wavelength_spread_tcl.SetValue(wavelength_spread_value)
+        self.wavelength_spread_tcl.SetToolTipString(wavelength_spread_hint)
+        self.wavelength_spread_sizer.AddMany([(wavelength_spread_txt, 0,
+                                               wx.LEFT, 15),
+                                              (self.wavelength_spread_tcl, 0, wx.LEFT, 15),
+                                              (wavelength_spread_unit_txt, 0, wx.LEFT, 10)])
+
+
+    def _layout_source_aperture(self):
+        """
+        Fill the sizer containing source aperture size
+        """
+        # get the wavelength
+        source_aperture_value = str(self.resolution.source_aperture_size[0])
+        if len(self.resolution.source_aperture_size) > 1:
+            source_aperture_value += ", "
+            source_aperture_value += str(self.resolution.source_aperture_size[1])
+        source_aperture_unit_txt = wx.StaticText(self, -1, '[cm]')
+        source_aperture_txt = wx.StaticText(self, -1, 'Source Aperture Size: ')
+        self.source_aperture_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        source_aperture_hint = "Source Aperture Size"
+        self.source_aperture_tcl.SetValue(source_aperture_value)
+        self.source_aperture_tcl.SetToolTipString(source_aperture_hint)
+        self.source_aperture_sizer.AddMany([(source_aperture_txt, 0, wx.LEFT, 15),
+                                            (self.source_aperture_tcl, 0, wx.LEFT, 15),
+                                            (source_aperture_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_sample_aperture(self):
+        """
+        Fill the sizer containing sample aperture size
+        """
+        # get the wavelength
+        sample_aperture_value = str(self.resolution.sample_aperture_size[0])
+        if len(self.resolution.sample_aperture_size) > 1:
+            sample_aperture_value += ", "
+            sample_aperture_value += str(self.resolution.sample_aperture_size[1])
+        sample_aperture_unit_txt = wx.StaticText(self, -1, '[cm]')
+        sample_aperture_txt = wx.StaticText(self, -1, 'Sample Aperture Size: ')
+        self.sample_aperture_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        sample_aperture_hint = "Sample Aperture Size"
+        self.sample_aperture_tcl.SetValue(sample_aperture_value)
+        self.sample_aperture_tcl.SetToolTipString(sample_aperture_hint)
+        self.sample_aperture_sizer.AddMany([(sample_aperture_txt, 0, wx.LEFT, 15),
+                                            (self.sample_aperture_tcl, 0, wx.LEFT, 15),
+                                            (sample_aperture_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_source2sample_distance(self):
+        """
+        Fill the sizer containing souce2sample_distance
+        """
+        # get the wavelength
+        source2sample_distance_value = str(self.resolution.source2sample_distance[0])
+
+        source2sample_distance_unit_txt = wx.StaticText(self, -1, '[cm]')
+        source2sample_distance_txt = wx.StaticText(self, -1,
+                                                   'Source to Sample Aperture Distance: ')
+        self.source2sample_distance_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        source2sample_distance_hint = "Source to Sample Aperture Distance"
+        self.source2sample_distance_tcl.SetValue(source2sample_distance_value)
+        self.source2sample_distance_tcl.SetToolTipString(source2sample_distance_hint)
+        self.source2sample_distance_sizer.AddMany([(source2sample_distance_txt, 0, wx.LEFT, 15),
+                                                   (self.source2sample_distance_tcl, 0, wx.LEFT, 15),
+                                                   (source2sample_distance_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_sample2sample_distance(self):
+        """
+        Fill the sizer containing sampleslit2sample_distance
+        """
+        # get the distance
+        sample2sample_distance_value = str(self.resolution.sample2sample_distance[0])
+
+        sample2sample_distance_unit_txt = wx.StaticText(self, -1, '[cm]')
+        sample2sample_distance_txt = wx.StaticText(self, -1, 'Sample Offset: ')
+        self.sample2sample_distance_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        sample2sample_distance_hint = "Sample Aperture to Sample Distance"
+        self.sample2sample_distance_tcl.SetValue(sample2sample_distance_value)
+        self.sample2sample_distance_tcl.SetToolTipString(sample2sample_distance_hint)
+        self.sample2sample_distance_sizer.AddMany([(sample2sample_distance_txt, 0, wx.LEFT, 15),
+                                                   (self.sample2sample_distance_tcl, 0, wx.LEFT, 15),
+                                                   (sample2sample_distance_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_sample2detector_distance(self):
+        """
+        Fill the sizer containing sample2detector_distance
+        """
+        # get the wavelength
+        sample2detector_distance_value = str(self.resolution.sample2detector_distance[0])
+
+        sample2detector_distance_unit_txt = wx.StaticText(self, -1, '[cm]')
+        sample2detector_distance_txt = wx.StaticText(self, -1,
+                                                     'Sample Aperture to Detector Distance: ')
+        self.sample2detector_distance_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        sample2detector_distance_hint = "Sample Aperture to Detector Distance"
+        self.sample2detector_distance_tcl.SetValue(sample2detector_distance_value)
+        self.sample2detector_distance_tcl.SetToolTipString(sample2detector_distance_hint)
+        self.sample2detector_distance_sizer.AddMany([\
+                        (sample2detector_distance_txt, 0, wx.LEFT, 15),
+                        (self.sample2detector_distance_tcl, 0, wx.LEFT, 15),
+                        (sample2detector_distance_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_detector_size(self):
+        """
+        Fill the sizer containing detector size
+        """
+        # get the wavelength
+        detector_size_value = str(self.resolution.detector_size[0])
+        if len(self.resolution.detector_size) > 1:
+            detector_size_value += ", "
+            detector_size_value += str(self.resolution.detector_size[1])
+        detector_size_unit_txt = wx.StaticText(self, -1, '')
+        detector_size_txt = wx.StaticText(self, -1, 'Number of Pixels on Detector: ')
+        self.detector_size_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        detector_size_hint = "Number of Pixels on Detector"
+        self.detector_size_tcl.SetValue(detector_size_value)
+        self.detector_size_tcl.SetToolTipString(detector_size_hint)
+        self.detector_size_sizer.AddMany([(detector_size_txt, 0, wx.LEFT, 15),
+                                          (self.detector_size_tcl, 0, wx.LEFT, 15),
+                                          (detector_size_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_detector_pix_size(self):
+        """
+        Fill the sizer containing detector pixel size
+        """
+        # get the detector_pix_size
+        detector_pix_size_value = str(self.resolution.detector_pix_size[0])
+        if len(self.resolution.detector_pix_size) > 1:
+            detector_pix_size_value += ", "
+            detector_pix_size_value += str(self.resolution.detector_pix_size[1])
+        detector_pix_size_unit_txt = wx.StaticText(self, -1, '[cm]')
+        detector_pix_size_txt = wx.StaticText(self, -1, 'Detector Pixel Size: ')
+        self.detector_pix_size_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        detector_pix_size_hint = "Detector Pixel Size"
+        self.detector_pix_size_tcl.SetValue(detector_pix_size_value)
+        self.detector_pix_size_tcl.SetToolTipString(detector_pix_size_hint)
+        self.detector_pix_size_sizer.AddMany([(detector_pix_size_txt, 0, wx.LEFT, 15),
+                                              (self.detector_pix_size_tcl, 0, wx.LEFT, 15),
+                                              (detector_pix_size_unit_txt, 0, wx.LEFT, 10)])
+
+    def _layout_input(self):
+        """
+        Fill the sizer containing inputs; qx, qy
+        """
+
+        q_title = wx.StaticText(self, -1, "[Q Location of the Estimation]:")
+        # sizers for inputs
+        inputQx_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        inputQy_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        # get the default dq
+        qx_value = str(_Q_DEFAULT)
+        qy_value = str(_Q_DEFAULT)
+        qx_unit_txt = wx.StaticText(self, -1, '[1/A]  ')
+        qy_unit_txt = wx.StaticText(self, -1, '[1/A]  ')
+        qx_name_txt = wx.StaticText(self, -1, 'Qx: ')
+        qy_name_txt = wx.StaticText(self, -1, 'Qy: ')
+        self.qx_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH * 3, -1))
+        self.qy_tcl = InputTextCtrl(self, -1, size=(_BOX_WIDTH * 3, -1))
+        qx_hint = "Type the Qx value."
+        qy_hint = "Type the Qy value."
+        self.qx_tcl.SetValue(qx_value)
+        self.qy_tcl.SetValue(qy_value)
+        self.qx_tcl.SetToolTipString(qx_hint)
+        self.qy_tcl.SetToolTipString(qy_hint)
+        inputQx_sizer.AddMany([(qx_name_txt, 0, wx.LEFT, 15),
+                               (self.qx_tcl, 0, wx.LEFT, 15),
+                               (qx_unit_txt, 0, wx.LEFT, 15)])
+        inputQy_sizer.AddMany([(qy_name_txt, 0, wx.LEFT, 15),
+                               (self.qy_tcl, 0, wx.LEFT, 15),
+                               (qy_unit_txt, 0, wx.LEFT, 15)])
+        self.input_sizer.AddMany([(q_title, 0, wx.LEFT, 15),
+                                  (inputQx_sizer, 0,
+                                   wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                  (inputQy_sizer, 0,
+                                   wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+
+    def _layout_output(self):
+        """
+        Fill the sizer containing dQ|| and dQ+
+        """
+        sigma_title = wx.StaticText(self, -1,
+                                    "[Standard Deviation of the Resolution Distribution]:")
+        # sizers for inputs
+        outputQxy_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        outputQ_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        sigma_unit = '[' + '1/A' + ']'
+        self.sigma_r_txt = wx.StaticText(self, -1, 'Sigma_x:   ')
+        self.sigma_r_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH * 0.8, -1))
+        self.sigma_phi_txt = wx.StaticText(self, -1, 'Sigma_y:')
+        self.sigma_phi_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH * 0.8, -1))
+        self.sigma_lamd_txt = wx.StaticText(self, -1, 'Sigma_lamd:')
+        self.sigma_lamd_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH * 0.7, -1))
+        sigma_1d_txt = wx.StaticText(self, -1, '( 1D:   Sigma:')
+        self.sigma_1d_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH * 0.7, -1))
+        sigmax_hint = " The x component of the geometric resolution,"
+        sigmax_hint += " excluding sigma_lamda."
+        sigmay_hint = " The y component of the geometric resolution,"
+        sigmay_hint += " excluding sigma_lamda."
+        sigma_hint_lamd = " The wavelength contribution in the radial direction"
+        sigma_hint_lamd += ".\n Note: The phi component is always zero."
+        sigma_hint_1d = " Resolution in 1-dimension (for 1D data)."
+        self.sigma_r_tcl.SetToolTipString(sigmax_hint)
+        self.sigma_phi_tcl.SetToolTipString(sigmay_hint)
+        self.sigma_lamd_tcl.SetToolTipString(sigma_hint_lamd)
+        self.sigma_1d_tcl.SetToolTipString(sigma_hint_1d)
+        sigma_r_unit_txt = wx.StaticText(self, -1, sigma_unit)
+        sigma_phi_unit_txt = wx.StaticText(self, -1, sigma_unit)
+        sigma_lamd_unit_txt = wx.StaticText(self, -1, sigma_unit)
+        sigma_1d_unit_txt = wx.StaticText(self, -1, sigma_unit + ' )')
+        outputQxy_sizer.AddMany([(self.sigma_r_txt, 0, wx.LEFT, 15),
+                                 (self.sigma_r_tcl, 0, wx.LEFT, 15),
+                                 (sigma_r_unit_txt, 0, wx.LEFT, 15),
+                                 (self.sigma_phi_txt, 0, wx.LEFT, 15),
+                                 (self.sigma_phi_tcl, 0, wx.LEFT, 15),
+                                 (sigma_phi_unit_txt, 0, wx.LEFT, 15)])
+        outputQ_sizer.AddMany([(self.sigma_lamd_txt, 0, wx.LEFT, 15),
+                               (self.sigma_lamd_tcl, 0, wx.LEFT, 15),
+                               (sigma_lamd_unit_txt, 0, wx.LEFT, 15),
+                               (sigma_1d_txt, 0, wx.LEFT, 15),
+                               (self.sigma_1d_tcl, 0, wx.LEFT, 15),
+                               (sigma_1d_unit_txt, 0, wx.LEFT, 15)])
+        self.output_sizer.AddMany([(sigma_title, 0, wx.LEFT, 15),
+                                   (outputQxy_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (outputQ_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+
+    def _layout_hint(self):
+        """
+        Fill the sizer containing hint
+        """
+        hint_msg = ""
+        #hint_msg += "This tool is to approximately compute "
+        #hint_msg += "the instrumental resolution (dQ)."
+
+        self.hint_txt = wx.StaticText(self, -1, hint_msg)
+        self.hint_sizer.AddMany([(self.hint_txt, 0, wx.LEFT, 15)])
+
+    def _layout_button(self):
+        """
+        Do the layout for the button widgets
+        """
+
+        wx_id = wx.NewId()
+        self.reset_button = wx.Button(self, wx_id, "Reset")
+        hint_on_reset = "..."
+        self.reset_button.SetToolTipString(hint_on_reset)
+        self.Bind(wx.EVT_BUTTON, self.on_reset, id=wx_id)
+        #compute button
+        wx_id = wx.NewId()
+        self.compute_button = wx.Button(self, wx_id, "Compute")
+        hint_on_compute = "Compute... Please wait until finished."
+        self.compute_button.SetToolTipString(hint_on_compute)
+        self.Bind(wx.EVT_BUTTON, self.on_compute, id=wx_id)
+        #help button
+        wx_id = wx.NewId()
+        self.help_button = wx.Button(self, wx_id, "HELP")
+        hint_on_help = "Help on using the Resolution Calculator"
+        self.help_button.SetToolTipString(hint_on_help)
+        self.Bind(wx.EVT_BUTTON, self.on_help, id=wx_id)
+        # close button
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this window.")
+
+        self.button_sizer.Add((110, -1))
+        self.button_sizer.AddMany([(self.reset_button, 0, wx.LEFT, 50),
+                                   (self.compute_button, 0, wx.LEFT, 15),
+                                   (self.help_button, 0, wx.LEFT, 15),
+                                   (self.bt_close, 0, wx.LEFT, 15)])
+        self.compute_button.SetFocus()
+
+    def _layout_image(self):
+        """
+        Layout for image plot
+        """
+        # Contribution by James C.
+        # Instantiate a figure object that will contain our plots.
+        # Make the fig a little smaller than the default
+        self.figure = Figure(figsize=(6.5, 6), facecolor='white')
+
+        # Initialize the figure canvas, mapping the figure object to the plot
+        # engine backend.
+        self.canvas = FigureCanvas(self, wx.ID_ANY, self.figure)
+
+        # Wx-Pylab magic ...
+        # Make our canvas the active figure manager for pylab so that when
+        # pylab plotting statements are executed they will operate on our
+        # canvas and not create a new frame and canvas for display purposes.
+        # This technique allows this application to execute code that uses
+        # pylab stataments to generate plots and embed these plots in our
+        # application window(s).
+        self.fm = FigureManagerBase(self.canvas, 1)
+        _pylab_helpers.Gcf.set_active(self.fm)
+
+        # Instantiate the matplotlib navigation toolbar and explicitly show it.
+        mpl_toolbar = Toolbar(self.canvas)
+        # Diable pan
+        mpl_toolbar.DeleteToolByPos(3)
+
+        # Add a toolbar into the frame
+        mpl_toolbar.Realize()
+
+        # Compute before adding the canvas to the sizer
+        self.on_compute()
+
+        # Fill up the sizer
+        if IS_WIN:
+            gap = 27
+        else:
+            gap = 13
+        self.vertical_r_sizer.Add(self.canvas, 0, wx.ALL | wx.EXPAND, 2)
+        self.vertical_r_spacer.Add((0, gap))
+        self.vertical_r_spacer.Add(self.vertical_r_sizer, 0, wx.ALL | wx.EXPAND, 2)
+        self.vertical_r_spacer.Add((0, gap))
+        self.vertical_r_spacer.Add(wx.StaticLine(self), 0, wx.ALL | wx.EXPAND, 2)
+        self.vertical_r_spacer.Add(mpl_toolbar, 0, wx.ALL | wx.EXPAND, 2)
+
+    def _do_layout(self):
+        """
+            Draw window layout
+        """
+        #  Title of parameters
+        instrument_txt = wx.StaticText(self, -1, '[Instrumental Parameters]:')
+        # Build individual layouts
+        self._define_structure()
+        self._layout_mass()
+        self._layout_wavelength()
+        self._layout_wavelength_spread()
+        self._layout_source_aperture()
+        self._layout_sample_aperture()
+        self._layout_source2sample_distance()
+        self._layout_sample2detector_distance()
+        self._layout_sample2sample_distance()
+        self._layout_detector_size()
+        self._layout_detector_pix_size()
+        self._layout_input()
+        self._layout_output()
+        self._layout_hint()
+        self._layout_button()
+        # Fill the sizers
+        self.boxsizer_source.AddMany([(instrument_txt, 0,
+                                       wx.EXPAND | wx.LEFT, 15),
+                                      (self.mass_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.wavelength_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.wavelength_spread_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.source_aperture_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.sample_aperture_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.source2sample_distance_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.sample2detector_distance_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.sample2sample_distance_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.detector_size_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (self.detector_pix_size_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (wx.StaticLine(self), 0,
+                                       wx.ALL | wx.EXPAND, 5),
+                                      (self.input_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                      (wx.StaticLine(self), 0,
+                                       wx.ALL | wx.EXPAND, 5),
+                                      (self.output_sizer, 0,
+                                       wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.vertical_l_sizer.AddMany([(self.boxsizer_source, 0, wx.ALL, 10),
+                                       (wx.StaticLine(self), 0,
+                                        wx.ALL | wx.EXPAND, 5),
+                                       (self.button_sizer, 0,
+                                        wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.main_sizer.Add(self.vertical_l_sizer, 0, wx.ALL, 10)
+
+        # Build image plot layout
+        self._layout_image()
+        # Add a vertical static line
+        self.main_sizer.Add(wx.StaticLine(self, -1, (2, 2),
+                                          (2, PANEL_HEIGHT * 0.94), style=wx.LI_VERTICAL))
+        # Add the plot to main sizer
+        self.main_sizer.Add(self.vertical_r_spacer, 0, wx.ALL, 10)
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def on_help(self, event):
+        """
+        Bring up the Resolution calculator Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/calculator/resolution_calculator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "Resolution Calculator Help")
+
+    def on_close(self, event):
+        """
+        close the window containing this panel
+        """
+        # get ready for other events
+        if event is not None:
+            event.Skip()
+        # Clear the plot
+        if self.image != None:
+            self.image.clf()
+            # reset image
+            self.image = None
+        # Close panel
+        self.parent.OnClose(None)
+
+    def on_compute(self, event=None):
+        """
+        Execute the computation of resolution
+        """
+        wx.CallAfter(self.on_compute_call, event)
+
+    def on_compute_call(self, event=None):
+        """
+        Execute the computation of resolution
+        """
+        # Skip event for next event
+        if event != None:
+            event.Skip()
+            msg = "Please Check your input values "
+            msg += "before starting the computation..."
+
+        # message
+        status_type = 'progress'
+        msg = 'Calculating...'
+        self._status_info(msg, status_type)
+
+        status_type = 'stop'
+        # Q min max list default
+        qx_min = []
+        qx_max = []
+        qy_min = []
+        qy_max = []
+        # possible max qrange
+        self.resolution.qxmin_limit = 0
+        self.resolution.qxmax_limit = 0
+        self.resolution.qymin_limit = 0
+        self.resolution.qymax_limit = 0
+        # q min and max of the detector
+        try:
+            # Get all the values at set to compute
+            # default num bin of wave list
+            self.num_wave = 10
+            wavelength = self._str2longlist(self.wavelength_tcl.GetValue())
+            source = self.source_cb.GetValue()
+            mass = self.source_mass[str(source)]
+            self.resolution.set_neutron_mass(float(mass))
+            wavelength_spread = self._str2longlist(\
+                        self.wavelength_spread_tcl.GetValue().split(';')[0])
+            # Validate the wave inputs
+            wave_input = self._validate_q_input(wavelength, wavelength_spread)
+            if wave_input != None:
+                wavelength, wavelength_spread = wave_input
+
+            self.resolution.set_wave(wavelength)
+            self.resolution.set_wave_spread(wavelength_spread)
+            source_aperture_size = self.source_aperture_tcl.GetValue()
+            source_aperture_size = self._string2list(source_aperture_size)
+            self.resolution.set_source_aperture_size(source_aperture_size)
+            sample_aperture_size = self.sample_aperture_tcl.GetValue()
+            sample_aperture_size = self._string2list(sample_aperture_size)
+            self.resolution.set_sample_aperture_size(sample_aperture_size)
+            source2sample_distance = self.source2sample_distance_tcl.GetValue()
+            source2sample_distance = self._string2list(source2sample_distance)
+            self.resolution.set_source2sample_distance(source2sample_distance)
+            sample2sample_distance = self.sample2sample_distance_tcl.GetValue()
+            sample2sample_distance = self._string2list(sample2sample_distance)
+            self.resolution.set_sample2sample_distance(sample2sample_distance)
+            sample2detector_distance = \
+                                self.sample2detector_distance_tcl.GetValue()
+            sample2detector_distance = \
+                                self._string2list(sample2detector_distance)
+            self.resolution.set_sample2detector_distance(\
+                                                    sample2detector_distance)
+            detector_size = self.detector_size_tcl.GetValue()
+            detector_size = self._string2list(detector_size)
+            self.resolution.set_detector_size(detector_size)
+            detector_pix_size = self.detector_pix_size_tcl.GetValue()
+            detector_pix_size = self._string2list(detector_pix_size)
+            self.resolution.set_detector_pix_size(detector_pix_size)
+            self.qx = self._string2inputlist(self.qx_tcl.GetValue())
+            self.qy = self._string2inputlist(self.qy_tcl.GetValue())
+
+            # Find min max of qs
+            xmin = min(self.qx)
+            xmax = max(self.qx)
+            ymin = min(self.qy)
+            ymax = max(self.qy)
+            if not self._validate_q_input(self.qx, self.qy):
+                raise
+        except:
+            msg = "An error occured during the resolution computation."
+            msg += "Please check your inputs..."
+            self._status_info(msg, status_type)
+            wx.MessageBox(msg, 'Warning')
+            return
+            #raise ValueError, "Invalid Q Input..."
+
+        # Validate the q inputs
+        q_input = self._validate_q_input(self.qx, self.qy)
+        if q_input != None:
+            self.qx, self.qy = q_input
+
+        # Make list of q min max for mapping
+        for i in range(len(self.qx)):
+            qx_min.append(xmin)
+            qx_max.append(xmax)
+        for i in range(len(self.qy)):
+            qy_min.append(ymin)
+            qy_max.append(ymax)
+
+        # Compute the resolution
+        if self.image != None:
+            #_pylab_helpers.Gcf.set_active(self.fm)
+            _pylab_helpers.Gcf.figs = {}
+            # Clear the image before redraw
+            self.image.clf()
+            # reset the image
+            self.resolution.reset_image()
+
+        # Compute and get the image plot
+        try:
+            from sas.sasgui.perspectives.calculator.resolcal_thread import CalcRes as thread
+            self.sigma_strings = '\nResolution: Computation is finished. \n'
+            cal_res = thread(func=self._map_func,
+                             qx=self.qx,
+                             qy=self.qy,
+                             qx_min=qx_min,
+                             qx_max=qx_max,
+                             qy_min=qy_min,
+                             qy_max=qy_max,
+                             image=self.image,
+                             completefn=self.complete)
+            cal_res.queue()
+            msg = "Computation is in progress..."
+            status_type = 'progress'
+            self._status_info(msg, status_type)
+        except:
+            raise
+
+    def complete(self, image, elapsed=None):
+        """
+        Callafter complete: wx call after needed for stable output
+        """
+        wx.CallAfter(self.complete_cal, image, elapsed)
+
+    def complete_cal(self, image, elapsed=None):
+        """
+        Complete computation
+        """
+        self.image = image
+        # Draw lines in image before drawing
+        wave_list, _ = self.resolution.get_wave_list()
+        if len(wave_list) > 1 and wave_list[-1] == max(wave_list):
+            # draw a green rectangle(limit for the longest wavelength
+            # to be involved) for tof inputs
+            self._draw_lines(self.image, color='g')
+        self._draw_lines(self.image, color='r')
+        # Draw image
+        self.image.draw()
+
+        # Get and format the sigmas
+        sigma_r = self.format_number(self.resolution.sigma_1)
+        sigma_phi = self.format_number(self.resolution.sigma_2)
+        sigma_lamd = self.format_number(self.resolution.sigma_lamd)
+        sigma_1d = self.format_number(self.resolution.sigma_1d)
+
+        # Set output values
+        self.sigma_r_tcl.SetValue(str(sigma_r))
+        self.sigma_phi_tcl.SetValue(str(sigma_phi))
+        self.sigma_lamd_tcl.SetValue(str(sigma_lamd))
+        self.sigma_1d_tcl.SetValue(str(sigma_1d))
+        msg = self.sigma_strings
+        msg += "\n"
+        status_type = 'stop'
+        self._status_info(msg, status_type)
+
+    def _draw_lines(self, image=None, color='r'):
+        """
+        Draw lines in image if applicable
+        : Param image: pylab object
+        """
+        if image == None:
+            return
+        if color == 'g':
+            # Get the params from resolution
+            # ploting range for largest wavelength
+            qx_min = self.resolution.qx_min
+            qx_max = self.resolution.qx_max
+            qy_min = self.resolution.qy_min
+            qy_max = self.resolution.qy_max
+            # detector range
+            detector_qx_min = self.resolution.detector_qx_min
+            detector_qx_max = self.resolution.detector_qx_max
+            detector_qy_min = self.resolution.detector_qy_min
+            detector_qy_max = self.resolution.detector_qy_max
+        else:
+            qx_min, qx_max, qy_min, qy_max = \
+                                self.resolution.get_detector_qrange()
+            # detector range
+            detector_qx_min = self.resolution.qxmin_limit
+            detector_qx_max = self.resolution.qxmax_limit
+            detector_qy_min = self.resolution.qymin_limit
+            detector_qy_max = self.resolution.qymax_limit
+
+        # Draw zero axis lines
+        if qy_min < 0 and qy_max >= 0:
+            image.axhline(linewidth=1)
+        if qx_min < 0 and qx_max >= 0:
+            image.axvline(linewidth=1)
+
+        # Find x and y ratio values to draw the detector outline
+        x_min = fabs(detector_qx_min - qx_min) / (qx_max - qx_min)
+        x_max = fabs(detector_qx_max - qx_min) / (qx_max - qx_min)
+        y_min = fabs(detector_qy_min - qy_min) / (qy_max - qy_min)
+        y_max = fabs(detector_qy_max - qy_min) / (qy_max - qy_min)
+
+        # Draw Detector outline
+        if detector_qy_min >= qy_min:
+            image.axhline(y=detector_qy_min + 0.0002,
+                          xmin=x_min, xmax=x_max,
+                          linewidth=2, color=color)
+        if detector_qy_max <= qy_max:
+            image.axhline(y=detector_qy_max - 0.0002,
+                          xmin=x_min, xmax=x_max,
+                          linewidth=2, color=color)
+        if detector_qx_min >= qx_min:
+            image.axvline(x=detector_qx_min + 0.0002,
+                          ymin=y_min, ymax=y_max,
+                          linewidth=2, color=color)
+        if detector_qx_max <= qx_max:
+            image.axvline(x=detector_qx_max - 0.0002,
+                          ymin=y_min, ymax=y_max,
+                          linewidth=2, color=color)
+        xmin = min(self.qx)
+        xmax = max(self.qx)
+        ymin = min(self.qy)
+        ymax = max(self.qy)
+        if color != 'g':
+            if xmin < detector_qx_min or xmax > detector_qx_max or \
+                        ymin < detector_qy_min or ymax > detector_qy_max:
+                # message
+                status_type = 'stop'
+                msg = 'At least one q value located out side of\n'
+                msg += " the detector range (%s < qx < %s, %s < qy < %s),\n" % \
+                        (self.format_number(detector_qx_min),
+                         self.format_number(detector_qx_max),
+                         self.format_number(detector_qy_min),
+                         self.format_number(detector_qy_max))
+                msg += " is ignored in computation.\n"
+
+                self._status_info(msg, status_type)
+                wx.MessageBox(msg, 'Warning')
+
+    def _map_func(self, qx, qy, qx_min, qx_max, qy_min, qy_max):
+        """
+        Prepare the Mapping for the computation
+        : params qx, qy, qx_min, qx_max, qy_min, qy_max:
+
+        : return: image (pylab)
+        """
+        try:
+            qx_value = float(qx)
+            qy_value = float(qy)
+        except:
+            raise
+        # calculate 2D resolution distribution image
+        image = self.resolution.compute_and_plot(qx_value, qy_value,
+                                                 qx_min, qx_max, qy_min, qy_max,
+                                                 self.det_coordinate)
+        # record sigmas
+        self.sigma_strings += " At Qx = %s, Qy = %s; \n" % (qx_value, qy_value)
+        self._sigma_strings()
+        return image
+
+    def _sigma_strings(self):
+        """
+        Recode sigmas as strins
+        """
+        sigma_r = self.format_number(self.resolution.sigma_1)
+        sigma_phi = self.format_number(self.resolution.sigma_2)
+        sigma_lamd = self.format_number(self.resolution.sigma_lamd)
+        sigma_1d = self.format_number(self.resolution.sigma_1d)
+        # Set output values
+        self.sigma_strings += "   sigma_x = %s\n" % sigma_r
+        self.sigma_strings += "   sigma_y = %s\n" % sigma_phi
+        self.sigma_strings += "   sigma_lamd = %s\n" % sigma_lamd
+        self.sigma_strings += "   sigma_1D = %s\n" % sigma_1d
+
+    def _validate_q_input(self, qx, qy):
+        """
+        Check if q inputs are valid
+        : params qx:  qx as a list
+        : params qy:  qy as a list
+
+        : return: True/False
+        """
+        # check qualifications
+        if qx.__class__.__name__ != 'list':
+            return None
+        if qy.__class__.__name__ != 'list':
+            return None
+        if len(qx) < 1:
+            return None
+        if len(qy) < 1:
+            return None
+        # allow one input
+        if len(qx) == 1 and len(qy) > 1:
+            qx = [qx[0] for ind in range(len(qy))]
+            #self.qx = qx
+        if len(qy) == 1 and len(qx) > 1:
+            qy = [qy[0] for ind in range(len(qx))]
+            #self.qy = qy
+        # check length
+        if len(qx) != len(qy):
+            return None
+        if qx == None or qy == None:
+            return None
+        return qx, qy
+
+    def on_reset(self, event):
+        """
+        Execute the reset
+        """
+        # skip for another event
+        if event != None:
+            event.Skip()
+        # init resolution_calculator
+        self.resolution = ResolutionCalculator()
+        self.resolution.get_all_instrument_params()
+        # reset all param values
+        self.source_cb.SetValue('Neutron')
+        self._on_source_selection(None)
+        self.wave_color_cb.SetValue('Monochromatic')
+        self._on_source_color(None)
+        #self.intensity_tcl.SetValue(str(self.resolution.intensity))
+        self.wavelength_tcl.SetValue(str(6.0))
+        self.wavelength_spread_tcl.SetValue(str(0.125))
+        self.resolution.set_spectrum(self.spectrum_dic['Flat'])
+        self.spectrum_txt.Show(False)
+        self.spectrum_cb.Show(False)
+        source_aperture_value = str(self.resolution.source_aperture_size[0])
+        if len(self.resolution.source_aperture_size) > 1:
+            source_aperture_value += ", "
+            source_aperture_value += \
+                str(self.resolution.source_aperture_size[1])
+        self.source_aperture_tcl.SetValue(str(source_aperture_value))
+        sample_aperture_value = str(self.resolution.sample_aperture_size[0])
+        if len(self.resolution.sample_aperture_size) > 1:
+            sample_aperture_value += ", "
+            sample_aperture_value += \
+                str(self.resolution.sample_aperture_size[1])
+        self.sample_aperture_tcl.SetValue(sample_aperture_value)
+        source2sample_distance_value = \
+            str(self.resolution.source2sample_distance[0])
+        self.source2sample_distance_tcl.SetValue(source2sample_distance_value)
+        sample2sample_distance_value = \
+            str(self.resolution.sample2sample_distance[0])
+        self.sample2sample_distance_tcl.SetValue(sample2sample_distance_value)
+        sample2detector_distance_value = \
+            str(self.resolution.sample2detector_distance[0])
+        self.sample2detector_distance_tcl.SetValue(\
+                                            sample2detector_distance_value)
+        detector_size_value = str(self.resolution.detector_size[0])
+        if len(self.resolution.detector_size) > 1:
+            detector_size_value += ", "
+            detector_size_value += str(self.resolution.detector_size[1])
+        self.detector_size_tcl.SetValue(detector_size_value)
+        detector_pix_size_value = str(self.resolution.detector_pix_size[0])
+        if len(self.resolution.detector_pix_size) > 1:
+            detector_pix_size_value += ", "
+            detector_pix_size_value += str(self.resolution.detector_pix_size[1])
+        self.detector_pix_size_tcl.SetValue(detector_pix_size_value)
+        #layout attribute
+        self.hint_sizer = None
+        # reset q inputs
+        self.qx_tcl.SetValue(str(_Q_DEFAULT))
+        self.qy_tcl.SetValue(str(_Q_DEFAULT))
+        # reset sigma outputs
+        self.sigma_r_tcl.SetValue('')
+        self.sigma_phi_tcl.SetValue('')
+        self.sigma_1d_tcl.SetValue('')
+        # reset radio button
+        #self.r_phi_rb.SetValue(True)
+        # Finally re-compute
+        self.on_compute()
+        # msg on info
+        msg = " Finished the resetting..."
+        self._status_info(msg, 'stop')
+
+    def format_number(self, value=None):
+        """
+        Return a float in a standardized, human-readable formatted string
+        """
+        try:
+            value = float(value)
+        except:
+            output = None
+            return output
+
+        output = "%-7.4g" % value
+        return output.lstrip().rstrip()
+
+    def _string2list(self, string):
+        """
+        Change NNN, NNN to list,ie. [NNN, NNN] where NNN is a number
+        """
+        new_string = []
+        # check the number of floats
+        try:
+            strg = float(string)
+            new_string.append(strg)
+        except:
+            string_split = string.split(',')
+            if len(string_split) == 2:
+                str_1 = string_split[0]
+                str_2 = string_split[1]
+                new_string.append(float(str_1))
+                new_string.append(float(str_2))
+            elif len(string_split) == 1:
+                str_1 = string_split[0]
+                new_string.append(float(str_1))
+            else:
+                msg = "The numbers must be one or two (separated by ',')..."
+                self._status_info(msg, 'stop')
+                raise RuntimeError, msg
+
+        return new_string
+
+    def _string2inputlist(self, string):
+        """
+        Change NNN, NNN,... to list,ie. [NNN, NNN,...] where NNN is a number
+
+        : return new_string: string like list
+        """
+        new_string = []
+        string_split = string.split(',')
+        length = len(string_split)
+        for ind in range(length):
+            try:
+                value = float(string_split[ind])
+                new_string.append(value)
+            except:
+                logging.error(sys.exc_value)
+
+        return new_string
+
+    def _str2longlist(self, string):
+        """
+        Change NNN, NNN,... to list, NNN - NNN ; NNN to list, or float to list
+
+        : return new_string: string like list
+        """
+        msg = "Wrong format of intputs."
+        try:
+            # is float
+            out = [float(string)]
+            return out
+        except:
+            if self.wave_color.lower().count('mono') > 0:
+                wx.MessageBox(msg, 'Warning')
+            else:
+                try:
+                    # has a '-'
+                    if string.count('-') > 0:
+                        value = string.split('-')
+                        if value[1].count(';') > 0:
+                            # has a ';'
+                            last_list = value[1].split(';')
+                            num = math.ceil(float(last_list[1]))
+                            max_value = float(last_list[0])
+                            self.num_wave = num
+                        else:
+                            # default num
+                            num = self.num_wave
+                            max_value = float(value[1])
+                        min_value = float(value[0])
+                        # make a list
+                        bin_size = math.fabs(max_value - min_value) / (num - 1)
+                        out = [min_value + bin_size * bnum for bnum in range(num)]
+                        return out
+                    if string.count(',') > 0:
+                        out = self._string2inputlist(string)
+                        return out
+                except:
+                    logging.error(sys.exc_value)
+
+    def _on_xy_coordinate(self, event=None):
+        """
+        Set the detector coordinate for sigmas to x-y coordinate
+        """
+        if event != None:
+            event.Skip()
+        # Set the coordinate in Cartesian
+        self.det_coordinate = 'cartesian'
+        self.sigma_r_txt.SetLabel('Sigma_x:')
+        self.sigma_phi_txt.SetLabel('Sigma_y:')
+        self._onparamEnter()
+
+    def _on_rp_coordinate(self, event=None):
+        """
+        Set the detector coordinate for sigmas to polar coordinate
+        """
+        if event != None:
+            event.Skip()
+        # Set the coordinate in polar
+        self.det_coordinate = 'polar'
+        self.sigma_r_txt.SetLabel('Sigma_r:   ')
+        self.sigma_phi_txt.SetLabel('Sigma_phi:')
+        self._onparamEnter()
+
+    def _status_info(self, msg='', type="update"):
+        """
+        Status msg
+        """
+        if type == "stop":
+            label = "Compute"
+            able = True
+        else:
+            label = "Wait..."
+            able = False
+        self.compute_button.Enable(able)
+        self.compute_button.SetLabel(label)
+        self.compute_button.SetToolTipString(label)
+        if self.parent.parent != None:
+            wx.PostEvent(self.parent.parent,
+                         StatusEvent(status=msg, type=type))
+
+
+    def _onparamEnter(self, event=None):
+        """
+        On Text_enter_callback, perform compute
+        """
+        self.on_compute()
+
+    def _on_source_selection(self, event=None):
+        """
+        On source combobox selection
+        """
+        if event != None:
+            combo = event.GetEventObject()
+            event.Skip()
+        else:
+            combo = self.source_cb
+        selection = combo.GetValue()
+        mass = self.source_mass[selection]
+        self.resolution.set_neutron_mass(mass)
+        source_hint = "Source Selection: Affect on"
+        source_hint += " the gravitational contribution.\n"
+        source_hint += "Mass of %s: m = %s [g]" % \
+                            (selection, str(self.resolution.get_neutron_mass()))
+        #source_tip.SetTip(source_hint)
+        self.mass_txt.ToolTip.SetTip(source_hint)
+
+    def _on_source_color(self, event=None):
+        """
+        On source color combobox selection
+        """
+        if event != None:
+            #combo = event.GetEventObject()
+            event.Skip()
+        #else:
+        combo = self.wave_color_cb
+        selection = combo.GetValue()
+        self.wave_color = selection
+        if self.wave_color.lower() == 'tof':
+            list = self.resolution.get_wave_list()
+            minw = min(list[0])
+            if len(list[0]) < 2:
+                maxw = 2 * minw
+            else:
+                maxw = max(list[0])
+            self.wavelength_tcl.SetValue('%s - %s' % (minw, maxw))
+            minw = min(list[1])
+            maxw = max(list[1])
+            self.wavelength_spread_tcl.SetValue('%s - %s' % (minw, maxw))
+            spectrum_val = self.spectrum_cb.GetValue()
+            self.resolution.set_spectrum(self.spectrum_dic[spectrum_val])
+            self.spectrum_txt.Show(True)
+            self.spectrum_cb.Show(True)
+
+        else:
+            wavelength = self.resolution.get_wavelength()
+            wavelength_spread = self.resolution.get_wavelength_spread()
+            self.wavelength_tcl.SetValue(str(wavelength))
+            self.wavelength_spread_tcl.SetValue(str(wavelength_spread))
+            self.resolution.set_spectrum(self.spectrum_dic['Flat'])
+            self.spectrum_txt.Show(False)
+            self.spectrum_cb.Show(False)
+        self.wavelength_sizer.Layout()
+        self.Layout()
+
+    def _on_spectrum_cb(self, event=None):
+        """
+        On spectrum ComboBox event
+        """
+        if event != None:
+            #combo = event.GetEventObject()
+            event.Skip()
+        else:
+            raise
+        selection = self.spectrum_cb.GetValue()
+        if selection == 'Add new':
+            path = self._selectDlg()
+            if path == None:
+                self.spectrum_cb.SetValue('Flat')
+                self.resolution.set_spectrum(self.spectrum_dic['Flat'])
+                msg = "No file has been chosen."
+                wx.MessageBox(msg, 'Info')
+                return
+            try:
+                basename = os.path.basename(path)
+                if basename not in self.spectrum_dic.keys():
+                    self.spectrum_cb.Append(basename)
+                self.spectrum_dic[basename] = self._read_file(path)
+                self.spectrum_cb.SetValue(basename)
+                self.resolution.set_spectrum(self.spectrum_dic[basename])
+                return
+            except:
+                raise
+
+        self.resolution.set_spectrum(self.spectrum_dic[selection])
+
+    def _selectDlg(self):
+        """
+        open a dialog file to select a customized spectrum
+        """
+        dlg = wx.FileDialog(self,
+                            "Choose a wavelength spectrum file: Intensity vs. wavelength",
+                            self.parent.parent.get_save_location() , "", "*.*", wx.OPEN)
+        path = None
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+        dlg.Destroy()
+        return path
+
+    def _read_file(self, path):
+        """
+        Read two columns file as tuples of numpy array
+
+        :param path: the path to the file to read
+
+        """
+        try:
+            if path == None:
+                wx.PostEvent(self.parent.parent, StatusEvent(status=\
+                            " Selected Distribution was not loaded: %s" % path))
+                return None, None
+            input_f = open(path, 'r')
+            buff = input_f.read()
+            lines = buff.split('\n')
+
+            wavelength = []
+            intensity = []
+            for line in lines:
+                toks = line.split()
+                try:
+                    wave = float(toks[0])
+                    intens = float(toks[1])
+                    wavelength.append(wave)
+                    intensity.append(intens)
+                except:
+                    # Skip non-data lines
+                    logging.error(sys.exc_value)
+
+            return [wavelength, intensity]
+        except:
+            raise
+
+class ResolutionWindow(widget.CHILD_FRAME):
+    """
+    Resolution Window
+    """
+    def __init__(self, parent=None, manager=None,
+                 title="SAS Resolution Estimator",
+                 size=(PANEL_WIDTH * 2, PANEL_HEIGHT), *args, **kwds):
+        kwds['title'] = title
+        kwds['size'] = size
+        widget.CHILD_FRAME.__init__(self, parent=parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self.panel = ResolutionCalculatorPanel(parent=self)
+        self.Bind(wx.EVT_CLOSE, self.OnClose)
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show(True)
+
+    def OnClose(self, event):
+        """
+        On close event
+        """
+        _pylab_helpers.Gcf.figs = {}
+        if self.manager != None:
+            self.manager.cal_res_frame = None
+        self.Destroy()
+
+
+if __name__ == "__main__":
+    app = wx.PySimpleApp()
+    widget.CHILD_FRAME = wx.Frame
+    frame = ResolutionWindow()
+    frame.Show(True)
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/sample_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/sample_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/sample_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,552 @@
+
+import wx
+import sys
+from copy import deepcopy
+from sas.sasgui.guiframe.utils import check_float
+
+_BOX_WIDTH = 60
+if sys.platform.count("win32") > 0:
+    _STATICBOX_WIDTH = 450
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 430
+    FONT_VARIANT = 0
+else:
+    _STATICBOX_WIDTH = 480
+    PANEL_WIDTH = 550
+    PANEL_HEIGHT = 430
+    FONT_VARIANT = 1
+
+class SampleDialog(wx.Dialog):
+    def __init__(self, parent=None, manager=None, sample=None,
+                 size=(PANEL_WIDTH, PANEL_HEIGHT), title='Sample Editor'):
+
+        wx.Dialog.__init__(self, parent=parent, size=size, title=title)
+        self.parent = parent
+        self.manager = manager
+        self._sample = sample
+        self._reset_sample = deepcopy(sample)
+        self._notes = ""
+        self._description = "Edit Sample"
+        self._do_layout()
+        self.set_values()
+
+    def _define_structure(self):
+        """
+            define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_sample = wx.StaticBox(self, -1, str("Sample"))
+        self.boxsizer_sample = wx.StaticBoxSizer(self.box_sample,
+                                                    wx.VERTICAL)
+        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.id_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.thickness_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.transmission_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.temperature_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.position_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.orientation_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.details_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_name(self):
+        """
+            Do the layout for sample name related widgets
+        """
+        ## Short name for sample [string]
+        sample_name_txt = wx.StaticText(self, -1, 'Sample Name : ')
+        self.sample_name_tcl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH * 5, 20), style=0)
+        self.name_sizer.AddMany([(sample_name_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                       (self.sample_name_tcl, 0, wx.EXPAND)])
+    def _layout_id(self):
+        """
+            Do the layout for id related widgets
+        """
+        ## ID [String]
+        id_txt = wx.StaticText(self, -1, 'ID: ')
+        self.id_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20), style=0)
+        self.id_sizer.AddMany([(id_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.id_tcl, 0, wx.LEFT, 55)])
+
+    def _layout_thickness(self):
+        """
+            Do the  layout for thickness related widgets
+        """
+        ## Thickness [float] [mm]
+        thickness_txt = wx.StaticText(self, -1, 'Thickness:')
+        self.thickness_tcl = wx.TextCtrl(self, -1,
+                                          size=(_BOX_WIDTH, 20), style=0)
+        thickness_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.thickness_unit_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                              style=0)
+        self.thickness_sizer.AddMany([(thickness_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.thickness_tcl, 0, wx.LEFT, 25),
+                            (thickness_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.thickness_unit_tcl, 0, wx.EXPAND)])
+    def _layout_transmission(self):
+        """
+            Do the  layout for transmission related widgets
+        """
+        ## Transmission [float] [fraction]
+        transmission = None
+        transmission_txt = wx.StaticText(self, -1, 'Transmission:')
+        self.transmission_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.transmission_sizer.AddMany([(transmission_txt,
+                                         0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.transmission_tcl, 0, wx.LEFT, 12)])
+
+    def _layout_temperature(self):
+        """
+            Do the  layout for temperature related widgets
+        """
+        ## Temperature [float] [C]
+        temperature_txt = wx.StaticText(self, -1, 'Temperature:')
+        self.temperature_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        temperature_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.temperature_unit_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20),
+                                                            style=0)
+        self.temperature_sizer.AddMany([(temperature_txt, 0,
+                                          wx.LEFT | wx.RIGHT, 10),
+                                     (self.temperature_tcl, 0, wx.LEFT, 10),
+                                (temperature_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.temperature_unit_tcl, 0, wx.EXPAND)])
+
+    def _layout_position(self):
+        """
+            Do the  layout for position related widgets
+        """
+        ## Position [Vector] [mm]
+        position_txt = wx.StaticText(self, -1, 'Position:')
+        x_position_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_position_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        y_position_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_position_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        z_position_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_position_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        position_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.position_unit_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.position_sizer.AddMany([(position_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (x_position_txt, 0, wx.LEFT, 14),
+                                     (self.x_position_tcl, 0, wx.RIGHT, 10),
+                                     (y_position_txt, 0, wx.EXPAND),
+                                     (self.y_position_tcl, 0, wx.RIGHT, 10),
+                                     (z_position_txt, 0, wx.EXPAND),
+                                     (self.z_position_tcl, 0, wx.RIGHT, 10),
+                                     (position_unit_txt, 0, wx.EXPAND),
+                                     (self.position_unit_tcl, 0, wx.RIGHT, 10)])
+    def _layout_orientation(self):
+        """
+            Do the  layout for orientation related widgets
+        """
+        ## Orientation [Vector] [degrees]
+        orientation_txt = wx.StaticText(self, -1, 'Orientation:')
+        x_orientation_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_orientation_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        y_orientation_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_orientation_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        z_orientation_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_orientation_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        orientation_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.orientation_unit_tcl = wx.TextCtrl(self, -1,
+                                                 size=(_BOX_WIDTH, 20), style=0)
+        self.orientation_sizer.AddMany([(orientation_txt, 0,
+                                          wx.LEFT | wx.RIGHT, 10),
+                                     (x_orientation_txt, 0, wx.LEFT, 0),
+                                     (self.x_orientation_tcl, 0, wx.RIGHT, 10),
+                                     (y_orientation_txt, 0, wx.EXPAND),
+                                     (self.y_orientation_tcl, 0, wx.RIGHT, 10),
+                                     (z_orientation_txt, 0, wx.EXPAND),
+                                     (self.z_orientation_tcl, 0, wx.RIGHT, 10),
+                                     (orientation_unit_txt, 0, wx.EXPAND),
+                            (self.orientation_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_details(self):
+        """
+            Do the layout for beam size name related widgets
+        """
+        ## Details
+        details = None
+        details_txt = wx.StaticText(self, -1, 'Details: ')
+        self.details_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH * 5, _BOX_WIDTH),
+                                       style=wx.TE_MULTILINE | wx.HSCROLL)
+        self.details_sizer.AddMany([(details_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                       (self.details_tcl, 0, wx.EXPAND)])
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, 'Apply')
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+        self.bt_apply.SetToolTipString("Apply current changes to the sample.")
+        self.bt_cancel = wx.Button(self, -1, 'Cancel')
+        self.bt_cancel.SetToolTipString("Cancel current changes.")
+        self.bt_cancel.Bind(wx.EVT_BUTTON, self.on_click_cancel)
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.SetToolTipString("Close window.")
+        self.button_sizer.AddMany([(self.bt_apply, 0, wx.LEFT, 200),
+                                   (self.bt_cancel, 0, wx.LEFT, 10),
+                                   (self.bt_close, 0, wx.LEFT, 10)])
+
+    def _do_layout(self, data=None):
+        """
+             Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_id()
+        self._layout_thickness()
+        self._layout_transmission()
+        self._layout_temperature()
+        self._layout_position()
+        self._layout_orientation()
+        self._layout_details()
+        self._layout_button()
+        self.boxsizer_sample.AddMany([(self.name_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.id_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.thickness_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.transmission_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.temperature_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.position_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                    (self.orientation_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.details_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.main_sizer.AddMany([(self.boxsizer_sample, 0, wx.ALL, 10),
+                                  (self.button_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def reset_sample(self):
+        """
+            Put initial values of the sample back to the current sample
+        """
+        self._sample.name = self._reset_sample.name
+        self._sample.ID = self._reset_sample.ID
+        self._sample.thickness = self._reset_sample.thickness
+        self._sample.thickness_unit = self._reset_sample.thickness_unit
+        self._sample.transmission = self._reset_sample.transmission
+        self._sample.temperature = self._reset_sample.temperature
+        self._sample.temperature_unit = self._reset_sample.temperature_unit
+
+        self._sample.position.x = self._reset_sample.position.x
+        self._sample.position.y = self._reset_sample.position.y
+        self._sample.position.z = self._reset_sample.position.x
+        self._sample.position_unit = self._reset_sample.position_unit
+
+        self._sample.orientation.x = self._reset_sample.orientation.x
+        self._sample.orientation.y = self._reset_sample.orientation.y
+        self._sample.orientation.z = self._reset_sample.orientation.x
+        self._sample.orientation_unit = self._reset_sample.orientation_unit
+
+        self._sample.details = self._reset_sample.details
+
+    def set_manager(self, manager):
+        """
+            Set manager of this window
+        """
+        self.manager = manager
+
+    def set_values(self):
+        """
+            take the sample values of the current data and display them
+            through the panel
+        """
+        sample = self._sample
+        #Name
+        self.sample_name_tcl.SetValue(str(sample.name))
+        #id 
+        self.id_tcl.SetValue(str(sample.ID))
+        #thickness
+        self.thickness_tcl.SetValue(str(sample.thickness))
+        self.thickness_unit_tcl.SetValue(str(sample.thickness_unit))
+        #transmission
+        self.transmission_tcl.SetValue(str(sample.transmission))
+        #temperature
+        self.temperature_tcl.SetValue(str(sample.temperature))
+        self.temperature_unit_tcl.SetValue(str(sample.temperature_unit))
+        #position
+        x, y, z = sample.position.x, sample.position.y , sample.position.z
+        self.x_position_tcl.SetValue(str(x))
+        self.y_position_tcl.SetValue(str(y))
+        self.z_position_tcl.SetValue(str(z))
+        self.position_unit_tcl.SetValue(str(sample.position_unit))
+        #orientation
+        x, y = sample.orientation.x, sample.orientation.y
+        z = sample.orientation.z
+        self.x_orientation_tcl.SetValue(str(x))
+        self.y_orientation_tcl.SetValue(str(y))
+        self.z_orientation_tcl.SetValue(str(z))
+        self.orientation_unit_tcl.SetValue(str(sample.orientation_unit))
+
+        self.set_details(sample)
+
+    def set_details(self, sample):
+        """
+            print details on the current sample
+        """
+        #detail
+        msg = ''
+        if sample.details is not None or sample.details:
+            for item in sample.details:
+                msg += "      %s\n" % item
+        self.details_tcl.SetValue(str(msg))
+
+    def get_sample(self):
+        """
+            return the current sample
+        """
+        return self._sample
+
+    def get_notes(self):
+        """
+            return notes
+        """
+        return self._notes
+
+    def on_change_name(self):
+        """
+            Change name
+        """
+        #Change the name of the sample
+        name = self.sample_name_tcl.GetValue().lstrip().rstrip()
+        if name == "" or name == str(None):
+            name = None
+        if self._sample.name != name:
+            self._notes += "Change sample 's "
+            self._notes += "name from %s to %s \n" % (self._sample.name, name)
+            self._sample.name = name
+
+    def on_change_id(self):
+        """
+            Change id of the sample
+        """
+        #Change id
+        id = self.id_tcl.GetValue().lstrip().rstrip()
+        self._sample.ID = id
+        self._notes += " Change ID from"
+        self._notes += " %s to %s \n" % (self._sample.ID, id)
+
+    def on_change_thickness(self):
+        """
+            Change thickness
+        """
+       #Change thickness
+        thickness = self.thickness_tcl.GetValue().lstrip().rstrip()
+        self._sample.thickness = thickness
+        self._notes += " Change thickness from"
+        self._notes += " %s to %s \n" % (self._sample.thickness, thickness)
+
+    def on_change_transmission(self):
+        """
+            Change transmission
+        """
+        #Change transmission
+        transmission = self.transmission_tcl.GetValue().lstrip().rstrip()
+        if self._sample.transmission != transmission:
+            self._notes += " Change transmission from"
+            self._notes += " %s to %s \n" % (self._sample.transmission,
+                                              transmission)
+            self._sample.transmission = transmission
+
+    def on_change_temperature(self):
+        """
+            Change temperature
+        """
+        #Change temperature
+        temperature = self.temperature_tcl.GetValue().lstrip().rstrip()
+        self._sample.temperature = temperature
+        self._notes += " Change temperature from"
+        self._notes += " %s to %s \n" % (self._sample.temperature, temperature)
+        #change temperature unit
+        unit = self.temperature_unit_tcl.GetValue().lstrip().rstrip()
+        if self._sample.temperature_unit != unit:
+            self._notes += " Change temperature's unit from "
+            self._notes += "%s to %s" % (self._sample.temperature_unit, unit)
+            self._sample.temperature_unit = unit
+
+    def on_change_position(self):
+        """
+            Change position
+        """
+        #Change x coordinate
+        x_position = self.x_position_tcl.GetValue().lstrip().rstrip()
+        if x_position == "" or x_position == str(None):
+            x_position = None
+        else:
+            if check_float(self.x_position_tcl):
+                if self._sample.position.x != float(x_position) :
+                    self._notes += "Change x of position from "
+                    self._notes += "%s to %s \n" % (self._sample.position.x,
+                                                     x_position)
+                    self._sample.position.x = float(x_position)
+            else:
+                self._notes += "Error: Expected a float for the position 's x "
+                self._notes += "won't changes x position from "
+                self._notes += "%s to %s" % (self._sample.position.x, x_position)
+        #Change y coordinate
+        y_position = self.y_position_tcl.GetValue().lstrip().rstrip()
+        if y_position == "" or y_position == str(None):
+            y_position = None
+            self._sample.position.y = y_position
+        else:
+            if check_float(self.y_position_tcl):
+                if self._sample.position.y != float(y_position):
+                    self._notes += "Change y of position from "
+                    self._notes += "%s to %s \n" % (self._sample.position.y,
+                                                    y_position)
+                    self._sample.position.y = float(y_position)
+            else:
+                self._notes += "Error: Expected a float for the beam size's y "
+                self._notes += "won't changes y position from "
+                self._notes += "%s to %s" % (self._sample.position.y, y_position)
+        #Change z coordinate
+        z_position = self.z_position_tcl.GetValue().lstrip().rstrip()
+        if z_position == "" or z_position == str(None):
+            z_position = None
+            self._sample.position.z = z_position
+        else:
+            if check_float(self.z_position_tcl):
+                if self._sample.position.z != float(z_position):
+                    self._notes += "Change z of position from "
+                    self._notes += "%s to %s \n" % (self._sample.position.z,
+                                                    z_position)
+                    self._sample.position.z = float(z_position)
+            else:
+                self._notes += "Error: Expected a float for position's x "
+                self._notes += "won't changes z position from "
+                self._notes += "%s to %s" % (self._sample.position.z, z_position)
+        #change the beam center unit
+        unit = self.position_unit_tcl.GetValue().lstrip().rstrip()
+        if self._sample.position_unit != unit:
+            self._notes += " Change position's unit from "
+            self._notes += "%s to %s" % (self._sample.position_unit, unit)
+
+    def on_change_orientation(self):
+        """
+            Change orientation
+        """
+        #Change x coordinate
+        x_orientation = self.x_orientation_tcl.GetValue().lstrip().rstrip()
+        if x_orientation == "" or x_orientation == str(None):
+            x_orientation = None
+        else:
+            if check_float(self.x_orientation_tcl):
+                if self._sample.orientation.x != float(x_orientation) :
+                    self._notes += "Change x of orientation from "
+                    self._notes += "%s to %s \n" % (self._sample.orientation.x,
+                                                   x_orientation)
+                    self._sample.orientation.x = float(x_orientation)
+            else:
+                self._notes += "Error: Expected a float for orientation 's x "
+                self._notes += "won't changes x orientation from "
+                self._notes += "%s to %s" % (self._sample.orientation.x,
+                                              x_orientation)
+        #Change y coordinate
+        y_orientation = self.y_orientation_tcl.GetValue().lstrip().rstrip()
+        if y_orientation == "" or y_orientation == str(None):
+            y_orientation = None
+            self._sample.orientation.y = y_orientation
+        else:
+            if check_float(self.y_orientation_tcl):
+                if self._sample.orientation.y != float(y_orientation):
+                    self._notes += "Change y of orientation from "
+                    self._notes += "%s to %s \n" % (self._sample.orientation.y,
+                                                     y_orientation)
+                    self._sample.orientation.y = float(y_orientation)
+            else:
+                self._notes += "Error: Expected a float for orientation's y "
+                self._notes += "won't changes y orientation from "
+                self._notes += "%s to %s" % (self._sample.orientation.y,
+                                            y_orientation)
+        #Change z coordinate
+        z_orientation = self.z_orientation_tcl.GetValue().lstrip().rstrip()
+        if z_orientation == "" or z_orientation == str(None):
+            z_orientation = None
+            self._sample.orientation.z = z_orientation
+        else:
+            if check_float(self.z_orientation_tcl):
+                if self._sample.orientation.z != float(z_orientation):
+                    self._notes += "Change z of orientation from "
+                    self._notes += "%s to %s \n" % (self._sample.orientation.z,
+                                                     z_orientation)
+                    self._sample.orientation.z = float(z_orientation)
+            else:
+                self._notes += "Error: Expected a float for orientation 's x "
+                self._notes += "won't changes z orientation from "
+                self._notes += "%s to %s" % (self._sample.orientation.z,
+                                              z_orientation)
+        #change the beam center unit
+        unit = self.orientation_unit_tcl.GetValue().lstrip().rstrip()
+        if self._sample.orientation_unit != unit:
+            self._notes += " Change orientation's unit from "
+            self._notes += "%s to %s" % (self._sample.orientation_unit, unit)
+
+    def on_change_details(self):
+        """
+            Change details
+        """
+        #Change details
+        details = self.details_tcl.GetValue().lstrip().rstrip()
+        msg = ""
+        if self._sample.details is not None or self._sample.details:
+            for item in self._sample.details:
+                if item != details:
+                    msg += "      %s\n" % item
+                    self._notes += " Change details from"
+                    self._notes += " %s to %s \n" % (msg, details)
+                    self._sample.details.append(details)
+
+    def on_click_apply(self, event):
+        """
+            Apply user values to the sample
+        """
+        self.on_change_name()
+        self.on_change_id()
+        self.on_change_thickness()
+        self.on_change_transmission()
+        self.on_change_temperature()
+        self.on_change_position()
+        self.on_change_orientation()
+        self.on_change_details()
+        self.set_details(self._sample)
+        if self.manager is not None:
+            self.manager.set_sample(self._sample)
+        if event is not None:
+            event.Skip()
+
+    def on_click_cancel(self, event):
+        """
+            leave the sample as it is and close
+        """
+        self.reset_sample()
+        self.set_values()
+        if self.manager is not None:
+             self.manager.set_sample(self._sample, self._notes)
+        if event is not None:
+            event.Skip()
+
+if __name__ == "__main__":
+    app = wx.App()
+    from sas.sascalc.dataloader.data_info import Sample
+    sample = Sample()
+    dlg = SampleDialog(sample=sample)
+    dlg.ShowModal()
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/sld_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/sld_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/sld_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,525 @@
+"""
+This module provide GUI for the neutron scattering length density calculator
+
+"""
+
+import wx
+import math
+import sys
+
+from sas.sasgui.guiframe.panel_base import PanelBase
+
+from sas.sasgui.guiframe.utils import format_number
+from sas.sasgui.guiframe.utils import check_float
+from sas.sasgui.guiframe.events import StatusEvent
+
+# the calculator default value for wavelength is 6
+#import periodictable
+from periodictable import formula
+from periodictable.xsf import xray_energy
+from periodictable.xsf import xray_sld_from_atoms
+from periodictable.nsf import neutron_scattering
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+WAVELENGTH = 6.0
+_BOX_WIDTH = 76
+_STATICBOX_WIDTH = 350
+_SCALE = 1e-6
+
+#SLD panel size 
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    _STATICBOX_WIDTH = 350
+    PANEL_SIZE = 400
+    FONT_VARIANT = 0
+else:
+    PANEL_TOP = 60
+    _STATICBOX_WIDTH = 380
+    PANEL_SIZE = 410
+    FONT_VARIANT = 1
+
+class SldPanel(wx.Panel, PanelBase):
+    """
+    Provides the SLD calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "SLD Calculator"
+    ## Name to appear on the window title bar
+    window_caption = "SLD Calculator"
+    ## Flag to tell the AUI manager to put this panel in the center pane
+    CENTER_PANE = True
+
+    def __init__(self, parent, base=None, *args, **kwds):
+        """
+        """
+        wx.Panel.__init__(self, parent, *args, **kwds)
+        PanelBase.__init__(self)
+        #Font size 
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        # Object that receive status event
+        self.base = base
+        self.wavelength = WAVELENGTH
+        self.parent = parent
+        #layout attribute
+        self.compound_ctl = None
+        self.density_ctl = None
+        self.compound = ""
+        self.density = ""
+        self.wavelength_ctl = None
+        self.neutron_sld_real_ctl = None
+        self.neutron_sld_im_ctl = None
+        self.mo_ka_sld_real_ctl = None
+        self.mo_ka_sld_im_ctl = None
+        self.cu_ka_sld_real_ctl = None
+        self.cu_ka_sld_im_ctl = None
+        self.neutron_abs_ctl = None
+        self.neutron_inc_ctl = None
+        self.neutron_length_ctl = None
+        self.button_calculate = None
+        #Draw the panel
+        self._do_layout()
+        self.SetAutoLayout(True)
+        self.Layout()
+
+    def _do_layout(self):
+        """
+        Draw window content
+        """
+        unit_a = '[A]'
+        unit_density = '[g/cm^(3)]'
+        unit_sld = '[1/A^(2)]'
+        unit_cm1 = '[1/cm]'
+        unit_cm = '[cm]'
+        sizer_input = wx.GridBagSizer(5, 5)
+        sizer_output = wx.GridBagSizer(5, 5)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
+        sizer2 = wx.BoxSizer(wx.HORIZONTAL)
+        sizer3 = wx.BoxSizer(wx.HORIZONTAL)
+        #---------inputs----------------
+        inputbox = wx.StaticBox(self, -1, "Input")
+        boxsizer1 = wx.StaticBoxSizer(inputbox, wx.VERTICAL)
+        boxsizer1.SetMinSize((_STATICBOX_WIDTH, -1))
+
+        compound_txt = wx.StaticText(self, -1, 'Compound ')
+        self.compound_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH * 2, -1))
+        density_txt = wx.StaticText(self, -1, 'Density ')
+        self.density_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        unit_density_txt = wx.StaticText(self, -1, unit_density)
+        wavelength_txt = wx.StaticText(self, -1, 'Wavelength ')
+        self.wavelength_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
+        self.wavelength_ctl.SetValue(str(self.wavelength))
+        unit_a_txt = wx.StaticText(self, -1, unit_a)
+        iy = 0
+        ix = 0
+        sizer_input.Add(compound_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_input.Add(self.compound_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_input.Add(density_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_input.Add(self.density_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_input.Add(unit_density_txt, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_input.Add(wavelength_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_input.Add(self.wavelength_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_input.Add(unit_a_txt, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        boxsizer1.Add(sizer_input)
+        sizer1.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
+        #---------Outputs sizer--------
+        outputbox = wx.StaticBox(self, -1, "Output")
+        boxsizer2 = wx.StaticBoxSizer(outputbox, wx.VERTICAL)
+        boxsizer2.SetMinSize((_STATICBOX_WIDTH, -1))
+
+        i_complex = '- i'
+        neutron_sld_txt = wx.StaticText(self, -1, 'Neutron SLD')
+        self.neutron_sld_real_ctl = wx.TextCtrl(self, -1,
+                                                 size=(_BOX_WIDTH, -1))
+        self.neutron_sld_real_ctl.SetEditable(False)
+        self.neutron_sld_real_ctl.SetToolTipString("Neutron SLD real.")
+        self.neutron_sld_im_ctl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, -1))
+        self.neutron_sld_im_ctl.SetEditable(False)
+        self.neutron_sld_im_ctl.SetToolTipString("Neutron SLD imaginary.")
+        neutron_sld_units_txt = wx.StaticText(self, -1, unit_sld)
+
+        cu_ka_sld_txt = wx.StaticText(self, -1, 'Cu Ka SLD')
+        self.cu_ka_sld_real_ctl = wx.TextCtrl(self, -1,
+                                               size=(_BOX_WIDTH, -1))
+        self.cu_ka_sld_real_ctl.SetEditable(False)
+        self.cu_ka_sld_real_ctl.SetToolTipString("Cu Ka SLD real.")
+        self.cu_ka_sld_im_ctl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, -1))
+        self.cu_ka_sld_im_ctl.SetEditable(False)
+        self.cu_ka_sld_im_ctl.SetToolTipString("Cu Ka SLD imaginary.")
+        cu_ka_sld_units_txt = wx.StaticText(self, -1, unit_sld)
+
+        mo_ka_sld_txt = wx.StaticText(self, -1, 'Mo Ka SLD')
+        self.mo_ka_sld_real_ctl = wx.TextCtrl(self, -1,
+                                               size=(_BOX_WIDTH, -1))
+        self.mo_ka_sld_real_ctl.SetEditable(False)
+        self.mo_ka_sld_real_ctl.SetToolTipString("Mo Ka SLD real.")
+        self.mo_ka_sld_im_ctl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, -1))
+        self.mo_ka_sld_im_ctl.SetEditable(False)
+        self.mo_ka_sld_im_ctl.SetToolTipString("Mo Ka SLD imaginary.")
+        mo_ka_sld_units_txt = wx.StaticText(self, -1, unit_sld)
+
+        neutron_inc_txt = wx.StaticText(self, -1, 'Neutron Inc. Xs')
+        self.neutron_inc_ctl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, -1))
+        self.neutron_inc_ctl.SetEditable(False)
+        self.neutron_inc_ctl.SetToolTipString("Neutron Inc. Xs")
+        neutron_inc_units_txt = wx.StaticText(self, -1, unit_cm1)
+
+        neutron_abs_txt = wx.StaticText(self, -1, 'Neutron Abs. Xs')
+        self.neutron_abs_ctl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, -1))
+        self.neutron_abs_ctl.SetEditable(False)
+        self.neutron_abs_ctl.SetToolTipString("Neutron Abs. Xs")
+        neutron_abs_units_txt = wx.StaticText(self, -1, unit_cm1)
+
+        neutron_length_txt = wx.StaticText(self, -1, 'Neutron 1/e length')
+        self.neutron_length_ctl = wx.TextCtrl(self, -1,
+                                               size=(_BOX_WIDTH, -1))
+        self.neutron_length_ctl.SetEditable(False)
+        self.neutron_length_ctl.SetToolTipString("Neutron 1/e length")
+        neutron_length_units_txt = wx.StaticText(self, -1, unit_cm)
+
+        iy = 0
+        ix = 0
+        sizer_output.Add(neutron_sld_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.neutron_sld_real_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(wx.StaticText(self, -1, i_complex),
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.neutron_sld_im_ctl,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(neutron_sld_units_txt,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_output.Add(cu_ka_sld_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.cu_ka_sld_real_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(wx.StaticText(self, -1, i_complex),
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.cu_ka_sld_im_ctl,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(cu_ka_sld_units_txt,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_output.Add(mo_ka_sld_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.mo_ka_sld_real_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(wx.StaticText(self, -1, i_complex),
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(self.mo_ka_sld_im_ctl,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 1
+        sizer_output.Add(mo_ka_sld_units_txt,
+                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_output.Add(neutron_inc_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.neutron_inc_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 2
+        sizer_output.Add(neutron_inc_units_txt, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_output.Add(neutron_abs_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.neutron_abs_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 2
+        sizer_output.Add(neutron_abs_units_txt, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        ix = 0
+        sizer_output.Add(neutron_length_txt, (iy, ix), (1, 1),
+                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_output.Add(self.neutron_length_ctl, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        ix += 2
+        sizer_output.Add(neutron_length_units_txt, (iy, ix), (1, 1),
+                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        boxsizer2.Add(sizer_output)
+        sizer2.Add(boxsizer2, 0, wx.EXPAND | wx.ALL, 10)
+        #-----Button  sizer------------
+
+        id = wx.NewId()
+        self.button_calculate = wx.Button(self, id, "Calculate")
+        self.button_calculate.SetToolTipString("Calculate SLD.")
+        self.Bind(wx.EVT_BUTTON, self.calculateSld, id=id)
+
+        id = wx.NewId()
+        self.button_help = wx.Button(self, id, "HELP")
+        self.button_help.SetToolTipString("help on SLD calculator.")
+        self.Bind(wx.EVT_BUTTON, self.on_help, id=id)
+
+        self.button_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.button_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.button_close.SetToolTipString("Close this window.")
+
+        sizer_button.Add((150, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(self.button_calculate, 0, wx.RIGHT | wx.ADJUST_MINSIZE, 20)
+        sizer_button.Add(self.button_help, 0, wx.RIGHT | wx.ADJUST_MINSIZE, 20)
+        sizer_button.Add(self.button_close, 0, wx.RIGHT | wx.ADJUST_MINSIZE, 20)
+        sizer3.Add(sizer_button)
+        #---------layout----------------
+        vbox = wx.BoxSizer(wx.VERTICAL)
+        vbox.Add(sizer1)
+        vbox.Add(sizer2)
+        vbox.Add(sizer3)
+        vbox.Fit(self)
+        self.SetSizer(vbox)
+
+    def on_help(self, event):
+        """
+        Bring up the SLD Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/calculator/sld_calculator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "General Scattering Calculator Help")
+
+    def on_close(self, event):
+        """
+        close the window containing this panel
+        """
+        self.parent.Close()
+
+    def calculate_xray_sld(self, element):
+        """
+        Get an element and compute the corresponding SLD for a given formula
+
+        :param element:  elements a string of existing atom
+
+        """
+        myformula = formula(str(element))
+        if len(myformula.atoms) != 1:
+            return
+        element = myformula.atoms.keys()[0]
+        energy = xray_energy(element.K_alpha)
+
+        self.sld_formula = formula(str(self.compound), density=self.density)
+        atom = self.sld_formula.atoms
+        return xray_sld_from_atoms(atom, density=self.density, energy=energy)
+
+    def check_inputs(self):
+        """Check validity user inputs"""
+        flag = True
+        msg = ""
+        if check_float(self.density_ctl):
+            self.density = float(self.density_ctl.GetValue())
+        else:
+            flag = False
+            msg += "Error for Density value :expect float"
+
+        self.wavelength = self.wavelength_ctl.GetValue()
+        if str(self.wavelength).lstrip().rstrip() == "":
+            self.wavelength = WAVELENGTH
+            self.wavelength_ctl.SetValue(str(WAVELENGTH))
+            self.wavelength_ctl.SetBackgroundColour(wx.WHITE)
+            self.wavelength_ctl.Refresh()
+            msg += "Default value for wavelength is 6.0"
+        else:
+            if check_float(self.wavelength_ctl):
+                self.wavelength = float(self.wavelength)
+            else:
+                flag = False
+                msg += "Error for wavelength value :expect float"
+
+        self.compound = self.compound_ctl.GetValue().lstrip().rstrip()
+        if self.compound != "":
+            try :
+                formula(self.compound)
+                self.compound_ctl.SetBackgroundColour(wx.WHITE)
+                self.compound_ctl.Refresh()
+            except:
+                self.compound_ctl.SetBackgroundColour("pink")
+                self.compound_ctl.Refresh()
+                flag = False
+                msg += "Enter correct formula"
+        else:
+            self.compound_ctl.SetBackgroundColour("pink")
+            self.compound_ctl.Refresh()
+            flag = False
+            msg += "Enter a formula"
+        return flag, msg
+
+    def calculate_sld_helper(self, element, density, molecule_formula):
+        """
+        Get an element and compute the corresponding SLD for a given formula
+
+        :param element:  elements a string of existing atom
+
+        """
+        element_formula = formula(str(element))
+        if len(element_formula.atoms) != 1:
+            return
+        element = element_formula.atoms.keys()[0]
+        energy = xray_energy(element.K_alpha)
+        atom = molecule_formula.atoms
+        return xray_sld_from_atoms(atom, density=density, energy=energy)
+
+
+    def calculateSld(self, event):
+        """
+            Calculate the neutron scattering density length of a molecule
+        """
+        self.clear_outputs()
+        try:
+            #Check validity user inputs
+            flag, msg = self.check_inputs()
+            if self.base is not None and msg.lstrip().rstrip() != "":
+                msg = "SLD Calculator: %s" % str(msg)
+                wx.PostEvent(self.base, StatusEvent(status=msg))
+            if not flag:
+               return
+            #get ready to compute
+            self.sld_formula = formula(self.compound,
+                                            density=self.density)
+            (sld_real, sld_im, _), (_, absorp, incoh), \
+                        length = neutron_scattering(compound=self.compound,
+                                   density=self.density,
+                                   wavelength=self.wavelength)
+            cu_real, cu_im = self.calculate_sld_helper(element="Cu",
+                                                 density=self.density,
+                                        molecule_formula=self.sld_formula)
+            mo_real, mo_im = self.calculate_sld_helper(element="Mo",
+                                                       density=self.density,
+                                     molecule_formula=self.sld_formula)
+            # set neutron sld values
+            val = format_number(sld_real * _SCALE)
+            self.neutron_sld_real_ctl.SetValue(val)
+            val = format_number(math.fabs(sld_im) * _SCALE)
+            self.neutron_sld_im_ctl.SetValue(val)
+            # Compute the Cu SLD
+            self.cu_ka_sld_real_ctl.SetValue(format_number(cu_real * _SCALE))
+            val = format_number(math.fabs(cu_im) * _SCALE)
+            self.cu_ka_sld_im_ctl.SetValue(val)
+            # Compute the Mo SLD
+            self.mo_ka_sld_real_ctl.SetValue(format_number(mo_real * _SCALE))
+            val = format_number(math.fabs(mo_im) * _SCALE)
+            self.mo_ka_sld_im_ctl.SetValue(val)
+            # set incoherence and absorption
+            self.neutron_inc_ctl.SetValue(format_number(incoh))
+            self.neutron_abs_ctl.SetValue(format_number(absorp))
+            # Neutron length
+            self.neutron_length_ctl.SetValue(format_number(length))
+            # display wavelength
+            self.wavelength_ctl.SetValue(str(self.wavelength))
+        except:
+            if self.base is not None:
+                msg = "SLD Calculator: %s" % (sys.exc_value)
+                wx.PostEvent(self.base, StatusEvent(status=msg))
+        if event is not None:
+            event.Skip()
+
+    def clear_outputs(self):
+        """
+        Clear the outputs textctrl
+        """
+        self.neutron_sld_real_ctl.SetValue("")
+        self.neutron_sld_im_ctl.SetValue("")
+        self.mo_ka_sld_real_ctl.SetValue("")
+        self.mo_ka_sld_im_ctl.SetValue("")
+        self.cu_ka_sld_real_ctl.SetValue("")
+        self.cu_ka_sld_im_ctl.SetValue("")
+        self.neutron_abs_ctl.SetValue("")
+        self.neutron_inc_ctl.SetValue("")
+        self.neutron_length_ctl.SetValue("")
+
+
+class SldWindow(widget.CHILD_FRAME):
+    """
+    """
+    def __init__(self, parent=None, title="SLD Calculator",
+                  base=None, manager=None,
+                  size=(PANEL_SIZE, PANEL_SIZE), *args, **kwds):
+        """
+        """
+        kwds['title'] = title
+        kwds['size'] = size
+        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
+        """
+        """
+        self.parent = parent
+        self.base = base
+        self.manager = manager
+        self.panel = SldPanel(self, base=base)
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show(True)
+
+    def on_close(self, event):
+        """
+        On close event
+        """
+        if self.manager != None:
+            self.manager.sld_frame = None
+        self.Destroy()
+
+
+class ViewApp(wx.App):
+    """
+    """
+    def OnInit(self):
+        """
+        """
+        widget.CHILD_FRAME = wx.Frame
+        frame = SldWindow(None, title='SLD Calculator')
+        frame.Show(True)
+        self.SetTopWindow(frame)
+        return True
+
+
+if __name__ == "__main__":
+    app = ViewApp(0)
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/slit_length_calculator_panel.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/slit_length_calculator_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/slit_length_calculator_panel.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,313 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation.
+
+See the license text in license.txt
+
+copyright 2008, 2009, University of Tennessee
+"""
+
+import wx
+import sys
+import os
+
+from sas.sasgui.guiframe.panel_base import PanelBase
+
+from sas.sasgui.guiframe.events import StatusEvent
+from sas.sascalc.calculator.slit_length_calculator import SlitlengthCalculator
+from calculator_widgets import OutputTextCtrl
+from calculator_widgets import InterActiveOutputTextCtrl
+from sas.sasgui.perspectives.calculator import calculator_widgets as widget
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+_BOX_WIDTH = 76
+#Slit length panel size 
+if sys.platform.count("win32") > 0:
+    PANEL_TOP = 0
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 210
+    FONT_VARIANT = 0
+else:
+    PANEL_TOP = 60
+    PANEL_WIDTH = 530
+    PANEL_HEIGHT = 210
+    FONT_VARIANT = 1
+
+class SlitLengthCalculatorPanel(wx.Panel, PanelBase):
+    """
+        Provides the slit length calculator GUI.
+    """
+    ## Internal nickname for the window, used by the AUI manager
+    window_name = "Slit Size Calculator"
+    ## Name to appear on the window title bar
+    window_caption = "Slit Size Calculator"
+    ## Flag to tell the AUI manager to put this panel in the center pane
+    CENTER_PANE = True
+
+    def __init__(self, parent, *args, **kwds):
+        wx.Panel.__init__(self, parent, *args, **kwds)
+        PanelBase.__init__(self)
+        #Font size 
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        #thread to read data 
+        self.reader = None
+        # Default location
+        self._default_save_location = os.getcwd()
+        # Object that receive status event
+        self.parent = parent
+        self._do_layout()
+
+    def _define_structure(self):
+        """
+            Define the main sizers building to build this application.
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_source = wx.StaticBox(self, -1, str("Slit Size Calculator"))
+        self.boxsizer_source = wx.StaticBoxSizer(self.box_source,
+                                                    wx.VERTICAL)
+        self.data_name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.slit_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.hint_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_data_name(self):
+        """
+            Fill the sizer containing data's name
+        """
+        data_name_txt = wx.StaticText(self, -1, 'Data: ')
+        self.data_name_tcl = OutputTextCtrl(self, -1,
+                                            size=(_BOX_WIDTH * 4, -1))
+        data_hint = "Loaded data"
+        self.data_name_tcl.SetToolTipString(data_hint)
+        #control that triggers importing data
+        id = wx.NewId()
+        self.browse_button = wx.Button(self, id, "Browse")
+        hint_on_browse = "Click on this button to import data in this panel."
+        self.browse_button.SetToolTipString(hint_on_browse)
+        self.Bind(wx.EVT_BUTTON, self.on_load_data, id=id)
+        self.data_name_sizer.AddMany([(data_name_txt, 0, wx.LEFT, 15),
+                                      (self.data_name_tcl, 0, wx.LEFT, 10),
+                                      (self.browse_button, 0, wx.LEFT, 10)])
+    def _layout_slit_size(self):
+        """
+            Fill the sizer containing slit size information
+        """
+        slit_size_txt = wx.StaticText(self, -1, 'Slit Size (FWHM/2): ')
+        self.slit_size_tcl = InterActiveOutputTextCtrl(self, -1,
+                                                       size=(_BOX_WIDTH, -1))
+        slit_size_hint = " Estimated full slit size"
+        self.slit_size_tcl.SetToolTipString(slit_size_hint)
+        slit_size_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.slit_size_unit_tcl = OutputTextCtrl(self, -1,
+                                                 size=(_BOX_WIDTH, -1))
+        slit_size_unit_hint = "Full slit size's unit"
+        self.slit_size_unit_tcl.SetToolTipString(slit_size_unit_hint)
+        self.slit_size_sizer.AddMany([(slit_size_txt, 0, wx.LEFT, 15),
+                                      (self.slit_size_tcl, 0, wx.LEFT, 10),
+                                      (slit_size_unit_txt, 0, wx.LEFT, 10),
+                                    (self.slit_size_unit_tcl, 0, wx.LEFT, 10)])
+
+    def _layout_hint(self):
+        """
+            Fill the sizer containing hint
+        """
+        hint_msg = "This calculation works only for  SAXSess beam profile data."
+        self.hint_txt = wx.StaticText(self, -1, hint_msg)
+        self.hint_sizer.AddMany([(self.hint_txt, 0, wx.LEFT, 15)])
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this window.")
+
+        id = wx.NewId()
+        self.button_help = wx.Button(self, id, "HELP")
+        self.button_help.SetToolTipString("Help for slit length calculator.")
+        self.Bind(wx.EVT_BUTTON, self.on_help, id=id)
+
+        self.button_sizer.AddMany([(self.button_help, 0, wx.LEFT, 280),
+                                   (self.bt_close, 0, wx.LEFT, 20)])
+
+    def _do_layout(self):
+        """
+            Draw window content
+        """
+        self._define_structure()
+        self._layout_data_name()
+        self._layout_slit_size()
+        self._layout_hint()
+        self._layout_button()
+        self.boxsizer_source.AddMany([(self.data_name_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.slit_size_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                     (self.hint_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.main_sizer.AddMany([(self.boxsizer_source, 0, wx.ALL, 10),
+                                  (self.button_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def choose_data_file(self, location=None):
+        path = None
+        filename = ''
+        if location == None:
+            location = os.getcwd()
+
+        wildcard = "SAXSess Data 1D (*.DAT, *.dat)|*.DAT"
+
+        dlg = wx.FileDialog(self, "Choose a file", location,
+                            "", wildcard, wx.OPEN)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            filename = os.path.basename(path)
+        dlg.Destroy()
+
+        return path
+
+    def on_help(self, event):
+        """
+        Bring up the slit length calculator Documentation whenever
+        the HELP button is clicked.
+
+        Calls DocumentationWindow with the path of the location within the
+        documentation tree (after /doc/ ....".  Note that when using old
+        versions of Wx (before 2.9) and thus not the release version of
+        installers, the help comes up at the top level of the file as
+        webbrowser does not pass anything past the # to the browser when it is
+        running "file:///...."
+
+    :param evt: Triggers on clicking the help button
+    """
+
+        _TreeLocation = "user/perspectives/calculator/slit_calculator_help.html"
+        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
+                                          "Slit Length Calculator Help")
+
+    def on_close(self, event):
+        """
+            close the window containing this panel
+        """
+        self.parent.Close()
+
+    def on_load_data(self, event):
+        """
+            Open a file dialog to allow the user to select a given file.
+            The user is only allow to load file with extension .DAT or .dat.
+            Display the slit size corresponding to the loaded data.
+        """
+        path = self.choose_data_file(location=self._default_save_location)
+
+        if path is None:
+            return
+        self._default_save_location = path
+        try:
+            #Load data
+            from load_thread import DataReader
+            ## If a thread is already started, stop it
+            if self.reader is not None and self.reader.isrunning():
+                self.reader.stop()
+            if self.parent.parent is not None:
+                wx.PostEvent(self.parent.parent,
+                                StatusEvent(status="Loading...",
+                                type="progress"))
+            self.reader = DataReader(path=path,
+                                    completefn=self.complete_loading,
+                                    updatefn=self.load_update)
+            self.reader.queue()
+            self.load_update()
+        except:
+            if self.parent.parent is None:
+                return
+            msg = "Slit Length Calculator: %s" % (sys.exc_value)
+            wx.PostEvent(self.parent.parent,
+                          StatusEvent(status=msg, type='stop'))
+            return
+
+    def load_update(self):
+        """
+        print update on the status bar
+        """
+        if self.parent.parent is None:
+                return
+        if self.reader.isrunning():
+            type = "progress"
+        else:
+            type = "stop"
+        wx.PostEvent(self.parent.parent, StatusEvent(status="",
+                                                  type=type))
+
+    def complete_loading(self, data=None, filename=''):
+        """
+            Complete the loading and compute the slit size
+        """
+        if data is None or data.__class__.__name__ == 'Data2D':
+            if self.parent.parent is None:
+                return
+            msg = "Slit Length cannot be computed for 2D Data"
+            wx.PostEvent(self.parent.parent,
+                         StatusEvent(status=msg, type='stop'))
+            return
+        self.data_name_tcl.SetValue(str(data.filename))
+        #compute the slit size
+        try:
+            x = data.x
+            y = data.y
+            if x == [] or  x is None or y == [] or y is None:
+                msg = "The current data is empty please check x and y"
+                raise ValueError, msg
+            slit_length_calculator = SlitlengthCalculator()
+            slit_length_calculator.set_data(x=x, y=y)
+            slit_length = slit_length_calculator.calculate_slit_length()
+        except:
+            if self.parent.parent is None:
+                return
+            msg = "Slit Size Calculator: %s" % (sys.exc_value)
+            wx.PostEvent(self.parent.parent,
+                          StatusEvent(status=msg, type='stop'))
+            return
+        self.slit_size_tcl.SetValue(str(slit_length))
+        #Display unit
+        self.slit_size_unit_tcl.SetValue('[Unknown]')
+        if self.parent.parent is None:
+            return
+        msg = "Load Complete"
+        wx.PostEvent(self.parent.parent, StatusEvent(status=msg, type='stop'))
+
+
+class SlitLengthCalculatorWindow(widget.CHILD_FRAME):
+    """
+    """
+    def __init__(self, parent=None, manager=None, title="Slit Size Calculator",
+                size=(PANEL_WIDTH, PANEL_HEIGHT), *args, **kwds):
+        """
+        """
+        kwds['size'] = size
+        kwds['title'] = title
+        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
+        self.parent = parent
+        self.manager = manager
+        self.panel = SlitLengthCalculatorPanel(parent=self)
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+        self.SetPosition((wx.LEFT, PANEL_TOP))
+        self.Show(True)
+
+    def on_close(self, event):
+        """
+        Close event
+        """
+        if self.manager != None:
+            self.manager.cal_slit_frame = None
+        self.Destroy()
+
+if __name__ == "__main__":
+    app = wx.PySimpleApp()
+    widget.CHILD_FRAME = wx.Frame
+    frame = SlitLengthCalculatorWindow()
+    frame.Show(True)
+    app.MainLoop()
Index: src/sas/sasgui/perspectives/calculator/source_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/source_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/calculator/source_editor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
@@ -0,0 +1,540 @@
+"""
+"""
+import wx
+import sys
+from copy import deepcopy
+from sas.sasgui.guiframe.utils import check_float
+
+_BOX_WIDTH = 60
+if sys.platform.count("win32") > 0:
+    _STATICBOX_WIDTH = 450
+    PANEL_WIDTH = 500
+    PANEL_HEIGHT = 430
+    FONT_VARIANT = 0
+else:
+    _STATICBOX_WIDTH = 480
+    PANEL_WIDTH = 550
+    PANEL_HEIGHT = 430
+    FONT_VARIANT = 1
+
+class SourceDialog(wx.Dialog):
+    def __init__(self, parent=None, manager=None, source=None, *args, **kwds):
+        kwds['title'] = "Source Editor"
+        kwds['size'] = (PANEL_WIDTH, PANEL_HEIGHT)
+        wx.Dialog.__init__(self, parent=parent, *args, **kwds)
+
+        self.parent = parent
+        self.manager = manager
+        self._source = source
+        self._reset_source = deepcopy(source)
+        self._notes = ""
+        self._description = "Edit source"
+        self._do_layout()
+        self.set_values()
+
+    def _define_structure(self):
+        """
+            define initial sizer
+        """
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.box_source = wx.StaticBox(self, -1, str("source"))
+        self.boxsizer_source = wx.StaticBoxSizer(self.box_source,
+                                                    wx.VERTICAL)
+        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.radiation_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.beam_shape_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.wavelength_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.wavelength_min_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.wavelength_max_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.wavelength_spread_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.beam_size_name_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.beam_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+    def _layout_name(self):
+        """
+            Do the layout for sample name related widgets
+        """
+        # Sample name [string]
+        sample_name_txt = wx.StaticText(self, -1, 'Sample Name : ')
+        self.sample_name_tcl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH * 5, 20), style=0)
+        self.name_sizer.AddMany([(sample_name_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                       (self.sample_name_tcl, 0, wx.EXPAND)])
+    def _layout_radiation(self):
+        """
+            Do the  layout for  radiation related widgets
+        """
+        #Radiation type [string]
+        radiation_txt = wx.StaticText(self, -1, 'Radiation: ')
+        self.radiation_tcl = wx.TextCtrl(self, -1,
+                                         size=(_BOX_WIDTH, 20), style=0)
+        self.radiation_sizer.AddMany([(radiation_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.radiation_tcl, 0, wx.LEFT, 20)])
+
+    def _layout_beam_shape(self):
+        """
+            Do the  layout for beam shape related widgets
+        """
+        #Beam shape [string]
+        beam_shape_txt = wx.StaticText(self, -1, 'Beam shape:')
+        self.beam_shape_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        self.beam_shape_sizer.AddMany([(beam_shape_txt, 0,
+                                         wx.LEFT | wx.RIGHT, 10),
+                                     (self.beam_shape_tcl, 0, wx.LEFT, 10)])
+
+    def _layout_wavelength(self):
+        """
+            Do the  layout for wavelength related widgets
+        """
+        #Wavelength [float] [Angstrom]
+        wavelength_txt = wx.StaticText(self, -1, 'wavelength:')
+        self.wavelength_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        wavelength_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.wavelength_unit_tcl = wx.TextCtrl(self, -1,
+                                                size=(_BOX_WIDTH, 20), style=0)
+        self.wavelength_sizer.AddMany([(wavelength_txt,
+                                         0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.wavelength_tcl, 0, wx.LEFT, 12),
+                            (wavelength_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.wavelength_unit_tcl, 0, wx.EXPAND)])
+
+    def _layout_wavelength_min(self):
+        """
+            Do the  layout for wavelength min related widgets
+        """
+        #Minimum wavelength [float] [Angstrom]
+        wavelength_min_txt = wx.StaticText(self, -1, 'Wavelength min:')
+        self.wavelength_min_tcl = wx.TextCtrl(self, -1,
+                                               size=(_BOX_WIDTH, 20), style=0)
+        wavelength_min_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.wavelength_min_unit_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.wavelength_min_sizer.AddMany([(wavelength_min_txt,
+                                            0, wx.LEFT | wx.RIGHT, 10),
+                                     (self.wavelength_min_tcl, 0, wx.LEFT, 10),
+                            (wavelength_min_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                            (self.wavelength_min_unit_tcl, 0, wx.EXPAND)])
+
+    def _layout_wavelength_max(self):
+        """
+            Do the  layout for wavelength max related widgets
+        """
+        #Maximum wavelength [float] [Angstrom]
+        wavelength_max_txt = wx.StaticText(self, -1, 'Wavelength max:')
+        self.wavelength_max_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        wavelength_max_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.wavelength_max_unit_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.wavelength_max_sizer.AddMany([(wavelength_max_txt, 0,
+                                             wx.LEFT | wx.RIGHT, 10),
+                                     (self.wavelength_max_tcl, 0, wx.LEFT, 7),
+                            (wavelength_max_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                            (self.wavelength_max_unit_tcl, 0, wx.EXPAND)])
+
+    def _layout_wavelength_spread(self):
+        """
+            Do the  layout for wavelength spread related widgets
+        """
+        ## Wavelength spread [float] [Angstrom]
+        wavelength_spread = None
+        wavelength_spread_unit = 'percent'
+        wavelength_spread_txt = wx.StaticText(self, -1, 'Wavelength spread:')
+        self.wavelength_spread_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        wavelength_spread_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.wavelength_spread_unit_tcl = wx.TextCtrl(self, -1,
+                                             size=(_BOX_WIDTH, 20), style=0)
+        self.wavelength_spread_sizer.AddMany([(wavelength_spread_txt,
+                                                0, wx.LEFT, 10),
+                                (self.wavelength_spread_tcl, 0, wx.LEFT, 5),
+                        (wavelength_spread_unit_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                        (self.wavelength_spread_unit_tcl, 0, wx.EXPAND)])
+    def _layout_beam_size_name(self):
+        """
+            Do the layout for beam size name related widgets
+        """
+        # Beam size name [string]
+        beam_size_name_txt = wx.StaticText(self, -1, 'Beam size name : ')
+        self.beam_size_name_tcl = wx.TextCtrl(self, -1,
+                                               size=(_BOX_WIDTH * 5, 20), style=0)
+        self.beam_size_name_sizer.AddMany([(beam_size_name_txt,
+                                             0, wx.LEFT | wx.RIGHT, 10),
+                                       (self.beam_size_name_tcl, 0, wx.EXPAND)])
+
+    def _layout_beam_size(self):
+        """
+            Do the  layout for beam size related widgets
+        """
+        ## Beam size [Vector] [mm]
+        beam_size = None
+        beam_size_unit = 'mm'
+
+        beam_size_txt = wx.StaticText(self, -1, 'Beam size:')
+        x_beam_size_txt = wx.StaticText(self, -1, 'x = ')
+        self.x_beam_size_tcl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, 20), style=0)
+        y_beam_size_txt = wx.StaticText(self, -1, 'y = ')
+        self.y_beam_size_tcl = wx.TextCtrl(self, -1,
+                                            size=(_BOX_WIDTH, 20), style=0)
+        z_beam_size_txt = wx.StaticText(self, -1, 'z = ')
+        self.z_beam_size_tcl = wx.TextCtrl(self, -1,
+                                           size=(_BOX_WIDTH, 20), style=0)
+        beam_size_unit_txt = wx.StaticText(self, -1, 'Unit: ')
+        self.beam_size_unit_tcl = wx.TextCtrl(self, -1,
+                                              size=(_BOX_WIDTH, 20), style=0)
+        self.beam_size_sizer.AddMany([(beam_size_txt, 0, wx.LEFT | wx.RIGHT, 10),
+                                     (x_beam_size_txt, 0, wx.LEFT, 17),
+                                     (self.x_beam_size_tcl, 0, wx.RIGHT, 10),
+                                     (y_beam_size_txt, 0, wx.EXPAND),
+                                     (self.y_beam_size_tcl, 0, wx.RIGHT, 10),
+                                     (z_beam_size_txt, 0, wx.EXPAND),
+                                     (self.z_beam_size_tcl, 0, wx.RIGHT, 10),
+                                     (beam_size_unit_txt, 0, wx.EXPAND),
+                                    (self.beam_size_unit_tcl, 0, wx.RIGHT, 10)])
+
+    def _layout_button(self):
+        """
+            Do the layout for the button widgets
+        """
+        self.bt_apply = wx.Button(self, -1, 'Apply')
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+        self.bt_apply.SetToolTipString("Apply current changes to the source.")
+        self.bt_cancel = wx.Button(self, -1, 'Cancel')
+        self.bt_cancel.SetToolTipString("Cancel current changes.")
+        self.bt_cancel.Bind(wx.EVT_BUTTON, self.on_click_cancel)
+        self.bt_close = wx.Button(self, wx.ID_CANCEL, 'Close')
+        self.bt_close.SetToolTipString("Close window.")
+        self.button_sizer.AddMany([(self.bt_apply, 0, wx.LEFT, 200),
+                                   (self.bt_cancel, 0, wx.LEFT, 10),
+                                   (self.bt_close, 0, wx.LEFT, 10)])
+
+    def _do_layout(self, data=None):
+        """
+             Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_radiation()
+        self._layout_beam_shape()
+        self._layout_wavelength()
+        self._layout_wavelength_min()
+        self._layout_wavelength_max()
+        self._layout_wavelength_spread()
+        self._layout_beam_size_name()
+        self._layout_beam_size()
+        self._layout_button()
+
+        self.boxsizer_source.AddMany([(self.name_sizer, 0,
+                                          wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.radiation_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.beam_shape_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.wavelength_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.wavelength_min_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.wavelength_max_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                    (self.wavelength_spread_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                   (self.beam_size_name_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5),
+                                    (self.beam_size_sizer, 0,
+                                     wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+        self.main_sizer.AddMany([(self.boxsizer_source, 0, wx.ALL, 10),
+                                  (self.button_sizer, 0,
+                                    wx.EXPAND | wx.TOP | wx.BOTTOM, 5)])
+
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def set_manager(self, manager):
+        """
+            Set manager of this window
+        """
+        self.manager = manager
+
+    def reset_source(self):
+        """
+            put back initial values of the source
+        """
+        self._source.name = self._reset_source.name
+        self._source.radiation = self._reset_source.radiation
+        self._source.beam_size_name = self._reset_source.beam_size_name
+
+        self._source.beam_size.x = self._reset_source.beam_size.x
+        self._source.beam_size.y = self._reset_source.beam_size.y
+        self._source.beam_size.z = self._reset_source.beam_size.z
+        self._source.beam_size_unit = self._reset_source.beam_size_unit
+
+        self._source.beam_shape = self._reset_source.beam_shape
+        self._source.wavelength = self._reset_source.wavelength
+        self._source.wavelength_unit = self._reset_source.wavelength_unit
+        ## Minimum wavelength [float] [Angstrom]
+        self._source.wavelength_min = self._reset_source.wavelength_min
+        self._source.wavelength_min_unit = self._reset_source.wavelength_min_unit
+
+        self._source.wavelength_max = self._reset_source.wavelength_max
+        self._source.wavelength_max_unit = self._reset_source.wavelength_max_unit
+
+        self._source.wavelength_spread = self._reset_source.wavelength_spread
+        self._source.wavelength_spread_unit = self._reset_source.wavelength_spread_unit
+
+    def set_values(self):
+        """
+            take the source values of the current data and display them
+            through the panel
+        """
+        source = self._source
+        #Name
+        self.sample_name_tcl.SetValue(str(source.name))
+        #Radiation
+        self.radiation_tcl.SetValue(str(source.radiation))
+        #Beam shape 
+        self.beam_shape_tcl.SetValue(str(source.beam_shape))
+        #Wavelength
+        self.wavelength_tcl.SetValue(str(source.wavelength))
+        self.wavelength_unit_tcl.SetValue(str(source.wavelength_unit))
+        #Wavelength min
+        self.wavelength_min_tcl.SetValue(str(source.wavelength_min))
+        self.wavelength_min_unit_tcl.SetValue(str(source.wavelength_min_unit))
+        #Wavelength max
+        self.wavelength_max_tcl.SetValue(str(source.wavelength_max))
+        self.wavelength_max_unit_tcl.SetValue(str(source.wavelength_max_unit))
+        #Wavelength spread
+        self.wavelength_spread_tcl.SetValue(str(source.wavelength_spread))
+        self.wavelength_spread_unit_tcl.SetValue(str(source.wavelength_spread_unit))
+        #Beam size 
+        self.beam_size_name_tcl.SetValue(str(source.beam_size_name))
+        x, y, z = source.beam_size.x, source.beam_size.y , source.beam_size.z
+        self.x_beam_size_tcl.SetValue(str(x))
+        self.y_beam_size_tcl.SetValue(str(y))
+        self.z_beam_size_tcl.SetValue(str(z))
+        self.beam_size_unit_tcl.SetValue(str(source.beam_size_unit))
+
+    def get_source(self):
+        """
+            return the current source
+        """
+        return self._source
+
+    def get_notes(self):
+        """
+            return notes
+        """
+        return self._notes
+
+    def on_change_name(self):
+        """
+            Change name
+        """
+        #Change the name of the source
+        name = self.sample_name_tcl.GetValue().lstrip().rstrip()
+        if name == "" or name == str(None):
+            name = None
+        if self._source.name != name:
+            self._notes += "Change sample 's "
+            self._notes += "name from %s to %s \n" % (self._source.name, name)
+            self._source.name = name
+
+    def on_change_radiation(self):
+        """
+            Change radiation of the sample
+        """
+        #Change radiation
+        radiation = self.radiation_tcl.GetValue().lstrip().rstrip()
+        self._source.radiation = radiation
+        self._notes += " Change radiation from"
+        self._notes += " %s to %s \n" % (self._source.radiation, radiation)
+
+    def on_change_beam_shape(self):
+        """
+            Change beams shape
+        """
+       #Change beam shape 
+        beam_shape = self.beam_shape_tcl.GetValue().lstrip().rstrip()
+        self._source.beam_shape = beam_shape
+        self._notes += " Change beam shape from"
+        self._notes += " %s to %s \n" % (self._source.beam_shape, beam_shape)
+
+    def on_change_wavelength(self):
+        """
+            Change the wavelength
+        """
+        #Change wavelength  
+        wavelength = self.wavelength_tcl.GetValue().lstrip().rstrip()
+        self._source.wavelength = wavelength
+        self._notes += " Change wavelength from"
+        self._notes += " %s to %s \n" % (self._source.wavelength, wavelength)
+        #change the wavelength  unit
+        unit = self.wavelength_unit_tcl.GetValue().lstrip().rstrip()
+        if self._source.wavelength_unit != unit:
+            self._notes += " Change wavelength's unit from "
+            self._notes += "%s to %s" % (self._source.wavelength_unit, unit)
+
+    def on_change_wavelength_min(self):
+        """
+            Change the wavelength minimum
+        """
+        #Change wavelength min
+        wavelength_min = self.wavelength_min_tcl.GetValue().lstrip().rstrip()
+        self._source.wavelength_min = wavelength_min
+        self._notes += " Change wavelength  min from"
+        self._notes += " %s to %s \n" % (self._source.wavelength_min,
+                                          wavelength_min)
+        #change the wavelength min unit
+        unit = self.wavelength_min_unit_tcl.GetValue().lstrip().rstrip()
+        if self._source.wavelength_min_unit != unit:
+            self._notes += " Change wavelength min's unit from "
+            self._notes += "%s to %s" % (self._source.wavelength_min_unit, unit)
+
+    def on_change_wavelength_max(self):
+        """
+            Change the wavelength maximum
+        """
+        #Change wavelength max
+        wavelength_max = self.wavelength_max_tcl.GetValue().lstrip().rstrip()
+        self._source.wavelength_max = wavelength_max
+        self._notes += " Change wavelength  max from"
+        self._notes += " %s to %s \n" % (self._source.wavelength_max,
+                                        wavelength_max)
+        #change the wavelength max unit
+        unit = self.wavelength_max_unit_tcl.GetValue().lstrip().rstrip()
+        if self._source.wavelength_max_unit != unit:
+            self._notes += " Change wavelength max's unit from "
+            self._notes += "%s to %s" % (self._source.wavelength_max_unit, unit)
+
+    def on_change_wavelength_spread(self):
+        """
+            Change the wavelength spread
+        """
+        #Change wavelength spread
+        wavelength_spread = self.wavelength_spread_tcl.GetValue().lstrip().rstrip()
+        self._notes += " Change wavelength  spread from"
+        self._notes += " %s to %s \n" % (self._source.wavelength_spread,
+                                         wavelength_spread)
+        self._source.wavelength_spread = wavelength_spread
+        #change the wavelength spread unit
+        unit = self.wavelength_spread_unit_tcl.GetValue().lstrip().rstrip()
+        if self._source.wavelength_spread_unit != unit:
+            self._notes += " Change wavelength spread's unit from "
+            self._notes += "%s to %s" % (self._source.wavelength_spread_unit,
+                                         unit)
+            self._source.wavelength_spread_unit = unit
+
+    def on_change_beam_size_name(self):
+        """
+            Change beam size name
+        """
+        #Change beam size name
+        name = self.beam_size_name_tcl.GetValue().lstrip().rstrip()
+        if name == "" or name == str(None):
+            name = None
+        if self._source.beam_size_name != name:
+            self._notes += "Change beam size  's "
+            self._notes += "name from %s to %s \n" % (self._source.beam_size_name,
+                                                       name)
+            self._source.name = name
+
+    def on_change_beam_size(self):
+        """
+            Change beam size
+        """
+        #Change x coordinate
+        x_beam_size = self.x_beam_size_tcl.GetValue().lstrip().rstrip()
+        if x_beam_size == "" or x_beam_size == str(None):
+            x_beam_size = None
+        else:
+            if check_float(self.x_beam_size_tcl):
+                if self._source.beam_size.x != float(x_beam_size) :
+                    self._notes += "Change x of beam size from "
+                    self._notes += "%s to %s \n" % (self._source.beam_size.x,
+                                                     x_beam_size)
+                    self._source.beam_size.x = float(x_beam_size)
+            else:
+                self._notes += "Error: Expected a float for the beam size 's x "
+                self._notes += "won't changes x beam size from "
+                self._notes += "%s to %s" % (self._source.beam_size.x,
+                                            x_beam_size)
+        #Change y coordinate
+        y_beam_size = self.y_beam_size_tcl.GetValue().lstrip().rstrip()
+        if y_beam_size == "" or y_beam_size == str(None):
+            y_beam_size = None
+            self._source.beam_size.y = y_beam_size
+        else:
+            if check_float(self.y_beam_size_tcl):
+                if self._source.beam_size.y != float(y_beam_size):
+                    self._notes += "Change y of beam size from "
+                    self._notes += "%s to %s \n" % (self._source.beam_size.y,
+                                                     y_beam_size)
+                    self._source.beam_size.y = float(y_beam_size)
+            else:
+                self._notes += "Error: Expected a float for the beam size's y "
+                self._notes += "won't changes y beam size from "
+                self._notes += "%s to %s" % (self._source.beam_size.y,
+                                              y_beam_size)
+        #Change z coordinate
+        z_beam_size = self.z_beam_size_tcl.GetValue().lstrip().rstrip()
+        if z_beam_size == "" or z_beam_size == str(None):
+            z_beam_size = None
+            self._source.beam_size.z = z_beam_size
+        else:
+            if check_float(self.z_beam_size_tcl):
+                if self._source.beam_size.z != float(z_beam_size):
+                    self._notes += "Change z of beam size from "
+                    self._notes += "%s to %s \n" % (self._source.beam_size.z,
+                                                     z_beam_size)
+                    self._source.beam_size.z = float(z_beam_size)
+            else:
+                self._notes += "Error: Expected a float for the beam size 's z "
+                self._notes += "won't changes z beam size from "
+                self._notes += "%s to %s" % (self._source.beam_size.z,
+                                              z_beam_size)
+        #change the beam center unit
+        unit = self.beam_size_unit_tcl.GetValue().lstrip().rstrip()
+        if self._source.beam_size_unit != unit:
+            self._notes += " Change beam size's unit from "
+            self._notes += "%s to %s" % (self._source.beam_size_unit, unit)
+            self._source.beam_size_unit = unit
+
+    def on_click_apply(self, event):
+        """
+        Apply user values to the source
+        """
+        self.on_change_name()
+        self.on_change_radiation()
+        self.on_change_beam_shape()
+        self.on_change_wavelength()
+        self.on_change_wavelength_min()
+        self.on_change_wavelength_max()
+        self.on_change_wavelength_spread()
+        self.on_change_beam_size_name()
+        self.on_change_beam_size()
+        if self.manager is not None:
+            self.manager.set_source(self._source, self._notes)
+        if event is not None:
+            event.Skip()
+
+    def on_click_cancel(self, event):
+        """
+        reset the current source
+        """
+        self.reset_source()
+        self.set_values()
+        if self.manager is not None:
+            self.manager.set_source(self._source)
+        if event is not None:
+            event.Skip()
+
+if __name__ == "__main__":
+    app = wx.App()
+    from sas.sascalc.dataloader.data_info import Source
+    source = Source()
+    dlg = SourceDialog(source=source)
+    dlg.ShowModal()
+    app.MainLoop()
