mgltools-networkeditor-1.5.7~rc1~cvs.20130519/0000755000175000017500000000000012146210577020136 5ustar debiandebianmgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/0000755000175000017500000000000012146210612022724 5ustar debiandebianmgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/naviMap.py0000644000175000017500000001546712055234722024715 0ustar debiandebian######################################################################### # # Date: Nov. 2012 Author: Michel Sanner # # sanner@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner and TSRI # ######################################################################### from NetworkEditor.net import AddNodeEvent, ConnectNodes, \ DeleteNodesEvent, DeleteConnectionsEvent from mglutil.util.callback import CallbackFunction import weakref class NavigationMap: """ draw a small representation of the total canvas area with a rectangle showing the outline of the visible portion of the map """ def __init__(self, network, scrolledCanvas, mapCanvas, scaleFactor=0.05): self.network = weakref.ref(network) editor = network.editor self.editor = weakref.ref(editor) self.mapCanvas = mapCanvas # where we draw the map self.sc_canvas = scrolledCanvas # where the network self.scaleFactor = scaleFactor canvas = self.netCanvas = scrolledCanvas.component('canvas') # find total size of the canvas bbox = map(int, canvas.configure('scrollregion')[-1]) # compute map width and height self.width = (bbox[2]-bbox[0])*scaleFactor self.height = (bbox[3]-bbox[1])*scaleFactor self.upperLeft = (0,0) # override callback for scroll bar to moce map to keep # it in the upperLeft corner of the Network cb = CallbackFunction(self.moveMapx, 'y') scrolledCanvas.component('vertscrollbar').configure(command=cb) cb = CallbackFunction(self.moveMapx, 'x') scrolledCanvas.component('horizscrollbar').configure(command=cb) # register interest in event that add items to the minimap editor.registerListener(AddNodeEvent, self.handleAddNode) editor.registerListener(ConnectNodes, self.handleConnectNodes) editor.registerListener(DeleteNodesEvent, self.handleDeleteNodes) editor.registerListener(DeleteConnectionsEvent, self.handleDeleteConnections) # create navi map outline self.outlineCID = canvas.create_rectangle( 0, 0, self.width, self.height, outline='black', tags=('navimap',)) # draw visible window outline self.visibleWinOutlineCID = canvas.create_rectangle( 0, 0, 10, 10, outline='blue', width=2, tags=('navimap','visibleWin',)) mapCanvas.tag_bind(self.visibleWinOutlineCID, "", self.moveVisibleWin_cb) canvas.tag_bind(self.visibleWinOutlineCID, "", self.moveCanvas) canvas.tag_bind(self.visibleWinOutlineCID, "", self.moveCanvasEnd) def __del__(self): self.unregisterCallBacks() def unregisterCallBacks(self): editor = self.editor() editor.unregisterListener(AddNodeEvent, self.handleAddNode) editor.unregisterListener(ConnectNodes, self.handleConnectNodes) editor.unregisterListener(DeleteNodesEvent, self.handleDeleteNodes) editor.unregisterListener(DeleteConnectionsEvent, self.handleDeleteConnections) def moveVisibleWin_cb(self, event): self.lastx = event.x self.lasty = event.y canvas = self.mapCanvas canvas.configure(cursor='hand2') canvas.itemconfigure(self.visibleWinOutlineCID, outline='green') def moveCanvas(self, event=None): ed = self.editor() dx = (event.x - self.lastx)/self.scaleFactor dy = (event.y - self.lasty)/self.scaleFactor canvas = self.netCanvas xo = max(0, canvas.canvasx(0)+dx) yo = max(0, canvas.canvasy(0)+dy) canvas.xview_moveto(xo/float(ed.totalWidth)) canvas.yview_moveto(yo/float(ed.totalHeight)) self.lastx = event.x self.lasty = event.y self.moveMapx(None, None, None) canvas.update_idletasks() def moveCanvasEnd(self, event=None): num = event.num canvas = self.mapCanvas canvas.configure(cursor='') def placeVisibleWin(self): if self.network().showNaviMap: netCanvas = self.netCanvas mapCanvas = self.mapCanvas width = netCanvas.winfo_width() height = netCanvas.winfo_height() if width==1 or height==1: netCanvas.after(100, self.placeVisibleWin) x0 = netCanvas.canvasx(0) y0 = netCanvas.canvasy(0) sc = self.scaleFactor mapCanvas.coords(self.visibleWinOutlineCID, x0+x0*sc, y0+y0*sc, x0+(x0+width)*sc, y0+(y0+height)*sc) mapCanvas.itemconfigure(self.visibleWinOutlineCID, outline='blue') def moveMapx(self, direction, how, *args): # call default callback to scroll canvas canvas = self.netCanvas if direction=='y': canvas.yview(how, *args) elif direction=='x': canvas.xview(how, *args) # canvas coords of upper left corner of visible part x = canvas.canvasx(0) y = canvas.canvasy(0) x0, y0 = self.upperLeft canvas.move('navimap', x-x0, y-y0) self.upperLeft = (x, y) self.placeVisibleWin() ## ## do not use self.mapCanvas but instead use object.network.naviMap as ## ed.refreshNet_cb will recreate a new NaviMap which will be diffrent ## from the one register in the call back ## def handleAddNode(self, event): n = event.node naviMap = n.network.naviMap if n.network == naviMap.network(): n.drawMapIcon(naviMap) def handleConnectNodes(self, event): c = event.connection naviMap = c.network.naviMap if c.network == naviMap.network(): c.drawMapIcon(c.network.naviMap) def handleDeleteNodes(self, event): for n in event.nodes: naviMap = n.network.naviMap if n.network != naviMap.network(): continue # this node is not on this map mapCanvas = naviMap.mapCanvas mapCanvas.delete(n.naviMapID) for p in n.inputPorts: for c in p.connections: mapCanvas.delete(c.naviMapID) for p in n.outputPorts: for c in p.connections: mapCanvas.delete(c.naviMapID) def handleDeleteConnections(self, event): for c in event.connection: naviMap = c.network.naviMap if c.network == naviMap.network(): c.network.naviMap.mapCanvas.delete(c.naviMapID) def printEvent(self, event): print event def clearMap(self): pass mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/.packager.py0000644000175000017500000000547007770420500025145 0ustar debiandebian# The .packager.py is used by the DistTools package to get the files to be # included in a distribution. def getFiles(root, what = 'all', plat=None): """ files <- getFiles(root, what='all', plat=None) files -- list of the files to be included in the download arguments: root -- path to the package. This is used by glob to get all the python modules. what -- string that can be 'all' and 'supported' to specified what files to include in the distribution. By default 'all' the files are added. plat -- platform ('linux2', 'irix646', 'sunos5' etc...) """ import os from glob import glob # 1- Specify the list of all the Python module allPyModule = ["*.py"] # 2- Specify the list of the non supported Python module. These files # will be removed from the release of the supported python modules. pynotsupported = [] # 3- Specify the documentation files and directories to be included in the # release docFiles = []#["doc/"] # 4-Specify the extraFiles to be included in the release. extraFiles = ["CVS", "RELNOTES"] # 5-Specify the testFiles to be included in the release. testFiles = ['Tests/*.py', 'Tests/CVS', 'LICENSE'] ######################################################### ## Where things are done for you . ######################################################### # if some files need to be removed, we need the exact list of the pymodule. if len(pynotsupported): # store the path of the current directory olddir = os.getcwd() os.chdir(root) files = [] # we use glob to get the exact list of files. for p in allPyModule: files = files + glob(p) allPyModule = files files = [] # need to get the list of the files ... no wild card possible. for p in pynotsupported: files = files + glob(p) pynotsupported = files os.chdir(olddir) # Creation of the proper list of files depending on the value of what if what == 'supported' and len(pynotsupported): # need to remove the non supported python files from all the python # files # These are the keys used for to make the releases... supportedFiles = filter(lambda x, l = pynotsupported: not x in l, allPyModule) return supportedFiles + testFiles + extraFiles elif what == 'all' or ( what == 'supported' and not len(pynotsupported)): # Other wise just add the documentation, test and extra files to all # the python modules. allFiles= allPyModule + docFiles + testFiles + extraFiles return allFiles elif what == 'documentation': return docFiles else: return [] mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/RELNOTES0000644000175000017500000014501611102420464024110 0ustar debiandebianNetwork Editor Release Notes ============================= ----------------------------- Release 1.5.4 (November 2008) ----------------------------- itemBase.py - fixed some problems in UserPanel related to the No GUI execution of networks - added NoGuiNetworkBuilder, NoGuiVPE and NoGuiExec classes to have an editor object even when we run withjout the gui. This enables the execution of network with macros as standalone programs - added class Communicator to support socket-based communication with network running as independent process - named the network process-`pid` during remote execution - added debug=False to to node.run() method - added select() to read from stdin and execute commands gotten from there when the network runs in a detached process - introduced button "run saved network without gui" - running network can be stopped with user panel items.py - now for macro network we are looking for the main network name - introduce typeManager for network when running without gui - run network withoout gui with separate stop button - saves raw values (r"") for types.StringType - introduced splitratio for connections - repaired blocking feature to enable data loop - corrected small bug when saving nodes inheriting original node - got rid of the reference to global 'Vision.ed' in OpalUtil net.py -now run pause and stop buttons correspond to the current network -corrected saving categories for webservices - added the clientSocket variable in the main scope of the server when command is executed. This variable point to the socket from which the command came from and can be used to specify where results should be sent back. -added test for empty connection list when calling select (necessary on windows) -added class Communicator to support socket-based communication with network running as independent process - named the network process-`pid` during remote execution - added GANTT diagram for time profile and debug capability - added node.nameInlastSavedNetwork to save name of node in network executed in a remote process - introduce typeManager for network when running without gui - added help to run network without gui - default network are in imediate mode - repaired blocking feature to enable data loop - now Network.freeze simply set runOnNewData to False - added arg -w on command line of stand alone network to run with or without vision ports.py -repaired special ports icon position simpleNE.py -added resourceFolder argument which defaults to 'mgltools' -solved rare problem when node name already exists -changed "continuous run" into "immediate run" -quit open in the correct window when several instance of vision are running widgets.py -repaired griding of widget when it moves between node and param panel -now for macro network we are looking for the main network name -added bindToRemoteProcessNode(), def _procTrigger() and def unbindFromRemoteProcess() to support binding GUI to remote process execution ----------------------------- Releases 1.4.6 - 1.5.2 (July 2008) ----------------------------- --Moved from Numeric to numpy Glyph.py - new file LICENSE - -added TSRI to the copyright part. customizedWidgets.py - mousewheel in scrollabled canvas - now widgetsOnBackWindowsCanGrabFocus is in mglutil.gui datatypes.py - added type 'object' as synonym for 'None' - repaired casting to list itemBase.py - better updateGuiCycle and solved re-run of stopped node - when the network stops, the last node hasn't run properly, so it is marked with forceExecution 1 - corrected error on postedmenu when delete connection - introduced "soft run" in vision - now parameter panel otion is immediate by default - first commit of NOGUI execution of networks - set runOnNewData to True while implementing, got rid of autorun - now parameter panel otion is not immediate by default - now networks runs in saved directory items.py - network don't freeze anymore, just the nodes in it - repaired networkDefaultDirectory - now network reloads the opal web categories - set runOnNewData to True while implementing, got rid of autorun - renamed runOnConnect into runOnNewData and generalized it to new data and set it to false - stop network from running when we rebind widget at loading time - in function node pos args can have widgets too - correct rebind widget - now pmv command can be cut and pasted - repaired saving of RunFunction node - added filebrowser for input files in function node - added combobox for selection in function node - now parameter panel option is not immediate by default - now widget's labels can have ballons - repaired saving of pmvviewernode - now FunctionNode can be saved with importString - readded construct parameter - FunctionNode takes function or string - introduced node Split - now FuntionNode has dynamic output ports - started dynamic output ports - selfGeneratingNode is now FunctionNode and eval is applied on the commandString - now selfgeneratingnode is not self-subclassed but use args on addNode() - corrected menu label - corrected bug in adding input port to doit function - made sure adt and pmv run without vision - introduced networkDefaultDirectory - automated reload of modified libraries - correct reload of overwritten nodes in libraries - user library can be reloaded macros.py - solves issue with saving macro nodes introduced buy noGuiRun - now runOnNewData is using reference_value - set runOnNewData to True while implementing, got rid of autorun net.py - better updateGuiCycle and solved re-run of stopped node - when the network stops, the last node hasn't run properly, - so it is marked with forceExecution 1 - introduced "soft run" in vision - now runOnNewData is using reference_value - now the nodes are not ran anymore when we load the network - corrected save for FunctionNode - adjusted menu shortcuts - removed adding of local path to sys.path - corrected filename for self running network ports.py - objects can retain their current position when reparenting in vision - stop network from running when we rebind widget at loading time - corrected save for FunctionNode - corrected getdescr, originalDatatype is returned only for inputport simpleNE.py - better filter for open and save dialogs - repaired networkDefaultDirectory - runOnNewData has an icon - now we can loadNetwork without taking the focus - now fonts are automatically saved in vision - repaired vision shell - now vision can re-run - argument "ins" can be pass to runVision() - added keyboard shortcuts - better font size on mac osx widgets.py - repaired networkDefaultDirectory - now runOnNewData is using reference_value - renamed runOnConnect into runOnNewData and generalized it to new data and set it to false - added combobox for selection in function node - corrected default value for labelBalloon - corrected save for FunctionNode - now widget's labels can have ballons - removed useless NERadioButton - made sure adt and pmv run without vision - corrected widget filename path on win32 - repaired configure of initialValue ----------------------------- Release 1.4.5 (May 2007) ----------------------------- New features, bug fixes: customizedWidgets.py - introduced widgetsOnBackWindowsCanGrabFocus now, on windows, the widget grab the focus only if the window is already activated. items.py - corrected bug when copying node with deleted port - corrected widget delete when port has been deleted and recreated macros.py - changed contextual menu 'Delete' network into 'Close' to solve bug net.py - now we run the nodes before the connection (in the saved network) ports.py - now, default port data is 'no data yet' simpleNE.py - added "Open Recent" menu widgets.py - introduced node ScrolledList - fixed to set _modified =True in NEEntry Button widget. Now entry widgets in saved networks remember values ----------------------------- Release 1.4.4 (December 2006) ----------------------------- New features, bug fixes: - moved user panels to the network and added delete user panel handling - added datatype 'coord2' - introduced self launchable network depending on the existence of a user panel - moved user panels to the network and added delete user panel handling - removed menubar in user panel, made buttons instead - introduced placer in user panels - repaired bug when deleting connections, the callback can destroy some connections in the list - we don't restore the pmvViewer states on first run (only for regular viewer) - moved n.beforeRemovingFromNetwork() after deleteConnections() - introduced node StickImageNE - we don't run automaticaly the saved network anymore - corrected the writing of launchVisionToRunNetworkAsApplication() - moved user panels to the network and added delete user panel handling - introduced self launchable network depending on the existence of a user panel - introduced placer in user panels - revised saving network as application - a safer saveNetwork() ------------------------------ Release 1.4.3 (September 2006) ------------------------------ New features, changes: - Network connections are highlighted in pink when mouse is over it. - node 'show/hide GUI' is deprecated; - reparenting menu is only for geometry nodes; - modified 'Next parenting' to affect any parenting; - introduced new SelectMultipleGeometry; - improved casting and validation; - suppressed unused ObjectType; - now loading of network in pmv shows both regular and pmv networks at the same time; - introduced pmv vision network as _pmvnet.py; - now when you delete a macro, the currentNetwork doesn't change anymore; Bug fixes: - corrected glflabels when several labels are given; - corrected name proposal when saving new node; - corrected saving of nodes for userlib; ----------------------------- Release 1.4.2 (May 2006) ------------------------------ Changes and bug fixes: Editor.py -corrected small bug to allow node saving when we delete a port -now macroNode only permits single connection ports. -corrected bug when deleting output ports -corrected bug when renaming ports datatypes.py -corrected validate in ArrayType -introduced StringType and ObjectType based on anyarraytype -now node save source code generates necessary lines to load synonyms -introduced Types files per library -corrected getTypeFromClass to return the instance of AnyArrayType for None -add indice2 type itemBase.py -corrected bug related to "show legend" and apply. -corrected bug when closing colormap settings -corrected bug when closing colormap settings items.py -cascade menu for FastLibs in vision toobar, smaller icon for diamond ports, smaller and toggable port's icons in the library GUI -now the nodes are drawn with their ports in the GUI as in the networks -better comments and menu lines on "save source code" -mRequiredTypes and mRequiredSynonyms are class variables in the class node -we prepare the save node to load types and widgets when instanciated (we still need to find a solution to load synonyms). Commented code that displays port in library vision gui -corrected bug when saving colormap node source code, -reduced default colormap node to 16 values -corrected bug on saving doit function when saving macros, -corrected bug on automatic loading of nodes in mydefaultlib -object's states are now saved at the end of the networks, - fixed NetworkNode.outputData to allow outputing None -added try: arround connections in saved networks -dependent code for nodes doesn't save doit if not necessary, when the port name changes, it changes in the code as well -corrected bug when saving colormap node source code net.py -corrected bug on saving doit function when saving macros, -corrected bug on automatic loading of nodes in mydefaultlib -now parenting geometry nodes can be done througth macros -added try except around masternet run() in saved network ports.py -corrected getDataStr to accept single values as Numeric array -changed gray to make not required ports more differentiable -adjusted port's display, now not required ports have a gray outline -input ports outline in vision library gui are black, gray or red. -modifications to prepare drawing the port icons in the vision gui simpleNE.py -added self.pyshell.begin() and self.pyshell.close() Release 1.4.1 (March 2006) ------------------------------- What's New since 1.3alpha2: -------------------------- -In datatypes.py: * moved colorRGB and colorsRGB types to NetworkEditor.datatypes; * moved the type InstanceMatricesType from NetworkEditor to DejaVuNodes; * added support of moving widget to macroParamPanel -In itemBase.py: * replaced 2 calls to c.tk.call by a single one in NetworkItems.run * modified port.getData to always print message if editor.verbose is true and data is bad or missing * modified NetworkNodeBase.computeFunction to turn node outline orange if data is missing * made NetworkItem.run check for 'Stop' status to avoid overwriting orange outline with execution failed outline color -In items.py: * added a check for the existance of initialValue in widgetDescr of unbound widgets when generating source code. * added node.ischild(node) method to find out if a given node is a child of another one. This is used in net.runNodes to remove children nodes of nodes who return 'STOP' * added safeName() method to NetworkNodeBase to remove all bad symbols from node name so we can use node name in saving network. * added NetworkNodeBase.getUniqueNodeName() to return a unique name usable as a variable in a saved network. Changed all definitions of nodeName to call this method instead * modified getNodeSourceCodeForWidgetValue to Set widget with run=1. That way Root nodes will present the data on their output port when restored. * modified net.getNetworkCreationSourceCode to freeze network before connecting nodes and unfreeze after the conections are created. This avoids running the network when it is restored. * replaced inputPorts[] by getInputPortByName() and outputPorts[] by getOutputPortByName() -In net.py: * modified scheduling to propagate node return value. Returning 'stop' prevents children nodes from running again * added new accelerator to freeze/unfreeze the current network. * added self.runOnConnect attribute to network to be able to prevent execution of the hold node upon connection. This was causing macros such as rotateScene to execute when restored from a file * made node2 runn upon deletion of a connection only if port2 is not required. * modified net.deleteConnections to set _newdata to True in input port of child node and schedule the child node's execution * check if the mouse moved only a few pixels upon releasing left mouse button. If we are below a threshold we assume we did not move. This is usefull for deselecting selected nodes for people who don't have a steady hand (who move the mouse when releasing the mouse button, or when the mouse pad is very soft and the mouse moves because it is pressed in the pad...) * added self.runOnConnect attribute to network to be able to prevent execution fo the chold node upon connection. This was causing macros such as rotateScene to execute when restored from a file * modified special connection to use stipple gray75 and with 4 by default * added comment in net.runNodes about status==0 .. I am not sure when this happens if at all .. and if we shoudl do the same as for stop or not * modified runNodes method to no longer break out of the loop running nodes when a node returns 'Stop', but rather remove all the children of this node from the list of nodes to be run. * modified getNodeSourceCodeForWidgetValue to Set widget with run=1. That way Root nodes will present the data on their output port when restored. * modified net.getNetworkCreationSourceCode to freeze network before connecting nodes and unfreeze after the conections are created. This avoids running the network when it is restored. -In ports.py: * changed PortsDescr.append to accept eiter a single dict or a list of kwy=values -In widgets.py: * added newValueCallback method to set _newdata when widget is modified so that portHasNewData works properly. This function is now the command bound to the widgets instead of scheduleNode which is called by newValueCallback. * added lockedOnPort key word for widgets allowing a programmer to disable widget unbinding. This facilitates writting the doit() method of the node as it can be expected that the widget is always there -In Editor.py: * added CodeEditorIdle_python24 class for editing files with python2.5 idlelib.EditorWindow -In macros.py: * made InputPortNode the last root in the list to be run by InputPortNode and removed OutputPortNode from this list as it is not a root ! * modified MacroNetwork.getNodeDefinitionSourceCode to also freeze before creating connections and unfreexing after * modified getHeaderBlock, getInitBlock, getBeforeBlock, getAfterBlock in MacroNode to ignore self.library because when we get source code we want to subclass MacroNode and not the original Macro * BugFixes: --------- -In Editor.py: * older versions of idlelib which may be distributed with Python cause problems with the source code editor. Modified the test in Editor.py so that it will fail when an older idlelib is detected and use the Pmw code editor instead. -In itemBase.py: * fixed frozen color when selecting/unselecting and when coloring by Node Library. Even if the node is colored by library, it is colored blue when frozen. -In items.py: * refresh network did not color the node icons correctly. Frozen color or node library color was lost. This was fixed in buildNodeIcon(). * fixed Node's updateCode() method which as assuming there is no space between doit() and the following self. * fixed safeName to avoid names starting with a number -In macros.py: * saving a macro with no connections would raise an exception. The problem was that we defined "macroNetworkName" inside the test if we have len(connections). Moved the definition outside of the test * autoRun=False was not saved for macro nodes. Fixed this. * added macNet._originalNodes = [] and macNet._originalConnections = [] in MacroNode.buildOriginalList to make sure that we build the list starting from an empty list. This caused a bug in the CoarseMolSurf macro which was written to subclass the UT_IsoDecim macro. Its beforeAddingToNetwork method called the on of UT_IsoDecim which put its nodes into the list. then the call to buildOriginalList at the end of beforeAddingToNetwork of the macro appended to the lsit of nodes from UT_IsoDecim -In widgets.py: * fixed ScrolledText widget to save its value, and 'right-click' to pop up menu. * fixed a bug in TkPortWidget: in the configure method we need to test if tkOptions is not None, since this might be None and then we cannot iterate over it Release September 1, 2004 ------------------------- What's New (compared to release Sep 18, 2003) - added a widget editor to modify the appearance of a widget - added a widget grid editor to modify the grid position of a widget and to modify the label of a widget - invisible ports are no longer taken into account when computing the position of ports. Hiding a port (by binding a widget), or deleting a port will cause all ports to the right to be shifted to the left. - displaying a previously hidden port (by unbinding a widget) triggers moving all ports right of it to the right. - added new method updateIconPosition() to ports.py which moves the port icon and its connections - added new port attribute port.visible which is needed for the new setPortPosX() - made widget background colored - addInputPort() and addOutputPort() methods in items.py now generate unique port names and correctly update node.inputPortsDescr/outputPortsDescr - update ports.py buildIcons() method to be more clever when resize the node icon, added support for SpecialPorts, and most importantly using the correct tags for Tkinter - added new method destroyIcon() to ports.py that destroys port menu and port icons - moved startConnectRubberBand, drawRubberBand, createConnection from class OutputPort to Port so that SpecialPorts (who now inherit from Port and not from InputPort or OutputPort still work) - cleaned up SpecialPorts: - they no longer implement their own buildIcon() and createIcon() method - but they implement a computePortPosX() method now - added a new TreeView widget to introspect a node or data and replaced the idlelib introspect GUI with this new widget, which allows us more flexibility - added autoRun attribute to itemBase; moved frozen attribute from NetworkNode to NetworkItems; added frozenTk and autoRunTk variable to NetworkItems; added toggleAutoRun_cb method to NetworkItems; moved toggleFrozen_cb, freeze and unfreeze methods from NetworkNode to NetworkItems; added check to run method to not run nodes with autoRun==False or frozen==True; added autoRun constructor option to NetworkItems - added new method _setModified() to access the attribute _modified - changed all direct access to _modified to go through this new method - completely rewrote the saving of networks: all methods called by getNodeDefinitionSourceCode() were rewritten. Note: older networks can still be loaded! - no code modification, but an organisatory change: moved code blocks that belong together (code for saving a network, code for generating source code) - added a new attribute _id to NetworkNode which is a unique number and used when saving networks - added configure() and getDescr() to NetworkNode which is now used by save/restore, copy/paste; changed getNodeSourceCodeForModifications() to use the new framework with configure() a node - added new attribute hasMoved which is set to True if the node changes its position in a network - show/hide special ports is now handled by the node's configure() method - showSpecialPorts() and hideSpecialPorts() set node._modified=True - changed attribute value self.specialPortsVisible from 0/1 to True/False - added new method resetTags() to node which resets both _modified and _original - changed all getNodeSourceCodeFor() methods to use _original - reworked the getNodeSourceCodeForPorts() method to make it more human-readable - adding or deleting a port does no longer set the node's _modified to True - deleting or unbinding a widget now sets port._modified=True We use this information now when we create code to save a network in getNodeSourceCodeForWidgets() - saving networks no longer sets widget values by default, but only if the widget value is different from the class defined initialValue - setting widget value now sets widget._modified=True This allows to save/restore macro nodes from node libraries with much less code to be saved (no more setting all widget values again) - updated connection.getSourceCode: we now test if the connection goes to a MacroInput/OutputNode, and whether we have to connect to 'new' or to an existing port (to avoid creating new ports) - macros now use again the getNodeDefinitionSourceCode() method to generate a string to save data - when saving, if we hit a macro node, we first recursively loop over all macro networks and add all the macro nodes, then we start filling the leaf macro networks with nodes, and connect them, then move down the tree towards the root macro(s) - added some "smartness" to saving macros: if the macro comes from a node library and we just change a widget value, we might not have a handle to the node. Thus, if we reach such a case we now check if the node has already be declared, if not, we add a line. - deleted all methods in MacroNetwork since we no longer need them (the editLog is gone) - changed signature of method getNodeSourceCode(): we no longer pass a keyword nodeNumber and forLib - added a new base class NetworkItemBase from which all network items inherit - changed attribute .editor to be a weakref and added mechanism to remain backwards compatible (even though this is a weakref, we can still access it as it would be a normal attribute, we do not need to call it -> not object.editor() - changed access to .editor to use the new method getEditor() - moved the method updateCode() from ports.py to items.py, subclassed this in macros, changed code wherever this was accessed - the paramPanel immediate flag can now be set through the node's configure() method with the new keyword paramPanelImmediate. Also, the node's getDescr() method returns this new keyword. This is used to save the status fo the immediate flag of the param Panel. - added new class UserPanel - added support to UserPanel for moving widgets and labels using arrow keys (after middle clicking to select widget to move) - added try/except statments to saved networks allowing to restore partial networks - made connectNodes accept either port numbers or port names - added 2 metods to node class getInputPortByName and getOutputPortByName - double-clicking on nodes with displayed paramPanel now hides the panel (double-clicking again displays panel, etc.) - added new method getSize() that returns a tuple with (width, height) of this node, in pixels - added blocking attribute to connections to allow for cycles - added a new class CodeEditor that inherits from either idlelib if avialable or from Pmw. SourceCodeEditor and CallbackEditor use this new base class - deleted classes SourceCodeEditorIdle and SourceCodeEditorPmw in Editor.py, fixed methods there to work with the new additions and changes - removed show/hide Param. Panel from node menu, replaced this with a new checkbutton entry named "Parameter Panel", added new variable paramPanelTk, double-clicking on node to show Param. Panel also sets this variable correctly - added a checkbutton under Edit menu to togle splined connections on and off - added toggleSplineConnections call back to betwork for handling checkbutton - added setSplineConnections methos to network to set smooth option on and off - added support to move selected nodes with arrow keys. Pressing arrow ke moves selected subgraph 1 pixel. Pressing SHIFT+Arrow key moves 10 pixels - made dynamic inputports of macrooutput nodes not required - added new accelerators for some operations (such as load/save network), create macro, etc. One can now hit CTRL+l to load a network, etc - moved the code to force node execution upon instanciation from Vision.VPE.endDD to NetworkEditor.net.AddNode so that nodes restored from a file behave the same way as when they are created from a library interactively - clarified definition of 'waiting' for execStatus - set execstatus to running and reset to waiting in run method - removed attempt the acquire and release runlock in toggle pause method as this method calls pause and resume which each do that - added runAgain attribute to handle rerunning the network in case a run was requested while te network is already running - added new method deleteConnection() which accepts "node1, port1, node2, port" (versus deleteConnections() which accepts a list of connections) - added new method nodeIdToNumber(): network nodes now have a unique ID. This method allows to get for a given node the current index in network.nodes - modified connectNodes to test singleConnection is True to support 'auto' as a valid value for this attribute - adding a node to a network tags the network modified - added source code to saved networks to restore user panels Bug Fixes - Fixed ParamPanel applyFun (was node.schedule) which would not trigger node to run, new applyFun is node.schedule_cb, which first sets node.forceExecution=True, then calls node.schedule() - deletePort() method now accepts and properly propagates keyword resize used to resize the node icon - rewrote large parts of itemBase.deletePort() method: - special ports are now correctly treated - proper deletion of port icons - proper handling of node.inputPortsDescr and node.outputPortsDescr - proper computing portposx for remaining ports - proper renumbering etc for remaining ports - fixed a bug in items.py when network is refreshed and special ports are shown the node menu correctly displays now 'hide special ports' - fixed items.py buildIcons() method: specialPorts buildIcons() method is now called here instead in addSpecialPorts() method (before, we would build the icons twice) - deleted the Port attribute "tags", which was the source of many strange problems, for example, after refreshing a network and then moving a node would also move port icons of other nodes, SpecialPorts would not move at all, and others... this is now fixed - fixed a bug when exiting with a expanded macro network: Pmw wants to set the current network after a network was deleted which would fail here since the macro's main network was already deleted from editor.networks. - fixed bug that caused node to run when asked to by the used if autoRun was False - commented out checking for new data in run since this will be done anyways in computeFunction called by run - fixed a bug when exiting with a expanded macro network: Pmw wants to set the current network after a network was deleted which would fail here since the macro's main network was already deleted from editor.networks. - compeletly rewrote the node.move() command, which is now using the network.moveSubGraph method, that also updates connections. - fixed moveSubGraph() method to update connections when nodes move, added new keyword absolute=False, that allows to move the subgraph BY dx,dy or TO dx,dy, when absolute=True - added new method updatePosXPosY() that sets the node's posx and posy this method is used in quite a few instances in net.py - bugfix: node.posx and node.posy were not set correctly. Now, when a node is moved, we call node.updatePosXPosY() to set these two attributes correctly. We use the upper left coordinate of node.outerBox as posx, posy - fixed finally the problem we had that nodes move after adding to a network when they have widgets bound. There was some very old code in buildNodeIcon that moved a node after instanciation, but with wrong values. Fixed this, also set initial posx, posy to 0, moved the 'move' statement from buildIcons to buildNodeIcon where it belongs - fixed a bug in determining the needed nodesize: if the last port was invisible, this would not work properly - set port attribute width, height to default None, updated this in all files - changed behaviour of ports so that they now use the new width, height defined in datatypes to build port icon; also updated datatypes; we no longer need port shapes 'rect1', 'rect2', etc, but just 'rect' since now the user can specify width, height and define the shape. Note: currently we continue to support keywords such as 'rect1', but we ask users to update their datatypes to use the new scheme - refreshing a network would add duplicate menu entries to connections pulldown menu. Fixed this by implementing a destroyIcon() method in NetworkConnection, and in net.py deleteNodes() now calls this method properly (instead of just looping over self.connections and set c.id=None) as was done previously - updated special ports to use proper icon size (8x8, not 4x4) - saving and restoring a node with displayed special ports would fail because we did not update the pulldown entry. Fixed this in items.py: we now call directly the two methods show/hide special ports, rather than calling a callback function. The two methods do implement the proper setting of the pulldownmenu; restoring a network with nodes with special ports connected would fail because in net.py the connectSpecialNodes() method was not updated to find ports by names (instead of numbers) - fixed yet another bug with special ports: if there was a connection, refresh network would not properly restore the connection - fixed saving nodes with unbound widgets (we now save "nodeX.unbindWidget(.." instead of "nodeX.deleteWidget(..." - fixed identation bug in saving networks which could cause endless loop - fixed a bug in getNodeSourceCodeForPorts where ports where not properly deleted if more than 1 port in a row were deleted - bugfix in saving widget values: if the value is not of type string, we should not put the value in quotes. - made checking for 'stop' pause' and 'frozen' case insentitive (scheduling nodes) - fixed a bug in node.computeFunction that caused string arguments passed to dynamic function to be lower-cased - changing the data on a port now also updates the Introspect GUI - fixed a bug in setting the source code: if we use " instead of ' the code is no longer loadable. Using string.replace in setFunction() - fixed a bug in saving source code: since port.getDescr() adds a key _previousWidgetDescr if the widget of this port is unbound, this gets passed to addInputPort(). Updated this method to accept _previousWidgetDescr and add this as an attribute to the port. This allows the port to rebind the widget. - saving a node with a deleted widget now saves a lines to delete the widget, not unbind it - saving a node with an unbound widget now first saves a line to set the widget value to the current value, THEN adds a line to unbind the widget. Therefore, after rebinding the widget, one gets the previous value, not the default value - fixed cut/copy/paste problems with nodes inside macro networks that came from a node library: these nodes are set _original=True and therefore did not generate source code upon cut/copy Fixed this problem by adding a new keyword ignoreOriginal=False to the signature of getNodeDefinitionSourceCode() which overrides the tests in there - saving source code of nodes that came from a node libary (like most nodes) we now inherit from the original node. This fixes a bug where we would loose methods that were defined in the original node. - Moved updating the source code editor GUI into the setFunction() method where it actually should be done. Now, configuring the node with new code automatically updates the GUI. - fixed a bug in copy/paste nodes inside a macro network that came from a node library: we copied the nodes but not the connections because we did not pass ignoreOriginal=True to the connection.getSourceCode() Needed to update methods in macros.py, items.py and net.py - bugfix: if only the compute function inside a macro node from a node libary was modified, we forgot to call the checkIfNodeIsDefined() method to add a line to get a handle to this node. This is now fixed. - the temporary editor attribute _tmpListOfSavedNodes is now an attribute of NetworkBuilder. This change was needed to address the issue above. Needed to update items.py, macros.py, net.py and simpleNE.py to use this new attribute properly - made list of children nodes unique in scheduleChildren() - modified node's rename method to avoid quotes in name, which could cause problems for saving/restoring and copy/paste - bugfix: deleting a node with an open widget editor now deletes this editor - fixed method moveSubGraph which did not move connections properly - fixed a bug in refreshing a network with nodes with widgets bound to node, and the node was expanded. Prior, this would cause the node icon to be redrawn compeletly wrong. Fixed this by hiding the widgets in node before destroying the node. - when we delete nodes, we do not want to update the node code signature. Fixed this in deleteNodes() - fixed a bug in deleteNodes() method: We were looping over the ports and inside the loop we would delete ports from the list we were looping over. This caused all kinds of unpredictable results: for example, deleting a node with open editor windows (such as port editor) would not always delete these windows. - net.py: connectNodes now calls port's afterConnect callback BEFORE the node is scheduled if data is present on the port - before and after connection callbacks can now return a new connection. This allows these callbacks to replace the connection that has been made - check for valid callback name when editing port callback - fixed a bug in deleteConnection(): we could reach a condition where 'conn' was undefined. Set conn=None by default. - prevented macro nodes and MacroInput nodes from executing when they are added to a network (they would execute because they have no inpout ports before connections are created). - made input ports of MacroOutputNode singleConnection='auto' to prevent creating a list of objects when there is a single parent - made stop, run set the execStatus in all macro networks in addition to the current network. This fixes the bug in which stoping a network with an iterate in a Macro node would not stop the iteration Backwards incompatibilities 1) Implemented much better handling of connect and disconnect events. This is incompatible with nodes that implement a beforeConnect(), etc method: Deleted the methods beforeConnect(), afterConnect(), beforeDisconnect() and afterDisconnect(). Added four new keywords to Port constructor: beforeConnect, afterConnect, beforeDisconnect, afterDisconnect. The value for these keywords is a textstring describing the source code for the method to be carried out (similar to the node source code). Updated configure() method of ports to handle these new keywords. Also, fixed configure() methods of ports: the class OutputPort did not use the configure method of the base class. Updated connectNodes() and disconnectNodes() methods in net.py to work with these new changes. Exposed these new features in the port editor: one can now use idlelib or a Pmw text editor to edit the function. Nodes which implement a beforeConnect() etc. method need to be rewritten. For an example, please have a look at Vision/StandardNodes.py class ListOf Release Dezember 17, 2003 ------------------------- What's New (compared to release Sep 18, 2003) - added try except statments around node instanciation code in saved networks This allows restoring networks with problems. - made connectNodes accept either port numbers or port names. The network source code generation now uses the port's naem by default - added 2 metods to node class getInputPortfromPortFromName and getOutputPortfromPortFromName - added a new getDescr() method for port description - added a getSourceCode() method in class NetworkConnection - commented out main menu entries to add/delete categories, to create libraries, to add nodes to libraries. This will be re-activated in a later release (once these things work properly) - added optional argument updateSignature to deletePort method. Was needed for runPmvCommand node - added capability to delete a node proxy icon in a category frame: added deleteNodeFromCategoryFrame() method to class NodeLibrary This method deletes the icon and calls a new rebuildNodesInCategoryFrame() method that first deletes all icons and rebuilds the icons - added new method deleteSmallIcon() to items.py which deletes the node proxy icon in a node library - added capability to change font of various GUI components: - changing the font for node proxies (node icons in node library windows) - changing network node fonts now works (using refresh to rebuild icons) Note: I do not change balloon font for ports. Don't know if I should or not - exposed this functionality in a Form accesible through the Edit menu - added a ChangeFont form to Forms.py - editor.font is now a dictionary that contains the fonts descriptions of the various gui components, also removed attribute editor.libraryFont because this is now stored in editor.font - added kw 'net=None' to refreshNet_cb() method in items.py - added new method getLeafNode() to find leaf node in a nested macro - added a couple more regression tests for load/save macros, delete networks with macros - made network canvas grey75 so white arrows for None type can be seen - added methods isModified() and resetModifiedTag() to items.py - users can no longer save a network if it is an instance of a MacroNetwork to prevent problems (i.e. saving the macro network but not the main net) - changed 'Color Node By Library' so that it will also color/uncolor all nodes already present in all networks - changing the datatype of a port now sets the flag self._modifed=True - changed balloon help in ViPEr so that every GUI element has its own attribute 'balloons'. This fixes some problems where we have to re-create Pmw.Balloon in order to move the yoffset, also this allows us to change the font individually - toolbarbutton.py needed to be updated after this change - turned multi threading off by default - enabled changing widget master in Editor - fixed ParamsForm (widget editor) to only return modified values - fixed ParamsForm to handle boolean properly - allow None as a valid entry for int and float type (need for dial and thumbwheel min and max) - added 'Cancel', 'Apply' buttons to widget editor - made widget editor destroy widget when new widget is built - added dict of currentValues to widget editor so we only add options that are no default values - added a file browser button to the right of the Entry in the WithFileBrowser widgets. This was done because on some Linux machines double-click event are somehow lost on the Entry ! - added menu entry 'Unbind Widget' to port menu. Unbinding a widget will add menu entry 'Rebind Widget' to port icon. This makes un- & rebinding widgets so much easier! - added type boolean to TypeManager - modified validation function of data type to return True instead of 1 - made widget background colored - made widget's label background colored - added beforeConnect(port1, port2) and afterConnect(conn) methods to node - changed frozen color to light cyan which helps reading text in nodes - modified loadNetwork to replace underscores by minus - added borderwidth=3, relief='sunken' , bg='#c3d0a6' to node.iconMaster Bug Fixes - fixed missing applyCmd method in PMW-based code editor - many fixes in saving macro networks. Fixed indentation of nested macros - many fixes for saving widgets - fixed some issues with _previousWidgetDescr - deleting a port now updates the node source code properly - fixed problem in saving code for nodes that come from different files such as done in the regression tests - fixed a bug in setting fonts on Windows machines - fixed deletion of nested macros - fixed a bug in getLibraryImportCode that would fail if a node has no library - fixed a bug in the exit_cb that would fail if a macro network is present (i.e. red ink when exiting ViPEr) - fixed a bug in macros where a macro could not be saved/restored if the macro name had a space in the name - fixed import net from NetworkEditor rather than simpleNE in macros.py - deleting a network with a macro would not delete the macro-subnetwork - fixed creating user libraries and adding nodes to these libraries (Note: this functionality is currently not exposed to users, wait for next release) - fixed a bug in deleting networks where sometimes a macro would not delete its macro network. The problem was that in the Network.delete() method we looped over self.nodes, but subsequently would delete an item from self.nodes, which would result in unpredictable results. Simple fix was to loop over a copy of this list - fixed a bug in copy/paste nodes which occured after changes in the getLibraryImportCode() method (the signature of this method changed) - fixed some bugs in scaling nodes: - we are now using the current node font rather than a system font when scaling (which looked really ugly on some operating systems) - show/hide widgets in node works again (sort of, this needs more work, at least now it does show and hide the widget rather than raise an exception) - resolved many conflicts with tags mixed up: we assigned node.iconMaster to ports (port.iconMaster = node.iconMaster). This caused almost unreproducible problems after 'refresh' was hit: port icons of nodes would move when other nodes were moved, etc - 'refresh' is supposed to reset scaling. However, we would use a wrong relposx so port icons would end up in wrong places after refresh. This was solved by calling setPortPosX() in createIcon() in ports.py - 'refresh' would not reset the scaled port icons. This was because the node's attribute scaleSum was not reset to 1.0 - creating a macro while the option 'Color Node by Library' was turned on would fail. This is now fixed - fixed unbind / rebind widget in class InputPort. Fixed bugs and made methods more clever (show/hide node widgets if specified previously, etc) - fixed default values for NEDial and NEThumbwheel in widgets.py - fixed many things in ParamsForm (the widget editor) - fixed bug which caused specified configuration options to be lost when widget was rebuilt. For instance (size=60, type='int') would loose type - fixed a bug in copy/paste nodes inside a macro network: in the method pasteNetwork_cb() where we execute the code object, we have to pass 'net' for self, rather than 'self' - fixed a bug in cutting selected nodes, where the Cut and Copy buttons would remain active and thus give the user the ability to overwrite the current paste_buffer with an empty list. Cutting now disables the Cut and Copy buttons - fixed a bug in pasting nodes when all networks were deleted. The Paste button still remains active if there is data in the paste buffer, but no exception will be raised if the button is pressed and no network is present. - fixed a bug in cut© nodes to prevent these operations on Macro Input- and Output Nodes of Macro Networks. One can now safely "Select All" nodes in a macro network and cut/copy them without copying these two nodes (which will get automatically deselected now upon cut/copy) Cut/Copy/Paste of Macro nodes of course still works as before - fixed bug that caused new nodes added to a frozen network not to be frozen - fixed a bug in widthFirstChildren - fixed bug in connectNodes. Port descr was assumed to have datatype key. Backwards compatible Changes (renaming of methods, attributes) - renamed method setPortPos() to setPortPosX() - renamed attribute modified to _modified, changed value to Boolean - changed in macro networks how macroinput and macrooutput node are saved - changed method getNodeDefinitionSourceCode() in MacroNode, split this in two halfes, main work is now done in MacroNode.getNodeSourceCode() - major overhaul of saving networks: changed the signature of all methods involved in producing source code for saving networks - changed again how networks are saved. Everything is much easier now, because we add the code to import all necessary libraries at the very beginning. Macro Nodes need no longer to save a beforeAddingToNetwork() method, they no longer have to loop over all ports to import libraries that are needed for port data types, and they don't have to import the libraries again in their afterAddingToNetwork() methods since this is now done at the beginning etc, etc, etc. Removed getLibraryImportCodeHeader method, removed self.libraryImportCache - moved deletePort() method from items to itemBase. Subclass this method in items and in macros Backwards INCOMPATIBLE Changes - split addLibrary method of Network into addLibraryFromName and addLibraryInstance. Addind an instance now requires to provide the module where the instance is defined and the name (both as strings). This was needed to generate the network descdription source code with the correct imports of libraries. - getLibraryImportCode was fixed in net.py and added to MacroNetwork (macros.py) - updated node libraries to reflect these changes Suggested Fix for users: Search and replace 'addLibrary' in existing saved networks with either 'addLibraryFromName' or addLibraryInstance' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Release November 25, 2002 ------------------------- What's New - every node now has its own param. panel. The param. panel has now an 'immediate' checkbutton (on by default: this triggers node execution upon value changes of widgets in the param. panel), an apply button, which forces node execution, and a dismiss button to hide the param. panel - added multi-threaded execution model and added a lock and condition to the network object such that iterate nodes can schedule the excution of sub-networks for each value in their list - added support for cut, copy and paste nodes and connections - added selectAll (nodes) functionality - added mechanism to dynamically load libaries - StandardNodes are now part of the NetworkEditor - added new nodes to StandardNodes: ScrolledText, Multi-Checkbuttons, ChangeBackground (to change the background color of the ViPEr canvas, call method, getattr, setattr, len, iterate - added more documentation to many nodes - added support for moving nodes by clicking and dragging with middle button. When middle button is pressed on top of a node, this node becomes temporarly selected and can be moved until the button is released. When the button is released, the selection is restored. - added the ability to show/hide widgets in nodes by double clicking on the node. - added ability to reshape connections using left mouse button - runNodes checks whether new data is provided on at least 1 input port before executing node. This can be overruled by setting the new forceExecution flag. Inputports now know whether they have new data. - scaling nodes scales the label and hides widgets when node is too small - added eventHandler - added new menus to simpleNE, changed old ones - regression tests were added Bug Fixes - data viewer windows are now destroyed when the corresponding node is deleted - loading and saving networks was completely reworked - many issues in Editor.py were adressed and fixed - nodes now resize according to the ports, the label and the widgets - picking on node has now priority over picking on connections - refresh now restores the highlighting of the selected nodes - fixed bug that was not putting widgets in node to the proper location - moved ballons help down 30 pixels, to avoid flashing when balloon appeared over node - fixed bug that caused widget value to disappear after show/hide/show. Compatibility Information - multi-threaded execution on Silicon Graphics platforms can lead to core dump. Therefore, the user can choose to run single-threaded execution which works fine on SGI. Known Issues - saving networks with macro nodes does not work currently. This will be fixed as soon as possible - Undo is disabled since it is currently broken - Hyperbolic Scaling was removed from the menu since this was never working properly - removed show special ports entry from menu nodes as it is broken mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/widgets.py0000644000175000017500000035426211737373726025005 0ustar debiandebian######################################################################### # # Date: Nov. 2001 Author: Michel Sanner, Daniel Stoffler # # sanner@scripps.edu # stoffler@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner, Daniel Stoffler and TSRI # ######################################################################### #SUGGESTIONS # # - create base class for NEDIAL AND NETHUMBWHEEL # - prefix PortWidget arguments with PW to avoid conflicts with widget args # - might be easier if configure methods were to consume (i.e. pop) the # arguemnts they do handle\ # __doc__ = """ This file defines the class PortWidget which is used to wrap GUI elements that are exposed to the visual programming environemnt as widgets. Widgets can be bound to ports that accept a single value (i.e. a single parent). They are built from a description dictionary using the port.createWidget, modified using port.configureWidget method and deleted using the port.deleteWidget method. The subclasses have to implement the following methods: configure(rebuild=True, **kw) set(value, run=0) getDescr() get() getDataForSaving() The method customizeWidget is called after the actual widget has been created. Every option that can be passed to the constructor has to be listed in the class variable dictionary configOpts. This dictionary is used to create a form for editing the widget's configuration. The keys of this dictionary are the keywords that can be passed to the constructor or the configue method. The configure method has to handle all the keywords of configOpts. In adition it has to recognize all keywords of the widget that is wrapped and configure the widget accordingly. All other arguments have to be ignored. Some options such as master can trigger the rebuilding of a widget. The method returns a tuple (action, descr) where action can be an instance of a new widget or 'rebuild' or 'resize' and descr is widget description modified by the keywords handled by this configuration method. The set() method is used to set the widget's value. This is the only alteration of the widget's state which does not set the widget's modified attribute. When this attribute is set, the widget's description will be saved into a network file. Value CANNOT be set using configure but must be set using the set method. The optional argument run=1/0 can be specified to force or prevent the execution of the node. The getDescr method has to return the complete description of the widget as a dictionary (just like widgets are specified in node defintions). This dictionnary can be passed directly to the widget's constructor. Widget can appear in the node's parameter panel (master=None or 'ParamPanel') or inside the node itself (master='node'). Widgets that appear in nodes can be either displayed or not (node.isexpanded()) Every widget is created in (self.master) along with it's label (if any). The widget itself is created in a frame called widgetFrame created in the constructor. This is done so that the widget and the label can be placed using the grid geometry manager. For widgets in nodes the master is the frame node.nodeWidgetMaster created in node.buildNodeIcon. For widgets in the parameter panel the master is node.paramPanel.widgetFrame. """ import Tkinter, Pmw, warnings import os, types, string import warnings import user from UserDict import UserDict from NetworkEditor.itemBase import NetworkItemsBase from mglutil.gui.BasicWidgets.Tk.thumbwheel import ThumbWheel from mglutil.gui.BasicWidgets.Tk.Dial import Dial from mglutil.gui.BasicWidgets.Tk.vector3DGUI import vectorGUI from mglutil.gui.BasicWidgets.Tk.xyzGUI import xyzGUI from mglutil.gui.BasicWidgets.Tk.multiButton import MultiCheckbuttons from mglutil.gui.BasicWidgets.Tk.multiButton import MultiRadiobuttons from mglutil.gui.BasicWidgets.Tk.multiButton import RegexpGUI from mglutil.gui.BasicWidgets.Tk.customizedWidgets import kbComboBox from mglutil.gui.BasicWidgets.Tk.fileBrowsers import FileOpenBrowser, \ FileSaveBrowser, DirOpenBrowser from mglutil.util.callback import CallbackManager, CallBackFunction from mglutil.util.relpath import abs2rel from mglutil.events import Event from time import time class WidgetValueChange(Event): def __init__(self, network, node, port, widget, value): """ """ self.timestamp = time() self.network = network self.node = node self.port = port self.widget = widget self.value = value class PortWidget(NetworkItemsBase): """base class for network editor widgets port: Port instance to which the widget is bound master: parent widget into which the widget will be created labelCfg: configuration dictionary for a Tkinter.Label (use to anme the widget ## FIXME .... labelSide is now in the widgetGridCfgXXX labelSide: 'left', 'right', 'bottom', 'top'. Describes relative position of label and widget. Is used to compute row and column for gridding. Defaults to left. labelGridCfg: label gridding options (other than 'row' and 'column') widgetGridCfg: label gridding options (other than 'row' and 'column') NEwidgetPosition: {'posx':i, 'posy':j} will be used to compute real row and col initialValue: used to set widget in constructor """ # this dict holds the list of keyword arguments that can be passed to # the constructor or the configure method. The defaultValues are REQUIRED # because they are used to create the widget's attributes configOpts = { 'master': { 'defaultValue':'ParamPanel', 'type':'string', 'description': 'name of the Tk frame in which the widget appears', # 'validValues': ['node', 'ParamPanel'] }, 'NEwidgetPosition': { 'defaultValue':{}, 'type':'dict', 'description': "dict of type { 'posx':i, 'posy':j} use to place\ this NEwidget (label+widget) relative to other NEwidgets (2x2 sub-grid)\ also accepts {'row':i, 'column':j} to specify the real row and column", }, ## 'labelSide': { ## 'defaultValue':'left', 'type':'string', ## 'validValues': ['left', 'right', 'top', 'bottom'], ## }, 'widgetGridCfg': { 'defaultValue': {'labelSide':'left'}, 'type':'dict', 'description': "grid configuration dict for widgetFrame.\ (other than 'row' and 'column').", }, 'labelGridCfg': { 'defaultValue': {}, 'type':'dict', 'description': "grid configuration dict for widget label. \ (other than 'row' and 'column').", }, 'labelCfg': { 'defaultValue':{'text':''}, 'type':'dict', 'description': 'dict of Tkinter.Label options' }, 'labelBalloon': { 'defaultValue':'', 'type':'string', 'description': 'a string used as ballon for the label' }, 'initialValue':{ 'defaultValue':None, 'type':'None', }, 'lockedOnPort':{ 'defaultValue':False, 'type':'boolean', 'description': 'when True this widget cannot be unbound' }, } # dict of option specific to subclasses ownConfigOpts = {} def __init__(self, port, **kw): name = port.name NetworkItemsBase.__init__(self, name) self.widget = None # will hold the actual widget self.objEditor = None # will point to widget editor when there is one self.widgetFrame = None # parent frame for widget # this allows to grid widgetFrame and tklabel in self.master #self.labelSide = None # can be 'left', 'right', 'top', bottom' self.port = port self.lockedOnPort = False # when True widget cannot be unbound self.inNode = False # True if widget appears in Node when displayed self._newdata = False # set to 1 by set method # reset to 0 after node ran self.lastUsedValue = None self._trigger = None # function to be called when widget changes value self.oldmaster = None # used in rebuilt() when widget moves between panels self.tklabel = None # Tkinter Label object used for widget's label self.labelCfg = {} # options for the widget's label self.labelBalloon = None # create all attributs that will not be created by configure because # they do not appear on kw # NOTE: use PortWidget instead self here, else we also get all keys # that were added in Subclasses! for key in PortWidget.configOpts.keys(): v = kw.get(key, None) default = self.configOpts[key]['defaultValue'] if isinstance(default, dict): default = default.copy() setattr(self, key, default) #if v is None: # self.configure will not do anyting for this key # setattr(self, key, self.configOpts[key]['defaultValue']) self.master = kw.get('master', self.master) # name of the master panel # this will be the master for self.widgetFrame node = port.node master = self.master self.inNode = False if master == 'node': self.inNode = True self.masterTk = node.nodeWidgetMaster self._trigger = node.schedule elif master == 'ParamPanel': self.masterTk = node.paramPanel.widgetFrame self._trigger = node.paramPanel.run elif master =='macroParamPanel': self.masterTk = node.network.macroNode.paramPanel.widgetFrame self._trigger = node.schedule elif master in port.network.userPanels.keys(): self.masterTk = port.network.userPanels[master].frame self._trigger = node.schedule else: lNetwork = port.network while hasattr(lNetwork, 'macroNode'): lNetwork = lNetwork.macroNode.network if master in lNetwork.userPanels.keys(): self.masterTk = lNetwork.userPanels[master].frame self._trigger = node.schedule break else: #we didn't break warnings.warn("%s is not a legal master for a widget"%master) return # create label self.tklabel = apply( Tkinter.Label, (self.masterTk,), self.labelCfg) self.tklabel.bind("", self.postWidgetMenu) self.tklabel.configure(bg='#c3d0a6') apply( self.configure, (False,), kw) # configure without rebuilding # create widget frame self.widgetFrame = Tkinter.Frame(self.masterTk) self.gridLabelAndWidget() # create menu to access port and widget editors and config panel # got rid of useless tearoff self.menu = Tkinter.Menu(port.node.getEditor(), title='Widget Menu', tearoff=False) self.menu.add_separator() if master in port.network.userPanels.keys(): self.menu.add_command(label='Label Editor', underline=0, command=self.label_cb) self.menu.add_command(label='Port Editor', underline=0, command=port.edit) self.menu.add_command(label='Widget Editor', underline=0, command=self.edit) if not self.lockedOnPort: self.menu.add_command(label='Unbind Widget', underline=0, command=self.port.unbindWidget) # we need to add an empty panel menu here, since we will delete it # in postWidgetMenu and rebuild it self.panelmenu = Tkinter.Menu(self.menu, tearoff=0) self.menu.add_cascade(label='Move to', menu=self.panelmenu, underline=0) self.vEditor = port.getEditor() self.labelWidgetEditor = None def bindToRemoteProcessNode(self): # save curret _trigger self._oldTrigger = self._trigger # build format string to change widget value in remote network port = self.port node = port.node self._cmdStr = '%s.inputPorts[%d].widget.set(' % ( node.nameInlastSavedNetwork,port.number) self._trigger = self._procTrigger def _procTrigger(self): value = self.get() if type(value)==types.StringType: cmd = self._cmdStr + "'%s', run=0)"%value else: cmd = self._cmdStr + str(value) + ', run=0)' import os procid = os.getpid() print 'SENDING', procid, cmd self.port.network.remoteProcessSocket.send(cmd) def unbindFromRemoteProcess(self): self._trigger = self._oldTrigger #del self._cmdStr def moveWidgetToPanel(self, name): #print "moveWidgetToPanel" self.widgetGridCfg.pop('row', None) self.widgetGridCfg.pop('column', None) self.labelGridCfg.pop('row', None) self.labelGridCfg.pop('column', None) if name == 'Node': name = 'node' self.configure(master=name) def postWidgetMenu(self, event=None): """Display menu that allows to display configuration panel or start port or widget editor""" #print "postWidgetMenu" self.panelmenu.delete(0, 'end') #self.menu.delete(self.menu.index('Move to')) #self.menu.add_cascade(label='Move to', menu=self.panelmenu) cb = CallBackFunction( self.moveWidgetToPanel, 'Node') self.panelmenu.add_command(label='Node', command=cb, underline=0) cb = CallBackFunction( self.moveWidgetToPanel, 'ParamPanel') self.panelmenu.add_command(label='ParamPanel', command=cb, underline=1) if hasattr(self.port.node.network, 'macroNode'): cb = CallBackFunction( self.moveWidgetToPanel, 'macroParamPanel') self.panelmenu.add_command(label='MacroParamPanel', command=cb, underline=2) for name in self.port.network.userPanels.keys(): cb = CallBackFunction( self.moveWidgetToPanel, name) self.panelmenu.add_command(label=name, command=cb) lNetwork = self.port.network while hasattr(lNetwork, 'macroNode'): lNetwork = lNetwork.macroNode.network for name in lNetwork.userPanels.keys(): cb = CallBackFunction( self.moveWidgetToPanel, name) self.panelmenu.add_command(label=name, command=cb) self.menu.post(event.x_root, event.y_root) def getWidgetGridCfg(self): """ select the right widgetGridCfg dictionary based on the panel by default we use se;f.widgetGridCfg, but then check if there is an entry in the node's widgetDescr that is specific for the master (i.e. 'node', 'ParamPanel' or a user panel """ gridCfg = self.widgetGridCfg # check for panel specific gridCfg descr = self.port.node.widgetDescr[self.name] if descr.has_key('widgetGridCfg'+self.master): gridCfg = descr['widgetGridCfg'+self.master] #print 'FFFFFFFF found panel grid', self.master, gridCfg return gridCfg def gridLabelAndWidget(self): #print "gridLabelAndWidget" if self.master is not None and self.master in self.port.network.userPanels.keys(): return if self.widgetFrame is None: return # if row and column are specified they are in widgetGridCfg # self.labelGridCfg can be used to specify other gridding options such # as stick, etc port = self.port # default gridCfg is widgetGridCfg #self.widgetGridCfg ## WARNING self.widgetGridCfg is the default but we use a panel ## specific dictionary if we find one ## WE always find one for user panels # get the right widgetGridCfg dict gridCfg = self.getWidgetGridCfg() # find out how many rows and columns self.port.editor.master.update() x,y = self.masterTk.grid_size() hasrow = hascol = True labelSide = gridCfg.pop('labelSide', 'left') if not gridCfg.has_key('row'): # programmer has not provided a row, pack label's rowspan down if # labelSide is 'top' gridCfg['row'] = y if labelSide=='top': gridCfg['row'] += self.labelGridCfg.get('rowspan', 1) hasrow = False if not gridCfg.has_key('column'): # programmer has not provided a column, pack in column 'columspan' # of label if labelSide is left, else pack at column 0 gridCfg['column'] = 0 if labelSide=='left': gridCfg['column'] += self.labelGridCfg.get('columnspan', 1) hascol = False # The label will be placed using this row and column info from # widgetGridCfg and labelSide self.labelGridCfg['row'] = gridCfg['row'] self.labelGridCfg['column'] = gridCfg['column'] #print 'HHHHHHHHHHHHH', self.name, self.master, labelSide, hasrow, hascol, x, y, gridCfg if labelSide == 'left': # subtract columspan for widget's column dx = self.labelGridCfg.get('columnspan', 1) self.labelGridCfg['column'] -= dx # if label would be grided at a negative column index and no column # was specified by the programmer for the widget, we put the # label at 0 and the widget at the label's columnspan if self.labelGridCfg['column']<0: self.labelGridCfg['column'] = 0 gridCfg['column'] = dx # grid the label if len(self.labelCfg['text']): apply( self.tklabel.grid, (), self.labelGridCfg) # grid the widget apply( self.widgetFrame.grid, (), gridCfg) elif labelSide == 'right': # add columspan for widget's column dx = self.labelGridCfg.get('columnspan', 1) self.labelGridCfg['column'] += dx # grid the label if len(self.labelCfg['text']): apply( self.tklabel.grid, (), self.labelGridCfg) # grid the widget apply( self.widgetFrame.grid, (), gridCfg) elif labelSide == 'top': # subtract rowspan for widget's row dx = self.labelGridCfg.get('rowspan', 1) self.labelGridCfg['row'] -= dx # if label would be grided at a negative row index and no row # was specified by the programmer for the widget, we put the # label at 0 and the widget at the label's rowspan if self.labelGridCfg['row']<0: self.labelGridCfg['row'] = 0 gridCfg['row'] = dx # grid the label if len(self.labelCfg['text']): apply( self.tklabel.grid, (), self.labelGridCfg) # grid the widget apply( self.widgetFrame.grid, (), gridCfg) elif labelSide == 'bottom': # add rowspan for widget's row dx = self.labelGridCfg.get('rowspan', 1) self.labelGridCfg['row'] += dx # grid the label if len(self.labelCfg['text']): apply( self.tklabel.grid, (), self.labelGridCfg) # grid the widget apply( self.widgetFrame.grid, (), gridCfg) else: warnings.warn("%s illegal labelSide"%labelSide) gridCfg['labelSide'] = labelSide def edit(self, event=None): """start widget editor""" if self.objEditor is None: form = WidgetEditor(self, None) self.objEditor = form if self.port.objEditor is not None: self.port.objEditor.editWidgetVarTk.set(1) def label_cb(self): #print "label_cb" if self.labelWidgetEditor is None: self.labelWidgetEditor = LabelWidgetEditor(panel=self.master, widget=self) else: if self.labelWidgetEditor.master.winfo_ismapped() == 0: self.labelWidgetEditor.master.deiconify() self.labelWidgetEditor.master.lift() def destroy(self): # hara kiri #if self.inNode and self.port.node.isExpanded(): #self.port.node.hideInNodeWidgets() self.tklabel.destroy() self.widgetFrame.destroy() if self.labelWidgetEditor is not None: self.labelWidgetEditor.master.destroy() self.labelWidgetEditor = None # kill circular references: self.port = None self.objEditor = None self.widget = None self.master = None self.menu = None def configure(self, rebuild=True, **kw): ## handles all keywords found in self.configOpts ## returns action, rebuildDescr where action is either None, 'rebuild' ## or 'resize' ## and rebuildDescr is the decsription dict modified for ## the keywords handled by this configure method ## for attributes that are of type dict make copies action = None # might become 'rebuild' or 'resize' if self.widget is None: rebuildDescr = kw.copy() else: rebuildDescr = self.getDescr().copy() rebuildDescr.update(kw) gridit = False # handle labelGridCfg first because handling master keyword might call # panel.getPackingDict which needs self.labelGridCfg to be up to date v = kw.get('labelGridCfg', None) if v: self.labelGridCfg.update(v) # update rebuildDescr rebuildDescr['labelGridCfg'] = self.labelGridCfg gridit = True widgetPlacerCfg = kw.get('widgetPlacerCfg', None) if widgetPlacerCfg: # and self.widgetFrame: # update rebuildDescr rebuildDescr['widgetPlacerCfg'] = widgetPlacerCfg for k, v in kw.items(): if k == 'master': if self.master not in self.port.network.userPanels.keys() \ or v != self.master: # the last part (or v != self.master) # has been hadded otherwise you need to say twice that you want # to move the widget from the user panel to the node action = 'rebuild' # go from string to Tk widget # self.master = self.port.node.findWidgetMasterTk(v) # if v!='node' and v!='ParamPanel': # panel = self.port.editor.panelFromName(v) # if panel: # descr = rebuildDescr.get('widgetGridCfg'+v, None) # if descr is None: # # default value for labelSide is oldmaster's # # labelSide. We get the widgetGridCfg of old master # labelSide = self.getWidgetGridCfg()['labelSide'] # pd = panel.getPackingDict(self, labelSide) # rebuildDescr['widgetGridCfg'+v] = pd # elif not descr.has_key('row') or \ # not descr.has_key('column'): # labelSide = descr.pop('labelSide', 'left') # pd = panel.getPackingDict(self, labelSide) # rebuildDescr['widgetGridCfg'+v].update(pd) self.oldmaster = self.master self.master = v rebuildDescr['master'] = v elif k == 'initialValue': rebuildDescr[k] = self.initialValue = v elif k == 'lockedOnPort': rebuildDescr[k] = self.lockedOnPort = v ## elif k=='labelSide': ## val = self.labelSide ## if action!='rebuild': ## if val in ['left', 'right'] and v not in ['left', 'right']: ## action = 'resize' ## if val in ['top', 'bottom'] and v not in ['top', 'bottom']: ## action = 'resize' ## rebuildDescr['labelSide'] = v #self.labelSide = v ## gridit = True elif k == 'widgetGridCfg': self.widgetGridCfg.update(v) rebuildDescr['widgetGridCfg'] = self.widgetGridCfg gridit = True elif k == 'labelCfg': if self.master not in self.port.network.userPanels.keys(): if len(self.labelCfg['text'])==0 and len(v['text'])>0: apply( self.tklabel.grid, (), self.labelGridCfg) if len(self.labelCfg['text'])>0 and len(v['text'])==0: self.tklabel.grid_forget() self.labelCfg.update(v) rebuildDescr['labelCfg'] = self.labelCfg apply( self.tklabel.configure, (), v) if self.master not in self.port.network.userPanels.keys(): gridit = True if self.inNode and action!='rebuild': action = 'resize' elif k == 'labelBalloon': # add balloon string to label self.labelBalloon = v if self.labelBalloon is not None: self.port.editor.balloons.bind(self.tklabel, self.labelBalloon) rebuildDescr['labelBalloon'] = self.labelBalloon elif k == 'NEwidgetPosition': assert isinstance(v, dict) self.NEwidgetPosition.update(v)# = v.copy() rebuildDescr['NEwidgetPosition'] = self.NEwidgetPosition gridit = True elif k[:13]=='widgetGridCfg' and len(k)>13: # widget panels grid configuration dicts descr = self.port.node.widgetDescr[self.name] if descr.has_key(self.name): descr[self.name].update(v) else: self.port.node.widgetDescr[self.name][k] = v gridit = True labelSide = kw.get('labelSide', None) if labelSide: warnings.warn( "'labelSide' in widgetDescr of node %s is deprecated, put labelSide in 'widgetGridCfg'"%self.port.node.name) widgetGridCfg = self.getWidgetGridCfg() widgetGridCfg['labelSide'] = labelSide if action!='rebuild': if labelSide in ['left', 'right'] and labelSide not in [ 'left', 'right']: action = 'resize' if labelSide in ['top', 'bottom'] and labelSide not in [ 'top', 'bottom']: action = 'resize' gridit = True if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) if gridit: if self.widget is not None: if self.tklabel is not None: self.tklabel.grid_forget() self.widgetFrame.grid_forget() self.gridLabelAndWidget() gridit = False if action=='resize' and rebuild: if gridit: if self.widget is not None: if self.tklabel is not None: self.tklabel.grid_forget() self.widgetFrame.grid_forget() self.gridLabelAndWidget() self.port.node.autoResize() gridit = False if action is None \ and self.widget is not None \ and kw.has_key('initialValue'): self.set(kw['initialValue'], 0); if gridit: self.gridLabelAndWidget() self._setModified(True) return action, rebuildDescr def rebuild(self, rebuildDescr): #print "rebuild", rebuildDescr # if the master has changed and the widget was ina userPanel # remove widget from list in userPanel value = self.get() addWidgetToPanel = False if self.oldmaster != rebuildDescr['master']: ## wdescr = self.port.node.widgetDescr[self.name] ## gcfg = wdescr.get('widgetGridCfg', None) ## if gcfg is not None: ## if not gcfg.has_key('row'): ## rebuildDescr.pop('row', None) ## if not gcfg.has_key('column'): ## rebuildDescr.pop('column', None) ## else: ## rebuildDescr.pop('row', None) ## rebuildDescr.pop('column', None) if self.oldmaster in self.port.network.userPanels.keys(): self.port.network.userPanels[self.oldmaster].deleteWidget(self) if rebuildDescr['master'] in self.port.network.userPanels.keys(): addWidgetToPanel = True port = self.port # unbind widget (this destroys the old widget) port.unbindWidget() # delete the not needed previousWidgetDescr port._previousWidgetDescr = None # create new widget port.createWidget(descr=rebuildDescr) # create new widget port.node.autoResize() if port.node.inNodeWidgetsVisibleByDefault and not \ port.node.isExpanded(): port.node.toggleNodeExpand_cb() #newWidget = apply(self.__class__, (self.port,), rebuildDescr) #newWidget._setModified(True) #newWidget.port = self.port newWidget = port.widget if self.objEditor: self.objEditor.widget = newWidget newWidget.objEditor = self.objEditor if addWidgetToPanel: panel = port.editor.panelFromName(newWidget.master) if panel: if rebuildDescr.has_key('widgetPlacerCfg'): widgetPlacerCfg = rebuildDescr['widgetPlacerCfg'] else: widgetPlacerCfg = None panel.addWidget(newWidget, widgetPlacerCfg=widgetPlacerCfg) newWidget.set(value,0) return newWidget, rebuildDescr def getDescr(self): # returns the widget's description dictionary # such a dictionary masterName = None if self.inNode: masterName = 'node' elif self.masterTk==self.port.node.paramPanel.widgetFrame: masterName = 'ParamPanel' else: node = self.port.node if hasattr(node.network, 'macroNode'): if self.masterTk==node.network.macroNode.paramPanel.widgetFrame: masterName = 'macroParamPanel' else: masterName = self.master else: if self.master in self.port.network.userPanels.keys(): masterName = self.master descr = { 'class':self.__class__.__name__, # go from Tk widget to string #'master': self.port.node.findWidgetMasterName(self), 'master': masterName, #'labelSide': self.labelSide, 'initialValue': self.initialValue, } if len(self.labelGridCfg.keys()): descr['labelGridCfg'] = self.labelGridCfg if len(self.widgetGridCfg.keys()): descr['widgetGridCfg'] = self.widgetGridCfg descr['labelCfg'] = self.labelCfg for k,v in self.port.node.widgetDescr[self.name].items(): if k[:13]=='widgetGridCfg' and len(k)>13: descr[k] = v widgetPlacerCfg = self.widgetFrame.place_info() if widgetPlacerCfg.has_key('relx') and widgetPlacerCfg.has_key('rely'): descr['widgetPlacerCfg'] = {'relx': widgetPlacerCfg['relx'], 'rely': widgetPlacerCfg['rely'] } return descr def set(self, value, run=1): self._setModified(True) self._newdata = True if self._trigger and run and self.port.network.runOnNewData.value is True: self._trigger(value) def get(self): # has to be implemented by subclass return None def getDataForSaving(self): # this method is called when a network is saved and the widget # value needs to be saved # by default, it returns what the widget.get method returns # it can be subclassed by widgets in order to provide data that # is different from what the widget.get method returns return self.get() def scheduleNode(self): self._setModified(True) # setting widget is a _modified event if self._trigger and self.port.network.runOnNewData.value is True: self._trigger() def newValueCallback(self, event=None): #print "PortWidget.newValueCallback" ed = self.port.network.getEditor() value = self.get() ed.dispatchEvent( WidgetValueChange(self.port.network, self.port.node, self.port, self, value) ) self._newdata = True self.scheduleNode() def compareToOrigWidgetDescr(self): """Compare this widget to the widgetDescr defined in a given network node base class and return a dictionary with the differences """ #print "PortWidget.compareToOrigWidgetDescr", self ownDescr = self.getDescr().copy() lConstrkw = {'masternet': self.port.network} lConstrkw.update(self.port.node.constrkw) dummy = apply(self.port.node.__class__,(),lConstrkw) # we need the base class node origWidgetDescr = self.__class__.configOpts.copy() nodeWidgetDescr = dummy.widgetDescr[self.port.name].copy() # create a defaults dict to compare agains for labelGridCfg labelGridDefaults = dict( rowspan=1, columnspan=1, sticky='w', padx=0, pady=0, ipadx=0, ipady=0) # update it with whatever we find in the nodeWidgetDescr if nodeWidgetDescr.has_key('labelGridCfg'): labelGridDefaults.update(nodeWidgetDescr['labelGridCfg'] ) # create a defaults dict to compare agains for widgetGridCfg # FIXME: DIFFERENT ROWS AND COLUMNS FOR DIFFERENT LABELSIDE VALUES! widgetGridDefaults = dict( row=0, column=1, rowspan=1, columnspan=1, sticky='w', padx=0, pady=0, ipadx=0, ipady=0, labelSide='left', labelCfg=dict(text='')) # update it with whatever we find in the nodeWidgetDescr if nodeWidgetDescr.has_key('widgetGridCfg'): widgetGridDefaults.update(nodeWidgetDescr['widgetGridCfg']) descr = {} # compare to widget definitions in node for k,v in ownDescr.items(): if k == 'initialValue': if nodeWidgetDescr.has_key('initialValue'): if v == nodeWidgetDescr['initialValue']: continue elif v == origWidgetDescr['initialValue']['defaultValue']: continue else: descr[k] = v continue elif k == 'labelCfg': if nodeWidgetDescr.has_key('labelCfg'): if v['text'] == nodeWidgetDescr['labelCfg']['text']: continue elif origWidgetDescr.has_key('labelCfg'): if v['text'] == origWidgetDescr['labelCfg']['defaultValue']['text']: continue descr[k] = v continue # labelGridCfg row and column are always automatically computed # by self.gridLabelAndWidget, using 'labelSide' elif k == 'labelGridCfg': tmpdict = {} for tk, tv in v.items(): if tk == 'row': continue elif tk == 'column': continue else: if tv != labelGridDefaults[tk]: tmpdict[tk] = tv if len(tmpdict.keys()): descr[k] = tmpdict continue elif k == 'widgetGridCfg' or k == 'widgetGridCfg%s'%self.master: tmpdict = {} for tk, tv in v.items(): if tk == 'row': continue elif tk == 'column': continue else: if tv != widgetGridDefaults[tk]: tmpdict[tk] = tv if len(tmpdict.keys()): descr[k] = tmpdict continue if k in nodeWidgetDescr.keys(): if v != nodeWidgetDescr[k]: descr[k] = v # compare to default configuration options of widget elif k in origWidgetDescr.keys(): if v != origWidgetDescr[k]['defaultValue']: descr[k] = v # not found in either, so we have to add it else: descr[k] = v return descr class LabelWidgetEditor: """class to manipulate the widget label in user panels """ def __init__(self, panel, widget): #print "LabelWidgetEditor.__init__" self.master = Tkinter.Toplevel() self.master.title('Widget Label Editor') self.master.protocol("WM_DELETE_WINDOW", self.master.withdraw) self.widget = widget self.panel = panel labelNameTk = Tkinter.StringVar() labelNameTk.set(widget.labelCfg['text']) labelSideTk = Tkinter.StringVar() labelSideTk.set(widget.widgetGridCfg['labelSide']) self.labelName = Tkinter.Label(self.master, text='name:') self.labelName.grid(row=0, column=0, sticky='w') self.entryName = Tkinter.Entry(self.master, width=10, textvariable=labelNameTk ) self.entryName.grid(row=0, column=1, sticky='we') self.entryName.bind('', self.renameWidget_cb) self.comboSide = Pmw.ComboBox( self.master, label_text='side:', labelpos='w', entryfield_value=self.widget.widgetGridCfg['labelSide'], scrolledlist_items=['left', 'right', 'top', 'bottom'], selectioncommand=self.resideWidget_cb, history=False ) self.comboSide.grid(row=0, column=3, sticky='we') def renameWidget_cb(self, event=None): self.widget.labelCfg['text'] = self.entryName.get() self.widget.tklabel['text'] = self.widget.labelCfg['text'] self.widget.port.network.userPanels[self.panel].rePlaceWidget(self.widget) def resideWidget_cb(self, event=None): #print "resideWidget_cb" lComboSide = self.comboSide.get() if lComboSide in ['left','right','top','bottom']: self.widget.widgetGridCfg['labelSide'] = self.comboSide.get() self.widget.port.network.userPanels[self.panel].rePlaceWidget(self.widget) else: self.comboSide.set(self.widget.widgetGridCfg['labelSide']) class NEThumbWheel(PortWidget): """NetworkEditor wrapper for Thumbwheel widget. Handles all PortWidget arguments and all Thumbwheel arguments except for value. Name: default: callback None canvasCfg {} continuous 1 height 40 increment 0.0 lockContinuous 0 lockBMin 0 lockBMax 0 lockBIncrement 0 lockIncrement 0 lockMin 0 lockMax 0 lockOneTurn 0 lockPrecision 0 lockShowLabel 0 lockType 0 lockValue 0 min None max None oneTurn 360. orient 'horizontal' precision 2 reportDelta 0 showLabel 1 type 'float' wheelLabCfg {} width 200 wheelPad 6 """ # this dict holds the list of keyword arguments that can be passed to # the constructor or the configure method. The defaultValues are REQUIRED # because they are used to create the widget's attributes configOpts = PortWidget.configOpts.copy() configOpts['initialValue'] = { 'defaultValue':0.0, 'type':'float', } ownConfigOpts = { 'callback': { 'defaultValue':None, 'type': 'None', 'description':"???", }, 'canvasCfg':{ 'defaultValue':{}, 'type':'dict', 'description': "???" }, 'continuous': { 'defaultValue':True, 'type':'boolean', 'description':"", }, 'height':{ 'defaultValue':40, 'min':5, 'max':500, 'type':'int', 'description': "Thumbwheel's height" }, 'increment': { 'defaultValue':0.0, 'type':'float', 'description':"", }, 'lockContinuous': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockBMin': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockBMax': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockBIncrement': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockIncrement': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockMin': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockMax': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockOneTurn': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockPrecision': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockShowLabel': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockType': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockValue': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'min': { 'defaultValue':None, 'type':'float', 'description':"", }, 'max': { 'defaultValue':None, 'type':'float', 'description':"", }, 'oneTurn': { 'defaultValue':360., 'type':'float', 'description':"horizontal of vertical.", }, 'orient': { 'defaultValue':'horizontal', 'type':'string', 'description':"Can bei 'horizontal' or 'vertical' or None", 'validValues':['horizontal', 'vertical', None], }, 'precision': { 'defaultValue':2, 'type':'int', 'validValues': [0,1,2,3,4,5,6,7,8,9], 'description':"", }, 'reportDelta': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'showLabel': { 'defaultValue':True, 'type':'boolean', 'description':"", }, 'type': { 'defaultValue':'float', 'type':'string', 'validValues': ['float', 'int'], 'description':"", }, 'wheelLabCfg':{ 'defaultValue':{}, 'type':'dict', 'description': "???" }, 'width':{ 'defaultValue':200, 'min':10, 'max':500, 'type':'int', 'description': "Thumbwheel's width" }, 'wheelPad':{ 'defaultValue':6, 'min':1, 'max':500, 'type':'int', 'description': "width of border around thumbwheel in pixels" }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): ## # create all attributes that will not be created by configure because ## # they do not appear on kw ## for key in self.ownConfigOpts.keys(): ## v = kw.get(key, None) ## if v is None: # self.configure will not do anyting for this key ## setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled by NEThumbweel and not by PortWidget widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) # call base class constructor apply( PortWidget.__init__, ( self, port), kw) # create the Thumbwheel widget self.widget = apply( ThumbWheel, (self.widgetFrame,), widgetcfg) self.widget.callbacks.AddCallback(self.newValueCallback) # rename Options Panel to port name self.widget.opPanel.setTitle("%s : %s"%(port.node.name, port.name) ) # overwrite right mouse button click self.widget.canvas.bind("", self.postWidgetMenu) self.widget.valueLabel.bind("", self.postWidgetMenu) # add menu entry to open configuration panel self.menu.insert_command(0, label='Option Panel', underline=0, command=self.toggleOptionsPanel) # register new callback for widget's optionsPanel Apply button # NOTE: idf.entryByName is at this time not built for k in self.widget.opPanel.idf: name = k.get('name', None) if name and name == 'ApplyButton': k['command'] = self.optionsPanelApply_cb elif name and name == 'OKButton': k['command'] = self.optionsPanelOK_cb # first, set initial value, else, if we have a min or max, the node # could run, because such keywords can affect the value if self.initialValue is not None: self.set(self.widget.type(self.initialValue), run=0) # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) def configure(self, rebuild=True, **kw): # call base class configure with rebuild=Flase. If rebuilt is needed # rebuildDescr will contain w=='rebuild' and rebuildDescr contains # modified descr action, rebuildDescr = apply( PortWidget.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k in self.ownConfigOpts.keys(): if k in ['width', 'height', 'wheelPad', 'orient']: action = 'rebuild' rebuildDescr[k] = v else: widgetOpts[k] = v if len(widgetOpts): apply( self.widget.configure, (), widgetOpts) if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: # if widget exists action = None return action, rebuildDescr def set(self, value, run=1): self._setModified(True) self.widget.setValue(value) self._newdata = True if run: self.scheduleNode() def get(self): return self.widget.get() def optionsPanelOK_cb(self, event=None): # register this widget to be modified when opPanel is used self.widget.opPanel.OK_cb() self._setModified(True) def optionsPanelApply_cb(self, event=None): # register this widget to be modified when opPanel is used self.widget.opPanel.Apply_cb() self._setModified(True) def toggleOptionsPanel(self, event=None): # rename the options panel title if the node name or port name has # changed. self.widget.opPanel.setTitle( "%s : %s"%(self.port.node.name, self.port.name) ) self.widget.toggleOptPanel() def getDescr(self): cfg = PortWidget.getDescr(self) for k in self.ownConfigOpts.keys(): if k == 'type': # type has to be handled separately _type = self.widget.type if _type == int: _type = 'int' else: _type = 'float' if _type != self.ownConfigOpts[k]['defaultValue']: cfg[k] = _type continue val = getattr(self.widget, k) if val != self.ownConfigOpts[k]['defaultValue']: cfg[k] = val return cfg class NEDial(PortWidget): """NetworkEditor wrapper for Dial widget. Handles all PortWidget arguments and all Dial arguments except for value. Name: default: callback None continuous 1 increment 0.0 lockContinuous 0 lockBMin 0 lockBMax 0 lockBIncrement 0 lockIncrement 0 lockMin 0 lockMax 0 lockOneTurn 0 lockPrecision 0 lockShowLabel 0 lockType 0 lockValue 0 min None max None oneTurn 360. precision 2 showLabel 1 size 50 type 'float' """ configOpts = PortWidget.configOpts.copy() configOpts['initialValue'] = { 'defaultValue':0.0, 'type':'float', } ownConfigOpts = { 'callback': { 'defaultValue':None, 'type': 'None', 'description':"???", }, 'continuous': { 'defaultValue':True, 'type':'boolean', 'description':"", }, 'increment': { 'defaultValue':0.0, 'type':'float', 'description':"", }, 'lockContinuous': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockBMin': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockBMax': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockBIncrement': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockIncrement': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockMin': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockMax': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockOneTurn': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockPrecision': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockShowLabel': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockType': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'lockValue': { 'defaultValue':False, 'type':'boolean', 'description':"", }, 'min': { 'defaultValue':None, 'type':'float', 'description':"", }, 'max': { 'defaultValue':None, 'type':'float', 'description':"", }, 'oneTurn': { 'defaultValue':360., 'type':'float', 'description':"", }, 'precision': { 'defaultValue':2, 'type':'int', 'description':"number of decimals used in label", }, 'showLabel': { 'defaultValue':True, 'type':'boolean', 'description':"", }, 'size':{ 'defaultValue': 50, 'min':20, 'max':500, 'type':'int' }, 'type': { 'defaultValue':'float', 'type':'string', 'validValues': ['float', 'int'], 'description':"", }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): ## # create all attributes that will not be created by configure because ## # they do not appear on kw ## for key in self.ownConfigOpts.keys(): ## v = kw.get(key, None) ## if v is None: # self.configure will not do anyting for this key ## setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled by NEThumbweel and not by PortWidget widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) # call base class constructor apply( PortWidget.__init__, ( self, port), kw) # create the Dial widget self.widget = apply( Dial, (self.widgetFrame,), widgetcfg) self.widget.callbacks.AddCallback(self.newValueCallback) # rename Options Panel to port name self.widget.opPanel.setTitle("%s : %s"%(port.node.name, port.name) ) # overwrite right mouse button click self.widget.canvas.bind("", self.postWidgetMenu) # add menu entry to open configuration panel self.menu.insert_command(0, label='Option Panel', underline=0, command=self.toggleOptionsPanel) # register new callback for widget's optionsPanel Apply button # NOTE: idf.entryByName is at this time not built for k in self.widget.opPanel.idf: name = k.get('name', None) if name and name == 'ApplyButton': k['command'] = self.optionsPanelApply_cb elif name and name == 'OKButton': k['command'] = self.optionsPanelOK_cb # first set default value, in case we have a min or max, else the # node would run if self.initialValue is not None: self.set(self.widget.type(self.initialValue), run=0) # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) self._setModified(False) # will be set to True by configure method def configure(self, rebuild=True, **kw): # call base class configure with rebuild=Flase. If rebuilt is needed # rebuildDescr will contain w=='rebuild' and rebuildDescr contains # modified descr action, rebuildDescr = apply( PortWidget.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k in self.ownConfigOpts: if k =='size': action = 'rebuild' rebuildDescr[k] = v else: widgetOpts[k] = v if len(widgetOpts): apply( self.widget.configure, (), widgetOpts) if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: # if widget exists action = None return action, rebuildDescr def set(self, value, run=1): self._setModified(True) self.widget.setValue(value) self._newdata = True if run: self.scheduleNode() def get(self): return self.widget.get() def optionsPanelOK_cb(self, event=None): # register this widget to be modified when opPanel is used self.widget.opPanel.OK_cb() self._setModified(True) def optionsPanelApply_cb(self, event=None): # register this widget to be modified when opPanel is used self.widget.opPanel.Apply_cb() self._setModified(True) def toggleOptionsPanel(self, event=None): # rename the options panel title if the node name or port name has # changed. self.widget.opPanel.setTitle( "%s : %s"%(self.port.node.name, self.port.name) ) self.widget.toggleOptPanel() def getDescr(self): cfg = PortWidget.getDescr(self) for k in self.ownConfigOpts.keys(): if k == 'type': # type has to be handled separately _type = self.widget.type if _type == int: _type = 'int' else: _type = 'float' if _type != self.ownConfigOpts[k]['defaultValue']: cfg[k] = _type continue val = getattr(self.widget, k) if val != self.ownConfigOpts[k]['defaultValue']: cfg[k] = val return cfg class TkPortWidget(PortWidget): """base class for basic Tkinter widgets such as Entry or Button these widget all have an attribute tkwidget which is the actual Tkinter widget they wrap.""" configOpts = PortWidget.configOpts.copy() def __init__(self, port, **kw): # this dict is used to save tk options applied to the widget # such as width, height, bg, etc.... self.widgetDescr = {} apply( PortWidget.__init__, (self, port), kw) # create a Tkvariable to store thwidget's state self.var = None def configure(self, rebuild=True, **kw): # handle all Tkinter keywords for self.widget action, rebuildDescr = apply( PortWidget.configure, (self, 0), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} tkOptions = self.widget.configure() for k, v in kw.items(): # skip base class keywords if k in PortWidget.configOpts: continue # check if it is a Tk option for this Tk widget if tkOptions is not None and k in tkOptions: widgetOpts[k] = v if len(widgetOpts): self.widgetDescr.update(widgetOpts) apply( self.widget.configure, (), widgetOpts) if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None return action, rebuildDescr def set(self, value, run=1): self._setModified(True) self.var.set(value) self._newdata = True if run: self.scheduleNode() def get(self): return self.var.get() def getDescr(self): cfg = PortWidget.getDescr(self) self.widgetDescr.pop('master', None) cfg.update(self.widgetDescr) return cfg class NEEntry(TkPortWidget): """widget wrapping a Tkinter Entry widget. Additional constructor arguments are any Tkinter.Entry arguments. """ configOpts = TkPortWidget.configOpts.copy() ownConfigOpts = {} ownConfigOpts['initialValue'] = { 'defaultValue':'', 'type':'string', } configOpts.update(ownConfigOpts) def __init__(self, port, **kw): # call base class constructor apply( TkPortWidget.__init__, (self, port), kw) self.var = Tkinter.StringVar() widgetcfg = {'textvariable':self.var} # create the Entry widget self.widget = apply( Tkinter.Entry, (self.widgetFrame,), widgetcfg ) self.widget.bind('', self.newValueCallback) #self.widget.bind('', self.newValueCallback) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widget.pack(side='left') # configure without rebuilding to avoid enless loop apply( self.configure, (False,), kw) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method def configure(self, rebuild=True, **kw): action, rebuildDescr = apply( TkPortWidget.configure, (self, 0), kw) # this methods just creates a resize action if width changes if self.widget is not None: if 'width' in kw: action = 'resize' if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) if action=='resize' and rebuild: self.port.node.autoResize() return action, rebuildDescr def get(self): val = self.var.get() if val: self._setModified(True) # replace " by ' because we wil use " for setting the widget value val = val.replace('"', "'") return val class NEEntryNotScheduling(NEEntry): def newValueCallback(self, event=None): #print "NEEntryNotScheduling.newValueCallback" self._newdata = True class NEEntryWithDirectoryBrowser(NEEntry): """widget wrapping a Tkinter Entry widget used to specify a file name. double clicking on the entry opens a directory browser. Additional constructor arguments are title, initialDir and any Tkinter.Entry argument. """ configOpts = NEEntry.configOpts.copy() ownConfigOpts = { 'title':{ 'defaultValue':'Choose Directory:', 'type':'string' }, } try: from Vision import networkDefaultDirectory ownConfigOpts.update({ 'initialDir':{ 'defaultValue':networkDefaultDirectory, 'type':'string' }, }) except ImportError: pass configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # create all self.title filetypes and initialDir with default values # if they are not specified in kw for key in self.ownConfigOpts.keys(): v = kw.get(key, None) if v is None: # self.configure will not do anyting for this key setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled only by this widget and not by base class # remove them from kw widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) self.initialDir = None # call base class constructor apply( NEEntry.__init__, (self, port), kw) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) # create the FleBrowser button self.FBbutton = Tkinter.Button(self.widgetFrame, text='...', relief='raised', command=self.getDirFromBrowser ) #self.FBbutton.grid(row=0, column=2) self.FBbutton.pack(side='right') # bind right mouse button click #self.widget.bind("", self.postWidgetMenu) self.widget.pack() # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) # bind function to display file browser # THIS DOES NOT WORK ON LINUX MACHINES self.widget.bind('', self.getDirFromBrowser) # create the file browser object (this does not display anything) self.getDirObj = DirOpenBrowser( parent=self.masterTk, title=self.title) self._setModified(False) # will be set to True by configure method def configure(self, rebuild=True, **kw): action, rebuildDescr = apply( NEEntry.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k=='title': self.title = v elif k=='initialDir': self.initialDir = v if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None return action, rebuildDescr def getDirFromBrowser(self, event=None): # display the directory from the browser # print "getDirFromBrowser" if self.port.network.filename is not None: lNetworkDir = os.path.dirname(self.port.network.filename) elif hasattr(self.port.network, 'macroNode') \ and self.port.network.macroNode.network.filename is not None: lNetworkDir = os.path.dirname(self.port.network.macroNode.network.filename) else: import Vision if hasattr(Vision, 'networkDefaultDirectory'): lNetworkDir = Vision.networkDefaultDirectory else: lNetworkDir = '.' self.getDirObj.lastDir = lNetworkDir folder = self.getDirObj.get() if folder: folder = os.path.abspath(folder) folder = abs2rel(folder, base=lNetworkDir) self.set(value=folder, run=0) self.scheduleNode() class NEEntryWithFileBrowser(NEEntry): """widget wrapping a Tkinter Entry widget used to specify a file name. double clicking on the entry opens a file browser. Additional constructor arguments are filetypes, title, initialDir and any Tkinter.Entry argument. """ configOpts = NEEntry.configOpts.copy() ownConfigOpts = { 'filetypes':{ 'defaultValue':[('all','*')], 'type':'string', 'description':"list of tuples defining files types" }, 'title':{ 'defaultValue':'Choose File:', 'type':'string' }, } try: from Vision import networkDefaultDirectory ownConfigOpts.update({ 'initialDir':{ 'defaultValue':networkDefaultDirectory, 'type':'string' }, }) except ImportError: pass configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # create all self.title filetypes and initialDir with default values # if they are not specified in kw for key in self.ownConfigOpts.keys(): v = kw.get(key, None) if v is None: # self.configure will not do anyting for this key setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled only by this widget and not by base class # remove them from kw widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) # call base class constructor apply( NEEntry.__init__, (self, port), kw) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) # create the FleBrowser button self.FBbutton = Tkinter.Button(self.widgetFrame, text='...', relief='raised', command=self.getFileFromBrowser ) #self.FBbutton.grid(row=0, column=2) self.FBbutton.pack(side='right') # bind right mouse button click #self.widget.bind("", self.postWidgetMenu) self.widget.pack() # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) # bind function to display file browser # THIS DOES NOT WORK ON LINUX MACHINES self.widget.bind('', self.getFileFromBrowser) # create the file browser object (this does not display anything) self.getFileObj = FileOpenBrowser(parent = self.masterTk, #lastDir=self.initialDir, title=self.title, filetypes=self.filetypes) self._setModified(False) # will be set to True by configure method def configure(self, rebuild=True, **kw): action, rebuildDescr = apply( NEEntry.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k=='filetypes': self.filetypes = v elif k=='title': self.title = v elif k=='initialDir': self.initialDir = v if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None return action, rebuildDescr def getFileFromBrowser(self, event=None): # display the file browser #print "getFileFromBrowser" if self.port.network.filename is not None: lNetworkDir = os.path.dirname(self.port.network.filename) elif hasattr(self.port.network, 'macroNode') \ and self.port.network.macroNode.network.filename is not None: lNetworkDir = os.path.dirname(self.port.network.macroNode.network.filename) else: import Vision if hasattr(Vision, 'networkDefaultDirectory'): lNetworkDir = Vision.networkDefaultDirectory else: lNetworkDir = '.' self.getFileObj.lastDir = lNetworkDir file = self.getFileObj.get() if file: file= os.path.abspath(file) file = abs2rel(file, base=lNetworkDir) self.set(value=file, run=0) self.scheduleNode() class NEEntryWithFileSaver(NEEntryWithFileBrowser): """widget wrapping a Tkinter Entry widget used to specify a file name. double clicking on the entry opens a SAVE file browser. Additional constructor arguments are filetypes, title, initialDir and any Tkinter.Entry argument. """ def __init__(self, port, **kw): apply( NEEntryWithFileBrowser.__init__, (self, port), kw) self.getFileObj = FileSaveBrowser( #lastDir=self.initialDir, title=self.title, filetypes=self.filetypes) class NECheckButton(TkPortWidget): """widget wrapping a Tkinter Entry widget. Additional constructor arguments are any Tkinter.Checkbutton arguments. """ configOpts = TkPortWidget.configOpts.copy() ownConfigOpts = { 'initialValue':{ 'defaultValue':0, 'type':'int', 'validValues': [0,1]}, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # call base class constructor apply( TkPortWidget.__init__, (self, port), kw) self.var = Tkinter.IntVar() widgetcfg = {'variable':self.var, 'command':self.newValueCallback } # configure without rebuilding to avoid enless loop #apply( self.configure, (False,), widgetcfg) # create the Checkbutton widget self.widget = apply( Tkinter.Checkbutton, (self.widgetFrame,), widgetcfg ) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widget.pack() # configure without rebuilding to avoid enless loop #guillaume: this call make the test hang (test_vizlib and test_flextreelib) #apply( self.configure, (False,), kw) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method def set(self, value, run=1): self._setModified(True) if value: self.var.set(1) else: self.var.set(0) self._newdata = True if run: self.scheduleNode() class NEButton(TkPortWidget): """widget wrapping a Tkinter Button widget. Additional constructor arguments are any Tkinter.Button arguments. """ configOpts = TkPortWidget.configOpts.copy() def __init__(self, port, **kw): # call base class constructor apply( TkPortWidget.__init__, (self, port), kw) self.var = Tkinter.IntVar() widgetcfg = {} cmd = kw.pop('command',None) if cmd is None: cmd = self.newValueCallback widgetcfg['command'] = cmd # configure without rebuilding to avoid enless loop #apply( self.configure, (False,), widgetcfg) # create the Button widget self.widget = apply( Tkinter.Button, (self.widgetFrame,), widgetcfg ) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widget.pack() # configure without rebuilding to avoid enless loop apply( self.configure, (False,), kw) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method ### useless, use NECheckbutton instead ##class NERadiobutton(TkPortWidget): ## """widget wrapping a Tkinter Radiobutton widget. ##Additional constructor arguments are any Tkinter.Radiobutton arguments. ##""" ## ## configOpts = TkPortWidget.configOpts.copy() ## ## def __init__(self, port, **kw): ## ## # call base class constructor ## apply( TkPortWidget.__init__, (self, port), kw) ## ## self.var = Tkinter.StringVar() ## widgetcfg ={'textvariable':self.var} ## widgetcfg.update( {'command':self.newValueCallback } ) ## ## # configure without rebuilding to avoid enless loop ## #apply( self.configure, (False,), widgetcfg) ## ## # create the Checkbutton widget ## self.widget = apply( Tkinter.Radiobutton, (self.widgetFrame,), ## widgetcfg ) ## # bind right mouse button click ## self.widget.bind("", self.postWidgetMenu) ## self.widget.pack() ## ## # configure without rebuilding to avoid enless loop ## apply( self.configure, (False,), kw) ## ## if self.initialValue is not None: ## self.set(self.initialValue, run=0) ## ## self._setModified(False) # will be set to True by configure method class PmwPortWidget(PortWidget): """base class for wrapping basic PMW widgets such as ComboBox """ configOpts = PortWidget.configOpts.copy() def __init__(self, port, **kw): # this dict is used to save Pmw widget options applied to the widget self.widgetDescr = {} apply( PortWidget.__init__, (self, port), kw) def configure(self, rebuild=True, **kw): # handle all Pmw keywords for self.widget action, rebuildDescr = apply( PortWidget.configure, (self, 0), kw) # handle ownConfigOpts that create an action if self.widget is not None: widgetOpts = {} for k, v in kw.items(): # skip base class keywords if k in PortWidget.configOpts: continue # we need to look at components widgets too comps = k.split('_') w = self.widget for c in comps[:-1]: w = self.widget.component(c) allowedOptions = w.configure() # check if it is a Tk option for this Tk widget if comps[-1] in allowedOptions: widgetOpts[k] = v if len(widgetOpts): self.widgetDescr.update(widgetOpts) apply( self.widget.configure, (), widgetOpts) if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None return action, rebuildDescr def getPmwOptions(self, widget, options): """returns the options that can be passed to a Pmw widget. Note: in the future, this method could be more clever and check what options are available in the Pmw widget and only allow these options as keys""" pmwkw = {} for k,v in options.items(): if k in ['labelSide', 'widgetGridCfg', 'labelGridCfg']: continue elif k == 'widgetGridCfg%s'%widget.master: continue elif k in widget.configOpts: continue else: pmwkw[k] = v return pmwkw class NEComboBox(PmwPortWidget): """widget wrapping a Pmw ComboBox widget. Additional constructor arguments are choices, fixed, any Pmw.ComboBox arguments. """ configOpts = PmwPortWidget.configOpts.copy() ownConfigOpts = { 'choices':{ 'defaultValue':[], 'type':'list', 'description':"list of values to choose from in dropdown", }, 'fixedChoices':{ 'defaultValue':0, 'type':'boolean', 'description':"when 1 only entries in list can be chosen", }, 'autoList':{ 'defaultValue':False, 'type':'boolean', 'description':"""when True the list of choices is generated when the node runs, and therefore there is no need to save the entire list. Only the the current value will be saved.""", }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # get all arguments handled only by this widget and not by base class # remove them from kw widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) combokw = { 'dropdown' : kw.pop('dropdown', True) } # call base class constructor apply( PmwPortWidget.__init__, (self, port), kw) for key in self.configOpts.keys(): v = kw.get(key, None) if v is None: # self.configure will not do anyting for this key setattr(self, key, self.configOpts[key]['defaultValue']) # get all combobox arguments # replace the selection command cmd = kw.pop('selectioncommand', None) if cmd: self.selectionCommand = cmd else: self.selectionCommand = None combokw['selectioncommand'] = self.set d = self.getPmwOptions(self, kw) combokw.update(d) # prevent Pmw from creating a label combokw['labelpos'] = None # now, create widget if combokw['dropdown'] is True: self.widget = apply( kbComboBox, (self.widgetFrame,), combokw) else: self.widget = apply( Pmw.ComboBox, (self.widgetFrame,), combokw) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widget.pack(side='left', anchor='n', fill='x')#, expand=1, padx=8, pady=8) # configure without rebuilding to avoid enless loop # now remove selectioncommand and labelspos, they are not part of Pmw del combokw['selectioncommand'] del combokw['labelpos'] del combokw['dropdown'] widgetcfg.update(combokw) apply( self.configure, (False,), widgetcfg) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method def configure(self, rebuild=True, **kw): #print "NEComboBox.configure", kw # handle all Tkinter keywords for self.widget action, rebuildDescr = apply( PmwPortWidget.configure, (self, 0), kw) # handle ownConfigOpts that create an action if self.widget is not None: PmwOptions = self.widget.configure() for k, v in kw.items(): # check if it is a Tk option for this Tk widget if k in ['entryfield_entry_width', 'entry_width']: action = 'resize' elif k == 'choices': self.setlist(v) elif k == 'fixedChoices': self.fixedChoices = v elif k == 'autoList': self.autoList = v if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None self.port.node.autoResize() return action, rebuildDescr def set(self, value, run=1): self._setModified(True) if value is None: return if value is '' and run: self.scheduleNode() return # once we get here Pmw has already added entry to list if it was # not there if self.fixedChoices: allValues = self.choices else: allValues = self.widget.get(0, 'end') self.choices = allValues newval = False if value in allValues: self.widget.selectitem(value) newval = True self._newdata = True else: self.widget.setentry(value) if (len(allValues) > 0) and (value != ''): warnings.warn("attribute %s not in the list of choices"%value) #self.widget.setentry('') # clear entry #self.setlist(self.choices) # remove from list if newval and run: if callable(self.selectionCommand): self.selectionCommand(value) self.scheduleNode() def setlist(self, choices): self.widget.component('scrolledlist').setlist(choices) self.choices = choices[:] self._setModified(True) def getlist(self): return self.choices[:] def get(self): return self.widget.get() def getDescr(self): cfg = PmwPortWidget.getDescr(self) cfg['fixedChoices'] = self.fixedChoices cfg['autoList'] = self.autoList if self.autoList is False: cfg['choices'] = self.choices # else: # cfg['choices'] = [self.widget.get()] cfg.update(self.widgetDescr) return cfg class NEVectorGUI(PortWidget): """NetworkEditor wrapper for vector3DGUI widget/ Handles all PortWidget arguments and all vector3DGUI arguments except for vector. Name: default: name vector size 200 continuous 1 mode XY precision 5 lockContinuous 0 lockPrecision 0 lockMode 0 callback None """ # description of parameters that can only be used with the widget's # constructor configOpts = PortWidget.configOpts.copy() configOpts['initialValue'] = { 'defaultValue':[1.,0,0], 'type':'list', } ownConfigOpts = { 'name':{ 'defaultValue':'vector', 'type':'string', 'description':'title of widget', }, 'size':{ 'defaultValue':200, 'min':100, 'max':500, 'type':'int', 'description': "GUI size"}, 'continuous': { 'defaultValue':1, 'type':'boolean', 'description':"", }, 'mode': { 'defaultValue':'XY', 'type':'string', 'validValues':['XY', 'X', 'Y', 'Z'], 'description':"any of XY, X, Y, Z", }, 'precision': { 'defaultValue':5, 'type':'int', 'min':1, 'max':10, 'description':'this is used only for display purpose.' }, 'lockContinuous': { 'defaultValue':0, 'type':'boolean', 'description':"", }, 'lockPrecision': { 'defaultValue':0, 'type':'boolean', 'description':"", }, 'lockMode': { 'defaultValue':0, 'type':'boolean', 'description':"", }, 'callback': { 'defaultValue':None, 'type': 'None', 'description':"???", }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # create all attributes that will not be created by configure because # they do not appear on kw for key in self.ownConfigOpts.keys(): v = kw.get(key, None) if v is None: # self.configure will not do anyting for this key setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled by NEThumbweel and not by PortWidget widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) # call base class constructor apply( PortWidget.__init__, ( self, port), kw) # create the vectorGUI widget self.widget = apply( vectorGUI, (self.widgetFrame,), widgetcfg) self.widget.callbacks.AddCallback(self.newValueCallback) # bind right mouse button click self.widget.canvas.bind("", self.postWidgetMenu) # register new callback for widget's optionsPanel Dismiss button # NOTE: idf.entryByName is at this time not built for k in self.widget.opPanel.idf: name = k.get('name', None) if name and name == 'DismissButton': k['command'] = self.optionsPanelDismiss_cb break # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method def get(self): return self.widget.vector def set(self, vector, run=1): self._setModified(True) self.widget.setVector(vector) self._newdata = True if run: self.scheduleNode() def optionsPanelDismiss_cb(self, event=None): # register this widget to be modified when opPanel is used self.widget.opPanel.Dismiss_cb() self._setModified(True) def configure(self, rebuild=True, **kw): # call base class configure with rebuild=Flase. If rebuilt is needed # rebuildDescr will contain w=='rebuild' and rebuildDescr contains # modified descr action, rebuildDescr = apply( PortWidget.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k in self.ownConfigOpts.keys(): if k in ['size']: action = 'rebuild' rebuildDescr[k] = v else: widgetOpts[k] = v if len(widgetOpts): apply( self.widget.configure, (), widgetOpts) if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: # if widget exists action = None return action, rebuildDescr def getDescr(self): cfg = PortWidget.getDescr(self) for k in self.ownConfigOpts.keys(): val = getattr(self.widget, k) if val != self.ownConfigOpts[k]['defaultValue']: cfg[k] = val return cfg class NEMultiCheckButtons(PortWidget): """This class builds multi-checkbutton panel. Checkbuttons are packed in a grid, the name of the button is used as label. valueList is used to add the checkbuttons: data is stored in tuples. First entry is a string of the button name. Optional second entry is a dictionary with Tkinter checkbutton options. Optional third entry is a dictionary with grid options. Name: default: valueList [] callback None sfcfg {} immediate 1 """ # description of parameters that can only be used with the widget's # constructor configOpts = PortWidget.configOpts.copy() ownConfigOpts = { 'callback': { 'defaultValue':None, 'type': 'None', 'description':"???", }, 'valueList': { 'defaultValue':[], 'type': 'list', 'description':"every list entry corresponds to a checkbutton", }, 'sfcfg': { 'defaultValue':{}, 'type': 'dict', 'description':"scrolled frame Tkinter configuration dict", }, 'immediate': { 'defaultValue':1, 'type': 'boolean', 'description':"if set to 0, checking a button does not call the \ callback", }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # create all attributes that will not be created by configure because # they do not appear on kw for key in self.ownConfigOpts.keys(): v = kw.get(key, None) if v is None: # self.configure will not do anyting for this key setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled by NEThumbweel and not by PortWidget widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) # call base class constructor apply( PortWidget.__init__, (self, port), kw) # create the MultiCheckbutton widget self.widget = apply( MultiCheckbuttons, (self.widgetFrame,),widgetcfg ) self.widget.callback = self.newValueCallback # bind right mouse button click self.widget.bind("", self.postWidgetMenu) # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method self.reGUI = None # the little gui for regular expression syntax self.addMyMenu() # this adds a new menu entry to the param.panel menu def set(self, data, run=0): if data is not None: self._setModified(True) for name in data: self.widget.buttonDict[name]['button'].var.set(1) self._newdata = True if run: self.port.node.paramPanel.forceRun() def get(self, event=None): return self.widget.get() def getDataForSaving(self, event=None): result = [] onButtons = self.widget.get(mode='Vision') for name,value in onButtons: if value == 1: result.append(name) return result def configure(self, rebuild=True, **kw): # call base class configure with rebuild=Flase. If rebuilt is needed # rebuildDescr will contain w=='rebuild' and rebuildDescr contains # modified descr action, rebuildDescr = apply( PortWidget.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k in self.ownConfigOpts.keys(): if k == 'checkbuttonNames': self.widget.rebuild(v) else: widgetOpts[k] = v if len(widgetOpts): apply( self.widget.configure, (), widgetOpts) return action, rebuildDescr def getDescr(self): cfg = PortWidget.getDescr(self) data = self.widget.get() dt = [] for val in data: dt.append(val[0]) cfg['checkbuttonNames'] = dt return cfg def addMyMenu(self): """Add menu entry to param.Panel menu""" paramPanel = self.port.node.paramPanel parent = paramPanel.mBar Special_button = Tkinter.Menubutton(parent, text='Special', underline=0) paramPanel.menuButtons['Special'] = Special_button Special_button.pack(side=Tkinter.LEFT, padx="1m") # got rid of useless tearoff Special_button.menu = Tkinter.Menu(Special_button, tearoff=False) Special_button.menu.add_separator() Special_button.menu.add_command(label='Check All', underline=0, command=self.widget.checkAll) Special_button.menu.add_command(label='Uncheck All', underline=0, command=self.widget.uncheckAll) Special_button.menu.add_command(label='Invert All', underline=0, command=self.widget.invertAll) Special_button.menu.add_separator() Special_button.menu.add_command(label='Regexp', underline=0, command=self.toggleRegexpGUI) Special_button['menu'] = Special_button.menu apply( paramPanel.mBar.tk_menuBar, paramPanel.menuButtons.values() ) def toggleRegexpGUI(self, event=None): if self.reGUI is None: self.reGUI = RegexpGUI(callback=self.widget.reSelect) else: self.reGUI.toggleVisibility() ### use NECombobox instead ## class NEMultiRadioButtons(TkPortWidget): ## """This class builds multi-radiobutton panel. Radiobuttons are packed in ## a grid, the name of the button is used as label. valueList is used to add ## the checkbuttons: data is stored in tuples. First entry is a string of ## the button name. Optional second entry is a dictionary with Tkinter ## checkbutton options. Optional third entry is a dictionary with grid ## options.""" ## def __init__(self, port=None, master=None, ## visibleInNodeByDefault=0, callback=None, ## label={'text':'File'}, labelSide='left', ## gridcfg=None, **kw): ## TkPortWidget.__init__(self, port, master, visibleInNodeByDefault, ## callback, label, labelSide, gridcfg) ## kw['callback'] = callback ## self.tkwidget = apply( MultiRadiobuttons, (self.top,), kw ) ## if labelSide in ['right', 'e', 'bottom', 's']: ## self.createLabel() ## def set(self, data, run=0): ## index = self.tkwidget.getIndex(data) ## self.tkwidget.buttonDict.values()[0]['button'].var.set(index) ## if run: ## self.port.node.paramPanel.forceRun() ## def get(self, event=None): ## return self.tkwidget.get() ## def getDataForSaving(self, event=None): ## currentVal = self.tkwidget.buttonDict.values()[0]['button'].var.get() ## name = self.tkwidget.buttonList[currentVal][0] ## return name ## def configure(self, **kw): ## if len(kw)==0: # we are saving ## cfg = PortWidget.configure(self) ## data = self.tkwidget.get() ## dt = [] ## for val in data: ## dt.append(val[0]) ## cfg['checkbuttonNames'] = dt ## #if hasattr(self.port.node, 'mode'): ## # cfg['mode'] = self.port.node.mode ## return cfg ## else: # we are loading ## if kw.has_key('checkbuttonNames'): ## data = kw['checkbuttonNames'] ## self.tkwidget.rebuild(data) ## #if kw.has_key('mode'): ## # self.port.node.mode = kw['mode'] class NEEntryField(PmwPortWidget): """ expose a PMW Entryfield as a Vision Widget """ configOpts = PortWidget.configOpts.copy() configOpts['initialValue'] = { 'defaultValue':"", } ownConfigOpts = { 'validate':{ 'defaultValue':{'validator' : 'alphanumeric'}, 'description':"widget width", }, 'dtype':{'defaultValue':str}, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # get all arguments handled only by this widget and not by base class # remove them from kw widgetcfg = {} self.validate = {'validator':'alphanumeric'} self.dtype = str for k in self.ownConfigOpts.keys(): if k in kw: v = kw[k] widgetcfg[k] = kw.pop(k) setattr(self, k, v) # call base class constructor apply( PmwPortWidget.__init__, (self, port), kw) validate = widgetcfg.pop('validate', None) # create widget self.widget = apply( Pmw.EntryField, (self.widgetFrame,), {'validate':validate}) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widgetFrame.bind("", self.postWidgetMenu) self.widget.pack(padx=5, pady=5, fill='both', expand=1) # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method # overwrite the callback function of the parameter panel's apply button self.port.node.paramPanel.applyFun = self.applyButton_cb def applyButton_cb(self, event=None): """This callback is called when the Apply button of the parameter panel is pressed. If we simply call node.schedule_cb() we do not modify the node and the value of the widget is not saved""" val = self.get() self.set(val) def get(self): data = self.widget.getvalue() return self.dtype(data) def set(self, value, run=1): if value is None: return self._setModified(True) self._newdata = True self.widget.setentry(value) if self.port.network.runOnNewData.value is True and run: self.port.node.schedule() def configure(self, rebuild=True, **kw): # handle all Tkinter keywords for self.widget action, rebuildDescr = apply( PmwPortWidget.configure, (self, 0), kw) # handle ownConfigOpts that create an action if self.widget is not None: PmwOptions = self.widget.configure() for k, v in kw.items(): # check if it is a Tk option for this Tk widget if k in ['hull_width', 'hull_height']: action = 'resize' elif k == 'initialValue': rebuildDescr['initialValue'] = self.initialValue = v if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None return action, rebuildDescr def getDescr(self): cfg = PmwPortWidget.getDescr(self) cfg['validate'] = self.validate cfg['dtype'] = self.dtype cfg.update(self.widgetDescr) return cfg class NEScrolledText(PmwPortWidget): configOpts = PortWidget.configOpts.copy() configOpts['initialValue'] = { 'defaultValue':"", 'type':'string', } ownConfigOpts = { 'hull_width':{ 'defaultValue':50, 'type':'int', 'description':"widget width", }, 'hull_height':{ 'defaultValue':50, 'type':'int', 'description':"hull height", }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # get all arguments handled only by this widget and not by base class # remove them from kw widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: v = kw[k] widgetcfg[k] = kw.pop(k) setattr(self, k, v) # call base class constructor apply( PmwPortWidget.__init__, (self, port), kw) scrollkw = self.getPmwOptions(self, kw) # create widget self.widget = apply( Pmw.ScrolledText, (self.widgetFrame,), scrollkw) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widgetFrame.bind("", self.postWidgetMenu) self.widget.pack(padx=5, pady=5, fill='both', expand=1) # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method # overwrite the callback function of the parameter panel's apply button self.port.node.paramPanel.applyFun = self.applyButton_cb def applyButton_cb(self, event=None): """This callback is called when the Apply button of the parameter panel is pressed. If we simply call node.schedule_cb() we do not modify the node and the value of the widget is not saved""" val = self.get() self.set(val) def get(self): data = self.widget.get() return data def set(self, value, run=1): if value is None: return self._setModified(True) self._newdata = True self.widget.settext(value) if self.port.network.runOnNewData.value is True and run: self.port.node.schedule() def configure(self, rebuild=True, **kw): # handle all Tkinter keywords for self.widget action, rebuildDescr = apply( PmwPortWidget.configure, (self, 0), kw) # handle ownConfigOpts that create an action if self.widget is not None: PmwOptions = self.widget.configure() for k, v in kw.items(): # check if it is a Tk option for this Tk widget if k in ['hull_width', 'hull_height']: action = 'resize' elif k == 'initialValue': rebuildDescr['initialValue'] = self.initialValue = v if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: action = None return action, rebuildDescr def getDescr(self): cfg = PmwPortWidget.getDescr(self) cfg['hull_width'] = self.hull_width cfg['hull_height'] = self.hull_height cfg.update(self.widgetDescr) return cfg class NEXYZGUI(PortWidget): # description of parameters that can only be used with the widget's # constructor configOpts = PortWidget.configOpts.copy() ownConfigOpts = { 'widthX':{'min':1, 'max':500, 'type':'int', 'defaultValue':100}, 'heightX':{'min':1, 'max':500, 'type':'int', 'defaultValue':26}, 'wheelPadX':{'min':1, 'max':500, 'type':'int', 'defaultValue':4}, 'widthY':{'min':1, 'max':500, 'type':'int', 'defaultValue':100}, 'heightY':{'min':1, 'max':500, 'type':'int', 'defaultValue':26}, 'wheelPadY':{'min':1, 'max':500, 'type':'int', 'defaultValue':4}, 'widthZ':{'min':1, 'max':500, 'type':'int', 'defaultValue':100}, 'heightZ':{'min':1, 'max':500, 'type':'int', 'defaultValue':26}, 'wheelPadZ':{'min':1, 'max':500, 'type':'int', 'defaultValue':4}, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # create all attributes that will not be created by configure because # they do not appear on kw for key in self.ownConfigOpts.keys(): v = kw.get(key, None) if v is None: # self.configure will not do anyting for this key setattr(self, key, self.ownConfigOpts[key]['defaultValue']) # get all arguments handled by NEThumbweel and not by PortWidget widgetcfg = {} for k in self.ownConfigOpts.keys(): if k in kw: widgetcfg[k] = kw.pop(k) # call base class constructor apply( PortWidget.__init__, ( self, port), kw) # create the Thumbwheel widget self.widget = apply( xyzGUI, (self.widgetFrame,), widgetcfg) # bind right mouse button click self.widget.bind("", self.postWidgetMenu) self.widget.callbacks.AddCallback(self.newValueCallback) # configure without rebuilding to avoid enless loop apply( self.configure, (False,), widgetcfg) if self.initialValue is not None: self.set(self.initialValue, run=0) self._setModified(False) # will be set to True by configure method def get(self): return self.widget.thumbx.value, self.widget.thumby.value, \ self.widget.thumbz.value def set(self, val, run=1): self._setModified(True) self.widget.set(val[0],val[1],val[2]) self._newdata = True if self.port.network.runOnNewData.value is True and run: self.port.node.schedule() def configure(self, rebuild=True, **kw): # call base class configure with rebuild=Flase. If rebuilt is needed # rebuildDescr will contain w=='rebuild' and rebuildDescr contains # modified descr action, rebuildDescr = apply( PortWidget.configure, (self, False), kw) # handle ownConfigOpts entries if self.widget is not None: widgetOpts = {} for k, v in kw.items(): if k in self.ownConfigOpts.keys(): if k in ['widthX', 'heightX', 'wheelPadX', 'widthY', 'heightY', 'wheelPadY', 'widthZ', 'heightZ', 'wheelPadZ',]: action = 'rebuild' rebuildDescr[k] = v else: widgetOpts[k] = v if len(widgetOpts): apply( self.widget.configure, (), widgetOpts) if action=='rebuild' and rebuild: action, rebuildDescr = self.rebuild(rebuildDescr) elif action=='resize' and rebuild: if self.widget and rebuild: # if widget exists action = None return action, rebuildDescr def getDescr(self): cfg = PortWidget.getDescr(self) for k in self.ownConfigOpts.keys(): if k == 'typeX': # type has to be handled separately _type = self.widget.type if _type == int: _type = 'int' else: _type = 'float' if _type != self.ownConfigOpts[k]['defaultValue']: cfg[k] = _type continue elif k == 'typeY': # type has to be handled separately _type = self.widget.type if _type == int: _type = 'int' else: _type = 'float' if _type != self.ownConfigOpts[k]['defaultValue']: cfg[k] = _type continue if k == 'typeZ': # type has to be handled separately _type = self.widget.type if _type == int: _type = 'int' else: _type = 'float' if _type != self.ownConfigOpts[k]['defaultValue']: cfg[k] = _type continue val = getattr(self.widget, k) if val != self.ownConfigOpts[k]['defaultValue']: cfg[k] = val return cfg class NEVectEntry(NEEntry): """ this widget is a specialized Entry for typing in vector values a setValue() method has been added which checks that the input is correct """ configOpts = TkPortWidget.configOpts.copy() ownConfigOpts = { 'initialValue':{ 'defaultValue':'0, 0, 0', 'type':'string', }, } configOpts.update( ownConfigOpts ) def __init__(self, port, **kw): # Tkportwidget NEEntry: NEEntry.__init__(self, port, **kw) self.widget.bind('', self.setValue_cb) self.point = [0., 0, 0] self.text = "0 0 0" if self.initialValue is not None: self.set(self.initialValue, run=0) self.updateField() def setValue_cb(self, event=None, run=1): v = self.var.get() try: val = string.split(v) except: self.updateField() return if val is None or len(val)!= 3: self.updateField() return try: oldtext = self.text self.text = v self.point=[] self.point.append(float(val[0])) self.point.append(float(val[1])) self.point.append(float(val[2])) except: self.text = oldtext self.updateField() return self.updateField() self._setModified(True) if run: self.scheduleNode() def updateField(self): self.var.set(self.text) def get(self): return self.point def set(self, point, run=1): self._setModified(True) self.point = point text = "%s %s %s"%(point[0], point[1], point[2]) self.text = text self.var.set(text) self._newdata = True if self.port.network.runOnNewData.value is True and run: self.port.node.schedule() widgetsTable = { NEButton.__name__: NEButton, NEThumbWheel.__name__: NEThumbWheel, NECheckButton.__name__: NECheckButton, NEMultiCheckButtons.__name__: NEMultiCheckButtons, #NEMultiRadioButtons.__name__: NEMultiRadioButtons, #NERadiobutton.__name__: NERadiobutton, NEEntry.__name__: NEEntry, NEEntryField.__name__: NEEntryField, NEEntryNotScheduling.__name__: NEEntryNotScheduling, NEScrolledText.__name__: NEScrolledText, NEEntryWithFileBrowser.__name__: NEEntryWithFileBrowser, NEEntryWithDirectoryBrowser.__name__: NEEntryWithDirectoryBrowser, NEEntryWithFileSaver.__name__: NEEntryWithFileSaver, NEDial.__name__: NEDial, NEVectorGUI.__name__: NEVectorGUI, NEComboBox.__name__: NEComboBox, NEXYZGUI.__name__:NEXYZGUI, NEVectEntry.__name__:NEVectEntry, } # used by Editors.py to make widgets available in editor's pull down menus #publicWidgetsTable = widgetsTable.copy() def createWidgetDescr( className, descr=None ): """This function will return a widget description dictionnary for a widget of type className. If descr is given it should be another widgetDescr dict. The option in descr that are suitable for className type widget will be carried over. """ wdescr = {'class':className} if descr: klass = widgetsTable[className] for k, v in descr.items(): if k in klass.configOpts: wdescr[k] = v return wdescr class WidgetEditor: """class to build an GUI to let the user input values. description has to be a dictionary where the keys is the name of a parameters and the value is a dictionary describing the type of value. value types can be described using the keywords: min, max, type, validValues, defaultValue min and max can be int or float or None defaultValue: if missing value will default to minimum. If no min we use max, if no max we set to 0 type can be 'int', 'float' ,'dict', 'list', 'tuple, or string validValues is a list of valid entries, this build a ComboBox widget one can specify a type or a list of valid values. parameters with a list of values will be displayed using a combobox all others use entryfield widgets """ def __init__(self, widget, master=None): #print "WidgetEditor.__init__" if master is None: master = Tkinter.Toplevel() self.top = Tkinter.Frame(master) master.protocol("WM_DELETE_WINDOW", self.Cancel_cb) self.widgetFormName = {} self.widget = widget self.port = widget.port # widget might change (rebuild), port does not widget.objEditor = self self.currentValues = {} # dictionnary used to save current values # so we can decide what has changed descr = self.widget.configOpts currentConfig = self.widget.getDescr() #for k, v in currentConfig.items(): # if k in descr: # if 'defaultValue' in descr[k]: # descr[k]['defaultValue'] = v #descr['initialValue']['defaultValue'] = self.widget.get() # build widgets for each parameter keys = descr.keys() keys.sort() row = 0 for k in keys: # the following keys are not exposed in the form if k in ['master', 'labelCfg', 'labelGridCfg', 'labelSide', 'widgetGridCfg', 'callback']: continue v = descr[k] opt = {'command':self.Apply_cb} w = None gridOpts = {'row':row, 'column':1, 'sticky':'ew'} type = v.get('type', None) if type is None: continue curval = currentConfig.pop(k, None) if v.has_key('validValues'): w = kbComboBox(self.top, scrolledlist_items=v['validValues'] ) if v.has_key('defaultValue'): opt['value'] = v['defaultValue'] w.selectitem(curval or v['defaultValue']) else: valid = {} if type is 'boolean': var = Tkinter.IntVar() opt['variable']=var w = apply( Tkinter.Checkbutton, (self.top,), opt ) w.var = var if v.has_key('defaultValue'): opt['value'] = curval or v['defaultValue'] var.set(opt['value']) gridOpts = {'row':row, 'column':1, 'sticky':'w'} elif type in ['int', 'float']: valid['stringtovalue'] = eval(type) if v.has_key('min'): valid['min'] = v['min'] valid['minstrict'] = 0 if v.has_key('max'): valid['max'] = v['max'] valid['maxstrict'] = 0 opt['validate'] = valid if curval: opt['value'] = curval elif v.has_key('defaultValue'): opt['value'] = v['defaultValue'] elif v.has_key('min'): opt['value'] = v['min'] elif v.has_key('max'): opt['value'] = v['max'] else: opt['value'] = eval( type+'(0)') if opt['value'] is None: opt['value'] = 'None' w = apply( Pmw.EntryField, (self.top,), opt ) elif type in ['dict', 'list', 'tuple']: if v.has_key('defaultValue'): opt['value'] = curval or v['defaultValue'] w = apply( Pmw.EntryField, (self.top,), opt ) elif type == 'string': if v.has_key('defaultValue'): opt['value'] = curval or v['defaultValue'] w = apply( Pmw.EntryField, (self.top,), opt ) else: if v.has_key('defaultValue'): opt['value'] = curval or v['defaultValue'] else: print 'skipping ', k, type if w: lab = Tkinter.Label(self.top, text=k) lab.grid(row=row, column=0, sticky='e') self.widgetFormName[k] = (w, v, opt['value']) apply( w.grid, (), gridOpts) row += 1 self.currentValues[k] = opt['value'] # add Tkinter Options entry row += 1 Tkinter.Label(self.top, text="Tkinter Options").grid( row=row, column=0, sticky='e') w = apply( Pmw.EntryField, (self.top,), {'command':self.Apply_cb} ) opt = {'defaultValue':None, 'type':'string'} self.widgetFormName['TkOptions'] = (w, opt, None) self.currentValues['TkOptions'] = None w.grid(row=row, column=1, sticky='ew') row += 1 # add OK button Tkinter.Button(self.top, text='OK', command=self.OK_cb ).grid( column=0, row=row, sticky='ew') # add Apply button Tkinter.Button(self.top, text='Apply', command=self.Apply_cb ).grid( column=1, row=row, sticky='ew') # add Cancel button Tkinter.Button(self.top, text='Cancel', command=self.Cancel_cb ).grid( column=2, row=row, sticky='ew') self.top.pack() def getValues(self): result = {} for k, w_and_v_and_val in self.widgetFormName.items(): w, v, value = w_and_v_and_val # parse TkOptions, append them to result. Since we parse and # append to results, we continue the loop after we are finished # parsing this key if k == "TkOptions": spl = string.split(w.get(), ",") if not len(spl): continue for s in spl: if not len(s): continue n,m = string.split(s, '=') n = string.strip(n) m = string.strip(m) result[n] = m continue if v['type']=='boolean': cast = bool elif v['type']=='int': cast = int elif v['type']=='float': cast = float elif v['type']=='dict': cast = eval elif v['type']=='list': cast = eval elif v['type']=='tuple': cast = eval elif v['type']=='string': cast = str else: cast = str # get result from Pmw.EntryField if isinstance(w, Pmw.EntryField): val = w.get() if val == 'None': if val != value: result[k] = None else: continue val = cast(val) elif isinstance(w, Tkinter.Checkbutton): val = cast(w.var.get()) else: val = cast(w.component('entryfield').get()) if val != self.currentValues[k]: result[k] = val self.currentValues[k] = val return result def Apply_cb(self, event=None): values = self.getValues() if len(values): # call port.configureWidget rather than widget.configure # because widget could be destroyed by configure and # port.configureWidget deletes the old widget #apply( port.configureWidget, (), values) w, descr = apply( self.widget.configure, (), values) # in case we rebuild widget: if self.widget != self.port.widget: self.widget = self.port.widget self.widget.objEditor = self def OK_cb(self, event=None): self.Apply_cb() self.Cancel_cb() def Cancel_cb(self, event=None): # unselect checkbutton in Editor form if self.port.node.objEditor: self.port.editWtk.deselect() self.widget.objEditor = None if self.port.objEditor is not None: self.port.objEditor.editWidgetVarTk.set(0) self.top.master.destroy() mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/datatypes.py0000644000175000017500000006740111263207306025311 0ustar debiandebian## Automatically adapted for numpy.oldnumeric Jul 23, 2007 by ######################################################################## # # Date: Nov. 2001 Author: Michel Sanner, Daniel Stoffler # # sanner@scripps.edu # stoffler@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner, Daniel Stoffler and TSRI # # Revision: Guillaume Vareille # ######################################################################### import types import numpy.oldnumeric as Numeric import warnings import array class AnyArrayType(dict): """Base class for defining types of Ports which can handle single values or arrays of arbitrary shape. this class must be inherited only !!!! get inspired by class IntType below the constructor arguments are: name: string used to identify an instance of a AnyArrayType object ex: float you can add the datashape to the name ex: float(0,>=3,4,0) defines a 4D array with constrains on the extent of each dimension. A value of 0 means there is not required extent Multi dimensional arrays can be specified by passing a datashape in parenthesis in the form of a string of comma separated expressions. The number of expressions is the number of dimensions. Valid expressions are : 0 : meaning there is no condition on this dimension. (in addition, the leading 0 dimensions are optional) x : length of this dimension must be x >x : length of this dimension must be > x =x: length of this dimension must be >= x <=x: length of this dimension must be <= x for instance a list of vertices can be represented by float(0,3) float: describe the smallest elementary datum (one coordinate). you can introduce your own elementary type (generally inheriting basic types), this allow you to easily define color and shape for this type (see InstancematType below) 3: because there are 3 coordinates per vertex. 0: because we don't have any limitation on the number of vertices. It is a leading 0, therefore this dimension is optional (meaning: if you're passing a single vertex [x,y,z], your are not oblige to put it in a list. ex: you can give [x,y,z] or [[x,y,z]]) """ def __init__(self, # used as a key the datatype manager name='None', datashape=None, # describe (0,>=3) klass=None, # class of Python object passing through port. # This is used for data type propagation dataDescr=None, # string describing data in tool tip color=None, # color of port icon shape='diamond', # shape used for port icon width=None, height=None, # width and height of port icon ): #import traceback;traceback.print_stack() #print "AnyArrayType __init__ name datashape", name, datashape self['name'] = name self['datashape'] = datashape self['class'] = klass self['dataDescr'] = dataDescr if color is None: color = 'white' self['color'] = color # port icon color if shape is None: shape = self._shape(datashape) self['shape'] = shape # port icon if width is None: width = 12 self['width'] = width # port icon width if height is None: if shape == 'circle' or shape == 'square': height = self['width'] else: height = 8 self['height'] = height # port icon height # list of lambda functions used to check dimension of incoming data self.dimensionTest = [] # compile dimension checking functions that will be used in validate if datashape is None: self.lenDimensionTest = 0 else: for dimlim in datashape[1:-1].split(','): # this case should be handled using >=1, i.e. required # dimension with no constriaint #if dimlim=='': # no restirction on this dimension # self.dimensionTest.append( None ) if dimlim.isdigit(): # we have a fixed extent for this dimension if int(dimlim)!=0: self.dimensionTest.append( lambda x: x==int(dimlim) ) else: # no restriction on this dimension self.dimensionTest.append( None ) else: strg = '' for c in dimlim: if c in ['>', '<']: strg += 'x '+c else: strg += c self.dimensionTest.append( eval('lambda x: %s'%strg) ) self.lenDimensionTest = len(self.dimensionTest) def validateDims(self, data): """data comes in as a numeric array, and returns True if required shapes pass tests, returns False is tests fail, or returns number of missing dimensions if test pass but optional dims are missing """ missingDimensions = self.lenDimensionTest - len(data.shape) for i in range(missingDimensions): data.shape = (1,) + data.shape for dim,f in zip(data.shape, self.dimensionTest): if f is not None: if not f(dim): return False, data if missingDimensions > 0: return missingDimensions, data else: return True, data def _shape(self, datashape): """This function is called by the constructor to return the number of edges used to draw the port icon based on the number of dimensions of the type. NOTE: this function is only used if the icon shape is not specified as an argument to the constructor. """ if datashape is not None: lDimensions = datashape[1:-1].split(',') lShape = len(lDimensions) * 2 - 1 if lDimensions[0] == '0': lShape -= 1 else: lShape = 0 #print "icon shape:", lShape return lShape def cast(self, data): return False, None def validate(self, data): #print "validate", data if self['class'] is None: return True, data if self['datashape'] is None: if isinstance(data, self['class']): return True, data else: return False, None try: lArray = Numeric.array(data) lArray0 = lArray.ravel()[0] while hasattr(lArray0,'shape'): lArray0 = lArray0.ravel()[0] if isinstance(lArray0, self['class']): return True, lArray else: return False, None except: return False, None # DEPRECATED CLASS: use AnyArrayType instead from UserDict import UserDict class AnyType( UserDict # , # AnyArrayType ): def __init__(self, name='Old None', color='white', shape='diamond', width=12, height=8, klass=None, dataDescr=None # , datashape=None ): UserDict.__init__(self) # AnyArrayType.__init__(self, name=name, color=color, # shape=shape, width=width, height=height, # klass=klass, dataDescr=dataDescr, # datashape=datashape) # warnings.warn('AnyType is deprecated, use AnyArrayType instead', # DeprecationWarning, stacklevel=2) # Note: use EVEN numbers for width, height, since they will be # divided by 2 for halfPortWidth, halfPortHeight self.data['name'] = name self.data['dataDescr'] = dataDescr self.data['shape'] = shape # port icon self.data['width'] = width # port icon width self.data['height'] = height # port icon height self.data['color'] = color # port icon color self.data['class'] = klass self.data['datashape'] = None # added to be compatible with def cast(self, data): """returns a success status (true, false) and the coerced data""" assert True, data # to silent pychecker return False, None def validate(self, data): """returns true if data if of the proper type""" return True class FloatType(AnyArrayType): def __init__(self, name='float', datashape=None, color='green', shape='circle', width=None, height=None): AnyArrayType.__init__(self, name=name, color=color, shape=shape, width=width, height=height, klass=float, datashape=datashape) def validate(self, data): if self['datashape'] is None: if isinstance(data, types.FloatType): return True, data else: return False, None try: lArray = Numeric.array(data, 'f') return True, lArray except: return False, None def cast(self, data): if self['datashape'] is None: if isinstance(data, types.FloatType): return True, data try: data = float(data) return True, data except: return False, data try: lArray = (Numeric.array(data)).astype('f') return True, lArray except: return False, None class IntType(AnyArrayType): def __init__(self, name='int', datashape=None,color='yellow', shape='circle', width=None, height=None): AnyArrayType.__init__(self, name=name, color=color, shape=shape, width=width, height=height, klass=int, datashape=datashape) def validate(self, data): if self['datashape'] is None: if isinstance(data, types.IntType): return True, data else: return False, None try: lArray = Numeric.array(data, 'i') return True, lArray except: return False, None def cast(self, data): if self['datashape'] is None: if isinstance(data, types.IntType): return True, data try: data = int(data) return True, data except: return False, data try: lArray = (Numeric.array(data)).astype('i') return True, lArray except: return False, None class BooleanType(AnyArrayType): def __init__(self, name='boolean', datashape=None, color='yellow', shape='rect', width=None, height=None): AnyArrayType.__init__(self, name=name, color=color, shape=shape, width=width, height=height, klass=bool, datashape=datashape) def validate(self, data): if self['datashape'] is None: if type(data) == types.BooleanType: return True, data else: return False, None try: lArray = Numeric.array(data, 'B') return True, lArray except: return False, None def cast(self, data): if self['datashape'] is None: if type(data) == types.BooleanType: return True, data try: data = bool(data) return True, data except: return False, data try: #FIXME: this 'b' stands for binary not boolean lArray = (Numeric.array(data)).astype('B') return True, lArray except: return False, None class StringType(AnyArrayType): def __init__(self, name='string', datashape=None, color='white', shape='oval', width=None, height=None): AnyArrayType.__init__(self, name=name, color=color, shape=shape, width=width, height=height, klass=str, datashape=datashape) def validate(self, data): if self['datashape'] is None: if type(data) == types.StringType: return True, data else: return False, None try: lArray = Numeric.array(data, 'O') #lShape = array.shape #lArray = map(str, array) #array.shape = lShape return True, lArray except: return False, None def cast(self, data): if self['datashape'] is None: if type(data) == types.StringType: return True, data try: data = str(data) return True, data except: return False, data try: lArray = (Numeric.array(data)).astype('O') return True, lArray except: return False, None #class StringType(AnyType): # # def __init__(self): # AnyType.__init__(self) # self.data['name'] = 'string' # self.data['color'] = 'white' # self.data['shape'] = 'oval' # self.data['width'] = 12 # circle # self.data['height'] = 12 # self.data['class'] = str # # def validate(self, data): # return type(data)==types.StringType # # def cast(self, data): # try: # return True, str(data) # except: # return False, data class ListType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'list' self.data['color'] = 'cyan' self.data['shape'] = 'oval' self.data['width'] = 12 # circle self.data['height'] = 12 self.data['class'] = list def validate(self, data): return type(data)==types.ListType def cast(self, data): try: if type(data)==types.StringType: try: lData = eval(data) except: lData = data else: lData = data try: lData = list(lData) except: lData = [lData] return True, lData except: return False, data class TupleType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'tuple' self.data['color'] = 'cyan' self.data['shape'] = 'oval' self.data['width'] = 12 # circle self.data['height'] = 12 self.data['class'] = tuple def validate(self, data): return type(data)==types.TupleType def cast(self, data): try: data = tuple(data) return True, data except: return False, data class DictType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'dict' self.data['color'] = 'cyan' self.data['shape'] = 'oval' self.data['width'] = 12 # circle self.data['height'] = 12 self.data['class'] = dict def validate(self, data): return type(data)==types.DictType class ArrayType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'array' self.data['color'] = 'cyan' self.data['shape'] = 'oval' self.data['width'] = 12 # circle self.data['height'] = 12 self.data['class'] = array.array def validate(self, data): return isinstance(data, array.ArrayType) class NumericArrayType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'NumericArray' self.data['color'] = 'orange' self.data['shape'] = 'pentagon' self.data['class'] = Numeric.array def validate(self, data): return isinstance(data, Numeric.ArrayType) def cast(self, data): try: data = Numeric.array(data) return True, data except: return False, data class VectorType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'vector' self.data['color'] = 'cyan' self.data['shape'] = 'oval' self.data['width'] = 12 # circle self.data['height'] = 12 self.data['class'] = None def validate(self, data): try: if type(data) != types.StringType: len(data) return True else: return False except: return False def cast(self, data): """returns a success status (true, false) and the coerced data """ if type(data) == types.StringType: return True, (data, ) return False, None class TriggerOut(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'triggerOut' self.data['color'] = 'orange' self.data['shape'] = 'square' del self.data['class'] class TriggerIn(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'triggerIn' self.data['color'] = 'orange' self.data['shape'] = 'square' del self.data['class'] class TkColorType(AnyType): def __init__(self): AnyType.__init__(self) self.data['name'] = 'tkcolor' self.data['color'] = 'orange' self.data['shape'] = 'square' del self.data['class'] def validate(self, data): import Tkinter try: Tkinter._default_root.winfo_rgb(data) return True finally: return False class TypeManager: """Port type object manager This object is used to register port type instances which are associated with port. new types can be registered by passing an instance of port type object to the .addType(instance) method. The .portTypeInstances dictionary provides a mapping between the a name used to describe the data acceptable for a port and an instance of a subclass of AnyArrayType. This name can be The .reverseLookUp dictionary is used to obtain a port type from the class of and object. This is used top propagate data types to ports that are mutatable """ def __init__(self): self.portTypeInstances = {} # key: name+datashape, value: instance self.reverseLookUp = {} # key:class, value:typeObject self.addType( AnyArrayType() ) # basic types self.addType( FloatType() ) self.addType( IntType() ) #self.addType( ObjectType() ) self.addType( BooleanType() ) self.addType( StringType() ) # for backward compatibility: self.portTypeInstances['str'] = self.portTypeInstances['string'] # synonyms self.addSynonym('coord2', 'float', '(2)', color='green', shape='oval') self.addSynonym('coord3', 'float', '(3)', color='green', shape='rect') self.addSynonym('normal3', 'float', '(3)', color='blue', shape='rect') self.addSynonym('colorfloat3or4', 'float', '(>=3 and <=4)', color='orange', shape='rect') self.addSynonym('instancemat', 'float', '(4,4)', color='cyan', shape='rect') self.addSynonym('indice2', 'int', '(2)', color='purple', shape='rect') self.addSynonym('indice2+', 'int', '(>=2)', color='purple', shape='rect') self.addSynonym('indice3or4', 'int', '(>=3 and <=4)', color='purple', shape='rect') self.addSynonym('2Darray', 'float', '(>0,>0)', color='cyan') self.addSynonym('object', existingTypeName='None') self.addSynonym('colorRGB', existingTypeName='colorfloat3or4') self.addSynonym('colorsRGB', existingTypeName='colorRGB',datashape='(0)') self.addSynonym('coordinates3D', existingTypeName='coord3',datashape='(0)') self.addSynonym('faceIndices', existingTypeName='indice3or4',datashape='(0)') self.addSynonym('normals3D', existingTypeName='normal3',datashape='(0)') self.addType( AnyType() ) self.addType( DictType() ) self.addType( ListType() ) self.addType( TkColorType() ) self.addType( TupleType() ) self.addType( ArrayType() ) self.addType( NumericArrayType() ) self.addType( VectorType() ) self.addType( TriggerIn() ) self.addType( TriggerOut() ) def addSynonym(self, synonymName, existingTypeName=None, datashape=None, color=None, shape=None, width=None, height=None): """ method to create synonym types synonymName: can be an existing name with a diferent datashape: (existingTypeName must be None, and the basename 'coord3' must be registered) self.addSynonym('coord3(3,4)') existingTypeName: must be a registered name (it can be datatshaped, if it is already registered like that) self.addSynonym('coordinates3D', existingTypeName='coord3',datashape='(3,4)') self.addSynonym('coordinates3D', existingTypeName='coord3(4)',datashape='(3)') self.addSynonym('coordinates3D', existingTypeName='coord3(3,4)') """ if existingTypeName is None: assert datashape is None lSplitName = synonymName.split('(') existingTypeName = lSplitName[0] if len(lSplitName) == 2: datashape = '(' + lSplitName[1] basicInstance = self.portTypeInstances[existingTypeName] innerDatashape = basicInstance['datashape'] if innerDatashape is not None: if datashape is None: datashape = innerDatashape else: innerDatashape = innerDatashape.split('(')[1] datashape = datashape.split(')')[0] datashape = datashape + ',' + innerDatashape if color is None: color = basicInstance['color'] if shape is None: shape = basicInstance['shape'] if width is None: width = basicInstance['width'] if height is None: if shape == 'circle' or shape == 'square': height = width else: height = 2 * width / 3 instance = basicInstance.__class__( name=synonymName, datashape=datashape, color=color, shape=shape, width=width, height=height) self.addType( instance ) def getSynonymDict(self, synonymName): lDict = {} typeInstance = self.portTypeInstances[synonymName] lDict['existingTypeName'] = typeInstance.__class__()['name'] if lDict['existingTypeName'] == synonymName: return None lDict['synonymName'] = synonymName lDict['datashape'] = typeInstance['datashape'] lDict['color'] = typeInstance['color'] lDict['shape'] = typeInstance['shape'] lDict['width'] = typeInstance['width'] lDict['height'] = typeInstance['height'] return lDict def addSynonymDict(self, aDict): if aDict.has_key('existingTypeName') is False: aDict['existingTypeName'] = None if aDict.has_key('datashape') is False: aDict['datashape'] = None if aDict.has_key('color') is False: aDict['color'] = None if aDict.has_key('shape') is False: aDict['shape'] = None if aDict.has_key('width') is False: aDict['width'] = None if aDict.has_key('height') is False: aDict['height'] = None self.addSynonym(aDict['synonymName'], existingTypeName=aDict['existingTypeName'], datashape=aDict['datashape'], color=aDict['color'], shape=aDict['shape'], width=aDict['width'], height=aDict['height']) def addType(self, dtypeinstance): """register a port type instance """ #print "addType dtypeinstance", dtypeinstance['name'], dtypeinstance['datashape'] #import pdb;pdb.set_trace() if isinstance(dtypeinstance, AnyType) or \ isinstance(dtypeinstance, AnyArrayType): # we reduce dtypeinstance to its base class name splittype = dtypeinstance['name'].split('(') basetypename = splittype[0] if dtypeinstance['datashape'] is not None: if dtypeinstance['name'].endswith(dtypeinstance['datashape']): # make sure the base type exists if self.portTypeInstances.has_key(basetypename) is None: msg = 'base datatype '+basetypename msg += ' not found in types table when adding %s'%str(dtypeinstance) warnings.warn(msg) return storagename = dtypeinstance['name'] else: storagename = basetypename if self.portTypeInstances.has_key(storagename): if isinstance(dtypeinstance, AnyType): if not ( \ isinstance(self.portTypeInstances[storagename], AnyType) \ and \ (dtypeinstance.data == self.portTypeInstances[storagename].data) \ ): msg = 'Warning! datatype '+storagename+' already registered differently' warnings.warn(msg) return elif isinstance(dtypeinstance, AnyArrayType): if not ( \ isinstance(self.portTypeInstances[storagename], AnyArrayType) \ and \ (dtypeinstance == self.portTypeInstances[storagename]) \ ): msg = 'Warning! datatype '+storagename+' already registered differently' warnings.warn(msg) return self.portTypeInstances[storagename] = dtypeinstance if dtypeinstance.has_key('class'): if not self.reverseLookUp.has_key(dtypeinstance['class']): self.reverseLookUp[dtypeinstance['class']] = dtypeinstance else: raise RuntimeError('bad dtypeinstance argument') def getType(self, fullName): #print "getType fullName", fullName #import traceback;traceback.print_stack() #import pdb;pdb.set_trace() splittype = fullName.split('(') typename = splittype[0] datashape = None if len(splittype)==2: datashape = '(' + splittype[1] if not self.portTypeInstances.has_key(typename): msg = 'base datatype ' + typename msg += ' not found in types table' warnings.warn(msg) #import traceback;traceback.print_stack() return self.portTypeInstances['None'] if self.portTypeInstances.has_key(fullName) is False: # base class exist but not with this datashape, we add it self.addSynonym(fullName, typename, datashape) return self.portTypeInstances[fullName] def getTypeFromClass(self, klass): """ return the data type object for types that can be looked up i.e. have a class attribute """ return self.reverseLookUp.get(klass, self.reverseLookUp.get(None)) def get(self, key, default=None): return self.portTypeInstances.get(key, default) mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/ports.py0000644000175000017500000023044212115252471024457 0ustar debiandebian## Automatically adapted for numpy.oldnumeric Jul 23, 2007 by ######################################################################### # # Date: Nov. 2001 Authors: Michel Sanner, Daniel Stoffler # # sanner@scripps.edu # stoffler@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner, Daniel Stoffler and TSRI # # revision: Guillaume Vareille # ######################################################################### # # $Header: /opt/cvs/python/packages/share1.5/NetworkEditor/ports.py,v 1.209 2013/03/05 02:15:21 sanner Exp $ # # $Id: ports.py,v 1.209 2013/03/05 02:15:21 sanner Exp $ # """ These objects are created through the addInputPort and addOutputPort methods of the NetworkNode2 object. They are not intended to be created by a user or a programer """ import types import numpy.oldnumeric as Numeric import Tkinter, Pmw import weakref, warnings from UserList import UserList from inspect import getargspec from NetworkEditor.itemBase import NetworkItems from NetworkEditor.widgets import PortWidget, widgetsTable from NetworkEditor.datatypes import AnyType, AnyArrayType from mglutil.gui.BasicWidgets.Tk.TreeWidget.objectBrowser import ObjectBrowser from mglutil.util.callback import CallBackFunction class NGWidget: """ dummy class for non graphical widget """ def __init__(self): self.value = None self._newdata = False def set(self, value, run=True): self.value = value self._newdata = True def get(self): return(self.value) def configure(self, **kw): # no need to worry for these kw.pop('choices', None) kw.pop('min', None) kw.pop('max', None) if len(kw) != 0: print 'WARNING running without GUI: NGWidget.configure() not implemented', kw def setlist(self, *args, **kw): print 'WARNING running without GUI: NGWidget.setlist() not implemented' class PortsDescr(list): """Class used to describe input and output ports as a list of dictionaries """ def __init__(self, node): self.node = weakref.ref(node) def getSize(self, portNum): return self[portNum].get('size', 20) def getLabel(self, portNum): return self[portNum].get('label', None) def getEdge(self, portNum): descr = self[portNum] found = False for k,v in descr.items(): if k[-4:]=='rpos': found = True break if not found: #import traceback #traceback.print_stack() print 'PORT EDGE NOT FOUND %rpos key missing', portNum, descr return 'top' if k=='ulrpos': if v[0]==0: return 'left' else: return 'top' elif k=='urrpos': if v[0]==0: return 'right' else: return 'top' elif k=='llrpos': if v[0]==0: return 'left' else: return 'bottom' elif k=='lrrpos': if v[0]==0: return 'right' else: return 'bottom' ## def getDrawingParams(self, portNum, node): ## # get the parameters from drawing this port ## # the position can be specified usinf ulrpos=(x,y) to specify an offset ## # from the upper left corner of the node's box. Likewise urrpos, ## # llrpos and lrrpos can be used. The offset can be integer for pixels ## # of a float < 1.0 for a percentage of the node's width. ## # the 'size' is return (default 20) ## # the 'fill' is the color used for the port's background ## # the 'line' is the color used for the port's arrow ## # the 'outline' is the color used for the port's outline ## # vector is a vector normal the node's edge, pointing outside in ## # coord system where +Y is up ## ulx, uly = node.UL ## width = node.activeWidth ## height = node.activeHeight ## descr = self[portNum] ## vector = descr.get('vector') ## # flip y as cairo origin is upper left corner ## vector = [vector[0], -vector[1]] ## size = self.getSize(portNum) ## fill = descr.get('fill', (1,1,1,1)) ## line = descr.get('line', (0,0,0,1)) ## outline = descr.get('outline', (0,0,0,1)) ## label = self.getLabel(portNum) ## dx, dy = descr.get('ulrpos', (None, None)) ## if dx is not None: ## if abs(dx) < 1.0: dx *= width ## if abs(dy) < 1.0: dy *= height ## #print 'UL', ulx, uly, width, height, dx, dy, descr.get('ulrpos'), ulx+dx, uly+dy ## return ulx+dx, uly+dy, size, vector, line, fill, outline, label ## dx, dy = descr.get('urrpos', (None, None)) ## if dx is not None: ## if abs(dx) < 1.0: dx *= width ## if abs(dy) < 1.0: dy *= height ## #print 'UR', ulx, uly, width, height, dx, dy, descr.get('urrpos'), ulx+width+dx, uly+dy ## return ulx+width+dx, uly+dy, size, vector, line, fill, outline, label ## dx, dy = descr.get('llrpos', (None, None)) ## if dx is not None: ## if abs(dx) < 1.0: dx *= width ## if abs(dy) < 1.0: dy *= height ## #print 'LL', ulx, height, dx, dy, descr.get('llrpos'), ulx+dx, uly+height+dy ## return ulx+dx, uly+height+dy, size, vector, line, fill, outline, label ## dx, dy = descr.get('lrrpos',(None, None)) ## if dx is not None: ## if abs(dx) < 1.0: dx *= width ## if abs(dy) < 1.0: dy *= height ## #print 'LR', ulx, width, dx, descr.get('lrrpos'), ulx+width+dx, uly+height+dy ## return ulx+width+dx, uly+height+dy, size, vector, line, fill, outline, label ## # fixme .. find a good location for this port ## return ulx+10, uly, size, vector, line, fill, outline, label def append(self, *args, **kw): if args is not None: if len(args)==1: if isinstance(args[0], dict): kw = args[0] else: raise ValueError('expected dictionary got ', args) elif len(args)>1: raise ValueError('bad argument for PortsDescr', args) assert kw.has_key('name') if not kw.has_key('datatype'): kw['datatype'] = 'None' if self.checkUniqueName(kw['name']): list.append(self, kw) class InputPortsDescr(PortsDescr): def checkUniqueName(self, name): for p in self.node().inputPortsDescr: if p['name'] == name: msg = 'port name %s already used in inputPortsDescr of node %s'%(name, self.node().name) warnings.warn(msg) return False return True class OutputPortsDescr(PortsDescr): def checkUniqueName(self, name): for p in self.node().outputPortsDescr: if p['name'] == name: msg = 'port name %s already used in outputPortsDescr of node %s'%(name, self.node().name) warnings.warn(msg) return False return True class Port(NetworkItems): """base class for input/output ports of network nodes""" def __init__(self, name, node, datatype='None', balloon=None, width=None, height=None, beforeConnect=None, afterConnect=None, beforeDisconnect=None, afterDisconnect=None, hasHiddenConnections=False, shape=None, color=None, originalDatatype=None, vector=None ): #print "Port.__init__", name, datatype, originalDatatype NetworkItems.__init__(self, name=name) self.node = node self.network = node.network self.objEditor = None # port editor widget self.connections = [] self.datatypeObject = None if originalDatatype is not None: self.originalDatatype = originalDatatype # some ports mutate their datatype else: self.originalDatatype = datatype # some ports mutate their datatype # upon connection. This used to restore the original type self.balloon = balloon self.balloonBase = None # holds the data independent part of tooltip self.widget = None self.id = None # canvas id for this port self.iconTag = None # canvas id for all primitives of this port self._id = None # a unique number, used for saving/restoring # this number is set in addInput/OutputPort() self.hasHiddenConnections = hasHiddenConnections self.menu = None self.data = 'no data yet' self.dataView = None self.relposx = 0 # center (not left or right corner) of port self.relposy = 0 # icon self.width = width self.height = height self.shape = shape self.vector = vector # vector orthogonal to shape edge, pointing outside self.vectorRotated = vector # vector orthogonal to shape edge, pointing outside self.color = color self.visible = True # set to false if widget is bound # various callback placeholders self.callbacks = {'beforeConnect':(None,None), 'afterConnect':(None,None), 'beforeDisconnect':(None,None), 'afterDisconnect':(None,None)} # set callbacks: pass code and attribute self.setCallback(beforeConnect, 'beforeConnect') self.setCallback(afterConnect, 'afterConnect') self.setCallback(beforeDisconnect, 'beforeDisconnect') self.setCallback(afterDisconnect, 'afterDisconnect') self.vEditor = None if node.getEditor(): self.vEditor = weakref.ref( node.getEditor() ) # VPE editor self.setDataType(datatype) def setCallback(self, code, key): """set callbacks for connect & disconnect evens. The dictionary self.callbacks holds the various callback methods key: name of the callback (such as 'beforeConnect') code: source code of the callback""" self.callbacks[key] = ( self.node.evalString(code), code ) def editorVisible(self): """returns True if the port's Editor is visible""" return self.objEditor is not None def getDatatypeObjectFromDatatype(self, datatype): ed = self.getEditor() tm = ed.typeManager if type(datatype) is types.StringType: return tm.getType(datatype) elif isinstance(datatype, AnyType) or \ isinstance(datatype, AnyArrayType): return datatype elif hasattr(datatype,'__class__'): return tm.getTypeFromClass(datatype.__class__) return tm.getTypeFromClass(None) def setDataType(self, datatype, tagModified=True, makeOriginal=False): """set the port's data type. Update Port's icon if visible """ #print "setDataType" ed = self.getEditor() lDatatypeObject = self.getDatatypeObjectFromDatatype(datatype) self.datatypeObject = lDatatypeObject if ed.hasGUI: if self.iconTag is not None: # port is visible self.halfPortWidth = self.datatypeObject['width'] / 2 self.halfPortHeight = self.datatypeObject['height'] / 2 self.deleteIcon() self.createIcon() if tagModified: self._setModified(True) if makeOriginal: self.originalDatatype = datatype def __repr__(self): if hasattr(self, 'node'): return "<%s: port %d called %s in node %s>"%( self.__class__, self.number, self.name, self.node.name) elif hasattr(self, 'number'): return "<%s: port %d called %s>"%( self.__class__, self.number, self.name) else: return "<%s: port called %s>"%( self.__class__, self.name) def getColor(self): return self.node.iconMaster.itemconfigure(self.id)['fill'][-1] def setColor(self, color): if self.iconTag: oldCol = self.node.iconMaster.itemconfigure(self.id)['fill'][-1] self.node.iconMaster.itemconfigure(self.id)['fill'] = color return oldCol def getCenterCoords(self): bb = self.node.iconMaster.bbox(self.id) return (bb[0]+bb[2])/2, (bb[1]+bb[3])/2 def buildIcons(self, resize=True): # set port width/height. This is usually specified in datatypeObject, # but can also be passed when instanciating a port if self.width is None: self.width = self.datatypeObject['width'] if self.height is None: self.height = self.datatypeObject['height'] self.halfPortWidth = self.width/2 self.halfPortHeight = self.height/2 if self.shape is not None: self.datatypeObject['shape'] = self.shape if self.color is not None: self.datatypeObject['color'] = self.color if self.iconTag is not None: return # build port dropdown menu ed = self.getEditor() # got rid of useless tearoff self.menu = Tkinter.Menu(ed, title=self.name, tearoff=False) self.menu.add_separator() self.menu.add_command(label='Show Data', command=self.showData) self.menu.add_command(label='Introspect Data', command=self.browseData) self.menu.add_command(label='Edit', command=self.edit)#Port_cb) if isinstance(self, InputPort): lCast = self._cast # constructor create a boolean value which gets overwritten # by a Tkinter.BooleanVar here. When we come from refreshNet_cb # it is already a Tkinter.BooleanVar if isinstance(lCast, Tkinter.BooleanVar): lCast = lCast.get() self._cast = Tkinter.BooleanVar() self._cast.set(lCast) lFuncCall = CallBackFunction(self._setModified, True) self.menu.add_checkbutton(label='Cast Data', variable=self._cast, onvalue=True, offvalue=False, command=lFuncCall) #this way we do not create a dependancy with DejaVu if self.name == 'parent' and hasattr(self.node,'selectedGeomIndex'): self.menu.add_separator() self.cascadeMenu = Tkinter.Menu(self.menu, tearoff=0) self.menu.add_cascade(label='Parenting applies to', menu=self.cascadeMenu) self.cascadeMenuVariable = Tkinter.StringVar() self.cascadeMenuVariable.set('current') self.cascadeMenu.add_radiobutton( label='None', variable=self.cascadeMenuVariable, command=lFuncCall, value='none', underline=0) self.cascadeMenu.add_radiobutton( label='Current geom', variable=self.cascadeMenuVariable, command=lFuncCall, value='current', underline=0) self.cascadeMenu.add_radiobutton( label='Sibling geoms', variable=self.cascadeMenuVariable, command=lFuncCall, value='siblings', underline=0) self.cascadeMenu.add_radiobutton( label='All geoms', variable=self.cascadeMenuVariable, command=lFuncCall, value='all', underline=0) self.retainPosition = Tkinter.BooleanVar() self.retainPosition.set(False) self.menu.add_checkbutton(label='retain position while parenting', variable=self.retainPosition, onvalue=True, offvalue=False, command=lFuncCall) # even when correctly set, it may happens that the menu doesn't # show the radio button correctly set at starting . # the following line may help #self.node.network.canvas.update_idletasks() visible = True if isinstance(self, InputPort): if self.widget is not None: visible = False elif isinstance(self, SpecialInputPort) or \ isinstance(self, SpecialOutputPort): if not self.node.specialPortsVisible: visible = False self.visible = visible if visible: # build port icon self.createIcon() if resize: self.node.autoResizeX() def showData(self): if self.dataView is None: self.openDataViewer() else: self.dataView.lift() def openDataViewer(self): if isinstance(self, InputPort): type = 'input' else: type = 'output' self.dataView = Pmw.TextDialog( None, scrolledtext_labelpos = 'n', title = 'node %s, port %s'%(self.node.name, self.name), defaultbutton=0, command=self.dismiss, label_text = 'Data %s of Port %d'%(type,self.number) ) self.updateDataView() self.dataView.pack() def dismiss(self, event=None): self.dataView.destroy() self.dataView = None def clearDataView(self): if not self.dataView: return self.dataView.settext('') def getDataStr(self, maxLen = None): # return a string that would be used in output port's show data #if type(self.data)==types.InstanceType: from mglutil.util.misc import isInstance if isInstance(self.data) is True: header = 'data:%s'%self.data.__class__ else: header = 'data:%s'%type(self.data) length = 0 if isinstance(self.data, Numeric.ArrayType): header += ', shape:' + str(self.data.shape) if len(self.data.shape) == 0: length = 0 else: length = reduce( lambda x,y: x*y, self.data.shape) elif hasattr(self.data, '__len__'): header += ', length:' + str(len(self.data)) if type(self.data) == 'str': length = len(self.data) else: length = len(str(self.data)) header += '\n' if length > 1000: return header + '-'*80 + '\n'+ "Too much data to print" datastr = repr(self.data) if maxLen: if len(datastr) < maxLen: return header + '-'*80 + '\n'+ datastr else: return header + '-'*80 + '\n'+ datastr[:maxLen] + '...' else: return header + '-'*80 + '\n'+ datastr def updateDataView(self): if not self.dataView: return self.dataView.settext(self.getDataStr()) def browseData(self): """open introspect GUI""" if self.objectBrowser is None: self.objectBrowser = ObjectBrowser( self.data, rootName='Data', title='node %s, port %s'%(self.node.name, self.name), refresh=self._introspectCB) else: self.objectBrowser.show() def _introspectCB(self): """helper method used for the introspect data GUI""" return self.data def edit(self, event=None): if self.objEditor: self.objEditor.top.master.lift() return from Editor import PortEditor self.objEditor = PortEditor(self) def typeName(self, t): if type(t)==types.ClassType or type(t)==types.BuiltinFunctionType: return t.__name__ else: return type(t).__name__ def deleteIcon(self): """deleting the port's icon""" if self.iconTag: self.visible = False if self.node.network: if isinstance(self, InputPort) or \ isinstance(self, SpecialInputPort): del self.node.network.inPortsId[self.id] elif isinstance(self, OutputPort) or \ isinstance(self, SpecialOutputPort): del self.node.network.outPortsId[self.id] canvas = self.node.iconMaster canvas.delete(self.iconTag) self.id = None self.iconTag = None def datatypeStr(self): if type(self.datatype)==types.StringType: return self.datatype # obsolete since all datatypes are now string !? tstr = self.typeName(self.datatype) d = self.datatype while 1: try: d = d[0] tstr = tstr + ' of ' + self.typeName(d) except: break return tstr def createIcon(self, pcx=None, pcy=None): #print "createIcon" ed = self.getEditor() if not ed.hasGUI: return # recompute posx, since some port.visible might be False (hidden port) self.visible = True self.relposx = self.computePortPosX() canvas = self.node.iconMaster if pcx is None: pcx = self.node.posx + self.relposx + 3 if pcy is None: pcy = self.node.posy + self.relposy + 3 # bb = canvas.bbox(self.node.id) # node.id == node.innerBox # pcx = bb[0] + self.relposx # pcy = bb[1] + self.relposy sca = self.node.scaleSum w = int(round(self.halfPortWidth*sca)) h = int(round(self.halfPortHeight*sca)) color = self.datatypeObject['color'] shape = self.datatypeObject['shape'] # add tags ptags = ['port', self.node.iconTag] if isinstance(self, InputPort): ptags.append('inputPort') elif isinstance(self, OutputPort): ptags.append('outputPort') elif isinstance(self, SpecialInputPort): ptags.append('specialInputPort') elif isinstance(self, SpecialOutputPort): ptags.append('specialOutputPort') if self.hasHiddenConnections is True: lOutlineColor = 'white' elif hasattr(self, 'required') and self.required is False: lOutlineColor = 'gray50' else: lOutlineColor = 'black' # FIXME: for compatibility reasons, we keep 'rect1', 'rect2', etc # In the near future (like next release, we will delete those # and only use 'rect', 'oval', ect, since now we can specify the # width, height in datatypes if shape in [0, 'oval', 'oval1', 'oval2', 'circle']: self.id = self.iconTag = canvas.create_oval( pcx-w, pcy-h, pcx+w, pcy+h, tags=tuple(ptags+[self.node.id]), fill=color, outline = lOutlineColor ) elif shape in [1, 'triang', 'triang1']: self.id = self.iconTag = canvas.create_polygon( pcx-(h/2)+2, pcy-w+2, pcx-h-2, pcy+w-2, pcx+h+2, pcy+w-2, tags=tuple(ptags+[self.node.id]), fill=color, outline = lOutlineColor ) elif shape in [2, 'rect', 'rect1', 'rect2', 'square']: self.id = self.iconTag = canvas.create_rectangle( pcx-w, pcy-h, pcx+w, pcy+h, tags=tuple(ptags+[self.node.id]), fill=color, outline = lOutlineColor ) elif shape in [3, 'diamond' ]: self.id = self.iconTag = canvas.create_polygon( pcx-h-3, pcy, pcx, pcy-h-3, pcx+h+3, pcy, pcx, pcy+h+3, tags=tuple(ptags+[self.node.id]), fill=color, outline = lOutlineColor ) else: #elif shape in [4, 'pentagon']: self.id = self.iconTag = canvas.create_polygon( pcx-(h/2)+1, pcy-w+2, pcx-h-4, pcy+w/2-4, pcx-h/2-2, pcy+w-2, pcx+h/2, pcy+w-2, pcx+h+2, pcy+w/2-4, tags=tuple(ptags+[self.node.id]), fill=color, outline = lOutlineColor ) # add balloon string txt = 'name : '+self.name + '\ntype : '+ self.datatypeObject['name'] if self.datatypeObject['dataDescr'] is not None: txt = txt + '\ntype descr. : '+self.datatypeObject['dataDescr'] txt = txt + '\nindex: '+str(self.number) if isinstance(self, InputPort): txt = txt + '\nrequired: ' + str(self.required) txt = txt + '\nsingle connection: '+ str(self.singleConnection) if self.balloon is not None: txt = txt + '\nport Descr. :' + self.balloon + '\n' # bind balloon ed.balloons.tagbind(canvas, self.id, txt) self.balloonBase = txt if self.node.network is not None: if isinstance(self, InputPort) or \ isinstance(self, SpecialInputPort): self.node.network.inPortsId[self.id] = self elif isinstance(self, OutputPort) or \ isinstance(self, SpecialOutputPort): self.node.network.outPortsId[self.id] = self if isinstance(self, InputPort): self.inputDataBallon() def destroyIcon(self): """called by net.deleteNodes(): delete port menu, delete port icon""" self.menu.destroy() self.menu = None self.deleteIcon() def computePortPosX(self): """compute offset from left. Convention: if a port is invisible, we set this port's relposx to the values of the last visible port left of this port, or to 0 and 0, if all ports left of it are invisible Note: relposx is the center of a port icon, not the left or right edge """ #print "computePortPosX" oldposx = self.relposx pnumber = self.number gap = 5 # gap between if pnumber == 0: if self.visible: relposx = gap + self.halfPortWidth else: relposx = 0 return relposx if isinstance(self, InputPort): ports = self.node.inputPorts else: ports = self.node.outputPorts prevPort = None # we loop backwards over ports and try to find one that is visible for i in range(len(ports))[(pnumber-1)::-1]: # going backwards prevPort = ports[i] if prevPort.visible: break else: prevPort = None # reset to None if prevPort is invisible if prevPort: # we have a port left of us, that is visible if self.visible: relposx = prevPort.relposx + prevPort.halfPortWidth + \ gap + self.halfPortWidth else: relposx = prevPort.relposx else: # no visible ports left of us if self.visible: relposx = gap + self.halfPortWidth else: relposx = 0 return relposx def destinationPortsCoords(self, tag): # get coordinates of all potential ports to connect to canvas = self.node.iconMaster items = canvas.find_withtag(tag) c = [] il = [] getc = canvas.coords for i in items: if self.id==i: continue ci = getc(i) c.append( ( (ci[0]+ci[2])/2, (ci[1]+ci[3])/2 ) ) il.append(i) # also look for ImageNodes from NetworkEditor.items import ImageNode for node in self.node.network.nodes: if node == self.node: continue if not isinstance(node, ImageNode): continue x, y = node.posx, node.posy for p in node.inputPorts: c.append( (p.posRotated[0]+x, p.posRotated[1]+y) ) il.append( p ) return Numeric.array(c, 'i'), il def getDescr(self): """Returns a dictionnary providing the port's configuration. """ cfg = {} cfg['name'] = self.name if self.datatypeObject['name'] != None: cfg['datatype'] = self.datatypeObject['name'] if isinstance(self, InputPort) \ and self.datatypeObject['name'] != self.originalDatatype: cfg['originalDatatype'] = self.originalDatatype if self.balloon: cfg['balloon'] = self.balloon cfg['width'] = self.halfPortWidth*2 cfg['height'] = self.halfPortHeight*2 cfg['color'] = self.datatypeObject['color'] cfg['shape'] = self.datatypeObject['shape'] if self.callbacks['beforeConnect'][1] is not None: cfg['beforeConnect'] = self.callbacks['beforeConnect'][1] if self.callbacks['afterConnect'][1] is not None: cfg['afterConnect'] = self.callbacks['afterConnect'][1] if self.callbacks['beforeDisconnect'][1] is not None: cfg['beforeDisconnect'] = self.callbacks['beforeDisconnect'][1] if self.callbacks['afterDisconnect'][1] is not None: cfg['afterDisconnect'] = self.callbacks['afterDisconnect'][1] return cfg def configure(self, redo=1, **kw): """configure a Port object. This also sets the port's attribute _modified=True. Supports the folloing keywords: width : icon width (int) height : icon on height (int) name : port name (string) balloon : tooltip describing data (string) datatype : datatype (string or data type object) """ changeIcon = 0 handledOps = {} for k,v in kw.items(): if k == 'width': handledOps[k] = v if v/2 != self.halfPortWidth: self.halfPortWidth = v/2 changeIcon = 1 elif k=='height': handledOps[k] = v if v/2 != self.halfPortHeight: self.halfPortHeight = v/2 changeIcon = 1 elif k == 'name': v = v.replace(' ', '_') handledOps[k] = v if v != self.name: if self.node: wdescr = self.node.widgetDescr if wdescr.has_key(self.name): wdescr[v] = wdescr[self.name] del wdescr[self.name] if isinstance(self, InputPort): port = 'ip' else: port = 'op' self.node.updateCode(port=port, action='rename', newname=v, oldname=self.name, tagModified=True) if isinstance(self,InputPort): self.node.inputPortByName[v] = self self.node.inputPortByName.pop(self.name) else: # OutputPort self.node.outputPortByName[v] = self self.node.outputPortByName.pop(self.name) self.name = v changeIcon = 1 elif k=='datatype': handledOps[k] = v self.setDataType(v, tagModified=True) changeIcon = 1 elif k=='originalDatatype': self.originalDatatype = v elif k=='balloon': handledOps[k] = v self.balloon = v changeIcon = 1 elif k=='defaultValue': handledOps[k] = v self.defaultValue = v elif k in ['beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect']: handledOps[k] = v self.setCallback(v, k) if len(handledOps.keys()): self._setModified(True) if redo: if changeIcon and self.iconTag: self.deleteIcon() self.createIcon() changeIcon = 0 return changeIcon, handledOps def updateIconPosition(self): """move this icon to new location, also update connections """ #print "updateIconPosition" if not self.visible or not self.id: return oldposx = self.relposx newposx = self.computePortPosX() # move ports self.relposx = newposx dx = newposx - oldposx if not dx: return # move port icon canvas = self.node.iconMaster canvas.move(self.id, dx, 0) # move connections for c in self.connections: c.updatePosition() def startConnectRubberBand(self, event): if isinstance(self, SpecialOutputPort): self.ipCoords, self.ipId = self.destinationPortsCoords( 'specialInputPort') else: self.ipCoords, self.ipId = self.destinationPortsCoords('inputPort') if len(self.ipCoords)==0: return c = self.rubberBandOrig = self.getCenterCoords() self.secondNodePort = None self.oldColor = self.node.getColor() self.node.setColor('green') canvas = self.node.iconMaster self.rubberBandLine = canvas.create_line( c[0], c[1], c[0], c[1], width=3, fill='green' ) num = event.num self.mouseButtonFlag = self.mouseButtonFlag | num canvas.bind(""%num, self.drawRubberBand) canvas.bind(""%num, self.connectNodesEnd) def drawRubberBand(self, event=None): canvas = self.node.iconMaster x = canvas.canvasx(event.x) y = canvas.canvasy(event.y) # compute distance to all input ports dist = self.ipCoords - (x, y) dist = Numeric.sum( dist*dist, 1 ) indsort = Numeric.argsort( dist ) portId = self.ipId[indsort[0]] #portId can be a canvas item for regular nodes or an InputPort #for ImageNodes if isinstance(portId, Port): port = portId else: port = self.node.network.inPortsId[portId] # closest port snap = 0 if dist[indsort[0]] < 400 or len(indsort)==1: # we are close enough to snap if port.node != self.node and \ port not in self.children: x, y = port.getCenterCoords() if self.secondNodePort: self.secondNodePort.node.setColor(self.oldColor2) self.secondNodePort = port self.oldColor2 = port.node.getColor() port.node.setColor('green') snap = 1 canvas.coords(self.rubberBandLine, self.rubberBandOrig[0], self.rubberBandOrig[1], x, y ) if not snap: if self.secondNodePort: self.secondNodePort.node.setColor(self.oldColor2) self.secondNodePort = None # end the command, remove rubberband, connect nodes def connectNodesEnd(self, event=None): num = event.num self.mouseButtonFlag = self.mouseButtonFlag & ~num self.node.setColor(self.oldColor) canvas = self.node.iconMaster canvas.unbind(""%num) canvas.unbind(""%num) canvas.delete(self.rubberBandLine) if not self.secondNodePort: return self.secondNodePort.node.setColor(self.oldColor2) ed = self.node.network kw = ed.defaultConnectionOptions kw['mode'] = ed.defaultConnectionMode kw.update(ed.defaultConnectionOptions) self.createConnection( self.secondNodePort, **kw) def createConnection(self, port2, **kw): ed = self.node.network port1 = self if isinstance(self, SpecialOutputPort): conn = ed.specialConnectNodes( *(port1.node, port2.node, port1.number, port2.number), **kw ) else: print 'connect', port1.node.name, port2.node.name, \ port1.name, port2.name, kw print port1.node conn = ed.connectNodes( *(port1.node, port2.node, port1.number, port2.number), **kw ) def compareToOrigPortDescr(self): """Compare this port to the original portDescr defined in a given network node base class and return a dictionary with the differences """ #print "compareToOrigPortDescr", self.name id = self._id lConstrkw = {'masternet': self.node.network} lConstrkw.update(self.node.constrkw) dummy = apply(self.node.__class__,(),lConstrkw) # we need the base class node if isinstance(self, (OutputPort, ImageOutputPort)): origDescr = dummy.outputPortsDescr[id] #print "origDescr", origDescr elif isinstance(self, (InputPort, ImageInputPort)): origDescr = dummy.inputPortsDescr[id] ownDescr = self.getDescr().copy() #print "ownDescr", ownDescr descr = {} for k,v in ownDescr.items(): if k in origDescr.keys(): if v != origDescr[k]: descr[k] = v ownDescr.pop(k) elif k == '_previousWidgetDescr': ownDescr.pop(k) elif k=='datatype' and ownDescr['datatype']== 'None': ownDescr.pop(k) elif k == 'width' and v == self.datatypeObject['width']: ownDescr.pop(k) elif k == 'height' and v == self.datatypeObject['height']: ownDescr.pop(k) elif k == 'color' and v == self.datatypeObject['color']: ownDescr.pop(k) elif k == 'shape' and v == self.datatypeObject['shape']: ownDescr.pop(k) elif k == 'required' and v == True: ownDescr.pop(k) elif k == 'singleConnection' and v == True: ownDescr.pop(k) elif k == 'cast' and v == True: ownDescr.pop(k) # and add all the stuff we didnt find in the orig descr descr.update(ownDescr) #print "descr", descr return descr class InputPort(Port): def __init__(self, name, node, datatype=None, required=True, balloon=None, width=None, height=None, singleConnection=True, beforeConnect=None, afterConnect=None, beforeDisconnect=None, afterDisconnect=None, shape=None, color=None, cast=True, originalDatatype=None, _previousWidgetDescr=None, # not used but will silence an "unexpected keyword argument" message defaultValue=None, vector=(0,-1) ): self.number = len(node.inputPorts) self.parents = [] # list of ports connected to this one self.widget = None self.editWtk = None self.delWtk = None self._missingDims = None # this will hold the number of missing # optional dimensions for AnyArrayType ports self._cast = cast self._previousWidgetDescr = None # Will hold widgetDescr when unbound self.widgetInMacro = {} # used to keep track of widgets re-bound to # macro nodes Port.__init__(self, name, node, datatype=datatype, balloon=balloon, width=width, height=height, beforeConnect=beforeConnect, afterConnect=afterConnect, beforeDisconnect=beforeDisconnect, afterDisconnect=afterDisconnect, shape=shape, color=color, originalDatatype=originalDatatype, vector=vector) self.mouseAction[''] = self.selfDefaultValueEditor self.defaultValue = defaultValue self.required = required self.singleConnection = singleConnection # port will accept only one # parent and provide the computational method with # an argument that is not a list created from multiple # parents (usually used for widgets, float, string etc...) # when new data is sent trough an output port, _newdata of all input # ports of children nodes is set to 1. When a child node has # processed the data it resets is to 0 # Also, when a connection is deleted, the child node's input port's # _newdata is set to True (since it used to provide data and doesn't # anymore) self._newdata = False # _validation is set to 'uncheck' when data new is output # it is set to 'valid' or 'invalid' in getData # it is used in getData to avoid revalidating data self._validationStatus = 'unchecked' def selfDefaultValueEditor(self, event): if self.required is True \ or len(self.connections) != 0: return if hasattr(self, 'topEntry') is False: self.topEntry = Tkinter.Toplevel() self.defaultValueVar = Tkinter.StringVar() if type(self.defaultValue) == types.StringType: self.defaultValueVar.set('\''+self.defaultValue +'\'') else: self.defaultValueVar.set(self.defaultValue) self.typedValueTK = Tkinter.Entry( master=self.topEntry, textvariable=self.defaultValueVar, relief='sunken', bg='yellow') self.topEntry.overrideredirect(1) self.topEntry.bind("", self.esc_cb) self.topEntry.bind("", self.return_cb) self.topEntry.bind("", self.return_cb) w = event.widget self.topEntry.geometry('+%d+%d'%(w.winfo_rootx() + event.x-30, w.winfo_rooty() + event.y-10)) self.typedValueTK.pack() self.typedValueTK.focus_set() self.typedValueTK.config(cursor='xterm') lenDefault = len(self.defaultValueVar.get()) self.typedValueTK.selection_to(lenDefault) def esc_cb(self, event): # return should destroy the topEntry #print "esc_cb" del self.defaultValueVar self.typedValueTK.destroy() del self.typedValueTK self.topEntry.destroy() del self.topEntry def return_cb(self, event): # return should destroy the topEntry #print "return_cb" try: lDefaultValue = eval(self.defaultValueVar.get()) if lDefaultValue != self.defaultValue: self.defaultValue = lDefaultValue self._modified = True self._newdata = True self.data = 'no data yet' self.inputDataBallon() finally: self.esc_cb(event) def hasNewData(self): # returns 1 if at least 1 parent provides new data # if a widget is connected to this port if self.widget: return self.widget._newdata else: return self._newdata def hasNewValidData(self): """returns 1 if at least 1 parent provides new and valid data """ # if a widget is connected to this port if self.widget: return self.widget._newdata elif self.data == 'no data yet': return False else: return self._newdata def releaseData(self): # the data provided by all connections has been processed # successfully by this node if self.widget: self.widget._newdata = False self.widget.lastUsedValue = self.widget.get() else: self._newdata = False def inputDataBallon(self): if self.balloonBase and self.id and self.network: datastr = self.getInputDataStr(maxLen=80) self.getEditor().balloons.tagbind(self.network.canvas, self.id, self.balloonBase + '\n' + datastr) def getInputDataStr(self, maxLen = None): # return a string that would be used in input port's show data length = 0 if isinstance(self.data, Numeric.ArrayType): if len(self.data.shape) == 0: length = 0 else: length = reduce( lambda x,y: x*y, self.data.shape) elif hasattr(self.data, '__len__'): try: length = len(self.data) except TypeError, e: length = len(str(self.data)) if length > 1000: return '-'*80 + '\n'+ "Too much data to print" datastr = repr(self.data) if maxLen: if len(datastr) < maxLen: return '-'*80 + '\n'+ datastr else: return '-'*80 + '\n'+ datastr[:maxLen] + '...' else: return '-'*80 + '\n'+ datastr def getDescr(self): """Returns a dictionnary providing the port's configuration. """ #print "InputPort getDescr" cfg = Port.getDescr(self) if self.name == 'parent': cfg['retainPosition'] = self.retainPosition.get() # for now, we always save it as we don't know yet what # will be the definitive default behaviour cfg['parenting'] = self.cascadeMenuVariable.get() cfg['cast'] = self._cast.get() cfg['required'] = self.required cfg['singleConnection'] = self.singleConnection cfg['defaultValue'] = self.defaultValue pdscr = self._previousWidgetDescr if pdscr is not None and len(pdscr): cfg['_previousWidgetDescr'] = pdscr return cfg def configure(self, redo=1, **kw): """configure a InputPort object. Supports the folloing keywords: width : icon width (int) height : icon on height (int) name : port name (string) datatype : datatype (string or data type object) required : port only runs if valid data is provided on this port (Boolean) singleConnection : ports accepts only one connection (Boolean) cast : data will be casted into desired type only if cast is True parenting : type of parenting (if self.name is 'parent') """ changeIcon, handledOps = apply( Port.configure, (self, 0), kw ) for k,v in kw.items(): if k == 'parenting': assert self.name == 'parent' assert v in ('none','current','siblings','all'), v handledOps[k] = v if v != self.cascadeMenuVariable.get(): self.cascadeMenuVariable.set(v) elif k=='retainPosition': assert type(v) is types.BooleanType handledOps[k] = v if v != self.retainPosition.get(): self.retainPosition.set(v) elif k == 'cast': assert (type(v) is types.BooleanType) or (type(v) is types.IntType and v in (0,1) ) handledOps[k] = v if v != self._cast.get(): self._cast.set(v) elif k == 'required': assert type(v) is types.BooleanType handledOps[k] = v if v != self.required: self.required = v changeIcon = 1 elif k in ['beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect']: self.setCallback(v, k) elif k=='singleConnection': assert v in [True, False, 'auto'] handledOps[k] = v if v != self.singleConnection: self.singleConnection = v changeIcon = 1 elif k=='_previousWidgetDescr': self._previousWidgetDescr = v self.menu.add_command(label='Rebind Widget', underline=0, command=self.rebindWidget) if len(handledOps.keys()): self._setModified(True) # update the port's description self.node.inputPortsDescr[self.number].update( handledOps ) if redo: if changeIcon and self.iconTag: self.deleteIcon() self.createIcon() def createWidget(self, rescale=1, descr=None): """Method to create and configure a port's widget. - if descr is specified update the node's widget description dictionary - create the widget from its description - hide the port's icon - set the port's widget attribute Options that are only usable with the constructor (not in the configure method) are extracted from descr and used to instanciate the widget. """ if descr: self.node.widgetDescr[self.name] = descr else: descr = self.node.widgetDescr.get(self.name, None) if descr is None: return modif = descr.pop('_modified', False) descr = descr.copy() # we create a non graphical widget that will be delete if the # GUI is created and replaced by a real widget if not self.getEditor().hasGUI: value = descr.get('initialValue', None) self.widget = NGWidget() if value: self.widget.set(value) return # get a copy of the widget's constructor only options widgetClass = descr.pop('class') if type(widgetClass) is types.StringType: widgetClass = widgetsTable[widgetClass] # instanciate the widget w = apply( widgetClass, (self, ), descr) # new widget, thus set _modifed and _original attribute accordingly w._setModified(modif) w._setOriginal(False) self.widget = w # set this port's widget emneter # delete port icon, move all icons right of it self.deleteIcon() self.replosx = self.computePortPosX() for port in self.node.inputPorts[self.number+1:]: if port.visible: port.updateIconPosition() # delete all connections to this port self.node.network.deleteConnections(self.connections, 0) self.node.autoResizeX() ## def configureWidget(self, **descr): ## # configure widget will create new widget and replace old one for ## # some options ## if self.widget: ## w, descr = apply( self.widget.configure, (self,), descr) ## if isinstance(w, PortWidget): ## w.set( self.widget.get(), run=0l ) ## self.deleteWidget() ## self.widget = w # set this port's widget emneter ## self.node.widgetDescr[self.name] = w.getDescr() ## self.deleteIcon() # hide port icon ## if w.inNode: ## self.node.hideInNodeWidgets() ## self.node.showInNodeWidgets() def rebindWidget(self, run=True): if self._previousWidgetDescr: wdescr = self._previousWidgetDescr if not self.node.isExpanded(): self.node.toggleNodeExpand_cb() self.createWidget(descr=wdescr) self.node.autoResize() if self.network.runOnNewData.value is True and run is True: self.node.schedule() def unbindWidget(self, force=False): #print "unbindWidget" ed = self.node.getEditor() if ed is None:# or isinstance(self.widget, NGWidget): return inNode = False if self.widget: if hasattr(self.widget,'onUnbind'): self.widget.onUnbind() if isinstance(self.widget, NGWidget) is False: try: # Uh, I know, this is not nice, but how else do I find out # if a menu entry is added to self.menu??? # do we have this entry alread? If yes, do nothing self.menu.index('Rebind Widget') except: # add new menu entry if not there self.menu.add_command(label='Rebind Widget', underline=0, command=self.rebindWidget) if hasattr(self.widget, 'inNode') and self.widget.inNode: inNode = True self.node.nodeWidgetMaster.configure(width=1) self.node.nodeWidgetMaster.configure(height=1) pw = self.widget.getDescr() else: pw = {} pw['initialValue'] = self.widget.get() self._previousWidgetDescr = pw self.deleteWidget() if inNode: wM = self.node.nodeWidgetMaster width = wM.winfo_reqwidth() height = wM.winfo_reqheight() wM.configure(width=width, height=height) if ed.hasGUI: self.node.autoResize() self.node.addSaveNodeMenuEntries() # unbinding a widget sets the port _modified flag self._setModified(True) def deleteWidget(self): """destroy the widget, remove the widgetDescription from dict and show the port icon, return the decription of the deleted widget """ #print "deleteWidget" if hasattr(self.widget,'onDelete'): self.widget.onDelete() wasExpanded = False inNode = False if isinstance(self.widget, NGWidget) is False: inNode = self.widget.inNode wdescr = self.widget.configure() # get widget's configuration else: wdescr = {} if inNode and self.node.isExpanded(): # repack node before destroying self.node.hideInNodeWidgets() # widget else we loose size wasExpanded = True del self.node.widgetDescr[self.name] # update node's widget description dict # destroy the icon if isinstance(self.widget, NGWidget) is False: self.widget.destroy() if wasExpanded and len(self.node.widgetDescr.keys()):# repack node self.node.showInNodeWidgets() #before destroying else: resize=True for v in self.node.widgetDescr.values(): if v.has_key('master') and v['master'] in ['node', 'Node']: resize = False break if resize is True and isinstance(self.widget, NGWidget) is False: # only resize, if we have no node widget left self.node.nodeWidgetMaster.configure(width=1, height=1) self.node.autoResize() # create port icon, move ports to the right, move connections self.createIcon() # show port's Icon self.relposx = self.computePortPosX() for port in self.node.inputPorts[self.number+1:]: if port.visible: port.updateIconPosition() self.widget = None # deleting a widget sets the port _modified flag self._setModified(True) return wdescr def buildIcons(self, resize=True): self.relposy = 0 Port.buildIcons(self, resize=resize) def badDataOnInputPort(self, parent): self._validationStatus = 'invalid' txt = 'Running node: %s \n' % self.node.name txt += 'Bad Data on port: %s \n' % self.name txt += 'comming from port: %s %s \n' % (parent.node.name, parent.name) txt += 'Active Casting:: %s' % self._cast.get() warnings.warn(txt) return False, None def getParentPortData(self, parent): """If data is not already marked as valid, try to validate it, if it fails, try to cast it, if it fails check for sequence of 1 object of proper type. Return validation status (true or false) and data. In the 2 latter cases, status of port's data remains 'unchecked'. """ #print "getParentPortData parent", parent, parent.data #print "getParentPortData self", self, self.data #if parent.data is None: # import pdb;pdb.set_trace() data = parent.data for conn in self.connections: if conn.port1 == parent: theConnection = conn break; else: # we didn't break assert False # we should have found the connection if theConnection.blocking is True \ and (type(data) == types.StringType) \ and (data == 'no data yet'): return False, data #None elif self.datatypeObject is None: self._validationStatus='valid' return True, data #Check is this input port's data has already been validates elif self._validationStatus=='valid': return True, parent.data elif self._validationStatus=='invalid': return False, parent.data elif self._validationStatus=='missingDims': data = parent.data for i in range(self._missingDims): data = [data] return True, data elif self._validationStatus=='unchecked': # None is always consider has valid data # and all the nodes must knows how to deal with it if data is None: return True, data # check is data is of proper type typeObj = self.datatypeObject if isinstance(typeObj, AnyType): # validate data ok = typeObj.validate(data) if ok: self._validationStatus='valid' return True, data if self._cast.get() in (0, False): return self.badDataOnInputPort(parent) # check for sequence of 1 element and accept if proper type try: # force exception if data is not a sequence if len(data)==1: # if sequence of length 1 data = data[0] ok = typeObj.validate(data) if ok: return True, data except: pass # isSequenceType return true for all instances #if isSequenceType(data) and len(data)==1: # data = data[0] # ok = typeObj.validate(data) # if ok: # return True, data # try to cast if there is a cast function ok, castdata = typeObj.cast(data) if ok: # we leave it _validationStatus unchecked return True, castdata # everything failed return self.badDataOnInputPort(parent) else: # AnyArrayType port # check that data can be turned into Numeric array #import pdb;pdb.set_trace() ok, lArray = typeObj.validate(data) if typeObj['datashape'] is None: if ok is True: self._validationStatus='valid' return True, data else: if self._cast.get() in (0, False): return self.badDataOnInputPort(parent) ok, castdata = typeObj.cast(data) if ok is True: self._validationStatus='unchecked' return True, castdata else: # check for sequence of 1 element and accept if proper type try: # force exception if data is not a sequence if len(data)==1: # if sequence of length 1 data = data[0] ok = typeObj.validate(data) if ok: return True, data else: ok, castdata = typeObj.cast(data) if ok is True: self._validationStatus='unchecked' return True, castdata else: return self.badDataOnInputPort(parent) except: return self.badDataOnInputPort(parent) if ok is False: if self._cast.get() in (0, False): return self.badDataOnInputPort(parent) else: # if port allows casting try ok, lArray = typeObj.cast(data) if ok is False: return self.badDataOnInputPort(parent) # the data is tagged unchecked when new data is presented # on the parent port, so we just leave it unchecked else: # remember this data has been casted self._validationStatus='casted' #print "casted" # lArray is now a Numeric array of the right data type # we now have to check dimensions # this functions return True when the dims match, the number # of missing optional dimensions, or False if required dims # fail the tests status, lArray = typeObj.validateDims(lArray) if status is True: if self._validationStatus=='casted': self._validationStatus='unchecked' return True, lArray else: self._validationStatus='valid' return True, data elif status is False: return self.badDataOnInputPort(parent) else: if self._validationStatus=='casted': self._validationStatus='unchecked' #return True, lArray else: self._validationStatus='missingDims' self._missingDims = status for i in range(status): data = [data] return True, data def getData(self): """ retrieve data for this port. the data is fetched from the parent port. 1 - if the parent node data is valide we return it 2 - if the data is not valid we return 'Stop' to prevent further execution 3 - if the parent port provide no data ('no data yet'): if the port is required we return 'Stop' if the port is optional we return 'no data yet' """ #print "getData", self #if self.node.name == 'output Ports' and self.name == 'if__if': #if self.name == 'nodes': # import pdb;pdb.set_trace() ed = self.getEditor() # if there are connections if len(self.parents): # if this port accepts only 1 connection if self.singleConnection is True: ok, allData = self.getParentPortData(self.parents[0]) if ok is False: if allData=='no data yet': # no data yet if self.required is True: self.data = 'no data yet' self.inputDataBallon() return 'Stop' else: # bad data self.data = 'no data yet' self.inputDataBallon() if ed.hasGUI and ed.verbose: s='Bad or missing data on port %s in node %s in network %s'%( self.name, self.node.name, self.node.network.name) warnings.warn(s) return 'Stop' # to prevent execution else: allData = [] # for each connection for parentPort in self.parents: data = parentPort.data ok, data = self.getParentPortData(parentPort) if ok is True: allData.append(data) if len(allData)==0: if self.required is True: if ed.hasGUI and ed.verbose: s='Data missing on required port %s in node %s in network %s'%( self.name, self.node.name, self.node.network.name) warnings.warn(s) self.data = 'no data yet' self.inputDataBallon() return 'Stop' else: allData = 'no data yet' elif len(self.parents)==1 and self.singleConnection=='auto': allData = allData[0] else: # no parents if self.widget: # get value from widget allData = self.widget.get() # NO validation on data from widgets ??? #ok, data = self.getParentPortData(self.parents[0]) #if not ok: # return 'Stop' # to prevent execution elif self.required is False: allData = self.defaultValue else: self.data = 'no data yet' self.inputDataBallon() return 'Stop' if type(allData)==types.StringType and allData == 'no data yet': if self.required: if ed.hasGUI and ed.verbose: s='Data missing on required port %s in node %s in network %s'%( self.name, self.node.name, self.node.network.name) warnings.warn(s) self.data = 'no data yet' self.inputDataBallon() return 'Stop' # else: # # get arguments description # sign = getargspec(self.node.dynamicComputeFunction) # args, varargs, varkw, defaults = sign # if defaults is not None: # optargs = args[-len(defaults):] # for i,argname in enumerate(optargs): # if argname==self.name: # allData = defaults[i] # elif len(self.parents) == 0: #no connections # allData = None # save data for viewer for instance self.data = allData self.inputDataBallon() return allData class OutputPort(Port): def __init__(self, name, node, datatype='None', balloon=None, width=None, height=None, beforeConnect=None, afterConnect=None, beforeDisconnect=None, afterDisconnect=None, shape=None, color=None, vector=(0,1)): self.number = len(node.outputPorts) self.children = [] # list of ports connected to this one Port.__init__(self, name, node, datatype, balloon, width, height, beforeConnect, afterConnect, beforeDisconnect, afterDisconnect, shape=shape, color=color, vector=vector) self.mouseAction[''] = self.startConnectRubberBand self.delWtk = None def setDataType(self, datatype, tagModified=True, makeOriginal=False): """set the port's data type. Update Port's icon if visible Update connections color""" ed = self.getEditor() Port.setDataType(self, datatype, tagModified=tagModified) # for output ports we also check to see if input port that are # connected take their type from output port for c in self.connections: p = c.port2 # we check if the port must be mutated before mutating it typename = p.datatypeObject['name'] if typename=='None' or typename != p.originalDatatype: p.setDataType(datatype, tagModified=tagModified) if ed.hasGUI: # change the connection color col = p.datatypeObject['color'] c.unhighlightOptions['fill'] = col c.iconMaster.itemconfigure(c.iconTag, fill=col) if tagModified: self._setModified(True) if makeOriginal: self.originalDatatype = datatype def outputData(self, data): self.data = data ed = self.getEditor() if ed.hasGUI: datastr = self.getDataStr(maxLen=80) node = self.node ed.balloons.tagbind(node.network.canvas, self.id, self.balloonBase+'\n'+datastr) for c in self.connections: p = c.port2 p._newdata = True p._validationStatus = 'unchecked' p._missingDims = None p.node.newData = 1 def resetData(self): self.data = None for c in self.connections: c.port2._newdata = False def configure(self, **kw): redo = 0 redo, handledOps = apply( Port.configure, (self, 0), kw ) if redo and self.iconTag: self.deleteIcon() self.createIcon() def buildIcons(self, resize=True): canvas = self.node.iconMaster bb = canvas.bbox(self.node.id) self.relposy = bb[3]-bb[1] Port.buildIcons(self, resize=resize) # FIXME maybe special ports should be hardcoded rather than lists class SpecialInputPort(Port): def __init__(self, name, node, datatype='triggerIn', balloon=None, width=8, height=8, vector=(-1,0)): self.number = len(node.specialInputPorts) self.parents = [] # list of ports connected to this one self.widget = None Port.__init__(self, name, node, datatype, balloon, \ width, height, vector=vector) self.required = False def createIcon(self, pcx=None, pcy=None): # compute offest from upper left corner self.relposx = 0 self.relposy = ((self.number*3)+2)*self.halfPortHeight Port.createIcon(self, pcx, pcy) def computePortPosX(self): relposx = 0 return relposx class RunNodeInputPort(SpecialInputPort): def __init__(self, node, datatype='triggerIn', vector=(-1,0)): SpecialInputPort.__init__(self, 'runNode', node, datatype, balloon='trigger this node', vector=vector) class RunChildrenInputPort(SpecialInputPort): def __init__(self, node, datatype=None, vector=(-1,0)): SpecialInputPort.__init__( self, 'runChildren', node, datatype, balloon="trigger this node's children", vector=vector) class SpecialOutputPort(Port): def __init__(self, name, node, datatype='None', balloon=None, width=8, height=8, vector=(1,0)): self.children = [] # list of ports connected to this one Port.__init__(self, name, node, datatype, balloon, width, height, vector=vector) self.number = len(node.specialOutputPorts) self.mouseAction[''] = self.startConnectRubberBand def createIcon(self, pcx=None, pcy=None): # compute offest from upper left corner self.relposx = 0 self.relposy = ((self.number*3)+2)*self.halfPortHeight Port.createIcon(self, pcx, pcy) def computePortPosX(self): self.network.canvas.update_idletasks() bb = self.node.iconMaster.bbox(self.node.id) relposx = bb[2]-bb[0] return relposx class TriggerOutputPort(SpecialOutputPort): def __init__(self, node, vector=(1,0)): SpecialOutputPort.__init__(self, 'trigger', node, 'triggerOut', balloon="trigger connected node") ## ## ImagePort are port used by ImageNodes ## class ImagePort: def __init__(self): self.pos = [0,0] # in node's coord system (i.e relative to ul) self.posRotated = [0,0] # in node's coord system (i.e relative to ul) self.mouseAction[''] = self.startMovePort self.posx = 0 self.posy = 0 self.id = -1 # Connection.updatePosition returns port.if is None self._hasMoved = False def startMovePort(self, event=None): canvas = self.node.iconMaster num = event.num canvas.bind(""%num, self.movePort) canvas.bind(""%num, self.endMovePort) self.intersectionPointID = canvas.create_oval( 0, 0, 1, 1, fill='red') self.newpos = None self.movePort(event) def movePort(self, event=None): p0 = self.node.center canvas = self.node.iconMaster p1 = canvas.canvasx(event.x), canvas.canvasy(event.y) vx, vy = p1[0]-p0[0], p1[1]-p0[1] p1 = p0[0]+99999*vx, p0[1]+99999*vy xi, yi = self.node.segmentIntersection(p0, p1) #print 'INTERSECTION', xi, yi if xi: # FIXME I need to move out by size/2 along perpendicular to edge canvas.coords(self.intersectionPointID, xi-5, yi-5, xi+5, yi+5) self.newpos = (xi,yi) def endMovePort(self, event=None): canvas = self.node.iconMaster canvas.unbind(""%event.num) canvas.unbind(""%event.num) canvas.delete(self.intersectionPointID) del self.intersectionPointID if self.newpos: nx, ny = self.newpos self.moveTo(nx, ny) self._hasMoved = True del self.newpos self.node.currentNodeStyle = None def moveTo(self, x, y): # x y are in the canvas coordinate system self.posx = x self.posy = y self.node.movePortTo(self, x, y) def createWidget(self, rescale=1, descr=None): return def createIcon(self): return def getCenterCoords(self): hpx, hpy = self.posRotated if hasattr(self.node, 'shadowOffset'): sx, sy = self.node.shadowOffset else: sx = sy = 0 return hpx+self.node.posx+sx, hpy+self.node.posy+sy class ImageInputPort(ImagePort, InputPort): def __init__(self, name, node, datatype='None', singleConnection=True, **kw): kw1 = {} if kw.get('required', None): kw1['required'] = kw['required'] if kw.get('balloon', None): kw1['balloon'] = kw['balloon'] if kw.get('width', None): kw1['width'] = kw['width'] if kw.get('height,', None): kw1['height,'] = kw['height,'] if kw.get('beforeConnec', None): kw1['beforeConnec'] = kw['beforeConnec'] if kw.get('afterConnec', None): kw1['afterConnec'] = kw['afterConnec'] if kw.get('beforeDisconnect', None): kw1['beforeDisconnect'] = kw['beforeDisconnect'] if kw.get('afterDisconnect', None): kw1['afterDisconnect'] = kw['afterDisconnect'] if kw.get('shape', None): kw1['shape'] = kw['shape'] if kw.get('color', None): kw1['color'] = kw['color'] if kw.get('cast', None): kw1['cast'] = kw['cast'] if kw.get('originalDatatyp', None): kw1['originalDatatyp'] = kw['originalDatatyp'] if kw.get('defaultValu', None): kw1['defaultValu'] = kw['defaultValu'] #print 'CREATING PORT', name, kw1 InputPort.__init__(self, name, node, datatype=datatype, singleConnection=singleConnection, **kw1) ImagePort.__init__(self) # _hasMoved is used to generate code to move to port when we save # the network self._hasMoved = False class ImageOutputPort(ImagePort, OutputPort): def __init__(self, name, node, datatype='None', **kw): OutputPort.__init__(self, name, node, datatype) ImagePort.__init__(self) def outputData(self, data): pass mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/items.py0000644000175000017500000061704012115252423024431 0ustar debiandebian# Automatically adapted for numpy.oldnumeric Jul 23, 2007 by ######################################################################## # # Date: Nov. 2001 Author: Michel Sanner, Daniel Stoffler # # sanner@scripps.edu # stoffler@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner, Daniel Stoffler and TSRI # # revision: Guillaume Vareille # ######################################################################### # # $Header: /opt/cvs/python/packages/share1.5/NetworkEditor/items.py,v 1.423 2013/03/05 02:14:43 sanner Exp $ # # $Id: items.py,v 1.423 2013/03/05 02:14:43 sanner Exp $ # import warnings, re, sys, os, user, inspect, copy, cmath, math, types, weakref import copy, random import string, threading, traceback import Tkinter, Pmw, tkFileDialog, Image, ImageTk import numpy.oldnumeric as Numeric import numpy from tkSimpleDialog import askstring from mglutil.util.packageFilePath import getResourceFolderWithVersion from NetworkEditor.ports import InputPort, OutputPort, RunNodeInputPort, \ TriggerOutputPort, SpecialOutputPort from mglutil.util.callback import CallBackFunction from mglutil.util.uniq import uniq from mglutil.util.misc import ensureFontCase from mglutil.util.misc import suppressMultipleQuotes from NetworkEditor.widgets import widgetsTable from NetworkEditor.Editor import NodeEditor from NetworkEditor.ports import InputPortsDescr, OutputPortsDescr, Port # FIXME .. dependency on Vision is bad here from mglutil.util.packageFilePath import findFilePath ICONPATH = findFilePath('Icons', 'Vision') # namespace note: node's compute function get compiled using their origin # module's __dict__ as global name space from itemBase import NetworkItems class NetworkNodeBase(NetworkItems): """Base class for a network editor Node """ def __init__(self, name='NoName', sourceCode=None, originalClass=None, constrkw=None, library=None, progbar=0, **kw): NetworkItems.__init__(self, name) self.objEditor = None # will be an instance of an Editor object self.network = None # VPE network self.originalClass = originalClass if originalClass is None: self.originalClass = self.__class__ self.library = library self.options = kw if constrkw is None: constrkw = {} self.constrkw = constrkw # dictionary of name:values to be added to # the call of the constructor when node # is instanciated from a saved network # used in getNodeSourceCode and # getNodesCreationSourceCode if not sourceCode: sourceCode = """def doit(self):\n\tpass\n""" self.setFunction(sourceCode) self.readOnly = False #self.mtstate = 0 # used by MTScheduler to schedule nodes in sub-tree #self.thread = None # will hold a ThreadNode object self.newData = 0 # set to 1 by outputData of parent node self.widthFirstTag = 0 # tag used by widthFirstTraversal self.isRootNode = 1 # when a node is created it isnot yet a child self.forceExecution = 0 # if the forceExecution flag is set # we will always run, else we check if new data is available self.expandedIcon = False # True when widgets in node are shown self.widgetsHiddenForScale = False # will be set to true if node scales # below 1.0 while widget are seen # does the node have a progrs bar ? self.hasProgBar = progbar if self.hasProgBar: self.progBarH = 3 # Progress bar width else: self.progBarH = 0 self.scaleSum = 1.0 # scale factor for this node's icon self.highlightOptions = {'highlightbackground':'red'} self.unhighlightOptions = {'highlightbackground':'gray50'} self.selectOptions = {'background':'yellow'} self.deselectOptions = {'background':'gray85'} self.posx = 0 # position where node will be placed on canvas self.posy = 0 # these vales are set in the addNode method of # the canvas to which this node is added # the are the upper left corner if self.innerBox self.center = [0,0] # center of innerBox self.inputPorts = [] # will hold a list of InputPort objects self.outputPorts = [] # will hold a list of OutputPort objects self._inputPortsID = 0 # used to assign InputPorts a unique number self._outputPortsID = 0 # used to assign OutputPorts a unique number self._id = None # a unique node number, which is assigned in # network.addNodes() #self.widgets = {} # {widgetName: PortWidget object } # # Used to save the widget when it is unbound self.specialInputPorts = [] self.specialOutputPorts = [] self.specialPortsVisible = False self.children = [] # list of children nodes self.parents = [] # list of parent nodes self.nodesToRunCache = [] # list of nodes to be run when this node # triggers self.condition = None self.funcEditorDialog = None # ports description, these dictionaries are used to create ports # at node's instanciation self.inputPortsDescr = InputPortsDescr(self) # [{optionName:optionValue}] self.outputPortsDescr = OutputPortsDescr(self) # [{optionName:optionValue}] self.widgetDescr = {} # {widgetName: {optionName:optionValue}} self.mouseAction[''] = self.toggleNodeExpand_cb self.mouseAction[''] = self.startMoveOneNode self.hasMoved = False # set to True in net.moveSubGraph() def resize(self, event): return def onStoppingExecution(self): pass def beforeAddingToNetwork(self, network): NetworkItems.beforeAddingToNetwork(self, network) def safeName(self, name): """remove all weird symbols from node name so that it becomes a regular string usabel as a Python variable in a saved network """ name = name.replace(' ', '_') # name cannot contain spaces if name[0].isdigit(): # first letter cannot be a number name = '_'+name if name.isalnum(): return name if name.isdigit(): return name # replace weird characters by '_' newname = '' for c in name: if c.isalnum(): newname += c else:# if c in ['/', '\\', '~', '$', '!']: newname+= '_' return newname def getUniqueNodeName(self): return '%s_%d'%(self.safeName(self.name), self._id) def configure(self, **kw): """Configure a NetworkNode object. Going through this framework tags the node modified. Supports the following keywords: name: node name (string) position: node position on canvas. Must be a tuple of (x,y) coords function: the computational method of this node expanded: True or False. If True: expand the node specialPortsVisible: True or False. If True: show the special ports paramPanelImmediate: True or False. This sets the node's paramPanel immediate state """ ed = self.getEditor() for k,v in kw.items(): if k == 'function': #solves some \n issues when loading saved networks v = v.replace('\'\'\'', '\'') v = v.replace('\"\"\"', '\'') v = v.replace('\'', '\'\'\'') #v = v.replace('\"', '\'\'\'') kw[k] = v self.setFunction(v, tagModified=True) elif ed is not None and ed.hasGUI: if k == 'name': self.rename(v, tagModified=True) elif k == 'position': self.move(v[0], v[1], absolute=True, tagModified=True) elif k == 'expanded': if self.isExpanded() and v is False: self.toggleNodeExpand_cb() elif not self.isExpanded() and v is True: self.toggleNodeExpand_cb() elif k == 'specialPortsVisible': if self.specialPortsVisible and v is False: self.hideSpecialPorts(tagModified=True) elif not self.specialPortsVisible and v is True: self.showSpecialPorts(tagModified=True) elif k == 'paramPanelImmediate': self.paramPanel.setImmediate(immediate=v, tagModified=True) elif k == 'frozen': if self.frozen is True and v is False: self.toggleFrozen_cb() elif self.frozen is False and v is True: self.toggleFrozen_cb() def getDescr(self): """returns a dict with the current configuration of this node""" cfg = {} cfg['name'] = self.name cfg['position'] = (self.posx, self.posy) cfg['function'] = self.sourceCode cfg['expanded'] = self.isExpanded() cfg['specialPortsVisible'] = self.specialPortsVisible cfg['paramPanelImmediate'] = self.paramPanel.immediateTk.get() cfg['frozen'] = self.frozen return cfg def rename(self, name, tagModified=True): """Rename a node. remember the name has changed, resize the node if necessary""" if name == self.name or name is None or len(name)==0: return # if name contains ' " remove them name = name.replace("'", "") name = name.replace('"', "") self.name=name if self.iconMaster is None: return canvas = self.iconMaster canvas.itemconfigure(self.textId, text=self.name) self.autoResizeX() if tagModified is True: self._setModified(True) def displayName(self, displayedName, tagModified=True): """display the displyed node name. remember the name has changed, resize the node if necessary""" if displayedName is None or len(displayedName)==0: return # if name contains ' " remove them displayedName = displayedName.replace("'", "") displayedName = displayedName.replace('"', "") if self.iconMaster is None: return canvas = self.iconMaster canvas.itemconfigure(self.textId, text=displayedName) self.autoResizeX() if tagModified is True: self._setModified(True) def ischild(self, node): """returns True is self is a child node of node """ conn = self.getInConnections() for c in conn: if c.blocking is True: node2 = c.port1.node if node2 == node: return True else: return node2.ischild(node) return False def isMacro(self): """Returns False if this node is not a MacroNode, returns True if MacroNode""" return False def startMoveOneNode(self, event): # get a handle to the network of this node net = self.network # save the current selection if len(net.selectedNodes): self.tempo_curSel = net.selectedNodes[:] # clear the current selection net.clearSelection() # select this node so we can move it net.selectNodes([self], undo=0) # call the function to register functions for moving selected nodes net.moveSelectedNodesStart(event) # register an additional function to deselect this node # and restore the original selection num = event.num # FIXME looks like I am binding this many times ! net.canvas.bind(""%num, self.moveSelectedNodeEnd,'+') def moveSelectedNodeEnd(self, event): # get a handle to the network of this node net = self.network # clear the selection (made of this node) net.clearSelection() # if we saved a selection when we started moving this node, restore it if hasattr(self, 'tempo_curSel'): net.selectNodes(self.tempo_curSel, undo=0) del self.tempo_curSel net.canvas.unbind(""%event.num) self.updateCenter() def updateCenter(self): canvas = self.network.canvas bb = canvas.bbox(self.innerBox) cx = self.posx + (bb[2]-bb[0])/2 cy = self.posy + (bb[3]-bb[1])/2 self.center = [cx,cy] def isModified(self): # loop over all input ports, all widgets, all outputports, and report # if anything has been modified modified = False if self._modified: return True # input ports and widgets for p in self.inputPorts: if p._modified: modified = True break if p.widget: if p.widget._modified: modified = True break if modified is True: return modified # output ports for p in self.outputPorts: if p._modified: modified = True break return modified def resetModifiedTag(self): """set _modified attribute to False in node, ports, widgets.""" self._modified = False for p in self.inputPorts: p._modified = False if p.widget: p.widget._modified = False for p in self.outputPorts: p._modified = False def resetTags(self): """set _modified attribute to False in node, ports, widgets. Also, sets _original attribute to True in node, ports, widgets And we also reset the two flags in all connections from and to ports""" self._modified = False self._original = True for p in self.inputPorts: p._modified = False p._original = True if p.widget: p.widget._modified = False p.widget._original = True for c in p.connections: c._modified = False c._original = True for p in self.outputPorts: p._modified = False p._original = True for c in p.connections: c._modified = False c._original = True def getInputPortByName(self, name): # return the an input port given its name for p in self.inputPorts: if p.name==name: return p warnings.warn( 'WARNING: input port "%s" not found in node %s'%(name, self.name)) def getOutputPortByName(self, name): # return the an output port given its name for p in self.outputPorts: if p.name==name: return p warnings.warn( 'WARNING: output port "%s" not found in node %s'%(name, self.name)) def getOutputPortByType(self, type, name=None): # return the matching or first output port given its type if len(self.outputPorts) == 0: return None lDatatypeObject = \ self.outputPorts[0].getDatatypeObjectFromDatatype(type) lPort = None for p in self.outputPorts: if p.datatypeObject == lDatatypeObject: if p.name == name: return p elif p is None: return p elif lPort is None: lPort = p if lPort is not None: return lPort return None def getSpecialInputPortByName(self, name): # return the an input port given its name for p in self.specialInputPorts: if p.name==name: return p warnings.warn( 'WARNING: special input port "%s" not found in node %s'%(name, self.name)) def getSpecialOutputPortByName(self, name): # return the an output port given its name for p in self.specialOutputPorts: if p.name==name: return p warnings.warn( 'WARNING: special output port "%s" not found in node %s'%(name, self.name)) def getInConnections(self): l = [] for p in self.inputPorts: l.extend(p.connections) for p in self.specialInputPorts: l.extend(p.connections) return l def getOutConnections(self): l = [] for p in self.outputPorts: l.extend(p.connections) for p in self.specialOutputPorts: l.extend(p.connections) return l def getConnections(self): return self.getInConnections()+self.getOutConnections() def getWidgetByName(self, name): port = self.inputPortByName[name] if port: if port.widget: return port.widget ############################################################################## # The following methods are needed to save a network # getNodeDefinitionSourceCode() is called by net.getNodesCreationSourceCode() ############################################################################## def getNodeDefinitionSourceCode(self, networkName, indent="", ignoreOriginal=False): """This method builds the text-string to describe a network node in a saved file. networkName: string holding the networkName indent: string of whitespaces for code indentation. Default: '' ignoreOriginal: True/False. Default: False. If set to True, the node's attr _original is ignored (used in cut/copy/paste nodes inside a macro that came from a node library where nodes are marked original This method is called by net.getNodesCreationSourceCode() NOTE: macros.py MacroNode re-implements this method!""" lines = [] nodeName = self.getUniqueNodeName() self.nameInSavedFile = nodeName ################################################################## # add lines to import node from library, instanciate node, and # add node to network ################################################################## indent, l = self.getNodeSourceCodeForInstanciation( networkName, indent=indent, ignoreOriginal=ignoreOriginal) lines.extend(l) ################################################################## # fetch code that desccribes the changes done to this node compared # to the base class node ################################################################## txt = self.getNodeSourceCodeForModifications( networkName, indent=indent, ignoreOriginal=ignoreOriginal) lines.extend(txt) txt = self.getStateDefinitionCode(nodeName=nodeName,indent=indent) lines.extend(txt) return lines def getStateDefinitionCode(self, nodeName, indent=''): #print "getStateDefinitionCode" return '' def getNodeSourceCodeForModifications(self, networkName, indent="", ignoreOriginal=False): """Return the code that describes node modifications compared to the original node (as described in a node library)""" lines = [] ################################################################## # add lines for ports if they changed compared to the base class ################################################################## indent, l = self.getNodeSourceCodeForPorts(networkName, indent , ignoreOriginal) lines.extend(l) ################################################################## # add lines for widgets: add/delete/configure/unbind, and set value ################################################################## indent, l = self.getNodeSourceCodeForWidgets(networkName, indent, ignoreOriginal) lines.extend(l) ################################################################## # add lines for node changes (name, expanded, etc) ################################################################## indent, l = self.getNodeSourceCodeForNode(networkName, indent, ignoreOriginal) lines.extend(l) return lines def getNodeSourceCodeForInstanciation(self, networkName="masterNet", indent="", ignoreOriginal=False, full=0): """This method is called when saving a network. Here, code is generated to import a node from a library, instanciate the node, and adding it to the network.""" lines = [] ed = self.getEditor() nodeName = self.getUniqueNodeName() ################################################################## # Abort if this node is original (for example, inside a macro node) ################################################################## if self._original is True and not full and not ignoreOriginal: return indent, lines ed._tmpListOfSavedNodes[nodeName] = self k = self.__class__ ################################################################## # add line to import node from the library ################################################################## if self.library is not None: libName = self.library.varName else: libName = None #if self.library.file is not None: # user defined library ## if False: # skip this test!! ## l = "from mglutil.util.packageFilePath import "+\ ## "getObjectFromFile\n" ## lines.append(indent+l) ## lines.append(indent+"%s = getObjectFromFile( '%s', '%s')\n"%( ## k.__name__, self.library.file, k.__name__)) ## else: ## line = "from "+k.__module__+" import "+k.__name__+"\n" ## lines.append(indent+line) ## else: line = "from "+k.__module__+" import "+k.__name__+"\n" lines.append(indent+line) ################################################################## # add line with constructor keywords if needed ################################################################## constrkw = '' for name, value in self.constrkw.items(): constrkw = constrkw + name+'='+str(value)+', ' # this line seems redondant, # but is usefull when the network is saved and resaved # especially with pmv nodes constrkw = constrkw + "constrkw="+str(self.constrkw)+', ' ################################################################## # add line to instanciate the node ################################################################## line=nodeName+" = "+k.__name__+"("+constrkw+"name='"+\ self.name+"'" if libName is not None: line = line + ", library="+libName line = line + ")\n" lines.append(indent+line) ################################################################## # add line to add the node to the network ################################################################## txt = networkName+".addNode("+nodeName+","+str(self.posx)+","+\ str(self.posy)+")\n" lines.append(indent+txt) return indent, lines def getNodeSourceCodeForPorts(self, networkName, indent="", ignoreOriginal=False,full=0, dummyNode=None, nodeName=None): """Create code used to save a network which reflects changes of ports compared to the port definitions in a given network node of a node library. We create text to configure a port with changes, adding a port or deleting a port. If optional keyword 'full' is set to 1, we will append text to configure unchanged ports""" lines = [] ed = self.getEditor() if dummyNode is None: # we need the base class node if issubclass(self.__class__, FunctionNode): lKw = {'masternet':self.network} lKw.update(self.constrkw) dummyNode = apply(self.__class__,(), lKw) else: dummyNode = self.__class__() if nodeName is None: nodeName = self.getUniqueNodeName() ############################################################### # created save strings for inputPorts ############################################################### i = 0 lDeleted = 0 for index in range(len(dummyNode.inputPortsDescr)): # Delete remaining input ports if necessary if i >= len(self.inputPorts): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "%s.deletePort(%s.inputPortByName['%s'])\n"%( #txt = "%s.deletePort(%s.getInputPortByName('%s'))\n"%( nodeName, nodeName, dummyNode.inputPortsDescr[index]['name']) lines.append(indent+txt) continue ip = self.inputPorts[i] # delete input port if ip._id != index: if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "%s.deletePort(%s.inputPortByName['%s'])\n"%( #txt = "%s.deletePort(%s.getInputPortByName('%s'))\n"%( nodeName, nodeName, dummyNode.inputPortsDescr[index]['name']) lines.append(indent+txt) lDeleted += 1 continue # modify input port else: if ip._modified is True or ignoreOriginal is True: if full: changes = ip.getDescr() else: changes = ip.compareToOrigPortDescr() if len(changes): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "apply(%s.inputPortByName['%s'].configure, (), %s)\n"%( nodeName, dummyNode.inputPortsDescr[ip._id]['name'], str(changes) ) #txt = "apply(%s.inputPorts[%s].configure, (), %s)\n"%( # nodeName, ip.number, str(changes) ) lines.append(indent+txt) i = i + 1 continue # check if we have to add additional input ports for p in self.inputPorts[len(dummyNode.inputPortsDescr) - lDeleted:]: if p._modified is True or ignoreOriginal is True: descr = p.getDescr() if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "apply(%s.addInputPort, (), %s)\n"%( nodeName, str(descr) ) lines.append(indent+txt) ############################################################### # created save strings for outputPorts ############################################################### i = 0 for index in range(len(dummyNode.outputPortsDescr)): # Delete remaining output ports if necessary if i >= len(self.outputPorts): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "%s.deletePort(%s.outputPortByName['%s'])\n"%( nodeName, nodeName, dummyNode.outputPortsDescr[index]['name']) lines.append(indent+txt) continue op = self.outputPorts[i] # delete output port if not op._id == index: if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "%s.deletePort(%s.outputPortByName['%s'])\n"%( nodeName, nodeName, dummyNode.outputPortsDescr[index]['name']) lines.append(indent+txt) continue # modify output port else: if op._modified is True or ignoreOriginal is True: if full: changes = op.getDescr() else: changes = op.compareToOrigPortDescr() if len(changes): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "apply(%s.outputPortByName['%s'].configure, (), %s)\n"%( nodeName, dummyNode.outputPortsDescr[op._id]['name'], str(changes) ) #txt = "apply(%s.outputPorts[%s].configure, (), %s)\n"%( # nodeName, op.number, str(changes) ) lines.append(indent+txt) i = i + 1 continue # check if we have to add additional output ports for p in self.outputPorts[len(dummyNode.outputPortsDescr):]: if p._modified is True or ignoreOriginal is True: descr = p.getDescr() if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "apply(%s.addOutputPort, (), %s)\n"%( nodeName, str(descr) ) lines.append(indent+txt) # makes the specials ports visible if necessary if self.specialPortsVisible: if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt = "apply(%s.configure, (), {'specialPortsVisible': True})\n"%(nodeName) lines.append(indent+txt) return indent, lines def getNodeSourceCodeForWidgets(self, networkName, indent="", ignoreOriginal=False, full=0, dummyNode=None, nodeName=None): """Create code used to save a network which reflects changes of widgets compared to the widget definitions in a given network node of a node library. We create text to configure a widget with changes, adding a widget or deleting a widget. If optional keyword 'full' is set to 1, we will append text to configure unchanged widgets.""" lines = [] ed = self.getEditor() if dummyNode is None: # we need the base class node if issubclass(self.__class__, FunctionNode): lKw = {'masternet':self.network} lKw.update(self.constrkw) dummyNode = apply(self.__class__,(), lKw) else: dummyNode = self.__class__() if nodeName is None: nodeName = self.getUniqueNodeName() for i in range(len(self.inputPorts)): p = self.inputPorts[i] if p._id >= len(dummyNode.inputPortsDescr): origDescr = None elif p.name != dummyNode.inputPortsDescr[p._id]['name']: origDescr = None else: try: origDescr = dummyNode.widgetDescr[p.name] except: origDescr = None w = p.widget try: ownDescr = w.getDescr() except: ownDescr = None ############################################################# # if current port has no widget and orig port had no widget: # continue ############################################################# if ownDescr is None and origDescr is None: pass ############################################################# # if current port has no widget and orig port had a widget: # unbind the widget. Also, check if the port was modified: # unbinding and deleting a widget sets the port._modifed=True ############################################################# elif ownDescr is None and origDescr is not None: if (p._modified is True) or (ignoreOriginal is True): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) ## distinguish between "delete" and "unbind": # 1) Delete event (we don't have _previousWidgetDescr) if p._previousWidgetDescr is None: txt = "%s.inputPortByName['%s'].deleteWidget()\n"%( nodeName, self.inputPortsDescr[i]['name']) #txt = "%s.inputPorts[%d].deleteWidget()\n"%( # nodeName, i) lines.append(indent+txt) # 2) unbind event (we have _previousWidgetDescr) else: # first, set widget to current value txt1 = self.getNodeSourceCodeForWidgetValue( networkName, i, indent, ignoreOriginal, full) lines.extend(txt1) # then unbind widget txt2 = "%s.inputPortByName['%s'].unbindWidget()\n"%( nodeName, self.inputPortsDescr[i]['name']) #txt2 = "%s.inputPorts[%d].unbindWidget()\n"%( # nodeName, i) lines.append(indent+txt2) ############################################################# # if current port has widget and orig port had no widget: # create the widget ############################################################# elif ownDescr is not None and origDescr is None: if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) # create widget txt = \ "apply(%s.inputPortByName['%s'].createWidget, (), {'descr':%s})\n"%( nodeName, self.inputPortsDescr[i]['name'], str(ownDescr ) ) #txt = \ # "apply(%s.inputPorts[%d].createWidget, (), {'descr':%s})\n"%( # nodeName, i, str(ownDescr ) ) lines.append(indent+txt) # Hack to set widget. This fixes the ill sized nodes # when new widgets have been added to a node (MS) wmaster = ownDescr.get('master', None) if wmaster=='node': txt = "%s.inputPortByName['%s'].widget.configure(master='node')\n"%(nodeName, self.inputPortsDescr[i]['name']) lines.append(indent+txt) # set widget value txt = self.getNodeSourceCodeForWidgetValue( networkName, i, indent, ignoreOriginal, full, nodeName) lines.extend(txt) ############################################################# # if current port has widget and orig port has widget: # check if both widgets are the same, then check if changes # occured. # If widgets are not the same, delete old widget, create new ############################################################# elif ownDescr is not None and origDescr is not None: if ownDescr['class'] == origDescr['class']: if p.widget._modified is True or ignoreOriginal is True: if full: changes = ownDescr else: changes = w.compareToOrigWidgetDescr() if len(changes): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) if changes.has_key('command'): # extract and build the correct CB function name lCommand = changes['command'] lCommandStr = str(lCommand) lCbIndex = lCommandStr.find('.') lCbFuncName = nodeName + lCommandStr[lCbIndex:] lCbIndex = lCbFuncName.find(' ') lCbFuncName = lCbFuncName[:lCbIndex] changes['command'] = lCbFuncName # the changes['command'] is now a string # so, we need to get rid of the quote # that comes with the output lChangesStr = str(changes) lQuoteIndex = lChangesStr.find(lCbFuncName) lChanges = lChangesStr[:lQuoteIndex-1] + \ lCbFuncName + \ lChangesStr[lQuoteIndex+len(lCbFuncName)+1:] else: lChanges = str(changes) txt = \ "apply(%s.inputPortByName['%s'].widget.configure, (), %s)\n"%( nodeName, self.inputPortsDescr[i]['name'], lChanges) #txt = \ #"apply(%s.inputPorts[%d].widget.configure, (), %s)\n"%( # nodeName, i, str(changes)) lines.append(indent+txt) else: if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) txt1 = "%s.inputPortByName['%s'].deleteWidget()\n"%( nodeName, self.inputPortsDescr[i]['name']) #txt1 = "%s.inputPorts[%d].deleteWidget()\n"%( # nodeName,i) txt2 = \ "apply(%s.inputPortByName['%s'].createWidget, (), {'descr':%s})\n"%( nodeName, self.inputPortsDescr[i]['name'], str(ownDescr) ) #txt2 = \ #"apply(%s.inputPorts[%d].createWidget, (), {'descr':%s})\n"%( # nodeName, i, str(ownDescr) ) lines.append(indent+txt1) lines.append(indent+txt2) # and set widget value txt = self.getNodeSourceCodeForWidgetValue( networkName, i, indent, ignoreOriginal, full, nodeName) lines.extend(txt) return indent, lines def getNodeSourceCodeForWidgetValue(self, networkName, portIndex, indent="", ignoreOriginal=False, full=0, nodeName=None): """Returns code to set the widget value. Note: here we have to take unbound widgets into account.""" ############################################################# # Setting widget value sets widget _modified=True ############################################################# lines = [] returnPattern = re.compile('\n') # used when data is type(string) p = self.inputPorts[portIndex] # we need the base class node if issubclass(self.__class__, FunctionNode): lKw = {'masternet':self.network} lKw.update(self.constrkw) dummyNode = apply(self.__class__,(), lKw) else: dummyNode = self.__class__() if nodeName is None: nodeName = self.getUniqueNodeName() ############################################################# # Get data and original widget description to check if value # changed ############################################################# ## do we have a widget ? if p.widget: ## is it an original widget? try: origDescr = dummyNode.widgetDescr[p.name] except: ## or a new widget origDescr = {} val = p.widget.getDataForSaving() ## do we have an unbound widget ? elif p.widget is None and p._previousWidgetDescr is not None: origDescr = p._previousWidgetDescr val = p._previousWidgetDescr['initialValue'] ## no widget ? else: return lines ############################################################# # Compare data to default value, return if values are the same ############################################################# ## ## CASE 1: BOUND WIDGET: ## if p.widget: ## # MS WHY ignor original when cut and copy??? ## # ignoreOriginal is set True when cut|copy ## #if not p.widget._modified and not ignoreOriginal: ## # return lines ## # 1) compare value to initial value of widget descr ## wdescr = p.widget.getDescr() ## if wdescr.has_key('initialValue'): ## if val==wdescr['initialValue']: # value is initial value ## return lines ## # 2) else: compare to initialValue in node base class definition ## else: ## # 3) if the widget's original description has an initialValue ## if origDescr.has_key('initialValue'): ## if val == origDescr['initialValue']: ## return lines ## # 4) else, compare to widget base class defined initialValue ## else: ## origWidgetDescr = p.widget.__class__.configOpts ## if val == origWidgetDescr['initialValue']['defaultValue']: ## return lines ## ## CASE 2: UNBOUND WIDGET: ## else: ## descr = dummyNode.widgetDescr[p.name] ## #if descr.has_key('initialValue') and val == descr['initialValue']: ## # return lines ############################################################# # Create text to save widget value ############################################################# if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) if p.widget is None: #widget has been unbinded in the before or after adding to network #as it will be unbinded later we can safely rebind it to set the widget datatxt = '%s.inputPortByName[\'%s\'].rebindWidget()\n'%( nodeName, self.inputPortsDescr[portIndex]['name']) lines.append(indent+datatxt) if type(val)==types.StringType: if returnPattern.search(val): #multi - line data datatxt = \ '%s.inputPortByName[\'%s\'].widget.set(r"""%s""", run=False)\n'%( nodeName, self.inputPortsDescr[portIndex]['name'], val) else: datatxt = '%s.inputPortByName[\'%s\'].widget.set(r"%s", run=False)\n'%( nodeName, self.inputPortsDescr[portIndex]['name'], val) else: if hasattr(val, 'getDescr'): datatxt = '%s.inputPortByName[\'%s\'].widget.set(%s, run=False)\n'%( nodeName, self.inputPortsDescr[portIndex]['name'], val.getDescr() ) else: datatxt = '%s.inputPortByName[\'%s\'].widget.set(%s, run=False)\n'%( nodeName, self.inputPortsDescr[portIndex]['name'], val) lines.append(indent+datatxt) return lines def getNodeSourceCodeForNode(self, networkName, indent="", ignoreOriginal=False, full=0, nodeName=None): """return code to configure a node with modifications compared to the node definition in a node library. Note: """ lines = [] if (self._modified is False) and (ignoreOriginal is False): return indent, lines if full: changes = self.getDescr().copy() else: changes = self.compareToOrigNodeDescr() if changes.has_key('name'): changes.pop('name') # name is passed to constructor if changes.has_key('position'): changes.pop('position') # position is set in addNode if nodeName is None: nodeName = self.getUniqueNodeName() if changes.has_key('function'): changes.pop('function') # function has to be set separately: code, i = self.getNodeSourceCodeForDoit( networkName=networkName, nodeName=nodeName, indent=indent, ignoreOriginal=ignoreOriginal) if code: # Note: the line to add the code to the node is returned # within 'code' lines.extend(code) if len(changes): txt = "apply(%s.configure, (), %s)\n"%( nodeName, str(changes)) lines.append(indent+txt) return indent, lines def getNodeSourceCodeForDoit(self, networkName, nodeName,indent="", ignoreOriginal=False, full=0): lines = [] ed = self.getEditor() if (self._modified is True) or (ignoreOriginal is True): if nodeName != 'self': lines = self.checkIfNodeForSavingIsDefined( lines, networkName, indent) lines.append(indent+"code = \"\"\"%s\"\"\"\n"%self.sourceCode) lines.append(indent+"%s.configure(function=code)\n"% nodeName) return lines, indent def getAfterConnectionsSourceCode(self, networkName, indent="", ignoreOriginal=False): """Here, we provide a hook for users to generate source code which might be needed to adress certain events after connections were formed: for example, connections might generate new ports.""" # The MacroOutputNode subclasses this method and returns real data lines = [] return lines def compareToOrigNodeDescr(self): """compare this node to the original node as defined in a given node library, such as StandardNodes. Return a dictionary containing the differences.""" ownDescr = self.getDescr().copy() dummy = self.__class__() # we need to create a base class node # we dont need to add the self generated port # as we only look here for the node modifications for k,v in ownDescr.items(): if k == 'name': if v == dummy.name: ownDescr.pop(k) elif k == 'position': # this is a bit tricky: the dummy node # has not been added to a net yet, thus we assume a new position continue elif k == 'function': #we don't compare the prototype as it is automatically generated #the code itself is what may have be changed if v[v.find(':'):] == dummy.sourceCode[dummy.sourceCode.find(':'):]: ownDescr.pop(k) elif k == 'expanded': if v == dummy.inNodeWidgetsVisibleByDefault: ownDescr.pop(k) elif k == 'specialPortsVisible': if v == dummy.specialPortsVisible: ownDescr.pop(k) elif k == 'paramPanelImmediate': # default value is 0 if v == 0 or v is False: ownDescr.pop(k) elif k == 'frozen': # default is False if v == dummy.frozen: ownDescr.pop(k) return ownDescr def checkIfNodeForSavingIsDefined(self, lines, networkName, indent): """This method fixes a problem with saving macros that come from a node library. If only a widget value has changed, we do not have a handle to the node. Thus, we need to create this additional line to get a handle """ ed = self.getEditor() nodeName = self.getUniqueNodeName() if ed._tmpListOfSavedNodes.has_key(nodeName) is False: # This part is a bit complicated: we need to define the various # macro nodes if we have nested macros and they are not explicitly # created (e.g. a macro from a node library) from macros import MacroNetwork if isinstance(self.network, MacroNetwork): roots = self.network.macroNode.getRootMacro() for macro in roots[1:]: # skip root, because this is always defined!? nn = macro.getUniqueNodeName() # was nn = 'node%d'%macro._id if ed._tmpListOfSavedNodes.has_key(nn) is False: txt = "%s = %s.macroNetwork.nodes[%d]\n"%( nn, macro.network.macroNode.getUniqueNodeName(), macro.network.nodeIdToNumber(macro._id)) lines.append(indent+txt) ed._tmpListOfSavedNodes[nn] = macro # now process the 'regular' nodes #import pdb;pdb.set_trace() txt = "%s = %s.nodes[%d]\n"%(nodeName, networkName, self.network.nodeIdToNumber(self._id)) lines.append(indent+txt) ed._tmpListOfSavedNodes[nodeName] = self return lines ############################################################################# #### The following methods are needed to generate source code (not for saving #### networks) ############################################################################# def saveSource_cb(self, dependencies=False): """ the classname is extracted from the given filename """ lPossibleFileName = "New" + self.name + ".py" lPossibleFileNameSplit = lPossibleFileName.split(' ') initialfile = '' for lSmallString in lPossibleFileNameSplit: initialfile += lSmallString userResourceFolder = self.getEditor().resourceFolderWithVersion if userResourceFolder is None: return userVisionDir = userResourceFolder + os.sep + 'Vision' + os.sep userLibsDir = userVisionDir + 'UserLibs' + os.sep defaultLibDir = userLibsDir + 'MyDefaultLib' file = tkFileDialog.asksaveasfilename( initialdir = defaultLibDir , filetypes=[('python source', '*.py'), ('all', '*')], title='Save source code in a category folder', initialfile=initialfile ) if file: # get rid of the extension and of the path lFileSplit = file.split('/') name = lFileSplit[-1].split('.')[0] self.saveSource(file, name, dependencies) # reload the modified library self.getEditor().loadLibModule(str(lFileSplit[-3])) def saveSource(self, filename, classname, dependencies=False): f = open(filename, "w") map( lambda x, f=f: f.write(x), self.getNodeSourceCode(classname, networkName='self.masterNetwork', dependencies=dependencies) ) f.close() def getNodeSourceCode(self, className, networkName='self.network', indent="", dependencies=False): """This method is called through the 'save source code' mechanism. Generate source code describing a node. This code can be put into a node library. This is not for saving networks. dependencies: True/False False: the node is fully independent from his original node. True : the node is saved as a subclass of the original node, and only modifications from the original are saved. """ lines = [] kw = {} # keywords dict kw['dependencies'] = dependencies indent0 = indent txt, indent = apply(self.getHeaderBlock, (className, indent), kw) lines.extend(txt) # this make sure the port types will be avalaible when saved code will run lTypes = {} lSynonyms = {} lPorts = self.inputPorts + self.outputPorts for p in lPorts: lName = p.datatypeObject.__class__.__name__ if (lTypes.has_key(lName) is False) and \ (p.datatypeObject.__module__ != 'NetworkEditor.datatypes'): lTypes[lName] = p.datatypeObject.__module__ lName = p.datatypeObject['name'] if lSynonyms.has_key(lName) is False: lSplitName = lName.split('(') lBaseName = lSplitName[0] if (len(lSplitName) == 2) and (lSynonyms.has_key(lBaseName) is False): lDict = self.network.getTypeManager().getSynonymDict(lBaseName) if lDict is not None: lSynonyms[lBaseName] = lDict lDict = self.network.getTypeManager().getSynonymDict(lName) if lDict is not None: lSynonyms[lName] = lDict kw['types'] = lTypes kw['synonyms'] = lSynonyms txt, indent = apply(self.getInitBlock, (className, indent), kw) kw.pop('types') kw.pop('synonyms') lines.extend(txt) if dependencies is True: nodeName = 'self' indent, txt = self.getNodeSourceCodeForNode(self.network, indent=indent, full=0, nodeName=nodeName) lines.extend(txt) lines.extend("\n\n" + indent0 + " " + \ "def afterAddingToNetwork(self):\n" + \ indent + "pass\n") constrkw = {} constrkw.update( self.constrkw ) constrkw['name'] = className dummyNode = apply( self.originalClass,(),constrkw) indent, txt = self.getNodeSourceCodeForPorts(self.network, indent=indent, ignoreOriginal=False, full=0, dummyNode=dummyNode, nodeName=nodeName) lines.extend(txt) indent, txt = self.getNodeSourceCodeForWidgets(self.network, indent=indent, ignoreOriginal=False, full=0, dummyNode=dummyNode, nodeName=nodeName) lines.extend(txt) elif dependencies is False: txt = self.getComputeFunctionSourceCode(indent=indent) lines.extend(txt) txt, indent = apply(self.getPortsCreationSourceCode, (self.inputPorts, 'input', indent), kw) lines.extend(txt) txt, indent = apply(self.getPortsCreationSourceCode, (self.outputPorts, 'output', indent), kw) lines.extend(txt) txt, indent = self.getWidgetsCreationSourceCode(indent) lines.extend(txt) else: assert(False) indent1 = indent + ' '*4 lines.extend("\n\n" + indent0 + " " + \ "def beforeAddingToNetwork(self, net):\n") # this make sure the host web service is loaded if self.constrkw.has_key('host'): lines.extend( indent + "try:\n" ) ## get library import cache ## then write libray import code cache = self.network.buildLibraryImportCache( {'files':[]}, self.network, selectedOnly=False) li = self.network.getLibraryImportCode( cache, indent1, editor="net.editor", networkName="net", importOnly=True, loadHost=True) lines.extend(li) lines.extend( indent + "except:\n" + \ indent1 + "print 'Warning! Could not load web services'\n\n") # this make sure the port widgets will be avalaible when saved code will run lines.extend(indent + "try:\n" ) lWidgetsClass = [] for p in self.inputPorts: lClass = p.widget.__class__ lModule = lClass.__module__ if ( lModule != 'NetworkEditor.widgets') \ and (lModule != '__builtin__') \ and (lModule not in lWidgetsClass): lWidgetsClass.append(lClass) lines.append(indent1 + "ed = net.getEditor()\n") for w in lWidgetsClass: lWidgetsClassName = w.__name__ lines.append(indent1 + "from %s import %s\n" % (w.__module__, lWidgetsClassName) ) lines.extend(indent1 + "if %s not in ed.widgetsTable.keys():\n" % lWidgetsClassName ) lines.extend(indent1 + 4*' ' + \ "ed.widgetsTable['%s'] = %s\n" % (lWidgetsClassName, lWidgetsClassName) ) lines.extend(indent + "except:\n" + \ indent1 + "import traceback; traceback.print_exc()\n" + \ indent1 + "print 'Warning! Could not import widgets'\n") lines.extend("\n") return lines #################################################### #### Helper Methods follow to generate save file ### #################################################### def getHeaderBlock(self, className, indent="", **kw): """Generate source code to import a node from a library or file.""" lines = [] dependencies = kw['dependencies'] import datetime lNow = datetime.datetime.now().strftime("%A %d %B %Y %H:%M:%S") lCopyright = \ """######################################################################## # # Vision Node - Python source code - file generated by vision # %s # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Daniel Stoffler, Michel Sanner and TSRI # # revision: Guillaume Vareille # ######################################################################### # # $%s$ # # $%s$ # """%(lNow, "Header:", "Id:") # if directly in the txt, CVS fills these fields lines.append(lCopyright) return lines, indent def getInitBlock(self, className, indent="", **kw): """Generate source code to define the __init__() method of the node, building the correct constrkw dict, etc.""" lines = [] dependencies = kw['dependencies'] lines.append(indent+"# import node's base class node\n") # if dependencies is True: # mod = self.originalClass.__module__ # klass = self.originalClass.__name__ # txt1 = "from %s import %s\n"%(mod,klass) # lines.append(indent+txt1) # txt2 = "class %s(%s):\n"%(className,klass) # lines.append(indent+txt2) # else: # txt1 = "from NetworkEditor.items import NetworkNode\n" # lines.append(indent+txt1) # txt2 = "class %s(NetworkNode):\n"%className # lines.append(indent+txt2) txt1 = "from NetworkEditor.items import NetworkNode\n" lines.append(indent+txt1) mod = self.originalClass.__module__ klass = self.originalClass.__name__ txt1 = "from %s import %s\n"%(mod,klass) lines.append(indent+txt1) txt2 = "class %s(%s):\n"%(className,klass) lines.append(indent+txt2) if self.originalClass.__doc__ is not None: lines.append(indent+' \"\"\"'+self.originalClass.__doc__) lines.append('\"\"\"\n') indent1 = indent + 4*" " indent2 = indent1 + 4*" " if kw.has_key('types'): lines.append(indent1 + "mRequiredTypes = " + kw['types'].__str__() + '\n') if kw.has_key('synonyms'): lines.append(indent1 + "mRequiredSynonyms = [\n") for lkey, lSynonym in kw['synonyms'].items(): lines.extend(indent2 + lSynonym.__str__() + ',\n') lines.append(indent1 + ']\n') # build constructor keyword from original class # constrkw is not used by the original class but only by NetworkNode constrkw = '' for name, value in self.constrkw.items(): constrkw = constrkw + name+'='+str(value)+', ' constrkw = constrkw + "constrkw = " + str(self.constrkw)+', ' indent += 4*" " lines.append(indent+\ "def __init__(self, %s name='%s', **kw):\n" % ( constrkw, className)) indent += 4*" " lines.append(indent+"kw['constrkw'] = constrkw\n") lines.append(indent+"kw['name'] = name\n") if dependencies is True: klass = self.originalClass.__name__ lines.append(indent+"apply(%s.__init__, (self,), kw)\n"%klass) # we just fully blank everything an recreate them # we will need to save only the differences and not everything #lines.append(indent+"self.inputPortsDescr = []\n") #lines.append(indent+"self.outputPortsDescr = []\n") #lines.append(indent+"self.widgetDescr = {}\n") if self._modified is True: lines.append(indent+"self.inNodeWidgetsVisibleByDefault = %s\n"%self.inNodeWidgetsVisibleByDefault) else: lines.append(indent+"apply( NetworkNode.__init__, (self,), kw)\n") if self.inNodeWidgetsVisibleByDefault: lines.append(indent+"self.inNodeWidgetsVisibleByDefault = True\n") return lines, indent def getPortsCreationSourceCode(self, ports, ptype='input', indent="",**kw): """generates code to create ports using the inputportsDescr and outputPortsDescr""" lines = [] dependencies = kw['dependencies'] assert ptype in ['input', 'output'] for p in ports: d = p.getDescr() if d is None: d = {} lines.append(indent+"self.%sPortsDescr.append(\n"%ptype) lines.append(indent+ 4*" " + "%s)\n"%str(d) ) return lines, indent def getWidgetsCreationSourceCode(self, indent="",**kw): """generating code to create widgets using the widgetDescr""" lines = [] for p in self.inputPorts: if p.widget is None: continue d = p.widget.getDescr() # save current widget value d['initialValue'] = p.widget.getDataForSaving() if d is None: d = {} lines.append(indent+"self.widgetDescr['%s'] = {\n"%p.name) lines.append(indent+ 4*" " + "%s\n"%str(d)[1:] ) #ommit first { return lines, indent def getComputeFunctionSourceCode(self, indent="", **kw): lines = [] nodeName = 'self' lines.append(indent+"code = \"\"\"%s\"\"\"\n"%self.sourceCode) lines.append(indent+"%s.configure(function=code)\n"% nodeName) return lines ###################### END of methods generating source code ################ ############################################################################# def outputData(self, **kw): for p in self.outputPorts: if kw.has_key(p.name): data = kw[p.name] kw.pop(p.name) p.outputData(data) else: ed = self.getEditor() if ed.hasGUI: ed.balloons.tagbind( self.network.canvas, p.id, p.balloonBase) if len(kw): for k in kw.keys(): warnings.warn( "WARNING: port %s not found in node %s"%(k, self.name) ) def setFunction(self, source, tagModified=True): """Set the node's compute function. If tagModified is True, we set _modified=True""" self.sourceCode = source self.dynamicComputeFunction = self.evalString(source) if tagModified: self._setModified(True) # update the source code editor if available if self.objEditor is not None: if self.objEditor.funcEditorDialog is not None: self.objEditor.funcEditorDialog.settext(source) def scheduleChildren(self, portList=None): """run the children of this node in the same thread as the parent if portList is None all children are scheduled, else only children of the specified ports are scheduled """ #print "NetworkNodeBase.scheduleChildren" net = self.network # get the list of nodes to run if portList is None: allNodes = net.getAllNodes(self.children) for n in self.children: n.forceExecution = 1 else: children = [] for p in portList: children.extend(map (lambda x: x.node, p.children) ) for n in children: n.forceExecution = 1 # since a node can be a child through multiple ports we have to # make the list of children unique allNodes = net.getAllNodes(uniq(children)) if len(allNodes): #self.forceExecution = 1 #print "SCHEDULE CHILDREN", allNodes net.runNodes(allNodes) def schedule_cb(self, event=None): self.forceExecution = 1 self.schedule() def schedule(self): """start an execution thread for the subtree under that node """ #print "NetworkNodeBase.schedule", self.network.runOnNewData net = self.network ed = net.getEditor() if ed.hasGUI: if hasattr(ed, 'buttonBar'): bl = ed.buttonBar.toolbarButtonDict if bl.has_key('softrun'): bl['softrun'].disable() bl['run'].disable() #bl['runWithoutGui'].disable() #if ed.withThreads is True: bl['pause'].enable() bl['stop'].enable() net.run([self]) def computeFunction(self): # make sure all required input ports present data # make sure data is valid and available # call self.dynamicComputeFunction # Return 'Go' after successful execution or 'Stop' otherwise for p in self.outputPorts: if p.dataView: p.clearDataView() if not self.dynamicComputeFunction: return 'Stop' lArgs = [self,] ed = self.getEditor() # for each input port of this node newData = 0 for port in self.inputPorts: # this make sure the text entries are used even if the user hasn't pressed return if ed.hasGUI and port.widget is not None: w = port.widget.widget if isinstance(w, Tkinter.Entry) \ or isinstance(w, Pmw.ComboBox): before = port.widget.lastUsedValue after = port.widget.widget.get() if before != after: port.widget._newdata = True if port.hasNewData(): newData = 1 data = port.getData() # returns 'Stop' if bad or missing data if type(data) is types.StringType: if data.lower()=='stop': # turn node outline to missing data color if ed.hasGUI and ed.flashNodesWhenRun: c = self.iconMaster c.tk.call((c._w, 'itemconfigure', self.innerBox, '-outline', '#ff6b00', '-width', 4)) return 'Stop' if port.dataView: port.updateDataView() # update Data Browser GUI (only if window is not deiconified) if port.objectBrowser and \ port.objectBrowser.root.state()=='normal': port.objectBrowser.root.after( 100, port.objectBrowser.refresh_cb ) lArgs.append(data) stat = 'Stop' if newData \ or self.forceExecution \ or (self.network and self.network.forceExecution): #print "running %s with:"%self.name, args lCurrentDir = os.getcwd() if self.network: if self.network.filename is not None: lNetworkDir = os.path.dirname(self.network.filename) elif hasattr(self.network, 'macroNode') \ and self.network.macroNode.network.filename is not None: lNetworkDir = os.path.dirname(self.network.macroNode.network.filename) else: import Vision if hasattr(Vision, 'networkDefaultDirectory'): lNetworkDir = Vision.networkDefaultDirectory else: lNetworkDir = '.' # MS WHY do we have to go there ? Oct 2010 # removed it because this prevented networks from .psf file # wfrom working as the tmp dir was deleted #if os.path.exists(lNetworkDir): os.chdir(lNetworkDir) try: stat = apply( self.dynamicComputeFunction, tuple(lArgs) ) finally: os.chdir(lCurrentDir) if stat is None: stat = 'Go' for p in self.outputPorts: # update Data Viewer GUI if p.dataView: p.updateDataView() # update Data Browser GUI (only if window is not deiconified) if p.objectBrowser and p.objectBrowser.root.state() =='normal': p.objectBrowser.root.after( 100, p.objectBrowser.refresh_cb ) for p in self.inputPorts: p.releaseData() return stat def growRight(self, id, dx): """Expand (and shrink) the x-dimension of the node icon to (and from) the right.""" # we get the coords coords = self.iconMaster.coords(id) # compute the middle point using the bounding box of this object bbox = self.iconMaster.bbox(id) xmid = (bbox[0] + bbox[2]) * 0.5 # add dx for every x coord right of the middle point for i in range(0,len(coords),2): if coords[i]>xmid: coords[i]=coords[i]+dx apply( self.iconMaster.coords, (id,)+tuple(coords) ) def growDown(self, id, dy): """Expand (and shrink) the y-dimension of the node icon to (and from) the top.""" # we get the coords coords = self.iconMaster.coords(id) # compute the middle point using the bounding box of this object bbox = self.iconMaster.bbox(id) ymid = (bbox[1] + bbox[3]) * 0.5 # add dy for every y coord below of the middle point for i in range(1,len(coords),2): if coords[i]>ymid: coords[i]=coords[i]+dy apply( self.iconMaster.coords, (id,)+tuple(coords) ) def updateCode(self, port='ip', action=None, tagModified=True, **kw): """update signature of compute function in source code. We re-write the first line with all port names as arguments. **kw are not used but allow to match updateCode signature of output ports """ code = self.sourceCode # handle input port if port == 'ip': if action=='add' or action=='remove' or action=='create': ## This was bas: 13 assumed that was no space between doit( and ## self. If there is a space we lost the f at the end of self #signatureBegin = code.index('def doit(')+13 signatureBegin = code.index('self')+4 signatureEnd = code[signatureBegin:].index('):') signatureEnd = signatureBegin+signatureEnd newCode = code[:signatureBegin] if action=='create': for p in self.inputPortsDescr: newCode += ', ' + p['name'] #if p['required'] is True: # newCode += "='NA' " #else: # newCode += '=None ' else: for p in self.inputPorts: newCode += ', ' + p.name #if p.required is True: # newCode += "='NA' " #else: # newCode += '=None ' newCode = newCode + code[signatureEnd:] elif action=='rename': newname = kw['newname'] oldname = kw['oldname'] newCode = code.replace(oldname, newname) # handle output port elif port == 'op': newname = kw['newname'] if action==None: return if action=='add': # add comment on how to output data olds = "## to ouput data on port %s use\n"%newname olds += "## self.outputData(%s=data)\n"%newname code += olds elif action=='remove': oldname = kw['oldname'] # remove comment on how to output data olds = "## to ouput data on port %s use\n"%oldname olds += "## self.outputData(%s=data)\n"%oldname code = code.replace(olds, '') elif action=='rename': oldname = kw['oldname'] olds = "## to ouput data on port %s use\n"%oldname olds += "## self.outputData(%s=data)\n"%oldname news = "## to ouput data on port %s use\n"%newname news += "## self.outputData(%s=data)\n"%newname code = code.replace(olds, news) else: raise ValueError ( "action should be either 'add', 'remove', 'rename', got ", action) newCode = code else: warnings.warn("Wrong port type specified!", stacklevel=2) return # finally, set the new code self.setFunction(newCode, tagModified=tagModified) def toggleNodeExpand_cb(self, event=None): widgetsInNode = self.getWidgetsForMaster('Node') if len(widgetsInNode)==0: widgetsInParamPanel = self.getWidgetsForMaster('ParamPanel') if len(widgetsInParamPanel): if self.paramPanel.master.winfo_ismapped() == 1: self.paramPanel.hide() self.paramPanelTk.set(0) else: self.paramPanel.show() self.paramPanelTk.set(1) else: if self.isExpanded(): self.expandedIcon = False self.hideInNodeWidgets() else: self.expandedIcon = True self.showInNodeWidgets() self._setModified(True) def getWidthForPorts(self, maxi=None): # compute the width in the icon required for input and output ports # if maxw is not none, the maximum is return if maxi is None: maxi = maxwidth = 0 # find last visible inputport if len(self.inputPorts): for p in self.inputPorts[::-1]: # going backwards if p.visible: break maxwidth = p.relposx+2*p.halfPortWidth if len(self.outputPorts): for p in self.outputPorts[::-1]: # going backwards if p.visible: break if p.relposx+2*p.halfPortWidth > maxwidth: maxwidth = p.relposx+2*p.halfPortWidth return max(maxi, int(round(maxwidth*self.scaleSum))) def getHeightForPorts(self, maxi=None): # compute the height in the icon required for input and output ports # if maxw is not none, the maximum is return maxheight = 0 if maxi is None: maxi = 0 # find last visible inputport if len(self.inputPorts): for p in self.inputPorts[::-1]: # going backwards if p.visible: break maxheight = p.relposy+2*p.halfPortHeight if len(self.outputPorts): for p in self.outputPorts[::-1]: # going backwards if p.visible: break if p.relposy+2*p.halfPortHeight > maxheight: maxheight = p.relposy+2*p.halfPortHeight return max(maxi, int(round(maxheight*self.scaleSum))) def getWidthForLabel(self, maxi=None): # compute the width in the icon required for the label # if maxis is not not, the maximum is return if maxi is None: maxi = 0 bb = self.iconMaster.bbox(self.textId) return max(maxi, 10+(bb[2]-bb[0]) ) # label has 2*5 padding def getWidthForNodeWidgets(self, maxi=None): # compute the width in the icon required for node widgets # if maxis is not not, the maximum is return if maxi is None: maxi = 0 if self.isExpanded(): return max(maxi, self.nodeWidgetMaster.winfo_reqwidth()+10) else: return maxi def autoResizeX(self): # we find how wide the innerBox has to be canvas = self.iconMaster neededWidth = self.getWidthForPorts() neededWidth = self.getWidthForLabel(neededWidth) neededWidth = self.getWidthForNodeWidgets(neededWidth) # get width of current innerbox bb = canvas.bbox(self.innerBox) w = bb[2]-bb[0] self.resizeIcon(dx=neededWidth-w) def autoResizeY(self): canvas = self.iconMaster bb = canvas.bbox(self.textId) labelH = 12+self.progBarH+(bb[3]-bb[1]) # label has 2*5 padding if self.isExpanded(): widgetH = self.nodeWidgetMaster.winfo_reqheight() if len(self.getWidgetsForMaster('Node')): labelH += 6 else: widgetH = 0 bb = canvas.bbox(self.innerBox) curh = bb[3]-bb[1] self.resizeIcon(dy=labelH+widgetH-curh) def autoResize(self): self.autoResizeX() self.autoResizeY() if len(self.getWidgetsForMaster('node')): # resize gets the right size but always grows to the right # by hiding and showing the widgets in node we fix this self.toggleNodeExpand_cb() self.toggleNodeExpand_cb() def getSize(self): """returns size of this node as a tuple of (width, height) in pixels""" bbox = self.iconMaster.bbox(self.outerBox) w = bbox[2] - bbox[0] h = bbox[3] - bbox[1] return (w, h) def hideInNodeWidgets(self, rescale=1): # hide widgets in node by destroying canvas object holding them # the NE widget is not destroyed canvas = self.iconMaster if rescale: self.autoResizeX() h = self.nodeWidgetMaster.winfo_reqheight() self.resizeIcon(dy=-h-6) #bb = canvas.bbox(self.nodeWidgetTkId) #self.resizeIcon(dy=bb[1]-bb[3]) canvas.delete(self.nodeWidgetTkId) def showInNodeWidgets(self, rescale=1): canvas = self.iconMaster widgetFrame = self.nodeWidgetMaster oldbb = canvas.bbox(self.innerBox)# find current bbox #if len(self.nodeWidgetsID): # bb = canvas.bbox(self.nodeWidgetsID[-1]) # find bbox of last widget #else: # bb = canvas.bbox(self.textId) # find bbox of text bb = canvas.bbox(self.textId) # find bbox of text # pack the frame containg the widgets so we can measure it's size widgetFrame.pack() canvas.update_idletasks() h = widgetFrame.winfo_reqheight() # before asking for its size w = widgetFrame.winfo_reqwidth() ## FIXME the frame is created with a given size. Since it is on a canvas ## it does not resize when widgets are added or removed ## newh =0 ## for p in self.inputPorts: ## if p.widget and p.widget.inNode: ## newh += p.widget.widgetFrame.winfo_reqheight() ## h = newh-h tags = (self.iconTag, 'node') # compute center (x,y) of canvas window # add a window below text for widgets self.nodeWidgetTkId = widgetWin = canvas.create_window( bb[0]+(w/2), bb[3]+ self.progBarH +(h/2), tags=tags, window=widgetFrame ) if self.selected: canvas.addtag_withtag('selected', widgetWin) if rescale: self.autoResizeX() self.resizeIcon(dy=h+6) def getWidgetsForMaster(self, masterName): """Return a dict of all widgets bound for a given master in a given node (self). Masters can be 'Node' or 'ParamPanel'. key is an instance of the port, value is an instance of the widget""" widgets = {} for k, v in self.widgetDescr.items(): master = v.get('master', 'ParamPanel') if master.lower()==masterName.lower(): # find corresponding widget to this port for port in self.inputPorts: if port.name == k: widget = port.widget break widgets[port] = widget return widgets def drawMapIcon(self, naviMap): canvas = naviMap.mapCanvas x0, y0 = naviMap.upperLeft scaleFactor = naviMap.scaleFactor c = self.network.canvas.bbox(self.iconTag) cid = canvas.create_rectangle( [x0+c[0]*scaleFactor, y0+c[1]*scaleFactor, x0+c[2]*scaleFactor, y0+c[3]*scaleFactor], fill='grey50', outline='black', tag=('navimap',)) self.naviMapID = cid return cid def buildIcons(self, canvas, posx, posy, small=False): """Build NODE icon with ports etc """ NetworkItems.buildIcons(self, canvas) network = self.network self.paramPanelTk = Tkinter.IntVar() # to toggle Param. Panel self.paramPanelTk.set(0) # build node's Icon ed = self.getEditor() if ed.hasGUI: self.buildNodeIcon(canvas, posx, posy, small=small) # instanciate output ports for p in self.outputPorts: p.buildIcons(resize=False) # instanciate input ports inNode = 0 for p in self.inputPorts: # this loop first so all port have halfPortWidth p.buildIcons( resize=False ) for p in self.inputPorts: p.createWidget() if p.widget is not None: inNode = max(inNode, p.widget.inNode) if ed.hasGUI: self.autoResizeX() # in case we added too many ports # at least one widget is in the node. We show it if visible by default if inNode: if self.inNodeWidgetsVisibleByDefault: self.expandedIcon = True self.showInNodeWidgets( rescale=1 ) # instanciate special input ports for ip in self.specialInputPorts: ip.buildIcons(resize=False) if not self.specialPortsVisible: ip.deleteIcon() # instanciate special output ports for op in self.specialOutputPorts: op.buildIcons(resize=False) if not self.specialPortsVisible: op.deleteIcon() # this is needed because only now do we know the true relposx of ports # if we do not do this here, some port icons might be outside the node # but we do not want to autoResize() because that would rebuild widgets if ed.hasGUI: self.autoResizeX() # add command entries to node's pull down self.menu.add_command(label='Run', command=self.schedule_cb, underline=0) self.menu.add_checkbutton(label="Frozen", variable = self.frozenTk, command=self.toggleFrozen_cb, underline=0) # self.menu.add_command(label='Freeze', command=self.toggleFreeze) self.menu.add_separator() self.menu.add_command(label='Edit', command=self.edit, underline=0) self.menu.add_command(label='Edit compute function', command=self.editComputeFunction_cb, underline=14) self.menu.add_command(label='Introspect', command=self.introspect, underline=0) self.menu.add_checkbutton(label="Parameter Panel", variable = self.paramPanelTk, command=self.toggleParamPanel_cb, underline=0) self.menu.add_separator() self.menu.add_command(label='Copy', command=self.copy_cb, underline=0) self.menu.add_command(label='Cut', command=self.cut_cb, underline=0) self.menu.add_command(label='Delete', command=self.delete_cb, underline=0) self.menu.add_command(label='Reset', command=self.replaceWith, underline=0) if self.__class__ is FunctionNode and hasattr(self.function, 'serviceName'): def buildhostCascadeMenu(): self.menu.cascadeMenu.delete(0, 'end') for lKey in self.library.libraryDescr.keys(): if lKey.startswith('http://') and lKey != suppressMultipleQuotes(self.constrkw['host']): cb = CallBackFunction( self.replaceWithHost, host=lKey) self.menu.cascadeMenu.add_command(label=lKey, command=cb) self.menu.cascadeMenu = Tkinter.Menu(self.menu, tearoff=0, postcommand=buildhostCascadeMenu) self.menu.add_cascade(label='Replace with node from', menu=self.menu.cascadeMenu) if self.specialPortsVisible: self.menu.add_command(label='Hide special ports', command=self.hideSpecialPorts, underline=5) else: self.menu.add_command(label='Show special ports', command=self.showSpecialPorts, underline=5) def addSaveNodeMenuEntries(self): """add 'save source code' and 'add to library' entries to node menu'""" if self.readOnly: return try: self.menu.index('Save as customized node') except: self.menu.add_separator() funcDependent = CallBackFunction(self.saveSource_cb, True) funcIndependent = CallBackFunction(self.saveSource_cb, False) if hasattr(self, 'geoms') is False: if issubclass(self.__class__, FunctionNode): pass # still in devellopment: #self.menu.add_command( # label='Save as customized node inheriting', # command=funcDependent) else: self.menu.add_command( label='Save as customized node', command=funcIndependent) ## PLEASE NOTE: This will be enabled in a future release of Vision ## ed = self.network.getEditor() ## if hasattr(ed, 'addNodeToLibrary'): ## fun = CallBackFunction( ed.addNodeToLibrary, self) ## self.menu.add_command(label='add to library', command=fun) def copy_cb(self, event=None): ed = self.network.getEditor() self.network.selectNodes([self]) ed.copyNetwork_cb(event) def cut_cb(self, event=None): ed = self.network.getEditor() self.network.selectNodes([self]) ed.cutNetwork_cb(event) def delete_cb(self, event=None): self.network.selectNodes([self]) nodeList = self.network.selectedNodes[:] self.network.deleteNodes(nodeList) def edit(self, event=None): if self.objEditor: self.objEditor.top.master.lift() return self.objEditor = NodeEditor(self) def editComputeFunction_cb(self, event=None): if not self.objEditor: self.objEditor = NodeEditor(self) self.objEditor.editButton.invoke() def evalString(self, str): if not str: return try: function = eval("%s"%str) except: #try: obj = compile(str, '', 'exec') if self.__module__ == '__main__': d = globals() else: # import the module from which this node comes mn = self.__module__ m = __import__(mn) # get the global dictionary of this module ind = string.find(mn, '.') if ind==-1: # not '.' was found d = eval('m'+'.__dict__') else: d = eval('m'+mn[ind:]+'.__dict__') # use the module's dictionary as global scope exec(obj, d) # when a function has names arguments it seems that the # co_names is (None, 'functionName') if len(obj.co_names)==0: function = None else: function = eval(obj.co_names[-1], d) #except: # raise ValueError return function def move(self, dx, dy, absolute=True, tagModified=True): """if absolute is set to False, the node moves about the increment dx,dy. If absolute is True, the node moves to the position dx,dy Connections are updated automatically.""" if self.editor.hasGUI: self.network.moveSubGraph([self], dx, dy, absolute=absolute, tagModified=tagModified) def getSourceCode(self): # this method is implemented in subclasses # create the source code to rebuild this object # used for saving or copying pass def toggleParamPanel_cb(self, event=None): if self.paramPanel.master.winfo_ismapped() == 0: self.paramPanel.show() else: self.paramPanel.hide() def ensureRootNode(self): # count parent to decide whether or not second node is a root lInConnections = self.getInConnections() if len(lInConnections) == 0: self.isRootNode = 1 if self not in self.network.rootNodes: self.network.rootNodes.append(self) else: for lConn in lInConnections: if lConn.blocking is True: if self in self.network.rootNodes: self.network.rootNodes.remove(self) break; else: # we didn't break # all the connections are not blocking self.isRootNode = 1 if self not in self.network.rootNodes: self.network.rootNodes.append(self) class NetworkNode(NetworkNodeBase): """This class implements a node that is represented using a Polygon """ def __init__(self, name='NoName', sourceCode=None, originalClass=None, constrkw=None, library=None, progbar=0, **kw): apply( NetworkNodeBase.__init__, (self, name, sourceCode, originalClass, constrkw, library, progbar), kw) self.highlightOptions = {'highlightbackground':'red'} self.unhighlightOptions = {'highlightbackground':'gray50'} self.selectOptions = {'fill':'yellow'} self.deselectOptions = {'fill':'gray85'} self.inNodeWidgetsVisibleByDefault = True self.inputPortByName = {} self.outputPortByName = {} def replaceWithHost(self, klass=None, library=None, host='http://krusty.ucsd.edu:8081/opal2'): """a function to replace a node with another node. the connections are recreated. the connected ports must have the same name in the new node and in the original node. """ #print "replaceWithHost", self, host constrkw = copy.deepcopy(self.constrkw) if library is None: library = self.library if library is not None: constrkw['library'] = library if klass is None: klass = self.__class__ constrkw['klass'] = klass if klass is FunctionNode \ and hasattr(self.function, 'serviceName') \ and host is not None: constrkw['host'] = suppressMultipleQuotes(host) serverName = host.split('http://')[-1] serverName = serverName.split('/')[0] serverName = serverName.split(':')[0] serverName = serverName.replace('.','_') # to replace exactly with the same one #constrkw['functionOrString'] = \ # self.function.serviceOriginalName.lower() + '_' + serverName # to pick any better version if self.library.libraryDescr.has_key(host): lversion = 0 # replace with the highest version available on the host #lversion = self.function.version # replace only if version is at least equal to current # to eval we need to bring the main scope into this local scope from mglutil.util.misc import importMainOrIPythonMain lMainDict = importMainOrIPythonMain() for modItemName in set(lMainDict).difference(dir()): locals()[modItemName] = lMainDict[modItemName] del constrkw['functionOrString'] for node in self.library.libraryDescr[host]['nodes']: try: lFunction = eval(node.kw['functionOrString']) if lFunction.serviceName == self.function.serviceName \ and lFunction.version >= lversion: constrkw['functionOrString'] = lFunction.serviceOriginalName + '_' + serverName except: pass if constrkw.has_key('functionOrString'): return apply(self.replaceWith, (), constrkw) else: return False def replaceWith(self, klass=None, **kw): """a function to replace a node with another node. the connections are recreated. the connected ports must have the same name in the new node and in the original node. """ if len(kw) == 0: kw = copy.deepcopy(self.constrkw) if kw.has_key('library') is False: kw['library'] = self.library if klass is None: klass = self.__class__ try: lNewNode = apply(klass,(),kw) lNewNode.inNodeWidgetsVisibleByDefault = self.expandedIcon #by default we want the new node to be in the same state as the curren one self.network.addNode(lNewNode, posx=self.posx, posy=self.posy) if self.specialPortsVisible is True: self.showSpecialPorts() lFailure = False for port in self.inputPorts: if lFailure is False: for connection in port.connections: if lFailure is False: try: self.network.connectNodes( connection.port1.node, lNewNode, connection.port1.name, port.name, blocking=connection.blocking ) except: lFailure = True for port in self.inputPorts: if port.widget is not None: try: lNewNode.inputPortByName[port.name].widget.set(port.widget.get()) except: pass lDownstreamNodeInputPortSingleConnection = {} if lFailure is False: for port in self.outputPorts: if lFailure is False: # input ports downstream have to accept multiple connections otherwise they can't be connected for connection in port.connections: lDownstreamNodeInputPortSingleConnection[connection.port2.node] = \ (connection.port2.name, connection.port2.singleConnection) connection.port2.singleConnection = 'multiple' try: self.network.connectNodes( lNewNode, connection.port2.node, port.name, connection.port2.name, blocking=connection.blocking ) except: lFailure = True # input ports downstream are set back to what they were for lNode, portNameSingleConnection in lDownstreamNodeInputPortSingleConnection.items(): lNode.inputPortByName[portNameSingleConnection[0]].singleConnection = portNameSingleConnection[1] if lFailure is False: self.network.deleteNodes([self]) #print "replaced" return True else: self.network.deleteNodes([lNewNode]) except Exception, e: print e #warnings.warn( str(e) ) return False def createPorts(self): for kw in self.outputPortsDescr: kw['updateSignature'] = False # prevent recreating source code sig. op = self.addOutputPort(**kw) # create all inputPorts from description for kw in self.inputPortsDescr: kw['updateSignature'] = False # prevent recreating source code sig. ip = self.addInputPort(**kw) # create widgets ip.createWidget() # create all specialPorts self.addSpecialPorts() def isExpanded(self): """returns True if widgets inside the node as displayed""" return self.expandedIcon def editorVisible(self): """returns True if the node Editor is visible""" return self.objEditor is not None def getColor(self): if self.iconMaster is None: return return self.iconMaster.itemconfigure(self.innerBox)['fill'][-1] def setColor(self, color): ## FOR unknown reasons c.tk.call((c._w, 'itemcget', self.innerBox, '-fill') can return 'None' sometimes on SGI when using threads c = self.iconMaster if c is None: print 'Canvas is None' return oldCol = c.tk.call((c._w, 'itemcget', self.innerBox, '-fill') ) while oldCol=='None': print "//////////////////////////", oldCol,self.innerBox, c oldCol = c.tk.call((c._w, 'itemcget', self.innerBox, '-fill') ) print "\\\\\\\\\\\\\\\\\\\\\\",oldCol,self.innerBox, c #oldCol = c.itemconfigure(self.innerBox)['fill'][-1] c.tk.call((c._w, 'itemconfigure', self.innerBox, '-fill', color)) #c.itemconfigure(self.innerBox, fill=color) return oldCol ## OBSOLETE was used for nodes that were widgets ## ## def highlight(self, event=None): ## if self.iconMaster is None: return ## apply( self.iconMaster.itemconfigure, (self.innerBox,), ## self.highlightOptions ) ## def unhighlight(self, event=None): ## if self.iconMaster is None: return ## apply( self.iconMaster.itemconfigure, (self.innerBox,), ## self.unhighlightOptions ) def getFont(self): if self.iconMaster is None: return return self.iconMaster.itemconfigure(self.textId)['font'][-1] def setFont(self, font): # has to be a tuple like this: (ensureFontCase('helvetica'),'-12','bold') if self.iconMaster is None: return assert font is not None and len(font) font = tuple(font) self.iconMaster.itemconfig(self.textId, font=font) def select(self): NetworkItems.select(self) if self.iconMaster is None: return apply( self.iconMaster.itemconfigure, (self.innerBox,), self.selectOptions ) def deselect(self): NetworkItems.deselect(self) if self.iconMaster is None: return apply( self.iconMaster.itemconfigure, (self.innerBox,), self.deselectOptions ) def resizeIcon(self, dx=0, dy=0): if dx: self.growRight(self.innerBox, dx) self.growRight(self.outerBox, dx) self.growRight(self.lowerLine, dx) self.growRight(self.upperLine, dx) # move the special outputPort icons if visible if self.specialPortsVisible: for p in self.specialOutputPorts: p.deleteIcon() p.createIcon() if dy: self.growDown(self.innerBox, dy) self.growDown(self.outerBox, dy) self.growDown(self.lowerLine, dy) self.growDown(self.upperLine, dy) for p in self.outputPorts: p.relposy = p.relposy + dy p.deleteIcon() p.createIcon() for c in p.connections: if c.id: c.updatePosition() def addInputPort(self, name=None, updateSignature=True, _previousWidgetDescr=None, **kw): # ): defaults = { 'balloon':None, '_previousWidgetDescr':None, 'required':True, 'datatype':'None', 'width':None, 'height':None, 'singleConnection':True, 'beforeConnect':None, 'afterConnect':None, 'beforeDisconnect':None, 'afterDisconnect':None, 'shape':None, 'color':None, 'cast':True, 'originalDatatype':None, 'defaultValue':None, 'inputPortClass':InputPort, } defaults.update(kw) kw = defaults """Create input port and creates icon NOTE: this method does not update the description""" number = len(self.inputPorts) if name is None: name = 'in'+str(number) # create unique name portNames = [] for p in self.inputPorts: portNames.append(p.name) if name in portNames: i = number while (True): newname = name+str(i) if newname not in portNames: break i = i+1 name = newname # create port inputPortClass = kw.pop('inputPortClass', InputPort) ip = inputPortClass( name, self, **kw) # name, self, datatype, required, balloon, width, height, # singleConnection, beforeConnect, afterConnect, beforeDisconnect, # afterDisconnect, shape, color, cast=cast, # originalDatatype=originalDatatype, # defaultValue=defaultValue, **kw # ) self.inputPorts.append(ip) if self.iconMaster: ip.buildIcons() if not self.getEditor().hasGUI: ip.createWidget() # create NGWidget # and add descr to node.inputPortsDescr if it does not exist pdescr = self.inputPortsDescr found = False for d in pdescr: if d['name'] == name: found = True break if not found: descr = {'name':name, 'datatype':kw['datatype'], 'required':kw['required'], 'balloon':kw['balloon'], 'singleConnection':kw['singleConnection']} self.inputPortsDescr.append(descr) if _previousWidgetDescr is not None: ip.previousWidgetDescr = _previousWidgetDescr # generate unique number, which is used for saving/restoring ip._id = self._inputPortsID self._inputPortsID += 1 ip._setModified(True) ip._setOriginal(False) # change signature of compute function if updateSignature is True: self.updateCode(port='ip', action='add', tagModified=False) self.inputPortByName[name] = ip return ip def refreshInputPortData(self): d = {} for p in self.inputPorts: d[p.name] = p.getData() return d def addOutputPort(self, name=None, updateSignature=True, **kw): defaults = {'datatype':'None', 'width':None, 'height':None, 'balloon':None, 'beforeConnect':None, 'afterConnect':None, 'beforeDisconnect':None, 'afterDisconnect':None, 'shape':None, 'color':None} defaults.update(kw) kw = defaults """Create output port and creates icon NOTE: this method does not update the description nor the function's signature""" number = len(self.outputPorts) if name is None: name = 'out'+str(number) # create unique name portNames = [] for p in self.outputPorts: portNames.append(p.name) if name in portNames: i = number while (True): newname = name+str(i) if newname not in portNames: break i = i+1 name = newname # create port outputPortClass = kw.pop('outputPortClass', OutputPort) op = outputPortClass(name, self, **kw) #datatype, balloon, width, height, #beforeConnect, afterConnect, beforeDisconnect, #afterDisconnect) self.outputPorts.append(op) if self.iconMaster: op.buildIcons() # and add descr to node.outputPortsDescr if it does not exist pdescr = self.outputPortsDescr found = False for d in pdescr: if d['name'] == name: found = True break if not found: descr = {'name':name, 'datatype':kw['datatype'], 'balloon':kw['balloon']} self.outputPortsDescr.append(descr) # generate unique number, which is used for saving/restoring op._id = self._outputPortsID self._outputPortsID += 1 op._setModified(True) op._setOriginal(False) # add comment to code on how to output data on that port if updateSignature is True: self.updateCode(port='op', action='add', newname=op.name, tagModified=False) self.outputPortByName[name] = op return op def deletePort(self, p, resize=True, updateSignature=True): NetworkItems.deletePort(self, p, resize) # update code first, then delete if updateSignature and isinstance(p, InputPort): self.updateCode(port='ip', action='remove', tagModified=False) self.inputPortByName.pop(p.name) elif updateSignature and isinstance(p, OutputPort): self.updateCode(port='op', action='remove', newname='', oldname=p.name, tagModified=False) self.outputPortByName.pop(p.name) def deletePortByName(self, portName, resize=True, updateSignature=True): """delete a port by specifying a port name (port names are unique within a given node).""" port = self.findPortByName() self.deletePort(port, resize=resize, updateSignature=updateSignature) def showSpecialPorts(self, tagModified=True, event=None): self.specialPortsVisible = True self._setModified(tagModified) for p in self.specialOutputPorts: p.createIcon() for p in self.specialInputPorts: p.createIcon() self.menu.entryconfigure('Show special ports', label='Hide special ports', command=self.hideSpecialPorts) def hideSpecialPorts(self, tagModified=True, event=None): self.specialPortsVisible = False self._setModified(tagModified) for p in self.specialOutputPorts: p.node.network.deleteConnections(p.connections, undo=1) p.deleteIcon() for p in self.specialInputPorts: p.node.network.deleteConnections(p.connections, undo=1) p.deleteIcon() self.menu.entryconfigure('Hide special ports', label='Show special ports', command=self.showSpecialPorts) def addSpecialPorts(self): """add special ports to special ports list. But do not build icons""" # port to receive an impulse that will trigger the execution of the # node ip = RunNodeInputPort(self) ip.network = self.network self.specialInputPorts.append( ip ) # port that always output an impulse upon successful completion # of the node's function op = TriggerOutputPort(self) op.network = self.network self.specialOutputPorts.append( op ) ed = self.getEditor() ip.vEditor = weakref.ref( ed ) op.vEditor = weakref.ref( ed ) def buildSmallIcon(self, canvas, posx, posy, font=None): """build node proxy icon (icons in library categories""" if font is None: font = self.ed.font['LibNodes'] font = tuple(font) self.textId = canvas.create_text( posx, posy, text=self.name, justify=Tkinter.CENTER, anchor='w', tags='node', font=font) self.iconTag = 'node'+str(self.textId) bb = canvas.bbox(self.textId) # adding the self.id as a unique tag for this node canvas.addtag_closest(self.iconTag, posx, posy, start=self.textId) bdx1 = 2 # x padding around label bdy1 = 0 # y padding around label bdx2 = bdx1+3 # label padding + relief width bdy2 = bdy1+3 # label padding + relief width self.innerBox = canvas.create_rectangle( bb[0]-bdx1, bb[1]-bdy1, bb[2]+bdx1, bb[3]+bdy1, tags=(self.iconTag,'node'), fill='gray85') # the innerBox is the canvas item used to designate this node self.id = self.innerBox # add a shadow below if self.library is not None: color1 = self.library.color else: color1 = 'gray95' # upper right triangle self.upperLine = canvas.create_polygon( bb[0]-bdx2, bb[1]-bdy2, bb[0]-bdx1, bb[1]-bdy1, bb[2]+bdx1, bb[3]+bdy1, bb[2]+bdx2, bb[3]+bdy2, bb[2]+bdx2, bb[1]-bdy2, width=4, tags=(self.iconTag,'node'), fill=color1 ) # lower left triangle self.lowerLine = canvas.create_polygon( bb[0]-bdx2, bb[1]-bdy2, bb[0]-bdx1, bb[1]-bdy1, bb[2]+bdx1, bb[3]+bdy1, bb[2]+bdx2, bb[3]+bdy2, bb[0]-bdx2, bb[3]+bdy2, width=4, tags=(self.iconTag,'node'), fill='gray45' ) self.outerBox = canvas.create_rectangle( bb[0]-bdx2, bb[1]-bdy2, bb[2]+bdx2, bb[3]+bdy2, width=1, tags=(self.iconTag,'node')) canvas.tag_raise(self.innerBox, self.outerBox) canvas.tag_raise(self.textId, self.innerBox) return bb def deleteSmallIcon(self, canvas, item): # Experimental! node = item.dummyNode canvas.delete(node.textId) canvas.delete(node.innerBox) canvas.delete(node.outerBox) canvas.delete(node.iconTag) def buildNodeIcon(self, canvas, posx, posy, small=False): # build a frame that will hold all widgets in node if hasattr(self.iconMaster,'tk'): self.nodeWidgetMaster = Tkinter.Frame( self.iconMaster, borderwidth=3, relief='sunken' , bg='#c3d0a6') ed = self.getEditor() if small is True: font = tuple(ed.font['LibNodes']) lInner = 2 lOuter = 4 else: font = tuple(ed.font['Nodes']) lInner = 5 lOuter = 8 self.textId = canvas.create_text( posx, posy, text=self.name, justify=Tkinter.CENTER, anchor='w', tags='node', font=font) canvas.tag_bind(self.textId, "", self.setLabel_cb) self.iconTag = 'node'+str(self.textId) # add self.iconTag tag to self.textId canvas.itemconfig(self.textId, tags=(self.iconTag,'node')) bb = canvas.bbox(self.textId) ## # NOTE: THIS LINE ADDS RANDOMLY WRONG TAGS TO NODES >>>> COPY/PASTE ## # WON'T WORK CORRECTLY!!! WITHOUT THIS LINE, EVERYTHING SEEMS TO ## # WORK FINE. ## #adding the self.id as a unique tag for this node ## canvas.addtag_closest(self.iconTag, posx, posy, start=self.textId) progBarH = self.progBarH # this method is also called by a network refresh, thus we need to # get the description of the node and color it accordingly (frozen # or colored by node library) color = "gray85" # default color is gray if self.editor.colorNodeByLibraryTk.get() == 1: if self.library is not None: color = self.library.color # color by node library color # if node is frozen, this overwrites everything if self.frozen: color = '#b6d3f6' # color light blue self.innerBox = canvas.create_rectangle( bb[0]-lInner, bb[1]-lInner, bb[2]+lInner, bb[3]+lInner+progBarH, tags=(self.iconTag,'node'), fill=color)#, width=2 ) # the innerBox is the canvas item used to designate this node self.id = self.innerBox # add a shadow below (color by Library) if self.library is not None: color1 = self.library.color else: color1 = 'gray95' self.outerBox = canvas.create_rectangle( bb[0]-lOuter, bb[1]-lOuter, bb[2]+lOuter, bb[3]+lOuter+progBarH, width=1, tags=(self.iconTag,'node')) # get a shortcut to the bounding boxes used later on ibb = canvas.bbox(self.innerBox) obb = canvas.bbox(self.outerBox) # upper right polygon (this is used to color the node icons upper # and right side with the corresponding node library color) self.upperLine = canvas.create_polygon( # note: we have to compensate +1 and -1 because of '1'-based # coord system obb[0]+1, obb[1]+1, ibb[0]+1, ibb[1]+1, ibb[2]-1, ibb[3]-1, obb[2]-1, obb[3]-1, obb[2]-1, obb[1]+1, width=4, tags=(self.iconTag,'node'), fill=color1 ) # lower left polygon (this is used to 'shade' the node icons lower # and right side with a dark grey color to give it a 3-D impression self.lowerLine = canvas.create_polygon( # note: we have to compensate +1 and -1 because of '1'-based # coord system obb[0]+1, obb[1]+1, ibb[0]+1, ibb[1]+1, ibb[2]-1, ibb[3]-1, obb[2]-1, obb[3]-1, obb[0]+1, obb[3]-1, width=4, tags=(self.iconTag,'node'), fill='gray45' ) canvas.tag_raise(self.outerBox) canvas.tag_raise(self.innerBox, self.outerBox) canvas.tag_raise(self.textId, self.innerBox) # add the progress bar if self.hasProgBar: pbid1 = canvas.create_rectangle( bb[0]-3, bb[3]-2, bb[2]+3, bb[3]+1+progBarH, tags=(self.iconTag,'node'), fill='green') self.pbid1 = pbid1 pbid2 = canvas.create_rectangle( bb[2]+3, bb[3]-2, bb[2]+3, bb[3]+1+progBarH, {'tags':(self.iconTag,'node'), 'fill':'red'} ) self.pbid2 = pbid2 # and set posx, posy self.updatePosXPosY(posx, posy) if self.network is not None: self.move(posx, posy) self.hasMoved = False # reset this attribute because the current # position is now the original position def setLabel_cb(self, event): self._tmproot = root = Tkinter.Toplevel() root.transient() root.geometry("+%d+%d"%root.winfo_pointerxy()) root.overrideredirect(True) self._tmpEntry = Tkinter.Entry(root) self._tmpEntry.pack() self._tmpEntry.bind("", self.setNewLabel_cb) def setNewLabel_cb(self, event): name = self._tmpEntry.get() self.rename(name) self._tmpEntry.destroy() self._tmproot.destroy() def setProgressBar(self, percent): """update node's progress bar. percent should be between 0.0 and 1.0""" if not self.hasProgBar: return canvas = self.iconMaster c = canvas.coords(self.pbid1) c[0] = c[0] + (c[2]-c[0])*percent canvas.coords(self.pbid2, c[0], c[1], c[2], c[3]) def updatePosXPosY(self, dx=None, dy=None): """set node.posx and node.posy after node has been moved""" bbox = self.iconMaster.bbox(self.outerBox) self.posx = bbox[0] self.posy = bbox[1] def setModifiedTag(self): """THIS METHOD REMAINS FOR BACKWARDS COMPATIBILITY WITH OLD NETWORKS! Sets self._modified=True""" self._setModified(True) class NetworkConnection(NetworkItems): """This class implements a connection between nodes, drawing lines between the centers of 2 ports. The mode can be set to 'straight' or 'angles' to have a straight line or lines using only right angles. smooth=1 option can be used for splines joinstyle = 'bevel', 'miter' and 'round' """ arcNum = 0 def __init__(self, port1, port2, mode='straight', name=None, blocking=True, smooth=False, splitratio=None, hidden=False, **kw): if name is None: name = port1.node.name+'('+port1.name+')'+'_'+port2.node.name+'('+port2.name+')' if splitratio is None: splitratio=[random.uniform(.2,.75), random.uniform(.2,.75)] # was [.5, .5] NetworkItems.__init__(self, name) self.blocking = blocking # when true a child node can not run before # the parent node has run self.hidden = hidden self.id2 = None self.iconTag2 = None self.port1 = port1 self.port2 = port2 port1.children.append(port2) #assert self not in port1.connections port1.connections.append(self) port1.node.children.append(port2.node) port2.parents.append(port1) #assert self not in port2.connections port2.connections.append(self) port2.node.parents.append(port1.node) leditor = self.port1.editor self.mode = mode if leditor is not None and hasattr(leditor, 'splineConnections'): self.smooth = leditor.splineConnections else: self.smooth = smooth self.splitratio = copy.deepcopy(splitratio) w = self.connectionWidth = 3 if port1.node.getEditor().hasGUI: col = port1.datatypeObject['color'] if not kw.has_key('arrow'): kw['arrow']='last' if not kw.has_key('fill'): kw['fill']=col if not kw.has_key('width'): kw['width']=w if not kw.has_key('width'): kw['width']=w if not kw.has_key('activefill'): kw['activefill']='pink' kw['smooth'] = self.smooth self.lineOptions = kw self.highlightOptions = {'fill':'red', 'width':w, 'arrow':'last'} self.unhighlightOptions = {'width':w, 'arrow':'last'} self.unhighlightOptions['fill'] = col self.selectOptions = { 'connection0': {'fill':'blue', 'width':w, 'arrow':'last'}, 'connection1': {'fill':'pink', 'width':w, 'arrow':'last'}, 'connection2': {'fill':'purple', 'width':w, 'arrow':'last'}, } self.deselectOptions = {'width':w, 'arrow':'last'} self.deselectOptions['fill'] = col self.mouseAction[''] = self.reshapeConnection self.parentMenu = None self.isBlockingTk = Tkinter.IntVar() self.isBlockingTk.set(self.blocking) def reshapeConnection(self, event): # get a handle to the network of this node c = self.iconMaster # register an additional function to reshape connection num = event.num # FIXME looks like I am binding this many times ! c.bind(""%num, self.moveReshape,'+') c.bind(""%num, self.moveEndReshape, '+') ## def moveReshape(self, event): ## c = self.iconMaster ## y = c.canvasy(event.y) ## dy = y - self.network.lasty ## self.network.lasty = y ## coords = c.coords(self.iconTag) ## coords[3] = coords[3]+dy ## coords[5] = coords[5]+dy ## apply( c.coords, (self.iconTag,)+tuple(coords) ) ## def moveEndReshape(self, event): ## c = self.iconMaster ## num = event.num ## c.unbind(""%num) ## c.bind(""%num, self.moveEndReshape, '+') # patch from Karl Gutwin 2003-03-27 16:05 def moveReshape(self, event): #print "moveReshape" c = self.iconMaster y = c.canvasy(event.y) x = c.canvasx(event.x) dy = y - self.network.lasty dx = x - self.network.lastx self.network.lasty = y self.network.lastx = x coords = c.coords(self.iconTag) if len(coords)==12: coords[4] = coords[4]+dx coords[6] = coords[6]+dx if y > ((coords[5]+coords[7])/2): coords[3] = coords[3]+dy coords[5] = coords[5]+dy else: coords[7] = coords[7]+dy coords[9] = coords[9]+dy else: coords[3] = coords[3]+dy coords[5] = coords[5]+dy self.calculateNewSplitratio(coords) apply( c.coords, (self.iconTag,)+tuple(coords) ) def calculateNewSplitratio(self, coords): self.splitratio[0] = coords[0]-coords[4] lDistance = coords[0]-coords[-2] if lDistance != 0: self.splitratio[0] /= float(lDistance) if self.splitratio[0] > 2: self.splitratio[0] = 2 elif self.splitratio[0] < -2: self.splitratio[0] = -2 self.splitratio[1] = coords[1]-coords[5] lDistance = coords[1]-coords[-1] if lDistance != 0: self.splitratio[1] /= float(lDistance) if self.splitratio[1] > 2: self.splitratio[1] = 2 elif self.splitratio[1] < -2: self.splitratio[1] = -2 def moveEndReshape(self, event): c = self.iconMaster num = event.num c.unbind(""%num) c.unbind(""%num) def highlight(self, event=None): if self.iconMaster is None: return c = self.iconMaster apply( c.itemconfigure, (self.iconTag,), self.highlightOptions ) def unhighlight(self, event=None): if self.iconMaster is None: return c = self.iconMaster apply( c.itemconfigure, (self.iconTag,), self.unhighlightOptions) def setColor(self, color): if self.iconMaster is None: return c = self.iconMaster apply( c.itemconfigure, (self.iconTag,), {'fill':color} ) self.unhighlightOptions['fill'] = color self.deselectOptions['fill'] = color def getColor(self): return self.deselectOptions['fill'] def select(self): self.selected = 1 if self.iconMaster is None: return sum = self.port1.node.selected + self.port2.node.selected if sum==2: self.iconMaster.addtag('selected', 'withtag', self.iconTag) apply( self.iconMaster.itemconfigure, (self.iconTag,), self.selectOptions['connection%d'%sum] ) def deselect(self): NetworkItems.deselect(self) if self.iconMaster is None: return sum = self.port1.node.selected + self.port2.node.selected if sum<2: self.iconMaster.dtag(self.iconTag, 'selected') if sum==0: opt = self.deselectOptions else: opt = self.selectOptions['connection%d'%sum] apply( self.iconMaster.itemconfigure, (self.iconTag,), opt ) def shadowColors(self, colorTk): # for a given Tkcolor return a dark tone 40% and light tone 80% c = self.iconMaster maxi = float(c.winfo_rgb('white')[0]) rgb = c.winfo_rgb(colorTk) base = ( rgb[0]/maxi*255, rgb[1]/maxi*255, rgb[2]/maxi*255 ) dark = "#%02x%02x%02x"%(base[0]*0.6,base[1]*0.6,base[2]*0.6) light = "#%02x%02x%02x"%(base[0]*0.8,base[1]*0.8,base[2]*0.8) return dark, light def toggleBlocking_cb(self, event=None): self.blocking = not self.blocking self.isBlockingTk.set(self.blocking) if not self.blocking: self.port2.node.ensureRootNode() def toggleVisibility_cb(self, event=None): self.setVisibility(not self.hidden) def setVisibility(self, hidden): self.hidden = hidden del self.network.connById[self.id] if self.id2 is not None: del self.network.connById[self.id2] self.deleteIcon() self.buildIcons(self.network.canvas) self.network.connById[self.id] = self if self.id2 is not None: self.network.connById[self.id2] = self def reparent_cb(self, type): node = self.port2.node self.network.deleteConnections([self]) node.reparentGeomType(type, reparentCurrent=False) def drawMapIcon(self, naviMap): if self.id is None: # geom nodes with aprentNode2 seen to create return # conenctions with no id mapCanvas = naviMap.mapCanvas x0, y0 = naviMap.upperLeft scaleFactor = naviMap.scaleFactor canvas = self.iconMaster cc = self.network.canvas.coords(self.id) nc = [] for i in range(0, len(cc), 2): nc.append( x0+cc[i]*scaleFactor ) nc.append( y0+cc[i+1]*scaleFactor ) if self.naviMapID is None: cid = mapCanvas.create_line( *nc, tag=('navimap',)) self.naviMapID = cid return cid else: mapCanvas.coords(self.naviMapID, *nc) def updatePosition(self): if self.iconMaster is None: return # spoted by guillaume, I am not sure what it means if self.port1 is None or self.port2 is None: import traceback traceback.print_stack() print c, id(id) print 'IT HAPPENED AGAIN: a conection is missing ports' return if self.port1.id is None or self.port2.id is None: return # one the ports is not visible c = self.iconMaster coords = self.getLineCoords() if self.hidden is False: apply( c.coords, (self.id,)+tuple(coords) ) else: if isinstance(self.port1, SpecialOutputPort): lcoords1 = (coords[0],coords[1],coords[0]+20,coords[1]) lcoords2 = (coords[-2]-16,coords[-1],coords[-2],coords[-1]) else: lcoords1 = (coords[0],coords[1],coords[0],coords[1]+20) lcoords2 = (coords[-2],coords[-1]-16,coords[-2],coords[-1]) apply( c.coords, (self.id,)+tuple(lcoords1) ) apply( c.coords, (self.id2,)+tuple(lcoords2) ) naviMap = self.port1.node.network.naviMap self.drawMapIcon(naviMap) def getLineCoords(self): canvas = self.iconMaster c1 = self.port1.getCenterCoords() c2 = self.port2.getCenterCoords() vx1, vy1 = self.port1.vectorRotated vy1 = -vy1 vx2, vy2 = self.port2.vectorRotated vy2 = -vy2 if self.mode == 'straight': outOffy = c1[0]+15*vx1 inOffy = c2[0]-15*vy1 return [ c1[0], c1[1], c1[0], outOffy, c2[0], inOffy, c2[0], c2[1] ] else: # if self.mode == 'angles': dx = c2[0]-c1[0] # check if port vectors are opposite dot = vx1*vx2 + vy1*vy2 if dot < 0.0: # 3 segments, 2 projecting out of node and 1 joining them # draw 1 segment along p1.vector # 1 segment along -p2.vector and a segement joing them proj = 20 p1x = c1[0]+proj*vx1 p1y = c1[1]+proj*vy1 p2x = c2[0]+proj*vx2 p2y = c2[1]+proj*vy2 #print 'getLineCoords A',c1[0], c1[1], p1x, p1y, p2x, p2y, c2[0], c2[1] #print 'A', c1, c2, vx1, vy1, vx2, vy2 return [ c1[0], c1[1], p1x, p1y, p2x, p2y, c2[0], c2[1] ] else: proj = 20 p1x = c1[0]+proj*vx1 # move up p1y = c1[1]+proj*vy1 perp = -vy1, vx1 # check that perpendicular vector point from on port to the other ppvx = c2[0]-c1[0] # vector form port1 to port2 ppvy = c2[1]-c1[1] dot = ppvx*perp[0] + ppvy*perp[1] if dot>0.0: sign = 1.0 else: sign = -1.0 p2x = p1x+proj*sign*perp[0] # move side ways a bit p2y = p1y+proj*sign*perp[1] p3x = c2[0]+proj*vx2 p3y = c2[1]+proj*vy2 #print 'B', c1, c2, vx1, vy1, vx2, vy2, perp #print 'getLineCoords B', c1[0], c1[1], p1x, p1y, p2x, p2y, p3x, p3y,c2[0], c2[1] return [ c1[0], c1[1], p1x, p1y, p2x, p2y, p3x, p3y, c2[0], c2[1] ] ## def getLineCoords(self): ## if isinstance(self.port1, SpecialOutputPort): ## return self.getLineCoordsLeftRightPorts() ## elif isinstance(self.port1, ImageOutputPort): ## return self.getLineCoordsLeftRightPorts() ## else: ## return self.getLineCoordsTopBottomPorts() ## def getLineCoordsLeftRightPorts(self): ## canvas = self.iconMaster ## c1 = self.port1.getCenterCoords() ## c2 = self.port2.getCenterCoords() ## if self.mode == 'straight': ## outOffy = c1[0]+15 ## inOffy = c2[0]-15 ## return [ c1[0], c1[1], c1[0], outOffy, ## c2[0], inOffy, c2[0], c2[1] ] ## else: # if self.mode == 'angles': ## dx = c2[0]-c1[0] ## if dx > 30: # draw just 1 segment down, 1 horizontal and 1 down again ## dx2 = dx * self.splitratio[0] ## outOffx = c1[0]+dx2 ## inOffx = c2[0]-(dx-dx2) ## return [ c1[0], c1[1], outOffx, c1[1], inOffx, c2[1], ## c2[0], c2[1] ] ## else: ## outOffx = c1[0]+15 # go right 15 pixels from output ## inOffx = c2[0]-15 # go left 15 pixel from input ## dy = c2[1]-c1[1] ## dy2 = dy * self.splitratio[1] ## mid = [ outOffx, c1[1]+dy2, inOffx, c2[1]-(dy-dy2) ] ## return [ c1[0], c1[1], outOffx, c1[1] ] + mid + \ ## [ inOffx, c2[1], c2[0], c2[1] ] ## def getLineCoordsTopBottomPorts(self): ## # implements straight and angle connections between nodes ## canvas = self.iconMaster ## c1 = self.port1.getCenterCoords() ## c2 = self.port2.getCenterCoords() ## if self.mode == 'straight': ## outOffy = c1[1]+15 ## inOffy = c2[1]-15 ## return [ c1[0], c1[1], c1[0], outOffy, ## c2[0], inOffy, c2[0], c2[1] ] ## else: # if self.mode == 'angles': ## dy = c2[1]-c1[1] ## if dy > 30: # draw just 1 segment down, 1 horizontal and 1 down again ## dy2 = dy * self.splitratio[1] ## outOffy = c1[1]+dy2 ## return [ c1[0], c1[1], c1[0], outOffy, c2[0], ## outOffy, c2[0], c2[1] ] ## else: ## outOffy = c1[1]+15 # go down 15 pixels from output ## inOffy = c2[1]-15 # go up 15 pixel from input ## dx = c2[0]-c1[0] ## dx2 = dx * self.splitratio[0] ## mid = [ c1[0]+dx2, outOffy, c2[0]-(dx-dx2), inOffy ] ## return [ c1[0], c1[1], c1[0], outOffy ] + mid + \ ## [ c2[0], inOffy, c2[0], c2[1] ] def buildIcons(self, canvas): """Build CONNECTION icon """ NetworkItems.buildIcons(self, canvas) kw = self.lineOptions arcTag = '__arc'+str(self.arcNum) self.arcNum = self.arcNum + 1 kw['tags'] = ('connection', arcTag) coords = self.getLineCoords() if self.hidden is False: g = apply( canvas.create_line, tuple(coords), kw ) else: #print "coords", coords if isinstance(self.port1, SpecialOutputPort): lcoords1 = (coords[0],coords[1],coords[0]+20,coords[1]) lcoords2 = (coords[-2]-16,coords[-1],coords[-2],coords[-1]) else: lcoords1 = (coords[0],coords[1],coords[0],coords[1]+20) lcoords2 = (coords[-2],coords[-1]-16,coords[-2],coords[-1]) g = apply( canvas.create_line, tuple(lcoords1), kw ) g2 = apply( canvas.create_line, tuple(lcoords2), kw ) self.iconTag2 = 'conn'+str(g2) self.id2 = g2 self.iconTag = 'conn'+str(g) self.id = g cb = CallBackFunction(self.network.deleteConnections, ([self])) if self.port2.name == 'parent' and hasattr(self.port2.node,'selectedGeomIndex'): # i.e. it's a geometry node cbSiblings = CallBackFunction(self.reparent_cb, ('siblings')) cbAll = CallBackFunction(self.reparent_cb, ('all')) self.menu.add_command(label='delete / reparent to root', command=cb) self.menu.add_command(label='reparent pointed siblings to root', command=cbSiblings) self.menu.add_command(label='reparent all pointed geoms to root', command=cbAll) else: self.menu.add_command(label='delete', command=cb) if self.hidden is False: self.menu.add_command(label='hide', command=self.toggleVisibility_cb) else: self.menu.add_command(label='show', command=self.toggleVisibility_cb) self.menu.add_checkbutton(label='blocking', variable = self.isBlockingTk, command=self.toggleBlocking_cb) # adding the self.id as a unique tag for this node canvas.addtag_withtag(self.iconTag, arcTag ) if self.hidden is True: canvas.addtag_withtag(self.iconTag2, arcTag ) canvas.dtag( arcTag ) canvas.lower(g, 'node') def getSourceCode(self, networkName, selectedOnly=0, indent="", ignoreOriginal=False, connName='conn'): # build and return connection creation source code from NetworkEditor.ports import TriggerOutputPort lines = [] conn = self if conn._original is True and ignoreOriginal is False: return lines if selectedOnly and \ conn.port1.node.selected+conn.port2.node.selected < 2: return lines node1 = conn.port1.node node2 = conn.port2.node n1Name = node1.getUniqueNodeName() n2Name = node2.getUniqueNodeName() lines = node1.checkIfNodeForSavingIsDefined(lines, networkName, indent) lines = node2.checkIfNodeForSavingIsDefined(lines, networkName, indent) lines.append(indent+'if %s is not None and %s is not None:\n'%( n1Name, n2Name)) if isinstance(conn.port1, TriggerOutputPort): line1 = networkName+".specialConnectNodes(\n" else: line1 = '%s = '%connName +networkName+".connectNodes(\n" ## line = line + "%s, %s, %d, %d)\n"%(n1Name, n2Name, ## conn.port1.number, conn.port2.number) port1Name = conn.port1.name port2Name = conn.port2.name from macros import MacroInputNode, MacroOutputNode # treat connections to MacroInputNode separately if isinstance(conn.port1.node, MacroInputNode): if len(conn.port1.connections) > 1: i = 0 for c in conn.port1.connections: if c == conn: break else: i = i + 1 if i == 0: port1Name = 'new' else: port1Name = 'new' # treat connections to MacroOutpuNode separately if isinstance(conn.port2.node, MacroOutputNode): if len(conn.port2.connections) > 1: i = 0 for c in conn.port2.connections: if c == conn: break else: i = i + 1 if i == 0: port2Name = 'new' else: port2Name = 'new' line2 = '%s, %s, "%s", "%s", blocking=%s\n'%( n1Name, n2Name, port1Name, port2Name, conn.blocking) line3 = '' if conn.splitratio != [.5,.5]: line3 = ', splitratio=%s'%(conn.splitratio) if self.hidden is True: line3 += ', hidden=True' line3 += ')\n' lines.append(indent + ' '*4 + 'try:\n') lines.append(indent + ' '*8 + line1) lines.append(indent + ' '*12 + line2) lines.append(indent + ' '*12 + line3) lines.append(indent + ' '*4 + 'except:\n') lines.append(indent + ' '*8 + \ 'print "WARNING: failed to restore connection between %s and %s in network %s"\n'%(n1Name,n2Name,networkName)) return lines def destroyIcon(self): self.deleteIcon() self.id = None self.iconMaster = None self.id2 = None self.network.canvas.delete(self.iconTag2) self.iconTag2 = None self.naviMapID = None class FunctionNode(NetworkNode): """ Base node for Vsiion nodes exposing a function or callable object The RunFunction node is an example of subclassing this node. Opal web services nodes are instance of this node exposing the opal web service python wrapper callable object This object support creating input ports for all parameters to the function. Positional (i.e. without default value) arguments always generate an input port visible on the node. For named arguments arguments a widget is created base on the type of the default value (e.g. entry for string, dial for int and float etc.) If the function or callable object has a .params attribute this attribute is expected to be a dictionary where the key is the name of the argument and the\ value is dictionary providing additional info about this parameter. the folloing keys are recognized in this dictionary: {'default': 'False', # default value (not used as it is taken from the function signature) 'type': 'boolean', 'description': 'string' #use to create tooltip 'ioType': 'INPUT', # can be INPUT, INOUT, } if type is FILE a a file browser will be generated if type is selection a values keywords should be present and provide a list of possible values that will be made available in a combobox widget """ codeBeforeDisconnect = """def beforeDisconnect(self, c): # upon disconnecting we want to set the attribute function to None c.port2.node.function = None # remove all ports beyond the 'function' and 'importString' input ports for p in c.port2.node.inputPorts[2:]: c.port2.node.deletePort(p) """ def passFunction(): pass passFunc = passFunction def __init__(self, functionOrString=None, importString=None, posArgsNames=[], namedArgs={}, **kw): if functionOrString is not None or kw.has_key('functionOrString') is False: kw['functionOrString'] = functionOrString elif kw.has_key('functionOrString') is True: functionOrString = kw['functionOrString'] if importString is not None or kw.has_key('importString') is False: kw['importString'] = importString elif kw.has_key('importString') is True: importString = kw['importString'] if len(posArgsNames)>0 or kw.has_key('posArgsNames') is False: kw['posArgsNames'] = posArgsNames elif kw.has_key('posArgsNames') is True: posArgsNames = kw['posArgsNames'] if len(namedArgs)>0 or kw.has_key('namedArgs') is False: kw['namedArgs'] = namedArgs elif kw.has_key('namedArgs') is True: namedArgs = kw['namedArgs'] if type(functionOrString) == types.StringType: # we add __main__ to the scope of the local function # the folowing code is similar to: "from __main__ import *" # but it doesn't raise any warning, and its probably more local # and self and in1 are still known in the scope of the eval function from mglutil.util.misc import importMainOrIPythonMain lMainDict = importMainOrIPythonMain() for modItemName in set(lMainDict).difference(dir()): locals()[modItemName] = lMainDict[modItemName] if importString is not None: try: lImport = eval(importString) if lImport == types.StringType: importString = lImport except: pass exec(importString) if kw.has_key('masternet') is True: masterNet = kw['masternet'] lfunctionOrString = functionOrString while type(lfunctionOrString) == types.StringType: try: function = eval(lfunctionOrString) except NameError: function = None lfunctionOrString = function else: function = functionOrString if function is not None and kw.has_key('library'): # so we know where to find the current editor function._vpe = kw['library'].ed function._node = self # so we can find the vision node if hasattr(function, 'params') and type(function.params) == types.DictType: argsDescription = function.params else: argsDescription = {} if inspect.isclass(function) is True: try: function = function() except: function = None if function is None: #def testFunction(a, b=1): # print 'testFunction', a, b # return a, b function = self.passFunc if hasattr(function, 'name'): name = function.name elif hasattr(function, '__name__'): name = function.__name__ else: name = function.__class__.__name__ kw['name'] = name apply( NetworkNode.__init__, (self,), kw ) self.function = function # function or command to be called self.posArgsNames = posArgsNames self.namedArgs = namedArgs # dict: key: arg name, value: arg default self.outputPortsDescr.append(datatype='None', name='result') #for key, value in outputDescr: # self.outputPortsDescr.append(datatype=value, name=key) # get arguments description from inspect import getargspec if hasattr(function, '__call__') and hasattr(function.__call__, 'im_func'): args = getargspec(function.__call__.im_func) else: args = getargspec(function) if len(args[0])>0 and args[0][0] == 'self': args[0].pop(0) # get rid of self allNames = args[0] defaultValues = args[3] if defaultValues is None: defaultValues = [] nbNamesArgs = len(defaultValues) if nbNamesArgs > 0: self.posArgsNames = args[0][:-nbNamesArgs] else: self.posArgsNames = args[0] d = {} for name, val in zip(args[0][-nbNamesArgs:], defaultValues): d[name] = val self.namedArgs = d # create widgets and ports for named arguments self.buildPortsForPositionalAndNamedArgs(self.posArgsNames, self.namedArgs, argsDescription=argsDescription) # create the constructor arguments such that when the node is restored # from file it will have all the info it needs if functionOrString is not None \ and type(functionOrString) == types.StringType: self.constrkw['functionOrString'] = "\'"+suppressMultipleQuotes(functionOrString)+"\'" if importString is not None: self.constrkw['importString'] = "\'"+suppressMultipleQuotes(importString)+"\'" elif hasattr(function, 'name'): # case of a Pmv command self.constrkw['command'] = 'masterNet.editor.vf.%s'%function.name elif hasattr(function, '__name__'): # a function is not savable, so we are trying to save something self.constrkw['functionOrString'] = "\'"+function.__name__+"\'" else: # a function is not savable, so we are trying to save something self.constrkw['functionOrString'] = "\'"+function.__class__.__name__+"\'" if (importString is None or importString == '') \ and self.constrkw.has_key('importString') is True: del self.constrkw['importString'] if len(self.posArgsNames) > 0: self.constrkw['posArgsNames'] = self.posArgsNames elif self.constrkw.has_key('posArgsNames') is True: del self.constrkw['posArgsNames'] if len(self.namedArgs) > 0: self.constrkw['namedArgs'] = self.namedArgs elif self.constrkw.has_key('namedArgs') is True: del self.constrkw['namedArgs'] if kw.has_key('host') is True: self.constrkw['host'] = '\"'+suppressMultipleQuotes(kw['host'])+'\"' elif self.constrkw.has_key('host') is True: del self.constrkw['host'] code = """def doit(self, *args): # get all positional arguments posargs = [] for pn in self.posArgsNames: posargs.append(locals()[pn]) # build named arguments kw = {} for arg in self.namedArgs.keys(): kw[arg] = locals()[arg] # call function try: if hasattr(self.function,'__call__') and hasattr(self.function.__call__, 'im_func'): result = apply( self.function.__call__, posargs, kw ) else: result = apply( self.function, posargs, kw ) except Exception, e: from warnings import warn warn(str(e)) result = None self.outputData(result=result) """ if code: self.setFunction(code) # change signature of compute function self.updateCode(port='ip', action='create', tagModified=False) def buildPortsForPositionalAndNamedArgs(self, args, namedArgs, argsDescription={}, createPortNow=False): lAllPortNames = args + namedArgs.keys() for name in lAllPortNames: if name in args: ipdescr = {'name':name, 'required':True} if argsDescription.get(name): lHasDefaultValue = True val = argsDescription[name]['default'] else: lHasDefaultValue = False else: ipdescr = {'name':name, 'required':False} lHasDefaultValue = True val = namedArgs[name] dtype = 'None' if lHasDefaultValue is True: if argsDescription.get(name) and argsDescription[name]['type']=='selection': dtype = 'string' self.widgetDescr[name] = { 'class': 'NEComboBox', 'initialValue':val, 'choices':argsDescription[name]['values'], 'labelGridCfg':{'sticky':'w'}, 'labelCfg':{'text':name}, } elif argsDescription.get(name) \ and argsDescription[name]['type']=='FILE' \ and ( argsDescription[name]['ioType']=='INPUT' \ or argsDescription[name]['ioType']=='INOUT'): dtype = 'string' self.widgetDescr[name] = { 'class': 'NEEntryWithFileBrowser', 'initialValue':val, 'labelGridCfg':{'sticky':'w'}, 'labelCfg':{'text':name}, } elif type(val) is types.BooleanType: dtype = 'boolean' self.widgetDescr[name] = { 'class': 'NECheckButton', 'initialValue':val==True, 'labelGridCfg':{'sticky':'w'}, 'labelCfg':{'text':name}, } elif type(val) in [ types.IntType, types.LongType]: dtype = 'int' self.widgetDescr[name] = { 'class': 'NEDial', 'size':50, 'showLabel':1, 'oneTurn':1, 'type':'int', 'initialValue':val, 'labelGridCfg':{'sticky':'w'}, 'labelCfg':{'text':name}, } elif type(val) in [types.FloatType, types.FloatType]: dtype = 'float' self.widgetDescr[name] = { 'class': 'NEDial', 'size':50, 'showLabel':1, 'oneTurn':1, 'type':'float', 'initialValue':val, 'labelGridCfg':{'sticky':'w'}, 'labelCfg':{'text':name}, } elif type(val) is types.StringType: dtype = 'string' self.widgetDescr[name] = { 'class': 'NEEntry', 'width':10, 'initialValue':val, 'labelGridCfg':{'sticky':'w'}, 'labelCfg':{'text':name}, } if argsDescription.get(name): self.widgetDescr[name]['labelBalloon'] = argsDescription[name]['description'] ipdescr.update({'datatype':dtype, 'balloon':'Defaults to '+str(val), 'singleConnection':True}) self.inputPortsDescr.append( ipdescr ) if createPortNow is True: # create port ip = apply( self.addInputPort, (), ipdescr ) # create widget if necessary if dtype != 'None': ip.createWidget(descr=self.widgetDescr[name]) def inSegment( p, s1, s2): ## inSegment(): determine if a point is inside a segment ## Input: a point p, and a collinear segment [s1,s2] ## Return: 1 = P is inside S ## 0 = P is not inside S if s1[0] != s2[0]: # [s1,s2] is not vertical if s1[0]<=p[0] and p[0]<=s2[0]: return True if s1[0]>=p[0] and p[0]>=s2[0]: return True else: # S is vertical, so test y coordinate if s1[1]<=p[1] and p[1]<=s2[1]: return True if s1[1]>=p[1] and p[1]>=s2[1]: return True return False def perp( a ): # return 2D vector orthogonal to a b = [0,0] b[0] = -a[1] b[1] = a[0] return b def seg_intersect(a1, a2, b1, b2) : # line segment a given by endpoints a1, a2 # line segment b given by endpoints b1, b2 # return x,y of intersection of 2 segments or None, None da = (a2[0]-a1[0], a2[1]-a1[1]) db = (b2[0]-b1[0], b2[1]-b1[1]) dp = (a1[0]-b1[0], a1[1]-b1[1]) dap = perp(da) denom = numpy.dot( dap, db) if denom==0.0: return None, None num = numpy.dot( dap, dp ) l = (num / denom) return l*db[0]+b1[0], l*db[1]+b1[1] ## ## Image nodes are node for which the node is represented by an image rendered ## using pycairo and added to the canvas rather than using Tkinter.Canvas ## primitives ## def rotateCoords(center, coords, angle): """ Rotate a list of 2D coords around the center by an angle given in degrees """ cangle = cmath.exp(angle*1j*math.pi/180) offset = complex(center[0], center[1]) rotatedxy = [] for i in range(0, len(coords), 2): v = cangle * (complex(coords[i], coords[i+1]) - offset) + offset rotatedxy.append(v.real) rotatedxy.append(v.imag) return rotatedxy class NodeStyle: """ Class to define the rendering style of a node. Every node has a NodeStyle instance that is used to render the node's image. The VPE has a NodeStylesManager that stores alternate styles for each node """ def __init__(self, **kw): #flowDirection='leftRight' self.rotAngle = 0.0 self.width = None self.height = None self.fillColor = (0.82, 0.88, 0.95, 0.5) self.outlineColor = (0.28, 0.45, 0.6, 1.) self.inputPorts = {} self.iportNumToName = [] # list of names of input ports self.outputPorts = {} self.oportNumToName = [] # list of names of output ports self.configure(**kw) #if flowDirection=='leftRight': # sideIn = 'left' # sideOut = 'right' #elif flowDirection=='topBottom': # sideIn = 'top' # sideOut = 'bottom' #else: # raise RuntimeError, "bad flowDirection" #self.flowDirection = flowDirection def getPortXY(self, descr, node): ulx, uly = node.UL width = node.activeWidth height = node.activeHeight dx, dy = descr.get('ulrpos', (None, None)) if dx is not None: if abs(dx) < 1.0: dx *= width if abs(dy) < 1.0: dy *= height return ulx+dx, uly+dy dx, dy = descr.get('urrpos', (None, None)) if dx is not None: if abs(dx) < 1.0: dx *= width if abs(dy) < 1.0: dy *= height return ulx+width+dx, uly+dy dx, dy = descr.get('llrpos', (None, None)) if dx is not None: if abs(dx) < 1.0: dx *= width if abs(dy) < 1.0: dy *= height return ulx+dx, uly+height+dy dx, dy = descr.get('lrrpos',(None, None)) if dx is not None: if abs(dx) < 1.0: dx *= width if abs(dy) < 1.0: dy *= height return ulx+width+dx, uly+height+dy # fixme .. find a good location for this port return ulx+10, uly def getEdge(self, styleDict): for k,v in styleDict.items(): if k[-4:]=='rpos': found = True break if not found: print 'PORT EDGE NOT FOUND %rpos key missing using "top"', portNum, descr return 'top' if k=='ulrpos': if v[0]==0: return 'left' else: return 'top' elif k=='urrpos': if v[0]==0: return 'right' else: return 'top' elif k=='llrpos': if v[0]==0: return 'left' else: return 'bottom' elif k=='lrrpos': if v[0]==0: return 'right' else: return 'bottom' def setInputPortStyles(self, ipStyles): for name, styleDict in ipStyles: self.iportNumToName.append(name) styleDict['edge'] = self.getEdge(styleDict) self.inputPorts[name] = styleDict def setOutputPortStyles(self, opStyles): for name, styleDict in opStyles: self.oportNumToName.append(name) styleDict['edge'] = self.getEdge(styleDict) self.outputPorts[name] = styleDict def configure(self, **kw): width = kw.get('width', None) if width: if width > 0 and isinstance(width, (int, float)): self.width = width else: print 'WARNING bad width', width, type(width) height = kw.get('height', None) if height: if height > 0 and isinstance(height, (int, float)): self.height = height else: print 'WARNING bad height', height, type(height) rotAngle = kw.get('rotAngle', None) if rotAngle is not None: if isinstance(rotAngle, (int, float)): self.rotAngle = rotAngle else: print 'WARNING bad rotAngle', rotAngle, type(rotAngle) fillColor = kw.get('fillColor', None) if fillColor: if len(fillColor)==4 and isinstance(fillColor[0], float): self.fillColor = fillColor else: print 'WARNING bad fillColor', fillColor, type(fillColor) outlineColor = kw.get('outlineColor', None) if outlineColor: if len(outlineColor)==4 and isinstance(outlineColor[0], float): self.outlineColor = outlineColor else: print 'WARNING bad outlineColor', outlineColor, type(outlineColor) inputPorts = kw.get('inputPorts', None) if inputPorts: assert isinstance(inputPorts, dict) self.inputPorts = inputPorts.copy() outputPorts = kw.get('outputPorts', None) if outputPorts: assert isinstance(outputPorts, dict) self.outputPorts = outputPorts.copy() iportNumToName = kw.get('iportNumToName', None) if iportNumToName: assert isinstance(iportNumToName, list) self.iportNumToName = iportNumToName[:] oportNumToName = kw.get('oportNumToName', None) if oportNumToName: assert isinstance(oportNumToName, list) self.oportNumToName = oportNumToName[:] def getStyle(self): style = {'width':self.width, 'height':self.height, 'fillColor': self.fillColor, 'outlineColor': self.outlineColor, 'rotAngle': self.rotAngle, 'inputPorts': self.inputPorts, 'iportNumToName' : self.iportNumToName, 'outputPorts': self.outputPorts, 'oportNumToName' : self.oportNumToName, } return style def copy(self): return self.__class__( width = self.width, height = self.height, rotAngle = self.rotAngle, fillColor = self.fillColor[:], outlineColor = self.outlineColor[:], inputPorts = self.inputPorts.copy(), iportNumToName = self.iportNumToName[:], outputPorts = self.outputPorts.copy(), oportNumToName = self.oportNumToName[:] ) def getSize(self): return self.width, self.height def getFillColor(self): return self.fillColor def getOutlineColor(self): return self.outlineColor def getAngle(self): return self.rotAngle class ImageNode(NetworkNode): def saveStylesDefinition(self): # now save style in styles folder from mglutil.util.packageFilePath import getResourceFolderWithVersion sm = self.editor.nodeStylesManager styles = sm.getStylesForNode(self) visionrcDir = getResourceFolderWithVersion() folder = os.path.join(visionrcDir, 'Vision', 'nodeStyles') filename = os.path.join(folder, "%s__%s.py"%( self.library.name.replace('.', '___'), self.__class__.__name__)) f = open(filename, 'w') f.write("styles = {\n") default = styles.get('default', styles.keys()[0]) f.write(" 'default' : '%s',\n"%default) for name, style in styles.items(): if name=='default': continue f.write(" '%s' : %s,\n"%(name, str(style.getStyle()))) f.write(" }\n") f.close() ## def getStylesDefinitionSourceCode(self, indent): ## #'EwSignals_v0.1|EwAmpDelaySignal' : { ## # 'default' : 'square80' ## # 'square80' : {'width':80, 'height':80}, ## # 'small rectangle' : {'width':200, 'height':120}, ## # 'large rectangle' : {'width':300, 'height':240}, ## # }, ## lines = [] ## lines.append(indent+"'%s|%s' : {\n"%(self.library.name, self.__class__.__name__)) ## indent1 = indent + ' ' ## for name, style in self.styles.items(): ## lines.append(indent1+"'%s' : %s,\n"%(name, str(style))) ## lines.append(indent+"},\n") ## return lines def __init__(self, name='NoName', library=None, iconFileName=None, iconPath='', **kw): """ This class implements a NetworkNode that is rendered using a single image generated using pycairo """ constrkw = kw.pop('constrkw', None) NetworkNode.__init__(*(self, name, None, constrkw, None, library, 0), **kw) # posx and posy are upper left corner of BoundingBox(self.innerBox) self.center= [0,0] # coords of node's center in canvas self.rotAngle = 0.0 # keep track of rotation angle self.selectOptions = {} self.deselectOptions = {} self.iconPath = iconPath self.iconFileName = iconFileName # create the node renderer from NetworkEditor.drawNode import CairoNodeRenderer self.renderer = CairoNodeRenderer() self.posx = None # x coord of upper left corner of the node's image self.posy = None # y coord of upper left corner of the node's image self.activeWidth = None # length of not node's box self.activeheight = None # height of not node's box self.UL = (None, None) # offset of upper left corner of box in image # node styles self.nodeStyle = None self.currentNodeStyle = None # None means no predefined style is applied # else it is the name of the style def resize(self, event): self.startDrawingResizeBox(event) def startDrawingResizeBox(self, event): num = event.num self.mouseButtonFlag = self.mouseButtonFlag & ~num canvas = self.network.canvas canvas.configure(cursor="bottom_right_corner") x1, y1, x2, y2 = self.getBoxCorners() self.origx = x1 self.origy = y1 x = canvas.canvasx(event.x) y = canvas.canvasy(event.y) self.hasMoved = 0 canvas.bind(""%num, self.endResizeBox) canvas.bind(""%num, self.resizeBox) self.resizeBoxID = canvas.create_rectangle(x1, y1, x2, y2, outline='green') # function to draw the box def selectionBoxMotion(self, event): self.ResizeBox(event) # call back for motion events def resizeBox(self, event): canvas = self.network.canvas #if self.resizeBoIDx: canvas.delete(self.resizeBoxID) x = canvas.canvasx(event.x) y = canvas.canvasy(event.y) # check if the mouse moved only a few pixels. If we are below a # threshold we assume we did not move. This is usefull for deselecting # nodes for people who don't have a steady hand (who move the mouse # when releasing the mouse button, or when the mouse pad is very soft # and the mouse moves because it is pressed in the pad...) if abs(self.origx-x) < 10 or abs(self.origy-y) < 10: self.hasMoved = 0 else: self.hasMoved = 1 canvas.coords(self.resizeBoxID, self.origx, self.origy, x, y) x1, y1, x2, y2 = canvas.bbox(self.resizeBoxID) self.nodeStyle.configure(width=x2-x1, height=y2-y1) self.boxCorners = self.getBoxCorners() self.redrawNode() #print 'ORIG2', self.origx, self.origy, x, y, canvas.bbox(self.resizeBoxID) # callback for ending command def endResizeBox(self, event): canvas = self.network.canvas canvas.configure(cursor="") x1, y1, x2, y2 = canvas.bbox(self.resizeBoxID) width = self.activeWidth = x2-x1 height = self.activeHeight = y2-y1 self.nodeStyle.configure(width=width, height=y2-y1) self.redrawNode() canvas.delete(self.resizeBoxID) num = event.num self.mouseButtonFlag = self.mouseButtonFlag & ~num canvas.unbind(""%num) canvas.unbind(""%num) del self.origx del self.origy del self.resizeBoxID self.currentNodeStyle = None # set the a style name that is a key in # ed.nodeStylesManager.styles OR set to None when the style is modified # but not saved as a style def getNodeDefinitionSourceCode(self, networkName, indent="", ignoreOriginal=False): # specialize this method to save the the system configuration lines = NetworkNode.getNodeDefinitionSourceCode( self, networkName, indent=indent, ignoreOriginal=ignoreOriginal) # save node rotation if self.rotAngle !=0.0: lines.append( indent + '%s.rotate(%f)\n'%( self.nameInSavedFile, self.rotAngle)) # save node style if self.currentNodeStyle: # we have a predefined style lines.append( indent + '%s.setStyle("%s")\n'%( self.nameInSavedFile, self.currentNodeStyle)) else: # the style is modified but not saved as a template lines.append( indent + 'nodeStyle = %s\n'%self.nodeStyle.getStyle()) lines.append( indent + '%s.nodeStyle.configure(**nodeStyle)\n'%( self.nameInSavedFile)) return lines def autoResizeX(self): return def drawMapIcon(self, naviMap): canvas = naviMap.mapCanvas x0, y0 = naviMap.upperLeft scaleFactor = naviMap.scaleFactor c = self.getBoxCorners() cid = canvas.create_rectangle( [x0+c[0]*scaleFactor, y0+c[1]*scaleFactor, x0+c[2]*scaleFactor, y0+c[3]*scaleFactor], fill='grey50', outline='black', tag=('navimap',)) ## import Image ## im = self.imShadow1 ## self.scaledImage = im.resize((int(im.size[0]*scaleFactor), ## int(im.size[1]*scaleFactor)), Image.ANTIALIAS) ## self.mapImagetk = ImageTk.PhotoImage(image=self.scaledImage) ## cid = canvas.create_image( x0+self.posx*scaleFactor, y0+self.posy*scaleFactor, ## image=self.mapImagetk) self.naviMapID = cid return cid def deleteIcon(self): NetworkNode.deleteIcon(self) if hasattr(self, 'imagetk'): del self.imagetk # else the selected rendered node remains if hasattr(self, 'imagetkRot'): del self.imagetkRot # else the selected rendered node remains if hasattr(self, 'imagetkSel'): del self.imagetkSel # else the selected rendered node remains if hasattr(self, 'imagetkSelRot'): del self.imagetkSelRot # else the selected rendered node remains def select(self): canvas = self.iconMaster tags = canvas.itemcget(self.id, 'tags').split() NetworkNode.select(self) canvas.itemconfigure(self.innerBox, tags=tags+['selected'], image = self.imagetkSelRot) def deselect(self): canvas = self.iconMaster tags = canvas.itemcget(self.id, 'tags').split() NetworkNode.deselect(self) canvas.itemconfigure(self.innerBox, tags=tags, image = self.imagetkRot) canvas.dtag(self.innerBox,'selected') def getColor(self): return (0.82, 0.88, 0.95, 0.5) def setColor(self, color): return def pickedComponent(self, x, y): xr = x - self.posx-self.shadowOffset[0] # x relative to image upper left corner yr = y - self.posy-self.shadowOffset[1] # y relative to image upper left corner # check if the (x,y) is the position of an input port for p in self.inputPorts: px, py = p.posRotated dx = abs(xr-px) dy = abs(yr-py) if dx<10 and dy<10: return p, 'input' # check if the (x,y) is the position of an output port for p in self.outputPorts: px, py = p.posRotated dx = abs(xr-px) dy = abs(yr-py) if dx<10 and dy<10: return p, 'output' # check if we clicked inside the node p1x, p1y, p2x, p2y = self.boxCorners #self.network.canvas.create_rectangle(p1x, p1y, p2x, p2y, outline='blue') angle = self.rotAngle if angle !=0.0: # unrotate the (x,y) point cx, cy = self.nodeCenter[0]+self.posx, self.nodeCenter[1]+self.posy x, y = rotateCoords((cx,cy), [x, y], angle) #self.network.canvas.create_rectangle(x-2, y-2, x+2, y+2, outline='red') if (x>=p1x and x<=p2x) and (y>=p1y and y<=p2y): # check if we picked resize handle #if (x-p1x<10 and y-p1y<10): # print 'UL resize' # return self, 'resize UL' if (p2x-x<10 and p2y-y<10): return self, 'resize' return self, 'node' return None, None def buildIcons(self, canvas, posx, posy, small=False): """Build NODE icon with ports etc""" NetworkNode.buildIcons(self, canvas, posx, posy, small) # add node editing menu entries self.menu.add_separator() self.menu.add_command(label='Rotate', command=self.rotate_cb) #self.menu.add_command(label='Resize', command=self.resize) self.stylesMenuTK = Tkinter.StringVar() self.stylesMenu = Tkinter.Menu(self.menu, tearoff=0, postcommand=self.fillStyleMenu) self.menu.add_cascade(label="styles", menu=self.stylesMenu) def fillStyleMenu(self, event=None): self.stylesMenu.delete(0, 'end') self.stylesMenu.add_command(label="Save As ...", command=self.saveStyle) self.stylesMenu.add_command(label="Set as Default", command=self.setDefaultStyle, state='disabled') self.stylesMenu.add_separator() self.stylesMenu.add_radiobutton( label='auto', variable=self.stylesMenuTK, command=self.setStyle_cb, value='auto') sm = self.editor.nodeStylesManager styles = sm.getStylesForNode(self) if styles: for name, style in styles.items(): if name=='default': continue self.stylesMenu.add_radiobutton( label=name, variable=self.stylesMenuTK, command=self.setStyle_cb, value=name) if self.currentNodeStyle: self.stylesMenuTK.set(self.currentNodeStyle) if self.currentNodeStyle: # enable Set as Default menu entry self.stylesMenu.entryconfigure(1, state='normal') else: self.stylesMenuTK.set('') def setDefaultStyle(self): name = self.stylesMenuTK.get() self.editor.nodeStylesManager.setDefault(self, name) self.saveStylesDefinition() def setStyle_cb(self): name = self.stylesMenuTK.get() self.setStyle(name) def setStyle(self, name): if name == 'auto': self.currentNodeStyle = 'auto' else: sm = self.editor.nodeStylesManager styles = sm.getStylesForNode(self) self.nodeStyle.configure( **styles[name].getStyle() ) self.currentNodeStyle = name self.redrawNode() def _saveStyle(self, result): #self.askNameWidget.withdraw() self.askNameWidget.deactivate() if result == 'OK':# or hasattr(result, "widget"): name = self.askNameWidget.get() style = self.nodeStyle.getStyle() sm = self.editor.nodeStylesManager sm.addStyle(self, name, NodeStyle(**style)) self.currentNodeStyle = name self.saveStylesDefinition() def saveStyle(self): master = self.editor.root w = Pmw.PromptDialog( master, title = 'style name', label_text = "Enter the name for this style", entryfield_labelpos = 'n', buttons = ('OK', 'Cancel'), command=self._saveStyle) sm = self.editor.nodeStylesManager styles = sm.getStylesForNode(self) if styles: nb = len(styles)+1 else: nb = 1 w.insertentry(0, "custom%d"%nb) w.component('entry').selection_range(0, Tkinter.END) w.component('entry').focus_set() w.component('entry').bind('', self._saveStyle) w.geometry( '+%d+%d' % (master.winfo_x()+200, master.winfo_y()+200)) self.askNameWidget = w w.activate() def drawNode(self, sx, sy, line, fill, macro, padding): renderer = self.renderer # render the node shape renderer.makeNodeImage(sx, sy, line, fill, macro) self.UL = list(renderer.ul) # add ports #ip = self.inputPortsDescr #for pn in range(len(ip)): # # find the position in image # x, y , size, vector, line, fill, outline, label = ip.getDrawingParams( # pn, self) #print 'ZZZZZZZZZZ', self.nodeStyle.iportNumToName for pn, portName in enumerate(self.nodeStyle.iportNumToName): portStyle = self.nodeStyle.inputPorts[portName] print 'DRAWNODE1', pn, id(portStyle), portStyle port = self.inputPorts[pn] port.vector = portStyle['vector'] port.vectorRotated = portStyle['vector'] x, y = self.nodeStyle.getPortXY(portStyle, self) #edge = ip[pn]['edge'] #renderer.drawPort('in', x, y, size, vector, line, fill, outline, label, edge) renderer.drawPort('in', x, y, portStyle) port.posRotated = [x,y] port.pos = (x,y) #op = self.outputPortsDescr #for pn in range(len(op)): # # find the position # x, y, size, vector, line, fill, outline, label = op.getDrawingParams( # pn, self) # #print 'DRAWNODE1', self.name, pn, vector for pn, portName in enumerate(self.nodeStyle.oportNumToName): portStyle = self.nodeStyle.outputPorts[portName] port = self.outputPorts[pn] port.vector = portStyle['vector'] port.vectorRotated = portStyle['vector'] x, y = self.nodeStyle.getPortXY(portStyle, self) #edge = op[pn]['edge'] #renderer.drawPort('out', x, y, size, vector, line, fill, outline, label, edge) renderer.drawPort('out', x, y, portStyle) port.posRotated = [x,y] port.pos = (x,y) renderer.drawLabel(self.name, padding) if self.iconFileName: filename = os.path.join(self.iconPath, self.iconFileName) renderer.drawIcon(filename) def getDefaultPortsStyleDict(self): ipStyles = [] incr = 1.0/(len(self.inputPortsDescr)+1) for n, pd in enumerate(self.inputPortsDescr): ipStyles.append( (pd['name'], { 'ulrpos':((n+1)*incr,0), 'vector':(0,1), 'size':15, 'fill':(1,1,1,1), 'line':(0.28, 0.45, 0.6, 1.), 'outline':(0.28, 0.45, 0.6, 1.), 'label':pd['name'], })) opStyles = [] incr = 1.0/(len(self.outputPortsDescr)+1) for n, pd in enumerate(self.outputPortsDescr): opStyles.append( (pd['name'], { 'llrpos':((n+1)*incr,0), 'vector':(0,-1), 'size':15, 'fill':(1,1,1,1), 'line':(0.28, 0.45, 0.6, 1.), 'outline':(0.28, 0.45, 0.6, 1.), 'label':pd['name'], })) return ipStyles, opStyles def computeDefaultNodeSize(self): renderer = self.renderer ## compute the size needed for the node # get size of label x_bearing, y_bearing, width, height = renderer.getLabelSize(self.name) iconwidth = iconheight = 0 if self.iconFileName: iconwidth = 20 iconheight = 20 - 10 # -10 is minus the port label height width += iconwidth height += iconheight # here we compute how much space we need around the label for port icons and labels # port label are written using "Sans', size 10 which is 8 pixels heigh # for now we add 10 above the label and 10 below for the label # then we add maxPortSize / 2 above and below for the ports glyph # on the sides we will all the max of the port label length + 4 + max port glyph / 2 maxPortSize = 0 if self.iconFileName: maxPortLabLen = {'left':10, 'right':0, 'top':0, 'bottom':0} maxPortLabHeight = {'left':0, 'right':0, 'top':10, 'bottom':0} else: maxPortLabLen = {'left':0, 'right':0, 'top':0, 'bottom':0} maxPortLabHeight = {'left':0, 'right':0, 'top':0, 'bottom':0} sumPortLabLenTop = 0.0 sumPortLabLenBottom = 0.0 ip = self.inputPortsDescr for pn in range(len(ip)): edge = ip[pn]['edge'] = ip.getEdge(pn) size = ip.getSize(pn) if size > maxPortSize: maxPortSize=size label = ip.getLabel(pn) if label: x_b, y_b, w, h = renderer.getLabelSize(label, 'Sans', size=10) if edge=='top': sumPortLabLenTop += w+10 elif edge=='bottom': sumPortLabLenBottom += w+10 if w > maxPortLabLen[edge]: maxPortLabLen[edge] = w if h > maxPortLabHeight[edge]: maxPortLabHeight[edge] = h op = self.outputPortsDescr for pn in range(len(op)): edge = op[pn]['edge'] = op.getEdge(pn) size = op.getSize(pn) if size > maxPortSize: maxPortSize=size label = op.getLabel(pn) if label: x_b, y_b, w, h = renderer.getLabelSize(label, 'Sans', size=10) if edge=='top': sumPortLabLenTop += w+10 elif edge=='bottom': sumPortLabLenBottom += w+10 if w > maxPortLabLen[edge]: maxPortLabLen[edge] = w if h > maxPortLabHeight[edge]: maxPortLabHeight[edge] = h pady = maxPortLabHeight['top'] + maxPortLabHeight['bottom'] + maxPortSize + 2*8 padx = maxPortLabLen['left'] + maxPortLabLen['right'] + maxPortSize + 2*8 padding = { 'left': max(5, maxPortLabLen['left']), 'right': max(5, maxPortLabLen['right']), 'top': max(5, maxPortLabHeight['top']), 'bottom': max(5, maxPortLabHeight['bottom']) } sx = max( max(width, sumPortLabLenTop, sumPortLabLenBottom) + iconwidth + 2*8, max(maxPortLabLen['bottom'], maxPortLabLen['top'], )) sy = height+pady #print 'GGGGG', maxPortSize, maxPortLabLen, maxPortLabHeight, width, height, padx, pady, sx, sy return sx, sy, padding def makeNodeImage(self, canvas, posx, posy): renderer = self.renderer if self.nodeStyle is None: # no styles defined. Happens when we first # click on a node in the tree #print 'NO NodeStyle' sm = self.editor.nodeStylesManager stylesDict = sm.getStylesForNode(self) sx, sy, padding = self.computeDefaultNodeSize() if stylesDict is None: # no style available for this class #print ' no style dict' sx, sy, padding = self.computeDefaultNodeSize() style = NodeStyle(width=sx, height=sy) ipStyles, opStyles = self.getDefaultPortsStyleDict() style.setInputPortStyles(ipStyles) style.setOutputPortStyles(opStyles) self.currentNodeStyle = None #'auto' else: #print ' with style dict' default = stylesDict['default'] style = stylesDict[default].copy() self.currentNodeStyle = default sideIn = 'left' sideOut = 'right' ip = self.inputPortsDescr for pn in range(len(ip)): ip[pn]['edge'] = sideIn op = self.outputPortsDescr for pn in range(len(op)): op[pn]['edge'] = sideOut self.nodeStyle = style elif self.currentNodeStyle == 'auto': #print 'Auto NodeStyle' sx, sy, padding = self.computeDefaultNodeSize() style = self.nodeStyle = NodeStyle(width=sx, height=sy) ipStyles, opStyles = self.getDefaultPortsStyleDict() style.setInputPortStyles(ipStyles) style.setOutputPortStyles(opStyles) else: #print 'WITH NodeStyle' # call to compute padding sx, sy, padding = self.computeDefaultNodeSize() sx, sy = self.nodeStyle.getSize() self.activeWidth = sx self.activeHeight = sy # render the node shape fill = self.nodeStyle.getFillColor() line = self.nodeStyle.getOutlineColor() rotAngle = self.nodeStyle.getAngle() #print 'ANGLE', self.nodeStyle.getAngle() from NetworkEditor.macros import MacroImageNode macro = isinstance(self, MacroImageNode) # draw the node self.drawNode(sx, sy, line, fill, macro, padding) image = renderer.getPilImage() self.imShadow1, offset = renderer.addDropShadow() self.shadowOffset = offset #self.imShadow1 = image self.imagetk = ImageTk.PhotoImage(image=self.imShadow1) self.imagetkRot = self.imagetk self.imwidth = self.imagetk.width() self.imheight = self.imagetk.height() self.boxCorners = self.getBoxCorners() # assign ports posx and posy for port in self.inputPorts+self.outputPorts: x,y = port.pos port.posx = posx + self.shadowOffset[0] + x port.posy = posy + self.shadowOffset[0] + y canvas.itemconfigure(self.innerBox, image=self.imagetk) # draw image outline #bb = canvas.bbox(self.innerBox) #canvas.create_rectangle(*bb, outline='black') ## this could be used to render selected nodes # draw rectangle around node image bbox ## x1, y1, x2, y2 = renderer.bbox ## self.nodeOutline = canvas.create_rectangle( ## x1+posx, y1+posy, x2+posx, y2+posy, fill='yellow', ## outline='yellow', tags=(self.iconTag,'node')) ## build an image with yellow background for selected state fill = (1., 1, 0., 0.5) line = (0.28, 0.45, 0.6, 1.) self.drawNode(sx, sy, line, fill, macro, padding) self.imShadow2, offset = renderer.addDropShadow() #self.imShadow2 = image self.imagetkSel = ImageTk.PhotoImage(image=self.imShadow2) self.imagetkSelRot = self.imagetkSel bb = canvas.bbox(self.innerBox) self.nodeCenter = (bb[2]-bb[0])*.5, (bb[3]-bb[1])*.5 #self.nodeCenter = (sx*.5, sy*.5) #print 'ROTATE', rotAngle, self.rotAngle self.rotate(rotAngle) def buildNodeIcon(self, canvas, posx, posy, small=False): renderer = self.renderer self.iconMaster = canvas # set posx, posy self.posx = posx self.posy = posy self.innerBox = canvas.create_image( posx, posy, anchor=Tkinter.NW, # image=self.imagetk, tags=(self.iconTag,'node')) self.makeNodeImage(canvas, posx, posy) self.outerBox = self.innerBox bb = canvas.bbox(self.innerBox) #print 'NODE IMAGE BBOX', bb, posx, posy, self.UL, self.activeWidth, self.activeHeight, self.shadowOffset # draw node's image bounding box #canvas.create_rectangle( # *bb, fill='', outline='yellow', tags=(self.iconTag,'node')) # draw node's box bounding box #canvas.create_rectangle( # bb[0]+self.UL[0]+self.shadowOffset[0], # bb[1]+self.UL[1]+self.shadowOffset[1], # bb[0]+self.UL[0]+self.shadowOffset[0]+self.activeWidth, # bb[1]+self.UL[1]+self.shadowOffset[1]+self.activeHeight, # fill='', outline='green', tags=(self.iconTag,'node')) #self.nodeCenter = (bb[2]-bb[0])*.5, (bb[3]-bb[1])*.5 self.textId = self.innerBox self.id = self.innerBox # used to build network.nodesById used for picking self.iconTag = 'node'+str(self.textId) # used in node.select #if self.network is not None: # self.move(posx, posy) self.hasMoved = False # reset this attribute because the current # position is now the original position ## ## functions used to rotate Nodes ## def rotateRelative(self, angle): canvas = self.iconMaster self.rotAngle = (self.rotAngle + angle)%360 # center of unrotated image cx, cy = self.nodeCenter # rotate node images imShadowRotated = self.imShadow1.rotate( self.rotAngle, Image.BICUBIC, expand=1) self.imagetkRot = ImageTk.PhotoImage(image=imShadowRotated) imShadowRotated = self.imShadow2.rotate( self.rotAngle, Image.BICUBIC, expand=1) self.imagetkSelRot = ImageTk.PhotoImage(image=imShadowRotated) canvas.itemconfigure(self.innerBox, image=self.imagetkRot) bb = canvas.bbox(self.innerBox) #bb = self.boxCorners rcx, rcy = (bb[2]-bb[0])*.5, (bb[3]-bb[1])*.5 canvas.coords(self.innerBox, self.posx+cx-rcx, self.posy+cy-rcy) # rotate input ports # node center in canvas coordinate system cx = self.posx + self.nodeCenter[0] cy = self.posy + self.nodeCenter[1] # node uper left corner in canvas coordinate system offx = self.posx+self.shadowOffset[0] offy = self.posy+self.shadowOffset[1] for p in self.inputPorts: hpx = p.pos[0]+offx hpy = p.pos[1]+offy hpxr, hpyr = rotateCoords( (cx, cy), (hpx, hpy), -self.rotAngle) p.posRotated = (hpxr-offx, hpyr-offy) # display hotspots #canvas.create_rectangle(hpxr-5, hpyr-5, hpxr+5, hpyr+5) a,b,c,d = rotateCoords( (cx, cy), (0, 0, p.vector[0], p.vector[1]), -self.rotAngle) p.vectorRotated = [c-a, d-b] # rotate output ports for p in self.outputPorts: hpx = p.pos[0]+offx hpy = p.pos[1]+offy hpxr, hpyr = rotateCoords( (cx, cy), (hpx, hpy), -self.rotAngle) p.posRotated = (hpxr-offx, hpyr-offy) # draw rectangel on hotspot for debuging #canvas.create_rectangle(hpxr-5, hpyr-5, hpxr+5, hpyr+5) a,b,c,d = rotateCoords( (cx, cy), (0, 0, p.vector[0], p.vector[1]), -self.rotAngle) p.vectorRotated = [c-a, d-b] #print p.vector, a,b,c,d, p.vectorRotated def rotate(self, absoluteAngle): angle = absoluteAngle-self.rotAngle self.rotateRelative(angle) def rotate_cb(self): canvas = self.iconMaster bb = canvas.bbox(self.innerBox) # posx and pos y are upper left corner # self.nodeCenter is center of node relative to posx, posy cx = self.posx+self.nodeCenter[0] cy = self.posy+self.nodeCenter[1] rad = 0.5*max(bb[2]-bb[0], bb[1]-bb[0])+10 #draw a circle with an arrow to indicate node rotation mode arcid = canvas.create_oval( cx-rad, cy-rad, cx+rad, cy+rad, outline='green', width=2.0, tags=('arc',)) self._arcid = arcid canvas.tag_bind(arcid, "", self.startRotate_cb) def startRotate_cb(self, event=None): canvas = self.iconMaster self._x0 = canvas.canvasx(event.x) - self.posx - self.nodeCenter[0] self._y0 = canvas.canvasy(event.y) - self.posy - self.nodeCenter[1] #self._deltaAngle = 0 canvas.itemconfigure(self._arcid, outline='red') canvas.tag_bind(self._arcid, "", self.moveToRotate_cb) canvas.tag_bind(self._arcid, "", self.endRotate_cb) #self._lineid = canvas.create_line( # self.posx + self.nodeCenter[0], self.posy + self.nodeCenter[1], # canvas.canvasx(event.x), canvas.canvasy(event.y), # fill='green', width=2.0, tags=('arc',)) self._textid = canvas.create_text( canvas.canvasx(event.x)+10, canvas.canvasy(event.y)-10, text='%d'%self.rotAngle, fill='black', tags=('arc',)) def fullAngle(self, x0, y0, x1, y1): n0 = math.sqrt(x0*x0 + y0*y0) n1 = math.sqrt(x1*x1 + y1*y1) x0 = x0/n0 y0 = y0/n0 x1 = x1/n1 y1 = y1/n1 tetha = math.acos( (x0*x1 + y0*y1)) if x0*y1-y0*x1 > 0: return math.degrees(tetha) else: return math.degrees(2*math.pi-tetha) def moveToRotate_cb(self, event=None): canvas = self.iconMaster x1 = canvas.canvasx(event.x) - self.posx - self.nodeCenter[0] y1 = canvas.canvasy(event.y) - self.posy - self.nodeCenter[1] #canvas.coords( # self._lineid, self.posx + self.nodeCenter[0], # self.posy + self.nodeCenter[1], # canvas.canvasx(event.x), canvas.canvasx(event.y)) angle = self.fullAngle(self._x0, -self._y0, x1, -y1) self.rotate(15*int(angle/15)) canvas.itemconfigure(self._textid, text='%d'%self.rotAngle) canvas.coords(self._textid, canvas.canvasx(event.x)+10, canvas.canvasx(event.y)-10) for p in self.inputPorts: for c in p.connections: c.updatePosition() for p in self.outputPorts: for c in p.connections: c.updatePosition() def endRotate_cb(self, event=None): canvas = self.iconMaster self.iconMaster.delete('arc') self.nodeStyle.configure(rotAngle=self.rotAngle) self.currentNodeStyle = None # set the a style name that is a key in del self._arcid del self._x0 del self._y0 def updatePosXPosY(self, dx, dy): """set node.posx and node.posy after node has been moved""" self.posx += dx self.posy += dy self.boxCorners = self.getBoxCorners() def getBoxCorners(self): dx, dy = self.UL sx, sy = self.shadowOffset p1x, p1y = self.posx + dx + sx, self.posy + dy + sy, p2x = self.posx + dx + sx + self.activeWidth p2y = self.posy + dy + sy + self.activeHeight return p1x, p1y, p2x, p2y ## ## functions used to move ports ## def segmentIntersection(self, p0, p1): canvas = self.iconMaster # center of unrotated image cx, cy = self.nodeCenter[0]+self.posx, self.nodeCenter[1]+self.posy p1x, p1y, p2x,p2y = self.boxCorners coords = (p1x, p1y, p2x, p1y, p2x, p2y, p1x, p2y, p1x, p1y) # rotate coords of the box box coords = rotateCoords((cx,cy), coords, -self.rotAngle) l = len(coords) for i in range(0, l, 2): p2 = (coords[i], coords[i+1]) p3 = (coords[(i+2)%l], coords[(i+3)%l]) xi, yi = seg_intersect(p0, p1, p2, p3) inSegment( (xi, yi), p2, p3) if inSegment( (xi, yi), p0, p1) and inSegment( (xi, yi), p2, p3): #print 'intersection with edge', i, p0, p1, p2, p3, xi, yi return xi,yi return None, None def movePortTo(self, port, x, y): # x.y are absolute coordinates on the canvas and are expected to be # on the node's box outline # # x and y are potentially on the rotated shape of the box # we undo the rotation to set the port description, re-create the image # and rotate the node #print 'MOVE PORT TO', x, y angle = self.rotAngle if angle !=0.0: # unrotate the (x,y) point cx, cy = self.nodeCenter[0]+self.posx, self.nodeCenter[1]+self.posy x, y = rotateCoords((cx,cy), [x,y], self.rotAngle) # find the port's style description from ports import ImageInputPort, ImageOutputPort if isinstance(port, ImageInputPort): name = self.nodeStyle.iportNumToName[port.number] pd = self.nodeStyle.inputPorts[name] else: name = self.nodeStyle.oportNumToName[port.number] pd = self.nodeStyle.outputPorts[name] #print 'BEFORE', pd # now remove the **rpos entry for k,v in pd.items(): if k[-4:]=='rpos': print k, pd[k] del pd[k] width = float(self.activeWidth) height = float(self.activeHeight) p1x, p1y, p2x, p2y = self.getBoxCorners() #print p1x, p1y, p2x, p2y if abs(x-p1x)<2: # left edge pd['ulrpos'] = (0, (y-p1y)/height) pd['vector'] = (-1, 0) #print 'ulrpos1', (x-p1x, y-p1y) elif abs(x-p2x)<2: # right edge pd['lrrpos'] = (0, (y-p2y)/height) pd['vector'] = (1, 0) #print 'lrrpos2', (x-p2x, y-p2y) elif abs(y-p1y)<2: # top edge pd['ulrpos'] = ((x-p1x)/width, 0) pd['vector'] = (0, 1) #print 'ulrpos1', (x-p1x, y-p1y), vector elif abs(y-p2y)<2: # bottom edge pd['lrrpos'] = ((x-p2x)/width, 0) pd['vector'] = (0, -1) #print 'lrrpos2', (x-p2x, y-p2y) else: print "ERROR: edge not found", x, y, p1x, p1y, p2x, p2y pd['edge'] = self.nodeStyle.getEdge(pd) #print 'AFTER', id(pd), pd self.rotate(angle) self.redrawNode() port._hasMoved = True def rename(self, name, tagModified=True): """Rename a node. remember the name has changed, resize the node if necessary""" if name == self.name or name is None or len(name)==0: return # if name contains ' " remove them name = name.replace("'", "") name = name.replace('"', "") self.name=name self.redrawNode() if tagModified is True: self._setModified(True) def redrawNode(self): self.makeNodeImage(self.iconMaster, self.posx, self.posy) # update all connections for port in self.inputPorts+self.outputPorts: for c in port.connections: if c.id: c.updatePosition() def movePortToRelativeLocation(self, port, corner, dx, dy): # move a port to a position relative to one of its corners # corner can be ul, ll, ur, or lr string # dx, dy are relative displacements from the corner point p1x, p1y, p2x,p2y = self.boxCorners if corner=='ul': cornerx, cornery = p1x, p1y elif corner=='ll': cornerx, cornery = p1x, p2y elif corner=='ur': cornerx, cornery = p2x, p1y elif corner=='lr': cornerx, cornery = p2x, p2y self.movePortTo(port, cornerx+dx, cornery+dy) def movePortToRelativePos(self, port, corner, edge, percentx, percenty): # move a port to a position specified by a corner and a percent of edge length angle = self.rotAngle if angle !=0.0: # unrotate the (x,y) point cx, cy = self.nodeCenter[0]+self.posx, self.nodeCenter[1]+self.posy x, y = rotateCoords((cx,cy), [x,y], self.rotAngle) # find the port's style description from ports import ImageInputPort, ImageOutputPort if isinstance(port, ImageInputPort): name = self.nodeStyle.iportNumToName[port.number] pd = self.nodeStyle.inputPorts[name] else: name = self.nodeStyle.oportNumToName[port.number] pd = self.nodeStyle.outputPorts[name] ## # find the port's description ## found = False ## for p, pd in zip(self.inputPorts, self.inputPortsDescr): ## if p==port: ## found = True ## break ## if not found: ## for p, pd in zip(self.outputPorts, self.outputPortsDescr): ## if p==port: ## found = True ## break # now remove the **rpos entry for k,v in pd.items(): if k[-4:]=='rpos': del pd[k] p1x, p1y, p2x,p2y = self.boxCorners width = p2x-p1x height = p2y-p1y if corner=='ul': pd['ulrpos'] = (percentx, percenty) elif corner=='ll': pd['llrpos'] = (percentx, percenty) elif corner=='ur': pd['urrpos'] = (percentx, percenty) elif corner=='lr': pd['lrrpos'] = (percentx, percenty) if edge=='left': pd['vector'] = (-1, 0) elif edge=='right': pd['vector'] = (1, 0) elif edge=='top': pd['vector'] = (0, 1) elif edge=='bottom': pd['vector'] = (0, -1) self.makeNodeImage(self.iconMaster, self.posx, self.posy) self.rotate(angle) # update all connections for p in self.inputPorts+self.outputPorts: for c in p.connections: if c.id: c.updatePosition() port._hasMoved = True #n = WarpIV.ed.currentNetwork.nodes[0] mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/Editor.py0000644000175000017500000015555511234076572024560 0ustar debiandebian######################################################################### # # Date: Nov. 2001 Authors: Michel Sanner, Daniel Stoffler # # sanner@scripps.edu # stoffler@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner, Daniel Stoffler and TSRI # ######################################################################### # $Header: /opt/cvs/python/packages/share1.5/NetworkEditor/Editor.py,v 1.84 2009/07/29 16:48:58 sanner Exp $ # # $Id: Editor.py,v 1.84 2009/07/29 16:48:58 sanner Exp $ # import Tkinter, Pmw import string, types from mglutil.gui.BasicWidgets.Tk.customizedWidgets import kbComboBox from mglutil.util.callback import CallBackFunction from NetworkEditor.ports import InputPort, OutputPort from NetworkEditor.widgets import WidgetEditor from NetworkEditor.widgets import PortWidget, widgetsTable, createWidgetDescr from NetworkEditor.gridEditor import GridEditor ## TODO # # expose more things, like port color, tags, Tk options # expose widgets # write source code describing node # Disable OK CANCEL in node editor while port editors are open # class ObjectEditor: """Base class for editors such as node and port editor""" def __init__(self, object, type, master=None): """Contructor of the editor. object is the obect the editor exposes (i.e. a node of a port for instance). type is a string used to name the Editor window 'Node Editor' or 'Port Editor'. master master is an optional container into which to place the editor. If ommited the editor will appear in its own top level. """ self.nbEditorWindows = 0 # count number of sub windows open # when more than 1 disable ok apply cancel # buttons self.obj = object # obejct to be edited (node or port) self.type = type if master is None: master = Tkinter.Toplevel() self.createForm(master) self.values = {} # dict of values describing state of the form self.modal = 0 def createForm(self, master): """Build the form. Every editor has a frame called top followed by an Entry allowing to change the edited-object's name. Every editor also has Ok, Apply, Cancel buttons as the bottom of the form. These buttons are in a frame called okFrame which is a child of top. """ self.top = Tkinter.Frame(master) self.top.master.title(self.type+' Editor: '+self.obj.name) self.top.master.protocol('WM_DELETE_WINDOW',self.Dismiss) # node name self.nameGroup = w = Pmw.Group(self.top, tag_text=self.type+' Name') self.nameTk = Tkinter.StringVar() self.nameEntry = Tkinter.Entry( w.interior(), textvariable=self.nameTk) self.nameEntry.bind('', self.nameEntryChange_cb) self.nameTk.set(self.obj.name) self.nameEntry.pack(padx = 6, pady = 6, expand='yes', fill='x') w.pack(padx = 6, pady = 6, expand='yes', fill='both') self.addOkApplyCancel() def addOkApplyCancel(self): """Add the Ok, Apply and Cancel buttons """ self.okFrame = f = Tkinter.Frame(self.top) self.okButton = Tkinter.Button(f, text='OK', command=self.OK) self.okButton.pack(side='left', expand=1, fill='x') self.applyButton = Tkinter.Button(f, text='Apply', command=self.Apply) self.applyButton.pack(side='left', expand=1, fill='x') self.cancelButton = Tkinter.Button(f, text='Cancel', command=self.Cancel) self.cancelButton.pack(side='right', expand=1, fill='x') f.pack(side='bottom', expand=1, fill='x') self.top.pack(padx=10, pady=10, expand='yes', fill='both') def manageEditorButtons(self): """Helper function to enable/disable the ok apply cancel buttons. These buttons are disabled if another window is opened and needs to be closed before this editor can be closed. """ if self.nbEditorWindows > 0: self.applyButton['state'] = 'disabled' self.okButton['state'] = 'disabled' self.cancelButton['state'] = 'disabled' else: self.applyButton['state'] = 'normal' self.okButton['state'] = 'normal' self.cancelButton['state'] = 'normal' def nameEntryChange_cb(self, event=None): # callback fron key in Entry holding name pass def Apply(self, event=None): """Callback for Apply Button""" pass def OK(self, event=None): """Callback for OK Button, calls Apply and Dismiss""" self.Apply() self.Dismiss() def Cancel(self): """Callback for Cancel Button""" self.Dismiss() def Dismiss(self, event=None): """Destroy edit form and reset objcet's point to editor""" if not self.modal: self.top.master.destroy() self.obj.objEditor = None else: self.top.master.quit() def go(self): """method for starting an editor in modal form (i.e. the application blocks until the editor is dismissed """ self.modal=1 self.top.master.mainloop() self.top.master.destroy() return self.values class NodeEditor(ObjectEditor): """Node Editor. Allows to rename, add input and output ports, modifed the node's functions and start editors for ports and widgets""" def __init__(self, node, master=None): ObjectEditor.__init__(self, node, 'node', master) self.gridEditor = None # widget grid configuration GUI self.funcEditorDialog = None # edit source code GUI def nameEntryChange_cb(self, event=None): """apply the new name to the node and remember it has been modified""" name = self.nameTk.get() self.obj.configure(name=name) if self.funcEditorDialog is not None: self.funcEditorDialog.top.title( "Code editor: Node %s"%self.obj.name) def Apply(self, event=None): self.nameEntryChange_cb() self.Dismiss() def Dismiss(self, event=None): if self.funcEditorDialog: self.funcEditorDialog.hide() self.funcEditorDialog = None if self.gridEditor is not None: self.gridEditor.Cancel_cb() self.gridEditor = None for p in self.obj.inputPorts: self.deletePortButtons(p) for p in self.obj.outputPorts: self.deletePortButtons(p) ObjectEditor.Dismiss(self) def createForm(self, master): """Create standard editor form and add a Pmw Group for input ports, one for output ports and a check button for viewing the source node's code. """ ObjectEditor.createForm(self, master) # input Ports w = Pmw.Group(self.top, tag_pyclass=Tkinter.Button, tag_command=self.newInputPort, tag_text='Add Input Port') ipf = self.ipFrame = Tkinter.Frame(w.interior(),) Tkinter.Label(ipf, text='port #').grid(row=0, column=0, sticky='ew') Tkinter.Label(ipf, text='name').grid(row=0, column=1, sticky='ew') Tkinter.Label(ipf, text='edit\nport').grid(row=0, column=2, sticky='ew') Tkinter.Label(ipf, text='edit\nwidget').grid(row=0, column=3, sticky='ew') Tkinter.Label(ipf, text='del.\nport').grid(row=0, column=4, sticky='ew') for p in self.obj.inputPorts: self.addPortToForm(p, ipf) self.ipFrame.pack(expand=1, fill='x', padx=3, pady=3) w.pack(padx=6, pady=6, expand='yes', fill='both') # output Ports w = Pmw.Group(self.top, tag_pyclass=Tkinter.Button, tag_command=self.newOutputPort, tag_text='Add Output Port') opf = self.opFrame = Tkinter.Frame(w.interior(),) Tkinter.Label(opf, text='port #').grid(row=0, column=0, sticky='ew') Tkinter.Label(opf, text='name').grid(row=0, column=1, sticky='ew') Tkinter.Label(opf, text='edit\nport').grid(row=0, column=2,sticky='ew') Tkinter.Label(opf, text='del.\nport').grid(row=0, column=4, sticky='ew') for p in self.obj.outputPorts: self.addPortToForm(p, opf) self.opFrame.pack(expand=1, fill='x', padx=3, pady=3) w.pack(padx = 6, pady = 6, expand='yes', fill='both') # widget grid editor w = Pmw.Group(self.top, tag_text='widgets grid config') self.editGridVarTk = Tkinter.IntVar() self.editGridButton = Tkinter.Checkbutton( w.interior(), text='Edit ...', var=self.editGridVarTk, command=self.editGrid_cb ) self.editGridButton.pack(expand='yes', fill='x', padx=3, pady=3) w.pack(padx = 6, pady = 6, expand='yes', fill='both') # compute function w = Pmw.Group(self.top, tag_text='compute function') self.editButtonVarTk = Tkinter.IntVar() self.editButton = Tkinter.Checkbutton( w.interior(), text='Edit ...', var=self.editButtonVarTk, command=self.editFunction_cb ) self.editButton.pack(expand='yes', fill='x', padx=3, pady=3) w.pack(padx = 6, pady = 6, expand='yes', fill='both') self.manageEditorButtons() def addPortToForm(self, port, frame): """create a new line of widgets for a new port in the node editor""" line = port.number+1 port.portLabelTk = Tkinter.Label(frame, text=str(port.number)) port.portLabelTk.grid(row=line, column=0, sticky='ew') port.portNameTk = Tkinter.Label(frame, text=str(port.name)) port.portNameTk.grid(row=line, column=1, sticky='w') cb1 = CallBackFunction( self.editPort, port) cb2 = CallBackFunction( self.deletePort, port) cbvar = Tkinter.IntVar() if port.objEditor is not None: self.nbEditorWindows += 1 cbvar.set(int(not port.objEditor==None)) port.portEditCB = Tkinter.Checkbutton(frame, command=cb1, variable=cbvar) port.portEditCB.var = cbvar port.portEditCB.grid(row=line, column=2, sticky='ew') port.portDelCB = Tkinter.Checkbutton(frame, command=cb2) port.portDelCB.grid(row=line, column=4, sticky='ew') if port.widget: cb = CallBackFunction( self.editWidget, port) port.editWtk = Tkinter.Checkbutton(self.ipFrame,command=cb) port.editWtk.grid(row=port.number+1, column=3, sticky='ew') def newInputPort(self, event=None): """create a new input port with a default name""" self.obj.addSaveNodeMenuEntries() port = apply( self.obj.addInputPort, (), {} ) port._setModified(True) port.editWtk = None port.delWtk = None # add port to node edition form self.addPortToForm( port, self.ipFrame ) # and update funcEditorDialog if self.funcEditorDialog is not None: self.funcEditorDialog.settext(self.obj.sourceCode) def newOutputPort(self, event=None): # create a new output port with a default name self.obj.addSaveNodeMenuEntries() port = apply( self.obj.addOutputPort, (), {} ) port._setModified(True) # add port to node edition form self.addPortToForm( port, self.opFrame ) # and update funcEditorDialog if self.funcEditorDialog is not None: self.funcEditorDialog.settext(self.obj.sourceCode) def deletePortButtons(self, port): """removes widgets for this port from node editor""" node = port.node # remove buttons fot this port form the node editor panel: if port.portLabelTk is not None: port.portLabelTk.destroy() if port.portNameTk is not None: port.portNameTk.destroy() if port.portEditCB is not None: port.portEditCB.destroy() if port.portDelCB is not None: port.portDelCB.destroy() if port.widget: if port.editWtk is not None: port.editWtk.destroy() port.portLabelTk = None port.portNameTk = None port.portEditCB = None port.portDelCB = None port.editWtk = None def deletePort(self, port, event=None): """delete a port""" self.obj.addSaveNodeMenuEntries() # delete port editor is any if port.objEditor: port.objEditor.Cancel() # if the node editor is up update it if port.node.objEditor: self.deletePortButtons(port) # get a handle to port befoer we lose it in deletePort node = port.node # delete port editor is any if port.objEditor: port.objEditor.Cancel() # delete the port from the node's list of ports self.obj.deletePort(port, resize=True) # renumber remaining input ports in editor panel if isinstance(port, InputPort): for p in node.inputPorts: p.portLabelTk.configure(text=str(p.number)) # renumber remaining output ports in editor panel elif isinstance(port, OutputPort): for p in node.outputPorts: p.portLabelTk.configure(text=str(p.number)) # and update funcEditorDialog if self.funcEditorDialog is not None: self.funcEditorDialog.settext(self.obj.sourceCode) def editGrid_cb(self, event=None): """open a widget grid configuration editor""" # Note, every time activate the checkbutton, we want to build a fresh # window, since in the meantime, somebody could have added a new widget if self.editGridVarTk.get() == 1: self.nbEditorWindows += 1 self.manageEditorButtons() self.gridEditor = GridEditor(node=self.obj) else: self.gridEditor.Cancel_cb() self.gridEditor = None def editFunction_cb(self, event=None): """callback function of edit source code check button. Manages button's state, and status of Ok, Apply and Cancel buttons """ if self.editButtonVarTk.get() == 1: self.nbEditorWindows += 1 self.manageEditorButtons() if self.funcEditorDialog is None: self.displaySourceCode() else: self.funcEditorDialog.show() return else: self.funcEditorDialog.cancelCmd() def displaySourceCode(self): """If no source code editor is created yet build one, else just show it If idlelib is found we use it, else we use default Pmw-based widget. """ if self.funcEditorDialog is None: self.funcEditorDialog = SourceCodeEditor( node=self.obj, editor=self) else: self.funcEditorDialog.show() def editPort(self, port, event=None): # create a port editor if port.objEditor is not None: status = port.portEditCB.var.get() if status == 1: #port.portEditCB.toggle() port.objEditor.top.master.deiconify() else: port.objEditor.top.master.withdraw() return else: port.objEditor = ipEditor = PortEditor(port) self.nbEditorWindows += 1 self.manageEditorButtons() def editWidget(self, port, event=None): """start widget editor""" if port.widget.objEditor: port.widget.objEditor.Cancel_cb() else: port.widget.edit() class PortEditor(ObjectEditor): def __init__(self, port, master=None): """PortEditor constructor. Port is the port which we want to edit. """ if isinstance(port, InputPort): title = 'Input Port' elif isinstance(port, OutputPort): title = 'Output Port' else: title = 'Port' ObjectEditor.__init__(self, port, title, master) port.objEditor = self self.node = port.node self.callbackEditorDialog = None # code editor window self.abortApply = 0 # setWidget() can remove port nodeEditor = self.node.objEditor if nodeEditor is not None: port.portEditCB.select() nodeEditor.nbEditorWindows -= 1 nodeEditor.manageEditorButtons() def createForm(self, master): """Create default form and add widgets for setting data type, start the widget editor, set the required, singleConnection and toolTip """ port = self.obj ObjectEditor.createForm(self, master) #w = Tkinter.Frame(self.top, relief='groove') # add protocol for killing this window self.top.master.protocol("WM_DELETE_WINDOW", self.Cancel) w = Pmw.Group(self.top, tag_text='Port options') # datatype vpe = port.getEditor() l = vpe.typeManager.portTypeInstances.keys() l.sort() self.dataType = kbComboBox( w.interior(), label_text='data type:', labelpos='w', entryfield_entry_width=14, scrolledlist_items = l ) if port.datatypeObject: self.dataType.setentry(port.datatypeObject['name']) self.dataType.grid() # Edit Callbacks w2 = Pmw.Group(self.top, tag_text='Port callbacks') l2 = ['beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect'] self.chooseCallback = kbComboBox( w2.interior(), label_text='edit source code', labelpos='w', entryfield_entry_width=14, scrolledlist_items = l2, selectioncommand=self.editCallback) self.chooseCallback.grid() w1 = None if isinstance(port, InputPort): # required var = Tkinter.IntVar() var.set(port.required) tk = Tkinter.Checkbutton( w.interior(), text='required', indicatoron=1, variable=var) tk.var = var tk.grid() self.requiredTk = tk # singleConnection from NetworkEditor.macros import MacroNode if isinstance(port.node, MacroNode): # to avoid multiple list enclosure, MacroInputNode # must always be singleConnection. # to have multiple connections, # the solution is to duplicate the input port in the macronode # (there is no such need for the MacroOutputNode) connTypes = ['True'] else: connTypes = ['True', 'False', 'auto'] self.singleConnectionTk = kbComboBox( w.interior(), label_text='single connection:', labelpos='w', scrolledlist_items=connTypes, entryfield_entry_width=15 ) status = str(port.singleConnection) self.singleConnectionTk.selectitem(status) self.singleConnectionTk.grid() # widget section w1 = Pmw.Group(self.top, tag_text='Widget') ## # port has a widget add Unbind button ## if port.widget: ## var = Tkinter.IntVar() ## tk = Tkinter.Checkbutton( ## w.interior(), text='single connection', ## variable=var, indicatoron=1) ## tk.var = var ## tk.grid() ## self.unbindWidget = tk # BIND WIDGET widgetList = [] if port.widget: # this port has a widget widgetList.append('Unbind') if port._previousWidgetDescr: widgetList.append('Rebind') widgetList = widgetList + widgetsTable.keys() widgetList.sort() # widget self.widgetType = kbComboBox( w1.interior(), label_text='Type:', labelpos='w', scrolledlist_items=widgetList, entryfield_entry_width=15 ) if port.widget: self.widgetType.set(port.widget.__class__.__name__) self.widgetType.grid(row=0, column=0, padx=6, pady=6) masterList = ['ParamPanel', 'Node'] if len(self.obj.network.userPanels.keys()): masterList += self.obj.network.userPanels.keys() self.widgetMaster = kbComboBox( w1.interior(), label_text='Place in:', labelpos='w', scrolledlist_items=masterList, entryfield_entry_width=15, selectioncommand=self.setMaster_cb) # self.setMaster_cb() method will update the GUI but not # call setMaster(), only Apply button will do that self.widgetMaster.grid(row=1, column=0, padx=6, pady=6) self.widgetMaster.selectitem('ParamPanel') # set the choice to current master if port.widget: if port.widget.inNode is True: self.widgetMaster.selectitem('Node') else: if port.widget.master in self.obj.network.userPanels.keys(): self.widgetMaster.selectitem(port.widget.master) self.editWidgetVarTk = Tkinter.IntVar() self.editWidgetButton = Tkinter.Checkbutton( w1.interior(), text='Edit Widget...', var=self.editWidgetVarTk, command=self.toggleWidgetEditor) self.editWidgetButton.grid() if self.obj.widget is not None: if self.obj.widget.objEditor: self.editWidgetVarTk.set(1) w.pack(padx = 6, pady = 6, expand='yes', fill='both') w2.pack(padx = 6, pady = 6, expand='yes', fill='both') if w1: w1.pack(padx = 6, pady = 6, expand='yes', fill='both') self.tt = Pmw.ScrolledText(self.top, usehullsize=1, borderframe=1, labelpos = 'nw', label_text='ToolTip', hull_width = 300, hull_height = 100, text_wrap='none' ) if port.balloon: self.tt.settext(port.balloon) self.tt.pack(padx=5, pady=5, fill='both', expand=1) self.top.pack(padx=10, pady=10, expand='yes', fill='both') def nameEntryChange_cb(self, event=None): """apply the new name to the node and remember it has been modified""" pass # disabled callback because we want this to run only if Apply is pressed ## name = self.nameTk.get() ## port = self.obj ## if name != port.name: ## port.configure(name=name) def editCallback(self, event=None): mode = self.chooseCallback.get() if mode in self.obj.callbacks.keys(): self.displayCallbackSourceCode(mode) def displayCallbackSourceCode(self, mode): if self.callbackEditorDialog is not None: self.callbackEditorDialog.hide() self.callbackEditor = None self.callbackEditorDialog = CallbackEditor( port=self.obj, mode=mode) def OK(self, event=None): ObjectEditor.OK(self) nodeEditor = self.obj.node.objEditor if nodeEditor: nodeEditor.nbEditorWindows -= 1 nodeEditor.manageEditorButtons() self.obj.portEditCB.toggle() def Cancel(self): """Callback for Cancel Button""" nodeEditor = self.obj.node.objEditor port = self.obj ObjectEditor.Cancel(self) if nodeEditor: nodeEditor.nbEditorWindows -= 1 nodeEditor.manageEditorButtons() port.portEditCB.toggle() def Apply(self, event=None): """Callback for Apply Button""" opts = {} port = self.obj name = self.nameTk.get() if name != self.obj.name: port.configure(name=name) # and rename entry in node editor window if hasattr(self.obj, 'portNameTk') and \ self.obj.portNameTk is not None: self.obj.portNameTk.configure(text=name) datatype = self.dataType.get() if datatype != port.datatypeObject['name']: opts['datatype'] = datatype if isinstance(port, InputPort): required = self.requiredTk.var.get()==1 if required != port.required: opts['required'] = required singleConnection = self.singleConnectionTk.get() if singleConnection == 'True': singleConnection = True elif singleConnection == 'False': singleConnection = False if singleConnection != port.singleConnection: opts['singleConnection'] = singleConnection widgetName = self.widgetType.get() if widgetName == 'Rebind': port.rebindWidget() widgetList = ['Unbind'] + widgetsTable.keys() widgetList.sort() self.widgetType.setlist(widgetList) self.widgetType.set('Unbind') elif widgetName == 'Unbind': port.unbindWidget() widgetList = ['Rebind'] + widgetsTable.keys() widgetList.sort() self.widgetType.setlist(widgetList) self.widgetType.set('Rebind') # destroy edit widget checkbutton to node editor if port.editWtk: port.editWtk.destroy() port.editWtk = None elif widgetName: if port.widget: currentWidget = port.widget oldwname = currentWidget.__class__.__name__ else: currentWidget = None oldwname = None if widgetName != currentWidget.__class__.__name__: # new widget editWidget = self.editWidgetVarTk.get() if editWidget: # Start Widget Editor was check so get wdescr from it form = WidgetEditor( widgetsTable[widgetName].configOpts) wdescr = form.go() wdescr['class'] = widgetName #for k,v in wdescr.items(): # print k, v else: if currentWidget: port._previousWidgetDescr=currentWidget.getDescr() wdescr = createWidgetDescr(widgetName, port._previousWidgetDescr) if currentWidget: wdescr['initialValue'] = port.widget.get() port.deleteWidget() port.createWidget(descr=wdescr) if port.widget.inNode: port.node.hideInNodeWidgets() port.node.showInNodeWidgets() # add edit widget checkbutton to node editor if applicable, # such as if we rebind a widget, or create a new one if port.widget and not port.editWtk and \ hasattr(port.node, 'objEditor') and \ port.node.objEditor is not None: nodeEditor = port.node.objEditor cb = CallBackFunction( nodeEditor.editWidget, port) port.editWtk = Tkinter.Checkbutton( nodeEditor.ipFrame,command=cb) port.editWtk.grid(row=port.number+1, column=3, sticky='ew') self.setMaster(self.widgetMaster.get()) tt = self.tt.get() if tt[-1]=='\n': tt = tt[:-1] if tt != port.balloon and len(tt)>0: opts['balloon'] = tt if len(opts): apply( port.configure, (), opts) port.node.addSaveNodeMenuEntries() def toggleWidgetEditor(self, event=None): port = self.obj if port.widget is None: self.editWidgetVarTk.set(0) return if port.widget.objEditor is None: # open form port.widget.edit() else: # close form port.widget.objEditor.Cancel_cb() def setWidget(self, widgetName): # this method does the following # update the node editor panels # update node.widgetDescr # update the port object port = self.obj node = port.node if widgetName == 'rebind from Macro': # Here we rebind from macro node to original node macroPort = port.widgetInMacro['macroPort'] wdescr = macroPort.node.widgetDescr[macroPort.name] port.widgetInMacro = {} self.rebindWidgetFromMacro(macroPort, wdescr) self.abortApply = 1 #after that the port no longer exists self.Dismiss() return elif widgetName == 'previous Node': # Here we rebind from macro node to original node wdescr = port.node.widgetDescr[port.name] port.node.objEditor.deletePortButtons(port) port.widgetInMacro = {} self.rebindWidgetFromMacro(port, wdescr) self.abortApply = 1 self.Dismiss() return if port.widget is None and port._previousWidgetDescr is None and \ widgetName is None or widgetName == '': return # if the port has a widget and the widget chooser in the panel # has the same type as the current widget we return # this happens when the apply or ok buttons are used and the # the widget combobox has not been changed if port.widget: if widgetName==port.widget.__class__.__name__: return if widgetName=='Unbind': w = 'Unbind' widgetList = ['Rebind'] + widgetsTable.keys() self.widgetType.setlist(widgetList) elif widgetName=='Rebind': w = 'Rebind' widgetList = ['Unbind'] + widgetsTable.keys() self.widgetType.setlist(widgetList) else: # if same widget as already bound: return if port.widget and widgetName==port.widget.__class__.__name__: return # build widget port.createWidget() w = port.widget if isinstance(w, PortWidget): widgetList = ['Unbind'] if port.widget: # current will become previous so widgetList.append('Rebind') widgetList = widgetList + widgetsTable.keys() self.widgetType.setlist(widgetList) port.setWidget(w) wmaster = self.masterTk.get() # and set the master here: self.setMaster(wmaster) # set the master in the widgetDescr so that setMaster does not # rebuild the widget again if Apply is pressed twice or more if wmaster == 'Node': mMaster = 'node' else: mMaster = wmaster # if we unbind, the node has no widgetDescr so we have to test here if node.widgetDescr.has_key(port.name): node.widgetDescr[port.name]['master'] = mMaster # add/remove edit widget button if not there yet if w is not None and port.editWtk is None: # widget but no button nodeEd = node.objEditor cb = CallBackFunction( nodeEd.editWidget, port) port.editWtk = Tkinter.Checkbutton(nodeEd.ipFrame,command=cb) port.editWtk.grid(row=port.number+1, column=3, sticky='ew') elif w is None and port.editWtk is not None: port.editWtk.destroy() port.editWtk = None ## if port.delWtk is None: ## cb = CallBackFunction( nodeEd.deletePort, port) ## port.delWtk = Tkinter.Checkbutton(nodeEd.ipFrame,command=cb) ## port.delWtk.grid(row=port.number+1, column=4, sticky='ew') # def widgetEditor(): # # self.masterTk = kbComboBox( # w, label_text='Master:', labelpos='w', # scrolledlist_items = []) # self.masterTk.grid(row=lastrow, column=0, columnspan=2) # # wmaster = None # if port.widget: # # MacroNodes are special: - the only widget for this port that # # can be selected is the one that has been bound from a node # # inside the macronetwork and the only master that can be # # chosen is the previous node # from NetworkEditor.macros import MacroNode # if isinstance(port.node, MacroNode): # wname = 'previous Node' # self.widgetType.setlist([wname]) # self.widgetType.selectitem(wname) # mas = port.node.widgetDescr[port.name]['origMaster'] # if mas == 'node': # mas = 'Node' # wmaster = mas # else: # name = None # for name,wid in widgetsTable.items(): # if wid==port.widget.__class__: # break # if name: # self.widgetType.selectitem(name) # # # now choose the appropriate master in the scrollbox # if wmaster: # self.updateMasterList(master=wmaster) # else: # self.updateMasterList() # # # is this a port where the widget has been bound to a macro node # elif len(port.widgetInMacro): # wname = 'rebind from Macro' # self.widgetType.setlist([wname]) # self.widgetType.selectitem(wname) # mas = port.widgetInMacro['master'] # if mas == 'node': # mas = 'Node' # self.masterTk.setlist([mas]) # self.masterTk.selectitem(mas) # # # else initialize with previousWidget's master to make sure # # that a triggered by Apply or OK places it back to the right # # panel # # elif port._previousWidgetDescr is not None: # widgetList = ['Rebind'] + widgetsTable.keys() # self.widgetType.setlist(widgetList) # # descr = port._previousWidgetDescr # if descr.has_key('master'): # wmaster = descr['master'] # if wmaster == 'node': # wmaster = 'Node' # else: # else used node's param panel (default) # wmaster = 'ParamPanel' # # self.updateMasterList(master=wmaster) # # else: # # no widget, no master available then # self.updateMasterList(clear=1) def rebindWidgetFromMacro(self, port, wdescr): cfg0 = port.widget.getConstructorOptions() cfg0['master'] = wdescr['origMaster'] # cfg0['visibleInNode'] = wdescr['origvisibleInNode'] cfg0['visibleInNodeByDefault'] = wdescr['origvisibleInNodeByDefault'] cfg1 = port.widget.configure() cfg1['master'] = wdescr['origMaster'] cfg2 = port.widget.get() origport = wdescr['origPort'] orignode = origport.node # 2) unbind widget and delete previousWidget port.setWidget('Unbind') port._previousWidgetDescr = None # 3) add widgetDescr to orignode del wdescr['origPort'] del wdescr['origMaster'] # del wdescr['origvisibleInNode'] del wdescr['origvisibleInNodeByDefault'] # 4) rebind widget to original node orignode.widgetDescr[origport.name] = wdescr origport.savedConfiguration = (cfg0, cfg1, cfg2) origport.createWidget() def setMaster_cb(self, event=None): # update GUI: if we change the master, we select in the widget list # the current widget whose master will be changed (for example, if # we are still in the mode 'unbind' or 'rebind') if self.obj.widget: self.widgetType.set(self.obj.widget.__class__.__name__) def setMaster(self, masterName): port = self.obj if port.widget is None and port._previousWidgetDescr is None: self.widgetMaster.component('entryfield').clear() if port.widget is None: return node = port.node if masterName=='Node': masterName = 'node' # save widget configuration wdescr = port.widget.getDescr() # save widget value wdescr['initialValue'] = port.widget.get() # FIXME, NOTE: IN THIS CURRENT RELEASE, WE DO NOT SUPPORT A MASTER # OTHER THAN NODE OR PARAMPANEL!! THE FOLLOWING CODE IS THEREFORE # NEVER EXECUTED. NOV-29-2003. # WE WILL RE-ACTIVATE THIS IN THE FUTURE # find if master is a Macro node masterlist = ['node', 'ParamPanel'] if len(self.obj.network.userPanels.keys()): masterlist += self.obj.network.userPanels.keys() if masterName not in masterlist: # ok, this is a macro # Note: simply changing the master of a widget does NOT work: # we have to delete the widget and reconstruct it! # Unbind/Rebind from one node to another one DOES NOT work. # 1) save configuration of this widget to the MacroNode # so that the widget can be properly rebuilt oldmaster = wdescr['master'] cfg0 = port.widget.getConstructorOptions() cfg0['master'] = 'ParamPanel' #cfg0['visibleInNode'] = 0 cfg0['visibleInNodeByDefault'] = 0 cfg1 = port.widget.configure() cfg1['master'] = 'ParamPanel' cfg2 = port.widget.get() # 2) unbind widget and delete previousWidgetDescr port.setWidget('Unbind') port._previousWidgetDescr = None self.widgetType.setlist([]) port.node.widgetDescr[port.name] = wdescr del port.node.widgetDescr[port.name] # 3) connect this node to a MacroInputNode and up the 'tree' from NetworkEditor.macros import MacroNetwork net = port.node.network portNumber = port.number origNumber = port.number macroNode = port.node # at first iteration this is of course # port.node, from then on its the macro node while isinstance(net, MacroNetwork): ipnode = net.ipNode c = net.connectNodes(ipnode, macroNode, 0, portNumber) opt = c.deselectOptions opt['stipple'] = 'gray25' apply (c.iconMaster.itemconfigure, (c.iconTag,), opt) macroNode = net.macroNode portNumber = c.port1.number-1 # macroNodes have port#-1 if macroNode.name == masterName: break net = macroNode.network # 4) add saved configuration to MacroNode mip = macroNode.inputPorts[portNumber] mip.savedConfiguration = (cfg0, cfg1, cfg2) # 5) add widget to MacroNode wdescr['master'] = 'ParamPanel' macroNode.widgetDescr[mip.name] = wdescr mip.createWidget() newwidget = macroNode.inputPorts[portNumber].widget # 6) store information in original node and macro node port.widgetInMacro = {'macroNode':macroNode, 'macroPort':mip, 'master':oldmaster, 'wdescr':wdescr} name = mip.name macroNode.widgetDescr[name]['origPort'] = port macroNode.widgetDescr[name]['origMaster'] = oldmaster #macroNode.widgetDescr[name]['origvisibleInNode'] = \ # wdescr['visibleInNode'] macroNode.widgetDescr[name]['origvisibleInNodeByDefault']=\ wdescr['visibleInNodeByDefault'] # 7) close port editor window self.abortApply = 1 self.Dismiss() return changeMaster=1 if wdescr.has_key('master') and wdescr['master'] == masterName: changeMaster = 0 # update the port description in the node oldmaster = wdescr['master'] wdescr['master'] = masterName if changeMaster: port.widget.configure(master=masterName) # unbind widget (this destroys the old widget) #port.unbindWidget() # delete the not needed previousWidgetDescr #port._previousWidgetDescr = None ## if port.node.isExpanded(): # do not bind widget to expanded node, ## # this leads to unpredictable results! ## port.node.toggleNodeExpand_cb() # create new widget #port.createWidget(descr=wdescr) # create new widget #port.node.autoResize() #if port.node.inNodeWidgetsVisibleByDefault and not \ # port.node.isExpanded(): # port.node.toggleNodeExpand_cb() def updateMasterList(self, event=None, master=None, clear=0): """this method is called when a widget is selected and when the editor panel is build""" port = self.obj if master == [] or clear: self.masterTk.component('entryfield').clear() return from NetworkEditor.macros import MacroNetwork, MacroNode mymaster = self.masterTk.get() if not master: master = mymaster master = 'ParamPanel' if not master or master == '': if port.widget: mmaster = port.node.widgetDescr[port.name].get('master', None) if mmaster: master = mmaster masterList = ['ParamPanel', 'Node'] #macronet = 0 # add all macro nodes in a 'macro tree' net = port.node.network while isinstance(net, MacroNetwork): macronode = net.macroNode masterList.append(macronode.name) net = macronode.network #macronet = 1 #if not macronet and master == 'Macro': # master = 'ParamPanel' self.masterTk.setlist(masterList) if master: self.masterTk.selectitem(master) class InputPortWidgetEditor(ObjectEditor): """Widget editor class. """ def __init__(self, widget, master=None): ObjectEditor.__init__(self, port, 'Input Port Widget', master) self.nameGroup.forget() self.createForm(master) def createForm(self, master): ObjectEditor.createForm(self, master) w = Pmw.Group(self.top, 'Widget Options') # datatype class CodeEditorPmw: """Reusable Pmw TextDialog widget to edit source code, fitted for the needs of the various code editors""" def __init__(self, master=None, title='untitled', code=None): if code is None: code = "" lines = code.split('\n') height = (len(lines)+10) * 10 if height > 300: height = 300 width = (max(map(len, lines)) + 1) * 10 if width > 600: width = 600 elif width < 120: width = 120 self.widget = Pmw.TextDialog( master, scrolledtext_labelpos='n', title=title, defaultbutton=None, buttons=(), label_text='Edit Code:', scrolledtext_usehullsize=1, scrolledtext_hull_width=width, scrolledtext_hull_height=height) self.top = self.widget self.settext(code) # add frame frame = Tkinter.Frame(self.widget.interior()) frame.pack(side='bottom', expand=1, fill='both') self.widget.status_bar = frame # add top self.widget.top = self.widget ed.top.protocol("WM_DELETE_WINDOW", self.cancelCmd) def settext(self, text): # settext replaces current text with new one, so no need for a clear() self.top.settext(text) def gettext(self, event=None): return self.top.getvalue() def clear(self): self.top.settext('') class CodeEditorIdle: """Idle code editor window, fitted to the needs for our various editors""" def __init__(self, master=None, title='untitled', code=None, font=None): from idlelib.EditorWindow import EditorWindow if code is None: code = "" lines = code.split('\n') height = len(lines)+5 if height > 24: height = 24 width = max(map(len, lines)) + 15 if width > 80: width = 80 elif width < 40: width = 40 ed = self.widget = EditorWindow(width=width, height=height) self.top = self.widget.top # delete menu entry Close and Exit for entry in [ 'Close','Exit', 'New Window', 'Save As...', 'Save Copy As...']: ed.menubar.children['file'].delete(entry) #ed.menubar.children['run'].delete(0,'end') ed.menubar.delete('Run') # rebind a bunch of methods ed.io.text.bind("<>",self.cancelCmd) ed.top.bind("<>",self.cancelCmd) # unbind a bunch of methods # FIXME: UNBIND SAVE SHORTCUTS: CTRL+SHIFT+S, ALT+SHIFT+S, F5, ALT+x ed.io.text.bind("<>", self.pass_cb) # overwrite top.protocol ed.top.protocol("WM_DELETE_WINDOW", self.cancelCmd) if font is not None: ed.io.text.configure(font=font) self.settext(code) # and set the title of this window ed.top.title(title) def pass_cb(self, event=None): pass def settext(self, text): self.clear() self.widget.io.text.insert("1.0", text) def gettext(self, event=None): return self.widget.io.text.get("1.0", 'end') def clear(self): self.widget.io.text.delete("1.0", "end") def cancelCmd(self, event=None): pass class CodeEditorIdle_python24(CodeEditorIdle): """ Idle code editor window, fitted to the needs for our various editors """ def __init__(self, master=None, title='untitled', code=None, font=None): from idlelib.EditorWindow import EditorWindow if code is None: code = "" lines = code.split('\n') height = len(lines) + 8 if height > 40: height = 40 width = max(map(len, lines)) if width > 80: width = 80 elif width < 40: width = 40 if master: ed = self.widget = EditorWindow(root=master) else: if hasattr(self, 'editor'): self.editor.top.master.inversedict = {} self.editor.top.master.vars = {} self.editor.top.master.close_all_callback = self.cancelCmd self.editor.top.master.root = self.editor.top.master.master ed = self.widget = EditorWindow(root=self.editor.top.master.master, flist=self.editor.top.master) else: self.port.editor.master.inversedict = {} self.port.editor.master.vars = {} self.port.editor.master.close_all_callback = self.cancelCmd ed = self.widget = EditorWindow(root=self.port.editor.master.master, flist=self.port.editor.master) width *= int(ed.top.tk.call("font", "measure", ed.text["font"], "0")) + 2 height *= int(ed.top.tk.call("font", "metrics", ed.text["font"], "-linespace")) ed.top.geometry("%dx%d+0+0" % (width, height)) self.top = self.widget.top # delete menu entry Close and Exit for entry in [ 'Close','Exit', 'New Window', 'Save As...', 'Save Copy As...']: ed.menubar.children['file'].delete(entry) ed.menubar.delete("Options") ed.menubar.delete('Run') # rebind a bunch of methods ed.io.text.bind("<>",self.cancelCmd) ed.top.bind("<>",self.cancelCmd) # unbind a bunch of methods # FIXME: UNBIND SAVE SHORTCUTS: CTRL+SHIFT+S, ALT+SHIFT+S, F5, ALT+x ed.io.text.bind("<>", self.pass_cb) # overwrite top.protocol ed.top.protocol("WM_DELETE_WINDOW", self.cancelCmd) # if font is not None: # ed.io.text.configure(font=font) self.settext(code) # and set the title of this window ed.top.title(title) ### Create CodeEditor class, depending on whether idlelib is available or not try: from idlelib.EditorWindow import EditorWindow # it gets a bit more complicated now. Python may be shipped with idlelib # which is an older version and does not fullfill our needs. Need to test # if this is such an old version. Here, the EditorWindow constructor has # no keyword width and height. We use inspect... from inspect import getargspec args = getargspec(EditorWindow.__init__)[0] assert "width" in args, \ "Older version of idlelib detected! Using Pmw code editor instead." assert "height" in args, \ "Older version of idlelib detected! Using Pmw code editor instead." class CodeEditor(CodeEditorIdle): def __init__(self, master=None, title='untitled', code=None,font=None): CodeEditorIdle.__init__(self, master, title, code, font) except: try: class CodeEditor(CodeEditorIdle_python24): def __init__(self, master=None, title='untitled', code=None,font=None): CodeEditorIdle_python24.__init__(self, master, title, code, font) except: import traceback traceback.print_exc() class CodeEditor(CodeEditorPmw): def __init__(self, master=None, title='untitled', code=None,font=None): CodeEditorPmw.__init__(self, master, title, code) class SourceCodeEditor(CodeEditor): """base class for sourc e code editor windows""" def __init__(self, master=None, title='untitled', node=None, editor=None): self.master = master self.node = node # Network node self.editor = editor # node editor self.top = None # Toplevel window holding editor code = self.node.sourceCode[:-1] if node is not None: title = "Code editor: Node %s"%node.name CodeEditor.__init__(self, master, title, code,font=node.editor.font['Root']) ed = self.widget # add OK and APPLY Button if code if modifiable if node and not node.readOnly: b=Tkinter.Button(master=ed.status_bar, text='Ok', command=self.okCmd) b.pack(side='left') b=Tkinter.Button(master=ed.status_bar, text='Apply', command=self.applyCmd) b.pack(side='left') # cancel is always accesible b=Tkinter.Button(master=ed.status_bar, text='Cancel', command=self.cancelCmd) b.pack(side='left') self.settext(code) ed.top.protocol("WM_DELETE_WINDOW", self.cancelCmd) def okCmd(self, event=None): self.applyCmd() # FIXME: save number of lines and number of chars!! Else if window is # resized and dismissed, size is lost self.cancelCmd() def applyCmd(self, event=None): code = self.gettext() self.node.configure(function=code) self.node.addSaveNodeMenuEntries() def cancelCmd(self, event=None): ed = self.editor ed.nbEditorWindows -= 1 self.node.objEditor.manageEditorButtons() self.node.objEditor.editButtonVarTk.set(0) self.clear() self.settext(self.node.sourceCode) self.hide() def show(self, event=None): # reset title self.top.title("Code editor: Node %s"%self.node.name) # reset source code self.settext(self.node.sourceCode) self.top.deiconify() def hide(self, event=None): self.top.withdraw() class CallbackEditor(CodeEditor): """Edit source code of port callbacks such as 'beforeConnect' or 'afterDisconnect'.""" def __init__(self, master=None, title='untitled', port=None, mode=None): if mode not in port.callbacks.keys(): return self.master = master self.port = port # node's port self.mode = mode # can be 'beforeConnect', 'afterConnect', etc code = port.callbacks[mode][1] if code is None: code = self.getDefaultCode() CodeEditor.__init__(self, master, title, code) ed = self.widget # add OK and APPLY Button b=Tkinter.Button(master=ed.status_bar, text='Ok', command=self.okCmd) b.pack(side='left') b=Tkinter.Button(master=ed.status_bar, text='Apply', command=self.applyCmd) b.pack(side='left') # cancel is always accesible b=Tkinter.Button(master=ed.status_bar, text='Cancel', command=self.cancelCmd) b.pack(side='left') ed.top.protocol( "WM_DELETE_WINDOW", self.cancelCmd) def getDefaultCode(self): mode = self.mode if mode in ['beforeConnect', 'afterDisconnect']: code = """def myFunc(self, p1, p2): pass """ elif mode in ['afterConnect', 'beforeDisconnect']: code = """def myFunc(self, c): pass """ else: code = "" return code def okCmd(self, event=None): self.applyCmd() # FIXME: save number of lines and number of chars!! Else if window is # resized and dismissed, size is lost self.cancelCmd() def applyCmd(self, event=None): code = self.gettext() apply(self.port.configure, (), {self.mode:code} ) def cancelCmd(self, event=None): self.clear() code = self.port.callbacks[self.mode][1] if code is not None: self.settext(code) self.hide() def show(self, event=None): code = self.port.callbacks[self.mode][1] if code is None: code = self.getDefaultCode() self.settext(code) self.top.deiconify() def hide(self, event=None): self.top.withdraw() mgltools-networkeditor-1.5.7~rc1~cvs.20130519/NetworkEditor/net.py0000644000175000017500000041210112115244135024066 0ustar debiandebian######################################################################### # # Date: Nov. 2001 Author: Michel Sanner, Daniel Stoffler # # sanner@scripps.edu # stoffler@scripps.edu # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Michel Sanner, Daniel Stoffler and TSRI # # revision: Guillaume Vareille # ######################################################################### import sys import os import types, math, string, threading, weakref import Tkinter, Pmw import traceback, time from time import time, sleep import re from tkSimpleDialog import askstring from mglutil.gui import widgetsOnBackWindowsCanGrabFocus from mglutil.util.callback import CallbackManager, CallBackFunction from mglutil.gui.Misc.Tk.KeybdModMonitor import KeyboardModifierMonitor from NetworkEditor.itemBase import NetworkItemsBase from NetworkEditor.items import NetworkNode, NetworkNodeBase, NetworkConnection, ImageNode from NetworkEditor.ports import InputPort, OutputPort, \ RunChildrenInputPort, RunNodeInputPort from NetworkEditor.flow import ExecutionThread, AfterExecution from NetworkEditor.itemBase import UserPanel from Tkinter import * from Glyph import Glyph from mglutil.events import Event class AddNodeEvent(Event): def __init__(self, network, node, x, y): """ """ self.timestamp = time() self.network = network self.node = node self.position = (x,y) class ConnectNodes(Event): def __init__(self, network, connection): """ """ self.timestamp = time() self.network = network self.connection = connection class DeleteNodesEvent(Event): def __init__(self, network, nodes): """ """ self.timestamp = time() self.network = network self.nodes = nodes class DeleteConnectionsEvent(Event): def __init__(self, network, connections): """ """ self.timestamp = time() self.network = network self.connection = connections class reference_value: def __init__(self, value): self.value = value import socket from select import select class Communicator: """This class provides support for socket-based communication with a network that is running without a GUI """ def __init__(self, network, host='', portBase=50001, listen=5): self.network = network self.host = host self.socket = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) i = 0 while 1: try: s.bind((self.host, portBase+i)) break except Exception,e: print e i += 1 if i==10: raise RuntimeError, "Communicator unable to bind" #if e.args[0]==98: # Address already in use #elif e.args[0]==22: # socket already bound s.listen(listen) s.setblocking(0) self.port = portBase+i self.connId = s.fileno() self.clientConnections = [] self.clientInIds = [] def clientConnect(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) i = 0 from time import sleep while 1: err = s.connect_ex((self.host, self.port)) if err == 111: sleep(0.2) i +=1 if i == 10: print "Communicator: failed to connect" return None continue break return s def handleCommunications(self): #print "handleCommunications" net = self.network ifd, ofd, efd = select([self.socket.fileno()], [], [], 0) if self.connId in ifd: conn, addr = self.socket.accept() self.clientConnections.append( (conn, addr) ) self.clientInIds.append( conn.fileno() ) print 'Connected by', addr if len(self.clientInIds)==0: return ifd, ofd, efd = select(self.clientInIds, [], [], 0) for conn in self.clientConnections: if conn[0].fileno() in ifd: input = conn[0].recv(4096) #os.read(_pid, 16384) if len(input)==0: # client diconnected self.clientInIds.remove(conn[0].fileno()) self.clientConnections.remove(conn) else: procid = os.getpid() print "process %d input"%procid, repr(input) #mod = __import__('__main__') #mod.__dict__['clientSocket'] = conn[0] #exec(input, mod.__dict__) from mglutil.util.misc import importMainOrIPythonMain lMainDict = importMainOrIPythonMain() lMainDict['clientSocket'] = conn[0] exec(input, lMainDict) class Network(KeyboardModifierMonitor, NetworkItemsBase): """class to hold all the information about a bunch of nodes and connections """ _nodesID = 0 # used to assign a node a unique number # accross all the networks. This is incremented # in the addNodes() method def connectToProcess(self, host, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) i = 0 from time import sleep while 1: err = s.connect_ex((host, port)) if err == 111: sleep(0.2) i +=1 if i == 10: print "Communicator: failed to connect" return None continue break return s def generateScript(self): """Create a Python script that corresponds to this network """ count = 0 src = "" ndict = {} # dump all doit methods for node in self.nodes: ndict[node] = [] funcsrc = node.sourceCode.replace('def doit(', node.name+str(count)+'doit(') src += fncscr+'\n' def __init__(self, name='Noname', origin='user'): KeyboardModifierMonitor.__init__(self) NetworkItemsBase.__init__(self, name) self.origin = origin # HACK MS, replace by event self.afterRunCb = None self.canvasFrame=None self.filename = None # file from which the network was loaded if # it is loaded from a file (includes path) self.origName = name # used to delete the Pmw.NoteBook page # correctly after network was renamed self.selectedNodes = [] self.selectedConnections = [] self.firstItemId = None self.canvas = None self.debug = False self.defaultConnectionMode = 'angles' self.defaultConnectionOptions = {} self.needsResetNodeCache = 1 self.hyperScaleRad = 200 # radius is pixel in which nodes scale to 1.0 self.scalingHyper = 0 self.selectionBox = None # canvas object used to draw selectionbox self.glyphSelect = None self.gselectionboxes = [] self.sbrev =0 self.circle = None self.rectangle = None self.currentcircle = None self.currentrectangle = None self.glyphselectionBox = None self.eventHandler = {} # stores callback managers for network events self.createCallbackManager('preSelection') doc = """Gets called every time the selection changes. No arguments.""" self.createCallbackManager('onSelectionChange',doc) self.createCallbackManager('loadLibrary') doc = """Gets called every time we save a network, to add source code at the very beginning of a saved network""" self.createCallbackManager('saveNetworkBegin', doc) doc = """Gets called every time we save a network, to add source code at the very end of a saved network""" self.createCallbackManager('saveNetworkEnd', doc) self.nodes = [] # list of nodes in network self.nodesById = {} # dictionary or nodes # (key: unique tag of the NetworkItem, # value: NetworkItem instance) self.rootNodes = [] # list of nodes with no parents, These are # used to run the network when a # node is added it appended tothat list # when a connection is created it is removed # each node has an isRootNode flag # these are used for turning picking event into netwokr objects self.inPortsId = {} # dictionary or ports # (key: unique id of the port, # value: (node, portnumber)) self.outPortsId = {} # dictionary or ports # (key: unique id of the port, # value: (node, portnumber)) self.connections = [] self.connById = {} # dictionary of connections between nodes # (key: unique tag of the NetworkItem, # value: NetworkItem instance) self.forceExecution = 0 # set to one when a network is forced to # execute. This will force the execution of # all root nodes in subnetworks as well as # pretent all data flwoing into subnetworks is # new self.mouseActionEvents = [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] # table of functions to call for specific mouse events on canvas self.mouseButtonFlag = 0 self.mouseAction = {} for event in self.mouseActionEvents: self.mouseAction[event] = None self.mouseAction[''] = self.startDrawingSelectionBox self.mouseAction[''] = self.moveCanvasOrSelectedNodes #self.moveSelectedNodesStart self.mouseAction[''] = self.postCanvasMenu self.mouseAction[''] = self.moveCanvasStart self.mouseAction[''] = self.scaleNetworkStart # global lock for running a node in this network # used to make sure only 1 node runs at a time self.RunNodeLock = threading.RLock() # global lock for running nodes in this network # used to avoid multiple runs to get started simultaneously self.RunLock = threading.RLock() self.RunLockCond = threading.Condition(self.RunLock) # lock for modifying execution abortion variable self.execStatusLock = threading.RLock() self.execStatusCond = threading.Condition(self.execStatusLock) self.execStatus = 'waiting' # waiting: ready to run new set of nodes # pending: set by network.run metho # when a new thread is created # running: nodes are currently running # stop: execution will stop ASAP # pause: execution will pause ASAP # frozen: frozen self.runAgain = False # set to False in runNodes, set to True # if runNodes is called during a run self.runOnNewData = reference_value(True) # set to false to avoid execution of the # node and its children node # upon connection or new data self.lastExecutedNodes = [] # this list is filled by runNodes() each # time this method runs a 3-tuple # (node, starttime, endtime) is added for # each node BEFORE it executes # lock used by iterate node self.iterateLock = threading.RLock() self.iterateCond = threading.Condition(self.iterateLock) self.postedMenu = None self.lastPosY = 10 # used in self.addNodes to auto place nodes self.userPanels = {} # dictionary of panel created by the user # to place widgets for an application # the key is the panel name # the value is the Tkinter Frame self.nodeToRunList = [] def onStoppingExecution(self): self.canvas.update_idletasks() for node in self.nodes: node.onStoppingExecution() def getTypeManager(self): ed = self.getEditor() if ed is not None: return ed.typeManager else: return self.typeManager def getNodeByName(self, name): """nodeList <- getNodeByName(self, name) Return all the nodes who's name match the name regular expression """ nodes = [] import re reg = re.compile(name) nodes = filter( lambda x,reg=reg: reg.match(x.name), self.nodes ) return nodes def nodeIdToNumber(self, id): """return the current index of this node in network.nodes if the node's _id is given.""" for i in range(len(self.nodes)): if self.nodes[i]._id == id: break return i def getNodesByIds(self, ids): """returns a list of nodes based on a list of ids""" found = [] for node in self.nodes: if node._id in ids: found.append(node) return found def freeze(self): self.runOnNewData.value = False def unfreeze(self): self.runOnNewData.value = True def createCallbackManager(self, event, doc=None): assert not self.eventHandler.has_key(event) self.eventHandler[event] = CallbackManager() self.eventHandler[event].doc = doc def addCallbackManager(self, event, func): assert self.eventHandler.has_key(event) assert callable(func) self.eventHandler[event].AddCallback(func) def describeCallbackManager(self, event): assert self.eventHandler.has_key(event) return self.eventHandler[event].doc def postCanvasMenu(self, event): ed = self.getEditor() if self.postedMenu: self.postedMenu.unpost() self.postedMenu = None elif ed.menuButtons['Networks'].menu: ed.menuButtons['Networks'].menu.post(event.x_root, event.y_root) self.postedMenu = ed.menuButtons['Networks'].menu def delete(self, saveValues=0): # this method destroys the network # this method is called from editor.deleteNetwork(), use that method # to delete a network, which is doing more stuff that is necessary! lRunOnNewDataValue = self.runOnNewData.value self.runOnNewData.value = False nodes = self.nodes[:] for n in nodes: self.deleteNodes([n]) n.deleteIcon() for c in self.connections: c.deleteIcon() if self.canvas: self.destroyIcons(saveValues=saveValues) for p in self.userPanels.values(): p.master.destroy() self.runOnNewData.value = lRunOnNewDataValue def destroyIcons(self, saveValues=0): # destroy the icons of node, ports, etc, but not the objects itself if not self.canvas: return for n in self.nodes: if n.objEditor: n.objEditor.Dismiss() if n.isExpanded(): n.toggleNodeExpand_cb() for p in n.inputPorts: # get rid of all widgets, but save parameters. These will be # used in node.createWidgets to reconstruct widgets if saveValues and p.widget: wcfg = p.widget.getDescr() wcfg['initialValue'] = p.widget.get() p.node.widgetDescr[p.name] = wcfg p.widget = None p.destroyIcon() for p in n.outputPorts: p.destroyIcon() for c in p.connections: c.destroyIcon() for p in n.specialInputPorts: for c in p.connections: c.destroyIcon() p.destroyIcon() for p in n.specialOutputPorts: for c in p.connections: c.destroyIcon() p.destroyIcon() n.menu.destroy() n.menu = None n.nodeWidgetsID = [] # ids of widgets in node n.iconTag = None n.iconMaster = None n.id = None # setting id to None will rebuild this icon # in self.buildIcons self.destroyCanvas() def destroyCanvas(self): # delete the Pmw.Notebook page and everything in it ed = self.getEditor() ed.networkArea.delete(self.origName) self.canvas.destroy() self.canvasFrame=None self.canvas = None self.firstItemId = None self.nodesById = {} self.connById = {} self.inPortsId = {} self.outPortsId = {} self.naviMap.unregisterCallBacks() def buildIcons(self): ed = self.getEditor() if self.canvas is None: self.createCanvas() lCanvasWasMissing = True else: lCanvasWasMissing = False for n in self.nodes: # Nodes belonging to a MacroNetwork are instanciated here if n.id is None: n.buildIcons(self.canvas, n.posx, n.posy) self.nodesById[n.id] = n # FIXME this is a hack ... when macro network is re-displayed # the widget appear bu the node is not scaled # unless inNode widget is visible by default hide it #widgetsInNode = n.getWidgetsForMaster('Node') #for w in widgetsInNode.values(): # if not w.visibleInNodeByDefault: # n.hideInNodeWidgets( w, rescale=0 ) for c in self.connections: if not c.id: c.buildIcons(self.canvas) self.connById[c.id] = c if c.id2 is not None: self.connById[c.id2] = c if lCanvasWasMissing is True: #FIXME this line was added because else nodes with widgets # in macros are 'flat' whenthe macro is expanded ed.setNetwork(self) self.bindCallbacks() def rename(self, name): ed = self.getEditor() if ed is not None: if self.name in ed.networks.keys(): del ed.networks[self.name] ed.networks[name] = self if self.canvas: ed.renameNetworkTab(self, name) self.name = name self._setModified(True) def enter(self, event=None): if widgetsOnBackWindowsCanGrabFocus is False: lActiveWindow = self.canvas.focus_get() if lActiveWindow is not None \ and ( lActiveWindow.winfo_toplevel() != self.canvas.winfo_toplevel() ): return self.canvas.focus_set() def configure(self, event=None): self.visibleWidth = event.width self.visibleHeight = event.height def bindCallbacks(self): # bind mouse button callbacks # can only be done after the network has be added to an editor # and the draw canas has been created ed = self.getEditor() if self.canvas is None: print 'too early to bind' return self.canvas.bind("", self.mouse1Down) self.canvas.bind("", self.mouse1Down) self.canvas.bind("", self.mouse1Down) self.canvas.bind("", self.mouse2Down) self.canvas.bind("", self.mouse2Down) self.canvas.bind("", self.mouse2Down) self.canvas.bind("", self.mouse1Up) self.canvas.bind("", self.mouse1Up) self.canvas.bind("", self.mouse1Up) # set the focus so that we get keyboard events, and add callbacks self.master = self.canvas.master # hack ot get this to work self.canvas.bind('', self.modifierDown) self.canvas.bind("", self.modifierUp) # bind accelerator keys such as Ctrl-a for selectAll, etc self.canvas.bind('', ed.selectAll_cb) self.canvas.bind('', ed.copyNetwork_cb) #self.canvas.bind('', ed.toggleFreezeNetwork_cb) self.canvas.bind('', ed.toggleRunOnNewData_cb) self.canvas.bind('', ed.createMacro_cb) self.canvas.bind('', ed.newNet_cb) self.canvas.bind('', ed.loadNetwork_cb) self.canvas.bind('', ed.interactiveExit_cb) self.canvas.bind('', ed.print_cb) self.canvas.bind('', ed.softrunCurrentNet_cb) self.canvas.bind('', ed.runCurrentNet_cb) self.canvas.bind('', ed.saveNetwork_cb) self.canvas.bind('', ed.pasteNetwork_cb) self.canvas.bind('', ed.closeNetwork_cb) self.canvas.bind('', ed.cutNetwork_cb) self.canvas.bind('', ed.undo) self.canvas.bind('', self.toggleShowMap) self.showNaviMap = True # bind arrow keys to move nodes with arrows func = CallBackFunction(self.arrowKeys_cb, 0, -1) self.canvas.bind('', func) func = CallBackFunction(self.arrowKeys_cb, 0, 1) self.canvas.bind('', func) func = CallBackFunction(self.arrowKeys_cb, -1, 0) self.canvas.bind('', func) func = CallBackFunction(self.arrowKeys_cb, 1, 0) self.canvas.bind('', func) # same, with SHIFT: 10x bigger move func = CallBackFunction(self.arrowKeys_cb, 0, -10) self.canvas.bind('', func) func = CallBackFunction(self.arrowKeys_cb, 0, 10) self.canvas.bind('', func) func = CallBackFunction(self.arrowKeys_cb, -10, 0) self.canvas.bind('', func) func = CallBackFunction(self.arrowKeys_cb, 10, 0) self.canvas.bind('', func) def toggleShowMap(self, event=None): if self.showNaviMap: # hide the map self.showNaviMap = False self.naviMap.mapCanvas.move('navimap', -10000, -10000) else: # show the map self.showNaviMap = True self.naviMap.mapCanvas.move('navimap', 10000, 10000) def createCanvas(self): """create the Canvas and Title widgets""" ed = self.getEditor() master = self.canvasFrame = ed.networkArea.add(self.name) self.origName = self.name self.scrollregion=[0 , 0, ed.totalWidth, ed.totalHeight] self.scrolledCanvas = Pmw.ScrolledCanvas( master, borderframe=1, #labelpos='n', label_text='main', usehullsize=0, hull_width=ed.visibleWidth, hull_height=ed.visibleHeight, vscrollmode='static', hscrollmode='static') self.canvas = self.scrolledCanvas.component('canvas') self.canvas.configure(background='grey75', width=ed.visibleWidth, height=ed.visibleHeight, scrollregion=tuple(self.scrollregion) ) self.firstItemId = self.canvas.create_line(0,0,0,0) from NetworkEditor.naviMap import NavigationMap self.naviMap = NavigationMap(self, self.scrolledCanvas, self.canvas, 0.05) ## import NetworkEditor, os ## file = os.path.join(NetworkEditor.__path__[0], "back1.gif") ## self.bg = Tkinter.PhotoImage(file=file) ## self.bgId = self.canvas.create_image(0, 0, anchor=Tkinter.NW, ## image=self.bg) # bind standard callbacks self.canvas.bind('', self.configure) self.canvas.bind("", ed.undo) self.canvas.bind("", ed.undo) # pack 'em up self.scrolledCanvas.pack(side=Tkinter.LEFT, expand=1,fill=Tkinter.BOTH) self.canvas.bind("", self.enter) # # MISC # def setSplineConnections(self, yesno): if yesno is True: self.defaultConnectionOptions['smooth'] = 1 for c in self.connections: apply( c.iconMaster.itemconfigure, (c.iconTag,),{'smooth':1}) else: self.defaultConnectionOptions['smooth'] = 0 for c in self.connections: apply( c.iconMaster.itemconfigure, (c.iconTag,),{'smooth':0}) # # save/restore source code generation # def saveToFile(self, filename, copyright=True): lines = [] #if len(self.currentNetwork.userPanels) > 0: #lines += ['#!%s\n'%sys.executable] #lines += ['#!/bin/ksh '+self.resourceFolder+'/pythonsh\n'] lines += ['#!/bin/ksh ~/.mgltools/pythonsh\n'] lines += self.getNetworkCreationSourceCode(copyright=copyright) f = open(filename, 'w') map( f.write, lines ) f.close() def getNetworkCreationSourceCode(self, networkName='masterNet', selectedOnly=0, indent="", withRun=True, ignoreOriginal=False, copyright=False, importOnly=False): """returns code to re-create a network containing nodes and connections selectedOnly: True/False. If set to true, we handle selected nodes only indent: a string with whitespaces for code indentation ignoreOriginal: True/False. Default:False. If set to True, we ignore the _original attribute of nodes (for example, nodes in a macro network that came from a node library where nodes are marked original copyright: if True copyright and network execution code is generated. This is not needed when we copy/paster for instance. )""" ed = self.getEditor() ed._tmpListOfSavedNodes = {} # clear this list # if selectedOnly is TRUE the sub-network of selected nodes and # connections between these nodes are saved lines = [] if copyright is True: import datetime lNow = datetime.datetime.now().strftime("%A %d %B %Y %H:%M:%S") lCopyright = \ """######################################################################## # # Vision Network - Python source code - file generated by vision # %s # # The Scripps Research Institute (TSRI) # Molecular Graphics Lab # La Jolla, CA 92037, USA # # Copyright: Daniel Stoffler, Michel Sanner and TSRI # # revision: Guillaume Vareille # ######################################################################### # # $%s$ # # $%s$ # """%(lNow, "Header:", "Id:") lines.append(lCopyright) lines.append(indent + """ if __name__=='__main__': from sys import argv if '--help' in argv or '-h' in argv or '-w' in argv: # run without Vision withoutVision = True from Vision.VPE import NoGuiExec ed = NoGuiExec() from NetworkEditor.net import Network import os masterNet = Network("process-"+str(os.getpid())) ed.addNetwork(masterNet) else: # run as a stand alone application while vision is hidden withoutVision = False from Vision import launchVisionToRunNetworkAsApplication, mainLoopVisionToRunNetworkAsApplication if '-noSplash' in argv: splash = False else: splash = True masterNet = launchVisionToRunNetworkAsApplication(splash=splash) import os masterNet.filename = os.path.abspath(__file__) """ ) lines.append(indent+'from traceback import print_exc\n') # lines.append(indent+"#### Network: "+self.name+" ####\n") # lines.append(indent+"#### File written by Vision ####\n\n") ## save code to create user panels for name, value in self.userPanels.items(): #print "name, value", name, value, dir(value.frame) lines.append(indent+ "masterNet.createUserPanel('%s' ,width=%d, height=%d)\n" %(name, value.frame.winfo_width(), value.frame.winfo_height() ) ) ## add stuff that users might want to add at very beginning of network callbacks = self.eventHandler['saveNetworkBegin'] txt = callbacks.CallCallbacks(indent=indent) for t in txt: lines.extend(t) ## get library import cache (called recursively) ## then write libray import code cache = {'files':[]} cache = self.buildLibraryImportCache(cache, self, selectedOnly) li = self.getLibraryImportCode(cache, indent, editor="%s.getEditor()"%networkName, importOnly=importOnly, loadHost=True) lines.extend(li) ## add node creation code li = self.getNodesCreationSourceCode(networkName, selectedOnly, indent, ignoreOriginal) lines.extend(li) ## add code to run individualy each node before the connections lines.append(indent+'#%s.run()\n'%networkName) ## add code to freeze network execution to avoid executing upon connection lines.append(indent+'%s.freeze()\n'%networkName) ## add connection creation code if len(self.connections): lines.append( '\n'+indent+"## saving connections for network "+\ "%s ##\n"%self.name) for i,conn in enumerate(self.connections): lines.extend(conn.getSourceCode( networkName, selectedOnly, indent, ignoreOriginal, connName='conn%d'%i)) ## add code to set correctly runOnNewData lines.append(indent+'%s.runOnNewData.value = %s\n'%(networkName,self.runOnNewData.value)) ## allow to add code after connections were formed (connections ## might generate new ports, for example -> see MacroOutputNode). for node in self.nodes: if selectedOnly: if not node.selected: continue lines.extend(node.getAfterConnectionsSourceCode( networkName, indent, ignoreOriginal) ) ## add stuff that users might want to add at very end of network callbacks = self.eventHandler['saveNetworkEnd'] txt = callbacks.CallCallbacks(indent=indent) for t in txt: lines.extend(t) for lNodeName, lNode in ed._tmpListOfSavedNodes.items(): #print "_tmpListOfSavedNodes", lNodeName, lNode if hasattr(lNode, 'vi'): lines.extend('\n\ndef loadSavedStates_%s(self=%s, event=None):\n'%(lNodeName, lNodeName) ) txt = lNode.vi.getObjectsStateDefinitionCode( viewerName='self.vi', indent=' ', withMode=False, includeBinding=False) lines.extend(txt) txt = lNode.vi.getViewerStateDefinitionCode( viewerName='self.vi', indent=' ', withMode=False, rootxy=False) lines.extend(txt) if lNode.__module__ == 'DejaVu.VisionInterface.DejaVuNodes': lines.extend('%s.restoreStates_cb = %s.restoreStatesFirstRun = loadSavedStates_%s\n'%(lNodeName, lNodeName, lNodeName) ) else: # 'Pmv.VisionInterface.PmvNodes': lines.extend('%s.restoreStates_cb = %s.restoreStatesFirstRun = loadSavedStates_%s\n'%(lNodeName, lNodeName, lNodeName) ) #lines.extend('%s.restoreStates_cb = loadSavedStates_%s\n'%(lNodeName, lNodeName) ) lines.extend('%s.menu.add_separator()\n'%lNodeName ) lines.extend("%s.menu.add_command(label='Restore states', command=%s.restoreStates_cb)\n"%(lNodeName, lNodeName) ) # finally, clear the list ed._tmpListOfSavedNodes = {} if copyright is True: lines += """ if __name__=='__main__': from sys import argv lNodePortValues = [] if (len(argv) > 1) and argv[1].startswith('-'): lArgIndex = 2 else: lArgIndex = 1 while lArgIndex < len(argv) and argv[lArgIndex][-3:]!='.py': lNodePortValues.append(argv[lArgIndex]) lArgIndex += 1 masterNet.setNodePortValues(lNodePortValues) if '--help' in argv or '-h' in argv: # show help masterNet.helpForNetworkAsApplication() elif '-w' in argv: # run without Vision and exit # create communicator from NetworkEditor.net import Communicator masterNet.communicator = Communicator(masterNet) print 'Communicator listening on port:', masterNet.communicator.port import socket f = open(argv[0]+'.sock', 'w') f.write("%s %i"%(socket.gethostbyname(socket.gethostname()), masterNet.communicator.port)) f.close() masterNet.run() else: # stand alone application while vision is hidden if '-e' in argv: # run and exit masterNet.run() elif '-r' in argv or len(masterNet.userPanels) == 0: # no user panel => run masterNet.run() mainLoopVisionToRunNetworkAsApplication(masterNet.editor) else: # user panel mainLoopVisionToRunNetworkAsApplication(masterNet.editor) """ return lines ## ## functions used to run networks as programs ## def helpForNetworkAsApplication(self): help_msg = """Run the network without displaying the Vision network editor. (If there is a User panel the network will just load and not run) usage: %s