changeset 294:c9dcc57116c2

new curve editing: select single/multiple points, delete selection, move selection
author Drew Perttula <drewp@bigasterisk.com>
date Sat, 18 Jun 2005 16:58:15 +0000
parents 1c590824dd14
children 8da6dc78999b
files bin/curvecalc light9/curve.py light9/zoomcontrol.py
diffstat 3 files changed, 144 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/bin/curvecalc	Sat Jun 18 16:59:04 2005 +0000
+++ b/bin/curvecalc	Sat Jun 18 16:58:15 2005 +0000
@@ -357,7 +357,8 @@
 root.bind("<Control-Key-r>", lambda evt: dispatcher.send('reload all subs'))
 
 create_status_lines(root)
-for helpline in ["Bindings: C-s save subterms; B1 drag point; C-B1 curve add point; S-B1 sketch points; Del point under mouse; 1..5 add point at time; Esc see current time; S-Esc see curtime to end; Mousewheel zoom; C-p play/pause music at mouse",
+for helpline in ["Bindings: C-s save subterms;  Esc see current time; S-Esc see curtime to end; Mousewheel zoom; C-p play/pause music at mouse",
+                 "Curve point bindings: B1 drag point; C-B1 curve add point; S-B1 sketch points; Del selected points; 1..5 add point at time; Alt-Shift-B1 drag select points",
                  "Available in functions: nsin/ncos period=amp=1; within(a,b) bef(x) aft(x) compare to time; smoove(x) cubic smoothstep; curvename(t) eval curve"]:
     tk.Label(root,text=helpline, font="Helvetica -12 italic",
              anchor='w').pack(side='top',fill='x')
--- a/light9/curve.py	Sat Jun 18 16:59:04 2005 +0000
+++ b/light9/curve.py	Sat Jun 18 16:58:15 2005 +0000
@@ -50,11 +50,11 @@
         i = bisect(self.points,(new_pt[0],None))
         self.points.insert(i,new_pt)
 
-    def indices_between(self, x1, x2):
-        leftidx = max(0, bisect_left(self.points, (x1,None)) - 1)
-        rightidx = min(len(self.points) - 1,
-                       bisect_left(self.points, (x2,None)) + 1)
-        return range(leftidx, rightidx+1)
+    def indices_between(self, x1, x2, beyond=0):
+        leftidx = max(0, bisect(self.points, (x1,None)) - beyond)
+        rightidx = min(len(self.points),
+                       bisect(self.points, (x2,None)) + beyond)
+        return range(leftidx, rightidx)
         
     def points_between(self, x1, x2):
         return [self.points[i] for i in self.indices_between(x1,x2)]
@@ -136,6 +136,7 @@
         dispatcher.connect(self.input_time,"input time")
         dispatcher.connect(self.update_curve,"zoom changed")
         dispatcher.connect(self.update_curve,"points changed",sender=self.curve)
+        dispatcher.connect(self.select_between,"select between")
         self.bind("<Configure>",self.update_curve)
         for x in range(1, 6):
             def add_kb_marker_point(evt, x=x):
@@ -161,14 +162,56 @@
                   dispatcher.send("music seek",
                                   t=self.world_from_screen(ev.x,0)[0]))
 
+        self.bind("<Motion>",
+                  self.dotmotion, add=True)
+        self.bind("<ButtonRelease-1>",
+                  self.dotrelease, add=True)
+
+
         # this binds on c-a-b1, etc
-        RegionZoom(self, self.world_from_screen, self.screen_from_world)
+        self.regionzoom = RegionZoom(self, self.world_from_screen,
+                                     self.screen_from_world)
 
         self.sketch = None # an in-progress sketch
         self.bind("<Shift-ButtonPress-1>", self.sketch_press)
         self.bind("<Shift-B1-Motion>", self.sketch_motion)
         self.bind("<Shift-ButtonRelease-1>", self.sketch_release)
 
