Ejemplo n.º 1
0
class ProjectDataView(tk.Frame):
    def __init__(self, parent, windows, **args):
        tk.Frame.__init__(self, parent, **args)
        # Load global configuration
        self.windows = windows
        self.project = windows.project
        self.root = windows.root
        self.getImage = windows.getImage
        self.protCfg = windows.protCfg
        self.settings = windows.getSettings()
        self.showGraph = self.settings.getRunsView()
        self.style = ttk.Style()

        self.root.bind("<F5>", self.refreshData)
        self.__autoRefresh = None
        self.__autoRefreshCounter = 3  # start by 3 secs

        self._dataGraph = windows.project.getSourceGraph(True)

        c = self._createContent()

        gui.configureWeigths(self)
        c.grid(row=0, column=0, sticky='news')

    def _createContent(self):
        """ Create the Data View for the Project.
        It has two panes:
            Left: containing the Protocol classes tree
            Right: containing the Data list
        """
        p = tk.PanedWindow(self, orient=tk.HORIZONTAL, bg='white')

        # Left pane, contains Data tree
        leftFrame = tk.Frame(p, bg='white')
        bgColor = '#eaebec'
        self._createDataTree(leftFrame, bgColor)
        gui.configureWeigths(leftFrame)

        # Right pane
        rightFrame = tk.Frame(p, bg='white')
        rightFrame.columnconfigure(0, weight=1)
        rightFrame.rowconfigure(1, weight=1)
        #rightFrame.rowconfigure(0, minsize=label.winfo_reqheight())
        self._fillRightPane(rightFrame)

        # Add sub-windows to PanedWindows
        p.add(leftFrame, padx=5, pady=5)
        p.add(rightFrame, padx=5, pady=5)
        p.paneconfig(leftFrame, minsize=300)
        p.paneconfig(rightFrame, minsize=400)

        return p

    def _createDataTree(self, parent, bgColor):
        """Create a tree on the left panel to store how 
        many object are from each type and the hierarchy.
        """
        self.style.configure("W.Treeview",
                             background=Color.LIGHT_GREY_COLOR,
                             borderwidth=0)
        self.dataTree = Tree(parent, show='tree', style='W.Treeview')
        self.dataTree.column('#0', minwidth=300)
        self.dataTree.tag_configure('protocol',
                                    image=self.getImage('python_file.gif'))
        self.dataTree.tag_configure('protocol_base',
                                    image=self.getImage('class_obj.gif'))
        f = tkFont.Font(family='helvetica', size='10', weight='bold')
        self.dataTree.tag_configure('non-empty', font=f)
        self.dataTree.grid(row=0, column=0, sticky='news')

        # bind click events
        self.dataTree.tag_bind(DATA_TAG, '<Double-1>', self._dataItemClick)
        self.dataTree.tag_bind(DATA_TAG, '<Return>', self._dataItemClick)

        # Program automatic refresh
        self.dataTree.after(3000, self._automaticRefreshData)

        self._updateDataTree()

    def _updateDataTree(self):
        def createClassNode(classObj):
            """ Add the object class to hierarchy and 
            any needed subclass. """
            className = classObj.__name__
            classNode = classesGraph.getNode(className)

            if not classNode:
                classNode = classesGraph.createNode(className)
                if className != 'EMObject' and classObj.__bases__:
                    baseClass = classObj.__bases__[0]
                    for b in classObj.__bases__:
                        if b.__name__ == 'EMObject':
                            baseClass = b
                            break
                    parent = createClassNode(baseClass)
                    parent.addChild(classNode)
                classNode.count = 0

            return classNode

        classesGraph = Graph()

        self.dataTree.clear()
        for node in self._dataGraph.getNodes():
            if node.pointer:
                classNode = createClassNode(node.pointer.get().getClass())
                classNode.count += 1

        populateTree(self.dataTree, classesGraph.getRootNodes())

    def _fillRightPane(self, parent):
        """
        # Create the right Pane that will be composed by:
        # a Action Buttons TOOLBAR in the top
        # and another vertical Pane with:
        # Runs History (at Top)
        # Sectected run info (at Bottom)
        """
        # Create the Action Buttons TOOLBAR
        toolbar = tk.Frame(parent, bg='white')
        toolbar.grid(row=0, column=0, sticky='news')
        gui.configureWeigths(toolbar)
        #toolbar.columnconfigure(0, weight=1)
        toolbar.columnconfigure(1, weight=1)

        self.runsToolbar = tk.Frame(toolbar, bg='white')
        self.runsToolbar.grid(row=0, column=0, sticky='sw')
        # On the left of the toolbar will be other
        # actions that can be applied to all runs (refresh, graph view...)
        self.allToolbar = tk.Frame(toolbar, bg='white')
        self.allToolbar.grid(row=0, column=10, sticky='se')
        #self.createActionToolbar()

        # Create the Run History tree
        v = ttk.PanedWindow(parent, orient=tk.VERTICAL)
        #runsFrame = ttk.Labelframe(v, text=' History ', width=500, height=500)
        runsFrame = tk.Frame(v, bg='white')
        self._createDataGraph(runsFrame)
        gui.configureWeigths(runsFrame)

        # Create the Selected Run Info
        infoFrame = tk.Frame(v)
        gui.configureWeigths(infoFrame)
        self._infoText = TaggedText(
            infoFrame,
            bg='white',
            handlers={'sci-open': self._openProtocolFormFromId})
        self._infoText.grid(row=0, column=0, sticky='news')

        v.add(runsFrame, weight=3)
        v.add(infoFrame, weight=1)
        v.grid(row=1, column=0, sticky='news')

    # TODO(josemi): check that the call to RoundedTextBox is correct
    # It looks suspicious because RoundedTextBox() needs 5 arguments, not 3
    def _createNode(self, canvas, node, y):
        """ Create Data node to be painted in the graph. """
        node.selected = False
        node.box = RoundedTextBox(canvas, node, y)
        return node.box

    def _createDataItem(self, canvas, node, y):
        if node.pointer is None:
            nodeText = "Project"
        else:
            label = getObjectLabel(node.pointer, self.project.mapper)
            nodeText = (label[:25] + '...') if len(label) > 25 else label

        textColor = 'black'
        color = '#ADD8E6'  #Lightblue
        item = self._dataCanvas.createTextbox(nodeText,
                                              100,
                                              y,
                                              bgColor=color,
                                              textColor=textColor)

        # Get the dataId
        if not node.isRoot():
            dataId = node.pointer.get().getObjId()

            if dataId in self.settings.dataSelection:
                item.setSelected(True)

        return item

    def _createDataGraph(self, parent):
        """ This will be the upper part of the right panel.
        It will contain the Data Graph with their relations.
        """
        self._dataCanvas = Canvas(parent, width=600, height=500)
        self._dataCanvas.grid(row=0, column=0, sticky='nsew')
        self._dataCanvas.onClickCallback = self._onClick
        self._dataCanvas.onDoubleClickCallback = self._onDoubleClick
        self._dataCanvas.onRightClickCallback = self._onRightClick

        self._updateDataGraph()

    def _updateDataGraph(self):
        lt = LevelTree(self._dataGraph)
        self._dataCanvas.clear()
        lt.setCanvas(self._dataCanvas)
        lt.paint(self._createDataItem)
        self._dataCanvas.updateScrollRegion()

    def _dataItemClick(self, e=None):
        # Get the tree widget that originated the event
        # from the left pane data tree
        tree = e.widget
        dataClassName = tree.getFirst()

        if dataClassName is not None:
            self._loopData(
                lambda item: self._selectItemByClass(item, dataClassName))

    def _selectObject(self, pobj):
        obj = pobj.get()
        self._selected = obj
        self._infoText.setReadOnly(False)
        self._infoText.setText('*Label:* ' +
                               getObjectLabel(pobj, self.project.mapper))
        self._infoText.addText('*Info:* ' + str(obj))
        self._infoText.addText('*Created by:*')
        self._infoText.addText(
            ' - ' + pobj.getObjValue().getObjectTag(pobj.getObjValue()) +
            ' (' + obj.getObjCreation() + ')')

        # Get the consumers (this will get the data!!)
        derivedData = self.project.getSourceChilds(pobj.getObjValue())

        if derivedData is not None and len(derivedData) > 0:

            self._infoText.addText('*Consumed by:*')
            for data in derivedData:

                # Get the protocol
                protocol = self.project.getObject(data.getObjParentId())
                self._infoText.addText(' - ' + protocol.getObjectTag(protocol))

        if obj.getObjComment():
            self._infoText.addText('*Comments:* ' + obj.getObjComment())
        self._infoText.setReadOnly(True)

    def _onClick(self, e=None):
        self._deselectAll()

        if e.node.pointer:
            self.toggleItemSelection(e, True)
            self._selectObject(e.node.pointer)

    def _invertSelection(self):

        self._loopData(self._invertAction)

    def _deselectAll(self):

        self._loopData(self._deselectItemAction)

    def _selectAll(self):

        self._loopData(self._selectItemAction)

    def _selectItemAction(self, item):
        self.toggleItemSelection(item, True)

    def _selectItemByClass(self, item, className):

        if not item.node.isRoot():

            data = item.node.pointer.get()
            self.toggleItemSelection(
                item, isinstance(data,
                                 Domain.getObjects()[className]))

    def _invertAction(self, item):
        self.toggleItemSelection(item, not item.getSelected())

    def _deselectItemAction(self, item):
        self.toggleItemSelection(item, False)

    def toggleItemSelection(self, item, select):

        if item.node.isRoot(): return

        selection = self.settings.dataSelection
        runSelection = self.settings.runSelection

        dataId = item.node.pointer.get().getObjId()
        protocolId = item.node.pointer.getObjValue().getObjId()
        if not select:
            try:
                if dataId in selection: selection.remove(dataId)
                if protocolId in runSelection: runSelection.remove(protocolId)
            except ValueError:
                print "id not in selection"

        else:
            selection.append(dataId)
            runSelection.append(protocolId)

        item.setSelected(select)

    def _loopData(self, action):

        results = []

        # Loop through all the items
        for key, item in self._dataCanvas.items.items():

            result = action(item)

            if result is not None:
                results.append(result)

        return results

    def _onDoubleClick(self, e=None):
        if e.node.pointer:

            self._selectObject(e.node.pointer)
            self._viewObject(e.node.pointer.get().getObjId())

            return
            # self._selectObject(e.node.pointer)
            # # Graph the first viewer available for this type of object
            # ViewerClass = findViewers(self._selected.getClassName(), DESKTOP_TKINTER)[0] #
            # viewer = ViewerClass(project=self.project)
            # viewer.visualize(self._selected)

    def _viewObject(self, objId):
        """ Call appropriate viewer for objId. """
        obj = self.project.getObject(int(objId))
        viewerClasses = findViewers(obj.getClassName(), DESKTOP_TKINTER)
        if not viewerClasses:
            return  # TODO: protest nicely
        viewer = viewerClasses[0](project=self.project, parent=self.windows)
        viewer.visualize(obj)

    def _openScipionLink(self, id):
        """ So far only protocols are coming through links"""
        self._openProtocolFormFromId(id)

    def _openProtocolFormFromId(self, protId):

        prot = self.project.getObject(int(protId))
        self._openProtocolForm(prot)

    def _openProtocolForm(self, prot):
        """Open the Protocol GUI Form given a Protocol instance"""

        w = gui.form.FormWindow(Message.TITLE_NAME_RUN + prot.getClassName(),
                                prot,
                                self._executeSaveProtocol,
                                self.windows,
                                hostList=self.project.getHostNames())
        w.adjustSize()
        w.show(center=True)

    def _executeSaveProtocol(self, prot, onlySave=False):
        if onlySave:
            self.project.saveProtocol(prot)
            msg = Message.LABEL_SAVED_FORM
            #            msg = "Protocol successfully saved."
        else:
            self.project.launchProtocol(prot)
            # Select the launched protocol to display its summary, methods..etc
            # self._selection.clear()
            # self._selection.append(prot.getObjId())
            # self._updateSelection()
            # self._lastStatus = None  # clear lastStatus to force re-load the logs
            msg = ""

        return msg

    def _onRightClick(self, e=None):
        return [(Message.LABEL_EDIT, self._editObject, Icon.ACTION_EDIT),
                ('Go to protocol', self._goToProtocol, Icon.ACTION_SEARCH)]

    def _editObject(self):
        """Open the Edit GUI Form given an instance"""
        EditObjectDialog(self, Message.TITLE_EDIT_OBJECT, self._selected,
                         self.project.mapper)

    def _goToProtocol(self):
        """Switch to protocols view selecting the correspondent protocol"""

    def refreshData(self, e=None, initRefreshCounter=True):
        """ Refresh the status of displayed data.
         Params:
            e: Tk event input
            initRefreshCounter: if True the refresh counter will be set to 3 secs
             then only case when False is from _automaticRefreshData where the
             refresh time is doubled each time to avoid refreshing too often.
        """
        self._dataGraph = self.windows.project.getSourceGraph(True)
        self._updateDataTree()
        self._updateDataGraph()

        if initRefreshCounter:
            self.__autoRefreshCounter = 3  # start by 3 secs
            if self.__autoRefresh:
                self.dataTree.after_cancel(self.__autoRefresh)
                self.__autoRefresh = self.dataTree.after(
                    self.__autoRefreshCounter * 1000,
                    self._automaticRefreshData)

    def _automaticRefreshData(self, e=None):
        """ Schedule automatic refresh increasing the time between refreshes. """
        self.refreshData(initRefreshCounter=False)
        secs = self.__autoRefreshCounter
        # double the number of seconds up to 30 min
        self.__autoRefreshCounter = min(2 * secs, 1800)
        self.__autoRefresh = self.dataTree.after(secs * 1000,
                                                 self._automaticRefreshData)
