RE: Problem with UNICODE filenames still not fixed :( (part 3 of 3)

"Simon C" <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <000801c5cc3b$5e75c380$6400a8c0@HOME>
Part 3 of 3


Simon
guiwidgets.py (text/plain, 79.3 KB)
#!/usr/bin/env python

### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: guiwidgets.py,v 1.268 2005/09/29 12:17:02 sawecw Exp $

"""Most of the graphical user interface elements making up BitPim"""

# standard modules
import os
import sys
import time
import copy
import StringIO
import getpass
import sha,md5
import zlib
import base64
import thread
import Queue
import shutil
import time

# wx. modules
import wx
import wx.html
import wx.lib.mixins.listctrl
import wx.lib.intctrl
import wx.lib.newevent

# my modules
import common
import helpids
import comscan
import usbscan
import comdiagnose
import analyser
import guihelper
import pubsub
import bphtml
import bitflingscan
import aggregatedisplay
import phone_media_codec
import pubsub

###
### BitFling cert stuff
###

BitFlingCertificateVerificationEvent, EVT_BITFLINGCERTIFICATEVERIFICATION = wx.lib.newevent.NewEvent()

####
#### A simple text widget that does nice pretty logging.
####        

    
class LogWindow(wx.Panel):

    theanalyser=None
    
    def __init__(self, parent):
        wx.Panel.__init__(self,parent, -1)
        # have to use rich2 otherwise fixed width font isn't used on windows
        self.tb=wx.TextCtrl(self, 1, style=wx.TE_MULTILINE| wx.TE_RICH2|wx.TE_DONTWRAP|wx.TE_READONLY)
        f=wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL )
        ta=wx.TextAttr(font=f)
        self.tb.SetDefaultStyle(ta)
        self.sizer=wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.tb, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(True)
        self.sizer.Fit(self)
        wx.EVT_IDLE(self, self.OnIdle)
        self.outstandingtext=StringIO.StringIO()

        wx.EVT_KEY_UP(self.tb, self.OnKeyUp)

    def Clear(self):
        self.tb.Clear()

    def OnSelectAll(self, _):
        self.tb.SetSelection(-1, -1)

    def OnIdle(self,_):
        if self.outstandingtext.tell():
            # this code is written to be re-entrant
            newt=self.outstandingtext.getvalue()
            self.outstandingtext.seek(0)
            self.outstandingtext.truncate()
            self.tb.AppendText(newt)

    def log(self, str, nl=True):
        now=time.time()
        t=time.localtime(now)
        self.outstandingtext.write("%d:%02d:%02d.%03d " % ( t[3], t[4], t[5],  int((now-int(now))*1000)))
        self.outstandingtext.write(str)
        if nl:
            self.outstandingtext.write("\n")

    def logdata(self, str, data, klass=None):
        o=self.outstandingtext
        self.log(str, nl=False)
        if data is not None:
            o.write(" Data - "+`len(data)`+" bytes\n")
            if klass is not None:
                try:
                    o.write("<#! %s.%s !#>\n" % (klass.__module__, klass.__name__))
                except:
                    klass=klass.__class__
                    o.write("<#! %s.%s !#>\n" % (klass.__module__, klass.__name__))
            o.write(common.datatohexstring(data))
        o.write("\n")

    def OnKeyUp(self, evt):
        keycode=evt.GetKeyCode()
        if keycode==ord('P') and evt.ControlDown() and evt.AltDown():
            # analyse what was selected
            data=self.tb.GetStringSelection()
            # or the whole buffer if it was nothing
            if data is None or len(data)==0:
                data=self.tb.GetValue()
            try:
                self.theanalyser.Show()
            except:
                self.theanalyser=None
                
            if self.theanalyser is None:
                self.theanalyser=analyser.Analyser(data=data)

            self.theanalyser.Show()
            self.theanalyser.newdata(data)
            evt.Skip()
            


###
### Dialog asking what you want to sync
###

class GetPhoneDialog(wx.Dialog):
    # sync sources ("Pretty Name", "name used to query profile")
    sources= ( ('PhoneBook', 'phonebook'),
                ('Calendar', 'calendar'),
                ('Wallpaper', 'wallpaper'),
                ('Ringtone', 'ringtone'),
               ('Memo', 'memo'),
               ('Todo', 'todo'),
               ('SMS', 'sms'),
               ('Call History', 'call_history'))
    
    # actions ("Pretty Name", "name used to query profile")
    actions = (  ("Get", "read"), )

    NOTREQUESTED=0
    MERGE=1
    OVERWRITE=2

    # type of action ("pretty name", "name used to query profile")
    types= ( ("Add", MERGE),
             ("Replace All", OVERWRITE))

    HELPID=helpids.ID_GET_PHONE_DATA

    def __init__(self, frame, title, id=-1):
        wx.Dialog.__init__(self, frame, id, title,
                          style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE)
        gs=wx.FlexGridSizer(2+len(self.sources), 1+len(self.types),5 ,10)
        gs.AddGrowableCol(1)
        gs.AddMany( [
            (wx.StaticText(self, -1, "Source"), 0, wx.EXPAND),])

        for pretty,_ in self.types:
            gs.Add(wx.StaticText(self, -1, pretty), 0, wx.ALIGN_CENTRE)


        self.cb=[]
        self.rb=[]

        for desc, source in self.sources:
            self.cb.append(wx.CheckBox(self, wx.NewId(), desc))
            wx.EVT_CHECKBOX(self, self.cb[-1].GetId(), self.DoOkStatus)
            gs.Add(self.cb[-1], 0, wx.EXPAND)
            first=True
            for tdesc,tval in self.types:
                if first:
                    style=wx.RB_GROUP
                    first=0
                else:
                    style=0
                self.rb.append( wx.RadioButton(self, -1, "", style=style) )
                if not self._dowesupport(source, self.actions[0][1], tval):
                    self.rb[-1].Enable(False)
                    self.rb[-1].SetValue(False)
                gs.Add(self.rb[-1], 0, wx.ALIGN_CENTRE)

        bs=wx.BoxSizer(wx.VERTICAL)
        bs.Add(gs, 0, wx.EXPAND|wx.ALL, 10)
        bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 7)
        
        but=self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP)
        bs.Add(but, 0, wx.EXPAND|wx.ALL, 10)
        
        self.SetSizer(bs)
        self.SetAutoLayout(True)
        bs.Fit(self)

        wx.EVT_BUTTON(self, wx.ID_HELP, self.OnHelp)

    def _setting(self, type):
        for index in range(len(self.sources)):
            if self.sources[index][1]==type:
                if not self.cb[index].GetValue():
                    print type,"not requested"
                    return self.NOTREQUESTED
                for i in range(len(self.types)):
                    if self.rb[index*len(self.types)+i].GetValue():
                        print type,self.types[i][1]
                        return self.types[i][1]
                assert False, "No selection for "+type
        assert False, "No such type "+type

    def GetPhoneBookSetting(self):
        return self._setting("phonebook")

    def GetCalendarSetting(self):
        return self._setting("calendar")

    def GetWallpaperSetting(self):
        return self._setting("wallpaper")

    def GetRingtoneSetting(self):
        return self._setting("ringtone")

    def GetMemoSetting(self):
        return self._setting("memo")

    def GetTodoSetting(self):
        return self._setting("todo")

    def GetSMSSetting(self):
        return self._setting("sms")
    def GetCallHistorySetting(self):
        return self._setting("call_history")

    def OnHelp(self,_):
        wx.GetApp().displayhelpid(self.HELPID)

    # this is what BitPim itself supports - the phones may support a subset
    _notsupported=(
        # ('phonebook', 'read', MERGE), # sort of is
        ('calendar', 'read', MERGE),
        ('wallpaper', 'read', MERGE),
        ('ringtone', 'read', MERGE),
        ('memo', 'read', MERGE),
        ('todo', 'read', MERGE))

    def _dowesupport(self, source, action, type):
        if (source,action,type) in self._notsupported:
            return False
        return True

    def UpdateWithProfile(self, profile):
        for cs in range(len(self.sources)):
            source=self.sources[cs][1]
            # we disable the checkbox
            self.cb[cs].Enable(False)
            # are any radio buttons enabled
            count=0
            for i in range(len(self.types)):
                assert len(self.types)==2
                if self.types[i][1]==self.MERGE:
                    type="MERGE"
                elif self.types[i][1]==self.OVERWRITE:
                    type="OVERWRITE"
                else:
                    assert False
                    continue
                if self._dowesupport(source, self.actions[0][1], self.types[i][1]) and \
                       profile.SyncQuery(source, self.actions[0][1], type):
                    self.cb[cs].Enable(True)
                    self.rb[cs*len(self.types)+i].Enable(True)
                    if self.rb[cs*len(self.types)+i].GetValue():
                        count+=1
                else:
                    self.rb[cs*len(self.types)+i].Enable(False)
                    self.rb[cs*len(self.types)+i].SetValue(False)
            if not self.cb[cs].IsEnabled():
                # ensure checkbox is unchecked if not enabled
                self.cb[cs].SetValue(False)
            else:
                # ensure one radio button is checked
                if count!=1:
                    done=False
                    for i in range(len(self.types)):
                        index=cs*len(self.types)+i
                        if self.rb[index].IsEnabled():
                            self.rb[index].SetValue(not done)
                            done=False
                            
    def ShowModal(self):
        # we ensure the OK button is in the correct state
        self.DoOkStatus()
        return wx.Dialog.ShowModal(self)

    def DoOkStatus(self, evt=None):
        # ensure the OK button is in the right state
        enable=False
        for i in self.cb:
            if i.GetValue():
                enable=True
                break
        self.FindWindowById(wx.ID_OK).Enable(enable)
        if evt is not None:
            evt.Skip()

class SendPhoneDialog(GetPhoneDialog):
    HELPID=helpids.ID_SEND_PHONE_DATA

    # actions ("Pretty Name", "name used to query profile")
    actions = (  ("Send", "write"), )
    
    def __init__(self, frame, title, id=-1):
        GetPhoneDialog.__init__(self, frame, title, id)

    # this is what BitPim itself doesn't supports - the phones may support less
    _notsupported=(
        ('call_history', 'write', None),)
        

###
###  The master config dialog
###

