new Auto Sync feature for calendar (Part 1 of 2)

"Simon C" <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <000f01c5be78$1864a850$6400a8c0@HOME>
New autosync feature for bitpim.

The purpose is to make bitpim automatically sync up with your phone with
your outlook calendar every time you connect it to the PC or at a preset
interval if it is left connected, if the phone supports bluetooth you just
have to be close to the PC for it to sync up as long as bitpim is running.

There is a new menu option "autosync" to configure and run the feature.

The implementation stores the Auto sync folder and filter settings in the
bitpim database, it uses the existing calendar code to import the calendar
from the PC and to send the data to the phone. The existing import calendar
feature still worksas it used to. You can use it with vcal, csv and outlook
calendars. To avoid that annoying "permission" dialog that pops up with
outlook, the autosync feature does not import the body of the calendar
events from outlook, so you only get the event title on the phone, not an
issue with LG phones as they don't support a memo for events anyway.

I and one other person have been testing for a couple of weeks with outlook
and it seems to be working OK. Tested on 4400, 6100 & 8100 although the code
is not phone specific.

It only works in one direction, i.e. outlook calendar copied into phone,
events added to the phone will be lost, a future enhancement would be to
push these back into outlook or at least not loose them. Another improvement
would a way to minimise bitpim and hide it, like bitfling does, this would
make leaving bitpim running easier, also auto starting with user login so it
is completely automatic.

I enhanced the alarm options in the calendar filter so you can choose the
ringtone, vibrate and force the alarms to on for all events, this affects
the existing import feature as well because the filter code is shared.

I included some 8100 calendar enhancements that are independent from the
rest of the code and should be comitted regardless of whether this feature
is.

If you decide to add this I can write up a help page for it.

One new source (auto_sync.py) and 8 edits with diffs attached. It is merged
with the latest code from CVS.
Some of the code changes in Part 2, message too big for sourceforge if I
attach all at the same time.


Simon
auto_sync.py (text/plain, 17.1 KB)
### BITPIM
###
### Copyright (C) 2004 Simon Capper <[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.
###

"""
Auto synchronization of calender
This module provides functionality to automatically sync the calender on your PC (outlook etc.)
with your phone. It will do so at a regular interval if the phone is connected and BitPim is running
This feature works with all BitPim calender types and phones that support the writing of calenders

"""

# standard modules
import copy
import sha
import time
import re

# wx modules
import wx

# BitPim modules
import database
import guiwidgets
import common_calendar
import guiwidgets
import gui

_data_key='auto_sync_settings'
_filter_keys=['start_offset', 'end_offset', 'no_alarm', 'rpt_events', "alarm_value",
              'ringtone', 'vibrate', 'categories', 'alarm_override']

def _getsettings(mw):
    settings=AutoSyncSettingsEntry()
    dict=mw.database.getmajordictvalues(_data_key, autosyncsettingsobjectfactory)
    for k,e in dict.items():
        settings.set_db_dict(e)
        # only expecting one entry
        break
    return settings

def UpdateOnConnect(mw):
    settings=_getsettings(mw)
    return settings.sync_on_connect

class SyncSchedule(object):
    def __init__(self, log=None):
        # get standard commport parameters
        self.__log=log
        self.__data={}

    def log(self, log_str):
        if self.__log is None:
            print log_str
        else:
            self.__log.log(log_str)
    def logdata(self, log_str, log_data):
        if self.__log is None:
            print log_str,log_data
        else:
            self.__log.logdata(log_str, log_data)

    def importcalenderdata(self):
        res=0
        for entry in self.mw.calenders:
            if entry[0]==self.settings.caltype:
                filter={}
                for setting in _filter_keys:
                    if self.settings._data.has_key(setting) and self.settings._data[setting]!=None:
                        # need to convert start and end from scalable values
                        if setting=='start_offset':
                            tm=time.gmtime(time.time()-(self.settings._data[setting]*24*60*60))
                            date=(tm.tm_year, tm.tm_mon, tm.tm_mday)
                            filter['start']=date
                        elif setting=='end_offset':
                            tm=time.gmtime(time.time()+(self.settings._data[setting]*24*60*60))
                            date=(tm.tm_year, tm.tm_mon, tm.tm_mday)
                            filter['end']=date
                        elif setting=="categories":
                            # convert categories into list
                            filter[setting]=self.settings._data[setting].split("||")
                        else:
                             filter[setting]=self.settings._data[setting]
                    else:
                        if setting=='start_offset':
                            filter['start']=None
                        if setting=='end_offset':
                            filter['end']=None
                res=entry[2](self.mw, self.settings.calender_id, filter)
                if res==1:
                    # imported calender OK!!!
                    self.log("Auto Sync: Imported calender OK")
        if not res:
            self.log("Auto Sync: Failed to import calender")
        return res

    def sendcalendertophone(self):
        res=1
        data={}
        todo=[]
        data['calendar_version']=self.mw.phoneprofile.BP_Calendar_Version
        self.mw.calendarwidget.getdata(data)
        todo.append( (self.mw.wt.writecalendar, "Calendar", False) )
        todo.append((self.mw.wt.rebootcheck, "Phone Reboot"))
        self.mw.MakeCall(gui.Request(self.mw.wt.getfundamentals),
                      gui.Callback(self.OnDataSendPhoneGotFundamentals, data, todo))
        return res

    def OnDataSendPhoneGotFundamentals(self, data, todo, exception, results):
        if exception!=None:
            if not self.silent:
                self.mw.HandleException(exception)
            self.log("Auto Sync: Failed, Exception getting phone fundementals")
            self.mw.OnBusyEnd()
            return
        data.update(results)
        # Now scribble to phone
        self.log("Auto Sync: Sending results to phone")
        self.mw.MakeCall(gui.Request(self.mw.wt.senddata, data, todo),
                      gui.Callback(self.OnDataSendCalenderResults))

    def OnDataSendCalenderResults(self, exception, results):
        if exception!=None:
            if not self.silent:
                self.mw.HandleException(exception)
            self.log("Auto Sync: Failed, Exception writing calender to phone")
            self.mw.OnBusyEnd()
            return
        if self.silent==0:
            wx.MessageBox('Phone Synchronized OK',
                          'Synchronize Complete', wx.OK)
        self.log("Auto Sync: Synchronize Completed OK")
        self.mw.OnBusyEnd()

    def sync(self, mw, silent):
        self.silent=silent
        # start the autosync process
        # import the calender, find the entry point for the import function
        self.mw=mw
        if mw.config.ReadInt("SafeMode", False):
            self.log("Auto Sync: Disabled, BitPim in safe mode")
            return 0
        if wx.IsBusy():
            self.log("Auto Sync: Failed, BitPim busy")
            return 0
        self.log("Auto Sync: Starting (silent mode=%d)..." % (silent))
        self.mw.OnBusyStart()
        self.mw.GetStatusBar().progressminor(0, 100, 'AutoSync in progress ...')
        # retrieve the configuration
        self.settings=_getsettings(mw)
        # update BitPims calender
        res=self.importcalenderdata()
        if res==1:
            # send updated calender to the phone
            res=self.sendcalendertophone()
        else:
            self.mw.OnBusyEnd()
            if silent==0:
                wx.MessageBox('Unable to synchronize phone schedule',
                              'Synchronize failed', wx.OK)
            self.log("Auto Sync: Failed, Unable to synchronize phone schedule")
        return res

#-------------------------------------------------------------------------------
class AutoSyncSettingsobject(database.basedataobject):
    _knownproperties=['caltype', 'calender_id', 'sync_on_connect', 'sync_frequency', \
                      'start_offset', 'end_offset', 'no_alarm', 'rpt_events', 'categories', \
                      'ringtone', 'vibrate', 'alarm_override', 'alarm_value']
    _knownlistproperties=database.basedataobject._knownlistproperties.copy()
    def __init__(self, data=None):
        if data is None or not isinstance(data, AutoSyncSettingsEntry):
            return;
        self.update(data.get_db_dict())
autosyncsettingsobjectfactory=database.dataobjectfactory(AutoSyncSettingsobject)

#-------------------------------------------------------------------------------
class AutoSyncSettingsEntry(object):
    _caltype_key='caltype'
    _calender_id_key='calender_id'
    _sync_on_connect_key='sync_on_connect'
    _sync_frequency_key='sync_frequency'
    #_start_offset_key='start_offset'
    #_end_offset_key='end_offset'
    #_no_alarm_key='no_alarm'
    #_categories_key='categories'
    #_rpt_event_key='rpt_event'
    #_vibrate_key='vibrate'
    def __init__(self):
        self._data={ 'serials': [] }
        # we only expect one record, so the ID is fixed
        self._set_id(99)

    def __eq__(self, rhs):
        return self.caltype==rhs.caltype and self.calender_id==rhs.calender_id and\
               self.sync_frequency==rhs.sync_frequency and self.sync_on_connect==rhs.sync_on_connect
    def __ne__(self, rhs):
        return (not __eq__(rhs))
    def get(self):
        return copy.deepcopy(self._data, {})
    def set(self, d):
        self._data={}
        self._data.update(d)

    def get_db_dict(self):
        return self.get()
    def set_db_dict(self, d):
        self.set(d)

    def _get_id(self):
        s=self._data.get('serials', [])
        for n in s:
            if n.get('sourcetype', None)=='bitpim':
                return n.get('id', None)
        return None
    def _set_id(self, id):
        s=self._data.get('serials', [])
        for n in s:
            if n.get('sourcetype', None)=='bitpim':
                n['id']=id
                return
        self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } )
    id=property(fget=_get_id, fset=_set_id)

    def _set_or_del(self, key, v, v_list=[]):
        if v is None or v in v_list:
            if self._data.has_key(key):
                del self._data[key]
        else:
            self._data[key]=v

    def _get_caltype(self):
        return self._data.get(self._caltype_key, 'None')
    def _set_caltype(self, v):
        self._set_or_del(self._caltype_key, v, [''])
    caltype=property(fget=_get_caltype, fset=_set_caltype)

    def _get_calender_id(self):
        return self._data.get(self._calender_id_key, '')
    def _set_calender_id(self, v):
        self._set_or_del(self._calender_id_key, v, [''])
    calender_id=property(fget=_get_calender_id, fset=_set_calender_id)

    def _get_sync_on_connect(self):
        return self._data.get(self._sync_on_connect_key, False)
    def _set_sync_on_connect(self, v):
        self._set_or_del(self._sync_on_connect_key, v, [''])
    sync_on_connect=property(fget=_get_sync_on_connect, fset=_set_sync_on_connect)

    def _get_sync_frequency(self):
        return self._data.get(self._sync_frequency_key, 0)
    def _set_sync_frequency(self, v):
        self._set_or_del(self._sync_frequency_key, v, [''])
    sync_frequency=property(fget=_get_sync_frequency, fset=_set_sync_frequency)

###
###  The autosync settings dialog
###