Ejemplo n.º 2
0
class ProjectDataView(tk.Frame):
    def __init__(self, parent, windows, **args):
        tk.Frame.__init__(self, parent, **args)
        # Load global configuration
        self.windows = windows
        self.project = windows.project
        self.root = windows.root
        self.getImage = windows.getImage
        self.protCfg = windows.protCfg
        self.icon = windows.icon
        self.settings = windows.getSettings()
        self.showGraph = self.settings.getRunsView()
        self.style = ttk.Style()
        
        self.root.bind("<F5>", self.refreshData)
        self.__autoRefresh = None
        self.__autoRefreshCounter = 3 # start by 3 secs  

        self._dataGraph = windows.project.getSourceGraph(True)

        c = self._createContent()
        
        gui.configureWeigths(self)
        c.grid(row=0, column=0, sticky='news')
        
    def _createContent(self):
        """ Create the Protocols View for the Project.
        It has two panes:
            Left: containing the Protocol classes tree
            Right: containing the Runs list
        """
        p = tk.PanedWindow(self, orient=tk.HORIZONTAL, bg='white')
        
        # Left pane, contains Data tree
        leftFrame = tk.Frame(p, bg='white')
        bgColor = '#eaebec'
        self._createDataTree(leftFrame, bgColor)
        gui.configureWeigths(leftFrame)
        
        # Right pane
        rightFrame = tk.Frame(p, bg='white')
        rightFrame.columnconfigure(0, weight=1)
        rightFrame.rowconfigure(1, weight=1)
        #rightFrame.rowconfigure(0, minsize=label.winfo_reqheight())
        self._fillRightPane(rightFrame)

        # Add sub-windows to PanedWindows
        p.add(leftFrame, padx=5, pady=5)
        p.add(rightFrame, padx=5, pady=5)
        p.paneconfig(leftFrame, minsize=300)
        p.paneconfig(rightFrame, minsize=400)        
        
        return p
    
    def _createDataTree(self, parent, bgColor):
        """Create a tree on the left panel to store how 
        many object are from each type and the hierarchy.
        """
        self.style.configure("W.Treeview", background=Color.LIGHT_GREY_COLOR, borderwidth=0)
        self.dataTree = Tree(parent, show='tree', style='W.Treeview')
        self.dataTree.column('#0', minwidth=300)
        self.dataTree.tag_configure('protocol', image=self.getImage('python_file.gif'))
        self.dataTree.tag_configure('protocol_base', image=self.getImage('class_obj.gif'))
        f = tkFont.Font(family='helvetica', size='10', weight='bold')
        self.dataTree.tag_configure('non-empty', font=f)
        self.dataTree.grid(row=0, column=0, sticky='news')
                        
        # Program automatic refresh
        self.dataTree.after(3000, self._automaticRefreshData)
        
        self._updateDataTree()
        
        
    def _updateDataTree(self):
            
        def createClassNode(classObj):
            """ Add the object class to hierarchy and 
            any needed subclass. """
            className = classObj.__name__
            classNode = classesGraph.getNode(className)
            
            if not classNode:
                classNode = classesGraph.createNode(className)
                if className != 'EMObject' and classObj.__bases__:
                    baseClass = classObj.__bases__[0]
                    for b in classObj.__bases__:
                        if b.__name__ == 'EMObject':
                            baseClass = b
                            break
                    parent = createClassNode(baseClass)
                    parent.addChild(classNode)
                classNode.count = 0
                
            return classNode
            
        classesGraph = Graph()
        
        self.dataTree.clear()
        for node in self._dataGraph.getNodes():
            if node.object:
                classNode = createClassNode(node.object.getClass())
                classNode.count += 1
        
        populateTree(self.dataTree, classesGraph.getRootNodes())

    
    def _fillRightPane(self, parent):
        """
        # Create the right Pane that will be composed by:
        # a Action Buttons TOOLBAR in the top
        # and another vertical Pane with:
        # Runs History (at Top)
        # Sectected run info (at Bottom)
        """
        # Create the Action Buttons TOOLBAR
        toolbar = tk.Frame(parent, bg='white')
        toolbar.grid(row=0, column=0, sticky='news')
        gui.configureWeigths(toolbar)
        #toolbar.columnconfigure(0, weight=1)
        toolbar.columnconfigure(1, weight=1)
        
        self.runsToolbar = tk.Frame(toolbar, bg='white')
        self.runsToolbar.grid(row=0, column=0, sticky='sw')
        # On the left of the toolbar will be other
        # actions that can be applied to all runs (refresh, graph view...)
        self.allToolbar = tk.Frame(toolbar, bg='white')
        self.allToolbar.grid(row=0, column=10, sticky='se')
        #self.createActionToolbar()

        # Create the Run History tree
        v = ttk.PanedWindow(parent, orient=tk.VERTICAL)
        #runsFrame = ttk.Labelframe(v, text=' History ', width=500, height=500)
        runsFrame = tk.Frame(v, bg='white')
        self._createDataGraph(runsFrame)
        gui.configureWeigths(runsFrame)
        
        # Create the Selected Run Info
        infoFrame = tk.Frame(v)
        gui.configureWeigths(infoFrame)
        self._infoText = TaggedText(infoFrame, bg='white')
        self._infoText.grid(row=0, column=0, sticky='news')
        
        v.add(runsFrame, weight=3)
        v.add(infoFrame, weight=1)
        v.grid(row=1, column=0, sticky='news')

    # TODO(josemi): check that the call to RoundedTextBox is correct
    # It looks suspicious because RoundedTextBox() needs 5 arguments, not 3
    def _createNode(self, canvas, node, y):
        """ Create Data node to be painted in the graph. """
        node.selected = False
        node.box = RoundedTextBox(canvas, node, y)
        return node.box

    def _createDataItem(self, canvas, node, y):
        if node.object is None:
            nodeText = "Project"
        else:
            label = getObjectLabel(node.object, self.project.mapper)
            nodeText = (label[:25]+'...') if len(label) > 25 else label
                
        textColor = 'black'
        color = '#ADD8E6' #Lightblue
        item = self._dataCanvas.createTextbox(nodeText, 100, y, bgColor=color, textColor=textColor)

        return item
    
    def _createDataGraph(self, parent):
        """ This will be the upper part of the right panel.
        It will contains the Data Graph with their relations. 
        """
        self._dataCanvas = Canvas(parent, width=600, height=500)
        self._dataCanvas.grid(row=0, column=0, sticky='nsew')
        self._dataCanvas.onClickCallback = self._onClick
        self._dataCanvas.onDoubleClickCallback = self._onDoubleClick
        self._dataCanvas.onRightClickCallback = self._onRightClick
       
        self._updateDataGraph()
        
    def _updateDataGraph(self): 
        lt = LevelTree(self._dataGraph)
        self._dataCanvas.clear()
        lt.setCanvas(self._dataCanvas)
        lt.paint(self._createDataItem)
        self._dataCanvas.updateScrollRegion()
        
    def _selectObject(self, obj):
        self._selected = obj
        self._infoText.setReadOnly(False)
        self._infoText.setText('*Label:* ' + getObjectLabel(obj, self.project.mapper))
        self._infoText.addText('*Info:* ' + str(obj))
        self._infoText.addText('*Created:* ' + '2014-11-22')
        if obj.getObjComment():
            self._infoText.addText('*Comments:* ' + obj.getObjComment())
        self._infoText.setReadOnly(True)
        
    def _onClick(self, e=None):
        if e.node.object:
            self._selectObject(e.node.object)
    
    def _onDoubleClick(self, e=None):
        if e.node.object:
            self._selectObject(e.node.object)
            # Graph the first viewer available for this type of object
            ViewerClass = findViewers(self._selected.getClassName(), DESKTOP_TKINTER)[0] #
            viewer = ViewerClass(project=self.project)
            viewer.visualize(self._selected)
        
    def _onRightClick(self, e=None):
        return [(Message.LABEL_EDIT, self._editObject, Icon.ACTION_EDIT)]
    
    def _editObject(self):
        """Open the Edit GUI Form given an instance"""
        EditObjectDialog(self, Message.TITLE_EDIT_OBJECT, self._selected, self.project.mapper)

    def refreshData(self, e=None, initRefreshCounter=True):
        """ Refresh the status of displayed data.
         Params:
            e: Tk event input
            initRefreshCounter: if True the refresh counter will be set to 3 secs
             then only case when False is from _automaticRefreshData where the
             refresh time is doubled each time to avoid refreshing too often.
        """        
        self._dataGraph = self.windows.project.getSourceGraph(True)
        self._updateDataTree()
        self._updateDataGraph()
 
        if initRefreshCounter:
            self.__autoRefreshCounter = 3 # start by 3 secs
            if self.__autoRefresh:
                self.dataTree.after_cancel(self.__autoRefresh)
                self.__autoRefresh = self.dataTree.after(self.__autoRefreshCounter*1000, self._automaticRefreshData)
         
    def _automaticRefreshData(self, e=None):
        """ Schedule automatic refresh increasing the time between refreshes. """
        self.refreshData(initRefreshCounter=False)
        secs = self.__autoRefreshCounter
        # double the number of seconds up to 30 min
        self.__autoRefreshCounter = min(2*secs, 1800)
        self.__autoRefresh = self.dataTree.after(secs*1000, self._automaticRefreshData)
