changeset 44:6540879e336e

fixed Stage a lot: ctrl-drag subtracts from selection; ctrl-a/ctrl-A select all/none; fixed Stage a lot: ctrl-drag subtracts from selection; ctrl-a/ctrl-A select all/none; rmb adjusts levels
author drewp
date Sun, 07 Jul 2002 13:07:31 +0000
parents 2cd759c2b3c7
children 62c47c3a90cb
files light8/stage.py light8/subediting.py
diffstat 2 files changed, 127 insertions(+), 83 deletions(-) [+]
line wrap: on
line diff
--- a/light8/stage.py	Sun Jul 07 12:31:18 2002 +0000
+++ b/light8/stage.py	Sun Jul 07 13:07:31 2002 +0000
@@ -36,26 +36,33 @@
         put pass any canvas options you want
         
       setimage(stageimage)
-        sets image to given filename (ppm, gif, etc) and resizes the canvas to the image size
+        sets image to given filename (ppm, gif, etc) and resizes the
+        canvas to the image size
 
       addlight(name, location, aim=None)
-        location and aim are pixel coord tuples. name will be passed back to you in the callback (see below)
+        location and aim are pixel coord tuples. name will be passed
+        back to you in the callback (see below)
 
-      setlightchangecb(cb)
-        give a function which will be called like this: cb(list_of_light_names, delta)
+      setsubediting(se)
+        give a subediting object to receive the 'startlevelchange' and
+        'levelchange' messages
       
 
     """
     def __init__(self,parent,**kw):
         Canvas.__init__(self,parent,**kw)
 
-        self.bind("<ButtonPress-1>", self.leftpress)
-        self.bind("<B1-Motion>", self.leftmotion)
-        self.bind("<ButtonRelease-1>", self.leftrelease)
+        self.bind("<ButtonPress>", self.press)
+        self.bind("<Motion>", self.motion)
+        self.bind("<ButtonRelease>", self.release)
+        self.bind("<Control-Key-a>", lambda ev: self.selectall())
+        self.bind("<Control-Key-A>", lambda ev: self.clearselection())
+#        self.bind("<Control-Shift-Key-a>",self.handlecontrol_a)
         
         self.halo=11 # search radius for clicked items
 
-        self.lmbstate=None # as you perform with LMB, this goes from None to 'pressed','rectangle','levelchange'
+        self.mode=None # as you perform with the mouse, this goes
+                       # from None to 'pressed','rectangle','levelchange', etc
 
         self.alllights=[]
         self.selectedlights=[]
@@ -80,24 +87,36 @@
         self.delete("selectbox")
         for l in self.selectedlights:
             for c in self.getlightbboxes(l):
-               self.create_rectangle(c[0]-2,c[1]-2,c[2]+2,c[3]+2,outline='red',tag="selectbox")            
-    
-    def clearselection(self,dyn=0):
+               self.create_rectangle(c[0]-2,c[1]-2,c[2]+2,c[3]+2,
+                                     outline='red',tag="selectbox")            
+
+    def selectall(self):
+        self.selectedlights= self.alllights[:]
+        self.updateselectionboxes()
+    def clearselection(self):
         self.selectedlights=[]
         self.updateselectionboxes()
 
     def markfordynselection(self):
         """call this before calls to replacedynselection"""
-        self.origselection = self.selectedlights
+        self.origselection = self.selectedlights[:]
 
-    def replacedynselection(self,newlightnames):
-        """as a dynamic selection changes, keep calling this function with the
-        names of the lights in the dynamic selection. the original selection (at the time
-        of markfordynselection) will be shown along with any new lights"""
-        self.selectedlights = self.origselection + [l for l in newlightnames if l not in self.origselection]
+    def replacedynselection(self,newlightnames,subtract=0):
+        """as a dynamic selection changes, keep calling this function
+        with the names of the lights in the dynamic selection. the
+        original selection (at the time of markfordynselection) will
+        be shown along with any new lights. if subtract=1, the selection will
+        be shown MINUS the newlights."""
+        if subtract==0:
+            # orig selection plus any newlights that weren't in the orig selection
+            self.selectedlights = self.origselection[:] + [l for l in newlightnames if l not in self.origselection]
+        else:
+            # orig selection lights except those that are in the newlightnames list
+            self.selectedlights = [l for l in self.origselection if l not in newlightnames]
         self.updateselectionboxes()
 
-    def select(self,lightname,select=1,dyn=0): # select=0 for deselect
+    def select(self,lightname,select=1): # select=0 for deselect
+        """select or deselect (select=0) a light by name"""
         if select:
             if lightname not in self.selectedlights:
                 self.selectedlights.append(lightname)
@@ -108,105 +127,116 @@
                 
 
     #
-    # LMB click or drag
+    # mouse handling
     #
-    def leftpress(self,ev):
+    def press(self,ev):
         
-        self.lmbstate='pressed'
-        self.lmbstart=(ev.x,ev.y)
-        print "click at",self.lmbstart
+        self.mode='pressed'
+        self.mousedownpos=(ev.x,ev.y)
+        print "click at",self.mousedownpos
 
+        button=ev.num
         shifted=ev.state & 1
         control=ev.state & 4
-        touching=self.findoverlappinglights((ev.x-self.halo,ev.y-self.halo,ev.x+self.halo,ev.y+self.halo))
+        touching=self.findoverlappinglights((ev.x-self.halo,ev.y-self.halo,
+                                             ev.x+self.halo,ev.y+self.halo))
         istouching=len(touching)>0
 
-        if not istouching:
-            # clicked in space
-            if not shifted and not control and len(self.selectedlights)>0:
-                # either a deselect (if no motion) or a level change (if motion)
-                self.lmbstate = 'deselect-or-level'
-            if shifted or control or len(self.selectedlights)==0:
-                # with shift/control, add/subtract lights to selection
-                self.lmbstate='rectangle'
+        if button==1:
+            if not istouching:
+                # clicked in space
+                if not shifted and not control and len(self.selectedlights)>0:
+                    # either a deselect (if no motion) or a level change (if motion)
+                    self.mode = 'deselect-or-rectangle'
+                if shifted or control or len(self.selectedlights)==0:
+                    # with shift/control, add/subtract lights to selection
+                    self.startrectangleselect()
 
-        else:
-            # clicked a selectable object
-            # toggle selection
-            if touching[0] in self.selectedlights:
-                if shifted:
-                    # deselect
-                    self.select(touching[0],0)
-                    # and do nothing else
-                    self.lmbstate=None
+            else:
+                # clicked a selectable object
+                # toggle selection
+                if touching[0] in self.selectedlights:
+                    if shifted or control:
+                        # deselect
+                        self.select(touching[0],0)
+                        # and do nothing else
+                        self.mode=None
+                    if not shifted:
+                        # select only this light
+                        self.clearselection()
+                        self.select(touching[0])
+                        # and adjust its level
+                        self.startlevelchange()
+
                 else:
-                    # select only this light
-                    self.clearselection()
+                    # clicked a light that wasn't selected
+                    if not shifted:
+                        self.clearselection()
                     self.select(touching[0])
-                    # and adjust its level
+                    # and adjust levels now
                     self.startlevelchange()
-                    
-            else:
-                # clicked a light that wasn't selected
-                if not shifted:
-                    self.clearselection()
-                self.select(touching[0])
-                # and adjust levels now
-                self.startlevelchange()
-                
-        if self.lmbstate=='rectangle':
-            self.markfordynselection()
-    def leftmotion(self,ev):
+
+        if button==3:
+            self.startlevelchange()
+
+    def motion(self,ev):
 
         coords=(ev.x,ev.y)
 
         shifted=ev.state & 1
         control=ev.state & 4
 
-        if self.lmbstate=='deselect-or-level':
-            if (coords[0]-self.lmbstart[0])**2+(coords[1]-self.lmbstart[1])**2>self.halo**2:
+        if self.mode=='deselect-or-rectangle':
+            if (coords[0]-self.mousedownpos[0])**2+(coords[1]-self.mousedownpos[1])**2>self.halo**2:
                 # they moved enough, it's a level change
-                self.startlevelchange()
+                self.startrectangleselect()
+                
 
-        if self.lmbstate=='levelchange':
-            delta = (self.lmbstart[1]-ev.y)
+        if self.mode=='levelchange':
+            delta = (self.mousedownpos[1]-ev.y)
             if self.subeditor:
                 self.subeditor.levelchange(self.selectedlights,delta)
 
-        if self.lmbstate=='rectangle':
+        if self.mode=='rectangle':
             sr = self.find_withtag('selectrect')
             if not sr:
-                sr=self.create_rectangle( self.lmbstart[0],self.lmbstart[1],coords[0],coords[1],
-                                          outlinestipple='gray50',outline='yellow',
-                                          tag='selectrect')
+                # make the selection rectangle
+                sr=self.create_rectangle( self.mousedownpos[0],self.mousedownpos[1],coords[0],coords[1],
+                                          outlinestipple='gray50',outline='yellow',tag='selectrect')
 
             # move rectangle with mouse
-            self.coords(sr,*(self.lmbstart+coords))
+            self.coords(sr,*(self.mousedownpos+coords))
 
             # redo the dynselection with the new rectangle
-            self.replacedynselection([o for o in self.findoverlappinglights((self.lmbstart+coords),1)])
-
-            # need to handle ctrl
+            self.replacedynselection(self.findoverlappinglights((self.mousedownpos+coords),1),
+                                     subtract=control)
 
-    def leftrelease(self,ev):
-        if self.lmbstate:
-
-            if self.lmbstate=='rectangle':
+    def release(self,ev):
+        if self.mode:
+            if self.mode=='rectangle':
                 self.delete('selectrect')
 
-            if self.lmbstate=='deselect-or-level':
+            if self.mode=='deselect-or-rectangle':
                 # they didn't move enough to promote the mode to level, so it's a deselect click
                 self.clearselection()
             
-            # all items that were in dynselection join the selection
-#            self.incorporatedynselection()
+            self.mode=None
+
+    #
+    #
+    #
             
-        self.lmbstate=None
     def startlevelchange(self):
-        self.lmbstate='levelchange'
+        """sets mode to levelchange AND notifies subeditor. this
+        should be done exactly once (per mouse drag), when you first
+        decide the mode is levelchange"""
+        self.mode='levelchange'
         if self.subeditor:
             self.subeditor.startlevelchange()
-
+    def startrectangleselect(self):
+        """sets mode to rectangle AND checkpoints the current selection"""
+        self.mode='rectangle'
+        self.markfordynselection()
     #
     # light names vs. canvas object tags
     #
@@ -233,8 +263,13 @@
                              fill='red',tag=tags+" hotspot")
             self.create_line(location[0],location[1],aim[0],aim[1],fill='lightblue',
                              arrow='last',arrowshape="9 15 6",tag='light')
-        self.create_text(location[0]-1,location[1]+6,anchor='n',text=name,fill='black',tag=tags,**dict([(k,v) for k,v in textstyle.items() if k!='fill']))
+        # shadow
+        self.create_text(location[0]-1,location[1]+6,
+                         anchor='n',text=name,fill='black',
+                         tag=tags,**dict([(k,v) for k,v in textstyle.items() if k!='fill']))
+        # text
         self.create_text(location[0],location[1]+5,anchor='n',text=name,tag=tags,**textstyle)
+        
         self.alllights.append(name)
         self.alllighttags[self.nametag(name)]=name
 
@@ -248,13 +283,13 @@
         return bboxes
 
     def findoverlappinglights(self,box,enclosed=0):
-        "returns all the different name_ tags for lights that are within (or enclosed by) the box"
+        "returns all the different names for lights that are within (or enclosed by) the box"
         lights=[]
         if enclosed:
             candidates = self.find_enclosed(*box)
         else:
             candidates = self.find_overlapping(*box)
-            
+
         for o in candidates:
             for t in self.gettags(o):
                 if t.startswith("name_"):
@@ -307,11 +342,20 @@
 
 if __name__=='__main__':
     root=Tk()
+    root.tk_focusFollowsMouse()
     root.wm_geometry("+376+330")
     s=Stage(root)
     s.setimage('guysanddolls.gif')
     s.pack()
 
     createlights(s)
+
+    class subediting_standin:
+        def startlevelchange(self):
+            print "start lev change"
+        def levelchange(self,lights,delta):
+            print "change",lights,delta
+    s.setsubediting(subediting_standin())
+    
     root.mainloop()
 
--- a/light8/subediting.py	Sun Jul 07 12:31:18 2002 +0000
+++ b/light8/subediting.py	Sun Jul 07 13:07:31 2002 +0000
@@ -61,7 +61,7 @@
                 # level was not in the sub
                 cl = self.getcurrentlevel(l)
                 if cl is None:
-                    print "light isn't even in the patch! skipping"
+                    print "light '%s' isn't even in the patch! skipping" % l
                     return
                 print "copying current light level",cl,"into the sub"
                 self.startlevels[l] = cl