class AutoSyncSettingsDialog(wx.Dialog):
    ID_CALSETTINGS=wx.NewId()
    def __init__(self, mainwindow, frame, title="Schedule Auto Sync Settings", id=-1):
        wx.Dialog.__init__(self, frame, id, title,
                          style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE)
        self.mw=mainwindow

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

        # calender type
        gs.Add( wx.StaticText(self, -1, "Calender Type"), pos=(0,0), flag=wx.ALIGN_CENTER_VERTICAL)
        # get a list of the possible calender types supported
        calendertype=('None',)
        # build a list of calender types for the user to select from
        for entry in self.mw.calenders:
            calendertype+=(entry[0], )

        self.caltype=wx.ComboBox(self, -1, calendertype[0], style=wx.CB_DROPDOWN|wx.CB_READONLY,choices=calendertype)
        gs.Add( self.caltype, pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL)
        gs.Add( wx.Button(self, self.ID_CALSETTINGS, "Calender Settings..."), pos=(0,2), flag=wx.ALIGN_CENTER_VERTICAL)

        # on connect
        gs.Add( wx.StaticText(self, -1, "Update when phone connected"), pos=(1,0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.sync_on_connect=wx.CheckBox(self, wx.NewId(), "")
        gs.Add( self.sync_on_connect, pos=(1,1), flag=wx.ALIGN_CENTER_VERTICAL)

        # frequency
        gs.Add( wx.StaticText(self, -1, "Update Frequency (mins) 0=never"), pos=(2,0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.sync_frequency=wx.lib.intctrl.IntCtrl(self, -1, value=0, min=0, max=1440)
        gs.Add( self.sync_frequency, pos=(2,1), flag=wx.ALIGN_CENTER_VERTICAL)

        # 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, wx.ID_CANCEL, self.OnCancel)
        wx.EVT_BUTTON(self, wx.ID_OK, self.OnOK)
        wx.EVT_BUTTON(self, self.ID_CALSETTINGS, self.OnConfigCalender)
        wx.EVT_COMBOBOX(self, self.caltype.GetId(), self.OnCaltypeChange)

        self.settings=AutoSyncSettingsEntry()

        self.SetSizer(bs)
        self.SetAutoLayout(True)
        bs.Fit(self)
        # read initial values from database
        self.getfromfs()
        self.auto_sync_timer_id = wx.NewId()
        self.auto_sync_timer = wx.Timer(self, self.auto_sync_timer_id)
        # start the timer
        self.SetAutoSyncTimer()

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

        wx.EVT_CLOSE(self, self.OnClose)

    def OnCaltypeChange(self, _):
        #see if the value has changed, if so automatically fire up the configuration for the calender
        if self.settings.caltype!=self.caltype.GetValue():
            self.OnConfigCalender()
        return

    def OnConfigCalender(self, _=None):
        old_folder=self.settings.calender_id
        for entry in self.mw.calenders:
            if entry[0]==self.caltype.GetValue():
                # if the calender type is changing blank out the folder name
                if self.settings.caltype!=self.caltype.GetValue():
                    self.settings.calender_id=''    
                filter={}
                for setting in _filter_keys:
                    if self.settings._data.has_key(setting) and self.settings._data[setting]!=None:
                        if setting=="categories":
                            # convert categories into list
                            filter[setting]=self.settings._data[setting].split("||")
                        else:
                            filter[setting]=self.settings._data[setting]
                res, temp=entry[1](self.mw, self.settings.calender_id, filter)
                if res==wx.ID_OK:
                    # temp is a tuple of the calender_id and the filter settings
                    self.settings.calender_id=temp[0]
                    for setting in _filter_keys:
                        if(temp[1].has_key(setting) and temp[1][setting]!=None):
                            if setting=="categories":
                                # convert categories into storable type
                                cat_str=""
                                for cat in temp[1][setting]:
                                    #use a || to separate individual categories
                                    if len(cat_str):
                                        cat_str=cat_str+"||"+cat
                                    else:
                                        cat_str=cat
                                self.settings._data[setting]=cat_str
                            else:
                                self.settings._data[setting]=temp[1][setting]
                        else:
                            if self.settings._data.has_key(setting):
                                del self.settings._data[setting]
                    self.settings.caltype=self.caltype.GetValue()
                else: # cancel pressed
                    #revert back to previous value
                    self.caltype.SetValue(self.settings.caltype)
                    self.settings.calender_id=old_folder
                return
        return

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

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

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

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

    def _save_to_db(self):
        db_rr={}
        self.settings.caltype=self.caltype.GetValue()
        self.settings.sync_on_connect=self.sync_on_connect.GetValue()
        self.settings.sync_frequency=self.sync_frequency.GetValue()
        db_rr[self.settings.id]=AutoSyncSettingsobject(self.settings)
        database.ensurerecordtype(db_rr, autosyncsettingsobjectfactory)
        self.mw.database.savemajordict(_data_key, db_rr)

    def getfromfs(self):
        self.settings=_getsettings(self.mw)
        self.caltype.SetValue(self.settings.caltype)
        self.sync_on_connect.SetValue(int(self.settings.sync_on_connect))
        self.sync_frequency.SetValue(int(self.settings.sync_frequency))
        return

    def updatevariables(self):
        self.mw.auto_save_dict=self.settings

    def ShowModal(self):
        self.getfromfs()
        ec=wx.Dialog.ShowModal(self)
        if ec==wx.ID_OK:
            self._save_to_db()
            self.updatevariables()
            self.SetAutoSyncTimer()
        return ec

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

    def SetAutoSyncTimer(self):
        # stop the previous timer (if any)
        self.auto_sync_timer.Stop()
        oneShot = True
        timeout=self.settings.sync_frequency*60000 # convert msecs
        if timeout:
            self.auto_sync_timer.Start(timeout, oneShot)
            self.Bind(wx.EVT_TIMER, self.OnTimer, self.auto_sync_timer)

    def OnTimer(self, event):
        self.mw.log("Auto Sync: Timed update")
        SyncSchedule(self.mw).sync(self.mw, silent=1)
        self.SetAutoSyncTimer()
com_lgvx8100.py (text/plain, 18.6 KB)
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
### Copyright (C) 2005 Simon Capper <[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.
###

"""Communicate with the LG VX8100 cell phone

The VX8100 is substantially similar to the VX7000 but also supports video.

"""

# standard modules
import re
import time
import cStringIO
import sha

# my modules
import common
import commport
import copy
import com_lgvx4400
import p_brew
import p_lgvx8100
import com_lgvx7000
import com_brew
import com_phone
import com_lg
import prototypes
import bpcalendar
import call_history
import sms
import memo


from prototypes import *



class Phone(com_lgvx7000.Phone):
    "Talk to the LG VX8100 cell phone"

    desc="LG-VX8100"

    protocolclass=p_lgvx8100
    serialsname='lgvx8100'

    builtinringtones= ('Low Beep Once', 'Low Beeps', 'Loud Beep Once', 'Loud Beeps', 'VZW Default Ringtone') + \
                      tuple(['Ringtone '+`n` for n in range(1,11)]) + \
                      ('No Ring',)

    ringtonelocations= (
        # type       index-file   size-file directory-to-use lowest-index-to-use maximum-entries type-major
        ( 'ringers', 'dload/my_ringtone.dat', 'dload/my_ringtonesize.dat', 'brew/16452/lk/mr', 100, 150, 1),
        )

    builtinwallpapers = () # none

    wallpaperlocations= (
        ( 'images', 'dload/image.dat', 'dload/imagesize.dat', 'brew/16452/mp', 100, 50, 0),
        )
        
    def __init__(self, logtarget, commport):
        com_lgvx4400.Phone.__init__(self, logtarget, commport)
        self.mode=self.MODENONE

    def getfundamentals(self, results):
        """Gets information fundamental to interopating with the phone and UI.

        Currently this is:

          - 'uniqueserial'     a unique serial number representing the phone
          - 'groups'           the phonebook groups
          - 'wallpaper-index'  map index numbers to names
          - 'ringtone-index'   map index numbers to ringtone names

        This method is called before we read the phonebook data or before we
        write phonebook data.
        """

        # use a hash of ESN and other stuff (being paranoid)
        self.log("Retrieving fundamental phone information")
        self.log("Phone serial number")
        results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest()
        # now read groups
        self.log("Reading group information")
        buf=prototypes.buffer(self.getfilecontents("pim/pbgroup.dat"))
        g=self.protocolclass.pbgroups()
        g.readfrombuffer(buf)
        self.logdata("Groups read", buf.getdata(), g)
        groups={}
        for i in range(len(g.groups)):
            if len(g.groups[i].name): # sometimes have zero length names
                groups[i]={'name': g.groups[i].name }
        results['groups']=groups
        self.getwallpaperindices(results)
        self.getringtoneindices(results)
        self.log("Fundamentals retrieved")
        return results

    def savegroups(self, data):
        groups=data['groups']
        keys=groups.keys()
        keys.sort()
        g=self.protocolclass.pbgroups()
        for k in keys:
            e=self.protocolclass.pbgroup()
            e.name=groups[k]['name']
            g.groups.append(e)
        buffer=prototypes.buffer()
        g.writetobuffer(buffer)
        self.logdata("New group file", buffer.getvalue(), g)
        self.writefile("pim/pbgroup.dat", buffer.getvalue())

    def getmemo(self, result):
        # read the memo file
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/neomemo.dat"))
            text_memo=self.protocolclass.textmemofile()
            text_memo.readfrombuffer(buf)
            res={}
            for m in text_memo.items:
                entry=memo.MemoEntry()
                entry.text=m.text
                entry.set_date_isostr("%d%02d%02dT%02d%02d00" % ((m.memotime)))
                res[entry.id]=entry
        except com_brew.BrewNoSuchFileException:
            res={}
        result['memo']=res
        return result

    def savememo(self, result, merge):
        text_memo=self.protocolclass.textmemofile()
        memo_dict=result.get('memo', {})
        keys=memo_dict.keys()
        keys.sort()
        text_memo.itemcount=len(keys)
        for k in keys:
            entry=self.protocolclass.textmemo()
            entry.text=memo_dict[k].text
            t=time.strptime(memo_dict[k].date, '%b %d, %Y %H:%M')
            entry.memotime=(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min)
            text_memo.items.append(entry)
        buf=prototypes.buffer()
        text_memo.writetobuffer(buf)
        self.writefile("sch/neomemo.dat", buf.getvalue())
        return result

    def getcalendar(self,result):
        res={}
        # Read exceptions file first
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/newschexception.dat"))
            ex=self.protocolclass.scheduleexceptionfile()
            ex.readfrombuffer(buf)
            self.logdata("Calendar exceptions", buf.getdata(), ex)
            exceptions={}
            for i in ex.items:
                try:
                    exceptions[i.pos].append( (i.year,i.month,i.day) )
                except KeyError:
                    exceptions[i.pos]=[ (i.year,i.month,i.day) ]
        except com_brew.BrewNoSuchFileException:
            exceptions={}

        # Now read schedule
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/newschedule.dat"))
            if len(buf.getdata())<3:
                # file is empty, and hence same as non-existent
                raise com_brew.BrewNoSuchFileException()
            sc=self.protocolclass.schedulefile()
            self.logdata("Calendar", buf.getdata(), sc)
            sc.readfrombuffer(buf)
            for event in sc.events:
                # the vx8100 has a bad entry when the calender is empty
                # stop processing the calender when we hit this record
                if event.pos==0: #invalid entry
                    break                   
                entry=bpcalendar.CalendarEntry()
                entry.description=event.description
                entry.start=event.start
                entry.end=event.end
                entry.vibrate=~(event.alarmindex_vibrate&0x1) # vibarate bit is inverted in phone 0=on, 1=off
                entry.repeat = self.makerepeat(event.repeat)
                min=event.alarmminutes
                hour=event.alarmhours
                if min==0x64 or hour==0x64:
                    entry.alarm=None # no alarm set
                else:
                    entry.alarm=hour*60+min
                entry.ringtone=result['ringtone-index'][event.ringtone]['name']
                entry.snoozedelay=0
                # check for exceptions and remove them
                if event.repeat[3] and exceptions.has_key(event.pos):
                    for year, month, day in exceptions[event.pos]:
                        entry.suppress_repeat_entry(year, month, day)
                res[entry.id]=entry

            assert sc.numactiveitems==len(res)
        except com_brew.BrewNoSuchFileException:
            pass # do nothing if file doesn't exist
        result['calendar']=res
        return result

    def makerepeat(self, repeat):
        # get all the variables out of the repeat tuple
        # and convert into a bpcalender RepeatEntry
        type,dow,interval,exceptions=repeat
        if type==0:
            repeat_entry=None
        else:
            repeat_entry=bpcalendar.RepeatEntry()
            if type==1: #daily
                repeat_entry.repeat_type=repeat_entry.daily
                repeat_entry.interval=interval
            elif type==5: #'monfri'
                repeat_entry.repeat_type=repeat_entry.daily
                repeat_entry.interval=0
            elif type==2: #'weekly'
                repeat_entry.repeat_type=repeat_entry.weekly
                repeat_entry.dow=dow
                repeat_entry.interval=interval
            elif type==3: #'monthly'
                repeat_entry.repeat_type=repeat_entry.monthly
                repeat_entry.interval=interval
                repeat_entry.dow=0
            elif type==6: #'monthly' #Xth Y day (e.g. 2nd friday each month)
                repeat_entry.repeat_type=repeat_entry.monthly
                repeat_entry.interval=interval #X
                repeat_entry.dow=dow #Y
            else: # =4 'yearly'
                repeat_entry.repeat_type=repeat_entry.yearly
        return repeat_entry

    def savecalendar(self, dict, merge):
        # ::TODO::
        # what will be written to the files
        eventsf=self.protocolclass.schedulefile()
        exceptionsf=self.protocolclass.scheduleexceptionfile()
        # what are we working with
        cal=dict['calendar']
        newcal={}
        #sort into start order, makes it possible to see if the calendar has changed        
        keys=[(x.start, k) for k,x in cal.items()]
        keys.sort()
        # number of entries
        eventsf.numactiveitems=len(keys)
        pos=1
        contains_alarms=False        
        # play with each entry
        for (_,k) in keys:
            # entry is what we will return to user
            entry=cal[k]
            data=self.protocolclass.scheduleevent()
            data.pos=eventsf.packetsize()
            data.description=entry.description
            data.start=entry.start
            data.end=entry.end
            self.setalarm(entry, data)
            data.ringtone=0
            for i in dict['ringtone-index']:
                if dict['ringtone-index'][i]['name']==entry.ringtone:
                    data.ringtone=i
            # check for exceptions and add them to the exceptions list
            exceptions=0
            if entry.repeat!=None:
                if data.end[:3]==entry.no_end_date:
                    data.end=(2100, 12, 31)+data.end[3:]
                for i in entry.repeat.suppressed:
                    de=self.protocolclass.scheduleexception()
                    de.pos=data.pos
                    de.day=i.date.day
                    de.month=i.date.month
                    de.year=i.date.year
                    exceptions=1
                    exceptionsf.items.append(de)
            if entry.repeat != None:
                data.repeat=(self.getrepeattype(entry, exceptions))
            else:
                data.repeat=((0,0,0,0))
            data.unknown1=0
            data.unknown2=0
            if data.alarmindex_vibrate!=1: # if alarm set
                contains_alarms=True
            # put entry in nice shiny new dict we are building
            entry=copy.copy(entry)
            newcal[data.pos]=entry
            eventsf.events.append(data)

        buf=prototypes.buffer()
        eventsf.writetobuffer(buf)
        # We check the existing calender as changes require a reboot for the alarms
        # to work properly, also no point writing the file if it is not changing
        if buf.getvalue()!=self.getfilecontents("sch/newschedule.dat"):
            self.logdata("Writing calendar", buf.getvalue(), eventsf)
            self.writefile("sch/newschedule.dat", buf.getvalue())
            if contains_alarms:
                self.log("Your phone has to be rebooted due to the calendar changing")
                dict["rebootphone"]=True
        else:
            self.log("Phone calendar unchanged, no update required")

        buf=prototypes.buffer()
        exceptionsf.writetobuffer(buf)
        self.logdata("Writing calendar exceptions", buf.getvalue(), exceptionsf)
        self.writefile("sch/newschexception.dat", buf.getvalue())

        # fix passed in dict
        dict['calendar']=newcal

        return dict

    def getrepeattype(self, entry, exceptions):
        #convert the bpcalender type into vx8100 type
        repeat_entry=bpcalendar.RepeatEntry()
        if entry.repeat.repeat_type==repeat_entry.monthly:
            dow=entry.repeat.dow
            if entry.repeat.dow==0:
                # set interval for month type 4 to start day of month, (required by vx8100)
                interval=entry.start[2]
                type=3
            else:
                interval=entry.repeat.interval
                type=6
        elif entry.repeat.repeat_type==repeat_entry.daily:
            dow=entry.repeat.dow
            interval=entry.repeat.interval
            if entry.repeat.interval==0:
                type=5
            else:
                type=1
        elif entry.repeat.repeat_type==repeat_entry.weekly:
            dow=entry.repeat.dow
            interval=entry.repeat.interval
            type=2
        elif entry.repeat.repeat_type==repeat_entry.yearly:
            # set interval to start day of month, (required by vx8100)
            interval=entry.start[2]
            # set dow to start month, (required by vx8100)
            dow=entry.start[1]
            type=4
        return (type, dow, interval, exceptions)

    def setalarm(self, entry, data):
        # vx8100 only allows certain repeat intervals, adjust to fit, it also stores an index to the interval
        if entry.alarm>=2880:
            entry.alarm=2880
            data.alarmminutes=0
            data.alarmhours=48
            data.alarmindex_vibrate=0x10
        elif entry.alarm>=1440:
            entry.alarm=1440
            data.alarmminutes=0
            data.alarmhours=24
            data.alarmindex_vibrate=0xe
        elif entry.alarm>=120:
            entry.alarm=120
            data.alarmminutes=0
            data.alarmhours=2
            data.alarmindex_vibrate=0xc
        elif entry.alarm>=60:
            entry.alarm=60
            data.alarmminutes=0
            data.alarmhours=1
            data.alarmindex_vibrate=0xa
        elif entry.alarm>=15:
            entry.alarm=15
            data.alarmminutes=15
            data.alarmhours=0
            data.alarmindex_vibrate=0x8
        elif entry.alarm>=10:
            entry.alarm=10
            data.alarmminutes=10
            data.alarmhours=0
            data.alarmindex_vibrate=0x6
        elif entry.alarm>=5:
            entry.alarm=5
            data.alarmminutes=5
            data.alarmhours=0
            data.alarmindex_vibrate=0x4
        elif entry.alarm>=0:
            entry.alarm=0
            data.alarmminutes=0
            data.alarmhours=0
            data.alarmindex_vibrate=0x2
        else: # no alarm
            data.alarmminutes=0x64
            data.alarmhours=0x64
            data.alarmindex_vibrate=1

        # set the vibrate bit
        if data.alarmindex_vibrate > 1 and entry.vibrate==0:
            data.alarmindex_vibrate+=1
        return

    my_model='VX8100'

    def getphoneinfo(self, phone_info):
        self.log('Getting Phone Info')
        try:
            s=self.getfilecontents('brew/version.txt')
            if s[:6]=='VX8100':
                phone_info.append('Model:', "VX8100")
                req=p_brew.firmwarerequest()
                res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse)
                phone_info.append('Firmware Version:', res.firmware)
                s=self.getfilecontents("nvm/$SYS.ESN")[85:89]
                txt='%02X%02X%02X%02X'%(ord(s[3]), ord(s[2]), ord(s[1]), ord(s[0]))
                phone_info.append('ESN:', txt)
                txt=self.getfilecontents("nvm/nvm/nvm_cdma")[180:190]
                phone_info.append('Phone Number:', txt)
        except:
            pass
        return


parentprofile=com_lgvx7000.Profile
class Profile(parentprofile):
    protocolclass=Phone.protocolclass
    serialsname=Phone.serialsname

    BP_Calendar_Version=3
    phone_manufacturer='LG Electronics Inc'
    phone_model='VX8100'

    WALLPAPER_WIDTH=176
    WALLPAPER_HEIGHT=184
    MAX_WALLPAPER_BASENAME_LENGTH=32
    WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ."
    WALLPAPER_CONVERT_FORMAT="jpg"
   
    MAX_RINGTONE_BASENAME_LENGTH=32
    RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ."

    # there is an origin named 'aod' - no idea what it is for except maybe
    # 'all other downloads'

    # the vx8100 supports bluetooth for connectivity to the PC, define the "bluetooth_mgd_id"
    # to enable bluetooth discovery during phone detection
    # the bluetooth address starts with LG's the three-octet OUI, all LG phone
    # addresses start with this, it provides a way to identify LG bluetooth devices
    # during phone discovery
    # OUI=Organizationally Unique Identifier
    # see http://standards.ieee.org/regauth/oui/index.shtml for more info
    bluetooth_mfg_id="001256"

    # the 8100 doesn't have seperate origins - they are all dumped in "images"
    imageorigins={}
    imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
    def GetImageOrigins(self):
        return self.imageorigins

    # our targets are the same for all origins
    imagetargets={}
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
                                      {'width': 176, 'height': 184, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "pictureid",
                                      {'width': 176, 'height': 184, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd",
                                      {'width': 96, 'height': 80, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
                                      {'width': 176, 'height': 220, 'format': "JPEG"}))

    def GetTargetsForImageOrigin(self, origin):
        return self.imagetargets

 
    def __init__(self):
        parentprofile.__init__(self)

    _supportedsyncs=(
        ('phonebook', 'read', None),   # all phonebook reading
        ('calendar', 'read', None),    # all calendar reading
        ('wallpaper', 'read', None),   # all wallpaper reading
        ('ringtone', 'read', None),    # all ringtone reading
        ('call_history', 'read', None),# all call history list reading
        ('sms', 'read', None),         # all SMS list reading
        ('memo', 'read', None),        # all memo list reading
        ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
        ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
        ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
        ('wallpaper', 'write', 'OVERWRITE'),
        ('ringtone', 'write', 'MERGE'),       # merge and overwrite ringtone
        ('ringtone', 'write', 'OVERWRITE'),
        ('sms', 'write', 'OVERWRITE'),        # all SMS list writing
        ('memo', 'write', 'OVERWRITE'),       # all memo list writing
        )
com_lgvx8100.py.diff (application/octet-stream, 3 KB)
Index: com_lgvx8100.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/com_lgvx8100.py,v
retrieving revision 1.6
diff -u -r1.6 com_lgvx8100.py
--- com_lgvx8100.py	13 Sep 2005 02:07:37 -0000	1.6
+++ com_lgvx8100.py	16 Sep 2005 08:46:51 -0000
@@ -242,19 +242,18 @@
         # what will be written to the files
         eventsf=self.protocolclass.schedulefile()
         exceptionsf=self.protocolclass.scheduleexceptionfile()
-
         # what are we working with
         cal=dict['calendar']
         newcal={}
-        keys=cal.keys()
+        #sort into start order, makes it possible to see if the calendar has changed        
+        keys=[(x.start, k) for k,x in cal.items()]
         keys.sort()
-        pos=1
-
         # number of entries
         eventsf.numactiveitems=len(keys)
-        
+        pos=1
+        contains_alarms=False        
         # play with each entry
-        for k in keys:
+        for (_,k) in keys:
             # entry is what we will return to user
             entry=cal[k]
             data=self.protocolclass.scheduleevent()
@@ -270,6 +269,8 @@
             # check for exceptions and add them to the exceptions list
             exceptions=0
             if entry.repeat!=None:
+                if data.end[:3]==entry.no_end_date:
+                    data.end=(2100, 12, 31)+data.end[3:]
                 for i in entry.repeat.suppressed:
                     de=self.protocolclass.scheduleexception()
                     de.pos=data.pos
@@ -282,20 +283,28 @@
                 data.repeat=(self.getrepeattype(entry, exceptions))
             else:
                 data.repeat=((0,0,0,0))
-
             data.unknown1=0
             data.unknown2=0
-
+            if data.alarmindex_vibrate!=1: # if alarm set
+                contains_alarms=True
             # put entry in nice shiny new dict we are building
             entry=copy.copy(entry)
             newcal[data.pos]=entry
             eventsf.events.append(data)
 
-        # scribble everything out
         buf=prototypes.buffer()
         eventsf.writetobuffer(buf)
-        self.logdata("Writing calendar", buf.getvalue(), eventsf)
-        self.writefile("sch/newschedule.dat", buf.getvalue())
+        # We check the existing calender as changes require a reboot for the alarms
+        # to work properly, also no point writing the file if it is not changing
+        if buf.getvalue()!=self.getfilecontents("sch/newschedule.dat"):
+            self.logdata("Writing calendar", buf.getvalue(), eventsf)
+            self.writefile("sch/newschedule.dat", buf.getvalue())
+            if contains_alarms:
+                self.log("Your phone has to be rebooted due to the calendar changing")
+                dict["rebootphone"]=True
+        else:
+            self.log("Phone calendar unchanged, no update required")
+
         buf=prototypes.buffer()
         exceptionsf.writetobuffer(buf)
         self.logdata("Writing calendar exceptions", buf.getvalue(), exceptionsf)
common_calendar.py (text/plain, 20.5 KB)
### BITPIM
###
### Copyright (C) 2004 Joe Pham <[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_calendar.py,v 1.10 2005/05/05 04:24:22 djpham Exp $

"Common stuff for the Calendar Import functions"

# system modules
import sys

# wxPython modules
import wx
import wx.calendar
import wx.lib.mixins.listctrl as listmix

# local modules
import guiwidgets
import pubsub

no_end_date=(4000, 1, 1, 0, 0)

def bp_repeat_str(dict, v):
    if v is None:
        return ''
    return v

def bp_date_str(dict, v):
    try:
        if v[0]>=no_end_date[0]:
            # no-end date, don't display it
            if dict.get('allday', False):
                return ''
            else:
                return '%02d:%02d'%v[3:]

        if dict.get('allday', False):
            return '%04d-%02d-%02d'%v[:3]
        else:
            return '%04d-%02d-%02d  %02d:%02d'% v
    except (ValueError, TypeError):
        return ''
    except:
        if __debug__: raise
        return ''

def bp_alarm_str(dict, v):
    try:
        if dict.get('alarm', False):
            v=dict.get('alarm_value', 0)
            if v:
                return '-%d min'%v
            else:
                return 'Ontime'
        else:
            return ''
    except (ValueError, TypeError):
        return ''
    except:
        if __debug__: raise
        return ''

def category_str(dict, v):
    try:
        s=''
        for d in v:
            if len(d):
                if len(s):
                    s+=', '+d
                else:
                    s=d
        return s
    except (ValueError, TypeError):
        return ''
    except:
        if __debug__: raise
        return ''

#-------------------------------------------------------------------------------
class PreviewDialog(wx.Dialog, listmix.ColumnSorterMixin):
    def __init__(self, parent, id, title, col_labels, data={},
                 config_name=None,
                 style=wx.CAPTION|wx.MAXIMIZE_BOX| \
                 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):

        wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
        
        self.__col_labels=col_labels
        self.__config_name=config_name
        self.itemDataMap={}
        # main boxsizer
        main_bs=wx.BoxSizer(wx.VERTICAL)
        # add custom controls here
        self.getcontrols(main_bs)
        # create a data preview list with supplied column labels
        self.__list=wx.ListView(self, wx.NewId())
        self.__image_list=wx.ImageList(16, 16)
        self.__ig_up=self.__image_list.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_UP,
                                                             wx.ART_OTHER,
                                                             (16, 16)))
        self.__ig_dn=self.__image_list.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN,
                                                             wx.ART_OTHER,
                                                             (16, 16)))
        self.__list.SetImageList(self.__image_list, wx.IMAGE_LIST_SMALL)
        li=wx.ListItem()
        li.m_mask=wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE
        li.m_image=-1
        for i, d in enumerate(self.__col_labels):
            # insert a column with specified name and width
            li.m_text=d[1]
            self.__list.InsertColumnInfo(i, li)
            self.__list.SetColumnWidth(i, d[2])
        main_bs.Add(self.__list, 1, wx.EXPAND, 0)
        self.populate(data)
        # the Mixin sorter
        listmix.ColumnSorterMixin.__init__(self, len(col_labels))
        # now the buttons
        self.getpostcontrols(main_bs)
        # handle events
        # all done
        self.SetSizer(main_bs)
        self.SetAutoLayout(True)
        main_bs.Fit(self)
        # save my own size, if specified
        if config_name is not None:
            guiwidgets.set_size(config_name, self)
            wx.EVT_SIZE(self, self.__save_size)

    def getcontrols(self, main_bs):
        # controls to be placed above the preview pane
        # by default, put nothing.
        pass

    def getpostcontrols(self, main_bs):
        # control to be placed below the preview pane
        # by default, just add the OK & CANCEL button
        main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        main_bs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        
    def populate(self, data):
        self.__list.DeleteAllItems()
        m={}
        m_count=0
        for k in data:
            try:
                d=data[k]
                col_idx=None
                mm={}
                for i, l in enumerate(self.__col_labels):
                    entry=d.get(l[0], None)
                    s=''
                    if l[3] is None:
                        s=str(entry)
                    else:
                        s=l[3](d, entry)
                    mm[i]=s
                    if i:
                        self.__list.SetStringItem(col_idx, i, s)
                    else:
                        col_idx=self.__list.InsertImageStringItem(sys.maxint, s, -1)
                self.__list.SetItemData(col_idx, m_count)
                m[m_count]=mm
                m_count += 1
            except:
                # something wrong happened, drop this event
                if __debug__: raise
        self.itemDataMap=m

    def GetListCtrl(self):
        return self.__list

    def GetSortImages(self):
        return (self.__ig_dn, self.__ig_up)

    def __save_size(self, evt):
        if self.__config_name is not None:
            guiwidgets.save_size(self.__config_name, self.GetRect())
        evt.Skip()

