FW: new Auto Sync feature for calendar (Part 2 of 3)

"Simon C" <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <001901c5be78$b4367d30$6400a8c0@HOME>
Part 2 of 3 code changes

Simon
gui.py (text/plain, 91.8 KB)
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: gui.py,v 1.215 2005/09/16 02:28:07 djpham Exp $

"""The main gui code for BitPim"""

# System modules
import thread, threading
import Queue
import time
import os
import cStringIO
import zipfile
import re
import sys
import shutil
import types
import datetime
import sha
import codecs

# wx modules
import wx
import wx.lib.colourdb
import wx.gizmos
import wx.html

# my modules
import guiwidgets
import common
import version
import helpids
import comdiagnose
import phonebook
import importexport
import wallpaper
import ringers
import guihelper
import bpcalendar
import bphtml
import bitflingscan
import database
import memo
import update
import todo
import sms_tab
import phoneinfo
import call_history
import phone_detect
import phone_media_codec
import hexeditor
import today
import pubsub
import com_brew
import auto_sync

if guihelper.IsMSWindows():
    import win32api
    import win32con
    import win32gui

###
### Used to check our threading
###
mainthreadid=thread.get_ident()
helperthreadid=-1 # set later



###
### Implements a nice flexible callback object
###

class Callback:
    "Callback class.  Extra arguments can be supplied at call time"
    def __init__(self, method, *args, **kwargs):
        if __debug__:
            global mainthreadid
            assert mainthreadid==thread.get_ident()
        self.method=method
        self.args=args
        self.kwargs=kwargs

    def __call__(self, *args, **kwargs):
        if __debug__:
            global mainthreadid
            assert mainthreadid==thread.get_ident()
        d=self.kwargs.copy()
        d.update(kwargs)
        apply(self.method, self.args+args, d)

class Request:
    def __init__(self, method, *args, **kwargs):
        # created in main thread
        if __debug__:
            global mainthreadid
            assert mainthreadid==thread.get_ident()
        self.method=method
        self.args=args
        self.kwargs=kwargs

    def __call__(self, *args, **kwargs):
        # called in helper thread
        if __debug__:
            global helperthreadid
            assert helperthreadid==thread.get_ident()
        d=self.kwargs.copy()
        d.update(kwargs)
        return apply(self.method, self.args+args, d)
        

###
### Event used for passing results back from helper thread
###

class HelperReturnEvent(wx.PyEvent):
    def __init__(self, callback, *args, **kwargs):
        if __debug__:
            # verify being called in comm worker thread
            global helperthreadid
            assert helperthreadid==thread.get_ident()
        global EVT_CALLBACK
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_CALLBACK)
        self.cb=callback
        self.args=args
        self.kwargs=kwargs

    def __call__(self):
        if __debug__:
            global mainthreadid
            assert mainthreadid==thread.get_ident()
        return apply(self.cb, self.args, self.kwargs)

###
### Our helper thread where all the work gets done
###

class WorkerThreadFramework(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self, name="BitPim helper")
        self.q=Queue.Queue()

    def setdispatch(self, dispatchto):
        self.dispatchto=dispatchto

    def checkthread(self):
        # Function to verify we are running in the correct
        # thread.  All functions in derived class should call this
        global helperthreadid
        assert helperthreadid==thread.get_ident()
        
    def run(self):
        global helperthreadid
        helperthreadid=thread.get_ident()
        first=1
        while True:
            if not first:
                wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.endbusycb))
            else:
                first=0
            item=self.q.get()
            wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.startbusycb))
            call=item[0]
            resultcb=item[1]
            ex=None
            res=None
            try:
                res=call()
            except Exception,e:
                ex=e
                if not hasattr(e,"gui_exc_info"):
                    ex.gui_exc_info=sys.exc_info()
                
            wx.PostEvent(self.dispatchto, HelperReturnEvent(resultcb, ex, res))
            if isinstance(ex, SystemExit):
                raise ex

    def progressminor(self, pos, max, desc=""):
        wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.progressminorcb, pos, max, desc))

    def progressmajor(self, pos, max, desc=""):
        wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.progressmajorcb, pos, max, desc))

    def progress(self, pos, max, desc=""):
        self.progressminor(pos, max, desc)

    def log(self, str):
        if self.dispatchto.wantlog:
            wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.logcb, str))

    def logdata(self, str, data, klass=None):
        if self.dispatchto.wantlog:
            wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.logdatacb, str, data, klass))


###
###  Splash screen
###

thesplashscreen=None  # set to non-none if there is one

class MySplashScreen(wx.SplashScreen):
    def __init__(self, app, config):
        self.app=app
        # how long are we going to be up for?
        time=config.ReadInt("splashscreentime", 2500)
        if time>0:
            bmp=guihelper.getbitmap("splashscreen")
            self.drawnameandnumber(bmp)
            wx.SplashScreen.__init__(self, bmp, wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,
                                    time,
                                    None, -1)
            wx.EVT_CLOSE(self, self.OnClose)
            self.Show()
            app.Yield(True)
            global thesplashscreen
            thesplashscreen=self
            return
        # timeout is <=0 so don't show splash screen
        self.goforit()

    def drawnameandnumber(self, bmp):
        dc=wx.MemoryDC()
        dc.SelectObject(bmp)
        # where we start writing
        x=23 
        y=40
        # Product name
        if False:
            str=version.name
            dc.SetTextForeground( wx.NamedColour("MEDIUMORCHID4") ) 
            dc.SetFont( self._gimmethedamnsizeirequested(25, wx.ROMAN, wx.NORMAL, wx.NORMAL) )
            w,h=dc.GetTextExtent(str)
            dc.DrawText(str, x, y)
            y+=h+0
        # Version number
        x=58
        y=127
        str=version.versionstring+"-"+version.vendor
        dc.SetTextForeground( wx.NamedColour("MEDIUMBLUE") )
        dc.SetFont( self._gimmethedamnsizeirequested(15, wx.ROMAN, wx.NORMAL, wx.NORMAL) )
        w,h=dc.GetTextExtent(str)
        dc.DrawText(str, x+10, y)
        y+=h+0
        # all done
        dc.SelectObject(wx.NullBitmap)

    def _gimmethedamnsizeirequested(self, ps, family, style, weight):
        # on Linux we have to ask for bigger than we want
        if guihelper.IsGtk():
            ps=ps*1.6
        font=wx.TheFontList.FindOrCreateFont(int(ps), family, style, weight)
        return font

    def goforit(self):
        self.app.makemainwindow()
        
    def OnClose(self, evt):
        self.goforit()
        evt.Skip()

####
#### Main application class.  Runs the event loop etc
####

# safe mode items
def _notsafefunc(*args, **kwargs):
    raise common.InSafeModeException()

class _NotSafeObject:
    def __getattr__(self, *args):  _notsafefunc()
    def __setattr__(self, *args): _notsafefunc()

_NotSafeObject=_NotSafeObject()

EVT_CALLBACK=None
class MainApp(wx.App):
    def __init__(self, *_):
        self.frame=None
        self.SAFEMODE=False
        codecs.register(phone_media_codec.search_func)
        wx.App.__init__(self, redirect=False, useBestVisual=True)
        
    def OnInit(self):
        self.made=False
        # Routine maintenance
        wx.lib.colourdb.updateColourDB()
        
        # Thread stuff
        global mainthreadid
        mainthreadid=thread.get_ident()

        # Establish config stuff
        cfgstr='bitpim'
        if guihelper.IsMSWindows():
            cfgstr="BitPim"  # nicely capitalized on Windows
        self.config=wx.Config(cfgstr, style=wx.CONFIG_USE_LOCAL_FILE)

        # for help to save prefs
        self.SetAppName(cfgstr)
        self.SetVendorName(cfgstr)

        # safe mode is read at startup and can't be changed
        self.SAFEMODE=self.config.ReadInt("SafeMode", False)

        # we used to initialise help here, but in wxPython the stupid help window
        # appeared on Windows just setting it up.  We now defer setting it up
        # until it is needed
        self.helpcontroller=None

        # html easy printing
        self.htmlprinter=bphtml.HtmlEasyPrinting(None, self.config, "printing")

        global EVT_CALLBACK
        EVT_CALLBACK=wx.NewEventType()

        # initialize the Brew file cache
        com_brew.file_cache=com_brew.FileCache(self.config.Read('path', ''))

        # get the splash screen up
        MySplashScreen(self, self.config)

        return True

    def ApplySafeMode(self):
        # make very sure we are in safe mode
        if not self.SAFEMODE:
            return
        if self.frame is None:
            return
        # ensure various objects/functions are changed to not-safe
        objects={self.frame:
                    ( "dlgsendphone", "OnDataSendPhone", "OnDataSendPhoneGotFundamentals", "OnDataSendPhoneResults"),
                 self.frame.filesystemwidget:
                    ( "OnFileDelete", "OnFileOverwrite", "OnNewSubdir", "OnNewFile", "OnDirDelete", "OnRestore"),
                 self.frame.wt:
                    ( "senddata", "writewallpaper", "writeringtone", "writephonebook", "writecalendar", "rmfile",
                      "writefile", "mkdir", "rmdir", "rmdirs", "restorefiles" ),
                 self.frame.phoneprofile:
                    ( "convertphonebooktophone", ),
                 self.frame.phonemodule.Phone:
                    ( "mkdir", "mkdirs", "rmdir", "rmfile", "rmdirs", "writefile", "savegroups", "savephonebook",
                      "savecalendar", "savewallpapers", "saveringtones")
                 }

        for obj, names in objects.iteritems():
            if obj is None:
                continue
            for name in names:
                field=getattr(obj, name, None)
                if field is None or field is _notsafefunc or field is _NotSafeObject:
                    continue
                if isinstance(field, (types.MethodType, types.FunctionType)):
                    newval=_notsafefunc
                else: newval=_NotSafeObject
                setattr(obj, name, newval)

        # remove various menu items if we can find them
        removeids=(guihelper.ID_DATASENDPHONE, guihelper.ID_FV_OVERWRITE, guihelper.ID_FV_NEWSUBDIR,
                   guihelper.ID_FV_NEWFILE, guihelper.ID_FV_DELETE, guihelper.ID_FV_RENAME,
                   guihelper.ID_FV_RESTORE, guihelper.ID_FV_ADD)
        mb=self.frame.GetMenuBar()
        menus=[mb.GetMenu(i) for i in range(mb.GetMenuCount())]
        fsw=self.frame.filesystemwidget
        if  fsw is not None:
            menus.extend( [fsw.filemenu, fsw.dirmenu] )
        for menu in menus:
            for id in removeids:
                item=menu.FindItemById(id)
                if item is not None:
                    menu.RemoveItem(item)
        
            
        

##    def setuphelpiwant(self):
##        """This is how the setuphelp code is supposed to be, but stuff is missing from wx"""
##        self.helpcontroller=wx.BestHelpController()
##        self.helpcontroller.Initialize(gethelpfilename)

    def _setuphelp(self):
        """Does all the nonsense to get help working"""

        # htmlhelp isn't correctly wrapper in wx package
        # Add the Zip filesystem
        wx.FileSystem_AddHandler(wx.ZipFSHandler())
        # Get the help working
        self.helpcontroller=wx.html.HtmlHelpController()
        self.helpcontroller.AddBook(guihelper.gethelpfilename()+".htb")
        self.helpcontroller.UseConfig(self.config, "help")

        # now context help
        # (currently borken)
        # self.helpprovider=wx.HelpControllerHelpProvider(self.helpcontroller)
        # wx.HelpProvider_Set(provider)

    def displayhelpid(self, id):
        if guihelper.IsMSWindows():
            import win32help
            fname=guihelper.gethelpfilename()+".chm"
            if id is None:
                id=helpids.ID_WELCOME
            # unfortunately I can't get the stupid help viewer to also make the treeview
            # on the left to go to the right place
            win32help.HtmlHelp(self.frame.GetHandle(), fname, win32help.HH_DISPLAY_TOPIC, id)
        else:
            if self.helpcontroller is None:
                self._setuphelp()
            if id is None:
                self.helpcontroller.DisplayContents()
            else:
                self.helpcontroller.Display(id)

    def makemainwindow(self):
        if self.made:
            return # already been called
        self.made=True
        # make the main frame
        self.frame=MainWindow(None, -1, "BitPim", self.config)
        self.frame.Connect(-1, -1, EVT_CALLBACK, self.frame.OnCallback)
        if guihelper.IsMac():
            self.frame.MacSetMetalAppearance(True)

        # make the worker thread
        wt=WorkerThread()
        wt.setdispatch(self.frame)
        wt.setDaemon(1)
        wt.start()
        self.frame.wt=wt
        self.SetTopWindow(self.frame)
        self.SetExitOnFrameDelete(True)
        self.ApplySafeMode()
        wx.CallAfter(self.CheckUpdate)

    update_delta={ 'Daily': 1, 'Weekly': 7, 'Monthly': 30 }
    def CheckUpdate(self):
        if self.frame is None: 
            return
        # tell the frame to do a check-for-update
        update_rate=self.config.Read('updaterate', '')
        if not len(update_rate) or update_rate =='Never':
            return
        last_update=self.config.Read('last_update', '')
        try:
            if len(last_update):
                last_date=datetime.date(int(last_update[:4]), int(last_update[4:6]),
                                        int(last_update[6:]))
                next_date=last_date+datetime.timedelta(\
                    self.update_delta.get(update_rate, 7))
            else:
                next_date=last_date=datetime.date.today()
        except ValueError:
            # month day swap problem
            next_date=last_date=datetime.date.today()
        if datetime.date.today()<next_date:
            return
        self.frame.AddPendingEvent(\
            wx.PyCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED,
                              guihelper.ID_HELP_UPDATE))

    def OnExit(self): 
        self.config.Flush()
        # we get stupid messages about daemon threads, and Python's library
        # doesn't provide any way to interrupt them, nor to suppress these
        # messages.  ::TODO:: maybe remove the onexit handler installed by
        # treading._MainThread
        sys.excepthook=donothingexceptionhandler

