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