Changeset - 1b81e55ebb73
[Not reviewed]
default
0 3 0
Drew Perttula - 13 years ago 2012-06-13 08:39:33
drewp@bigasterisk.com
new selectmanip can translate points in X. previous multi-point dragging has been lost along the way
Ignore-this: 1364fa68e7963e7de12a6349d23d3be8
3 files changed with 142 insertions and 67 deletions:
0 comments (0 inline, 0 general)
light9/curvecalc/curveview.py
Show inline comments
 
@@ -62,36 +62,134 @@ class Sketch:
 
        # the simplified curve may now be too far away from some of
 
        # the points, so we'll put them back. this has an unfortunate
 
        # bias toward reinserting the earlier points
 
        for i in to_remove:
 
            p = pts[i]
 
            if abs(self.curveview.curve(p[0]) - p[1]) > .1:
 
                self.curveview.add_point(p)
 
                finalPoints.append(p)
 
            
 
        self.curveview.update_curve()
 
        self.curveview.select_points(finalPoints)
 

	
 
class SelectManip(object):
 
    """
 
    selection manipulator is created whenever you have a selection. It
 
    draws itself on the canvas and edits the points when you drag
 
    various parts
 
    """
 
    def __init__(self, parent, getSelectedIndices, getWorldPoint, getScreenPoint, getCanvasSize, setPoints, getWorldTime):
 
        """parent goocanvas group"""
 
        self.getSelectedIndices = getSelectedIndices
 
        self.getWorldPoint = getWorldPoint
 
        self.getScreenPoint = getScreenPoint
 
        self.getCanvasSize = getCanvasSize
 
        self.setPoints = setPoints
 
        self.getWorldTime = getWorldTime
 
        self.grp = goocanvas.Group(parent=parent)
 
        
 
        self.title = goocanvas.Text(parent=self.grp, text="selectmanip", x=10, y=10, fill_color='white', font="ubuntu 10")
 

	
 
        self.bbox = goocanvas.Rect(parent=self.grp,
 
                                   stroke_color='yellow',
 
                                   fill_color_rgba=0xffff0030,
 
                                   line_width=.7)
 

	
 
        self.xTrans = goocanvas.Polyline(parent=self.grp, close_path=True,
 
                                         fill_color_rgba=0xffffff88,
 
                                         )
 
        self.xTrans.connect("button-press-event", self.onXPress)
 
        self.xTrans.connect("button-release-event", self.onXRelease)
 
        self.xTrans.connect("motion-notify-event", self.onXMotion)
 
        self.update()
 

	
 
    def onXPress(self, item, target_item, event):
 
        self.dragStartTime = self.getWorldTime(event.x)
 
        self.origPoints = [self.getWorldPoint(i) for i in
 
                           self.getSelectedIndices()]
 
        return True
 

	
 
    def onXMotion(self, item, target_item, event):
 
        if hasattr(self, 'dragStartTime'):
 
            dt = self.getWorldTime(event.x) - self.dragStartTime
 
            self.setPoints(
 
                (i, (orig[0] + dt, orig[1]))
 
                for i, orig in zip(self.getSelectedIndices(), self.origPoints))
 
            
 

	
 
    def onXRelease(self, item, target_item, event):
 
        del self.dragStartTime
 

	
 
    def update(self):
 
        """if the view or selection or selected point positions
 
        change, call this to redo the layout of the manip"""
 
        idxs = self.getSelectedIndices()
 
        pts = [self.getScreenPoint(i) for i in idxs]
 
        
 
        b = self.bbox.props
 
        b.x = min(p[0] for p in pts) - 5
 
        b.y = min(p[1] for p in pts) - 5
 
        margin = 10 if len(pts) > 1 else 0
 
        b.width = max(p[0] for p in pts) - b.x + margin
 
        b.height = min(max(p[1] for p in pts) - b.y + margin,
 
                       self.getCanvasSize().height - b.y - 1)
 

	
 
        b.visibility = goocanvas.ITEM_VISIBLE if len(pts) > 1 else goocanvas.ITEM_HIDDEN
 

	
 
        self.title.props.text = "%s %s selected" % (
 
            len(idxs), "point" if len(idxs) == 1 else "points")
 

	
 
        centerX = b.x + b.width / 2
 

	
 
        midY = self.getCanvasSize().height / 2
 
        x1 = centerX - 30
 
        x2 = centerX - 20
 
        x3 = centerX + 20
 
        x4 = centerX + 30
 
        y1 = midY - 10
 
        y2 = midY - 5
 
        y3 = midY + 5
 
        y4 = midY + 10
 
        shape = [
 
            (x1, midY), # left tip
 
            (x2, y1),
 
            (x2, y2),
 
            
 
            (x3, y2),
 
            (x3, y1),
 
            (x4, midY), # right tip
 
            (x3, y4),
 
            (x3, y3),
 
            
 
            (x2, y3),
 
            (x2, y4)
 
            ]
 

	
 
        self.xTrans.props.points = goocanvas.Points(shape)
 

	
 
    def destroy(self):
 
        self.grp.remove()
 

	
 
