changeset 152:e6ca7c1f0b1e

loads and saves as xml now loads and saves as xml now draggable field centers and radii
author drewp
date Mon, 07 Jul 2003 06:25:20 +0000
parents 990a9474d0e7
children ed277a009f57
files flax/tracker
diffstat 1 files changed, 161 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/flax/tracker	Sun Jul 06 16:33:06 2003 +0000
+++ b/flax/tracker	Mon Jul 07 06:25:20 2003 +0000
@@ -1,80 +1,79 @@
 #!/usr/bin/python
 
-from __future__ import division
+from __future__ import division,nested_scopes
 from skim.zooming import Zooming,Pair
 from math import sqrt,sin,cos
 from pygame.rect import Rect
+from xmlnodebase import xmlnodeclass,collectiveelement,xmldocfile
+import dispatcher
 
 import Tkinter as tk
-class Field:
+
+def pairdist(pair1,pair2):
+    return sqrt((pair1[0]-pair2[0])**2+(pair1[1]-pair2[1])**2)
+
+def canvashighlighter(canvas,obj,attribute,normalval,highlightval):
+    """creates bindings on a canvas obj that make attribute go
+    from normal to highlight when the mouse is over the obj"""
+    canvas.tag_bind(obj,"<Enter>",
+                    lambda ev: canvas.itemconfig(obj,**{attribute:highlightval}))
+    canvas.tag_bind(obj,"<Leave>",
+                    lambda ev: canvas.itemconfig(obj,**{attribute:normalval}))
+
+class Field(xmlnodeclass):
     
     """one light has a field of influence. for any point on the
     canvas, you can ask this field how strong it is. """
 
-    center=Pair(0,0)
-    falloff=0
+    def name(self,newval=None):
+        """light/sub name"""
+        return self._getorsetattr("name",newval)
+    def center(self,x=None,y=None):
+        """x,y float coords for the center of this light in the field. returns
+        a Pair, although it accepts x,y"""
+        return Pair(self._getorsettypedattr("x",float,x),
+                    self._getorsettypedattr("y",float,y))
 
-    def setcenter(self,x,y):
-        self.center=Pair(x,y)
-
-    def setfalloff(self,dist):
+    def falloff(self,dist=None):
         
         """linear falloff from 1 at center, to 0 at dist pixels away
         from center"""
-        self.falloff=dist
+        return self._getorsettypedattr("falloff",float,dist)
 
     def getdistforintensity(self,intens):
         """returns the distance you'd have to be for the given intensity (0..1)"""
-        return (1-intens)*self.falloff
+        return (1-intens)*self.falloff()
 
     def calc(self,x,y):
         """returns field strength at point x,y"""
-        dist=sqrt( (x-self.center.x)**2 + (y-self.center.y)**2 )
-        return max(0,(self.falloff-dist)/self.falloff)
+        dist=pairdist(Pair(x,y),self.center())
+        return max(0,(self.falloff()-dist)/self.falloff())
 
-class FieldSet:
+class Fieldset(collectiveelement):
     """group of fields. persistent."""
-    fields=None # lightname : Field
-    def __init__(self):
-        self.fields={}
-    def loadfromfile(self,f):
-        for line in f:
-            try:
-                col=line.index(':')
-                newfield=Field()
-                name=line[:col]
-                params={}
-                for param in line[col+1:].split():
-                    eq=param.index('=')
-                    params[param[:eq]]=float(param[eq+1:])
-                newfield.setcenter(params['x'],params['y'])
-                newfield.setfalloff(params['falloff'])
-                self.addfield(name,newfield)
-                newfield.name=name # fields have names?
-            except ValueError:
-                print "error on line: %s" % line
-    def savetofile(self,f):
-        for name,field in self.fields.items():
-            print >>f,"%s: x=%s y=%s falloff=%s" % (name,
-                                                    field.center.x,field.center.y,
-                                                    field.falloff)
-    def addfield(self,name,f):
-        """consider the given field in this set with the given name"""
-        assert isinstance(f,Field)
-        self.fields[name]=f
+    def childtype(self): return Field
+
+    def version(self):
+        """read-only version attribute on fieldset tag"""
+        return self._getorsetattr("version",None)
+
     def report(self,x,y):
         """reports active fields and their intensities"""
-        for name,f in self.fields.items():
+        active=0
+        for f in self.getall():
+            name=f.name()
             intens=f.calc(x,y)
             if intens>0:
                 print name,intens,
