Changeset - 41c6fbe95214
[Not reviewed]
default
0 4 0
drewp@bigasterisk.com - 12 years ago 2013-06-13 21:59:32
drewp@bigasterisk.com
drag sub into curve area to get a curve+subterm and a 0..1 fade at the current play time
Ignore-this: ac77acf1cfbcfbce2f6b77da670b407
4 files changed with 75 insertions and 7 deletions:
0 comments (0 inline, 0 general)
bin/curvecalc
Show inline comments
 
@@ -69,84 +69,114 @@ class Main(object):
 
        mainwin.parse_geometry("1x1-0+0")
 

	
 
        # this is the only one i found that would set the size right,
 
        # but it's a minimum size, which i don't really want
 
        mainwin.set_size_request(1678, 922)
 

	
 
        songChoice = Observable(None) # to be connected with the session song
 

	
 
        def setSong():
 
            songChoice(graph.value(session, L9['currentSong']))
 
        graph.addHandler(setSong)
 
        # next here, watch songChoice and patch the graph
 
        
 
        ec = EditChoice(graph, songChoice, label="Editing song:")
 
        wtree.get_object("currentSongEditChoice").add(ec)
 
        ec.show()
 
        
 
        wtree.get_object("subterms").connect("add", self.onSubtermChildAdded)
 
        
 

	
 
        self.refreshCurveView()       
 
        
 
        self.makeStatusLines(wtree.get_object("status"))
 

	
 

	
 
        self.acceptDragsOnCurveViews()
 
        
 
        def connect(w):
 
            w.drag_dest_set(flags=gtk.DEST_DEFAULT_ALL,
 
                            targets=[('text/uri-list', 0, 0)],
 
                            actions=gtk.gdk.ACTION_COPY)
 
            w.connect("drag-data-received", self.onDataReceived)
 
        #connect(mainwin)
 
        # that's not enough- deeper windows don't accept the
 
        # event. 
 
        #mainwin.forall(connect) # not very effective
 

	
 
        wtree.get_object("newSubZone").drag_dest_set(flags=gtk.DEST_DEFAULT_ALL,
 
                            targets=[('text/uri-list', 0, 0)],
 
                            actions=gtk.gdk.ACTION_COPY)
 
        
 
        # this probably isn't rerunning often enough to catch new data
 
        #connect(wtree.get_object("subterms")) # works for that area
 

	
 
        # may not work
 
        wtree.get_object("paned1").set_position(600)
 

	
 
    def acceptDragsOnCurveViews(self):
 
        w = self.wtree.get_object("curves")
 
        w.drag_dest_set(flags=gtk.DEST_DEFAULT_ALL,
 
                        targets=[('text/uri-list', 0, 0)],
 
                        actions=gtk.gdk.ACTION_COPY)
 
        def recv(widget, context, x, y, selection,
 
                       targetType, time):
 
            subUri = URIRef(selection.data.strip())
 
            print "into curves", subUri
 
            with self.graph.currentState(
 
                    tripleFilter=(subUri, RDFS.label, None)) as current:
 
                subName = current.label(subUri)
 

	
 
            try:
 
                self.makeSubterm(subName, withCurve=True,
 
                                 sub=subUri,
 
                                 expr="%s(t)" % subName)
 
            except SubtermExists:
 
                # we're not making sure the expression/etc are
 
                # correct-- user mihgt need to fix things
 
                pass
 
            curveView = self.curvesetView.row(subName).curveView
 
            t = self.lastSeenInputTime # curveView.current_time() # new curve hasn't heard the time yet. this has gotten too messy- everyone just needs to be able to reach the time source
 
            print "time", t
 
            curveView.add_points([(t - .5, 0),
 
                                  (t, 1)])
 
        w.connect("drag-data-received", recv)
 
        
 
    def onDataReceived(self, widget, context, x, y, selection,
 
                       targetType, time):
 
        data = selection.data.strip()
 
        if '?' in data:
 
            self.handleSubtermDrop(data)
 
            return
 
        uri = URIRef(data)
 
        subName = self.graph.label(uri)
 
        
 
        try:
 
            self.makeSubterm(subName, withCurve=True)
 
        except SubtermExists:
 
            pass
 
        curveView = self.curvesetView.row(subName).curveView
 
        t = self.lastSeenInputTime # curveView.current_time() # new curve hasn't heard the time yet. this has gotten too messy- everyone just needs to be able to reach the time source
 
        print "time", t
 
        curveView.add_points([(t - .5, 0),
 
                              (t, 1)])
 

	
 
    def onDragDataInNewSubZone(self, widget, context, x, y, selection,
 
                       targetType, time):
 
        self.makeSubterm(newname="cx", withCurve=True,
 
                         sub=URIRef(selection.data.strip()))
 
        
 
    def handleSubtermDrop(self, data):
 
        params = parse_qsl(data.split('?')[1])
 
        flattened = dict(params)
 
        self.makeSubterm(flattened['subtermName'],
 
                         expr=flattened['subtermExpr'])
 

	
 
        for cmd, name in params:
 
            if cmd == 'curve':
 
                self.curveset.new_curve(name)
 

	
 
    def onNewCurve(self, *args):
 
        dialog = self.wtree.get_object("newCurve")
 
        entry = self.wtree.get_object("newCurveName")
 
        # if you don't have songx, that should be the suggested name
 
        entry.set_text("")
 
        if dialog.run() == 1:
 