class Curveview(object):
 
    """
 
    graphical curve widget only. Please pack .widget
 
    """
 
    def __init__(self, curve, knobEnabled=False, isMusic=False,
 
                 zoomControl=None):
 
        """knobEnabled=True highlights the previous key and ties it to a
 
        hardware knob"""
 
        self.redrawsEnabled = False
 

	
 
        self.rebuild()
 
        
 
        self.redrawsEnabled = False
 
        self.curve = curve
 
        self.knobEnabled = knobEnabled
 
        self._isMusic = isMusic
 
        self.zoomControl = zoomControl
 
        self._time = -999
 
        self.last_mouse_world = None
 
        self.culled = False # have we been putting off updates?
 
        self.entered = False # is the mouse currently over this widget
 
        self.selected_points=[] # idx of points being dragged
 
        # self.bind("<Enter>",self.focus)
 
        dispatcher.connect(self.playPause, "onPlayPause")
 
        dispatcher.connect(self.input_time, "input time")
 
@@ -104,56 +202,45 @@ class Curveview(object):
 
        if self.knobEnabled:
 
            dispatcher.connect(self.knob_in, "knob in")
 
            dispatcher.connect(self.slider_in, "set key")
 

	
 

	
 
        # todo: hold control to get a [+] cursor
 
        #        def curs(ev):
 
        #            print ev.state
 
        #        self.bind("<KeyPress>",curs)
 
        #        self.bind("<KeyRelease-Control_L>",lambda ev: curs(0))
 
      
 
        # this binds on c-a-b1, etc
 
        if 0:
 
        if 0: # unported
 
            self.regionzoom = RegionZoom(self, self.world_from_screen,
 
                                         self.screen_from_world)
 

	
 
        self.sketch = None # an in-progress sketch
 

	
 
        self.dragging_dots = False
 
        self.selecting = False
 
        
 
        if 0:
 
            self.bind("<ButtonPress-1>",#"<Alt-Key>",
 
                      self.select_press, add=True)
 
            self.bind("<Motion>", self.select_motion, add=True)
 
            self.bind("<ButtonRelease-1>", #"<Alt-KeyRelease>",
 
                      self.select_release, add=True)
 

	
 
            self.bind("<ButtonPress-1>", self.check_deselect, add=True)
 

	
 
            self.bind("<Key-m>", lambda *args: self.curve.toggleMute())
 

	
 
    def rebuild(self):
 
        """
 
        sometimes after windows get resized, canvas gets stuck with a
 
        weird offset. I can't find where it is, so for now we support
 
        rebuilding the canvas widget
 
        """
 
        if hasattr(self, 'widget'):
 
            self.widget.destroy()
 
            self._time = -999
 
            print "rebuilding canvas"
 

	
 
        self.timelineLine = self.curveGroup = None
 
        self.timelineLine = self.curveGroup = self.selectManip = None
 
        self.widget = gtk.EventBox()
 
        self.widget.set_can_focus(True)
 
        self.widget.add_events(gtk.gdk.KEY_PRESS_MASK |
 
                               gtk.gdk.FOCUS_CHANGE_MASK)
 
        self.onFocusOut()
 

	
 
        box = gtk.VBox()
 
        box.set_border_width(1)
 
        self.widget.add(box)
 
        box.show()
 
        
 
        self.canvas = goocanvas.Canvas()
 
