diff light8/ExtSliderMapper.py @ 0:45b12307c695

Initial revision
author drewp
date Wed, 03 Jul 2002 09:37:57 +0000
parents
children 5f0c6bc8e9de
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light8/ExtSliderMapper.py	Wed Jul 03 09:37:57 2002 +0000
@@ -0,0 +1,353 @@
+""" The External Slider Mapping widget determines which pots map to which
+submasters.  It tells you the status of each mapping and saves and loads
+presets.  The show is relying on this module!  Do not lose it!
+
+FUQ (frequently unasked question(s))
+
+1. What's with all the *args?
+
+It lets functions take any number of arguments and throw them away.
+Callbacks do this, and we typically don't care about what they have to say. """
+
+from Tix import *
+from uihelpers import FancyDoubleVar, get_selection
+
+stdfont = ('Arial', 8)
+colors = ('lightBlue', 'lightPink', 'lightGreen', 'aquamarine1')
+
+class SliderMapping:
+    def __init__(self, color, default='disconnected', synced=0, 
+                 extinputlevel=0, sublevel=0):
+        self.color = color
+        self.subname = StringVar() # name of submaster we're connected to
+        self.subname.set(default)
+        self.sublevel = DoubleVar() # scalelevel variable of that submaster
+        # self.sublevel = FancyDoubleVar() # scalelevel variable of that submaster
+        self.sublevel.set(sublevel)
+        self.synced = BooleanVar() # currently synced
+        self.synced.set(synced)
+        self.extlevel = DoubleVar() # external slider's input
+        self.extlevel.set(extinputlevel)
+        self.widgets = [] # list of widgets drawn
+        self.sublabel = None # the label which represents a sub level.  
+                             # we hold on to it so we can change its 
+                             # textvariable
+        self.statuslabel = None # tells us sync status
+        self.lastbgcolor = None # last background color drawn to avoid 
+                                # unnecessary redraws
+        self.subnames = [] # we need to keep track of this for idiotic reasons
+    def sync(self, *args):
+        self.synced.set(1)
+        self.color_bg()
+    def unsync(self, *args):
+        self.synced.set(0)
+        self.color_bg()
+    def issynced(self):
+        return self.synced.get()
+    def disconnect(self, *args):
+        self.set_subname('disconnected') # a bit hack-like
+        # self.sublevel.delete_named('sync')
+        '''
+        try:
+            if self.sublevel.unsync_trace_cbname is not None:
+                # self.sublevel.trace_vdelete('w', 
+                    # self.sublevel.unsync_trace_cbname)
+                self.sublevel._tk.call('trace', 'vdelete', self.sublevel._name, 
+                    'w', self.sublevel.unsync_trace_cbname)
+                self.sublevel.unsync_trace_cbname = None
+        except AttributeError:
+            pass
+        '''
+            
+        self.sublabel.configure(text="N/A")
+        self.color_bg()
+    def isdisconnected(self):
+        return self.subname.get() == 'disconnected' # a bit more hack-like
+    def check_synced(self, *args):
+        'If external level is near than the sublevel, it synces'
+        if self.isdisconnected(): 
+            self.unsync()
+            return
+
+        if abs(self.extlevel.get() - self.sublevel.get()) <= 0.02:
+            self.sync()
+    def changed_extinput(self, newlevel):
+        'When a new external level is received, this incorporates it'
+        self.extlevel.set(newlevel)
+        self.check_synced()
+        self.color_bg()
+    def set_subname(self, newname):
+        try:
+            self.listbox.listbox.select_clear(0, END)
+        except IndexError:
+            pass
+        try:
+            newindex = self.subnames.index(newname)
+            self.listbox.listbox.select_set(newindex)
+            self.listbox.listbox.see(newindex)
+        except ValueError:
+            pass
+
+        self.subname.set(newname)
+        self.unsync()
+        self.color_bg()
+    def color_bg(self):
+        if self.widgets:
+            if self.isdisconnected():
+                color = 'honeyDew4'
+            # stupid hack
+            # elif abs(self.extlevel.get() - self.sublevel.get()) <= 0.02:
+            elif self.issynced():
+                color = 'honeyDew2'
+            else: # unsynced
+                color = 'red'
+
+            if self.statuslabel: # more stupid hackery
+                if color == 'honeyDew2': # connected
+                    self.statuslabel['text'] = 'Sync'
+                elif self.extlevel.get() < self.sublevel.get():
+                    self.statuslabel['text'] = 'No sync (go up)'
+                else:
+                    self.statuslabel['text'] = 'No sync (go down)'
+
+            # print "color", color, "lastbgcolor", self.lastbgcolor
+            if self.lastbgcolor == color: return
+            for widget in self.widgets:
+                widget.configure(bg=color)
+            self.lastbgcolor = color
+    def set_sublevel_var(self, newvar):
+        'newvar is one of the variables in scalelevels'
+
+        if newvar is not self.sublevel:
+            # self.sublevel.delete_named('sync')
+            self.sublevel = newvar
+            self.sublabel.configure(textvariable=newvar)
+            # self.sublevel.trace_named('sync', lambda *args: self.unsync(*args))
+            '''
+            try:
+                if self.sublevel.unsync_trace_cbname is not None:
+                    # remove an old trace
+                    self.sublevel.trace_vdelete('w',
+                        self.sublevel.unsync_trace_cbname)
+            except AttributeError:
+                pass # it didn't have one
+
+            self.sublevel = newvar
+            self.sublevel.unsync_trace_cbname = self.sublevel.trace('w', 
+                self.unsync)
+            '''
+
+        # if self.sublabel:
+            # self.sublabel.configure(textvariable=newvar)
+        self.check_synced()
+    def get_mapping(self):
+        'Get name of submaster currently mapped'
+        return self.subname.get()
+    def get_level_pair(self):
+        'Returns suitable output for ExtSliderMapper.get_levels()'
+        return (self.subname.get(), self.extlevel.get())
+    def listbox_cb(self, *args):
+        selection = get_selection(self.listbox.listbox)
+        self.disconnect()
+        self.subname.set(self.subnames[selection])
+        self.listbox.listbox.select_set(selection)
+    def draw_interface(self, master, subnames):
+        'Draw interface into master, given a list of submaster names'
+        self.subnames = subnames
+        frame = Frame(master, bg='black')
+        self.listbox = ScrolledListBox(frame, scrollbar='y', bg='black')
+        self.listbox.listbox.bind("<<ListboxSelect>>", self.listbox_cb, add=1)
+        self.listbox.listbox.configure(font=stdfont, exportselection=0, 
+            selectmode=BROWSE, bg='black', fg='white', 
+            selectbackground=self.color)
+        self.listbox.vsb.configure(troughcolor=self.color)
+        # self.listbox.listbox.insert(END, "disconnected")
+        for s in subnames:
+            self.listbox.listbox.insert(END, s)
+        statframe = Frame(frame, bg='black')
+
+        self.statuslabel = Label(statframe, text="No sync", width=15, 
+            font=stdfont)
+        self.statuslabel.grid(columnspan=2, sticky=W)
+        ilabel = Label(statframe, text="Input", fg='blue', font=stdfont)
+        ilabel.grid(row=1, sticky=W)
+        extlabel = Label(statframe, textvariable=self.extlevel, width=5, 
+            font=stdfont)
+        extlabel.grid(row=1, column=1)
+        rlabel = Label(statframe, text="Real", font=stdfont)
+        rlabel.grid(row=2, sticky=W)
+        self.sublabel = Label(statframe, text="N/A", width=5, font=stdfont)
+        self.sublabel.grid(row=2, column=1)
+        disc_button = Button(statframe, text="Disconnect", 
+            command=self.disconnect, padx=0, pady=0, font=stdfont)
+        disc_button.grid(row=3, columnspan=2)
+        statframe.pack(side=BOTTOM, expand=1, fill=BOTH)
+        self.listbox.pack(expand=1, fill=BOTH)
+        frame.pack(side=LEFT, expand=1, fill=BOTH)
+
+        self.widgets = [frame, self.listbox, statframe, self.statuslabel, 
+                        ilabel, extlabel, rlabel, self.sublabel, disc_button]
+
+class ExtSliderMapper(Frame):
+    def __init__(self, parent, sliderlevels, sliderinput, 
+                 lightboard, filename='slidermapping', numsliders=4):
+        'Slider levels is scalelevels, sliderinput is an ExternalInput object'
+        Frame.__init__(self, parent, bg='black')
+        self.parent = parent
+        self.sliderlevels = sliderlevels
+        self.sliderinput = sliderinput
+        self.filename = filename
+        self.numsliders = numsliders
+        self.lightboard = lightboard
+        self.file = None
+
+        # don't call setup, let them do that when scalelevels is created
+    def setup(self):
+        self.subnames = self.sliderlevels.keys()
+        self.subnames.sort()
+        self.presets = {}
+        self.load_presets()
+
+        self.current_preset = StringVar() # name of current preset
+        self.current_mappings = []
+        for i, color in zip(range(self.numsliders), colors):
+            self.current_mappings.append(SliderMapping(color))
+
+        self.draw_interface()
+    def load_presets(self, *args):
+        self.presets = {}
+        self.file = open(self.filename, 'r')
+        lines = self.file.readlines()
+        for l in lines:
+            tokens = l[:-1].split('\t')
+            name = tokens.pop(0)
+            self.presets[name] = tokens
+        self.file.close()
+        if args: # called from callback
+            self.draw_interface()
+    def save_presets(self):
+        self.file = open(self.filename, 'w')
+        self.file.seek(0)
+        preset_names = self.presets.keys()
+        preset_names.sort()
+        for p in preset_names:
+            s = '\t'.join([p] + self.presets[p]) + '\n'
+            self.file.write(s)
+        self.file.close()
+    def load_scalelevels(self):
+        for slidermap in self.current_mappings:
+            try:
+                v = self.sliderlevels[slidermap.get_mapping()]
+                slidermap.set_sublevel_var(v)
+                # print "ESM: Yes submaster named", slidermap.get_mapping()
+            except KeyError:
+                name = slidermap.get_mapping()
+                if name != 'disconnected':
+                    print "ESM: No submaster named", name
+                
+    def get_levels(self):
+        'Called by changelevels, returns a dict of new values for submasters'
+        if not self.sliderinput: return {}
+
+        self.load_scalelevels() # freshen our input from the submasters
+
+        for m, color in zip(self.current_mappings, colors):
+            name = m.get_mapping()
+            lastsub = self.subs_highlighted.get(color)
+            if name is not lastsub:
+                if lastsub is not None:
+                    try: 
+                        self.lightboard.highlight_sub(lastsub, 'restore')
+                    except KeyError:
+                        pass
+                try:
+                    self.lightboard.highlight_sub(name, color)
+                except KeyError:
+                    pass
+                self.subs_highlighted[color] = name
+
+        rawlevels = self.sliderinput.get_levels()
+        for rawlev, slidermap in zip(rawlevels, self.current_mappings):
+            slidermap.changed_extinput(rawlev)
+
+        return dict([m.get_level_pair()
+            for m in self.current_mappings
+            if m.issynced()])
+    def draw_interface(self):
+        self.subs_highlighted = {}
+        subchoiceframe = Frame(self, bg='black')
+        for m in self.current_mappings:
+            m.draw_interface(subchoiceframe, self.subnames)
+        subchoiceframe.pack()
+        
+        presetframe = Frame(self, bg='black')
+        Label(presetframe, text="Preset:", font=('Arial', 10), bg='black', 
+            fg='white').pack(side=LEFT)
+        self.presetcombo = ComboBox(presetframe, variable=self.current_preset, 
+                                    editable=1, command=self.apply_preset,
+                                    dropdown=1)
+        self.presetcombo.slistbox.configure(bg='black')
+        self.presetcombo.slistbox.listbox.configure(bg='black', fg='white')
+        self.presetcombo.entry.configure(bg='black', fg='white')
+        self.draw_presets()
+        self.presetcombo.pack(side=LEFT)
+        Button(presetframe, text="Prev", padx=0, pady=0, bg='black', 
+                fg='white', font=stdfont, 
+                command=self.prev_preset).pack(side=LEFT)
+        Button(presetframe, text="Next", padx=0, pady=0, bg='black', 
+                fg='white', font=stdfont, 
+                command=self.next_preset).pack(side=LEFT)
+        Button(presetframe, text="Add", padx=0, pady=0, bg='black', 
+                fg='white', font=stdfont, 
+                command=self.add_preset).pack(side=LEFT)
+        Button(presetframe, text="Delete", padx=0, pady=0, bg='black', 
+                fg='white', font=stdfont,
+                command=self.delete_preset).pack(side=LEFT)
+        Button(presetframe, text="Disconnect", padx=0, pady=0, bg='black', 
+                fg='white', font=stdfont,
+                command=self.disconnect_all).pack(side=LEFT)
+        Button(presetframe, text="Reload", padx=0, pady=0, bg='black', 
+                fg='white', font=stdfont,
+                command=self.load_presets).pack(side=LEFT)
+        presetframe.pack(side=BOTTOM)
+    def apply_preset(self, preset):
+        if not preset: return
+        preset_mapping = self.presets.get(preset)
+        if not preset_mapping: return
+        self.disconnect_all()
+        for subname, slidermap in zip(preset_mapping, self.current_mappings):
+            slidermap.set_subname(subname)
+    def change_preset_by_index(self, delta):
+        preset_names = self.presets.keys()
+        preset_names.sort()
+        try:
+            next = preset_names[preset_names.index(self.current_preset.get())
+                + delta]
+            self.current_preset.set(next)
+            self.apply_preset(next)
+        except (IndexError, ValueError):
+            print "Light 8.8: Can't go in that direction.  Dig up!"
+    def next_preset(self, *args):
+        self.change_preset_by_index(1)
+    def prev_preset(self, *args):
+        self.change_preset_by_index(-1)
+    def delete_preset(self, *args):
+        del self.presets[self.current_preset.get()]
+        self.presetcombo.slistbox.listbox.delete(0, END)
+        self.draw_presets()
+        self.save_presets()
+    def add_preset(self, *args):
+        self.presets[self.current_preset.get()] = [m.get_mapping() 
+                for m in self.current_mappings]
+        self.presetcombo.slistbox.listbox.delete(0, END)
+        self.draw_presets()
+        self.save_presets()
+        self.draw_interface()
+    def draw_presets(self):
+        preset_names = self.presets.keys()
+        preset_names.sort()
+        for p in preset_names:
+            self.presetcombo.slistbox.listbox.insert(END, p)
+    def disconnect_all(self):
+        for m in self.current_mappings:
+            m.disconnect()