#-------------------------------------------------------------------------------
class FilterDialogBase(wx.Dialog):
    unnamed="Select:"
    def __init__(self, parent, id, caption, categories, style=wx.DEFAULT_DIALOG_STYLE):
        wx.Dialog.__init__(self, parent, id,
                           title=caption, style=style)
        # the main box sizer
        bs=wx.BoxSizer(wx.VERTICAL)
        # the flex grid sizers for the editable items
        main_fgs=wx.FlexGridSizer(0, 1, 0, 0)
        fgs=wx.FlexGridSizer(3, 2, 0, 5)
        fgs1=wx.FlexGridSizer(0, 1, 0, 0)
        fgs2=wx.FlexGridSizer(0, 2, 0, 5)
        # set the date options
        self.SetDateControls(fgs, fgs1)
        # new repeat to single events option
        self.__rpt_chkbox=wx.CheckBox(self, id=wx.NewId(), label='Repeat Events:',
                                      style=wx.ALIGN_RIGHT)
        self.__rpt_chkbox.Disable()
        fgs.Add(self.__rpt_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        self.__rpt_chkbox_text=wx.StaticText(self, -1, 'Import as multi-single events.')
        fgs.Add(self.__rpt_chkbox_text, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
        self.__rpt_chkbox_text.Disable()
        # alarm option
        choices=('Disable All Alarms', 'Use Alarm Settings From Calender', 
                 'Set Alarm On All Events') 
        self.__alarm_setting = wx.RadioBox(self, id=wx.NewId(),
                                           label="Select Alarm Settings For Imported Events",
                                           choices=choices,
                                           majorDimension=1,
                                           size=(280,-1))
        fgs1.Add(self.__alarm_setting, 0, wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 5)
        #alarm vibrate
        self.__vibrate=wx.CheckBox(self, id=wx.NewId(), label='Alarm Vibrate:',
                                   style=wx.ALIGN_RIGHT)
        fgs2.Add(self.__vibrate, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        self.__vibrate_text=wx.StaticText(self, -1, 'Enable vibrate for alarms.')
        fgs2.Add(self.__vibrate_text, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 5)
        # alarm settings
        self.__ringtone_text=wx.StaticText(self, -1, 'Alarm Ringtone:')
        fgs2.Add(self.__ringtone_text, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        self.__ringtone=wx.ComboBox(self, id=wx.NewId(),
                                    style=wx.CB_DROPDOWN|wx.CB_READONLY,
                                    choices=[self.unnamed], size=(160,-1))
        fgs2.Add(self.__ringtone, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
        # alarm value
        self.__alarm_value_text=wx.StaticText(self, -1, 'Alert before (mins):')
        fgs2.Add(self.__alarm_value_text, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        self.__alarm_value=wx.lib.intctrl.IntCtrl(self, id=wx.NewId(), size=(50,-1), 
                                               value=0, min=0, max=1000)
        fgs2.Add( self.__alarm_value, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
        # category option
        self.__cat_chkbox=wx.CheckBox(self, id=wx.NewId(), label='Categories:',
                                      style=wx.ALIGN_RIGHT)
        fgs2.Add(self.__cat_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        for i,c in enumerate(categories):
            if not len(c):
                categories[i]='<None>'
        self.__cats=wx.CheckListBox(self, choices=categories, size=(160, 50))
        self.__cats.Disable()
        fgs2.Add(self.__cats, 0, wx.ALIGN_LEFT, 0)
        main_fgs.Add(fgs, 1, wx.EXPAND|wx.ALL, 0)
        main_fgs.Add(fgs1, 1, wx.EXPAND|wx.ALL, 0)
        main_fgs.Add(fgs2, 1, wx.EXPAND|wx.ALL, 0)
        bs.Add(main_fgs, 1, wx.EXPAND|wx.ALL, 5)
        # the buttons
        bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        bs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        # event handles
        wx.EVT_CHECKBOX(self, self._start_date_chkbox.GetId(), self.OnCheckBox)
        wx.EVT_CHECKBOX(self, self._end_date_chkbox.GetId(), self.OnCheckBox)
        wx.EVT_CHECKBOX(self, self.__cat_chkbox.GetId(), self.OnCheckBox)
        wx.EVT_RADIOBOX(self, self.__alarm_setting.GetId(), self.OnAlarmSetting)
        # all done
        self.SetSizer(bs)
        self.SetAutoLayout(True)
        bs.Fit(self)

    def ShowModal(self):
        # request ringtones from 
        pubsub.subscribe(self.OnRingtoneUpdates, pubsub.ALL_RINGTONES)
        wx.CallAfter(pubsub.publish, pubsub.REQUEST_RINGTONES) # make the call once we are onscreen
        return wx.Dialog.ShowModal(self)

    def OnRingtoneUpdates(self, msg):
        "Receives pubsub message with ringtone list"
        tones=msg.data[:]
        #cur=self.Get()
        try:
            self.__ringtone.Clear()
            self.__ringtone.Append(self.unnamed)
            for p in tones:
                self.__ringtone.Append(p)
            rt=self.__ringtone.SetStringSelection(self.ringtone)
        except:
            self.ringtone=self.unnamed

    def __set_cats(self, chk_box, c, data):
        if data is None:
            chk_box.SetValue(False)
            c.Disable()
        else:
            chk_box.SetValue(True)
            c.Enable()
            for i,d in enumerate(data):
                if not len(d):
                    data[i]='<None>'
            for i in range(c.GetCount()):
                c.Check(i, c.GetString(i) in data)

    def __set_rpt(self, data):
        if self._start_date_chkbox.GetValue() and\
           self._end_date_chkbox.GetValue():
            self.__rpt_chkbox.Enable()
            self.__rpt_chkbox_text.Enable()
            self.__rpt_chkbox.SetValue(data)
        else:
            self.__rpt_chkbox.SetValue(False)
            self.__rpt_chkbox.Disable()
            self.__rpt_chkbox_text.Disable()

    def __set_alarm_fields(self, value):
        if value==0:
            self.__vibrate.Disable()
            self.__alarm_value.Disable()
            self.__ringtone.Disable()
            self.__vibrate_text.Disable()
            self.__alarm_value_text.Disable()
            self.__ringtone_text.Disable()
        elif value==1:
            self.__vibrate.Enable()
            self.__alarm_value.Disable()
            self.__ringtone.Enable()
            self.__vibrate_text.Enable()
            self.__alarm_value_text.Disable()
            self.__ringtone_text.Enable()
        else:
            self.__vibrate.Enable()
            self.__alarm_value.Enable()
            self.__ringtone.Enable()
            self.__vibrate_text.Enable()
            self.__alarm_value_text.Enable()
            self.__ringtone_text.Enable()

    def set_base(self, data):
        self.__set_rpt(data.get('rpt_events', False))
        no_alarm=data.get('no_alarm', False)
        alarm_override=data.get('alarm_override', False)
        if no_alarm:
            value=0
        elif alarm_override:
            value=2
        else:
            value=1
        self.__set_alarm_fields(value)
        self.__alarm_setting.SetSelection(value)
        self.ringtone=data.get('ringtone', self.unnamed)
        try:
            self.__ringtone.SetStringSelection(ringtone)
        except:
            self.__ringtone.SetStringSelection(self.unnamed)
        value=data.get('vibrate', False);
        self.__vibrate.SetValue(value)
        self.__alarm_value.SetValue(data.get('alarm_value', 0))
        self.__set_cats(self.__cat_chkbox, self.__cats, data.get('categories', None))

    def get_base(self, r):
        r['rpt_events']=self.__rpt_chkbox.GetValue()
        value=self.__alarm_setting.GetSelection()
        if value==0:
            r['no_alarm']=True
            r['alarm_override']=False
        elif value==1:
            r['no_alarm']=False
            r['alarm_override']=False
        else:
            r['no_alarm']=False
            r['alarm_override']=True
        r['ringtone']=self.__ringtone.GetStringSelection()
        r['vibrate']=self.__vibrate.GetValue()
        r['alarm_value']=self.__alarm_value.GetValue()
        if self.__cat_chkbox.GetValue():
            c=[]
            for i in range(self.__cats.GetCount()):
                if self.__cats.IsChecked(i):
                    s=self.__cats.GetString(i)
                    if s=='<None>':
                        c.append('')
                    else:
                        c.append(s)
            r['categories']=c
        else:
            r['categories']=None
        return
    
    def OnAlarmSetting(self, _):
        self.__set_alarm_fields(self.__alarm_setting.GetSelection())

    def OnCheckBox(self, evt):
        evt_id=evt.GetId()
        if evt_id==self._start_date_chkbox.GetId():
            w1,w2=self._start_date_chkbox, self._start_date
        elif evt_id==self._end_date_chkbox.GetId():
            w1,w2=self._end_date_chkbox, self._end_date
        else:
            w1,w2=self.__cat_chkbox, self.__cats
        if w1.GetValue():
            w2.Enable()
        else:
            w2.Disable()
        # turn on the repeat event option of both start date and end date
        # are specified.
        if self._start_date_chkbox.GetValue() and \
           self._end_date_chkbox.GetValue():
            self.__rpt_chkbox.Enable()
            self.__rpt_chkbox_text.Enable()
        else:
            self.__rpt_chkbox.SetValue(False)
            self.__rpt_chkbox.Disable()
            self.__rpt_chkbox_text.Disable()

#-------------------------------------------------------------------------------
class FilterDialog(FilterDialogBase):
    def __init__(self, parent, id, caption, categories, style=wx.DEFAULT_DIALOG_STYLE):
        FilterDialogBase.__init__(self, parent, id, caption, categories, style)

    def SetDateControls(self, fgs, fgs1):
        self._start_date_chkbox=wx.CheckBox(self, id=wx.NewId(), 
                                             label='Start Date:',
                                             style=wx.ALIGN_RIGHT)
        fgs.Add(self._start_date_chkbox, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL, 0)
        self._start_date=wx.calendar.CalendarCtrl(self, -1, wx.DateTime_Now(),
                                          style = wx.calendar.CAL_SUNDAY_FIRST
                                          | wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
        self._start_date.Disable()
        fgs.Add(self._start_date, 1, wx.ALIGN_LEFT, 5)
        self._end_date_chkbox=wx.CheckBox(self, id=wx.NewId(),
                                           label='End Date:',
                                           style=wx.ALIGN_RIGHT)
        fgs.Add(self._end_date_chkbox, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL, 0)
        self._end_date=wx.calendar.CalendarCtrl(self, -1, wx.DateTime_Now(),
                                          style = wx.calendar.CAL_SUNDAY_FIRST
                                          | wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
        self._end_date.Disable()
        fgs.Add(self._end_date, 1, wx.ALIGN_LEFT, 5)

    def __set_date(self, chk_box, cal, d):
        if d is None:
            chk_box.SetValue(False)
            cal.Disable()
        else:
            chk_box.SetValue(True)
            cal.Enable()
            dt=wx.DateTime()
            dt.Set(d[2], year=d[0], month=d[1]-1)
            cal.SetDate(dt)

    def set(self, data):
        self.__set_date(self._start_date_chkbox, self._start_date,
                        data.get('start', None))
        self.__set_date(self._end_date_chkbox, self._end_date,
                        data.get('end', None))
        self.set_base(data)

    def get(self):
        r={}
        if self._start_date_chkbox.GetValue():
            dt=self._start_date.GetDate()
            r['start']=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
        else:
            r['start']=None
        if self._end_date_chkbox.GetValue():
            dt=self._end_date.GetDate()
            r['end']=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
        else:
            r['end']=None
        self.get_base(r)
        return r
    
#-------------------------------------------------------------------------------
class AutoSyncFilterDialog(FilterDialogBase):
    def __init__(self, parent, id, caption, categories, style=wx.DEFAULT_DIALOG_STYLE):
        FilterDialogBase.__init__(self, parent, id, caption, categories, style)

    def SetDateControls(self, fgs, fgs1):
        #start_offset
        self._start_date_chkbox=wx.CheckBox(self, id=wx.NewId(), 
                                             label='Start Offset (days):',
                                             style=wx.ALIGN_RIGHT)
        fgs.Add(self._start_date_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        self._start_date=wx.lib.intctrl.IntCtrl(self, id=wx.NewId(), size=(50,-1), 
                                                 value=0, min=0, max=1000)
        self._start_date.Disable()
        fgs.Add( self._start_date, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
        #end_offset
        self._end_date_chkbox=wx.CheckBox(self, id=wx.NewId(),
                                           label='End Offset (days):',
                                           style=wx.ALIGN_RIGHT)
        fgs.Add(self._end_date_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
        self._end_date=wx.lib.intctrl.IntCtrl(self, id=wx.NewId(), size=(50,-1), 
                                               value=0, min=0, max=1000)
        self._end_date.Disable()
        fgs.Add( self._end_date, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
        fgs1.Add(wx.StaticText(self, -1, 'Note: The start offset is the number of days' + 
                               ' in the past, and the end offset is the number of days' +
                               ' in the future imported from the calender into your phone. If' +
                               ' disabled, all past and/or future events are imported.',
                               size=(270,55)),
                               0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 5)


    def __set_start_date(self, d):
        if d is None:
            self._start_date_chkbox.SetValue(False)
            self._start_date.Disable()
        else:
            self._start_date_chkbox.SetValue(True)
            self._start_date.Enable()
            self._start_date.SetValue(d)

    def __set_end_date(self, d):
        if d is None:
            self._end_date_chkbox.SetValue(False)
            self._end_date.Disable()
        else:
            self._end_date_chkbox.SetValue(True)
            self._end_date.Enable()
            self._end_date.SetValue(d)

    def set(self, data):
        self.__set_start_date(data.get('start_offset', None))
        self.__set_end_date(data.get('end_offset', None))
        self.set_base(data)

    def get(self):
        r={}
        if self._start_date_chkbox.GetValue():
            r['start_offset']=self._start_date.GetValue()
        else:
            r['start_offset']=None
        if self._end_date_chkbox.GetValue():
            r['end_offset']=self._end_date.GetValue()
        else:
            r['end_offset']=None
        self.get_base(r)
        return r
common_calendar.py.diff (application/octet-stream, 19.1 KB)
Index: common_calendar.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/common_calendar.py,v
retrieving revision 1.10
diff -u -r1.10 common_calendar.py
--- common_calendar.py	5 May 2005 04:24:22 -0000	1.10
+++ common_calendar.py	16 Sep 2005 10:21:32 -0000
@@ -19,6 +19,7 @@
 
 # local modules
 import guiwidgets
+import pubsub
 
 no_end_date=(4000, 1, 1, 0, 0)
 
@@ -180,76 +181,101 @@
         evt.Skip()
 
 #-------------------------------------------------------------------------------
-class FilterDialog(wx.Dialog):
+class FilterDialogBase(wx.Dialog):
+    unnamed="Select:"
     def __init__(self, parent, id, caption, categories, style=wx.DEFAULT_DIALOG_STYLE):
         wx.Dialog.__init__(self, parent, id,
                            title=caption, style=style)
         # the main box sizer
         bs=wx.BoxSizer(wx.VERTICAL)
         # the flex grid sizers for the editable items
-        fgs=wx.FlexGridSizer(3, 3, 0, 5)
-        fgs.Add(wx.StaticText(self, -1, 'Start Date:'), 0, wx.ALIGN_CENTRE, 0)
-        self.__start_date_chkbox=wx.CheckBox(self, id=wx.NewId())
-        fgs.Add(self.__start_date_chkbox, 0, wx.ALIGN_CENTRE, 0)
-        self.__start_date=wx.calendar.CalendarCtrl(self, -1, wx.DateTime_Now(),
-                                          style = wx.calendar.CAL_SUNDAY_FIRST
-                                          | wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
-        self.__start_date.Disable()
-        fgs.Add(self.__start_date, 1, wx.ALIGN_LEFT, 5)
-        fgs.Add(wx.StaticText(self, -1, 'End Date:'), 0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
-        self.__end_date_chkbox=wx.CheckBox(self, id=wx.NewId())
-        fgs.Add(self.__end_date_chkbox, 0, wx.ALIGN_CENTRE, 0)
-        self.__end_date=wx.calendar.CalendarCtrl(self, -1, wx.DateTime_Now(),
-                                          style = wx.calendar.CAL_SUNDAY_FIRST
-                                          | wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
-        self.__end_date.Disable()
-        fgs.Add(self.__end_date, 1, wx.ALIGN_LEFT, 5)
+        main_fgs=wx.FlexGridSizer(0, 1, 0, 0)
+        fgs=wx.FlexGridSizer(3, 2, 0, 5)
+        fgs1=wx.FlexGridSizer(0, 1, 0, 0)
+        fgs2=wx.FlexGridSizer(0, 2, 0, 5)
+        # set the date options
+        self.SetDateControls(fgs, fgs1)
         # new repeat to single events option
-        fgs.Add(wx.StaticText(self, -1, 'Repeat Events:'), 0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
-        self.__rpt_chkbox=wx.CheckBox(self, id=wx.NewId())
+        self.__rpt_chkbox=wx.CheckBox(self, id=wx.NewId(), label='Repeat Events:',
+                                      style=wx.ALIGN_RIGHT)
         self.__rpt_chkbox.Disable()
-        fgs.Add(self.__rpt_chkbox, 0, wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 5)
-        fgs.Add(wx.StaticText(self, -1, 'Import as multi-single events.'),
-                0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
+        fgs.Add(self.__rpt_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
+        self.__rpt_chkbox_text=wx.StaticText(self, -1, 'Import as multi-single events.')
+        fgs.Add(self.__rpt_chkbox_text, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
+        self.__rpt_chkbox_text.Disable()
         # alarm option
-        fgs.Add(wx.StaticText(self, -1, 'No Alarm'), 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 0)
-        self.__no_alarm_chkbox=wx.CheckBox(self, id=wx.NewId())
-        fgs.Add(self.__no_alarm_chkbox, 0, wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 5)
-        fgs.Add(wx.StaticText(self, -1, 'Turn off all events alarms.'),
-                0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
+        choices=('Disable All Alarms', 'Use Alarm Settings From Calender', 
+                 'Set Alarm On All Events') 
+        self.__alarm_setting = wx.RadioBox(self, id=wx.NewId(),
+                                           label="Select Alarm Settings For Imported Events",
+                                           choices=choices,
+                                           majorDimension=1,
+                                           size=(280,-1))
+        fgs1.Add(self.__alarm_setting, 0, wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 5)
+        #alarm vibrate
+        self.__vibrate=wx.CheckBox(self, id=wx.NewId(), label='Alarm Vibrate:',
+                                   style=wx.ALIGN_RIGHT)
+        fgs2.Add(self.__vibrate, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
+        self.__vibrate_text=wx.StaticText(self, -1, 'Enable vibrate for alarms.')
+        fgs2.Add(self.__vibrate_text, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 5)
+        # alarm settings
+        self.__ringtone_text=wx.StaticText(self, -1, 'Alarm Ringtone:')
+        fgs2.Add(self.__ringtone_text, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
+        self.__ringtone=wx.ComboBox(self, id=wx.NewId(),
+                                    style=wx.CB_DROPDOWN|wx.CB_READONLY,
+                                    choices=[self.unnamed], size=(160,-1))
+        fgs2.Add(self.__ringtone, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
+        # alarm value
+        self.__alarm_value_text=wx.StaticText(self, -1, 'Alert before (mins):')
+        fgs2.Add(self.__alarm_value_text, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
+        self.__alarm_value=wx.lib.intctrl.IntCtrl(self, id=wx.NewId(), size=(50,-1), 
+                                               value=0, min=0, max=1000)
+        fgs2.Add( self.__alarm_value, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
         # category option
-        fgs.Add(wx.StaticText(self, -1, 'Categories:'), 0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE, 0)
-        self.__cat_chkbox=wx.CheckBox(self, id=wx.NewId())
-        fgs.Add(self.__cat_chkbox, 0, wx.ALIGN_CENTRE, 0)
+        self.__cat_chkbox=wx.CheckBox(self, id=wx.NewId(), label='Categories:',
+                                      style=wx.ALIGN_RIGHT)
+        fgs2.Add(self.__cat_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
         for i,c in enumerate(categories):
             if not len(c):
                 categories[i]='<None>'
-        self.__cats=wx.CheckListBox(self, choices=categories, size=(180, 50))
+        self.__cats=wx.CheckListBox(self, choices=categories, size=(160, 50))
         self.__cats.Disable()
-        fgs.Add(self.__cats, 0, wx.ALIGN_LEFT, 5)
-        bs.Add(fgs, 1, wx.EXPAND|wx.ALL, 5)
+        fgs2.Add(self.__cats, 0, wx.ALIGN_LEFT, 0)
+        main_fgs.Add(fgs, 1, wx.EXPAND|wx.ALL, 0)
+        main_fgs.Add(fgs1, 1, wx.EXPAND|wx.ALL, 0)
+        main_fgs.Add(fgs2, 1, wx.EXPAND|wx.ALL, 0)
+        bs.Add(main_fgs, 1, wx.EXPAND|wx.ALL, 5)
         # the buttons
         bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
         bs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
         # event handles
-        wx.EVT_CHECKBOX(self, self.__start_date_chkbox.GetId(), self.OnCheckBox)
-        wx.EVT_CHECKBOX(self, self.__end_date_chkbox.GetId(), self.OnCheckBox)
+        wx.EVT_CHECKBOX(self, self._start_date_chkbox.GetId(), self.OnCheckBox)
+        wx.EVT_CHECKBOX(self, self._end_date_chkbox.GetId(), self.OnCheckBox)
         wx.EVT_CHECKBOX(self, self.__cat_chkbox.GetId(), self.OnCheckBox)
+        wx.EVT_RADIOBOX(self, self.__alarm_setting.GetId(), self.OnAlarmSetting)
         # all done
         self.SetSizer(bs)
         self.SetAutoLayout(True)
         bs.Fit(self)
 
-    def __set_date(self, chk_box, cal, d):
-        if d is None:
-            chk_box.SetValue(False)
-            cal.Disable()
-        else:
-            chk_box.SetValue(True)
-            cal.Enable()
-            dt=wx.DateTime()
-            dt.Set(d[2], year=d[0], month=d[1]-1)
-            cal.SetDate(dt)
+    def ShowModal(self):
+        # request ringtones from 
+        pubsub.subscribe(self.OnRingtoneUpdates, pubsub.ALL_RINGTONES)
+        wx.CallAfter(pubsub.publish, pubsub.REQUEST_RINGTONES) # make the call once we are onscreen
+        return wx.Dialog.ShowModal(self)
+
+    def OnRingtoneUpdates(self, msg):
+        "Receives pubsub message with ringtone list"
+        tones=msg.data[:]
+        #cur=self.Get()
+        try:
+            self.__ringtone.Clear()
+            self.__ringtone.Append(self.unnamed)
+            for p in tones:
+                self.__ringtone.Append(p)
+            rt=self.__ringtone.SetStringSelection(self.ringtone)
+        except:
+            self.ringtone=self.unnamed
 
     def __set_cats(self, chk_box, c, data):
         if data is None:
@@ -265,37 +291,76 @@
                 c.Check(i, c.GetString(i) in data)
 
     def __set_rpt(self, data):
-        if self.__start_date_chkbox.GetValue() and\
-           self.__end_date_chkbox.GetValue():
+        if self._start_date_chkbox.GetValue() and\
+           self._end_date_chkbox.GetValue():
             self.__rpt_chkbox.Enable()
+            self.__rpt_chkbox_text.Enable()
             self.__rpt_chkbox.SetValue(data)
         else:
             self.__rpt_chkbox.SetValue(False)
             self.__rpt_chkbox.Disable()
+            self.__rpt_chkbox_text.Disable()
 
-    def set(self, data):
-        self.__set_date(self.__start_date_chkbox, self.__start_date,
-                        data.get('start', None))
-        self.__set_date(self.__end_date_chkbox, self.__end_date,
-                        data.get('end', None))
+    def __set_alarm_fields(self, value):
+        if value==0:
+            self.__vibrate.Disable()
+            self.__alarm_value.Disable()
+            self.__ringtone.Disable()
+            self.__vibrate_text.Disable()
+            self.__alarm_value_text.Disable()
+            self.__ringtone_text.Disable()
+        elif value==1:
+            self.__vibrate.Enable()
+            self.__alarm_value.Disable()
+            self.__ringtone.Enable()
+            self.__vibrate_text.Enable()
+            self.__alarm_value_text.Disable()
+            self.__ringtone_text.Enable()
+        else:
+            self.__vibrate.Enable()
+            self.__alarm_value.Enable()
+            self.__ringtone.Enable()
+            self.__vibrate_text.Enable()
+            self.__alarm_value_text.Enable()
+            self.__ringtone_text.Enable()
+
+    def set_base(self, data):
         self.__set_rpt(data.get('rpt_events', False))
-        self.__no_alarm_chkbox.SetValue(data.get('no_alarm', False))
+        no_alarm=data.get('no_alarm', False)
+        alarm_override=data.get('alarm_override', False)
+        if no_alarm:
+            value=0
+        elif alarm_override:
+            value=2
+        else:
+            value=1
+        self.__set_alarm_fields(value)
+        self.__alarm_setting.SetSelection(value)
+        self.ringtone=data.get('ringtone', self.unnamed)
+        try:
+            self.__ringtone.SetStringSelection(ringtone)
+        except:
+            self.__ringtone.SetStringSelection(self.unnamed)
+        value=data.get('vibrate', False);
+        self.__vibrate.SetValue(value)
+        self.__alarm_value.SetValue(data.get('alarm_value', 0))
         self.__set_cats(self.__cat_chkbox, self.__cats, data.get('categories', None))
 
-    def get(self):
-        r={}
-        if self.__start_date_chkbox.GetValue():
-            dt=self.__start_date.GetDate()
-            r['start']=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
-        else:
-            r['start']=None
-        if self.__end_date_chkbox.GetValue():
-            dt=self.__end_date.GetDate()
-            r['end']=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
-        else:
-            r['end']=None
+    def get_base(self, r):
         r['rpt_events']=self.__rpt_chkbox.GetValue()
-        r['no_alarm']=self.__no_alarm_chkbox.GetValue()
+        value=self.__alarm_setting.GetSelection()
+        if value==0:
+            r['no_alarm']=True
+            r['alarm_override']=False
+        elif value==1:
+            r['no_alarm']=False
+            r['alarm_override']=False
+        else:
+            r['no_alarm']=False
+            r['alarm_override']=True
+        r['ringtone']=self.__ringtone.GetStringSelection()
+        r['vibrate']=self.__vibrate.GetValue()
+        r['alarm_value']=self.__alarm_value.GetValue()
         if self.__cat_chkbox.GetValue():
             c=[]
             for i in range(self.__cats.GetCount()):
@@ -308,14 +373,17 @@
             r['categories']=c
         else:
             r['categories']=None
-        return r
+        return
     
+    def OnAlarmSetting(self, _):
+        self.__set_alarm_fields(self.__alarm_setting.GetSelection())
+
     def OnCheckBox(self, evt):
         evt_id=evt.GetId()
-        if evt_id==self.__start_date_chkbox.GetId():
-            w1,w2=self.__start_date_chkbox, self.__start_date
-        elif evt_id==self.__end_date_chkbox.GetId():
-            w1,w2=self.__end_date_chkbox, self.__end_date
+        if evt_id==self._start_date_chkbox.GetId():
+            w1,w2=self._start_date_chkbox, self._start_date
+        elif evt_id==self._end_date_chkbox.GetId():
+            w1,w2=self._end_date_chkbox, self._end_date
         else:
             w1,w2=self.__cat_chkbox, self.__cats
         if w1.GetValue():
@@ -324,10 +392,137 @@
             w2.Disable()
         # turn on the repeat event option of both start date and end date
         # are specified.
-        if self.__start_date_chkbox.GetValue() and \
-           self.__end_date_chkbox.GetValue():
+        if self._start_date_chkbox.GetValue() and \
+           self._end_date_chkbox.GetValue():
             self.__rpt_chkbox.Enable()
+            self.__rpt_chkbox_text.Enable()
         else:
             self.__rpt_chkbox.SetValue(False)
             self.__rpt_chkbox.Disable()
+            self.__rpt_chkbox_text.Disable()
+
+#-------------------------------------------------------------------------------
+class FilterDialog(FilterDialogBase):
+    def __init__(self, parent, id, caption, categories, style=wx.DEFAULT_DIALOG_STYLE):
+        FilterDialogBase.__init__(self, parent, id, caption, categories, style)
+
+    def SetDateControls(self, fgs, fgs1):
+        self._start_date_chkbox=wx.CheckBox(self, id=wx.NewId(), 
+                                             label='Start Date:',
+                                             style=wx.ALIGN_RIGHT)
+        fgs.Add(self._start_date_chkbox, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL, 0)
+        self._start_date=wx.calendar.CalendarCtrl(self, -1, wx.DateTime_Now(),
+                                          style = wx.calendar.CAL_SUNDAY_FIRST
+                                          | wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
+        self._start_date.Disable()
+        fgs.Add(self._start_date, 1, wx.ALIGN_LEFT, 5)
+        self._end_date_chkbox=wx.CheckBox(self, id=wx.NewId(),
+                                           label='End Date:',
+                                           style=wx.ALIGN_RIGHT)
+        fgs.Add(self._end_date_chkbox, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL, 0)
+        self._end_date=wx.calendar.CalendarCtrl(self, -1, wx.DateTime_Now(),
+                                          style = wx.calendar.CAL_SUNDAY_FIRST
+                                          | wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
+        self._end_date.Disable()
+        fgs.Add(self._end_date, 1, wx.ALIGN_LEFT, 5)
 
+    def __set_date(self, chk_box, cal, d):
+        if d is None:
+            chk_box.SetValue(False)
+            cal.Disable()
+        else:
+            chk_box.SetValue(True)
+            cal.Enable()
+            dt=wx.DateTime()
+            dt.Set(d[2], year=d[0], month=d[1]-1)
+            cal.SetDate(dt)
+
+    def set(self, data):
+        self.__set_date(self._start_date_chkbox, self._start_date,
+                        data.get('start', None))
+        self.__set_date(self._end_date_chkbox, self._end_date,
+                        data.get('end', None))
+        self.set_base(data)
+
+    def get(self):
+        r={}
+        if self._start_date_chkbox.GetValue():
+            dt=self._start_date.GetDate()
+            r['start']=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
+        else:
+            r['start']=None
+        if self._end_date_chkbox.GetValue():
+            dt=self._end_date.GetDate()
+            r['end']=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
+        else:
+            r['end']=None
+        self.get_base(r)
+        return r
+    
+#-------------------------------------------------------------------------------
+class AutoSyncFilterDialog(FilterDialogBase):
+    def __init__(self, parent, id, caption, categories, style=wx.DEFAULT_DIALOG_STYLE):
+        FilterDialogBase.__init__(self, parent, id, caption, categories, style)
+
+    def SetDateControls(self, fgs, fgs1):
+        #start_offset
+        self._start_date_chkbox=wx.CheckBox(self, id=wx.NewId(), 
+                                             label='Start Offset (days):',
+                                             style=wx.ALIGN_RIGHT)
+        fgs.Add(self._start_date_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
+        self._start_date=wx.lib.intctrl.IntCtrl(self, id=wx.NewId(), size=(50,-1), 
+                                                 value=0, min=0, max=1000)
+        self._start_date.Disable()
+        fgs.Add( self._start_date, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
+        #end_offset
+        self._end_date_chkbox=wx.CheckBox(self, id=wx.NewId(),
+                                           label='End Offset (days):',
+                                           style=wx.ALIGN_RIGHT)
+        fgs.Add(self._end_date_chkbox, 0, wx.ALIGN_RIGHT|wx.TOP|wx.BOTTOM, 5)
+        self._end_date=wx.lib.intctrl.IntCtrl(self, id=wx.NewId(), size=(50,-1), 
+                                               value=0, min=0, max=1000)
+        self._end_date.Disable()
+        fgs.Add( self._end_date, 0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 2)
+        fgs1.Add(wx.StaticText(self, -1, 'Note: The start offset is the number of days' + 
+                               ' in the past, and the end offset is the number of days' +
+                               ' in the future imported from the calender into your phone. If' +
+                               ' disabled, all past and/or future events are imported.',
+                               size=(270,55)),
+                               0, wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM, 5)
+
+
+    def __set_start_date(self, d):
+        if d is None:
+            self._start_date_chkbox.SetValue(False)
+            self._start_date.Disable()
+        else:
+            self._start_date_chkbox.SetValue(True)
+            self._start_date.Enable()
+            self._start_date.SetValue(d)
+
+    def __set_end_date(self, d):
+        if d is None:
+            self._end_date_chkbox.SetValue(False)
+            self._end_date.Disable()
+        else:
+            self._end_date_chkbox.SetValue(True)
+            self._end_date.Enable()
+            self._end_date.SetValue(d)
+
+    def set(self, data):
+        self.__set_start_date(data.get('start_offset', None))
+        self.__set_end_date(data.get('end_offset', None))
+        self.set_base(data)
+
+    def get(self):
+        r={}
+        if self._start_date_chkbox.GetValue():
+            r['start_offset']=self._start_date.GetValue()
+        else:
+            r['start_offset']=None
+        if self._end_date_chkbox.GetValue():
+            r['end_offset']=self._end_date.GetValue()
+        else:
+            r['end_offset']=None
+        self.get_base(r)
+        return r
csv_calendar.py.diff (application/octet-stream, 5.4 KB)
Index: csv_calendar.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/csv_calendar.py,v
retrieving revision 1.2
diff -u -r1.2 csv_calendar.py
--- csv_calendar.py	22 Jun 2005 00:07:43 -0000	1.2
+++ csv_calendar.py	19 Sep 2005 15:28:31 -0000
@@ -157,7 +157,11 @@
         'end': None,
         'categories': None,
         'rpt_events': False,
-        'no_alarm': False
+        'no_alarm': False,
+        'ringtone': None,
+        'alarm_override':False,
+        'vibrate':False,
+        'alarm_value':0
         }
     def __init__(self, file_name=None):
         self.__calendar_keys=(
@@ -259,7 +263,10 @@
         if self.__file_name is None:
             # no file name specified
             return
-        csv_file=file(self.__file_name, 'rb')
+        try:
+            csv_file=file(self.__file_name, 'rb')
+        except:
+            return
         reader=csv.reader(csv_file)
         # retrieve the header and build the header keys
         h=reader.next()
@@ -321,8 +328,17 @@
         v=e.get('priority', None)
         if v is not None:
             ce.priority=v
-        if not self.__filter.get('no_alarm', False) and e.get('alarm', False):
+        if not self._filter.get('no_alarm', False) and \
+               not self._filter.get('alarm_override', False) and \
+               e.get('alarm', False):
             ce.alarm=e.get('alarm_value', 0)
+            ce.ringtone=self._filter.get('ringtone', "")
+            ce.vibrate=self._filter.get('vibrate', False)
+        elif not self._filter.get('no_alarm', False) and \
+               self._filter.get('alarm_override', False):
+            ce.alarm=self._filter.get('alarm_value', 0)
+            ce.ringtone=self._filter.get('ringtone', "")
+            ce.vibrate=self._filter.get('vibrate', False)
         ce.allday=e.get('allday', False)
         ce_start=e.get('start', None)
         ce_end=e.get('end', None)
@@ -479,7 +495,7 @@
         wx.EndBusyCursor()
 
     def OnBrowseFolder(self, evt):
-        dlg=wx.FileDialog(self, "Pick a CSV Calendar 4File", wildcard='*.csv')
+        dlg=wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv')
         id=dlg.ShowModal()
         if id==wx.ID_CANCEL:
             dlg.Destroy()
@@ -505,3 +521,72 @@
         return self.__oc.get_category_list()
             
 #-------------------------------------------------------------------------------
+def ImportCal(folder, filters):
+    _oc=CSVCalendarImportData(folder)
+    _oc.set_filter(filters)
+    _oc.read()
+    res={ 'calendar':_oc.get() }
+    return res
+
+#-------------------------------------------------------------------------------
+class CSVAutoConfCalDialog(wx.Dialog):
+    def __init__(self, parent, id, title, folder, filters,
+                 style=wx.CAPTION|wx.MAXIMIZE_BOX| \
+                 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
+        self._oc=CSVCalendarImportData()
+        self._oc.set_filter(filters)
+        self.__read=False
+        wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
+        main_bs=wx.BoxSizer(wx.VERTICAL)
+        hbs=wx.BoxSizer(wx.HORIZONTAL)
+        # label
+        hbs.Add(wx.StaticText(self, -1, "CSV Calendar File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
+        # where the folder name goes
+        self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
+        self.folderctrl.SetValue(folder)
+        hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
+        # browse button
+        id_browse=wx.NewId()
+        hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
+        main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
+        main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
+        wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
+        hbs=wx.BoxSizer(wx.HORIZONTAL)
+        hbs.Add(wx.Button(self, wx.ID_OK, 'OK'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
+        hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
+        id_filter=wx.NewId()
+        hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
+        hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0,  wx.ALIGN_CENTRE|wx.ALL, 5)
+        main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
+        wx.EVT_BUTTON(self, id_filter, self.OnFilter)
+        wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
+        self.SetSizer(main_bs)
+        self.SetAutoLayout(True)
+        main_bs.Fit(self)
+
+    def OnBrowseFolder(self, evt):
+        dlg=wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv')
+        id=dlg.ShowModal()
+        if id==wx.ID_CANCEL:
+            dlg.Destroy()
+            return
+        self.folderctrl.SetValue(dlg.GetPath())
+        self.__read=False
+        dlg.Destroy()
+
+    def OnFilter(self, evt):
+        # read the calender to get the category list
+        if not self.__read:
+            self._oc.read(self.folderctrl.GetValue())
+            self.__read=True
+        cat_list=self._oc.get_category_list()
+        dlg=common_calendar.AutoSyncFilterDialog(self, -1, 'Filtering Parameters', cat_list)
+        dlg.set(self._oc.get_filter())
+        if dlg.ShowModal()==wx.ID_OK:
+            self._oc.set_filter(dlg.get())
+
+    def GetFolder(self):
+        return self.folderctrl.GetValue()
+
+    def GetFilter(self):
+        return self._oc.get_filter()
csv_calendar.py (text/plain, 22.3 KB)
### BITPIM
###
### Copyright (C) 2004 Joe Pham <[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: csv_calendar.py,v 1.2 2005/06/22 00:07:43 djpham Exp $

"Deals with CSV calendar import/export stuff"

# System modules
import csv
import datetime

# wxPython modules
import wx

# Others

# My modules
import bpcalendar
import common_calendar
import helpids

#------------------------------------------------------------------------------
class ExportCSVDialog(wx.Dialog):
    def __init__(self, parent, title):
        super(ExportCSVDialog, self).__init__(parent, -1, title)
        # make the ui
        vbs=wx.BoxSizer(wx.VERTICAL)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        hbs.Add(wx.StaticText(self, -1, "File"), 0, wx.ALL|wx.ALIGN_CENTRE, 5)
        self.filenamectrl=wx.TextCtrl(self, -1, "calendar.csv")
        hbs.Add(self.filenamectrl, 1, wx.ALL|wx.EXPAND, 5)
        self.browsectrl=wx.Button(self, wx.NewId(), "Browse...")
        hbs.Add(self.browsectrl, 0, wx.ALL|wx.EXPAND, 5)
        vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
        # selection GUI
        vbs.Add(self.GetSelectionGui(self), 5, wx.EXPAND|wx.ALL, 5)
        # the buttons
        vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
        vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
        # event handlers
        wx.EVT_BUTTON(self, self.browsectrl.GetId(), self.OnBrowse)
        wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
        # all done
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)

    def GetSelectionGui(self, parent):
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        self.__selection=wx.RadioBox(parent, wx.NewId(), 'Events Selection',
                                     choices=['All', 'Date Range'],
                                     style=wx.RA_SPECIFY_ROWS)
        hbs.Add(self.__selection, 0, wx.EXPAND|wx.ALL, 5)
        sbs=wx.StaticBoxSizer(wx.StaticBox(parent, -1, 'Date Range'), wx.VERTICAL)
        gs=wx.FlexGridSizer(-1, 2, 5, 5)
        gs.AddGrowableCol(1)
        gs.Add(wx.StaticText(self, -1, 'Start:'), 0, wx.ALL, 0)
        self.__start_date=wx.DatePickerCtrl(self, style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY)
        gs.Add(self.__start_date, 0, wx.ALL, 0)
        gs.Add(wx.StaticText(self, -1, 'End:'), 0, wx.ALL, 0)
        self.__end_date=wx.DatePickerCtrl(self, style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY)
        gs.Add(self.__end_date, 0, wx.ALL, 0)
        sbs.Add(gs, 1, wx.EXPAND|wx.ALL, 5)
        hbs.Add(sbs, 0, wx.EXPAND|wx.ALL, 5)
        return hbs

    def OnBrowse(self, _):
        dlg=wx.FileDialog(self, defaultFile=self.filenamectrl.GetValue(),
                          wildcard="CSV files (*.cvs)|*.csv", style=wx.SAVE|wx.CHANGE_DIR)
        if dlg.ShowModal()==wx.ID_OK:
            self.filenamectrl.SetValue(dlg.GetPath())
        dlg.Destroy()
    def __get_str(self, entry, field):
        s=getattr(entry, field, '')
        if s is None:
            s=''
        if isinstance(s, unicode):
            return s.encode('ascii', 'ignore')
        else:
            return str(s)
    def OnOk(self, _):
        # do export
        filename=self.filenamectrl.GetValue()
        csv_event_template=(
            ('Start', 'start_str', None),
            ('End', 'end_str', None),
            ('Description', 'description', None),
            ('Location', 'location', None),
            ('Priority', 'priority', None),
            ('Alarm', 'alarm', None),
            ('All-Day', 'allday', None),
            ('Notes', 'notes', None),
            ('Categories', 'categories_str', None),
            ('Ringtone', 'ringtone', None),
            ('Wallpaper', 'wallpaper', None))
        csv_repeat_template=(
            ('Repeat Type', 'repeat_type', None),
            ('Repeat Interval', 'interval', None),
            ('Day-of-Week', 'dow_str', None),
            ('Excluded Dates', 'suppressed_str', None))
        try:
            f=file(filename, 'wt')
        except:
            f=None
        if f is None:
            dlg=wx.MessageDialog(self, 'Failed to open file ['+filename+']',
                             'Export Error')
            dlg.ShowModal()
            dlg.Destroy()
            self.EndModal(wx.ID_OK)
        s=['"'+x[0]+'"' for x in csv_event_template]+\
           ['"'+x[0]+'"' for x in csv_repeat_template]
        f.write(','.join(s)+'\n')
        all_items=self.__selection.GetSelection()==0
        dt=self.__start_date.GetValue()
        range_start=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
        dt=self.__end_date.GetValue()
        range_end=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
        #---
        def __write_rec(f, cal_dict):
            for k,e in cal_dict.items():
                if not all_items and \
                   (e.end < range_start or e.start>range_end):
                    continue
                l=[]
                for field in csv_event_template:
                    if field[2] is None:
                        s=self.__get_str(e, field[1])
                    else:
                        s=field[2](e, field[1])
                    l+=['"'+s.replace('"', '')+'"']
                rpt=e.repeat
                if rpt is None:
                    l+=['']*len(csv_repeat_template)
                else:
                    for field in csv_repeat_template:
                        if field[2] is None:
                            s=self.__get_str(rpt, field[1])
                        else:
                            s=field[2](rpt, field[1])
                        l+=['"'+s.replace('"', '')+'"']
                f.write(','.join(l)+'\n')
        #---
        cal_dict=self.GetParent().GetCalendarData()
        __write_rec(f, cal_dict)
        f.close()
        self.EndModal(wx.ID_OK)

#------------------------------------------------------------------------------
class CSVCalendarImportData(object):
    __default_filter={
        'start': None,
        'end': None,
        'categories': None,
        'rpt_events': False,
        'no_alarm': False,
        'ringtone': None,
        'alarm_override':False,
        'vibrate':False,
        'alarm_value':0
        }
    def __init__(self, file_name=None):
        self.__calendar_keys=(
            ('Start', 'start', self.__set_datetime),
            ('End', 'end', self.__set_datetime),
            ('Description', 'description', self.__set_str),
            ('Location', 'location', self.__set_str),
            ('Priority', 'priority', self.__set_priority),
            ('Alarm', 'alarm_value',self.__set_alarm),
            ('All-Day', 'allday', self.__set_bool),
            ('Notes', 'notes', self.__set_str),
            ('Categories', 'categories', self.__set_categories),
            ('Ringtone', 'ringtone', self.__set_str),
            ('Wallpaper', 'wallpaper', self.__set_str),
            ('Repeat Type', 'repeat_type', self.__set_repeat_type),
            ('Repeat Interval', 'repeat_interval', self.__set_int),
            ('Day-of-Week', 'repeat_dow', self.__set_dow),
            ('Excluded Dates', 'exceptions', self.__set_exceptions)
            )
        self.__file_name=file_name
        self.__data=[]
        self.__filter=self.__default_filter
        self.read()

    def __accept(self, entry):
        # start & end time within specified filter
        if self.__filter['start'] is not None and \
           entry['start'][:3]<self.__filter['start'][:3]:
            return False
        if self.__filter['end'] is not None and \
           entry['end'][:3]>self.__filter['end'][:3] and \
           entry['end'][:3]!=common_calendar.no_end_date[:3]:
            return False
        # check the catefory
        c=self.__filter['categories']
        if c is None or not len(c):
            # no categories specified => all catefories allowed.
            return True
        if len([x for x in entry['categories'] if x in c]):
            return True
        return False

    def get(self):
        res={}
        single_rpt=self.__filter.get('rpt_events', False)
        for k in self.__data:
            try:
                if self.__accept(k):
                    if k.get('repeat', False) and single_rpt:
                        d=self.__generate_repeat_events(k)
                    else:
                        d=[k]
                    for n in d:
                        ce=bpcalendar.CalendarEntry()
                        self.__populate_entry(n, ce)
                        res[ce.id]=ce
            except:
                if module_debug:
                    raise
        return res

    def get_category_list(self):
        l=[]
        for e in self.__data:
            l+=[x for x in e.get('categories', []) if x not in l]
        return l
            
    def set_filter(self, filter):
        self.__filter=filter

    def get_filter(self):
        return self.__filter

    def get_display_data(self):
        cnt=0
        res={}
        single_rpt=self.__filter.get('rpt_events', False)
        for k in self.__data:
            if self.__accept(k):
                if k.get('repeat', False) and single_rpt:
                    d=self.__generate_repeat_events(k)
                else:
                    d=[k.copy()]
                for n in d:
                    if self.__filter.get('no_alarm', False):
                        n['alarm']=False
                    res[cnt]=n
                    cnt+=1
        return res

    def get_file_name(self):
        if self.__file_name is not None:
            return self.__file_name
        return ''

    def read(self, file_name=None):
        if file_name is not None:
            self.__file_name=file_name
        if self.__file_name is None:
            # no file name specified
            return
        try:
            csv_file=file(self.__file_name, 'rb')
        except:
            return
        reader=csv.reader(csv_file)
        # retrieve the header and build the header keys
        h=reader.next()
        header_keys=[]
        for e in h:
            k=None
            for x in self.__calendar_keys:
                if e==x[0]:
                    k=x
                    break
            header_keys.append(k)
        # loop through the file, read each line, and parse it
        self.__data=[]
        for row in reader:
            d={}
            for i,e in enumerate(row):
                if header_keys[i] is None:
                    continue
                elif header_keys[i][2] is None:
                    self.__set_str(e, d, header_keys[i][1])
                else:
                    header_keys[i][2](e, d, header_keys[i][1])
            self.__data.append(d)
        csv_file.close()

    def __populate_repeat_entry(self, e, ce):
        # populate repeat entry data
        if not e.get('repeat', False) or e.get('repeat_type', None) is None:
            #  not a repeat event
            return
        rp=bpcalendar.RepeatEntry()
        rp_type=e['repeat_type']
        rp_interval=e.get('repeat_interval', 1)
        rp_dow=e.get('repeat_dow', 0)

        if rp_type==rp.daily:
            # daily event
            rp.repeat_type=rp.daily
            rp.interval=rp_interval
        elif rp_type==rp.weekly or rp_type==rp.monthly:
            rp.repeat_type=rp_type
            rp.interval=rp_interval
            rp.dow=rp_dow
        elif rp_type==rp.yearly:
            rp.repeat_type=rp.yearly
        else:
            # not yet supported
            return
        # add the list of exceptions
        for k in e.get('exceptions', []):
            rp.add_suppressed(*k[:3])
        # all done
        ce.repeat=rp
            
    def __populate_entry(self, e, ce):
        # populate an calendar entry with data
        ce.description=e.get('description', None)
        ce.location=e.get('location', None)
        v=e.get('priority', None)
        if v is not None:
            ce.priority=v
        if not self._filter.get('no_alarm', False) and \
               not self._filter.get('alarm_override', False) and \
               e.get('alarm', False):
            ce.alarm=e.get('alarm_value', 0)
            ce.ringtone=self._filter.get('ringtone', "")
            ce.vibrate=self._filter.get('vibrate', False)
        elif not self._filter.get('no_alarm', False) and \
               self._filter.get('alarm_override', False):
            ce.alarm=self._filter.get('alarm_value', 0)
            ce.ringtone=self._filter.get('ringtone', "")
            ce.vibrate=self._filter.get('vibrate', False)
        ce.allday=e.get('allday', False)
        ce_start=e.get('start', None)
        ce_end=e.get('end', None)
        if ce_start is None and ce_end is None:
            raise ValueError, "No start or end datetime"
        if ce_start is not None:
            ce.start=ce_start
        if ce_end is not None:
            ce.end=ce_end
        if ce_start is None:
            ce.start=ce.end
        elif ce_end is None:
            ce.end=ce.start
        ce.notes=e.get('notes', None)
        v=[]
        for k in e.get('categories', []):
            v.append({ 'category': k })
        ce.categories=v
        # look at repeat
        self.__populate_repeat_entry(e, ce)

    def __generate_repeat_events(self, e):
        # generate multiple single events from this repeat event
        ce=bpcalendar.CalendarEntry()
        self.__populate_entry(e, ce)
        l=[]
        new_e=e.copy()
        new_e['repeat']=False
        for k in ('repeat_type', 'repeat_interval', 'repeat_dow'):
            if new_e.has_key(k):
                del new_e[k]
        s_date=datetime.datetime(*self.__filter['start'])
        e_date=datetime.datetime(*self.__filter['end'])
        one_day=datetime.timedelta(1)
        this_date=s_date
        while this_date<=e_date:
            date_l=(this_date.year, this_date.month, this_date.day)
            if ce.is_active(*date_l):
                new_e['start']=date_l+new_e['start'][3:]
                new_e['end']=date_l+new_e['end'][3:]
                l.append(new_e.copy())
            this_date+=one_day
        return l
        
    def __set_str(self, v, d, key):
        d[key]=str(v)
    def __set_datetime(self, v, d, key):
        # the date time should be in this format: YYYY-MM-DD hh:mm
        # quick check for the format
        if v[4]!='-' or v[7]!='-' or v[10]!= ' ' or v[13]!=':':
            return
        d[key]=(int(v[:4]), int(v[5:7]), int(v[8:10]), int(v[11:13]),
                int(v[14:16]))
    def __set_priority(self, v, d, key):
        if len(v):
            d[key]=int(v)
        else:
            d[key]=None
    def __set_alarm(self, v, d, key):
        if len(v):
            d[key]=int(v)
            d['alarm']=d[key]!=-1
        else:
            d[key]=None
            d['alarm']=False
    def __set_int(self, v, d, key):
        if not len(v):
            d[key]=None
        else:
            d[key]=int(v)
    def __set_bool(self, v, d, key):
        d[key]=v.upper()=='TRUE'
    def __set_categories(self, v, d, key):
        if v is None or not len(v):
            d[key]=[]
        else:
            d[key]=v.split(';')
    def __set_repeat_type(self, v, d, key):
        if len(v):
            d[key]=str(v)
            d['repeat']=True
        else:
            d['repeat']=False
    def __set_dow(self, v, d, key):
        dow=0
        for e in v.split(';'):
            dow|=bpcalendar.RepeatEntry.dow_names.get(e, 0)
        d[key]=dow
    def __set_exceptions(self, v, d, key):
        l=[]
        for e in v.split(';'):
            if len(e):
                if e[4]=='-' and e[7]=='-':
                    l.append( (int(e[:4]), int(e[5:7]), int(e[8:10])) )
        d[key]=l
#------------------------------------------------------------------------------
class CSVImportDialog(common_calendar.PreviewDialog):
    
    __column_labels=[
        ('description', 'Description', 400, None),
        ('start', 'Start', 150, common_calendar.bp_date_str),
        ('end', 'End', 150, common_calendar.bp_date_str),
        ('repeat_type', 'Repeat', 80, common_calendar.bp_repeat_str),
        ('alarm', 'Alarm', 80, common_calendar.bp_alarm_str),
        ('categories', 'Category', 150, common_calendar.category_str)
        ]
    ID_ADD=wx.NewId()
    def __init__(self, parent, id, title):
        self.__oc=CSVCalendarImportData()
        common_calendar.PreviewDialog.__init__(self, parent, id, title,
                               self.__column_labels,
                               self.__oc.get_display_data(),
                               config_name='import/calendar/csvdialog')

    def getcontrols(self, main_bs):
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        # label
        hbs.Add(wx.StaticText(self, -1, "CSV File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
        # where the folder name goes
        self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
        self.folderctrl.SetValue(self.__oc.get_file_name())
        hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
        # browse button
        id_browse=wx.NewId()
        hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
        main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
        main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
    def getpostcontrols(self, main_bs):
        main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        id_import=wx.NewId()
        hbs.Add(wx.Button(self, id_import, 'Import'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        hbs.Add(wx.Button(self, wx.ID_OK, 'Replace All'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        hbs.Add(wx.Button(self, self.ID_ADD, 'Add'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        id_filter=wx.NewId()
        hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)       
        hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0,  wx.ALIGN_CENTRE|wx.ALL, 5)
        main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        wx.EVT_BUTTON(self, id_import, self.OnImport)
        wx.EVT_BUTTON(self, id_filter, self.OnFilter)
        wx.EVT_BUTTON(self, self.ID_ADD, self.OnAdd)
        wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))

    def OnImport(self, evt):
        wx.BeginBusyCursor()
        dlg=wx.ProgressDialog('CSV Calendar Import',
                              'Importing CSV Calendar Data, please wait ...',
                              parent=self)
        self.__oc.read(self.folderctrl.GetValue())
        self.populate(self.__oc.get_display_data())
        dlg.Destroy()
        wx.EndBusyCursor()

    def OnBrowseFolder(self, evt):
        dlg=wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv')
        id=dlg.ShowModal()
        if id==wx.ID_CANCEL:
            dlg.Destroy()
            return
        self.folderctrl.SetValue(dlg.GetPath())
        dlg.Destroy()

    def OnFilter(self, evt):
        cat_list=self.__oc.get_category_list()
        dlg=common_calendar.FilterDialog(self, -1, 'Filtering Parameters', cat_list)
        dlg.set(self.__oc.get_filter())
        if dlg.ShowModal()==wx.ID_OK:
            self.__oc.set_filter(dlg.get())
            self.populate(self.__oc.get_display_data())

    def OnAdd(self, evt):
        self.EndModal(self.ID_ADD)

    def get(self):
        return self.__oc.get()

    def get_categories(self):
        return self.__oc.get_category_list()
            
#-------------------------------------------------------------------------------
def ImportCal(folder, filters):
    _oc=CSVCalendarImportData(folder)
    _oc.set_filter(filters)
    _oc.read()
    res={ 'calendar':_oc.get() }
    return res

#-------------------------------------------------------------------------------
class CSVAutoConfCalDialog(wx.Dialog):
    def __init__(self, parent, id, title, folder, filters,
                 style=wx.CAPTION|wx.MAXIMIZE_BOX| \
                 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
        self._oc=CSVCalendarImportData()
        self._oc.set_filter(filters)
        self.__read=False
        wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
        main_bs=wx.BoxSizer(wx.VERTICAL)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        # label
        hbs.Add(wx.StaticText(self, -1, "CSV Calendar File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
        # where the folder name goes
        self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
        self.folderctrl.SetValue(folder)
        hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
        # browse button
        id_browse=wx.NewId()
        hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
        main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
        main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
        hbs=wx.BoxSizer(wx.HORIZONTAL)
        hbs.Add(wx.Button(self, wx.ID_OK, 'OK'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        id_filter=wx.NewId()
        hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0,  wx.ALIGN_CENTRE|wx.ALL, 5)
        main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
        wx.EVT_BUTTON(self, id_filter, self.OnFilter)
        wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
        self.SetSizer(main_bs)
        self.SetAutoLayout(True)
        main_bs.Fit(self)

    def OnBrowseFolder(self, evt):
        dlg=wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv')
        id=dlg.ShowModal()
        if id==wx.ID_CANCEL:
            dlg.Destroy()
            return
        self.folderctrl.SetValue(dlg.GetPath())
        self.__read=False
        dlg.Destroy()

    def OnFilter(self, evt):
        # read the calender to get the category list
        if not self.__read:
            self._oc.read(self.folderctrl.GetValue())
            self.__read=True
        cat_list=self._oc.get_category_list()
        dlg=common_calendar.AutoSyncFilterDialog(self, -1, 'Filtering Parameters', cat_list)
        dlg.set(self._oc.get_filter())
        if dlg.ShowModal()==wx.ID_OK:
            self._oc.set_filter(dlg.get())

    def GetFolder(self):
        return self.folderctrl.GetValue()

    def GetFilter(self):
        return self._oc.get_filter()
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.