Changeset - 1d41d8658a36
[Not reviewed]
default
0 2 0
drewp@bigasterisk.com - 20 years ago 2005-06-13 00:43:41
drewp@bigasterisk.com
initial zoom-to-range feature in curveview
2 files changed with 125 insertions and 26 deletions:
0 comments (0 inline, 0 general)
light9/curve.py
Show inline comments
 
@@ -40,24 +40,105 @@ class Curve:
 
            return self.points[i][1]
 

	
 
        p1,p2 = self.points[i],self.points[i+1]
 
        frac = (t-p1[0])/(p2[0]-p1[0])
 
        y = p1[1]+(p2[1]-p1[1])*frac
 
        return y
 

	
 
    def insert_pt(self,new_pt):
 
        i = bisect(self.points,(new_pt[0],None))
 
        self.points.insert(i,new_pt)
 
    __call__=eval
 

	
 
class RegionZoom:
 
    """rigs c-a-b1 to drag out an area to zoom to."""
 
    def __init__(self, canvas, world_from_screen, screen_from_world):
 
        self.canvas, self.world_from_screen = canvas, world_from_screen
 
        self.screen_from_world = screen_from_world
 

	
 
        for evtype, method in [("ButtonPress-1",self.press),
 
                               ("Motion",self.motion),
 
                               ("ButtonRelease",self.release)]:
 
            canvas.bind("<Control-Alt-%s>" % evtype, method)
 
            if evtype != "ButtonPress-1":
 
                canvas.bind("<%s>" % evtype, method)
 
        canvas.bind("<Leave>", self.finish)
 
        self.start_t = None
 

	
 
    def press(self,ev):
 
        if self.start_t is not None:
 
            self.finish()
 
            
 
        self.start_t = self.end_t = self.world_from_screen(ev.x,0)[0]
 
        self.start_x = ev.x
 
        can = self.canvas
 

	
 
        for pos in ('start_t','end_t','hi','lo'):
 
            can.create_line(0,0,50,50, width=3, fill='black',
 
                            tags=("regionzoom",pos))
 
        # if updatelines isn't called here, subsequent updatelines
 
        # will fail for reasons i don't understand
 
        self.updatelines()
 

	
 
        self.old_cursor = can.cget("cursor")
 
        #xcursorgen
 
        can.config(cursor="@/home/drewp/projects/light9/cout red")
 
        
 
    def updatelines(self):
 
        can = self.canvas
 
        pos_x = {}
 
        height = can.winfo_height()
 
        for pos in ('start_t', 'end_t'):
 
            pos_x[pos] = x = self.screen_from_world((getattr(self,pos),0))[0]
 
            cid = can.find_withtag("regionzoom && %s" % pos)
 
            can.coords(cid, x, 0, x, height)
 
            
 
        for tag,frac in [('hi',.1),('lo',.9)]:
 
            cid = can.find_withtag("regionzoom && %s" % tag)
 
            can.coords(cid, pos_x['start_t'], frac * height,
 
                       pos_x['end_t'], frac * height)
 

	
 
    def motion(self,ev):
 
        if self.start_t is None:
 
            return
 

	
 
        self.end_t = self.world_from_screen(ev.x,0)[0]
 
        self.updatelines()
 

	
 
    def release(self,ev):
 
        if self.start_t is None:
 
            return
 
        
 
        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)
 

	
 
            self.finish()
 
            return
 
            
 
        dispatcher.send("zoom to range",
 
                        start=min(self.start_t, self.end_t),
 
                        end=max(self.start_t, self.end_t))
 
        self.finish()
 
        
 
    def finish(self, *ev):
 
        self.canvas.delete("regionzoom")
 
        self.start_t = None
 
        self.canvas.config(cursor=self.old_cursor)
 

	
 
class Curveview(tk.Canvas):
 
    def __init__(self,master,curve,**kw):
 
        self.curve=curve
 
        self._time = 0
 
        tk.Canvas.__init__(self,master,width=10,height=10,
 
                           relief='sunken',bd=1,
 
                           closeenough=5,takefocus=1, **kw)
 
        self.selected_points=[] # idx of points being dragged
 
        self.update()
 
        # self.bind("<Enter>",self.focus)
 
        dispatcher.connect(self.input_time,"input time")
 
        dispatcher.connect(self.update,"zoom changed")
 
@@ -71,24 +152,26 @@ class Curveview(tk.Canvas):
 
            self.bind("<Key-%s>" % x, add_kb_marker_point)
 

	
 

	
 
        for butnum,factor in (5, 1.5),(4, 1/1.5):
 
            self.bind("<ButtonPress-%s>"%butnum,
 
                      lambda ev,factor=factor:
 
                      dispatcher.send("zoom about mouse",
 
                                      t=self.world_from_screen(ev.x,0)[0],
 
                                      factor=factor))
 
        self.bind("<Key-Escape>",lambda ev:
 
                  dispatcher.send("see time",
 
                                  t=self.current_time()))
 
        RegionZoom(self, self.world_from_screen, self.screen_from_world)
 

	
 
    def current_time(self):
 
        return self._time
 

	
 
    def screen_from_world(self,p):
 
        start,end = self.zoom
 
        ht = self.winfo_height()
 
        return (p[0]-start)/(end-start)*self.winfo_width(), (ht-5)-p[1]*(ht-10)
 
    def world_from_screen(self,x,y):
 
        start,end = self.zoom
 
        ht = self.winfo_height()
 
        return x/self.winfo_width()*(end-start)+start, ((ht-5)-y)/(ht-10)
 
    
 