class ConfigDialog(wx.Dialog):
    phonemodels={ 'LG-VX3200': 'com_lgvx3200',
                  'LG-VX4400': 'com_lgvx4400',
                  'LG-VX4500': 'com_lgvx4500',
                  'LG-VX4600 (Telus Mobility)': 'com_lgvx4600',
                  'LG-VX4650 (Verizon Wireless)': 'com_lgvx4650',
                  'LG-VX6000': 'com_lgvx6000',
                  'LG-VX6100': 'com_lgvx6100',
                  'LG-VX7000': 'com_lgvx7000',
                  'LG-VX8000 (Verizon Wireless)': 'com_lgvx8000',
                  'LG-VX8100 (Verizon Wireless)': 'com_lgvx8100',
                  'LG-TM520': 'com_lgtm520',
                  'LG-VX10': 'com_lgtm520',
                  'MM-7400': 'com_sanyo7400',
                  'PM-8200': 'com_sanyo8200',
                  'RL-4920': 'com_sanyo4920',
                  'SCP-4900': 'com_sanyo4900',
                  'SCP-5300': 'com_sanyo5300',
                  'SCP-5400': 'com_sanyo5400',
                  'SCP-5500': 'com_sanyo5500',
                  'SCP-7200': 'com_sanyo7200',
                  'SCP-7300': 'com_sanyo7300',
                  'SCP-8100': 'com_sanyo8100',
                  'SCP-8100 (Bell Mobility)': 'com_sanyo8100_bell',
                  'SCH-A310': 'com_samsungscha310',
                  'SPH-A460': 'com_samsungspha460',
                  'SPH-A620 (VGA1000)': 'com_samsungspha620',
                  'SPH-N200': 'com_samsungsphn200',
                  'SCH-A650': 'com_samsungscha650',
                  'SCH-A670': 'com_samsungscha670',
                  'SK6100 (Pelephone)' : 'com_sk6100', 
                  'Other CDMA phone': 'com_othercdma',
                  }

    if __debug__:
        phonemodels.update( {'Audiovox CDM-8900': 'com_audiovoxcdm8900',     # phone is too fragile for normal use
                             'LG-PM325 (Sprint)': 'com_lgpm325',
                             })
    update_choices=('Never', 'Daily', 'Weekly', 'Monthly')
    setme="<setme>"
    ID_DIRBROWSE=wx.NewId()
    ID_COMBROWSE=wx.NewId()
    ID_RETRY=wx.NewId()
    ID_BITFLING=wx.NewId()
    def __init__(self, mainwindow, frame, title="BitPim Settings", id=-1):
        wx.Dialog.__init__(self, frame, id, title,
                          style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE)
        self.mw=mainwindow

        self.bitflingresponsequeues={}

        gs=wx.GridBagSizer(10, 10)
        gs.AddGrowableCol(1)

        # safemode
        gs.Add( wx.StaticText(self, -1, "Read Only"), pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.safemode=wx.CheckBox(self, wx.NewId(), "Block writing anything to the phone")
        gs.Add( self.safemode, pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL)

        # where we store our files
        gs.Add( wx.StaticText(self, -1, "Disk storage"), pos=(1,0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.diskbox=wx.TextCtrl(self, -1, self.setme, size=(400,-1))
        gs.Add( self.diskbox, pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL)
        gs.Add( wx.Button(self, self.ID_DIRBROWSE, "Browse ..."), pos=(1,2), flag=wx.ALIGN_CENTER_VERTICAL)

        # phone type
        gs.Add( wx.StaticText(self, -1, "Phone Type"), pos=(2,0), flag=wx.ALIGN_CENTER_VERTICAL)
        keys=self.phonemodels.keys()
        keys.sort()
        self.phonebox=wx.ComboBox(self, -1, "LG-VX4400", style=wx.CB_DROPDOWN|wx.CB_READONLY,choices=keys)
        self.phonebox.SetValue("LG-VX4400")
        gs.Add( self.phonebox, pos=(2,1), flag=wx.ALIGN_CENTER_VERTICAL)

        # com port
        gs.Add( wx.StaticText(self, -1, "Com Port"), pos=(3,0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.commbox=wx.TextCtrl(self, -1, self.setme, size=(200,-1))
        gs.Add( self.commbox, pos=(3,1), flag=wx.ALIGN_CENTER_VERTICAL)
        gs.Add( wx.Button(self, self.ID_COMBROWSE, "Browse ..."), pos=(3,2), flag=wx.ALIGN_CENTER_VERTICAL)

        # Automatic check for update
        gs.Add(wx.StaticText(self, -1, 'Check for Update'), pos=(4,0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.updatebox=wx.ComboBox(self, -1, self.update_choices[0],
                                   style=wx.CB_DROPDOWN|wx.CB_READONLY,
                                   choices=self.update_choices)
        gs.Add(self.updatebox, pos=(4,1), flag=wx.ALIGN_CENTER_VERTICAL)
        # always start with the 'Today' tab
        gs.Add(wx.StaticText(self, -1, 'Startup'), pos=(5,0),
               flag=wx.ALIGN_CENTER_VERTICAL)
        self.startup=wx.CheckBox(self, wx.NewId(), 'Always start with the Today tab')
        gs.Add(self.startup, pos=(5,1), flag=wx.ALIGN_CENTER_VERTICAL)
        # bitfling
        if bitflingscan.IsBitFlingEnabled():
            self.SetupBitFlingCertVerification()
            gs.Add( wx.StaticText( self, -1, "BitFling"), pos=(6,0), flag=wx.ALIGN_CENTER_VERTICAL)
            self.bitflingenabled=wx.CheckBox(self, self.ID_BITFLING, "Enabled")
            gs.Add(self.bitflingenabled, pos=(6,1), flag=wx.ALIGN_CENTER_VERTICAL)
            gs.Add( wx.Button(self, self.ID_BITFLING, "Settings ..."), pos=(6,2), flag=wx.ALIGN_CENTER_VERTICAL)
            wx.EVT_BUTTON(self, self.ID_BITFLING, self.OnBitFlingSettings)
            wx.EVT_CHECKBOX(self, self.ID_BITFLING, self.ApplyBitFlingSettings)
            if self.mw.config.Read("bitfling/password","<unconfigured>") \
               == "<unconfigured>":
                self.mw.config.WriteInt("bitfling/enabled", 0)
                self.bitflingenabled.SetValue(False)
                self.bitflingenabled.Enable(False)
        else:
            self.bitflingenabled=None
        # crud at the bottom
        bs=wx.BoxSizer(wx.VERTICAL)
        bs.Add(gs, 0, wx.EXPAND|wx.ALL, 10)
        bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 7)
        
        but=self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP)
        bs.Add(but, 0, wx.CENTER|wx.ALL, 10)

        wx.EVT_BUTTON(self, wx.ID_HELP, self.OnHelp)
        wx.EVT_BUTTON(self, self.ID_DIRBROWSE, self.OnDirBrowse)
        wx.EVT_BUTTON(self, self.ID_COMBROWSE, self.OnComBrowse)
        wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK)

        self.setdefaults()

        self.SetSizer(bs)
        self.SetAutoLayout(True)
        bs.Fit(self)

        # Retrieve saved settings... (we only care about position)
        set_size("ConfigDialog", self, screenpct=-1,  aspect=3.5)

        wx.EVT_CLOSE(self, self.OnClose)

    def OnCancel(self, _):
        self.saveSize()

    def OnOK(self, _):
        self.saveSize()
        # validate directory
        dir=self.diskbox.GetValue()
        try:
            os.makedirs(dir)
        except:
            pass
        if os.path.isdir(dir):
            self.EndModal(wx.ID_OK)
            self.ApplyBitFlingSettings()
            return
        wx.TipWindow(self.diskbox, "No such directory - please correct")
            

    def OnHelp(self, _):
        wx.GetApp().displayhelpid(helpids.ID_SETTINGS_DIALOG)

    def OnDirBrowse(self, _):
        dlg=wx.DirDialog(self, defaultPath=self.diskbox.GetValue(), style=wx.DD_NEW_DIR_BUTTON)
        res=dlg.ShowModal()
        v=dlg.GetPath()
        dlg.Destroy()
        if res==wx.ID_OK:
            self.diskbox.SetValue(v)

    def OnComBrowse(self, _):
        self.saveSize()
        if self.mw.wt is not None:
            self.mw.wt.clearcomm()
        # remember its size
        # w=self.mw.config.ReadInt("combrowsewidth", 640)
        # h=self.mw.config.ReadInt("combrowseheight", 480)
        p=self.mw.config.ReadInt("combrowsesash", 200)
        dlg=CommPortDialog(self, __import__(self.phonemodels[self.phonebox.GetValue()]), defaultport=self.commbox.GetValue(), sashposition=p)
        # dlg.SetSize(wx.Size(w,h))
        # dlg.Centre()
        res=dlg.ShowModal()
        v=dlg.GetPort()
        
        # sz=dlg.GetSize()
        # self.mw.config.WriteInt("combrowsewidth", sz.GetWidth())
        # self.mw.config.WriteInt("combrowseheight", sz.GetHeight())

        self.mw.config.WriteInt("combrowsesash", dlg.sashposition)
        dlg.Destroy()
        if res==wx.ID_OK:
            self.commbox.SetValue(v)

    def ApplyBitFlingSettings(self, _=None):
        if self.bitflingenabled is not None:
            if self.bitflingenabled.GetValue():
                bitflingscan.flinger.configure(self.mw.config.Read("bitfling/username", "<unconfigured>"),
                                               bitflingscan.decode(self.mw.config.Read("bitfling/password",
                                                                                       "<unconfigured>")),
                                               self.mw.config.Read("bitfling/host", "<unconfigured>"),
                                               self.mw.config.ReadInt("bitfling/port", 12652))
            else:
                bitflingscan.flinger.unconfigure()

    def OnBitFlingSettings(self, _):
        dlg=BitFlingSettingsDialog(None, self.mw.config)
        if dlg.ShowModal()==wx.ID_OK:
            dlg.SaveSettings()
        dlg.Destroy()
        self.ApplyBitFlingSettings()
        if self.mw.config.Read("bitfling/password","<unconfigured>") \
               != "<unconfigured>":
            self.bitflingenabled.Enable(True)
        
    def SetupBitFlingCertVerification(self):
        "Setup all the voodoo needed for certificate verification to happen, not matter which thread wants it"
        EVT_BITFLINGCERTIFICATEVERIFICATION(self, self._wrapVerifyBitFlingCert)
        bitflingscan.flinger.SetCertVerifier(self.dispatchVerifyBitFlingCert)
        bitflingscan.flinger.setthreadeventloop(wx.SafeYield)

    def dispatchVerifyBitFlingCert(self, addr, key):
        """Handle a certificate verification from any thread

        The request is handed to the main gui thread, and then we wait for the
        results"""
        print thread.get_ident(),"dispatchVerifyBitFlingCert called"
        q=self.bitflingresponsequeues.get(thread.get_ident(), None)
        if q is None:
            q=Queue.Queue()
            self.bitflingresponsequeues[thread.get_ident()]=q
        print thread.get_ident(), "Posting BitFlingCertificateVerificationEvent"
        wx.PostEvent(self, BitFlingCertificateVerificationEvent(addr=addr, key=key, q=q))
        print thread.get_ident(), "After posting BitFlingCertificateVerificationEvent, waiting for response"
        res, exc = q.get()
        print thread.get_ident(), "Got response", res, exc
        if exc is not None:
            ex=exc[1]
            ex.gui_exc_info=exc[2]
            raise ex
        return res
        
    def _wrapVerifyBitFlingCert(self, evt):
        """Receive the event in the main gui thread for cert verification

        We unpack the parameters, call the verification method"""
        print "_wrapVerifyBitFlingCert"
        
        addr, hostkey, q = evt.addr, evt.key, evt.q
        self.VerifyBitFlingCert(addr, hostkey, q)

    def VerifyBitFlingCert(self, addr, key, q):
        print "VerifyBitFlingCert for", addr, "type",key.get_name()
        # ::TODO:: reject if not dsa
        # get fingerprint
        fingerprint=common.hexify(key.get_fingerprint())
        # do we already know about it?
        existing=wx.GetApp().config.Read("bitfling/certificates/%s" % (addr[0],), "")
        if len(existing):
            fp=existing
            if fp==fingerprint:
                q.put( (True, None) )
                return
        # throw up the dialog
        print "asking user"
        dlg=AcceptCertificateDialog(None, wx.GetApp().config, addr, fingerprint, q)
        dlg.ShowModal()

    def OnClose(self, evt):
        self.saveSize()
        # Don't destroy the dialong, just put it away...
        self.EndModal(wx.ID_CANCEL)

    def setfromconfig(self):
        if len(self.mw.config.Read("path", "")):
            self.diskbox.SetValue(self.mw.config.Read("path", ""))
        if len(self.mw.config.Read("lgvx4400port")):
            self.commbox.SetValue(self.mw.config.Read("lgvx4400port", ""))
        if self.mw.config.Read("phonetype", "") in self.phonemodels:
            self.phonebox.SetValue(self.mw.config.Read("phonetype"))
        if self.bitflingenabled is not None:
            self.bitflingenabled.SetValue(self.mw.config.ReadInt("bitfling/enabled", 0))
            self.ApplyBitFlingSettings()
        self.safemode.SetValue(self.mw.config.ReadInt("Safemode", 0))
        self.updatebox.SetValue(self.mw.config.Read("updaterate",
                                                    self.update_choices[0]))
        self.startup.SetValue(self.mw.config.ReadInt("startwithtoday", 0))

    def setdefaults(self):
        if self.diskbox.GetValue()==self.setme:
            if guihelper.IsMSWindows(): # we want subdir of my documents on windows
                    # nice and painful
                    from win32com.shell import shell, shellcon
                    path=shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
                    path=os.path.join(str(path), "bitpim")
            else:
                path=os.path.expanduser("~/.bitpim-files")
            self.diskbox.SetValue(path)
        if self.commbox.GetValue()==self.setme:
            comm="auto"
            self.commbox.SetValue(comm)

    def updatevariables(self):
        path=self.diskbox.GetValue()
        self.mw.configpath=path
        self.mw.ringerpath=self._fixup(os.path.join(path, "ringer"))
        self.mw.wallpaperpath=self._fixup(os.path.join(path, "wallpaper"))
        self.mw.phonebookpath=self._fixup(os.path.join(path, "phonebook"))
        self.mw.calendarpath=self._fixup(os.path.join(path, "calendar"))
        oldpath=self.mw.config.Read("path", "")
        self.mw.config.Write("path", path)
        self.mw.commportsetting=str(self.commbox.GetValue())
        self.mw.config.Write("lgvx4400port", self.mw.commportsetting)
        if self.mw.wt is not None:
            self.mw.wt.clearcomm()
        # comm parameters (retry, timeouts, flow control etc)
        commparm={}
        commparm['retryontimeout']=self.mw.config.ReadInt("commretryontimeout", False)
        commparm['timeout']=self.mw.config.ReadInt('commtimeout', 3)
        commparm['hardwareflow']=self.mw.config.ReadInt('commhardwareflow', False)
        commparm['softwareflow']=self.mw.config.ReadInt('commsoftwareflow', False)
        commparm['baud']=self.mw.config.ReadInt('commbaud', 115200)
        self.mw.commparams=commparm
        # phone model
        self.mw.config.Write("phonetype", self.phonebox.GetValue())
        self.mw.phonemodule=__import__(self.phonemodels[self.phonebox.GetValue()])
        self.mw.phoneprofile=self.mw.phonemodule.Profile()
        pubsub.publish(pubsub.PHONE_MODEL_CHANGED, self.mw.phonemodule)
        #  bitfling
        if self.bitflingenabled is not None:
            self.mw.bitflingenabled=self.bitflingenabled.GetValue()
            self.mw.config.WriteInt("bitfling/enabled", self.mw.bitflingenabled)
        # safemode - make sure you have to restart to disable
        self.mw.config.WriteInt("SafeMode", self.safemode.GetValue())
        if self.safemode.GetValue():
            wx.GetApp().SAFEMODE=True
        wx.GetApp().ApplySafeMode()
        # check for update rate
        self.mw.config.Write('updaterate', self.updatebox.GetValue())
        # startup option
        self.mw.config.WriteInt('startwithtoday', self.startup.GetValue())
        # ensure config is saved
        self.mw.config.Flush()
        self.mw.EnsureDatabase(path, oldpath)
        # update the status bar
        self.mw.SetPhoneModelStatus()
        # update the cache path
        self.mw.update_cache_path()

    def _fixup(self, path):
        # os.path.join screws up adding root directory of a drive to
        # a directory.  eg join("c:\", "foo") gives "c:\\foo" whch
        # is invalid.  This function fixes that
        if len(path)>=3:
            if path[1]==':' and path[2]=='\\' and path[3]=='\\':
                return path[0:2]+path[3:]
        return path
        
    def needconfig(self):
        # Set base config
        self.setfromconfig()
        # do we know the phone?
        if self.mw.config.Read("phonetype", "") not in self.phonemodels:
            return True
        # are any at unknown settings
        if self.diskbox.GetValue()==self.setme or \
           self.commbox.GetValue()==self.setme:
            # fill in and set defaults
            self.setdefaults()
            self.updatevariables()
            # any still unset?
            if self.diskbox.GetValue()==self.setme or \
                   self.commbox.GetValue()==self.setme:
                return True
        # does data directory exist?
        try:
            os.makedirs(self.diskbox.GetValue())
        except:
            pass
        if not os.path.isdir(self.diskbox.GetValue()):
            return True

        return False

    def ShowModal(self):
        self.setfromconfig()
        ec=wx.Dialog.ShowModal(self)
        if ec==wx.ID_OK:
            self.updatevariables()
        return ec

    def saveSize(self):
        save_size("ConfigDialog", self.GetRect())

###
### The select a comm port dialog box
###

class CommPortDialog(wx.Dialog):
    ID_LISTBOX=1
    ID_TEXTBOX=2
    ID_REFRESH=3
    ID_SASH=4
    ID_SAVE=5
    
    def __init__(self, parent, selectedphone, id=-1, title="Choose a comm port", defaultport="auto", sashposition=0):
        wx.Dialog.__init__(self, parent, id, title, style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self.parent=parent
        self.port=defaultport
        self.sashposition=sashposition
        self.selectedphone=selectedphone
        
        p=self # parent widget

        # the listbox and textbox in a splitter
        splitter=wx.SplitterWindow(p, self.ID_SASH, style=wx.SP_3D|wx.SP_LIVE_UPDATE)
        self.lb=wx.ListBox(splitter, self.ID_LISTBOX, style=wx.LB_SINGLE|wx.LB_HSCROLL|wx.LB_NEEDED_SB)
        self.tb=wx.html.HtmlWindow(splitter, self.ID_TEXTBOX, size=wx.Size(400,400)) # default style is auto scrollbar
        splitter.SplitHorizontally(self.lb, self.tb, sashposition)

        # the buttons
        buttsizer=wx.GridSizer(1, 5)
        buttsizer.Add(wx.Button(p, wx.ID_OK, "OK"), 0, wx.ALL, 10)
        buttsizer.Add(wx.Button(p, self.ID_REFRESH, "Refresh"), 0, wx.ALL, 10)
        buttsizer.Add(wx.Button(p, self.ID_SAVE, "Save..."), 0, wx.ALL, 10)
        buttsizer.Add(wx.Button(p, wx.ID_HELP, "Help"), 0, wx.ALL, 10)
        buttsizer.Add(wx.Button(p, wx.ID_CANCEL, "Cancel"), 0, wx.ALL, 10)

        # vertical join of the two
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(splitter, 1, wx.EXPAND)
        vbs.Add(buttsizer, 0, wx.CENTER)

        # hook into self
        p.SetSizer(vbs)
        p.SetAutoLayout(True)
        vbs.Fit(p)

        # update dialog
        wx.CallAfter(self.OnRefresh)

        # hook in all the widgets
        wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnCancel)
        wx.EVT_BUTTON(self, wx.ID_HELP, self.OnHelp)
        wx.EVT_BUTTON(self, self.ID_REFRESH, self.OnRefresh)
        wx.EVT_BUTTON(self, self.ID_SAVE, self.OnSave)
        wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
        wx.EVT_LISTBOX(self, self.ID_LISTBOX, self.OnListBox)
        wx.EVT_LISTBOX_DCLICK(self, self.ID_LISTBOX, self.OnListBox)
        wx.EVT_SPLITTER_SASH_POS_CHANGED(self, self.ID_SASH, self.OnSashChange)

        # Retrieve saved settings... Use 40% of screen if not specified
        set_size("CommDialog", self, screenpct=60)
        wx.EVT_CLOSE(self, self.OnClose)

    def OnSashChange(self, _=None):
        self.sashposition=self.FindWindowById(self.ID_SASH).GetSashPosition()

    def OnRefresh(self, _=None):
        self.tb.SetPage("<p><b>Refreshing</b> ...")
        self.lb.Clear()
        self.Update()
        ports=comscan.comscan()+usbscan.usbscan()
        if bitflingscan.IsBitFlingEnabled():
            ports=ports+bitflingscan.flinger.scan()
        self.portinfo=comdiagnose.diagnose(ports, self.selectedphone)
        if len(self.portinfo):
            self.portinfo=[ ("Automatic", "auto",
                             "<p>BitPim will try to detect the correct port automatically when accessing your phone"
                             ) ]+\
                           self.portinfo
        self.lb.Clear()
        sel=-1
        for name, actual, description in self.portinfo:
            if sel<0 and self.GetPort()==actual:
                sel=self.lb.GetCount()
            self.lb.Append(name)
        if sel<0:
            sel=0
        if self.lb.GetCount():
            self.lb.SetSelection(sel)
            self.OnListBox()
        else:
            self.FindWindowById(wx.ID_OK).Enable(False)
            self.tb.SetPage("<html><body>You do not have any com/serial ports on your system</body></html>")

    def OnListBox(self, _=None):
        # enable/disable ok button
        p=self.portinfo[self.lb.GetSelection()]
        if p[1] is None:
            self.FindWindowById(wx.ID_OK).Enable(False)
        else:
            self.port=p[1]
            self.FindWindowById(wx.ID_OK).Enable(True)
        self.tb.SetPage(p[2])
        

    def OnSave(self, _):
        html=StringIO.StringIO()
        
        print >>html, "<html><head><title>BitPim port listing - %s</title></head>" % (time.ctime(), )
        print >>html, "<body><h1>BitPim port listing - %s</h1><table>" % (time.ctime(),)

        for long,actual,desc in self.portinfo:
            if actual is None or actual=="auto": continue
            print >>html, '<tr  bgcolor="#77ff77"><td colspan=2>%s</td><td>%s</td></tr>' % (long,actual)
            print >>html, "<tr><td colspan=3>%s</td></tr>" % (desc,)
            print >>html, "<tr><td colspan=3><hr></td></tr>"
        print >>html, "</table></body></html>"
        dlg=wx.FileDialog(self, "Save port details as", defaultFile="bitpim-ports.html", wildcard="HTML files (*.html)|*.html",
                         style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
        if dlg.ShowModal()==wx.ID_OK:
            open(dlg.GetPath(), "wt").write(html.getvalue())
        dlg.Destroy()

    def OnCancel(self, _):
        self.saveSize()
        self.EndModal(wx.ID_CANCEL)

    def OnOk(self, _):
        self.saveSize()
        self.EndModal(wx.ID_OK)

    def OnHelp(self, _):
        wx.GetApp().displayhelpid(helpids.ID_COMMSETTINGS_DIALOG)

    def OnClose(self, evt):
        self.saveSize()
        # Don't destroy the dialong, just put it away...
        self.EndModal(wx.ID_CANCEL)

    def GetPort(self):
        return self.port

    def saveSize(self):
        save_size("CommDialog", self.GetRect())

###
###  Accept certificate dialog
###


class AcceptCertificateDialog(wx.Dialog):

    def __init__(self, parent, config, addr, fingerprint, q):
        parent=self.FindAGoodParent(parent)
        wx.Dialog.__init__(self, parent, -1, "Accept certificate?", style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self.config=config
        self.q=q
        self.addr=addr
        self.fingerprint=fingerprint
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        hbs.Add(wx.StaticText(self, -1, "Host:"), 0, wx.ALL, 5)
        hbs.Add(wx.StaticText(self, -1, addr[0]), 0, wx.ALL, 5)
        hbs.Add(wx.StaticText(self, -1, " Fingerprint:"), 0, wx.ALL, 5)
        hbs.Add(wx.StaticText(self, -1, fingerprint), 1, wx.ALL, 5)
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
        vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 7)
        but=self.CreateButtonSizer(wx.YES|wx.NO|wx.HELP)
        vbs.Add(but, 0, wx.ALIGN_CENTER|wx.ALL, 10)

        self.SetSizer(vbs)
        vbs.Fit(self)

        wx.EVT_BUTTON(self, wx.ID_YES, self.OnYes)
        wx.EVT_BUTTON(self, wx.ID_NO, self.OnNo)
        wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnNo)



    def OnYes(self, _):
        wx.GetApp().config.Write("bitfling/certificates/%s" % (self.addr[0],), self.fingerprint)
        wx.GetApp().config.Flush()
        if self.IsModal():
            self.EndModal(wx.ID_YES)
        else:
            self.Show(False)
        wx.CallAfter(self.Destroy)
        print "returning true from AcceptCertificateDialog"
        self.q.put( (True, None) )

    def OnNo(self, _):
        if self.IsModal():
            self.EndModal(wx.ID_NO)
        else:
            self.Show(False)
        wx.CallAfter(self.Destroy)
        print "returning false from AcceptCertificateDialog"
        self.q.put( (False, None) )

    def FindAGoodParent(self, suggestion):
        win=wx.Window_FindFocus()
        while win is not None:
            try:
                if win.IsModal():
                    print "FindAGoodParent is",win
                    return win
            except AttributeError:
                parent=win.GetParent()
                win=parent
        return suggestion
        
###
###  BitFling settings dialog
###

class BitFlingSettingsDialog(wx.Dialog):

    ID_USERNAME=wx.NewId()
    ID_PASSWORD=wx.NewId()
    ID_HOST=wx.NewId()
    ID_PORT=wx.NewId()
    ID_TEST=wx.NewId()
    passwordsentinel="@+_-3@<,"

    def __init__(self, parent, config):
        wx.Dialog.__init__(self, parent, -1, "Edit BitFling settings", style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self.config=config
        gs=wx.FlexGridSizer(1, 2, 5, 5)
        gs.AddGrowableCol(1)
        gs.AddMany([
            (wx.StaticText(self, -1, "Username"), 0, wx.ALIGN_CENTER_VERTICAL),
            (wx.TextCtrl(self, self.ID_USERNAME), 1, wx.EXPAND),
            (wx.StaticText(self, -1, "Password"), 0, wx.ALIGN_CENTER_VERTICAL),
            (wx.TextCtrl(self, self.ID_PASSWORD, style=wx.TE_PASSWORD), 1, wx.EXPAND),
            (wx.StaticText(self, -1, "Host"), 0, wx.ALIGN_CENTER_VERTICAL),
            (wx.TextCtrl(self, self.ID_HOST), 1, wx.EXPAND),
            (wx.StaticText(self, -1, "Port"), 0, wx.ALIGN_CENTER_VERTICAL),
            (wx.lib.intctrl.IntCtrl(self, self.ID_PORT, value=12652, min=1, max=65535), 0)
            ])
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(gs, 0, wx.EXPAND|wx.ALL, 5)
        vbs.Add((1,1), 1, wx.EXPAND)
        vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 10)

        gs=wx.GridSizer(1,4, 5,5)
        gs.Add(wx.Button(self, wx.ID_OK, "OK"))
        gs.Add(wx.Button(self, self.ID_TEST, "Test"))
        gs.Add(wx.Button(self, wx.ID_HELP, "Help"))
        gs.Add(wx.Button(self, wx.ID_CANCEL, "Cancel"))
        vbs.Add(gs, 0, wx.ALIGN_CENTER|wx.ALL, 10)
        self.SetSizer(vbs)
        vbs.Fit(self)
        set_size("BitFlingConfigDialog", self, -20, 0.5)

        # event handlers
        wx.EVT_BUTTON(self, self.ID_TEST, self.OnTest)

        # fill in data
        self.FindWindowById(self.ID_USERNAME).SetValue(config.Read("bitfling/username", getpass.getuser()))
        if len(config.Read("bitfling/password", "")):
            self.FindWindowById(self.ID_PASSWORD).SetValue(self.passwordsentinel)
        self.FindWindowById(self.ID_HOST).SetValue(config.Read("bitfling/host", ""))
        self.FindWindowById(self.ID_PORT).SetValue(config.ReadInt("bitfling/port", 12652))

    def ShowModal(self):
        res=wx.Dialog.ShowModal(self)
        save_size("BitFlingConfigDialog", self.GetRect())
        return res

    def GetSettings(self):
        username=self.FindWindowById(self.ID_USERNAME).GetValue()
        pwd=self.FindWindowById(self.ID_PASSWORD).GetValue()
        if pwd==self.passwordsentinel:
            pwd=bitflingscan.decode(self.config.Read("bitfling/password", self.passwordsentinel))
        host=self.FindWindowById(self.ID_HOST).GetValue()
        port=self.FindWindowById(self.ID_PORT).GetValue()
        return username, pwd, host, port

    def SaveSettings(self):
        "Copy settings from dialog fields into config object"
        username,pwd,host,port=self.GetSettings()
        self.config.Write("bitfling/username", username)
        self.config.Write("bitfling/password", bitflingscan.encode(pwd))
        self.config.Write("bitfling/host", host)
        self.config.WriteInt("bitfling/port", port)

    def OnTest(self, _):
        wx.CallAfter(self._OnTest)

    def _OnTest(self, _=None):
        try:
            bitflingscan.flinger.configure(*self.GetSettings())
            res=bitflingscan.flinger.getversion()
            dlg=wx.MessageDialog(self, "Succeeded. Remote version is %s" % (res,) , "Success", wx.OK|wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()
        except Exception,ex:
            res="Failed: %s: %s" % sys.exc_info()[:2]
            if hasattr(ex, "gui_exc_info"):
                print common.formatexception( ex.gui_exc_info)
            else:
                print common.formatexception()
            dlg=wx.MessageDialog(self, res, "Failed", wx.OK|wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()

###
### File viewer
###
media_codec=phone_media_codec.codec_name
class MyFileDropTarget(wx.FileDropTarget):
    def __init__(self, target):
        wx.FileDropTarget.__init__(self)
        self.target=target
        
    def OnDropFiles(self, x, y, filenames):
        return self.target.OnDropFiles(x,y,filenames)

class FileView(wx.Panel):

    # Various DC objects used for drawing the items.  We have to calculate them in the constructor as
    # the app object hasn't been constructed when this file is imported.
    item_selection_brush=None
    item_selection_pen=None
    item_line_font=None
    item_term="..."
    item_guardspace=None
    # Files we should ignore
    skiplist= ( 'desktop.ini', 'thumbs.db', 'zbthumbnail.info' )

    # how much data do we want in call to getdata
    NONE=0
    SELECTED=1
    ALL=2

    # maximum length of a filename
    maxlen=-1  # set via phone profile
    # acceptable characters in a filename
    filenamechars=None # set via phone profile

    def __init__(self, mainwindow, parent, watermark=None):
        wx.Panel.__init__(self,parent,style=wx.CLIP_CHILDREN)

        if not hasattr(self, "organizemenu"):
            self.organizemenu=None
        
        # item attributes
        if self.item_selection_brush is None:
            self.item_selection_brush=wx.TheBrushList.FindOrCreateBrush("MEDIUMPURPLE2", wx.SOLID)
            self.item_selection_pen=wx.ThePenList.FindOrCreatePen("MEDIUMPURPLE2", 1, wx.SOLID)
            f1=wx.TheFontList.FindOrCreateFont(10, wx.SWISS, wx.NORMAL, wx.BOLD)
            f2=wx.TheFontList.FindOrCreateFont(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
            self.item_line_font=[f1, f2, f2, f2]
            dc=wx.MemoryDC()
            dc.SelectObject(wx.EmptyBitmap(100,100))
            self.item_guardspace=dc.GetTextExtent(self.item_term)[0]
            del dc

        # no redraw ickiness
        # wx.EVT_ERASE_BACKGROUND(self, lambda evt: None)
        
        self.mainwindow=mainwindow
        self.thedir=None
        self.wildcard="I forgot to set wildcard in derived class|*"
        self.__dragging=False

        # use the aggregatedisplay to do the actual item display
        self.aggdisp=aggregatedisplay.Display(self, self, watermark) # we are our own datasource
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(self.aggdisp, 1, wx.EXPAND|wx.ALL, 2)
        self.SetSizer(vbs)
        timerid=wx.NewId()
        self.thetimer=wx.Timer(self, timerid)
        wx.EVT_TIMER(self, timerid, self.OnTooltipTimer)
        self.motionpos=None
        wx.EVT_MOUSE_EVENTS(self.aggdisp, self.OnMouseEvent)
        self.tipwindow=None
        if guihelper.IsMSWindows():
            # turn on drag-and-drag for windows
            wx.EVT_MOTION(self.aggdisp, self.OnStartDrag)

        # Menus

        self.itemmenu=wx.Menu()
        self.itemmenu.Append(guihelper.ID_FV_OPEN, "Open")
        self.itemmenu.Append(guihelper.ID_FV_SAVE, "Save ...")
        self.itemmenu.AppendSeparator()
        if guihelper.IsMSWindows():
            self.itemmenu.Append(guihelper.ID_FV_COPY, "Copy")
        self.itemmenu.Append(guihelper.ID_FV_DELETE, "Delete")
        self.itemmenu.Append(guihelper.ID_FV_RENAME, "Rename")
        self.itemmenu.AppendSeparator()
        # self.itemmenu.Append(guihelper.ID_FV_RENAME, "Rename")
        self.itemmenu.Append(guihelper.ID_FV_REFRESH, "Refresh")

        self.bgmenu=wx.Menu()
        if self.organizemenu is not None:
            self.bgmenu.AppendMenu(wx.NewId(), "Organize by", self.organizemenu)
        self.bgmenu.Append(guihelper.ID_FV_ADD, "Add ...")
        self.bgmenu.Append(guihelper.ID_FV_PASTE, "Paste")
        self.bgmenu.Append(guihelper.ID_FV_REFRESH, "Refresh")

        wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_OPEN, self.OnLaunch)
        wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_SAVE, self.OnSave)
        if guihelper.IsMSWindows():
            wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_COPY, self.OnCopy)
        wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_DELETE, self.OnDelete)
        wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_RENAME, self.OnRename)
        wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_REFRESH, lambda evt: self.OnRefresh())
        wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_ADD, self.OnAdd)
        wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_PASTE, self.OnPaste)
        wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_REFRESH, lambda evt: self.OnRefresh)

        wx.EVT_RIGHT_UP(self.aggdisp, self.OnRightClick)
        aggregatedisplay.EVT_ACTIVATE(self.aggdisp, self.aggdisp.GetId(), self.OnLaunch)

        self.droptarget=MyFileDropTarget(self)
        self.SetDropTarget(self.droptarget)

    def OnRightClick(self, evt):
        """Popup the right click context menu

        @param widget:  which widget to popup in
        @param position:  position in widget
        @param onitem: True if the context menu is for an item
        """
        if len(self.aggdisp.GetSelection()):
            menu=self.itemmenu
            item=self.GetSelectedItems()[0]
            menu.Enable(guihelper.ID_FV_RENAME, len(self.GetSelectedItems())==1)
            # we always launch on mac
            if not guihelper.IsMac():
                menu.FindItemById(guihelper.ID_FV_OPEN).Enable(guihelper.GetOpenCommand(item.fileinfo.mimetypes, item.filename) is not None)
        else:
            menu=self.bgmenu
            menu.Enable(guihelper.ID_FV_PASTE, self.CanPaste())
        if menu is None:
            return
        self.aggdisp.PopupMenu(menu, evt.GetPosition())

    def OnLaunch(self, _):
        item=self.GetSelectedItems()[0]
        if guihelper.IsMac():
            import findertools
            findertools.launch(item.filename)
            return
        cmd=guihelper.GetOpenCommand(item.fileinfo.mimetypes, item.filename)
        if cmd is None:
            wx.Bell()
        else:
            wx.Execute(cmd, wx.EXEC_ASYNC)

    if guihelper.IsMSWindows():
        # drag-and-drop files only works in Windows
        def OnStartDrag(self, evt):
            evt.Skip()
            if not evt.LeftIsDown():
                return
            items=self.GetSelectedItems()
            if not len(items):
                return
            drag_source=wx.DropSource(self)
            file_names=wx.FileDataObject()
            for item in items:
                file_names.AddFile(item.filename)
            drag_source.SetData(file_names)
            self.__dragging=True
            res=drag_source.DoDragDrop(wx.Drag_AllowMove)
            self.__dragging=False
            # check of any of the files have been removed,
            # can't trust result returned by DoDragDrop
            for item in items:
                # this used to use os.access function, but it does not
                # support unicode filenames
                if not os.path.isfile(item.filename):
                    item.RemoveFromIndex()

    def OnMouseEvent(self, evt):
        self.motionpos=evt.GetPosition()
        evt.Skip()
        self.thetimer.Stop()
        if evt.AltDown() or evt.MetaDown() or evt.ControlDown() or evt.ShiftDown() or evt.Dragging() or evt.IsButton():
            return
        self.thetimer.Start(1750, wx.TIMER_ONE_SHOT)

    def OnTooltipTimer(self, _):
        x,y=self.aggdisp.CalcUnscrolledPosition(*self.motionpos)
        res=self.aggdisp.HitTest(x,y)
        if res.item is not None:
            try:    self.tipwindow.Destroy()
            except: pass
            self.tipwindow=res.item.DisplayTooltip(self.aggdisp, res.itemrectscrolled)

    def OnRefresh(self):
        self.aggdisp.UpdateItems()

    def GetSelectedItems(self):
        return [item for _,_,_,item in self.aggdisp.GetSelection()]

    def GetAllItems(self):
        return [item for _,_,_,item in self.aggdisp.GetAllItems()]

    def OnSelectAll(self, _):
        self.aggdisp.SelectAll()

    def EndSelectedFilesContext(self, context, deleteitems=False):
        # We have a fun additional problem.  By default Windows
        # returns a code that it is copying, when in fact it is
        # moving.  Consequently we do a delete if either the
        # source file is gone, or deleteitems is true
        if not deleteitems:
            for item in context:
                if not os.path.exists(item.filename):
                    print "Forcing EndSelectedFilesContext to delete mode even though not specified"
                    deleteitems=True
                    break
        if deleteitems:
            for item in context:
                if os.path.exists(item.filename):
                    os.remove(item.filename)
            for item in context:
                item.RemoveFromIndex()
            self.OnRefresh()

    def OnSave(self, _):
        # If one item is selected we ask for a filename to save.  If
        # multiple then we ask for a directory, and users don't get
        # the choice to affect the names of files.  Note that we don't
        # allow users to select a different format for the file - we
        # just copy it as is.
        items=self.GetSelectedItems()
        if len(items)==1:
            ext=getext(items[0].name)
            if ext=="": ext="*"
            else: ext="*."+ext
            dlg=wx.FileDialog(self, "Save item", wildcard=ext, defaultFile=items[0].name, style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
            if dlg.ShowModal()==wx.ID_OK:
                shutil.copyfile(items[0].filename, dlg.GetPath())
            dlg.Destroy()
        else:
            dlg=wx.DirDialog(self, "Save items to", style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON)
            if dlg.ShowModal()==wx.ID_OK:
                for item in items:
                    shutil.copyfile(item.filename, os.path.join(dlg.GetPath(), basename(item.filename)))
            dlg.Destroy()

    if guihelper.IsMSWindows():
        def OnCopy(self, _):
            items=self.GetSelectedItems()
            if not len(items):
                # nothing selected
                return
            file_names=wx.FileDataObject()
            for item in items:
                file_names.AddFile(item.filename)
            if wx.TheClipboard.Open():
                wx.TheClipboard.SetData(file_names)
                wx.TheClipboard.Close()
        def CanCopy(self):
            return len(self.GetSelectedItems())

    def OnPaste(self, _=None):
        if not wx.TheClipboard.Open():
            # can't access the clipboard
            return
        if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_FILENAME)):
            file_names=wx.FileDataObject()
            has_data=wx.TheClipboard.GetData(file_names)
        else:
            has_data=False
        wx.TheClipboard.Close()
        if has_data:
            self.OnAddFiles(file_names.GetFilenames())
    def CanPaste(self):
        """ Return True if can accept clipboard data, False otherwise
        """
        if not wx.TheClipboard.Open():
            return False
        r=wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_FILENAME))
        wx.TheClipboard.Close()
        return r

    def OnDelete(self,_):
        items=self.GetSelectedItems()
        for item in items:
            os.remove(item.filename)
        for item in items:
            item.RemoveFromIndex()
        self.OnRefresh()

    def genericpopulatefs(self, dict, key, indexkey, version):
        try:
            os.makedirs(self.thedir)
        except:
            pass
        if not os.path.isdir(self.thedir):
            raise Exception("Bad directory for "+key+" '"+self.thedir+"'")

        # delete all files we don't know about if 'key' contains replacements
        if dict.has_key(key):
            print key,"present - updating disk"
            for f in os.listdir(self.thedir):
                # delete them all except windows magic ones which we ignore
                if f.lower() not in self.skiplist:
                    os.remove(os.path.join(self.thedir, f))

            d=dict[key]
            for i in d:
                open(os.path.join(self.thedir, i.encode(media_codec)), "wb").write(d[i])
        d={}
        d[indexkey]=dict[indexkey]
        common.writeversionindexfile(os.path.join(self.thedir, "index.idx"), d, version)
        return dict

    def genericgetfromfs(self, result, key, indexkey, currentversion):
        try:
            os.makedirs(self.thedir)
        except:
            pass
        if not os.path.isdir(self.thedir):
            raise Exception("Bad directory for "+key+" '"+self.thedir+"'")
        dict={}
        for file in os.listdir(self.thedir):
            if file=='index.idx':
                d={}
                d['result']={}
                common.readversionedindexfile(os.path.join(self.thedir, file), d, self.versionupgrade, currentversion)
                result.update(d['result'])
            elif file.lower() in self.skiplist:
                # ignore windows detritus
                continue
            elif key is not None:
                dict[file.decode(media_codec)]=open(os.path.join(self.thedir, file), "rb").read()
        if key is not None:
            result[key]=dict
        if indexkey not in result:
            result[indexkey]={}
        return result

    def OnDropFiles(self, _, dummy, filenames):
        # There is a bug in that the most recently created tab
        # in the notebook that accepts filedrop receives these
        # files, not the most visible one.  We find the currently
        # viewed tab in the notebook and send the files there
        if self.__dragging:
            # I'm the drag source, forget 'bout it !
            return
        target=self # fallback
        t=self.mainwindow.nb.GetPage(self.mainwindow.nb.GetSelection())
        if isinstance(t, FileView):
            # changing target in dragndrop
            target=t
        target.OnAddFiles(filenames)

    def OnAdd(self, _=None):
        dlg=wx.FileDialog(self, "Choose files", style=wx.OPEN|wx.MULTIPLE, wildcard=self.wildcard)
        if dlg.ShowModal()==wx.ID_OK:
            self.OnAddFiles(dlg.GetPaths())
        dlg.Destroy()

    def CanRename(self):
        return len(self.GetSelectedItems())==1
    # subclass needs to define this
    media_notification_type=None
    def OnRename(self, _=None):
        items=self.GetSelectedItems()
        if len(items)!=1:
               # either none or more than 1 items selected
               return
        old_name=items[0].name
        dlg=wx.TextEntryDialog(self, "Enter a new name:", "Item Rename",
                               old_name)
        if dlg.ShowModal()==wx.ID_OK:
            new_name=dlg.GetValue()
            if len(new_name) and new_name!=old_name:
                old_file_name=items[0].filename
                new_file_name=self.getshortenedbasename(new_name)
                try:
                    os.rename(old_file_name, new_file_name)
                    items[0].RenameInIndex(os.path.basename(
                        str(new_file_name).decode(media_codec)))
                    pubsub.publish(pubsub.MEDIA_NAME_CHANGED,
                                   data={ pubsub.media_change_type: self.media_notification_type,
                                          pubsub.media_old_name: old_file_name,
                                          pubsub.media_new_name: new_file_name })
                except:
                    pass
        dlg.Destroy()
          
    def OnAddFiles(self,_):
        raise Exception("not implemented")

    def decodefilename(self, filename):
        path,filename=os.path.split(filename)
        decoded_file=str(filename).decode(media_codec)
        return os.path.join(path, filename)

    def getshortenedbasename(self, filename, newext=''):
        filename=basename(filename)
        if not 'A' in self.filenamechars:
            filename=filename.lower()
        if not 'a' in self.filenamechars:
            filename=filename.upper()
        if len(newext):
            filename=stripext(filename)
        filename="".join([x for x in filename if x in self.filenamechars])
        filename=filename.replace("  "," ").replace("  ", " ")  # remove double spaces
        if len(newext):
            filename+='.'+newext
        if len(filename)>self.maxlen:
            chop=len(filename)-self.maxlen
            filename=stripext(filename)[:-chop].strip()+'.'+getext(filename)
        return os.path.join(self.thedir, filename.encode(media_codec))

    def genericgetdata(self,dict,want, mediapath, mediakey, mediaindexkey):
        # this was originally written for wallpaper hence using the 'wp' variable
        dict.update(self._data)
        items=None
        if want==self.SELECTED:
            items=self.GetSelectedItems()
            if len(items)==0:
                want=self.ALL

        if want==self.ALL:
            items=self.GetAllItems()

        if items is not None:
            wp={}
            i=0
            for item in items:
                data=open(item.filename, "rb").read()
                wp[i]={'name': item.name, 'data': data}
                v=item.origin
                if v is not None:
                    wp[i]['origin']=v
                i+=1
            dict[mediakey]=wp
                
        return dict

    def log(self, log_str):
        self.mainwindow.log(log_str)

