From a722a49be1207255a97a23cdf94b80a1396f8e22 Mon Sep 17 00:00:00 2001
From: Russell Taylor <taylorrj@ornl.gov>
Date: Wed, 4 Jan 2012 14:02:58 -0500
Subject: [PATCH] Complete wrapping of MantidPlot python objects with proxies.
 Re #1037.

In general, users should only ever get references to proxy objects,
which are automatically set to None when the referenced window is
closed.
---
 .../MantidPlot/mantidplotpy/mantidplot.py     | 126 +++++++++++++++--
 .../Mantid/MantidPlot/mantidplotpy/proxies.py | 130 +++++++++++++++++-
 2 files changed, 239 insertions(+), 17 deletions(-)

diff --git a/Code/Mantid/MantidPlot/mantidplotpy/mantidplot.py b/Code/Mantid/MantidPlot/mantidplotpy/mantidplot.py
index a6a8254dd5c..2a26d839ab7 100644
--- a/Code/Mantid/MantidPlot/mantidplotpy/mantidplot.py
+++ b/Code/Mantid/MantidPlot/mantidplotpy/mantidplot.py
@@ -15,6 +15,10 @@ import proxies
 from PyQt4 import QtCore, QtGui
 from PyQt4.QtCore import Qt
 
+# Import into the global namespace qti classes that:
+#   (a) don't need a proxy & (b) can be constructed from python
+from qti import PlotSymbol, ImageSymbol, ArrowMarker, ImageMarker
+
 #-------------------------- Wrapped MantidPlot functions -----------------
 
 # Overload for consistency with qtiplot table(..) & matrix(..) commands
@@ -35,7 +39,7 @@ def table(name):
     Returns:
         A handle to the table.
     """
-    return proxies.QtProxyObject(qti.app.table(name))
+    return proxies.MDIWindow(qti.app.table(name))
 
 def newTable(name=None,rows=30,columns=2):
     """Create a table.
@@ -49,9 +53,9 @@ def newTable(name=None,rows=30,columns=2):
         A handle to the created table.
     """
     if name is None:
-        return proxies.QtProxyObject(qti.app.newTable())
+        return proxies.MDIWindow(qti.app.newTable())
     else:
-        return proxies.QtProxyObject(qti.app.newTable(name,rows,columns))
+        return proxies.MDIWindow(qti.app.newTable(name,rows,columns))
 
 def matrix(name):
     """Get a handle on a matrix.
@@ -62,7 +66,7 @@ def matrix(name):
     Returns:
         A handle to the matrix.
     """
-    return proxies.QtProxyObject(qti.app.matrix(name))
+    return 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').
@@ -76,9 +80,9 @@ def newMatrix(name=None,rows=32,columns=32):
         A handle to the created matrix.
     """
     if name is None:
-        return proxies.QtProxyObject(qti.app.newMatrix())
+        return proxies.MDIWindow(qti.app.newMatrix())
     else:
-        return proxies.QtProxyObject(qti.app.newMatrix(name,rows,columns))
+        return proxies.MDIWindow(qti.app.newMatrix(name,rows,columns))
 
 def graph(name):
     """Get a handle on a graph widget.
@@ -117,7 +121,7 @@ def note(name):
     Returns:
         A handle to the note.
     """
-    return proxies.QtProxyObject(qti.app.note(name))
+    return proxies.MDIWindow(qti.app.note(name))
 
 def newNote(name=None):
     """Create a note.
@@ -129,9 +133,9 @@ def newNote(name=None):
         A handle to the created note.
     """
     if name is None:
-        return proxies.QtProxyObject(qti.app.newNote())
+        return proxies.MDIWindow(qti.app.newNote())
     else:
-        return proxies.QtProxyObject(qti.app.newNote(name))
+        return proxies.MDIWindow(qti.app.newNote(name))
 
 #-----------------------------------------------------------------------------
 # Intercept qtiplot "plot" command and forward to plotSpectrum for a workspace