@@ -105,42 +188,45 @@ class Curveview(tk.Canvas):
 

	
 
        visible_x = (self.world_from_screen(0,0)[0],
 
                     self.world_from_screen(self.winfo_width(),0)[0])
 

	
 
        visleftidx = max(0,bisect_left(cp,(visible_x[0],None))-1)
 
        visrightidx = min(len(cp)-1,bisect_left(cp,(visible_x[1],None))+1)
 
                             
 
        visible_points = cp[visleftidx:visrightidx+1]
 
        visible_idxs = range(visleftidx,visrightidx+1)
 
        
 
        self.delete('curve')
 

	
 
        for x in range(0,self.winfo_width(),3):
 
        if self.winfo_height() < 30:
 
            self._draw_gradient()
 
        else:
 
            self._draw_markers(visible_x)
 
            self._draw_line(visible_points)
 

	
 
            self.dots = {} # idx : canvas rectangle
 

	
 
            if len(visible_points)<50:
 
                self._draw_handle_points(visible_idxs,visible_points)
 

	
 
    def _draw_gradient(self):
 
        gradient_res = 3
 
        for x in range(0,self.winfo_width(),gradient_res):
 
            wx = self.world_from_screen(x,0)[0]
 
            mag = self.curve.eval(wx)
 
            self.create_line(x,0, x,70,
 
                             fill=gradient(mag,
 
                                           low=(20,10,50),
 
                                           high=(255,187,255)),
 
                             width=3, tags='curve')
 

	
 

	
 
        self._draw_markers(visible_x)
 
        
 
        self._draw_line(visible_points)
 
        
 
        self.dots = {} # idx : canvas rectangle
 

	
 
        if len(visible_points)<50:
 
            self._draw_handle_points(visible_idxs,visible_points)
 
                             width=gradient_res, 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(-4,"-4")
 

	
 
        endtimes = dispatcher.send("get max time")
light9/zoomcontrol.py
Show inline comments
 
@@ -2,53 +2,60 @@ from __future__ import division
 
import Tkinter as tk
 
from dispatch import dispatcher
 

	
 
class Zoomcontrol(object,tk.Canvas):
 

	
 
    mintime=-5
 

	
 
    def maxtime():
 
        doc = "seconds at the right edge of the bar"
 
        def fget(self): return self._maxtime
 
        def fset(self, value):
 
            self._maxtime = value
 
            self.updatewidget()
 
            self.redrawzoom()
 
        return locals()
 
    maxtime = property(**maxtime())
 
    
 

	
 
    _end = _start = 0
 
    def start():
 
        def fget(self): return self._start
 
        def fset(self,v): self._start = max(self.mintime,v)
 
        def fset(self,v):
 
            v = max(self.mintime,v)
 
            if v < self._end:
 
                self._start = v
 
        return locals()
 
    start = property(**start())
 

	
 
    def end():
 
        def fget(self): return self._end
 
        def fset(self,v): self._end = min(self.maxtime,v)
 
        def fset(self,v):
 
            v = min(self.maxtime,v)
 
            if v > self._start:
 
                self._end = v
 
        return locals()
 
    end = property(**end())
 
        
 

	
 
    def __init__(self,master,**kw):
 
        self.maxtime=370
 
        self.start=0
 
        self.end=20
 
        tk.Canvas.__init__(self,master,width=250,height=30,
 
                           relief='raised',bd=1,bg='gray60',**kw)
 
        self.leftbrack = self.create_line(0,0,0,0,0,0,0,0,width=5)
 
        self.rightbrack = self.create_line(0,0,0,0,0,0,0,0,width=5)
 
        self.shade = self.create_rectangle(0,0,0,0,fill='gray70',outline=None)
 
        self.time = self.create_line(0,0,0,0,fill='red',width=2)
 
        self.updatewidget()
 
        self.bind("<Configure>",self.updatewidget)
 
        self.redrawzoom()
 
        self.bind("<Configure>",self.redrawzoom)
 

	
 
        if 0:
 
            # works, but you have to stay in the widget while you drag
 
            self.bind("<ButtonPress-1>",self.press)
 
            self.tag_bind(self.leftbrack,"<B1-Motion>",
 
                          lambda ev: self.adjust(ev,'start'))
 
            self.tag_bind(self.rightbrack,"<B1-Motion>",
 
                          lambda ev: self.adjust(ev,'end'))
 
            self.tag_bind(self.shade,"<B1-Motion>",
 
                          lambda ev: self.adjust(ev,'offset'))
 
        else:
 
            # works better
 