+
+        # hold alt-shift to select, since i had a hard time detecting
+        # other combos right
+        self.selecting = False
+        self.bind("<Alt-Key>", self.select_press)
+        self.bind("<Motion>", self.select_motion, add=True)
+        self.bind("<Alt-KeyRelease>", self.select_release)
+
+        self.bind("<ButtonPress-1>", self.check_deselect, add=True)
+
+    def check_deselect(self,ev):
+        try:
+            self.find_index_near(ev.x, ev.y)
+        except ValueError:
+            self.selected_points[:] = []
+            self.highlight_selected_dots()
+
+    def select_press(self,ev):
+        if not self.selecting:
+            self.selecting = True
+            self.select_start = self.world_from_screen(ev.x,0)[0]
+            cursors.push(self,"gumby")
+        
+    def select_motion(self,ev):
+        if not self.selecting:
+            return
+        
+    def select_release(self,ev):
+        if not self.selecting:
+            return
+        cursors.pop(self)
+        self.selecting = False
+        s,e = (self.select_start, self.world_from_screen(ev.x,0)[0])
+        self.select_between(min(s,e), max(s,e))
+
     def sketch_press(self,ev):
         self.sketch = Sketch(self,ev)
 
@@ -209,8 +252,9 @@
         visible_x = (self.world_from_screen(0,0)[0],
                      self.world_from_screen(self.winfo_width(),0)[0])
 
-        visible_points = self.curve.points_between(*visible_x)
-        visible_idxs = self.curve.indices_between(*visible_x)
+        visible_idxs = self.curve.indices_between(visible_x[0], visible_x[1],
+                                                  beyond=1)
+        visible_points = [cp[i] for i in visible_idxs]
         
         self.delete('curve')
 
@@ -280,7 +324,7 @@
         #            print ev.state
         #        self.bind("<KeyPress>",curs)
         #        self.bind("<KeyRelease-Control_L>",lambda ev: curs(0))
-        self.tag_bind(line,"<Control-ButtonPress-1>",self.newpointatmouse)
+        self.tag_bind(line,"<Control-ButtonPress-1>",self.new_point_at_mouse)
 
 
     def _draw_handle_points(self,visible_idxs,visible_points):
@@ -299,10 +343,6 @@
                                        tags=('curve','point', 'handle%d' % i))
             self.tag_bind('handle%d' % i,"<ButtonPress-1>",
                           lambda ev,i=i: self.dotpress(ev,i))
-            self.bind("<Motion>",
-                      lambda ev,i=i: self.dotmotion(ev,i))
-            self.bind("<ButtonRelease-1>",
-                      lambda ev,i=i: self.dotrelease(ev,i))
             #self.tag_bind('handle%d' % i, "<Key-d>",
             #              lambda ev, i=i: self.remove_point_idx(i))
                       
@@ -312,18 +352,21 @@
             # had a hard time tag_binding to the points, so i trap at
             # the widget level (which might be nice anyway when there
             # are multiple pts selected)
-            tags = self.gettags(self.find_closest(ev.x, ev.y))
-            try:
-                handletags = [t for t in tags if t.startswith('handle')]
-                i = int(handletags[0][6:])
-            except IndexError:
-                return
-            self.remove_point_idx(i)
+            if self.selected_points:
+                self.remove_point_idx(*self.selected_points)
         self.bind("<Key-Delete>", delpoint)
 
         self.highlight_selected_dots()
+
+    def find_index_near(self,x,y):
+        tags = self.gettags(self.find_closest(x, y))
+        try:
+            handletags = [t for t in tags if t.startswith('handle')]
+            return int(handletags[0][6:])
+        except IndexError:
+            raise ValueError("no point found")
         
-    def newpointatmouse(self, ev):
+    def new_point_at_mouse(self, ev):
         p = self.world_from_screen(ev.x,ev.y)
         x, y = p
         y = max(0, y)
@@ -336,8 +379,29 @@
         self.curve.insert_pt(p)
         self.update_curve()
         
-    def remove_point_idx(self, i):
-        self.curve.points.pop(i)
+    def remove_point_idx(self, *idxs):
+        idxs = list(idxs)
+        while idxs:
+            i = idxs.pop()
+
+            self.curve.points.pop(i)
+            newsel = []
+            newidxs = []
+            for si in range(len(self.selected_points)):
+                sp = self.selected_points[si]
+                if sp == i:
+                    continue
+                if sp > i:
+                    sp -= 1
+                newsel.append(sp)
+            for ii in range(len(idxs)):
+                if ii > i:
+                    ii -= 1
+                newidxs.append(idxs[ii])
+
+            self.selected_points[:] = newsel
+            idxs[:] = newidxs
+            
         self.update_curve()
 
     def highlight_selected_dots(self):
@@ -348,29 +412,47 @@
                 self.itemconfigure(d,fill='blue')
         
     def dotpress(self,ev,dotidx):