@@ -169,60 +256,57 @@ class Curveview(object):
 

	
 
        self.canvas.connect("leave-notify-event", self.onLeave)
 
        self.canvas.connect("enter-notify-event", self.onEnter)
 
        self.canvas.connect("motion-notify-event", self.onMotion)
 
        self.canvas.connect("scroll-event", self.onScroll)
 
        self.canvas.connect("button-release-event", self.onRelease)
 
        self.root.connect("button-press-event", self.onCanvasPress)
 

	
 
        self.widget.connect("key-press-event", self.onKeyPress)
 

	
 
        self.widget.connect("focus-in-event", self.onFocusIn)
 
        self.widget.connect("focus-out-event", self.onFocusOut)
 
        self.widget.connect("event", self.onAny)
 
        #self.widget.connect("event", self.onAny)
 

	
 
    def onAny(self, w, event):
 
        print "   %s on %s" % (event, w)
 
        
 
    def onFocusIn(self, *args):
 
        self.widget.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("red"))
 

	
 
    def onFocusOut(self, widget=None, event=None):
 
        self.widget.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("gray30"))
 

	
 
    def onKeyPress(self, widget, event):
 
        print "canvas key", event
 
        if event.string in '12345':
 
        if event.string in list('12345'):
 
            x = int(event.string)
 
            self.add_point((self.current_time(), (x - 1) / 4.0))
 

	
 
    def onExpose(self, *args):
 
        if self.culled:
 
            self.update_curve()
 

	
 
    def onDelete(self):
 
        if self.selected_points:
 
            self.remove_point_idx(*self.selected_points)
 
        
 
            
 
    def onCanvasPress(self, item, target_item, event):
 
        # when we support multiple curves per canvas, this should find
 
        # the close one and add a point to that. Binding to the line
 
        # itself is probably too hard to hit. Maybe a background-color
 
        # really thick line would be a nice way to allow a sloppier
 
        # click
 

	
 
        print "focus on", self.widget
 
        self.widget.grab_focus()
 
        print "done grab"
 
        
 
        if event.get_state() & gtk.gdk.CONTROL_MASK:
 
            self.new_point_at_mouse(event)
 
        elif event.get_state() & gtk.gdk.SHIFT_MASK:
 
            self.sketch_press(event)
 
        else:
 
            self.select_press(event)
 

	
 
        # this stops some other handler that wants to unfocus 
 
        return True
 

	
 
    def playPause(self):
 
@@ -267,42 +351,60 @@ class Curveview(object):
 

	
 
        if value is None:
 
            value = self.curve.eval(self.current_time())
 

	
 
        self.curve.insert_pt((self.current_time(), value))
 
        self.update_curve()
 

	
 
    def print_state(self, msg=""):
 
        if 1:
 
            print "%s: dragging_dots=%s selecting=%s" % (
 
                msg, self.dragging_dots, self.selecting)
 

	
 
    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_points(self, pts):
 
        """set selection to the given point values (tuples, not indices)"""
 
        idxs = []
 
        for p in pts:
 
            idxs.append(self.curve.points.index(p))
 
        self.select_indices(idxs)
 

	
 
    def select_indices(self, idxs):
 
        """set selection to these point indices"""
 
        """set selection to these point indices. This is the only
 
        writer to self.selected_points"""
 
        self.selected_points = idxs
 
        self.highlight_selected_dots()
 
        if self.selected_points and not self.selectManip:
 
            self.selectManip = SelectManip(
 
                self.root,
 
                getSelectedIndices=lambda: self.selected_points,
 
                getWorldPoint=lambda i: self.curve.points[i],
 
                getScreenPoint=lambda i: self.screen_from_world(self.curve.points[i]),
 
                getWorldTime=lambda x: self.world_from_screen(x, 0)[0],
 
                getCanvasSize=lambda: self.size,
 
                setPoints=self.setPoints,
 
                )
 
        if not self.selected_points and self.selectManip:
 
            self.selectManip.destroy()
 
            self.selectManip = None
 

	
 
        self.selectionChanged()
 

	
 
    def setPoints(self, updates):
 
        for i, pt in updates:
 
            self.curve.points[i] = pt
 
        self.update_curve()
 
        
 
    def selectionChanged(self):
 
        if self.selectManip:
 
            self.selectManip.update()
 

	
 
    def select_press(self,ev):
 
        # todo: these select_ handlers are getting called on c-a-drag
 
        # zooms too. the dispatching should be more selective than
 
        # just calling both handlers all the time
 
        self.print_state("select_press")
 
        if self.dragging_dots:
 
            return
 
        if not self.selecting:
 
            self.selecting = True
 
            self.select_start = self.world_from_screen(ev.x,0)[0]
 
            #cursors.push(self,"gumby")
 
