Index: src/sas/qtgui/DataExplorer.py
===================================================================
--- src/sas/qtgui/DataExplorer.py	(revision 4b71e919d11ca53f1721dd9c28b6eaa80628737d)
+++ src/sas/qtgui/DataExplorer.py	(revision 28a84e9481334b02e53418a1f5b381680caf142b)
@@ -655,9 +655,11 @@
         model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
         data = model_item.child(0).data().toPyObject()
-        if data.__class__.__name__ == "Data1D":
+        if isinstance(data, Data1D):
             text_to_show = GuiUtils.retrieveData1d(data)
+            # Hardcoded sizes to enable full width rendering with default font
             self.txt_widget.resize(420,600)
         else:
             text_to_show = GuiUtils.retrieveData2d(data)
+            # Hardcoded sizes to enable full width rendering with default font
             self.txt_widget.resize(700,600)
 
@@ -669,4 +671,5 @@
 
         self.txt_widget.show()
+        # Move the slider all the way up, if present
         vertical_scroll_bar = self.txt_widget.verticalScrollBar()
         vertical_scroll_bar.triggerAction(QtGui.QScrollBar.SliderToMinimum)
@@ -674,7 +677,13 @@
     def saveDataAs(self):
         """
-        """
-        print "saveDataAs"
-        pass
+        Save the data points as either txt or xml
+        """
+        index = self.treeView.selectedIndexes()[0]
+        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
+        data = model_item.child(0).data().toPyObject()
+        if isinstance(data, Data1D):
+            GuiUtils.saveData1D(data)
+        else:
+            GuiUtils.saveData2D(data)
 
     def quickDataPlot(self):
@@ -727,5 +736,5 @@
         #                 |----> Process
         #                          |-----> process[0].name
-        #
+        #     |-------> THEORIES
 
         # Top-level item: checkbox with label
@@ -744,9 +753,13 @@
         info_item = GuiUtils.infoFromData(data)
 
-        # Set info_item as the only child
+        # Set info_item as the first child
         checkbox_item.setChild(1, info_item)
+
+        # Caption for the theories
+        checkbox_item.setChild(2, QtGui.QStandardItem("THEORIES"))
 
         # New row in the model
         self.model.appendRow(checkbox_item)
+
 
     def updateModelFromPerspective(self, model_item):
Index: src/sas/qtgui/GuiUtils.py
===================================================================
--- src/sas/qtgui/GuiUtils.py	(revision 4b71e919d11ca53f1721dd9c28b6eaa80628737d)
+++ src/sas/qtgui/GuiUtils.py	(revision 28a84e9481334b02e53418a1f5b381680caf142b)
@@ -1,4 +1,4 @@
 """
-    Global defaults and various utility functions usable by the general GUI
+Global defaults and various utility functions usable by the general GUI
 """
 
@@ -28,4 +28,5 @@
 from sas.sasgui.guiframe.dataFitting import Data1D
 from sas.sasgui.guiframe.dataFitting import Data2D
+from sas.sascalc.dataloader.loader import Loader
 
 
@@ -338,4 +339,7 @@
     representation
     """
+    if not isinstance(data, Data1D):
+        msg = "Incorrect type passed to retrieveData1d"
+        raise AttributeError, msg
     try:
         xmin = min(data.x)
@@ -423,2 +427,106 @@
 
     return text