-        self.selected_points=[dotidx]
+        if dotidx not in self.selected_points:
+            self.selected_points=[dotidx]
+        self.highlight_selected_dots()
+        self.last_mouse_world = self.world_from_screen(ev.x, ev.y)
+
+    def select_between(self,start,end):
+        self.selected_points = self.curve.indices_between(start,end)
         self.highlight_selected_dots()
 
-    def dotmotion(self,ev,dotidx):
+    def dotmotion(self,ev):
+        if not ev.state & 256:
+            return # not lmb-down
         cp = self.curve.points
         moved=0
+
+        cur = self.world_from_screen(ev.x, ev.y)
+        delta = (cur[0] - self.last_mouse_world[0],
+                 cur[1] - self.last_mouse_world[1])
+        self.last_mouse_world = cur
+        
         for idx in self.selected_points:
-            x,y = self.world_from_screen(ev.x,ev.y)
-            y = max(0,min(1,y))
-            if idx>0 and x<=cp[idx-1][0]:
+
+            newp = [cp[idx][0] + delta[0], cp[idx][1] + delta[1]]
+            
+            newp[1] = max(0,min(1,newp[1]))
+            
+            if idx>0 and newp[0] <= cp[idx-1][0]:
                 continue
-            if idx<len(cp)-1 and x>=cp[idx+1][0]:
+            if idx<len(cp)-1 and newp[0] >= cp[idx+1][0]:
                 continue
             moved=1
-            cp[idx] = (x,y)
+            cp[idx] = tuple(newp)
         if moved:
             self.update_curve()
+
     def unselect(self):
         self.selected_points=[]
         self.highlight_selected_dots()
         
-    def dotrelease(self,ev,dotidx):
-        self.unselect()
+    def dotrelease(self,ev):
+        pass #self.unselect()
         
 class Curveset:
     curves = None # curvename : curve
--- a/light9/zoomcontrol.py	Sat Jun 18 16:59:04 2005 +0000
+++ b/light9/zoomcontrol.py	Sat Jun 18 16:58:15 2005 +0000
@@ -170,7 +170,7 @@
 
 
 class RegionZoom:
-    """rigs c-a-b1 to drag out an area to zoom to.
+    """rigs c-a-b1 to drag out an area to zoom to. also catches other types of drag events, like b1 drag for selecting points
 
     this is used with Curveview
     """
@@ -181,16 +181,26 @@
         for evtype, method in [("ButtonPress-1",self.press),
                                ("Motion",self.motion),
                                ("ButtonRelease-1",self.release)]:
-            canvas.bind("<Control-Alt-%s>" % evtype, method)
-            if evtype != "ButtonPress-1":
-                canvas.bind("<%s>" % evtype, method)
+            #canvas.bind("<Control-Alt-%s>" % evtype, method, add=True)
+            if 1 or evtype != "ButtonPress-1":
+                canvas.bind("<%s>" % evtype, method,add=True)
         canvas.bind("<Leave>", self.finish)
         self.start_t = self.old_cursor = None
-        self.state = None
+        self.state = self.mods = None
 
     def press(self,ev):
         if self.state is not None:
             self.finish()
+
+        if ev.state == 12:
+            self.mods = "c-a"
+        elif ev.state == 13:
+            self.mods = "c-s-a"
+        elif ev.state == 0:
+            return # no 
+            self.mods = "none"
+        else:
+            return
         self.state = "buttonpress"
             
         self.start_t = self.end_t = self.world_from_screen(ev.x,0)[0]
@@ -236,19 +246,22 @@
         
         if abs(self.start_x - ev.x) < 10:
             # clicked
-            factor = 1/1.5
-            if ev.state & 1:
-                factor = 1.5 # c-s-a-b1 zooms out
-            dispatcher.send("zoom about mouse",
-                            t=self.start_t,
-                            factor=factor)
+            if self.mods in ("c-a", "c-s-a"):
+                factor = 1/1.5
+                if self.mods == "c-s-a":
+                    factor = 1.5 # c-s-a-b1 zooms out
+                dispatcher.send("zoom about mouse",
+                                t=self.start_t,
+                                factor=factor)
 
             self.finish()
             return
-            
-        dispatcher.send("zoom to range",
-                        start=min(self.start_t, self.end_t),
-                        end=max(self.start_t, self.end_t))
+
+        start,end = min(self.start_t, self.end_t),max(self.start_t, self.end_t)
+        if self.mods == "c-a":
+            dispatcher.send("zoom to range", start=start, end=end)
+        elif self.mods == "none":
+            dispatcher.send("select between", start=start, end=end)
         self.finish()
         
     def finish(self, *ev):