Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
class AmountScreen(Frame):
    def __init__(self, master, labeltext="Wähle die Menge:", seltext="Select", whole=1, frac="", unit="ounce", callback=None):
        super(AmountScreen, self).__init__(master, class_="Amount")
        self.master = master
        self.callback = callback
        fracvals = ["", "1/8", "1/4", "1/3", "3/8", "1/2", "5/8", "2/3", "3/4", "7/8"]
        unitvals = ["dash", "ml", "tsp", "tbsp", "ounce", "cup"]
        self.bgcolor = master.bgcolor
        self.configure(bg=self.bgcolor)

        lbl = Label(self, text=labeltext)
        self.wholespin = TouchSpinner(self, width=40, value=whole, minval=0, maxval=99, incdecval=1, justify=RIGHT)
        self.fracspin = TouchSpinner(self, width=40, value=frac, values=fracvals)
        self.unitspin = TouchSpinner(self, width=40, value=unit, values=unitvals, justify=LEFT)
        selbtn = RectButton(self, text=seltext, width=120, command=self.handle_button_select)
        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.wholespin.grid(column=1, row=2, padx=5, pady=20, sticky=N+E)
        self.fracspin.grid(column=2, row=2, padx=5, pady=20, sticky=N)
        self.unitspin.grid(column=3, row=2, padx=5, pady=20, sticky=N+W)
        selbtn.grid(column=1, row=9, padx=20, pady=10, sticky=S+W)
        backbtn.grid(column=3, row=9, 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 handle_button_select(self):
        self.master.save_configs()
        numer, denom = 0, 1
        val = float(self.wholespin.get())
        frac = self.fracspin.get()
        if frac:
            numer, denom = frac.split("/")
        val += float(numer) / float(denom)
        val *= unit_measures[self.unitspin.get()]
        if callable(self.callback):
            self.callback(val)
        else:
            self.master.screen_pop()

    def handle_button_back(self):
        self.master.screen_pop()
Ejemplo n.º 3
0
    def __init__(self, master, labeltext="Wähle die Menge:", seltext="Select", whole=1, frac="", unit="ounce", callback=None):
        super(AmountScreen, self).__init__(master, class_="Amount")
        self.master = master
        self.callback = callback
        fracvals = ["", "1/8", "1/4", "1/3", "3/8", "1/2", "5/8", "2/3", "3/4", "7/8"]
        unitvals = ["dash", "ml", "tsp", "tbsp", "ounce", "cup"]
        self.bgcolor = master.bgcolor
        self.configure(bg=self.bgcolor)

        lbl = Label(self, text=labeltext)
        self.wholespin = TouchSpinner(self, width=40, value=whole, minval=0, maxval=99, incdecval=1, justify=RIGHT)
        self.fracspin = TouchSpinner(self, width=40, value=frac, values=fracvals)
        self.unitspin = TouchSpinner(self, width=40, value=unit, values=unitvals, justify=LEFT)
        selbtn = RectButton(self, text=seltext, width=120, command=self.handle_button_select)
        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.wholespin.grid(column=1, row=2, padx=5, pady=20, sticky=N+E)
        self.fracspin.grid(column=2, row=2, padx=5, pady=20, sticky=N)
        self.unitspin.grid(column=3, row=2, padx=5, pady=20, sticky=N+W)
        selbtn.grid(column=1, row=9, padx=20, pady=10, sticky=S+W)
        backbtn.grid(column=3, row=9, 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)
Ejemplo n.º 4
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()
Ejemplo n.º 5
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()
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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()
Ejemplo n.º 8
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)