Changeset - 295b867fd810
[Not reviewed]
default
0 8 0
drewp@bigasterisk.com - 12 years ago 2013-01-15 21:02:08
drewp@bigasterisk.com
just whitespace (hopefully)
Ignore-this: a364fab649d9e795f703cfe794f07ca6
3 files changed with 5 insertions and 1 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -357,225 +357,228 @@ class KeyboardComposer(Frame, SubClient)
 
        self.keyhints.pack_configure(before=row)
 

	
 
        if not fromGraph:
 
            self.graph.patchObject(self.session, self.session, L9['currentRow'],
 
                                   Literal(self.current_row))
 

	
 
        for col in range(1, 9):
 
            try:
 
                subbox = self.slider_table[(self.current_row, col - 1)]
 
                self.sliders.valueOut("button-upper%d" % col, True)
 
            except KeyError:
 
                # unfilled bottom row has holes (plus rows with incomplete
 
                # groups
 
                self.sliders.valueOut("button-upper%d" % col, False)
 
                self.sliders.valueOut("slider%d" % col, 0)
 
                continue
 
            self.send_to_hw(subbox.name, col)
 
            
 
    def got_nudger(self, number, direction, full=0):
 
        try:
 
            subbox = self.slider_table[(self.current_row, number)]
 
        except KeyError:
 
            return
 

	
 
        if direction == 'up':
 
            if full:
 
                subbox.scale.fade(1)
 
            else:
 
                subbox.scale.increase()
 
        else:
 
            if full:
 
                subbox.scale.fade(0)
 
            else:
 
                subbox.scale.decrease()
 

	
 
    def hw_slider_moved(self, col, value):
 
        value = int(value * 100) / 100
 
        try:
 
            subbox = self.slider_table[(self.current_row, col)]
 
        except KeyError:
 
            return # no slider assigned at that column
 
        subbox.scale.set(value)
 

	
 
    def send_to_hw(self, subUri, hwNum):
 
        if isinstance(self.sliders, DummySliders):
 
            return
 
            
 
        v = round(127 * self.slider_vars[subUri].get())
 
        chan = "slider%s" % hwNum
 
        
 
        # workaround for some rounding issue, where we receive one
 
        # value and then decide to send back a value that's one step
 
        # lower.  -5 is a fallback for having no last value.  hopefully
 
        # we won't really see it
 
        if abs(v - self.sliders.lastValue.get(chan, -5)) <= 1:
 
            return
 
        self.sliders.valueOut(chan, v)
 
            
 
    def make_row(self):
 
        row = Frame(self, bd=2, bg='black')
 
        row.pack(expand=1, fill=BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
        return row
 

	
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 

	
 
    def get_levels(self):
 
        return dict([(uri, box.slider_var.get()) 
 
            for uri, box in self.subbox.items()])
 

	
 
    def get_levels_as_sub(self):
 
        scaledsubs = [self.submasters.get_sub_by_uri(sub) * level
 
            for sub, level in self.get_levels().items() if level > 0.0]
 
        maxes = sub_maxes(*scaledsubs)
 
        return maxes
 

	
 
    def save_current_stage(self, subname):
 
        log.info("saving current levels as %s", subname)
 
        sub = self.get_levels_as_sub()
 
        sub.name = subname
 
        sub.temporary = 0
 
        sub.save()
 

	
 
    def send_frequent_updates(self):
 
        """called when we get a fade -- send events as quickly as possible"""
 
        if time.time() <= self.stop_frequent_update_time:
 
            self.send_levels()
 
            self.after(10, self.send_frequent_updates)
 

	
 
    def alltozero(self):
 
        for uri, subbox in self.subbox.items():
 
            if subbox.scale.scale_var.get() != 0:
 
                subbox.scale.fade(value=0.0, length=0)
 

	
 
# move to web lib
 
def postArgGetter(request):
 
    """return a function that takes arg names and returns string
 
    values. Supports args encoded in the url or in postdata. No
 
    support for repeated args."""
 
    # this is something nevow normally does for me
 
    request.content.seek(0)
 
    fields = cgi.FieldStorage(request.content, request.received_headers,
 
                              environ={'REQUEST_METHOD': 'POST'})
 
    def getArg(n):
 
        try:
 
            return request.args[n][0]
 
        except KeyError:
 
            return fields[n].value
 
    return getArg
 

	
 

	
 
class LevelServerHttp(resource.Resource):
 
    isLeaf = True
 
    def __init__(self,name_to_subbox):
 
        self.name_to_subbox = name_to_subbox
 

	
 
    def render_POST(self, request):
 
        arg = postArgGetter(request)
 
        
 
        if request.path == '/fadesub':
 
            # fadesub?subname=scoop&level=0&secs=.2
 
            self.name_to_subbox[arg('subname')].scale.fade(
 
                float(arg('level')),
 
                float(arg('secs')))
 
            return "set %s to %s" % (arg('subname'), arg('level'))
 
        else:
 
            raise NotImplementedError(repr(request))
 

	
 
class Sliders(BCF2000):
 
    def __init__(self, kc):
 
        devices = ['/dev/snd/midiC1D0', '/dev/snd/midiC2D0', '/dev/snd/midiC3D0']
 
        for dev in devices:
 
            try:
 
                BCF2000.__init__(self, dev=dev)
 
            except IOError, e:
 
                if dev is devices[-1]:
 
                    raise
 
            else:
 
                break
 

	
 
        self.kc = kc
 
    def valueIn(self, name, value):
 
        kc = self.kc
 
        if name.startswith("slider"):
 
            kc.hw_slider_moved(int(name[6:]) - 1, value / 127)
 
        elif name.startswith("button-upper"):
 
            kc.change_row(kc.current_row)
 
        elif name.startswith("button-lower"):
 
            col = int(name[12:]) - 1
 
            self.valueOut(name, 0)
 
            try:
 
                tkslider = kc.slider_table[(kc.current_row, col)]
 
            except KeyError:
 
                return
 

	
 
            slider_var = tkslider.slider_var
 
            if slider_var.get() == 1:
 
                slider_var.set(0)
 
            else:
 
                slider_var.set(1)
 
        elif name.startswith("button-corner"):
 
            button_num = int(name[13:]) - 1
 
            if button_num == 1:
 
                diff = -1
 
            elif button_num == 3:
 
                diff = 1
 
            else:
 
                return
 

	
 
            kc.change_row(kc.current_row + diff)
 
            self.valueOut(name, 0)
 

	
 
if __name__ == "__main__":
 
    parser = OptionParser()
 
    parser.add_option('--no-sliders', action='store_true',
 
                      help="don't attach to hardware sliders")
 
    clientsession.add_option(parser)
 
    parser.add_option('-v', action='store_true', help="log info level")
 
    opts, args = parser.parse_args()
 

	
 
    logging.basicConfig(level=logging.INFO if opts.v else logging.WARN)
 
    log = logging.getLogger('keyboardcomposer')
 

	
 
    graph = SyncedGraph("keyboardcomposer")
 

	
 
    # i think this also needs delayed start (like subcomposer has), to have a valid graph
 
    # before setting any stuff from the ui
 
    
 
    root = Tk()
 
    initTkdnd(root.tk, 'tkdnd/trunk/')
 

	
 
    session = clientsession.getUri('keyboardcomposer', opts)
 

	
 
    tl = toplevelat("Keyboard Composer - %s" % opts.session,
 
                    existingtoplevel=root, graph=graph, session=session)
 

	
 
    kc = KeyboardComposer(tl, graph, session,
 
                          hw_sliders=not opts.no_sliders)
 
    kc.pack(fill=BOTH, expand=1)
 

	
 
    for helpline in ["Bindings: B3 mute; C-l edit levels in subcomposer"]:
 
        tk.Label(root,text=helpline, font="Helvetica -12 italic",
 
                 anchor='w').pack(side='top',fill='x')
 

	
 
    if 0: # needs fixing, or maybe it's obsolete because other progs can just patch the rdf graph
 
        import twisted.internet
 
        try:
 
            reactor.listenTCP(networking.keyboardComposer.port,
 
                              server.Site(LevelServerHttp(kc.name_to_subbox)))
 
        except twisted.internet.error.CannotListenError, e:
 
            log.warn("Can't (and won't!) start level server:")
 
            log.warn(e)
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
    
 
    tksupport.install(root,ms=10)
 

	
 

	
 
#    prof.watchPoint("/usr/lib/python2.4/site-packages/rdflib-2.3.3-py2.4-linux-i686.egg/rdflib/Graph.py", 615)
 

	
 
    prof.run(reactor.run, profile=False)
bin/rdfdb
Show inline comments
 
@@ -116,280 +116,280 @@ from light9 import networking, showconfi
 
from rdflib import ConjunctiveGraph, URIRef, Graph
 
from light9.rdfdb.graphfile import GraphFile
 
from light9.rdfdb.patch import Patch, ALLSTMTS
 
from light9.rdfdb.rdflibpatch import patchQuads
 
from light9.rdfdb import syncedgraph
 

	
 
from twisted.internet.inotify import INotify
 
logging.basicConfig(level=logging.DEBUG)
 
log = logging.getLogger()
 

	
 
try:
 
    import sys
 
    sys.path.append("../homeauto/lib")
 
    from cycloneerr import PrettyErrorHandler
 
except ImportError:
 
    class PrettyErrorHandler(object):
 
        pass
 

	
 
class Client(object):
 
    """
 
    one of our syncedgraph clients
 
    """
 
    def __init__(self, updateUri, label, db):
 
        self.db = db
 
        self.label = label
 
        self.updateUri = updateUri
 
        self.sendAll()
 

	
 
    def __repr__(self):
 
        return "<%s client at %s>" % (self.label, self.updateUri)
 

	
 
    def sendAll(self):
 
        """send the client the whole graph contents"""
 
        log.info("sending all graphs to %s at %s" %
 
                 (self.label, self.updateUri))
 
        self.sendPatch(Patch(
 
            addQuads=self.db.graph.quads(ALLSTMTS),
 
            delQuads=[]))
 
        
 
    def sendPatch(self, p):
 
        return syncedgraph.sendPatch(self.updateUri, p)
 

	
 
class Db(object):
 
    """
 
    the master graph, all the connected clients, all the files we're watching
 
    """
 
    def __init__(self):
 
        # files from cwd become uris starting with this. *should* be
 
        # building uris from the show uri in $LIGHT9_SHOW/URI
 
        # instead. Who wants to keep their data in the same dir tree
 
        # as the source code?!
 
        self.topUri = URIRef("http://light9.bigasterisk.com/")
 

	
 
        self.clients = []
 
        self.graph = ConjunctiveGraph()
 

	
 
        self.notifier = INotify()
 
        self.notifier.startReading()
 
        self.graphFiles = {} # context uri : GraphFile
 

	
 
        self.findAndLoadFiles()
 

	
 
    def findAndLoadFiles(self):
 
        self.initialLoad = True
 
        try:
 
            dirs = [
 
                "show/dance2012/sessions",
 
                "show/dance2012/subs",
 
                "show/dance2012/subterms",
 
                ]
 

	
 
            for topdir in dirs:
 
                for dirpath, dirnames, filenames in os.walk(topdir):
 
                    for base in filenames:
 
                        self.watchFile(os.path.join(dirpath, base))
 
                # todo: also notice new files in this dir
 

	
 
            self.watchFile("show/dance2012/config.n3")
 
            self.watchFile("show/dance2012/patch.n3")
 
        finally:
 
            self.initialLoad = False
 
            
 
        self.summarizeToLog()
 

	
 
    def uriFromFile(self, filename):
 
        if filename.endswith('.n3'):
 
            # some legacy files don't end with n3. when we write them
 
            # back this might not go so well
 
            filename = filename[:-len('.n3')]
 
        return URIRef(self.topUri + filename)
 
        
 
    def fileForUri(self, ctx):
 
        if not ctx.startswith(self.topUri):
 
            raise ValueError("don't know what filename to use for %s" % ctx)
 
        return ctx[len(self.topUri):] + ".n3"
 

	
 
    def watchFile(self, inFile):
 
        ctx = self.uriFromFile(inFile)
 
        gf = GraphFile(self.notifier, inFile, ctx, self.patch, self.getSubgraph)
 
        self.graphFiles[ctx] = gf
 
        gf.reread()
 
        
 
    def patch(self, p, dueToFileChange=False):
 
        """
 
        apply this patch to the master graph then notify everyone about it
 

	
 
        dueToFileChange if this is a patch describing an edit we read
 
        *from* the file (such that we shouldn't write it back to the file)
 

	
 
        if p has a senderUpdateUri attribute, we won't send this patch
 
        back to the sender with that updateUri
 
        """
 
        ctx = p.getContext()
 
        log.info("patching graph %s -%d +%d" % (
 
            ctx, len(p.delQuads), len(p.addQuads)))
 

	
 
        patchQuads(self.graph, p.delQuads, p.addQuads, perfect=True)
 
        senderUpdateUri = getattr(p, 'senderUpdateUri', None)
 
        #if not self.initialLoad:
 
        #    self.summarizeToLog()
 
        for c in self.clients:
 
            if c.updateUri == senderUpdateUri:
 
                # this client has self-applied the patch already
 
                continue
 
            d = c.sendPatch(p)
 
            d.addErrback(self.clientErrored, c)
 
        if not dueToFileChange:
 
            self.dirtyFiles([ctx])
 
        sendToLiveClients(asJson=p.jsonRepr)
 

	
 
    def dirtyFiles(self, ctxs):
 
        """mark dirty the files that we watch in these contexts.
 

	
 
        the ctx might not be a file that we already read; it might be
 
        for a new file we have to create, or it might be for a
 
        transient context that we're not going to save
 

	
 
        if it's a ctx with no file, error
 
        """
 
        for ctx in ctxs:
 
            g = self.getSubgraph(ctx)
 

	
 
            if ctx not in self.graphFiles:
 
                outFile = self.fileForUri(ctx)
 
                self.graphFiles[ctx] = GraphFile(self.notifier, outFile, ctx,
 
                                                 self.patch, self.getSubgraph)
 
            
 
            self.graphFiles[ctx].dirty(g)
 

	
 
    def clientErrored(self, err, c):
 
        err.trap(twisted.internet.error.ConnectError)
 
        log.info("connection error- dropping client %r" % c)
 
        self.clients.remove(c)
 
        self.sendClientsToAllLivePages()        
 

	
 
    def summarizeToLog(self):
 
        log.info("contexts in graph (%s total stmts):" % len(self.graph))
 
        for c in self.graph.contexts():
 
            log.info("  %s: %s statements" %
 
                     (c.identifier, len(self.getSubgraph(c.identifier))))
 

	
 
    def getSubgraph(self, uri):
 
        """
 
        this is meant to return a live view of the given subgraph, but
 
        if i'm still working around an rdflib bug, it might return a
 
        copy
 

	
 
        and it's returning triples, but I think quads would be better
 
        """
 
        # this is returning an empty Graph :(
 
        #return self.graph.get_context(uri)
 

	
 
        g = Graph()
 
        for s in self.graph.triples(ALLSTMTS, uri):
 
            g.add(s)
 
        return g
 
    
 
    def addClient(self, updateUri, label):
 
        [self.clients.remove(c)
 
         for c in self.clients if c.updateUri == updateUri]
 

	
 
        log.info("new client %s at %s" % (label, updateUri))
 
        self.clients.append(Client(updateUri, label, self))
 
        self.sendClientsToAllLivePages()
 

	
 
    def sendClientsToAllLivePages(self):
 
        sendToLiveClients({"clients":[
 
            dict(updateUri=c.updateUri, label=c.label)
 
            for c in self.clients]})        
 

	
 
class GraphResource(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        pass
 
        self.write(self.settings.db.graph.serialize(format='n3'))
 
    
 
class Patches(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def __init__(self, *args, **kw):
 
        cyclone.web.RequestHandler.__init__(self, *args, **kw)
 
        p = syncedgraph.makePatchEndpointPutMethod(self.settings.db.patch)
 
        self.put = lambda: p(self)
 

	
 
    def get(self):
 
        pass
 

	
 
class GraphClients(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def get(self):
 
        pass
 
    
 
    def post(self):
 
        upd = self.get_argument("clientUpdate")
 
        try:
 
            self.settings.db.addClient(upd, self.get_argument("label"))
 
        except:
 
            import traceback
 
            traceback.print_exc()
 
            raise
 

	
 
liveClients = set()
 
def sendToLiveClients(d=None, asJson=None):
 
    j = asJson or json.dumps(d)
 
    for c in liveClients:
 
        c.sendMessage(j)
 

	
 
class Live(cyclone.websocket.WebSocketHandler):
 
    
 
    def connectionMade(self, *args, **kwargs):
 
        log.info("websocket opened")
 
        liveClients.add(self)
 
        self.settings.db.sendClientsToAllLivePages()
 

	
 
    def connectionLost(self, reason):
 
        log.info("websocket closed")
 
        liveClients.remove(self)
 

	
 
    def messageReceived(self, message):
 
        log.info("got message %s" % message)
 
        self.sendMessage(message)
 

	
 
class NoExts(cyclone.web.StaticFileHandler):
 
    # .xhtml pages can be get() without .xhtml on them
 
    def get(self, path, *args, **kw):
 
        if path and '.' not in path:
 
            path = path + ".xhtml"
 
        cyclone.web.StaticFileHandler.get(self, path, *args, **kw)
 

	
 
if __name__ == "__main__":
 
    logging.basicConfig()
 
    log = logging.getLogger()
 

	
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
                      help="logging.DEBUG")
 
    (options, args) = parser.parse_args()
 

	
 
    log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 

	
 
    db = Db()
 
    
 
    from twisted.python import log as twlog
 
    twlog.startLogging(sys.stdout)
 
            
 
    port = 8051
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/live', Live),
 
        (r'/graph', GraphResource),
 
        (r'/patches', Patches),
 
        (r'/graphClients', GraphClients),
 

	
 
        (r'/(.*)', NoExts,
 
         {"path" : "light9/rdfdb/web",
 
          "default_filename" : "index.xhtml"}),
 

	
 
        ], debug=True, db=db))
 
    log.info("serving on %s" % port)
 
    prof.run(reactor.run, profile=False)