-        print
+                active+=1
+        if active>0:
+            print
     def getbounds(self):
         """returns xmin,xmax,ymin,ymax for the non-zero areas of this field"""
         r=None
-        for f in self.fields.values():
+        for f in self.getall():
             rad=f.getdistforintensity(0)
-            fx,fy=f.center.x,f.center.y
+            fx,fy=f.center()
             fieldrect=Rect(fx-rad,fy-rad,rad*2,rad*2)
             if r is None:
                 r=fieldrect
@@ -82,70 +81,141 @@
                 r=r.union(fieldrect)
         return r.left,r.right,r.top,r.bottom
 
+class Fieldsetfile(xmldocfile):
+    def __init__(self,filename):
+        self._openornew(filename,topleveltype=Fieldset)
+    def fieldset(self):
+        return self._gettoplevel()
+
+########################################################################
+########################################################################
+
 class FieldDisplay:
     """the view for a Field."""
     def __init__(self,canvas,field):
         self.canvas=canvas
         self.field=field
         self.tags=[str(id(self))] # canvas tag to id our objects
-    def draw(self):
+        
+    def setcoords(self):
+        """adjust canvas obj coords to match the field"""
+        # this uses the canvas object ids saved by makeobjs
+        f=self.field
+        c=self.canvas
+        w2c=self.canvas.world2canvas
+
+        # rings
+        for intens,ring in self.rings.items():
+            rad=f.getdistforintensity(intens)
+            p1=w2c(*(f.center()-Pair(rad,rad)))
+            p2=w2c(*(f.center()+Pair(rad,rad)))
+            c.coords(ring,p1[0],p1[1],p2[0],p2[1])
+
+        # text
+        p1=w2c(*f.center())
+        c.coords(self.txt,*p1)
+
+    def makeobjs(self):
+        """(re)create the canvas objs (null coords) and make their bindings"""
         c=self.canvas
         f=self.field
         c.delete(self.tags)
 
         w2c=self.canvas.world2canvas
 