class FileViewDisplayItem(object):

    datakey="Someone forgot to set me"
    PADDING=3

    def __init__(self, view, key, mediapath):
        self.view=view
        self.key=key
        self.thumbsize=10,10
        self.mediapath=mediapath
        self.setvals()
        self.lastw=None

    def setvals(self):
        me=self.view._data[self.datakey][self.key]
        self.name=me['name']
        self.origin=me.get('origin', None)
        self.filename=os.path.join(self.mediapath, self.name.encode(media_codec))
        self.fileinfo=self.view.GetFileInfo(self.filename)
        self.size=self.fileinfo.size
        self.short=self.fileinfo.shortdescription()
        self.long=self.fileinfo.longdescription()
        self.thumb=None
        self.selbbox=None
        self.lines=[self.name, self.short,
                    '%.1f kb' % (self.size/1024.0,)]
        if self.origin:
            self.lines.append(self.origin)

    def setthumbnailsize(self, thumbnailsize):
        self.thumbnailsize=thumbnailsize
        self.thumb=None
        self.selbox=None

    def Draw(self, dc, width, height, selected):
        if self.thumb is None:
            self.thumb=self.view.GetItemThumbnail(self.name, self.thumbnailsize[0], self.thumbnailsize[1])
        redrawbbox=False
        if selected:
            if self.lastw!=width or self.selbbox is None:
                redrawbbox=True
            else:
                oldb=dc.GetBrush()
                oldp=dc.GetPen()
                dc.SetBrush(self.view.item_selection_brush)
                dc.SetPen(self.view.item_selection_pen)
                dc.DrawRectangle(*self.selbbox)
                dc.SetBrush(oldb)
                dc.SetPen(oldp)
        dc.DrawBitmap(self.thumb, self.PADDING+self.thumbnailsize[0]/2-self.thumb.GetWidth()/2, self.PADDING, True)
        xoff=self.PADDING+self.thumbnailsize[0]+self.PADDING
        yoff=self.PADDING*2
        widthavailable=width-xoff-self.PADDING
        maxw=0
        old=dc.GetFont()
        for i,line in enumerate(self.lines):
            dc.SetFont(self.view.item_line_font[i])
            w,h=DrawTextWithLimit(dc, xoff, yoff, line, widthavailable, self.view.item_guardspace, self.view.item_term)
            maxw=max(maxw,w)
            yoff+=h
        dc.SetFont(old)
        self.lastw=width
        self.selbbox=(0,0,xoff+maxw+self.PADDING,max(yoff+self.PADDING,self.thumb.GetHeight()+self.PADDING*2))
        if redrawbbox:
            return self.Draw(dc, width, height, selected)
        return self.selbbox

    def DisplayTooltip(self, parent, rect):
        res=["Name: "+self.name, "Origin: "+(self.origin, "default")[self.origin is None],
             'File size: %.1f kb (%d bytes)' % (self.size/1024.0, self.size), "\n"+self.datatype+" information:\n", self.long]
        # tipwindow takes screen coordinates so we have to transform
        x,y=parent.ClientToScreen(rect[0:2])
        return wx.TipWindow(parent, "\n".join(res), 1024, wx.Rect(x,y,rect[2], rect[3]))

    def RemoveFromIndex(self):
        del self.view._data[self.datakey][self.key]
        self.view.modified=True
        self.view.OnRefresh()

    def RenameInIndex(self, new_name):
        self.view._data[self.datakey][self.key]['name']=new_name
        self.view.modified=True
        self.view.OnRefresh()

###
### Various platform independent filename functions
###

basename=common.basename
stripext=common.stripext
getext=common.getext


###
### A dialog showing a message in a fixed font, with a help button
###

class MyFixedScrolledMessageDialog(wx.Dialog):
    """A dialog displaying a readonly text control with a fixed width font"""
    def __init__(self, parent, msg, caption, helpid, pos = wx.DefaultPosition, size = (850,600)):
        wx.Dialog.__init__(self, parent, -1, caption, pos, size, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)

        text=wx.TextCtrl(self, 1,
                        style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 |
                        wx.TE_DONTWRAP  )
        # Fixed width font
        f=wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL )
        ta=wx.TextAttr(font=f)
        text.SetDefaultStyle(ta)

        text.AppendText(msg) # if i supply this in constructor then the font doesn't take
        text.SetInsertionPoint(0)
        text.ShowPosition(text.XYToPosition(0,0))

        # vertical sizer
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(text, 1, wx.EXPAND|wx.ALL, 10)

        # buttons
        vbs.Add(self.CreateButtonSizer(wx.OK|wx.HELP), 0, wx.ALIGN_RIGHT|wx.ALL, 10)

        # plumb
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        wx.EVT_BUTTON(self, wx.ID_HELP, lambda _,helpid=helpid: wx.GetApp().displayhelpid(helpid))