@@ -237,7 +241,7 @@ def importImage(filename):
     Returns:
         A handle to the matrix containing the image data.
     """
-    return proxies.QtProxyObject(qti.app.importImage(filename))
+    return proxies.MDIWindow(qti.app.importImage(filename))
 
 #-----------------------------------------------------------------------------
 def newPlot3D():
@@ -249,6 +253,96 @@ def plot3D(*args):
     else:
         return proxies.Graph3D(qti.app.plot3D(args[0]._getHeldObject(),*args[1:]))
 
+#-----------------------------------------------------------------------------
+#-------------------------- 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 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 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 proxies.Folder(qti.app.addFolder(name,parentFolder))
+
+def deleteFolder(folder):
+    """Delete the referenced folder"""
+    return 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 qti.app.changeFolder(folder._getHeldObject(),force)
+
+def copyFolder(source, destination):
+    """Copy a folder (and its contents) into another.
+    
+    Returns:
+        True on success.
+    """
+    return qti.app.copyFolder(source._getHeldObject(),destination._getHeldObject())
+
+def openTemplate(filename):
+    """Load a previously saved window template"""
+    return proxies.MDIWindow(qti.app.openTemplate(filename))
+
+def saveAsTemplate(window, filename):
+    """Save the characteristics of the given window to file"""
+    qti.app.saveAsTemplate(window._getHeldObject(), filename)
+
+def setWindowName(window, name):
+    """Set the given window to have the given name"""
+    qti.app.setWindowName(window._getHeldObject(), name)
+
+def setPreferences(layer):
+    qti.app.setPreferences(graph._getHeldObject())
+
+def clone(window):
+    return proxies.MDIWindow(qti.app.clone(window._getHeldObject()))
+
+def tableToMatrix(table):
+    return proxies.MDIWindow(qti.app.tableToMatrix(table._getHeldObject()))
+
+def matrixToTable(matrix, conversionType=qti.app.Direct):
+    return proxies.MDIWindow(qti.app.matrixToTable(matrix._getHeldObject(),conversionType))
+
 #-----------------------------------------------------------------------------
 #-------------------------- Wrapped MantidUI functions -----------------------
 #-----------------------------------------------------------------------------
@@ -275,7 +369,7 @@ def getInstrumentView(name, tab=-1):
     Returns:
         A handle to the created instrument view widget.
     """
-    return proxies.QtProxyObject(qti.app.mantidUI.getInstrumentView(name,tab))
+    return proxies.MDIWindow(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.
@@ -307,7 +401,7 @@ def importTableWorkspace(name, visible=False):
     Returns:
         A handle to the newly created table.
     """
-    return proxies.QtProxyObject(qti.app.mantidUI.importTableWorkspace(name,False,visible))
+    return proxies.MDIWindow(qti.app.mantidUI.importTableWorkspace(name,False,visible))
 
 #-----------------------------------------------------------------------------
 #-------------------------- SliceViewer functions ----------------------------
@@ -400,6 +494,14 @@ def closeAllSliceViewers():
 # 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'
diff --git a/Code/Mantid/MantidPlot/mantidplotpy/proxies.py b/Code/Mantid/MantidPlot/mantidplotpy/proxies.py
index 1bbfa987304..f042b5dffe6 100644
--- a/Code/Mantid/MantidPlot/mantidplotpy/proxies.py
+++ b/Code/Mantid/MantidPlot/mantidplotpy/proxies.py
@@ -25,8 +25,9 @@ class QtProxyObject(QtCore.QObject):
         QtCore.QObject.__init__(self)
         self.__obj = toproxy
         # Connect to track the destroyed
-        QtCore.QObject.connect( self.__obj, QtCore.SIGNAL("destroyed()"),
-                                self._heldObjectDestroyed)
+        if self.__obj is not None:
+            QtCore.QObject.connect( self.__obj, QtCore.SIGNAL("destroyed()"),
+                                    self._heldObjectDestroyed)
 
     def __del__(self):
         # Disconnect the signal or you get a segfault on quitting MantidPlot
@@ -75,12 +76,23 @@ class QtProxyObject(QtCore.QObject):
         self.__obj = obj
 
 #-----------------------------------------------------------------------------
-class Graph(QtProxyObject):
-    """Proxy for the qti.Graph object.
+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 Folder(self._getHeldObject().folder())
+
+#-----------------------------------------------------------------------------
+class Graph(MDIWindow):
+    """Proxy for the qti.Graph object.
+    """
+    def __init__(self, toproxy):
+        MDIWindow.__init__(self,toproxy)
+
     def activeLayer(self):
         """Get a handle to the presently active layer """
         return Layer(self._getHeldObject().activeLayer())
@@ -120,6 +132,15 @@ class Graph(QtProxyObject):
             height=0
         return 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).
+        """
+        self._getHeldObject().insertCurve(graph._getHeldObject(),index)
+
 #-----------------------------------------------------------------------------
 class Layer(QtProxyObject):
     """Proxy for the qti.Layer object.
@@ -195,6 +216,10 @@ class Layer(QtProxyObject):
         """
         self._getHeldObject().addErrorBars(yColName,errTable._getHeldObject(),errColName,type,width,cap,color,through,minus,plus)
 
+    def addHistogram(self, matrix):
+        """Add a matrix histogram  to the graph"""
+        self._getHeldObject().addHistogram(matrix._getHeldObject())
+
     def newLegend(self, text):
         """Create a new legend.
         
@@ -255,7 +280,102 @@ class Spectrogram(QtProxyObject):
         return QtProxyObject(self._getHeldObject().matrix())
 
 #-----------------------------------------------------------------------------
-class MantidMatrix(QtProxyObject):
+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 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 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 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 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 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 Graph(self._getHeldObject().graph(name,recursive))
+
+    def rootFolder(self):
+        """Get the folder at the root of the hierarchy"""
+        return Folder(self._getHeldObject().rootFolder())
+
+#-----------------------------------------------------------------------------
+class MantidMatrix(MDIWindow):
     """Proxy for the qti.MantidMatrix object.
     """
     def __init__(self, toproxy):
-- 
GitLab