-        def oval(rad,color):
-            p1=w2c(*(f.center-Pair(rad,rad)))
-            p2=w2c(*(f.center+Pair(rad,rad)))
-            c.create_oval(p1[0],p1[1],p2[0],p2[1],
-                          outline=color,width=2,tags=self.tags,
-                          outlinestipple='gray50')
-
-        oval(.01,'white')
-        for intens,color in ((1,'white'),
+        # make rings
+        self.rings={} # rad,canvasobj
+        for intens,color in (#(1,'white'),
                              (.8,'gray90'),(.6,'gray80'),(.4,'gray60'),(.2,'gray50'),
                              (0,'#000080')):
-                             
-            oval(f.getdistforintensity(intens),color)
-        if hasattr(f,'name'):
-            p1=w2c(*f.center)
-            c.create_text(p1[0],p1[1],text=f.name,
-                          fill='white',anchor='c')
-        
+            self.rings[intens]=c.create_oval(0,0,0,0,
+                                          outline=color,width=2,tags=self.tags,
+                                          outlinestipple='gray50')
+
+        # make text
+        self.txt=c.create_text(0,0,text=f.name(),font="arial 10 bold",
+                               fill='white',anchor='c',
+                               tags=self.tags)
+
+        # highlight text bindings
+        canvashighlighter(c,self.txt,'fill',normalval='white',highlightval='red')
+
+        # position drag bindings
+        def press(ev):
+            self._lastmouse=ev.x,ev.y
+        def motion(ev):
+            dcan=Pair(*[a-b for a,b in zip((ev.x,ev.y),self._lastmouse)])
+            dworld=c.canvas2world_vector(*dcan)
+            self.field.center(*(self.field.center()+dworld))
+            self._lastmouse=ev.x,ev.y
+            dispatcher.send("field coord changed")
+            self.setcoords() # redraw
+        def release(ev):
+            del self._lastmouse
+        c.tag_bind(self.txt,"<ButtonPress-1>",press)
+        c.tag_bind(self.txt,"<B1-Motion>",motion)
+        c.tag_bind(self.txt,"<B1-ButtonRelease>",release)
+
+        # radius drag bindings
+        outerring=self.rings[0]
+        canvashighlighter(c,outerring,
+                          'outline',normalval='#000080',highlightval='#4040ff')
+        def motion(ev):
+            worldmouse=self.canvas.canvas2world(ev.x,ev.y)
+            currentdist=pairdist(worldmouse,self.field.center())
+            self.field.falloff(currentdist)
+            dispatcher.send("field coord changed")
+            self.setcoords()
+        c.tag_bind(outerring,"<B1-Motion>",motion)
+
+        self.setcoords()
+    
 class Tracker(tk.Frame):
 
     """whole tracker widget, which is mostly a view for a
-    FieldSet. tracker makes its own fieldset"""
+    Fieldset. tracker makes its own fieldset"""
 
     # world coords of the visible canvas (preserved even in window resizes)
     xmin=0
     xmax=100
     ymin=0
     ymax=100
+
+    fieldsetfile=None
+    displays=None # Field : FieldDisplay. we keep these in sync with the fieldset
     
     def __init__(self,master):
         tk.Frame.__init__(self,master)
 
-        self.fieldset=FieldSet()
-        self.displays={} # Field : FieldDisplay
+        self.displays={}
         
-        c=Zooming(self,bg='black')
+        c=self.canvas=Zooming(self,bg='black',closeenough=5)
         c.pack(fill='both',exp=1)
 
-        for x,y in ((5,5),(5,95),(95,5),(95,95)):
-            c.create_text(x,y,text="%s,%s"%(x,y),fill='white')
-
+        # preserve edge coords over window resize
         c.bind("<Configure>",self.configcoords)
         
-        self.canvas=c
+        c.bind("<Motion>",
+               lambda ev: self._fieldset().report(*c.canvas2world(ev.x,ev.y)))
+        def save(ev):
+            print "saving"
+            self.fieldsetfile.save()
+        master.bind("<Key-s>",save)
+        dispatcher.connect(self.autobounds,"field coord changed")
 
-        c.bind("<Motion>",
-               lambda ev: self.fieldset.report(*c.canvas2world(ev.x,ev.y)))
+    def _fieldset(self):
+        return self.fieldsetfile.fieldset()
 
+    def load(self,filename):
+        self.fieldsetfile=Fieldsetfile(filename)
+        self.displays.clear()
+        for f in self.fieldsetfile.fieldset().getall():
+            self.displays[f]=FieldDisplay(self.canvas,f)
+            self.displays[f].makeobjs()
+        self.autobounds()
 
     def configcoords(self,*args):
-        # force canvas coords to line up right
+        # force our canvas coords to stay at the edges of the window
         c=self.canvas
         cornerx,cornery=c.canvas2world(0,0)
         c.move(cornerx-self.xmin,
@@ -155,24 +225,30 @@
                    c.winfo_height()/(self.ymax-self.ymin))
 
     def autobounds(self):
-        """figure out our bounds from the fieldset, and adjust the display zooms"""
-        self.xmin,self.xmax,self.ymin,self.ymax=self.fieldset.getbounds()
+        """figure out our bounds from the fieldset, and adjust the display zooms.
+        writes the corner coords onto the canvas."""
+        self.xmin,self.xmax,self.ymin,self.ymax=self._fieldset().getbounds()
 
-    def addfield(self,name,f):
-        """add the named Field to this display"""
-        self.fieldset.addfield(name,f)
-        self.displays[f]=FieldDisplay(self.canvas,f)
-        self.displays[f].draw()
+        self.configcoords()
+        
+        c=self.canvas
+        c.delete('cornercoords')
+        for x,anc2 in ((self.xmin,'w'),(self.xmax,'e')):
+            for y,anc1 in ((self.ymin,'n'),(self.ymax,'s')):
+                pos=c.world2canvas(x,y)
+                c.create_text(pos[0],pos[1],text="%s,%s"%(x,y),
+                              fill='white',anchor=anc1+anc2,
+                              tags='cornercoords')
+        [d.setcoords() for d in self.displays.values()]
 
+########################################################################
+########################################################################
+                
 root=tk.Tk()
 
 tra=Tracker(root)
 tra.pack(fill='both',exp=1)
 
-tra.fieldset.loadfromfile(file("fieldsets/demo"))
-for f in tra.fieldset.fields.values():
-    tra.displays[f]=FieldDisplay(tra.canvas,f)
-    tra.displays[f].draw()
-tra.autobounds()
+tra.load("fieldsets/demo")
 
 root.mainloop()