~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/shopgui.py

  • Committer: Bazaar Package Importer
  • Author(s): Rolf Leggewie
  • Date: 2008-07-26 13:29:41 UTC
  • Revision ID: james.westby@ubuntu.com-20080726132941-6ldd73qmacrzz0bn
Tags: upstream-0.14.0
ImportĀ upstreamĀ versionĀ 0.14.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 #!/usr/bin/env python
 
2
import gtk.glade, gtk, gobject, pango, sys, os.path, time, os, string
 
3
import recipeManager, convert, reccard
 
4
from gtk_extras import WidgetSaver, mnemonic_manager
 
5
from gtk_extras import dialog_extras as de
 
6
from gtk_extras import treeview_extras as te
 
7
import exporters.printer as printer
 
8
from gdebug import *
 
9
from gglobals import *
 
10
from gettext import gettext as _
 
11
#from nutrition.nutritionLabel import NutritionLabel
 
12
#from nutrition.nutrition import NutritionInfoList
 
13
from gtk_extras.FauxActionGroups import ActionManager
 
14
 
 
15
 
 
16
class ShopGui (ActionManager):
 
17
    """A class to manage our shopping window."""
 
18
    def __init__ (self, RecGui, conv=None):
 
19
        debug("__init__ (self, RecGui):",5)
 
20
        self.glade = gtk.glade.XML(os.path.join(gladebase,'shopList.glade'))
 
21
        self.init_action_manager()
 
22
        self.mm = mnemonic_manager.MnemonicManager()
 
23
        self.mm.add_glade(self.glade)
 
24
        self.mm.fix_conflicts_peacefully()
 
25
        self.conv=conv
 
26
        self.rg = RecGui
 
27
        self.recs = {}
 
28
        self.extras = []
 
29
        self.data,self.pantry=self.grabIngsFromRecs([])
 
30
        self.setup_popup()
 
31
        self.create_slTree(self.data)
 
32
        self.create_pTree(self.pantry)
 
33
        self.create_rtree()
 
34
        ## We need to keep track of including/excluding options...
 
35
        ## Here's our solution: we have a dictionary where we can lookg
 
36
        ## up ingredients by recipe by key
 
37
        ## {'recipe_id' : {'key1' : False
 
38
        ##                 'key2  : True}} ... where true and false mean include/exclude
 
39
        self.includes = {}
 
40
        self.widget = self.glade.get_widget('shopList')
 
41
        self.stat = self.glade.get_widget('statusbar1')
 
42
        self.contid = self.stat.get_context_id('main')
 
43
        self.conf = [] #WidgetSavers...
 
44
        self.conf.append(WidgetSaver.WindowSaver(self.widget,
 
45
                                            self.rg.prefs.get('shopGuiWin',{}),
 
46
                                                 show=False))
 
47
        panes = ['vpaned1','hpaned1']
 
48
        for p in panes:
 
49
            w=self.glade.get_widget(p)
 
50
            self.conf.append(WidgetSaver.WidgetSaver(
 
51
                w,
 
52
                self.rg.prefs.get('shop%s'%p,{'position':w.get_position()})
 
53
                ))
 
54
        self.setup_shop_dialog()
 
55
        self.glade.signal_autoconnect({
 
56
            'shopHide' : self.hide,
 
57
            'shopSave' : self.save,
 
58
            'shopPrint' : self.printList,
 
59
            'shopClear' : self.clear,
 
60
            'move_to_shopping' : self.rem_selection_from_pantry,
 
61
            'move_to_pantry' : self.add_selection_to_pantry,
 
62
            'show_help': lambda *args: de.show_faq(HELP_FILE,jump_to='Shopping'),
 
63
            #'show_nutritional_info': self.show_nutritional_info,
 
64
            })
 
65
 
 
66
    def init_action_manager (self):
 
67
        ActionManager.__init__(
 
68
            self,self.glade,
 
69
            {'shopGroup':[{'shopRemove':[{'tooltip':_('Move selected items from shopping list to "pantry" list. You can also move items by dragging and dropping.')},
 
70
                                         # widgets
 
71
                                         ['moveToPantryButton',
 
72
                                          'remove_from_shopping_list_menuitem',
 
73
                                          'add_to_pantry_popup_menuitem',
 
74
                                          ],
 
75
                                         ]},
 
76
                          {'pantryRemove':[{'tooltip':_('Move selected items back to the shopping list. You can also move items by dragging and dropping.')},
 
77
                                           # widgets
 
78
                                           ['moveToShoppingListButton',
 
79
                                            'add_to_sl_popup_menuitem',
 
80
                                            'return_to_shopping_menuitem',
 
81
                                            ]],
 
82
                           }                 
 
83
                          ],
 
84
             },
 
85
            # callbacks
 
86
            [('pantryRemove',self.rem_selection_from_pantry),
 
87
             ('shopRemove',self.add_selection_to_pantry),
 
88
             ])
 
89
 
 
90
    def create_rtree (self):
 
91
        debug("create_rtree (self):",5)
 
92
        self.rmodel = self.create_rmodel()
 
93
        #self.rectree = gtk.TreeView(self.rmodel)
 
94
        self.rectree = self.glade.get_widget('shopRTree')
 
95
        self.glade.signal_connect('ingmen_pantry',self.add_selection_to_pantry)
 
96
        self.glade.signal_connect('panmen_remove',self.rem_selection_from_pantry)        
 
97
        self.rectree.set_model(self.rmodel)
 
98
        renderer = gtk.CellRendererText()
 