@@ -424,71 +526,40 @@ class Curveview(object):
 
                                                  beyond=1)
 
        visible_points = [cp[i] for i in visible_idxs]
 

	
 
        if getattr(self, 'curveGroup', None):
 
            self.curveGroup.remove()
 
        self.curveGroup = goocanvas.Group(parent=self.root)
 

	
 
        # this makes gtk quietly stop working. Getting called too early?
 
        #self.canvas.set_property("background-color",
 
        #                         "gray20" if self.curve.muted else "black")
 

	
 
        if self.size.height < 40:
 
            #self._draw_gradient()
 
            self._draw_line(visible_points, area=True)
 
        else:
 
            self._draw_markers(visible_x)
 
            self._draw_line(visible_points)
 

	
 
            self.dots = {} # idx : canvas rectangle
 

	
 
            if len(visible_points) < 50 and not self.curve.muted:
 
                self._draw_handle_points(visible_idxs,visible_points)
 

	
 
        self.selectionChanged()
 

	
 
    def is_music(self):
 
        """are we one of the music curves (which might be drawn a bit
 
        differently)"""
 
        return self._isMusic
 
 
 
    def _draw_gradient(self):
 
        # not yet ported
 
        t1 = time.time()
 
        gradient_res = 6 if self.is_music() else 3
 
        startX = startColor = None
 
        rects = 0
 
        for x in range(0, self.size.width, gradient_res):
 
            wx = self.world_from_screen(x,0)[0]
 
            mag = self.curve.eval(wx, allow_muting=False)
 
            if self.curve.muted:
 
                low = (8, 8, 8)
 
                high = (60, 60, 60)
 
            else:
 
                low = (20, 10, 50)
 
                high = (255, 187, 255)
 
            color = gradient(mag, low=low, high=high)
 
            if color != startColor:
 
                if startColor is not None:
 
                    self._draw_gradient_slice(startX, x, startColor)
 
                    rects += 1
 
                startX = x
 
                startColor = color
 

	
 
        if startColor is not None:
 
            self._draw_gradient_slice(startX, self.size.width, startColor)
 
            rects += 1
 
        log.debug("redraw %s rects in %.02f ms", rects, 1000 * (time.time()-t1))
 

	
 
    def _draw_gradient_slice(self, x1, x2, color):
 
        self.create_rectangle(x1, 0, x2, 40,
 
                              fill=color, width=0, tags='curve')        
 

	
 
    def _draw_markers(self,visible_x):
 
        mark = self._draw_one_marker
 

	
 
        mark(0, "0")
 
        t1,t2=visible_x
 
        if t2-t1<30:
 
            for t in range(int(t1),int(t2)+1):
 
                mark(t,str(t))
 
        mark(introPad, str(introPad))
 

	
 
        endtimes = dispatcher.send("get max time")
 