@@ -56,96 +63,102 @@ class Zoomcontrol(object,tk.Canvas):
 
            self.tag_bind(self.leftbrack,"<Enter>",
 
                          lambda ev: self.press(ev,'start'))
 
            self.tag_bind(self.shade,"<Enter>",
 
                          lambda ev: self.press(ev,'offset'))
 
            self.tag_bind(self.rightbrack,"<Enter>",
 
                          lambda ev: self.press(ev,'end'))
 
            self.bind("<B1-Motion>",self.adjust)
 
            self.bind("<ButtonRelease-1>",self.release)
 
        
 
        dispatcher.connect(lambda: (self.start,self.end),"zoom area",weak=0)
 
        dispatcher.connect(self.input_time,"input time")
 
        dispatcher.connect(lambda maxtime: (setattr(self,'maxtime',maxtime+15),
 
                                            self.updatewidget()),
 
                                            self.redrawzoom()),
 
                           "max time",weak=0)
 
        dispatcher.connect(self.zoom_about_mouse,"zoom about mouse")
 
        dispatcher.connect(self.see_time,"see time")
 
        dispatcher.connect(self.zoom_to_range,"zoom to range")
 
        self.created=1
 
    def zoom_to_range(self,start,end):
 
        self.start = start
 
        self.end = end
 
        self.redrawzoom()
 

	
 
    def zoom_about_mouse(self,t,factor):
 
        self.start = t - factor*(t-self.start)
 
        self.end = t + factor*(self.end-t)
 
        self.updatewidget()
 
        dispatcher.send("zoom changed")
 
        self.redrawzoom()
 

	
 
    def see_time(self,t):
 
        vis_seconds = self.end - self.start
 
        margin = vis_seconds * .9 # left side is nicest
 
        if t < self.start:
 
            self.offset -= (self.start - t) + margin
 
        # t doesn't have to be ALL the way off-screen
 
        if t > (self.end - vis_seconds * .3): 
 
            self.offset += (t - self.end) + margin
 
        self.updatewidget()
 
        dispatcher.send("zoom changed")
 
        self.redrawzoom()
 
            
 
    def input_time(self,val):
 
        t=val
 
        x=self.can_for_t(t)
 
        self.coords(self.time,x,0,x,self.winfo_height())
 
    def press(self,ev,attr):
 
        self.adjustingattr = attr
 
        
 
    def release(self,ev):
 
        if hasattr(self,'adjustingattr'): del self.adjustingattr
 
        if hasattr(self,'lastx'): del self.lastx
 
    def adjust(self,ev,attr=None):
 

	
 
        if not hasattr(self,'adjustingattr'):
 
            return
 
        attr = self.adjustingattr
 
        
 
        if not hasattr(self,'lastx'):
 
            self.lastx = ev.x
 
        new = self.can_for_t(getattr(self,attr)) + (ev.x - self.lastx)
 
        self.lastx = ev.x
 
        setattr(self,attr,self.t_for_can(new))
 
        self.updatewidget()
 
        dispatcher.send("zoom changed")
 
        self.redrawzoom()
 
        
 
    def offset():
 
        doc = "virtual attr that adjusts start and end together"
 
        def fget(self):
 
            return self.start
 
        def fset(self, value):
 
            d = self.end-self.start
 
            self.start = value
 
            self.end = self.start+d
 
        return locals()
 
    offset = property(**offset())
 

	
 
    def can_for_t(self,t):
 
        return (t-self.mintime)/(self.maxtime-self.mintime)*(self.winfo_width()-30)+20
 
    def t_for_can(self,x):
 
        return (x-20)/(self.winfo_width()-30)*(self.maxtime-self.mintime)+self.mintime
 

	
 
    def updatewidget(self,*args):
 
    def redrawzoom(self,*args):
 
        """redraw pieces based on start/end"""
 
        dispatcher.send("zoom changed")
 
        if not hasattr(self,'created'): return
 
        y1,y2=3,self.winfo_height()-3
 
        lip = 6
 
        scan = self.can_for_t(self.start)
 
        ecan = self.can_for_t(self.end)
 
        self.coords(self.leftbrack,scan+lip,y1,scan,y1,scan,y2,scan+lip,y2)
 
        self.coords(self.rightbrack,ecan-lip,y1,ecan,y1,ecan,y2,ecan-lip,y2)
 
        self.coords(self.shade,scan+5,y1+lip,ecan-5,y2-lip)
 
        self.delete("tics")
 
        lastx=-1000
 
        for t in range(0,int(self.maxtime)):
 
            x = self.can_for_t(t)
 
            if 0<x<self.winfo_width() and x-lastx>30:
 
                txt=str(t)
 
                if lastx==-1000:
 
                    txt=txt+"sec"
 
                self.create_line(x,0,x,15,
 
                                 tags=('tics',))
 
                self.create_text(x,self.winfo_height()-1,anchor='s',
 
                                 text=txt,tags=('tics',),font='6x13')
 
                lastx = x
 

	
0 comments (0 inline, 0 general)