Example #1
0
 def setup_nutrition_index (self):
     if not hasattr(self,'full_inglist'):
         self.add_ingredients([])
     if not hasattr(self,'nutInfoIndex'):
         self.nutInfoIndex = NutritionInfoIndex(
             self.rd, prefs=self.prefs, ui=self.ui,
             ingredients=self.full_inglist,
             in_string=self.in_string,
             )
     self.path.append((self.goto_page_index,[]))
     self.goto_page_index()
Example #2
0
class NutritionInfoDruid (gobject.GObject):

    """A druid (or "wizard") to guide a user through helping Gourmet
    calculate nutritional information for an ingredient.

    This consists in finding a USDA equivalent of the ingredient in
    question and possibly of converting a unit.
    """

    NUT_PAGE = 0
    UNIT_PAGE = 1
    CUSTOM_PAGE = 2
    DENSITY_PAGE = 3
    INFO_PAGE = 4
    INDEX_PAGE = 5

    DEFAULT_AMOUNT = 8
    DEFAULT_UNIT = 'oz.'

    __gsignals__ = {
        # The key callback will return a tuple (old_key,new_key)
        'key-changed':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_PYOBJECT,(gobject.TYPE_PYOBJECT,)),
        # The unit callback will return a tuple ((old_unit,old_key),(new_unit,new_key))
        'unit-changed':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_PYOBJECT,(gobject.TYPE_PYOBJECT,)),
        'finish':(gobject.SIGNAL_RUN_LAST,gobject.TYPE_NONE,())
        }

    def __init__ (self, nd, prefs, rec=None, in_string=''):
        self.ui = gtk.Builder()
        self.ui.add_from_file(os.path.join(current_path,'nutritionDruid.ui'))
        self.mm = MnemonicManager()
        self.mm.add_builder(self.ui)
        self.mm.fix_conflicts_peacefully()
        self.prefs = prefs
        self.nd = nd
        self.rec = rec
        self.in_string = in_string or (rec and _('recipe') or _('selection'))
        self.rd = self.nd.db
        self.def_ingredient_amounts = {} # For default amounts for nutritional label...
        self.amounts = {} # List amounts by ingredient
        self.ing_to_index = {} # A way to keep track of the order of our ingredients...
        self._setup_widgets_()
        # keep track of pages/setups we've been on
        self.path = []
        self.curpage = 0
        self.prevDruidButton.set_sensitive(False)
        # Initiate our gobject-ness so we can emit signals.
        gobject.GObject.__init__(self)
        # Save our position with our widget saver...
        WidgetSaver.WindowSaver(self.ui.get_object('window'),
                                self.prefs.get('nutritionDruid',{})
                                )


    def _setup_widgets_ (self):
        self.controls = []
        self.widgets =  ['notebook',
                         # ingKey Changing Stuff
                         'ingKeyLabel','ingKeyEntry','changeKeyButton','applyKeyButton',
                         'ingKeyLabel2',
                         # Search stuff
                         'usdaSearchEntry','usdaSearchAsYouTypeToggle','usdaFindButton',
                         'usdaFirstButton','usdaBackButton','usdaForwardButton',
                         'usdaLastButton','usdaShowingLabel',
                         'usdaTreeview','foodGroupComboBox',
                         'customBox','customButton',
                         # Unit adjusting stuff
                         'convertUnitLabel','fromUnitComboBoxEntry','fromUnitLabel',
                         'changeUnitButton','cancelUnitButton','saveUnitButton',
                         'fromAmountEntry','toUnitCombo','toAmountEntry',
                         # Wizard buttons
                         'prevDruidButton','ignoreButton','applyButton',
                         # Custom nutritional information screen
                         'massUnitComboBox','customNutritionAmountEntry',
                         # Density-Choosing page
                         'densityLabel','densityBox',
                         # Index page...
                         'editButton',
                         # INFO PAGE
                         'infoIngredientKeyLabel','infoUSDALabel','nutritionLabelBox',
                         'infoDensityLabel','infoOtherEquivalentsLabel',
                         'infoCustomEquivalentsTable',
                         ]
        for widget_name in self.widgets:
            setattr(self,widget_name,self.ui.get_object(widget_name))
            if not getattr(self,widget_name): print "WIDGET: ",widget_name,"NOT FOUND."
            # make a list of all core control widgets
            if widget_name!='notebook': self.controls.append(getattr(self,widget_name))
        self.usdaIndex = NutritionUSDAIndex(self.rd,
                                            prefs=self.prefs,
                                            widgets=[(w,getattr(self,w)) for w in self.widgets])
        self.ui.connect_signals(
            {'previousPage':self.previous_page_cb,
             'applyPage':self.apply_cb,
             'ignorePage':self.ignore_cb,
             'customPage':self.custom_cb,
             'usdaPage':self.usda_cb,
             'close':self.close,
             'on_foodGroupComboBox_changed':self.usdaIndex.food_group_filter_changed_cb,
             'edit':self.view_nutritional_info,
             'infoEditUSDAAssociation':self.info_edit_usda_association,
             }
            )
        # hide our tabs...
        self.notebook.set_show_tabs(False)
        # custom widgety stuff
        self.changeIngKeyAction = SpecialAction(highlight_widgets=[self.ingKeyEntry,self.applyKeyButton,
                                                                   ],
                                                initially_hidden=True,
                                                hide_on_highlight=[self.ingKeyLabel,self.changeKeyButton,
                                                                   ],
                                                all_controls=self.controls)
        self.changeKeyButton.connect('clicked',self.changeIngKeyAction.highlight_action)
        self.applyKeyButton.connect('clicked',self.apply_ingkey)
        self.changeUnitAction = SpecialAction(highlight_widgets=[self.fromUnitComboBoxEntry,
                                                                 self.saveUnitButton,
                                                                 self.cancelUnitButton,],
                                              initially_hidden=True,
                                              hide_on_highlight=[self.fromUnitLabel,self.changeUnitButton],
                                              all_controls=self.controls)
        self.changeUnitButton.connect('clicked',self.changeUnitAction.highlight_action)
        self.cancelUnitButton.connect('clicked',self.changeUnitAction.dehighlight_action)
        self.saveUnitButton.connect('clicked',self.save_unit_cb)
        # Nutrition box...
        self.custom_box=self.ui.get_object('customBox')
        self.customNutritionAmountEntry.connect('changed',self.custom_unit_changed)
        self.massUnitComboBox.connect('changed',self.custom_unit_changed)
        self._setup_custom_box()

    ### BEGIN METHODS FOR NUTRITIONAL INFORMATION INDEX

    def setup_nutrition_index (self):
        if not hasattr(self,'full_inglist'):
            self.add_ingredients([])
        if not hasattr(self,'nutInfoIndex'):
            self.nutInfoIndex = NutritionInfoIndex(
                self.rd, prefs=self.prefs, ui=self.ui,
                ingredients=self.full_inglist,
                in_string=self.in_string,
                )
        self.path.append((self.goto_page_index,[]))
        self.goto_page_index()

    ### END METHODS FOR NUTRITIONAL INFORMATION INDEX

    ### BEGIN METHODS FOR DISPLAYING CURRENT NUTRITIONAL INFO
    def view_nutritional_info (self, *args):
        nutalias = self.nutInfoIndex.get_selected_ingredient()
        if nutalias.ndbno == 0:
            # Then this is not really an item... we better edit it!
            self.add_ingredients(
                [(
                nutalias.ingkey,
                self.get_amounts_and_units_for_ingkey(nutalias.ingkey)
                  )]
                )
        else:
            self.show_info_page(nutalias)
            self.path.append(
                (self.show_info_page,[nutalias])
                )

    def show_info_page (self, nutalias):
        self.infoIngredientKeyLabel.set_text(nutalias.ingkey)
        self.infoUSDALabel.set_text(nutalias.desc)
        self.goto_page_info()
        self.prevDruidButton.set_sensitive(True)
        self.set_nutritional_label(nutalias)
        self.set_density_info(nutalias)
        self.info_nutalias = nutalias

    def set_density_info (self, nutalias):
        densities,extra_units = self.nd.get_conversions(nutalias.ingkey)
        density_texts = []
        for k,v in densities.items():
            if not k:
                density_texts = ['%.2f'%v] + density_texts
            else:
                density_texts.append('%s: %.2f'%(k,v))
        self.infoDensityLabel.set_text('\n'.join(density_texts) or 'None')
        eutexts = ['%s: %s g'%(k,v) for k,v in extra_units.items() ]
        eutexts.sort()
        extra_units_text = '\n'.join(eutexts)
        self.infoOtherEquivalentsLabel.set_text(
            extra_units_text or 'None'
            )
        others = self.rd.fetch_all(self.rd.nutritionconversions_table,ingkey=nutalias.ingkey)
        other_label = '\n'.join(['%s: %.1f g'%(
            conv.unit or '100 %s'%_('ml'),1.0/conv.factor
            ) for conv in others])
        if others:
            self.populate_custom_equivalents_table(others)
        else:
            self.infoCustomEquivalentsTable.hide()

    def populate_custom_equivalents_table (self, equivalents):
        # Remove previous children...
        for c in self.infoCustomEquivalentsTable.get_children():
            self.infoCustomEquivalentsTable.remove(c); c.unparent()
        for n,eq in enumerate(equivalents):
            lab = gtk.Label("%s: %.1f g"%(
                eq.unit or 'No unit', 1.0/eq.factor)
                            )
            rembut = gtk.Button('C_hange'); rembut.set_use_underline(True)
            rembut.connect('clicked',
                           self.info_edit_equivalent,
                           eq)
            self.infoCustomEquivalentsTable.attach(lab,
                                                   0,1,n,n+1)
            self.infoCustomEquivalentsTable.attach(rembut,
                                                   1,2,n,n+1)
        self.infoCustomEquivalentsTable.show_all()

    def set_nutritional_label (self, nutalias):
        if not hasattr(self,'nutritionLabel'):
            from nutritionLabel import NutritionLabel
            self.nutritionAmountLabel = gtk.Label()
            self.nutritionLabel = NutritionLabel(self.prefs, custom_label=' ')
            self.nutritionLabelBox.pack_start(self.nutritionAmountLabel,
                                              fill=0,
                                              expand=0)
            self.nutritionLabelBox.pack_start(self.nutritionLabel,
                                              fill=0,
                                              expand=0,
                                              )
            self.nutritionAmountLabel.set_alignment(0.0,0.0)
            self.nutritionAmountLabel.show()
            self.nutritionLabel.show()
        amount,unit = self.def_ingredient_amounts.get(nutalias.ingkey,
                                                  (self.DEFAULT_AMOUNT,
                                                   self.DEFAULT_UNIT)
                                                  )
        nutinfo = self.nd.get_nutinfo_for_inglist([
            MockObject(amount=amount,
                       unit=unit,
                       ingkey=nutalias.ingkey)
            ],
                                                  self.rd
                                                  )
        if nutinfo._get_vapor():
            amount = self.DEFAULT_AMOUNT; unit = self.DEFAULT_UNIT
            nutinfo = self.nd.get_nutinfo_for_inglist([
            MockObject(amount=amount,
                       unit=unit,
                       ingkey=nutalias.ingkey)
            ],
                                                      self.rd
                                                      )

        self.nutritionLabel.set_nutinfo(
            nutinfo
            )
        self.nutritionAmountLabel.set_markup(
            '<i>Nutritional information for %(amount)s %(unit)s</i>'%{
            'amount':amount,
            'unit':unit,
            })

    def get_amounts_and_units_for_ingkey (self, ingkey):
        """Return a list of amounts and units present in database for ingkey"""
        amounts_and_units = []
        ings = self.rd.fetch_all(self.rd.ingredients_table,ingkey=ingkey)
        for i in ings:
            a,u = i.amount,i.unit
            if (a,u) not in amounts_and_units:
                amounts_and_units.append((a,u))
        return amounts_and_units

    # start nutritional-info callbacks
    def info_edit_usda_association (self, *args):
        """Edit the USDA association for the item on the information page."""
        self.edit_nutinfo(ingkey=self.info_nutalias.ingkey,
                          desc=self.info_nutalias.desc)
        self.path.append(
            (self.edit_nutinfo,
             [self.info_nutalias.ingkey,
              self.info_nutalias.desc])
            )

    def info_edit_equivalent (self, button, eq):
        """Edit equivalents callback. eq is a nutalias DB object.
        """
        self.amounts[self.ingkey] = {}
        self.amount = 1
        self.ingkey = self.info_nutalias.ingkey
        self.set_from_unit(eq.unit)
        self.fromAmountEntry.set_text('1')
        conv = self.nd.get_conversion_for_amt(1,eq.unit,self.info_nutalias.ingkey)
        amt_in_grams = conv * 100
        self.setup_to_units()
        to_unit = cb.cb_set_active_text(self.toUnitCombo,'g')
        self.toAmountEntry.set_text(convert.float_to_frac(amt_in_grams,
                                                          fractions=convert.FRACTIONS_ASCII
                                                          ))

        # Hack to avoid adding ourselves to the path on a "back" event
        # -- if button is None, then we know we were called
        # artificially from previous_page_cb (not from a button press
        # event)
        if button:
            self.path.append(
                (self.info_edit_equivalent,
                 [None,eq])
                )
        self.goto_page_unit_convert()

    # end nutritional-info callbacks

    ### END METHODS FOR DISPLAYING CURRENT NUTRITIONAL INFO

    ### BEGIN  METHODS FOR CUSTOM NUTRITIONAL INTERFACE
    def _setup_custom_box (self):
        """Setup the interface for entering custom nutritional information.
        """
        t = gtk.Table()
        masses = [i[0] for i in defaults.UNIT_GROUPS['metric mass']\
                  + defaults.UNIT_GROUPS['imperial weight']]
        cb.set_model_from_list(
            self.massUnitComboBox,
            masses)
        cb.cb_set_active_text(self.massUnitComboBox,'g')
        self.customNutritionAmountEntry.set_value(100)
        self.nutrition_info = {}
        self.custom_box.add(t)
        self.changing_percent_internally = False
        self.changing_number_internally = False
        l=gtk.Label('%RDA'); l.show()
        t.attach(l,2,3,0,1)
        for n,nutstuff in enumerate(NUT_LAYOUT):
            if nutstuff == SEP:
                hs = gtk.HSeparator()
                t.attach(hs,0,2,n+1,n+2,xoptions=gtk.FILL)
                hs.show()
                continue
            label_txt,typ,name,properties,show_percent,unit = nutstuff
            if unit: label_txt += " (" + unit + ")"
            label = gtk.Label(label_txt); label.show()
            label.set_alignment(0,0.5)
            t.attach(label,0,1,n+1,n+2,xoptions=gtk.FILL)
            entry = NumberEntry(default_to_fractions=False)
            entry.show()
            t.attach(entry,1,2,n+1,n+2,xoptions=gtk.FILL)
            if show_percent:
                percent_entry = NumberEntry(default_to_fractions=False,
                                            decimals=0)
                percent_entry.entry.set_width_chars(4)
                percent_entry.show()
                percent_label = gtk.Label('%'); percent_label.show()
                t.attach(percent_entry,2,3,n+1,n+2)
                t.attach(percent_label,3,4,n+1,n+2)
                percent_label.set_alignment(0,0.5)
                percent_entry.connect('changed',self.percent_changed_cb,name,entry)
                percent_entry.entry.set_width_chars(5)
            else: percent_entry = None
            entry.connect('changed',self.number_changed_cb,name,percent_entry)
        t.set_row_spacings(6)
        t.set_col_spacings(12)
        t.show()

    def number_changed_cb (self, widget, name, percent_widget):
        v = widget.get_value()
        self.nutrition_info[name]=v
        if not v: return
        if self.changing_number_internally: return
        if percent_widget:
            rda = RECOMMENDED_INTAKE.get(name,None)*2000
            if rda:
                self.changing_percent_internally = True
                percent_widget.set_value((float(v)/rda)*100)
                self.changing_percent_internally = False

    def percent_changed_cb (self, widget, name, number_widget):
        if self.changing_percent_internally: return
        v = widget.get_value()
        if not v: return
        if number_widget:
            rda = RECOMMENDED_INTAKE.get(name,None)*2000
            if rda:
                self.changing_number_internally = True
                number_widget.set_value(
                    v*0.01*rda
                    )
                self.changing_number_internally = False

    def custom_unit_changed (self, *args):
        amount = self.customNutritionAmountEntry.get_value()
        unit = cb.cb_get_active_text(self.massUnitComboBox)
        if amount and unit:
            base_convert = self.nd.conv.converter(unit,'g')/float(100)
            self.custom_factor = 1/(base_convert * amount)

    def apply_custom (self, *args):
        nutinfo = self.nutrition_info.copy()
        for k,v in nutinfo.items():
            if type(v)==int or type(v)==float: nutinfo[k]=v*self.custom_factor
            # Special case fat, which is listed as one item but is in
            # fact a combination of 3. We'll have to fudge the info
            # about mono- v. poly- unsaturated fats.
            if k=='fat':
                totfat = v * self.custom_factor
                unsatfat = totfat - nutinfo.get('fasat',0)
                del nutinfo['fat']
                nutinfo['fapoly']=unsatfat # Fudge
        nutinfo['desc']=self.ingkey
        ndbno = self.nd.add_custom_nutrition_info(nutinfo)

    ### END METHODS FOR CUSTOM NUTRITIONAL INTERFACE

    ### METHODS TO SET CURRENT ITEM AND UNIT INFO
    def set_ingkey (self, txt):
        self.ingKeyEntry.set_text(txt)
        self.ingKeyLabel.set_markup('<i><b>'+txt+'</b></i>') # USDA Page
        self.ingKeyLabel2.set_markup('<i><b>'+txt+'</b></i>') # Custom Page
        self.nutrition_info['desc']=txt
        self.ingkey = txt

    ### BEGIN METHODS FOR SETTING UNIT EQUIVALENTS

    def set_from_unit (self, txt):
        if not self.ingkey:
            return
        if txt:
            self.fromUnitLabel.set_text(txt)
            self.fromUnitComboBoxEntry.get_children()[0].set_text(txt)
            self.fromUnit = txt
            curamt = ' '.join([convert.float_to_frac(self.amount,
                                                     fractions=convert.FRACTIONS_ASCII),
                               self.fromUnit,self.ingkey])
        else:
            self.fromUnitLabel.set_text(self.ingkey+' (no unit)')
            self.fromUnit = ''
            curamt = convert.float_to_frac(
                self.amount,
                fractions=convert.FRACTIONS_ASCII)+' '+self.ingkey
        self.convertUnitLabel.set_markup(
            '<span weight="bold" size="larger">' + \
            _('Convert unit for %s')%self.ingkey + \
            '</span>' + \
            '\n<i>' + \
            _('In order to calculate nutritional information, Gourmet needs you to help it convert "%s" into a unit it understands.')%curamt + \
            '</i>')

    def setup_to_units (self):
        """Setup list of units we need to convert to.

        Usually, this will be a list of mass units.
        """
        masses = [i[0] for i in defaults.UNIT_GROUPS['metric mass'] + defaults.UNIT_GROUPS['imperial weight']]
        volumes = [i[0] for i in  defaults.UNIT_GROUPS['metric volume'] + defaults.UNIT_GROUPS['imperial volume']]
        to_units = masses
        self.densities,self.extra_units = self.nd.get_conversions(self.ingkey)
        for d in self.densities.keys():
            if d:
                to_units.extend(["%s (%s)"%(u,d) for u in volumes])
            else:
                to_units.extend(volumes)
        to_units.sort()
        for u in self.extra_units:
            to_units = [u]+to_units
        cb.set_model_from_list(self.toUnitCombo,
                               to_units)
        self.toUnitCombo.set_active(0)
        self.toUnitCombo.set_wrap_width(3)

    def apply_amt_convert (self,*args):
        to_unit = cb.cb_get_active_text(self.toUnitCombo)
        base_convert = self.nd.conv.converter('g',to_unit)
        if not base_convert:
            self.densities,self.extra_units = self.nd.get_conversions(self.ingkey)
            if self.extra_units.has_key(to_unit):
                base_convert = 1/self.extra_units[to_unit]
            else:
                # this is a density, we hope...
                if to_unit.find(' (')>0:
                    to_unit,describer = to_unit.split(' (')
                    describer = describer[0:-1]
                    density = self.densities[describer]
                else:
                    if not self.densities.has_key(None):
                        raise RuntimeError("Unable to make sense of conversion from %s %s"%(to_unit,self.ingkey))
                    density = self.densities[None]
                base_convert = self.nd.conv.converter('g',to_unit,density=density)
        to_amount = convert.frac_to_float(self.toAmountEntry.get_text())
        from_amount = convert.frac_to_float(self.fromAmountEntry.get_text())
        ratio = from_amount / to_amount
        factor = base_convert * ratio
        from_unit = self.fromUnit
        self.nd.set_conversion(self.ingkey,from_unit,factor)

    ### END METHODS FOR SETTING UNIT EQUIVALENTS

    ### BEGIN METHODS FOR SEARCHING USDA INDEX

    def autosearch_ingkey (self):
        """Automatically do a search for our current ingkey.

        We're pretty smart about this: in other words, we won't do a
        search that doesn't have results.
        """
        self.usdaIndex.set_search(self.ingkey)

    def apply_nut_equivalent (self,*args):
        nut = self.usdaIndex.get_selected_usda_item()
        self.nd.set_key_from_ndbno(self.ingkey,nut)
        # Now see if we need to do any conversion or not
        self.setup_to_units()

    ### END METHODS FOR SEARCHING USDA INDEX

    ### BEGIN CALLBACKS FOR QUICK-CHANGES OF INGREDIENT KEY / UNIT

    def apply_ingkey (self,*args):
        key = self.ingKeyEntry.get_text()
        if key==self.ingkey:
            self.changeIngKeyAction.dehighlight_action()
            return
        #ings = self.rd.fetch_all(self.rd.ingredients_table,ingkey=self.ingkey)
        #self.rd.modify_ings(ings,{'ingkey':key})
        if self.rec:
            try:
                user_says_yes = de.getBoolean(
                    label=_('Change ingredient key'),
                    sublabel=_(
                    'Change ingredient key from %(old_key)s to %(new_key)s everywhere or just in the recipe %(title)s?'
                    )%{'old_key':self.ingkey,
                       'new_key':key,
                       'title':self.rec.title
                       },
                    custom_no=_('Change _everywhere'),
                    custom_yes=_('_Just in recipe %s')%self.rec.title
                    )
            except de.UserCancelledError:
                self.changeIngKeyAction.dehighlight_action()
                return
        else:
            if not de.getBoolean(label=_('Change ingredient key'),
                                 sublabel=_('Change ingredient key from %(old_key)s to %(new_key)s everywhere?'
                                            )%{'old_key':self.ingkey,
                                               'new_key':key,
                                               },
                                 cancel=False,
                                 ):
                self.changeIngKeyAction.dehighlight_action()
                return
        if self.rec and user_says_yes:
            self.rd.update_by_criteria(self.rd.ingredients_table,
                           {'ingkey':self.ingkey,
                            'recipe_id':self.rec.id},
                           {'ingkey':key}
                           )
        else:
            self.rd.update_by_criteria(self.rd.ingredients_table,
                          {'ingkey':self.ingkey},
                          {'ingkey':key}
                          )
        old_key = self.ingkey
        self.set_ingkey(key)
        # Update amounts dictionary...
        self.amounts[key] = self.amounts[old_key]
        del self.amounts[old_key]
        self.autosearch_ingkey()
        self.changeIngKeyAction.dehighlight_action()
        if self.nd.get_nutinfo(key):
            self.setup_to_units()
            self.check_next_amount()
        self.emit('key-changed',(old_key,key))

    def save_unit_cb (self,*args):
        from_unit = self.fromUnitComboBoxEntry.get_children()[0].get_text()
        old_from_unit = self.fromUnit
        #ings = self.rd.fetch_all(self.rd.ingredients_table,ingkey=self.ingkey,unit=old_from_unit)
        #self.rd.modify_ings(ings,{'unit':from_unit})
        if self.rec and de.getBoolean(
            label=_('Change unit'),
            sublabel=_(
            'Change unit from %(old_unit)s to %(new_unit)s for all ingredients %(ingkey)s or just in the recipe %(title)s?'
            )%{'old_unit':old_from_unit,
               'new_unit':from_unit,
               'ingkey':self.ingkey,
               'title':self.rec.title
               },
            custom_no=_('Change _everywhere'),
            custom_yes=_('_Just in recipe %s')%self.rec.title
            ):
            self.rd.update_by_criteria(self.rd.ingredients_table,
                           {'ingkey':self.ingkey,
                            'unit':old_from_unit,
                            'recipe_id':self.rec.id},
                           {'unit':from_unit}
                           )
        else:
            self.rd.update_by_criteria(self.rd.ingredients_table,
                          {'ingkey':self.ingkey,
                           'unit':old_from_unit},
                          {'unit':from_unit}
                          )
        self.set_from_unit(self.fromUnitComboBoxEntry.get_children()[0].get_text())
        self.changeUnitAction.dehighlight_action()
        self.emit('unit-changed',((old_from_unit,self.ingkey),(from_unit,self.ingkey)))

    ### END CALLBACKS FOR QUICK-CHANGES OF INGREDIENT KEY / UNIT

    ### BEGIN METHODS FOR DENSITY-CHOOSING INTERFACE

    def get_density (self,amount,unit):
        self.densityLabel.set_text(
            _("""In order to calculate nutritional information for "%(amount)s %(unit)s %(ingkey)s", Gourmet needs to know its density. Our nutritional database has several descriptions of this food with different densities. Please select the correct one below.""")%({'amount':amount,'unit':unit,'ingkey':self.ingkey})
            )
        for c in self.densityBox.get_children():
            self.densityBox.remove(c)
            c.unparent()
        group = None
        def density_callback (rb, name):
            self.custom_density = name
        for d in self.densities.keys():
            group = gtk.RadioButton(group,str(d)+' '+'(%.2f)'%self.densities[d])
            group.connect('toggled',density_callback,d)
            self.densityBox.pack_start(group,expand=False,fill=False)
            group.show()
        group.set_active(True)
        self.custom_density = d
        self.goto_page_density()

    def apply_density (self):
        self.nd.set_density_for_key(
            self.ingkey,
            self.custom_density
            )
        for c in self.densityBox.get_children(): c.hide()

    ### END METHODS FOR DENSITY CHANGING INTERFACE

    ### BEGIN CALLBACKS TO WALK THROUGH INGREDIENTS

    def add_ingredients (self, inglist, full_inglist=[]):
        """Add a list of ingredients for our druid to guide the user through.

        Our ingredient list is in the following form for, believe it
        or not, good reason:

        [(ingkey, [(amount,unit),(amount,unit),(amount,unit)]),
         (ingkey, [(amount,unit),(amount,unit),(amount,unit)]),
         ...
         ]

        The ingkey is a string, of course.
        amount can be a float or None
        unit can be a string or None

        For each item in the list, we will ask the user to select a
        USDA equivalent.

        Once we've done that, we'll check if the user needs to convert
        the unit as well.
        """
        # to start, we take our first ing
        self.inglist = inglist
        if not full_inglist:
            if self.rec:
                self.full_inglist = []
                for i in self.rd.get_ings(self.rec):
                    self.full_inglist.append(i.ingkey)
                    self.def_ingredient_amounts[i.ingkey] = (i.amount,i.unit)
            else:
                self.full_inglist = []
                for ingkey,amounts_and_units in self.inglist:
                    self.full_inglist.append(ingkey)
                    if amounts_and_units:
                        self.def_ingredient_amounts[ingkey] = amounts_and_units[0]
        self.ing_index = 0
        self.setup_next_ing()

    def setup_next_ing (self):
        """Move to next ingredient."""
        if self.ing_index >= len(self.inglist):
            self.finish()
            return
        ing = self.inglist[self.ing_index]
        self.ing_index+=1
        if not ing:
            return
        ingkey,amounts = ing
        self.ing_to_index[ingkey] = self.ing_index
        self.amounts[ingkey] = amounts
        self.amount_index = 0
        self.set_ingkey(ingkey)
        if not self.nd.get_nutinfo(ingkey):
            self.edit_nutinfo()
            self.path.append((self.edit_nutinfo,[ingkey]))
        else:
            self.setup_to_units()
            self.check_next_amount()

    def edit_nutinfo (self, ingkey=None, desc=None):
        self.amounts[ingkey or desc] = self.get_amounts_and_units_for_ingkey(ingkey)
        self.amount_index = 0
        if ingkey:
            self.set_ingkey(ingkey)
        if desc:
            self.usdaIndex.set_search(desc)
        else:
            self.autosearch_ingkey()
        self.goto_page_key_to_nut()
        ing_index = self.ing_to_index.get(ingkey,None)
        if ing_index: self.ing_index = ing_index

    def check_next_amount (self):
        """Check the next amount on our amounts list.

        If the amount is already convertible, we don't do anything.
        If the amount is not convertible, we ask our user for help!
        """
        if self.amount_index >= len(self.amounts[self.ingkey]):
            self.setup_next_ing()
            return
        amount,unit = self.amounts[self.ingkey][self.amount_index]
        if not amount: amount=1
        self.amount = amount
        self.amount_index += 1
        existing_conversion = self.nd.get_conversion_for_amt(amount,unit,self.ingkey,fudge=True)
        existing_conversion_fudged = (existing_conversion
                                      and
                                      (not self.nd.get_conversion_for_amt(amount,unit,self.ingkey,fudge=False)
                                       ))
        if existing_conversion_fudged:
            self.get_density(amount,unit)
        elif existing_conversion:
            self.check_next_amount()
        else:
            self.edit_units(amount, unit, self.ingkey)
            self.path.append((self.edit_units,
                              [amount,unit,self.ingkey,self.amount_index])
                             )

    def edit_units (self, amount, unit, ingkey, indx=None):
        self.set_ingkey(ingkey)
        self.set_from_unit(unit)
        if indx is not None: self.amount_index = indx
        self.fromAmountEntry.set_text(convert.float_to_frac(amount,
                                                            fractions=convert.FRACTIONS_ASCII)
                                      )
        self.toAmountEntry.set_text(convert.float_to_frac(amount,
                                                          fractions=convert.FRACTIONS_ASCII)
                                    )
        self.goto_page_unit_convert()

    def previous_page_cb (self, *args):
        """Move to the previous item in self.path


        PATH ITEMS are in the form:

        (CUSTOM_METHOD,ARGS)

        We'll call CUSTOM_METHOD(ARGS)
        """
        self.path.pop() # pop off current page...
        method,args = self.path[-1]
        if callable(method):
            method(*args)
        else:
            # for convenience, if the method isn't callable, we take
            # it to be a page
            self.notebook.set_current_page(method)
        if len(self.path) <= 1:
            self.prevDruidButton.set_sensitive(False)
        return

    def apply_cb (self, *args):
        page = self.notebook.get_current_page()
        if page == self.NUT_PAGE:
            self.apply_nut_equivalent()
            self.check_next_amount()
        elif page == self.UNIT_PAGE:
            self.apply_amt_convert()
            # if out of amounts, this will move to the next ingredient
            self.check_next_amount()
        elif page == self.CUSTOM_PAGE:
            if not self.custom_factor:
                de.show_message(_("To apply nutritional information, Gourmet needs a valid amount and unit."))
                return
            self.apply_custom()
            self.check_next_amount()
        elif page == self.DENSITY_PAGE:
            self.apply_density()
            self.check_next_amount()
        self.curpage += 1
        self.prevDruidButton.set_sensitive(True)

    def ignore_cb (self, *args):
        page = self.notebook.get_current_page()
        self.curpage += 1
        self.prevDruidButton.set_sensitive(True)
        if page == self.NUT_PAGE:
            self.setup_next_ing()
        else:
            self.check_next_amount()

    ### END CALLBACKS TO WALK THROUGH INGREDIENTS

    ### BEGIN CONVENIENCE METHODS FOR SWITCHING PAGES
    def goto_page_key_to_nut (self):
        for b in [self.applyButton,self.ignoreButton]: b.show()
        for b in [self.editButton]: b.hide()
        self.notebook.set_current_page(self.NUT_PAGE)

    def goto_page_unit_convert(self):
        for b in [self.applyButton,self.ignoreButton]: b.show()
        for b in [self.editButton]: b.hide()
        self.notebook.set_current_page(self.UNIT_PAGE)

    def goto_page_custom (self):
        for b in [self.applyButton,self.ignoreButton]: b.show()
        for b in [self.editButton]: b.hide()
        self.notebook.set_current_page(self.CUSTOM_PAGE)

    def goto_page_density (self):
        for b in [self.applyButton,self.ignoreButton]: b.show()
        for b in [self.editButton]: b.hide()
        self.notebook.set_current_page(self.DENSITY_PAGE)

    def goto_page_index (self):
        for b in [self.editButton]: b.show()
        for b in [self.applyButton,self.ignoreButton]: b.hide()
        self.notebook.set_current_page(self.INDEX_PAGE)

    def goto_page_info (self):
        for b in [self.editButton,self.applyButton,self.ignoreButton]: b.hide()
        self.notebook.set_current_page(self.INFO_PAGE)

    ### END CONVENIENCE METHODS FOR SWITCHING PAGES

    def custom_cb (self, *args): self.goto_page_custom()

    def usda_cb (self, *args): self.goto_page_key_to_nut()

    ### BEGIN METHODS FOR STARTING AND FINISHING

    def show (self):
        self.ui.get_object('window').show()

    def finish (self):
        # When done -- goto nutritional index page...
        if not hasattr(self,'nutInfoIndex'):
            self.setup_nutrition_index()
        else:
            self.nutInfoIndex.reset()
            self.goto_page_index()

    def close (self, *args):
        self.ui.get_object('window').hide()
        self.emit('finish')