Files @ 1f877950ad28
Branch filter:

Location: light9/bin/gyrocontroller

Drew Perttula
new picamserve for raspberry pi camera -> http, especially with crop control
Ignore-this: 38fc34a6eff577c05129df87d6133a95
#!/usr/bin/env python
# vi: syntax=python

import run_local
from light9.Submaster import Submasters, Submaster, combine_subdict, \
    get_sub_by_name
from light9.subclient import SubClient
import light9.Patch as Patch

import Tix as Tk

# TODO: move to Utility?
class circcycle:
    """Like itertools.cycle, but with a prev() method too.  You lose
    all iterator benefits by using this, since it will store the whole
    sequence/iterator in memory.  Also, do not run this on an infinite
    iterator, as it tuple'ifies the input."""
    def __init__(self, sequence):
        self.sequence = tuple(sequence)
        self.index = None
    def __iter__(self):
        return self
    def change_index(self, delta):
        if self.index is None:
            if delta > 0:
                self.index = (-1 + delta) % len(self.sequence)
            elif delta < 0:
                self.index = delta % len(self.sequence)
        else:
            self.index = (self.index + delta) % len(self.sequence)
    def next(self):
        self.change_index(1)
        return self.sequence[self.index]
    def prev(self):
        self.change_index(-1)
        return self.sequence[self.index]

class AbstractSimpleController(SubClient):
    """Simple controller with minimal input and output:
    
    Input is 4 directions and 3 buttons.
    Output is an integer and a color and maybe more.
    
    Left + B1/right + B1: prev/next sub
    Y-axis + B3: set current level
    B2: toggle keep/solo mode
    Triple-B2: clear kept levels"""
    def __init__(self, subnames):
        SubClient.__init__(self)
        self.subnames = subnames
        self.refresh()
    def get_sub(self, name):
        return get_sub_by_name(name, self.submasters)
    def refresh(self):
        # reload subs from disk
        self.submasters = Submasters()
        self.all_subs = circcycle(self.subnames)
        self.current_sub = self.get_sub(self.all_subs.next())
        self.current_level = 1.0
        self.kept_levels = {} # subname : level [0..1]
        self.keep_solo_mode = 'solo' # either 'keep' or 'solo'
    def clear_kept_levels(self):
        self.kept_levels.clear()
    def next(self):
        if self.keep_solo_mode == 'keep':
            self.kept_levels[self.current_sub] = self.current_level

        self.current_sub = self.get_sub(self.all_subs.next())
    def prev(self):
        if self.keep_solo_mode == 'keep':
            self.kept_levels[self.current_sub] = self.current_level

        self.current_sub = self.get_sub(self.all_subs.prev())
    def toggle_keep_mode(self):
        if self.keep_solo_mode == 'keep':
            self.kept_levels[self.current_sub] = self.current_level
            self.keep_solo_mode = 'solo'
        else:
            self.keep_solo_mode = 'keep'

    def get_levels_as_sub(self):
        if self.keep_solo_mode == 'keep':
            # send all levels in self.kept_levels
            self.kept_levels[self.current_sub] = self.current_level
            levelsub = combine_subdict(self.kept_levels)
        else:
            levelsub = self.current_sub * self.current_level

        return levelsub

