Example #1
0
class FeedEditScreen(Frame):
    def __init__(self, master, feed):
        super(FeedEditScreen, self).__init__(master)
        self.master = master
        self.feed = feed
        self.avail = IntVar()
        self.avail.set(1 if feed.avail else 0)
        self.bgcolor = master.bgcolor
        self.configure(bg=self.bgcolor)

        self.lbl = Label(self, text="Zutat #%d: %s" % (feed.motor_num, feed.getName()))
        renamebtn = RectButton(self, text="Zutat umbenennen", width=150, command=self.handle_button_rename)
        calibbtn = RectButton(self, text="Calibration", width=150, command=self.handle_button_calib)
        self.feedbtn = RectButton(self, text="Start Feed", width=150, command=self.handle_button_feed)
        enbtn = TouchCheckbox(self, text="Available", variable=self.avail, command=self.handle_button_enable)
        self.proofspin = TouchSpinner(self, width=150, value=self.feed.proof, minval=0, maxval=100, incdecval=1, format="Alkohol: %d", changecmd=self.handle_proof_change)
        self.remainingspin = TouchSpinner(self, width=150, value=self.feed.remaining, minval=0, maxval=5000, incdecval=50, format="Restinhalt: %d", changecmd=self.handle_remaining_change)
        backbtn = RectButton(self, text="\u23ce", width=120, command=self.handle_button_back)

        self.lbl.grid(column=1, row=1, columnspan=2, pady=20, sticky=E+W)
        renamebtn.grid(column=1, row=2, padx=20, pady=10, sticky=E+W)
        #calibbtn.grid(column=1, row=3, padx=20, pady=10, sticky=E+W)
        #self.feedbtn.grid(column=1, row=4, padx=20, pady=10, sticky=E+W)
        self.remainingspin.grid(column=1, row=3, rowspan=3, padx=20, pady=10)
        enbtn.grid(column=2, row=2, padx=20, pady=10, sticky=E+W)
        self.proofspin.grid(column=2, row=3, rowspan=3, padx=20, pady=10)
        backbtn.grid(column=2, row=9, columnspan=3, padx=20, pady=10, sticky=S+E)

        self.columnconfigure(0, weight=1)
        self.columnconfigure(3, weight=1)
        self.rowconfigure(0, minsize=10)
        self.rowconfigure(8, weight=1)
        self.rowconfigure(10, minsize=10)

    def handle_proof_change(self, oldval, newval):
        self.feed.proof = newval

    def handle_remaining_change(self, oldval, newval):
        self.feed.remaining = newval

    def handle_button_enable(self, ev=None):
        self.feed.avail = True if self.avail.get() != 0 else False

    def handle_button_feed(self):
        if self.feed.isFlowing():
            self.feed.stopFeed()
            self.feedbtn.config(text="Start Feed")
        else:
            self.feed.startFeed()
            self.feedbtn.config(text="Stop Feed")
        self.master.update()

    def handle_button_rename(self):
        self.master.screen_push(AlphaScreen(self.master, label="Name for feed #%d:" % self.feed.motor_num, defval=self.feed.getName(), callback=self.rename_complete))

    def handle_button_calib(self):
        self.master.screen_push(CalibScreen(self.master, self.feed))

    def handle_button_back(self):
        self.feed.avail = True if self.avail.get() != 0 else False
        self.master.save_configs()
        if self.feed.isFlowing():
            self.feed.stopFeed()
        self.master.screen_pop()

    def rename_complete(self, val):
        self.feed.rename(val)
        self.lbl.config(text="Feed #%d: %s" % (self.feed.motor_num, self.feed.getName()))
        self.master.save_configs()
        self.master.screen_pop()
