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()
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)
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()
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()
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)