#!/usr/pkg/bin/python2.7
import gtk.glade, gtk, time, re, gtk.gdk, gobject
import WidgetSaver, Undo
import dialog_extras as de
import treeview_extras as te
import cb_extras as cb
from gglobals import *
from gdebug import debug
import mnemonic_manager

class RecIndex:
    """We handle the 'index view' of recipes, which puts
    a recipe model into a tree and allows it to be searched
    and sorted. We're a separate class from the main recipe
    program so that we can be created again (e.g. in the recSelector
    dialog called from a recipe card."""
    def __init__ (self, model, glade, rd, rg, editable=False):
        #self.visible = 1 # can equal 1 or 2
        self.editable=editable
        self.rtcols=rg.rtcols
        self.rtcolsdic=rg.rtcolsdic
        self.rtwidgdic=rg.rtwidgdic
        self.prefs=rg.prefs
        self.glade = glade
        self.rmodel = model
        self.rd = rd
        self.rg = rg
        self.srchentry=self.glade.get_widget('rlistSearchbox')
	self.limitButton = self.glade.get_widget('rlAddButton')
        # Don't # allow for special keybindings
        #self.srchentry.connect('key_press_event',self.srchentry_keypressCB)
        self.searchByDic = {
            _('title'):'title',
            _('ingredient'):'ingredient',
            _('instructions'):'instructions',
            _('notes'):'modifications',
            _('category'):'category',
            _('cuisine'):'cuisine',
            _('rating'):'rating',
            _('source'):'source',
            }
        self.searchByList = [_('title'),
                             _('ingredient'),
                             _('category'),
                             _('cuisine'),
                             _('rating'),
                             _('source'),
                             _('instructions'),
                             _('notes'),
                             ]
        # ACK, this breaks internationalization!
        #self.SEARCH_KEY_DICT = {
        #    "t":_("title"),
        #    "i":_("ingredient"),
        #    "c":_("category"),
        #    "u":_("cuisine"),
        #    's':_("source"),
        #    }
        self.SEARCH_MENU_KEY = "b"
        self.srchLimitBar=self.glade.get_widget('srchLimitBar')
        self.srchLimitBar.hide()
        self.srchLimitLabel=self.glade.get_widget('srchLimitLabel')
        self.srchLimitClearButton = self.glade.get_widget('srchLimitClear')
        self.srchLimitText=self.srchLimitLabel.get_text()
        self.srchLimitDefaultText=self.srchLimitText
        self.searchButton = self.glade.get_widget('searchButton')
        self.rSearchByMenu = self.glade.get_widget('rlistSearchByMenu')
        cb.set_model_from_list(self.rSearchByMenu, self.searchByList)
        cb.setup_typeahead(self.rSearchByMenu)
        self.rSearchByMenu.set_active(0)
        self.rSearchByMenu.connect('changed',self.search_as_you_type)
        self.sautTog = self.glade.get_widget('searchAsYouTypeToggle')
        self.sautTog.connect('toggled',self.toggleTypeSearchCB)
        self.regexpTog = self.glade.get_widget('regexpTog')
        self.rectree = self.glade.get_widget('recTree')
        self.rectree.connect('start-interactive-search',lambda *args: self.srchentry.grab_focus())
        self.stat = self.glade.get_widget('statusbar')
        self.contid = self.stat.get_context_id('main')
        self.setup_search_views()
        self.setup_rectree()
        self.glade.signal_autoconnect({
            'rlistSearch': self.search_as_you_type,
            'ingredientSearch' : lambda *args: self.set_search_by('ingredient'),
            'titleSearch' : lambda *args: self.set_search_by('title'),
            'ratingSearch' : lambda *args: self.set_search_by('rating'),
            'categorySearch' : lambda *args: self.set_search_by('category'),
            'cuisineSearch' : lambda *args: self.set_search_by('cuisine'),
            'search' : self.search,
            'rlistReset' : self.reset_search,
            'rlistLimit' : self.limit_search,
            'search_as_you_type_toggle' : self.toggleTypeSearchCB,})
        # this has to come after the type toggle is connected!
        self.rg.conf.append(WidgetSaver.WidgetSaver(
            self.sautTog,
            self.prefs.get('sautTog',
                           {'active':self.sautTog.get_active()}),
            ['toggled']))
        self.rg.conf.append(WidgetSaver.WidgetSaver(
            self.regexpTog,
            self.prefs.get('regexpTog',
                           {'active':self.regexpTog.get_active()}),
            ['toggled']))        
        # and we update our count with each deletion.
        self.rd.delete_hooks.append(self.set_reccount)
        # setup a history
        self.uim=self.glade.get_widget('undo_menu_item')
        self.rim=self.glade.get_widget('redo_menu_item')
        self.raim=self.glade.get_widget('reapply_menu_item')
        self.history = Undo.UndoHistoryList(self.uim,self.rim,self.raim)
        # Fix up our mnemonics with some heavenly magic
        self.mm = mnemonic_manager.MnemonicManager()
        self.mm.add_glade(self.glade)
        self.mm.add_treeview(self.rectree)
        self.mm.fix_conflicts_peacefully()

    def setup_search_views (self):
        """Setup our views of the database."""
        self.lsrch = ["",""]
        self.lsrchvw = self.rd.rview.select(deleted=False)
        self.searchvw = self.rd.rview.select(deleted=False)

    #def srchentry_keypressCB (self, widget, event):
    #    """Handle keypress in search entry
    #
    #    We allow Alt- combinations to change what we're searching by.
    #    """
    #    if event.state==gtk.gdk.MOD1_MASK:
    #        if self.SEARCH_KEY_DICT.has_key(event.string):
    #            self.set_search_by(self.SEARCH_KEY_DICT[event.string])
    #        elif self.SEARCH_MENU_KEY == event.string:
    #            self.rSearchByMenu.popup()

    def make_rec_visible (self, rec):
        """Make sure recipe REC shows up in our index."""
        debug('make_rec_visible',0)
        self.visible.append(rec.id)
        if not self.rg.wait_to_filter:
            self.rmodel_filter.refilter()
    
    def setup_rectree (self):
        """Create our recipe treemodel."""
        self.rmodel_filter = self.rmodel.filter_new()
        #self.rmodel_filter.set_modify_func(types, self.add_recipe_attr)
        # we allow filtering for searches...
        self.visible = [x.id for x in self.rd.rview]
        # visibility_fun checks to see if the Rec ID is in self.visible
        self.rmodel_filter.set_visible_func(self.visibility_fun)
        # make sortable...
        self.rmodel_sortable = gtk.TreeModelSort(self.rmodel_filter)
        self.rectree.set_model(self.rmodel_sortable)
        self.rectree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        self.selection_changed()
        self.set_reccount()
        self.setup_reccolumns()
        # this has to come after columns are added or else adding columns resets out column order!
        self.rectree_conf=te.TreeViewConf(self.rectree,
                                          hidden=self.prefs.get('rectree_hidden_columns',[]),
                                          order=self.prefs.get('rectree_column_order',{}))
        self.rectree_conf.apply_column_order()
        self.rectree_conf.apply_visibility()
        #self.rectree.connect("row-activated",self.recTreeSelectRec)#self.recTreeSelectRec)
        self.rectree.get_selection().connect("changed",self.selection_changedCB)
        self.rectree.expand_all()
        self.rectree.show()

    def set_reccount (self, *args):
        """Display the count of currently visible recipes."""
        debug("set_reccount (self, *args):",5)
        self.count = len(self.lsrchvw)
        self.stat.push(self.contid,_("%s Recipes")%self.count)
        if self.count == 1:
            sel = self.rectree.get_selection()
            if sel: sel.select_path((0,))

    def setup_reccolumns (self):
        """Setup the columns of our recipe index TreeView"""
        renderer = gtk.CellRendererPixbuf()
        col = gtk.TreeViewColumn("",renderer,pixbuf=1)
        col.set_min_width(-1)
        self.rectree.append_column(col)
        n = 2
        crc = True
        if not hasattr(gtk,'CellRendererCombo'):
            print 'CellRendererCombo not yet supported'
            print 'Update pygtk/gtk for more comboboxes'
            print 'in your treemodels!'
        for c in self.rtcols:
            if self.editable and CRC_AVAILABLE and self.rtwidgdic[c]=='Combo':
                renderer = gtk.CellRendererCombo()
                model = gtk.ListStore(str)
                map(lambda i: model.append([i]),self.rg.rd.get_unique_values(c))
                renderer.set_property('model',model)
                renderer.set_property('text-column',0)
            else:
                renderer = gtk.CellRendererText()
            renderer.set_property('editable',self.editable)
            renderer.connect('edited',self.rtree_edited_cb,n, c)
            titl = self.rtcolsdic[c]
            col = gtk.TreeViewColumn('_%s'%titl,renderer, text=n)
            col.set_reorderable(True)
            col.set_resizable(True)
            #col.set_clickable(True)
            #col.connect('clicked', self.column_sort)
            self.rectree.append_column(col)
            col.set_sort_column_id(n)
            debug("Column %s is %s->%s"%(n,c,self.rtcolsdic[c]),5)
            n += 1

    def toggleTypeSearchCB (self, widget):
        """Toggle search-as-you-type option."""
        if widget.get_active():
            self.search_as_you_type=True
            self.searchButton.hide()
        else:
            self.search_as_you_type=False
            self.searchButton.show()

    def toggleRegexpCB (self, widget):
        """Toggle search-with-regexp option."""
        if widget.get_active():
            self.message('Advanced searching (regular expressions) turned on')
        else:
            self.message('Advanced searching off')

    def regexpp (self):
        """Return True if we're using regexps"""
        if self.regexpTog.get_active():
            return True
        else:
            return False

    def search_as_you_type (self, *args):
        """If we're searching-as-we-type, search."""
        if self.search_as_you_type:
            self.search()

    def set_search_by (self, str):
        """Manually set the search by label to str"""
        debug('set_search_by',0)
        #self.rSearchByMenu.get_children()[0].set_text(str)
        cb.cb_set_active_text(self.rSearchByMenu, str)
        self.search()
    
    def search (self, *args):
        debug("search (self, *args):",5)
        txt = self.srchentry.get_text()
        searchBy = cb.cb_get_active_text(self.rSearchByMenu)
        searchBy = self.searchByDic[unicode(searchBy)]
	if txt and self.limitButton: self.limitButton.set_sensitive(True)
        elif self.limitButton: self.limitButton.set_sensitive(False)
        if [txt, searchBy] == self.lsrch:
            debug("Same search!",0)
            return
        if self.srchentry.window: self.srchentry.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
        gobject.idle_add(lambda *args: self.do_search(txt, searchBy))
        
    def do_search (self, txt, searchBy):
        ## first -- are we a continuation of the previous search of not?
        debug('do_search called with txt=%s, searchBy=%s'%(txt,searchBy),5)
        # if we're not using regular expressions, we escape our text.        
        addedtextp = re.match("^%s"%re.escape(txt),self.lsrch[0]) and len(txt) > len(self.lsrch[0])
        samesearchp = self.lsrch and searchBy == self.lsrch[1]
        if not addedtextp or not samesearchp:
            # if we're not, we reset our lsrchvw (our searchvw)
            self.lsrchvw = self.searchvw
        if txt:
            if searchBy == "ingredient":
                # somewhat counterintuitive behavior (google-like search)
                #self.lsrchvw=self.rd.ings_search(txt.split(),rview=self.lsrchvw)
                # less counterintuitive (exact search)
                self.lsrchvw=self.rd.ing_search(txt,rview=self.lsrchvw,use_regexp=self.regexpp())
            else:
                self.lsrchvw=self.rd.search(self.lsrchvw,searchBy,txt,use_regexp=self.regexpp())
        else:
            self.lsrchvw = self.searchvw
        self.lsrch = [txt, searchBy]
        self.update_rmodel(self.lsrchvw)
        self.set_reccount()
        self.srchentry.window.set_cursor(None)

    def limit_search (self, *args):
        debug("limit_search (self, *args):",5)
        self.search() # make sure we've done the search...
        self.searchvw=self.lsrchvw
        self.srchLimitBar.show()
        if self.srchLimitDefaultText==self.srchLimitText:
            newtext=_(" %s contains %s")%(self.lsrch[1],self.lsrch[0])
        else:
            newtext=_(", %s contains %s")%(self.lsrch[1],self.lsrch[0])
        self.srchLimitText="%s%s"%(self.srchLimitLabel.get_text(),newtext)
        self.srchLimitLabel.set_markup("<i>%s</i>"%self.srchLimitText)
        self.srchentry.set_text("")

    def reset_search (self, *args):
        debug("reset_search (self, *args):",5)
        self.srchLimitLabel.set_text(self.srchLimitDefaultText)
        self.srchLimitText=self.srchLimitDefaultText
        self.srchLimitBar.hide()
        self.searchvw=self.rd.rview
        self.lsrch=["",""] # reset search so we redo it
        self.search()

    def get_rec_from_iter (self, iter):
        debug("get_rec_from_iter (self, iter): %s"%iter,5)
        obj=self.rectree.get_model().get_value(iter,0)
        retval=self.rd.get_rec(obj.id)
        return retval

    def rtree_edited_cb (self, renderer, path_string, text, colnum, attribute):
        debug("rtree_edited_cb (self, renderer, path_string, text, colnum, attribute):",5)
        indices = path_string.split(':')
        path = tuple( map(int, indices))
        store = self.rectree.get_model()
        iter = store.get_iter(path)
        if not iter: return
        self.rmodel.set_value(iter, colnum, text)
        rec=self.get_rec_from_iter(iter)        
        if "%s"%getattr(rec,attribute)!=text:
            # only bother with this if the value has actually changed!
            self.rd.undoable_modify_rec(rec,
                                        {attribute:text},
                                        self.history,
                                        get_current_rec_method=lambda *args: self.recTreeSelectedRecs()[0],
                                        )
            self.update_modified_recipe(rec,attribute,text)
        # for metakit, which isn't automitting very nicely...
        self.rd.save()

    def update_modified_recipe(self,rec,attribute,text):
        """Update a modified recipe.

        Subclasses can use this to update other widgets duplicating
        the information in the index view."""
        pass

    def recTreeSelectedRecs (self):
        debug("recTreeSelectedRecs (self):",5)
        def foreach(model,path,iter,recs):
            debug("foreach(model,path,iter,recs):",5)
            try:
                recs.append(model[path][0])
                #recs.append(self.get_rec_from_iter(iter))
            except:
                debug("DEBUG: There was a problem with iter: %s path: %s"%(iter,path),1)
        recs=[]
        sel = self.rectree.get_selection()
        if sel:
            sel.selected_foreach(foreach,recs)
            return recs
        else:
            return []

    def selection_changedCB (self, *args):
        """We pass along true or false to selection_changed
        to say whether there is a selection or not."""
        debug("selection_changed (self, *args):",5)
        v=self.rectree.get_selection().get_selected_rows()[1]
        if v: selected=True
        else: selected=False
        self.selection_changed(v)

    def selection_changed (self, selected=False):
        """This is a way to act whenever the selection changes."""
        pass

    def visibility_fun (self, model, iter):
        try:
            if (model.get_value(iter,0) and
                not model.get_value(iter,0).deleted and
                model.get_value(iter, 0).id in self.visible):
                return True
            else: return False
        except:
            debug('something bizaare just happened in visibility_fun',0)
            return False
 
    def update_rmodel (self, rview):
        debug('update_rmodel... changing filtering criteria',0)
        self.visible = map(lambda r: r.id, rview)
        debug('visible=%s'%self.visible,0)
        debug('refiltering')
        self.rmodel_filter.refilter()
        debug('update_rmodel finished')