# do nothing exception handler
def donothingexceptionhandler(*args):
    pass

# Entry point
def run(*args):
    m=MainApp(*args)
    res=m.MainLoop()
    return res

###
### Main Window (frame) class
###

class MenuCallback:
    "A wrapper to help with callbacks that ignores arguments when invoked"
    def __init__(self, func, *args, **kwargs):
        self.func=func
        self.args=args
        self.kwargs=kwargs
        
    def __call__(self, *args):
        return self.func(*self.args, **self.kwargs)
        
class MainWindow(wx.Frame):
    def __init__(self, parent, id, title, config):
        wx.Frame.__init__(self, parent, id, title,
                         style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
        wx.GetApp().frame=self

        wx.GetApp().htmlprinter.SetParentFrame(self)

        sys.excepthook=Callback(self.excepthook)
        ### plumbing, callbacks        
        self.wt=None # worker thread
        self.progressminorcb=Callback(self.OnProgressMinor)
        self.progressmajorcb=Callback(self.OnProgressMajor)
        self.logcb=Callback(self.OnLog)
        self.logdatacb=Callback(self.OnLogData)
        self.startbusycb=Callback(self.OnBusyStart)
        self.endbusycb=Callback(self.OnBusyEnd)

        ### random variables
        self.exceptiondialog=None
        self.wantlog=1  # do we want to receive log information
        self.config=config
        self.progmajortext=""
        self.lw=None
        self.lwdata=None
        self.filesystemwidget=None
        self.__owner_name=''

        self.database=None
        
        ### Status bar

        sb=guiwidgets.MyStatusBar(self)
        self.SetStatusBar(sb)
        self.SetStatusBarPane(sb.GetHelpPane())

        ### Art
        # establish the custom art provider for custom icons
        # this is a global setting, so no need to call it for each toolbar
        wx.ArtProvider_PushProvider(guihelper.ArtProvider())

        ### Menubar

        menuBar = wx.MenuBar()
        self.SetMenuBar(menuBar)
        menu = wx.Menu()
        # menu.Append(guihelper.ID_FILENEW,  "&New", "Start from new")
        # menu.Append(guihelper.ID_FILEOPEN, "&Open", "Open a file")
        # menu.Append(guihelper.ID_FILESAVE, "&Save", "Save your work")
        menu.Append(guihelper.ID_FILEPRINT, "&Print...", "Print phonebook")
        # menu.AppendSeparator()
        
        # imports
        impmenu=wx.Menu()
        for desc, help, func in importexport.GetPhonebookImports():
            x=wx.NewId()
            impmenu.Append(x, desc, help)
            wx.EVT_MENU(self, x, MenuCallback(func, self) )

        menu.AppendMenu(guihelper.ID_FILEIMPORT, "Import", impmenu)

        # exports
        expmenu=wx.Menu()
        for desc, help, func in importexport.GetPhonebookExports():
            x=wx.NewId()
            expmenu.Append(x, desc, help)
            wx.EVT_MENU(self, x, MenuCallback(func, self) )

        menu.AppendMenu(guihelper.ID_FILEEXPORT, "Export", expmenu)

        if not guihelper.IsMac():
            menu.AppendSeparator()
            menu.Append(guihelper.ID_FILEEXIT, "E&xit", "Close down this program")
        menuBar.Append(menu, "&File");
        self.__menu_edit=menu=wx.Menu()
        menu.Append(guihelper.ID_EDITSELECTALL, "Select All\tCtrl+A", "Select All")
        menu.AppendSeparator()
        menu.Append(guihelper.ID_EDITADDENTRY, "New...\tCtrl+N", "Add an item")
        menu.Append(guihelper.ID_EDITCOPY, "Copy\tCtrl+C", "Copy to the clipboard")
        menu.Append(guihelper.ID_EDITPASTE,"Paste\tCtrl+V", "Paste from the clipboard")
        menu.Append(guihelper.ID_EDITDELETEENTRY, "Delete\tDel", "Delete currently selected entry")
        menu.Append(guihelper.ID_EDITRENAME, "Rename\tF2", "Rename currently selected entry")
        menu.AppendSeparator()
        menu.Append(guihelper.ID_EDITPHONEINFO,
                    "&Phone Info", "Display Phone Information")
        menu.AppendSeparator()
        menu.Append(guihelper.ID_EDITDETECT,
                    "Detect Phone", "Auto Detect Phone")
        if guihelper.IsMac():
            wx.App_SetMacPreferencesMenuItemId(guihelper.ID_EDITSETTINGS)
            menu.Append(guihelper.ID_EDITSETTINGS, "&Preferences...", "Edit Settings")
        else:
            menu.AppendSeparator()
            menu.Append(guihelper.ID_EDITSETTINGS, "&Settings", "Edit settings")
        menuBar.Append(menu, "&Edit");

        menu=wx.Menu()
        menu.Append(guihelper.ID_DATAGETPHONE, "Get Phone &Data ...", "Loads data from the phone")
        menu.Append(guihelper.ID_DATASENDPHONE, "&Send Phone Data ...", "Sends data to the phone")
        menu.Append(guihelper.ID_DATAHISTORICAL, "Historical Data", "View Current & Historical Data")
        menuBar.Append(menu, "&Data")

        menu=wx.Menu()
        menu.Append(guihelper.ID_AUTOSYNCSETTINGS, "&Configure AutoSync Settings...", "Configures Schedule Auto-Synchronisation")
        menu.Append(guihelper.ID_AUTOSYNCEXECUTE, "&Synchronize Schedule Now", "Synchronize Schedule Now")
        menuBar.Append(menu, "&AutoSync")

        menu=wx.Menu()
        menu.Append(guihelper.ID_VIEWCOLUMNS, "Columns ...", "Which columns to show")
        menu.AppendCheckItem(guihelper.ID_VIEWPREVIEW, "Phonebook Preview", "Toggle Phonebook Preview Pane")
        menu.AppendSeparator()
        menu.AppendCheckItem(guihelper.ID_VIEWLOGDATA, "View protocol logging", "View protocol logging information")
        menu.Append(guihelper.ID_VIEWCLEARLOGS, "Clear logs", "Clears the contents of the log panes")
        menu.AppendSeparator()
        menu.AppendCheckItem(guihelper.ID_VIEWFILESYSTEM, "View filesystem", "View filesystem on the phone")
        menuBar.Append(menu, "&View")
        

        menu=wx.Menu()
        if guihelper.IsMac():
            menu.Append(guihelper.ID_HELPHELP, "&BitPim Help", "Help for the panel you are looking at")
            menu.AppendSeparator()
        else:
            menu.Append(guihelper.ID_HELPHELP, "&Help", "Help for the panel you are looking at")
        menu.Append(guihelper.ID_HELPTOUR, "&Tour", "Tour of BitPim")
        menu.Append(guihelper.ID_HELPCONTENTS, "&Contents", "Table of contents for the online help")
        menu.Append(guihelper.ID_HELPSUPPORT, "&Support", "Getting support for BitPim")
        if version.vendor=='official':
            menu.AppendSeparator()
            menu.Append(guihelper.ID_HELP_UPDATE, "&Check for Update", "Checking for any BitPim Update")
        if guihelper.IsMac():
            wx.App_SetMacAboutMenuItemId(guihelper.ID_HELPABOUT)
            menu.Append(guihelper.ID_HELPABOUT, "&About BitPim", "Display program information")
            wx.App_SetMacHelpMenuTitleName("&Help")
            wx.App_SetMacExitMenuItemId(guihelper.ID_FILEEXIT)
        else:
            menu.AppendSeparator()
            menu.Append(guihelper.ID_HELPABOUT, "&About", "Display program information")
        menuBar.Append(menu, "&Help");

        ### toolbar
        self.tb=self.CreateToolBar(wx.TB_HORIZONTAL|wx.TB_TEXT)
        self.tb.SetToolBitmapSize(wx.Size(32,32))
        sz=self.tb.GetToolBitmapSize()

        # add and delete tools
        self.tooladd=self.tb.AddLabelTool(guihelper.ID_EDITADDENTRY, "Add", wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_TOOLBAR, sz),
                                          shortHelp="Add", longHelp="Add an item")
        self.tooldelete=self.tb.AddLabelTool(guihelper.ID_EDITDELETEENTRY, "Delete", wx.ArtProvider.GetBitmap(wx.ART_DEL_BOOKMARK, wx.ART_TOOLBAR, sz),
                                             shortHelp="Delete", longHelp="Delete item")
            
        # You have to make this call for the toolbar to draw itself properly
        self.tb.Realize()


        ### persistent dialogs
        self.dlggetphone=guiwidgets.GetPhoneDialog(self, "Get Data from Phone")
        self.dlgsendphone=guiwidgets.SendPhoneDialog(self, "Send Data to Phone")

        ### Events we handle
        wx.EVT_MENU_OPEN(self, self.OnMenuOpen)
        wx.EVT_MENU(self, guihelper.ID_FILEPRINT, self.OnFilePrint)
        wx.EVT_MENU(self, guihelper.ID_FILEEXIT, self.OnExit)
        wx.EVT_MENU(self, guihelper.ID_EDITSETTINGS, self.OnEditSettings)
        wx.EVT_MENU(self, guihelper.ID_DATAGETPHONE, self.OnDataGetPhone)
        wx.EVT_MENU(self, guihelper.ID_DATASENDPHONE, self.OnDataSendPhone)
        wx.EVT_MENU(self, guihelper.ID_DATAHISTORICAL, self.OnDataHistorical)
        wx.EVT_MENU(self, guihelper.ID_VIEWCOLUMNS, self.OnViewColumns)
        wx.EVT_MENU(self, guihelper.ID_VIEWPREVIEW, self.OnViewPreview)
        wx.EVT_MENU(self, guihelper.ID_VIEWCLEARLOGS, self.OnViewClearLogs)
        wx.EVT_MENU(self, guihelper.ID_VIEWLOGDATA, self.OnViewLogData)
        wx.EVT_MENU(self, guihelper.ID_VIEWFILESYSTEM, self.OnViewFilesystem)
        wx.EVT_MENU(self, guihelper.ID_EDITADDENTRY, self.OnEditAddEntry)
        wx.EVT_MENU(self, guihelper.ID_EDITDELETEENTRY, self.OnEditDeleteEntry)
        wx.EVT_MENU(self, guihelper.ID_EDITSELECTALL, self.OnEditSelectAll)
        wx.EVT_MENU(self, guihelper.ID_EDITCOPY, self.OnCopyEntry)
        wx.EVT_MENU(self, guihelper.ID_EDITPASTE, self.OnPasteEntry)
        wx.EVT_MENU(self, guihelper.ID_EDITRENAME, self.OnRenameEntry)
        wx.EVT_MENU(self, guihelper.ID_HELPABOUT, self.OnHelpAbout)
        wx.EVT_MENU(self, guihelper.ID_HELPHELP, self.OnHelpHelp)
        wx.EVT_MENU(self, guihelper.ID_HELPCONTENTS, self.OnHelpContents)
        wx.EVT_MENU(self, guihelper.ID_HELPSUPPORT, self.OnHelpSupport)
        wx.EVT_MENU(self, guihelper.ID_HELPTOUR, self.OnHelpTour)
        wx.EVT_MENU(self, guihelper.ID_HELP_UPDATE, self.OnCheckUpdate)
        wx.EVT_MENU(self, guihelper.ID_EDITPHONEINFO, self.OnPhoneInfo)
        wx.EVT_MENU(self, guihelper.ID_EDITDETECT, self.OnDetectPhone)
        wx.EVT_MENU(self, guihelper.ID_AUTOSYNCSETTINGS, self.OnAutoSyncSettings)
        wx.EVT_MENU(self, guihelper.ID_AUTOSYNCEXECUTE, self.OnAutoSyncExecute)
        wx.EVT_CLOSE(self, self.OnClose)

        ### Double check our size is meaningful, and make bigger
        ### if necessary (especially needed on Mac and Linux)
        if min(self.GetSize())<250:
            self.SetSize( (640, 480) )


        ### Is config set?
        self.configdlg=guiwidgets.ConfigDialog(self, self)
        if self.configdlg.needconfig():
            self.CloseSplashScreen()
            if self.configdlg.ShowModal()!=wx.ID_OK:
                self.OnExit()
                return
        self.configdlg.updatevariables()
        
        ### Set autosync settings dialog
        self.calenders=importexport.GetCalenderAutoSyncImports()
        print "cal " +`self.calenders`
        self.autosyncsetting=auto_sync.AutoSyncSettingsDialog(self, self)
        self.autosyncsetting.updatevariables()

        ### notebook
        self.nb=wx.Notebook(self,-1, style=wx.NO_FULL_REPAINT_ON_RESIZE|wx.CLIP_CHILDREN)

        ### notebook tabs
        if self.config.ReadInt("console", 0):
            import developer
            self.nb.AddPage(developer.DeveloperPanel(self.nb, {'mw': self, 'db': self.database} ), "Console")
        self.todaywidget=today.TodayWidget(self, self.nb)
        self.nb.AddPage(self.todaywidget, "Today")
        self.phonewidget=phonebook.PhoneWidget(self, self.nb, self.config)
        self.nb.AddPage(self.phonewidget, "PhoneBook")
        self.wallpaperwidget=wallpaper.WallpaperView(self, self.nb)
        self.nb.AddPage(self.wallpaperwidget, "Wallpaper")
        self.ringerwidget=ringers.RingerView(self, self.nb)
        self.nb.AddPage(self.ringerwidget, "Ringers")
        self.calendarwidget=bpcalendar.Calendar(self, self.nb)
        self.nb.AddPage(self.calendarwidget, "Calendar")
        self.memowidget=memo.MemoWidget(self, self.nb)
        self.nb.AddPage(self.memowidget, "Memo")
        self.todowidget=todo.TodoWidget(self, self.nb)
        self.nb.AddPage(self.todowidget, 'Todo')
        self.smswidget=sms_tab.SMSWidget(self, self.nb)
        self.nb.AddPage(self.smswidget, 'SMS')
        self.callhistorywidget=call_history.CallHistoryWidget(self, self.nb)
        self.nb.AddPage(self.callhistorywidget, 'Call History')

        ### logwindow (last notebook tab)
        self.lw=guiwidgets.LogWindow(self.nb)
        self.nb.AddPage(self.lw, "Log")

        # Final widgets that depend on config
        lv=self.config.ReadInt("viewlogdata", 0)
        if lv:
            menuBar.Check(guihelper.ID_VIEWLOGDATA, 1)
            self.OnViewLogData(None)

        fv=self.config.ReadInt("viewfilesystem", 0)
        if fv:
            menuBar.Check(guihelper.ID_VIEWFILESYSTEM, 1)
            self.OnViewFilesystem(None)
            wx.Yield()
        # whether or not to turn on phonebook preview pane
        if self.config.ReadInt("viewpreview", 1):
            menuBar.Check(guihelper.ID_VIEWPREVIEW, 1)
        else:
            self.phonewidget.OnViewPreview(False)
        # update the the status bar info
        self.SetPhoneModelStatus()
        self.SetVersionsStatus()
        # now register for notebook changes
        wx.EVT_NOTEBOOK_PAGE_CHANGED(self, -1, self.OnNotebookPageChanged)


        # show the last page we were on
        if self.config.ReadInt('startwithtoday', 0):
            pg='Today'
        else:
            pg=self.config.Read("viewnotebookpage", "")
        sel=0
        if len(pg):
            for i in range(self.nb.GetPageCount()):
                if pg==self.nb.GetPageText(i):
                    sel=i
                    break

        if sel==self.nb.GetSelection():
            # no callback is generated if we change to the page we are already
            # on, but we need to update toolbar etc so fake it
            self.OnNotebookPageChanged()
        else:
            self.nb.SetSelection(sel)

        # Retrieve saved settings... Use 90% of screen if not specified
        guiwidgets.set_size("MainWin", self, screenpct=90)

        ### Lets go visible
        self.Show()

        # Show tour on first use
        if self.config.ReadInt("firstrun", True):
            self.config.WriteInt("firstrun", False)
            self.config.Flush()
            wx.CallAfter(self.OnHelpTour)

        # Populate all widgets from disk
        wx.CallAfter(self.OnPopulateEverythingFromDisk)
        # check for device changes
        if guihelper.IsMSWindows():
            # save the old window proc
            self.oldwndproc = win32gui.SetWindowLong(self.GetHandle(),
                                                     win32con.GWL_WNDPROC,
                                                     self.MyWndProc)
        # response to pubsub request
        pubsub.subscribe(self.OnReqChangeTab, pubsub.REQUEST_TAB_CHANGED)

    def CloseSplashScreen(self):
        ### remove splash screen if there is one
        global thesplashscreen
        if thesplashscreen is not None:
            try:
        # on Linux this is often already deleted and generates an exception
                thesplashscreen.Show(False)
            except:
                pass
            thesplashscreen=None
            wx.SafeYield(onlyIfNeeded=True)

    def OnMenuOpen(self, evt):
        if evt.GetMenu()!=self.__menu_edit:
            return
        widget=self.nb.GetPage(self.nb.GetSelection())
        enable_copy=hasattr(widget, "OnCopy") and \
                     hasattr(widget, "CanCopy") and \
                     widget.CanCopy()
        enable_paste=hasattr(widget, "OnCopy") and \
                      hasattr(widget, "CanPaste") and \
                      widget.CanPaste()
        enable_rename=hasattr(widget, "OnRename") and \
                       hasattr(widget, "CanRename") and \
                       widget.CanRename()
        menu_bar=self.GetMenuBar()
        menu_bar.Enable(guihelper.ID_EDITCOPY, enable_copy)
        menu_bar.Enable(guihelper.ID_EDITPASTE, enable_paste)
        menu_bar.Enable(guihelper.ID_EDITRENAME, enable_rename)

    def OnExit(self,_=None):
        self.Close()

    # It has been requested that we shutdown
    def OnClose(self, event):
        self.saveSize()
        if not self.wt:
            # worker thread doesn't exist yet
            self.Destroy()
            return
        if event.CanVeto():
            # should we close? dirty data? prompt to save?
            pass # yup close for now
        # Shutdown helper thread
        self.MakeCall( Request(self.wt.exit), Callback(self.OnCloseResults) )

    def OnCloseResults(self, exception, _):
        assert isinstance(exception, SystemExit)
        # assume it worked
        self.Destroy()
        wx.GetApp().ExitMainLoop()

    # about and help

    def OnHelpAbout(self,_):
        import version

        str="BitPim Version "+version.versionstring+" - "+version.vendor
        if version.isdevelopmentversion(): str+="\nDevelopment Version"
        str+="\n\n"
        if len(version.extrainfo):
            str+=version.extrainfo+"\n\n"
        str+=version.contact

        d=wx.MessageDialog(self, str, "About BitPim", wx.OK|wx.ICON_INFORMATION)
        d.ShowModal()
        d.Destroy()
        
    def OnHelpHelp(self, _):
        text=re.sub("[^A-Za-z]", "", self.nb.GetPageText(self.nb.GetSelection()))
        wx.GetApp().displayhelpid(getattr(helpids, "ID_TAB_"+text.upper()))

    def OnHelpContents(self, _):
        wx.GetApp().displayhelpid(None)

    def OnHelpSupport(self, _):
        wx.GetApp().displayhelpid(helpids.ID_HELPSUPPORT)

    def OnHelpTour(self, _=None):
        wx.GetApp().displayhelpid(helpids.ID_TOUR)

    def DoCheckUpdate(self):
        s=update.check_update()
        if not len(s):
            # Failed to update
            return
        # update our config with the latest version and date
        self.config.Write('latest_version', s)
        self.config.Write('last_update',
                          time.strftime('%Y%m%d', time.localtime()))
        # update the status bar
        self.SetVersionsStatus()

    def OnCheckUpdate(self, _):
        self.DoCheckUpdate()

    def SetPhoneModelStatus(self):
        phone=self.config.Read('phonetype', 'None')
        port=self.config.Read('lgvx4400port', 'None')
        self.GetStatusBar().set_phone_model(
            '%s %s/%s'%(self.__owner_name, phone, port))

    def OnPhoneInfo(self, _):
        self.MakeCall(Request(self.wt.getphoneinfo),
                      Callback(self.OnDisplayPhoneInfo))
    def OnDisplayPhoneInfo(self, exception, phone_info):
        if self.HandleException(exception): return
        if phone_info is None:
            # data not available
            dlg=wx.MessageDialog(self, "Phone Info not available",
                             "Phone Info Error", style=wx.OK)
        else:
            dlg=phoneinfo.PhoneInfoDialog(self, phone_info)
        dlg.ShowModal()
        dlg.Destroy()

    def OnDetectPhone(self, _):
        if wx.IsBusy():
            wx.MessageBox("BitPim is busy.  You can't change settings until it has finished talking to your phone.",
                         "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
            return
        self.__detect_phone()
    def __detect_phone(self, using_port=None, check_auto_sync=0):
        self.OnBusyStart()
        self.GetStatusBar().progressminor(0, 100, 'Phone detection in progress ...')
        self.MakeCall(Request(self.wt.detectphone, using_port),
                      Callback(self.OnDetectPhoneReturn, check_auto_sync))
    def __get_owner_name(self, esn):
        """ retrieve or ask user for the owner's name of this phone
        """
        if esn is None or not len(esn):
            return None
        # esn is found, check if we detected this phone before
        phone_id='phones/'+sha.new(esn).hexdigest()
        phone_name=self.config.Read(phone_id, '<None/>')
        s=None
        if phone_name=='<None/>':
            # not seen before
            dlg=guiwidgets.AskPhoneNameDialog(
                self, 'A new phone has been detected,\n'
                "Would you like to enter the owner's name:")
            r=dlg.ShowModal()
            if r==wx.ID_OK:
                # user gave a name
                s=dlg.GetValue()
            elif r==wx.ID_CANCEL:
                s=''
            if s is not None:
                self.config.Write(phone_id, s)
            dlg.Destroy()
            return s
        return phone_name
        
    def OnDetectPhoneReturn(self, check_auto_sync, exception, r):
        self.OnBusyEnd()
        if self.HandleException(exception): return
        if r is None:
            self.__owner_name=''
            wx.MessageBox('No phone detected/recognized',
                          'Phone Detection Failed', wx.OK)
        else:
            self.__owner_name=self.__get_owner_name(r.get('phone_esn', None))
            if self.__owner_name is None:
                self.__owner_name=''
            else:
                self.__owner_name+="'s"
            self.config.Write("phonetype", r['phone_name'])
            self.commportsetting=str(r['port'])
            self.wt.clearcomm()
            self.config.Write("lgvx4400port", r['port'])
            self.phonemodule=__import__(r['phone_module'])
            self.phoneprofile=self.phonemodule.Profile()
            pubsub.publish(pubsub.PHONE_MODEL_CHANGED, self.phonemodule)
            self.SetPhoneModelStatus()
            wx.MessageBox('Found %s %s on %s'%(self.__owner_name,
                                               r['phone_name'],
                                               r['port']),
                          'Phone Detection', wx.OK)
            if check_auto_sync:
                # see if we should re-sync the calender on connect, do it silently
                self.__autosync_phone(silent=1)
        
    def WindowsOnDeviceChanged(self, type, name="", drives=[], flag=None):
        if not name.lower().startswith("com"):
            return
        if type=='DBT_DEVICEREMOVECOMPLETE':
            print "Device remove", name
            # device is removed, if it's ours, clear the port
            if name==self.config.Read('lgvx4400port', '') and \
               self.wt is not None:
                self.wt.clearcomm()
            return
        if type!='DBT_DEVICEARRIVAL':
            # not interested
            return
        print 'New device on port:',name
        if wx.IsBusy():
            # current phone operation ongoing, abort this
            return
        # check the new device
        check_auto_sync=auto_sync.UpdateOnConnect(self)
        self.__detect_phone(name, check_auto_sync)

    def MyWndProc(self, hwnd, msg, wparam, lparam):

        if msg==win32con.WM_DEVICECHANGE:
            type,params=DeviceChanged(wparam, lparam).GetEventInfo()
            self.OnDeviceChanged(type, **params)
            return True

        # Restore the old WndProc.  Notice the use of win32api
        # instead of win32gui here.  This is to avoid an error due to
        # not passing a callable object.
        if msg == win32con.WM_DESTROY:
            win32api.SetWindowLong(self.GetHandle(),
                                   win32con.GWL_WNDPROC,
                                   self.oldwndproc)

        # Pass all messages (in this case, yours may be different) on
        # to the original WndProc
        return win32gui.CallWindowProc(self.oldwndproc,
                                       hwnd, msg, wparam, lparam)

    if guihelper.IsMSWindows():
        OnDeviceChanged=WindowsOnDeviceChanged

    def SetVersionsStatus(self):
        current_v=version.version
        latest_v=self.config.Read('latest_version')
        self.GetStatusBar().set_versions(current_v, latest_v)

    def OnViewColumns(self, _):
        dlg=phonebook.ColumnSelectorDialog(self, self.config, self.phonewidget)
        dlg.ShowModal()
        dlg.Destroy()

    def OnViewPreview(self, evt):
        if evt.IsChecked():
            config=1
            preview_on=True
        else:
            config=0
            preview_on=False
        self.config.WriteInt('viewpreview', config)
        self.phonewidget.OnViewPreview(preview_on)

    def OnViewLogData(self, _):
        # toggle state of the log data
        logdatatitle="Protocol Log"
        if self.lwdata is None:
            self.lwdata=guiwidgets.LogWindow(self.nb)
            self.nb.AddPage(self.lwdata, logdatatitle)
            self.config.WriteInt("viewlogdata", 1)
        else:
            self.lwdata=None
            for i in range(0,self.nb.GetPageCount()):
                if self.nb.GetPageText(i)==logdatatitle:
                    self.nb.DeletePage(i)
                    break
            self.config.WriteInt("viewlogdata", 0)

    def OnViewFilesystem(self,_):
        # toggle filesystem view
        logtitle="Log"
        fstitle="Filesystem"
        if self.filesystemwidget is None:
            for i in range(0, self.nb.GetPageCount()):
                if self.nb.GetPageText(i)==logtitle:
                    self.filesystemwidget=FileSystemView(self, self.nb, id=97)
                    self.nb.InsertPage(i, self.filesystemwidget, fstitle, True)
                    self.config.WriteInt("viewfilesystem", True)
                    return
            assert False, "log page is missing!"
            return
        self.filesystemwidget=None
        for i in range(0, self.nb.GetPageCount()):
            if self.nb.GetPageText(i)==fstitle:
                self.nb.DeletePage(i)
                self.config.WriteInt("viewfilesystem", False)
                return
        assert False, "filesytem view page is missing!"
        
    def update_cache_path(self):
        com_brew.file_cache.set_path(self.configpath)

    def OnFilePrint(self,_):
        self.nb.GetPage(self.nb.GetSelection()).OnPrintDialog(self, self.config)

    ### 
    ### Main bit for getting stuff from phone
    ###

    def OnDataHistorical(self, _):
        self.nb.GetPage(self.nb.GetSelection()).OnHistoricalData()

    def OnDataGetPhone(self,_):
        todo=[]
        dlg=self.dlggetphone
        dlg.UpdateWithProfile(self.phoneprofile)
        if dlg.ShowModal()!=wx.ID_OK:
            return
        todo.append((self.wt.rebootcheck, "Phone Reboot"))
        self.MakeCall(Request(self.wt.getdata, dlg, todo),
                      Callback(self.OnDataGetPhoneResults))

    def OnDataGetPhoneResults(self, exception, results):
        if self.HandleException(exception): return
        self.OnLog(`results.keys()`)
        self.OnLog(`results['sync']`)
        # phonebook
        if results['sync'].has_key('phonebook'):
            v=results['sync']['phonebook']

            print "phonebookmergesetting is",v
            if v=='MERGE': 
                merge=True
            else:
                merge=False
            self.phonewidget.importdata(results['phonebook'], results.get('categories', []), merge)

        # wallpaper
        updwp=False # did we update the wallpaper
        if results['sync'].has_key('wallpaper'):
            v=results['sync']['wallpaper']
            if v=='MERGE': raise Exception("Not implemented")
            updwp=True
            self.wallpaperwidget.populatefs(results)
            self.wallpaperwidget.populate(results)
        # wallpaper-index
        if not updwp and results.has_key('wallpaper-index'):
            self.wallpaperwidget.updateindex(results['wallpaper-index'])
        # ringtone
        updrng=False # did we update ringtones
        if results['sync'].has_key('ringtone'):
            v=results['sync']['ringtone']
            if v=='MERGE': raise Exception("Not implemented")
            updrng=True
            self.ringerwidget.populatefs(results)
            self.ringerwidget.populate(results)
        # ringtone-index
        if not updrng and results.has_key('ringtone-index'):
            self.ringerwidget.updateindex(results['ringtone-index'])            
        # calendar
        if results['sync'].has_key('calendar'):
            v=results['sync']['calendar']
            if v=='MERGE': raise Exception("Not implemented")
            results['calendar_version']=self.phoneprofile.BP_Calendar_Version
            self.calendarwidget.populatefs(results)
            self.calendarwidget.populate(results)
        # memo
        if results['sync'].has_key('memo'):
            v=results['sync']['memo']
            if v=='MERGE': raise Exception("Not implemented")
            self.memowidget.populatefs(results)
            self.memowidget.populate(results)
        # todo
        if results['sync'].has_key('todo'):
            v=results['sync']['todo']
            if v=='MERGE': raise NotImplementedError
            self.todowidget.populatefs(results)
            self.todowidget.populate(results)
        # SMS
        if results['sync'].has_key('sms'):
            v=results['sync']['sms']
            if v=='MERGE':
                self.smswidget.merge(results)
            else:
                self.smswidget.populatefs(results)
                self.smswidget.populate(results)
        # call history
        if results['sync'].has_key('call_history'):
            v=results['sync']['call_history']
            if v=='MERGE':
                self.callhistorywidget.merge(results)
            else:
                self.callhistorywidget.populatefs(results)
                self.callhistorywidget.populate(results)
    ###
    ### Main bit for sending data to the phone
    ###
    def OnDataSendPhone(self, _):
        dlg=self.dlgsendphone
        print self.phoneprofile
        dlg.UpdateWithProfile(self.phoneprofile)
        if dlg.ShowModal()!=wx.ID_OK:
            return
        data={}
        convertors=[]
        todo=[]
        funcscb=[]
        
        ### Wallpaper
        v=dlg.GetWallpaperSetting()
        if v!=dlg.NOTREQUESTED:
            merge=True
            if v==dlg.OVERWRITE: merge=False
            if merge:
                want=self.wallpaperwidget.SELECTED
            else:
                want=self.wallpaperwidget.ALL
            self.wallpaperwidget.getdata(data, want)
            todo.append( (self.wt.writewallpaper, "Wallpaper", merge) )
            # funcscb.append( self.wallpaperwidget.populate )

        ### Ringtone
        v=dlg.GetRingtoneSetting()
        if v!=dlg.NOTREQUESTED:
            merge=True
            if v==dlg.OVERWRITE: merge=False
            if merge:
                want=self.ringerwidget.SELECTED
            else:
                want=self.ringerwidget.ALL
            self.ringerwidget.getdata(data, want)
            todo.append( (self.wt.writeringtone, "Ringtone", merge) )
            # funcscb.append( self.ringerwidget.populate )

        ### Calendar
        v=dlg.GetCalendarSetting()
        if v!=dlg.NOTREQUESTED:
            merge=True
            if v==dlg.OVERWRITE: merge=False
            data['calendar_version']=self.phoneprofile.BP_Calendar_Version
            self.calendarwidget.getdata(data)
            todo.append( (self.wt.writecalendar, "Calendar", merge) )

        ### Phonebook
        v=dlg.GetPhoneBookSetting()
        if v!=dlg.NOTREQUESTED:
            if v==dlg.OVERWRITE: 
                self.phonewidget.getdata(data)
                todo.append( (self.wt.writephonebook, "Phonebook") )
            convertors.append(self.phonewidget.converttophone)
            # writing will modify serials so we need to update
            funcscb.append(self.phonewidget.updateserials)

        ### Memo
        v=dlg.GetMemoSetting()
        if v!=dlg.NOTREQUESTED:
            merge=v!=dlg.OVERWRITE
            self.memowidget.getdata(data)
            todo.append((self.wt.writememo, "Memo", merge))

        ### Todo
        v=dlg.GetTodoSetting()
        if v!=dlg.NOTREQUESTED:
            merge=v!=dlg.OVERWRITE
            self.todowidget.getdata(data)
            todo.append((self.wt.writetodo, "Todo", merge))

        ### SMS
        v=dlg.GetSMSSetting()
        if v!=dlg.NOTREQUESTED:
            merge=v!=dlg.OVERWRITE
            self.smswidget.getdata(data)
            todo.append((self.wt.writesms, "SMS", merge))

        todo.append((self.wt.rebootcheck, "Phone Reboot"))
        self.MakeCall(Request(self.wt.getfundamentals),
                      Callback(self.OnDataSendPhoneGotFundamentals, data, todo, convertors, funcscb))

    def OnDataSendPhoneGotFundamentals(self,data,todo,convertors, funcscb, exception, results):
        if self.HandleException(exception): return
        data.update(results)
        # call each widget to update fundamentals
        # for widget in self.calendarwidget, self.wallpaperwidget, self.ringerwidget, self.phonewidget:
        #    widget.updatefundamentals(data)
        
        # call convertors
        for f in convertors:
            f(data)

        # Now scribble to phone
        self.MakeCall(Request(self.wt.senddata, data, todo),
                      Callback(self.OnDataSendPhoneResults, funcscb))

    def OnDataSendPhoneResults(self, funcscb, exception, results):
        if self.HandleException(exception): return
        print results.keys()
        for f in funcscb:
            f(results)

    def GetCalendarData(self):
        # return calendar data for export
        d={}
        return self.calendarwidget.getdata(d).get('calendar', {})

    # Get data from disk
    def OnPopulateEverythingFromDisk(self,_=None):
        self.OnBusyStart()
        try:
            results={}
            # get info
            self.phonewidget.getfromfs(results)
            self.wallpaperwidget.getfromfs(results)
            self.ringerwidget.getfromfs(results)
            self.calendarwidget.getfromfs(results)
            self.memowidget.getfromfs(results)
            self.todowidget.getfromfs(results)
            self.smswidget.getfromfs(results)
            self.callhistorywidget.getfromfs(results)
            # update controls
            wx.SafeYield(onlyIfNeeded=True)
            self.phonewidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.wallpaperwidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.ringerwidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.calendarwidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.memowidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.todowidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.smswidget.populate(results)
            wx.SafeYield(onlyIfNeeded=True)
            self.callhistorywidget.populate(results)
            # close the splash screen if it is still up
            self.CloseSplashScreen()
        finally:
            self.OnBusyEnd()
        
    # deal with configuring the phone (commport)
    def OnEditSettings(self, _=None):
        if wx.IsBusy():
            wx.MessageBox("BitPim is busy.  You can't change settings until it has finished talking to your phone.",
                         "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
        else:
            # clear the ower's name for manual setting
            self.__owner_name=''
            self.configdlg.ShowModal()

    # deal with configuring the phone (commport)
    def OnAutoSyncSettings(self, _=None):
        if wx.IsBusy():
            wx.MessageBox("BitPim is busy.  You can't change settings until it has finished talking to your phone.",
                         "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
        else:
            # clear the ower's name for manual setting
            self.__owner_name=''
            self.autosyncsetting.ShowModal()

    def OnAutoSyncExecute(self, _=None):
        if wx.IsBusy():
            wx.MessageBox("BitPim is busy.  You can't run autosync until it has finished talking to your phone.",
                         "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
            return
        self.__autosync_phone()

    def __autosync_phone(self, silent=0):
        r=auto_sync.SyncSchedule(self).sync(self, silent)
        

    def OnReqChangeTab(self, msg=None):
        if msg is None:
            return
        data=msg.data
        if not isinstance(data, int):
            # wrong data type
            if __debug__:
                raise TypeError
            return
        self.nb.SetSelection(data)

    # deal with graying out/in menu items on notebook page changing
    def OnNotebookPageChanged(self, _=None):
        # remember what we are looking at
        text=self.nb.GetPageText(self.nb.GetSelection())
        if text is not None:
            self.config.Write("viewnotebookpage", text)
        # does the page have editable properties?
        widget=self.nb.GetPage(self.nb.GetSelection())
        # force focus to its child
        widget.SetFocus()
        enable_add=hasattr(widget, "OnAdd")
        enable_del=hasattr(widget, "OnDelete")
        enable_print=hasattr(widget, "OnPrintDialog")
        enable_select_all=hasattr(widget, "OnSelectAll")
        enable_historical_data=hasattr(widget, 'OnHistoricalData')

        sz=self.tb.GetToolBitmapSize()
        mapbmpadd={id(self.ringerwidget): guihelper.ART_ADD_RINGER,
                   id(self.wallpaperwidget): guihelper.ART_ADD_WALLPAPER,
                   id(self.phonewidget): guihelper.ART_ADD_CONTACT,
                   id(self.memowidget): guihelper.ART_ADD_MEMO,
                   id(self.todowidget): guihelper.ART_ADD_TODO,
                   id(self.smswidget): guihelper.ART_ADD_SMS
                   }
        mapbmpdelete={id(self.ringerwidget): guihelper.ART_DEL_RINGER,
                      id(self.wallpaperwidget): guihelper.ART_DEL_WALLPAPER,
                      id(self.phonewidget): guihelper.ART_DEL_CONTACT,
                      id(self.memowidget): guihelper.ART_DEL_MEMO,
                      id(self.todowidget): guihelper.ART_DEL_TODO,
                      id(self.smswidget): guihelper.ART_DEL_SMS                      
                      }
        bmpadd=wx.ArtProvider.GetBitmap(mapbmpadd.get(id(widget), wx.ART_ADD_BOOKMARK), wx.ART_TOOLBAR, sz)
        self.tooladd.SetNormalBitmap(bmpadd)
        bmpdel=wx.ArtProvider.GetBitmap(mapbmpdelete.get(id(widget), wx.ART_DEL_BOOKMARK), wx.ART_TOOLBAR, sz)
        self.tooldelete.SetNormalBitmap(bmpdel)
        # this has to be called to force the actual update
        self.GetToolBar().Realize()

        # Toolbar
        self.GetToolBar().EnableTool(guihelper.ID_EDITADDENTRY, enable_add)
        self.GetToolBar().EnableTool(guihelper.ID_EDITDELETEENTRY, enable_del)
        # menu items
        menu_bar=self.GetMenuBar()
        menu_bar.Enable(guihelper.ID_EDITADDENTRY, enable_add)
        menu_bar.Enable(guihelper.ID_EDITDELETEENTRY, enable_del)
        menu_bar.Enable(guihelper.ID_DATAHISTORICAL, enable_historical_data)

        # View Columns .. is only in Phonebook
        is_phone_widget=widget is self.phonewidget
        menu_bar.Enable(guihelper.ID_VIEWCOLUMNS, is_phone_widget)
        menu_bar.Enable(guihelper.ID_VIEWPREVIEW, is_phone_widget)
        # as is File Print
        menu_bar.Enable(guihelper.ID_FILEPRINT, enable_print)

        # select all
        menu_bar.Enable(guihelper.ID_EDITSELECTALL, enable_select_all)
         
    # add/delete entry in the current tab
    def OnEditAddEntry(self, evt):
        self.nb.GetPage(self.nb.GetSelection()).OnAdd(evt)

    def OnEditDeleteEntry(self, evt):
        self.nb.GetPage(self.nb.GetSelection()).OnDelete(evt)

    def OnEditSelectAll(self, evt):
        self.nb.GetPage(self.nb.GetSelection()).OnSelectAll(evt)

    def OnCopyEntry(self, evt):
        self.nb.GetPage(self.nb.GetSelection()).OnCopy(evt)

    def OnPasteEntry(self, evt):
        self.nb.GetPage(self.nb.GetSelection()).OnPaste(evt)

    def OnRenameEntry(self, evt):
        self.nb.GetPage(self.nb.GetSelection()).OnRename(evt)

    # Busy handling
    def OnBusyStart(self):
        self.GetStatusBar().set_app_status("BUSY")
        wx.BeginBusyCursor(wx.StockCursor(wx.CURSOR_ARROWWAIT))

    def OnBusyEnd(self):
        wx.EndBusyCursor()
        self.GetStatusBar().set_app_status("Ready")
        self.OnProgressMajor(0,1)

    # progress and logging
    def OnViewClearLogs(self, _):
        self.lw.Clear()
        if self.lwdata is not None:
            self.lwdata.Clear()

    def OnProgressMinor(self, pos, max, desc=""):
        self.GetStatusBar().progressminor(pos, max, desc)

    def OnProgressMajor(self, pos, max, desc=""):
        self.GetStatusBar().progressmajor(pos, max, desc)

    def OnLog(self, str):
        str=common.strorunicode(str)
        self.lw.log(str)
        if self.lwdata is not None:
            self.lwdata.log(str)
        if str.startswith("<!= "):
            p=str.index("=!>")+3
            dlg=wx.MessageDialog(self, str[p:], "Alert", style=wx.OK|wx.ICON_EXCLAMATION)
            dlg.ShowModal()
            dlg.Destroy()
            self.OnLog("Alert dialog closed")
    log=OnLog
    def OnLogData(self, str, data, klass=None):
        if self.lwdata is not None:
            self.lwdata.logdata(str,data, klass)

    def excepthook(self, type, value, traceback):
        if not hasattr(value, "gui_exc_info"):
            value.gui_exc_info=(type,value,traceback)
        self.HandleException(value)

    def HandleException(self, exception):
        """returns true if this function handled the exception
        and the caller should not do any further processing"""
        if exception is None: return False
        assert isinstance(exception, Exception)
        self.CloseSplashScreen()
        # always close comm connection when we have any form of exception
        self.wt.clearcomm()
        text=None
        title=None
        style=None
        # Here is where we turn the exception into something user friendly
        if isinstance(exception, common.CommsDeviceNeedsAttention):
            text="%s: %s" % (exception.device, exception.message)
            title="Device needs attention - "+exception.device
            style=wx.OK|wx.ICON_INFORMATION
            help=lambda _: wx.GetApp().displayhelpid(helpids.ID_DEVICE_NEEDS_ATTENTION)
        elif isinstance(exception, common.CommsOpenFailure):
            text="%s: %s" % (exception.device, exception.message)
            title="Failed to open communications - "+exception.device
            style=wx.OK|wx.ICON_INFORMATION
            help=lambda _: wx.GetApp().displayhelpid(helpids.ID_FAILED_TO_OPEN_DEVICE)
        elif isinstance(exception, common.AutoPortsFailure):
            text=exception.message
            title="Failed to automatically detect port"
            style=wx.OK|wx.ICON_INFORMATION
            help=lambda _: wx.GetApp().displayhelpid(helpids.ID_FAILED_TO_AUTODETECT_PORT)
        elif isinstance(exception, common.HelperBinaryNotFound) and exception.basename=="pvconv":
            text="The Qualcomm PureVoice converter program (%s) was not found.\nPlease see the help. Directories looked in are:\n\n " +\
                  "\n ".join(exception.paths)
            text=text % (exception.fullname,)
            title="Failed to find PureVoice converter"
            style=wx.OK|wx.ICON_INFORMATION
            help=lambda _: wx.GetApp().displayhelpid(helpids.ID_NO_PVCONV)
        elif isinstance(exception, common.PhoneBookBusyException):
            text="The phonebook is busy on your phone.\nExit back to the main screen and then repeat the operation."
            title="Phonebook busy on phone"
            style=wx.OK|wx.ICON_INFORMATION
            help=lambda _: wx.GetApp().displayhelpid(helpids.ID_PHONEBOOKBUSY)
        elif isinstance(exception, common.IntegrityCheckFailed):
            text="The phonebook on your phone is partially corrupt.  Please read the\nhelp for more details on the cause and fix"
            title="IntegrityCheckFailed"
            style=wx.OK|wx.ICON_EXCLAMATION
            help=lambda _: wx.GetApp().displayhelpid(helpids.ID_LG_INTEGRITYCHECKFAILED)
            
        if text is not None:
            self.OnLog("Error: "+title+"\n"+text)
            dlg=guiwidgets.AlertDialogWithHelp(self,text, title, help, style=style)
            dlg.ShowModal()
            dlg.Destroy()
            return True

        if self.exceptiondialog is None:
            self.excepttime=time.time()
            self.exceptcount=0
            self.exceptiondialog=guiwidgets.ExceptionDialog(self, exception)
            try:
                self.OnLog("Exception: "+self.exceptiondialog.getexceptiontext())
            except AttributeError:
                # this can happen if main gui hasn't been built yet
                pass
        else:
            self.exceptcount+=1
            if self.exceptcount<10:
                print "Ignoring an exception as the exception dialog is already up"
                self.OnLog("Exception during exception swallowed")
            return True
            
        self.exceptiondialog.ShowModal()
        self.exceptiondialog.Destroy()
        self.exceptiondialog=None
        return True
        
    # plumbing for the multi-threading

    def OnCallback(self, event):
        assert isinstance(event, HelperReturnEvent)
        event()

    def MakeCall(self, request, cbresult):
        assert isinstance(request, Request)
        assert isinstance(cbresult, Callback)
        self.wt.q.put( (request, cbresult) )

    # remember our size and position

    def saveSize(self):
        guiwidgets.save_size("MainWin", self.GetRect())

    # deal with the database
    def EnsureDatabase(self, newpath, oldpath):
        newdbpath=os.path.abspath(os.path.join(newpath, "bitpim.db"))
        if oldpath is not None and len(oldpath) and oldpath!=newpath:
            # copy database to new location
            if self.database:
                self.database=None # cause it to be closed
            olddbpath=os.path.abspath(os.path.join(oldpath, "bitpim.db"))
            if os.path.exists(olddbpath) and not os.path.exists(newdbpath):
                shutil.copyfile(olddbpath, newdbpath)
        self.database=None # allow gc
        self.database=database.Database(newdbpath)
            
        

###
### Container for midi files
###  

#class MidiFileList(wx.ListCtrl):
#    pass




###
###  Class that does all the comms and other stuff in a seperate
###  thread.  
###

class WorkerThread(WorkerThreadFramework):
    def __init__(self):
        WorkerThreadFramework.__init__(self)
        self.commphone=None

    def exit(self):
        if __debug__: self.checkthread()
        for i in range(0,0):
            self.progressmajor(i, 2, "Shutting down helper thread")
            time.sleep(1)
        self.log("helper thread shut down")
        raise SystemExit("helper thread shutdown")


    def clearcomm(self):
        if self.commphone is None:
            return
        self.commphone.close()
        self.commphone=None
        
        
    def setupcomm(self):
        if __debug__: self.checkthread()
        if self.commphone is None:
            import commport
            if self.dispatchto.commportsetting is None or \
               len(self.dispatchto.commportsetting)==0:
                raise common.CommsNeedConfiguring("Comm port not configured", "DEVICE")

            if self.dispatchto.commportsetting=="auto":
                autofunc=comdiagnose.autoguessports
            else:
                autofunc=None
            comcfg=self.dispatchto.commparams

            name=self.dispatchto.commportsetting
            if name.startswith("bitfling::"):
                klass=bitflingscan.CommConnection
            else:
                klass=commport.CommConnection
                
            comport=klass(self, self.dispatchto.commportsetting, autolistfunc=autofunc,
                          autolistargs=(self.dispatchto.phonemodule,),
                          baud=comcfg['baud'], timeout=comcfg['timeout'],
                          hardwareflow=comcfg['hardwareflow'],
                          softwareflow=comcfg['softwareflow'],
                          configparameters=comcfg)
                
            try:
                self.commphone=self.dispatchto.phonemodule.Phone(self, comport)
            except:
                comport.close()
                raise

    def getfundamentals(self):
        if __debug__: self.checkthread()
        self.setupcomm()
        results={}
        self.commphone.getfundamentals(results)
        return results

    def getdata(self, req, todo):
        if __debug__: self.checkthread()
        self.setupcomm()
        results=self.getfundamentals()
        com_brew.file_cache.esn=results.get('uniqueserial', None)
        willcall=[]
        sync={}
        for i in (
            (req.GetPhoneBookSetting, self.commphone.getphonebook, "Phone Book", "phonebook"),
            (req.GetCalendarSetting, self.commphone.getcalendar, "Calendar", "calendar",),
            (req.GetWallpaperSetting, self.commphone.getwallpapers, "Wallpaper", "wallpaper"),
            (req.GetRingtoneSetting, self.commphone.getringtones, "Ringtones", "ringtone"),
            (req.GetMemoSetting, self.commphone.getmemo, "Memo", "memo"),
            (req.GetTodoSetting, self.commphone.gettodo, "Todo", "todo"),
            (req.GetSMSSetting, self.commphone.getsms, "SMS", "sms"),
            (req.GetCallHistorySetting, self.commphone.getcallhistory, 'Call History', 'call_history')):
            st=i[0]()
            if st==req.MERGE:
                sync[i[3]]="MERGE"
                willcall.append(i)
            elif st==req.OVERWRITE:
                sync[i[3]]="OVERWRITE"
                willcall.append(i)

        results['sync']=sync
        count=0
        for i in willcall:
            self.progressmajor(count, len(willcall), i[2])
            count+=1
            i[1](results)

        for xx in todo:
            func=xx[0]
            desc=xx[1]
            args=[results]
            if len(xx)>2:
                args.extend(xx[2:])
            apply(func, args)

        return results

    def senddata(self, dict, todo):
        count=0
        for xx in todo:
            func=xx[0]
            desc=xx[1]
            args=[dict]
            if len(xx)>2:
                args.extend(xx[2:])
            self.progressmajor(count,len(todo),desc)
            apply(func, args)
            count+=1
        return dict

    def writewallpaper(self, data, merge):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.savewallpapers(data, merge)

    def writeringtone(self, data, merge):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.saveringtones(data, merge)

    def writephonebook(self, data):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.savephonebook(data)

    def rebootcheck(self, results):
        if __debug__: self.checkthread()
        if results.has_key('rebootphone'):
            self.log("BitPim is rebooting your phone for changes to take effect")
            self.phonerebootrequest()
            self.clearcomm()
            
    def writecalendar(self, data, merge):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.savecalendar(data, merge)

    def writememo(self, data, merge):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.savememo(data, merge)

    def writetodo(self, data, merge):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.savetodo(data, merge)

    def writesms(self, data, merge):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.savesms(data, merge)

    def getphoneinfo(self):
        if __debug__: self.checkthread()
        self.setupcomm()
        if hasattr(self.commphone, 'getphoneinfo'):
            phone_info=phoneinfo.PhoneInfo()
            getattr(self.commphone, 'getphoneinfo')(phone_info)
            return phone_info

    def detectphone(self, using_port=None):
        self.clearcomm()
        return phone_detect.DetectPhone(self).detect(using_port)


    # various file operations for the benefit of the filesystem viewer
    def dirlisting(self, path, recurse=0):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.getfilesystem(path, recurse)

    def getfile(self, path):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.getfilecontents(path)

    def rmfile(self,path):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.rmfile(path)

    def writefile(self,path,contents):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.writefile(path, contents)

    def mkdir(self,path):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.mkdir(path)

    def rmdir(self,path):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.rmdir(path)

    def rmdirs(self,path):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.rmdirs(path)

    # offline/reboot/modemmode
    def phonerebootrequest(self):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.offlinerequest(reset=True)

    def phoneofflinerequest(self):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.offlinerequest()

    def modemmoderequest(self):
        if __debug__: self.checkthread()
        self.setupcomm()
        return self.commphone.modemmoderequest()

    # backups etc
    def getbackup(self,path,recurse=0):
        if __debug__: self.checkthread()
        self.setupcomm()
        self.progressmajor(0,0,"Listing files")
        files=self.dirlisting(path, recurse)
        if path=="/" or path=="":
            strip=0 # root dir
        else:
            strip=len(path)+1 # child

        keys=files.keys()
        keys.sort()
        
        op=cStringIO.StringIO()
        zip=zipfile.ZipFile(op, "w", zipfile.ZIP_DEFLATED)

        count=0
        for k in keys:
            count+=1
            if files[k]['type']!='file':
                continue
            self.progressmajor(count, len(keys)+1, "Getting files")
            # get the contents
            contents=self.getfile(k)
            # an artificial sleep. if you get files too quickly, the 4400 eventually
            # runs out of buffers and returns truncated packets
            time.sleep(0.3)
            # add to zip file
            zi=zipfile.ZipInfo()
            zi.filename=k[strip:]
            if files[k]['date'][0]==0:
                zi.date_time=(0,0,0,0,0,0)
            else:
                zi.date_time=time.gmtime(files[k]['date'][0])[:6]
            zi.compress_type=zipfile.ZIP_DEFLATED
            zip.writestr(zi, contents)
        zip.close()

        return op.getvalue()
    
    def restorefiles(self, files):
        if __debug__: self.checkthread()
        self.setupcomm()

        results=[]

        seendirs=[]

        count=0
        for name, contents in files:
            self.progressmajor(count, len(files), "Restoring files")
            count+=1
            d=guihelper.dirname(name)
            if d not in seendirs:
                seendirs.append(d)
                self.commphone.mkdirs(d)
            self.writefile(name, contents)
            results.append( (True, name) )
            # add a deliberate sleep - some phones (eg vx7000) get overwhelmed when writing
            # lots of files in a tight loop
            time.sleep(0.3)

        return results


class FileSystemView(wx.gizmos.TreeListCtrl):

    # we have to add None objects to all nodes otherwise the tree control refuses
    # sort (somewhat lame imho)
    def __init__(self, mainwindow, parent, id=-1):
        self.datacolumn=False # used for debugging and inspection of values
        wx.gizmos.TreeListCtrl.__init__(self, parent, id, style=wx.WANTS_CHARS|wx.TR_DEFAULT_STYLE)
        self.AddColumn("Name")
        self.AddColumn("Size")
        self.AddColumn("Date")
        self.SetMainColumn(0)
        self.SetColumnWidth(0, 300)
        self.SetColumnWidth(2, 200)
        if self.datacolumn:
            self.AddColumn("Extra Stuff")
            self.SetColumnWidth(3, 400)
        self.SetColumnAlignment(1, wx.LIST_FORMAT_RIGHT)
        self.mainwindow=mainwindow
        self.root=self.AddRoot("/")
        self.SetPyData(self.root, None)
        self.SetItemHasChildren(self.root, True)
        self.SetPyData(self.AppendItem(self.root, "Retrieving..."), None)
        self.dirhash={ "": 1}
        wx.EVT_TREE_ITEM_EXPANDED(self, id, self.OnItemExpanded)
        wx.EVT_TREE_ITEM_ACTIVATED(self,id, self.OnItemActivated)

        self.filemenu=wx.Menu()
        self.filemenu.Append(guihelper.ID_FV_SAVE, "Save ...")
        self.filemenu.Append(guihelper.ID_FV_HEXVIEW, "Hexdump")
        self.filemenu.AppendSeparator()
        self.filemenu.Append(guihelper.ID_FV_DELETE, "Delete")
        self.filemenu.Append(guihelper.ID_FV_OVERWRITE, "Overwrite ...")

        self.dirmenu=wx.Menu()
        self.dirmenu.Append(guihelper.ID_FV_NEWSUBDIR, "Make subdirectory ...")
        self.dirmenu.Append(guihelper.ID_FV_NEWFILE, "New File ...")
        self.dirmenu.AppendSeparator()
        self.dirmenu.Append(guihelper.ID_FV_BACKUP, "Backup directory ...")
        self.dirmenu.Append(guihelper.ID_FV_BACKUP_TREE, "Backup entire tree ...")
        self.dirmenu.Append(guihelper.ID_FV_RESTORE, "Restore ...")
        self.dirmenu.AppendSeparator()
        self.dirmenu.Append(guihelper.ID_FV_REFRESH, "Refresh")
        self.dirmenu.AppendSeparator()
        self.dirmenu.Append(guihelper.ID_FV_DELETE, "Delete")
        self.dirmenu.AppendSeparator()
        self.dirmenu.Append(guihelper.ID_FV_OFFLINEPHONE, "Offline Phone")
        self.dirmenu.Append(guihelper.ID_FV_REBOOTPHONE, "Reboot Phone")
        self.dirmenu.Append(guihelper.ID_FV_MODEMMODE, "Go to modem mode")
        # generic (neither file nor directory) menu
        self.genericmenu=wx.Menu()
        self.genericmenu.Append(guihelper.ID_FV_OFFLINEPHONE, "Offline Phone")
        self.genericmenu.Append(guihelper.ID_FV_REBOOTPHONE, "Reboot Phone")
        self.genericmenu.Append(guihelper.ID_FV_MODEMMODE, "Go to modem mode")

        wx.EVT_MENU(self.filemenu, guihelper.ID_FV_SAVE, self.OnFileSave)
        wx.EVT_MENU(self.filemenu, guihelper.ID_FV_HEXVIEW, self.OnHexView)
        wx.EVT_MENU(self.filemenu, guihelper.ID_FV_DELETE, self.OnFileDelete)
        wx.EVT_MENU(self.filemenu, guihelper.ID_FV_OVERWRITE, self.OnFileOverwrite)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_NEWSUBDIR, self.OnNewSubdir)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_NEWFILE, self.OnNewFile)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_DELETE, self.OnDirDelete)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_BACKUP, self.OnBackupDirectory)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_BACKUP_TREE, self.OnBackupTree)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_RESTORE, self.OnRestore)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_REFRESH, self.OnDirRefresh)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_OFFLINEPHONE, self.OnPhoneOffline)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_REBOOTPHONE, self.OnPhoneReboot)
        wx.EVT_MENU(self.dirmenu, guihelper.ID_FV_MODEMMODE, self.OnModemMode)
        wx.EVT_MENU(self.genericmenu, guihelper.ID_FV_OFFLINEPHONE, self.OnPhoneOffline)
        wx.EVT_MENU(self.genericmenu, guihelper.ID_FV_REBOOTPHONE, self.OnPhoneReboot)
        wx.EVT_MENU(self.genericmenu, guihelper.ID_FV_MODEMMODE, self.OnModemMode)
        wx.EVT_RIGHT_DOWN(self.GetMainWindow(), self.OnRightDown)
        wx.EVT_RIGHT_UP(self.GetMainWindow(), self.OnRightUp)

    def OnRightUp(self, event):
        pt = event.GetPosition();
        item, flags,unknown = self.HitTest(pt)
        if item.IsOk():
            self.SelectItem(item)
            # is it a file or a directory
            path=self.itemtopath(item)
            if path in self.dirhash:
                if self.dirhash[path]:
                    self.PopupMenu(self.dirmenu, pt)
                    return
            self.PopupMenu(self.filemenu, pt)
        else:
            self.PopupMenu(self.genericmenu, pt)
                    
    def OnRightDown(self,event):
        # You have to capture right down otherwise it doesn't feed you right up
        pt = event.GetPosition();
        item, flags, unknown = self.HitTest(pt)
        try:
            self.SelectItem(item)
        except:
            pass

    def OnItemActivated(self,_):
        # is it a file or a directory
        item=self.GetSelection()
        path=self.itemtopath(item)
        if path in self.dirhash:
            if self.dirhash[path]:
                # directory - we ignore
                return
        self.OnHexView(self)
        

    def OnItemExpanded(self, event):
        item=event.GetItem()
        self.OnDirListing(self.itemtopath(item))

    def OnDirListing(self, path):
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.dirlisting, path),
                     Callback(self.OnDirListingResults, path) )

    def OnDirListingResults(self, path, exception, result):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        item=self.pathtoitem(path)
        l=[]
        child,cookie=self.GetFirstChild(item)
        for dummy in range(0,self.GetChildrenCount(item,False)):
            l.append(child)
            child,cookie=self.GetNextChild(item,cookie)
        # we now have a list of children in l
        for file in result:
            f=guihelper.basename(file)
            found=None
            for i in l:
                if self.GetItemText(i)==f:
                    found=i
                    break
            made=0
            if found is None:
                found=self.AppendItem(item, f)
                self.SetPyData(found, None)
                made=1
            if result[file]['type']=='file':
                self.dirhash[result[file]['name']]=0
                self.SetItemHasChildren(found, False)
                self.SetItemText(found, `result[file]['size']  `, 1)
                self.SetItemText(found, "  "+result[file]['date'][1], 2)
                if self.datacolumn:
                    self.SetItemText(found, result[file]['data'], 3)
            else: # it is a directory
                self.dirhash[result[file]['name']]=1
                self.SetItemHasChildren(found, True)
                if made: # only add this for new items
                    self.SetPyData(self.AppendItem(found, "Retrieving..."), None)
        for i in l: # remove all children not present in result
            if not result.has_key(self.itemtopath(i)):
                self.Delete(i)
        self.SortChildren(item)


    def OnFileSave(self, _):
        path=self.itemtopath(self.GetSelection())
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.getfile, path),
                     Callback(self.OnFileSaveResults, path) )
        
    def OnFileSaveResults(self, path, exception, contents):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        bn=guihelper.basename(path)
        ext=guihelper.getextension(bn)
        if len(ext):
            ext="%s files (*.%s)|*.%s" % (ext.upper(), ext, ext)
        else:
            ext="All files|*"
        dlg=wx.FileDialog(self, "Save File As", defaultFile=bn, wildcard=ext,
                             style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
        if dlg.ShowModal()==wx.ID_OK:
            open(dlg.GetPath(), "wb").write(contents)
        dlg.Destroy()

    def OnHexView(self, _):
        path=self.itemtopath(self.GetSelection())
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.getfile, path),
                     Callback(self.OnHexViewResults, path) )
        
    def OnHexViewResults(self, path, exception, result):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        # ::TODO:: make this use HexEditor
