From fd5bfe0b1715d5849e8b0fd2bcd20ac1a257a48d Mon Sep 17 00:00:00 2001
From: Federico Montesino Pouzols <federico.montesino-pouzols@stfc.ac.uk>
Date: Fri, 14 Nov 2014 06:14:46 +0000
Subject: [PATCH] mantidplot dir renamed to pymantidplot, avoid name conflict,
 re #8912

---
 Code/Mantid/MantidPlot/CMakeLists.txt         |   10 +-
 Code/Mantid/MantidPlot/mantidplot.py          |   11 +
 .../MantidPlot/pymantidplot/__init__.py       | 1034 +++++++++++++++
 .../pymantidplot/future/__init__.py           |    0
 .../MantidPlot/pymantidplot/future/pyplot.py  | 1180 +++++++++++++++++
 .../Mantid/MantidPlot/pymantidplot/proxies.py |  907 +++++++++++++
 6 files changed, 3137 insertions(+), 5 deletions(-)
 create mode 100644 Code/Mantid/MantidPlot/mantidplot.py
 create mode 100644 Code/Mantid/MantidPlot/pymantidplot/__init__.py
 create mode 100644 Code/Mantid/MantidPlot/pymantidplot/future/__init__.py
 create mode 100644 Code/Mantid/MantidPlot/pymantidplot/future/pyplot.py
 create mode 100644 Code/Mantid/MantidPlot/pymantidplot/proxies.py