Example #2
0
class TouchSpinner(Frame):
    def __init__(self,
                 master,
                 value=0,
                 values=None,
                 minval=0,
                 maxval=100,
                 incdecval=1,
                 format="%s",
                 justify=CENTER,
                 **kwargs):
        super(TouchSpinner, self).__init__(master, class_="TouchSpinner")
        self.master = master
        self.value = value
        self.values = values
        self.min_value = minval
        self.max_value = maxval
        self.format = format
        self.justify = justify
        self.incdecval = incdecval
        self.changecmd = None

        self.upbtn = RectButton(self,
                                text="+",
                                repeatdelay=500,
                                repeatinterval=100,
                                command=self._button_up)
        self.vallbl = Label(self, text=format % value, justify=justify)
        self.dnbtn = RectButton(self,
                                text="−",
                                repeatdelay=500,
                                repeatinterval=100,
                                command=self._button_dn)

        self.upbtn.pack(side=TOP, fill=X)
        self.dnbtn.pack(side=BOTTOM, fill=X)
        self.vallbl.pack(side=LEFT, fill=BOTH, expand=1)

        self.config(**kwargs)
        self._refresh_label()

    def _button_up(self):
        if self.values:
            try:
                idx = min(self.values.index(self.value) + 1, len(self.values))
            except:
                idx = 0
            newval = self.values[idx] if self.values else ""
        else:
            newval = min(self.value + self.incdecval, self.max_value)
        self.set(newval)

    def _button_dn(self):
        if self.values:
            try:
                idx = max(self.values.index(self.value) - 1, 0)
            except:
                idx = 0
            newval = self.values[idx] if self.values else ""
        else:
            newval = max(self.value - self.incdecval, self.min_value)
        self.set(newval)

    def _refresh_label(self):
        valstr = self.format % self.value
        self.vallbl.config(text=valstr)

    def get(self):
        return self.value

    def set(self, value):
        oldval = self.value
        self.value = value
        self._refresh_label()
        if callable(self.changecmd):
            self.changecmd(oldval, value)

    def config(self,
               value=None,
               minval=None,
               maxval=None,
               justify=CENTER,
               format=None,
               incdecval=None,
               changecmd=None,
               width=None,
               state=None):
        if value is not None:
            self.set(value)
        if minval is not None:
            self.min_value = minval
        if maxval is not None:
            self.max_value = maxval
        if justify is not None:
            self.justify = justify
        if format is not None:
            self.format = format
            self._refresh_label()
        if incdecval is not None:
            self.incdecval = incdecval
        if changecmd is not None:
            self.changecmd = changecmd
        if width is not None:
            self.upbtn.config(width=width)
            self.dnbtn.config(width=width)
        if state is not None:
            self.upbtn.config(state=state)
            self.dnbtn.config(state=state)
            self.vallbl.config(state=state)