99
 
 
100
        #renderer.set_property('editable',True)
 
101
        #renderer.connect('edited',tst)
 
102
        titl = gtk.TreeViewColumn(_("Title"),renderer,text=1)
 
103
        mult = gtk.TreeViewColumn(_("x"),renderer,text=2)
 
104
        self.rectree.append_column(titl)
 
105
        self.rectree.append_column(mult)
 
106
        titl.set_resizable(True)
 
107
        titl.set_clickable(True)
 
108
        titl.set_reorderable(True)
 
109
        mult.set_resizable(True)
 
110
        mult.set_clickable(True)
 
111
        mult.set_reorderable(True)
 
112
        self.rectree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
 
113
        self.rectree.show()
 
114
        
 
115
    def create_rmodel (self):
 
116
        debug("create_rmodel (self):",5)
 
117
        mod = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING)
 
118
        for r,mult in self.recs.values():
 
119
            iter = mod.append(None)
 
120
            mod.set_value(iter,0,r)
 
121
            mod.set_value(iter,1,r.title)
 
122
            mod.set_value(iter,2,convert.float_to_frac(mult))
 
123
        return mod
 
124
 
 
125
    # def show_nutritional_info (self, *args):
 
126
#         """Show nutritional information for this shopping list.
 
127
#         """
 
128
#         nl = NutritionLabel(self.rg.prefs,
 
129
#                             custom_label=_('Nutritional Information for Shopping List')
 
130
#                             )
 
131
#         ings = []
 
132
#         for d in [self.sh.dic,self.sh.mypantry]:
 
133
#             for k,units in d.items():
 
134
#                 for a,u in units:
 
135
#                     ings.append([k,a,u])
 
136
#         nutinfo = NutritionInfoList(
 
137
#             [self.nd.get_nutinfo_for_item(*i) for i in ings]
 
138
#             )
 
139
#         nl.set_nutinfo(nutinfo)
 
140
#         sw = gtk.ScrolledWindow()
 
141
#         sw.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
 
142
#         sw.add_with_viewport(nl)
 
143
#         sw.show(); nl.show()
 
144
#         md = de.ModalDialog(title=_("Nutritional Information for Shopping List"),modal=False)
 
145
#         md.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
 
146
#         md.vbox.pack_start(sw,fill=True,expand=True)
 
147
#         md.set_default_size(600,500)
 
148
#         nl.show()
 
149
#         md.show()
 
150
 
 
151
    def save (self, *args):
 
152
        debug("save (self, *args):",5)
 
153
        self.message('Saving')
 
154
        self.doSave(de.select_file(_("Save Shopping List As..."),
 
155
                                   filename=os.path.join(os.path.expanduser("~"),
 
156
                                                         "%s %s"%(_('Shopping List'),
 
157
                                                                  time.strftime("%x").replace("/","-"),
 
158
                                                                  )),
 
159
                                   action=gtk.FILE_CHOOSER_ACTION_SAVE,
 
160
                                   ))
 
161
        self.message(_('Saved!'))
 
162
        
 
163
    def doSave (self, filename):
 
164
        debug("doSave (self, filename):",5)
 
165
        #of = open(filename,'w')
 
166
        #self.writeHeader(of,)
 
167
        #self.sh.pretty_print(of)
 
168
        #self.doTextPrint(of)
 
169
        #of.close()
 
170
        import exporters.lprprinter
 
171
        self._printList(exporters.lprprinter.SimpleWriter,file=filename,show_dialog=False)
 
172
 
 
173
    def printList (self, *args):
 
174
        debug("printList (self, *args):",0)
 
175
        self._printList(printer.SimpleWriter,dialog_parent=self.widget)
 
176
        
 
177
    def _printList (self, printer, *args, **kwargs):
 
178
        w = printer(*args,**kwargs)
 
179
        w.write_header(_("Shopping list for %s")%time.strftime("%x"))
 
180
        w.write_subheader(_("For the following recipes:"))
 
181
        for r,mult in self.recs.values():
 
182
            itm = "%s"%r.title
 
183
            if mult != 1:
 
184
                itm += _(" x%s")%mult
 
185
            w.write_paragraph(itm)
 
186
        write_itm = lambda a,i: w.write_paragraph("%s %s"%(a,i))
 
187
        self.sh.list_writer(w.write_subheader,write_itm)
 
188
        w.close()
 
189
 
 
190
    def getSelectedRecs (self):
 
191
        """Return each recipe in list"""
 
192
        def foreach(model,path,iter,recs):
 
193
            debug("foreach(model,path,iter,recs):",5)
 
194
            try:
 
195
                rec=model.get_value(iter,0)
 
196
                recs.append(rec)
 
197
            except:
 
198
                debug("DEBUG: There was a problem with iter: %s path: %s"%(iter,path),1)
 
199
        recs=[]
 
200
        self.rectree.get_selection().selected_foreach(foreach,recs)
 
201
        debug("%s selected recs: %s"%(len(recs),recs),3)
 
202
        return recs
 
203
 
 
204
    def clear (self, *args):
 
205
        debug("clear (self, *args):",5)
 
206
        selectedRecs=self.getSelectedRecs()
 
207
        if selectedRecs:
 
208
            for t in selectedRecs:
 
209
                self.recs.__delitem__(t.id)
 
210
                debug("clear removed %s"%t,3)
 
211
            self.resetSL()
 
212
        elif de.getBoolean(label=_("No recipes selected. Do you want to clear the entire list?")):
 
213
            self.recs = {}
 