###
###  Dialog that deals with exceptions
###
import StringIO

class ExceptionDialog(wx.Dialog):
    def __init__(self, parent, exception, title="Exception"):
        wx.Dialog.__init__(self, parent, title=title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.THICK_FRAME|wx.MAXIMIZE_BOX, size=(740, 580))
        self.maintext=wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2|wx.HSCROLL)
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(self.maintext, 1, wx.EXPAND|wx.ALL, 5)

        buttsizer=wx.GridSizer(1, 3)
        buttsizer.Add(wx.Button(self, wx.ID_CANCEL, "Abort BitPim"), 0, wx.ALL, 10)
        buttsizer.Add(wx.Button(self, wx.ID_HELP, "Help"), 0, wx.ALL, 10)
        buttsizer.Add(wx.Button(self, wx.ID_OK, "Continue"), 0, wx.ALL, 10)

        vbs.Add(buttsizer, 0, wx.ALIGN_RIGHT|wx.ALL, 5)

        wx.EVT_BUTTON(self, wx.ID_CANCEL, self.abort)
        wx.EVT_BUTTON(self, wx.ID_HELP, lambda _: wx.GetApp().displayhelpid(helpids.ID_EXCEPTION_DIALOG))
        
        self.SetSizer(vbs)
        self._text=""
        self.addexception(exception)

    def abort(self,_):
        import os
        os._exit(1)
        
    def addexception(self, exception):
        s=StringIO.StringIO()
        s.write("An unexpected exception has occurred.\nPlease see the help for details on what to do.\n\n")
        if hasattr(exception, 'gui_exc_info'):
            s.write(common.formatexception(exception.gui_exc_info))
        else:
            s.write("Exception with no extra info.\n%s\n" % (exception.str(),))
        self._text=s.getvalue()
        self.maintext.SetValue(self._text)
        
    def getexceptiontext(self):
        return self._text

###
###  Too much freaking effort for a simple statusbar.  Mostly copied from the demo.
###

class MyStatusBar(wx.StatusBar):
    __total_panes=3
    __version_index=2
    __phone_model_index=2
    __app_status_index=0
    __gauge_index=1
    __major_progress_index=2
    __minor_progress_index=2
    __help_str_index=2
    __general_pane=2
    __pane_width=[50, 180, -1]
    def __init__(self, parent, id=-1):
        wx.StatusBar.__init__(self, parent, id)
        self.__major_progress_text=self.__version_text=self.__phone_text=''
        self.sizechanged=False
        wx.EVT_SIZE(self, self.OnSize)
        wx.EVT_IDLE(self, self.OnIdle)
        self.gauge=wx.Gauge(self, 1000, 1)
        self.SetFieldsCount(self.__total_panes)
        self.SetStatusWidths(self.__pane_width)
        self.Reposition()

    def OnSize(self,_):
        self.sizechanged=True

    def OnIdle(self,_):
        if not len(self.GetStatusText(self.__general_pane)):
            self.__set_version_phone_text()
        if self.sizechanged:
            try:
                self.Reposition()
            except:
                # this works around a bug in wx (on Windows only)
                # where we get a bogus exception.  See SF bug
                # 873155 
                pass

    def Reposition(self):
        self.sizeChanged = False
        rect=self.GetFieldRect(self.__gauge_index)
        self.gauge.SetPosition(wx.Point(rect.x+2, rect.y+2))
        self.gauge.SetSize(wx.Size(rect.width-4, rect.height-4))

    def progressminor(self, pos, max, desc=""):
        self.gauge.SetRange(max)
        self.gauge.SetValue(pos)
        if len(self.__major_progress_text):
            s=self.__major_progress_text
            if len(desc):
                s+=' - '+desc
        else:
            s=desc
        self.SetStatusText(s, self.__minor_progress_index)

    def progressmajor(self, pos, max, desc=""):
        if len(desc) and max:
            self.__major_progress_text="%d/%d %s" % (pos+1, max, desc)
        else:
            self.__major_progress_text=desc
        self.progressminor(0,1)

    def GetHelpPane(self):
        return self.__help_str_index
    def set_app_status(self, str=''):
        self.SetStatusText(str, self.__app_status_index)
    def set_phone_model(self, str=''):
        self.__phone_text=str
        self.__set_version_phone_text()
    def set_versions(self, current, latest=''):
        s='BitPim '+current
        if len(latest):
            s+='/Latest '+latest
        else:
            s+='/Latest <Unknown>'
        self.__version_text=s
        self.__set_version_phone_text()
    def __set_version_phone_text(self):
        if guihelper.IsMac():
            s = self.__version_text+'         '+self.__phone_text
        else:
            s = self.__version_text+'\t'+self.__phone_text
        self.SetStatusText(s, self.__general_pane)

###
###  A MessageBox with a help button
###

class AlertDialogWithHelp(wx.Dialog):
    """A dialog box with Ok button and a help button"""
    def __init__(self, parent, message, caption, helpfn, style=wx.DEFAULT_DIALOG_STYLE, icon=wx.ICON_EXCLAMATION):
        wx.Dialog.__init__(self, parent, -1, caption, style=style|wx.DEFAULT_DIALOG_STYLE)

        p=self # parent widget

        # horiz sizer for bitmap and text
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        hbs.Add(wx.StaticBitmap(p, -1, wx.ArtProvider_GetBitmap(self.icontoart(icon), wx.ART_MESSAGE_BOX)), 0, wx.CENTER|wx.ALL, 10)
        hbs.Add(wx.StaticText(p, -1, message), 1, wx.CENTER|wx.ALL, 10)

        # the buttons
        buttsizer=self.CreateButtonSizer(wx.HELP|style)

        # Both vertical
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 10)
        vbs.Add(buttsizer, 0, wx.CENTER|wx.ALL, 10)

        # wire it in
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)

        wx.EVT_BUTTON(self, wx.ID_HELP, helpfn)

    def icontoart(self, id):
        if id&wx.ICON_EXCLAMATION:
            return wx.ART_WARNING
        if id&wx.ICON_INFORMATION:
            return wx.ART_INFORMATION
        # ::TODO:: rest of these
        # fallthru
        return wx.ART_INFORMATION

###
### Yet another dialog with user selectable buttons
###

class AnotherDialog(wx.Dialog):
    """A dialog box with user supplied buttons"""
    def __init__(self, parent, message, caption, buttons, helpfn=None,
                 style=wx.DEFAULT_DIALOG_STYLE, icon=wx.ICON_EXCLAMATION):
        """Constructor

        @param message:  Text displayed in body of dialog
        @param caption:  Title of dialog
        @param buttons:  A list of tuples.  Each tuple is a string and an integer id.
                         The result of calling ShowModal() is the id
        @param helpfn:  The function called if the user presses the help button (wx.ID_HELP)
        """
        wx.Dialog.__init__(self, parent, -1, caption, style=style)

        p=self # parent widget

        # horiz sizer for bitmap and text
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        hbs.Add(wx.StaticBitmap(p, -1, wx.ArtProvider_GetBitmap(self.icontoart(icon), wx.ART_MESSAGE_BOX)), 0, wx.CENTER|wx.ALL, 10)
        hbs.Add(wx.StaticText(p, -1, message), 1, wx.CENTER|wx.ALL, 10)

        # the buttons
        buttsizer=wx.BoxSizer(wx.HORIZONTAL)
        for label,id in buttons:
            buttsizer.Add( wx.Button(self, id, label), 0, wx.ALL|wx.ALIGN_CENTER, 5)
            if id!=wx.ID_HELP:
                wx.EVT_BUTTON(self, id, self.OnButton)
            else:
                wx.EVT_BUTTON(self, wx.ID_HELP, helpfn)
                
        # Both vertical
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 10)
        vbs.Add(buttsizer, 0, wx.CENTER|wx.ALL, 10)

        # wire it in
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)

    def OnButton(self, event):
        self.EndModal(event.GetId())

    def icontoart(self, id):
        if id&wx.ICON_EXCLAMATION:
            return wx.ART_WARNING
        if id&wx.ICON_INFORMATION:
            return wx.ART_INFORMATION
        # ::TODO:: rest of these
        # fallthru
        return wx.ART_INFORMATION

###
### Utility code
###

def DrawTextWithLimit(dc, x, y, text, widthavailable, guardspace, term="..."):
    """Draws text and if it will overflow the width available, truncates and  puts ... at the end

    @param x: start position for text
    @param y: start position for text
    @param text: the string to draw
    @param widthavailable: the total amount of space available
    @param guardspace: if the text is longer than widthavailable then this amount of space is
             reclaimed from the right handside and term put there instead.  Consequently
             this value should be at least the width of term
    @param term: the string that is placed in the guardspace if it gets truncated.  Make sure guardspace
             is at least the width of this string!
    @returns: The extent of the text that was drawn in the end as a tuple of (width, height)
    """
    w,h=dc.GetTextExtent(text)
    if w<widthavailable:
        dc.DrawText(text,x,y)
        return w,h
    extents=dc.GetPartialTextExtents(text)
    limit=widthavailable-guardspace
    # find out how many chars in we have to go before hitting limit
    for i,offset in enumerate(extents):
        if offset>limit:
            break
    # back off 1 in case the new text's a tad long
    if i:
        i-=1
    text=text[:i]+term
    w,h=dc.GetTextExtent(text)
    assert w<=widthavailable
    dc.DrawText(text, x, y)
    return w,h
    
        

###
###  Window geometry/positioning memory
###


def set_size(confname, window, screenpct=50, aspect=1.0):
    """Sets remembered/calculated dimensions/position for window

    @param confname: subkey to store/get this windows's settings from
    @param window:  the window object itself
    @param screenpct: percentage of the screen the window should occupy.
             If this value is negative then the window will not be resized,
             only repositioned (unless the current size is silly)
    @param aspect:  aspect ratio.  If greater than one then it is
             how much wider than tall the window is, and if less
             than one then the other way round
    """
    confobj=wx.GetApp().config

    # frig confname
    confname="windows/"+confname

    # Get screen size, scale according to percentage supplied
    screenSize = wx.GetClientDisplayRect()
    if (aspect >= 1):
        newWidth = screenSize.width * abs(screenpct) / 100
        newHeight = screenSize.height * abs(screenpct) / aspect / 100
    else:
        newWidth = screenSize.width * abs(screenpct) * aspect / 100
        newHeight = screenSize.height * abs(screenpct) / 100

    if screenpct<=0:
        rs_width,rs_height=window.GetSizeTuple()
    else:
        # Retrieve values (if any) from config database for this config object
        rs_width  = confobj.ReadInt(confname + "/width", int(newWidth))
        rs_height = confobj.ReadInt(confname + "/height", int(newHeight))

    # suitable magic number to show not configured.  it is an exercise for the reader
    # why it isn't -65536 (hint: virtual desktops)
    unconfigured=-65245

    rs_x = confobj.ReadInt(confname + "/x", unconfigured)
    rs_y = confobj.ReadInt(confname + "/y", unconfigured)

    # Check for small window
    if rs_height < 96:
        rs_height = newHeight
    if rs_width < 96:
        rs_width = newWidth

    # Make sure window is no larger than about screen size
    #
    # determine ratio of original oversized window so we keep the ratio if we resize...
    rs_aspect = rs_width/rs_height
    if rs_aspect >= 1:
        if rs_width > screenSize.width:
            rs_width = screenSize.width
        if rs_height > (screenSize.height):
            rs_height = (screenSize.height / rs_aspect) - screenSize.y 
    else:
        if rs_width > screenSize.width:
            rs_width = screenSize.width * rs_aspect
        if rs_height > screenSize.height - screenSize.y:
            rs_height = screenSize.height - screenSize.y

    # Off the screen?  Just pull it back a little bit so it's visible....
    if rs_x!=unconfigured and rs_x > screenSize.width:
        rs_x = screenSize.width - 50
    if rs_x!=unconfigured and rs_x + rs_width < screenSize.x:
        rs_x = screenSize.x
    if rs_y!=unconfigured and rs_y > screenSize.height:
        rs_y = screenSize.height - 50
    if rs_y!=unconfigured and rs_y + rs_height < screenSize.y:
        rs_y = screenSize.y
        

    if screenpct<=0 and (rs_width,rs_height)==window.GetSizeTuple():
        # set position only, and no need to resize
        if rs_x!=unconfigured and rs_y!=unconfigured:
            print "setting %s to position %d, %d" % (confname, rs_x, rs_y)
            window.SetPosition(wx.Point(rs_x, rs_y))
    else:
        if rs_x==unconfigured or rs_y==unconfigured:
            print "setting %s to size %d x %d" % (confname, rs_width, rs_height)
            window.SetSize(wx.Size(rs_width, rs_height))
        else:
            print "setting %s to position %d, %d - size %d x %d" % (confname, rs_x, rs_y, rs_width, rs_height)
            window.SetDimensions(rs_x, rs_y, rs_width, rs_height)

def save_size(confname, myRect):
    """Saves size to config.  L{set_size}

    @param confname: Same string as in set_size
    @param myRect:  Window size you want remembered, typically window.GetRect()
    """
    confobj=wx.GetApp().config
    
    confname="windows/"+confname

    x = myRect.x
    y = myRect.y
    width = myRect.width
    height = myRect.height

    confobj.WriteInt(confname + "/x", x)
    confobj.WriteInt(confname + "/y", y)
    confobj.WriteInt(confname + "/width", width)
    confobj.WriteInt(confname + "/height", height)
    confobj.Flush()

class LogProgressDialog(wx.ProgressDialog):
    """ display log string and progress bar at the same time
    """
    def __init__(self, title, message, maximum=100, parent=None,
                 style=wx.PD_AUTO_HIDE|wx.PD_APP_MODAL):
        super(LogProgressDialog, self).__init__(title, message, maximum,
                                                parent, style)
        self.__progress_value=0
    def Update(self, value, newmsg='', skip=None):
        self.__progress_value=value
        super(LogProgressDialog, self).Update(value, newmsg, skip)
    def log(self, msgstr):
        super(LogProgressDialog, self).Update(self.__progress_value, msgstr)

class AskPhoneNameDialog(wx.Dialog):
    def __init__(self, parent, message, caption="Enter phone owner's name"):
        """ Ask a user to enter an owner's name of a phone.
        Similar to the wx.TextEntryDialog but has 3 buttons, Ok, No Thanks, and
        Maybe latter.
        """
        super(AskPhoneNameDialog, self).__init__(parent, -1, caption)
        vbs=wx.BoxSizer(wx.VERTICAL)
        vbs.Add(wx.StaticText(self, -1, message), 0, wx.ALL, 5)
        self.__text_ctrl=wx.TextCtrl(self, -1, style=wx.TE_PROCESS_ENTER)
        vbs.Add(self.__text_ctrl,  0, wx.EXPAND|wx.ALL, 5)
        vbs.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        ok_btn=wx.Button(self, wx.ID_OK, 'OK')
        hbs.Add(ok_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        cancel_btn=wx.Button(self, wx.ID_CANCEL, 'No Thanks')
        hbs.Add(cancel_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        maybe_btn=wx.Button(self, wx.NewId(), 'Maybe next time')
        hbs.Add(maybe_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        vbs.Add(hbs, 1, wx.ALL, 5)
        wx.EVT_BUTTON(self, maybe_btn.GetId(), self.__OnMaybe)
        wx.EVT_TEXT_ENTER(self, self.__text_ctrl.GetId(), self.__OnTextEnter)
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)
    def GetValue(self):
        return self.__text_ctrl.GetValue()
    def __OnMaybe(self, evt):
        self.EndModal(evt.GetId())
    def __OnTextEnter(self, _):
        self.EndModal(wx.ID_OK)

class HistoricalDataDialog(wx.Dialog):
    Current_Data=0
    Historical_Data=1
    _Historical_Date=1
    _Historical_Event=2
    def __init__(self, parent, caption='Historical Data Selection',
                 current_choice=Current_Data,
                 historical_date=None,
                 historical_events=None):
        super(HistoricalDataDialog, self).__init__(parent, -1, caption)
        vbs=wx.BoxSizer(wx.VERTICAL)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        self.data_selector=wx.RadioBox(self, wx.NewId(),
                                       'Data Selection:',
                                       choices=('Current', 'Historical Date',
                                                'Historical Event'),
                                       style=wx.RA_SPECIFY_ROWS)
        self.data_selector.SetSelection(current_choice)
        wx.EVT_RADIOBOX(self, self.data_selector.GetId(), self.OnSelectData)
        hbs.Add(self.data_selector, 0, wx.ALL, 5)
        static_bs=wx.StaticBoxSizer(wx.StaticBox(self, -1,
                                                 'Historical Date:'),
                                    wx.VERTICAL)
        self.data_date=wx.DatePickerCtrl(self,
                                         style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY)
        if historical_date is not None:
            self.data_date.SetValue(wx.DateTimeFromTimeT(historical_date))
        self.data_date.Enable(current_choice==self._Historical_Date)
        static_bs.Add(self.data_date, 1, wx.EXPAND, 0)
        hbs.Add(static_bs, 0, wx.ALL, 5)
        # historical events
        static_bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, 'Historical Events:'),
                                    wx.VERTICAL)
        self.hist_events=wx.ListBox(self, -1, style=wx.LB_SINGLE)
        if historical_events:
            self._populate_historical_events(historical_events)
        self.hist_events.Enable(current_choice==self._Historical_Event)
        static_bs.Add(self.hist_events, 1, wx.EXPAND, 0)
        hbs.Add(static_bs, 0, wx.ALL, 5)

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

    def OnSelectData(self, evt):
        self.data_date.Enable(evt.GetInt()==self._Historical_Date)
        self.hist_events.Enable(evt.GetInt()==self._Historical_Event)
        
    def GetValue(self):
        choice=self.data_selector.GetSelection()
        if choice==self.Current_Data:
            mode=self.Current_Data
            time_t=None
        elif choice==self._Historical_Date:
            dt=self.data_date.GetValue()
            dt.SetHour(23)
            dt.SetMinute(59)
            dt.SetSecond(59)
            mode=self.Historical_Data
            time_t=dt.GetTicks()
        else:
            sel=self.hist_events.GetSelection()
            if sel==wx.NOT_FOUND:
                mode=self.Current_Data
                time_t=None
            else:
                mode=self.Historical_Data
                time_t=self.hist_events.GetClientData(sel)
        return mode, time_t

    def _populate_historical_events(self, historical_events):
        keys=historical_events.keys()
        keys.sort()
        keys.reverse()
        for k in keys:
            # build the string
            self.hist_events.Append('%s  %02d-Adds  %02d-Dels  %02d-Mods'%\
                                    (time.strftime('%b %d, %y %H:%M:%S',
                                                   time.localtime(k)),
                                     historical_events[k]['add'],
                                     historical_events[k]['del'],
                                     historical_events[k]['mod']),
                                    k)
guiwidgets.py.diff (application/octet-stream, 1.2 KB)
Index: guiwidgets.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/guiwidgets.py,v
retrieving revision 1.268
diff -u -r1.268 guiwidgets.py
--- guiwidgets.py	29 Sep 2005 12:17:02 -0000	1.268
+++ guiwidgets.py	8 Oct 2005 18:47:26 -0000
@@ -1188,8 +1188,9 @@
             # check of any of the files have been removed,
             # can't trust result returned by DoDragDrop
             for item in items:
-                if not os.access(item.filename, os.F_OK):
-                    # item has been moved, remove from index
+                # this used to use os.access function, but it does not
+                # support unicode filenames
+                if not os.path.isfile(item.filename):
                     item.RemoveFromIndex()
 
     def OnMouseEvent(self, evt):
@@ -1407,6 +1408,11 @@
     def OnAddFiles(self,_):
         raise Exception("not implemented")
 