Example #3
0
class RecipeEditScreen(Frame):
    def __init__(self, master, recipe):
        super(RecipeEditScreen, self).__init__(master)
        self.master = master
        self.recipe = recipe
        self.newfeed = None

        self.renamebtn = RectButton(self,
                                    text="Recipe: %s" % recipe.getName(),
                                    width=200,
                                    justify=LEFT,
                                    command=self.handle_button_rename)
        self.retypebtn = RectButton(self,
                                    text="Category: %s" % recipe.getType(),
                                    width=200,
                                    justify=LEFT,
                                    command=self.handle_button_retype)
        self.upbtn = RectButton(self,
                                text="\u25b2",
                                state=DISABLED,
                                repeatdelay=500,
                                repeatinterval=100,
                                command=self.handle_button_up)
        self.ingrlb = Listbox(self, width=40, height=5)
        self.dnbtn = RectButton(self,
                                text="\u25bc",
                                state=DISABLED,
                                repeatdelay=500,
                                repeatinterval=100,
                                command=self.handle_button_dn)
        self.ingradd = RectButton(self,
                                  text="\u2795",
                                  width=50,
                                  command=self.handle_button_ingr_add)
        self.ingramt = RectButton(self,
                                  text="\u270e",
                                  width=50,
                                  command=self.handle_button_ingr_amt)
        self.ingrdel = RectButton(self,
                                  text="\u2796",
                                  width=50,
                                  command=self.handle_button_ingr_del)
        backbtn = RectButton(self,
                             text="\u23ce",
                             width=120,
                             command=self.handle_button_back)
        self.ingrlb.bind('<<ListboxSelect>>', self.ingredient_listbox_select)

        self.renamebtn.grid(column=1, row=1, pady=10, sticky=N + E + W)
        self.retypebtn.grid(column=3, row=1, pady=10, sticky=N + E + W)
        self.upbtn.grid(column=1, row=3, sticky=S + E + W)
        self.ingrlb.grid(column=1,
                         row=4,
                         rowspan=5,
                         padx=2,
                         pady=1,
                         sticky=N + S + E + W)
        self.dnbtn.grid(column=1, row=9, sticky=N + E + W)
        self.ingradd.grid(column=3, row=5, pady=10, sticky=N + W)
        self.ingramt.grid(column=3, row=6, pady=0, sticky=N + W)
        self.ingrdel.grid(column=3, row=7, pady=10, sticky=N + W)
        backbtn.grid(column=3, row=9, sticky=S + E)

        self.columnconfigure(0, minsize=10)
        self.columnconfigure(1, weight=1)
        self.columnconfigure(2, minsize=10)
        self.columnconfigure(3, weight=1)
        self.columnconfigure(4, minsize=10)

        self.rowconfigure(0, minsize=10)
        self.rowconfigure(2, minsize=15)
        self.rowconfigure(4, weight=1)
        self.rowconfigure(8, weight=1)
        self.rowconfigure(99, minsize=10)
        self.update_ingr_listbox()

    def handle_button_rename(self):
        self.master.screen_push(
            AlphaScreen(self.master,
                        label="Name for Recipe:",
                        defval=self.recipe.getName(),
                        callback=self.rename_complete))

    def handle_button_retype(self):
        self.master.screen_push(
            SelectScreen(self.master,
                         Recipe.getPossibleTypeNames(),
                         labeltext="Select the recipe type:",
                         callback=self.retype_complete))

    def update_scroll_btns(self):
        start, end = self.ingrlb.yview()
        self.upbtn.config(state=DISABLED if start == 0.0 else NORMAL)
        self.dnbtn.config(state=DISABLED if end == 1.0 else NORMAL)

    def handle_button_up(self):
        self.ingrlb.yview_scroll(-5, UNITS)
        self.after(100, self.update_scroll_btns)

    def handle_button_dn(self):
        self.ingrlb.yview_scroll(5, UNITS)
        self.after(100, self.update_scroll_btns)

    def ingredient_listbox_select(self, ev=None):
        selidx = self.ingrlb.curselection()
        if not selidx:
            self.sel_ingr = None
        else:
            self.sel_ingr = self.recipe.ingredients[selidx[0]]

    def handle_button_ingr_add(self):
        currfeeds = [ingr.feed.name for ingr in self.recipe.ingredients]
        feeds = [
            feed for feed in SupplyFeed.getNames() if feed not in currfeeds
        ]
        self.master.screen_push(
            SelectScreen(self.master,
                         feeds,
                         labeltext="Select the ingredient:",
                         callback=self.add_ingredient_step1))

    def add_ingredient_step1(self, feedname):
        self.newfeed = feedname
        self.master.screen_push(
            AmountScreen(self.master,
                         whole=1,
                         unit="ounce",
                         labeltext="Select the amount:",
                         callback=self.add_ingredient_step2))

    def add_ingredient_step2(self, ml):
        self.recipe.add_ingredient(self.newfeed, ml)
        self.newfeed = None
        self.update_ingr_listbox()
        self.master.screen_pop()
        self.master.screen_pop()

    def handle_button_ingr_del(self):
        self.master.screen_push(
            SelectScreen(
                self.master, ["Confirm"],
                labeltext="Are you sure you want to delete that ingredient?",
                callback=self.del_ingredient_step1))

    def del_ingredient_step1(self, confirm):
        if confirm == "Confirm":
            self.recipe.ingredients.remove(self.sel_ingr)
            self.update_ingr_listbox()
        self.master.screen_pop()

    def handle_button_ingr_amt(self):
        whole, frac, unit = self.sel_ingr.fractionalBarUnits(
            metric=self.master.use_metric)
        self.master.screen_push(
            AmountScreen(self.master,
                         whole=whole,
                         frac=frac,
                         unit=unit,
                         labeltext="Select the amount:",
                         callback=self.edit_ingredient_step1))

    def edit_ingredient_step1(self, amt):
        self.sel_ingr.milliliters = amt
        self.update_ingr_listbox()
        self.master.screen_pop()

    def update_ingr_listbox(self):
        self.ingrlb.delete(0, END)
        for ingr in self.recipe.ingredients:
            self.ingrlb.insert(
                END, ingr.readableDesc(metric=self.master.use_metric))
        self.ingrlb.focus()
        self.ingrlb.selection_clear(0, END)
        self.ingrlb.selection_anchor(0)
        self.ingrlb.selection_set(0)
        self.ingrlb.activate(0)
        self.ingrlb.see(0)
        self.ingredient_listbox_select()
        self.after(100, self.update_scroll_btns)

    def handle_button_back(self):
        self.master.save_configs()
        self.master.screen_pop()

    def rename_complete(self, val):
        self.recipe.rename(val)
        self.renamebtn.config(text="Recipe: %s" % self.recipe.getName())
        self.master.save_configs()
        self.master.screen_pop()

    def retype_complete(self, val):
        self.recipe.retype(val)
        self.retypebtn.config(text="Category: %s" % self.recipe.getType())
        self.master.save_configs()
        self.master.screen_pop()