214
            self.extras = []
 
215
            self.resetSL()            
 
216
        else:
 
217
            debug("clear cancelled",2)
 
218
        
 
219
    def addRec (self, rec, mult, includes={}):
 
220
        debug("addRec (self, rec, mult, includes={}):",5)
 
221
        """Add recipe to our list, assuming it's not already there.
 
222
        includes is a dictionary of optional items we want to include/exclude."""
 
223
        self.recs[rec.id]=(rec,mult)
 
224
        self.includes[rec.id]=includes
 
225
        self.resetSL()
 
226
 
 
227
    def commit_category_orders(self,tv,space_before=None,
 
228
                               space_after=None):
 
229
        """Commit the order of categories to memory.
 
230
        We allow for making room before or after a given
 
231
        iter, in which case"""
 
232
        mod=tv.get_model()
 
233
        iter=mod.get_iter_first()
 
234
        prev_pos = -100
 
235
        while iter:
 
236
            cat=mod.get_value(iter,0)
 
237
            if self.sh.catorder_dic.has_key(cat):
 
238
                # positions are all integers -- this allows changing positions
 
239
                # to be neatly dropped in as 0.5s
 
240
                pos = int(self.sh.catorder_dic[cat])
 
241
            else:
 
242
                pos = 0
 
243
            if pos <= prev_pos:
 
244
                pos=prev_pos+1
 
245
            self.sh.catorder_dic[cat]=pos
 
246
            iter=mod.iter_next(iter)
 
247
            prev_pos=pos
 
248
        
 
249
    def resetSL (self):
 
250
        debug("resetSL (self):",5)
 
251
        self.data,self.pantry = self.grabIngsFromRecs(self.recs.values(),self.extras)
 
252
        self.slMod = self.createIngModel(self.data)
 
253
        self.pMod = self.createIngModel(self.pantry)
 
254
        self.slTree.set_model(self.slMod)
 
255
        self.pTree.set_model(self.pMod)
 
256
        self.rectree.set_model(self.create_rmodel())
 
257
        self.slTree.expand_all()
 
258
        self.pTree.expand_all()
 
259
        self.pTree_sel_changed_cb(self.pTree.get_selection())
 
260
        self.slTree_sel_changed_cb(self.slTree.get_selection())
 
261
 
 
262
    def create_slTree (self, data):
 
263
        debug("create_slTree (self, data):",5)
 
264
        self.slMod = self.createIngModel(data)
 
265
        self.slTree = self.create_ingTree(self.glade.get_widget('shopITree'),
 
266
                                     self.slMod)
 
267
        self.slTree.connect('popup-menu',self.popup_ing_menu)
 
268
        def slTree_popup_cb (tv, event):
 
269
            debug("slTree_popup_cb (tv, event):",5)
 
270
            if event.button==3 or event.type == gtk.gdk._2BUTTON_PRESS:
 
271
                self.popup_ing_menu(tv,event)
 
272
                return True
 
273
        self.slTree.connect('button-press-event',slTree_popup_cb)
 
274
        self.slTree.get_selection().connect('changed',self.slTree_sel_changed_cb)
 
275
        # reset the first time
 
276
        self.slTree_sel_changed_cb(self.slTree.get_selection())
 
277
 
 
278
    def create_pTree (self, data):
 
279
        debug("create_pTree (self, data):",5)
 
280
        self.pMod = self.createIngModel(data)
 
281
        self.pTree = self.create_ingTree(self.glade.get_widget('pantryTree'),
 
282
                                    self.pMod)
 
283
        self.pTree.connect('popup-menu',self.popup_pan_menu)
 
284
        self.pTree.get_selection().connect('changed',self.pTree_sel_changed_cb)
 
285
        # reset the first time...
 
286
        self.pTree_sel_changed_cb(self.pTree.get_selection())
 
287
        def pTree_popup_cb (tv, event):
 
288
            debug("pTree_popup_cb (tv, event):",5)
 
289
            if event.button==3 or event.type == gtk.gdk._2BUTTON_PRESS:
 
290
                self.popup_pan_menu(tv,event)
 
291
                return True
 
292
            
 
293
        self.pTree.connect('button-press-event',pTree_popup_cb)
 
294
        
 
295
    def create_ingTree (self, widget, model):
 
296
        debug("create_ingTree (self, widget, model):",5)
 
297
        #self.slTree = gtk.TreeView(self.slMod)
 
298
        tree=widget
 
299
        tree.set_model(self.slMod)
 
300
        ## add multiple selections
 
301
        tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
 
302
        ## adding drag and drop functionality
 
303
        targets = [('GOURMET_SHOPPER_SW', gtk.TARGET_SAME_WIDGET, 0),
 
304
                   ('GOURMET_SHOPPER', gtk.TARGET_SAME_APP, 1),
 
305
                   ('text/plain',0,2),
 
306
                   ('STRING',0,3),
 
307
                   ('STRING',0,4),
 
308
                   ('COMPOUND_TEXT',0,5),
 
309
                   ('text/unicode',0,6),]
 