light9/uihelpers.py
Show inline comments
 
"""all the tiny tk helper functions"""
 

	
 
from __future__ import nested_scopes
 
#from Tkinter import Button
 
from rdflib import Literal
 
from Tix import Button, Toplevel, Tk, IntVar, Entry, DoubleVar
 
from light9.namespaces import L9
 

	
 
windowlocations = {
 
    'sub' : '425x738+00+00',
 
    'console' : '168x24+848+000',
 
    'leveldisplay' : '144x340+870+400',
 
    'cuefader' : '314x212+546+741',
 
    'effect' : '24x24+0963+338',
 
    'stage' : '823x683+37+030',
 
    'scenes' : '504x198+462+12',
 
}
 

	
 
def bindkeys(root,key, func):
 
    root.bind(key, func)
 
    for w in root.winfo_children():
 
        w.bind(key, func)
 

	
 

	
 
def toplevel_savegeometry(tl,name):
 
    try:
 
        geo = tl.geometry()
 
        if not geo.startswith("1x1"):
 
            f=open(".light9-window-geometry-%s" % name.replace(' ','_'),'w')
 
            f.write(tl.geometry())
 
        # else the window never got mapped
 
    except Exception, e:
 
        # it's ok if there's no saved geometry
 
        pass
 

	
 
