PyXR

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



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2004 Joe Pham <djpham@bitpim.org>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: csv_calendar.py 4380 2007-08-29 00:17:07Z djpham $
0009 
0010 "Deals with CSV calendar import/export stuff"
0011 
0012 # System modules
0013 from __future__ import with_statement
0014 import csv
0015 import datetime
0016 
0017 # wxPython modules
0018 import wx
0019 
0020 # Others
0021 
0022 # My modules
0023 import bpcalendar
0024 import common_calendar
0025 import helpids
0026 import guihelper
0027 
0028 module_debug=False
0029 
0030 #-------------------------------------------------------------------------------
0031 class ImportDataSource(common_calendar.ImportDataSource):
0032     # how to define, and retrieve calendar import data source
0033     message_str="Pick a CSV Calendar File"
0034     wildcard='*.csv'
0035 
0036 #------------------------------------------------------------------------------
0037 ExportCSVDialogParent=common_calendar.ExportCalendarDialog
0038 class ExportCSVDialog(ExportCSVDialogParent):
0039     _default_file_name="calendar.csv"
0040     _wildcards="CSV files (*.csv)|*.csv"
0041 
0042     def __init__(self, parent, title):
0043         super(ExportCSVDialog, self).__init__(parent, title)
0044 
0045     def __get_str(self, entry, field):
0046         s=getattr(entry, field, '')
0047         if s is None:
0048             s=''
0049         if isinstance(s, unicode):
0050             return s.encode('ascii', 'ignore')
0051         else:
0052             return str(s)
0053 
0054     def _export(self):
0055         # do export
0056         filename=self.filenamectrl.GetValue()
0057         csv_event_template=(
0058             ('Start', 'start_str', None),
0059             ('End', 'end_str', None),
0060             ('Description', 'description', None),
0061             ('Location', 'location', None),
0062             ('Priority', 'priority', None),
0063             ('Alarm', 'alarm', None),
0064             ('All-Day', 'allday', None),
0065             ('Notes', 'notes', None),
0066             ('Categories', 'categories_str', None),
0067             ('Ringtone', 'ringtone', None),
0068             ('Wallpaper', 'wallpaper', None))
0069         csv_repeat_template=(
0070             ('Repeat Type', 'repeat_type', None),
0071             ('Repeat Interval', 'interval', None),
0072             ('Repeat Interval2', 'interval2', None),
0073             ('Day-of-Week', 'dow_str', None),
0074             ('Excluded Dates', 'suppressed_str', None))
0075         try:
0076             f=file(filename, 'wt')
0077         except:
0078             f=None
0079         if f is None:
0080             guihelper.MessageDialog(self, 'Failed to open file ['+filename+']',
0081                                     'Export Error')
0082             return
0083         s=['"'+x[0]+'"' for x in csv_event_template]+\
0084            ['"'+x[0]+'"' for x in csv_repeat_template]
0085         f.write(','.join(s)+'\n')
0086         all_items=self._selection.GetSelection()==0
0087         dt=self._start_date.GetValue()
0088         range_start=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
0089         dt=self._end_date.GetValue()
0090         range_end=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
0091         #---
0092         def __write_rec(f, cal_dict):
0093             for k,e in cal_dict.items():
0094                 if not all_items and \
0095                    (e.end < range_start or e.start>range_end):
0096                     continue
0097                 l=[]
0098                 for field in csv_event_template:
0099                     if field[2] is None:
0100                         s=self.__get_str(e, field[1])
0101                     else:
0102                         s=field[2](e, field[1])
0103                     l+=['"'+s.replace('"', '')+'"']
0104                 rpt=e.repeat
0105                 if rpt is None:
0106                     l+=['']*len(csv_repeat_template)
0107                 else:
0108                     for field in csv_repeat_template:
0109                         if field[2] is None:
0110                             s=self.__get_str(rpt, field[1])
0111                         else:
0112                             s=field[2](rpt, field[1])
0113                         l+=['"'+s.replace('"', '')+'"']
0114                 f.write(','.join(l)+'\n')
0115         #---
0116         cal_dict=self.GetParent().GetCalendarData()
0117         __write_rec(f, cal_dict)
0118         f.close()
0119 
0120 #------------------------------------------------------------------------------
0121 class CSVCalendarImportData(object):
0122     __default_filter={
0123         'start': None,
0124         'end': None,
0125         'categories': None,
0126         'rpt_events': False,
0127         'no_alarm': False,
0128         'ringtone': None,
0129         'alarm_override':False,
0130         'vibrate':False,
0131         'alarm_value':0
0132         }
0133     def __init__(self, file_name=None):
0134         self.__calendar_keys=(
0135             ('Start', 'start', self.__set_datetime),
0136             ('End', 'end', self.__set_datetime),
0137             ('Description', 'description', self.__set_str),
0138             ('Location', 'location', self.__set_str),
0139             ('Priority', 'priority', self.__set_priority),
0140             ('Alarm', 'alarm_value',self.__set_alarm),
0141             ('All-Day', 'allday', self.__set_bool),
0142             ('Notes', 'notes', self.__set_str),
0143             ('Categories', 'categories', self.__set_categories),
0144             ('Ringtone', 'ringtone', self.__set_str),
0145             ('Wallpaper', 'wallpaper', self.__set_str),
0146             ('Repeat Type', 'repeat_type', self.__set_repeat_type),
0147             ('Repeat Interval', 'repeat_interval', self.__set_int),
0148             ('Repeat Interval2', 'repeat_interval2', self.__set_int),
0149             ('Day-of-Week', 'repeat_dow', self.__set_dow),
0150             ('Excluded Dates', 'exceptions', self.__set_exceptions)
0151             )
0152         self.__file_name=file_name
0153         self.__data=[]
0154         self.__filter=self.__default_filter
0155         self.read()
0156 
0157     def __accept(self, entry):
0158         # start & end time within specified filter
0159         if self.__filter['start'] is not None and \
0160            entry['start'][:3]<self.__filter['start'][:3]:
0161             return False
0162         if self.__filter['end'] is not None and \
0163            entry['end'][:3]>self.__filter['end'][:3] and \
0164            entry['end'][:3]!=common_calendar.no_end_date[:3]:
0165             return False
0166         # check the catefory
0167         c=self.__filter['categories']
0168         if c is None or not len(c):
0169             # no categories specified => all catefories allowed.
0170             return True
0171         if len([x for x in entry['categories'] if x in c]):
0172             return True
0173         return False
0174 
0175     def get(self):
0176         res={}
0177         single_rpt=self.__filter.get('rpt_events', False)
0178         for k in self.__data:
0179             try:
0180                 if self.__accept(k):
0181                     if k.get('repeat', False) and single_rpt:
0182                         d=self.__generate_repeat_events(k)
0183                     else:
0184                         d=[k]
0185                     for n in d:
0186                         ce=bpcalendar.CalendarEntry()
0187                         self.__populate_entry(n, ce)
0188                         res[ce.id]=ce
0189             except:
0190                 if module_debug:
0191                     raise
0192         return res
0193 
0194     def get_category_list(self):
0195         l=[]
0196         for e in self.__data:
0197             l+=[x for x in e.get('categories', []) if x not in l]
0198         return l
0199             
0200     def set_filter(self, filter):
0201         self.__filter=filter
0202 
0203     def get_filter(self):
0204         return self.__filter
0205 
0206     def get_display_data(self):
0207         cnt=0
0208         res={}
0209         single_rpt=self.__filter.get('rpt_events', False)
0210         for k in self.__data:
0211             if self.__accept(k):
0212                 if k.get('repeat', False) and single_rpt:
0213                     d=self.__generate_repeat_events(k)
0214                 else:
0215                     d=[k.copy()]
0216                 for n in d:
0217                     if self.__filter.get('no_alarm', False):
0218                         n['alarm']=False
0219                     res[cnt]=n
0220                     cnt+=1
0221         return res
0222 
0223     def get_file_name(self):
0224         if self.__file_name is not None:
0225             return self.__file_name
0226         return ''
0227 
0228     def read(self, file_name=None, dlg=None):
0229         if file_name is not None:
0230             self.__file_name=file_name
0231         if self.__file_name is None:
0232             # no file name specified
0233             return
0234         try:
0235             csv_file=file(self.__file_name, 'rb')
0236         except:
0237             return
0238         reader=csv.reader(csv_file)
0239         # retrieve the header and build the header keys
0240         h=reader.next()
0241         header_keys=[]
0242         for e in h:
0243             k=None
0244             for x in self.__calendar_keys:
0245                 if e==x[0]:
0246                     k=x
0247                     break
0248             header_keys.append(k)
0249         # loop through the file, read each line, and parse it
0250         self.__data=[]
0251         for row in reader:
0252             d={}
0253             for i,e in enumerate(row):
0254                 if header_keys[i] is None:
0255                     continue
0256                 elif header_keys[i][2] is None:
0257                     self.__set_str(e, d, header_keys[i][1])
0258                 else:
0259                     header_keys[i][2](e, d, header_keys[i][1])
0260             self.__data.append(d)
0261         csv_file.close()
0262 
0263     def __populate_repeat_entry(self, e, ce):
0264         # populate repeat entry data
0265         if not e.get('repeat', False) or e.get('repeat_type', None) is None:
0266             #  not a repeat event
0267             return
0268         rp=bpcalendar.RepeatEntry()
0269         rp_type=e['repeat_type']
0270         rp_interval=e.get('repeat_interval', 1)
0271         rp_interval2=e.get('repeat_interval2', 1)
0272         rp_dow=e.get('repeat_dow', 0)
0273 
0274         if rp_type==rp.daily:
0275             # daily event
0276             rp.repeat_type=rp.daily
0277             rp.interval=rp_interval
0278         elif rp_type==rp.weekly or rp_type==rp.monthly:
0279             rp.repeat_type=rp_type
0280             rp.interval=rp_interval
0281             rp.interval2=rp_interval2
0282             rp.dow=rp_dow
0283         elif rp_type==rp.yearly:
0284             rp.repeat_type=rp.yearly
0285         else:
0286             # not yet supported
0287             return
0288         # add the list of exceptions
0289         for k in e.get('exceptions', []):
0290             rp.add_suppressed(*k[:3])
0291         # all done
0292         ce.repeat=rp
0293             
0294     def __populate_entry(self, e, ce):
0295         # populate an calendar entry with data
0296         ce.description=e.get('description', None)
0297         ce.location=e.get('location', None)
0298         v=e.get('priority', None)
0299         if v is not None:
0300             ce.priority=v
0301         if not self.__filter.get('no_alarm', False) and \
0302                not self.__filter.get('alarm_override', False) and \
0303                e.get('alarm', False):
0304             ce.alarm=e.get('alarm_value', 0)
0305             ce.ringtone=self.__filter.get('ringtone', "")
0306             ce.vibrate=self.__filter.get('vibrate', False)
0307         elif not self.__filter.get('no_alarm', False) and \
0308                self.__filter.get('alarm_override', False):
0309             ce.alarm=self.__filter.get('alarm_value', 0)
0310             ce.ringtone=self.__filter.get('ringtone', "")
0311             ce.vibrate=self.__filter.get('vibrate', False)
0312         ce.allday=e.get('allday', False)
0313         ce_start=e.get('start', None)
0314         ce_end=e.get('end', None)
0315         if ce_start is None and ce_end is None:
0316             raise ValueError, "No start or end datetime"
0317         if ce_start is not None:
0318             ce.start=ce_start
0319         if ce_end is not None:
0320             ce.end=ce_end
0321         if ce_start is None:
0322             ce.start=ce.end
0323         elif ce_end is None:
0324             ce.end=ce.start
0325         ce.notes=e.get('notes', None)
0326         v=[]
0327         for k in e.get('categories', []):
0328             v.append({ 'category': k })
0329         ce.categories=v
0330         # look at repeat
0331         self.__populate_repeat_entry(e, ce)
0332 
0333     def __generate_repeat_events(self, e):
0334         # generate multiple single events from this repeat event
0335         ce=bpcalendar.CalendarEntry()
0336         self.__populate_entry(e, ce)
0337         l=[]
0338         new_e=e.copy()
0339         new_e['repeat']=False
0340         for k in ('repeat_type', 'repeat_interval', 'repeat_dow'):
0341             if new_e.has_key(k):
0342                 del new_e[k]
0343         s_date=datetime.datetime(*self.__filter['start'])
0344         e_date=datetime.datetime(*self.__filter['end'])
0345         one_day=datetime.timedelta(1)
0346         this_date=s_date
0347         while this_date<=e_date:
0348             date_l=(this_date.year, this_date.month, this_date.day)
0349             if ce.is_active(*date_l):
0350                 new_e['start']=date_l+new_e['start'][3:]
0351                 new_e['end']=date_l+new_e['end'][3:]
0352                 l.append(new_e.copy())
0353             this_date+=one_day
0354         return l
0355         
0356     def __set_str(self, v, d, key):
0357         d[key]=str(v)
0358     def __set_datetime(self, v, d, key):
0359         # the date time should be in this format: YYYY-MM-DD hh:mm
0360         # quick check for the format
0361         if v[4]!='-' or v[7]!='-' or v[10]!= ' ' or v[13]!=':':
0362             return
0363         d[key]=(int(v[:4]), int(v[5:7]), int(v[8:10]), int(v[11:13]),
0364                 int(v[14:16]))
0365     def __set_priority(self, v, d, key):
0366         if len(v):
0367             d[key]=int(v)
0368         else:
0369             d[key]=None
0370     def __set_alarm(self, v, d, key):
0371         if len(v):
0372             d[key]=int(v)
0373             d['alarm']=d[key]!=-1
0374         else:
0375             d[key]=None
0376             d['alarm']=False
0377     def __set_int(self, v, d, key):
0378         if not len(v):
0379             d[key]=None
0380         else:
0381             d[key]=int(v)
0382     def __set_bool(self, v, d, key):
0383         d[key]=v.upper()=='TRUE'
0384     def __set_categories(self, v, d, key):
0385         if v is None or not len(v):
0386             d[key]=[]
0387         else:
0388             d[key]=v.split(';')
0389     def __set_repeat_type(self, v, d, key):
0390         if len(v):
0391             d[key]=str(v)
0392             d['repeat']=True
0393         else:
0394             d['repeat']=False
0395     def __set_dow(self, v, d, key):
0396         dow=0
0397         for e in v.split(';'):
0398             dow|=bpcalendar.RepeatEntry.dow_names.get(e, 0)
0399         d[key]=dow
0400     def __set_exceptions(self, v, d, key):
0401         l=[]
0402         for e in v.split(';'):
0403             if len(e):
0404                 if e[4]=='-' and e[7]=='-':
0405                     l.append( (int(e[:4]), int(e[5:7]), int(e[8:10])) )
0406         d[key]=l
0407 #------------------------------------------------------------------------------
0408 class CSVImportDialog(common_calendar.PreviewDialog):
0409     
0410     __column_labels=[
0411         ('description', 'Description', 400, None),
0412         ('start', 'Start', 150, common_calendar.bp_date_str),
0413         ('end', 'End', 150, common_calendar.bp_date_str),
0414         ('repeat_type', 'Repeat', 80, common_calendar.bp_repeat_str),
0415         ('alarm', 'Alarm', 80, common_calendar.bp_alarm_str),
0416         ('categories', 'Category', 150, common_calendar.category_str)
0417         ]
0418 
0419     def __init__(self, parent, id, title):
0420         self.__oc=CSVCalendarImportData()
0421         common_calendar.PreviewDialog.__init__(self, parent, id, title,
0422                                self.__column_labels,
0423                                self.__oc.get_display_data(),
0424                                config_name='import/calendar/csvdialog')
0425 
0426     def getcontrols(self, main_bs):
0427         hbs=wx.BoxSizer(wx.HORIZONTAL)
0428         # label
0429         hbs.Add(wx.StaticText(self, -1, "CSV File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
0430         # where the folder name goes
0431         self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
0432         self.folderctrl.SetValue(self.__oc.get_file_name())
0433         hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
0434         # browse button
0435         id_browse=wx.NewId()
0436         hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
0437         main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
0438         main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
0439         wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
0440     def getpostcontrols(self, main_bs):
0441         main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
0442         hbs=wx.BoxSizer(wx.HORIZONTAL)
0443         id_import=wx.NewId()
0444         hbs.Add(wx.Button(self, id_import, 'Import'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0445         hbs.Add(wx.Button(self, wx.ID_OK, 'Replace All'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0446         hbs.Add(wx.Button(self, self.ID_ADD, 'Add'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0447         hbs.Add(wx.Button(self, self.ID_MERGE, 'Merge'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0448         hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0449         id_filter=wx.NewId()
0450         hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)       
0451         hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0,  wx.ALIGN_CENTRE|wx.ALL, 5)
0452         main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0453         wx.EVT_BUTTON(self, id_import, self.OnImport)
0454         wx.EVT_BUTTON(self, id_filter, self.OnFilter)
0455         wx.EVT_BUTTON(self, self.ID_ADD, self.OnEndModal)
0456         wx.EVT_BUTTON(self, self.ID_MERGE, self.OnEndModal)
0457         wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
0458 
0459     @guihelper.BusyWrapper
0460     def OnImport(self, evt):
0461         with guihelper.WXDialogWrapper(wx.ProgressDialog('CSV Calendar Import',
0462                                                          'Importing CSV Calendar Data, please wait ...',
0463                                                          parent=self)) as dlg:
0464             self.__oc.read(self.folderctrl.GetValue())
0465             self.populate(self.__oc.get_display_data())
0466 
0467     def OnBrowseFolder(self, evt):
0468         with guihelper.WXDialogWrapper(wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv'),
0469                                        True) as (dlg, id):
0470             if id==wx.ID_OK:
0471                 self.folderctrl.SetValue(dlg.GetPath())
0472 
0473     def OnFilter(self, evt):
0474         cat_list=self.__oc.get_category_list()
0475         with guihelper.WXDialogWrapper(common_calendar.FilterDialog(self, -1, 'Filtering Parameters', cat_list),
0476                                        True) as (dlg, retcode):
0477             if retcode==wx.ID_OK:
0478                 self.__oc.set_filter(dlg.get())
0479                 self.populate(self.__oc.get_display_data())
0480 
0481     def OnEndModal(self, evt):
0482         self.EndModal(evt.GetId())
0483 
0484     def get(self):
0485         return self.__oc.get()
0486 
0487     def get_categories(self):
0488         return self.__oc.get_category_list()
0489             
0490 #-------------------------------------------------------------------------------
0491 def ImportCal(folder, filters):
0492     _oc=CSVCalendarImportData(folder)
0493     _oc.set_filter(filters)
0494     _oc.read()
0495     res={ 'calendar':_oc.get() }
0496     return res
0497 
0498 #-------------------------------------------------------------------------------
0499 class CSVAutoConfCalDialog(wx.Dialog):
0500     def __init__(self, parent, id, title, folder, filters,
0501                  style=wx.CAPTION|wx.MAXIMIZE_BOX| \
0502                  wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
0503         self._oc=CSVCalendarImportData()
0504         self._oc.set_filter(filters)
0505         self.__read=False
0506         wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
0507         main_bs=wx.BoxSizer(wx.VERTICAL)
0508         hbs=wx.BoxSizer(wx.HORIZONTAL)
0509         # label
0510         hbs.Add(wx.StaticText(self, -1, "CSV Calendar File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
0511         # where the folder name goes
0512         self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
0513         self.folderctrl.SetValue(folder)
0514         hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
0515         # browse button
0516         id_browse=wx.NewId()
0517         hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2)
0518         main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
0519         main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
0520         wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
0521         hbs=wx.BoxSizer(wx.HORIZONTAL)
0522         hbs.Add(wx.Button(self, wx.ID_OK, 'OK'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0523         hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0524         id_filter=wx.NewId()
0525         hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0526         hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0,  wx.ALIGN_CENTRE|wx.ALL, 5)
0527         main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0528         wx.EVT_BUTTON(self, id_filter, self.OnFilter)
0529         wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
0530         self.SetSizer(main_bs)
0531         self.SetAutoLayout(True)
0532         main_bs.Fit(self)
0533 
0534     def OnBrowseFolder(self, evt):
0535         with guihelper.WXDialogWrapper(wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv'),
0536                                        True) as (dlg, retcode):
0537             if retcode==wx.ID_OK:
0538                 self.folderctrl.SetValue(dlg.GetPath())
0539                 self.__read=False
0540 
0541     def OnFilter(self, evt):
0542         # read the calender to get the category list
0543         if not self.__read:
0544             self._oc.read(self.folderctrl.GetValue())
0545             self.__read=True
0546         cat_list=self._oc.get_category_list()
0547         with guihelper.WXDialogWrapper(common_calendar.AutoSyncFilterDialog(self, -1, 'Filtering Parameters', cat_list)) as dlg:
0548             dlg.set(self._oc.get_filter())
0549             if dlg.ShowModal()==wx.ID_OK:
0550                 self._oc.set_filter(dlg.get())
0551 
0552     def GetFolder(self):
0553         return self.folderctrl.GetValue()
0554 
0555     def GetFilter(self):
0556         return self._oc.get_filter()
0557 

Generated by PyXR 0.9.4