"""
Contacts Listbox with filtering support
"""
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  $Id: ContactListWidget.py 92 2004-11-28 15:34:44Z henning $

from Tkinter import *
import Pmw
import InputWidgets
import ToolTip
import string
import Set
import broadcaster
import Preferences

class ContactListWidget(Frame):
    DISPLAYFIELD = 'DisplayName'
    SORTBY = 'SortName'
    def __init__(self, master, model, **kws):
        Frame.__init__(self, master)
        self.model = model
        self.selectcommand = kws.get('selectcommand', None)
        self.dblclickcommand = kws.get('dblclickcommand', None)
        self.categories = Set.Set("All", "No Category")
        dispfield = Preferences.get('client.contactlist_displayfield')
        if dispfield: self.DISPLAYFIELD = dispfield
        sortby = Preferences.get('client.contactlist_sortby')
        if sortby: self.SORTBY = sortby
        self.selFilter = InputWidgets.SearchableCombobox(self,
                         command=self.applyFilter, label_text="Filter:")
        self.selFilter.pack()
        ToolTip.ToolTip(self.selFilter, "filter contact list by category")
        kws = {}
        if sys.platform != 'win32':
            # Thin Scrollbars:
            kws['horizscrollbar_borderwidth'] = 1
            kws['horizscrollbar_width'] = 10
            kws['vertscrollbar_borderwidth'] = 1
            kws['vertscrollbar_width'] = 10
        self.listbox = Pmw.ScrolledListBox(self,
            scrollmargin=0,
            selectioncommand=self._listboxClick,
            dblclickcommand=self._listboxDblClick,
            listbox_highlightthickness=0,
            **kws)

        self.listbox.pack(fill=BOTH,expand=TRUE)
        self.registerAtBroadcaster()

    def registerAtBroadcaster(self):
        "Register our Widget's Callback Handlers"
        broadcaster.Register(self.updateList,
            source='Contacts', title='Opened')
        broadcaster.Register(self.updateList,
            source='Contacts', title='Closed')
        # Import Hook not needed because New and Save will do the job:    
        #broadcaster.Register(self.updateList,
        #    source='Contacts', title='Imported')
        broadcaster.Register(self.onContactSave,
            source='Contact', title='Saved')
        broadcaster.Register(self.onContactNew,
            source='Contact', title='Added')
        broadcaster.Register(self.onContactDel,
            source='Contact', title='Deleted')

    def getUpdatedContactData(self):
        """Returns handle, DisplayName and Categories of the modified contact
        Call only from a broadcaster callback function"""
        handle = broadcaster.CurrentData()['handle']
        if broadcaster.CurrentTitle() != "Deleted":
            attrlist = self.model.QueryAttributes([handle],
                (self.DISPLAYFIELD,'Categories'))
            return (handle, attrlist[0]) 
        else:
            return (handle, None)
                
    def onContactNew(self):
        handle, attr = self.getUpdatedContactData()
        self._contacthandles.insert(0, handle)
        self._contactdispnames.insert(0, attr[0])
        self._contactcategories.insert(0, attr[1])
        self._filteredhandles.insert(0, handle)
        self._filtereddispnames.insert(0, attr[0])
        self.buildCategories()
        self.applyFilter()
        # Do not do selectContact(handle) now,
        # because we don't know if the contact is in our filtered list.
        
    def onContactDel(self):
        handle, attr = self.getUpdatedContactData()
        idx = self._contacthandles.index(handle)
        del self._contacthandles[idx]
        del self._contactdispnames[idx]
        del self._contactcategories[idx]
        try:
            filtidx = self._filteredhandles.index(handle)
            del self._filteredhandles[filtidx]
            del self._filtereddispnames[filtidx]
        except ValueError:
            pass
        self.applyFilter()    
                
    def onContactSave(self):
        handle, attr = self.getUpdatedContactData()
        idx = self._contacthandles.index(handle)
        self._contactdispnames[idx] = attr[0]
        self._contactcategories[idx] = attr[1]
        # Now get the sorted list from the server:
        srvidx = self.model.ListHandles(self.SORTBY).index(handle)
        if idx != srvidx:
            def move(list, x, y):
                temp = list[x]; del list[x]
                list.insert(y, temp)
            # and move our saved contact to it's sorted place:    
            move(self._contacthandles, idx, srvidx)
            move(self._contactdispnames, idx, srvidx)
            move(self._contactcategories, idx, srvidx)
        self.buildCategories()
        self.applyFilter()
        self.selectContact(handle)

    def _listboxClick(self):
        "Event triggered by clicking on the listbox"
        sel = self.listbox.curselection()
        if sel and self.selectcommand:
            self.selectcommand(self._filteredhandles[int(sel[0])])
            
    def _listboxDblClick(self):
        "Event triggered by double-clicking on the listbox"
        sel = self.listbox.curselection()
        if sel and self.dblclickcommand:
            self.dblclickcommand(self._filteredhandles[int(sel[0])])

    currentFilter = "None"
    def getCurrentCategory(self):
        """If a card wants to appear in our filtered list
        it must include the category returned by this function
        in it's 'categories' field"""
        ret = self.currentFilter
        if ret == "All" or ret == "No Category": ret = ""
        return ret
    
    def applyFilter(self, filter=None):
        "If called with no arguments the filter will be unchanged"
        if filter is None:
            filter = self.currentFilter
        else:    
            self.currentFilter = filter
            if filter == "All":
                self._filteredhandles[:] = self._contacthandles
                self._filtereddispnames[:] = self._contactdispnames
            else:
                self._filteredhandles = []
                self._filtereddispnames = []
                for str, dispname, handle in zip(self._contactcategories, 
                                                 self._contactdispnames,
                                                 self._contacthandles):
                    if filter == "No Category":
                        if str.strip() == "":
                            self._filteredhandles.append(handle)
                            self._filtereddispnames.append(dispname)
                    else:
                        if str.find(filter) != -1:
                            self._filteredhandles.append(handle)
                            self._filtereddispnames.append(dispname)
        self.listbox.setlist(self._filtereddispnames)
        self.selFilter.set(filter)

    _filteredhandles = []
    _filtereddispnames = []
    _contacthandles = []
    _contactdispnames = []
    def updateList(self):
        "Completely update our list"
        self._contacthandles[:] = self.model.ListHandles(self.SORTBY)
        attrlist = self.model.QueryAttributes(self._contacthandles,
            (self.DISPLAYFIELD,'Categories'))
        self._contactdispnames = [dispname for dispname, cats in attrlist]
        self._contactcategories = [cats for dispname, cats in attrlist]
        self.buildCategories()
        self.applyFilter("All")
        broadcaster.Broadcast('Notification', 'Status', 
            {'message':'%d Contacts loaded.' % (len(self._contacthandles[:]),)})
        
    def buildCategories(self):
        "Update our categories set"
        for str in self._contactcategories:
            cats = filter(None, map(string.strip, str.split(',')))
            for cat in cats:
                self.categories.add(cat)
        self.selFilter.setlist(zip(self.categories.items(),self.categories.items()))       
        
    def selectContact(self, handle): 
        "Set the listbox selection"
        try:
            idx = self._filteredhandles.index(handle)
            self.listbox.selection_clear()
            self.listbox.selection_set(idx)
        except ValueError:
            # The Contact does not appear in our filtered list!
            idx = 0
            self.listbox.selection_clear()
        self.listbox.see(idx)
        