def toplevelat(name, existingtoplevel=None, graph=None, session=None):
 
    tl = existingtoplevel or Toplevel()
 
    tl.title(name)
 

	
 
    lastSaved = [None]
 
    setOnce = [False]
 
    def setPosFromGraphOnce():
 
        """
 
        the graph is probably initially empty, but as soon as it gives
 
        us one window position, we stop reading them
 
        """
 
        if setOnce[0]:
 
            return
 
        geo = graph.value(session, L9.windowGeometry)
 

	
 
        if geo is not None and geo != lastSaved[0]:
 
            setOnce[0] = True
 
            tl.geometry(geo)
 
            lastSaved[0] = geo
 

	
 
    def savePos():
 
        geo = tl.geometry()
 

	
 
        # todo: need a way to filter out the startup window sizes that
 
        # weren't set by the user
 
        if geo.startswith("1x1") or geo.startswith(("378x85", "378x86")):
 
            return
 
        if geo == lastSaved[0]:
 
            return
 
        lastSaved[0] = geo
 
        graph.patchObject(session, session, L9.windowGeometry, Literal(geo))
 

	
 
    if graph is not None and session is not None:
 
        graph.addHandler(setPosFromGraphOnce)
 

	
 
    if name in windowlocations:
 
        tl.geometry(positionOnCurrentDesktop(windowlocations[name]))
 

	
 
    if graph is not None:
 
        tl._toplevelat_funcid = tl.bind("<Configure>",lambda ev,tl=tl,name=name: savePos())
 

	
 
    return tl
 

	
 