+
+def _onTXTSave(data, path):
+    """
+    Save file as formatted txt
+    """
+    with open(path,'w') as out:
+        has_errors = True
+        if data.dy == None or data.dy == []:
+            has_errors = False
+        # Sanity check
+        if has_errors:
+            try:
+                if len(data.y) != len(data.dy):
+                    has_errors = False
+            except:
+                has_errors = False
+        if has_errors:
+            if data.dx != None and data.dx != []:
+                out.write("<X>   <Y>   <dY>   <dX>\n")
+            else:
+                out.write("<X>   <Y>   <dY>\n")
+        else:
+            out.write("<X>   <Y>\n")
+
+        for i in range(len(data.x)):
+            if has_errors:
+                if data.dx != None and data.dx != []:
+                    if  data.dx[i] != None:
+                        out.write("%g  %g  %g  %g\n" % (data.x[i],
+                                                        data.y[i],
+                                                        data.dy[i],
+                                                        data.dx[i]))
+                    else:
+                        out.write("%g  %g  %g\n" % (data.x[i],
+                                                    data.y[i],
+                                                    data.dy[i]))
+                else:
+                    out.write("%g  %g  %g\n" % (data.x[i],
+                                                data.y[i],
+                                                data.dy[i]))
+            else:
+                out.write("%g  %g\n" % (data.x[i],
+                                        data.y[i]))
+
+def saveData1D(data):
+    """
+    Save 1D data points
+    """
+    default_name = os.path.basename(data.filename)
+    default_name, extension = os.path.splitext(default_name)
+    default_name += "_out" + extension
+
+    wildcard = "Text files (*.txt);;"\
+                "CanSAS 1D files(*.xml)"
+    kwargs = {
+        'caption'   : 'Save As',
+        'directory' : default_name,
+        'filter'    : wildcard,
+        'parent'    : None,
+    }
+    # Query user for filename.
+    filename = QtGui.QFileDialog.getSaveFileName(**kwargs)
+
+    # User cancelled.
+    if not filename:
+        return
+
+    filename = str(filename)
+
+    #Instantiate a loader
+    loader = Loader()
+    if os.path.splitext(filename)[1].lower() == ".txt":
+        _onTXTSave(data, filename)
+    if os.path.splitext(filename)[1].lower() == ".xml":
+        loader.save(filename, data, ".xml")
+
+def saveData2D(data):
+    """
+    Save data2d dialog
+    """
+    default_name = os.path.basename(data.filename)
+    default_name, _ = os.path.splitext(default_name)
+    ext_format = ".dat"
+    default_name += "_out" + ext_format
+
+    wildcard = "IGOR/DAT 2D file in Q_map (*.dat)"
+    kwargs = {
+        'caption'   : 'Save As',
+        'directory' : default_name,
+        'filter'    : wildcard,
+        'parent'    : None,
+    }
+    # Query user for filename.
+    filename = QtGui.QFileDialog.getSaveFileName(**kwargs)
+
+    # User cancelled.
+    if not filename:
+        return
+    filename = str(filename)
+    #Instantiate a loader
+    loader = Loader()
+
+    if os.path.splitext(filename)[1].lower() == ext_format:
+        loader.save(filename, data, ext_format)
Index: src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py
===================================================================
--- src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py	(revision 8cb6cd6e52ab1f1031a89aeaad89921ef6ed9e98)
+++ src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py	(revision 28a84e9481334b02e53418a1f5b381680caf142b)
@@ -17,5 +17,4 @@
 from UI.TabbedInvariantUI import tabbedInvariantUI
 from InvariantDetails import DetailsDialog
-from Plotter import Plotter
 from InvariantUtils import WIDGETS
 
@@ -58,5 +57,4 @@
         self._helpView = QtWebKit.QWebView()
         self.detailsDialog = DetailsDialog(self)
-        # self._plotter = Plotter(self)
 
         self._low_extrapolate = False
@@ -65,4 +63,7 @@
         self._high_extrapolate = False
         self._high_power_value  = False
+
+        # No reason to have this widget resizable
+        self.setFixedSize(422, 400)
 
         self.communicate = GuiUtils.Communicate()
@@ -159,7 +160,4 @@
         """
         """
-        self._plotter = Plotter(self)
-        if self._low_extrapolate or self._high_extrapolate:
-            self._plotter.show()
         self.model = model
         self.mapper.toFirst()
@@ -236,5 +234,5 @@
             self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
         try:
-            surface, surface_error                 = \
+            surface, surface_error = \
                 inv.get_surface_with_error(self._contrast, self._porod)
         except Exception as ex:
@@ -251,8 +249,4 @@
             return self.model
 
-        self._plotter.clean()
-        self._plotter.x_label("Q(A$^{-1}$)")
-        self._plotter.y_label("Intensity(cm$^{-1}$)")
-
         if self._low_extrapolate:
             # for presentation in InvariantDetails
@@ -266,7 +260,4 @@
             # Plot the chart
             title = "Low-Q extrapolation"
-            self._plotter.data(extrapolated_data)
-            self._plotter.title(title)
-            self._plotter.plot()
 
             # Convert the data into plottable
@@ -295,7 +286,4 @@
             # Plot the chart
             title = "High-Q extrapolation"
-            self._plotter.data(high_out_data)
-            self._plotter.title(title)
-            self._plotter.plot()
 
             # Add the plot to the model item
Index: src/sas/qtgui/UnitTesting/DataExplorerTest.py
===================================================================
--- src/sas/qtgui/UnitTesting/DataExplorerTest.py	(revision 8cb6cd6e52ab1f1031a89aeaad89921ef6ed9e98)
+++ src/sas/qtgui/UnitTesting/DataExplorerTest.py	(revision 28a84e9481334b02e53418a1f5b381680caf142b)
@@ -619,4 +619,109 @@
             self.form.updateModelFromPerspective(bad_item)
 