+    def decodefilename(self, filename):
+        path,filename=os.path.split(filename)
+        decoded_file=str(filename).decode(media_codec)
+        return os.path.join(path, filename)
+
     def getshortenedbasename(self, filename, newext=''):
         filename=basename(filename)
         if not 'A' in self.filenamechars:
hexeditor.py (text/plain, 47.2 KB)
#!/usr/bin/env python
### BITPIM
###
### Copyright (C) 2003-2004 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: hexeditor.py,v 1.15 2005/06/15 02:52:21 djpham Exp $

"""A hex editor widget"""

# system modules
import string
import struct

# wx modules
import wx
from wx.lib import masked
from wx.lib import scrolledpanel as scrolled

# bitpim modules
import common

#-------------------------------------------------------------------------------
class DataStruct(object):
    def __init__(self, _name):
        self.name=_name
        self.fields=[]
    def set(self, dict):
        self.name=dict.keys()[0]
        self.fields=[]
        for f in dict[self.name]:
            if f['type']==DataItem.numeric_type:
                item=NumericDataItem(f['name'])
            elif f['type']==DataItem.string_type:
                item=StringDataItem(f['name'])
            else:
                item=DataItem(f['name'], DataItem.struct_type)
            item.set(f)
            self.fields.append(item)
    def get(self):
        l=[]
        for f in self.fields:
            l.append(f.get())
        return { self.name: l }
    def encode(self, data, buffer_offset=0):
        # encode data to each & every fields and return the results
        l=[]
        start=0
        data_len=len(data)
        for f in self.fields:
            s=f.encode(data, start)
            start=f.start+f.len
            l.append( { '[0x%04X=%d]%s'%(f.start+buffer_offset,
                                         f.start+buffer_offset, f.name): `s` })
            if start>=data_len:
                break
        return l

#-------------------------------------------------------------------------------
class DataItem(object):
    """Represent a data item/component with in a record, which is a list of
    these things.
    """
    offset_from_start='From Start'
    offset_from_prev='From Last Field'
    string_type='string'
    numeric_type='numeric'
    struct_type='struct'
    def __init__(self, _name, _type=numeric_type):
        self.name=_name
        self.offset_type=self.offset_from_start
        self.offset=0
        self.size=1
        self.start=self.len=None   # start & length/size of actual data encoded
        # numeric fields
        self.unsigned=True
        self.LE=True
        # string fields
        self.fixed=True
        self.null_terminated=False
        self.type=_type
    def _get_type(self):
        return self._type
    def _set_type(self, _type):
        if _type not in (self.numeric_type, self.string_type, self.struct_type):
            raise TypeError
        self._type=_type
        if _type==self.numeric_type:
            self.__class__=NumericDataItem
        elif _type==self.string_type:
            self.__class__=StringDataItem
    type=property(fget=_get_type, fset=_set_type)
    def get(self):
        return { 'name': self.name, 'offset_type': self.offset_type,
                 'offset': self.offset, 'type': self.type }
    def set(self, d):
        self.name=d.get('name', '<None>')
        self.offset_type=d.get('offset_type', None)
        self.offset=d.get('offset', None)
        self.type=d.get('type', None)
    def encode(self, s, start=None):
        """Encode the value of this item based on the string s"""
        raise NotImplementedError

#-------------------------------------------------------------------------------
class NumericDataItem(DataItem):
    _fmts={ # struct pack/unpack formats
        True: { # unsigned
            True: { # little endian
                1: 'B', 2: '<H', 4: '<I' }, # size
            False: { # big endian
                1: 'B', 2: '>H', 4: '>I' } }, # size
        False: { # signed
            True: { # little endian
                1: 'b', 2: '<h', 4: '<i' }, # size
            False: { # big endian
                1: 'b', 2: '>h', 4: '>i' } } } # size
            
    def __init__(self, name):
        super(NumericDataItem, self).__init__(name, self.numeric_type)
    def get(self):
        r=super(NumericDataItem, self).get()
        r.update(
            { 'unsigned': self.unsigned,
              'little_endian': self.LE,
              'size': self.size })
        return r
    def set(self, d):
        super(NumericDataItem, self).set(d)
        if d.get('type', None)!=self.numeric_type:
            raise TypeError
        self.unsigned=d.get('unsigned', True)
        self.LE=d.get('little_endian', True)
        self.size=d.get('size', 1)
    def encode(self, s, start=None):
        fmt=self._fmts[self.unsigned][self.LE][self.size]
        self.len=struct.calcsize(fmt)
        if self.offset_type==self.offset_from_start:
            self.start=self.offset
        else:
            if start is None:
                raise ValueError
            self.start=start+self.offset
        return struct.unpack(fmt, s[self.start:self.start+self.len])[0]

#-------------------------------------------------------------------------------
class StringDataItem(DataItem):
    def __init__(self, name):
        super(StringDataItem, self).__init__(name, self.string_type)
    def get(self):
        r=super(StringDataItem, self).get()
        r.update({ 'fixed': self.fixed, 'size': self.size,
                   'null_terminated': self.null_terminated })
        return r
    def set(self, d):
        super(StringDataItem, self).set(d)
        if d.get('type', None)!=self.string_type:
            raise TypeError
        self.fixed=d.get('fixed', True)
        self.size=d.get('size', 0)
        self.null_terminated=d.get('null_terminated', False)
    def encode(self, s, start=None):
        if self.offset_type==self.offset_from_start:
            self.start=self.offset
        else:
            if start is None:
                raise ValueError
            self.start=start+self.offset
        if self.fixed:
            # fixed length string
            if self.size==-1:
                # take all available space
                self.len=len(s)-self.offset
                s0=s[self.start:]
            else:
                # fixed size
                s0=s[self.start:self.start+self.size]
                self.len=self.size
        else:
            # pascal style variable string
            self.len=ord(s[self.start])
            s0=s[self.start+1:self.start+1+self.len]
        if self.null_terminated:
            i=s0.find('\x00')
            if i==-1:
                return s0
            else:
                self.len=i
                return s0[:i]
        else:
            return s0