def positionOnCurrentDesktop(xform, screenWidth=1920, screenHeight=1440):
 
    size, x, y = xform.split('+')
 
    x = int(x) % screenWidth
 
    y = int(y) % screenHeight
 
    return "%s+%s+%s" % (size, x, y)
 
    
 

	
 
def toggle_slider(s):
 
    if s.get() == 0:
 
        s.set(100)
 
    else:
 
        s.set(0)
 

	
 
# for lambda callbacks    
 
def printout(t):
 
    print t
 

	
 
def printevent(ev):
 
    for k in dir(ev):
 
        if not k.startswith('__'):
 
            print k,getattr(ev,k)
 
    print ""
 
    
 
def eventtoparent(ev,sequence):
 
    "passes an event to the parent, screws up TixComboBoxes"
 

	
 
    wid_class = str(ev.widget.__class__)
 
    if wid_class == 'Tix.ComboBox' or wid_class == 'Tix.TixSubWidget':
 
        return
 

	
 
    evdict={}
 
    for x in ['state', 'time', 'y', 'x', 'serial']:
 
        evdict[x]=getattr(ev,x)
 
#    evdict['button']=ev.num
 
    par=ev.widget.winfo_parent()
 
    if par!=".":
 
        ev.widget.nametowidget(par).event_generate(sequence,**evdict)
 
    #else the event made it all the way to the top, unhandled
 

	
 