@@ -618,41 +689,44 @@ class Curveview(object):
 
            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
 
            self.select_indices(newsel)
 
            idxs[:] = newidxs
 
            
 
        self.update_curve()
 

	
 
    def highlight_selected_dots(self):
 
        if not self.redrawsEnabled:
 
            return
 

	
 
        for i,d in self.dots.items():
 
            if i in self.selected_points:
 
                d.set_property('fill_color', 'red')
 
            else:
 
                d.set_property('fill_color', 'blue')
 
        
 
    def dotpress(self, r1, r2, ev, dotidx):
 
        self.print_state("dotpress")
 
        if dotidx not in self.selected_points:
 
            self.selected_points=[dotidx]
 
        self.highlight_selected_dots()
 
            self.select_indices([dotidx])
 

	
 
        self.last_mouse_world = self.world_from_screen(ev.x, ev.y)
 
        self.dragging_dots = True
 

	
 
    def select_between(self,start,end):
 
        if start > end:
 
            start, end = end, start
 
        self.select_indices(self.curve.indices_between(start,end))
 

	
 
    def onEnter(self, widget, event):
 
        self.entered = True
 

	
 
    def onLeave(self, widget, event):
 
@@ -689,26 +763,25 @@ class Curveview(object):
 
            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 newp[0] >= cp[idx+1][0]:
 
                continue
 
            moved=1
 
            cp[idx] = tuple(newp)
 
        if moved:
 
            self.update_curve()
 

	
 
    def unselect(self):
 
        self.selected_points=[]
 
        self.highlight_selected_dots()
 
        self.select_indices([])
 

	
 
    def onScroll(self, widget, event):
 
        t = self.world_from_screen(event.x, 0)[0]
 
        self.zoomControl.zoom_about_mouse(
 
            t, factor=1.5 if event.direction == gtk.gdk.SCROLL_DOWN else 1/1.5)
 
        
 
    def onRelease(self, widget, event):
 
        self.print_state("dotrelease")
 

	
 
        if event.state & gtk.gdk.SHIFT_MASK: # relese-B1
 
            self.sketch_release(event)
 
            return
light9/curvecalc/zoomcontrol.py
Show inline comments
 
@@ -53,26 +53,26 @@ class ZoomControl(object):
 

	
 
    def __init__(self, **kw):
 
        self.widget = goocanvas.Canvas(bounds_padding=5)
 
        self.widget.set_property("background-color", "gray60")
 
        self.widget.set_size_request(-1, 30)
 

	
 
        endtimes = dispatcher.send("get max time")
 
        if endtimes:
 
            self.maxtime = endtimes[0][1]
 
        else:
 
            self.maxtime = 0
 

	
 
        self.start=0
 
        self.end=20
 
        self.start = 0
 
        self.end = 250
 

	
 
        self.root = self.widget.get_root_item()
 
        self.leftbrack = goocanvas.Polyline(parent=self.root,
 
                                            line_width=5, stroke_color='black')
 
        self.rightbrack = goocanvas.Polyline(parent=self.root,
 
                                             line_width=5, stroke_color='black')
 
        self.shade = goocanvas.Rect(parent=self.root,
 
                                    fill_color='gray70',
 
                                    line_width=.5)
 
        self.time = goocanvas.Polyline(parent=self.root,
 
                                       line_width=2,
 
                                       stroke_color='red')
readme
Show inline comments
 
@@ -19,24 +19,26 @@ Browse to http://localhost:8040/ to see 
 

	
 
See bin/listsongs for a way to make zsh autocomplete on the last
 
argument to bin/curvecalc
 

	
 

	
 

	
 
--------------------------------
 

	
 
curvecalc upgrades:
 

	
 
draw handles for moving and resizing a fade with various anchor points
 

	
 
single click doesn't deselect
 

	
 
add vidref on mousemove. separate process? yes
 

	
 
sub autocomplete
 

	
 
fix fader UIs
 

	
 
drop grids with keystrokes. grid all the beats that way? different
 
keys make different grid colors. do the beats from various songs with
 
different colors.
 

	
 
be able to select grid lines to delete them. maybe grid is on a
 
special curve row
0 comments (0 inline, 0 general)