@@ -169,73 +199,96 @@ class Main(object):
 
        
 
        dialog = self.wtree.get_object("newSubterm")
 
        # the plan is to autocomplete this on existing subterm names
 
        # (but let you make one up, too)
 
        entry = self.wtree.get_object("newSubtermName").get_children()[0]
 
        entry.set_text("")
 
        entry.grab_focus()
 
        if dialog.run() == 1:
 
            newname = entry.get_text()
 
            wc = self.wtree.get_object("newSubtermMakeCurve").get_active()
 
            self.makeSubterm(newname, withCurve=wc)
 
        dialog.hide()
 

	
 
    def currentSong(self):
 

	
 
        with self.graph.currentState(
 
                tripleFilter=(self.session, L9['currentSong'], None)
 
        ) as current:
 
            return current.value(self.session, L9['currentSong'])
 

	
 
    def songSubtermsContext(self):
 
        return self.currentSong()
 

	
 
    def makeSubterm(self, newname, withCurve=False, expr=None, sub=None):
 
        """
 
        raises SubtermExists if we had a subterm with a sub with the given
 
        name. what about a no-sub term with the same label? who knows
 
        """
 
        assert isinstance(newname, Literal), repr(newname)
 
        if withCurve:
 
            self.curveset.new_curve(newname, renameIfExisting=False)
 
        if newname in self.all_subterm_labels():
 
            raise SubtermExists("have a subterm who sub is named %r" % newname)
 
        with self.graph.currentState() as current:
 
            song = self.currentSong()
 
            for i in range(1000):
 
                uri = song + "/subterm/%d" % i
 
                if (uri, None, None) not in current:
 
                    break
 
            else:
 
                raise ValueError("can't pick a name for the new subterm")
 

	
 
        ctx = self.songSubtermsContext()
 
        quads = [
 
            (uri, RDF.type, L9.Subterm, ctx),
 
            (uri, RDFS.label, Literal(newname), ctx),
 
            (self.currentSong(), L9['subterm'], uri, ctx),
 
            ]
 
        if sub is not None:
 
            quads.append((uri, L9['sub'], sub, ctx))
 
        if expr is not None:
 
            quads.append((uri, L9['expression'], Literal(expr), ctx))
 
        self.graph.patch(Patch(addQuads=quads))
 
            
 
        if withCurve:
 
            self.curveset.new_curve(newname)
 
        return uri
 
                         
 

	
 
    def all_subterm_labels(self):
 
        """
 
        Literal labels of subs in subterms. doesn't currently include labels of the
 
        subterm resources. I'm not sure what I'm going to do with
 
        those.
 
        """
 
        labels = []
 
        with self.graph.currentState() as current:
 
            for st in current.objects(
 
                    current.value(self.session, L9['currentSong']),
 
                    L9['subterm']):
 
                sub = current.value(st, L9['sub'])
 
                if sub is not None:
 
                    labels.append(current.label(sub))
 
        return labels
 
        
 
    def set_subterms_from_graph(self):
 
        """rebuild all the gtktable 'subterms' widgets and the
 
        self.currentSubterms list"""
 
        song = self.graph.value(self.session, L9['currentSong'])
 

	
 
        newList = []
 
        for st in set(self.graph.objects(song, L9['subterm'])):
 
            log.debug("song %s has subterm %s", song, st)
 
            term = Subterm(self.graph, st, self.songSubtermsContext(),
 
                               self.curveset)
 
            newList.append(term)
 
        self.currentSubterms[:] = newList
 

	
 
        master = self.wtree.get_object("subterms")
 
        log.debug("removing subterm widgets")
 
        [master.remove(c) for c in master.get_children()]
 
        for term in self.currentSubterms:
 
            add_one_subterm(term, self.curveset, master)
 
        master.show_all()
 
        log.debug("%s table children showing" % len(master.get_children()))
 
        
 
    def refreshTheme(self):
 
        gtk.rc_reparse_all()
 
        reactor.callLater(1, self.refreshTheme)
 