Example #4
0
class CalibScreen(Frame):
    def __init__(self, master, feed):
        super(CalibScreen, self).__init__(master)
        self.master = master
        self.feed = feed
        self.dispensed = 0.0
        self.target_ml = 0.0
        self.duty_cycle = 1.0
        self.dispensing = False
        self.start_pid = None
        self.stop_pid = None

        lbl = Label(self,
                    text="Feed #%d: %s" % (feed.motor_num, feed.getName()))
        self.dutyspin = TouchSpinner(self,
                                     width=150,
                                     value=100,
                                     minval=10,
                                     maxval=100,
                                     incdecval=5,
                                     format="Duty: %d%%")
        self.amntspin = TouchSpinner(self,
                                     width=150,
                                     value=100,
                                     minval=25,
                                     maxval=500,
                                     incdecval=25,
                                     format="Amount: %d ml")
        self.pourbtn = RectButton(self,
                                  text="Pour",
                                  width=150,
                                  command=self.handle_button_pour)
        self.flowspin = TouchSpinner(self,
                                     width=150,
                                     value=feed.remaining,
                                     minval=0.1,
                                     maxval=50.0,
                                     incdecval=0.1,
                                     format="Flow: %.1f ml/s",
                                     changecmd=self._remaining_change)
        self.overspin = TouchSpinner(self,
                                     width=150,
                                     value=feed.pulse_overage,
                                     minval=0.0,
                                     maxval=2.0,
                                     incdecval=0.01,
                                     format="Pulse: %.2f ml",
                                     changecmd=self._overage_change)
        self.displbl = Label(self, text="")
        backbtn = RectButton(self,
                             text="\u23ce",
                             width=120,
                             command=self.handle_button_back)

        lbl.grid(column=1,
                 row=1,
                 columnspan=3,
                 padx=20,
                 pady=10,
                 sticky=N + E + W)
        self.dutyspin.grid(column=1, row=2, padx=20, pady=20, sticky=N)
        self.amntspin.grid(column=2, row=2, padx=20, pady=20, sticky=N)
        self.pourbtn.grid(column=3, row=2, padx=20, pady=20, sticky=N)
        self.flowspin.grid(column=1, row=3, padx=20, pady=20, sticky=N)
        self.overspin.grid(column=2, row=3, padx=20, pady=20, sticky=N)
        self.displbl.grid(column=3, row=3, padx=20, pady=20, sticky=N)
        backbtn.grid(column=1,
                     row=9,
                     columnspan=4,
                     padx=20,
                     pady=10,
                     sticky=S + E)

        self.columnconfigure(0, weight=1)
        self.columnconfigure(4, weight=1)
        self.rowconfigure(0, minsize=10)
        self.rowconfigure(8, weight=1)
        self.rowconfigure(10, minsize=10)

    def _remaining_change(self, oldval, newval):
        self.feed.remaining = newval

    def _overage_change(self, oldval, newval):
        self.feed.pulse_overage = newval

    def _pour_mode(self):
        self.dutyspin.config(state=DISABLED)
        self.amntspin.config(state=DISABLED)
        self.flowspin.config(state=DISABLED)
        self.overspin.config(state=DISABLED)
        self.pourbtn.config(text="Cancel")
        self.dispensing = True

    def _conf_mode(self):
        self.dutyspin.config(state=NORMAL)
        self.amntspin.config(state=NORMAL)
        self.flowspin.config(state=NORMAL)
        self.overspin.config(state=NORMAL)
        self.pourbtn.config(text="Pour")
        self.dispensing = False
        self._cancel_feed()

    def _cancel_feed(self):
        if self.feed.isFlowing():
            self.feed.stopFeed()
        if self.start_pid:
            self.after_cancel(self.start_pid)
            self.start_pid = None
        if self.stop_pid:
            self.after_cancel(self.stop_pid)
            self.stop_pid = None

    def handle_button_pour(self):
        if self.dispensing:
            # Cancel button pressed
            self._conf_mode()
        else:
            # Pour button pressed
            self._pour_mode()
            self.dispensed = 0.0
            self.target_ml = self.amntspin.get()
            self.duty_cycle = self.dutyspin.get() / 100.0
            self._feed_cycle_start()

    def _feed_cycle_start(self):
        self.start_pid = None
        if self.dispensed >= self.target_ml - 0.05:
            self._conf_mode()
            return
        self.start_pid = self.after(1001, self._feed_cycle_start)
        remaining_ml = self.target_ml - self.dispensed
        remtime = remaining_ml / self.feed.remaining
        stop_ms = int(max(0.01, min(remtime, self.duty_cycle)) * 1000)
        self.stop_pid = self.after(stop_ms, self._feed_cycle_stop, stop_ms)
        if self.duty_cycle < 1.0 or self.dispensed == 0.0:
            self.feed.startFeed()
        self.displbl.config(
            text="Dispensed:\n%.1f ml (%d%%)" %
            (self.dispensed, int(100 * self.dispensed / self.target_ml + 0.5)))

    def _feed_cycle_stop(self, ms):
        self.dispensed += self.feed.remaining * (ms / 1000.0)
        self.dispensed += self.feed.pulse_overage
        if self.duty_cycle < 1.0 or self.dispensed >= self.target_ml - 0.05:
            self.feed.stopFeed()
        self.displbl.config(
            text="Dispensed:\n%.1f ml (%d%%)" %
            (self.dispensed, int(100 * self.dispensed / self.target_ml + 0.5)))

    def handle_button_back(self):
        if self.feed.isFlowing():
            self.feed.stopFeed()
        self.feed.remaining = self.flowspin.get()
        self.feed.pulse_overage = self.overspin.get()
        self.master.save_configs()
        self.master.screen_pop()