def colorlabel(label):
 
    """color a label based on its own text"""
 
    txt=label['text'] or "0"
 
    lev=float(txt)/100
 
    low=(80,80,180)
 
    high=(255,55,050)
 
    out = [int(l+lev*(h-l)) for h,l in zip(high,low)]
 
    col="#%02X%02X%02X" % tuple(out)
 
    label.config(bg=col)
 

	
 
# TODO: get everyone to use this
 
def colorfade(low, high, percent):
 
    '''not foolproof.  make sure 0 < percent < 1'''
 
    out = [int(l+percent*(h-l)) for h,l in zip(high,low)]
 
    col="#%02X%02X%02X" % tuple(out)
 
    return col
 

	
 
def colortotuple(anytkobj, colorname):
 
    'pass any tk object and a color name, like "yellow"'
 
    rgb = anytkobj.winfo_rgb(colorname)
 
    return [v / 256 for v in rgb]
 

	
 
class Togglebutton(Button):
 
    """works like a single radiobutton, but it's a button so the
 
    label's on the button face, not to the side. the optional command
 
    callback is called on button set, not on unset. takes a variable
 
    just like a checkbutton"""
 
    def __init__(self,parent,variable=None,command=None,downcolor='red',**kw):
 

	
 
        self.oldcommand = command
 
        Button.__init__(self,parent,command=self.invoke,**kw)
 

	
 
        self._origbkg = self.cget('bg')
 
        self.downcolor = downcolor
 

	
 
        self._variable = variable
 
        if self._variable:
 
            self._variable.trace('w',self._varchanged)
 
            self._setstate(self._variable.get())
 
        else:
 
            self._setstate(0)
 

	
 
        self.bind("<Return>",self.invoke)
 
        self.bind("<1>",self.invoke)
 
        self.bind("<space>",self.invoke)
 

	
 
    def _varchanged(self,*args):
 
        self._setstate(self._variable.get())
 
        
 
    def invoke(self,*ev):
 
        if self._variable:
 
            self._variable.set(not self.state)
 
        else:
 
            self._setstate(not self.state)
 
        
 
        if self.oldcommand and self.state: # call command only when state goes to 1
 
            self.oldcommand()
 
        return "break"
 

	
 
    def _setstate(self,newstate):
 
        self.state = newstate
 
        if newstate: # set
 
            self.config(bg=self.downcolor,relief='sunken')
 
        else: # unset
 
            self.config(bg=self._origbkg,relief='raised')
 
        return "break"
 

	
 

	
 