310
        tree.drag_source_set(gtk.gdk.BUTTON1_MASK, targets,
 
311
                             gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
 
312
        tree.enable_model_drag_dest(targets,
 
313
                                    gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
 
314
        tree.connect('drag_begin', self.on_drag_begin)
 
315
        #tree.connect('drag_data_get', self.on_drag_data_get)
 
316
        tree.connect('drag_data_received', self.on_drag_data_received)
 
317
        #tree.connect('drag_motion', self.on_drag_motion)
 
318
        #tree.connect('drag_drop', self.on_drag_drop)
 
319
        renderer = gtk.CellRendererText()
 
320
        for n,t in [[0,'Item'],[1,'Amount']]:
 
321
            col = gtk.TreeViewColumn(t,renderer,text=n)
 
322
            col.set_resizable(True)
 
323
            tree.append_column(col)
 
324
        tree.expand_all()
 
325
        tree.show()
 
326
        return tree
 
327
 
 
328
    def slTree_sel_changed_cb (self, selection):
 
329
        """Callback handler for selection change on shopping treeview."""
 
330
        if selection.count_selected_rows()>0:
 
331
            self.shopRemove.set_sensitive(True)
 
332
            # if we have items selected, the pantry tree should not
 
333
            # this makes these feel more like one control/list divided
 
334
            # into two sections.            
 
335
            self.pTree.get_selection().unselect_all()
 
336
        else:
 
337
            self.shopRemove.set_sensitive(False)
 
338
 
 
339
    def pTree_sel_changed_cb (self, selection):
 
340
        """Callback handler for selection change on pantry treeview"""
 
341
        if selection.count_selected_rows()>0:
 
342
            self.pantryRemove.set_sensitive(True)
 
343
            # if we have items selected, the shopping tree should not.
 
344
            # this makes these feel more like one control/list divided
 
345
            # into two sections.
 
346
            self.slTree.get_selection().unselect_all()
 
347
        else:
 
348
            self.pantryRemove.set_sensitive(False)
 
349
 
 
350
    def on_drag_begin(self, tv, context):
 
351
        debug("on_drag_begin(self, tv, context):",5)
 
352
        self.tv=tv
 
353
        self.ssave=te.selectionSaver(self.slTree,1)
 
354
        self.pssave=te.selectionSaver(self.pTree,1)
 
355
 
 
356
    def on_drag_motion (self, tv, context, x, y, time):
 
357
        debug("on_drag_motion (self, tv, context, x, y, time):",5)
 
358
        pass
 
359
 
 
360
    def on_drag_data_get (self, tv, context, selection, info, time):
 
361
        debug("on_drag_data_get (self, tv, context, selection, info, time):",5)
 
362
        debug('on_drag_data_get %s %s %s %s %s'%(tv,context,selection,info,time),5)
 
363
        self.drag_selection=selection
 
364
        debug("Selection data: %s %s %s %s"%(self.dragged,
 
365
                                             self.drag_selection.data,
 
366
                                             dir(self.drag_selection),
 
367
                                             self.drag_selection.get_text()),5)
 
368
 
 
369
    def ingSelection (self,return_iters=False):
 
370
        """A way to find out what's selected. By default, we simply return
 
371
        the list of ingredient keys. If return_iters is True, we return the selected
 
372
        iters themselves as well (returning a list of [key,iter]s)"""
 
373
        debug("ingSelection (self):",5)
 
374
        def foreach(model,path,iter,selected):
 
375
            debug("foreach(model,path,iter,selected):",5)
 
376
            selected.append(iter)
 
377
        selected=[]
 
378
        self.tv.get_selection().selected_foreach(foreach,selected)
 
379
        debug("multiple selections = %s"%selected,3)
 
380
        #ts,itera=self.tv.get_selection().get_selected()
 
381
        selected_keys=[]
 
382
        for itera in selected:
 
383
            key=self.tv.get_model().get_value(itera, 0)
 
384
            if return_iters:
 
385
                selected_keys.append((key,itera))
 
386
            else:
 
387
                selected_keys.append(key)
 
388
        debug("ingSelection returns: %s"%selected_keys,3)
 
389
        return selected_keys
 
390
 
 
391
    def popup_ing_menu (self, tv, event=None, *args):
 
392
        debug("popup_ing_menu (self, tv, *args):",5)
 
393
        self.tv = tv
 
394
        if not event:
 
395
            event = gtk.get_current_event()
 
396
        t = (event and hasattr(event,'time') and getattr(event,'time')
 
397
                or 0)
 
398
        btn = (event and hasattr(event,'button') and getattr(event,'button')
 
399
               or 0)
 
400
        print 'self.pop.popup(None,None,None,',btn,t,')'        
 
401
        self.pop.popup(None,None,None,btn,t)
 
402
        return True
 
403
 
 
404
    def popup_pan_menu (self, tv, event=None, *args):
 
405
        debug("popup_pan_menu (self, tv, *args):",5)
 
406
        self.tv = tv
 
407
        if not event:
 
408
            event = gtk.get_current_event()
 
409
        t = (event and hasattr(event,'time') and getattr(event,'time')
 
410
                or 0)
 
411
        btn = (event and hasattr(event,'button') and getattr(event,'button')
 
412
               or 0)
 
413
        print 'self.panpop.popup(None,None,None,',btn,t,')'
 
414
        self.panpop.popup(None,None,None,btn,t)
 
415
        return True
 
416
 
 
417
    def setup_popup (self):
 
418
        debug("setup_popup (self):",5)
 
419
        def make_popup (pop):
 
420
            debug("make_popup (pop):",5)
 
421
            catitm = gtk.MenuItem("_Change Category")
 
422
            popcats=gtk.Menu()
 
423
            catitm.set_submenu(popcats)
 
424
            catitm.show()
 
425
            popcats.show()
 
426
            pop.add(catitm)
 
427
            for i in self.sh.get_orgcats():
 
428
                itm = gtk.MenuItem(i)
 
429
                popcats.append(itm)
 
430
                itm.connect('activate',self.popup_callback,i)
 
431
                itm.show()
 
432
            create = gtk.MenuItem('New Category')
 
433
            popcats.append(create)
 
434
            create.connect('activate',self.add_sel_to_newcat)
 
435
            create.show()
 
436
        self.pop=self.glade.get_widget('ingmen')
 
437
        make_popup(self.pop)        
 
438
        self.panpop = self.glade.get_widget('panmen')
 
439
        make_popup(self.panpop)
 
440
        
 
441
    def popup_callback (self, menuitem, string):
 
442
        debug("popup_callback (self, menuitem, string):",5)
 
443
        kk=self.ingSelection()
 
444
        if self.sh.get_orgcats().__contains__(string):
 
445
            for k in kk:
 
446
                self.sh.orgdic[k]=string
 
447
        else:
 
448
            debug("WARNING: orgcats %s does not contain %s" %(self.sh.get_orgcats(), string),4)
 
449
        ssave=te.selectionSaver(self.slTree,1)
 
450
        pssave=te.selectionSaver(self.pTree,1)
 
451
        self.resetSL()
 
452
        ssave.restore_selections(tv=self.slTree)
 
453
        pssave.restore_selections(tv=self.slTree)        
 
454
 
 
455
    def add_sel_to_newcat (self, menuitem, *args):
 
456
        debug("add_sel_to_newcat (self, menuitem, *args):",5)
 
457
        kk=self.ingSelection()
 
458
        sublab = ', '.join(kk)
 
459
        cat = de.getEntry(label=_('Enter Category'),
 
460
                          sublabel=_("Category to add %s to") %sublab,
 
461
                          entryLabel=_('Category:'),
 
462
                          parent=self.widget)
 
463
        if cat:
 
464
            for k in kk:
 
465
                self.sh.orgdic[k]=cat
 
466
            self.pop.get_children()[-1].hide()
 
467
            self.panpop.get_children()[-1].hide()
 
468
            self.setup_popup()
 
469
            ssave=te.selectionSaver(self.slTree,1)
 
470
            pssave=te.selectionSaver(self.pTree,1)
 
471
            self.resetSL()
 
472
            ssave.restore_selections(tv=self.slTree)
 
473
            pssave.restore_selections(tv=self.slTree)        
 
474
 
 
475
    def add_selection_to_pantry (self, *args):
 
476
        """Add selected items to pantry."""
 
477
        debug("add_selection_to_pantry (self, *args):",5)
 
478
        self.tv = self.slTree
 
479
        self.ssave=te.selectionSaver(self.slTree,1)
 
480
        self.pssave=te.selectionSaver(self.pTree,1)
 
481
        kk = self.ingSelection()
 
482
        for k in kk:
 
483
            self.sh.add_to_pantry(k)
 
484
        self.resetSL()
 
485
        self.ssave.restore_selections(tv=self.pTree)
 
486
        
 
487
    def rem_selection_from_pantry (self, *args):
 
488
        """Add selected items to shopping list."""
 
489
        debug("rem_selection_from_pantry (self, *args):",5)
 
490
        self.tv = self.pTree
 
491
        self.ssave=te.selectionSaver(self.slTree,1)
 
492
        self.pssave=te.selectionSaver(self.pTree,1)
 
493
        for k in self.ingSelection():
 
494
            self.sh.remove_from_pantry(k)
 
495
        self.resetSL()
 
496
        self.pssave.restore_selections(tv=self.slTree)
 
497
 
 
498
    def on_drag_data_received(self, tv, context,x,y,selection,info,time):
 
499
        debug("on_drag_data_received(self, tv,context,x,y,selection,info,time):",5)
 
500
        if str(selection.target) == 'GOURMET_SHOPPER_SW':
 
501
            ## in this case, we're recategorizing
 
502
            #try:
 
503
            dest = tv.get_dest_row_at_pos(x,y)
 
504
            if dest:
 
505
                path,drop_where = dest
 
506
                iter=tv.get_model().get_iter((path[0])) #grab the category (outside of tree)
 
507
                cat=tv.get_model().get_value(iter,0)
 
508
                for sel,iter in self.ingSelection(return_iters=True):
 
509
                    path=tv.get_model().get_path(iter)
 
510
                    if len(path) == 1:
 
511
                        # if we're moving an entire category, then we
 
512
                        # need to reorder categories.
 
513
                        debug("Saving new category orders",0)
 
514
                        self.commit_category_orders(tv)
 
515
                        # and now we need to move our new category into place...
 
516
                        if drop_where==gtk.TREE_VIEW_DROP_AFTER or drop_where==gtk.TREE_VIEW_DROP_INTO_OR_AFTER:
 
517
                            new_pos=self.sh.catorder_dic[cat]+0.5
 
518
                        else:
 
519
                            new_pos=self.sh.catorder_dic[cat]-0.5
 
520
                        self.sh.catorder_dic[sel] = new_pos
 
521
                        debug("%s moved to position %s"%(sel,self.sh.catorder_dic[sel]),0)
 
522
                        debug("The current order is: %s"%self.sh.catorder_dic)
 
523
                    else:
 
524
                        self.sh.orgdic[sel]=cat
 
525
                self.resetSL()
 
526
                self.ssave.restore_selections(tv=self.slTree)
 
527
                self.pssave.restore_selections(tv=self.pTree)        
 
528
            #except TypeError:
 
529
            else:
 
530
                debug("Out of range!",0)
 
531
        elif str(selection.target) == 'GOURMET_SHOPPER':
 
532
            ## in this case, we're moving
 
533
            if tv == self.pTree:
 
534
                self.add_selection_to_pantry()
 
535
            else:
 
536
                self.rem_selection_from_pantry()
 
537
        
 
538
    def on_drag_drop (self, tv, context, x, y, time):
 
539
        debug("on_drag_drop (self, tv, context, x, y, time):",5)        
 
540
        #model = tv.get_model()
 
541
        otv,oselection=self.dragged
 
542
        if otv==self.pTree and tv==self.slTree:
 
543
            self.rem_selection_from_pantry()
 
544
        elif otv==self.slTree and tv==self.pTree:
 
545
            self.add_selection_to_pantry()
 
546
        else:
 
547
            try:
 
548
                path,drop_where = tv.get_dest_row_at_pos(x,y)
 
549
                iter=tv.get_model().get_iter((path[0])) #grab the category (outside of tree)
 
550
                cat=tv.get_model().get_value(iter,0)
 
551
                for sel in oselection:
 
552
                    self.sh.orgdic[sel]=cat
 
553
                self.resetSL()
 
554
            except TypeError, e:
 
555
                self.message("Out of range! %s")
 
556
        return False
 
557
 
 
558
    def createIngModel (self, data):
 
559
        debug("createIngModel (self, data):",5)
 
560
        """Data is a list of lists, where each item is [ing amt]"""
 
561
        mod = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
 
562
        for c,lst in data:
 
563
            catiter = mod.append(None)
 
564
            mod.set_value(catiter, 0, c)
 
565
            for i in lst:
 
566
                ing = i[0]
 
567
                amt = i[1]
 
568
                iter = mod.append(catiter)
 
569
                mod.set_value(iter, 0, ing)
 
570
                mod.set_value(iter, 1, amt)
 
571
        return mod
 
572
        
 
573
    def grabIngsFromRecs (self, recs, start=[]):
 
574
        debug("grabIngsFromRecs (self, recs):",5)
 
575
        """Handed an array of (rec . mult)s, we combine their ingredients.
 
576
        recs may be IDs or objects."""
 
577
        lst = start[0:]
 
578
        for rec,mult in recs:
 
579
            lst.extend(self.grabIngFromRec(rec,mult=mult))
 
580
        self.sh = recipeManager.DatabaseShopper(lst, self.rg.rd, conv=self.conv)
 
581
        data = self.sh.organize(self.sh.dic)
 
582
        pantry = self.sh.organize(self.sh.mypantry)
 
583
        debug("returning: data=%s pantry=%s"%(data,pantry),5)
 
584
        return data,pantry
 
585
            
 
586
    def grabIngFromRec (self, rec, mult=1):
 
587
        """Get an ingredient from a recipe and return a list with our amt,unit,key"""
 
588
        """We will need [[amt,un,key],[amt,un,key]]"""
 
589
        debug("grabIngFromRec (self, rec=%s, mult=%s):"%(rec,mult),5)
 
590
        # Grab all of our ingredients
 
591
        ings = self.rg.rd.get_ings(rec)
 
592
        lst = []
 
593
        include_dic = self.includes.get(rec.id) or {}
 
594
        for i in ings:
 
595
            if hasattr(i,'refid'): refid=i.refid
 
596
            else: refid=None
 
597
            debug("adding ing %s, %s"%(i.item,refid),4)
 
598
            if i.optional:
 
599
                # handle boolean includes value which applies to ALL ingredients
 
600
                if not include_dic:
 
601
                    continue
 
602
                if type(include_dic) == dict :
 
603
                    # Then we have to look at the dictionary itself...
 
604
                    if ((not include_dic.has_key(i.ingkey))
 
605
                        or
 
606
                        not include_dic[i.ingkey]):
 
607
                        # we ignore our ingredient (don't add it)
 
608
                        continue
 
609
            if self.rg.rd.get_amount(i):
 
610
                amount=self.rg.rd.get_amount(i,mult=mult)                
 
611
            else: amount=None            
 
612
            if refid:
 
613
                ## a reference tells us to get another recipe
 
614
                ## entirely.  it has two parts: i.item (regular name),
 
615
                ## i.refid, i.refmult (amount we multiply recipe by)
 
616
                ## if we don't have the reference (i.refid), we just
 
617
                ## output the recipe name
 
618
                debug("Grabbing recipe as ingredient!",2)
 
619
                # disallow recursion
 
620
                subrec = self.rg.rd.get_referenced_rec(i)
 
621
                if subrec.id == rec.id:
 
622
                    de.show_message(
 
623
                        label=_('Recipe calls for itself as an ingredient.'),
 
624
                        sublabel=_('Ingredient %s will be ignored.')%rec.title + _('Infinite recursion is not allowed in recipes!'))
 
625
                    continue
 
626
                if subrec:
 
627
                    # recipe refs need an amount. We'll
 
628
                    # assume if need be.
 
629
                    amt = self.rg.rd.get_amount_as_float(i)
 
630
                    if not amt: amount=amt
 
631
                    refmult=mult*amt
 
632
                    if not include_dic.has_key(subrec.id):
 
633
                        d = getOptionalIngDic(self.rg.rd.get_ings(subrec),
 
634
                                              refmult,
 
635
                                              self.rg.prefs,
 
636
                                              self.rg)
 
637
                        include_dic[subrec.id]=d
 
638
                    nested_list=self.grabIngFromRec(subrec,
 
639
                                                    refmult)
 
640
                    lst.extend(nested_list)
 
641
                    continue
 
642
                else:
 
643
                    # it appears we don't have this recipe
 
644
                    debug("We don't have recipe %s"%i.item,0)
 
645
                    if not i.unit:
 
646
                        i.unit='recipe'
 
647
                    if not i.ingkey:
 
648
                        i.ingkey=i.item
 
649
            lst.append([amount,i.unit,i.ingkey])
 
650
        debug("grabIngFromRec returning %s"%lst,5)
 
651
        return lst
 
652
 
 
653
    def show (self, *args):
 
654
        debug("show (self, *args):",5)
 
655
        self.widget.show()
 
656
        try:
 
657
            self.widget.present()
 
658
        except:
 
659
            self.widget.grab_focus()
 
660
 
 
661
    def hide (self, *args):
 
662
        debug("hide (self, *args):",5)
 
663
        for c in self.conf:
 
664
            c.save_properties()
 
665
        self.widget.hide()
 
666
        self.sie.sdW.hide()
 
667
        return True
 
668
 
 
669
    def setup_shop_dialog (self):
 
670
        self.orgdic = self.sh.orgdic
 
671
        self.sie = ShopIngredientEditor(self.rg, self)
 
672
 
 
673
    def message (self, txt):
 
674
        debug("message (self, txt): %s"%txt,5)
 
675
        self.stat.push(self.contid,txt)
 
676
        self.rg.message(txt)
 
677
    
 
678
class OptionalIngDialog (de.ModalDialog):
 
679
    """A dialog to query the user about whether to use optional ingredients."""
 
680
    def __init__ (self,vw,prefs,rg,mult=1,default=False):
 
681
        debug("__init__ (self,vw,default=False):",5)
 
682
        self.rg=rg
 
683
        de.ModalDialog.__init__(
 
684
            self, default,
 
685
            label=_("Select optional ingredients."),
 
686
            sublabel=_("Please specify which of the following optional ingredients you'd like to include on your shopping list."))
 
687
        self.mult = mult
 
688
        self.vw=vw
 
689
        self.ret = {}
 
690
        self.create_tree()
 
691
        self.cb = gtk.CheckButton("Always use these settings")
 
692
        self.cb.set_active(prefs.get('remember_optionals_by_default',False))
 
693
        alignment = gtk.Alignment()
 
694
        alignment.set_property('xalign',1.0)
 
695
        alignment.add(self.cb)
 
696
        self.vbox.add(alignment)
 
697
        alignment.show()
 
698
        self.cb.show()
 
699
        
 
700
    def create_model (self):
 
701
        """Create the TreeModel to show optional ingredients."""
 
702
        debug("create_model (self):",5)
 
703
        self.mod = gtk.TreeStore(gobject.TYPE_PYOBJECT, #the ingredient obj
 
704
                                 gobject.TYPE_STRING, #amount
 
705
                                 gobject.TYPE_STRING, #unit
 
706
                                 gobject.TYPE_STRING, #item
 
707
                                 gobject.TYPE_BOOLEAN) #include
 
708
        for i in self.vw:
 
709
            iter=self.mod.append(None)
 
710
            self.mod.set_value(iter,0,i)
 
711
            if self.mult==1:
 
712
                self.mod.set_value(iter,1,
 
713
                                   self.rg.rd.get_amount_as_string(i)
 
714
                                   )
 
715
            else:
 
716
                self.mod.set_value(iter,1,
 
717
                                   self.rg.rd.get_amount_as_string(i,float(self.mult))
 
718
                                   )
 
719
            self.mod.set_value(iter,2,i.unit)
 
720
            self.mod.set_value(iter,3,i.item)
 
721
            self.mod.set_value(iter,4,self.default)
 
722
            self.ret[i.ingkey]=self.default
 
723
 
 
724
    def create_tree (self):
 
725
        """Create our TreeView and populate it with columns."""
 
726
        debug("create_tree (self):",5)
 
727
        self.create_model()
 
728
        self.tree = gtk.TreeView(self.mod)
 
729
        txtr = gtk.CellRendererText()
 
730
        togr = gtk.CellRendererToggle()
 
731
        togr.set_property('activatable',True)
 
732
        togr.connect('toggled',self.toggle_ing_cb)
 
733
        #togr.start_editing()
 
734
        for n,t in [[1,'Amount'],[2,'Unit'],[3,'Item']]:
 
735
            col = gtk.TreeViewColumn(t,txtr,text=n)
 
736
            col.set_resizable(True)
 
737
            self.tree.append_column(col)
 
738
        bcol = gtk.TreeViewColumn('Add to Shopping List',
 
739
                                  togr, active=4)
 
740
        self.tree.append_column(bcol)
 
741
        self.vbox.add(self.tree)
 
742
        self.tree.show()
 
743
 
 
744
    def toggle_ing_cb (self, cellrenderertoggle, path, *args):
 
745
        debug("toggle_ing_cb (self, cellrenderertoggle, path, *args):",5)
 
746
        crt=cellrenderertoggle
 
747
        iter=self.mod.get_iter(path)
 
748
        val = self.mod.get_value(iter,4)
 
749
        newval = not val
 
750
        self.ret[self.mod.get_value(iter,0).ingkey]=newval
 
751
        self.mod.set_value(iter,4,newval)
 
752
 
 
753
    def run (self):
 
754
        self.show()
 
755
        if self.modal: gtk.main()
 
756
        if self.cb.get_active() and self.ret:
 
757
            # if we are saving our settings permanently...
 
758
            # we add ourselves to the shopoptional attribute
 
759
            for row in self.mod:
 
760
                ing = row[0]
 
761
                ing_include = row[4]
 
762
                if ing_include: self.rg.rd.modify_ing(ing,{'shopoptional':2})
 
763
                else: self.rg.rd.modify_ing(ing,{'shopoptional':1})
 
764
        return self.ret
 
765
 
 
766
class ShopIngredientEditor (reccard.IngredientEditor):
 
767
    def __init__ (self, RecGui, ShopGui):
 
768
        self.sg = ShopGui
 
769
        self.last_key = ""
 
770
        reccard.IngredientEditor.__init__(self, RecGui, None)
 
771
        #self.ingBox = self.keyBox
 
772
 
 
773
    def init_dics (self):
 
774
        self.orgdic = self.sg.sh.orgdic
 
775
        self.shopcats = self.sg.sh.get_orgcats()
 
776
 
 
777
    def setup_glade (self):
 
778
        self.glade = self.sg.glade
 
779
        #self.glade.signal_connect('ieApply', self.apply)
 
780
        #self.ieBox.hide()
 
781
        self.amountBox = self.glade.get_widget('sdAmount')
 
782
        self.unitBox = self.glade.get_widget('sdUnit')
 
783
        self.keyBox = self.glade.get_widget('sdKey')
 
784
        self.shopBox = self.glade.get_widget('sdShopCat')
 
785
        # add some of our own signals...
 
786
        self.glade.signal_connect('sdAdd',self.add)
 
787
        self.sdToggle = False
 
788
        self.glade.signal_connect('shopAdd',self.showShopDialog)
 
789
        self.glade.signal_connect('sdHide',self.hideShopDialog)
 
790
        self.glade.signal_connect('sdAdd',self.add)
 
791
        self.sdW = self.glade.get_widget('ShopDialog')
 
792
        self.keyBox.get_children()[0].connect('changed',self.setKeyList)
 
793
 
 
794
    def setKeyList (self, *args):
 
795
        self.curkey = self.keyBox.entry.get_text()
 
796
        if self.curkey == self.last_key:
 
797
            return
 
798
        def vis (m, iter):
 
799
            x= m.get_value(iter,0)
 
800
            if x.find(self.curkey) > -1:
 
801
                return True
 
802
            else: return False
 
803
        self.keyBox.get_model().set_visible_func(vis)
 
804
        self.keyBox.get_model().refilter()
 
805
        self.last_key=self.curkey
 
806
        
 
807
    def add (self, *args):
 
808
        amt = self.amountBox.get_text()
 
809
        if amt:
 
810
            try:
 
811
                amt=convert.frac_to_float(amt)
 
812
            except:
 
813
                reccard.show_amount_error(amt)
 
814
                raise
 
815
        unit = self.unitBox.entry.get_text()
 
816
        key=self.getKey()
 
817
        shopcat = self.shopBox.child.get_text()
 
818
        if shopcat:
 
819
            self.rg.sl.sh.add_org_itm(key,shopcat)
 
820
        if key:
 
821
            itm = [amt, unit, key]
 
822
            self.sg.extras.append(itm)
 
823
            self.sg.resetSL()
 
824
            self.new()
 
825
 
 
826
    def returned (self, *args):
 
827
        self.add()
 
828
        self.amountBox.grab_focus()
 
829
        
 
830
    def toggleShopDialog (self, *args):
 
831
        if self.sdToggle: self.showShopDialog()
 
832
        else: self.hideShopDialog()
 
833
        
 
834
    def showShopDialog (self,*args):
 
835
        self.sdW.present()
 
836
        self.sdToggle=True
 
837
 
 
838
    def hideShopDialog (self,*args):
 
839
        self.sdW.hide()
 
840
        self.sdToggle=False
 
841
        return True
 
842
 
 
843
 
 
844
def getOptionalIngDic (ivw, mult, prefs, rg):
 
845
    """Return a dictionary of optional ingredients with a TRUE|FALSE value
 
846
 
 
847
    Alternatively, we return a boolean value, in which case that is
 
848
    the value for all ingredients.
 
849
 
 
850
    The dictionary will tell us which ingredients to add to our shopping list.
 
851
    We look at prefs to see if 'shop_always_add_optional' is set, in which case
 
852
    we don't ask our user."""    
 
853
    debug("getOptionalIngDic (ivw):",5)
 
854
    #vw = ivw.select(optional=True)
 
855
    vw = filter(lambda r: r.optional==True, ivw)
 
856
    # optional_mode: 0==ask, 1==add, -1==dont add
 
857
    optional_mode=prefs.get('shop_handle_optional',0)
 
858
    if optional_mode:
 
859
        if optional_mode==1:
 
860
            return True
 
861
        elif optional_mode==-1:
 
862
            return False
 
863
    elif len(vw) > 0:
 
864
        if not None in [i.shopoptional for i in vw]:
 
865
            # in this case, we have a simple job -- load our saved
 
866
            # defaults
 
867
            dic = {}
 
868
            for i in vw:
 
869
                if i.shopoptional==2: dic[i.ingkey]=True
 
870
                else: dic[i.ingkey]=False
 
871
            return dic
 
872
        # otherwise, we ask our user
 
873
        print 'Run OID with ',vw,prefs,rg,mult
 
874
        oid=OptionalIngDialog(vw, prefs, rg,mult )
 
875
        retval = oid.run()
 
876
        if retval:
 
877
            return retval
 
878
        else:
 
879
            raise "Option Dialog cancelled!"