Ejemplo n.º 3
0
class ProjectDataView(tk.Frame):
    def __init__(self, parent, windows, **args):
        tk.Frame.__init__(self, parent, **args)
        # Load global configuration
        self.windows = windows
        self.project = windows.project
        self.root = windows.root
        self.getImage = windows.getImage
        self.protCfg = windows.protCfg
        self.icon = windows.icon
        self.settings = windows.getSettings()
        self.showGraph = self.settings.getRunsView()
        self.style = ttk.Style()
        
        self.root.bind("<F5>", self.refreshData)
        self.__autoRefresh = None
        self.__autoRefreshCounter = 3 # start by 3 secs  

        self._dataGraph = windows.project.getSourceGraph(True)

        c = self._createContent()
        
        gui.configureWeigths(self)
        c.grid(row=0, column=0, sticky='news')
        
    def _createContent(self):
        """ Create the Data View for the Project.
        It has two panes:
            Left: containing the Protocol classes tree
            Right: containing the Data list
        """
        p = tk.PanedWindow(self, orient=tk.HORIZONTAL, bg='white')
        
        # Left pane, contains Data tree
        leftFrame = tk.Frame(p, bg='white')
        bgColor = '#eaebec'
        self._createDataTree(leftFrame, bgColor)
        gui.configureWeigths(leftFrame)
        
        # Right pane
        rightFrame = tk.Frame(p, bg='white')
        rightFrame.columnconfigure(0, weight=1)
        rightFrame.rowconfigure(1, weight=1)
        #rightFrame.rowconfigure(0, minsize=label.winfo_reqheight())
        self._fillRightPane(rightFrame)

        # Add sub-windows to PanedWindows
        p.add(leftFrame, padx=5, pady=5)
        p.add(rightFrame, padx=5, pady=5)
        p.paneconfig(leftFrame, minsize=300)
        p.paneconfig(rightFrame, minsize=400)        
        
        return p
    
    def _createDataTree(self, parent, bgColor):
        """Create a tree on the left panel to store how 
        many object are from each type and the hierarchy.
        """
        self.style.configure("W.Treeview", background=Color.LIGHT_GREY_COLOR, borderwidth=0)
        self.dataTree = Tree(parent, show='tree', style='W.Treeview')
        self.dataTree.column('#0', minwidth=300)
        self.dataTree.tag_configure('protocol', image=self.getImage('python_file.gif'))
        self.dataTree.tag_configure('protocol_base', image=self.getImage('class_obj.gif'))
        f = tkFont.Font(family='helvetica', size='10', weight='bold')
        self.dataTree.tag_configure('non-empty', font=f)
        self.dataTree.grid(row=0, column=0, sticky='news')

        # bind click events
        self.dataTree.tag_bind(DATA_TAG, '<Double-1>', self._dataItemClick)
        self.dataTree.tag_bind(DATA_TAG, '<Return>', self._dataItemClick)

        # Program automatic refresh
        self.dataTree.after(3000, self._automaticRefreshData)
        
        self._updateDataTree()
        
        
    def _updateDataTree(self):
            
        def createClassNode(classObj):
            """ Add the object class to hierarchy and 
            any needed subclass. """
            className = classObj.__name__
            classNode = classesGraph.getNode(className)
            
            if not classNode:
                classNode = classesGraph.createNode(className)
                if className != 'EMObject' and classObj.__bases__:
                    baseClass = classObj.__bases__[0]
                    for b in classObj.__bases__:
                        if b.__name__ == 'EMObject':
                            baseClass = b
                            break
                    parent = createClassNode(baseClass)
                    parent.addChild(classNode)
                classNode.count = 0
                
            return classNode
            
        classesGraph = Graph()
        
        self.dataTree.clear()
        for node in self._dataGraph.getNodes():
            if node.pointer:
                classNode = createClassNode(node.pointer.get().getClass())
                classNode.count += 1
        
        populateTree(self.dataTree, classesGraph.getRootNodes())

    
    def _fillRightPane(self, parent):
        """
        # Create the right Pane that will be composed by:
        # a Action Buttons TOOLBAR in the top
        # and another vertical Pane with:
        # Runs History (at Top)
        # Sectected run info (at Bottom)
        """
        # Create the Action Buttons TOOLBAR
        toolbar = tk.Frame(parent, bg='white')
        toolbar.grid(row=0, column=0, sticky='news')
        gui.configureWeigths(toolbar)
        #toolbar.columnconfigure(0, weight=1)
        toolbar.columnconfigure(1, weight=1)
        
        self.runsToolbar = tk.Frame(toolbar, bg='white')
        self.runsToolbar.grid(row=0, column=0, sticky='sw')
        # On the left of the toolbar will be other
        # actions that can be applied to all runs (refresh, graph view...)
        self.allToolbar = tk.Frame(toolbar, bg='white')
        self.allToolbar.grid(row=0, column=10, sticky='se')
        #self.createActionToolbar()

        # Create the Run History tree
        v = ttk.PanedWindow(parent, orient=tk.VERTICAL)
        #runsFrame = ttk.Labelframe(v, text=' History ', width=500, height=500)
        runsFrame = tk.Frame(v, bg='white')
        self._createDataGraph(runsFrame)
        gui.configureWeigths(runsFrame)
        
        # Create the Selected Run Info
        infoFrame = tk.Frame(v)
        gui.configureWeigths(infoFrame)
        self._infoText = TaggedText(infoFrame, bg='white', handlers={'sci-open': self._openProtocolFormFromId})
        self._infoText.grid(row=0, column=0, sticky='news')
        
        v.add(runsFrame, weight=3)
        v.add(infoFrame, weight=1)
        v.grid(row=1, column=0, sticky='news')

    # TODO(josemi): check that the call to RoundedTextBox is correct
    # It looks suspicious because RoundedTextBox() needs 5 arguments, not 3
    def _createNode(self, canvas, node, y):
        """ Create Data node to be painted in the graph. """
        node.selected = False
        node.box = RoundedTextBox(canvas, node, y)
        return node.box

    def _createDataItem(self, canvas, node, y):
        if node.pointer is None:
            nodeText = "Project"
        else:
            label = getObjectLabel(node.pointer, self.project.mapper)
            nodeText = (label[:25]+'...') if len(label) > 25 else label
                
        textColor = 'black'
        color = '#ADD8E6' #Lightblue
        item = self._dataCanvas.createTextbox(nodeText, 100, y, bgColor=color, textColor=textColor)

        # Get the dataId
        if not node.isRoot():
            dataId = node.pointer.get().getObjId()

            if dataId in self.settings.dataSelection:
                item.setSelected(True)

        return item
    
    def _createDataGraph(self, parent):
        """ This will be the upper part of the right panel.
        It will contain the Data Graph with their relations.
        """
        self._dataCanvas = Canvas(parent, width=600, height=500)
        self._dataCanvas.grid(row=0, column=0, sticky='nsew')
        self._dataCanvas.onClickCallback = self._onClick
        self._dataCanvas.onDoubleClickCallback = self._onDoubleClick
        self._dataCanvas.onRightClickCallback = self._onRightClick
       
        self._updateDataGraph()
        
    def _updateDataGraph(self): 
        lt = LevelTree(self._dataGraph)
        self._dataCanvas.clear()
        lt.setCanvas(self._dataCanvas)
        lt.paint(self._createDataItem)
        self._dataCanvas.updateScrollRegion()

    def _dataItemClick(self, e=None):
        # Get the tree widget that originated the event
        # from the left pane data tree
        tree = e.widget
        dataClassName = tree.getFirst()

        if dataClassName is not None:
            self._loopData(lambda item: self._selectItemByClass(item, dataClassName))


    def _selectObject(self, pobj):
        obj = pobj.get()
        self._selected = obj
        self._infoText.setReadOnly(False)
        self._infoText.setText('*Label:* ' + getObjectLabel(pobj, self.project.mapper))
        self._infoText.addText('*Info:* ' + str(obj))
        self._infoText.addText('*Created by:*')
        self._infoText.addText(' - ' + pobj.getObjValue().getObjectTag(pobj.getObjValue()) + ' (' + obj.getObjCreation() + ')')

        # Get the consumers (this will get the data!!)
        derivedData = self.project.getSourceChilds(pobj.getObjValue())

        if derivedData is not None and len(derivedData) > 0:

            self._infoText.addText('*Consumed by:*')
            for data in derivedData:

                # Get the protocol
                protocol = self.project.getObject(data.getObjParentId())
                self._infoText.addText(' - ' + protocol.getObjectTag(protocol))


        if obj.getObjComment():
            self._infoText.addText('*Comments:* ' + obj.getObjComment())
        self._infoText.setReadOnly(True)
        
    def _onClick(self, e=None):
        self._deselectAll()

        if e.node.pointer:
            self.toogleItemSelection(e, True)
            self._selectObject(e.node.pointer)

    def _invertSelection(self):

        self._loopData(self._invertAction)

    def _deselectAll(self):

        self._loopData(self._deselectItemAction)

    def _selectAll(self):

        self._loopData(self._selectItemAction)

    def _selectItemAction(self, item):
        self.toogleItemSelection(item, True)

    def _selectItemByClass(self, item, className):

        if not item.node.isRoot():

            data = item.node.pointer.get()
            self.toogleItemSelection(item, isinstance(data, getObjects()[className]))

    def _invertAction(self, item):
        self.toogleItemSelection(item, not item.getSelected())

    def _deselectItemAction(self, item):
        self.toogleItemSelection(item, False)

    def toogleItemSelection(self, item, select):

        if item.node.isRoot(): return

        selection = self.settings.dataSelection
        runSelection = self.settings.runSelection

        dataId = item.node.pointer.get().getObjId()
        protocolId = item.node.pointer.getObjValue().getObjId()
        if not select:
            try:
                if dataId in selection: selection.remove(dataId)
                if protocolId in runSelection: runSelection.remove(protocolId)
            except ValueError:
                print "id not in selection"

        else:
            selection.append(dataId)
            runSelection.append(protocolId)

        item.setSelected(select)

    def _loopData(self, action):

        results = []

        # Loop through all the items
        for key, item in self._dataCanvas.items.items():

            result = action(item)

            if result is not None:
                results.append(result)

        return results

    def _onDoubleClick(self, e=None):
        if e.node.pointer:

            self._selectObject(e.node.pointer)
            self._viewObject(e.node.pointer.get().getObjId())

            return
            # self._selectObject(e.node.pointer)
            # # Graph the first viewer available for this type of object
            # ViewerClass = findViewers(self._selected.getClassName(), DESKTOP_TKINTER)[0] #
            # viewer = ViewerClass(project=self.project)
            # viewer.visualize(self._selected)

    def _viewObject(self, objId):
        """ Call appropriate viewer for objId. """
        obj = self.project.getObject(int(objId))
        viewerClasses = findViewers(obj.getClassName(), DESKTOP_TKINTER)
        if not viewerClasses:
            return  # TODO: protest nicely
        viewer = viewerClasses[0](project=self.project, parent=self.windows)
        viewer.visualize(obj)

    def _openScipionLink(self, id):
        """ So far only protocols are coming through links"""
        self._openProtocolFormFromId(id)

    def _openProtocolFormFromId(self, protId):

        prot = self.project.getObject(int(protId))
        self._openProtocolForm(prot)

    def _openProtocolForm(self, prot):
        """Open the Protocol GUI Form given a Protocol instance"""

        w = gui.form.FormWindow(Message.TITLE_NAME_RUN + prot.getClassName(), prot,
                                  self._executeSaveProtocol, self.windows,
                                  hostList=self.project.getHostNames())
        w.adjustSize()
        w.show(center=True)

    def _executeSaveProtocol(self, prot, onlySave=False):
        if onlySave:
            self.project.saveProtocol(prot)
            msg = Message.LABEL_SAVED_FORM
            #            msg = "Protocol successfully saved."
        else:
            self.project.launchProtocol(prot)
            # Select the launched protocol to display its summary, methods..etc
            # self._selection.clear()
            # self._selection.append(prot.getObjId())
            # self._updateSelection()
            # self._lastStatus = None  # clear lastStatus to force re-load the logs
            msg = ""

        return msg

    def _onRightClick(self, e=None):
        return [
            (Message.LABEL_EDIT, self._editObject, Icon.ACTION_EDIT),
            ('Go to protocol', self._goToProtocol, Icon.ACTION_SEARCH)
        ]
    
    def _editObject(self):
        """Open the Edit GUI Form given an instance"""
        EditObjectDialog(self, Message.TITLE_EDIT_OBJECT, self._selected, self.project.mapper)

    def _goToProtocol(self):
        """Switch to protocols view selecting the correspondent protocol"""


    def refreshData(self, e=None, initRefreshCounter=True):
        """ Refresh the status of displayed data.
         Params:
            e: Tk event input
            initRefreshCounter: if True the refresh counter will be set to 3 secs
             then only case when False is from _automaticRefreshData where the
             refresh time is doubled each time to avoid refreshing too often.
        """        
        self._dataGraph = self.windows.project.getSourceGraph(True)
        self._updateDataTree()
        self._updateDataGraph()
 
        if initRefreshCounter:
            self.__autoRefreshCounter = 3 # start by 3 secs
            if self.__autoRefresh:
                self.dataTree.after_cancel(self.__autoRefresh)
                self.__autoRefresh = self.dataTree.after(self.__autoRefreshCounter*1000, self._automaticRefreshData)
         
    def _automaticRefreshData(self, e=None):
        """ Schedule automatic refresh increasing the time between refreshes. """
        self.refreshData(initRefreshCounter=False)
        secs = self.__autoRefreshCounter
        # double the number of seconds up to 30 min
        self.__autoRefreshCounter = min(2*secs, 1800)
        self.__autoRefresh = self.dataTree.after(secs*1000, self._automaticRefreshData)