Example #5
0
class ListScreen(Frame):
    def __init__(
            self,
            master,
            items_cb,
            label_text="",
            add_cb=None,
            del_cb=None,
            edit_cb=None,
            raise_cb=None,
            lower_cb=None,
            #en_ckb=None,
            rem_sp=None,
            ep_cb=None,
            add_lbl="\u002b",
            del_lbl="\u2212",
            edit_lbl="\u270e",
            raise_lbl="\u2b06",
            lower_lbl="\u2b07",
            ep_lbl="Export PDF",
            extra_btns=None):
        super(ListScreen, self).__init__(master)
        self.master = master
        self.bgcolor = master.bgcolor
        self.configure(bg=self.bgcolor)
        self.items_cb = items_cb
        self.add_cb = add_cb
        self.del_cb = del_cb
        self.edit_cb = edit_cb
        self.raise_cb = raise_cb
        self.lower_cb = lower_cb
        self.rem_sp = rem_sp
        self.ep_cb = ep_cb
        self.sel_rem = 700
        self.extra_btns = []
        self.items = []

        btnwidth = 150
        if label_text:
            self.lbl = Label(self, text=label_text)
        self.upbtn = RectButton(self,
                                text="\u25b2",
                                state=DISABLED,
                                repeatdelay=500,
                                repeatinterval=100,
                                command=self.handle_button_up)
        self.lbox = Listbox(self,
                            width=40,
                            height=5,
                            fg="#000000",
                            bg="#ffffff")
        self.dnbtn = RectButton(self,
                                text="\u25bc",
                                state=DISABLED,
                                repeatdelay=500,
                                repeatinterval=100,
                                command=self.handle_button_dn)
        if self.add_cb:
            self.addbtn = RectButton(self,
                                     text=add_lbl,
                                     width=btnwidth,
                                     command=self.handle_button_add)
        if self.edit_cb:
            self.editbtn = RectButton(self,
                                      text=edit_lbl,
                                      width=btnwidth,
                                      command=self.handle_button_edit)
        if self.del_cb:
            self.delbtn = RectButton(self,
                                     text=del_lbl,
                                     width=btnwidth,
                                     command=self.handle_button_del)
        if self.raise_cb:
            self.raisebtn = RectButton(self,
                                       text=raise_lbl,
                                       width=btnwidth,
                                       command=self.handle_button_raise)
        if self.lower_cb:
            self.lowerbtn = RectButton(self,
                                       text=lower_lbl,
                                       width=btnwidth,
                                       command=self.handle_button_lower)
        if self.rem_sp:
            self.remainspin = TouchSpinner(
                self,
                width=150,
                value=self.sel_rem,
                minval=-50,
                maxval=5000,
                incdecval=50,
                format="Restinhalt: %d",
                changecmd=self.handle_remaining_change)
        if self.ep_cb:
            self.epbtn = RectButton(self,
                                    text=ep_lbl,
                                    width=btnwidth,
                                    command=self.handle_button_ep)

        if extra_btns:
            self.extra_btns = []
            for d in extra_btns:
                txt = d['name']
                cb = d['callback']
                btn = RectButton(
                    self,
                    text=txt,
                    width=btnwidth,
                    command=lambda x=cb: self.handle_button_extra(x))
                btn.en_cb = en
                self.extra_btns.append(btn)
        backbtn = RectButton(self,
                             text="\u23ce",
                             width=120,
                             command=self.handle_button_back)
        self.lbox.bind('<<ListboxSelect>>', self.listbox_select)

        if label_text:
            self.lbl.grid(column=1, row=1, columnspan=3, sticky=N + W)
        self.upbtn.grid(column=1, row=2, sticky=S + E + W)
        self.lbox.grid(column=1,
                       row=3,
                       rowspan=95,
                       padx=2,
                       pady=1,
                       sticky=N + S + E + W)
        self.dnbtn.grid(column=1, row=98, sticky=N + E + W)
        if self.add_cb:
            self.addbtn.grid(column=3, row=3, pady=5, sticky=N + W)
        if self.edit_cb:
            self.editbtn.grid(column=3, row=4, pady=5, sticky=N + W)
        if self.del_cb:
            self.delbtn.grid(column=3, row=5, pady=5, sticky=N + W)
        if self.raise_cb:
            self.raisebtn.grid(column=3, row=7, pady=5, sticky=N + W)
        if self.lower_cb:
            self.lowerbtn.grid(column=3, row=8, pady=5, sticky=N + W)
        if self.rem_sp:
            self.remainspin.grid(column=3, row=13, rowspan=3, padx=20, pady=10)
        if self.ep_cb:
            self.epbtn.grid(column=3, row=16, pady=5, sticky=N + W)

        for n, btn in enumerate(self.extra_btns):
            btn.grid(column=3, row=15 + n, pady=5, sticky=N + W)
        backbtn.grid(column=3, row=98, sticky=S + E)

        self.columnconfigure(0, minsize=10)
        self.columnconfigure(1, weight=1)
        self.columnconfigure(2, minsize=10)
        self.columnconfigure(3, weight=1)
        self.columnconfigure(4, minsize=10)

        self.rowconfigure(0, minsize=10)
        self.rowconfigure(6, weight=1)
        self.rowconfigure(9, weight=1)
        self.rowconfigure(97, weight=1)
        self.rowconfigure(99, minsize=10)

        self.update_listbox()

    def update_listbox(self):
        items = self.items_cb()
        if type(items[0]) in [tuple, list]:
            items = [{"name": name, "data": data} for name, data in items]
        elif type(items[0]) is str:
            items = [{"name": name, "data": None} for name in items]
        self.items = items
        selidx = self.lbox.curselection()
        selidx = selidx[0] if selidx else 0
        if selidx >= len(items):
            selidx = END
        self.lbox.delete(0, END)
        for item in items:
            fg = item.get('fgcolor', None)
            bg = item.get('bgcolor', None)
            fg = fg if fg else "#000000"
            bg = bg if bg else "#ffffff"
            self.lbox.insert(END, item['name'])
            self.lbox.itemconfig(END, foreground=fg)
            self.lbox.itemconfig(END, background=bg)
        self.lbox.focus()
        self.lbox.selection_clear(0, END)
        self.lbox.selection_anchor(selidx)
        self.lbox.selection_set(selidx)
        self.lbox.activate(selidx)
        self.lbox.see(selidx)
        self.listbox_select()
        self.after(100, self.update_button_states)

    def listbox_select(self, ev=None):
        self.sel_idx = None
        self.sel_txt = None
        self.sel_dat = None
        self.sel_rem = None
        selidx = self.lbox.curselection()
        if selidx:
            selidx = selidx[0]
            item = self.items[selidx]
            self.sel_idx = selidx
            self.sel_txt = item.get('name')
            self.sel_dat = item.get('data')
        self.after(100, self.update_button_states)

    def update_button_states(self):
        start, end = self.lbox.yview()
        selidx = self.lbox.curselection()
        selidx = selidx[0] if selidx else None
        endidx = self.lbox.index(END) - 1 if selidx is not None else None
        self.upbtn.config(state=NORMAL if start > 0.0 else DISABLED)
        self.dnbtn.config(state=NORMAL if end < 1.0 else DISABLED)
        if self.del_cb:
            self.delbtn.config(
                state=NORMAL if selidx is not None else DISABLED)
        if self.edit_cb:
            self.editbtn.config(
                state=NORMAL if selidx is not None else DISABLED)
        if self.raise_cb:
            self.raisebtn.config(state=NORMAL if selidx is not None
                                 and selidx > 0 else DISABLED)
        if self.lower_cb:
            self.lowerbtn.config(state=NORMAL if selidx is not None
                                 and selidx < endidx else DISABLED)
        if self.rem_sp:
            self.remainspin.config(
                state=NORMAL if selidx is not None else DISABLED)
        if self.ep_cb:
            self.epbtn.config(state=NORMAL if selidx is not None else DISABLED)
        for btn in self.extra_btns:
            btn.config(state=NORMAL if btn.en_cb(selidx) else DISABLED)

    def _scroll(self, n):
        self.lbox.yview_scroll(n, UNITS)
        self.update()
        self.update_button_states()

    def handle_button_up(self):
        for i in range(5):
            self.after(100 * i, self._scroll, -1)

    def handle_button_dn(self):
        for i in range(5):
            self.after(100 * i, self._scroll, 1)

    def handle_button_add(self):
        self.add_cb()
        self.update_listbox()

    def handle_button_del(self):
        self.del_cb(self.sel_idx, self.sel_txt, self.sel_dat)
        self.update_listbox()

    def handle_button_edit(self):
        self.edit_cb(self.sel_idx, self.sel_txt, self.sel_dat)
        self.update_listbox()

    def handle_button_raise(self):
        self.raise_cb(self.sel_idx, self.sel_txt, self.sel_dat)
        selidx = self.sel_idx - 1 if self.sel_idx > 0 else 0
        self.lbox.selection_clear(0, END)
        self.lbox.selection_anchor(selidx)
        self.lbox.selection_set(selidx)
        self.lbox.activate(selidx)
        self.lbox.see(selidx)
        self.listbox_select()
        self.update_listbox()

    def handle_button_lower(self):
        self.lower_cb(self.sel_idx, self.sel_txt, self.sel_dat)
        endidx = self.lbox.index(END) - 1
        selidx = self.sel_idx + 1 if self.sel_idx < endidx else endidx
        self.lbox.selection_clear(0, END)
        self.lbox.selection_anchor(selidx)
        self.lbox.selection_set(selidx)
        self.lbox.activate(selidx)
        self.lbox.see(selidx)
        self.listbox_select()
        self.update_listbox()

    def handle_button_extra(self, cb):
        cb(self.sel_idx, self.sel_txt, self.sel_dat)
        self.listbox_select()
        self.update_listbox()

    def handle_button_back(self):
        self.master.screen_pop()

    def handle_remaining_change(self, oldval, newval):
        self.sel_dat.remaining = newval
        self.update_listbox()
        self.update_button_states()
        self.master.save_configs()
        logging.info("Zutat " + self.sel_dat.name + " aufgefüllt auf: " +
                     str(newval) + " ml")

    def handle_button_ep(self):
        #cur_export = ExportPdf()
        ExportPdf.exportpdf(self)