Example #1
0
    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()
Example #2
0
    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
Example #3
0
    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()
Example #4
0
    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()
Example #5
0
    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'])
Example #6
0
    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
Example #7
0
 def _water_fix(self):
     if not self.allow_water.get():
         self.vg.set(100 - float_or_zero(self.pg.get()))
Example #8
0
 def _recalc_water(self, *args):
     self.water.set(100 - (float_or_zero(self.pg.get()) + float_or_zero(self.vg.get())))