Ejemplo n.º 4
0
class ProjectDataView(tk.Frame):
    def __init__(self, parent, windows, **args):
        tk.Frame.__init__(self, parent, **args)
        # Load global configuration
        self.windows = windows
        self.project = windows.project
        self.root = windows.root
        self.getImage = windows.getImage
        self.protCfg = windows.protCfg
        self.icon = windows.icon
        self.settings = windows.getSettings()
        self.showGraph = self.settings.getRunsView()
        self.style = ttk.Style()

        self.root.bind("<F5>", self.refreshData)
        self.__autoRefresh = None
        self.__autoRefreshCounter = 3  # start by 3 secs

        self._dataGraph = windows.project.getSourceGraph(True)

        c = self._createContent()

        gui.configureWeigths(self)
        c.grid(row=0, column=0, sticky='news')

    def _createContent(self):
        """ Create the Protocols View for the Project.
        It has two panes:
            Left: containing the Protocol classes tree
            Right: containing the Runs list
        """
        p = tk.PanedWindow(self, orient=tk.HORIZONTAL, bg='white')

        # Left pane, contains Data tree
        leftFrame = tk.Frame(p, bg='white')
        bgColor = '#eaebec'
        self._createDataTree(leftFrame, bgColor)
        gui.configureWeigths(leftFrame)

        # Right pane
        rightFrame = tk.Frame(p, bg='white')
        rightFrame.columnconfigure(0, weight=1)
        rightFrame.rowconfigure(1, weight=1)
        #rightFrame.rowconfigure(0, minsize=label.winfo_reqheight())
        self._fillRightPane(rightFrame)

        # Add sub-windows to PanedWindows
        p.add(leftFrame, padx=5, pady=5)
        p.add(rightFrame, padx=5, pady=5)
        p.paneconfig(leftFrame, minsize=300)
        p.paneconfig(rightFrame, minsize=400)

        return p

    def _createDataTree(self, parent, bgColor):
        """Create a tree on the left panel to store how 
        many object are from each type and the hierarchy.
        """
        self.style.configure("W.Treeview",
                             background=Color.LIGHT_GREY_COLOR,
                             borderwidth=0)
        self.dataTree = Tree(parent, show='tree', style='W.Treeview')
        self.dataTree.column('#0', minwidth=300)
        self.dataTree.tag_configure('protocol',
                                    image=self.getImage('python_file.gif'))
        self.dataTree.tag_configure('protocol_base',
                                    image=self.getImage('class_obj.gif'))
        f = tkFont.Font(family='helvetica', size='10', weight='bold')
        self.dataTree.tag_configure('non-empty', font=f)
        self.dataTree.grid(row=0, column=0, sticky='news')

        # Program automatic refresh
        self.dataTree.after(3000, self._automaticRefreshData)

        self._updateDataTree()

    def _updateDataTree(self):
        def createClassNode(classObj):
            """ Add the object class to hierarchy and 
            any needed subclass. """
            className = classObj.__name__
            classNode = classesGraph.getNode(className)

            if not classNode:
                classNode = classesGraph.createNode(className)
                if className != 'EMObject' and classObj.__bases__:
                    baseClass = classObj.__bases__[0]
                    for b in classObj.__bases__:
                        if b.__name__ == 'EMObject':
                            baseClass = b
                            break
                    parent = createClassNode(baseClass)
                    parent.addChild(classNode)
                classNode.count = 0

            return classNode

        classesGraph = Graph()

        self.dataTree.clear()
        for node in self._dataGraph.getNodes():
            if node.pointer:
                classNode = createClassNode(node.pointer.get().getClass())
                classNode.count += 1

        populateTree(self.dataTree, classesGraph.getRootNodes())

    def _fillRightPane(self, parent):
        """
        # Create the right Pane that will be composed by:
        # a Action Buttons TOOLBAR in the top
        # and another vertical Pane with:
        # Runs History (at Top)
        # Sectected run info (at Bottom)
        """
        # Create the Action Buttons TOOLBAR
        toolbar = tk.Frame(parent, bg='white')
        toolbar.grid(row=0, column=0, sticky='news')
        gui.configureWeigths(toolbar)
        #toolbar.columnconfigure(0, weight=1)
        toolbar.columnconfigure(1, weight=1)

        self.runsToolbar = tk.Frame(toolbar, bg='white')
        self.runsToolbar.grid(row=0, column=0, sticky='sw')
        # On the left of the toolbar will be other
        # actions that can be applied to all runs (refresh, graph view...)
        self.allToolbar = tk.Frame(toolbar, bg='white')
        self.allToolbar.grid(row=0, column=10, sticky='se')
        #self.createActionToolbar()

        # Create the Run History tree
        v = ttk.PanedWindow(parent, orient=tk.VERTICAL)
        #runsFrame = ttk.Labelframe(v, text=' History ', width=500, height=500)
        runsFrame = tk.Frame(v, bg='white')
        self._createDataGraph(runsFrame)
        gui.configureWeigths(runsFrame)

        # Create the Selected Run Info
        infoFrame = tk.Frame(v)
        gui.configureWeigths(infoFrame)
        self._infoText = TaggedText(infoFrame, bg='white')
        self._infoText.grid(row=0, column=0, sticky='news')

        v.add(runsFrame, weight=3)
        v.add(infoFrame, weight=1)
        v.grid(row=1, column=0, sticky='news')

    # TODO(josemi): check that the call to RoundedTextBox is correct
    # It looks suspicious because RoundedTextBox() needs 5 arguments, not 3
    def _createNode(self, canvas, node, y):
        """ Create Data node to be painted in the graph. """
        node.selected = False
        node.box = RoundedTextBox(canvas, node, y)
        return node.box

    def _createDataItem(self, canvas, node, y):
        if node.pointer is None:
            nodeText = "Project"
        else:
            label = getObjectLabel(node.pointer, self.project.mapper)
            nodeText = (label[:25] + '...') if len(label) > 25 else label

        textColor = 'black'
        color = '#ADD8E6'  #Lightblue
        item = self._dataCanvas.createTextbox(nodeText,
                                              100,
                                              y,
                                              bgColor=color,
                                              textColor=textColor)

        return item

    def _createDataGraph(self, parent):
        """ This will be the upper part of the right panel.
        It will contains the Data Graph with their relations. 
        """
        self._dataCanvas = Canvas(parent, width=600, height=500)
        self._dataCanvas.grid(row=0, column=0, sticky='nsew')
        self._dataCanvas.onClickCallback = self._onClick
        self._dataCanvas.onDoubleClickCallback = self._onDoubleClick
        self._dataCanvas.onRightClickCallback = self._onRightClick

        self._updateDataGraph()

    def _updateDataGraph(self):
        lt = LevelTree(self._dataGraph)
        self._dataCanvas.clear()
        lt.setCanvas(self._dataCanvas)
        lt.paint(self._createDataItem)
        self._dataCanvas.updateScrollRegion()

    def _selectObject(self, pobj):
        obj = pobj.get()
        self._selected = obj
        self._infoText.setReadOnly(False)
        self._infoText.setText('*Label:* ' +
                               getObjectLabel(pobj, self.project.mapper))
        self._infoText.addText('*Info:* ' + str(obj))
        self._infoText.addText('*Created:* ' + '2014-11-22')
        if obj.getObjComment():
            self._infoText.addText('*Comments:* ' + obj.getObjComment())
        self._infoText.setReadOnly(True)

    def _onClick(self, e=None):
        if e.node.pointer:
            self._selectObject(e.node.pointer)

    def _onDoubleClick(self, e=None):
        if e.node.pointer:
            self._selectObject(e.node.pointer)
            # Graph the first viewer available for this type of object
            ViewerClass = findViewers(self._selected.getClassName(),
                                      DESKTOP_TKINTER)[0]  #
            viewer = ViewerClass(project=self.project)
            viewer.visualize(self._selected)

    def _onRightClick(self, e=None):
        return [(Message.LABEL_EDIT, self._editObject, Icon.ACTION_EDIT)]

    def _editObject(self):
        """Open the Edit GUI Form given an instance"""
        EditObjectDialog(self, Message.TITLE_EDIT_OBJECT, self._selected,
                         self.project.mapper)

    def refreshData(self, e=None, initRefreshCounter=True):
        """ Refresh the status of displayed data.
         Params:
            e: Tk event input
            initRefreshCounter: if True the refresh counter will be set to 3 secs
             then only case when False is from _automaticRefreshData where the
             refresh time is doubled each time to avoid refreshing too often.
        """
        self._dataGraph = self.windows.project.getSourceGraph(True)
        self._updateDataTree()
        self._updateDataGraph()

        if initRefreshCounter:
            self.__autoRefreshCounter = 3  # start by 3 secs
            if self.__autoRefresh:
                self.dataTree.after_cancel(self.__autoRefresh)
                self.__autoRefresh = self.dataTree.after(
                    self.__autoRefreshCounter * 1000,
                    self._automaticRefreshData)

    def _automaticRefreshData(self, e=None):
        """ Schedule automatic refresh increasing the time between refreshes. """
        self.refreshData(initRefreshCounter=False)
        secs = self.__autoRefreshCounter
        # double the number of seconds up to 30 min
        self.__autoRefreshCounter = min(2 * secs, 1800)
        self.__autoRefresh = self.dataTree.after(secs * 1000,
                                                 self._automaticRefreshData)