@@ -264,48 +317,49 @@ class Main(object):
 

	
 
    def onPythonConsole(self, item):
 
        ns = dict()
 
        ns.update(globals())
 
        ns.update(self.__dict__)
 
        togglePyConsole(self, item, ns)
 
        
 
    def onSeeCurrentTime(self, item):
 
        dispatcher.send("see time")
 

	
 
    def onSeeTimeUntilEnd(self, item):
 
        dispatcher.send("see time until end")
 

	
 
    def onZoomAll(self, item):
 
        dispatcher.send("show all")
 

	
 
    def onPlayPause(self, item):
 
        # since the X coord in a curveview affects the handling, one
 
        # of them may be able to pick this up
 
        results = dispatcher.send("onPlayPause")
 
        times = [t for listener, t in results if t is not None]
 
        self.music.playOrPause(t=times[0] if times else None)
 

	
 
    def onSave(self, *args):
 
        # only doing curves still. I hope to eliminate all this.
 
        with self.graph.currentState() as g:
 
            song = g.value(self.session, L9['currentSong'])
 

	
 
            log.info("saving curves for %r", song)
 
            self.curveset.save(basename=os.path.join(
 
                showconfig.curvesDir(),
 
                showconfig.songFilenameFromURI(song)))
 
            log.info("saved")
 

	
 
    def makeStatusLines(self, master):
 
        """various labels that listen for dispatcher signals"""
 
        for row, (signame, textfilter) in enumerate([
 
            ('input time', lambda t: "%.2fs"%t),
 
            ('output levels',
 
             lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
 
                                                     for n,v in
 
                                                     levels.items()[:2]
 
                                                     if v>0]),70)),
 
            ('update period', lambda t: "%.1fms"%(t*1000)),
 
            ('update status', lambda x: str(x)),
 
            ]):
 
            key = gtk.Label("%s:" % signame)
 
            value = gtk.Label("")
 
            master.resize(row + 1, 2)
light9/curvecalc/curve.py
Show inline comments
 
from __future__ import division
 
import glob, time, logging, ast
 
from bisect import bisect_left,bisect
 
import louie as dispatcher
 
from rdflib import Literal
 

	
 
from bcf2000 import BCF2000
 

	
 
log = logging.getLogger()
 
