class Studio(Frame): """The Studio class is a GUI for the digital library.""" _meter = None _grid = None _dual_box = None _auto_queue = None _entry = None _search_results = None _selected_cart = None def __init__(self): """Construct a Studio window.""" Frame.__init__(self) # make the window resizable top = self.master.winfo_toplevel() for row in range(2, GRID_ROWS + 2): for col in range(0, GRID_COLS): top.rowconfigure(row, weight=1) top.columnconfigure(col, weight=1) self.rowconfigure(row, weight=1) self.columnconfigure(col, weight=1) # initialize the title title = Label(self.master, font=FONT_TITLE, text=TEXT_TITLE) title.grid(row=0, column=0, columnspan=GRID_COLS) # initialize the meter self._meter = Meter(self.master, METER_WIDTH, self._get_meter_data) self._meter.grid(row=1, column=0, columnspan=GRID_COLS) # initialize the cart grid self._grid = Grid(self, GRID_ROWS, GRID_COLS, True, self._cart_start, self._cart_stop, self._cart_end, self.add_cart) # initialize the dual box self._dual_box = DualBox(self) self._dual_box.grid(row=GRID_ROWS + 2, column=0, columnspan=4) # intialize the auto-queue control self._auto_queue = BooleanVar() self._auto_queue.set(False) control = Frame(self.master, bd=2, relief=Tkinter.SUNKEN) Checkbutton(control, text=TEXT_AUTOSLOT, variable=self._auto_queue, onvalue=True, offvalue=False).pack(anchor=Tkinter.NW) control.grid(row=GRID_ROWS + 2, column=4, columnspan=GRID_COLS - 4) # initialize the search box, button Label(control, font=FONT, text=TEXT_SEARCHBOX).pack(anchor=Tkinter.NW) self._entry = Entry(control, takefocus=True, width=45) self._entry.bind("<Return>", self.search) # self._entry.grid(row=GRID_ROWS + 3, column=0, columnspan=5) self._entry.pack(anchor=Tkinter.NW) self._entry.focus_set() button = Button(control, text=TEXT_SEARCH, command=self.search) # button.grid(row=GRID_ROWS + 3, column=5) button.pack(anchor=Tkinter.S) # begin the event loop self.master.protocol("WM_DELETE_WINDOW", self.master.destroy) self.master.title(TEXT_TITLE) self.master.mainloop() def _search_internal(self): """Search the digital library in a separate thread.""" query = self._entry.get() if len(query) >= 3: print "Searching library with query \"%s\"..." % query self._search_results = database.search_library(query) self._dual_box.fill(self._search_results) print "Found %d results." % len(self._search_results) def search(self, *args): """Search the digital library. :param args """ thread.start_new_thread(self._search_internal, ()) def select_cart(self, index): """Select a cart from the search results. :param index: index of cart in search results """ if index is not None: self._selected_cart = self._search_results[index] def add_cart(self, key): """Add the selected cart to the grid. :param key """ if not self._grid.has_cart(key) and self._selected_cart is not None: self._grid.set_cart(key, self._selected_cart) def _cart_start(self): """Start the meter when a cart starts.""" self._meter.start() def _cart_stop(self): """Reset the meter when a cart stops.""" self._meter.reset() def _cart_end(self, key): """Reset the meter when a cart ends. Also, if auto-queue is enabled, queue the next cart. :param key """ self._meter.reset() if self._auto_queue.get(): next_key = get_next_key(GRID_ROWS, GRID_COLS, key) if self._grid.has_cart(next_key): self._grid.start(next_key) def _get_meter_data(self): """Get meter data for the currently active cart.""" return self._grid.get_active_cell().get_cart().get_meter_data()
class Automation(Frame): """The Automation class is a GUI that provides radio automation.""" _state = None _button_text = None _button = None _meter = None _cart_queue = None _list_time = None _list_track = None _list_artist = None def __init__(self): """Construct an Automation window.""" Frame.__init__(self) # initialize title title = Label(self.master, font=FONT_TITLE, text=TEXT_TITLE) title.grid(row=0, column=0, columnspan=3) # initialize button and state self._state = STATE_STOPPED self._button_text = StringVar() self._button = Button(self.master, textvariable=self._button_text, command=self._update_state, width=16, height=2) self._button.config(bd=2) self._button.grid(row=0, column=3) # initialize the meter self._meter = Meter(self.master, METER_WIDTH, self._get_meter_data) self._meter.grid(row=1, column=0, columnspan=4) # initialize playlist view playlist = Frame(self.master, bd=2, relief=Tkinter.SUNKEN) Label(playlist, font=FONT, anchor=Tkinter.CENTER, width=16, text=TEXT_PLAYLIST_TIME).grid(row=0, column=0) Label(playlist, font=FONT, anchor=Tkinter.CENTER, width=32, text=TEXT_PLAYLIST_TRACK).grid(row=0, column=1) Label(playlist, font=FONT, anchor=Tkinter.CENTER, width=32, text=TEXT_PLAYLIST_ARTIST).grid(row=0, column=2) inner_playlist = Frame(playlist) scroll = Scrollbar(inner_playlist, orient=Tkinter.VERTICAL, command=self._scroll_playlist) self._list_time = Listbox(inner_playlist, selectmode=Tkinter.SINGLE, yscrollcommand=scroll.set, exportselection=0, width=16, height=20) self._list_track = Listbox(inner_playlist, selectmode=Tkinter.SINGLE, yscrollcommand=scroll.set, exportselection=0, width=32, height=20) self._list_artist = Listbox(inner_playlist, selectmode=Tkinter.SINGLE, yscrollcommand=scroll.set, exportselection=0, width=32, height=20) scroll.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) self._list_time.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=True, padx=2, pady=2) self._list_track.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=True, padx=2, pady=2) self._list_artist.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=True, padx=2, pady=2) inner_playlist.grid(row=1, column=0, columnspan=3) playlist.grid(row=4, column=0, columnspan=4) # initialize cart queue self._cart_queue = CartQueue(self._cart_start, self._cart_stop) self._cart_queue.add_tracks() self._update_ui() # begin the event loop self.master.protocol("WM_DELETE_WINDOW", self.master.destroy) self.master.title(TEXT_TITLE) self.master.mainloop() def _scroll_playlist(self, *args): """Scroll the playlist view. :param args """ self._list_time.yview(*args) self._list_track.yview(*args) self._list_artist.yview(*args) def _update_state(self): """Move Automation to the next state. The state machine is as follows: STATE_STOPPED -> STATE_PLAYING -> STATE_STOPPING -> STATE_STOPPED """ if self._state is STATE_STOPPED: print "Starting Automation..." self._cart_queue.start() self._state = STATE_PLAYING elif self._state is STATE_PLAYING: print "Stopping Automation after this track..." self._cart_queue.stop_soft() self._state = STATE_STOPPING elif self._state is STATE_STOPPING: print "Stopping Automation immediately." self._cart_queue.transition() self._state = STATE_STOPPED self._update_ui() def _cart_start(self): """Start the meter when a cart starts.""" self._meter.start() self._update_ui() def _cart_stop(self): """Reset the meter when a cart stops. Also, if a soft stop occured, update the button state. """ self._meter.reset() if self._state is STATE_STOPPING: self._state = STATE_STOPPED self._update_ui() def _update_ui(self): """Update the button and playlist.""" self._button_text.set(TEXT_BUTTON[self._state]) self._button.config(bg=COLOR_BUTTON[self._state], highlightbackground=COLOR_BUTTON[self._state]) self._list_time.delete(0, Tkinter.END) self._list_track.delete(0, Tkinter.END) self._list_artist.delete(0, Tkinter.END) for cart in self._cart_queue.get_queue(): self._list_time.insert(Tkinter.END, cart.start_time.strftime("%I:%M:%S %p")) self._list_track.insert(Tkinter.END, cart.title) self._list_artist.insert(Tkinter.END, cart.issuer) def _get_meter_data(self): """Get meter data for the first track in the queue.""" queue = self._cart_queue.get_queue() if len(queue) > 0: return queue[0].get_meter_data() else: return None
class CartMachine(Frame): """The CartMachine class is a GUI that provides a grid of carts.""" _meter = None _grid = None def __init__(self): """Construct a CartMachine window.""" Frame.__init__(self) # make the window resizable top = self.winfo_toplevel() for row in range(2, GRID_ROWS + 2): for col in range(0, GRID_COLS): top.rowconfigure(row, weight=1) top.columnconfigure(col, weight=1) self.rowconfigure(row, weight=1) self.columnconfigure(col, weight=1) # initialize the title title = Label(self.master, font=FONT_TITLE, text=TEXT_TITLE) title.grid(row=0, column=0, columnspan=GRID_COLS - 1, sticky=Tkinter.N) # initialize the reload button reload_button = Button(self.master, \ bg=COLOR_RELOAD_BG, fg=COLOR_RELOAD_FG, \ font=FONT_RELOAD, text=TEXT_RELOAD, \ command=self.reload) reload_button.grid(row=0, column=GRID_COLS - 1) # initialize the meter self._meter = Meter(self.master, METER_WIDTH, self._get_meter_data) self._meter.grid(row=1, column=0, columnspan=GRID_COLS) # self._meter.grid_propagate(0) # initialize the grid self._grid = Grid(self, GRID_ROWS, GRID_COLS, False, self._cart_start, self._cart_stop, self._cart_end, None) self._load() # begin the event loop self.master.protocol("WM_DELETE_WINDOW", self.master.destroy) self.master.title(TEXT_TITLE) self.mainloop() def _load(self): """Load the grid with carts. Since there are four cart types, each type is assigned to a corner of the grid, and the carts in that type expand from that corner. Carts are added one type at a time until the grid is full. Typically, since PSAs are the most numerous cart type, they fill middle space not covered by the other types. """ # generate a progression of cells for each corner progs = {} for cart_type in CONFIG_CARTS: progs[cart_type] = progression(GRID_ROWS, GRID_COLS, CONFIG_CARTS[cart_type]["corner"]) # get a dictonary of carts for each cart type carts = database.get_carts() # apply shuffling and limiting to each cart type for cart_type in carts: random.shuffle(carts[cart_type]) limit = CONFIG_CARTS[cart_type]["limit"] if limit is not -1: carts[cart_type] = carts[cart_type][0:limit] # insert carts until the grid is full or all carts are inserted num_inserted = 0 for i in range(0, max(GRID_ROWS, GRID_COLS)): for cart_type in carts: # insert a layer for each cart type num_toinsert = 1 + 2 * i while len(carts[cart_type]) > 0 and num_toinsert > 0: # pop the first empty coordinate from the progression key = progs[cart_type].pop(0) while self._grid.has_cart(key): key = progs[cart_type].pop(0) # add the cart to the grid self._grid.set_cart(key, carts[cart_type].pop(0)) num_inserted += 1 num_toinsert -= 1 # exit if the grid is full if num_inserted is GRID_ROWS * GRID_COLS: return # exit if all carts are inserted if len([key for key in carts if len(carts[key]) > 0]) is 0: break def reload(self): """Reload the cart machine.""" if self._grid.is_playing(): return print "Reloading the Cart Machine..." self._grid.clear() self._load() print "Cart Machine reloaded." def _cart_start(self): """Start the meter when a cart starts.""" self._meter.start() def _cart_stop(self): """Reset the meter when a cart stops.""" self._meter.reset() def _cart_end(self, key): """Reset the meter when a cart ends.""" self._meter.reset() def _get_meter_data(self): """Get meter data for the currently active cart.""" return self._grid.get_active_cell().get_cart().get_meter_data()