"""Main PyCoCuMa widget.

This window provides the basic decorations, primarily including the menubar.
It is used to bring up other windows.
"""
#  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: MainView.py 92 2004-11-28 15:34:44Z henning $

import sys
import os
import string
from Tkinter import *
import tkMessageBox
import Pmw
import IconImages
import debug
import broadcaster
import broker
import types
import Preferences

class MainView:

    from ContactSelectboxWidget import ContactSelectboxWidget
    from ContactListWidget import ContactListWidget
    from ContactEditWidget import ContactEditWidget
    from ContactViewWidget import ContactViewWidget
    #from ContactJournalWidget import ContactJournalWidget
    from ListViewWidget import ListViewWidget
    from IOBinding import IOBinding
    import Query
    import Bindings
    
    def __init__(self, version, model, tkroot=None, slavewindow=False):
        self.isslavewindow = slavewindow
        if not tkroot:
            tkroot = Pmw.initialise()
            tkroot.withdraw()
        self.tkroot = tkroot
        self.__version = version
        self.model = model
        
        self.initOptionDatabase()
        self.createWidgets()
        self.centerWindow()
        self.createMenubar()
    
        self.applyBindings()
        self.registerAtBroadcaster()
    
        # Only Import and Export:
        self.io = self.IOBinding(self)

        # Test whether this is the first start-up with this version:
        if Preferences.get('client.version') != self.__version:
            # if true, show our fancy help window:
            self.showHelpWindow()
            Preferences.set('client.version', self.__version)
    
    def applyBindings(self, keydefs=None):
        "Apply Key-Bindings / Add, Connect to Events"
        if keydefs is None:
            keydefs = self.Bindings.default_keydefs
        self.keydefs = keydefs
        top = self.top
        for event, keylist in keydefs.items():
            if keylist:
                top.event_add(event, *keylist)
        
        top.bind("<<close-application>>", self.close)
        top.bind("<<edit-preferences>>", self.showPreferencesDialog)
        top.bind("<<about-pycocuma>>", self.showAboutDialog)
        top.bind("<<help>>", self.showHelpWindow)
        
        top.bind("<<connect-to-server>>", self.connect_to_server_event)
        top.bind("<<disconnect-from-server>>", self.disconnect_from_server_event)

        # Application-wide Bindings (default: F1-F5 Keys):
        top.bind_all("<<view-list>>", self.view_list_event)
        top.bind_all("<<view-card>>", self.view_card_event)
        top.bind_all("<<edit-contact>>", self.edit_contact_event)
        top.bind_all("<<view-journal>>", self.view_journal)
        top.bind_all("<<view-calendar>>", self.view_calendar)
        top.bind_all("<<view-lettercomposer>>", self.view_lettercomposer)
        
        top.bind("<<new-contact>>", self.new_contact_event)
        top.bind("<<del-contact>>", self.del_contact_event)
        top.bind("<<save-contact>>", self.save_contact_event)
        top.bind("<<dup-contact>>", self.dup_contact_event)
        top.bind("<<export-contact>>", self.export_contact_event)
        top.bind("<<find-contact>>", self.find_contact_event)
        
        top.bind("<<query-find-contact>>", self.query_find_contact)
        top.bind("<<query-find-next>>", self.query_find_next)
        top.bind("<<query-birthdays>>", self.query_birthdays)
        top.bind("<<page-view>>", self.page_view)
    
    def registerAtBroadcaster(self):
        "Register our Callback Handlers"
        # Show Message Box on Notification Broadcast:
        broadcaster.Register(self.onNotification,
            source='Notification')
        broadcaster.Register(self.onContactsOpen,
            source='Contacts', title='Opened')
        broadcaster.Register(self.onContactsClose,
            source='Contacts', title='Closed')
        broadcaster.Register(self.onContactsImport,
            source='Contacts', title='Imported')
        broadcaster.Register(self.onContactModify,
            source='Contact', title='Modified')
        # Broadcasted by JournalEditWidget:    
        broadcaster.Register(self.onJournalAttendeeClick,
            source='Journal', title='Attendee Clicked')
        broadcaster.Register(self.onCommandComposeLetter,
            source='Command', title='Compose Letter')
    
    menu_specs = [
        ("file", "_File"),
        ("contact", "_Contact"),
        ("query", "_Query"),
        ("view", "_View"),
        ("settings", "_Settings"),
        ("help", "_Help"),
        ]
        
    def createMenubar(self):
        "Create the Menus and fill them"
        mbar = self._menubar
        self.menudict = menudict = {}
        for name, label in self.menu_specs:
            underline, label = prepstr(label)
            menudict[name] = menu = Menu(mbar, name=name)
            mbar.add_cascade(label=label, menu=menu, underline=underline)
        self.fillMenus()
    
    def fillMenus(self, defs=None, keydefs=None):
        """Fill the menus. Menus that are absent or None in
        self.menudict are ignored."""
        if defs is None:
            defs = self.Bindings.menudefs
        if keydefs is None:
            keydefs = self.Bindings.default_keydefs
        menudict = self.menudict
        for mname, itemlist in defs:
            menu = menudict.get(mname)
            if not menu:
                continue
            for item in itemlist:
                if not item:
                    menu.add_separator()
                else:
                    label, event = item
                    checkbutton = (label[:1] == '!')
                    if checkbutton:
                        label = label[1:]
                    underline, label = prepstr(label)
                    accelerator = get_accelerator(keydefs, event)
                    def command(win=self.top, event=event):
                        win.event_generate(event)
                    if checkbutton:
                        var = self.getrawvar(event, BooleanVar)
                        menu.add_checkbutton(label=label, underline=underline,
                            command=command, accelerator=accelerator,
                            variable=var)
                    else:
                        menu.add_command(label=label, underline=underline,
                            command=command, accelerator=accelerator)
    
    def initOptionDatabase(self):
        try:
            family, size, mod = Preferences.get('client.font', types.ListType)
        except:
            family = None
        if family:
            self.tkroot.option_add("*font", (family, size, mod))
        if sys.platform != 'win32':
            # Enable 'MouseOver'-highlighting of buttons, etc:
            self.tkroot.option_add("*activeBackground", "#ececec")
            self.tkroot.option_add("*activeForeground", "black")
            # I prefer windows-alike look on unix:
            # (Listbox, Entry and Text widgets have grey background
            # on unix per default)
            self.tkroot.option_add("*Listbox*background", "white")
            # 2004-02-27: I no longer want flat listboxes:
            # self.tkroot.option_add("*Listbox*relief", "flat")
            self.tkroot.option_add("*Entry*background", "white")            
            self.tkroot.option_add("*Text*background", "white")          
        
    def createWidgets(self):
        "create the top level window"
        self._menubar = Menu(self.tkroot)
        top = self.top = Toplevel(self.tkroot, class_='PyCoCuMa', menu=self._menubar)
        top.protocol('WM_DELETE_WINDOW', self.close)
        top.title('PyCoCuMa %s' % self.__version)
        top.iconname('PyCoCuMa')
        try:
            os.chdir(os.path.dirname(sys.argv[0]))
            if sys.platform == "win32":
                top.iconbitmap("pycocuma.ico")
            else:
                top.iconbitmap("@pycocuma.xbm")
                top.iconmask("@pycocuma_mask.xbm")
        except:
            debug.echo("Could not set TopLevel window icon")

        top.rowconfigure(1, weight=1)
        top.columnconfigure(1, weight=1)
        top.withdraw()

        import ToolTip
    
        ## Top Bar:
        topbar = Frame(top)
        topbar.grid(columnspan=2,sticky=W+E)
        topbar.columnconfigure(0, weight=1)
        topbar.columnconfigure(7, weight=1)
    
        self.selContact = self.ContactSelectboxWidget(topbar, self.model, self.openContact)
        self.selContact.grid(sticky=W+E, padx=2, pady=2)
    
        # create icons (should already have been done by SplashScreen!)
        IconImages.createIconImages()

        # create buttons
        self.btnNewContact = Button(topbar, image=IconImages.IconImages["newcontact"], command=self.newContact)
        ToolTip.ToolTip(self.btnNewContact, "Create New Contact")

        self.btnDelContact = Button(topbar, image=IconImages.IconImages["delcontact"], command=self.delContact, state=DISABLED)
        ToolTip.ToolTip(self.btnDelContact, "Delete Contact")

        self.btnSaveContact = Button(topbar, image=IconImages.IconImages["savecontact"], command=self.saveContact, state=DISABLED)
        ToolTip.ToolTip(self.btnSaveContact, "Save Contact to Server")

        self.btnDuplicateContact = Button(topbar, image=IconImages.IconImages["dupcontact"], command=self.duplicateContact, state=DISABLED)
        ToolTip.ToolTip(self.btnDuplicateContact, "Duplicate this Contact")

        self.btnExportContact = Button(topbar, image=IconImages.IconImages["exportcontact"], command=self.exportContact, state=DISABLED)
        ToolTip.ToolTip(self.btnExportContact, "Export this Contact to file")

        # selectively display buttons
        btnColumn = 1
        for btn in Preferences.get("client.topbar", astype=types.ListType):
            if btn == "newContact":
                self.btnNewContact.grid(column=btnColumn,row=0, padx=2, pady=2)
                btnColumn += 1
            elif btn == "delContact":
                self.btnDelContact.grid(column=btnColumn,row=0, padx=2, pady=2)
                btnColumn += 1
            elif btn == "saveContact":
                self.btnSaveContact.grid(column=btnColumn,row=0, padx=2, pady=2)
                btnColumn += 1
            elif btn == "duplicateContact":
                self.btnDuplicateContact.grid(column=btnColumn,row=0, padx=2, pady=2)
                btnColumn += 1
            elif btn == "exportContact":
                self.btnExportContact.grid(column=btnColumn,row=0, padx=2, pady=2)
                btnColumn += 1
            elif btn == "SEP":
                # Separator Line:
                sep = Frame(topbar, width=2, borderwidth=1,
                            relief=SUNKEN, height=32)
                sep.grid(column=btnColumn, row=0, padx=4, pady=2)
                btnColumn += 1
        
        dummy = Frame(topbar)
        dummy.grid(column=7, row=0, sticky=W+E)
        ####
        
        ## List of Contacts (left):
        self.lstContacts = self.ContactListWidget(top, self.model,
            selectcommand=self.openContact,
            dblclickcommand=self.viewContact)
        self.lstContacts.grid(row=1,column=0,sticky=N+S+W+E)
        ####
        
        self._notebook = Pmw.NoteBook(top, pagemargin=2,
            lowercommand=self.notebookLower,
            raisecommand=self.notebookRaise)
        self._notebook.grid(row=1,column=1,sticky=W+E+N+S)
        
        self.listview = None
        page = self._notebook.add('List View')
        self.listview = self.ListViewWidget(page, self.model,
            selectcommand=self.openContact,
            dblclickcommand=self.viewContact)
        self.listview.pack(fill=BOTH, expand=1)
        
        self.contactview = None
        page = self._notebook.add('View Card')
        self.contactview = self.ContactViewWidget(page,
            selectcommand=self.editContact)
        self.contactview.grid(sticky=W+E+N+S)
    
        page = self._notebook.add('Edit Contact')
        self.contactedit = self.ContactEditWidget(page)
        self.contactedit.pack(fill=BOTH, expand=1)
    
        self._notebook.setnaturalsize()

        # Add Message/Status bar at bottom of window:
        self.messagebar = Pmw.MessageBar(top, silent=True, 
            entry_relief=SUNKEN, entry_borderwidth=1, entry_highlightthickness=0)
        self.messagebar.grid(columnspan=2, sticky=W+E)
        
    def centerWindow(self, relx=0.5, rely=0.3):
        "Center the Main Window on Screen"
        widget = self.top
        master = self.tkroot
        widget.update_idletasks() # Actualize geometry information
        if master.winfo_ismapped():
            m_width = master.winfo_width()
            m_height = master.winfo_height()
            m_x = master.winfo_rootx()
            m_y = master.winfo_rooty()
        else:
            m_width = master.winfo_screenwidth()
            m_height = master.winfo_screenheight()
            m_x = m_y = 0
        w_width = widget.winfo_reqwidth()
        w_height = widget.winfo_reqheight()
        x = m_x + (m_width - w_width) * relx
        y = m_y + (m_height - w_height) * rely
        if x+w_width > master.winfo_screenwidth():
            x = master.winfo_screenwidth() - w_width
        elif x < 0:
            x = 0
        if y+w_height > master.winfo_screenheight():
            y = master.winfo_screenheight() - w_height
        elif y < 0:
            y = 0
        widget.geometry("+%d+%d" % (x, y))
        widget.deiconify() # Become visible at the desired location
    
    def notebookLower(self, page):
        "Event triggered when lowering a page of the notebook"
        pass
        
    def notebookRaise(self, page):
        "Event triggered when raising another page of the notebook"
        if page == "List View" and self.listview:
            #try:
            #    self.listview.partial_update(self.listview_update_contacts)
            #except:
            #    pass
            self.listview_update_contacts.clear()
        elif page == "View Card" and self.contactview and self.contactview_must_update:
            self.contactview.rebindWidgets()
            self.contactview_must_update = 0
    
    def newContact(self):
        "Add new (empty) contact"
        newhandle = self.model.NewContact({
            'fn':'*New Untitled Card*',
            'categories':self.lstContacts.getCurrentCategory()})
        self.editContact(newhandle)
        self.set_saved(0)
    
    def askDelContact(self, fn):
        "Display Dialog asking if user really wants to delete the card"
        m = tkMessageBox.Message(
            title="Confirm Delete",
            message="Do You really want to delete the card '%s'?" % (fn),
            icon=tkMessageBox.QUESTION,
            type=tkMessageBox.YESNO,
            master=self.top)
        return m.show()
        
    def delContact(self):
        "Delete contact currently editing"
        answer =  self.askDelContact(self.contactedit.boundto().fn.get())
        # Sometimes (esp. after Import) the answer is True
        # instead of 'yes': Why???
        if answer == 'yes' or answer == True:
            handle = self.contactedit.cardhandle()
            self.model.DelContact(handle)
            self.contact_modified = 0
            self.openContact()
            self.set_saved(0)

    def askSaveContact(self):
        "Display Dialog asking if user wants to save changes"
        m = tkMessageBox.Message(
            title="Save Changes?",
            message="This Contact has been modified.\nSave the Changes?",
            icon=tkMessageBox.QUESTION,
            type=tkMessageBox.YESNOCANCEL,
            master=self.top)
        return m.show()

    def saveContact(self):
        "Upload the modified Contact to the Server"
        # To get the latest changes from all textedit widgets:
        self.contactedit.rebindWidgets()
        # this must come after rebindWidgets,
        # because rebindWidgets could trigger a Modify-Broadcast:
        self.btnSaveContact["state"] = DISABLED
        handle = self.contactedit.cardhandle() 
        if handle is None:
            handle = self.model.NewContact()
        self.model.PutContact(handle, self.contactedit.boundto().VCF_repr())
        self.contact_modified = 0

    def duplicateContact(self):
        "Duplicate the current Card"
        import vcard
        newhandle = self.model.NewContact()
        card = vcard.vCard(self.contactedit.boundto().VCF_repr())
        card.fn.set(card.fn.get()+" (Copy)")
        self.model.PutContact(newhandle, card.VCF_repr())
        self.editContact(newhandle)
        self.set_saved(0)

    def exportContact(self):
        "Export the current card to file"
        import converters
        converters.Export(self.top, self.model, targetformat=None,
            cardhandles=[self.contactedit.cardhandle()])
        
    def openContact(self, handle=None):
        "Open contact by handle or default (first)"
        if self.contact_modified:
            # Sometimes (esp. after Import) the answer is True
            # instead of 'yes': Why???
            answer = self.askSaveContact() 
            if answer == 'yes' or answer == True:
                self.saveContact()
        if handle is None:
            handles = self.model.ListHandles()
            if handles:
                contact = self.model.GetContact(handles[0])
            else:
                contact = None
        else:
            contact = self.model.GetContact(handle)
        self.contactview.bind_contact(contact)
        self.contactedit.bind_contact(contact)
        if contact:
            # Inform other widgets of newly opened contact:
            self.selContact.selectContact(contact.handle())
            self.lstContacts.selectContact(contact.handle())
            self.listview.selectContact(contact.handle())
            self.btnDelContact["state"]=NORMAL
            self.btnDuplicateContact["state"]=NORMAL
            self.btnExportContact["state"]=NORMAL
            broadcaster.Broadcast('Contact', 'Opened',
                data={'handle':contact.handle()})
        else:  
            self.btnDelContact["state"]=DISABLED
            self.btnDuplicateContact["state"]=DISABLED
            self.btnExportContact["state"]=DISABLED
        self.btnSaveContact["state"]=DISABLED
        self.contact_modified = 0
    
    def viewContact(self, handle=None):
        "Open the 'View Contact' Tab"
        self.openContact(handle)
        self._notebook.selectpage("View Card")
        
    def editContact(self, handle=None):
        "Open the 'Edit Contact' Tab"
        self.openContact(handle)
        self._notebook.selectpage("Edit Contact")
        
    def connect_to_server_event(self, event=None):
        "Show Connect Dialog"
        import ConnectDialog
        dlg = ConnectDialog.ConnectDialog(self.top, title="Connect")
        defaultstrings = {'xmlrpc':'http://localhost:8810',
                          'file':os.path.expanduser("~/addressbook.vcf")}
        type = broker.Request('Connection Type')
        str = broker.Request('Connection String')
        if type == 'none': 
            type = Preferences.get('client.connection_type')
            str = Preferences.get('client.connection_string')
        defaultstrings[type] = str
        def doconnect(btn, self=self, dlg=dlg):
            if btn == 'Connect':
                type, str = dlg.getvalue()
                self.model.Close()
                self.model.Open(type, str)
            dlg.deactivate()    
        dlg['command'] = doconnect
        dlg.activate(type, defaultstrings)
        
    def disconnect_from_server_event(self, event=None):
        "Disconnect From Server (close Model)"
        self.model.Close()
        
    def view_list_event(self, event=None):
        self._notebook.selectpage("List View")
        self.top.deiconify()
        self.top.lift()
    
    def view_card_event(self, event=None):
        self._notebook.selectpage("View Card")
        self.top.deiconify()
        self.top.lift()
    
    def edit_contact_event(self, event=None):
        self._notebook.selectpage("Edit Contact")
        self.top.deiconify()
        self.top.lift()
        
    def new_contact_event(self, event=None):
        self.newContact()
    
    def del_contact_event(self, event=None):
        self.delContact()

    def save_contact_event(self, event=None):
        self.saveContact()
    
    def dup_contact_event(self, event=None):
        self.duplicateContact()
    
    def export_contact_event(self, event=None):
        self.exportContact()

    def find_contact_event(self, event=None):
        self.selContact.focus_set()
    
    def onNotification(self):
        "Show Messagebox on Notification"
        title = broadcaster.CurrentTitle()
        showdialog = False
        if title == 'Error': 
            self.messagebar.message('systemerror', 'ERROR: ' + broadcaster.CurrentData()['message'])
            icon = tkMessageBox.ERROR
            showdialog = True
        elif title == 'Status':
            self.messagebar.message('state', broadcaster.CurrentData()['message'])
        elif title == 'Event':
            self.messagebar.message('systemevent', broadcaster.CurrentData()['message'])
        else:
            self.messagebar.message('usererror', 'WARNING: ' + broadcaster.CurrentData()['message'])
            icon = tkMessageBox.WARNING
            showdialog = True
        if showdialog:
            m = tkMessageBox.Message(
                title=title,
                message=broadcaster.CurrentData()['message'],
                icon=icon,
                type=tkMessageBox.OK,
                master=self.top)
            m.show()        
    
    def onContactsOpen(self):
        "Callback, triggered on Broadcast"
        self.set_saved(1)
        self.btnNewContact["state"] = NORMAL
        self.btnSaveContact["state"] = DISABLED
        # (Re-)Enable all Menu Items:
        for menu in ["file", "query"]:
            for i in range(self.menudict[menu].index(END)+1):
                if self.menudict[menu].type(i) == 'command':
                       self.menudict[menu].entryconfigure(i, state=NORMAL)
        self.openContact()
        self.selContact.focus_set()
        
    def onContactsClose(self):
        "Callback, triggered on Broadcast"
        self.set_saved(1)
        self.btnNewContact["state"] = DISABLED
        self.btnDelContact["state"] = DISABLED
        self.btnDuplicateContact["state"] = DISABLED
        self.btnExportContact["state"] = DISABLED
        self.btnSaveContact["state"] = DISABLED
        # Disable all Menu Items except Connect and Close:
        for menu in ["file", "query"]:
            for i in range(self.menudict[menu].index(END)+1):
                if self.menudict[menu].type(i) == 'command'\
                    and self.menudict[menu].entrycget(i, 'label') != 'Connect...'\
                    and self.menudict[menu].entrycget(i, 'label') != 'Close':
                       self.menudict[menu].entryconfigure(i, state=DISABLED)
        
    def onContactsImport(self):
        "Callback, triggered on Broadcast"
        self.set_saved(0)
        self.openContact(broadcaster.CurrentData()['handle'])
    
    listview_update_contacts = {}
    contactview_must_update = 0
    contact_modified = 0
    def onContactModify(self):
        "File was modified since last save"
        self.listview_update_contacts[broadcaster.CurrentData()['handle']] = 1
        self.contactview_must_update = 1
        self.contact_modified = 1
        self.set_saved(0)
        self.btnSaveContact["state"] = NORMAL
        self.updateTitlebar()

    def onJournalAttendeeClick(self):
        # First try UID, and only if uid is not defined,
        # search by FormattedName:
        fieldval = broadcaster.CurrentData().get('uid')
        if fieldval: 
            fieldname = 'UID'
        else:
            fieldval = broadcaster.CurrentData().get('fn')
            fieldname = 'FormattedName'
        handles = self.model.ListHandles()
        attrs = self.model.QueryAttributes(handles, fieldname)
        try:
            idx = attrs.index(fieldval)
        except ValueError:
            broadcaster.Broadcast('Notification', 'Error',
                data={'message':"A contact with %s '%s' could not be found." % (fieldname, fieldval)})
            return
        self.openContact(handles[idx])    

    def onCommandComposeLetter(self):
        self.view_lettercomposer()
        card = broadcaster.CurrentData()['card']
        adr = broadcaster.CurrentData()['adr']
        self.lettercomposer.composeTo(card, adr)
        
        
    _is_saved = 0
    def set_saved(self, flag):
        self._is_saved = flag
        self.updateTitlebar()
    def get_saved(self):
        return self._is_saved
    
    def close(self, event=None):
        reply = 'none'
        if self.contact_modified:
            reply = self.askSaveContact()
            if reply == 'yes' or reply == True:
                self.saveContact()
        # We need str() here, because reply is a tcl_Obj:       
        if str(reply) != 'cancel':
            self._close()
        
    def _close(self):
        if self.isslavewindow:
            self.withdraw()
        else:
            self.tkroot.quit()
            self.io.close(); self.io = None
            self.top.destroy()
    
    def window(self):
        "Returns Tk's TopLevel Widget"
        return self.top
    
    def updateTitlebar(self):
        "Update the Window's Titlebar"
        con_str = broker.Request('Connection String')
        if con_str:
            title = "PyCoCuMa - %s" % con_str
            if not self.get_saved():
                title = "*%s*" % title
        else:
            title = "PyCoCuMa %s" % self.__version
        self.top.wm_title(title)
    
    _prefwin = None
    def showPreferencesDialog(self, event=None):
        "Open Preference Editor"
        if not self._prefwin:
            import PreferencesDialog
            self._prefwin = PreferencesDialog.PreferencesDialog(self.top)
        self._prefwin.show()
    
    def showAboutDialog(self, event=None):
        "Show a simple About Dialog"
        tkMessageBox.showinfo('About PyCoCuMa ' + self.__version, 
        """PyCoCuMa %s
(Pythonic Contact and Customer Management)

Copyright 2003-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.
        """ % self.__version)
    
    _helpwin = None
    def showHelpWindow(self, event=None):
        "Open Help Window"
        if not self._helpwin:
            import HelpWindow
            self._helpwin = HelpWindow.HelpWindow(self.top)
        self._helpwin.show()
        
    def withdraw(self):
        "Withdraw: Forward to TopLevel Method"
        self.top.withdraw()
    
    def deiconify(self):
        "DeIconify: Forward to TopLevel Method"
        self.top.deiconify()
        
    def query_birthdays(self, event=None):
        "Display List of upcoming Birthdays"
        self.Query.Birthdays(self.top, self.model)
        
    def query_find_contact(self, event=None):
        "Find/Search Dialog"
        start = self.contactedit.cardhandle()
        handle = self.Query.FindContact(self.top, self.model, start)
        if handle != None and handle != start:
            self.openContact(handle)
    
    def query_find_next(self, event=None):
        "Find the Next Contact with same Searchoptions"
        start = self.contactedit.cardhandle()
        handle = self.Query.FindContact(self.top, self.model, start, 'search_next')
        if handle != None and handle != start:
            self.openContact(handle)
    
    def page_view(self, event=None):
        "View PDF Document (uses PDFLaTeX)"
        import tempfile
        import converters
        filename = tempfile.mktemp('.tex')
        fd = converters.EncodedFileWriter(file(filename, "wb"), 'latin-1')
        converters.export2latex(self.model, fd)
        fd.close()
        import texwrapper
        texwrapper.run_pdflatex(filename)
        texwrapper.view_pdf(filename)

    journalwin = None
    def view_journal(self, event=None):
        "Open Journal Window"
        from JournalWindow import JournalWindow
        if not self.journalwin:
            self.journalwin = JournalWindow(self.model, self.top)
        self.journalwin.show()    
        
    lettercomposer = None
    def view_lettercomposer(self, event=None):
        "Open Letter Composer Window"
        from LetterComposer import LetterComposer
        if not self.lettercomposer:
            self.lettercomposer = LetterComposer(self.top)
        self.lettercomposer.show()    
        
    calendarwin = None
    def view_calendar(self, event=None):
        "Open Calendar Window"
        from CalendarWindow import CalendarWindow
        if not self.calendarwin:
            self.calendarwin = CalendarWindow(self.model, self.top)
        self.calendarwin.show()    
        

keynames = {
 'bracketleft': '[',
 'bracketright': ']',
 'slash': '/',
}

def prepstr(s):
    # Helper to extract the underscore from a string, e.g.
    # prepstr("Co_py") returns (2, "Copy").
    i = string.find(s, '_')
    if i >= 0:
        s = s[:i] + s[i+1:]
    return i, s

def get_accelerator(keydefs, event):
    import re
    keylist = keydefs.get(event)
    if not keylist:
        return ""
    s = keylist[0]
    s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
    s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
    s = re.sub("Key-", "", s)
    s = re.sub("Control-", "Ctrl-", s)
    s = re.sub("-", "+", s)
    s = re.sub("><", " ", s)
    s = re.sub("<", "", s)
    s = re.sub(">", "", s)
    return s