# todo: move to config, consolidate with ascoltami, musicPad, etc
 
introPad = 4
 
postPad = 4
 

	
 
class Curve(object):
 
    """curve does not know its name. see Curveset"""
 
    def __init__(self):
 
        self.points = [] # x-sorted list of (x,y)
 
        self._muted = False
 

	
 
    def __repr__(self):
 
        return "<%s (%s points)>" % (self.__class__.__name__, len(self.points))
 

	
 
    def muted():
 
        doc = "Whether to currently send levels (boolean, obviously)"
 
        def fget(self):
 
            return self._muted
 
        def fset(self, val):
 
            self._muted = val
 
            dispatcher.send('mute changed', sender=self)
 
@@ -122,49 +123,49 @@ def slope(p1,p2):
 
        return 0
 
    return (p2[1] - p1[1]) / (p2[0] - p1[0])
 

	
 

	
 
class Sliders(BCF2000):
 
    def __init__(self, cb, knobCallback, knobButtonCallback):
 
        BCF2000.__init__(self)
 
        self.cb = cb
 
        self.knobCallback = knobCallback
 
        self.knobButtonCallback = knobButtonCallback
 
    def valueIn(self, name, value):
 
        if name.startswith("slider"):
 
            self.cb(int(name[6:]), value / 127)
 
        if name.startswith("knob"):
 
            self.knobCallback(int(name[4:]), value / 127)
 
        if name.startswith("button-knob"):
 
            self.knobButtonCallback(int(name[11:]))
 

	
 
        
 
class Curveset(object):
 
    
 
    curves = None # curvename : curve
 
    def __init__(self, sliders=False):
 
        """sliders=True means support the hardware sliders"""
 
        self.curves = {} # name : Curve
 
        self.curves = {} # name (str) : Curve
 
        self.curveName = {} # reverse
 
        self.sliderCurve = {} # slider number (1 based) : curve name
 
        self.sliderNum = {} # reverse
 
        if sliders:
 
            self.sliders = Sliders(self.hw_slider_in, self.hw_knob_in, 
 
                                   self.hw_knob_button)
 
            dispatcher.connect(self.curvesToSliders, "curves to sliders")
 
            dispatcher.connect(self.knobOut, "knob out")
 
            self.lastSliderTime = {} # num : time
 
            self.sliderSuppressOutputUntil = {} # num : time
 
            self.sliderIgnoreInputUntil = {}
 
        else:
 
            self.sliders = None
 
        self.markers = Markers()
 

	
 
    def sorter(self, name):
 
        return (not name in ['music', 'smooth_music'], name)
 

	
 
    def load(self,basename, skipMusic=False):
 
        """find all files that look like basename-curvename and add
 
        curves with their contents
 

	
 
        This fires 'add_curve' dispatcher events to announce the new curves.
 
        """
 