diff --git a/Code/Mantid/MantidPlot/CMakeLists.txt b/Code/Mantid/MantidPlot/CMakeLists.txt
index 0488bd82125..a34141cea76 100644
--- a/Code/Mantid/MantidPlot/CMakeLists.txt
+++ b/Code/Mantid/MantidPlot/CMakeLists.txt
@@ -852,8 +852,8 @@ set( MTDPLOTPY_FILES
   proxies.py
 )
 copy_files_to_dir ( "${MTDPLOTPY_FILES}"
-                            ${CMAKE_CURRENT_SOURCE_DIR}/mantidplot
-                            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/mantidplot
+                            ${CMAKE_CURRENT_SOURCE_DIR}/pymantidplot
+                            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/pymantidplot
                             MTDPLOT_INSTALL_FILES )
                             
 set( FUTURE_FILES
@@ -861,8 +861,8 @@ set( FUTURE_FILES
   pyplot.py
 )
 copy_files_to_dir ( "${FUTURE_FILES}"
-                            ${CMAKE_CURRENT_SOURCE_DIR}/mantidplot/future
-                            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/mantidplot/future
+                            ${CMAKE_CURRENT_SOURCE_DIR}/pymantidplot/future
+                            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/pymantidplot/future
                             MTDPLOT_FUTURE_INSTALL_FILES )
 
 # IPython scripts
@@ -1016,7 +1016,7 @@ install ( TARGETS MantidPlot RUNTIME DESTINATION ${BIN_DIR}
 # Ship required files. Explicit so some can be disabled easily if necessary
 install ( FILES ${PY_FILES} DESTINATION ${BIN_DIR} )
 foreach(PY_FILE ${MTDPLOTPY_FILES} )
-  install ( FILES mantidplot/${PY_FILE} DESTINATION ${BIN_DIR}/mantidplot )
+  install ( FILES pymantidplot/${PY_FILE} DESTINATION ${BIN_DIR}/pymantidplot )
 endforeach()
 foreach(PY_FILE ${IPY_FILES} )
   install ( FILES ipython_widget/${PY_FILE} DESTINATION ${BIN_DIR}/ipython_widget )
diff --git a/Code/Mantid/MantidPlot/mantidplot.py b/Code/Mantid/MantidPlot/mantidplot.py
new file mode 100644
index 00000000000..48e47f867ff
--- /dev/null
+++ b/Code/Mantid/MantidPlot/mantidplot.py
@@ -0,0 +1,11 @@
+#-------------------------------------------------------------------
+# mantidplot.py
+# 
+# Load 'pymantidplot' which cannot be called 'mantidplot' because of 
+# name conflict with the MantidPlot binary (especially on osx)
+#-------------------------------------------------------------------
+
+try:
+    import pymantidplot
+except ImportError:
+    raise ImportError('Could not import mantidplot (when trying to import pymantidplot). Something is broken in this installation, please check.')
diff --git a/Code/Mantid/MantidPlot/pymantidplot/__init__.py b/Code/Mantid/MantidPlot/pymantidplot/__init__.py
new file mode 100644
index 00000000000..fbfa00c4976
--- /dev/null
+++ b/Code/Mantid/MantidPlot/pymantidplot/__init__.py
@@ -0,0 +1,1034 @@
+"""
+MantidPlot module to gain access to plotting functions etc.
+Requires that the main script be run from within MantidPlot
+"""
+# Requires MantidPlot
+try:
+    import _qti
+except ImportError:
+    raise ImportError('The "mantidplot" module can only be used from within MantidPlot.')
+
+import mantidplot.proxies as proxies
+from mantidplot.proxies import threadsafe_call, new_proxy, getWorkspaceNames
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import Qt
+import os
+import time
+import mantid.api
+
+# Import into the global namespace qti classes that:
+#   (a) don't need a proxy & (b) can be constructed from python or (c) have enumerations within them
+from _qti import (PlotSymbol, ImageSymbol, ArrowMarker, ImageMarker,
+                  GraphOptions, InstrumentWindow, InstrumentWindowPickTab, InstrumentWindowMaskTab)
+
+# Make the ApplicationWindow instance accessible from the mantidplot namespace
+from _qti import app
+
+# Alias threadsafe_call so users have a more understandable name
+gui_cmd = threadsafe_call
+
+#---------------- Mantid Python access functions--------------------------
+# Grab a few Mantid things so that we can recognise workspace variables
+def _get_analysis_data_service():
+    """Returns an object that can be used to get a workspace by name from Mantid
+
+    Returns:
+        A object that acts like a dictionary to retrieve workspaces
+    """
+    import mantid
+    return mantid.AnalysisDataService.Instance()
+
+#-------------------------- Wrapped MantidPlot functions -----------------
+
+def runPythonScript(code, async = False, quiet = False, redirect = True):
+    """
+        Redirects the runPythonScript method to the app object
+        @param code :: A string of code to execute
+        @param async :: If the true the code is executed in a separate thread
+        @param quiet :: If true no messages reporting status are issued
+        @param redirect :: If true then output is redirected to MantidPlot
+    """
+    if async and QtCore.QThread.currentThread() != QtGui.qApp.thread():
+        async = False
+    threadsafe_call(_qti.app.runPythonScript, code, async, quiet, redirect)
+
+# Overload for consistency with qtiplot table(..) & matrix(..) commands
+def workspace(name):
+    """Get a handle on a workspace.
+
+    Args:
+        name: The name of the workspace in the Analysis Data Service.
+    """
+    return _get_analysis_data_service()[name]
+
+def table(name):
+    """Get a handle on a table.
+
+    Args:
+        name: The name of the table.
+
+    Returns:
+        A handle to the table.
+    """
+    return new_proxy(proxies.MDIWindow, _qti.app.table, name)
+
+def newTable(name=None,rows=30,columns=2):
+    """Create a table.
+
+    Args:
+        name: The name to give to the table (if None, a unique name will be generated).
+        row: The number of rows in the table (default: 30).
+        columns: The number of columns in the table (default: 2).
+
+    Returns:
+        A handle to the created table.
+    """
+    if name is None:
+        return new_proxy(proxies.MDIWindow, _qti.app.newTable)
+    else:
+        return new_proxy(proxies.MDIWindow, _qti.app.newTable, name,rows,columns)
+
+def matrix(name):
+    """Get a handle on a matrix.
+
+    Args:
+        name: The name of the matrix.
+
+    Returns:
+        A handle to the matrix.
+    """
+    return new_proxy(proxies.MDIWindow, _qti.app.matrix, name)
+
+def newMatrix(name=None,rows=32,columns=32):
+    """Create a matrix (N.B. This is not the same as a 'MantidMatrix').
+
+    Args:
+        name: The name to give to the matrix (if None, a unique name will be generated).
+        row: The number of rows in the matrix (default: 32).
+        columns: The number of columns in the matrix (default: 32).
+
+    Returns:
+        A handle to the created matrix.
+    """
+    if name is None:
+        return new_proxy(proxies.MDIWindow, _qti.app.newMatrix)
+    else:
+        return new_proxy(proxies.MDIWindow, _qti.app.newMatrix,name,rows,columns)
+
+def graph(name):
+    """Get a handle on a graph widget.
+
+    Args:
+        name: The name of the graph window.
+
+    Returns:
+        A handle to the graph.
+    """
+    return new_proxy(proxies.Graph, _qti.app.graph, name)
+
+def newGraph(name=None,layers=1,rows=1,columns=1):
+    """Create a graph window.
+
+    Args:
+        name: The name to give to the graph (if None, a unique name will be generated).
+        layers: The number of plots (a.k.a. layers) to put in the graph window (default: 1).
+        rows: The number of rows of to put in the graph window (default: 1).
+        columns: The number of columns of to put in the graph window (default: 1).
+
+    Returns:
+        A handle to the created graph widget.
+    """
+    if name is None:
+        return new_proxy(proxies.Graph, _qti.app.newGraph)
+    else:
+        return new_proxy(proxies.Graph, _qti.app.newGraph,name,layers,rows,columns)
+
+def note(name):
+    """Get a handle on a note.
+
+    Args:
+        name: The name of the note.
+
+    Returns:
+        A handle to the note.
+    """
+    return new_proxy(proxies.MDIWindow, _qti.app.note, name)
+
+def newNote(name=None):
+    """Create a note.
+
+    Args:
+        name: The name to give to the note (if None, a unique name will be generated).
+
+    Returns:
+        A handle to the created note.
+    """
+    if name is None:
+        return new_proxy(proxies.MDIWindow, _qti.app.newNote)
+    else:
+        return new_proxy(proxies.MDIWindow, _qti.app.newNote, name)
+
+def newTiledWindow(name=None):
+    """Create an empty tiled window.
+
+    Args:
+        name: The name to give to the window (if None, a unique name will be generated).
+
+    Returns:
+        A handle to the created window.
+    """
+    if name is None:
+        return new_proxy(proxies.TiledWindowProxy, _qti.app.newTiledWindow)
+    else:
+        return new_proxy(proxies.TiledWindowProxy, _qti.app.newTiledWindow, name)
+
+#-----------------------------------------------------------------------------
+# Intercept qtiplot "plot" command and forward to plotSpectrum for a workspace
+def plot(source, *args, **kwargs):
+    """Create a new plot given a workspace, table or matrix.
+
+    Args:
+        source: what to plot; if it is a Workspace, will
+                call plotSpectrum()
+
+    Returns:
+        A handle to the created Graph widget.
+    """
+    if hasattr(source, '_getHeldObject') and isinstance(source._getHeldObject(), QtCore.QObject):
+        return new_proxy(proxies.Graph,_qti.app.plot, source._getHeldObject(), *args, **kwargs)
+    else:
+        return plotSpectrum(source, *args, **kwargs)
+
+#----------------------------------------------------------------------------------------------------
+def plotSpectrum(source, indices, error_bars = False, type = -1, window = None, clearWindow = False):
+    """Open a 1D Plot of a spectrum in a workspace.
+
+    This plots one or more spectra, with X as the bin boundaries,
+    and Y as the counts in each bin.
+
+    Args:
+        source: workspace or name of a workspace
+        indices: workspace index, or tuple or list of workspace indices to plot
+        error_bars: bool, set to True to add error bars.
+        type: curve style for plot (-1: unspecified; 0: line, default; 1: scatter/dots)
+        window: window used for plotting. If None a new one will be created
+        clearWindow: if is True, the window specified will be cleared before adding new curve
+    Returns:
+        A handle to window if one was specified, otherwise a handle to the created one. None in case of error.
+    """
+    workspace_names = getWorkspaceNames(source)
+    __checkPlotWorkspaces(workspace_names)
+    # check spectrum indices
+    index_list = __getWorkspaceIndices(indices)
+    if len(index_list) == 0:
+        raise ValueError("No spectrum indices given")
+    for idx in index_list:
+        if idx < 0:
+            raise ValueError("Wrong spectrum index (<0): %d" % idx)
+    for name in workspace_names:
+        max_spec = workspace(name).getNumberHistograms() - 1
+        for idx in index_list:
+            if idx > max_spec:
+                raise ValueError("Wrong spectrum index for workspace '%s': %d, which is bigger than the"
+                                 " number of spectra in this workspace - 1 (%d)" % (name, idx, max_spec))
+
+    # Unwrap the window object, if any specified
+    if window != None:
+      window = window._getHeldObject()
+
+    graph = proxies.Graph(threadsafe_call(_qti.app.mantidUI.plot1D,
+                                          workspace_names, index_list, True, error_bars,
+                                          type, window, clearWindow))
+    if graph._getHeldObject() == None:
+        raise RuntimeError("Cannot create graph, see log for details.")
+    else:
+        return graph
+
+#----------------------------------------------------------------------------------------------------
+# IPython auto-complete can't handle enumerations as defaults
+DEFAULT_2D_STYLE = int(_qti.Layer.ColorMap)
+
+def plot2D(source, style = DEFAULT_2D_STYLE, window = None):
+    """Open a 2D plot of the given workspace(s)
+
+    Produces a 2D histogram for each of the given workspaces
+
+    Args:
+        source: workspace or name of a workspace
+        style: Indicates the type of plot required. Default=ColorMap
+        window: window used for plotting. If None a new one will be created
+    Returns:
+        If a single workspace is specified then the handle is returned, otherwise a list
+        of handles for each new window is returned
+    """
+    names = getWorkspaceNames(source)
+    if len(names) == 0:
+        raise ValueError("No workspace names given to plot")
+
+    # Unwrap the window object, if any specified
+    if window != None:
+      window = window._getHeldObject()
+
+    handles = []
+    cfunc = _qti.app.mantidUI.drawSingleColorFillPlot
+    for name in names:
+        g = proxies.Graph(threadsafe_call(cfunc, name, style, window))
+        if g:
+            handles.append(g)
+        else:
+            raise RuntimeError("Cannot create graph from workspace '%s'" % name)
+
+    if len(handles) == 1: return handles[0]
+    else: return handles
+
+#----------------------------------------------------------------------------------------------------
+
+# IPython couldn't correctly display complex enum value in doc pop-up, so we extract integer value
+# of enum manually.
+DEFAULT_MD_NORMALIZATION = int(mantid.api.MDNormalization.VolumeNormalization)
+
+def plotMD(source, plot_axis=-2, normalization = DEFAULT_MD_NORMALIZATION, error_bars = False, window = None,
+           clearWindow = False):
+    """Open a 1D plot of a MDWorkspace.
+
+    Args:
+        source: Workspace(s) to plot
+        plot_axis: Index of the plot axis (defaults to auto-select)
+        normalization: Type of normalization required (defaults to volume)
+        error_bars: Flag for error bar plotting.
+        window: window used for plotting. If None a new one will be created
+        clearWindow: if is True, the window specified will be cleared before adding new curve
+    Returns:
+        A handle to the matrix containing the image data.
+    """
+    workspace_names = getWorkspaceNames(source)
+    __checkPlotMDWorkspaces(workspace_names)
+
+    for name in workspace_names:
+        non_integrated_dims = mantid.api.mtd[name].getNonIntegratedDimensions()
+        if not len(non_integrated_dims) == 1:
+            raise ValueError("'%s' must have a single non-integrated dimension in order to be rendered via plotMD" % name)
+
+    # check axis index
+    for name in workspace_names:
+        ws = workspace(name)
+        if hasattr(ws, "axes"):
+            max_axis = workspace(name).axes()
+            # see choice in MantidQwtIMDWorkspaceData::setPlotAxisChoice, -2: auto, -1: distance
+            if plot_axis < -2 or plot_axis > max_axis:
+                raise ValueError("Incorrect axis index given for workspace '%s': %d, should be < %d" % (name, plot_axis, max_axis))
+
+    # Unwrap the window object, if any specified
+    if window != None:
+      window = window._getHeldObject()
+
+    graph = proxies.Graph(threadsafe_call(_qti.app.mantidUI.plotMDList, workspace_names, plot_axis, normalization,
+      error_bars, window, clearWindow))
+
+    return graph
+
+def fitBrowser():
+    """
+    Access the fit browser.
+    """
+    import mantidqtpython
+    return proxies.FitBrowserProxy(_qti.app.mantidUI.fitFunctionBrowser())
+
+#-----------------------------------------------------------------------------
+def plotBin(source, indices, error_bars = False, type = -1, window = None, clearWindow = False):
+    """Create a 1D Plot of bin count vs spectrum in a workspace.
+
+    This puts the spectrum number as the X variable, and the
+    count in the particular bin # (in 'indices') as the Y value.
+
+    If indices is a tuple or list, then several curves are created, one
+    for each bin index.
+
+    Args:
+        source: workspace or name of a workspace
+        indices: bin number(s) to plot
+        error_bars: bool, set to True to add error bars.
+        type: Plot style
+        window: window used for plotting. If None a new one will be created
+        clearWindow: if is True, the window specified will be cleared before adding new curve
+    Returns:
+        A handle to window if one was specified, otherwise a handle to the created one. None in case of error.
+    """
+    workspace_names = getWorkspaceNames(source)
+    __checkPlotWorkspaces(workspace_names)
+    index_list = __getWorkspaceIndices(indices)
+    if len(index_list) == 0:
+        raise ValueError("No indices given")
+
+    for idx in index_list:
+        if idx < 0:
+            raise ValueError("Wrong bin index (<0): %d" % idx)
+    for name in workspace_names:
+        max_bin = workspace(name).blocksize() - 1
+        for idx in index_list:
+            if idx > max_bin:
+                raise ValueError("Wrong bin index for workspace '%s': %d, which is bigger than the"
+                                 " number of bins in this workspace - 1 (%d)" % (name, idx, max_bin))
+
+    # Unwrap the window object, if any specified
+    if window != None:
+      window = window._getHeldObject()
+
+    graph = proxies.Graph(threadsafe_call(_qti.app.mantidUI.plot1D,
+                                          workspace_names, index_list, False, error_bars,
+                                          type, window, clearWindow))
+    if graph._getHeldObject() == None:
+        raise RuntimeError("Cannot create graph, see log for details.")
+    else:
+        return graph
+
+#-----------------------------------------------------------------------------
+def stemPlot(source, index, power=None, startPoint=None, endPoint=None):
+    """Generate a stem-and-leaf plot from an input table column or workspace spectrum
+
+    Args:
+        source: A reference to a workspace or a table.
+        index: For a table, the column number or name. For a workspace, the workspace index.
+        power: The stem unit as a power of 10. If not provided, a dialog will appear with a suggested value.
+        startPoint: The first point (row or bin) to use (Default: the first one).
+        endPoint: The last point (row or bin) to use (Default: the last one).
+
+    Returns:
+        A string representation of the stem plot
+    """
+    # Turn the optional arguments into the magic numbers that the C++ expects
+    if power==None:
+        power=1001
+    if startPoint==None:
+        startPoint=0
+    if endPoint==None:
+        endPoint=-1
+
+    if isinstance(source,proxies.QtProxyObject):
+        source = source._getHeldObject()
+    elif hasattr(source, 'getName'):
+        # If the source is a workspace, create a table from the specified index
+        wsName = source.getName()
+        source = threadsafe_call(_qti.app.mantidUI.workspaceToTable.wsName,wsName,[index],False,True)
+        # The C++ stemPlot method takes the name of the column, so get that
+        index = source.colName(2)
+    # Get column name if necessary
+    if isinstance(index, int):
+        index = source.colName(index)
+    # Call the C++ method
+    return threadsafe_call(_qti.app.stemPlot, source,index,power,startPoint,endPoint)
+
+#-----------------------------------------------------------------------------
+def waterfallPlot(table, columns):
+    """Create a waterfall plot from data in a table.
+
+    Args:
+        table: A reference to the table containing the data to plot
+        columns: A tuple of the column numbers whose data to plot
+
+    Returns:
+        A handle to the created plot (Layer).
+    """
+    return new_proxy(proxies.Graph, _qti.app.waterfallPlot, table._getHeldObject(),columns)
+
+#-----------------------------------------------------------------------------
+def importImage(filename):
+    """Load an image file into a matrix.
+
+    Args:
+        filename: The name of the file to load.
+
+    Returns:
+        A handle to the matrix containing the image data.
+    """
+    return new_proxy(proxies.MDIWindow, _qti.app.importImage, filename)
+
+#-----------------------------------------------------------------------------
+def newPlot3D():
+    return new_proxy(proxies.Graph3D, _qti.app.newPlot3D)
+
+def plot3D(*args):
+    if isinstance(args[0],str):
+        return new_proxy(proxies.Graph3D, _qti.app.plot3D, *args)
+    else:
+        return new_proxy(proxies.Graph3D, _qti.app.plot3D, args[0]._getHeldObject(),*args[1:])
+
+#-----------------------------------------------------------------------------
+def selectMultiPeak(source, showFitPropertyBrowser = True, xmin = None, xmax = None):
+    """Switch on the multi-peak selecting tool for fitting with the Fit algorithm.
+
+    Args:
+        source: A reference to a MultiLayer with the data to fit.
+        showFitPropertyBrowser: Whether to show the FitPropertyBrowser or not.
+        xmin: An optionall minimum X value to select
+        xmax: An optionall maximum X value to select
+    """
+    if xmin is not None and xmax is not None:
+        threadsafe_call(_qti.app.selectMultiPeak, source._getHeldObject(), showFitPropertyBrowser, xmin, xmax)
+    else:
+        threadsafe_call(_qti.app.selectMultiPeak, source._getHeldObject(), showFitPropertyBrowser)
+
+def disableTools():
+    """Disable all the tools from all the graphs within MantidPlot."""
+    threadsafe_call(_qti.app.disableTools)
+
+#------------------------------------------------------------------------------
+def setToolbarsVisible(visible):
+    """Show/hide MantidPlot toolbars
+
+    Args:
+        visible: If True, make toolbars visible, if False - hidden
+    """
+    threadsafe_call(_qti.app.setToolbarsVisible, visible)
+
+#-----------------------------------------------------------------------------
+#-------------------------- Project/Folder functions -----------------------
+#-----------------------------------------------------------------------------
+def windows():
+    """Get a list of the open windows."""
+    f = _qti.app.windows()
+    ret = []
+    for item in f:
+        ret.append(proxies.MDIWindow(item))
+    return ret
+
+def activeFolder():
+    """Get a handle to the currently active folder."""
+    return new_proxy(proxies.Folder, _qti.app.activeFolder)
+
+# These methods don't seem to work
+#def appendProject(filename, parentFolder=None):
+#    if parentFolder is not None:
+#        parentFolder = parentFolder._getHeldObject()
+#    return proxies.Folder(_qti.app.appendProject(filename,parentFolder))
+#
+#def saveFolder(folder, filename, compress=False):
+#    _qti.app.saveFolder(folder._getHeldObject(),filename,compress)
+
+def rootFolder():
+    """Get a handle to the top-level folder."""
+    return new_proxy(proxies.Folder, _qti.app.rootFolder)
+
+def addFolder(name,parentFolder=None):
+    """Create a new folder.
+
+    Args:
+        name: The name of the folder to create.
+        parentFolder: If given, make the new folder a subfolder of this one.
+
+    Returns:
+        A handle to the newly created folder.
+    """
+    if parentFolder is not None:
+        parentFolder = parentFolder._getHeldObject()
+    return new_proxy(proxies.Folder, _qti.app.addFolder, name,parentFolder)
+
+def deleteFolder(folder):
+    """Delete the referenced folder"""
+    return threadsafe_call(_qti.app.deleteFolder, folder._getHeldObject())
+
+def changeFolder(folder, force=False):
+    """Changes the current folder.
+
+    Args:
+        folder: A reference to the folder to change to.
+        force: Whether to do stuff even if the new folder is already the active one (default: no).
+
+    Returns:
+        True on success.
+    """
+    return threadsafe_call(_qti.app.changeFolder, folder._getHeldObject(),force)
+
+def copyFolder(source, destination):
+    """Copy a folder (and its contents) into another.
+
+    Returns:
+        True on success.
+    """
+    return threadsafe_call(_qti.app.copyFolder, source._getHeldObject(),destination._getHeldObject())
+
+def openTemplate(filename):
+    """Load a previously saved window template"""
+    return new_proxy(proxies.MDIWindow,_qti.app.openTemplate, filename)
+
+def saveAsTemplate(window, filename):
+    """Save the characteristics of the given window to file"""
+    threadsafe_call(_qti.app.saveAsTemplate, window._getHeldObject(), filename)
+
+def setWindowName(window, name):
+    """Set the given window to have the given name"""
+    threadsafe_call(_qti.app.setWindowName, window._getHeldObject(), name)
+
+def setPreferences(layer):
+    threadsafe_call(_qti.app.setPreferences, layer._getHeldObject())
+
+def clone(window):
+    return new_proxy(proxies.MDIWindow, _qti.app.clone, window._getHeldObject())
+
+def tableToMatrix(table):
+    return new_proxy(proxies.MDIWindow, _qti.app.tableToMatrix, table._getHeldObject())
+
+def matrixToTable(matrix, conversionType=_qti.app.Direct):
+    return new_proxy(proxies.MDIWindow, _qti.app.matrixToTable, matrix._getHeldObject(),conversionType)
+
+#-----------------------------------------------------------------------------
+#-------------------------- Wrapped MantidUI functions -----------------------
+#-----------------------------------------------------------------------------
+
+def mergePlots(graph1,graph2):
+    """Combine two graphs into a single plot"""
+    return new_proxy(proxies.Graph, _qti.app.mantidUI.mergePlots,graph1._getHeldObject(),graph2._getHeldObject())
+
+def convertToWaterfall(graph):
+    """Convert a graph (containing a number of plotted spectra) to a waterfall plot"""
+    threadsafe_call(_qti.app.mantidUI.convertToWaterfall, graph._getHeldObject())
+
+def getMantidMatrix(name):
+    """Get a handle to the named Mantid matrix"""
+    return new_proxy(proxies.MantidMatrix, _qti.app.mantidUI.getMantidMatrix, name)
+
+def getInstrumentView(name, tab=InstrumentWindow.RENDER):
+    """Create an instrument view window based on the given workspace.
+
+    Args:
+        name: The name of the workspace.
+        tab: The index of the tab to display initially, (default=InstrumentWindow.RENDER)
+
+    Returns:
+        A handle to the created instrument view widget.
+    """
+    ads = _get_analysis_data_service()
+    if name not in ads:
+        raise ValueError("Workspace '%s' does not exist" % name)
+    return new_proxy(proxies.InstrumentWindow, _qti.app.mantidUI.getInstrumentView, name, tab)
+
+def importMatrixWorkspace(name, firstIndex=None, lastIndex=None, showDialog=False, visible=False):
+    """Create a MantidMatrix object from the named workspace.
+
+    Args:
+        name: The name of the workspace.
+        firstIndex: The workspace index to start at (default: the first one).
+        lastIndex: The workspace index to stop at (default: the last one).
+        showDialog: Whether to bring up a dialog to allow options to be entered manually (default: no).
+        visible: Whether to initially show the created matrix (default: no).
+
+    Returns:
+        A handle to the created matrix.
+    """
+    # Turn the optional arguments into the magic numbers that the C++ expects
+    if firstIndex is None:
+        firstIndex = -1
+    if lastIndex is None:
+        lastIndex = -1
+    return new_proxy(proxies.MantidMatrix, _qti.app.mantidUI.importMatrixWorkspace, name,
+                             firstIndex,lastIndex,showDialog,visible)
+
+def importTableWorkspace(name, visible=False):
+    """Create a MantidPlot table from a table workspace.
+
+    Args:
+        name: The name of the workspace.
+        visible: Whether to initially show the created matrix (default: no).
+
+    Returns:
+        A handle to the newly created table.
+    """
+    return new_proxy(proxies.MDIWindow,_qti.app.mantidUI.importTableWorkspace, name,False,visible)
+
+def createScriptInputDialog(alg_name, preset_values, optional_msg, enabled, disabled):
+    """Raises a property input dialog for an algorithm"""
+    return threadsafe_call(_qti.app.mantidUI.createScriptInputDialog, alg_name, preset_values, optional_msg, enabled, disabled)
+
+#-----------------------------------------------------------------------------
+#-------------------------- SliceViewer functions ----------------------------
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+def plotSlice(source, label="", xydim=None, slicepoint=None,
+                    colormin=None, colormax=None, colorscalelog=False,
+                    normalization=1,
+                    limits=None, **kwargs):
+    """Opens the SliceViewer with the given MDWorkspace(s).
+
+    Args:
+        source :: one workspace, or a list of workspaces
+
+    Optional Keyword Args:
+        label :: label for the window title
+        xydim :: indexes or names of the dimensions to plot, as an (X,Y) list or tuple. See SliceViewer::setXYDim()
+        slicepoint :: list with the slice point in each dimension.  Must be the same length as the number of dimensions of the workspace. See SliceViewer::setSlicePoint()
+        colormin :: value of the minimum color in the scale. See SliceViewer::setColorScaleMin()
+        colormax :: value of the maximum color in the scale. See SliceViewer::setColorScaleMax()
+        colorscalelog :: value of the maximum color in the scale. See SliceViewer::setColorScaleLog()
+        limits :: list with the (xleft, xright, ybottom, ytop) limits to the view to show. See SliceViewer::setXYLimits()
+        normalization :: 0=none; 1=volume (default); 2=# of events.
+
+    Returns:
+        a (list of) handle(s) to the SliceViewerWindow widgets that were open.
+        Use SliceViewerWindow.getSlicer() to get access to the functions of the
+        SliceViewer, e.g. setting the view and slice point.
+    """
+    workspace_names = getWorkspaceNames(source)
+    try:
+        import mantidqtpython
+    except:
+        print "Could not find module mantidqtpython. Cannot open the SliceViewer."
+        return
+
+    # Make a list of widgets to return
+    out = []
+    for wsname in workspace_names:
+        window = __doSliceViewer(wsname, label=label,
+           xydim=xydim, slicepoint=slicepoint, colormin=colormin,
+           colormax=colormax, colorscalelog=colorscalelog, limits=limits,
+           normalization=normalization,
+           **kwargs)
+        pxy = proxies.SliceViewerWindowProxy(window)
+        out.append(pxy)
+
+    # Return the widget alone if only 1
+    if len(out) == 1:
+        return out[0]
+    else:
+        return out
+
+
+#-----------------------------------------------------------------------------
+def getSliceViewer(source, label=""):
+    """Retrieves a handle to a previously-open SliceViewerWindow.
+    This allows you to get a handle on, e.g., a SliceViewer that was open
+    by the MultiSlice view in VATES Simple Interface.
+    Will raise an exception if not found.
+
+    Args:
+        source :: name of the workspace that was open
+        label :: additional label string that was used to identify the window.
+
+    Returns:
+        a handle to the SliceViewerWindow object that was created before.
+    """
+    import mantidqtpython
+    workspace_names = getWorkspaceNames(source)
+    if len(workspace_names) != 1:
+        raise Exception("Please specify only one workspace.")
+    else:
+        svw = threadsafe_call(mantidqtpython.MantidQt.Factory.WidgetFactory.Instance().getSliceViewerWindow, workspace_names[0], label)
+        if svw is not None:
+            return proxies.SliceViewerWindowProxy(svw)
+        else:
+            return None
+
+
+#-----------------------------------------------------------------------------
+def closeAllSliceViewers():
+    """
+    Closes all currently open SliceViewer windows. This might be useful to
+    clean up your desktop after opening many windows.
+    """
+    import mantidqtpython
+    threadsafe_call(mantidqtpython.MantidQt.Factory.WidgetFactory.Instance().closeAllSliceViewerWindows)
+
+#-----------------------------------------------------------------------------
+# Legacy function
+plotTimeBin = plotBin
+
+# import 'safe' methods (i.e. no proxy required) of ApplicationWindow into the global namespace
+# Only 1 at the moment!
+appImports = [
+        'saveProjectAs'
+        ]
+for name in appImports:
+    globals()[name] = getattr(_qti.app,name)
+
+# Ensure these functions are available as without the _qti.app.mantidUI prefix
+MantidUIImports = [
+    'getSelectedWorkspaceName'
+    ]
+# Update globals
+for name in MantidUIImports:
+    globals()[name] = getattr(_qti.app.mantidUI,name)
+
+# Set some aliases for Layer enumerations so that old code will still work
+Layer = _qti.Layer
+Layer.Log10 = _qti.GraphOptions.Log10
+Layer.Linear = _qti.GraphOptions.Linear
+Layer.Left = _qti.GraphOptions.Left
+Layer.Right = _qti.GraphOptions.Right
+Layer.Bottom = _qti.GraphOptions.Bottom
+Layer.Top = _qti.GraphOptions.Top
+
+#-----------------------------------------------------------------------------
+#--------------------------- "Private" functions -----------------------------
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+def __doSliceViewer(wsname, label="", xydim=None, slicepoint=None,
+                    colormin=None, colormax=None, colorscalelog=False,
+                    limits=None, normalization=1):
+    """Open a single SliceViewerWindow for the workspace, and shows it
+
+    Args:
+        wsname :: name of the workspace
+    See plotSlice() for full list of keyword parameters.
+
+    Returns:
+        A handle to the created SliceViewerWindow widget
+    """
+    import mantidqtpython
+    from PyQt4 import QtCore
+
+    svw = threadsafe_call(mantidqtpython.MantidQt.Factory.WidgetFactory.Instance().createSliceViewerWindow, wsname, label)
+    threadsafe_call(svw.show)
+
+    # -- Connect to main window's shut down signal ---
+    QtCore.QObject.connect(_qti.app, QtCore.SIGNAL("shutting_down()"),
+                    svw, QtCore.SLOT("close()"))
+
+    sv = threadsafe_call(svw.getSlicer)
+    # --- X/Y Dimensions ---
+    if (not xydim is None):
+        if len(xydim) != 2:
+            raise Exception("You need to specify two values in the 'xydim' parameter")
+        else:
+            threadsafe_call(sv.setXYDim, xydim[0], xydim[1])
+
+    # --- Slice point ---
+    if not slicepoint is None:
+        for d in xrange(len(slicepoint)):
+            try:
+                val = float(slicepoint[d])
+            except ValueError:
+                raise ValueError("Could not convert item %d of slicepoint parameter to float (got '%s'" % (d, slicepoint[d]))
+            sv.setSlicePoint(d, val)
+
+    # Set the normalization before the color scale
+    threadsafe_call(sv.setNormalization, normalization)
+
+    # --- Color scale ---
+    if (not colormin is None) and (not colormax is None):
+        threadsafe_call(sv.setColorScale, colormin, colormax, colorscalelog)
+    else:
+        if (not colormin is None): threadsafe_call(sv.setColorScaleMin, colormin)
+        if (not colormax is None): threadsafe_call(sv.setColorScaleMax, colormax)
+    try:
+        threadsafe_call(sv.setColorScaleLog, colorscalelog)
+    except:
+        print "Log color scale not possible."
+
+    # --- XY limits ---
+    if not limits is None:
+        threadsafe_call(sv.setXYLimits, limits[0], limits[1], limits[2], limits[3])
+
+    return svw
+
+
+def get_screenshot_dir():
+    """ Returns the directory for screenshots,
+    or NONE if not set """
+    expected_env_var = 'MANTID_SCREENSHOT_REPORT'
+    dest = os.getenv(expected_env_var)
+    if not dest is None:
+        # Create the report directory if needed
+        if not os.path.exists(dest):
+            os.mkdir(dest)
+    else:
+        errormsg = "The expected environmental does not exist: " + expected_env_var
+        print errormsg
+        raise RuntimeError(errormsg)
+    return dest
+
+
+
+
+#======================================================================
+def _replace_report_text(filename, section, newtext):
+    """ Search html report text to
+replace a line <!-- Filename --> etc.
+Then, the contents of that section are replaced
+@param filename :: full path to .html report
+@param section :: string giving the name of the section
+@param newtext :: replacement contents of that section. No new lines!
+@return the new contents of the entire page
+"""
+    # Get the current report contents if any
+    if os.path.exists(filename):
+        f = open(filename, 'r')
+        contents = f.read()
+        f.close()
+    else:
+        contents = ""
+
+
+    lines = contents.splitlines()
+    sections = dict()
+    # Find the text in each section
+    for line in lines:
+        if line.startswith("<!-- "):
+            # All lines should go <!-- Section -->
+            n = line.find(" ", 5)
+            if n > 0:
+                current_section = line[5:n].strip()
+                current_text = line[n+4:]
+                sections[current_section] = current_text
+
+    # Replace the section
+    sections[section] = newtext.replace("\n","")
+
+    # Make the output
+    items = sections.items()
+    items.sort()
+    output = []
+    for (section_name, text) in items:
+        output.append("<!-- %s -->%s" % (section_name, text))
+
+    # Save the new text
+    contents = os.linesep.join(output)
+    f = open(filename, 'w')
+    f.write(contents)
+    f.close()
+
+
+class Screenshot(QtCore.QObject):
+    """
+        Handles taking a screenshot while
+        ensuring the call takes place on the GUI
+        thread
+    """
+
+    def take_picture(self, widget, filename):
+        """
+        Takes a screenshot and saves it to the
+        filename given, ensuring the call is processed
+        through a slot if the call is from a separate
+        thread
+        """
+        # First save the screenshot
+        widget.resize(widget.size())
+        QtCore.QCoreApplication.processEvents()
+
+        pix = QtGui.QPixmap.grabWidget(widget)
+        pix.save(filename)
+
+def screenshot(widget, filename, description, png_exists=False):
+    """ Take a screenshot of the widget for displaying in a html report.
+
+    The MANTID_SCREENSHOT_REPORT environment variable must be set
+    to the destination folder. Screenshot taking is skipped otherwise.
+
+    :param widget: QWidget to grab.
+    :param filename: Save to this file (no extension!).
+    :param description: Short descriptive text of what the screenshot should look like.
+    :param png_exists: if True, then the 'filename' already exists. Don't grab a screenshot, but add to the report.
+    """
+    dest = get_screenshot_dir()
+    if not dest is None:
+        report = os.path.join(dest, "index.html")
+
+        if png_exists:
+            pass
+        else:
+            # Find the widget if handled with a proxy
+            if hasattr(widget, "_getHeldObject"):
+                widget = widget._getHeldObject()
+
+        if widget is not None:
+            camera = Screenshot()
+            threadsafe_call(camera.take_picture, widget, os.path.join(dest, filename+".png"))
+
+        # Modify the section in the HTML page
+        section_text = '<h2>%s</h2>' % filename
+        now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
+        section_text += '%s (%s)<br />' % (description, now)
+        section_text += '<img src="%s.png" alt="%s"></img>' % (filename, description)
+
+        _replace_report_text(report, filename, section_text)
+
+def screenshot_to_dir(widget, filename, screenshot_dir):
+    """Take a screenshot_to_dir of a widget
+
+    @param widget :: QWidget to take an image of
+    @param filename :: Destination filename for that image
+    @param screenshot_dir :: Directory to put the screenshots into.
+    """
+    # Find the widget if handled with a proxy
+    if hasattr(widget, "_getHeldObject"):
+        widget = widget._getHeldObject()
+
+    if widget is not None:
+        camera = Screenshot()
+        imgpath = os.path.join(screenshot_dir, filename)
+        threadsafe_call(camera.take_picture, widget, imgpath)
+        return imgpath
+    else:
+        raise RuntimeError("Unable to retrieve widget. Has it been deleted?")
+
+
+#=============================================================================
+# Helper methods
+#=============================================================================
+
+def __getWorkspaceIndices(source):
+    """
+        Returns a list of workspace indices from a source.
+        The source can be a list, a tuple, an int or a string.
+    """
+    index_list = []
+    if isinstance(source,list) or isinstance(source,tuple):
+        for i in source:
+            nums = __getWorkspaceIndices(i)
+            for j in nums:
+                index_list.append(j)
+    elif isinstance(source, int):
+        index_list.append(source)
+    elif isinstance(source, str):
+        elems = source.split(',')
+        for i in elems:
+            try:
+                index_list.append(int(i))
+            except ValueError:
+                pass
+    else:
+        raise TypeError('Incorrect type passed as index argument "' + str(source) + '"')
+    return index_list
+
+# Common checks for plotSpectrum, plotMD, and plotBin:
+def __checkPlotWorkspaces(workspace_names):
+    """Check that a list of workspaces is not empty and all the elements exist
+
+    Throws Python-level exceptions if the list is empty or any of the spaces don't exist.
+
+    Args:
+        workspace_names: list of names of workspace(s)
+
+    Returns:
+        Nothing, just throws exceptions in case of error/inconsistent inputs
+    """
+
+    if len(workspace_names) == 0:
+        raise ValueError("No workspace names given")
+    for name in workspace_names:
+        if not mantid.api.mtd.doesExist(name):
+            raise ValueError("Workspace '%s' does not exist in the workspace list" % name)
+        if not isinstance(mantid.api.mtd[name], mantid.api.IMDWorkspace):
+            raise ValueError("Workspace '%s' is not an IMDWorkspace" % name)
+
+def __checkPlotMDWorkspaces(workspace_names):
+    """Check that a list of workspaces is not empty AND all the elements exist AND they are
+    IMDWorkspace(s). First part of the check is done based on __checkPlotWorkspaces()
+
+    Throws Python-level exceptions if the list is empty or any of the spaces don't exist or
+    are not IMDWorkspace.
+
+    Args:
+        workspace_names: list of names of workspace(s)
+
+    Returns:
+        Nothing, just throws exceptions in case of error/inconsistent inputs
+    """
+    __checkPlotWorkspaces(workspace_names)
+    for name in workspace_names:
+        if not isinstance(mantid.api.mtd[name], mantid.api.IMDWorkspace):
+            raise ValueError("Workspace '%s' is not an IMDWorkspace" % name)
+
+#-----------------------------------------------------------------------------
diff --git a/Code/Mantid/MantidPlot/pymantidplot/future/__init__.py b/Code/Mantid/MantidPlot/pymantidplot/future/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Code/Mantid/MantidPlot/pymantidplot/future/pyplot.py b/Code/Mantid/MantidPlot/pymantidplot/future/pyplot.py
new file mode 100644
index 00000000000..d6bf362f5f5
--- /dev/null
+++ b/Code/Mantid/MantidPlot/pymantidplot/future/pyplot.py
@@ -0,0 +1,1180 @@
+"""============================================================================
+New Python command line interface for plotting in Mantid (ala matplotlib)
+============================================================================
+
+The idea behind this new module is to provide a simpler, more
+homogeneous command line interface (CLI) to the Mantid plotting
+functionality. This new interface is meant to resemble MatPlotLib as
+far as possible, and provide a more manageable, limited number of plot
+options.
+
+The module is at a very early stage of development and provides
+limited functionality. This is very much work in progress at the
+moment. Feedback is very much welcome!
+
+To use this new functionality you first need to import the new pyplot module:
+
+    from mantidplot.future.pyplot import *
+
+Please do not forget this step, otherwise you may get arcane error
+messages from functions of the old mantidplot Python CLI.
+
+Simple plots can be created and manipulated with a handul of
+commands. See the following examples.
+
+Plot an array (python list)
+---------------------------
+
+    plot([0.1, 0.3, 0.2, 4])
+    # The array values will be inserted in a workspace named 'array_dummy_workspace_...'
+
+
+Plot a Mantid workspace
+-----------------------
+
+    # first, load a workspace. You can do this with a Load command or just from the GUI menus
+    ws=Load("/path/to/MAR11060.raw", OutputWorkspace="foo")
+    plot(ws, [100])
+
+The list or array of values will be inserted in a workspace named
+'array_dummy_workspace_...', for example: array_dummy_workspace_1,
+array_dummy_workspace_2, as you create new plots.
+
+
+The plot commands accept a list of options (kwargs) as parameters
+passed by name. With these options you can modify plot properties,
+such as line styles, colors, axis scale, etc. The following example
+illustrates the use of a few options. You can refer to the list of
+options provided further down in this document. In principle, any
+combination of options is supported, as long as it makes sense!
+
+Plot an array with a different style
+------------------------------------
+
+    a = [0.1, 0.3, 0.2, 4]
+    plot(a)
+    import numpy as np
+    y = np.sin(np.linspace(-2.28, 2.28, 1000))
+    plot(y)
+
+
+If you have used the traditional Mantid command line interface in
+Python you will probably remember the plotSpectrum, plotBin and plotMD
+functions. These are supported in this new interface as shown in the
+following examples.
+
+Plot spectra using workspace objects and workspace names
+--------------------------------------------------------
+
+    # please make sure that you use the right path and file name
+    mar=Load('/path/to/MAR11060.raw', OutputWorkspace="MAR11060")
+    plot('MAR11060', [10,100,500])
+    plot(mar,[3, 500, 800])
+
+Let's load one more workspace so we can see some examples with list of workspaces
+
+    loq=Load('/path/to/LOQ48097.raw', OutputWorkspace="LOQ48097")
+
+The next lines are all equivalent, you can use workspace objects or
+names in the list passed to plot:
+
+    plot([mar, 'LOQ48097'], [800, 900])
+    plot([mar, loq], [800, 900])
+    plot(['MAR11060', loq], [800, 900])
+
+Here, the plot function is making a guess and plotting the spectra of
+these workspaces (instead of the bins or anything else). You can make
+that choice more explicit by specifying the 'tool' argument:
+
+    plot(['MAR11060', loq], [800, 900], tool='plot_spectrum')
+
+Alternatively, you can use the plot_spectrum command, which is
+equivalent to the plot command with the keyword argument
+tool='plot_spectrum':
+
+    plot_spectrum(['MAR11060', loq], [800, 900])
+
+Plotting bins
+-------------
+
+To plot workspace bins you can use the keyword 'tool' with the value 'plot_bin', like this:
+
+    ws=Load('/path/to/HRP39182.RAW', OutputWorkspace="HRP39182")
+    plot(ws, [1, 5, 7, 100], tool='plot_bin')
+
+or, alternatively, you can use the plot_bin command:
+
+    plot_bin(ws, [1, 5, 7, 100], linewidth=4, linestyle=':')
+
+Plotting MD workspaces
+---------------------
+
+Similarly, to plot MD workspaces you can use the keyword 'tool' with the value 'plot_md', like this:
+
+    simple_md_ws = CreateMDWorkspace(Dimensions='3',Extents='0,10,0,10,0,10',Names='x,y,z',Units='m,m,m',SplitInto='5',MaxRecursionDepth='20',OutputWorkspace=MDWWorkspaceName)
+    plot(simple_md_ws, tool='plot_md')
+
+or a specific plot_md command:
+
+    plot_md(simple_md_wsws)
+
+For simplicity, these examples use a dummy MD workspace. Please refer
+to the Mantid (http://www.mantidproject.org/MBC_MDWorkspaces) for a
+more real example, which necessarily gets more complicated and data
+intensive.
+
+Changing style properties
+-------------------------
+
+You can modify the style of your plots. For example like this (for a
+full list of options currently supported, see below).
+
+    lines=plot(loq, [100, 104], tool='plot_spectrum', linestyle='-.', marker='*', color='red')
+
+Notice that the plot function returns a list of lines, which
+correspond to the spectra lines. At present the lines have limited
+functionality. Essentially, the data underlying these lines data can
+be retrieved as follows:
+
+    lines[0].get_xdata()
+    lines[0].get_ydata()
+
+If you use plot_spectrum, the number of elements in the output lines
+should be equal to the number of bins in the corresponding
+workspace. Conversely, if you use plot_bin, the number of elements in
+the output lines should be equal to the number of spectra in the
+workspace.
+
+Other properties can be modified using different functions, as in
+matplotlib's pyplot. For example:
+
+    title('Test plot of LOQ')
+    xlabel('ToF')
+    ylabel('Counts')
+    ylim(0, 8)
+    xlim(1e3, 4e4)
+    grid('on')
+
+You can also save the current figure into a file like this:
+
+    savefig('example_saved_figure.png')
+
+where the file format is guessed from the file extension. The same
+extensions as in the MantidPlot figure export dialog are supported,
+including jpg, png, tif, ps, and svg.
+
+Additional options supported as keyword arguments (kwargs):
+-----------------------------------------------------------
+
+There is a couple of important plot options that are set as keyword
+arguments:
+
+============  ================
+Option name   Values supported
+------------  ----------------
+error_bars    True, False (default)
+hold          on, off
+
+error_bars has the same meaning as in the traditional mantidplot plot
+functions: it defines whether error bars should be added to the
+plots. hold has the same behavior as in matplotlib and pyplot. If the
+value of hold is 'on' in a plot command, the new plot will be drawn on
+top of the current plot window, without clearing it. This makes it
+possible to make plots incrementally.
+
+Multi-plot commands
+-------------------
+
+In this version of future.pyplot there is limited support for
+multi-plot commands (as in pyplot and matlab). For example, you can
+type commands like the following:
+
+plot(ws, [100, 101], 'r', ws, [200, 201], 'b', tool='plot_spectrum')
+
+This command will plot spectra 100 and 101 in red and spectra 200 and
+201 in blue on the same figure. You can also combine different
+workspaces, for example:
+
+plot(ws, [100, 101], 'r', mar, [50, 41], 'b', tool='plot_spectrum')
+
+
+Style options supported as keyword arguments
+--------------------------------------------
+
+Unless otherwise stated, these options are in principle supported in
+all the plot variants. These options have the same (or as closed as
+possible) meaning as in matplotlib.
+
+============  ================
+Option name   Values supported
+------------  ----------------
+linewidth     real values
+linestyle     '-', '--', '-.' '.'
+marker        'o', 'v', '^', '<', '>', 's', '*', 'h', '|', '_'
+color         color character or string ('b', 'blue', 'g', 'green', 'k', 'black', 'y', 'yellow', 'c', 'cyan', 'r', 'red'. 'm', 'magenta', etc.). RGB colors are not supported at the moment.
+============  ================
+
+Functions that modify plot properties
+-------------------------------------
+
+Here is a list of the functions supported at the moment. The offer the
+same functionality as their counterparts in matplotlib's pyplot.
+
+- title
+- xlabel
+- ylabel
+- ylim
+- xlim
+- axis
+- grid
+- savefig
+
+"""
+
+try:
+    import _qti
+except ImportError:
+    raise ImportError('The \'mantidplot\' module can only be used from within MantidPlot.')
+
+import numpy as np
+from PyQt4 import Qt, QtGui, QtCore
+from mantid.api import (IMDWorkspace as IMDWorkspace, MatrixWorkspace as MatrixWorkspace, AlgorithmManager as AlgorithmManager, AnalysisDataService as ADS)
+from mantid.api import mtd
+#    return __is_workspace(arg) or (mantid.api.mtd.doesExist(arg) and isinstance(mantid.api.mtd[arg], mantid.api.IMDWorkspace))
+from mantid.simpleapi import CreateWorkspace as CreateWorkspace
+import mantidplot  
+
+print ("You are loading '" + __name__ + "', which is an experimental module." +
+"""
+Please note: this module is at a very early stage of development and
+provides limited functionality. This is work in progress and feedback
+is very much welcome! Please let us know any wishes and suggestions.""")
+
+class Line2D():
+    """
+        A very minimal replica of matplotlib.Line.Line2D. The true Line2D
+        is a sublcass of matplotlib.artist and provides tons of
+        functionality. At the moment this just provides get_xdata()
+        and get_ydata().  It also holds its Graph object and through
+        it it would be possible to provide additional selected
+        functionality. Keep in mind that providing GUI line/plot
+        manipulation functionality would require a proxy for this
+        class.
+    """
+
+    def __init__(self, graph, index, x_data, y_data, fig=None):
+        self._graph = graph
+        self._index = index   # will (may) be needed to change properties of this line
+        self._xdata = x_data
+        self._ydata = y_data
+        self._fig = None
+
+    def get_xdata(self):
+        return self._xdata
+
+    def get_ydata(self):
+        return self._ydata
+
+    def figure(self):
+        return self._fig
+
+class Figure():
+    """
+    A very minimal replica of matplotlib.figure.Figure. This class is
+    here to support manipulation of multiple figures from the command
+    line.
+    """
+
+    """For the moment this is just a very crude wrapper for Graph
+    (proxy to qti Multilayer), and it is here just to hide (the rather
+    obscure) Graph from users.
+    """
+    # Holds the set of figure ids (integers) as they're being created and/or destroyed
+    __figures = {}
+    # Always increasing seq number, not necessarily the number of figures in the dict
+    __figures_seq = 0
+
+    def __init__(self, num):
+        if isinstance(num, int):
+            # normal matplotlib use, like figure(2)
+            missing = -1
+            fig = __figures.get(num, missing)
+            if missing == fig:
+                self._graph = Figure.__empty_graph()
+                __figures[num] = self
+                if num > __figures_seq:
+                    __figures_seq = num
+            else:
+                if None == fig._graph._getHeldObject():
+                    # has been destroyed!
+                    self._graph = Figure.__empty_graph()
+                else:
+                    self._graph = fig._graph
+        elif isinstance(num, mantidplot.proxies.Graph):
+            if None == num._getHeldObject():
+                # deleted Graph!
+                self._graph = Figure.__empty_graph()
+            else:
+                self._graph = num
+            num = Figure.__make_new_fig_number()
+            Figure.__figures[num] = self
+        else:
+            raise ValueError("To create a Figure you need to specify a figure number or a Graph object." )
+
+    @classmethod
+    def fig_seq(cls):
+        """ Helper method, returns the current sequence number for figures"""
+        return cls.__figures_seq
+
+    @classmethod
+    def __make_new_fig_number(cls):
+        """ Helper method, creates and return a new figure number"""
+        num = cls.__figures_seq + 1
+        avail = False
+        while not avail:
+            missing = -1
+            fig = cls.__figures.get(num, missing)
+            if missing == fig:
+                avail = True   # break
+            else:
+                num += 1
+        cls.__figures_seq = num
+        return num
+
+    @staticmethod
+    def __empty_graph():
+        """Helper method, just create a new Graph with an 'empty' plot"""
+        lines = plot([0])
+        return lines._graph
+
+
+def __empty_fig():
+    """Helper function, for the functional interface. Makes a blank/empty figure"""
+    lines = plot([0]) # for the very first figure, this would generate infinite recursion!
+    return Figure(lines[0]._graph)
+
+# TODO/TOTHINK: no 'hold' function support for now. How to handle multi-plots with different types/tools? Does it make sense at all?
+__hold_status = False
+
+__last_shown_fig = None
+
+def __last_fig():
+    """
+        Helper function, especially for the functional interface.
+        Avoid using it inside the plot_spectrum, plot_bin, etc. as there's risk of infinite recursion
+        Returns :: last figure, creating new one if there was none
+    """
+    global __last_shown_fig
+    if not __last_shown_fig:
+        f = __empty_fig()
+        __last_shown_fig = f
+    return __last_shown_fig
+
+def __update_last_shown_fig(g):
+    """
+        Helper function, especially for the functional interface.
+        @param g :: graph object
+        Returns :: new last fig
+    """
+    global __last_shown_fig
+    __last_shown_fig = Figure(g)
+    return __last_shown_fig
+
+def __is_array(arg):
+    """
+        Is the argument a python or numpy list?
+        @param arg :: argument
+        
+        Returns :: True if the argument is a python or numpy list
+    """
+    return isinstance(arg, list) or isinstance(arg, np.ndarray) 
+
+def __is_array_or_int(arg):
+    """
+        Is the argument a valid workspace index/indices, which is to say:
+        Is the argument an int, or python or numpy list?
+        @param arg :: argument
+
+        Returns :: True if the argument is an integer, or a python or numpy list
+    """
+    return isinstance(arg, int) or __is_array(arg)
+
+
+def __is_registered_workspace_name(arg):
+    """"
+        Check whether the argument passed is the name of a registered workspace
+
+        @param arg :: argument (supposedly a workspace name)
+
+        Returns :: True if arg is a correct workspace name
+    """
+    return (mtd.doesExist(arg) and isinstance(mtd[arg], IMDWorkspace))
+
+def __is_valid_single_workspace_arg(arg):
+    """"
+        Check whether the argument passed can be used as a workspace input. Note that this differs from
+        __is_workspace() in that workspace names are also accepted. Throws ValueError with informative
+        message if arg is not a valid workspace object or name.
+
+        @param arg :: argument (supposedly one workspace, possibly given by name)
+
+        Returns :: True if arg can be accepted as a workspace
+    """
+    if __is_workspace(arg) or __is_registered_workspace_name(arg):
+        return True
+    else:
+         raise ValueError("This parameter is not a valid workspace: " + str(arg))
+
+def __is_valid_workspaces_arg(arg):
+    """"
+        Check whether the argument passed can be used as a workspace(s) input. Note that this differs from
+        __is_workspace() in that lists of workspaces and workspace names are also accepted.
+
+        @param arg :: argument (supposedly one or more workspaces, possibly given by name)
+
+        Returns :: True if arg can be accepted as a workspace or a list of workspaces
+    """
+    if 0 == len(arg):
+        return False
+
+    if isinstance(arg, basestring):
+        return __is_valid_single_workspace_arg(arg)
+    else:
+        for name in arg:
+            # name can be a workspace name or a workspace object
+            try:
+                __is_valid_single_workspace_arg(name)
+            except:
+                raise ValueError("This parameter passed in a list of workspaces is not a valid workspace: " + str(name))
+    return True
+
+def __is_data_pair(a, b):
+    """
+        Are the two arguments passed (a and b) a valid data pair for plotting, like in plot(x, y) or
+        plot(ws, [0, 1, 2])?
+        @param a :: first argument passed (supposedly array or workspace(s))
+        @param b :: second argument (supposedly an array of values, or indices)
+
+        Returns :: True if the arguments can be used to plot something is an integer, or a python or numpy list
+    """
+    res = (__is_array(a) and __is_array(a)) or (__is_valid_workspaces_arg(a) and __is_array_or_int(b))
+    return res
+
+def __is_workspace(arg):
+    """
+        Is the argument a Mantid MatrixWorkspace?
+        @param arg :: argument
+        
+        Returns :: True if the argument a MatrixWorkspace
+    """
+    return isinstance(arg, MatrixWorkspace)
+
+def __is_array_of_workspaces(arg):
+    """
+        Is the argument a sequence of Mantid MatrixWorkspaces?
+        @param arg :: argument
+        
+        Returns :: True if the argument is a sequence of  MatrixWorkspace
+    """
+    return __is_array(arg) and len(arg) > 0 and __is_workspace(arg[0])
+
+
+def __create_workspace(x, y, name="array_dummy_workspace"):
+    """
+        Create a workspace. Also puts it in the ADS
+        @param x :: x array
+        @param y :: y array
+        @param name :: workspace name
+        
+        Returns :: Workspace
+    """    
+    alg = AlgorithmManager.create("CreateWorkspace")
+    alg.setChild(True) 
+    alg.initialize()
+    # fake empty workspace (when doing plot([]), cause setProperty needs non-empty data)
+    if [] == x:
+        x = [0]
+    if [] == y:
+        y = [0]
+    alg.setProperty("DataX", x)
+    alg.setProperty("DataY", y)
+    name = name + "_" + str(Figure.fig_seq())
+    alg.setPropertyValue("OutputWorkspace", name) 
+    alg.execute()
+    ws = alg.getProperty("OutputWorkspace").value
+    ADS.addOrReplace(name, ws) # Cannot plot a workspace that is not in the ADS
+    return ws
+
+
+def __list_of_lines_from_graph(g, first_line=0):
+    """
+        Produces a python list of line objects, with one object per line plotted on the passed graph
+        Note: at the moment these objects are of class Line2D which is much simpler than matplotlib.lines.Line2D
+        This function should always be part of the process of creating a new figure/graph, and it guarantees
+        that this figure being created is registered as the last shown figure.
+
+        @param g :: graph (with several plot layers = qti Multilayer)
+        @param first_line :: index to start from (useful for hold='on', multi-plots, etc.)
+
+        Returns :: List of line objects
+    """
+    if None == g:
+        raise ValueError("Got empty Graph object, cannot get its lines." )
+    # assume we use a single layer
+    active = g.activeLayer()
+    res = []
+    for i in range(first_line, active.numCurves()):
+        x_data = []
+        y_data = []
+        d = active.curve(i).data()
+        for i in range(0, active.curve(i).data().size()):
+            x_data.append(d.x(i))
+            y_data.append(d.y(i))
+        res.append(Line2D(g, i, x_data, y_data))
+
+    fig = __update_last_shown_fig(g)
+    for lines in res:
+        lines._fig = fig
+
+    return res;
+
+def __matplotlib_defaults(l):
+    """
+        Tries to (approximately) mimic the default plot properties of a pylab.plot()
+        @param l :: layer (plot) from a mantidplot Graph object
+
+        Returns :: nothing, just modifies properties of the layer passed
+    """
+    if None == l:
+        raise ValueError("Got empty Layer object, cannot modify its properties." )
+    l.removeLegend()
+    for i in range(0, l.numCurves()):
+        l.setCurveLineColor(i, __color_char_to_color_idx['b'])
+    l.setTitle(' ')
+    l.setXTitle(' ')
+    l.setYTitle(' ')
+
+__marker_to_plotsymbol = {
+    'o': _qti.PlotSymbol.Ellipse, 'v': _qti.PlotSymbol.DTriangle, '^': _qti.PlotSymbol.UTriangle,
+    '<': _qti.PlotSymbol.LTriangle, '>': _qti.PlotSymbol.RTriangle, 's': _qti.PlotSymbol.Rect,
+    '*': _qti.PlotSymbol.Star1, 'h': _qti.PlotSymbol.Hexagon, '|': _qti.PlotSymbol.VLine,
+    '_': _qti.PlotSymbol.HLine
+}
+
+"""Contains all the supported line styles"""
+__linestyle_to_qt_penstyle = {
+    '-': QtCore.Qt.SolidLine, '--': QtCore.Qt.DashLine,
+    '-.': QtCore.Qt.DashDotLine, ':': QtCore.Qt.DotLine
+} # other available: Qt.DashDotDotLine, Qt.CustomDashLine
+
+def __apply_linestyle(graph, linestyle, first_line=0):
+    """
+        Sets the linestyle of lines/curves of the active layer of the graph passed
+
+        @param graph :: mantidplot graph (figure)
+        @param linestyle :: linestyle string
+        @param first_line :: index of first line to which the linestyle will apply
+                             (useful when in hold mode / adding lines)
+
+        Returns :: nothing, just modifies the line styles of the active layer of the graph passed
+    """
+    global __linestyle_to_qt_penstyle
+    wrong = 'inexistent'
+    penstyle = __linestyle_to_qt_penstyle.get(linestyle, wrong)
+    if wrong == penstyle:
+        raise ValueError("Wrong linestyle given, unrecognized: " + linestyle)
+    l = graph.activeLayer()
+    for i in range(first_line, l.numCurves()):
+        l.setCurveLineStyle(i, penstyle)
+
+# beware this is not Qt.Qt.color_name (black, etc.)
+__color_char_to_color_idx = {
+    'k': 0, 'r': 1, 'g': 2, 'b': 3, 'c': 4, 'm': 5, 'y': 18,
+    'black': 0, 'red': 1, 'green': 2, 'blue': 3, 'cyan': 4, 'magenta': 5, 'orange': 6,
+    'purple': 7, 'darkGreen': 8, 'darkBlue': 9, 'brown': 10, 'gray': 17, 'yellow': 18
+}
+
+def __apply_line_color(graph, c, first_line=0):
+    """
+        Sets the color of curves of the active layer of the graph passed
+
+        @param graph :: mantidplot graph (figure)
+        @param c :: color string
+        @param first_line :: index of first line to which the color will apply
+                             (useful when in hold mode / adding lines)
+
+        Returns :: nothing, just modifies the line styles of the active layer of the graph passed
+    """
+    inex = 'inexistent'
+    col_idx = __color_char_to_color_idx.get(c, inex)
+    if inex == col_idx:
+        col_idx = QtGui.QColor(c)
+    l = graph.activeLayer()
+    for i in range(first_line, l.numCurves()):
+        l.setCurveLineColor(i, col_idx) # beware this is not Qt.Qt.black, but could be faked with QtGui.QColor("orange")
+
+def __apply_marker(graph, marker, first_line=0):
+    """
+        Sets the marker of curves of the active layer of the graph passed
+
+        @param graph :: mantidplot graph (figure)
+        @param marker :: line marker character
+        @param first_line :: index of first line to which the color will apply
+                             (useful when in hold mode / adding lines)
+
+        Returns :: nothing
+    """
+    wrong = 'inexistent'
+    sym_code = __marker_to_plotsymbol.get(marker, wrong)
+    if wrong == sym_code:
+        raise ValueError("Warning: unrecognized marker: " + str(marker))
+    sym = _qti.PlotSymbol(sym_code, QtGui.QBrush(), QtGui.QPen(), QtCore.QSize(5,5))
+    l = graph.activeLayer()
+    for idx in range(first_line, l.numCurves()):
+        l.setCurveSymbol(idx, sym)
+
+def __is_marker(char):
+    """ Is it a marker character
+        @param char :: suspected marker character coming from a linestyle string
+        Returns :: True if it's a marker character
+    """
+    inex = 'inexistent'
+    m = __marker_to_plotsymbol.get(char, inex)
+    return m != inex
+
+__linestyle_to_qt_penstyle = {
+    '-': QtCore.Qt.SolidLine, '--': QtCore.Qt.DashLine,
+    '-.': QtCore.Qt.DashDotLine, ':': QtCore.Qt.DotLine
+} # other available: Qt.DashDotDotLine, Qt.CustomDashLine
+
+def __is_linestyle(s, i):
+    """
+        Check if we have a linestyle string in string s at position i
+        @param s :: input (style) string
+        @param i :: index where to start checking in string s
+
+        Returns :: 0 if no linestyle string is identified, length of the string (1 or 2) otherwise
+    """
+    global __linestyle_to_qt_penstyle
+
+    if len(s) <= i:
+        return 0
+
+    if len(s) > len(s) + 1:
+        # can check 2 chars
+        wrong = 'inexistent'
+        penstyle = __linestyle_to_qt_penstyle.get(linestyle, wrong)
+        if wrong != penstyle:
+            return 2
+
+    if '-'==s[i] or ':'==s[i]:
+        return 1
+    else:
+        return 0
+
+def __apply_plot_args(graph, first_line, *args):
+    """
+        Applies args, like '-r' etc.
+        @param graph :: a graph (or figure) that can contain multiple layers
+        @param first_line :: first line to which the options will apply (useful when in hold mode / adding lines)
+        @param args :: plot arguments
+
+        Returns :: nothing, just uses kwargs to modify properties of the layer passed
+    """
+    if None==graph or len(args) < 1 or ((),) == args:
+        return
+
+    for a in args:
+        if isinstance(a, basestring):
+            # this will eat characters as they come, without minding much the past/previous characters
+            # users can chain as many modifiers as they wish. It could be modified to be more strict/picky
+            i = 0
+            while i < len(a):
+                linestyle_len = __is_linestyle(a,i)
+                if linestyle_len > 0:
+                    __apply_linestyle(graph, a[i:i+linestyle_len], first_line)
+                    i += 2
+                elif __is_marker(a[i]):
+                    __apply_marker(graph, a[i:], first_line)
+                    i += 1
+                elif a[i].isalpha():
+                    __apply_line_color(graph, a[i], first_line)
+                    i += 1
+                else:
+                    # TOTHINK - error here? like this? sure? or just a warning?
+                    raise ValueError("Unrecognized character in input string: " + str(a[i]))
+        else:
+            raise ValueError("Expecting style string, but got an unrecognized input parameter: " + str(a) + ", of type: " + str(type(a)))
+
+def __apply_plot_kwargs(graph, first_line=0, **kwargs):
+    """
+        Applies kwargs
+        @param graph :: a graph (or figure) that can contain multiple layers
+
+        Returns :: nothing, just uses kwargs to modify properties of the layer passed
+    """
+    if None==graph or None==kwargs or ((),) == kwargs:
+        return
+
+    for key in kwargs:
+        if 'linestyle' == key:
+            __apply_linestyle(graph, kwargs[key])
+
+        elif 'linewidth' == key:
+            l = graph.activeLayer()
+            for i in range(first_line, l.numCurves()):
+                l.setCurveLineWidth(i, kwargs[key])
+
+        elif 'color' == key:
+            __apply_line_color(graph, kwargs[key], first_line)
+
+        elif 'marker' == key:
+            __apply_marker(graph, kwargs[key], first_line)
+
+def __is_multiplot_command(*args, **kwargs):
+    """
+        Finds out if the passed *args make a valid multi-plot command. At the same time, splits the
+        multi-plot command line into individual plot commands.
+
+        @param args :: curve data and options.
+        @param kwargs :: plot keyword options
+
+        Returns :: tuple: (boolean: whether it is a multiplot command, list of single plot commands as tuples)
+    """
+    # A minimum multi-plot command would be plot(x, y, z, w) or plot(ws1, idx1, ws2, idx2)
+    nargs = len(args)
+    # this will be a list with the sequence of individual plots (a tuples, each describing a single plot)
+    plots_seq = []
+    if nargs < 4:
+        return (False, [])
+    i = 0
+    while i < nargs:
+        a = []
+        b = []
+        style = ''
+        if (nargs-i) >= 3:
+            if __is_data_pair(args[i], args[i+1]):
+                a = args[i]
+                b = args[i+1]
+                i += 2
+            else:
+                return (False, []);
+            # can have style string, but don't get confused with single workspace name strings!
+            if (not __is_registered_workspace_name(args[i])) and isinstance(args[i], basestring):
+                style = args[i]
+                i += 1
+            plots_seq.append((a,b,style))
+
+        elif (nargs-i) >= 2:
+            if __is_data_pair(args[i], args[i+1]):
+                a = args[i]
+                b = args[i+1]
+                i += 2
+            else:
+                return (False, [])
+            plots_seq.append((a, b, ''))
+
+        elif (nargs-i) > 0:
+            raise ValueError("Not plottable. I do not know what to do with this last parameter: " + args[i] + ", of type " + str(type(args)))
+
+    return (i == nargs, plots_seq)
+
+def __process_multiplot_command(plots_seq, **kwargs):
+    """
+        Make one plot at a time when given a multi-plot command.
+
+        @param plots_seq :: list of individual plot parameters
+        @param kwargs :: plot style options
+
+        Returns :: the list of curves included in the plot
+    """
+    lines = []
+    if len(plots_seq) >= 1:
+        if not 'hold' in kwargs:
+            kwargs['hold'] = 'off'
+        lines = plot(*(plots_seq[0]), **kwargs)
+    for i in range(1, len(plots_seq)):
+        kwargs['hold'] = 'on'
+        lines.extend(plot(*(plots_seq[i]), **kwargs))
+    return lines
+
+def __translate_hold_kwarg(**kwargs):
+    """
+    Helper function to translate from hold='on'/'off' kwarg to a True/False value for the 
+    mantidplot window and window error_bars
+
+    @param kwargs :: keyword arguments passed to a plot function, this function only cares about hold. Any
+                     value different from 'on' will be considered as 'off'
+
+    Returns :: tuple with a couple of values: True/False value for window, and True/False for clearWindow, 
+               to be used with plotSpectrum, plotBin, etc.
+    """
+    # window and clearWindow
+    window_val = None
+    clearWindow_val = False
+    hold_name = 'hold'
+    missing_off = -1
+    str_val = kwargs.get(hold_name, missing_off)
+    if str_val != missing_off and str_val == 'on':
+        if None == __last_shown_fig:
+            window_val = None
+        else:
+            window_val = __last_fig()._graph
+        clearWindow_val = False
+
+    return window_val, clearWindow_val
+
+def __translate_error_bars_kwarg(**kwargs):
+    """
+    Helper function to translate from error_bars=True/False kwarg to a True/False value for the 
+    mantidplot error_bars argument
+
+    @param kwargs :: keyword arguments passed to a plot function. This function only cares about 'error_bars'.
+                     Any value different from 'True' will be considered as 'False'
+
+    Returns :: True/False value for error_bars, to be used with plotSpectrum, plotBin, etc.
+
+    """
+    # error_bars param
+    bars_val = False
+    bars_name = 'error_bars'
+    missing_off = -1
+    str_val = kwargs.get(bars_name, missing_off)
+    if str_val != missing_off and str_val == 'True':
+        bars_val = True
+
+    return bars_val
+
+def __plot_as_workspace(*args, **kwargs):
+    """
+        plot spectrum via qti plotting framework to plot a workspace.
+
+        @param args :: curve data and options.
+        @param kwargs :: plot line options
+
+        Returns :: List of line objects
+    """
+    return plot_spectrum(*args, **kwargs)
+
+def __plot_as_workspaces_list(*args, **kwargs):
+    """
+        Plot a series of workspaces
+        @param args :: curve data and options.
+        @param kwargs :: plot line options
+        
+        Returns :: List of line objects
+    """
+    # mantidplot.plotSpectrum can already handle 1 or more input workspaces.
+    return __plot_as_workspace(*args, **kwargs)
+
+
+def __plot_as_array(*args, **kwargs):
+    """
+        Plot from an array
+        @param args :: curve data and options.
+        @param kwargs :: plot line options
+        
+        Returns :: the list of curves (1) included in the plot
+    """
+    y = args[0]
+    idx_style = len(args)   # have to guess if we get plot(x,'r'), or plot(x, y, 'r') or no style string
+    if len(args) > 1:
+        if __is_array(args[1]):
+            ws = __create_workspace(y, args[1])
+            idx_style = 2
+        elif isinstance(args[1], basestring):
+            x = range(0, len(y), 1) # 0 to n, incremented by 1.
+            ws = __create_workspace(x, y)
+            # have to assume that args[1] is a style string
+            idx_style = 1
+        else:
+            raise ValueError("Inputs are of type: " + str(type(args)) + ". Not plottable." )
+    else:
+        x = range(0, len(y), 1)
+        ws = __create_workspace(x, y)
+
+    lines = __plot_as_workspace(ws, [0], *args[idx_style:], **kwargs)
+    graph = None
+    if len(lines) > 0:
+        graph = lines[0]._graph
+    else:
+        raise Exception("Could not plot a workspace: " + ws)
+    # something to improve: if the C++ Graph class provided a plot1D that doesn't do show(), so that
+    # we could modify properties behind the scene and at the end do the show(). Con: do we really need
+    # to load the qti layer with more methods because of outer layers like here?
+    if 0 == len(kwargs):
+        __matplotlib_defaults(graph.activeLayer())
+    return __list_of_lines_from_graph(graph)
+
+def __plot_with_tool(tool, *args, **kwargs):
+    bin_tool_name = 'plot_bin'
+    spectrum_tool_name = 'plot_spectrum'
+    md_tool_name = 'plot_md'
+
+    if bin_tool_name == tool or spectrum_tool_name == tool:
+        if len(args) < 2:
+            raise ValueError("To plot using %s as a tool you need to give at least two parameters"%tool)
+
+    if bin_tool_name == tool:
+        return plot_bin(args[0], args[1], *args[2:], **kwargs)
+    elif md_tool_name == tool:
+        return plot_md(args[0], *args[1:], **kwargs)
+    elif spectrum_tool_name == tool:
+        return plot_spectrum(args[0], args[1], *args[2:], **kwargs)
+    # here you would add slice/spectrum/instrument viewer, etc. and maybe you'll want to put them in a dict
+    else:
+        raise ValueError("Unrecognized tool specified: '" + tool + ";. Cannot plot this. ")
+
+def __plot_with_best_guess(*args, **kwargs):
+    y = args[0]
+    if __is_array(y):
+        if __is_array_of_workspaces(y):
+            return __plot_as_workspaces_list(*args, **kwargs)
+        else:
+            return __plot_as_array(*args, **kwargs)
+    else:
+        # mantidplot.plotSpectrum can handle workspace names (strings)
+        return __plot_as_workspace(*args, **kwargs)
+
+def plot_bin(workspaces, indices, *args, **kwargs):
+    """
+    X-Y plot of the bin counts in a workspace.
+
+    Plots one or more bin, selected by indices, using spectra numbers as x-axis and bin counts for 
+    each spectrum as y-axis.
+
+    @param workspaces :: workspace or list of workspaces (both workspace objects and names accepted)
+    @param indices :: indices of the bin(s) to plot
+
+    Returns :: the list of curves included in the plot
+    """
+    # Find optional params to plotBin
+    bars_val = __translate_error_bars_kwarg(**kwargs)
+    window_val, clearWindow_val = __translate_hold_kwarg(**kwargs)
+
+    # to change properties on the new lines being added
+    first_line = 0
+    if None != window_val:
+        first_line = window_val.activeLayer().numCurves()
+
+    graph = mantidplot.plotBin(workspaces, indices, error_bars=bars_val, type=-1, window=window_val, clearWindow=clearWindow_val)
+
+    __apply_plot_args(graph, first_line, *args)
+    __apply_plot_kwargs(graph, first_line, **kwargs)
+
+    return __list_of_lines_from_graph(graph, first_line)
+
+
+def plot_md(workspaces, *args, **kwargs):
+    """
+    X-Y plot of an MDWorkspace.
+
+    @param workspaces :: workspace or list of workspaces (both workspace objects and names accepted)
+
+    Returns :: the list of curves included in the plot
+    """
+    # Find optional params to plotBin
+    bars_val = __translate_error_bars_kwarg(**kwargs)
+    window_val, clearWindow_val = __translate_hold_kwarg(**kwargs)
+
+    # to change properties on the new lines being added
+    first_line = 0
+    if None != window_val:
+        first_line = window_val.activeLayer().numCurves()
+
+    graph = mantidplot.plotMD(workspaces, normalization=mantidplot.DEFAULT_MD_NORMALIZATION, error_bars=bars_val, window=window_val, clearWindow=clearWindow_val)
+
+    __apply_plot_args(graph, first_line, *args)
+    __apply_plot_kwargs(graph, first_line, **kwargs)
+
+    return __list_of_lines_from_graph(graph, first_line)
+
+
+def plot_spectrum(workspaces, indices, *args, **kwargs):
+    """X-Y Plot of spectra in a workspace.
+
+    Plots one or more spectra, selected by indices, using bin boundaries as x-axis
+    and the spectra values in each bin as y-axis.
+
+    @param workspaces :: workspace or list of workspaces (both workspace objects and names accepted)
+    @param indices :: indices of the spectra to plot, given as a single integer or a list of integers
+
+    Returns :: the list of curves included in the plot
+
+    """
+    # Find optional params to plotSpectrum
+    bars_val = __translate_error_bars_kwarg(**kwargs)
+    window_val, clearWindow_val = __translate_hold_kwarg(**kwargs)
+
+    # to change properties on the new lines being added
+    first_line = 0
+    if None != window_val:
+        first_line = window_val.activeLayer().numCurves()
+
+    graph = mantidplot.plotSpectrum(workspaces, indices, error_bars=bars_val, type=-1, window=window_val, clearWindow=clearWindow_val)
+
+    __apply_plot_args(graph, first_line, *args)
+    __apply_plot_kwargs(graph, first_line, **kwargs)
+
+    return __list_of_lines_from_graph(graph, first_line)
+
+
+def plot(*args, **kwargs):
+    """Plot the data in various forms depending on what arguments are passed.  Currently supported
+       inputs: arrays (as Python lists or numpy arrays) and workspaces (by name or workspace objects).
+
+        @param args :: curve data and options
+        @param kwargs :: plot line options
+
+        Returns :: the list of curves included in the plot
+
+        args can take different forms depending on what you plot. You can plot:
+        - a python list or array (x) for example like this:
+            plot(x)
+        - a workspace (ws) for example like this:
+            plot(ws, [100,101])  # this will plot spectra 100 and 101
+        - a list of workspaces (ws, ws2, ws3, etc.) for example like this:
+            plot([ws, ws2, ws3], [100,101])
+        - workspaces identified by their names:
+            plot(['HRP39182', 'MAR11060.nxs'], [100,101])
+
+        You can also pass matplotlib/pyplot style strings as arguments, for example:
+        - plot(x, '-.')
+
+        As keyword arguments (kwargs) you can specify multiple
+        parameters, for example: linewidth, linestyle, marker, color.
+
+        An important keyword argument is tool. At the moment the
+        following values are supported:
+        - plot_spectrum  (default for workspaces)
+        - plot_bin
+        - plot_md
+
+        Please see the documentation of this module (use help()) for more details.
+
+    """
+    nargs = len(args)
+    if nargs < 1:
+        raise ValueError("You did not pass any argument. You must provide data to plot.")
+
+    # TOTHINK: should there be an exception if it's plot_md (tool='plot_md')
+    (is_it, plots_seq) = __is_multiplot_command(*args, **kwargs)
+    if is_it:
+        return __process_multiplot_command(plots_seq, **kwargs)
+    elif len(args) > 3:
+        raise ValueError("Could not interpret the arguments passed. You passed more than 3 positional arguments but this does not seem to be a correct multi-plot command. Please check your command and make sure that the workspaces given are correct.")
+
+    # normally guess; exception if e.g. a parameter tool='plot_bin' is given
+    try:
+        tool_val = kwargs['tool']
+        del kwargs['tool']
+        return __plot_with_tool(tool_val, *args, **kwargs)
+    except KeyError:
+        return __plot_with_best_guess(*args, **kwargs)
+
+
+#=============================================================================
+# Functions, for pyplot / old matlab style manipulation of figures
+#=============================================================================
+
+def xlim(xmin, xmax):
+    """
+    Set the boundaries of the x axis
+
+    @param xmin :: minimum value
+    @param xmax :: maximum value
+    """
+    l = __last_fig()._graph.activeLayer()
+    l.setAxisScale(2, xmin, xmax)
+
+def ylim(ymin, ymax):
+    """
+    Set the boundaries of the y axis
+
+    @param ymin :: minimum value
+    @param ymax :: maximum value
+    """
+    l = __last_fig()._graph.activeLayer()
+    l.setAxisScale(0, ymin, ymax)
+
+def xlabel(lbl):
+    """
+    Set the label or title of the x axis
+
+    @param lbl :: x axis lbl
+    """
+    l = __last_fig()._graph.activeLayer()
+    l.setXTitle(lbl)
+
+def ylabel(lbl):
+    """
+    Set the label or title of the y axis
+
+    @param lbl :: y axis lbl
+    """
+    l = __last_fig()._graph.activeLayer()
+    l.setYTitle(lbl)
+
+def title(title):
+    """
+    Set title of active plot
+
+    @param title :: title string
+    """
+    l = __last_fig()._graph.activeLayer()
+    l.setTitle(title)
+
+def axis(lims):
+    """
+    Set the boundaries of the x and y axes
+
+    @param lims :: list or vector specifying min x, max x, min y, max y
+    """
+    l = __last_fig()._graph.activeLayer()
+    if 4 != len(lims):
+        raise ValueError("Error: 4 real values are required for the x and y axes limits")
+    l.setScale(*lims)
+
+def grid(opt='on'):
+    """
+    Set title of active plot
+
+    @param title :: title string
+    """
+    l = __last_fig()._graph.activeLayer()
+    if None == opt or 'on' == opt:
+        l.showGrid()
+    elif 'off' == opt:
+        # TODO is there support for a 'hideGrid' in qti? Apparently not.
+        print "Sorry, hiding/disabling grids is currenlty not supported"
+
+def figure(num=None):
+    """
+    Return Figure object for a new figure or an existing one (if there is any
+    with the number passed as parameter).
+
+    @param num :: figure number (optional). If empty, a new figure is created.
+    """
+    if not num:
+        return __empty_fig()
+    else:
+        if num < 0:
+            raise ValueError("The figure number cannot be negative")
+
+    missing = None
+    fig = Figure.__figures.get(num, None)
+    if None == fig:
+        return __empty_fig()
+    else:
+        return fig
+
+def savefig(name):
+    """
+    Save current plot into a file. The format is guessed from the file extension (.eps, .png, .jpg, etc.)
+
+    @param name :: file name
+    """
+    if not name:
+        raise ValueError("Error: you need to specify a non-empty file name")
+    l = __last_fig()._graph.activeLayer()
+    l.saveImage(name);
diff --git a/Code/Mantid/MantidPlot/pymantidplot/proxies.py b/Code/Mantid/MantidPlot/pymantidplot/proxies.py
new file mode 100644
index 00000000000..4a2e66b47f5
--- /dev/null
+++ b/Code/Mantid/MantidPlot/pymantidplot/proxies.py
@@ -0,0 +1,907 @@
+"""
+Module containing classes that act as proxies to the various MantidPlot gui objects that are
+accessible from python. They listen for the QObject 'destroyed' signal and set the wrapped
+reference to None, thus ensuring that further attempts at access do not cause a crash.
+"""
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import Qt, pyqtSlot
+import __builtin__
+import mantid
+
+#-----------------------------------------------------------------------------
+#--------------------------- MultiThreaded Access ----------------------------
+#-----------------------------------------------------------------------------
+
+class CrossThreadCall(QtCore.QObject):
+    """
+    Defines a dispatch call that marshals
+    function calls between threads
+    """
+    __callable = None
+    __args = []
+    __kwargs = {}
+    __func_return = None
+    __exception = None
+
+    def __init__(self, callable):
+        """ Construct the object
+        """
+        QtCore.QObject.__init__(self)
+        self.moveToThread(QtGui.qApp.thread())
+        self.__callable = callable
+        self.__call__.__func__.__doc__ = callable.__doc__
+
+    def dispatch(self, *args, **kwargs):
+        """Dispatches a call to callable with
+        the given arguments using QMetaObject.invokeMethod
+        to ensure the call happens in the object's thread
+        """
+        self.__args = args
+        self.__kwargs = kwargs
+        self.__func_return = None
+        self.__exception = None
+        return self._do_dispatch()
+
+    def __call__(self, *args, **kwargs):
+        """
+        Calls the dispatch method
+        """
+        return self.dispatch(*args, **kwargs)
+
+    @pyqtSlot()
+    def _do_dispatch(self):
+        """Perform a call to a GUI function across a
+        thread and return the result
+        """
+        if QtCore.QThread.currentThread() != QtGui.qApp.thread():
+            QtCore.QMetaObject.invokeMethod(self, "_do_dispatch", Qt.BlockingQueuedConnection)
+        else:
+            try:
+                self.__func_return = self.__callable(*self.__args, **self.__kwargs)
+            except Exception, exc:
+                self.__exception = exc
+
+        if self.__exception is not None:
+            raise self.__exception # Ensures this happens in the right thread
+        return self.__func_return
+
+    def _get_argtype(self, argument):
+        """
+            Returns the argument type that will be passed to
+            the QMetaObject.invokeMethod call.
+
+            Most types pass okay, but enums don't so they have
+            to be coerced to ints. An enum is currently detected
+            as a type that is not a bool and inherits from __builtin__.int
+        """
+        argtype = type(argument)
+        return argtype
+        if isinstance(argument, __builtin__.int) and argtype != bool:
+            argtype = int
+        return argtype
+
+def threadsafe_call(callable, *args, **kwargs):
+    """
+        Calls the given function with the given arguments
+        by passing it through the CrossThreadCall class. This
+        ensures that the calls to the GUI functions
+        happen on the correct thread.
+    """
+    caller = CrossThreadCall(callable)
+    return caller.dispatch(*args, **kwargs)
+
+def new_proxy(classType, callable, *args, **kwargs):
+    """
+    Calls the callable object with the given args and kwargs dealing
+    with possible thread-safety issues.
+    If the returned value is not None it is wrapped in a new proxy of type classType
+
+        @param classType :: A new proxy class for the return type
+        @param callable :: A python callable object, i.e. a function/method
+        @param \*args :: The positional arguments passed on as given
+        @param \*kwargs :: The keyword arguments passed on as given
+    """
+    obj = threadsafe_call(callable, *args, **kwargs)
+    if obj is None:
+        return obj
+    return classType(obj)
+
+#-----------------------------------------------------------------------------
+#--------------------------- Proxy Objects -----------------------------------
+#-----------------------------------------------------------------------------
+
+class QtProxyObject(QtCore.QObject):
+    """Generic Proxy object for wrapping Qt C++ QObjects.
+    This holds the QObject internally and passes methods to it.
+    When the underlying object is deleted, the reference is set
+    to None to avoid segfaults.
+    """
+    def __init__(self, toproxy):
+        QtCore.QObject.__init__(self)
+        self.__obj = toproxy
+        # Connect to track the destroyed
+        if self.__obj is not None:
+            self.connect(self.__obj, QtCore.SIGNAL("destroyed()"),
+                         self._kill_object, Qt.DirectConnection)
+
+    def __del__(self):
+        """
+        Disconnect the signal
+        """
+        self._disconnect_from_destroyed()
+
+    def close(self):
+        """
+        Reroute a method call to the the stored object
+        """
+        self._disconnect_from_destroyed()
+        if hasattr(self.__obj, 'closeDependants'):
+            threadsafe_call(self.__obj.closeDependants)
+        if hasattr(self.__obj, 'close'):
+            threadsafe_call(self.__obj.close)
+        self._kill_object()
+
+    def inherits(self, className):
+        """
+        Reroute a method call to the stored object
+        """
+        return threadsafe_call(self.__obj.inherits, className)
+
+    def _disconnect_from_destroyed(self):
+        """
+        Disconnects from the wrapped object's destroyed signal
+        """
+        if self.__obj is not None:
+            self.disconnect(self.__obj, QtCore.SIGNAL("destroyed()"),
+                            self._kill_object)
+
+    def __getattr__(self, attr):
+        """
+        Reroute a method call to the the stored object via
+        the threadsafe call mechanism. Essentially this guarantees
+        that when the method is called it wil be on the GUI thread
+        """
+        callable = getattr(self._getHeldObject(), attr)
+        return CrossThreadCall(callable)
+
+    def __dir__(self):
+        return dir(self._getHeldObject())
+
+    def __str__(self):
+        """
+        Return a string representation of the proxied object
+        """
+        return str(self._getHeldObject())
+
+    def __repr__(self):
+        """
+        Return a string representation of the proxied object
+        """
+        return `self._getHeldObject()`
+
+    def _getHeldObject(self):
+        """
+        Returns a reference to the held object
+        """
+        return self.__obj
+
+    def _kill_object(self):
+        """
+        Release the stored instance
+        """
+        self.__obj = None
+
+    def _swap(self, obj):
+        """
+        Swap an object so that the proxy now refers to this object
+        """
+        self.__obj = obj
+
+#-----------------------------------------------------------------------------
+class MDIWindow(QtProxyObject):
+    """Proxy for the _qti.MDIWindow object.
+    Also used for subclasses that do not need any methods intercepted (e.g. Table, Note, Matrix)
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def folder(self):
+        return new_proxy(Folder, self._getHeldObject().folder)
+
+#-----------------------------------------------------------------------------
+class Graph(MDIWindow):
+    """Proxy for the _qti.Graph object.
+    """
+    # When checking the SIP interface, remember the following name mappings (PyName):
+    # C++ 'Multilayer' class => Python 'Graph' class
+    # C++ 'Graph' class => Python 'Layer' class
+
+    def __init__(self, toproxy):
+        MDIWindow.__init__(self,toproxy)
+
+    def activeLayer(self):
+        """Get a handle to the presently active layer """
+        return new_proxy(Layer, self._getHeldObject().activeLayer)
+
+    def setActiveLayer(self, layer):
+        """Set the active layer to that specified.
+
+        Args:
+            layer: A reference to the Layer to make the active one. Must belong to this Graph.
+        """
+        threadsafe_call(self._getHeldObject().setActiveLayer, layer._getHeldObject())
+
+    def layer(self, num):
+        """ Get a handle to a specified layer
+
+        Args:
+            num: The index of the required layer
+        """
+        return new_proxy(Layer, self._getHeldObject().layer, num)
+
+    def addLayer(self, x=0, y=0, width=None, height=None):
+        """Add a layer to the graph.
+
+        Args:
+            x: The coordinate in pixels (from the graph's left) of the top-left of the new layer (default: 0).
+            y: The coordinate in pixels (from the graph's top) of the top-left of the new layer (default: 0).
+            width: The width of the new layer (default value if not specified).
+            height: The height of the new layer (default value if not specified).
+
+        Returns:
+            A handle to the newly created layer.
+        """
+        # Turn the optional arguments into the magic numbers that the C++ expects
+        if width is None:
+            width=0
+        if height is None:
+            height=0
+        return new_proxy(Layer, self._getHeldObject().addLayer, x,y,width,height)
+
+    def insertCurve(self, graph, index):
+        """Add a curve from another graph to this one.
+
+        Args:
+            graph: A reference to the graph from which the curve is coming (does nothing if this argument is the present Graph).
+            index: The index of the curve to add (counts from zero).
+        """
+        threadsafe_call(self._getHeldObject().insertCurve, graph._getHeldObject(), index)
+
+
+#-----------------------------------------------------------------------------
+class Layer(QtProxyObject):
+    """Proxy for the _qti.Layer object.
+    """
+    # These methods are used for the new matplotlib-like CLI
+    # These ones are provided by the C++ class Graph, which in the SIP declarations is renamed as Layer
+    # The only purpose of listing them here is that these will be returned by this class' __dir()__, and
+    #    shown interactively, while the ones not listed and/or overloaded here may not be shown in ipython, etc.
+    additional_methods = ['logLogAxes', 'logXLinY', 'logXLinY',
+                          'removeLegend', 'saveImage', 'setAxisScale', 'setCurveLineColor', 'setCurveLineStyle',
+                          'setCurveLineWidth', 'setCurveSymbol', 'setScale', 'setTitle', 'setXTitle', 'setYTitle']
+
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def insertCurve(self, *args):
+        """Add a curve from a workspace, table or another Layer to the plot
+
+        Args:
+            The first argument should be a reference to a workspace, table or layer, or a workspace name.
+            Subsequent arguments vary according to the type of the first.
+
+        Returns:
+            A boolean indicating success or failure.
+        """
+        if isinstance(args[0],str):
+            return threadsafe_call(self._getHeldObject().insertCurve, *args)
+        elif hasattr(args[0], 'getName'):
+            return threadsafe_call(self._getHeldObject().insertCurve, args[0].getName(),*args[1:])
+        else:
+            return threadsafe_call(self._getHeldObject().insertCurve, args[0]._getHeldObject(),*args[1:])
+
+    def addCurves(self, table, columns, style=0, lineWidth=1, symbolSize=3, startRow=0, endRow=-1):
+        """Add curves based on table columns to the plot.
+
+        Args:
+            table: A reference to the table containing the data to plot.
+            columns: A tuple of column indices.
+            style: The curve style (default: Line).
+            lineWidth: The width of the curve line (default: 1).
+            symbolSize: The curve's symbol size (default: 3).
+            startRow: The first row to include in the curve's data (default: the first one)
+            endRow: The last row to include in the curve's data (default: the last one)
+
+        Returns:
+            A boolean indicating success or failure.
+        """
+        return threadsafe_call(self._getHeldObject().addCurves, table._getHeldObject(),columns,style,lineWidth,symbolSize,startRow,endRow)
+
+    def addCurve(self, table, columnName, style=0, lineWidth=1, symbolSize=3, startRow=0, endRow=-1):
+        """Add a curve based on a table column to the plot.
+
+        Args:
+            table: A reference to the table containing the data to plot.
+            columns: The name of the column to plot.
+            style: The curve style (default: Line).
+            lineWidth: The width of the curve line (default: 1).
+            symbolSize: The curve's symbol size (default: 3).
+            startRow: The first row to include in the curve's data (default: the first one)
+            endRow: The last row to include in the curve's data (default: the last one)
+
+        Returns:
+            A boolean indicating success or failure.
+        """
+        return threadsafe_call(self._getHeldObject().addCurve, table._getHeldObject(),columnName,style,lineWidth,symbolSize,startRow,endRow)
+
+    def addErrorBars(self, yColName, errTable, errColName, type=1, width=1, cap=8, color=Qt.black, through=False, minus=True, plus=True):
+        """Add error bars to a plot that was created from a table column.
+
+        Args:
+            yColName: The name of the column pertaining to the curve's data values.
+            errTable: A reference to the table holding the error values.
+            errColName: The name of the column containing the error values.
+            type: The orientation of the error bars - horizontal (0) or vertical (1, the default).
+            width: The line width of the error bars (default: 1).
+            cap: The length of the cap on the error bars (default: 8).
+            color: The color of error bars (default: black).
+            through: Whether the error bars are drawn through the symbol (default: no).
+            minus: Whether these errors should be shown as negative errors (default: yes).
+            plus: Whether these errors should be shown as positive errors (default: yes).
+        """
+        threadsafe_call(self._getHeldObject().addErrorBars, yColName,errTable._getHeldObject(),errColName,type,width,cap,color,through,minus,plus)
+
+    def errorBarSettings(self, curveIndex, errorBarIndex=0):
+        """Get a handle to the error bar settings for a specified curve.
+
+        Args:
+            curveIndex: The curve to get the settings for
+            errorBarIndex: A curve can hold more than one set of error bars. Specify which one (default: the first).
+                           Note that a curve plotted from a workspace can have only one set of error bars (and hence settings).
+
+        Returns: A handle to the error bar settings object.
+        """
+        return new_proxy(QtProxyObject, self._getHeldObject().errorBarSettings, curveIndex,errorBarIndex)
+
+    def addHistogram(self, matrix):
+        """Add a matrix histogram  to the graph"""
+        threadsafe_call(self._getHeldObject().addHistogram, matrix._getHeldObject())
+
+    def newLegend(self, text):
+        """Create a new legend.
+
+        Args:
+            text: The text of the legend.
+
+        Returns:
+            A handle to the newly created legend widget.
+        """
+        return new_proxy(QtProxyObject, self._getHeldObject().newLegend, text)
+
+    def legend(self):
+        """Get a handle to the layer's legend widget."""
+        return new_proxy(QtProxyObject, self._getHeldObject().legend)
+
+    def grid(self):
+        """Get a handle to the grid object for this layer."""
+        return new_proxy(QtProxyObject, self._getHeldObject().grid)
+
+    def spectrogram(self):
+        """If the layer contains a spectrogram, get a handle to the spectrogram object."""
+        return new_proxy(QtProxyObject, self._getHeldObject().spectrogram)
+
+
+    def __dir__(self):
+        """Returns the list of attributes of this object."""
+        # The first part (explicitly defined ones) are here for the traditional Mantid CLI,
+        # the additional ones have been added for the matplotlib-like CLI (without explicit
+        # declaration/documentation here in the proxies layer.
+        return ['insertCurve', 'addCurves', 'addCurve', 'addErrorBars', 'errorBarSettings', 'addHistogram',
+                'newLegend', 'legend', 'grid', 'spectrogram' ] + self.additional_methods
+
+
+#-----------------------------------------------------------------------------
+class Graph3D(QtProxyObject):
+    """Proxy for the _qti.Graph3D object.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def setData(self, table, colName, type=0):
+        """Set a table column to be the data source for this plot.
+
+        Args:
+            table: A reference to the table.
+            colName: The name of the column to set as the data source.
+            type: The plot type.
+        """
+        threadsafe_call(self._getHeldObject().setData, table._getHeldObject(),colName,type)
+
+    def setMatrix(self, matrix):
+        """Set a matrix (N.B. not a MantidMatrix) to be the data source for this plot.
+
+        Args:
+            matrix: A reference to the matrix.
+        """
+        threadsafe_call(self._getHeldObject().setMatrix, matrix._getHeldObject())
+
+#-----------------------------------------------------------------------------
+class Spectrogram(QtProxyObject):
+    """Proxy for the _qti.Spectrogram object.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def matrix(self):
+        """Get a handle to the data source."""
+        return new_proxy(QtProxyObject, self._getHeldObject().matrix)
+
+#-----------------------------------------------------------------------------
+class Folder(QtProxyObject):
+    """Proxy for the _qti.Folder object.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def windows(self):
+        """Get a list of the windows in this folder"""
+        f = self._getHeldObject().windows()
+        ret = []
+        for item in f:
+            ret.append(MDIWindow(item))
+        return ret
+
+    def folders(self):
+        """Get a list of the subfolders of this folder"""
+        f = self._getHeldObject().folders()
+        ret = []
+        for item in f:
+            ret.append(Folder(item))
+        return ret
+
+    def folder(self, name, caseSensitive=True, partialMatch=False):
+        """Get a handle to a named subfolder.
+
+        Args:
+            name: The name of the subfolder.
+            caseSensitive: Whether to search case-sensitively or not (default: yes).
+            partialMatch: Whether to return a partial match (default: no).
+
+        Returns:
+            A handle to the requested folder, or None if no match found.
+        """
+        return new_proxy(Folder, self._getHeldObject().folder, name,caseSensitive,partialMatch)
+
+    def findWindow(self, name, searchOnName=True, searchOnLabel=True, caseSensitive=False, partialMatch=True):
+        """Get a handle to the first window matching the search criteria.
+
+        Args:
+            name: The name of the window.
+            searchOnName: Whether to search the window names (takes precedence over searchOnLabel).
+            searchOnLabel: Whether to search the window labels.
+            caseSensitive: Whether to search case-sensitively or not (default: no).
+            partialMatch: Whether to return a partial match (default: yes).
+
+        Returns:
+            A handle to the requested window, or None if no match found.
+        """
+        return new_proxy(MDIWindow, self._getHeldObject().findWindow, name,searchOnName,searchOnLabel,caseSensitive,partialMatch)
+
+    def window(self, name, cls='MdiSubWindow', recursive=False):
+        """Get a handle to a named window of a particular type.
+
+        Args:
+            name: The name of the window.
+            cls: Search only for windows of type inheriting from this class (N.B. This is the C++ class name).
+            recursive: If True, do a depth-first recursive search (default: False).
+
+        Returns:
+            A handle to the window, or None if no match found.
+        """
+        return new_proxy(MDIWindow, self._getHeldObject().window, name,cls,recursive)
+
+    def table(self, name, recursive=False):
+        """Get a handle to the table with the given name.
+
+        Args:
+            name: The name of the table to search for.
+            recursive: If True, do a depth-first recursive search (default: False).
+        """
+        return new_proxy(MDIWindow, self._getHeldObject().table, name,recursive)
+
+    def matrix(self, name, recursive=False):
+        """Get a handle to the matrix with the given name.
+
+        Args:
+            name: The name of the matrix to search for.
+            recursive: If True, do a depth-first recursive search (default: False).
+        """
+        return  new_proxy(MDIWindow, self._getHeldObject().matrix, name,recursive)
+
+    def graph(self, name, recursive=False):
+        """Get a handle to the graph with the given name.
+
+        Args:
+            name: The name of the graph to search for.
+            recursive: If True, do a depth-first recursive search (default: False).
+        """
+        return new_proxy(Graph, self._getHeldObject().graph, name,recursive)
+
+    def rootFolder(self):
+        """Get the folder at the root of the hierarchy"""
+        return new_proxy(Folder, self._getHeldObject().rootFolder)
+
+#-----------------------------------------------------------------------------
+class MantidMatrix(MDIWindow):
+    """Proxy for the _qti.MantidMatrix object.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def plotGraph3D(self, style=3):
+        """Create a 3D plot of the workspace data.
+
+        Args:
+            style: The qwt3d plotstyle of the generated graph (default: filled mesh)
+
+        Returns:
+            A handle to the newly created graph (a Graph3D object)
+        """
+        return new_proxy(Graph3D, self._getHeldObject().plotGraph3D, style)
+
+    def plotGraph2D(self, type=16):
+        """Create a spectrogram from the workspace data.
+
+        Args:
+            type: The style of the plot (default: ColorMap)
+
+        Returns:
+            A handle the newly created graph (a Graph object)
+        """
+        return new_proxy(Graph, self._getHeldObject().plotGraph2D, type)
+
+#-----------------------------------------------------------------------------
+class InstrumentWindow(MDIWindow):
+    """Proxy for the instrument window
+    """
+
+    def __init__(self, toproxy):
+        """Creates a proxy object around an instrument window
+
+        Args:
+            toproxy: The raw C object to proxy
+        """
+        QtProxyObject.__init__(self, toproxy)
+
+    def getTab(self, name_or_tab):
+        """Retrieve a handle to the given tab
+
+        Args:
+            name_or_index: A string containing the title or tab type
+
+        Returns:
+            A handle to a tab widget
+        """
+        handle = new_proxy(QtProxyObject, self._getHeldObject().getTab, name_or_tab)
+        if handle is None:
+            raise ValueError("Invalid tab title '%s'" % str(name_or_tab))
+        return handle
+
+    # ----- Deprecated functions -----
+    def changeColormap(self, filename=None):
+        import warnings
+        warnings.warn("InstrumentWindow.changeColormap has been deprecated. Use the render tab method instead.")
+        callable = QtProxyObject.__getattr__(self, "changeColormap")
+        if filename is None:
+            callable()
+        else:
+            callable(filename)
+
+    def setColorMapMinValue(self, value):
+        import warnings
+        warnings.warn("InstrumentWindow.setColorMapMinValue has been deprecated. Use the render tab setMinValue method instead.")
+        QtProxyObject.__getattr__(self, "setColorMapMinValue")(value)
+
+    def setColorMapMaxValue(self, value):
+        import warnings
+        warnings.warn("InstrumentWindow.setColorMapMaxValue has been deprecated. Use the render tab setMaxValue method instead.")
+        QtProxyObject.__getattr__(self, "setColorMapMaxValue")(value)
+
+    def setColorMapRange(self, minvalue, maxvalue):
+        import warnings
+        warnings.warn("InstrumentWindow.setColorMapRange has been deprecated. Use the render tab setRange method instead.")
+        QtProxyObject.__getattr__(self, "setColorMapRange")(minvalue,maxvalue)
+
+    def setScaleType(self, scale_type):
+        import warnings
+        warnings.warn("InstrumentWindow.setScaleType has been deprecated. Use the render tab setScaleType method instead.")
+        QtProxyObject.__getattr__(self, "setScaleType")(scale_type)
+
+    def setViewType(self, view_type):
+        import warnings
+        warnings.warn("InstrumentWindow.setViewType has been deprecated. Use the render tab setSurfaceType method instead.")
+        QtProxyObject.__getattr__(self, "setViewType")(view_type)
+
+    def selectComponent(self, name):
+        import warnings
+        warnings.warn("InstrumentWindow.selectComponent has been deprecated. Use the tree tab selectComponentByName method instead.")
+        QtProxyObject.__getattr__(self, "selectComponent")(name)
+
+#-----------------------------------------------------------------------------
+class SliceViewerWindowProxy(QtProxyObject):
+    """Proxy for a C++ SliceViewerWindow object.
+
+    It will pass-through method calls that can be applied to the
+    SliceViewer widget contained within.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self, toproxy)
+
+    def __getattr__(self, attr):
+        """
+        Reroute a method call to the the stored object
+        """
+        if self._getHeldObject() is None:
+            raise Exception("Error! The SliceViewerWindow has been deleted.")
+
+        # Pass-through to the contained SliceViewer widget.
+        sv = self.getSlicer()
+        # But only those attributes that are methods on the SliceViewer
+        if attr in SliceViewerProxy.slicer_methods:
+            return getattr(sv, attr)
+        else:
+            # Otherwise, pass through to the stored object
+            return getattr(self._getHeldObject(), attr)
+
+    def __str__(self):
+        """
+        Return a string representation of the proxied object
+        """
+        if self._getHeldObject() is None:
+            return "None"
+        else:
+            return 'SliceViewerWindow(workspace="%s")' % self._getHeldObject().getSlicer().getWorkspaceName()
+
+    def __repr__(self):
+        """
+        Return a string representation of the proxied object
+        """
+        return `self._getHeldObject()`
+
+    def __dir__(self):
+        """
+        Returns the list of attributes for this object.
+        Might allow tab-completion to work under ipython
+        """
+        return SliceViewerProxy.slicer_methods + ['showLine']
+
+    def getLiner(self):
+        """
+        Returns the LineViewer widget that is part of this
+        SliceViewerWindow
+        """
+        return LineViewerProxy(self._getHeldObject().getLiner())
+
+    def getSlicer(self):
+        """
+        Returns the SliceViewer widget that is part of this
+        SliceViewerWindow
+        """
+        return SliceViewerProxy(self._getHeldObject().getSlicer())
+
+    def showLine(self, start, end, width=None, planar_width=0.1, thicknesses=None,
+                 num_bins=100):
+        """Opens the LineViewer and define a 1D line along which to integrate.
+
+        The line is created in the same XY dimensions and at the same slice
+        point as is currently shown in the SliceViewer.
+
+        Args:
+            start :: (X,Y) coordinates of the start point in the XY dimensions
+                of the current slice.
+            end :: (X,Y) coordinates of the end point in the XY dimensions
+                of the current slice.
+            width :: if specified, sets all the widths (planar and other
+                dimensions) to this integration width.
+            planar_width :: sets the XY-planar (perpendicular to the line)
+                integration width. Default 0.1.
+            thicknesses :: list with one thickness value for each dimension in the
+                workspace (including the XY dimensions, which are ignored).
+                e.g. [0,1,2,3] in a XYZT workspace.
+            num_bins :: number of bins by which to divide the line.
+                Default 100.
+
+        Returns:
+            The LineViewer object of the SliceViewerWindow. There are methods
+            available to modify the line drawn.
+        """
+        # First show the lineviewer
+        self.getSlicer().toggleLineMode(True)
+        liner = self.getLiner()
+
+        # Start and end point
+        liner.setStartXY(start[0], start[1])
+        liner.setEndXY(end[0], end[1])
+
+        # Set the width.
+        if not width is None:
+            liner.setThickness(width)
+        else:
+            liner.setPlanarWidth(planar_width)
+            if not thicknesses is None:
+                for d in xrange(len(thicknesses)):
+                    liner.setThickness(d, thicknesses[i])
+        # Bins
+        liner.setNumBins(num_bins)
+        liner.apply()
+
+        # Return the proxy to the LineViewer widget
+        return liner
+
+#-----------------------------------------------------------------------------
+def getWorkspaceNames(source):
+    """Takes a "source", which could be a WorkspaceGroup, or a list
+    of workspaces, or a list of names, and converts
+    it to a list of workspace names.
+
+    Args:
+        source :: input list or workspace group
+
+    Returns:
+        list of workspace names
+    """
+    ws_names = []
+    if isinstance(source, list) or isinstance(source,tuple):
+        for w in source:
+            names = getWorkspaceNames(w)
+            ws_names += names
+    elif hasattr(source, 'getName'):
+        if hasattr(source, '_getHeldObject'):
+            wspace = source._getHeldObject()
+        else:
+            wspace = source
+        if wspace == None:
+            return []
+        if hasattr(wspace, 'getNames'):
+            grp_names = wspace.getNames()
+            for n in grp_names:
+                if n != wspace.getName():
+                    ws_names.append(n)
+        else:
+            ws_names.append(wspace.getName())
+    elif isinstance(source,str):
+        w = None
+        try:
+            # for non-existent names this raises a KeyError
+            w = mantid.AnalysisDataService.Instance()[source]
+        except Exception, exc:
+            raise ValueError("Workspace '%s' not found!"%source)
+
+        if w != None:
+            names = getWorkspaceNames(w)
+            for n in names:
+                ws_names.append(n)
+    else:
+        raise TypeError('Incorrect type passed as workspace argument "' + str(source) + '"')
+    return ws_names
+
+#-----------------------------------------------------------------------------
+class ProxyCompositePeaksPresenter(QtProxyObject):
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def getPeaksPresenter(self, source):
+        to_present = None
+        if isinstance(source, str):
+            to_present = source
+        elif isinstance(source, mantid.api.Workspace):
+            to_present = source.getName()
+        else:
+            raise ValueError("getPeaksPresenter expects a Workspace name or a Workspace object.")
+        if not mantid.api.mtd.doesExist(to_present):
+                raise ValueError("%s does not exist in the workspace list" % to_present)
+
+        return new_proxy(QtProxyObject, self._getHeldObject().getPeaksPresenter, to_present)
+
+#-----------------------------------------------------------------------------
+class SliceViewerProxy(QtProxyObject):
+    """Proxy for a C++ SliceViewer widget.
+    """
+    # These are the exposed python method names
+    slicer_methods = ["setWorkspace", "getWorkspaceName", "showControls", "openFromXML", "getImage", "saveImage", "copyImageToClipboard", "setFastRender", "getFastRender", "toggleLineMode", "setXYDim", "setXYDim", "getDimX", "getDimY", "setSlicePoint", "setSlicePoint", "getSlicePoint", "getSlicePoint", "setXYLimits", "getXLimits", "getYLimits", "zoomBy", "setXYCenter", "resetZoom", "loadColorMap", "setColorScale", "setColorScaleMin", "setColorScaleMax", "setColorScaleLog", "getColorScaleMin", "getColorScaleMax", "getColorScaleLog", "setColorScaleAutoFull", "setColorScaleAutoSlice", "setColorMapBackground", "setTransparentZeros", "setNormalization", "getNormalization", "setRebinThickness", "setRebinNumBins", "setRebinMode", "setPeaksWorkspaces", "refreshRebin"]
+
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self, toproxy)
+
+    def __dir__(self):
+        """Returns the list of attributes for this object.   """
+        return self.slicer_methods()
+
+    def setPeaksWorkspaces(self, source):
+        workspace_names = getWorkspaceNames(source)
+        if len(workspace_names) == 0:
+            raise ValueError("No workspace names given to setPeaksWorkspaces")
+        for name in workspace_names:
+            if not mantid.api.mtd.doesExist(name):
+                raise ValueError("%s does not exist in the workspace list" % name)
+        if not isinstance(mantid.api.mtd[name], mantid.api.IPeaksWorkspace):
+            raise ValueError("%s is not an IPeaksWorkspace" % name)
+
+        return new_proxy(ProxyCompositePeaksPresenter, self._getHeldObject().setPeaksWorkspaces, workspace_names)
+
+
+#-----------------------------------------------------------------------------
+class LineViewerProxy(QtProxyObject):
+    """Proxy for a C++ LineViewer widget.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self, toproxy)
+
+    def __dir__(self):
+        """Returns the list of attributes for this object.   """
+        return ["apply", "showPreview", "showFull", "setStartXY", "setEndXY", "setThickness", "setThickness",
+                "setThickness", "setPlanarWidth", "getPlanarWidth", "setNumBins", "setFixedBinWidthMode", "getFixedBinWidth",
+                "getFixedBinWidthMode", "getNumBins", "getBinWidth", "setPlotAxis", "getPlotAxis"]
+
+
+#-----------------------------------------------------------------------------
+class FitBrowserProxy(QtProxyObject):
+    """
+        Proxy for the FitPropertyBrowser object.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+
+#-----------------------------------------------------------------------------
+class TiledWindowProxy(QtProxyObject):
+    """
+        Proxy for the TiledWindow object.
+    """
+    def __init__(self, toproxy):
+        QtProxyObject.__init__(self,toproxy)
+
+    def addWidget(self, tile, row, col):
+        """
+        Add a new sub-window at a given position in the layout.
+        The layout will re-shape itself if necessary to fit in the new tile.
+
+        Args:
+
+            tile :: An MdiSubWindow to add.
+            row :: A row index at which to place the new tile.
+            col :: A column index at which to place the new tile.
+        """
+        threadsafe_call(self._getHeldObject().addWidget, tile._getHeldObject(), row, col)
+
+    def insertWidget(self, tile, row, col):
+        """
+        Insert a new sub-window at a given position in the layout.
+        The widgets to the right and below the inserted tile will be shifted
+        towards the bottom of the window. If necessary a new row will be appended.
+        The number of columns doesn't change.
+
+        Args:
+
+            tile :: An MdiSubWindow to insert.
+            row :: A row index at which to place the new tile.
+            col :: A column index at which to place the new tile.
+        """
+        threadsafe_call(self._getHeldObject().insertWidget, tile._getHeldObject(), row, col)
+
+    def getWidget(self, row, col):
+        """
+        Get a sub-window at a location in this TiledWindow.
+
+        Args:
+            row :: A row of a sub-window.
+            col :: A column of a sub-window.
+        """
+        return MDIWindow( threadsafe_call(self._getHeldObject().getWidget, row, col) )
+
+    def clear(self):
+        """
+        Clear the content this TiledWindow.
+        """
+        threadsafe_call(self._getHeldObject().clear)
-- 
GitLab