def close(self, ok_clicked, **kwargs): ''' Close and return the fludo.Liquid if ok_button is clicked. Otherwise just close. ''' if ok_clicked: self.callback(fludo.Liquid( name=DEFAULT_INGREDIENT_NAME if not self.name.get() else self.name.get(), pg=float_or_zero(self.pg.get()), vg=float_or_zero(self.vg.get()), nic=float_or_zero(self.nic.get()), cost_per_ml=float_or_zero(self.cost.get()) )) super().close()
def _set_fill(self) -> None: ''' Only Mixer must call this when toggling the fill. ''' self.ml_scale.grid_forget() self.fill_label.grid(row=self.mixer.get_ingredient_grid_row(self), column=1) self.ml_entry.configure(state='readonly') try: self.ml.trace_vdelete('w', self._ml_traceid) del(self._ml_traceid) except (AttributeError, tk._tkinter.TclError): # not set pass try: self._fill_traceid except AttributeError: self._fill_traceid = self.ml_max.trace('w', lambda var, idx, op: self.ml.set( int((self.mixer.get_bottle_volume() - sum([float_or_zero(ingr.ml.get()) for ingr in self.mixer._ingredient_list if ingr != self])) * 10) / 10 )) self.mixer.update(self) set_icon(self.fill_button, icons['bottle-icon-filled'], compound=tk.NONE) self.fill_set = True
def set_bottle_volume(self, ml: Union[int, float]) -> None: ''' Updates the bottle volume (size). ''' if ml > CONTAINER_MAX: raise Exception('Parameter ml larger than maximum allowed!') if ml < CONTAINER_MIN: raise Exception('Parameter ml smaller than minimum allowed!') ratio = ml / self._bottle_vol self._bottle_vol = ml # Recalc every ingredients volume to preserve ratio. for ingredient in self._ingredient_list: new_value = float_or_zero(ingredient.ml.get()) * ratio ingredient.ml_scale.configure(to=new_value + 1) # dummy scale limit change ingredient.ml_scale.set(new_value) # so that we can update it if self.bottle_viewer is not None: self.bottle_viewer.set_bottle_size(self.get_bottle_volume()) self.update()
def add_ingredient(self, liquid_or_ingredient: Union[fludo.Liquid, 'MixtureIngredientController']) -> None: ''' Adds an ingredient to the mixture. If liquid_or_ingredient is a fludo.Liquid (or descendant) it will create a MixtureIngredientController representing the liquid. If it's already a MixtureIngredientController, then it will be used as is. ''' if liquid_or_ingredient is None: return if isinstance(liquid_or_ingredient, fludo.Liquid): ingredient = MixerIngredientController(self, liquid_or_ingredient, auto_add=False) elif isinstance(liquid_or_ingredient, MixerIngredientController): ingredient = liquid_or_ingredient else: raise TypeError('Paremeter liquid_or_ingredient isn\'t the right type.') self.frame.interior.grid_rowconfigure(self.get_ingredient_grid_row(ingredient), minsize=30) current_total_vol = sum([float_or_zero(row.ml.get()) for row in self._ingredient_list]) remaining_vol = self._bottle_vol - current_total_vol ingredient.ml_scale.configure(to=remaining_vol) ingredient.ml_max.set(remaining_vol) self._ingredient_list.append(ingredient) if len(self._ingredient_list) >= MAX_INGREDIENTS: self.add_button.configure(state=tk.DISABLED) self.add_button_ttip = CreateToolTip(self.add_button, 'Max number of ingredients reached.') self.update() # Hide start message if not self._labels_shown: self.labels_frame.grid(row=2, column=0, sticky=tk.E) self.start_label.grid_forget()
def update(self, skip_limiting_ingredient: Optional['MixerIngredientController'] = None) -> None: ''' Updates the Mixer. Called whenever a MixerIngredientController is changed. This method updates every MixerIngredientController instance as well, limiting their possible maximum volume that can be entered. Because the limit is constantly recalculated, to avoid rounding errors on the instance that's currently changed by the user, the changing instance can be skipped from limiting. ''' # The ingredient which is currently calling this update when using it's scale should be # skipped from the limit calculation. # Calc current total volume of ingredients (skipping ingredient that has the fill flag). current_total_vol = sum([float_or_zero(ingredient.ml.get()) for ingredient in self._ingredient_list if not ingredient.fill_set]) # Calc free volume within the bottle free_volume = self._bottle_vol - current_total_vol new_total_cost = 0 for ingredient in self._ingredient_list: # Clac ingredients possible max volume rounded to 1 digits of precision ingredient_max = int((float_or_zero(ingredient.ml.get()) + free_volume) * 10) / 10 # Limit the scale and set the max label if ingredient != skip_limiting_ingredient: ingredient.ml_scale.configure(to=ingredient_max) ingredient.ml_max.set(ingredient_max) # If the remaining volume is smaller than the rounding error, set max label to 'Full' if free_volume < 0.1: ingredient.ml_max.set('Full') else: ingredient.ml_max.set(ingredient.ml_scale['to']) # If fill is set for an ingredient, clear the max label if ingredient.fill_set: ingredient.ml_max.set('') # Update the volume of the liquid represented by the ingredient instance. # This propagates the change of the ml variable to the liquid object. ingredient.liquid.update_ml(float_or_zero(ingredient.ml.get())) new_total_cost = ingredient.liquid.get_cost() self.total_cost = new_total_cost # Update the status bar message if self.fill_set or free_volume < 0.1: self.liquid_volume.set(' | Vol. %(limit).1f ml (bottle full)' % { 'limit': self._bottle_vol}) else: self.liquid_volume.set(' | Vol. %(vol).1f ml (in %(limit).1f ml. bottle)' % { 'vol': sum([float_or_zero(ingredient.ml.get()) for ingredient in self._ingredient_list]), 'limit': self._bottle_vol}) mixture = self.get_mixture() if mixture: self.mixture_description.set('%d%% PG / %d%% VG, Nic. %.1f mg/ml, Cost: %.1f' % ( mixture.pg, mixture.vg, mixture.nic, mixture.get_cost())) else: self.mixture_description.set('Nothing to mix. |') if self.bottle_viewer is not None: self.bottle_viewer.set_ingredients(self.dump()['ingredients'])
def _validate_entries(self, action, value, widget_name): ''' Validator for all entry fields. ''' if 'name_entry' in widget_name: if action == '-1': # focus change if not value: self.name.set(DEFAULT_INGREDIENT_NAME) elif self.name.get() == DEFAULT_INGREDIENT_NAME: self.name.set('') if len(value) < 30: return True else: return False if 'pg_entry' in widget_name: if float_or_zero(value) > 100: return False if not self.allow_water.get(): self.vg.set(100 - float_or_zero(value)) return True elif (float_or_zero(value) + float_or_zero(self.vg.get())) > 100: self.vg.set(100 - float_or_zero(value)) if action == '-1': # focus change if not value: self.pg.set(0) if 'vg_entry' in widget_name: if float_or_zero(value) > 100: return False if not self.allow_water.get(): self.pg.set(100 - float_or_zero(value)) return True elif (float_or_zero(value) + float_or_zero(self.pg.get())) > 100: self.pg.set(100 - float_or_zero(value)) if action == '-1': # focus change if not value: self.vg.set(0) if 'pg_entry' in widget_name or 'vg_entry' in widget_name: if value: try: float(value) return True except (TypeError, ValueError): return False else: return True # allow empty string if 'nic_entry' in widget_name or 'cost_entry' in widget_name: if action == '-1': # focus change if not value: self.nic.set(0) if value: try: float(value) if float(value) < 0 or float(value) > MAX_NIC_CONCENTRATION: return False else: return True except (TypeError, ValueError): return False else: return True # allow empty string
def _water_fix(self): if not self.allow_water.get(): self.vg.set(100 - float_or_zero(self.pg.get()))
def _recalc_water(self, *args): self.water.set(100 - (float_or_zero(self.pg.get()) + float_or_zero(self.vg.get())))