##        dlg=guiwidgets.MyFixedScrolledMessageDialog(self, common.datatohexstring(result),
##                                                    path+" Contents", helpids.ID_HEXVIEW_DIALOG)
        dlg=hexeditor.HexEditorDialog(self, result, path+" Contents")
        dlg.Show()

    def OnFileDelete(self, _):
        path=self.itemtopath(self.GetSelection())
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.rmfile, path),
                     Callback(self.OnFileDeleteResults, guihelper.dirname(path)) )
        
    def OnFileDeleteResults(self, parentdir, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        self.OnDirListing(parentdir)

    def OnFileOverwrite(self,_):
        path=self.itemtopath(self.GetSelection())
        dlg=wx.FileDialog(self, style=wx.OPEN|wx.HIDE_READONLY|wx.CHANGE_DIR)
        if dlg.ShowModal()!=wx.ID_OK:
            dlg.Destroy()
            return
        infile=dlg.GetPath()
        contents=open(infile, "rb").read()
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.writefile, path, contents),
                     Callback(self.OnFileOverwriteResults, guihelper.dirname(path)) )
        dlg.Destroy()
        
    def OnFileOverwriteResults(self, parentdir, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        self.OnDirListing(parentdir)

    def OnNewSubdir(self, _):
        dlg=wx.TextEntryDialog(self, "Subdirectory name?", "Create Subdirectory", "newfolder")
        if dlg.ShowModal()!=wx.ID_OK:
            dlg.Destroy()
            return
        parent=self.itemtopath(self.GetSelection())
        if len(parent):
            path=parent+"/"+dlg.GetValue()
        else:
            path=dlg.GetValue()
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.mkdir, path),
                     Callback(self.OnNewSubdirResults, parent) )
        dlg.Destroy()
            
    def OnNewSubdirResults(self, parentdir, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        self.OnDirListing(parentdir)
        
    def OnNewFile(self,_):
        parent=self.itemtopath(self.GetSelection())
        dlg=wx.FileDialog(self, style=wx.OPEN|wx.HIDE_READONLY|wx.CHANGE_DIR)
        if dlg.ShowModal()!=wx.ID_OK:
            dlg.Destroy()
            return
        infile=dlg.GetPath()
        contents=open(infile, "rb").read()
        if len(parent):
            path=parent+"/"+os.path.basename(dlg.GetPath())
        else:
            path=os.path.basename(dlg.GetPath()) # you can't create files in root but I won't stop you
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.writefile, path, contents),
                     Callback(self.OnNewFileResults, parent) )
        dlg.Destroy()
        
    def OnNewFileResults(self, parentdir, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        self.OnDirListing(parentdir)

    def OnDirDelete(self, _):
        path=self.itemtopath(self.GetSelection())
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.rmdirs, path),
                     Callback(self.OnDirDeleteResults, guihelper.dirname(path)) )
        
    def OnDirDeleteResults(self, parentdir, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        self.OnDirListing(parentdir)

    def OnPhoneReboot(self,_):
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.phonerebootrequest),
                     Callback(self.OnPhoneRebootResults) )

    def OnPhoneRebootResults(self, exception, _):
        # special case - we always clear the comm connection
        # it is needed if the reboot succeeds, and if it didn't
        # we probably have bad comms anyway
        mw=self.mainwindow
        mw.wt.clearcomm()
        if mw.HandleException(exception): return

    def OnPhoneOffline(self,_):
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.phoneofflinerequest),
                     Callback(self.OnPhoneOfflineResults) )

    def OnPhoneOfflineResults(self, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return

    def OnModemMode(self,_):
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.modemmoderequest),
                     Callback(self.OnModemModeResults) )

    def OnModemModeResults(self, exception, _):
        mw=self.mainwindow
        if mw.HandleException(exception): return


    def OnBackupTree(self, _):
        self.OnBackup(recurse=100)

    def OnBackupDirectory(self, _):
        self.OnBackup()

    def OnBackup(self, recurse=0):
        path=self.itemtopath(self.GetSelection())
        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.getbackup, path, recurse),
                     Callback(self.OnBackupResults, path) )

    def OnBackupResults(self, path, exception, backup):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        bn=guihelper.basename(path)
        if len(bn)<1:
            bn="root"
        bn+=".zip"
        ext="Zip files|*.zip|All Files|*"
        dlg=wx.FileDialog(self, "Save File As", defaultFile=bn, wildcard=ext,
                             style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
        if dlg.ShowModal()==wx.ID_OK:
            open(dlg.GetPath(), "wb").write(backup)
        dlg.Destroy()

    def OnRestore(self, _):
        ext="Zip files|*.zip|All Files|*"
        path=self.itemtopath(self.GetSelection())
        bn=guihelper.basename(path)
        if len(bn)<1:
            bn="root"
        bn+=".zip"
        ext="Zip files|*.zip|All Files|*"
        dlg=wx.FileDialog(self, "Open backup file", defaultFile=bn, wildcard=ext,
                             style=wx.OPEN|wx.HIDE_READONLY|wx.CHANGE_DIR)
        if dlg.ShowModal()!=wx.ID_OK:
            return
        name=dlg.GetPath()
        if not zipfile.is_zipfile(name):
            dlg=guiwidgets.AlertDialogWithHelp(self.mainwindow, name+" is not a valid zipfile.", "Zip file required",
                                               lambda _: wx.GetApp().displayhelpid(helpids.ID_NOT_A_ZIPFILE),
                                               style=wx.OK|wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()
            return
        zipf=zipfile.ZipFile(name, "r")
        xx=zipf.testzip()
        if xx is not None:
            dlg=guiwidgets.AlertDialogWithHelp(self.mainwindow, name+" has corrupted contents.  Use a repair utility to fix it",
                                               "Zip file corrupted",
                                               lambda _: wx.GetApp().displayhelpid(helpids.ID_ZIPFILE_CORRUPTED),
                                               style=wx.OK|wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()
            return

        dlg=RestoreDialog(self.mainwindow, "Restore files", zipf, path, self.OnRestoreOK)
        dlg.Show(True)

    def OnRestoreOK(self, zipf, names, parentdir):
        if len(names)==0:
            wx.MessageBox("You didn't select any files to restore!", "No files selected",
                         wx.OK|wx.ICON_EXCLAMATION)
            return
        l=[]
        for zipname, fsname in names:
            l.append( (fsname, zipf.read(zipname)) )

        mw=self.mainwindow
        mw.MakeCall( Request(mw.wt.restorefiles, l),
                     Callback(self.OnRestoreResults, parentdir) )

    def OnRestoreResults(self, parentdir, exception, results):
        mw=self.mainwindow
        if mw.HandleException(exception): return
        ok=filter(lambda s: s[0], results)
        fail=filter(lambda s: not s[0], results)

        if len(parentdir):
            dirs=[]
            for _, name in results:
                while(len(name)>len(parentdir)):
                    name=guihelper.dirname(name)
                    if name not in dirs:
                        dirs.append(name)
            dirs.sort()
            for d in dirs:
                self.OnDirListing(d)

        self.OnDirListing(parentdir)

        if len(ok) and len(fail)==0:
            dlg=wx.MessageDialog(mw, "All files restored ok", "All files restored",
                                wx.OK|wx.ICON_INFORMATION)
            dlg.Show(True)
            return
        if len(fail) and len(ok)==0:
            wx.MessageBox("All files failed to restore", "No files restored",
                         wx.OK|wx.ICON_ERROR)
            return

        op="Failed to restore some files.  Check the log for reasons.:\n\n"
        for s,n in fail:
            op+="   "+n+"\n"

        wx.MessageBox(op, "Some restores failed", wx.OK|wx.ICON_ERROR)
            

    def OnDirRefresh(self, _):
        path=self.itemtopath(self.GetSelection())
        self.OnDirListing(path)

    def itemtopath(self, item):
        if item==self.root: return ""
        res=self.GetItemText(item)
        while True:
            parent=self.GetItemParent(item)
            if parent==self.root:
                return res
            item=parent
            res=self.GetItemText(item)+"/"+res
        # can't get here, but pychecker doesn't seem to realise
        assert False
        return ""
        
    def pathtoitem(self, path):
        if path=="": return self.root
        dirs=path.split('/')
        node=self.root
        for n in range(0, len(dirs)):
            foundnode=None
            child,cookie=self.GetFirstChild(node)
            for dummy in range(0, self.GetChildrenCount(node, False)):
                d=self.GetItemText(child)
                if d==dirs[n]:
                    node=child
                    foundnode=node
                    break
                child,cookie=self.GetNextChild(node,cookie)
            if foundnode is not None:
                continue
            # make the node
            node=self.AppendItem(node, dirs[n])
            self.SetPyData(node, None)
        return node

class RestoreDialog(wx.Dialog):
    """A dialog that lists all the files that will be restored"""
    
    def __init__(self, parent, title, zipf, path, okcb):
        """Constructor

        @param path: Placed before names in the archive.  Should not include a
                       trailing slash.
        """
        wx.Dialog.__init__(self, parent, -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add( wx.StaticText(self, -1, "Choose files to restore"), 0, wx.ALIGN_CENTRE|wx.ALL, 5)

        nl=zipf.namelist()
        nl.sort()

        prefix=path
        if len(prefix)=="/" or prefix=="":
            prefix=""
        else:
            prefix+="/"

        nnl=map(lambda i: prefix+i, nl)

        self.clb=wx.CheckListBox(self, -1, choices=nnl, style=wx.LB_SINGLE|wx.LB_HSCROLL|wx.LB_NEEDED_SB, size=wx.Size(200,300))

        for i in range(len(nnl)):
            self.clb.Check(i, True)

        vbs.Add( self.clb, 1, wx.EXPAND|wx.ALL, 5)

        vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)

        vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
    
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)

        wx.EVT_BUTTON(self, wx.ID_HELP, lambda _: wx.GetApp().displayhelpid(helpids.ID_RESTOREDIALOG))
        wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK)
        self.okcb=okcb
        self.zipf=zipf
        self.nl=zip(nl, nnl)
        self.path=path

    def OnOK(self, _):
        names=[]
        for i in range(len(self.nl)):
            if self.clb.IsChecked(i):
                names.append(self.nl[i])
        self.okcb(self.zipf, names, self.path)
        self.Show(False)
        self.Destroy()
#-------------------------------------------------------------------------------
# For windows platform only
if guihelper.IsMSWindows():
    import struct
    class DeviceChanged:

        DBT_DEVICEARRIVAL = 0x8000
        DBT_DEVICEQUERYREMOVE = 0x8001
        DBT_DEVICEQUERYREMOVEFAILED = 0x8002
        DBT_DEVICEREMOVEPENDING =  0x8003
        DBT_DEVICEREMOVECOMPLETE = 0x8004
        DBT_DEVICETYPESPECIFIC = 0x8005    
        DBT_DEVNODES_CHANGED = 7
        DBT_CONFIGCHANGED = 0x18

        DBT_DEVTYP_OEM = 0
        DBT_DEVTYP_DEVNODE = 1
        DBT_DEVTYP_VOLUME = 2
        DBT_DEVTYP_PORT = 3
        DBT_DEVTYP_NET = 4

        DBTF_MEDIA   =   0x0001
        DBTF_NET    =    0x0002

        def __init__(self, wparam, lparam):
            self._info=None
            for name in dir(self):
                if name.startswith("DBT") and \
                   not name.startswith("DBT_DEVTYP") and \
                   getattr(self,name)==wparam:
                    self._info=(name, dict(self._decode_struct(lparam)))

        def GetEventInfo(self):
            return self._info
            
        def _decode_struct(self, lparam):
            if lparam==0: return ()
            format = "iii"
            buf = win32gui.PyMakeBuffer(struct.calcsize(format), lparam)
            dbch_size, dbch_devicetype, dbch_reserved = struct.unpack(format, buf)

            buf = win32gui.PyMakeBuffer(dbch_size, lparam) # we know true size now

            if dbch_devicetype==self.DBT_DEVTYP_PORT:
                name=""
                for b in buf[struct.calcsize(format):]:
                    if b!="\x00":
                        name+=b
                        continue
                    break
                return ("name", name),

            if dbch_devicetype==self.DBT_DEVTYP_VOLUME:
                # yes, the last item is a WORD, not a DWORD like the hungarian would lead you to think
                format="iiiih0i"
                dbcv_size, dbcv_devicetype, dbcv_reserved, dbcv_unitmask, dbcv_flags = struct.unpack(format, buf)
                units=[chr(ord('A')+x) for x in range(26) if dbcv_unitmask&(2**x)]
                flag=""
                for name in dir(self):
                    if name.startswith("DBTF_") and getattr(self, name)==dbcv_flags:
                        flag=name
                        break

                return ("drives", units), ("flag", flag)

            print "unhandled devicetype struct", dbch_devicetype
            return ()
gui.py.diff (application/octet-stream, 5.1 KB)
Index: gui.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/gui.py,v
retrieving revision 1.215
diff -u -r1.215 gui.py
--- gui.py	16 Sep 2005 02:28:07 -0000	1.215
+++ gui.py	16 Sep 2005 08:49:14 -0000
@@ -57,6 +57,7 @@
 import today
 import pubsub
 import com_brew
+import auto_sync
 
 if guihelper.IsMSWindows():
     import win32api
@@ -598,6 +599,11 @@
         menuBar.Append(menu, "&Data")
 
         menu=wx.Menu()
+        menu.Append(guihelper.ID_AUTOSYNCSETTINGS, "&Configure AutoSync Settings...", "Configures Schedule Auto-Synchronisation")
+        menu.Append(guihelper.ID_AUTOSYNCEXECUTE, "&Synchronize Schedule Now", "Synchronize Schedule Now")
+        menuBar.Append(menu, "&AutoSync")
+
+        menu=wx.Menu()
         menu.Append(guihelper.ID_VIEWCOLUMNS, "Columns ...", "Which columns to show")
         menu.AppendCheckItem(guihelper.ID_VIEWPREVIEW, "Phonebook Preview", "Toggle Phonebook Preview Pane")
         menu.AppendSeparator()
@@ -676,6 +682,8 @@
         wx.EVT_MENU(self, guihelper.ID_HELP_UPDATE, self.OnCheckUpdate)
         wx.EVT_MENU(self, guihelper.ID_EDITPHONEINFO, self.OnPhoneInfo)
         wx.EVT_MENU(self, guihelper.ID_EDITDETECT, self.OnDetectPhone)
+        wx.EVT_MENU(self, guihelper.ID_AUTOSYNCSETTINGS, self.OnAutoSyncSettings)
+        wx.EVT_MENU(self, guihelper.ID_AUTOSYNCEXECUTE, self.OnAutoSyncExecute)
         wx.EVT_CLOSE(self, self.OnClose)
 
         ### Double check our size is meaningful, and make bigger
@@ -693,6 +701,12 @@
                 return
         self.configdlg.updatevariables()
         
+        ### Set autosync settings dialog
+        self.calenders=importexport.GetCalenderAutoSyncImports()
+        print "cal " +`self.calenders`
+        self.autosyncsetting=auto_sync.AutoSyncSettingsDialog(self, self)
+        self.autosyncsetting.updatevariables()
+
         ### notebook
         self.nb=wx.Notebook(self,-1, style=wx.NO_FULL_REPAINT_ON_RESIZE|wx.CLIP_CHILDREN)
 
@@ -910,11 +924,11 @@
                          "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
             return
         self.__detect_phone()
-    def __detect_phone(self, using_port=None):
+    def __detect_phone(self, using_port=None, check_auto_sync=0):
         self.OnBusyStart()
         self.GetStatusBar().progressminor(0, 100, 'Phone detection in progress ...')
         self.MakeCall(Request(self.wt.detectphone, using_port),
-                      Callback(self.OnDetectPhoneReturn))
+                      Callback(self.OnDetectPhoneReturn, check_auto_sync))
     def __get_owner_name(self, esn):
         """ retrieve or ask user for the owner's name of this phone
         """
