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