+    def testContextMenu(self):
+        """
+        See if the context menu is present
+        """
+        # get Data1D
+        p_file=["cyl_400_20.txt"]
+        # Read in the file
+        output, message = self.form.readData(p_file)
+        self.form.loadComplete((output, message))
+
+        # Pick up the treeview index corresponding to that file
+        index = self.form.treeView.indexAt(QtCore.QPoint(5,5))
+        self.form.show()
+
+        # Find out the center pointof the treeView row
+        rect = self.form.treeView.visualRect(index).center()
+
+        self.form.context_menu.exec_ = MagicMock()
+
+        # Move the mouse pointer to the first row
+        QTest.mouseMove(self.form.treeView.viewport(), pos=rect)
+
+        # This doesn't invoke the action/signal. Investigate why?
+        # QTest.mouseClick(self.form.treeView.viewport(), Qt.RightButton, pos=rect)
+
+        # Instead, send the signal directly
+        self.form.treeView.customContextMenuRequested.emit(rect)
+
+        # app.exec_() # debug
+
+        # See that the menu has been shown
+        self.form.context_menu.exec_.assert_called_once()
+
+    def testShowDataInfo(self):
+        """
+        Test of the showDataInfo method
+        """
+        # get Data1D
+        p_file=["cyl_400_20.txt"]
+        # Read in the file
+        output, message = self.form.readData(p_file)
+        self.form.loadComplete((output, message))
+
+        # select the data
+        self.form.treeView.selectAll()
+
+        # Call the tested method
+        self.form.showDataInfo()
+
+        # Test the properties
+        self.assertTrue(self.form.txt_widget.isReadOnly())
+        self.assertEqual(self.form.txt_widget.windowTitle(), "Data Info: cyl_400_20.txt")
+        self.assertIn("Waveln_max", self.form.txt_widget.toPlainText())
+
+        # Slider moved all the way up
+        self.assertEqual(self.form.txt_widget.verticalScrollBar().sliderPosition(), 0)
+
+    def testSaveDataAs(self):
+        """
+        Test the Save As context menu action
+        """
+        # get Data1D
+        p_file=["cyl_400_20.txt"]
+        # Read in the file
+        output, message = self.form.readData(p_file)
+        self.form.loadComplete((output, message))
+
+        # select the data
+        self.form.treeView.selectAll()
+
+        QFileDialog.getSaveFileName = MagicMock()
+
+        # Call the tested method
+        self.form.saveDataAs()
+        QFileDialog.getSaveFileName.assert_called_with(
+                                caption="Save As",
+                                directory='cyl_400_20_out.txt',
+                                filter='Text files (*.txt);;CanSAS 1D files(*.xml)',
+                                parent=None)
+        QFileDialog.getSaveFileName.assert_called_once()
+
+        # get Data2D
+        p_file=["Dec07031.ASC"]
+        # Read in the file
+        output, message = self.form.readData(p_file)
+        self.form.loadComplete((output, message))
+
+        # select the data
+        index = self.form.model.index(1, 0)
+        selmodel = self.form.treeView.selectionModel()
+        selmodel.setCurrentIndex(index, QItemSelectionModel.NoUpdate)
+        selmodel.select(index, QItemSelectionModel.Select|QItemSelectionModel.Rows)
+
+        QFileDialog.getSaveFileName = MagicMock()
+
+        # Call the tested method
+        self.form.saveDataAs()
+        QFileDialog.getSaveFileName.assert_called_with(
+                                caption="Save As",
+                                directory='Dec07031_out.dat',
+                                filter='IGOR/DAT 2D file in Q_map (*.dat)',
+                                parent=None)
+        QFileDialog.getSaveFileName.assert_called_once()
+
+
 if __name__ == "__main__":
     unittest.main()
Index: src/sas/qtgui/UnitTesting/GuiUtilsTest.py
===================================================================
--- src/sas/qtgui/UnitTesting/GuiUtilsTest.py	(revision e540cd2f6e912b110651b7e76c9b4cc9c7183d9b)
+++ src/sas/qtgui/UnitTesting/GuiUtilsTest.py	(revision 28a84e9481334b02e53418a1f5b381680caf142b)
@@ -13,4 +13,10 @@
 # Tested module
 from GuiUtils import *
+
+class FakeData(object):
+    '''Data1D/2D object for testing'''
+    def __init__(self):
+        self.x = []
+        self.y = []
 
 class GuiUtilsTest(unittest.TestCase):
@@ -189,4 +195,32 @@
             openLink(bad_url3)
 
+    def testRetrieveData1d(self):
+        """
+        """
+        self.assertRaises(retrieveData1d("BOOP"))
+
+        # data = FakeData()        
+        pass
+
+    def testRetrieveData2d(self):
+        """
+        """
+        pass
+
+    def testOnTXTSave(self):
+        """
+        """
+        pass
+
+    def testSaveData1D(self):
+        """
+        """
+        pass
+
+    def testSaveData2D(self):
+        """
+        """
+        pass
+
 if __name__ == "__main__":
     unittest.main()