class FancyDoubleVar(DoubleVar):
 
    def __init__(self,master=None):
 
        DoubleVar.__init__(self,master)
 
        self.callbacklist = {} # cbname : mode
 
        self.namedtraces = {} # name : cbname
 
    def trace_variable(self,mode,callback):
 
        """Define a trace callback for the variable.
 

	
 
        MODE is one of "r", "w", "u" for read, write, undefine.
 
        CALLBACK must be a function which is called when
 
        the variable is read, written or undefined.
 

	
 
        Return the name of the callback.
 
        """
 
        cbname = self._master._register(callback)
 
        self._tk.call("trace", "variable", self._name, mode, cbname)
 
        
 
        # we build a list of the trace callbacks (the py functrions and the tcl functionnames)
 
        self.callbacklist[cbname] = mode
 
#        print "added trace:",callback,cbname
 
        
 
        return cbname
 
    trace=trace_variable
 
    def disable_traces(self):
 
        for cb,mode in self.callbacklist.items():
 
#            DoubleVar.trace_vdelete(self,v[0],k)
 
            self._tk.call("trace", "vdelete", self._name, mode,cb)
 
            # but no master delete!
 
            
 
    def recreate_traces(self):
 
        for cb,mode in self.callbacklist.items():
 
#            self.trace_variable(v[0],v[1])
 
            self._tk.call("trace", "variable", self._name, mode,cb)
 

	
 
    def trace_named(self, name, callback):
 
        if name in self.namedtraces:
 
            print "FancyDoubleVar: already had a trace named %s - replacing it" % name
 
            self.delete_named(name)
 

	
 
        cbname = self.trace_variable('w',callback) # this will register in self.callbacklist too
 
        
 
        self.namedtraces[name] = cbname
 
        return cbname
 
        
 
    def delete_named(self, name):
 
        if name in self.namedtraces:
 

	
 
            cbname = self.namedtraces[name]
 
            
 
            self.trace_vdelete('w',cbname)
 
	    #self._tk.call("trace","vdelete",self._name,'w',cbname)
 
            print "FancyDoubleVar: successfully deleted trace named %s" % name
 
        else:
 
            print "FancyDoubleVar: attempted to delete named %s which wasn't set to any function" % name
 

	
 
def get_selection(listbox):
 
    'Given a listbox, returns first selection as integer'
 
    selection = int(listbox.curselection()[0]) # blech
 
    return selection
 

	
 
if __name__=='__main__':
 
    root=Tk()
 
    root.tk_focusFollowsMouse()
 
    iv=IntVar()
 
    def cb():
0 comments (0 inline, 0 general)