class TkGyro(Tk.Canvas, AbstractSimpleController):
    def __init__(self, master, subnames):
        Tk.Canvas.__init__(self, master, bg='black', bd=0, highlightthickness=0,
            confine=None)
        AbstractSimpleController.__init__(self, subnames)
        height = int(self.winfo_screenheight())
        width = int(self.winfo_screenwidth())
        self.left = self.create_rectangle((0, 0, width / 2, height),
            tags='left', fill='black')
        self.right = self.create_rectangle((width / 2, 0, width, height),
            tags='right', fill='black')
        self.levelbar = self.create_rectangle(0, 0, width, 5, tags='level',
            fill='yellow', state='disabled', outline='')

        # the text is disabled so that it doesn't receive events
        self.modetext = self.create_text((width / 2, height / 2), 
            font='Courier 200', fill='white', text=self.keep_solo_mode,
            state='disabled')
        self.flashtextafter = '' # current after timer for displaying text

        def setfill(item, color):
            self.itemconfig(item, fill=color)
        def setlevel(evt):
            if evt.state & 0x400 or evt.num == 3:
                y = (height - evt.y) / float(height)
                self.flash_text('<%d>' % (y * 100))
                self.current_level = y
                self.coords(self.levelbar, 0, evt.y, width, evt.y + 5)
                self.send_levels()

        data = ((self.left, 'left', 'blue', self.prev), 
                (self.right, 'right', 'red', self.next))
        for item, tag, color, method in data:
            self.tag_bind(tag, '<Enter>', 
                lambda evt, item=item, color=color: setfill(item, color))
            self.tag_bind(tag, '<Leave>', 
                lambda evt, item=item, color=color: setfill(item, 'black'))
            self.tag_bind(tag, '<ButtonPress-1>', 
                lambda evt, item=item, color=color: setfill(item, 'green'), '+')
            self.tag_bind(tag, '<ButtonRelease-1>', 
                lambda evt, item=item, color=color: setfill(item, color), '+')
            self.tag_bind(tag, '<Button-1>', 
                lambda evt, method=method: method(), '+')

        # B2+drag sets current level, double-B2 resets kept levels
        self.tag_bind('all', '<Motion>', setlevel, '+')
        self.tag_bind('all', '<ButtonPress-3>', setlevel, '+')
        self.tag_bind('all', '<Triple-Button-2>', 
            lambda evt: self.clear_kept_levels())
        # B3 toggles between keep and solo mode
        self.tag_bind('all', '<Button-2>', lambda evt: self.toggle_keep_mode())

        self.send_levels_loop()
    def toggle_keep_mode(self):
        AbstractSimpleController.toggle_keep_mode(self)
        self.show_current_mode()
        self.send_levels()
    def show_current_mode(self):
        if self.keep_solo_mode == 'keep':
            self.keep_solo_mode = 'keep'
        else:
            self.keep_solo_mode = ''

        self.itemconfig(self.modetext, text=self.keep_solo_mode)
    def clear_kept_levels(self):
        AbstractSimpleController.clear_kept_levels(self)
        self.flash_text('cleared')
        self.send_levels()
    def flash_text(self, text):
        self.itemconfig(self.modetext, text=text)
        self.after_cancel(self.flashtextafter)
        self.flashtextafter = self.after(2000, self.show_current_mode)
    def next(self):
        AbstractSimpleController.next(self)
        self.flash_text(self.current_sub.name)
        self.send_levels()
    def prev(self):
        AbstractSimpleController.prev(self)
        self.flash_text(self.current_sub.name)
        self.send_levels()

if __name__ == "__main__":
    import sys, fileinput
    subnames = sys.argv[1:] 
    if not subnames:
        subnames = [line.strip() for line in fileinput.input()]

    root = Tk.Tk()
    # these are hints to Fvwm2 if you add this to your .fvwm2rc:
    #   Style "*NOTITLE*"   NoTitle
    #   Style "*NOBORDER*"  BorderWidth 0, NoHandles
    #   Style "*ONTOP*"     StaysOnTop Sticky
    # hopefully, there's a better way to do this within Tk
    root.title("NOTITLE NOBORDER ONTOP")
    root.wm_geometry('%sx%s' % (root.winfo_screenwidth(), 
                                root.winfo_screenheight()))

    gyro = TkGyro(root, subnames)
    gyro.pack(fill='both', expand=1)
    def quit(event):
        gyro.send_zeroes()
        root.destroy()
    root.bind('<Key-q>', quit)
    root.maxsize(root.winfo_screenwidth(), root.winfo_screenheight())
    Tk.mainloop()