@@ -941,7 +955,7 @@
             return s
         return phone_name
         
-    def OnDetectPhoneReturn(self, exception, r):
+    def OnDetectPhoneReturn(self, check_auto_sync, exception, r):
         self.OnBusyEnd()
         if self.HandleException(exception): return
         if r is None:
@@ -966,6 +980,9 @@
                                                r['phone_name'],
                                                r['port']),
                           'Phone Detection', wx.OK)
+            if check_auto_sync:
+                # see if we should re-sync the calender on connect, do it silently
+                self.__autosync_phone(silent=1)
         
     def WindowsOnDeviceChanged(self, type, name="", drives=[], flag=None):
         if not name.lower().startswith("com"):
@@ -985,7 +1002,8 @@
             # current phone operation ongoing, abort this
             return
         # check the new device
-        self.__detect_phone(name)
+        check_auto_sync=auto_sync.UpdateOnConnect(self)
+        self.__detect_phone(name, check_auto_sync)
 
     def MyWndProc(self, hwnd, msg, wparam, lparam):
 
@@ -1317,6 +1335,27 @@
             self.__owner_name=''
             self.configdlg.ShowModal()
 
+    # deal with configuring the phone (commport)
+    def OnAutoSyncSettings(self, _=None):
+        if wx.IsBusy():
+            wx.MessageBox("BitPim is busy.  You can't change settings until it has finished talking to your phone.",
+                         "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
+        else:
+            # clear the ower's name for manual setting
+            self.__owner_name=''
+            self.autosyncsetting.ShowModal()
+
+    def OnAutoSyncExecute(self, _=None):
+        if wx.IsBusy():
+            wx.MessageBox("BitPim is busy.  You can't run autosync until it has finished talking to your phone.",
+                         "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
+            return
+        self.__autosync_phone()
+
+    def __autosync_phone(self, silent=0):
+        r=auto_sync.SyncSchedule(self).sync(self, silent)
+        
+
     def OnReqChangeTab(self, msg=None):
         if msg is None:
             return
@@ -1735,6 +1774,7 @@
         self.clearcomm()
         return phone_detect.DetectPhone(self).detect(using_port)
 
+
     # various file operations for the benefit of the filesystem viewer
     def dirlisting(self, path, recurse=0):
         if __debug__: self.checkthread()
guihelper.py (text/plain, 7.6 KB)
### BITPIM
###
### Copyright (C) 2003-2004 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: guihelper.py,v 1.41 2005/07/12 04:38:13 djpham Exp $

"Various helper routines for gui based code"

# These routines were initially in gui.py but that led to circular imports
# which confused the heck out of pychecker

# standard modules
import os
import glob
import sys

# wx modules
import wx

# my modules
import common  # note we modify the common module contents

###
### The various IDs we use.  Code below munges the integers into sequence
###

# Main menu items

ID_FILENEW=1
ID_FILEOPEN=1
ID_FILESAVE=1
ID_FILEIMPORT=1
ID_FILEEXPORT=1
ID_FILEPRINT=1
ID_FILEPRINTPREVIEW=1
ID_FILEEXIT=1
ID_EDITADDENTRY=1
ID_EDITDELETEENTRY=1
ID_EDITSELECTALL=1
ID_EDITSETTINGS=1
ID_EDITPHONEINFO=1
ID_EDITDETECT=1
ID_EDITCOPY=1
ID_EDITPASTE=1
ID_EDITRENAME=1
ID_DATAGETPHONE=1
ID_DATASENDPHONE=1
ID_DATAHISTORICAL=1
ID_AUTOSYNCSETTINGS=1
ID_AUTOSYNCEXECUTE=1
ID_VIEWCOLUMNS=1
ID_VIEWPREVIEW=1
ID_VIEWLOGDATA=1
ID_VIEWCLEARLOGS=1
ID_VIEWFILESYSTEM=1
ID_HELPHELP=1
ID_HELPCONTENTS=1
ID_HELPTOUR=1
ID_HELPSUPPORT=1
ID_HELP_UPDATE=1
ID_HELPABOUT=1

# file/filesystem viewer context menus
ID_FV_SAVE=1
ID_FV_HEXVIEW=1
ID_FV_OVERWRITE=1
ID_FV_NEWSUBDIR=1
ID_FV_NEWFILE=1
ID_FV_DELETE=1
ID_FV_OPEN=1
ID_FV_RENAME=1
ID_FV_REFRESH=1
ID_FV_PROPERTIES=1
ID_FV_ADD=1
ID_FV_BACKUP=1
ID_FV_BACKUP_TREE=1
ID_FV_RESTORE=1
ID_FV_PASTE=1
ID_FV_OFFLINEPHONE=1
ID_FV_REBOOTPHONE=1
ID_FV_MODEMMODE=1
ID_FV_COPY=1

# keep map around
idmap={}
# Start at 2 (if anything ends up being one then this code didn't spot it
for idmapname in locals().keys():
    if idmapname.startswith('ID_'):
        idnum=wx.NewId()
        # locals()[idmapname]=idnum
        exec "%s = %d" % (idmapname, idnum )
        idmap[idnum]=idmapname

###
### Various functions not attached to classes
###


# These are the mime-types as used internally in wxWidgets
_wxmimemapping={
    'bmp': 'image/x-bmp',
    'ico': 'image/x-ico',
    'cur': 'image/x-cur',
    'ani': 'image/x-ani',
    'gif': 'image/gif',
    'iff': 'image/iff',
    'jpg': 'image/jpeg',
    'jpeg': 'image/jpeg',
    'pcx': 'image/pcx',
    'png': 'image/png',
    'pnm': 'image/pnm',
    'xpm': 'image/xpm',
    }

def getwxmimetype(filename):
    "Returns wx's mime type for the extension of filename, or None"
    return _wxmimemapping.get(getextension(filename.lower()), None)

def BusyWrapper(method):
    def _busywrapper(*args, **kwargs):
        wx.BeginBusyCursor()
        try:
            return method(*args, **kwargs)
        finally:
            wx.EndBusyCursor()

    setattr(_busywrapper, "__doc__", getattr(method, "__doc__"))

    return _busywrapper

# Filename functions.  These work on brew names which use forward slash /
# as the directory delimiter.  The builtin Python functions can't be used
# as they are platform specific (eg they use \ on Windows)

def getextension(str):
    """Returns the extension of a filename (characters after last period)

    An empty string is returned if the file has no extension.  The period
    character is not returned"""
    str=basename(str)
    if str.rfind('.')>=0:
        return str[str.rfind('.')+1:]
    return ""

def basename(str):
    """Returns the last part of the name (everything after last /)"""
    if str.rfind('/')<0: return str
    return str[str.rfind('/')+1:]

def dirname(str):
    """Returns everything before the last / in the name""" 
    if str.rfind('/')<0: return ""
    return str[:str.rfind('/')]

def IsMSWindows():
    """Are we running on Windows?

    @rtype: Bool"""
    return wx.Platform=='__WXMSW__'

def IsGtk():
    """Are we running on GTK (Linux)

    @rtype: Bool"""
    return wx.Platform=='__WXGTK__'

def IsMac():
    """Are we running on Mac

    @rtype: Bool"""
    return wx.Platform=='__WXMAC__'
    

def getbitmap(name):
    """Gets a bitmap from the resource directory

    @rtype: wxBitmap
    """
    return getimage(name).ConvertToBitmap()

def getimage(name):
    """Gets an image from the resource directory

    @rtype: wx.Image
    """
    for ext in ("", ".png", ".jpg"):
        if os.path.exists(getresourcefile(name+ext)):
            return wx.Image(getresourcefile(name+ext))
    print "You need to make "+name+".png"
    return getimage('unknown')


def getresourcefile(filename):
    """Returns name of file by adding it to resource directory pathname

    No attempt is made to verify the file exists
    @rtype: string
    """
    return os.path.join(resourcedirectory, filename)

def gethelpfilename():
    """Returns what name we use for the helpfile

    Without trailing extension as wxBestHelpController figures that out"""

    # we look in a help subdirectory first which is
    # present in the developer tree
    j=os.path.join
    paths=( (j(resourcedirectory, "..", "help"), True),
            (resourcedirectory, False) )

    if IsMSWindows():
        name="bitpim.chm"
    else:
        name="bitpim.htb"

    for p,mention in paths:
        if os.path.isfile(j(p, name)):
            if mention:
                print "Using help file from "+p
            return j(p, "bitpim")

    assert False

def getresourcefiles(wildcard):
    "Returns a list of filenames matching the wildcard in the resource directory"
    l=glob.glob(os.path.join(resourcedirectory, wildcard))
    l.sort()
    return l

# Where to find bitmaps etc
if IsMac():
    p=os.getcwd()
else:
    p=sys.path[0]
if os.path.isfile(p): # zip importer in action
    p=os.path.dirname(p)
resourcedirectory=os.path.abspath(os.path.join(p, 'resources'))

# See strorunicode comment in common
if wx.USE_UNICODE:
    def strorunicode(s):
        if s is None: return s
        if isinstance(s, unicode): return s
        return str(s)

    common.strorunicode=strorunicode
    del strorunicode

else:
    def strorunicode(s):
        if s is None: return s
        try:
            return str(s)
        except UnicodeEncodeError:
            return s.encode("ascii", "replace")

    common.strorunicode=strorunicode
    del strorunicode

# mime-type stuff
def GetOpenCommand(mimetypes, filename):
    # go through list of mime-types until we can find a command for opening filename
    for mt in mimetypes:
        ft=wx.TheMimeTypesManager.GetFileTypeFromMimeType(mt)
        if ft is None:
            continue
        cmd=ft.GetOpenCommand(filename)
        if cmd is not None and len(cmd):
            return cmd
    return None
    
# Art provider stuff

# Our constants
_ourart={
    "ART_ADD_WALLPAPER": "add_picture",
    "ART_DEL_WALLPAPER": "delete_picture",
    "ART_ARROW_UP": "arrow_up",
    "ART_ARROW_DOWN": "arrow_down",
    "ART_ARROW_LEFT": "arrow_left",
    "ART_ARROW_RIGHT": "arrow_right",
    "ART_ADD_FIELD": "add_field",
    "ART_DEL_FIELD": "delete_field",
    "ART_ADD_CONTACT": "add_contact",
    "ART_DEL_CONTACT": "delete_contact",
    "ART_ADD_WALLPAPER": "add_picture",
    "ART_ADD_RINGER": "add_ringer",
    "ART_DEL_RINGER": "delete_ringer",
    "ART_ADD_MEMO": "add_memo",
    "ART_DEL_MEMO": "delete_memo",
    "ART_ADD_TODO": "add_todo",
    "ART_DEL_TODO": "delete_todo",
    "ART_ADD_SMS": "add_sms",
    "ART_DEL_SMS": "delete_sms"
    }

# populate namespace
for s in _ourart: globals()[s]=s
    
class ArtProvider(wx.ArtProvider):
    """ArtProvider manages the art for the application"""

    def CreateBitmap(self, artid, client, size):
        """Loads a bitmap and returns it depending on the parameters
        """
        if artid in _ourart:
            return getbitmap(_ourart[artid])
        print "not returning a bitmap for",artid
        return wx.NullBitmap
guihelper.py.diff (application/octet-stream, 459 B)
Index: guihelper.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/guihelper.py,v
retrieving revision 1.41
diff -u -r1.41 guihelper.py
--- guihelper.py	12 Jul 2005 04:38:13 -0000	1.41
+++ guihelper.py	31 Aug 2005 18:02:09 -0000
@@ -49,6 +49,8 @@
 ID_DATAGETPHONE=1
 ID_DATASENDPHONE=1
 ID_DATAHISTORICAL=1
+ID_AUTOSYNCSETTINGS=1
+ID_AUTOSYNCEXECUTE=1
 ID_VIEWCOLUMNS=1
 ID_VIEWPREVIEW=1
 ID_VIEWLOGDATA=1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.