PyXR

c:\projects\bitpim\src \ gui.py



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2005 Roger Binns <rogerb@rogerbinns.com>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: gui.py 4625 2008-07-01 00:21:27Z djpham $
0009 
0010 """The main gui code for BitPim""" 
0011 
0012 # System modules
0013 from __future__ import with_statement
0014 import contextlib
0015 import thread, threading
0016 import Queue
0017 import time
0018 import os
0019 import cStringIO
0020 import zipfile
0021 import re
0022 import sys
0023 import shutil
0024 import types
0025 import datetime
0026 import sha
0027 import codecs
0028 
0029 # wx modules
0030 import wx
0031 import wx.lib.colourdb
0032 import wx.html
0033 
0034 # my modules
0035 import guiwidgets
0036 import common
0037 import version
0038 import helpids
0039 import comdiagnose
0040 import phonebook
0041 import importexport
0042 import guihelper
0043 import bphtml
0044 import bitflingscan
0045 import update
0046 import phoneinfo
0047 import phone_detect
0048 import phone_media_codec
0049 import pubsub
0050 import phones.com_brew as com_brew
0051 import auto_sync
0052 import phone_root
0053 import playlist
0054 import fileview
0055 import data_recording
0056 import analyser
0057 import t9editor
0058 import newdb_wiz
0059 import bp_config
0060 
0061 if guihelper.IsMSWindows():
0062     import win32api
0063     import win32con
0064     import win32gui
0065     import msvcrt
0066 else:
0067     import fcntl
0068 
0069 ###
0070 ### Used to check our threading
0071 ###
0072 mainthreadid=thread.get_ident()
0073 helperthreadid=-1 # set later
0074 
0075 ###
0076 ### Used to handle Task Bar Icon feature (Windows only)
0077 ###
0078 if guihelper.IsMSWindows():
0079     class TaskBarIcon(wx.TaskBarIcon):
0080         def __init__(self, mw):
0081             super(TaskBarIcon, self).__init__()
0082             self.mw=mw
0083             self._set_icon()
0084             wx.EVT_TASKBAR_LEFT_DCLICK(self, self.OnDclkRestore)
0085 
0086         def _create_menu(self):
0087             _menu=wx.Menu()
0088             _id=wx.NewId()
0089             if self.mw.IsIconized():
0090                 _menu.Append(_id, 'Restore')
0091                 wx.EVT_MENU(self, _id, self.OnRestore)
0092             else:
0093                 _menu.Append(_id, 'Minimize')
0094                 wx.EVT_MENU(self, _id, self.OnMinimize)
0095             _menu.AppendSeparator()
0096             _id=wx.NewId()
0097             _menu.Append(_id, 'Close')
0098             wx.EVT_MENU(self, _id, self.OnClose)
0099             return _menu
0100 
0101         def _set_icon(self):
0102             _icon=wx.Icon(guihelper.getresourcefile('bitpim.ico'),
0103                           wx.BITMAP_TYPE_ICO)
0104             if _icon.Ok():
0105                 self.SetIcon(_icon, 'BitPim')
0106 
0107         def CreatePopupMenu(self):
0108             return self._create_menu()
0109         def OnDclkRestore(self, _):
0110             self.mw.Iconize(False)
0111             wx.PostEvent(self.mw, wx.IconizeEvent(self.mw.GetId(), False))
0112         def OnRestore(self, _):
0113             self.mw.Iconize(False)
0114         def OnMinimize(self, _):
0115             self.mw.Iconize(True)
0116         def OnClose(self, _):
0117             self.RemoveIcon()
0118             self.mw.Close()
0119 
0120 ###
0121 ### Implements a nice flexible callback object
0122 ###
0123 
0124 class Callback:
0125     "Callback class.  Extra arguments can be supplied at call time"
0126     def __init__(self, method, *args, **kwargs):
0127         if __debug__:
0128             global mainthreadid
0129             assert mainthreadid==thread.get_ident()
0130         self.method=method
0131         self.args=args
0132         self.kwargs=kwargs
0133 
0134     def __call__(self, *args, **kwargs):
0135         if __debug__:
0136             global mainthreadid
0137             assert mainthreadid==thread.get_ident()
0138         d=self.kwargs.copy()
0139         d.update(kwargs)
0140         apply(self.method, self.args+args, d)
0141 
0142 class Request:
0143     def __init__(self, method, *args, **kwargs):
0144         # created in main thread
0145         if __debug__:
0146             global mainthreadid
0147             assert mainthreadid==thread.get_ident()
0148         self.method=method
0149         self.args=args
0150         self.kwargs=kwargs
0151 
0152     def __call__(self, *args, **kwargs):
0153         # called in helper thread
0154         if __debug__:
0155             global helperthreadid
0156             assert helperthreadid==thread.get_ident()
0157         d=self.kwargs.copy()
0158         d.update(kwargs)
0159         return apply(self.method, self.args+args, d)
0160         
0161 
0162 ###
0163 ### Event used for passing results back from helper thread
0164 ###
0165 
0166 class HelperReturnEvent(wx.PyEvent):
0167     def __init__(self, callback, *args, **kwargs):
0168         if __debug__:
0169             # verify being called in comm worker thread
0170             global helperthreadid
0171 ##            assert helperthreadid==thread.get_ident()
0172         global EVT_CALLBACK
0173         wx.PyEvent.__init__(self)
0174         self.SetEventType(EVT_CALLBACK)
0175         self.cb=callback
0176         self.args=args
0177         self.kwargs=kwargs
0178 
0179     def __call__(self):
0180         if __debug__:
0181             global mainthreadid
0182 ##            assert mainthreadid==thread.get_ident()
0183         return apply(self.cb, self.args, self.kwargs)
0184 
0185 ###
0186 ### Our helper thread where all the work gets done
0187 ###
0188 
0189 thesplashscreen=None  # set to non-none if there is one
0190 
0191 class MySplashScreen(wx.SplashScreen):
0192     def __init__(self, app, config):
0193         self.app=app
0194         # how long are we going to be up for?
0195         time=config.ReadInt("splashscreentime", 2500)
0196         if time>0:
0197             bmp=guihelper.getbitmap("splashscreen")
0198             self.drawnameandnumber(bmp)
0199             wx.SplashScreen.__init__(self, bmp, wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,
0200                                     time,
0201                                     None, -1)
0202             wx.EVT_CLOSE(self, self.OnClose)
0203             self.Show()
0204             app.Yield(True)
0205             global thesplashscreen
0206             thesplashscreen=self
0207             return
0208         # timeout is <=0 so don't show splash screen
0209         self.goforit()
0210 
0211     def drawnameandnumber(self, bmp):
0212         dc=wx.MemoryDC()
0213         dc.SelectObject(bmp)
0214         # where we start writing
0215         x=23 
0216         y=40
0217         # Product name
0218         if False:
0219             str=version.name
0220             dc.SetTextForeground( wx.NamedColour("MEDIUMORCHID4") ) 
0221             dc.SetFont( self._gimmethedamnsizeirequested(25, wx.ROMAN, wx.NORMAL, wx.NORMAL) )
0222             w,h=dc.GetTextExtent(str)
0223             dc.DrawText(str, x, y)
0224             y+=h+0
0225         # Version number
0226         x=58
0227         y=127
0228         str=version.versionstring+"-"+version.vendor
0229         dc.SetTextForeground( wx.NamedColour("MEDIUMBLUE") )
0230         dc.SetFont( self._gimmethedamnsizeirequested(15, wx.ROMAN, wx.NORMAL, wx.NORMAL) )
0231         w,h=dc.GetTextExtent(str)
0232         dc.DrawText(str, x+10, y)
0233         y+=h+0
0234         # all done
0235         dc.SelectObject(wx.NullBitmap)
0236 
0237     def _gimmethedamnsizeirequested(self, ps, family, style, weight):
0238         # on Linux we have to ask for bigger than we want
0239         if guihelper.IsGtk():
0240             ps=ps*1.6
0241         font=wx.TheFontList.FindOrCreateFont(int(ps), family, style, weight)
0242         return font
0243 
0244     def goforit(self):
0245         self.app.makemainwindow()
0246         
0247     def OnClose(self, evt):
0248         self.goforit()
0249         evt.Skip()
0250 
0251 class BitPimExit(Exception):
0252     pass
0253 
0254 class WorkerThreadFramework(threading.Thread):
0255     def __init__(self):
0256         threading.Thread.__init__(self, name="BitPim helper")
0257         self.q=Queue.Queue()
0258 
0259     def setdispatch(self, dispatchto):
0260         self.dispatchto=dispatchto
0261 
0262     def checkthread(self):
0263         # Function to verify we are running in the correct
0264         # thread.  All functions in derived class should call this
0265         global helperthreadid
0266         assert helperthreadid==thread.get_ident()
0267         
0268     def run(self):
0269         global helperthreadid
0270         helperthreadid=thread.get_ident()
0271         first=1
0272         while True:
0273             if not first:
0274                 wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.endbusycb))
0275             else:
0276                 first=0
0277             item=self.q.get()
0278             wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.startbusycb))
0279             call=item[0]
0280             resultcb=item[1]
0281             ex=None
0282             res=None
0283             try:
0284                 res=call()
0285             except Exception,e:
0286                 ex=e
0287                 if not hasattr(e,"gui_exc_info"):
0288                     ex.gui_exc_info=sys.exc_info()
0289                 
0290             wx.PostEvent(self.dispatchto, HelperReturnEvent(resultcb, ex, res))
0291             if isinstance(ex, BitPimExit):
0292                 # gracefully end this thread!
0293                 break
0294 
0295     def progressminor(self, pos, max, desc=""):
0296         wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.progressminorcb, pos, max, desc))
0297 
0298     def progressmajor(self, pos, max, desc=""):
0299         wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.progressmajorcb, pos, max, desc))
0300 
0301     def progress(self, pos, max, desc=""):
0302         self.progressminor(pos, max, desc)
0303 
0304     def log(self, str):
0305         if self.dispatchto.wantlog:
0306             wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.logcb, str))
0307 
0308     def logdata(self, str, data, klass=None, data_type=None):
0309         if self.dispatchto.wantlog:
0310             wx.PostEvent(self.dispatchto, HelperReturnEvent(self.dispatchto.logdatacb, str, data, klass,
0311                                                             data_type))
0312 
0313 ####
0314 #### Main application class.  Runs the event loop etc
0315 ####
0316 
0317 # safe mode items
0318 def _notsafefunc(*args, **kwargs):
0319     raise common.InSafeModeException()
0320 
0321 class _NotSafeObject:
0322     def __getattr__(self, *args):  _notsafefunc()
0323     def __setattr__(self, *args): _notsafefunc()
0324 
0325 _NotSafeObject=_NotSafeObject()
0326 
0327 class Event(object):
0328     """Simple Event class that supports Context Manager"""
0329     def __init__(self):
0330         self._event=threading.Event()
0331     def __enter__(self):
0332         self._event.set()
0333     def __exit__(self, exc_type, exc_value, tb):
0334         self._event.clear()
0335     def set(self):
0336         return self._event.set()
0337     def clear(self):
0338         return self._event.clear()
0339     def isSet(self):
0340         return self._event.isSet()
0341     def wait(self, timeout=None):
0342         return self._event.wait(timeout)
0343 
0344 EVT_CALLBACK=None
0345 class MainApp(wx.App):
0346     def __init__(self, argv, config_filename=None):
0347         self.frame=None
0348         self.SAFEMODE=False
0349         codecs.register(phone_media_codec.search_func)
0350         self._config_filename=config_filename
0351         # simple Event object to flag when entering/leaving critical section
0352         self.critical=Event()
0353         wx.App.__init__(self, redirect=False,
0354                         useBestVisual=not guihelper.IsGtk())
0355 
0356     def lock_file(self, filename):
0357         # if the file can be locked, lock it and return True.
0358         # return False otherwise.
0359         try:
0360             self.lockedfile=file(filename, 'w')
0361         except IOError:
0362             # failed to create the file, just bail
0363             self.lockedfile=None
0364             return True
0365         try:
0366             if guihelper.IsMSWindows():
0367                 msvcrt.locking(self.lockedfile.fileno(),
0368                                msvcrt.LK_NBLCK, 1)
0369             else:
0370                 # Linux & Mac
0371                 fcntl.flock(self.lockedfile.fileno(),
0372                             fcntl.LOCK_EX|fcntl.LOCK_NB)
0373             return True
0374         except IOError:
0375             return False
0376 
0377     def usingsamedb(self):
0378         # using a simple file locking method
0379         return not self.lock_file(os.path.join(self.config._path, '.lock'))
0380 
0381     def OnInit(self):
0382         self.made=False
0383         # Routine maintenance
0384         wx.lib.colourdb.updateColourDB()
0385         
0386         # Thread stuff
0387         global mainthreadid
0388         mainthreadid=thread.get_ident()
0389 
0390         # for help to save prefs
0391         cfgstr='bitpim'
0392         self.SetAppName(cfgstr)
0393         self.SetVendorName(cfgstr)
0394 
0395         # Establish config stuff
0396         self.config=bp_config.Config(self._config_filename)
0397         # Check to see if we're the 2nd instance running on the same DB
0398         if self.usingsamedb():
0399             guihelper.MessageDialog(None, 'Another copy of BitPim is using the same data dir:\n%s'%self.config._path,
0400                                     'BitPim Error',
0401                                     style=wx.OK|wx.ICON_ERROR)
0402             return False
0403         # this is for wx native use, like the freaking help controller !
0404         self.wxconfig=wx.Config(cfgstr, style=wx.CONFIG_USE_LOCAL_FILE)
0405 
0406         # safe mode is read at startup and can't be changed
0407         self.SAFEMODE=self.config.ReadInt("SafeMode", False)
0408 
0409         # we used to initialise help here, but in wxPython the stupid help window
0410         # appeared on Windows just setting it up.  We now defer setting it up
0411         # until it is needed
0412         self.helpcontroller=None
0413 
0414         # html easy printing
0415         self.htmlprinter=bphtml.HtmlEasyPrinting(None, self.config, "printing")
0416 
0417         global EVT_CALLBACK
0418         EVT_CALLBACK=wx.NewEventType()
0419 
0420         # initialize the Brew file cache
0421         com_brew.file_cache=com_brew.FileCache(self.config.Read('path', ''))
0422 
0423         # get the splash screen up
0424         MySplashScreen(self, self.config)
0425 
0426         return True
0427 
0428     def ApplySafeMode(self):
0429         # make very sure we are in safe mode
0430         if not self.SAFEMODE:
0431             return
0432         if self.frame is None:
0433             return
0434         # ensure various objects/functions are changed to not-safe
0435         objects={self.frame:
0436                     ( "dlgsendphone", "OnDataSendPhone", "OnDataSendPhoneGotFundamentals", "OnDataSendPhoneResults"),
0437                  self.frame.tree.filesystemwidget:
0438                     ( "OnFileDelete", "OnFileOverwrite", "OnNewSubdir", "OnNewFile", "OnDirDelete", "OnRestore"),
0439                  self.frame.wt:
0440                     ( "senddata", "writewallpaper", "writeringtone", "writephonebook", "writecalendar", "rmfile",
0441                       "writefile", "mkdir", "rmdir", "rmdirs", "restorefiles" ),
0442                  self.frame.phoneprofile:
0443                     ( "convertphonebooktophone", ),
0444                  self.frame.phonemodule.Phone:
0445                     ( "mkdir", "mkdirs", "rmdir", "rmfile", "rmdirs", "writefile", "savegroups", "savephonebook",
0446                       "savecalendar", "savewallpapers", "saveringtones")
0447                  }
0448 
0449         for obj, names in objects.iteritems():
0450             if obj is None:
0451                 continue
0452             for name in names:
0453                 field=getattr(obj, name, None)
0454                 if field is None or field is _notsafefunc or field is _NotSafeObject:
0455                     continue
0456                 if isinstance(field, (types.MethodType, types.FunctionType)):
0457                     newval=_notsafefunc
0458                 else: newval=_NotSafeObject
0459                 setattr(obj, name, newval)
0460 
0461         # remove various menu items if we can find them
0462         removeids=(guihelper.ID_DATASENDPHONE, guihelper.ID_FV_OVERWRITE, guihelper.ID_FV_NEWSUBDIR,
0463                    guihelper.ID_FV_NEWFILE, guihelper.ID_FV_DELETE, guihelper.ID_FV_RENAME,
0464                    guihelper.ID_FV_RESTORE, guihelper.ID_FV_ADD)
0465         mb=self.frame.GetMenuBar()
0466         menus=[mb.GetMenu(i) for i in range(mb.GetMenuCount())]
0467         fsw=self.frame.tree.filesystemwidget
0468         if  fsw is not None:
0469             menus.extend( [fsw.list.filemenu, fsw.tree.dirmenu, fsw.list.genericmenu] )
0470         for menu in menus:
0471             for id in removeids:
0472                 item=menu.FindItemById(id)
0473                 if item is not None:
0474                     menu.RemoveItem(item)
0475         
0476             
0477         
0478 
0479 ##    def setuphelpiwant(self):
0480 ##        """This is how the setuphelp code is supposed to be, but stuff is missing from wx"""
0481 ##        self.helpcontroller=wx.BestHelpController()
0482 ##        self.helpcontroller.Initialize(gethelpfilename)
0483 
0484     def _setuphelp(self):
0485         """Does all the nonsense to get help working"""
0486         if guihelper.IsMSWindows():
0487             self.helpcontroller=True
0488             return
0489         elif guihelper.IsMac():
0490             # we use apple's help mechanism
0491             from Carbon import AH
0492             path=os.path.abspath(os.path.join(guihelper.resourcedirectory, "..", "..", ".."))
0493             # path won't exist if we aren't a bundle
0494             if  os.path.exists(path) and path.endswith(".app"):
0495                 res=AH.AHRegisterHelpBook(path)
0496                 self.helpcontroller=True
0497                 return
0498 
0499         # Standard WX style help
0500         # htmlhelp isn't correctly wrapper in wx package
0501         # Add the Zip filesystem
0502         wx.FileSystem_AddHandler(wx.ZipFSHandler())
0503         # Get the help working
0504         self.helpcontroller=wx.html.HtmlHelpController()
0505         self.helpcontroller.AddBook(guihelper.gethelpfilename()+".htb")
0506         self.helpcontroller.UseConfig(self.wxconfig, "help")
0507 
0508         # now context help
0509         # (currently borken)
0510         # self.helpprovider=wx.HelpControllerHelpProvider(self.helpcontroller)
0511         # wx.HelpProvider_Set(provider)
0512 
0513     def displayhelpid(self, id):
0514         """Display a specific Help Topic"""
0515         if self.helpcontroller is None:
0516             self._setuphelp()
0517 
0518         if guihelper.IsMSWindows():
0519             import win32help
0520             fname=guihelper.gethelpfilename()+".chm>Help"
0521             if id is None:
0522                 id=helpids.ID_WELCOME
0523             # display the topic
0524             _hwnd=win32gui.GetDesktopWindow()
0525             win32help.HtmlHelp(_hwnd, fname, win32help.HH_DISPLAY_TOPIC, id)
0526             # and sync the TOC
0527             win32help.HtmlHelp(_hwnd, fname, win32help.HH_SYNC, id)
0528 
0529         elif guihelper.IsMac() and self.helpcontroller is True:
0530             from Carbon import AH
0531             res=AH.AHGotoPage('BitPim Help', id, None)
0532 
0533         else:
0534             if id is None:
0535                 self.helpcontroller.DisplayContents()
0536             else:
0537                 self.helpcontroller.Display(id)
0538 
0539     def makemainwindow(self):
0540         if self.made:
0541             return # already been called
0542         self.made=True
0543         # make the main frame
0544         title='BitPim'
0545         name=self.config.Read('name', None)
0546         if name:
0547             title+=' - '+name
0548         self.frame=MainWindow(None, -1, title, self.config)
0549         self.frame.Connect(-1, -1, EVT_CALLBACK, self.frame.OnCallback)
0550         if guihelper.IsMac():
0551             self.frame.MacSetMetalAppearance(True)
0552 
0553         # make the worker thread
0554         wt=WorkerThread()
0555         wt.setdispatch(self.frame)
0556         wt.setDaemon(1)
0557         wt.start()
0558         self.frame.wt=wt
0559         self.SetTopWindow(self.frame)
0560         self.SetExitOnFrameDelete(True)
0561         self.ApplySafeMode()
0562         wx.CallAfter(self.CheckDetectPhone)
0563         wx.CallAfter(self.CheckUpdate)
0564         # double-check the locked file
0565         if self.lockedfile is None:
0566             self.usingsamedb()
0567 
0568     update_delta={ 'Daily': 1, 'Weekly': 7, 'Monthly': 30 }
0569     def CheckUpdate(self):
0570         if version.isdevelopmentversion():
0571             return
0572         if self.frame is None: 
0573             return
0574         # tell the frame to do a check-for-update
0575         update_rate=self.config.Read('updaterate', '')
0576         if not len(update_rate) or update_rate =='Never':
0577             return
0578         last_update=self.config.Read('last_update', '')
0579         try:
0580             if len(last_update):
0581                 last_date=datetime.date(int(last_update[:4]), int(last_update[4:6]),
0582                                         int(last_update[6:]))
0583                 next_date=last_date+datetime.timedelta(\
0584                     self.update_delta.get(update_rate, 7))
0585             else:
0586                 next_date=last_date=datetime.date.today()
0587         except ValueError:
0588             # month day swap problem
0589             next_date=last_date=datetime.date.today()
0590         if datetime.date.today()<next_date:
0591             return
0592         self.frame.AddPendingEvent(\
0593             wx.PyCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED,
0594                               guihelper.ID_HELP_UPDATE))
0595 
0596     def CheckDetectPhone(self):
0597         if self.config.ReadInt('autodetectstart', 0) or self.frame.needconfig:
0598             self.frame.AddPendingEvent(
0599                 wx.PyCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED,
0600                                   guihelper.ID_EDITDETECT))
0601 
0602     def OnExit(self): 
0603         self.config.Flush()
0604         # we get stupid messages about daemon threads, and Python's library
0605         # doesn't provide any way to interrupt them, nor to suppress these
0606         # messages.  ::TODO:: maybe remove the onexit handler installed by
0607         # treading._MainThread
0608         sys.excepthook=donothingexceptionhandler
0609 
0610     def ExitMainLoop(self):
0611         if guihelper.IsGtk():
0612             # This hangs for GTK, so what the heck!
0613             self.OnExit()
0614             sys.exit(0)
0615         super(MainApp, self).ExitMainLoop()
0616 
0617 # do nothing exception handler
0618 def donothingexceptionhandler(*args):
0619     pass
0620 
0621 # Entry point
0622 def run(argv, kwargs):
0623     return MainApp(argv, **kwargs).MainLoop()
0624 
0625 ###
0626 ### Main Window (frame) class
0627 ###
0628 
0629 class MenuCallback:
0630     "A wrapper to help with callbacks that ignores arguments when invoked"
0631     def __init__(self, func, *args, **kwargs):
0632         self.func=func
0633         self.args=args
0634         self.kwargs=kwargs
0635         
0636     def __call__(self, *args):
0637         return self.func(*self.args, **self.kwargs)
0638 
0639 class MainWindow(wx.Frame):
0640     def __init__(self, parent, id, title, config):
0641         wx.Frame.__init__(self, parent, id, title,
0642                          style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
0643         wx.GetApp().frame=self
0644 
0645         wx.GetApp().htmlprinter.SetParentFrame(self)
0646 
0647         sys.excepthook=Callback(self.excepthook)
0648         ### plumbing, callbacks        
0649         self.wt=None # worker thread
0650         self.progressminorcb=Callback(self.OnProgressMinor)
0651         self.progressmajorcb=Callback(self.OnProgressMajor)
0652         self.logcb=Callback(self.OnLog)
0653         self.logdatacb=Callback(self.OnLogData)
0654         self.startbusycb=Callback(self.OnBusyStart)
0655         self.endbusycb=Callback(self.OnBusyEnd)
0656         self.queue=Queue.Queue()
0657 
0658         ### random variables
0659         self.exceptiondialog=None
0660         self.wantlog=1  # do we want to receive log information
0661         self.config=config
0662         self.progmajortext=""
0663         self.__owner_name=''
0664 
0665         self._taskbar=None
0666         self._taskbar_on_closed=False
0667         self._close_button=False
0668         self.__phone_detect_at_startup=False
0669         self._autodetect_delay=0
0670         self._dr_rec=None   # Data Recording
0671         self._dr_play=None  # Data Play back
0672 
0673         ### Status bar
0674 
0675         sb=guiwidgets.MyStatusBar(self)
0676         self.SetStatusBar(sb)
0677         self.SetStatusBarPane(sb.GetHelpPane())
0678 
0679         ### Art
0680         # establish the custom art provider for custom icons
0681         # this is a global setting, so no need to call it for each toolbar
0682         wx.ArtProvider.PushProvider(guihelper.ArtProvider())
0683 
0684         # frame icon
0685         ib=wx.IconBundle()
0686         ib.AddIconFromFile(guihelper.getresourcefile("bitpim.ico"), wx.BITMAP_TYPE_ANY)
0687         self.SetIcons(ib)
0688 
0689         ### Menubar
0690 
0691         menuBar = wx.MenuBar()
0692         menu = wx.Menu()
0693         # menu.Append(guihelper.ID_FILENEW,  "&New", "Start from new")
0694         # menu.Append(guihelper.ID_FILEOPEN, "&Open", "Open a file")
0695         # menu.Append(guihelper.ID_FILESAVE, "&Save", "Save your work")
0696         menu.Append(guihelper.ID_FILEPRINT, "&Print...", "Print phonebook")
0697         # menu.AppendSeparator()
0698         
0699         # imports
0700         impmenu=wx.Menu()
0701         for x, desc, help, func in importexport.GetPhonebookImports():
0702             if isinstance(func, tuple):
0703                 # submenu
0704                 _submenu=wx.Menu()
0705                 for _id, _desc, _help, _func in func:
0706                     _submenu.Append(_id, _desc, _help)
0707                     if _func:
0708                         wx.EVT_MENU(self, _id, MenuCallback(_func, self))
0709                 impmenu.AppendMenu(x, desc, _submenu, help)
0710             else:
0711                 impmenu.Append(x, desc, help)
0712                 wx.EVT_MENU(self, x, MenuCallback(func, self) )
0713 
0714         menu.AppendMenu(guihelper.ID_FILEIMPORT, "&Import", impmenu)
0715 
0716         # exports
0717         expmenu=wx.Menu()
0718         for x, desc, help, func in importexport.GetPhonebookExports():
0719             expmenu.Append(x, desc, help)
0720             wx.EVT_MENU(self, x, MenuCallback(func, self) )
0721 
0722         menu.AppendMenu(guihelper.ID_FILEEXPORT, "&Export", expmenu)
0723 
0724         if not guihelper.IsMac():
0725             menu.AppendSeparator()
0726             menu.Append(guihelper.ID_FILEEXIT, "E&xit", "Close down this program")
0727         menuBar.Append(menu, "&File");
0728         self.__menu_edit=menu=wx.Menu()
0729         menu.Append(guihelper.ID_EDITSELECTALL, "&Select All\tCtrl+A", "Select All")
0730         menu.AppendSeparator()
0731         menu.Append(guihelper.ID_EDITADDENTRY, "&New...\tCtrl+N", "Add an item")
0732         menu.Append(guihelper.ID_EDITCOPY, "&Copy\tCtrl+C", "Copy to the clipboard")
0733         menu.Append(guihelper.ID_EDITPASTE,"&Paste\tCtrl+V", "Paste from the clipboard")
0734         menu.Append(guihelper.ID_EDITDELETEENTRY, "&Delete", "Delete currently selected entry")
0735         menu.Append(guihelper.ID_EDITRENAME, "&Rename\tF2", "Rename currently selected entry")
0736         menu.AppendSeparator()
0737         menu.Append(guihelper.ID_EDITDETECT,
0738                     "D&etect Phone", "Auto Detect Phone")
0739         if guihelper.IsMac():
0740             wx.App_SetMacPreferencesMenuItemId(guihelper.ID_EDITSETTINGS)
0741             menu.Append(guihelper.ID_EDITSETTINGS, "Pre&ferences...", "Edit Settings")
0742         else:
0743             menu.AppendSeparator()
0744             menu.Append(guihelper.ID_EDITSETTINGS, "&Settings", "Edit settings")
0745         menuBar.Append(menu, "&Edit");
0746 
0747         menu=wx.Menu()
0748         menu.Append(guihelper.ID_DATAGETPHONE, "Get Phone &Data ...", "Loads data from the phone")
0749         menu.Append(guihelper.ID_DATASENDPHONE, "&Send Phone Data ...", "Sends data to the phone")
0750         menu.Append(guihelper.ID_DATAHISTORICAL, "&Historical Data ...", "View Current & Historical Data")
0751         menu.AppendSeparator()
0752         menu.Append(guihelper.ID_DATANEWDB, 'Create New Storage ...',
0753                     'Create a New BitPim Storage Area')
0754         menuBar.Append(menu, "&Data")
0755 
0756         menu=wx.Menu()
0757         menu.Append(guihelper.ID_VIEWCOLUMNS, "&Columns ...", "Which columns to show")
0758         menu.AppendCheckItem(guihelper.ID_VIEWPREVIEW, "&Phonebook Preview", "Toggle Phonebook Preview Pane")
0759         menu.AppendSeparator()
0760         menu.AppendCheckItem(guihelper.ID_VIEWLOGDATA, "&View protocol logging", "View protocol logging information")
0761         menu.Append(guihelper.ID_VIEWCLEARLOGS, "Clear &Logs", "Clears the contents of the log panes")
0762         menu.AppendSeparator()
0763         menu.AppendCheckItem(guihelper.ID_VIEWFILESYSTEM, "View &Filesystem", "View filesystem on the phone")
0764         menu.AppendSeparator()
0765         menu.Append(guihelper.ID_EDITPHONEINFO,
0766                     "Phone &Info", "Display Phone Information")
0767         menuBar.Append(menu, "&View")
0768         # Debug menu
0769         menu=wx.Menu()
0770         menu.Append(guihelper.ID_DR_SETTINGS, '&Data Recording',
0771                     'Data Recording Settings')
0772 ##        menu.Append(guihelper.ID_DEBUG_SCRIPT, '&Script',
0773 ##                    'Run Debug Script')
0774         menuBar.Append(menu, "De&bug")
0775         # Help menu
0776         menu=wx.Menu()
0777         if guihelper.IsMac():
0778             menu.Append(guihelper.ID_HELPHELP, "&Help on this panel", "Help for the panel you are looking at")
0779         else:
0780             menu.Append(guihelper.ID_HELPHELP, "&Help", "Help for the panel you are looking at")
0781         menu.Append(guihelper.ID_HELPTOUR, "&Tour", "Tour of BitPim")
0782         menu.Append(guihelper.ID_HELPCONTENTS, "&Contents", "Table of contents for the online help")
0783         menu.Append(guihelper.ID_HELPHOWTOS, "H&owTos", "Help on how to do certain function")
0784         menu.Append(guihelper.ID_HELPFAQ, "&FAQ", "Frequently Asked Questions")
0785         menu.Append(guihelper.ID_HELPSUPPORT, "&Support", "Getting support for BitPim")
0786         menu.Append(guihelper.ID_HELPPHONE, "Your &Phone", "Help on specific phonemodel")
0787         if version.vendor=='official':
0788             menu.AppendSeparator()
0789             menu.Append(guihelper.ID_HELP_UPDATE, "&Check for Update", "Check for any BitPim Update")
0790         if guihelper.IsMac():
0791             wx.App_SetMacAboutMenuItemId(guihelper.ID_HELPABOUT)
0792             menu.Append(guihelper.ID_HELPABOUT, "&About BitPim", "Display program information")
0793             wx.App_SetMacHelpMenuTitleName("&Help")
0794             wx.App_SetMacExitMenuItemId(guihelper.ID_FILEEXIT)
0795         else:
0796             menu.AppendSeparator()
0797             menu.Append(guihelper.ID_HELPABOUT, "&About", "Display program information")
0798         menuBar.Append(menu, "&Help");
0799         self.SetMenuBar(menuBar)
0800 
0801         ### toolbar
0802         self.tb=self.CreateToolBar(wx.TB_HORIZONTAL)
0803         self.tb.SetToolBitmapSize(wx.Size(32,32))
0804         sz=self.tb.GetToolBitmapSize()
0805 
0806         # add and delete tools
0807         self.tb.AddSimpleTool(guihelper.ID_DATAGETPHONE, wx.ArtProvider.GetBitmap(guihelper.ART_DATAGETPHONE, wx.ART_TOOLBAR, sz),
0808                                                 "Get Phone Data", "Synchronize BitPim with Phone")
0809         self.tb.AddLabelTool(guihelper.ID_DATASENDPHONE, "Send Phone Data", wx.ArtProvider.GetBitmap(guihelper.ART_DATASENDPHONE, wx.ART_TOOLBAR, sz),
0810                                           shortHelp="Send Phone Data", longHelp="Synchronize Phone with BitPim")
0811         self.tb.AddLabelTool(guihelper.ID_DATAHISTORICAL, "BitPim Help", wx.ArtProvider.GetBitmap(guihelper.ART_DATAHISTORICAL, wx.ART_TOOLBAR, sz),
0812                                              shortHelp="Historical Data", longHelp="Show Historical Data")
0813         self.tb.AddSeparator()
0814         self.tb.AddLabelTool(guihelper.ID_EDITADDENTRY, "Add", wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_TOOLBAR, sz),
0815                                           shortHelp="Add", longHelp="Add an item")
0816         self.tb.AddLabelTool(guihelper.ID_EDITDELETEENTRY, "Delete", wx.ArtProvider.GetBitmap(wx.ART_DEL_BOOKMARK, wx.ART_TOOLBAR, sz),
0817                                              shortHelp="Delete", longHelp="Delete item")
0818         self.tb.AddLabelTool(guihelper.ID_EDITPHONEINFO, "Phone Info", wx.ArtProvider.GetBitmap(guihelper.ART_EDITPHONEINFO, wx.ART_TOOLBAR, sz),
0819                                           shortHelp="Phone Info", longHelp="Show Phone Info")
0820         self.tb.AddLabelTool(guihelper.ID_EDITDETECT, "Find Phone", wx.ArtProvider.GetBitmap(guihelper.ART_EDITDETECT, wx.ART_TOOLBAR, sz),
0821                                           shortHelp="Find Phone", longHelp="Find Phone")
0822         self.tb.AddLabelTool(guihelper.ID_EDITSETTINGS, "Edit Settings", wx.ArtProvider.GetBitmap(guihelper.ART_EDITSETTINGS, wx.ART_TOOLBAR, sz),
0823                                           shortHelp="Edit Settings", longHelp="Edit BitPim Settings")
0824         self.tb.AddSeparator()
0825         self.tb.AddSimpleTool(guihelper.ID_AUTOSYNCEXECUTE, wx.ArtProvider.GetBitmap(guihelper.ART_AUTOSYNCEXECUTE, wx.ART_TOOLBAR, sz),
0826                                             "Autosync Calendar", "Synchronize Phone Calendar with PC")
0827         self.tb.AddSeparator()
0828         self.tb.AddLabelTool(guihelper.ID_HELPHELP, "BitPim Help", wx.ArtProvider.GetBitmap(guihelper.ART_HELPHELP, wx.ART_TOOLBAR, sz),
0829                                              shortHelp="BitPim Help", longHelp="BitPim Help")
0830 
0831 
0832         # You have to make this call for the toolbar to draw itself properly
0833         self.tb.Realize()
0834 
0835         ### persistent dialogs
0836         self.dlggetphone=guiwidgets.GetPhoneDialog(self, "Get Data from Phone")
0837         self.dlgsendphone=guiwidgets.SendPhoneDialog(self, "Send Data to Phone")
0838 
0839         # the splitter
0840         self.sw=wx.SplitterWindow(self, wx.NewId(), style=wx.SP_3D|wx.SP_NO_XP_THEME|wx.SP_LIVE_UPDATE)
0841 
0842         ### create main tree view
0843         self.tree = phone_root.PhoneTree(self.sw, self, wx.NewId())
0844 
0845         ### Events we handle
0846         wx.EVT_MENU(self, guihelper.ID_FILEPRINT, self.tree.OnFilePrint)
0847         wx.EVT_MENU(self, guihelper.ID_FILEEXIT, self.OnExit)
0848         wx.EVT_MENU(self, guihelper.ID_EDITSETTINGS, self.OnEditSettings)
0849         wx.EVT_MENU(self, guihelper.ID_DATAGETPHONE, self.OnDataGetPhone)
0850         wx.EVT_MENU(self, guihelper.ID_DATASENDPHONE, self.OnDataSendPhone)
0851         wx.EVT_MENU(self, guihelper.ID_DATAHISTORICAL, self.tree.OnDataHistorical)
0852         wx.EVT_MENU(self, guihelper.ID_DATANEWDB, self.OnNewDB)
0853         wx.EVT_MENU(self, guihelper.ID_VIEWCOLUMNS, self.tree.OnViewColumns)
0854         wx.EVT_MENU(self, guihelper.ID_VIEWPREVIEW, self.tree.OnViewPreview)
0855         wx.EVT_MENU(self, guihelper.ID_VIEWCLEARLOGS, self.tree.OnViewClearLogs)
0856         wx.EVT_MENU(self, guihelper.ID_VIEWLOGDATA, self.tree.OnViewLogData)
0857         wx.EVT_MENU(self, guihelper.ID_VIEWFILESYSTEM, self.tree.OnViewFilesystem)
0858         wx.EVT_MENU(self, guihelper.ID_EDITADDENTRY, self.tree.OnEditAddEntry)
0859         wx.EVT_MENU(self, guihelper.ID_EDITDELETEENTRY, self.tree.OnEditDeleteEntry)
0860         wx.EVT_MENU(self, guihelper.ID_EDITSELECTALL, self.tree.OnEditSelectAll)
0861         wx.EVT_MENU(self, guihelper.ID_EDITCOPY, self.tree.OnCopyEntry)
0862         wx.EVT_MENU(self, guihelper.ID_EDITPASTE, self.tree.OnPasteEntry)
0863         wx.EVT_MENU(self, guihelper.ID_EDITRENAME, self.tree.OnRenameEntry)
0864         wx.EVT_MENU(self, guihelper.ID_HELPABOUT, self.OnHelpAbout)
0865         wx.EVT_MENU(self, guihelper.ID_HELPHELP, self.OnHelpHelp)
0866         wx.EVT_MENU(self, guihelper.ID_HELPCONTENTS, self.OnHelpContents)
0867         wx.EVT_MENU(self, guihelper.ID_HELPHOWTOS, self.OnHelpHowtos)
0868         wx.EVT_MENU(self, guihelper.ID_HELPFAQ, self.OnHelpFAQ)
0869         wx.EVT_MENU(self, guihelper.ID_HELPSUPPORT, self.OnHelpSupport)
0870         wx.EVT_MENU(self, guihelper.ID_HELPTOUR, self.OnHelpTour)
0871         wx.EVT_MENU(self, guihelper.ID_HELP_UPDATE, self.OnCheckUpdate)
0872         wx.EVT_MENU(self, guihelper.ID_HELPPHONE, self.OnHelpPhone)
0873         wx.EVT_MENU(self, guihelper.ID_EDITPHONEINFO, self.OnPhoneInfo)
0874         wx.EVT_MENU(self, guihelper.ID_EDITDETECT, self.OnDetectPhone)
0875         wx.EVT_MENU(self, guihelper.ID_AUTOSYNCSETTINGS, self.OnAutoSyncSettings)
0876         wx.EVT_MENU(self, guihelper.ID_AUTOSYNCEXECUTE, self.OnAutoSyncExecute)
0877         wx.EVT_MENU(self, guihelper.ID_DR_SETTINGS, self.OnDataRecording)
0878         wx.EVT_CLOSE(self, self.OnClose)
0879 
0880         ### Double check our size is meaningful, and make bigger
0881         ### if necessary (especially needed on Mac and Linux)
0882         if min(self.GetSize())<250:
0883             self.SetSize( (640, 480) )
0884 
0885         ### Is config set?
0886         self.configdlg=guiwidgets.ConfigDialog(self, self)
0887         self.needconfig=self.configdlg.needconfig()
0888         self.configdlg.updatevariables()
0889         
0890         pos=self.config.ReadInt("mainwindowsplitterpos", 200)
0891         self.tree.active_panel.OnPreActivate()
0892         self.sw.SplitVertically(self.tree, self.tree.active_panel, pos)
0893         self.tree.active_panel.OnPostActivate()
0894         self.sw.SetMinimumPaneSize(50)
0895         wx.EVT_SPLITTER_SASH_POS_CHANGED(self, id, self.OnSplitterPosChanged)
0896         self.tree.Expand(self.tree.root)
0897 
0898         # multiple phones can be added here, although we have to figure out which phone
0899         # to use in send/get phone data.
0900         self.tree.CreatePhone("Phone", self.config, self.configpath, "bitpim.db")
0901         #self.tree.CreatePhone("Different database", self.config, "C:/Documents and Settings/Simon/My Documents/bitpim_old")
0902 
0903         ### Set autosync settings dialog
0904         self.calenders=importexport.GetCalenderAutoSyncImports()
0905         self.autosyncsetting=auto_sync.AutoSyncSettingsDialog(self, self)
0906         self.autosyncsetting.updatevariables()
0907         self.CloseSplashScreen()
0908 
0909         # add update handlers for controls that are not always available
0910         wx.EVT_UPDATE_UI(self, guihelper.ID_AUTOSYNCEXECUTE, self.AutosyncUpdateUIEvent)
0911         wx.EVT_UPDATE_UI(self, guihelper.ID_DATASENDPHONE, self.tree.DataSendPhoneUpdateUIEvent)
0912         wx.EVT_UPDATE_UI(self, guihelper.ID_EDITDELETEENTRY, self.tree.DataDeleteItemUpdateUIEvent)
0913         wx.EVT_UPDATE_UI(self, guihelper.ID_EDITADDENTRY, self.tree.DataAddItemUpdateUIEvent)
0914         wx.EVT_UPDATE_UI(self, guihelper.ID_DATAHISTORICAL, self.tree.HistoricalDataUpdateUIEvent)
0915         wx.EVT_UPDATE_UI(self, guihelper.ID_VIEWCOLUMNS, self.tree.ViewColumnsUpdateUIEvent)
0916         wx.EVT_UPDATE_UI(self, guihelper.ID_VIEWPREVIEW, self.tree.ViewPreviewDataUpdateUIEvent)
0917         wx.EVT_UPDATE_UI(self, guihelper.ID_FILEPRINT, self.tree.FilePrintDataUpdateUIEvent)
0918         wx.EVT_UPDATE_UI(self, guihelper.ID_EDITSELECTALL, self.tree.SelectAllDataUpdateUIEvent)
0919         wx.EVT_UPDATE_UI(self, guihelper.ID_EDITCOPY, self.tree.EditCopyUpdateUIEvent)
0920         wx.EVT_UPDATE_UI(self, guihelper.ID_EDITPASTE, self.tree.EditPasteUpdateUIEvent)
0921         wx.EVT_UPDATE_UI(self, guihelper.ID_EDITRENAME, self.tree.EditRenameUpdateUIEvent)
0922         wx.EVT_UPDATE_UI(self, guihelper.ID_VIEWLOGDATA, self.tree.ViewLogDataUIEvent)
0923         wx.EVT_UPDATE_UI(self, guihelper.ID_VIEWFILESYSTEM, self.tree.ViewFileSystemUIEvent)
0924         wx.EVT_UPDATE_UI(self, guihelper.ID_HELPPHONE, self.OnHelpPhoneUpdateUI)
0925 
0926         # Retrieve saved settings... Use 90% of screen if not specified
0927         guiwidgets.set_size("MainWin", self, screenpct=90)
0928 
0929         ### Lets go visible
0930         self.Show()
0931 
0932         # Show tour on first use
0933         if self.config.ReadInt("firstrun", True):
0934             self.config.WriteInt("firstrun", False)
0935             self.config.Flush()
0936             wx.CallAfter(self.OnHelpTour)
0937 
0938         # check for device changes
0939         if guihelper.IsMSWindows():
0940             if self.config.ReadInt('taskbaricon', 0):
0941                 self._taskbar=TaskBarIcon(self)
0942                 self._taskbar_on_closed=self.config.ReadInt('taskbaricon1', 0)
0943             # save the old window proc
0944             self.oldwndproc = win32gui.SetWindowLong(self.GetHandle(),
0945                                                      win32con.GWL_WNDPROC,
0946                                                      self.MyWndProc)
0947         if self._taskbar and self._taskbar.IsOk():
0948             wx.EVT_ICONIZE(self, self.OnIconize)
0949 
0950         # response to pubsub request
0951         pubsub.subscribe(self.OnReqChangeTab, pubsub.REQUEST_TAB_CHANGED)
0952         # setup the midnight timer
0953         self._setup_midnight_timer()
0954 
0955         if self.IsIconized() and self._taskbar:
0956             # Ugly hack to force the icon onto the system tray when
0957             # the app is started minimized !!
0958             wx.CallAfter(self.Show, False)
0959         self.GetStatusBar().set_app_status_ready()
0960         # Linux USB port notification
0961         if guihelper.IsGtk():
0962             import comm_notify
0963             comm_notify.start_server(self)
0964 
0965     def OnSplitterPosChanged(self,_):
0966         pos=self.sw.GetSashPosition()
0967         self.config.WriteInt("mainwindowsplitterpos", pos)        
0968 
0969     def SetActivePanel(self, panel):
0970         w2=self.sw.GetWindow2()
0971         if w2 is None or w2 is panel:
0972             return
0973         panel.OnPreActivate()
0974         w2.Show(False)
0975         self.sw.ReplaceWindow(w2, panel)
0976         panel.Show(True)
0977         panel.SetFocus()
0978         panel.OnPostActivate()
0979 
0980     def GetActiveMemoWidget(self):
0981         return self.tree.GetActivePhone().memowidget
0982 
0983     def GetActiveMediaWidget(self):
0984         return self.tree.GetActivePhone().mediawidget
0985 
0986     def GetActiveRingerWidget(self):
0987         return self.tree.GetActivePhone().mediawidget.GetRinger()
0988 
0989     def GetActiveWallpaperWidget(self):
0990         return self.tree.GetActivePhone().mediawidget.GetWallpaper()
0991 
0992     def GetActiveTodoWidget(self):
0993         return self.tree.GetActivePhone().todowidget
0994 
0995     def GetActiveCalendarWidget(self):
0996         return self.tree.GetActivePhone().calendarwidget
0997 
0998     def GetActivePlaylistWidget(self):
0999         return self.tree.GetActivePhone().playlistwidget
1000 
1001     def GetActivePhonebookWidget(self):
1002         return self.tree.GetActivePhone().phonewidget
1003 
1004     def GetActiveCallHistoryWidget(self):
1005         return self.tree.GetActivePhone().callhistorywidget
1006 
1007     def GetActiveSMSWidget(self):
1008         return self.tree.GetActivePhone().smswidget
1009 
1010     def GetActiveT9EditorWidget(self):
1011         return self.tree.GetActivePhone().t9editorwidget
1012 
1013     def GetCurrentActiveWidget(self):
1014         return self.tree.GetActiveWidget()
1015 
1016     def GetActiveDatabase(self):
1017         return self.tree.GetActivePhone().GetDatabase()
1018 
1019     def UpdateToolbarOnPanelChange(self, add_image, add_help, delete_image, delete_help):
1020         sz=self.tb.GetToolBitmapSize()
1021         pos=self.GetToolBar().GetToolPos(guihelper.ID_EDITADDENTRY)
1022         self.GetToolBar().DeleteTool(guihelper.ID_EDITADDENTRY)
1023         self.tooladd=self.tb.InsertLabelTool(pos, guihelper.ID_EDITADDENTRY, add_help, 
1024                                              wx.ArtProvider.GetBitmap(add_image, wx.ART_TOOLBAR, sz),
1025                                              shortHelp=add_help, longHelp="Add an item")
1026         pos=self.GetToolBar().GetToolPos(guihelper.ID_EDITDELETEENTRY)
1027         self.GetToolBar().DeleteTool(guihelper.ID_EDITDELETEENTRY)
1028         self.tooldelete=self.tb.InsertLabelTool(pos, guihelper.ID_EDITDELETEENTRY, delete_help, 
1029                                                 wx.ArtProvider.GetBitmap(delete_image, wx.ART_TOOLBAR, sz),
1030                                                 shortHelp=delete_help, longHelp="Delete item")
1031         self.tb.Realize()
1032 
1033 
1034     def CloseSplashScreen(self):
1035         ### remove splash screen if there is one
1036         global thesplashscreen
1037         if thesplashscreen is not None:
1038             try:
1039         # on Linux this is often already deleted and generates an exception
1040                 thesplashscreen.Show(False)
1041             except:
1042                 pass
1043             thesplashscreen=None
1044             wx.SafeYield(onlyIfNeeded=True)
1045 
1046     def AutosyncUpdateUIEvent(self, event):
1047         event.Enable(self.autosyncsetting.IsConfigured())
1048 
1049     def OnExit(self,_=None):
1050         self.Close(True)
1051 
1052     # It has been requested that we shutdown
1053     def OnClose(self, event):
1054         if self._taskbar_on_closed and self._close_button and \
1055            event.CanVeto():
1056             self._close_button=False
1057             event.Veto()
1058             self.Iconize(True)
1059             return
1060         if not self.IsIconized():
1061             self.saveSize()
1062         if not self.wt:
1063             # worker thread doesn't exist yet
1064             self.Destroy()
1065             return
1066         # Shutdown helper thread
1067         self.MakeCall( Request(self.wt.exit), Callback(self.OnCloseResults) )
1068 
1069     def OnCloseResults(self, exception, _):
1070         assert isinstance(exception, BitPimExit)
1071         # assume it worked
1072         if self._taskbar:
1073             self._taskbar.Destroy()
1074         self.Destroy()
1075 
1076     def OnIconize(self, evt):
1077         if evt.Iconized():
1078             self.Show(False)
1079         else:
1080             self.Show(True)
1081             self.Raise()
1082 
1083     # deal with configuring the phone (commport)
1084     def OnEditSettings(self, _=None):
1085         if wx.IsBusy():
1086             wx.MessageBox("BitPim is busy.  You can't change settings until it has finished talking to your phone.",
1087                                                          "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
1088         else:
1089             # clear the ower's name for manual setting
1090             self.__owner_name=''
1091             self.configdlg.ShowModal()
1092     # about and help
1093 
1094     def OnHelpAbout(self,_):
1095         guiwidgets.show_about_dlg(self)
1096         
1097     def OnHelpHelp(self, _):
1098         wx.GetApp().displayhelpid(self.GetCurrentActiveWidget().GetHelpID())
1099 
1100     def OnHelpHowtos(self, _):
1101         wx.GetApp().displayhelpid(helpids.ID_HOWTOS)
1102 
1103     def OnHelpFAQ(self, _):
1104         wx.GetApp().displayhelpid(helpids.ID_FAQ)
1105 
1106     def OnHelpContents(self, _):
1107         wx.GetApp().displayhelpid(None)
1108 
1109     def OnHelpSupport(self, _):
1110         wx.GetApp().displayhelpid(helpids.ID_HELPSUPPORT)
1111 
1112     def OnHelpTour(self, _=None):
1113         wx.GetApp().displayhelpid(helpids.ID_TOUR)
1114 
1115     def OnHelpPhoneUpdateUI(self, event):
1116         if self.phonemodule and hasattr(self.phonemodule.Phone, 'desc'):
1117             event.SetText(self.phonemodule.Phone.desc)
1118         else:
1119             event.SetText('Phone')  
1120         event.Enable(bool(hasattr(self.phonemodule.Phone, "helpid") and\
1121                      self.phonemodule.Phone.helpid))
1122     def OnHelpPhone(self, _):
1123         wx.GetApp().displayhelpid(self.phonemodule.Phone.helpid)
1124     def DoCheckUpdate(self):
1125         s=update.check_update()
1126         if not len(s):
1127             # Failed to update
1128             return
1129         # update our config with the latest version and date
1130         self.config.Write('latest_version', s)
1131         self.config.Write('last_update',
1132                           time.strftime('%Y%m%d', time.localtime()))
1133         # update the status bar
1134         self.SetVersionsStatus()
1135 
1136     def OnCheckUpdate(self, _):
1137         self.DoCheckUpdate()
1138 
1139     def SetPhoneModelStatus(self, stat=guiwidgets.SB_Phone_Set):
1140         phone=self.config.Read('phonetype', 'None')
1141         port=self.config.Read('lgvx4400port', 'None')
1142         if self.__owner_name=='':
1143             self.GetStatusBar().set_phone_model('%s on %s'%(phone, port),
1144                                                 stat)
1145         else:
1146             self.GetStatusBar().set_phone_model('%s %s on %s'%(self.__owner_name, phone, port),
1147                                                 stat)
1148 
1149     def OnPhoneInfo(self, _):
1150         self.MakeCall(Request(self.wt.getphoneinfo),
1151                       Callback(self.OnDisplayPhoneInfo))
1152     def OnDisplayPhoneInfo(self, exception, phone_info):
1153         if self.HandleException(exception): return
1154         if phone_info is None:
1155             # data not available
1156             dlg=wx.MessageDialog(self, "Phone Info not available",
1157                              "Phone Info Error", style=wx.OK)
1158         else:
1159             dlg=phoneinfo.PhoneInfoDialog(self, phone_info)
1160         with guihelper.WXDialogWrapper(dlg, True):
1161             pass
1162 
1163     def OnDetectPhone(self, _=None):
1164         if wx.IsBusy():
1165             # main thread is busy, put it on the queue for the next turn
1166             self.queue.put((self.OnDetectPhone, (), {}), False)
1167             return
1168         self.__detect_phone()
1169     def __detect_phone(self, using_port=None, check_auto_sync=0, delay=0, silent_fail=False):
1170         self.OnBusyStart()
1171         self.GetStatusBar().progressminor(0, 100, 'Phone detection in progress ...')
1172         self.MakeCall(Request(self.wt.detectphone, using_port, None, delay),
1173                       Callback(self.OnDetectPhoneReturn, check_auto_sync, silent_fail))
1174     def _detect_this_phone(self, check_auto_sync=0, delay=0, silent_fail=False):
1175         # (re)detect the current phone model
1176         self.OnBusyStart()
1177         self.GetStatusBar().progressminor(0, 100, 'Phone detection in progress ...')
1178         self.MakeCall(Request(self.wt.detectphone,
1179                               self.config.Read('lgvx4400port', ''),
1180                               self.config.Read('phonetype', ''), delay),
1181                       Callback(self.OnDetectThisPhoneReturn, check_auto_sync,
1182                                silent_fail))
1183     def OnDetectThisPhoneReturn(self, check_auto_sync, silent_fail,
1184                                 exception, r):
1185         if self.HandleException(exception):
1186             self.OnBusyEnd()
1187             return
1188         if r:
1189             # detected!
1190             return self.OnDetectPhoneReturn(check_auto_sync, silent_fail,
1191                                             exception, r)
1192         # Failed to detect current model, retry for all models
1193         self.queue.put((self.__detect_phone, (),
1194                         { 'check_auto_sync': check_auto_sync,
1195                           'silent_fail': silent_fail }), False)
1196         self.OnBusyEnd()
1197 
1198     def __get_owner_name(self, esn, style=wx.DEFAULT_DIALOG_STYLE):
1199         """ retrieve or ask user for the owner's name of this phone
1200         """
1201         if esn is None or not len(esn):
1202             return None
1203         # esn is found, check if we detected this phone before
1204         phone_id='phones/'+sha.new(esn).hexdigest()
1205         phone_name=self.config.Read(phone_id, '<None/>')
1206         s=None
1207         if phone_name=='<None/>':
1208             # not seen before
1209             with guihelper.WXDialogWrapper(guiwidgets.AskPhoneNameDialog(self, 'A new phone has been detected,\n'
1210                                                                          "Would you like to enter the owner's name:", style=style),
1211                                            True) as (dlg, r):
1212                 if r==wx.ID_OK:
1213                     # user gave a name
1214                     s=dlg.GetValue()
1215                 elif r==wx.ID_CANCEL:
1216                     s=''
1217                 if s is not None:
1218                     self.config.Write(phone_id, s)
1219                 return s
1220         return phone_name
1221         
1222     def OnDetectPhoneReturn(self, check_auto_sync, silent_fail, exception, r):
1223         self._autodetect_delay=0
1224         self.OnBusyEnd()
1225         if self.HandleException(exception): return
1226         if r is None:
1227             if not silent_fail:
1228                 self.__owner_name=''
1229                 with guihelper.WXDialogWrapper(wx.MessageDialog(self, 'No phone detected/recognized.\nRun Settings?',
1230                                                                 'Phone Detection Failed', wx.YES_NO),
1231                                                True) as (_dlg, retcode):
1232                     if retcode==wx.ID_YES:
1233                         wx.CallAfter(self.OnEditSettings)
1234                     self.SetPhoneModelStatus(guiwidgets.SB_Phone_Set)
1235         else:
1236             if silent_fail:
1237                 self.__owner_name=None
1238             else:
1239                 self.__owner_name=self.__get_owner_name(r.get('phone_esn', None))
1240             if self.__owner_name is None or self.__owner_name=='':
1241                 self.__owner_name=''
1242             else:
1243                 self.__owner_name+="'s"
1244             self.config.Write("phonetype", r['phone_name'])
1245             self.commportsetting=str(r['port'])
1246             self.wt.clearcomm()
1247             self.config.Write("lgvx4400port", r['port'])
1248             self.phonemodule=common.importas(r['phone_module'])
1249             self.phoneprofile=self.phonemodule.Profile()
1250             pubsub.publish(pubsub.PHONE_MODEL_CHANGED, self.phonemodule)
1251             self.SetPhoneModelStatus(guiwidgets.SB_Phone_Detected)
1252             if not silent_fail:
1253                 if self.__owner_name =='':
1254                     wx.MessageBox('Found %s on %s'%(r['phone_name'],
1255                                                     r['port']),
1256                                                     'Phone Detection', wx.OK)
1257                 else:
1258                     wx.MessageBox('Found %s %s on %s'%(self.__owner_name,
1259                                                        r['phone_name'],
1260                                                        r['port']),
1261                                                        'Phone Detection', wx.OK)
1262             if check_auto_sync:
1263                 # see if we should re-sync the calender on connect, do it silently
1264                 self.__autosync_phone(silent=1)
1265 
1266     def AddComm(self, name):
1267         # A new comm port became available
1268         print 'New device on port:',name
1269         # check the new device
1270         check_auto_sync=auto_sync.UpdateOnConnect(self)
1271         if name and name.lower()==self.config.Read('lgvx4400port', '').lower():
1272             _func=self._detect_this_phone
1273             _args=(check_auto_sync, self._autodetect_delay, True)
1274         else:
1275             _func=self.__detect_phone
1276             _args=(name, check_auto_sync, self._autodetect_delay, True)
1277         if wx.IsBusy():
1278             # current phone operation ongoing, queue this
1279             self.queue.put((_func, _args, {}), False)
1280         else:
1281             _func(*_args)
1282 
1283     def RemoveComm(self, name):
1284         # This comm just went away
1285         print "Device remove", name
1286         # device is removed, if it's ours, clear the port
1287         if name and name.lower()==self.config.Read('lgvx4400port', '').lower():
1288             if self.wt:
1289                 self.wt.clearcomm()
1290             self.SetPhoneModelStatus(guiwidgets.SB_Phone_Unavailable)
1291 
1292     def NotifyComm(self, evt):
1293         if evt.type==evt.add:
1294             self.AddComm(evt.comm)
1295         else:
1296             self.RemoveComm(evt.comm)
1297 
1298     def OnCommNotification(self, evt):
1299         print 'OnCommNotification'
1300         if wx.Thread_IsMain():
1301             self.NotifyComm(evt)
1302         else:
1303             wx.CallAfter(self.NotifyComm, evt)
1304 
1305     def WindowsOnDeviceChanged(self, type, name="", drives=[], flag=None):
1306         if not name.lower().startswith("com"):
1307             return
1308         if type=='DBT_DEVICEREMOVECOMPLETE':
1309             self.RemoveComm(name)
1310             return
1311         if type!='DBT_DEVICEARRIVAL':
1312             # not interested
1313             return
1314         self.AddComm(name)
1315 
1316     def MyWndProc(self, hwnd, msg, wparam, lparam):
1317 
1318         if msg==win32con.WM_DEVICECHANGE:
1319             try:
1320                 type,params=DeviceChanged(wparam, lparam).GetEventInfo()
1321                 self.OnDeviceChanged(type, **params)
1322                 return True
1323             except:
1324                 # something bad happened! Bail and let Windows handle it
1325                 return win32gui.CallWindowProc(self.oldwndproc, hwnd, msg,
1326                                                wparam, lparam)
1327 
1328         # Restore the old WndProc.  Notice the use of win32api
1329         # instead of win32gui here.  This is to avoid an error due to
1330         # not passing a callable object.
1331         if msg == win32con.WM_DESTROY:
1332             win32api.SetWindowLong(self.GetHandle(),
1333                                    win32con.GWL_WNDPROC,
1334                                    self.oldwndproc)
1335         if self._taskbar_on_closed and \
1336            msg==win32con.WM_NCLBUTTONDOWN and \
1337            wparam==win32con.HTCLOSE:
1338             # The system Close Box was clicked!
1339             self._close_button=True
1340 
1341         # Pass all messages (in this case, yours may be different) on
1342         # to the original WndProc
1343         return win32gui.CallWindowProc(self.oldwndproc,
1344                                        hwnd, msg, wparam, lparam)
1345 
1346     if guihelper.IsMSWindows():
1347         OnDeviceChanged=WindowsOnDeviceChanged
1348 
1349     def SetVersionsStatus(self):
1350         current_v=version.version
1351         latest_v=self.config.Read('latest_version')
1352         self.GetStatusBar().set_versions(current_v, latest_v)
1353 
1354     def update_cache_path(self):
1355         com_brew.file_cache.set_path(self.configpath)
1356 
1357     def OnNewDB(self, _):
1358         newdb_wiz.create_new_db(self, self.config)
1359 
1360     ### 
1361     ### Main bit for getting stuff from phone
1362     ###
1363 
1364     def OnDataGetPhone(self,_):
1365         todo=[]
1366         dlg=self.dlggetphone
1367         dlg.UpdateWithProfile(self.phoneprofile)
1368         if dlg.ShowModal()!=wx.ID_OK:
1369             return
1370         self._autodetect_delay=self.phoneprofile.autodetect_delay
1371         todo.append((self.wt.rebootcheck, "Phone Reboot"))
1372         wx.GetApp().critical.set()
1373         self.MakeCall(Request(self.wt.getdata, dlg, todo),
1374                       Callback(self.OnDataGetPhoneResults))
1375 
1376     def OnDataGetPhoneResults(self, exception, results):
1377         with wx.GetApp().critical:
1378             if self.HandleException(exception): return
1379             self.OnLog(`results.keys()`)
1380             self.OnLog(`results['sync']`)
1381             # phonebook
1382             if results['sync'].has_key('phonebook'):
1383                 v=results['sync']['phonebook']
1384 
1385                 print "phonebookmergesetting is",v
1386                 if v=='MERGE': 
1387                     merge=True
1388                 else:
1389                     merge=False
1390                 self.GetActivePhonebookWidget().importdata(results['phonebook'], results.get('categories', []), merge)
1391 
1392             # wallpaper
1393             updwp=False # did we update the wallpaper
1394             if results['sync'].has_key('wallpaper'):
1395                 v=results['sync']['wallpaper']
1396                 if v=='MERGE': raise Exception("Not implemented")
1397                 updwp=True
1398                 self.GetActiveWallpaperWidget().populatefs(results)
1399                 self.GetActiveWallpaperWidget().populate(results)
1400             # wallpaper-index
1401             if not updwp and results.has_key('wallpaper-index'):
1402                 self.GetActiveWallpaperWidget().updateindex(results)
1403             # ringtone
1404             updrng=False # did we update ringtones
1405             if results['sync'].has_key('ringtone'):
1406                 v=results['sync']['ringtone']
1407                 if v=='MERGE': raise Exception("Not implemented")
1408                 updrng=True
1409                 self.GetActiveRingerWidget().populatefs(results)
1410                 self.GetActiveRingerWidget().populate(results)
1411             # ringtone-index
1412             if not updrng and results.has_key('ringtone-index'):
1413                 self.GetActiveRingerWidget().updateindex(results)            
1414             # calendar
1415             if results['sync'].has_key('calendar'):
1416                 v=results['sync']['calendar']
1417                 if v=='MERGE': raise Exception("Not implemented")
1418                 results['calendar_version']=self.phoneprofile.BP_Calendar_Version
1419                 self.GetActiveCalendarWidget().mergedata(results)
1420     ##            self.GetActiveCalendarWidget().populatefs(results)
1421     ##            self.GetActiveCalendarWidget().populate(results)
1422             # memo
1423             if results['sync'].has_key('memo'):
1424                 v=results['sync']['memo']
1425                 if v=='MERGE': raise Exception("Not implemented")
1426                 self.GetActiveMemoWidget().populatefs(results)
1427                 self.GetActiveMemoWidget().populate(results)
1428             # todo
1429             if results['sync'].has_key('todo'):
1430                 v=results['sync']['todo']
1431                 if v=='MERGE': raise NotImplementedError
1432                 self.GetActiveTodoWidget().populatefs(results)
1433                 self.GetActiveTodoWidget().populate(results)
1434             # SMS
1435             if results['sync'].has_key('sms'):
1436                 v=results['sync']['sms']
1437                 if v=='MERGE':
1438                     self.GetActiveSMSWidget().merge(results)
1439                 else:
1440                     self.GetActiveSMSWidget().populatefs(results)
1441                     self.GetActiveSMSWidget().populate(results)
1442             # call history
1443             if results['sync'].has_key('call_history'):
1444                 v=results['sync']['call_history']
1445                 if v=='MERGE':
1446                     self.GetActiveCallHistoryWidget().merge(results)
1447                 else:
1448                     self.GetActiveCallHistoryWidget().populatefs(results)
1449                     self.GetActiveCallHistoryWidget().populate(results)
1450             # Playlist
1451             if results['sync'].has_key(playlist.playlist_key):
1452                 if results['sync'][playlist.playlist_key]=='MERGE':
1453                     raise NotImplementedError
1454                 self.GetActivePlaylistWidget().populatefs(results)
1455                 self.GetActivePlaylistWidget().populate(results)
1456             # T9 User DB
1457             if results['sync'].has_key(t9editor.dict_key):
1458                 if results['sync'][t9editor.dict_key]=='MERGE':
1459                     raise NotImplementedError
1460                 self.GetActiveT9EditorWidget().populatefs(results)
1461                 self.GetActiveT9EditorWidget().populate(results)
1462     ###
1463     ### Main bit for sending data to the phone
1464     ###
1465     def OnDataSendPhone(self, _):
1466         dlg=self.dlgsendphone
1467         print self.phoneprofile
1468         dlg.UpdateWithProfile(self.phoneprofile)
1469         if dlg.ShowModal()!=wx.ID_OK:
1470             return
1471         data={}
1472         convertors=[]
1473         todo=[]
1474         funcscb=[]
1475         
1476         ### Wallpaper
1477         v=dlg.GetWallpaperSetting()
1478         if v!=dlg.NOTREQUESTED:
1479             merge=True
1480             if v==dlg.OVERWRITE: merge=False
1481             if merge:
1482                 want=self.GetActiveWallpaperWidget().SELECTED
1483             else:
1484                 want=self.GetActiveWallpaperWidget().ALL
1485             self.GetActiveWallpaperWidget().getdata(data, want)
1486             todo.append( (self.wt.writewallpaper, "Wallpaper", merge) )
1487             # funcscb.append( self.wallpaperwidget.populate )
1488 
1489         ### Ringtone
1490         v=dlg.GetRingtoneSetting()
1491         if v!=dlg.NOTREQUESTED:
1492             merge=True
1493             if v==dlg.OVERWRITE: merge=False
1494             if merge:
1495                 want=self.GetActiveRingerWidget().SELECTED
1496             else:
1497                 want=self.GetActiveRingerWidget().ALL
1498             self.GetActiveRingerWidget().getdata(data, want)
1499             todo.append( (self.wt.writeringtone, "Ringtone", merge) )
1500             # funcscb.append( self.ringerwidget.populate )
1501 
1502         ### Calendar
1503         v=dlg.GetCalendarSetting()
1504         if v!=dlg.NOTREQUESTED:
1505             merge=True
1506             if v==dlg.OVERWRITE: merge=False
1507             data['calendar_version']=self.phoneprofile.BP_Calendar_Version
1508             self.GetActiveCalendarWidget().getdata(data)
1509             todo.append( (self.wt.writecalendar, "Calendar", merge) )
1510 
1511         ### Phonebook
1512         v=dlg.GetPhoneBookSetting()
1513         if v!=dlg.NOTREQUESTED:
1514             if v==dlg.OVERWRITE: 
1515                 self.GetActivePhonebookWidget().getdata(data)
1516                 todo.append( (self.wt.writephonebook, "Phonebook") )
1517             convertors.append(self.GetActivePhonebookWidget().converttophone)
1518             # writing will modify serials so we need to update
1519             funcscb.append(self.GetActivePhonebookWidget().updateserials)
1520 
1521         ### Memo
1522         v=dlg.GetMemoSetting()
1523         if v!=dlg.NOTREQUESTED:
1524             merge=v!=dlg.OVERWRITE
1525             self.GetActiveMemoWidget().getdata(data)
1526             todo.append((self.wt.writememo, "Memo", merge))
1527 
1528         ### Todo
1529         v=dlg.GetTodoSetting()
1530         if v!=dlg.NOTREQUESTED:
1531             merge=v!=dlg.OVERWRITE
1532             self.GetActiveTodoWidget().getdata(data)
1533             todo.append((self.wt.writetodo, "Todo", merge))
1534 
1535         ### SMS
1536         v=dlg.GetSMSSetting()
1537         if v!=dlg.NOTREQUESTED:
1538             merge=v!=dlg.OVERWRITE
1539             self.GetActiveSMSWidget().getdata(data)
1540             todo.append((self.wt.writesms, "SMS", merge))
1541 
1542         ### Playlist
1543         v=dlg.GetPlaylistSetting()
1544         if v!=dlg.NOTREQUESTED:
1545             merge=v!=dlg.OVERWRITE
1546             self.GetActivePlaylistWidget().getdata(data)
1547             todo.append((self.wt.writeplaylist, "Playlist", merge))
1548 
1549         ### T9 User DB
1550         v=dlg.GetT9Setting()
1551         if v!=dlg.NOTREQUESTED:
1552             merge=v!=dlg.OVERWRITE
1553             self.GetActiveT9EditorWidget().getdata(data)
1554             todo.append((self.wt.writet9, "T9", merge))
1555 
1556         data['reboot_delay']=self.phoneprofile.reboot_delay
1557         self._autodetect_delay=self.phoneprofile.autodetect_delay
1558         todo.append((self.wt.rebootcheck, "Phone Reboot"))
1559         self.MakeCall(Request(self.wt.getfundamentals),
1560                       Callback(self.OnDataSendPhoneGotFundamentals, data, todo, convertors, funcscb))
1561 
1562     def OnDataSendPhoneGotFundamentals(self,data,todo,convertors, funcscb, exception, results):
1563         if self.HandleException(exception): return
1564         data.update(results)
1565         # call each widget to update fundamentals
1566         # for widget in self.calendarwidget, self.wallpaperwidget, self.ringerwidget, self.phonewidget:
1567         #    widget.updatefundamentals(data)
1568         
1569         # call convertors
1570         for f in convertors:
1571             f(data)
1572 
1573         # Now scribble to phone
1574         self.MakeCall(Request(self.wt.senddata, data, todo),
1575                       Callback(self.OnDataSendPhoneResults, funcscb))
1576 
1577     def OnDataSendPhoneResults(self, funcscb, exception, results):
1578         if self.HandleException(exception): return
1579         print results.keys()
1580         for f in funcscb:
1581             f(results)
1582 
1583     def GetCalendarData(self):
1584         # return calendar data for export
1585         d={}
1586         return self.GetActiveCalendarWidget().getdata(d).get('calendar', {})
1587 
1588         
1589     def OnAutoSyncSettings(self, _=None):
1590         if wx.IsBusy():
1591             with guihelper.WXDialogWrapper(wx.MessageBox("BitPim is busy.  You can't change settings until it has finished talking to your phone.",
1592                                                          "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION),
1593                                            True):
1594                 pass
1595         else:
1596             # clear the ower's name for manual setting
1597             self.__owner_name=''
1598             self.autosyncsetting.ShowModal()
1599 
1600     def OnAutoSyncExecute(self, _=None):
1601         if wx.IsBusy():
1602             wx.MessageBox("BitPim is busy.  You can't run autosync until it has finished talking to your phone.",
1603                          "BitPim is busy.", wx.OK|wx.ICON_EXCLAMATION)
1604             return
1605         self.__autosync_phone()
1606 
1607     def __autosync_phone(self, silent=0):
1608         r=auto_sync.SyncSchedule(self).sync(self, silent)
1609         
1610     # deal with configuring the phone (commport)
1611     def OnReqChangeTab(self, msg=None):
1612         if msg is None:
1613             return
1614         data=msg.data
1615         if not isinstance(data, int):
1616             # wrong data type
1617             if __debug__:
1618                 raise TypeError
1619             return
1620 
1621     # Busy handling
1622     def OnBusyStart(self):
1623         self.GetStatusBar().set_app_status_busy()
1624         wx.BeginBusyCursor(wx.StockCursor(wx.CURSOR_ARROWWAIT))
1625 
1626     def OnBusyEnd(self):
1627         wx.EndBusyCursor()
1628         self.GetStatusBar().set_app_status_ready()
1629         self.OnProgressMajor(0,1)
1630         # fire the next one in the queue
1631         if not self.queue.empty():
1632             _q=self.queue.get(False)
1633             wx.CallAfter(_q[0], *_q[1], **_q[2])
1634 
1635     # progress and logging
1636     def OnProgressMinor(self, pos, max, desc=""):
1637         self.GetStatusBar().progressminor(pos, max, desc)
1638 
1639     def OnProgressMajor(self, pos, max, desc=""):
1640         self.GetStatusBar().progressmajor(pos, max, desc)
1641 
1642     def OnLog(self, str):
1643         if self.__phone_detect_at_startup:
1644             return
1645         str=common.strorunicode(str)
1646         if data_recording.DR_On:
1647             data_recording.record(data_recording.DR_Type_Note, str)
1648         self.tree.lw.log(str)
1649         if self.tree.lwdata is not None:
1650             self.tree.lwdata.log(str)
1651         if str.startswith("<!= "):
1652             p=str.index("=!>")+3
1653             guihelper.MessageDialog(self, str[p:], "Alert", style=wx.OK|wx.ICON_EXCLAMATION)
1654             self.OnLog("Alert dialog closed")
1655     log=OnLog
1656     def OnLogData(self, str, data, klass=None, data_type=None):
1657         if data_recording.DR_On:
1658             data_recording.record(data_recording.DR_Type_Note, str)
1659             data_recording.record(data_type or data_recording.DR_Type_Data,
1660                                 data, klass)
1661         if self.tree.lwdata is not None:
1662             self.tree.lwdata.logdata(str,data, klass)
1663 
1664     def excepthook(self, type, value, traceback):
1665         if not hasattr(value, "gui_exc_info"):
1666             value.gui_exc_info=(type,value,traceback)
1667         self.HandleException(value)
1668 
1669     def HandleException(self, exception):
1670         """returns true if this function handled the exception
1671         and the caller should not do any further processing"""
1672         if exception is None: return False
1673         assert isinstance(exception, Exception)
1674         self.CloseSplashScreen()
1675         # always close comm connection when we have any form of exception
1676         if self.wt is not None:
1677             self.wt.clearcomm()
1678         text=None
1679         title=None
1680         style=None
1681         # Here is where we turn the exception into something user friendly
1682         if isinstance(exception, common.CommsDeviceNeedsAttention):
1683             text="%s: %s" % (exception.device, exception.message)
1684             title="Device needs attention - "+exception.device
1685             style=wx.OK|wx.ICON_INFORMATION
1686             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_DEVICE_NEEDS_ATTENTION)
1687         elif isinstance(exception, common.CommsOpenFailure):
1688             text="%s: %s" % (exception.device, exception.message)
1689             title="Failed to open communications - "+exception.device
1690             style=wx.OK|wx.ICON_INFORMATION
1691             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_FAILED_TO_OPEN_DEVICE)
1692         elif isinstance(exception, common.AutoPortsFailure):
1693             text=exception.message
1694             title="Failed to automatically detect port"
1695             style=wx.OK|wx.ICON_INFORMATION
1696             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_FAILED_TO_AUTODETECT_PORT)
1697         elif isinstance(exception, common.HelperBinaryNotFound) and exception.basename=="pvconv":
1698             text="The Qualcomm PureVoice converter program (%s) was not found.\nPlease see the help. Directories looked in are:\n\n " +\
1699                   "\n ".join(exception.paths)
1700             text=text % (exception.fullname,)
1701             title="Failed to find PureVoice converter"
1702             style=wx.OK|wx.ICON_INFORMATION
1703             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_NO_PVCONV)
1704         elif isinstance(exception, common.PhoneBookBusyException):
1705             text="The phonebook is busy on your phone.\nExit back to the main screen and then repeat the operation."
1706             title="Phonebook busy on phone"
1707             style=wx.OK|wx.ICON_INFORMATION
1708             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_PHONEBOOKBUSY)
1709         elif isinstance(exception, common.IntegrityCheckFailed):
1710             text="The phonebook on your phone is partially corrupt.  Please read the\nhelp for more details on the cause and fix"
1711             title="IntegrityCheckFailed"
1712             style=wx.OK|wx.ICON_EXCLAMATION
1713             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_LG_INTEGRITYCHECKFAILED)
1714         elif isinstance(exception, common.CommsDataCorruption):
1715             text=exception.message+"\nPlease see the help."
1716             title="Communications Error - "+exception.device
1717             style=wx.OK|wx.ICON_EXCLAMATION
1718             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_COMMSDATAERROR)
1719         elif isinstance(exception, com_brew.BrewAccessDeniedException):
1720             text="Access to the file/directory has been blocked on this phone by the phone provider"
1721             title="Access Denied"
1722             style=wx.OK|wx.ICON_EXCLAMATION
1723             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_BREW_ACCESS_DENIED)
1724         elif isinstance(exception, common.PhoneStringEncodeException):
1725             text="Unable to convert the text <%s> into a format your phone can understand, change the text to contain only %s characters" % (exception.string, `exception.codec`)
1726             title="Text Conversion Error"
1727             style=wx.OK|wx.ICON_EXCLAMATION
1728             help=lambda _: wx.GetApp().displayhelpid(helpids.ID_BREW_ACCESS_DENIED)
1729             
1730         if text is not None:
1731             self.OnLog("Error: "+title+"\n"+text)
1732             with guihelper.WXDialogWrapper(guiwidgets.AlertDialogWithHelp(self,text, title, help, style=style),
1733                                            True):
1734                 pass
1735             return True
1736 
1737         if self.exceptiondialog is None:
1738             self.excepttime=time.time()
1739             self.exceptcount=0
1740             self.exceptiondialog=guiwidgets.ExceptionDialog(self, exception)
1741             try:
1742                 self.OnLog("Exception: "+self.exceptiondialog.getexceptiontext())
1743             except AttributeError:
1744                 # this can happen if main gui hasn't been built yet
1745                 pass
1746         else:
1747             self.exceptcount+=1
1748             if self.exceptcount<10:
1749                 print "Ignoring an exception as the exception dialog is already up"
1750                 try:
1751                     self.OnLog("Exception during exception swallowed")
1752                 except AttributeError:
1753                     # this can happen if main gui hasn't been built yet
1754                     pass
1755             return True
1756             
1757         self.exceptiondialog.ShowModal()
1758         self.exceptiondialog.Destroy()
1759         self.exceptiondialog=None
1760         return True
1761 
1762     # midnight timer stuff
1763     def _OnTimer(self, _):
1764         self.MakeCall(Request(self._pub_timer),
1765                       Callback(self._OnTimerReturn))
1766 
1767     def _pub_timer(self):
1768         pubsub.publish(pubsub.MIDNIGHT)
1769 
1770     def _OnTimerReturn(self, exceptions, result):
1771         self._timer.Start(((3600*24)+1)*1000, True)
1772 
1773     def _setup_midnight_timer(self):
1774         _today=datetime.datetime.now()
1775         _timer_val=24*3600-_today.hour*3600-_today.minute*60-_today.second+1
1776         self._timer=wx.Timer(self)
1777         wx.EVT_TIMER(self, self._timer.GetId(), self._OnTimer)
1778         self._timer.Start(_timer_val*1000, True)
1779         print _timer_val,'seconds till midnight'
1780 
1781     # Data Recording stuff
1782     def OnDataRecording(self, _):
1783         with guihelper.WXDialogWrapper(guiwidgets.DRRecFileDialog(self),
1784                                        True):
1785             pass
1786 
1787     # plumbing for the multi-threading
1788 
1789     def OnCallback(self, event):
1790         assert isinstance(event, HelperReturnEvent)
1791         event()
1792 
1793     def MakeCall(self, request, cbresult):
1794         assert isinstance(request, Request)
1795         assert isinstance(cbresult, Callback)
1796         self.wt.q.put( (request, cbresult) )
1797 
1798     # remember our size and position
1799 
1800     def saveSize(self):
1801         guiwidgets.save_size("MainWin", self.GetRect())
1802 
1803 ###
1804 ### Container for midi files
1805 ###  
1806 
1807 #class MidiFileList(wx.ListCtrl):
1808 #    pass
1809 
1810 
1811 
1812 
1813 ###
1814 ###  Class that does all the comms and other stuff in a seperate
1815 ###  thread.  
1816 ###
1817 
1818 class WorkerThread(WorkerThreadFramework):
1819     def __init__(self):
1820         WorkerThreadFramework.__init__(self)
1821         self.commphone=None
1822         data_recording.register(self.OnDataRecording, self.OnDataRecording,
1823                                 self.OnDataRecording)
1824 
1825     def exit(self):
1826         if __debug__: self.checkthread()
1827         for i in range(0,0):
1828             self.progressmajor(i, 2, "Shutting down helper thread")
1829             time.sleep(1)
1830         self.log("helper thread shut down")
1831         raise BitPimExit("helper thread shutdown")
1832 
1833 
1834     def clearcomm(self):
1835         if self.commphone is None:
1836             return
1837         self.commphone.close()
1838         self.commphone=None
1839         
1840         
1841     def setupcomm(self):
1842         if __debug__: self.checkthread()
1843         if self.commphone is None:
1844             import commport
1845             if self.dispatchto.commportsetting is None or \
1846                len(self.dispatchto.commportsetting)==0:
1847                 raise common.CommsNeedConfiguring("Comm port not configured", "DEVICE")
1848 
1849             if self.dispatchto.commportsetting=="auto":
1850                 autofunc=comdiagnose.autoguessports
1851             else:
1852                 autofunc=None
1853             comcfg=self.dispatchto.commparams
1854 
1855             name=self.dispatchto.commportsetting
1856             if name.startswith("bitfling::"):
1857                 klass=bitflingscan.CommConnection
1858             else:
1859                 klass=commport.CommConnection
1860                 
1861             comport=klass(self, self.dispatchto.commportsetting, autolistfunc=autofunc,
1862                           autolistargs=(self.dispatchto.phonemodule,),
1863                           baud=comcfg['baud'], timeout=comcfg['timeout'],
1864                           hardwareflow=comcfg['hardwareflow'],
1865                           softwareflow=comcfg['softwareflow'],
1866                           configparameters=comcfg)
1867                 
1868             try:
1869                 self.commphone=self.dispatchto.phonemodule.Phone(self, comport)
1870             except:
1871                 comport.close()
1872                 raise
1873 
1874     def getfundamentals(self):
1875         if __debug__: self.checkthread()
1876         self.setupcomm()
1877         results={}
1878         self.commphone.getfundamentals(results)
1879         return results
1880 
1881     def getdata(self, req, todo):
1882         if __debug__: self.checkthread()
1883         self.setupcomm()
1884         results=self.getfundamentals()
1885         com_brew.file_cache.esn=results.get('uniqueserial', None)
1886         willcall=[]
1887         sync={}
1888         for i in (
1889             (req.GetPhoneBookSetting, self.commphone.getphonebook, "Phone Book", "phonebook"),
1890             (req.GetCalendarSetting, self.commphone.getcalendar, "Calendar", "calendar",),
1891             (req.GetWallpaperSetting, self.commphone.getwallpapers, "Wallpaper", "wallpaper"),
1892             (req.GetRingtoneSetting, self.commphone.getringtones, "Ringtones", "ringtone"),
1893             (req.GetMemoSetting, self.commphone.getmemo, "Memo", "memo"),
1894             (req.GetTodoSetting, self.commphone.gettodo, "Todo", "todo"),
1895             (req.GetSMSSetting, self.commphone.getsms, "SMS", "sms"),
1896             (req.GetCallHistorySetting, self.commphone.getcallhistory, 'Call History', 'call_history'),
1897             (req.GetPlaylistSetting, self.commphone.getplaylist, 'Play List', 'playlist'),
1898             (req.GetT9Setting, self.commphone.gett9db, 'T9 DB', t9editor.dict_key),
1899             ):
1900             st=i[0]()
1901             if st==req.MERGE:
1902                 sync[i[3]]="MERGE"
1903                 willcall.append(i)
1904             elif st==req.OVERWRITE:
1905                 sync[i[3]]="OVERWRITE"
1906                 willcall.append(i)
1907 
1908         results['sync']=sync
1909         count=0
1910         for i in willcall:
1911             self.progressmajor(count, len(willcall), i[2])
1912             count+=1
1913             i[1](results)
1914 
1915         for xx in todo:
1916             func=xx[0]
1917             desc=xx[1]
1918             args=[results]
1919             if len(xx)>2:
1920                 args.extend(xx[2:])
1921             apply(func, args)
1922 
1923         return results
1924 
1925     def senddata(self, dict, todo):
1926         count=0
1927         for xx in todo:
1928             func=xx[0]
1929             desc=xx[1]
1930             args=[dict]
1931             if len(xx)>2:
1932                 args.extend(xx[2:])
1933             self.progressmajor(count,len(todo),desc)
1934             apply(func, args)
1935             count+=1
1936         return dict
1937 
1938     def writewallpaper(self, data, merge):
1939         if __debug__: self.checkthread()
1940         self.setupcomm()
1941         return self.commphone.savewallpapers(data, merge)
1942 
1943     def writeringtone(self, data, merge):
1944         if __debug__: self.checkthread()
1945         self.setupcomm()
1946         return self.commphone.saveringtones(data, merge)
1947 
1948     def writephonebook(self, data):
1949         if __debug__: self.checkthread()
1950         self.setupcomm()
1951         return self.commphone.savephonebook(data)
1952 
1953     def rebootcheck(self, results):
1954         if __debug__: self.checkthread()
1955         if results.has_key('rebootphone'):
1956             self.log("BitPim is rebooting your phone for changes to take effect")
1957             delay=0
1958             if results.has_key('reboot_delay'):
1959                 delay=results['reboot_delay']
1960             self.phonerebootrequest(delay)
1961             self.clearcomm()
1962         elif results.get('clearcomm', False):
1963             # some model (eg Moto) needs to clear comm after certain mode
1964             self.clearcomm()
1965 
1966     def writecalendar(self, data, merge):
1967         if __debug__: self.checkthread()
1968         self.setupcomm()
1969         return self.commphone.savecalendar(data, merge)
1970 
1971     def writememo(self, data, merge):
1972         if __debug__: self.checkthread()
1973         self.setupcomm()
1974         return self.commphone.savememo(data, merge)
1975 
1976     def writetodo(self, data, merge):
1977         if __debug__: self.checkthread()
1978         self.setupcomm()
1979         return self.commphone.savetodo(data, merge)
1980 
1981     def writesms(self, data, merge):
1982         if __debug__: self.checkthread()
1983         self.setupcomm()
1984         return self.commphone.savesms(data, merge)
1985 
1986     def writeplaylist(self, data, merge):
1987         if __debug__: self.checkthread()
1988         self.setupcomm()
1989         return self.commphone.saveplaylist(data, merge)
1990 
1991     def writet9(self, data, merge):
1992         if __debug__:
1993             self.checkthread()
1994         self.setupcomm()
1995         return self.commphone.savet9db(data, merge)
1996 
1997     def getphoneinfo(self):
1998         if __debug__: self.checkthread()
1999         self.setupcomm()
2000         if hasattr(self.commphone, 'getphoneinfo'):
2001             phone_info=phoneinfo.PhoneInfo()
2002             getattr(self.commphone, 'getphoneinfo')(phone_info)
2003             return phone_info
2004 
2005     def detectphone(self, using_port=None, using_model=None, delay=0):
2006         self.clearcomm()
2007         time.sleep(delay)
2008         return phone_detect.DetectPhone(self).detect(using_port, using_model)
2009 
2010     # various file operations for the benefit of the filesystem viewer
2011     def dirlisting(self, path, recurse=0):
2012         if __debug__: self.checkthread()
2013         self.setupcomm()
2014         try:
2015             return self.commphone.getfilesystem(path, recurse)
2016         except:
2017             self.log('Failed to read dir: '+path)
2018             return {}
2019 
2020     def getfileonlylist(self, path):
2021         if __debug__: self.checkthread()
2022         self.setupcomm()
2023         try:
2024             return self.commphone.listfiles(path)
2025         except:
2026             self.log('Failed to read filesystem')
2027             return {}
2028 
2029     def getdironlylist(self, path, recurse):
2030         results=self.commphone.listsubdirs(path)
2031         subdir_list=[x['name'] for k,x in results.items()]
2032         if recurse:
2033             for _subdir in subdir_list:
2034                 try:
2035                     results.update(self.getdironlylist(_subdir, recurse))
2036                 except:
2037                     self.log('Failed to list directories in ' +_subdir)
2038         return results
2039 
2040     def fulldirlisting(self):
2041         if __debug__: self.checkthread()
2042         self.setupcomm()
2043         try:
2044             return self.getdironlylist("", True)
2045         except:
2046             self.log('Failed to read filesystem')
2047             return {}
2048 
2049     def singledirlisting(self, path):
2050         if __debug__: self.checkthread()
2051         self.setupcomm()
2052         try:
2053             return self.getdironlylist(path, False)
2054         except:
2055             self.log('Failed to read filesystem')
2056             return {}
2057 
2058     def getfile(self, path):
2059         if __debug__: self.checkthread()
2060         self.setupcomm()
2061         return self.commphone.getfilecontents(path)
2062 
2063     def rmfile(self,path):
2064         if __debug__: self.checkthread()
2065         self.setupcomm()
2066         return self.commphone.rmfile(path)
2067 
2068     def writefile(self,path,contents):
2069         if __debug__: self.checkthread()
2070         self.setupcomm()
2071         return self.commphone.writefile(path, contents)
2072 
2073     def mkdir(self,path):
2074         if __debug__: self.checkthread()
2075         self.setupcomm()
2076         return self.commphone.mkdir(path)
2077 
2078     def rmdir(self,path):
2079         if __debug__: self.checkthread()
2080         self.setupcomm()
2081         return self.commphone.rmdir(path)
2082 
2083     def rmdirs(self,path):
2084         if __debug__: self.checkthread()
2085         self.setupcomm()
2086         return self.commphone.rmdirs(path)
2087 
2088     # offline/reboot/modemmode
2089     def phonerebootrequest(self, reboot_delay=0):
2090         if __debug__: self.checkthread()
2091         self.setupcomm()
2092         return self.commphone.offlinerequest(reset=True, delay=reboot_delay)
2093 
2094     def phoneofflinerequest(self):
2095         if __debug__: self.checkthread()
2096         self.setupcomm()
2097         return self.commphone.offlinerequest()
2098 
2099     def modemmoderequest(self):
2100         if __debug__: self.checkthread()
2101         self.setupcomm()
2102         return self.commphone.modemmoderequest()
2103 
2104     # backups etc
2105     def getbackup(self,path,recurse=0):
2106         if __debug__: self.checkthread()
2107         self.setupcomm()
2108         self.progressmajor(0,0,"Listing files")
2109         files=self.dirlisting(path, recurse)
2110         if path=="/" or path=="":
2111             strip=0 # root dir
2112         else:
2113             strip=len(path)+1 # child
2114 
2115         keys=files.keys()
2116         keys.sort()
2117         
2118         op=cStringIO.StringIO()
2119         with contextlib.closing(zipfile.ZipFile(op, "w", zipfile.ZIP_DEFLATED)) as zip:
2120             count=0
2121             for k in keys:
2122                 try:
2123                     count+=1
2124                     if files[k]['type']!='file':
2125                         continue
2126                     self.progressmajor(count, len(keys)+1, "Getting files")
2127                     # get the contents
2128                     contents=self.getfile(k)
2129                     # an artificial sleep. if you get files too quickly, the 4400 eventually
2130                     # runs out of buffers and returns truncated packets
2131                     time.sleep(0.3)
2132                     # add to zip file
2133                     zi=zipfile.ZipInfo()
2134                     # zipfile does not like unicode. cp437 works on windows well, may be
2135                     # a better choice than ascii, but no phones currently support anything
2136                     # other than ascii for filenames
2137                     if k[strip]=='/':
2138                         zi.filename=common.get_ascii_string(k[strip+1:], 'ignore')
2139                     else:
2140                         zi.filename=common.get_ascii_string(k[strip:], 'ignore')
2141                     if files[k]['date'][0]==0:
2142                         zi.date_time=(0,0,0,0,0,0)
2143                     else:
2144                         zi.date_time=time.gmtime(files[k]['date'][0])[:6]
2145                     zi.compress_type=zipfile.ZIP_DEFLATED
2146                     zip.writestr(zi, contents)
2147                 except:
2148                     self.log('Failed to read file: '+k)
2149         return op.getvalue()
2150     
2151     def restorefiles(self, files):
2152         if __debug__: self.checkthread()
2153         self.setupcomm()
2154 
2155         results=[]
2156 
2157         seendirs=[]
2158 
2159         count=0
2160         for name, contents in files:
2161             self.progressmajor(count, len(files), "Restoring files")
2162             count+=1
2163             d=guihelper.dirname(name)
2164             if d not in seendirs:
2165                 seendirs.append(d)
2166                 self.commphone.mkdirs(d)
2167             self.writefile(name, contents)
2168             results.append( (True, name) )
2169             # add a deliberate sleep - some phones (eg vx7000) get overwhelmed when writing
2170             # lots of files in a tight loop
2171             time.sleep(0.3)
2172 
2173         return results
2174 
2175     def OnDataRecording(self, _=None):
2176         self.clearcomm()
2177 
2178 #-------------------------------------------------------------------------------
2179 # For windows platform only
2180 if guihelper.IsMSWindows():
2181     import struct
2182     class DeviceChanged:
2183 
2184         DBT_DEVICEARRIVAL = 0x8000
2185         DBT_DEVICEQUERYREMOVE = 0x8001
2186         DBT_DEVICEQUERYREMOVEFAILED = 0x8002
2187         DBT_DEVICEREMOVEPENDING =  0x8003
2188         DBT_DEVICEREMOVECOMPLETE = 0x8004
2189         DBT_DEVICETYPESPECIFIC = 0x8005    
2190         DBT_DEVNODES_CHANGED = 7
2191         DBT_CONFIGCHANGED = 0x18
2192 
2193         DBT_DEVTYP_OEM = 0
2194         DBT_DEVTYP_DEVNODE = 1
2195         DBT_DEVTYP_VOLUME = 2
2196         DBT_DEVTYP_PORT = 3
2197         DBT_DEVTYP_NET = 4
2198 
2199         DBTF_MEDIA   =   0x0001
2200         DBTF_NET    =    0x0002
2201 
2202         def __init__(self, wparam, lparam):
2203             self._info=None
2204             for name in dir(self):
2205                 if name.startswith("DBT") and \
2206                    not name.startswith("DBT_DEVTYP") and \
2207                    getattr(self,name)==wparam:
2208                     self._info=(name, dict(self._decode_struct(lparam)))
2209 
2210         def GetEventInfo(self):
2211             return self._info
2212             
2213         def _decode_struct(self, lparam):
2214             if lparam==0: return ()
2215             format = "iii"
2216             buf = win32gui.PyMakeBuffer(struct.calcsize(format), lparam)
2217             dbch_size, dbch_devicetype, dbch_reserved = struct.unpack(format, buf)
2218 
2219             buf = win32gui.PyMakeBuffer(dbch_size, lparam) # we know true size now
2220 
2221             if dbch_devicetype==self.DBT_DEVTYP_PORT:
2222                 name=""
2223                 for b in buf[struct.calcsize(format):]:
2224                     if b!="\x00":
2225                         name+=b
2226                         continue
2227                     break
2228                 return ("name", name),
2229 
2230             if dbch_devicetype==self.DBT_DEVTYP_VOLUME:
2231                 # yes, the last item is a WORD, not a DWORD like the hungarian would lead you to think
2232                 format="iiiih0i"
2233                 dbcv_size, dbcv_devicetype, dbcv_reserved, dbcv_unitmask, dbcv_flags = struct.unpack(format, buf)
2234                 units=[chr(ord('A')+x) for x in range(26) if dbcv_unitmask&(2**x)]
2235                 flag=""
2236                 for name in dir(self):
2237                     if name.startswith("DBTF_") and getattr(self, name)==dbcv_flags:
2238                         flag=name
2239                         break
2240 
2241                 return ("drives", units), ("flag", flag)
2242 
2243             print "unhandled devicetype struct", dbch_devicetype
2244             return ()
2245 

Generated by PyXR 0.9.4