#-------------------------------------------------------------------------------
class GeneralInfoSizer(wx.FlexGridSizer):
    def __init__(self, parent):
        super(GeneralInfoSizer, self).__init__(-1, 2, 5, 5)
        self.AddGrowableCol(1)
        self.Add(wx.StaticText(parent, -1, 'Struct Name:'), 0, wx.EXPAND|wx.ALL, 5)
        self._struct_name=wx.TextCtrl(parent, -1, '')
        self.Add(self._struct_name, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Field Name:'), 0, wx.EXPAND|wx.ALL, 5)
        self._name=wx.TextCtrl(parent, -1, '')
        self.Add(self._name, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Type:'), 0, wx.EXPAND|wx.ALL, 5)
        self._type=wx.ComboBox(parent, wx.NewId(),
                                choices=[DataItem.numeric_type,
                                         DataItem.string_type],
                                value=DataItem.numeric_type,
                                style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.Add(self._type, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Offset Type:'), 0, wx.EXPAND|wx.ALL, 5)
        self._offset_type=wx.ComboBox(parent, -1,
                                       value=DataItem.offset_from_start,
                                       choices=[DataItem.offset_from_start,
                                                DataItem.offset_from_prev],
                                       style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.Add(self._offset_type, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Offset Value:'), 0, wx.EXPAND|wx.ALL, 5)
        self._offset=masked.NumCtrl(parent, wx.NewId(),
                                    allowNegative=False,
                                    min=0)
        self.Add(self._offset, 0, wx.ALL, 5)
        self._fields_group=(self._name, self._type, self._offset_type,
                            self._offset)
    def set(self, data):
        if isinstance(data, DataStruct):
            self._struct_name.SetValue(data.name)
        elif isinstance(data, DataItem):
            self._name.SetValue(data.name)
            self._type.SetValue(data.type)
            self._offset_type.SetValue(data.offset_type)
            self._offset.SetValue(data.offset)
    def get(self, data):
        data.name=self._name.GetValue()
        data.type=self._type.GetValue()
        data.offset_type=self._offset_type.GetValue()
        data.offset=int(self._offset.GetValue())
        return data
    def show(self, show_struct=False, show_field=False):
        self._struct_name.Enable(show_struct)
        for w in self._fields_group:
            w.Enable(show_field)
    def _get_struct_name(self):
        return self._struct_name.GetValue()
    struct_name=property(fget=_get_struct_name)
    def _get_type(self):
        return self._type.GetValue()
    type=property(fget=_get_type)
    def _get_type_id(self):
        return self._type.GetId()
    type_id=property(fget=_get_type_id)
        
#-------------------------------------------------------------------------------
class NumericInfoSizer(wx.FlexGridSizer):
    _sign_choices=['Unsigned', 'Signed']
    _endian_choices=['Little Endian', 'Big Endian']
    _size_choices=['1', '2', '4']
    def __init__(self, parent):
        super(NumericInfoSizer, self).__init__(-1, 2, 5, 5)
        self.AddGrowableCol(1)
        self.Add(wx.StaticText(parent, -1, 'Signed:'), 0, wx.EXPAND|wx.ALL, 5)
        self._sign=wx.ComboBox(parent, -1, value=self._sign_choices[0],
                               choices=self._sign_choices,
                               style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.Add(self._sign, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Endian:'), 0, wx.EXPAND|wx.ALL, 5)
        self._endian=wx.ComboBox(parent, -1, value=self._endian_choices[0],
                                 choices=self._endian_choices,
                                 style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.Add(self._endian, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Size:'), 0, wx.EXPAND|wx.ALL, 5)
        self._size=wx.ComboBox(parent, -1, value=self._size_choices[0],
                               choices=self._size_choices,
                               style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.Add(self._size, 0, wx.EXPAND|wx.ALL, 5)
    def set(self, data):
        if data.unsigned:
            self._sign.SetValue(self._sign_choices[0])
        else:
            self._sign.SetValue(self._sign_choices[1])
        if data.LE:
            self._endian.SetValue(self._endian_choices[0])
        else:
            self._endian.SetValue(self._endian_choices[1])
        self._size.SetValue(`data.size`)
    def get(self, data):
        data.unsigned=self._sign.GetValue()==self._sign_choices[0]
        data.LE=self._endian.GetValue()==self._endian_choices[0]
        data.size=int(self._size.GetValue())
        return data

#-------------------------------------------------------------------------------
class StringInfoSizer(wx.FlexGridSizer):
    _fixed_choices=['Fixed', 'Pascal']
    def __init__(self, parent):
        super(StringInfoSizer, self).__init__(-1, 2, 5, 5)
        self.AddGrowableCol(1)
        self.Add(wx.StaticText(parent, -1, 'Fixed/Pascal:'), 0, wx.EXPAND|wx.ALL, 5)
        self._fixed=wx.ComboBox(parent, -1, value=self._fixed_choices[0],
                                 choices=self._fixed_choices,
                                 style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.Add(self._fixed, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Max Length:'), 0, wx.EXPAND|wx.ALL, 5)
        self._max_len=masked.NumCtrl(parent, -1, value=1, min=-1)
        self.Add(self._max_len, 0, wx.EXPAND|wx.ALL, 5)
        self.Add(wx.StaticText(parent, -1, 'Null Terminated:'), 0, wx.EXPAND|wx.ALL, 5)
        self._null_terminated=wx.CheckBox(parent, -1)
        self.Add(self._null_terminated, 0, wx.EXPAND|wx.ALL, 5)
    def set(self, data):
        if data.fixed:
            self._fixed.SetValue(self._fixed_choices[0])
        else:
            self._fixed.SetValue(self._fixed_choices[1])
        self._max_len.SetValue(`data.size`)
        self._null_terminated.SetValue(data.null_terminated)
    def get(self, data):
        data.fixed=self._fixed.GetValue()==self._fixed_choices[0]
        data.size=int(self._max_len.GetValue())
        data.null_terminated=self._null_terminated.GetValue()
        return data
        
#-------------------------------------------------------------------------------
class TemplateDialog(wx.Dialog):
    _type_choices=['Numeric', 'String']
    _struct_type='struct'
    _field_type='field'
    def __init__(self, parent):
        super(TemplateDialog, self).__init__(parent, -1,
                                             'Hex Template Editor',
                                             style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self._data=[]
        self._item_tree=self._numeric_bs=self._string_bs=None
        self._general_bs=None
        self._tree_root=None
        self._field_info=self._field_info_hbs=None
        self._info_sizer={ NumericDataItem.numeric_type: self._numeric_bs,
                           StringDataItem.string_type: self._string_bs }
        main_vbs=wx.BoxSizer(wx.VERTICAL)
        hbs1=wx.BoxSizer(wx.HORIZONTAL)
        hbs1.Add(self._create_tree_pane(), 1, wx.EXPAND|wx.ALL, 5)
        hbs1.Add(self._create_info_pane(), 2, wx.EXPAND|wx.ALL, 5)
        main_vbs.Add(hbs1, 1, wx.EXPAND|wx.ALL, 5)
        main_vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
        main_vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        self.SetSizer(main_vbs)
        self.SetAutoLayout(True)
        main_vbs.Fit(self)

    def _create_tree_pane(self):
        vbs=wx.BoxSizer(wx.VERTICAL)
        sw=scrolled.ScrolledPanel(self, -1)
        self._item_tree=wx.TreeCtrl(sw, wx.NewId(),
                                    style=wx.TR_DEFAULT_STYLE|wx.TR_HAS_BUTTONS)
        wx.EVT_TREE_SEL_CHANGED(self, self._item_tree.GetId(),
                                self._OnTreeSel)
        self._tree_root=self._item_tree.AddRoot('Data Templates')
        sw_bs=wx.BoxSizer(wx.VERTICAL)
        sw_bs.Add(self._item_tree, 1, wx.EXPAND|wx.ALL, 0)
        sw.SetSizer(sw_bs)
        sw.SetAutoLayout(True)
        sw_bs.Fit(sw)
        sw.SetupScrolling()
        vbs.Add(sw, 1, wx.EXPAND|wx.ALL, 5)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        hbs.Add(wx.Button(self, wx.ID_ADD, 'Add'), 0, wx.EXPAND|wx.ALL, 5)
        hbs.Add(wx.Button(self, wx.ID_DELETE, 'Delete'), 0, wx.EXPAND|wx.ALL, 5)
        wx.EVT_BUTTON(self, wx.ID_ADD, self._OnAdd)
        wx.EVT_BUTTON(self, wx.ID_DELETE, self._OnDelete)
        vbs.Add(hbs, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5)
        return vbs
    def _create_info_pane(self):
        # main boxsize
        vbs=wx.BoxSizer(wx.VERTICAL)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        # Type & offset
        static_bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, 'Field Type'),
                                    wx.VERTICAL)
        self._general_bs=GeneralInfoSizer(self)
        wx.EVT_COMBOBOX(self, self._general_bs.type_id, self._OnTypeChanged)
        static_bs.Add(self._general_bs, 0, wx.EXPAND|wx.ALL, 5)
        hbs.Add(static_bs, 0, wx.ALL, 5)
        # all info
        self._field_info=wx.StaticBoxSizer(wx.StaticBox(self, -1, 'Field Info'),
                                    wx.VERTICAL)
        # numeric info box
        self._numeric_bs=NumericInfoSizer(self)
        self._field_info.Add(self._numeric_bs, 0, wx.EXPAND|wx.ALL, 5)
        self._string_bs=StringInfoSizer(self)
        self._field_info.Add(self._string_bs, 0, wx.EXPAND|wx.ALL, 5)
        hbs.Add(self._field_info, 0, wx.ALL, 5)
        vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5)
        hbs1=wx.BoxSizer(wx.HORIZONTAL)
        hbs1.Add(wx.Button(self, wx.ID_SAVE, 'Set'), 0, wx.EXPAND|wx.ALL, 5)
        hbs1.Add(wx.Button(self, wx.ID_REVERT, 'Revert'), 0, wx.EXPAND|wx.ALL, 5)
        wx.EVT_BUTTON(self, wx.ID_SAVE, self._OnSave)
        wx.EVT_BUTTON(self, wx.ID_REVERT, self._OnRevert)
        vbs.Add(hbs1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
        self._field_info_hbs=hbs
        return vbs
    def _show_field_info(self, _struct=False, field=False,
                         numeric_field=False, string_field=False):
        # show/hide individual fields
        self._general_bs.show(_struct, field)
        self._field_info.Show(self._numeric_bs, numeric_field)
        self._field_info.Show(self._string_bs, string_field)
        self._field_info.Layout()
        self._field_info_hbs.Layout()
    def _populate(self):
        # clear the tree and repopulate
        self._item_tree.DeleteChildren(self._tree_root)
        for i,e in enumerate(self._data):
            item=self._item_tree.AppendItem(self._tree_root, e.name)
            self._item_tree.SetPyData(item, { 'type': self._struct_type,
                                              'index': i })
            for i1,e1 in enumerate(e.fields):
                field_item=self._item_tree.AppendItem(item, e1.name)
                self._item_tree.SetPyData(field_item, { 'type': self._field_type,
                                                        'index': i,
                                                        'field_index': i1 })
        self.expand()
    def _populate_struct(self, _item_index):
        self._general_bs.set(self._data[_item_index])
        self._show_field_info(True)
    def _populate_each(self, _struct_index, _item_index):
        _struct=self._data[_struct_index]
        _item=_struct.fields[_item_index]
        self._general_bs.set(_item)
        if _item.type==DataItem.numeric_type:
            self._show_field_info(True, True, True)
            self._numeric_bs.set(_item)
        else:
            self._show_field_info(True, True, False, True)
            self._string_bs.set(_item)
    def _OnTypeChanged(self, _):
        new_type=self._general_bs.type
        self._show_field_info(True, True, new_type==DataItem.numeric_type,
                              new_type==DataItem.string_type)
    def _OnAdd(self, _):
        sel_idx=self._item_tree.GetSelection()
        if not sel_idx.IsOk():
            return
        if sel_idx==self._tree_root:
            # add a new structure
            struct_item=DataStruct('New Struct')
            self._data.append(struct_item)
        else:
            # add a new field to the existing structure
            data_item=self._item_tree.GetPyData(sel_idx)
            item=NumericDataItem('New Field')
            self._data[data_item['index']].fields.append(item)
        self._populate()
    def _OnDelete(self, _):
        sel_idx=self._item_tree.GetSelection()
        if not sel_idx.IsOk():
            return
        node_data=self._item_tree.GetPyData(sel_idx)
        if node_data is None:
            return
        if node_data['type']==self._field_type:
            # del this field
            del self._data[node_data['index']].fields[node_data['field_index']]
        else:
            # del this struct and its fields
            del self._data[node_data['index']]
        # and re-populate the tree
        self._populate()
    def _OnSave(self, _):
        sel_idx=self._item_tree.GetSelection()
        if not sel_idx.IsOk():
            return
        node_data=self._item_tree.GetPyData(sel_idx)
        if node_data is None:
            return
        # update the struct name
        self._data[node_data['index']].name=self._general_bs.struct_name
        if node_data['type']==self._field_type:
            data_item=self._data[node_data['index']].\
                       fields[node_data['field_index']]
            data_item=self._general_bs.get(data_item)
            if data_item.type==DataItem.numeric_type:
                data_item=self._numeric_bs.get(data_item)
            else:
                data_item=self._string_bs.get(data_item)
            self._data[node_data['index']].fields[node_data['field_index']]=data_item
            self._item_tree.SetItemText(self._item_tree.GetItemParent(sel_idx),
                                        self._data[node_data['index']].name)
            self._item_tree.SetItemText(sel_idx, data_item.name)
        else:
            self._item_tree.SetItemText(sel_idx, self._data[node_data['index']].name)
            
    def _OnRevert(self, _):
        sel_idx=self._item_tree.GetSelection()
        if not sel_idx.IsOk():
            return
        node_data=self._item_tree.GetPyData(sel_idx)
        if node_data is None:
            self._show_field_info()
        else:
            self._populate_struct(node_data['index'])
            if node_data['type']==self._field_type:
                self._populate_each(node_data['index'], node_data['field_index'])

    def _OnTreeSel(self, evt):
        sel_idx=evt.GetItem()
        if not sel_idx.IsOk():
            # invalid selection
            return
        item_data=self._item_tree.GetPyData(sel_idx)
        if item_data is None:
            self._show_field_info()
        else:
            self._populate_struct(item_data['index'])
            if item_data['type']==self._field_type:
                self._populate_each(item_data['index'], item_data['field_index'])
    def expand(self):
        # expand the tree
        self._item_tree.Expand(self._tree_root)
        (id, cookie)=self._item_tree.GetFirstChild(self._tree_root)
        while id.IsOk():
            self._item_tree.Expand(id)
            (id, cookie)=self._item_tree.GetNextChild(self._tree_root, cookie)
    def set(self, l):
        self._data=l
        self._populate()
    def get(self):
        return self._data
        
#-------------------------------------------------------------------------------
class HexEditor(wx.ScrolledWindow):

    _addr_range=xrange(8)
    _hex_range_start=10
    _hex_range_start2=33
    _hex_range=xrange(_hex_range_start, 58)
    _ascii_range_start=60
    _ascii_range=xrange(60, 76)

    def __init__(self, parent, id=-1, style=wx.WANTS_CHARS,
                 _set_pos=None, _set_sel=None, _set_val=None):
        wx.ScrolledWindow.__init__(self, parent, id, style=style)
        self.parent=parent
        self.data=""
        self.title=""
        self.buffer=None
        self.hasfocus=False
        self.dragging=False
        self.current_ofs=None
        self._module=None
        self._templates=[]
        self._search_string=None
        # ways of displaying status
        self.set_pos=_set_pos or self._set_pos
        self.set_val=_set_val or self._set_val
        self.set_sel=_set_sel or self._set_sel
        # some GUI setup
        self.SetBackgroundColour("WHITE")
        self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
        self.sethighlight(wx.NamedColour("BLACK"), wx.NamedColour("YELLOW"))
        self.setnormal(wx.NamedColour("BLACK"), wx.NamedColour("WHITE"))
        self.setfont(wx.TheFontList.FindOrCreateFont(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
        self.OnSize(None)
        self.highlightrange(None, None)
        # other stuff
        self._create_context_menu()
        self._map_events()

    def _map_events(self):
        wx.EVT_SCROLLWIN(self, self.OnScrollWin)
        wx.EVT_PAINT(self, self.OnPaint)
        wx.EVT_SIZE(self, self.OnSize)
        wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
        wx.EVT_SET_FOCUS(self, self.OnGainFocus)
        wx.EVT_KILL_FOCUS(self, self.OnLoseFocus)
        wx.EVT_LEFT_DOWN(self, self.OnStartSelection)
        wx.EVT_LEFT_UP(self, self.OnEndSelection)
        wx.EVT_MOTION(self, self.OnMakeSelection)
        wx.EVT_RIGHT_UP(self, self.OnRightClick)

    def _create_context_menu(self):
        self._reload_menu_id=self._apply_menu_id=None
        menu_items=(
            ('File', (('Load', self.OnLoadFile),
                      ('Save As', self.OnSaveAs),
                      ('Save Selection As', self.OnSaveSelection),
                      ('Save Hexdump As', self.OnSaveHexdumpAs))),
            ('Set Selection', (('Start', self.OnStartSelMenu),
                               ('End', self.OnEndSelMenu))),
            ('Value', self.OnViewValue),
            ('Search', (('Search', self.OnSearch),
                        ('Search Again', self.OnSearchAgain))),
            ('Import Python Module', self.OnImportModule),
            ('Reload Python Module', self.OnReloadModule, '_reload_menu_id'),
            ('Apply Python Func', self.OnApplyFunc, '_apply_menu_id'),
            ('Template', (('Load', self.OnTemplateLoad),
                          ('Save As', self.OnTemplateSaveAs),
                          ('Edit', self.OnTemplateEdit),
                          ('Apply', self.OnTemplateApply)))
            )
        self._bgmenu=wx.Menu()
        for menu_item in menu_items:
            if isinstance(menu_item[1], tuple):
                # submenu
                sub_menu=wx.Menu()
                for submenu_item in menu_item[1]:
                    id=wx.NewId()
                    sub_menu.Append(id, submenu_item[0])
                    wx.EVT_MENU(self, id, submenu_item[1])
                self._bgmenu.AppendMenu(wx.NewId(), menu_item[0], sub_menu)
            else:
                # regular menu item
                id=wx.NewId()
                self._bgmenu.Append(id, menu_item[0])
                wx.EVT_MENU(self, id, menu_item[1])
                if len(menu_item)>2:
                    # need to save menu ID
                    setattr(self, menu_item[2], id)

    def SetData(self, data):
        self.data=data
        self.needsupdate=True
        self.updatescrollbars()
        self.Refresh()

    def SetTitle(self, title):
        self.title=title

    def SetStatusDisplay(self, _set_pos=None, _set_sel=None, _set_val=None):
        self.set_pos=_set_pos or self._set_pos
        self.set_sel=_set_sel or self._set_sel
        self.set_val=_set_val or self._set_val

    def OnEraseBackground(self, _):
        pass
    def _set_pos(self, pos):
        pass
    def _set_sel(self, sel_start, sel_end):
        pass
    def _set_val(self, v):
        pass

    def _to_char_line(self, x, y):
        """Convert an x,y point to (char, line)
        """
        return x/self.charwidth, y/self.charheight
    def _to_xy(self, char, line):
        return char*self.charwidth, line*self.charheight
    def _to_buffer_offset(self, char, line):
        if char in self._hex_range:
            if char>self._hex_range_start2:
                char-=1
            if ((char-self._hex_range_start)%3)<2:
                return line*16+(char-self._hex_range_start)/3
        elif char in self._ascii_range:
            return line*16+char-self._ascii_range_start
    def _set_and_move(self, evt):
        c,l=self._to_char_line(evt.GetX(), evt.GetY())
        self.GetCaret().Move(self._to_xy(c, l))
        x0, y0=self.GetViewStart()
        char_x=c+x0
        line_y=l+y0
        return self._to_buffer_offset(char_x, line_y)
    _value_formats=(
        ('unsigned char', 'B', struct.calcsize('B')),
        ('signed char', 'b', struct.calcsize('b')),
        ('LE unsigned short', '<H', struct.calcsize('<H')),
        ('LE signed short', '<h', struct.calcsize('<h')),
        ('BE unsigned short', '>H', struct.calcsize('>H')),
        ('BE signed short', '>h', struct.calcsize('>h')),
        ('LE unsigned int', '<I', struct.calcsize('<I')),
        ('LE signed int', '<i', struct.calcsize('<i')),
        ('BE unsigned int', '>I', struct.calcsize('>I')),
        ('BE signed int', '>i', struct.calcsize('>i')),
        )
    def _gen_values(self, _data, _ofs):
        """ Generate the values of various number formats starting at the
        current offset.
        """
        n=_data[_ofs:]
        len_n=len(n)
        s='0x%X=%d'%(_ofs, _ofs)
        res=[{ 'Data Offset': s}, {'':''} ]
        for i,e in enumerate(self._value_formats):
            if len_n<e[2]:
                continue
            v=struct.unpack(e[1], n[:e[2]])[0]
            if i%2:
                s='%d'%v
            else:
                fmt='0x%0'+str(e[2]*2)+'X=%d'
                s=fmt%(v,v)
            res.append({ e[0]: s })
        return res

    def _apply_template(self, template_name):
        # if user specifies a block, encode that,
        if self.highlightstart is None or self.highlightstart==-1 or \
           self.highlightend is None or self.highlightend==-1:
            # no selection
            _data=self.data[self.current_ofs:]
            _ofs=self.current_ofs
        else:
            _data=self.data[self.highlightstart:self.highlightend]
            _ofs=self.highlightstart
        for f in self._templates:
            if f.name==template_name:
                l=[{ 'Template': f.name },
                   { 'Data Offset': '0x%04X=%d'%(_ofs, _ofs) }]
                return l+f.encode(_data, _ofs)
        return []

    def _display_result(self, result):
        """ Display the results from applying a Python routine over the data
        """
        s=''
        for d in result:
            for k,e in d.items():
                s+=k+':\t'+e+'\n'
        dlg=wx.MessageDialog(self, s, 'Results', style=wx.OK)
        dlg.ShowModal()
        dlg.Destroy()

    def OnLoadFile(self, _):
        dlg=wx.FileDialog(self, 'Select a file to load',
                          style=wx.OPEN|wx.FILE_MUST_EXIST)
        if dlg.ShowModal()==wx.ID_OK:
            self.SetData(file(dlg.GetPath(), 'rb').read())
        dlg.Destroy()
    def OnSaveAs(self, _):
        dlg=wx.FileDialog(self, 'Select a file to save',
                          style=wx.SAVE|wx.OVERWRITE_PROMPT)
        if dlg.ShowModal()==wx.ID_OK:
            file(dlg.GetPath(), 'wb').write(self.data)
        dlg.Destroy()
    def hexdumpdata(self):
        res=""
        l=len(self.data)
        if self.title:
            res += self.title+": "+`l`+" bytes\n"
        res += "<#! !#>\n"
        pos=0
        while pos<l:
            text="%08X "%(pos)
            line=self.data[pos:pos+16]
            for i in range(len(line)):
                text+="%02X "%(ord(line[i]))
            text+="   "*(16-len(line))
            text+="    "
            for i in range(len(line)):
                c=line[i]
                if (ord(c)>=32 and string.printable.find(c)>=0):
                    text+=c
                else:
                    text+='.'
            res+=text+"\n"
            pos+=16
        return res
        
    def OnSaveHexdumpAs(self, _):
        dlg=wx.FileDialog(self, 'Select a file to save',
                          style=wx.SAVE|wx.OVERWRITE_PROMPT)
        if dlg.ShowModal()==wx.ID_OK:
            file(dlg.GetPath(), 'wb').write(self.hexdumpdata())
        dlg.Destroy()
    def OnSaveSelection(self, _):
        if self.highlightstart is None or self.highlightstart==-1 or \
           self.highlightend is None or self.highlightend==-1:
            # no selection
            return
        dlg=wx.FileDialog(self, 'Select a file to save',
                          style=wx.SAVE|wx.OVERWRITE_PROMPT)
        if dlg.ShowModal()==wx.ID_OK:
            file(dlg.GetPath(), 'wb').write(
                self.data[self.highlightstart:self.highlightend])
        dlg.Destroy()

    def OnReloadModule(self, _):
        try:
            reload(self._module)
        except:
            self._module=None
            w=wx.MessageDialog(self, 'Failed to reload module',
                               'Reload Module Error',
                               style=wx.OK|wx.ICON_ERROR)
            w.ShowModal()
            w.Destroy()
    def OnApplyFunc(self, _):
        choices=[x for x in dir(self._module) \
                 if callable(getattr(self._module, x))]
        dlg=wx.SingleChoiceDialog(self, 'Select a function to apply:',
                            'Apply Python Func',
                            choices)
        if dlg.ShowModal()==wx.ID_OK:
            try:
                res=getattr(self._module, dlg.GetStringSelection())(
                    self, self.data, self.current_ofs)
                self._display_result(res)
            except:
                w=wx.MessageDialog(self, 'Apply Func raised an exception',
                                   'Apply Func Error',
                                   style=wx.OK|wx.ICON_ERROR)
                w.ShowModal()
                w.Destroy()
        dlg.Destroy()
    def OnImportModule(self, _):
        dlg=wx.TextEntryDialog(self, 'Enter the name of a Python Module:',
                               'Module Import')
        if dlg.ShowModal()==wx.ID_OK:
            try:
                self._module=__import__(dlg.GetValue())
            except ImportError:
                self._module=None
                w=wx.MessageDialog(self, 'Failed to import module: '+dlg.GetValue(),
                                 'Module Import Error',
                                 style=wx.OK|wx.ICON_ERROR)
                w.ShowModal()
                w.Destroy()
        dlg.Destroy()

    def OnStartSelMenu(self, evt):
        ofs=self.current_ofs
        if ofs is not None:
            self.highlightstart=ofs
            self.needsupdate=True
            self.Refresh()
        self.set_sel(self.highlightstart, self.highlightend)
            
    def OnEndSelMenu(self, _):
        ofs=self.current_ofs
        if ofs is not None:
            self.highlightend=ofs+1
            self.needsupdate=True
            self.Refresh()
        self.set_sel(self.highlightstart, self.highlightend)

    def OnViewValue(self, _):
        ofs=self.current_ofs
        if ofs is not None:
            self._display_result(self._gen_values(self.data, ofs))

    def OnStartSelection(self, evt):
        self.highlightstart=self.highlightend=None
        ofs=self._set_and_move(evt)
        if ofs is not None:
            self.highlightstart=ofs
            self.dragging=True
            self.set_val(self.data[ofs:])
        else:
            self.set_val(None)
        self.needsupdate=True
        self.Refresh()
        self.set_pos(ofs)
        self.set_sel(self.highlightstart, self.highlightend)
        
    def OnMakeSelection(self, evt):
        if not self.dragging:
            return
        ofs=self._set_and_move(evt)
        if ofs is not None:
            self.highlightend=ofs+1
            self.needsupdate=True
            self.Refresh()
        self.set_pos(ofs)
        self.set_sel(self.highlightstart, self.highlightend)
    def OnEndSelection(self, evt):
        self.dragging=False
        ofs=self._set_and_move(evt)
        self.set_pos(ofs)
        self.set_sel(self.highlightstart, self.highlightend)

    def OnRightClick(self, evt):
        self.current_ofs=self._set_and_move(evt)
        if self.current_ofs is None:
            self.set_val(None)
        else:
            self.set_val(self.data[self.current_ofs:])
        self.set_pos(self.current_ofs)
        self._bgmenu.Enable(self._apply_menu_id, self._module is not None)
        self._bgmenu.Enable(self._reload_menu_id, self._module is not None)
        self.PopupMenu(self._bgmenu, evt.GetPosition())

    def OnTemplateLoad(self, _):
        dlg=wx.FileDialog(self, 'Select a file to load',
                          wildcard='*.tmpl',
                          style=wx.OPEN|wx.FILE_MUST_EXIST)
        if dlg.ShowModal()==wx.ID_OK:
            result={}
            try:
                execfile(dlg.GetPath())
            except UnicodeError:
                common.unicode_execfile(dlg.GetPath())
            exist_keys={}
            for i,e in enumerate(self._templates):
                exist_keys[e.name]=i
            for d in result['templates']:
                data_struct=DataStruct('new struct')
                data_struct.set(d)
                if exist_keys.has_key(data_struct.name):
                    self._templates[exist_keys[data_struct.name]]=data_struct
                else:
                    self._templates.append(data_struct)
        dlg.Destroy()
    def OnTemplateSaveAs(self, _):
        dlg=wx.FileDialog(self, 'Select a file to save',
                          wildcard='*.tmpl',
                          style=wx.SAVE|wx.OVERWRITE_PROMPT)
        if dlg.ShowModal()==wx.ID_OK:
            r=[x.get() for x in self._templates]
            common.writeversionindexfile(dlg.GetPath(),
                                         { 'templates': r }, 1)
        dlg.Destroy()
    def OnTemplateApply(self, _):
        if not self._templates:
            # no templates to apply
            return
        choices=[x.name for x in self._templates]
        dlg=wx.SingleChoiceDialog(self, 'Select a template to apply:',
                            'Apply Data Template',
                            choices)
        if dlg.ShowModal()==wx.ID_OK:
            try:
                res=self._apply_template(dlg.GetStringSelection())
                self._display_result(res)
            except:
                raise
                w=wx.MessageDialog(self, 'Apply Template raised an exception',
                                   'Apply Template Error',
                                   style=wx.OK|wx.ICON_ERROR)
                w.ShowModal()
                w.Destroy()
        dlg.Destroy()
    def OnTemplateEdit(self, _):
        dlg=TemplateDialog(self)
        dlg.set(self._templates)
        if dlg.ShowModal()==wx.ID_OK:
            self._templates=dlg.get()
        dlg.Destroy()

    def OnSearch(self, evt):
        dlg=wx.TextEntryDialog(self, 'Enter data to search (1 0x23 045 ...):',
                               'Search Data')
        if dlg.ShowModal()==wx.ID_OK:
            l=dlg.GetValue().split(' ')
            s=''
            for e in l:
                if e[0:2]=='0x':
                    s+=chr(int(e, 16))
                elif e[0]=='0':
                    s+=chr(int(e, 8))
                else:
                    s+=chr(int(e))
            i=self.data[self.current_ofs:].find(s)
            if i!=-1:
                self._search_string=s
                self.highlightstart=i+self.current_ofs
                self.highlightend=self.highlightstart+len(s)
                self.needsupdate=True
                self.Refresh()
                self.set_sel(self.highlightstart, self.highlightend)
            else:
                self._search_string=None
    def OnSearchAgain(self, evt):
        if self._search_string is not None:
            i=self.data[self.current_ofs:].find(self._search_string)
            if i==-1:
                return
            self.highlightstart=i+self.current_ofs
            self.highlightend=self.highlightstart+len(self._search_string)
            self.needsupdate=True
            self.Refresh()
            self.set_sel(self.highlightstart, self.highlightend)

    def OnSize(self, evt):
        # uncomment these lines to prevent going wider than is needed
        # if self.width>self.widthinchars*self.charwidth:
        #    self.SetClientSize( (self.widthinchars*self.charwidth, self.height) )
        if evt is None:
            self.width=(self.widthinchars+3)*self.charwidth
            self.height=self.charheight*20
            self.SetClientSize((self.width, self.height))
            self.SetCaret(wx.Caret(self, (self.charwidth, self.charheight)))
            self.GetCaret().Show(True)
        else:
            self.width,self.height=self.GetClientSizeTuple()
        self.needsupdate=True

    def OnGainFocus(self,_):
        self.hasfocus=True
        self.needsupdate=True
        self.Refresh()

    def OnLoseFocus(self,_):
        self.hasfocus=False
        self.needsupdate=True
        self.Refresh()

    def highlightrange(self, start, end):
        self.needsupdate=True
        self.highlightstart=start
        self.highlightend=end
        self.Refresh()
        self.set_pos(None)
        self.set_sel(self.highlightstart, self.highlightend)
        self.set_val(None)

    def _ishighlighted(self, pos):
        return pos>=self.highlightstart and pos<self.highlightend

    def sethighlight(self, foreground, background):
        self.highlight=foreground,background

    def setnormal(self, foreground, background):
        self.normal=foreground,background

    def setfont(self, font):
        dc=wx.ClientDC(self)
        dc.SetFont(font)
        self.charwidth, self.charheight=dc.GetTextExtent("M")
        self.font=font
        self.updatescrollbars()

    def updatescrollbars(self):
        # how many lines are we?
        lines=len(self.data)/16
        if lines==0 or len(self.data)%16:
            lines+=1
        self.datalines=lines
##        lines+=1 # status line
        # fixed width
        self.widthinchars=8+2+3*16+1+2+16
        self.SetScrollbars(self.charwidth, self.charheight, self.widthinchars, lines, self.GetViewStart()[0], self.GetViewStart()[1])

    def _setnormal(self,dc):
        dc.SetTextForeground(self.normal[0])
        dc.SetTextBackground(self.normal[1])

    def _sethighlight(self,dc):
        dc.SetTextForeground(self.highlight[0])
        dc.SetTextBackground(self.highlight[1])    

    def _setstatus(self,dc):
        dc.SetTextForeground(self.normal[1])
        dc.SetTextBackground(self.normal[0])
        dc.SetBrush(wx.BLACK_BRUSH)
        

    def OnDraw(self, dc):
        xd,yd=self.GetViewStart()
        st=0  # 0=normal, 1=highlight
        dc.BeginDrawing()
        dc.SetBackgroundMode(wx.SOLID)
        dc.SetFont(self.font)
        for line in range(yd, min(self.datalines, yd+self.height/self.charheight+1)):
            # address
            self._setnormal(dc)
            st=0
            dc.DrawText("%08X" % (line*16), 0, line*self.charheight)
            # bytes
            for i in range(16):
                pos=line*16+i
                if pos>=len(self.data):
                    break
                hl=self._ishighlighted(pos)
                if hl!=st:
                    if hl:
                        st=1
                        self._sethighlight(dc)
                    else:
                        st=0
                        self._setnormal(dc)
                if hl:
                    space=""
                    if i<15:
                        if self._ishighlighted(pos+1):
                            space=" "
                            if i==7:
                                space="  "
                else:
                    space=""
                c=self.data[pos]
                dc.DrawText("%02X%s" % (ord(c),space), (10+(3*i)+(i>=8))*self.charwidth, line*self.charheight)
                if not (ord(c)>=32 and string.printable.find(c)>=0):
                    c='.'
                dc.DrawText(c, (10+(3*16)+2+i)*self.charwidth, line*self.charheight)

##        if self.hasfocus:
##            self._setstatus(dc)
##            w,h=self.GetClientSizeTuple()
##            dc.DrawRectangle(0,h-self.charheight+yd*self.charheight,self.widthinchars*self.charwidth,self.charheight)
##            dc.DrawText("A test of stuff "+`yd`, 0, h-self.charheight+yd*self.charheight)
                
        dc.EndDrawing()

    def updatebuffer(self):
        if self.buffer is None or \
           self.buffer.GetWidth()!=self.width or \
           self.buffer.GetHeight()!=self.height:
            if self.buffer is not None:
                del self.buffer
            self.buffer=wx.EmptyBitmap(self.width, self.height)

        mdc=wx.MemoryDC()
        mdc.SelectObject(self.buffer)
        mdc.SetBackground(wx.TheBrushList.FindOrCreateBrush(self.GetBackgroundColour(), wx.SOLID))
        mdc.Clear()
        self.PrepareDC(mdc)
        self.OnDraw(mdc)
        mdc.SelectObject(wx.NullBitmap)
        del mdc

    def OnPaint(self, event):
        if self.needsupdate:
            self.needsupdate=False
            self.updatebuffer()
        dc=wx.PaintDC(self)
        dc.BeginDrawing()
        dc.DrawBitmap(self.buffer, 0, 0, False)
        dc.EndDrawing()

    def OnScrollWin(self, event):
        self.needsupdate=True
        self.Refresh() # clear whole widget
        event.Skip() # default event handlers now do scrolling etc

class HexEditorDialog(wx.Dialog):
    _pane_widths=[-2, -3, -4]
    _pos_pane_index=0
    _sel_pane_index=1
    _val_pane_index=2
    def __init__(self, parent, data='', title='BitPim Hex Editor', helpd_id=-1):
        super(HexEditorDialog, self).__init__(parent, -1, title,
                                              size=(500, 500),
                                              style=wx.DEFAULT_DIALOG_STYLE|\
                                              wx.RESIZE_BORDER)
        self._status_bar=wx.StatusBar(self, -1)
        self._status_bar.SetFieldsCount(len(self._pane_widths))
        self._status_bar.SetStatusWidths(self._pane_widths)
        vbs=wx.BoxSizer(wx.VERTICAL)
        self._hex_editor=HexEditor(self, _set_pos=self.set_pos,
                                   _set_val=self.set_val,
                                   _set_sel=self.set_sel)
        self._hex_editor.SetData(data)
        self._hex_editor.SetTitle(title)
        vbs.Add(self._hex_editor, 1, wx.EXPAND|wx.ALL, 5)
        vbs.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
        ok_btn=wx.Button(self, wx.ID_OK, 'OK')
        vbs.Add(ok_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        vbs.Add(self._status_bar, 0, wx.EXPAND|wx.ALL, 0)
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)
    def set_pos(self, pos):
        """Display the current buffer offset in the format of
        Pos: 0x12=18
        """
        if pos is None:
            s=''
        else:
            s='Pos: 0x%X=%d'%(pos, pos)
        self._status_bar.SetStatusText(s, self._pos_pane_index)
    def set_sel(self, sel_start, sel_end):
        if sel_start is None or sel_start==-1 or\
           sel_end is None or sel_end ==-1:
            s=''
        else:
            sel_len=sel_end-sel_start
            sel_end-=1
            s='Sel: 0x%X=%d to 0x%X=%d (0x%X=%d bytes)'%(
                sel_start, sel_start, sel_end, sel_end,
                sel_len, sel_len)
        self._status_bar.SetStatusText(s, self._sel_pane_index)
    def set_val(self, v):
        if v:
            # char
            s='Val: 0x%02X=%d'%(ord(v[0]), ord(v[0]))
            if len(v)>1:
                # short
                u_s=struct.unpack('<H', v[:struct.calcsize('<H')])[0]
                s+=' 0x%04X=%d'%(u_s,  u_s)
            if len(v)>3:
                # int/long
                u_i=struct.unpack('<I', v[:struct.calcsize('<I')])[0]
                s+=' 0x%08X=%d'%(u_i, u_i)
        else:
            s=''
        self._status_bar.SetStatusText(s, self._val_pane_index)

    def set(self, data):
        self._hex_editor.SetData(data)

        
if __name__=='__main__':
    import sys

    if len(sys.argv)!=2:
        print 'Usage:',sys.argv[0],'<File Name>'
        sys.exit(1)
    app=wx.PySimpleApp()
    dlg=HexEditorDialog(None, file(sys.argv[1], 'rb').read(),
                        sys.argv[1])
    if True:
        dlg.ShowModal()
    else:
        import hotshot
        f=hotshot.Profile("hexeprof",1)
        f.runcall(dlg.ShowModal)
        f.close()
        import hotshot.stats
        stats=hotshot.stats.load("hexeprof")
        stats.strip_dirs()
        # stats.sort_stats("cumulative")
        stats.sort_stats("time", "calls")
        stats.print_stats(30)

    dlg.Destroy()
    sys.exit(0)
hexeditor.py.diff (application/octet-stream, 728 B)
Index: hexeditor.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/hexeditor.py,v
retrieving revision 1.15
diff -u -r1.15 hexeditor.py
--- hexeditor.py	15 Jun 2005 02:52:21 -0000	1.15
+++ hexeditor.py	8 Oct 2005 18:40:22 -0000
@@ -886,7 +886,10 @@
                           style=wx.OPEN|wx.FILE_MUST_EXIST)
         if dlg.ShowModal()==wx.ID_OK:
             result={}
-            execfile(dlg.GetPath())
+            try:
+                execfile(dlg.GetPath())
+            except UnicodeError:
+                common.unicode_execfile(dlg.GetPath())
             exist_keys={}
             for i,e in enumerate(self._templates):
                 exist_keys[e.name]=i
makedist.py (text/plain, 13.8 KB)
#!/usr/bin/env python
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
### Copyright (C) 2003-2004 Steven Palm <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: makedist.py,v 1.94 2005/07/19 06:39:06 rogerb Exp $


# Runs on Linux, Windows and Mac

"""Builds a binary distribution of BitPim

This code runs on Windows, Linux and Mac, and will create binary distributions
suitable for mass use"""

import os
import shutil
import sys
import glob
import re
import version

def rmrf(path):
    """Delete directory tree like rm -rf does"""
    entries=os.listdir(path)
    for e in entries:
        fullname=os.path.join(path,e)
        if os.path.isdir(fullname):
            rmrf(fullname)
        else:
            os.remove(fullname)
    os.rmdir(path)

def run(*args):
    """Execute the command.

    The path is searched"""
    print `args`
    sl=os.spawnl
    if sys.platform!='win32':
        sl=os.spawnlp
        ret=apply(sl, (os.P_WAIT,args[0])+args)
    else:
        # win98 was fine with above code, winxp just chokes
        # so we call system() instead
        str=""
        for a in args:
            if a.find(' ')>=0:
                str+=' "'+a+'"'
            else:
                str+=" "+a
        str=str[1:] # remove first space
        # If you ever wanted proof how idiotic windows is, here it is
        # if there is a value enclosed in double quotes, it is
        # taken as the window title, even if it comes after all
        # the switches, so i have to supply one, otherwise it mistakes
        # the command to run as the window title
        ret=os.system('start /b /wait "%s" %s' % (args[0], str))
    print "returned", ret
    if ret!=0: raise Exception("The command failed")

def sanitycheck():
    "Check everything is ok"
    print "=== Sanity check ==="

    print "python version",
    if sys.version_info<(2,3):
       raise Exception("Should be at least Python 2,3 - this is "+sys.version)
    print "  OK"

    print "wxPython version",
    import wx
    if wx.VERSION[:4]!=(2,6,1,0):
        raise Exception("Should be wxPython 2.6.1.0.  This is "+`wx.VERSION`)
    print "  OK"

    print "wxPython is unicode build",
    if not wx.USE_UNICODE:
        raise Exception("You need a unicode build of wxPython")
    print "  OK"
    
    print "native.usb",
    import native.usb
    print "  OK"

    print "pycrypto version",
    expect='2.0.1'
    import Crypto
    if Crypto.__version__!=expect:
        raise Exception("Should be %s version of pycrypto - you have %s" % (expect, Crypto.__version__))
    print "  OK"

    print "paramiko version",
    expect='1.4 (oddish)'
    import paramiko
    if paramiko.__version__!=expect:
        raise Exception("Should be %s version of paramiko - you have %s" % (expect, paramiko.__version__))
    print "  OK"
    
    print "bitfling",
    import bitfling
    print "  OK"

    print "pyserial",
    import serial
    print "  OK"

    print "apsw",
    import apsw
    ver="3.2.7-r1"
    if apsw.apswversion()!=ver:
        raise Exception("Should be apsw version %s - you have %s" % (ver, apsw.apswversion()))
    print "  OK"

    print "sqlite",
    ver="3.2.7"
    if apsw.sqlitelibversion()!=ver:
        raise Exception("Should be sqlite version %s - you have %s" % (ver, apsw.sqlitelibversion()))
    print "  OK"

        
    print "jaro/winkler string matcher",
    import native.strings.jarow
    print "  OK"

    # bsddb (Linux only, for evolution)
    if sys.platform=="linux2":
        print "bsddb ",
        import bsddb
        print "  OK"

    print "=== All checks out ==="
    

def clean():
    """Remove temporary directories created by various packaging tools"""
    if os.path.isdir("dist"):
        rmrf("dist")
    if os.path.isdir("build"):
        rmrf("build")
    for file in ['setup.cfg']:
        if os.path.isfile(file):
            os.remove(file)

def resources():
    """Get a list of the resources (images, executables, sounds etc) we ship

    @rtype: dict
    @return: The key for each entry in the dict is a directory name, and the value
             is a list of files within that directory"""
    tbl={}
    # list of files
    exts=[ '*.xy', '*.png', '*.ttf', '*.wav', '*.jpg', '*.css', '*.pdc', '*.ids']
    if sys.platform=='win32':
        # on windows we also want the chm help file and the manifest needed to get Xp style widgets
        exts=exts+['*.chm', '*.manifest']
        exts=exts+['helpers/*.exe','helpers/*.dll']
    if sys.platform=='linux2':
        exts=exts+['helpers/*.lbin', '*.htb']
    if sys.platform=='darwin':
        exts=exts+['helpers/*.mbin', '*.htb']
    # list of directories to look in
    dirs=[ os.path.join('.', 'resources'), '.' ]
    # don't ship list
    dontship=["group.png"]         # group is not used as is vx4400 specific anyway
    dontship.append("pvconv.exe")  # Qualcomm won't answer if I can ship this
    for wildcard in exts:
        for dir in dirs:
            for file in glob.glob(os.path.join(dir, wildcard)):
                if os.path.basename(file).lower() in dontship: continue 
                d=os.path.dirname(file)
                if not tbl.has_key(d):
                    tbl[d]=[]
                tbl[d].append(file)

    files=[]
    for i in tbl.keys():
        files.append( (i, tbl[i]) )

    return files

def getallencodings():
    "Return the list of encodings modules"
    # bring in every codec in existence
    import encodings
    
    res=[]
    for f in glob.glob(os.path.join(os.path.dirname(encodings.__file__), "*.py")):
        v="encodings."+os.path.basename(f)[:-3]
        if v.endswith("__init__"): continue
        if v not in res:
            try:
                __import__(v)
                res.append(v)
            except: # many codecs exist, but fail on import with all sorts of random exceptions
                pass
    return res

def isofficialbuild():
    "Work out if this is an official build"
    import socket
    h=socket.gethostname().lower()
    # not built by rogerb (or stevep/n9yty) are unofficial
    return h in ('rh9bitpim.rogerbinns.com', "roger-sqyvr14d3",
             "smpbook.n9yty.com", "smpbook.local.", "rogerbmac.rogerbinns.com", "rogerbmac.local")

def ensureofficial():
    """If this is not an official build then ensure that version.vendor doesn't say it is"""
    if not isofficialbuild():
        if version.vendor=="official":
            # it isn't official, so modify file
            f=open("version.py", "rt").read()
            newf=f.replace('vendor="official"', 'vendor="unofficial"')
            assert newf!=f
            open("version.py", "wt").write(newf)

def copyresources(dest):
    """Copies the resources to the specified destination directory.

    The directory structure is preserved in the copy"""
    for dir,files in resources():
	if not os.path.exists(os.path.join(dest,dir)):
            os.makedirs(os.path.join(dest,dir))
        for file in files:
            print file
            shutil.copy(file, os.path.join(dest, file))

def getsubs():
    """Gets the list of substitutions to be performed on the template files

    A partial current list is:
        - VERSION: The full version number of the product
        - OUTFILE: The filename of resulting installer (Windows specific)
        - NAME:    The product name (in lower case)
        - RELEASE: The release is an increment if multiple releases are
                   made of the same version.  This corresponds to the last
                   part of the filename for RPM packages

    @rtype: dict
    """
    # Get version info
    import version
    
    verstr=version.version
    if version.testver:
        verstr+="-test"+`version.testver`

    if not isofficialbuild():
        verstr+="-unofficial"

    filename="bitpim-"+verstr+"-setup"

    if sys.platform=='linux2':
        # linux needs all the dash bits as underscores
        verstr=re.sub("-", "_", verstr)
            


    res={}
    res['VERSION']=verstr
    res['OUTFILE']=filename
    res['NAME']=version.name.lower()
    res['RELEASE']=`version.release`
    res['COPYRIGHT']=version.copyright
    res['DQVERSION']=".".join([`i` for i in version.dqver])
    res['DESCRIPTION']=version.description
    res['COPYRIGHT']=version.copyright
    res['URL']=version.url
    return res

def dosubs(infile, outfile, subs):
    """Performs substitutions on a template file

    @param infile:  filename to read
    @param outfile: filename to write resutl to
    @type subs: dict
    @param subs: the substitutions to make
    """
    stuff=open(infile, "rt").read()

    for k in subs:
        stuff=re.sub("%%"+k+"%%", subs[k], stuff)

    open(outfile, "w").write(stuff)


def windowsbuild():
    """Do all the steps necessary to make a Windows installer"""
    # check libusb
    import win32api
    try:
        win32api.FreeLibrary(win32api.LoadLibrary("libusb0.dll"))
    except:
        raise Exception("You need libusb0.dll to be available to do a build.  You only need the dll, not the rest of libusb-win32.  (It doesn't have to be available on the end user system, but does need to be present to do a correct build")
        
    # clean up
    clean()
    # need setup.cfg with version info in it
    v=getsubs()
    # Build python and stuff
    coms=[f[:-3] for f in glob.glob("com_*.py")]
    run( "c:\\python23\python", "p2econfig.py", "py2exe", "-O2", "-i", ",".join(coms+getallencodings()))
    # Rename to correct thing
    os.rename("dist\\bp.exe", "dist\\bitpim.exe")
    # Remove files I don't want - py2exe did this correctly once
    for f in ("libusb0.dll", "w9xpopen.exe"):
        ff=os.path.join("dist", f)
        if os.path.isfile(ff):
            os.remove(ff)

    v=getsubs()
    dosubs("bitpim.iss", "bitpim-out.iss", v)
    filename=v['OUTFILE']

    # Run innosetup
    run("c:\\program files\\inno setup 5\\compil32.exe", "/cc", "bitpim-out.iss")

    # copy to S: drive
    if os.path.isdir("s:\\"):
        shutil.copyfile("dist\\"+filename+".exe", "s:\\bprel\\"+filename+".exe")

def linuxbuild():
    """Do all the steps necessary to make a Linux RPM"""
    try:
        rmrf("i386")
    except:
        pass
    clean()
    os.mkdir("dist")
    coms=[f[:-3] for f in glob.glob("com_*.py")]
    cxfreezedir='/opt/cx_Freeze-3.0.1'
    j=os.path.join
    run("env", "PYTHONOPTIMIZE=2", "PATH=%s:%s" % (cxfreezedir, os.environ["PATH"]), "FreezePython", 
        "--install-dir="+os.path.abspath("dist"), "--base-name", j(cxfreezedir,"bases","Console"), 
        "--init-script", j(cxfreezedir,"initscripts","ConsoleSetLibPath.py"), "--include-modules", 
        ",".join(coms+getallencodings()), "bp.py")
    copyresources("dist")
    v=getsubs()
    instdir="/usr/lib/%s-%s" % (v['NAME'], v['VERSION'])
    run("sh", "-e", "-c", "cd dist ; ../unixpkg/getallwxlibs")
    run("sh", "-e", "-c", "cd dist ; ../unixpkg/rpathfixup")
    run("sh", "-e", "-c", "cd dist ; strip *.so")
    run("sh", "-e", "-c", "cd dist ; tar cvf ../dist.tar *")
    clean()
    os.mkdir("dist")
    for f in glob.glob("unixpkg/*"):
        if os.path.isfile(f):
            shutil.copy(f, "dist")

    dosubs("unixpkg/bitpim.spec", "dist/bitpim.spec", v)
    shutil.copy("dist.tar", "dist")
    os.remove("dist.tar")

    n="%s-%s" % (v['NAME'], v['VERSION'])
    try:
        rmrf(n)
    except:
        pass
    
    os.rename("dist", n)
    run("tar", "cvfz", n+".tar.gz", n)
    rmrf(n)
    run("rpmbuild", "-ta", '--define=_rpmdir %s' % (os.getcwd(),), "--target", "i386-linux", n+".tar.gz")
    os.remove(n+".tar.gz")
    
def macbuild():
    """Do all the steps necessary to make a Mac dimg
    
    Now using the py2app stuff from Bob Ippolito <[email protected]>,
    the replacement for the crufty `bundlebuilder` stuff previously
    used for MacOS X executables.  This will be the new standard for
    PythonMac bundling.
    
    It can be found for now at:
    
    http://undefined.org/python/#py2app
    
    """
    try:
        rmrf("dist")
    except:
        pass
    clean()
    os.mkdir("dist")
    from distutils.core import setup
    import py2app
    import string
    name = "BitPim"
    iconfile = "bitpim.icns"
    v=getsubs()
    data_files=[]
    includePackages=[]
    verstr = "%s-%s" % (v['NAME'], version.versionstring)

    includes=[f[:-3] for f in glob.glob("com_*.py")]
    
    plist = dict(
        CFBundleIconFile            = iconfile,
        CFBundleName                = name,
        CFBundleShortVersionString  = verstr,
        CFBundleGetInfoString       = verstr,
        CFBundleExecutable          = name,
        CFBundleIdentifier          = 'org.bitpim.bitpim',
    )
    opts = dict(py2app=dict(
                 compressed=1,
                 iconfile=iconfile,
                 plist=plist,
                 includes=includes,
                 optimize=2
                 ))
    app = [ dict(
                 script="bp.py",
                 ),]
    setup( data_files = resources(), options = opts,  app = app, script_args = ("py2app",) )

    os.system("find dist -name '*.so' -print0 | xargs -0 strip -x")

    #
    if (os.uname()[2] >= "7.0.0"):
        ret=os.system('hdiutil create -srcfolder dist -volname BitPim -nouuid -noanyowners dist/PANTHER-%s.dmg' % verstr)
        print "image creation returned", ret
    else:
        # print "Create disk image with dist folder as the source in DiskCopy with name:\n    -> JAGUAR-%s.dmg <-." % verstr
        ret=os.system('/usr/local/bin/buildDMG.pl -buildDir=dist -compressionLevel=9 -volName=BitPim -dmgName=JAGUAR-%s.dmg dist/BitPim.app' % verstr)
        print "image creation returned", ret

if __name__=='__main__':
    # do a sanity check first
    sanitycheck()
    # ensure only official builds are marked as such
    ensureofficial()
    if sys.platform=='win32':
        windowsbuild()
    elif sys.platform=='linux2':
        linuxbuild()
    elif sys.platform=='darwin':
        macbuild()
    else:
        print "Unknown platform", sys.platform
makedist.py.diff (application/octet-stream, 739 B)
Index: makedist.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/makedist.py,v
retrieving revision 1.94
diff -u -r1.94 makedist.py
--- makedist.py	19 Jul 2005 06:39:06 -0000	1.94
+++ makedist.py	8 Oct 2005 18:36:20 -0000
@@ -111,13 +111,13 @@
 
     print "apsw",
     import apsw
-    ver="3.2.2-r1"
+    ver="3.2.7-r1"
     if apsw.apswversion()!=ver:
         raise Exception("Should be apsw version %s - you have %s" % (ver, apsw.apswversion()))
     print "  OK"
 
     print "sqlite",
-    ver="3.2.2"
+    ver="3.2.7"
     if apsw.sqlitelibversion()!=ver:
         raise Exception("Should be sqlite version %s - you have %s" % (ver, apsw.sqlitelibversion()))
     print "  OK"
common.py.diff (application/octet-stream, 1.2 KB)
Index: common.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/common.py,v
retrieving revision 1.50
diff -u -r1.50 common.py
--- common.py	17 May 2005 08:39:48 -0000	1.50
+++ common.py	8 Oct 2005 18:17:44 -0000
@@ -202,9 +202,28 @@
                print formatexception()
                raise
 
+def unicode_execfile(filename, dict1=0, dict2=0):
+     # this version allows the path portion of the filename to 
+     # contain non-acsii characters, the filename itself cannot contain
+     # ascii characters, execfile does not work if the filename contains
+     # non-ascii characters
+     curdir=os.getcwdu()
+     filepath, file=os.path.split(filename)
+     os.chdir(filepath)
+     if dict1==0:
+        execfile(file)
+     elif dict2==0:
+        execfile(file, dict1)
+     else:
+        execfile(file, dict1, dict2)
+     os.chdir(curdir)
+
 def readversionedindexfile(filename, dict, versionhandlerfunc, currentversion):
      assert currentversion>0
-     execfile(filename, dict, dict)
+     try:
+         execfile(filename, dict, dict)
+     except UnicodeError:
+         unicode_execfile(filename, dict, dict)
      if not dict.has_key('FILEVERSION'):
           version=0
      else:
common.py (text/plain, 21.1 KB)
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: common.py,v 1.50 2005/05/17 08:39:48 rogerb Exp $


# Documentation
"""Various classes and functions that are used by GUI and command line versions of BitPim"""

# standard modules
import string
import cStringIO
import StringIO
import sys
import traceback
import tempfile
import random
import os

class FeatureNotAvailable(Exception):
     """The device doesn't support the feature"""
     def __init__(self, device, message="The device doesn't support the feature"):
          Exception.__init__(self, "%s: %s" % (device, message))
          self.device=device
          self.message=message

class IntegrityCheckFailed(Exception):
     def __init__(self, device, message):
          Exception.__init__(self, "%s: %s" % (device, message))
          self.device=device
          self.message=message

class PhoneBookBusyException(Exception):
     "The phonebook is busy on the phone"
     pass
                  
class HelperBinaryNotFound(Exception):
     def __init__(self, basename, fullname, paths):
          Exception.__init__(self, "Helper binary %s not found.  It should be in one of %s" % (fullname, ", ".join(paths)))
          self.basename=basename
          self.fullname=fullname
          self.paths=paths

class CommandExecutionFailed(Exception):
     def __init__(self, retcode, args):
          Exception.__init__(self, "Command execution failed with code %d: %s" % (retcode, " ".join(args)))
          self.retcode=retcode
          self.args=args
          
class ConversionNotSupported(Exception):
     def __init__(self, msg):
          Exception.__init__(self, msg)
          self.msg=msg

class InSafeModeException(Exception):
     def __init__(self):
          Exception.__init__(self, "BitPim is in safe mode - this operation has been blocked")

# generic comms exception and then various specialisations
class CommsException(Exception):
     """Generic commmunications exception"""
     def __init__(self, message, device="<>"):
        Exception.__init__(self, "%s: %s" % (device, message))
        self.device=device
        self.message=message

class CommsNeedConfiguring(CommsException):
     """The communication settings need to be configured"""
     pass

class CommsDeviceNeedsAttention(CommsException):
     """The communication port or device attached to it needs some
     manual intervention"""
     pass

class CommsDataCorruption(CommsException):
     """There was some form of data corruption"""
     pass

class CommsTimeout(CommsException):
    """Timeout while reading or writing the commport"""
    pass

class CommsOpenFailure(CommsException):
    """Failed to open the communications port/device"""
    pass

class CommsWrongPort(CommsException):
     """The wrong port has been selected, typically the modem port on an LG composite device"""
     pass

class AutoPortsFailure(CommsException):
     """Failed to auto detect a useful port"""
     def __init__(self, portstried):
          self.device="auto"
          self.message="Failed to auto-detect the port to use.  "
          if portstried is not None and len(portstried):
               self.message+="I tried "+", ".join(portstried)
          else:
               self.message+="I couldn't detect any candidate ports"
          CommsException.__init__(self, self.message, self.device)

def datatohexstring(data):
    """Returns a pretty printed hexdump of the data

    @rtype: string"""
    res=cStringIO.StringIO()
    lchar=""
    lhex="00000000 "
    for count in range(0, len(data)):
        b=ord(data[count])
        lhex=lhex+"%02x " % (b,)
        if b>=32 and string.printable.find(chr(b))>=0:
            lchar=lchar+chr(b)
        else:
            lchar=lchar+'.'

        if (count+1)%16==0:
            res.write(lhex+"    "+lchar+"\n")
            lhex="%08x " % (count+1,)
            lchar=""
    if len(data):
        while (count+1)%16!=0:
            count=count+1
            lhex=lhex+"   "
        res.write(lhex+"    "+lchar+"\n")
    return res.getvalue()

def hexify(data):
     "Turns binary data into a hex string (like the output of MD5/SHA hexdigest)"
     return "".join(["%02x" % (ord(x),) for x in data])

def prettyprintdict(dictionary, indent=0):
     """Returns a pretty printed version of the dictionary

     The elements are sorted into alphabetical order, and printed
     one per line.  Dictionaries within the values are also pretty
     printed suitably indented.

     @rtype: string"""

     res=cStringIO.StringIO()

     # the indent string
     istr="  "

     # opening brace
     res.write("%s{\n" % (istr*indent,))
     indent+=1

     # sort the keys
     keys=dictionary.keys()
     keys.sort()

     # print each key
     for k in keys:
          v=dictionary[k]
          # is it a dict
          if isinstance(v, dict):
               res.write("%s%s:\n%s\n" % (istr*indent, `k`, prettyprintdict(v, indent+1)))
          else:
               # is it a list of dicts?
               if isinstance(v, list):
                    dicts=0
                    for item in v:
                         if isinstance(item, dict):
                              dicts+=1
                    if dicts and dicts==len(v):
                         res.write("%s%s:\n%s[\n" % (istr*indent,`k`,istr*(indent+1)))
                         for item in v:
                              res.write(prettyprintdict(item, indent+2))
                         res.write("%s],\n" % (istr*(indent+1)))
                         continue
               res.write("%s%s: %s,\n" % (istr*indent, `k`, `v`))

     # closing brace
     indent-=1
     if indent>0:
          comma=","
     else:
          comma=""
     res.write("%s}%s\n" % (istr*indent,comma))

     return res.getvalue()

     
class exceptionwrap:
     """A debugging assist class that helps in tracking down functions returning exceptions"""
     def __init__(self, callable):
          self.callable=callable
          
     def __call__(self, *args, **kwargs):
          try:

               return self.callable(*args, **kwargs)
          except:
               print "in exception wrapped call", `self.callable`
               print formatexception()
               raise

def unicode_execfile(filename, dict1=0, dict2=0):
     # this version allows the path portion of the filename to 
     # contain non-acsii characters, the filename itself cannot contain
     # ascii characters, execfile does not work if the filename contains
     # non-ascii characters
     curdir=os.getcwdu()
     filepath, file=os.path.split(filename)
     os.chdir(filepath)
     if dict1==0:
        execfile(file)
     elif dict2==0:
        execfile(file, dict1)
     else:
        execfile(file, dict1, dict2)
     os.chdir(curdir)

def readversionedindexfile(filename, dict, versionhandlerfunc, currentversion):
     assert currentversion>0
     try:
         execfile(filename, dict, dict)
     except UnicodeError:
         unicode_execfile(filename, dict, dict)
     if not dict.has_key('FILEVERSION'):
          version=0
     else:
          version=dict['FILEVERSION']
          del dict['FILEVERSION']
     if version<currentversion:
          versionhandlerfunc(dict, version)

def writeversionindexfile(filename, dict, currentversion):
     assert currentversion>0
     f=open(filename, "w")
     for key in dict:
          v=dict[key]
          if isinstance(v, type({})):
               f.write("result['%s']=%s\n" % (key, prettyprintdict(dict[key])))
          else:
               f.write("result['%s']=%s\n" % (key, `v`))
     f.write("FILEVERSION=%d\n" % (currentversion,))
     f.close()

def formatexceptioneh(*excinfo):
     print formatexception(excinfo)

def formatexception(excinfo=None, lastframes=8):
     """Pretty print exception, including local variable information.

     See Python Cookbook, recipe 14.4.

     @param excinfo: tuple of information returned from sys.exc_info when
               the exception occurred.  If you don't supply this then
               information about the current exception being handled
               is used
     @param lastframes: local variables are shown for these number of
                  frames
     @return: A pretty printed string
               """
     if excinfo is None:
          excinfo=sys.exc_info()

     s=StringIO.StringIO()
     traceback.print_exception(*excinfo, **{'file': s})
     tb=excinfo[2]

     while True:
          if not tb.tb_next:
               break
          tb=tb.tb_next
     stack=[]
     f=tb.tb_frame
     while f:
          stack.append(f)
          f=f.f_back
     stack.reverse()
     if len(stack)>lastframes:
          stack=stack[-lastframes:]
     print >>s, "\nVariables by last %d frames, innermost last" % (lastframes,)
     for frame in stack:
          print >>s, ""
          print >>s, "Frame %s in %s at line %s" % (frame.f_code.co_name,
                                                    frame.f_code.co_filename,
                                                    frame.f_lineno)
          for key,value in frame.f_locals.items():
               # filter out modules
               if type(value)==type(sys):
                    continue
               print >>s,"%15s = " % (key,),
               try:
                    if type(value)==type({}):
                         kk=value.keys()
                         kk.sort()
                         print >>s, "Keys",kk
                         print >>s, "%15s   " % ("",) ,
                    print >>s,`value`[:80]
               except:
                    print >>s,"(Exception occurred printing value)"
     return s.getvalue()
                    
def gettempfilename(extension):
    "Returns a filename to be used for a temporary file"
    # safest Python 2.3 method
    x=tempfile.NamedTemporaryFile(suffix="."+extension)
    n=x.name
    x.close()
    del x
    return n

def getfullname(name):
     """Returns the object corresponding to name.
     Imports will be done as necessary to resolve
     every part of the name"""
     mods=name.split('.')
     dict={}
     for i in range(len(mods)):
          # import everything
          try:
               exec "import %s" % (".".join(mods[:i])) in dict, dict
          except:
               pass
     # ok, we should have the name now
     return eval(name, dict, dict)

def list_union(*lists):
     res=[]
     for l in lists:
          for item in l:
               if item not in res:
                    res.append(item)
     return res

def getkv(dict, key, updates=None):
     "Gets a key and value from a dict, returning as a dict potentially applying updates"
     d={key: dict[key].copy()} 
     if updates: 
          d[key].update(updates)
     return d 

# some obfuscation
# obfuscate pwd
_magic=[ord(x) for x in "IamAhaPp12&s]"]

# the oldies are the best
def obfus_encode(str):
    res=[]
    for i in range(len(str)):
        res.append(ord(str[i])^_magic[i%len(_magic)])
    return "".join(["%02x" % (x,) for x in res])

def obfus_decode(str):
    res=[]
    for i in range(0, len(str), 2):
        res.append(int(str[i:i+2], 16))
    x=""
    for i in range(len(res)):
        x+=chr(res[i]^_magic[i%len(_magic)])
    return x

# unicode byte order markers to codecs
# this really should be part of the standard library

# we try to import the encoding first.  that has the side
# effect of ensuring that the freeze tools pick up the
# right bits of code as well
import codecs

_boms=[]
# 64 bit 
try:
     import encodings.utf_64
     _boms.append( (codecs.BOM64_BE, "utf_64") )
     _boms.append( (codecs.BOM64_LE, "utf_64") )
except:  pass

# 32 bit
try:
     import encodings.utf_32
     _boms.append( (codecs.BOM_UTF32, "utf_32") )
     _boms.append( (codecs.BOM_UTF32_BE, "utf_32") )
     _boms.append( (codecs.BOM_UTF32_LE, "utf_32") )
except:  pass

# 16 bit
try:
     import encodings.utf_16
     _boms.append( (codecs.BOM_UTF16, "utf_16") )
     _boms.append( (codecs.BOM_UTF16_BE, "utf_16") )
     _boms.append( (codecs.BOM_UTF16_LE, "utf_16") )
except:  pass

# 8 bit
try:
     import encodings.utf_8
     _boms.append( (codecs.BOM_UTF8, "utf_8") )
except: pass

# NB: the 32 bit and 64 bit versions have the BOM constants defined in Py 2.3
# but no corresponding encodings module.  They are here for completeness.
# The order of above also matters since the first ones have longer
# boms than the latter ones, and we need to be unambiguous

_maxbomlen=max([len(bom) for bom,codec in _boms])

def opentextfile(name):
     """This function detects unicode byte order markers and if present
     uses the codecs module instead to open the file instead with
     appropriate unicode decoding, else returns the file using standard
     open function"""
     f=open(name, "rb")
     start=f.read(_maxbomlen)
     for bom,codec in _boms:
          if start.startswith(bom):
               f.close()
               # some codecs don't do readline, so we have to vector via stringio
               # many postings also claim that the BOM is returned as the first
               # character but that hasn't been the case in my testing
               return StringIO.StringIO(codecs.open(name, "r", codec).read())
     f.close()
     return open(name, "rtU")


# don't you just love i18n

# the following function is actually defined in guihelper and
# inserted into this module.  the intention is to ensure this
# module doesn't have to import wx.  The guihelper version
# checks if wx is in unicode mode

#def strorunicode(s):
#     if isinstance(s, unicode): return s
#     return str(s)

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

# The CRC and escaping mechanisms are the same as used in PPP, HDLC and
# various other standards.

pppterminator="\x7e"

def pppescape(data):
    return data.replace("\x7d", "\x7d\x5d") \
           .replace("\x7e", "\x7d\x5e")

def pppunescape(d):
    if d.find("\x7d")<0: return d
    res=list(d)
    try:
        start=0
        while True:
            p=res.index("\x7d", start)
            res[p:p+2]=chr(ord(res[p+1])^0x20)
            start=p+1
    except ValueError:
        return "".join(res)

# See http://www.repairfaq.org/filipg/LINK/F_crc_v35.html for more info
# on CRC
_crctable=(
    0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,   # 0 - 7
    0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,   # 8 - 15
    0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,   # 16 - 23
    0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,   # 24 - 31
    0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,   # 32 - 39
    0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,   # 40 - 47
    0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,   # 48 - 55
    0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,   # 56 - 63
    0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,   # 64 - 71
    0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,   # 72 - 79
    0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,   # 80 - 87
    0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,   # 88 - 95
    0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,   # 96 - 103
    0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,   # 104 - 111
    0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,   # 112 - 119
    0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,   # 120 - 127
    0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,   # 128 - 135
    0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,   # 136 - 143
    0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,   # 144 - 151
    0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,   # 152 - 159
    0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,   # 160 - 167
    0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,   # 168 - 175
    0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,   # 176 - 183
    0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,   # 184 - 191
    0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,   # 192 - 199
    0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,   # 200 - 207
    0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,   # 208 - 215
    0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,   # 216 - 223
    0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,   # 224 - 231
    0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,   # 232 - 239
    0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,   # 240 - 247
    0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,   # 248 - 255
    )

def crc(data, initial=0xffff):
    "CRC calculation - returns 16 bit integer"
    res=initial
    for byte in data:
        curres=res
        res=res>>8  # zero extended
        val=(ord(byte)^curres) & 0xff
        val=_crctable[val]
        res=res^val

    res=(~res)&0xffff
    return res

def crcs(data, initial=0xffff):
    "CRC calculation - returns 2 byte string LSB"
    r=crc(data, initial)
    return "%c%c" % ( r& 0xff, (r>>8)&0xff)



###
### Pathname processing (independent of host OS)
###

def basename(name):
    if name.rfind('\\')>=0 or name.rfind('/')>=0:
        pos=max(name.rfind('\\'), name.rfind('/'))
        name=name[pos+1:]
    return name

def stripext(name):
    if name.rfind('.')>=0:
        name=name[:name.rfind('.')]
    return name

def getext(name):
    if name.rfind('.')>=0:
        return name[name.rfind('.')+1:]
    return ''

#-------------------------------------------------------------------------------
# number <-> string conversion routines

def LSBUint16(v):
    if len(v)<2:
        return None
    return ord(v[0])+(ord(v[1])<<8)

def LSBUint32(v):
    if len(v)<4:
        return None
    return ord(v[0])+(ord(v[1])<<8)+(ord(v[2])<<16)+(ord(v[3])<<24)
    
def MSBUint16(v):
    if len(v)<2:
        return None
    return ord(v[1])+(ord(v[0])<<8)

def MSBUint32(v):
    if len(v)<4:
        return None
    return ord(v[3])+(ord(v[2])<<8)+(ord(v[1])<<16)+(ord(v[0])<<24)

def LSBstr16(v):
    return chr(v&0xff)+chr((v>>8)&0xff)

def LSBstr32(v):
    return chr(v&0xff)+chr((v>>8)&0xff)+chr((v>>16)&0xff)+chr((v>>24)&0xff)

def MSBstr16(v):
    return chr((v>>8)&0xff)+chr(v&0xff)

def MSBstr32(v):
    return chr((v>>24)&0xff)+chr((v>>16)&0xff)+chr((v>>8)&0xff)+chr(v&0xff)


###
### Binary handling
###

nibbles=("0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111")

def tobinary(v):
     return nibbles[v>>4]+nibbles[v&0x0f]

def frombinary(v):
     res=0
     for i in v:
          res*=2
          res+=bool(i=="1")
     return res

# This could be done more efficiently through clever combinations of
# masks and shifts but it is harder to write and debug (I tried :-)
# and isn't any more effective on the short strings we work with
def decodecharacterbits(bytes, bitsperchar, charconv=chr, terminator=None):
     """Decodes the characters out of a string of bytes where each
     character takes a fixed number of bits (eg 7)

     @param bytes: atring containing the raw bytes
     @param bitsperchar: number of bits making up each character
     @param charconv: the function used to convert the integer value
                 into a character
     @param terminator: if this character is seen then it and the remaining bits are ignored"""
     bits="".join([tobinary(ord(c)) for c in bytes])
     value=[]
     while len(bits):
            c=charconv(frombinary(bits[:bitsperchar]))
            if c==terminator:
                break
            value.append(c)
            bits=bits[bitsperchar:]
     return "".join(value)
            
###
### Cache information against a file
###

def statinfo(filename):
    """Returns a simplified version of os.stat results that can be used to tell if a file
    has changed.  The normal structure returned also has things like last access time
    which should not be used to tell if a file has changed."""
    try:
        s=os.stat(filename)
        return (s.st_mode, s.st_ino, s.st_dev, s.st_uid, s.st_gid, s.st_size, s.st_mtime,
                s.st_ctime)
    except:
        return None

class FileCache:

     def __init__(self, lowwater=100, hiwater=140):
          self.items={}
          self.hiwater=hiwater
          self.lowwater=lowwater

     def get(self, filename):
          v=self.items.get(filename, None)
          if v is None: return None
          si,value=v
          # check freshness
          if si==statinfo(filename):
               return value
          return None

     def set(self, filename, value):
          # we deliberately return value make this easy to use in return statement
          if len(self.items)>=self.hiwater:
               while len(self.items)>self.lowwater:
                    del self.items[random.choice(self.items.keys())]
          # yes there is a race condition with statinfo changing after program
          # has calculated value but before calling this function
          self.items[filename]=statinfo(filename),value
          return value
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.