Exemple #1
0
class Test(Frame):
    _meter = None
    _position = 0
    _length = 10000
    _step = 500

    def __init__(self):
        Frame.__init__(self)

        # width = (CART_WIDTH + CART_MARGIN) * NUM_COLS
        width = METER_WIDTH

        self._meter = Meter(self.master, width, self._get_meter_data)
        self._meter.grid(row=0, column=0, columnspan=NUM_COLS) #, sticky=E+W

        Canvas(self.master, width=900, height=100, bg='#00F').grid(row=2, column=0, columnspan=NUM_COLS)

        self._meter.start()
        thread.start_new_thread(self._run, ())

        self.master.title("Testing Program")
        self.master.mainloop()

    def _run(self):
        while True:
            self._position = (self._position + self._step) % self._length
            time.sleep(self._step / 1000.)

    def _get_meter_data(self):
        return (self._position, self._length, "Fruity", "Blergs")
Exemple #2
0
class Test(Frame):
    _meter = None
    _position = 0
    _length = 10000
    _step = 500

    def __init__(self):
        Frame.__init__(self)

        # width = (CART_WIDTH + CART_MARGIN) * NUM_COLS
        width = METER_WIDTH

        self._meter = Meter(self.master, width, self._get_meter_data)
        self._meter.grid(row=0, column=0, columnspan=NUM_COLS)  #, sticky=E+W

        Canvas(self.master, width=900, height=100,
               bg='#00F').grid(row=2, column=0, columnspan=NUM_COLS)

        self._meter.start()
        thread.start_new_thread(self._run, ())

        self.master.title("Testing Program")
        self.master.mainloop()

    def _run(self):
        while True:
            self._position = (self._position + self._step) % self._length
            time.sleep(self._step / 1000.)

    def _get_meter_data(self):
        return (self._position, self._length, "Fruity", "Blergs")
Exemple #3
0
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()
Exemple #4
0
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
Exemple #5
0
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()
Exemple #6
0
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()
Exemple #7
0
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
Exemple #8
0
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()