@@ -173,76 +174,82 @@ class Curveset(object):
 
            curvename = filename[filename.rfind('-')+1:]
 
            if skipMusic and curvename in ['music', 'smooth_music']:
 
                continue
 
            c=Curve()
 
            c.load(filename)
 
            curvename = curvename.replace('-','_')
 
            self.add_curve(curvename,c)
 

	
 
        try:
 
            self.markers.load("%s.markers" % basename)
 
        except IOError:
 
            print "no marker file found"
 
            
 
    def save(self,basename):
 
        """writes a file for each curve with a name
 
        like basename-curvename"""
 
        for name,cur in self.curves.items():
 
            cur.save("%s-%s" % (basename,name))
 
        self.markers.save("%s.markers" % basename)
 

	
 
    def curveNamesInOrder(self):
 
        return sorted(self.curves.keys(), key=self.sorter)
 
            
 
    def add_curve(self,name,curve):
 
        if isinstance(name, Literal):
 
            name = str(name) 
 
        if name in self.curves:
 
            raise ValueError("can't add a second curve named %r" % name)
 
        self.curves[name] = curve
 
        self.curveName[curve] = name
 

	
 
        if self.sliders and name not in ['smooth_music', 'music']:
 
            num = len(self.sliderCurve) + 1
 
            if num <= 8:
 
                self.sliderCurve[num] = name
 
                self.sliderNum[name] = num
 
            else:
 
                num = None
 
        else:
 
            num = None
 
            
 
        dispatcher.send("add_curve", slider=num, knobEnabled=num is not None,
 
                        sender=self, name=name)
 

	
 
    def globalsdict(self):
 
        return self.curves.copy()
 
    
 
    def get_time_range(self):
 
        return 0, dispatcher.send("get max time")[0][1]
 

	
 
    def new_curve(self,name):
 
    def new_curve(self, name, renameIfExisting=True):
 
        if isinstance(name, Literal):
 
            name = str(name)
 
        if name=="":
 
            print "no name given"
 
            return
 
        if not renameIfExisting and name in self.curves:
 
            return
 
        while name in self.curves:
 
           name=name+"-1"
 

	
 
        c = Curve()
 
        s,e = self.get_time_range()
 
        c.points.extend([(s,0), (e,0)])
 
        self.add_curve(name,c)
 

	
 
    def hw_slider_in(self, num, value):
 
        try:
 
            curve = self.curves[self.sliderCurve[num]]
 
        except KeyError:
 
            return
 

	
 
        now = time.time()
 
        if now < self.sliderIgnoreInputUntil.get(num):
 
            return
 
        # don't make points too fast. This is the minimum spacing
 
        # between slider-generated points.
 
        self.sliderIgnoreInputUntil[num] = now + .1
 
        
 
        # don't push back on the slider for a little while, since the
 
        # user might be trying to slowly move it. This should be
 
        # bigger than the ignore time above.
light9/curvecalc/curvecalc.glade
Show inline comments
 
@@ -650,48 +650,49 @@
 
                <property name="expand">True</property>
 
                <property name="fill">True</property>
 
                <property name="position">1</property>
 
              </packing>
 
            </child>
 
          </object>
 
          <packing>
 
            <property name="expand">False</property>
 
            <property name="fill">False</property>
 
            <property name="padding">5</property>
 
            <property name="position">3</property>
 
          </packing>
 
        </child>
 
      </object>
 
    </child>
 
  </object>
 
  <object class="GtkAccelGroup" id="accelgroup1"/>
 
  <object class="GtkAdjustment" id="adjustment1">
 
    <property name="upper">100</property>
 
    <property name="step_increment">1</property>
 
    <property name="page_increment">10</property>
 
  </object>
 
  <object class="GtkTextBuffer" id="help">
 
    <property name="text">Mousewheel zoom; C-p play/pause music at mouse
 
Drag sub into curve area for new curve+subterm
 
Keys in a selected curve: C to collapse; R to rebuild broken canvas widget; 1..5 add point at time cursor; q,w,e,r,t,y set marker at time cursor
 
Curve point bindings: B1 drag point; C-B1 curve add point; S-B1 sketch points; 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; chan(name); curvename(t) eval curve</property>
 
  </object>
 
  <object class="GtkImage" id="image2">
 
    <property name="visible">True</property>
 
    <property name="can_focus">False</property>
 
    <property name="stock">gtk-refresh</property>
 
  </object>
 
  <object class="GtkListStore" id="liststore1"/>
 
  <object class="GtkDialog" id="newCurve">
 
    <property name="can_focus">False</property>
 
    <property name="border_width">5</property>
 
    <property name="title" translatable="yes">New curve</property>
 
    <property name="modal">True</property>
 
    <property name="window_position">mouse</property>
 
    <property name="type_hint">normal</property>
 
    <child internal-child="vbox">
 
      <object class="GtkVBox" id="dialog-vbox1">
 
        <property name="visible">True</property>
 
        <property name="can_focus">False</property>
 
        <property name="spacing">2</property>
 
        <child internal-child="action_area">
 
          <object class="GtkHButtonBox" id="dialog-action_area1">
light9/curvecalc/curveview.py
Show inline comments
 
from __future__ import division
 
import math, time, logging
 
import gtk, goocanvas
 
import louie as dispatcher
 
from rdflib import Literal
 
from light9.curvecalc.zoomcontrol import RegionZoom
 
from light9.curvecalc import cursors
 
from light9.curvecalc.curve import introPad, postPad
 
from light9.dmxchanedit import gradient
 

	
 
log = logging.getLogger()
 
print "curveview.py toplevel"
 
def vlen(v):
 
    return math.sqrt(v[0]*v[0] + v[1]*v[1])
 

	
 
def angle_between(base, p0, p1):
 
    p0 = p0[0] - base[0], p0[1] - base[1]
 
    p1 = p1[0] - base[0], p1[1] - base[1]
 
    p0 = [x/vlen(p0) for x in p0]
 
    p1 = [x/vlen(p1) for x in p1]
 
    dot = p0[0]*p1[0]+p0[1]*p1[1]
 
    dot = max(-1,min(1,dot))
 
    return math.degrees(math.acos(dot))
 

	
 
class Sketch:
 
    """a sketch motion on a curveview, with temporary points while you
 
    draw, and simplification when you release"""
 
    
 
    def __init__(self,curveview,ev):
 
@@ -1162,58 +1163,63 @@ class Curvesetview(object):
 
            return
 

	
 
        if event.string == 'c':
 
            r = self.row_under_mouse()
 
            # calling toggled() had no effect; don't know why
 
            r.collapsed.set_active(not r.collapsed.get_active())
 
 
 
    def row_under_mouse(self):
 
        x, y = self.curvesVBox.get_pointer()
 
        for r in self.allCurveRows:
 
            inRowX, inRowY = self.curvesVBox.translate_coordinates(r.box, x, y)
 
            _, _, w, h = r.box.get_allocation()
 
            if 0 <= inRowX < w and 0 <= inRowY < h:
 
                return r
 
        raise ValueError("no curveRow is under the mouse")
 

	
 
    def focus_entry(self):
 
        self.entry.focus()
 

	
 
    def new_curve(self, event):
 
        self.curveset.new_curve(self.newcurvename.get())
 
        self.newcurvename.set('')
 
        
 
    def add_curve(self, name, slider=None, knobEnabled=False):
 
        if isinstance(name, Literal):
 
            name = str(name)
 
        curve = self.curveset.curves[name]
 
        f = CurveRow(name, curve, self.curveset.markers,
 
                     slider, knobEnabled, self.zoomControl)
 
        self.curvesVBox.pack_start(f.box)
 
        f.box.show_all()
 
        self.allCurveRows.add(f)
 
        f.curveView.goLive()
 

	
 
    def row(self, name):
 
        if isinstance(name, Literal):
 
            name = str(name)
 
        matches = [r for r in self.allCurveRows if r.name == name]
 
        if not matches:
 
            raise ValueError("no curveRow named %r" % name)
 
            raise ValueError("no curveRow named %r. only %s" %
 
                             (name, [r.name for r in self.allCurveRows]))
 
        return matches[0]
 

	
 
    def goLive(self):
 
        """for startup performance, none of the curves redraw
 
        themselves until this is called once (and then they're normal)"""
 
        
 
        for cr in self.allCurveRows:
 
            cr.curveView.goLive()
 

	
 
    def onDelete(self):
 
        for r in self.allCurveRows:
 
            r.onDelete()
 

	
 
    def collapseAll(self):
 
        for r in self.allCurveRows:
 
            r.collapsed.set_active(True)
 

	
 
    def collapseNone(self):
 
        for r in self.allCurveRows:
 
            r.collapsed.set_active(False)
 

	
 
        
0 comments (0 inline, 0 general)